Upgrade make(1). 1/2
[dragonfly.git] / lib / libc / locale / setlocale.c
1 /*
2  * Copyright (c) 1996 - 2002 FreeBSD Project
3  * Copyright (c) 1991, 1993
4  *      The Regents of the University of California.  All rights reserved.
5  *
6  * This code is derived from software contributed to Berkeley by
7  * Paul Borman at Krystal Technologies.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * @(#)setlocale.c      8.1 (Berkeley) 7/4/93
34  * $FreeBSD: head/lib/libc/locale/setlocale.c 228921 2011-12-27 23:28:01Z jilles $
35  */
36
37
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <errno.h>
41 #include <limits.h>
42 #include <locale.h>
43 #include <paths.h>      /* for _PATH_LOCALE */
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 #include "collate.h"
48 #include "lmonetary.h"  /* for __monetary_load_locale() */
49 #include "lnumeric.h"   /* for __numeric_load_locale() */
50 #include "lmessages.h"  /* for __messages_load_locale() */
51 #include "setlocale.h"
52 #include "ldpart.h"
53 #include "../stdtime/timelocal.h" /* for __time_load_locale() */
54
55 /*
56  * Category names for getenv()
57  */
58 static const char categories[_LC_LAST][12] = {
59     "LC_ALL",
60     "LC_COLLATE",
61     "LC_CTYPE",
62     "LC_MONETARY",
63     "LC_NUMERIC",
64     "LC_TIME",
65     "LC_MESSAGES",
66 };
67
68 /*
69  * Current locales for each category
70  */
71 static char current_categories[_LC_LAST][ENCODING_LEN + 1] = {
72     "C",
73     "C",
74     "C",
75     "C",
76     "C",
77     "C",
78     "C",
79 };
80
81 /*
82  * Path to locale storage directory
83  */
84 char *_PathLocale;
85
86 /*
87  * The locales we are going to try and load
88  */
89 static char new_categories[_LC_LAST][ENCODING_LEN + 1];
90 static char saved_categories[_LC_LAST][ENCODING_LEN + 1];
91
92 static char current_locale_string[_LC_LAST * (ENCODING_LEN + 1/*"/"*/ + 1)];
93
94 static char *currentlocale(void);
95 static char *loadlocale(int);
96 const char *__get_locale_env(int);
97 int __get_locale_str(int category, const char *str, char * const res);
98
99 char *
100 setlocale(int category, const char *locale)
101 {
102         int i, j, len, saverr;
103         const char *env, *r;
104         char lres[ENCODING_LEN + 1] = "";
105
106         if (category < LC_ALL || category >= _LC_LAST) {
107                 errno = EINVAL;
108                 return (NULL);
109         }
110
111         if (locale == NULL)
112                 return (category != LC_ALL ?
113                     current_categories[category] : currentlocale());
114
115         /*
116          * Default to the current locale for everything.
117          */
118         for (i = 1; i < _LC_LAST; ++i)
119                 (void)strcpy(new_categories[i], current_categories[i]);
120
121         /*
122          * Now go fill up new_categories from the locale argument
123          */
124         if (!*locale) {
125                 if (category == LC_ALL) {
126                         for (i = 1; i < _LC_LAST; ++i) {
127                                 env = __get_locale_env(i);
128                                 if (strlen(env) > ENCODING_LEN) {
129                                         errno = EINVAL;
130                                         return (NULL);
131                                 }
132                                 (void)strcpy(new_categories[i], env);
133                         }
134                 } else {
135                         env = __get_locale_env(category);
136                         if (strlen(env) > ENCODING_LEN) {
137                                 errno = EINVAL;
138                                 return (NULL);
139                         }
140                         (void)strcpy(new_categories[category], env);
141                 }
142         } else if (category != LC_ALL) {
143                 if (strlen(locale) > ENCODING_LEN) {
144                         errno = EINVAL;
145                         return (NULL);
146                 }
147                 (void)strcpy(new_categories[category], locale);
148         } else {
149                 if ((r = strchr(locale, '/')) != NULL) {
150                         for (i = 1; r[1] == '/'; ++r)
151                                 ;
152                         if (!r[1]) {
153                                 errno = EINVAL;
154                                 return (NULL);  /* Hmm, just slashes... */
155                         }
156                         do {
157                                 if (i == _LC_LAST)
158                                         break;  /* Too many slashes... */
159                                 if ((len = r - locale) > ENCODING_LEN) {
160                                         errno = EINVAL;
161                                         return (NULL);
162                                 }
163                                 (void)strlcpy(new_categories[i], locale,
164                                               len + 1);
165                                 i++;
166                                 while (*r == '/')
167                                         r++;
168                                 locale = r;
169                                 while (*r && *r != '/')
170                                         r++;
171                         } while (*locale);
172                         while (i < _LC_LAST) {
173                                 (void)strcpy(new_categories[i],
174                                              new_categories[i - 1]);
175                                 i++;
176                         }
177                 } else if ('L' == locale[0] && strchr(locale, ';')) {
178                         for (i = 1; i < _LC_LAST; ++i) {
179                                 __get_locale_str(i, locale, lres);
180                                 (void)strcpy(new_categories[i], lres);
181                         }
182                 } else {
183                         if (strlen(locale) > ENCODING_LEN) {
184                                 errno = EINVAL;
185                                 return (NULL);
186                         }
187                         for (i = 1; i < _LC_LAST; ++i)
188                                 (void)strcpy(new_categories[i], locale);
189                 }
190         }
191
192         if (category != LC_ALL)
193                 return (loadlocale(category));
194
195         for (i = 1; i < _LC_LAST; ++i) {
196                 (void)strcpy(saved_categories[i], current_categories[i]);
197                 if (loadlocale(i) == NULL) {
198                         saverr = errno;
199                         for (j = 1; j < i; j++) {
200                                 (void)strcpy(new_categories[j],
201                                              saved_categories[j]);
202                                 if (loadlocale(j) == NULL) {
203                                         (void)strcpy(new_categories[j], "C");
204                                         (void)loadlocale(j);
205                                 }
206                         }
207                         errno = saverr;
208                         return (NULL);
209                 }
210         }
211         return (currentlocale());
212 }
213
214 static char *
215 currentlocale(void)
216 {
217         int i;
218
219         (void)strcpy(current_locale_string, current_categories[1]);
220
221         for (i = 2; i < _LC_LAST; ++i)
222                 if (strcmp(current_categories[1], current_categories[i])) {
223                         for (i = 2; i < _LC_LAST; ++i) {
224                                 (void)strcat(current_locale_string, "/");
225                                 (void)strcat(current_locale_string,
226                                              current_categories[i]);
227                         }
228                         break;
229                 }
230         return (current_locale_string);
231 }
232
233 static char *
234 loadlocale(int category)
235 {
236         char *new = new_categories[category];
237         char *old = current_categories[category];
238         int (*func)(const char *);
239         int saved_errno;
240
241         if ((new[0] == '.' &&
242              (new[1] == '\0' || (new[1] == '.' && new[2] == '\0'))) ||
243             strchr(new, '/') != NULL) {
244                 errno = EINVAL;
245                 return (NULL);
246         }
247
248         saved_errno = errno;
249         errno = __detect_path_locale();
250         if (errno != 0)
251                 return (NULL);
252         errno = saved_errno;
253
254         switch (category) {
255         case LC_CTYPE:
256                 func = __wrap_setrunelocale;
257                 break;
258         case LC_COLLATE:
259                 func = __collate_load_tables;
260                 break;
261         case LC_TIME:
262                 func = __time_load_locale;
263                 break;
264         case LC_NUMERIC:
265                 func = __numeric_load_locale;
266                 break;
267         case LC_MONETARY:
268                 func = __monetary_load_locale;
269                 break;
270         case LC_MESSAGES:
271                 func = __messages_load_locale;
272                 break;
273         default:
274                 errno = EINVAL;
275                 return (NULL);
276         }
277
278         if (strcmp(new, old) == 0)
279                 return (old);
280
281         if (func(new) != _LDP_ERROR) {
282                 (void)strcpy(old, new);
283                 (void)strcpy(__xlocale_global_locale.components[category-1]->locale, new);
284                 return (old);
285         }
286
287         return (NULL);
288 }
289
290 static int
291 getlocstr(const char *name, const char *str, char * const res)
292 {
293         const char *np, *cp;
294         int len;
295
296         np = str;
297
298         while ((cp = strchr (np, '=')) != NULL) {
299                 if (!strncmp(np, name, (cp - np))) {
300                         np = cp + 1;
301                         cp = strchr(cp, ';');
302                         if (cp == NULL)
303                                 cp = str + strlen(str);
304                         len = cp - np;
305                         if (len > ENCODING_LEN)
306                                 len = ENCODING_LEN;
307                         strncpy(res, np, len);
308                         res[len] = '\0';
309                         return 0;
310                 } else {
311                         cp = strchr(cp, ';');
312                         if (cp != NULL)
313                                 np = cp + 1;
314                         else
315                                 break;
316                 }
317         }
318
319         return 1;
320 }
321 /*
322  * Similar function like __get_locale_env() but from a string.
323  */
324 int
325 __get_locale_str(int category, const char *str, char * const res)
326 {
327         int check;
328
329         /* 1. check LC_ALL. */
330         check = getlocstr(categories[0], str, res);
331
332         /* 2. check LC_* */
333         if (check)
334                 check = getlocstr(categories[category], str, res);
335
336         /* 3. check LANG */
337         if (check)
338                 check = getlocstr("LANG", str, res);
339
340         /* 4. if none is set, fall to "C" */
341         if (check) {
342                 res[0] = 'C';
343                 res[1] = '\0';
344         }
345
346         return check;
347 }
348
349
350 const char *
351 __get_locale_env(int category)
352 {
353         const char *env;
354
355         /* 1. check LC_ALL. */
356         env = getenv(categories[0]);
357
358         /* 2. check LC_* */
359         if (env == NULL || !*env)
360                 env = getenv(categories[category]);
361
362         /* 3. check LANG */
363         if (env == NULL || !*env)
364                 env = getenv("LANG");
365
366         /* 4. if none is set, fall to "C" */
367         if (env == NULL || !*env)
368                 env = "C";
369
370         return (env);
371 }
372
373 /*
374  * Detect locale storage location and store its value to _PathLocale variable
375  */
376 int
377 __detect_path_locale(void)
378 {
379         if (_PathLocale == NULL) {
380                 char *p = getenv("PATH_LOCALE");
381
382                 if (p != NULL && !issetugid()) {
383                         if (strlen(p) + 1/*"/"*/ + ENCODING_LEN +
384                             1/*"/"*/ + CATEGORY_LEN >= PATH_MAX)
385                                 return (ENAMETOOLONG);
386                         _PathLocale = strdup(p);
387                         if (_PathLocale == NULL)
388                                 return (errno == 0 ? ENOMEM : errno);
389                 } else
390                         _PathLocale = _PATH_LOCALE;
391         }
392         return (0);
393 }