Merge branch 'master' of ssh://crater.dragonflybsd.org/repository/git/dragonfly
[dragonfly.git] / lib / libc / locale / setlocale.c
blob0ce6c903848a87eaf0e86223c52630b856cd9642
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 $ */
4 /*
5 * Copyright (c) 1991, 1993
6 * The Regents of the University of California. All rights reserved.
8 * This code is derived from software contributed to Berkeley by
9 * Paul Borman at Krystal Technologies.
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.
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.
36 #define _CTYPE_PRIVATE
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"
54 #include "../citrus/citrus_namespace.h"
55 #include "../citrus/citrus_region.h"
56 #include "../citrus/citrus_lookup.h"
57 #include "../citrus/citrus_bcs.h"
59 #define _LOCALE_ALIAS_NAME "locale.alias"
60 #define _LOCALE_SYM_FORCE "/force"
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);
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 *);
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 }
89 * Current locales for each category
91 static char current_categories[_LC_LAST][32] = {
92 "C",
93 "C",
94 "C",
95 "C",
96 "C",
97 "C",
98 "C"
102 * The locales we are going to try and load
104 static char new_categories[_LC_LAST][32];
106 static char current_locale_string[_LC_LAST * 33];
107 const char *_PathLocale;
109 static int
110 load_ctype(const char *locale)
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);
120 return(0);
123 static void
124 revert_ctype(void)
126 _xpg4_setrunelocale("C");
127 __runetable_to_netbsd_ctype("C");
130 static void
131 revert_collate(void)
133 __collate_load_tables("C");
136 static int
137 load_messages(const char *locale)
139 char name[PATH_MAX];
140 struct stat st;
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.
147 snprintf(name, sizeof(name), "%s/%s", _PathLocale, locale);
149 if (stat(name, &st) < 0)
150 return(-1);
151 if (!S_ISDIR(st.st_mode))
152 return(-1);
153 return(0);
156 char *
157 setlocale(int category, const char *locale)
159 int i, loadlocale_success;
160 size_t len;
161 const char *env, *r;
163 __mb_len_max_runtime = 32;
165 if (issetugid() ||
166 (!_PathLocale && !(_PathLocale = getenv("PATH_LOCALE"))))
167 _PathLocale = _PATH_LOCALE;
169 if (category < 0 || category >= (int)__arysize(categories))
170 return(NULL);
172 if (locale == NULL)
173 return(category ?
174 current_categories[category] : currentlocale());
177 * Default to the current locale for everything.
179 for (i = 1; i < _LC_LAST; ++i)
180 strlcpy(new_categories[i], current_categories[i],
181 sizeof(new_categories[i]));
184 * Now go fill up new_categories from the locale argument
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]));
194 else {
195 env = __get_locale_env(category);
196 strlcpy(new_categories[category], env,
197 sizeof(new_categories[category]));
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]));
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. */
231 if (i + 1 != _LC_LAST)
232 return(NULL); /* too few slashes. */
236 if (category)
237 return(loadlocale(category));
239 loadlocale_success = 0;
240 for (i = 1; i < _LC_LAST; ++i) {
241 if (loadlocale(i) != NULL)
242 loadlocale_success = 1;
246 * If all categories failed, return NULL; we don't need to back
247 * changes off, since none happened.
249 if (!loadlocale_success)
250 return(NULL);
252 return(currentlocale());
255 static char *
256 currentlocale(void)
258 int i;
260 strlcpy(current_locale_string, current_categories[1],
261 sizeof(current_locale_string));
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;
272 return(current_locale_string);
275 static void
276 revert_to_default(int category)
278 _DIAGASSERT(category >= 0 && category < _LC_LAST);
280 if (categories[category].revert_function != NULL)
281 categories[category].revert_function();
284 static int
285 force_locale_enable(int category)
287 revert_to_default(category);
289 return(0);
292 static int
293 load_locale_sub(int category, const char *locname, int isspecial)
295 char name[PATH_MAX];
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);
304 /* check whether special symbol */
305 if (isspecial && _bcs_strcasecmp(locname, _LOCALE_SYM_FORCE) == 0)
306 return(force_locale_enable(category));
308 /* sanity check */
309 if (strchr(locname, '/') != NULL)
310 return(-1);
312 snprintf(name, sizeof(name), "%s/%s/%s", _PathLocale, locname,
313 categories[category].name);
315 if (category > 0 && category < (int)__arysize(categories) &&
316 categories[category].load_function != NULL)
317 return(categories[category].load_function(locname));
319 return(0);
322 static char *
323 loadlocale(int category)
325 char aliaspath[PATH_MAX], loccat[PATH_MAX], buf[PATH_MAX];
326 const char *alias;
328 _DIAGASSERT(0 < category && category < __arysize(categories));
330 if (strcmp(new_categories[category], current_categories[category]) == 0)
331 return(current_categories[category]);
333 /* (1) non-aliased file */
334 if (!load_locale_sub(category, new_categories[category], 0))
335 goto success;
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;
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;
353 return(NULL);
355 success:
356 strlcpy(current_categories[category], new_categories[category],
357 sizeof(current_categories[category]));
358 return(current_categories[category]);
361 static const char *
362 __get_locale_env(int category)
364 const char *env;
366 _DIAGASSERT(category != LC_ALL);
368 /* 1. check LC_ALL. */
369 env = getenv(categories[0].name);
371 /* 2. check LC_* */
372 if (env == NULL || *env == '\0')
373 env = getenv(categories[category].name);
375 /* 3. check LANG */
376 if (env == NULL || *env == '\0')
377 env = getenv("LANG");
379 /* 4. if none is set, fall to "C" */
380 if (env == NULL || *env == '\0' || strchr(env, '/'))
381 env = "C";
383 return(env);