1 /* $OpenBSD: setlocale.c,v 1.25 2016/05/23 00:05:15 guenther Exp $ */ 2 /* 3 * Copyright (c) 1991, 1993 4 * The Regents of the University of California. All rights reserved. 5 * 6 * This code is derived from software contributed to Berkeley by 7 * Paul Borman at Krystal Technologies. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34 #include <locale.h> 35 #include <limits.h> 36 #include <paths.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <unistd.h> 41 42 #include "localedef.h" 43 #include "rune.h" 44 #include "rune_local.h" 45 /* 46 * Category names for getenv() 47 */ 48 static char *categories[_LC_LAST] = { 49 "LC_ALL", 50 "LC_COLLATE", 51 "LC_CTYPE", 52 "LC_MONETARY", 53 "LC_NUMERIC", 54 "LC_TIME", 55 "LC_MESSAGES" 56 }; 57 58 /* 59 * Current locales for each category 60 */ 61 static char current_categories[_LC_LAST][32] = { 62 "C", 63 "C", 64 "C", 65 "C", 66 "C", 67 "C", 68 "C" 69 }; 70 71 static char current_locale_string[_LC_LAST * 33]; 72 73 static char *currentlocale(void); 74 static void revert_to_default(int); 75 static int load_locale_sub(int, const char *); 76 static char *loadlocale(int, const char *); 77 static const char *__get_locale_env(int); 78 79 char * 80 setlocale(int category, const char *locale) 81 { 82 int i, loadlocale_success; 83 size_t len; 84 const char *env, *r; 85 char new_categories[_LC_LAST][32]; 86 87 if (category < 0 || category >= _LC_LAST) 88 return (NULL); 89 90 if (!locale) 91 return (category ? 92 current_categories[category] : currentlocale()); 93 94 /* 95 * Default to the current locale for everything. 96 */ 97 for (i = 1; i < _LC_LAST; ++i) 98 (void)strlcpy(new_categories[i], current_categories[i], 99 sizeof(new_categories[i])); 100 101 /* 102 * Now go fill up new_categories from the locale argument 103 */ 104 if (!*locale) { 105 if (category == LC_ALL) { 106 for (i = 1; i < _LC_LAST; ++i) { 107 env = __get_locale_env(i); 108 (void)strlcpy(new_categories[i], env, 109 sizeof(new_categories[i])); 110 } 111 } 112 else { 113 env = __get_locale_env(category); 114 (void)strlcpy(new_categories[category], env, 115 sizeof(new_categories[category])); 116 } 117 } else if (category) { 118 (void)strlcpy(new_categories[category], locale, 119 sizeof(new_categories[category])); 120 } else { 121 if ((r = strchr(locale, '/')) == 0) { 122 for (i = 1; i < _LC_LAST; ++i) { 123 (void)strlcpy(new_categories[i], locale, 124 sizeof(new_categories[i])); 125 } 126 } else { 127 for (i = 1;;) { 128 if (*locale == '/') 129 return (NULL); /* invalid format. */ 130 len = r - locale; 131 if (len + 1 > sizeof(new_categories[i])) 132 return (NULL); /* too long */ 133 (void)memcpy(new_categories[i], locale, len); 134 new_categories[i][len] = '\0'; 135 if (*r == 0) 136 break; 137 if (*(locale = ++r) == 0) 138 /* slash followed by NUL */ 139 return (NULL); 140 /* skip until NUL or '/' */ 141 while (*r && *r != '/') 142 r++; 143 if (++i == _LC_LAST) 144 return (NULL); /* too many slashes. */ 145 } 146 if (i + 1 != _LC_LAST) 147 return (NULL); /* too few slashes. */ 148 } 149 } 150 151 if (category) 152 return (loadlocale(category, new_categories[category])); 153 154 loadlocale_success = 0; 155 for (i = 1; i < _LC_LAST; ++i) { 156 if (loadlocale(i, new_categories[i]) != NULL) 157 loadlocale_success = 1; 158 } 159 160 /* 161 * If all categories failed, return NULL; we don't need to back 162 * changes off, since none happened. 163 */ 164 if (!loadlocale_success) 165 return NULL; 166 167 return (currentlocale()); 168 } 169 DEF_STRONG(setlocale); 170 171 static char * 172 currentlocale(void) 173 { 174 int i; 175 176 (void)strlcpy(current_locale_string, current_categories[1], 177 sizeof(current_locale_string)); 178 179 for (i = 2; i < _LC_LAST; ++i) 180 if (strcmp(current_categories[1], current_categories[i])) { 181 (void)snprintf(current_locale_string, 182 sizeof(current_locale_string), "%s/%s/%s/%s/%s/%s", 183 current_categories[1], current_categories[2], 184 current_categories[3], current_categories[4], 185 current_categories[5], current_categories[6]); 186 break; 187 } 188 return (current_locale_string); 189 } 190 191 static void 192 revert_to_default(int category) 193 { 194 switch (category) { 195 case LC_CTYPE: 196 (void)_xpg4_setrunelocale("C"); 197 __install_currentrunelocale_ctype(); 198 break; 199 case LC_MESSAGES: 200 case LC_COLLATE: 201 case LC_MONETARY: 202 case LC_NUMERIC: 203 case LC_TIME: 204 break; 205 } 206 } 207 208 static int 209 set_lc_messages_locale(const char *locname) 210 { 211 const char *dot, *loc_encoding; 212 213 /* Assumes "language[_territory][.codeset]" locale name. */ 214 dot = strrchr(locname, '.'); 215 if (dot == NULL) 216 return -1; 217 loc_encoding = dot + 1; 218 219 return strcmp(loc_encoding, "UTF-8") == 0 ? 0 : -1; 220 } 221 222 static int 223 load_locale_sub(int category, const char *locname) 224 { 225 /* check for the default locales */ 226 if (!strcmp(locname, "C") || !strcmp(locname, "POSIX")) { 227 revert_to_default(category); 228 return 0; 229 } 230 231 /* sanity check */ 232 if (strchr(locname, '/') != NULL) 233 return -1; 234 235 switch (category) { 236 case LC_CTYPE: 237 if (_xpg4_setrunelocale(locname)) 238 return -1; 239 __install_currentrunelocale_ctype(); 240 break; 241 242 case LC_MESSAGES: 243 return set_lc_messages_locale(locname); 244 245 case LC_COLLATE: 246 case LC_MONETARY: 247 case LC_NUMERIC: 248 case LC_TIME: 249 return -1; 250 } 251 252 return 0; 253 } 254 255 static char * 256 loadlocale(int category, const char *locname) 257 { 258 if (strcmp(locname, current_categories[category]) == 0) 259 return (current_categories[category]); 260 261 if (!load_locale_sub(category, locname)) { 262 (void)strlcpy(current_categories[category], 263 locname, sizeof(current_categories[category])); 264 return current_categories[category]; 265 } else { 266 return NULL; 267 } 268 } 269 270 static const char * 271 __get_locale_env(int category) 272 { 273 const char *env; 274 275 /* 1. check LC_ALL. */ 276 env = getenv(categories[0]); 277 278 /* 2. check LC_* */ 279 if (!env || !*env) 280 env = getenv(categories[category]); 281 282 /* 3. check LANG */ 283 if (!env || !*env) 284 env = getenv("LANG"); 285 286 /* 4. if none is set, fall to "C" */ 287 if (!env || !*env || strchr(env, '/')) 288 env = "C"; 289 290 return env; 291 } 292