1 /* $NetBSD: src/lib/libc/locale/setlocale.c,v 1.47 2004/07/21 20:27:46 tshiozak Exp $ */ 2 3 /* 4 * Copyright (c) 1991, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Paul Borman at Krystal Technologies. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #define _CTYPE_PRIVATE 36 37 #include <sys/types.h> 38 #include <sys/localedef.h> 39 #include <sys/stat.h> 40 #include <assert.h> 41 #include <ctype.h> 42 #include <limits.h> 43 #include <locale.h> 44 #include <paths.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 #include "collate.h" 50 #include "rune.h" 51 #include "rune_local.h" 52 53 #include "../citrus/citrus_namespace.h" 54 #include "../citrus/citrus_region.h" 55 #include "../citrus/citrus_lookup.h" 56 #include "../citrus/citrus_bcs.h" 57 58 #define _LOCALE_ALIAS_NAME "locale.alias" 59 #define _LOCALE_SYM_FORCE "/force" 60 61 static char *currentlocale(void); 62 static void revert_to_default(int); 63 static int force_locale_enable(int); 64 static int load_locale_sub(int, const char *, int); 65 static char *loadlocale(int); 66 static const char *__get_locale_env(int); 67 68 static void revert_collate(void); 69 static int load_ctype(const char *); 70 static void revert_ctype(void); 71 static int load_messages(const char *); 72 73 static const struct { 74 const char *name; 75 int (*load_function)(const char *); 76 void (*revert_function)(void); 77 } categories[] = { 78 { "LC_ALL", NULL, NULL }, 79 { "LC_COLLATE", __collate_load_tables, revert_collate }, 80 { "LC_CTYPE", load_ctype, revert_ctype }, 81 { "LC_MONETARY", NULL, NULL }, 82 { "LC_NUMERIC", NULL, NULL }, 83 { "LC_TIME", NULL, NULL }, 84 { "LC_MESSAGES", load_messages, NULL } 85 }; 86 87 /* 88 * Current locales for each category 89 */ 90 static char current_categories[_LC_LAST][32] = { 91 "C", 92 "C", 93 "C", 94 "C", 95 "C", 96 "C", 97 "C" 98 }; 99 100 /* 101 * The locales we are going to try and load 102 */ 103 static char new_categories[_LC_LAST][32]; 104 105 static char current_locale_string[_LC_LAST * 33]; 106 const char *_PathLocale; 107 108 static int 109 load_ctype(const char *locale) 110 { 111 if (_xpg4_setrunelocale(locale)) 112 return(-1); 113 if (__runetable_to_netbsd_ctype(locale)) { 114 /* very unfortunate, but need to go to "C" locale */ 115 revert_ctype(); 116 return(-1); 117 } 118 119 return(0); 120 } 121 122 static void 123 revert_ctype(void) 124 { 125 _xpg4_setrunelocale("C"); 126 __runetable_to_netbsd_ctype("C"); 127 } 128 129 static void 130 revert_collate(void) 131 { 132 __collate_load_tables("C"); 133 } 134 135 static int 136 load_messages(const char *locale) 137 { 138 char name[PATH_MAX]; 139 struct stat st; 140 141 /* 142 * XXX we don't have LC_MESSAGES support yet, 143 * but catopen may use the value of LC_MESSAGES category. 144 * so return successfully if locale directory is present. 145 */ 146 snprintf(name, sizeof(name), "%s/%s", _PathLocale, locale); 147 148 if (stat(name, &st) < 0) 149 return(-1); 150 if (!S_ISDIR(st.st_mode)) 151 return(-1); 152 return(0); 153 } 154 155 char * 156 setlocale(int category, const char *locale) 157 { 158 int i, loadlocale_success; 159 size_t len; 160 const char *env, *r; 161 162 __mb_len_max_runtime = 32; 163 164 if (issetugid() || 165 (!_PathLocale && !(_PathLocale = getenv("PATH_LOCALE")))) 166 _PathLocale = _PATH_LOCALE; 167 168 if (category < 0 || category >= (int)__arysize(categories)) 169 return(NULL); 170 171 if (locale == NULL) 172 return(category ? 173 current_categories[category] : currentlocale()); 174 175 /* 176 * Default to the current locale for everything. 177 */ 178 for (i = 1; i < _LC_LAST; ++i) 179 strlcpy(new_categories[i], current_categories[i], 180 sizeof(new_categories[i])); 181 182 /* 183 * Now go fill up new_categories from the locale argument 184 */ 185 if (*locale == '\0') { 186 if (category == LC_ALL) { 187 for (i = 1; i < _LC_LAST; ++i) { 188 env = __get_locale_env(i); 189 strlcpy(new_categories[i], env, 190 sizeof(new_categories[i])); 191 } 192 } 193 else { 194 env = __get_locale_env(category); 195 strlcpy(new_categories[category], env, 196 sizeof(new_categories[category])); 197 } 198 } else if (category) { 199 strlcpy(new_categories[category], locale, 200 sizeof(new_categories[category])); 201 } else { 202 if ((r = strchr(locale, '/')) == NULL) { 203 for (i = 1; i < _LC_LAST; ++i) { 204 strlcpy(new_categories[i], locale, 205 sizeof(new_categories[i])); 206 } 207 } else { 208 for (i = 1;;) { 209 _DIAGASSERT(*r == '/' || *r == 0); 210 _DIAGASSERT(*locale != 0); 211 if (*locale == '/') 212 return(NULL); /* invalid format. */ 213 len = r - locale; 214 if (len + 1 > sizeof(new_categories[i])) 215 return(NULL); /* too long */ 216 memcpy(new_categories[i], locale, len); 217 new_categories[i][len] = '\0'; 218 if (*r == 0) 219 break; 220 _DIAGASSERT(*r == '/'); 221 if (*(locale = ++r) == 0) 222 /* slash followed by NUL */ 223 return(NULL); 224 /* skip until NUL or '/' */ 225 while (*r && *r != '/') 226 r++; 227 if (++i == _LC_LAST) 228 return(NULL); /* too many slashes. */ 229 } 230 if (i + 1 != _LC_LAST) 231 return(NULL); /* too few slashes. */ 232 } 233 } 234 235 if (category) 236 return(loadlocale(category)); 237 238 loadlocale_success = 0; 239 for (i = 1; i < _LC_LAST; ++i) { 240 if (loadlocale(i) != NULL) 241 loadlocale_success = 1; 242 } 243 244 /* 245 * If all categories failed, return NULL; we don't need to back 246 * changes off, since none happened. 247 */ 248 if (!loadlocale_success) 249 return(NULL); 250 251 return(currentlocale()); 252 } 253 254 static char * 255 currentlocale(void) 256 { 257 int i; 258 259 strlcpy(current_locale_string, current_categories[1], 260 sizeof(current_locale_string)); 261 262 for (i = 2; i < _LC_LAST; ++i) 263 if (strcmp(current_categories[1], current_categories[i])) { 264 snprintf(current_locale_string, 265 sizeof(current_locale_string), "%s/%s/%s/%s/%s/%s", 266 current_categories[1], current_categories[2], 267 current_categories[3], current_categories[4], 268 current_categories[5], current_categories[6]); 269 break; 270 } 271 return(current_locale_string); 272 } 273 274 static void 275 revert_to_default(int category) 276 { 277 _DIAGASSERT(category >= 0 && category < _LC_LAST); 278 279 if (categories[category].revert_function != NULL) 280 categories[category].revert_function(); 281 } 282 283 static int 284 force_locale_enable(int category) 285 { 286 revert_to_default(category); 287 288 return(0); 289 } 290 291 static int 292 load_locale_sub(int category, const char *locname, int isspecial) 293 { 294 char name[PATH_MAX]; 295 296 /* check for the default locales */ 297 if (!strcmp(new_categories[category], "C") || 298 !strcmp(new_categories[category], "POSIX")) { 299 revert_to_default(category); 300 return(0); 301 } 302 303 /* check whether special symbol */ 304 if (isspecial && _bcs_strcasecmp(locname, _LOCALE_SYM_FORCE) == 0) 305 return(force_locale_enable(category)); 306 307 /* sanity check */ 308 if (strchr(locname, '/') != NULL) 309 return(-1); 310 311 snprintf(name, sizeof(name), "%s/%s/%s", _PathLocale, locname, 312 categories[category].name); 313 314 if (category > 0 && category < (int)__arysize(categories) && 315 categories[category].load_function != NULL) 316 return(categories[category].load_function(locname)); 317 318 return(0); 319 } 320 321 static char * 322 loadlocale(int category) 323 { 324 char aliaspath[PATH_MAX], loccat[PATH_MAX], buf[PATH_MAX]; 325 const char *alias; 326 327 _DIAGASSERT(0 < category && category < __arysize(categories)); 328 329 if (strcmp(new_categories[category], current_categories[category]) == 0) 330 return(current_categories[category]); 331 332 /* (1) non-aliased file */ 333 if (!load_locale_sub(category, new_categories[category], 0)) 334 goto success; 335 336 /* (2) lookup locname/catname type alias */ 337 snprintf(aliaspath, sizeof(aliaspath), "%s/" _LOCALE_ALIAS_NAME, 338 _PathLocale); 339 snprintf(loccat, sizeof(loccat), "%s/%s", new_categories[category], 340 categories[category].name); 341 alias = _lookup_alias(aliaspath, loccat, buf, sizeof(buf), 342 _LOOKUP_CASE_SENSITIVE); 343 if (!load_locale_sub(category, alias, 1)) 344 goto success; 345 346 /* (3) lookup locname type alias */ 347 alias = _lookup_alias(aliaspath, new_categories[category], 348 buf, sizeof(buf), _LOOKUP_CASE_SENSITIVE); 349 if (!load_locale_sub(category, alias, 1)) 350 goto success; 351 352 return(NULL); 353 354 success: 355 strlcpy(current_categories[category], new_categories[category], 356 sizeof(current_categories[category])); 357 return(current_categories[category]); 358 } 359 360 static const char * 361 __get_locale_env(int category) 362 { 363 const char *env; 364 365 _DIAGASSERT(category != LC_ALL); 366 367 /* 1. check LC_ALL. */ 368 env = getenv(categories[0].name); 369 370 /* 2. check LC_* */ 371 if (env == NULL || *env == '\0') 372 env = getenv(categories[category].name); 373 374 /* 3. check LANG */ 375 if (env == NULL || *env == '\0') 376 env = getenv("LANG"); 377 378 /* 4. if none is set, fall to "C" */ 379 if (env == NULL || *env == '\0' || strchr(env, '/')) 380 env = "C"; 381 382 return(env); 383 } 384