xref: /dragonfly/lib/libc/locale/setlocale.c (revision 4d108676)
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 }
394