1 /* $OpenBSD: setlocale.c,v 1.17 2007/11/28 10:24:38 chl 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 <sys/localedef.h> 35 #include <locale.h> 36 #include <limits.h> 37 #include <paths.h> 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <unistd.h> 42 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 /* 72 * The locales we are going to try and load 73 */ 74 static char new_categories[_LC_LAST][32]; 75 76 static char current_locale_string[_LC_LAST * 33]; 77 78 static char *currentlocale(void); 79 static void revert_to_default(int); 80 static int load_locale_sub(int, const char *, int); 81 static char *loadlocale(int); 82 static const char *__get_locale_env(int); 83 84 char * 85 setlocale(int category, const char *locale) 86 { 87 int i, loadlocale_success; 88 size_t len; 89 const char *env, *r; 90 91 if (issetugid() != 0 || 92 ((!_PathLocale && !(_PathLocale = getenv("PATH_LOCALE"))) || 93 !*_PathLocale)) 94 _PathLocale = _PATH_LOCALE; 95 96 if (category < 0 || category >= _LC_LAST) 97 return (NULL); 98 99 if (!locale) 100 return (category ? 101 current_categories[category] : currentlocale()); 102 103 /* 104 * Default to the current locale for everything. 105 */ 106 for (i = 1; i < _LC_LAST; ++i) 107 (void)strlcpy(new_categories[i], current_categories[i], 108 sizeof(new_categories[i])); 109 110 /* 111 * Now go fill up new_categories from the locale argument 112 */ 113 if (!*locale) { 114 if (category == LC_ALL) { 115 for (i = 1; i < _LC_LAST; ++i) { 116 env = __get_locale_env(i); 117 (void)strlcpy(new_categories[i], env, 118 sizeof(new_categories[i])); 119 } 120 } 121 else { 122 env = __get_locale_env(category); 123 (void)strlcpy(new_categories[category], env, 124 sizeof(new_categories[category])); 125 } 126 } else if (category) { 127 (void)strlcpy(new_categories[category], locale, 128 sizeof(new_categories[category])); 129 } else { 130 if ((r = strchr(locale, '/')) == 0) { 131 for (i = 1; i < _LC_LAST; ++i) { 132 (void)strlcpy(new_categories[i], locale, 133 sizeof(new_categories[i])); 134 } 135 } else { 136 for (i = 1;;) { 137 if (*locale == '/') 138 return (NULL); /* invalid format. */ 139 len = r - locale; 140 if (len + 1 > sizeof(new_categories[i])) 141 return (NULL); /* too long */ 142 (void)memcpy(new_categories[i], locale, len); 143 new_categories[i][len] = '\0'; 144 if (*r == 0) 145 break; 146 if (*(locale = ++r) == 0) 147 /* slash followed by NUL */ 148 return (NULL); 149 /* skip until NUL or '/' */ 150 while (*r && *r != '/') 151 r++; 152 if (++i == _LC_LAST) 153 return (NULL); /* too many slashes. */ 154 } 155 if (i + 1 != _LC_LAST) 156 return (NULL); /* too few slashes. */ 157 } 158 } 159 160 if (category) 161 return (loadlocale(category)); 162 163 loadlocale_success = 0; 164 for (i = 1; i < _LC_LAST; ++i) { 165 if (loadlocale(i) != NULL) 166 loadlocale_success = 1; 167 } 168 169 /* 170 * If all categories failed, return NULL; we don't need to back 171 * changes off, since none happened. 172 */ 173 if (!loadlocale_success) 174 return NULL; 175 176 return (currentlocale()); 177 } 178 179 static char * 180 currentlocale(void) 181 { 182 int i; 183 184 (void)strlcpy(current_locale_string, current_categories[1], 185 sizeof(current_locale_string)); 186 187 for (i = 2; i < _LC_LAST; ++i) 188 if (strcmp(current_categories[1], current_categories[i])) { 189 (void)snprintf(current_locale_string, 190 sizeof(current_locale_string), "%s/%s/%s/%s/%s/%s", 191 current_categories[1], current_categories[2], 192 current_categories[3], current_categories[4], 193 current_categories[5], current_categories[6]); 194 break; 195 } 196 return (current_locale_string); 197 } 198 199 static void 200 revert_to_default(int category) 201 { 202 switch (category) { 203 case LC_CTYPE: 204 (void)_xpg4_setrunelocale("C"); 205 __install_currentrunelocale_ctype(); 206 break; 207 case LC_MESSAGES: 208 case LC_COLLATE: 209 case LC_MONETARY: 210 case LC_NUMERIC: 211 case LC_TIME: 212 break; 213 } 214 } 215 216 static int 217 load_locale_sub(int category, const char *locname, int isspecial) 218 { 219 char name[PATH_MAX]; 220 int len; 221 222 /* check for the default locales */ 223 if (!strcmp(new_categories[category], "C") || 224 !strcmp(new_categories[category], "POSIX")) { 225 revert_to_default(category); 226 return 0; 227 } 228 229 /* sanity check */ 230 if (strchr(locname, '/') != NULL) 231 return -1; 232 233 len = snprintf(name, sizeof(name), "%s/%s/%s", 234 _PathLocale, locname, categories[category]); 235 if (len < 0 || len >= sizeof(name)) 236 return -1; 237 238 switch (category) { 239 case LC_CTYPE: 240 if (_xpg4_setrunelocale(locname)) 241 return -1; 242 __install_currentrunelocale_ctype(); 243 break; 244 245 case LC_MESSAGES: 246 case LC_COLLATE: 247 case LC_MONETARY: 248 case LC_NUMERIC: 249 case LC_TIME: 250 return -1; 251 } 252 253 return 0; 254 } 255 256 static char * 257 loadlocale(int category) 258 { 259 if (strcmp(new_categories[category], 260 current_categories[category]) == 0) 261 return (current_categories[category]); 262 263 if (!load_locale_sub(category, new_categories[category], 0)) { 264 (void)strlcpy(current_categories[category], 265 new_categories[category], sizeof(current_categories[category])); 266 return current_categories[category]; 267 } else { 268 return NULL; 269 } 270 } 271 272 static const char * 273 __get_locale_env(int category) 274 { 275 const char *env; 276 277 /* 1. check LC_ALL. */ 278 env = getenv(categories[0]); 279 280 /* 2. check LC_* */ 281 if (!env || !*env) 282 env = getenv(categories[category]); 283 284 /* 3. check LANG */ 285 if (!env || !*env) 286 env = getenv("LANG"); 287 288 /* 4. if none is set, fall to "C" */ 289 if (!env || !*env || strchr(env, '/')) 290 env = "C"; 291 292 return env; 293 } 294