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