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