1 /* $NetBSD: setlocale.c,v 1.52 2007/09/29 07:55:45 tnozaki 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 #include <sys/cdefs.h> 36 #if defined(LIBC_SCCS) && !defined(lint) 37 #if 0 38 static char sccsid[] = "@(#)setlocale.c 8.1 (Berkeley) 7/4/93"; 39 #else 40 __RCSID("$NetBSD: setlocale.c,v 1.52 2007/09/29 07:55:45 tnozaki Exp $"); 41 #endif 42 #endif /* LIBC_SCCS and not lint */ 43 44 #define _CTYPE_PRIVATE 45 46 #include "namespace.h" 47 #include <sys/localedef.h> 48 #include <sys/types.h> 49 #include <sys/stat.h> 50 #include <assert.h> 51 #include <limits.h> 52 #include <ctype.h> 53 #define __SETLOCALE_SOURCE__ 54 #include <locale.h> 55 #include <paths.h> 56 #include <stdio.h> 57 #include <stdlib.h> 58 #include <string.h> 59 #include <unistd.h> 60 #ifdef WITH_RUNE 61 #include "rune.h" 62 #include "rune_local.h" 63 #else 64 #include "ctypeio.h" 65 #endif 66 #include "timeio.h" 67 68 #ifdef CITRUS 69 #include <citrus/citrus_namespace.h> 70 #include <citrus/citrus_region.h> 71 #include <citrus/citrus_lookup.h> 72 #include <citrus/citrus_bcs.h> 73 #else 74 #include <locale/aliasname_local.h> 75 #define _lookup_alias(p, a, b, s, c) __unaliasname((p), (a), (b), (s)) 76 #define _bcs_strcasecmp(a, b) strcasecmp((a), (b)) 77 #endif 78 79 #define _LOCALE_SYM_FORCE "/force" 80 81 /* 82 * Category names for getenv() 83 */ 84 static const char *const categories[_LC_LAST] = { 85 "LC_ALL", 86 "LC_COLLATE", 87 "LC_CTYPE", 88 "LC_MONETARY", 89 "LC_NUMERIC", 90 "LC_TIME", 91 "LC_MESSAGES" 92 }; 93 94 /* 95 * Current locales for each category 96 */ 97 static char current_categories[_LC_LAST][32] = { 98 "C", 99 "C", 100 "C", 101 "C", 102 "C", 103 "C", 104 "C" 105 }; 106 107 /* 108 * The locales we are going to try and load 109 */ 110 static char new_categories[_LC_LAST][32]; 111 112 static char current_locale_string[_LC_LAST * 33]; 113 114 static char *currentlocale __P((void)); 115 static void revert_to_default __P((int)); 116 static int force_locale_enable __P((int)); 117 static int load_locale_sub __P((int, const char *, int)); 118 static char *loadlocale __P((int)); 119 static const char *__get_locale_env __P((int)); 120 121 char * 122 __setlocale(category, locale) 123 int category; 124 const char *locale; 125 { 126 int i, loadlocale_success; 127 size_t len; 128 const char *env, *r; 129 130 if (issetugid() || 131 (!_PathLocale && !(_PathLocale = getenv("PATH_LOCALE")))) 132 _PathLocale = _PATH_LOCALE; 133 134 if (category < 0 || category >= _LC_LAST) 135 return (NULL); 136 137 if (!locale) 138 return (category ? 139 current_categories[category] : currentlocale()); 140 141 /* 142 * Default to the current locale for everything. 143 */ 144 for (i = 1; i < _LC_LAST; ++i) 145 (void)strlcpy(new_categories[i], current_categories[i], 146 sizeof(new_categories[i])); 147 148 /* 149 * Now go fill up new_categories from the locale argument 150 */ 151 if (!*locale) { 152 if (category == LC_ALL) { 153 for (i = 1; i < _LC_LAST; ++i) { 154 env = __get_locale_env(i); 155 (void)strlcpy(new_categories[i], env, 156 sizeof(new_categories[i])); 157 } 158 } 159 else { 160 env = __get_locale_env(category); 161 (void)strlcpy(new_categories[category], env, 162 sizeof(new_categories[category])); 163 } 164 } else if (category) { 165 (void)strlcpy(new_categories[category], locale, 166 sizeof(new_categories[category])); 167 } else { 168 if ((r = strchr(locale, '/')) == 0) { 169 for (i = 1; i < _LC_LAST; ++i) { 170 (void)strlcpy(new_categories[i], locale, 171 sizeof(new_categories[i])); 172 } 173 } else { 174 for (i = 1;;) { 175 _DIAGASSERT(*r == '/' || *r == 0); 176 _DIAGASSERT(*locale != 0); 177 if (*locale == '/') 178 return (NULL); /* invalid format. */ 179 len = r - locale; 180 if (len + 1 > sizeof(new_categories[i])) 181 return (NULL); /* too long */ 182 (void)memcpy(new_categories[i], locale, len); 183 new_categories[i][len] = '\0'; 184 if (*r == 0) 185 break; 186 _DIAGASSERT(*r == '/'); 187 if (*(locale = ++r) == 0) 188 /* slash followed by NUL */ 189 return (NULL); 190 /* skip until NUL or '/' */ 191 while (*r && *r != '/') 192 r++; 193 if (++i == _LC_LAST) 194 return (NULL); /* too many slashes. */ 195 } 196 if (i + 1 != _LC_LAST) 197 return (NULL); /* too few slashes. */ 198 } 199 } 200 201 if (category) 202 return (loadlocale(category)); 203 204 loadlocale_success = 0; 205 for (i = 1; i < _LC_LAST; ++i) { 206 if (loadlocale(i) != NULL) 207 loadlocale_success = 1; 208 } 209 210 /* 211 * If all categories failed, return NULL; we don't need to back 212 * changes off, since none happened. 213 */ 214 if (!loadlocale_success) 215 return NULL; 216 217 return (currentlocale()); 218 } 219 220 static char * 221 currentlocale() 222 { 223 int i; 224 225 (void)strlcpy(current_locale_string, current_categories[1], 226 sizeof(current_locale_string)); 227 228 for (i = 2; i < _LC_LAST; ++i) 229 if (strcmp(current_categories[1], current_categories[i])) { 230 (void)snprintf(current_locale_string, 231 sizeof(current_locale_string), "%s/%s/%s/%s/%s/%s", 232 current_categories[1], current_categories[2], 233 current_categories[3], current_categories[4], 234 current_categories[5], current_categories[6]); 235 break; 236 } 237 return (current_locale_string); 238 } 239 240 static void 241 revert_to_default(category) 242 int category; 243 { 244 switch (category) { 245 case LC_CTYPE: 246 #ifdef WITH_RUNE 247 (void)_xpg4_setrunelocale("C"); 248 #else 249 if (_ctype_ != _C_ctype_) { 250 /* LINTED const castaway */ 251 free((void *)_ctype_); 252 _ctype_ = _C_ctype_; 253 } 254 if (_toupper_tab_ != _C_toupper_) { 255 /* LINTED const castaway */ 256 free((void *)_toupper_tab_); 257 _toupper_tab_ = _C_toupper_; 258 } 259 if (_tolower_tab_ != _C_tolower_) { 260 /* LINTED const castaway */ 261 free((void *)_tolower_tab_); 262 _tolower_tab_ = _C_tolower_; 263 } 264 #endif 265 break; 266 case LC_TIME: 267 if (_CurrentTimeLocale != &_DefaultTimeLocale) { 268 free((void *)_CurrentTimeLocale); 269 _CurrentTimeLocale = &_DefaultTimeLocale; 270 } 271 break; 272 case LC_MESSAGES: 273 case LC_COLLATE: 274 case LC_MONETARY: 275 case LC_NUMERIC: 276 break; 277 } 278 } 279 280 static int 281 force_locale_enable(category) 282 int category; 283 { 284 revert_to_default(category); 285 286 return 0; 287 } 288 289 static int 290 load_locale_sub(category, locname, isspecial) 291 int category; 292 const char *locname; 293 int isspecial; 294 { 295 char name[PATH_MAX]; 296 297 /* check for the default locales */ 298 if (!strcmp(new_categories[category], "C") || 299 !strcmp(new_categories[category], "POSIX")) { 300 revert_to_default(category); 301 return 0; 302 } 303 304 /* check whether special symbol */ 305 if (isspecial && _bcs_strcasecmp(locname, _LOCALE_SYM_FORCE) == 0) 306 return force_locale_enable(category); 307 308 /* sanity check */ 309 if (strchr(locname, '/') != NULL) 310 return -1; 311 312 (void)snprintf(name, sizeof(name), "%s/%s/%s", 313 _PathLocale, locname, categories[category]); 314 315 switch (category) { 316 case LC_CTYPE: 317 #ifdef WITH_RUNE 318 if (_xpg4_setrunelocale(__UNCONST(locname))) 319 return -1; 320 #else 321 if (!__loadctype(name)) 322 return -1; 323 #endif 324 break; 325 326 case LC_MESSAGES: 327 /* 328 * XXX we don't have LC_MESSAGES support yet, 329 * but catopen may use the value of LC_MESSAGES category. 330 * so return successfully if locale directory is present. 331 */ 332 (void)snprintf(name, sizeof(name), "%s/%s", 333 _PathLocale, locname); 334 /* local */ 335 { 336 struct stat st; 337 if (stat(name, &st) < 0) 338 return -1; 339 if (!S_ISDIR(st.st_mode)) 340 return -1; 341 } 342 break; 343 344 case LC_TIME: 345 if (!__loadtime(name)) 346 return -1; 347 break; 348 case LC_COLLATE: 349 case LC_MONETARY: 350 case LC_NUMERIC: 351 return -1; 352 } 353 354 return 0; 355 } 356 357 static char * 358 loadlocale(category) 359 int category; 360 { 361 char aliaspath[PATH_MAX], loccat[PATH_MAX], buf[PATH_MAX]; 362 const char *alias; 363 364 _DIAGASSERT(0 < category && category < _LC_LAST); 365 366 if (strcmp(new_categories[category], current_categories[category]) == 0) 367 return (current_categories[category]); 368 369 /* (1) non-aliased file */ 370 if (!load_locale_sub(category, new_categories[category], 0)) 371 goto success; 372 373 /* (2) lookup locname/catname type alias */ 374 (void)snprintf(aliaspath, sizeof(aliaspath), 375 "%s/" _LOCALE_ALIAS_NAME, _PathLocale); 376 (void)snprintf(loccat, sizeof(loccat), "%s/%s", 377 new_categories[category], categories[category]); 378 alias = _lookup_alias(aliaspath, loccat, buf, sizeof(buf), 379 _LOOKUP_CASE_SENSITIVE); 380 if (!load_locale_sub(category, alias, 1)) 381 goto success; 382 383 /* (3) lookup locname type alias */ 384 alias = _lookup_alias(aliaspath, new_categories[category], 385 buf, sizeof(buf), _LOOKUP_CASE_SENSITIVE); 386 if (!load_locale_sub(category, alias, 1)) 387 goto success; 388 389 return NULL; 390 391 success: 392 (void)strlcpy(current_categories[category], 393 new_categories[category], 394 sizeof(current_categories[category])); 395 return current_categories[category]; 396 } 397 398 static const char * 399 __get_locale_env(category) 400 int category; 401 { 402 const char *env; 403 404 _DIAGASSERT(category != LC_ALL); 405 406 /* 1. check LC_ALL. */ 407 env = getenv(categories[0]); 408 409 /* 2. check LC_* */ 410 if (!env || !*env) 411 env = getenv(categories[category]); 412 413 /* 3. check LANG */ 414 if (!env || !*env) 415 env = getenv("LANG"); 416 417 /* 4. if none is set, fall to "C" */ 418 if (!env || !*env || strchr(env, '/')) 419 env = "C"; 420 421 return env; 422 } 423