0ce6c903848a87eaf0e86223c52630b856cd9642
[dragonfly.git] / lib / libc / locale / setlocale.c
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 }