1 /* $NetBSD: setlocale.c,v 1.49 2005/12/02 11:10:45 yamt 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.49 2005/12/02 11:10:45 yamt 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 67 #ifdef CITRUS 68 #include <citrus/citrus_namespace.h> 69 #include <citrus/citrus_region.h> 70 #include <citrus/citrus_lookup.h> 71 #include <citrus/citrus_bcs.h> 72 #else 73 #include <locale/aliasname_local.h> 74 #define _lookup_alias(p, a, b, s, c) __unaliasname((p), (a), (b), (s)) 75 #define _bcs_strcasecmp(a, b) strcasecmp((a), (b)) 76 #endif 77 78 #define _LOCALE_ALIAS_NAME "locale.alias" 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 (void)__runetable_to_netbsd_ctype("C"); 249 #else 250 if (_ctype_ != _C_ctype_) { 251 /* LINTED const castaway */ 252 free((void *)_ctype_); 253 _ctype_ = _C_ctype_; 254 } 255 if (_toupper_tab_ != _C_toupper_) { 256 /* LINTED const castaway */ 257 free((void *)_toupper_tab_); 258 _toupper_tab_ = _C_toupper_; 259 } 260 if (_tolower_tab_ != _C_tolower_) { 261 /* LINTED const castaway */ 262 free((void *)_tolower_tab_); 263 _tolower_tab_ = _C_tolower_; 264 } 265 #endif 266 break; 267 case LC_MESSAGES: 268 case LC_COLLATE: 269 case LC_MONETARY: 270 case LC_NUMERIC: 271 case LC_TIME: 272 break; 273 } 274 } 275 276 static int 277 force_locale_enable(category) 278 int category; 279 { 280 revert_to_default(category); 281 282 return 0; 283 } 284 285 static int 286 load_locale_sub(category, locname, isspecial) 287 int category; 288 const char *locname; 289 int isspecial; 290 { 291 char name[PATH_MAX]; 292 293 /* check for the default locales */ 294 if (!strcmp(new_categories[category], "C") || 295 !strcmp(new_categories[category], "POSIX")) { 296 revert_to_default(category); 297 return 0; 298 } 299 300 /* check whether special symbol */ 301 if (isspecial && _bcs_strcasecmp(locname, _LOCALE_SYM_FORCE) == 0) 302 return force_locale_enable(category); 303 304 /* sanity check */ 305 if (strchr(locname, '/') != NULL) 306 return -1; 307 308 (void)snprintf(name, sizeof(name), "%s/%s/%s", 309 _PathLocale, locname, categories[category]); 310 311 switch (category) { 312 case LC_CTYPE: 313 #ifdef WITH_RUNE 314 if (_xpg4_setrunelocale(__UNCONST(locname))) 315 return -1; 316 if (__runetable_to_netbsd_ctype(locname)) { 317 /* very unfortunate, but need to go to "C" locale */ 318 revert_to_default(category); 319 return -1; 320 } 321 #else 322 if (!__loadctype(name)) 323 return -1; 324 #endif 325 break; 326 327 case LC_MESSAGES: 328 /* 329 * XXX we don't have LC_MESSAGES support yet, 330 * but catopen may use the value of LC_MESSAGES category. 331 * so return successfully if locale directory is present. 332 */ 333 (void)snprintf(name, sizeof(name), "%s/%s", 334 _PathLocale, locname); 335 /* local */ 336 { 337 struct stat st; 338 if (stat(name, &st) < 0) 339 return -1; 340 if (!S_ISDIR(st.st_mode)) 341 return -1; 342 } 343 break; 344 345 case LC_COLLATE: 346 case LC_MONETARY: 347 case LC_NUMERIC: 348 case LC_TIME: 349 return -1; 350 } 351 352 return 0; 353 } 354 355 static char * 356 loadlocale(category) 357 int category; 358 { 359 char aliaspath[PATH_MAX], loccat[PATH_MAX], buf[PATH_MAX]; 360 const char *alias; 361 362 _DIAGASSERT(0 < category && category < _LC_LAST); 363 364 if (strcmp(new_categories[category], current_categories[category]) == 0) 365 return (current_categories[category]); 366 367 /* (1) non-aliased file */ 368 if (!load_locale_sub(category, new_categories[category], 0)) 369 goto success; 370 371 /* (2) lookup locname/catname type alias */ 372 (void)snprintf(aliaspath, sizeof(aliaspath), 373 "%s/" _LOCALE_ALIAS_NAME, _PathLocale); 374 (void)snprintf(loccat, sizeof(loccat), "%s/%s", 375 new_categories[category], categories[category]); 376 alias = _lookup_alias(aliaspath, loccat, buf, sizeof(buf), 377 _LOOKUP_CASE_SENSITIVE); 378 if (!load_locale_sub(category, alias, 1)) 379 goto success; 380 381 /* (3) lookup locname type alias */ 382 alias = _lookup_alias(aliaspath, new_categories[category], 383 buf, sizeof(buf), _LOOKUP_CASE_SENSITIVE); 384 if (!load_locale_sub(category, alias, 1)) 385 goto success; 386 387 return NULL; 388 389 success: 390 (void)strlcpy(current_categories[category], 391 new_categories[category], 392 sizeof(current_categories[category])); 393 return current_categories[category]; 394 } 395 396 static const char * 397 __get_locale_env(category) 398 int category; 399 { 400 const char *env; 401 402 _DIAGASSERT(category != LC_ALL); 403 404 /* 1. check LC_ALL. */ 405 env = getenv(categories[0]); 406 407 /* 2. check LC_* */ 408 if (!env || !*env) 409 env = getenv(categories[category]); 410 411 /* 3. check LANG */ 412 if (!env || !*env) 413 env = getenv("LANG"); 414 415 /* 4. if none is set, fall to "C" */ 416 if (!env || !*env || strchr(env, '/')) 417 env = "C"; 418 419 return env; 420 } 421