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 * 4. 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
34#if defined(LIBC_SCCS) && !defined(lint)
35static char sccsid[] = "@(#)setlocale.c	8.1 (Berkeley) 7/4/93";
36#endif /* LIBC_SCCS and not lint */
37#include <sys/cdefs.h>
38__FBSDID("$FreeBSD: src/lib/libc/locale/setlocale.c,v 1.51 2007/01/09 00:28:00 imp Exp $");
39
40#include "xlocale_private.h"
41
42#include <sys/types.h>
43#include <sys/stat.h>
44#include <errno.h>
45#include <limits.h>
46#include <locale.h>
47#include <paths.h>	/* for _PATH_LOCALE */
48#include <stdlib.h>
49#include <string.h>
50#include <unistd.h>
51#include "collate.h"
52#include "lmonetary.h"	/* for __monetary_load_locale() */
53#include "lnumeric.h"	/* for __numeric_load_locale() */
54#include "lmessages.h"	/* for __messages_load_locale() */
55#include "setlocale.h"
56#include "ldpart.h"
57#include "timelocal.h" /* for __time_load_locale() */
58
59/*
60 * Category names for getenv()
61 */
62static const char * const categories[_LC_LAST] = {
63    "LC_ALL",
64    "LC_COLLATE",
65    "LC_CTYPE",
66    "LC_MONETARY",
67    "LC_NUMERIC",
68    "LC_TIME",
69    "LC_MESSAGES",
70};
71
72/*
73 * Current locales for each category
74 */
75static char current_categories[_LC_LAST][ENCODING_LEN + 1] = {
76    "C",
77    "C",
78    "C",
79    "C",
80    "C",
81    "C",
82    "C",
83};
84
85/*
86 * Path to locale storage directory
87 */
88char	*_PathLocale;
89
90/*
91 * The locales we are going to try and load
92 */
93static char new_categories[_LC_LAST][ENCODING_LEN + 1];
94static char saved_categories[_LC_LAST][ENCODING_LEN + 1];
95
96static char	*currentlocale(void);
97static char	*loadlocale(int);
98__private_extern__ const char *__get_locale_env(int);
99
100#define	UNLOCK_AND_RETURN(x)	{XL_UNLOCK(&__global_locale); return (x);}
101
102char *
103setlocale(category, locale)
104	int category;
105	const char *locale;
106{
107	int i, j, len, saverr, save__numeric_fp_cvt;
108        const char *env, *r;
109	locale_t save__lc_numeric_loc;
110
111	if (category < LC_ALL || category >= _LC_LAST) {
112		errno = EINVAL;
113		return (NULL);
114	}
115
116	if (locale == NULL)
117		return (category != LC_ALL ?
118		    current_categories[category] : currentlocale());
119
120	XL_LOCK(&__global_locale);
121	/*
122	 * Default to the current locale for everything.
123	 */
124	for (i = 1; i < _LC_LAST; ++i)
125		(void)strcpy(new_categories[i], current_categories[i]);
126
127	/*
128	 * Now go fill up new_categories from the locale argument
129	 */
130	if (!*locale) {
131		if (category == LC_ALL) {
132			for (i = 1; i < _LC_LAST; ++i) {
133				env = __get_locale_env(i);
134				if (strlen(env) > ENCODING_LEN) {
135					errno = EINVAL;
136					UNLOCK_AND_RETURN (NULL);
137				}
138				(void)strcpy(new_categories[i], env);
139			}
140		} else {
141			env = __get_locale_env(category);
142			if (strlen(env) > ENCODING_LEN) {
143				errno = EINVAL;
144				UNLOCK_AND_RETURN (NULL);
145			}
146			(void)strcpy(new_categories[category], env);
147		}
148	} else if (category != LC_ALL) {
149		if (strlen(locale) > ENCODING_LEN) {
150			errno = EINVAL;
151			UNLOCK_AND_RETURN (NULL);
152		}
153		(void)strcpy(new_categories[category], locale);
154	} else {
155		if ((r = strchr(locale, '/')) == NULL) {
156			if (strlen(locale) > ENCODING_LEN) {
157				errno = EINVAL;
158				UNLOCK_AND_RETURN (NULL);
159			}
160			for (i = 1; i < _LC_LAST; ++i)
161				(void)strcpy(new_categories[i], locale);
162		} else {
163			for (i = 1; r[1] == '/'; ++r)
164				;
165			if (!r[1]) {
166				errno = EINVAL;
167				UNLOCK_AND_RETURN (NULL);	/* Hmm, just slashes... */
168			}
169			do {
170				if (i == _LC_LAST)
171					break;  /* Too many slashes... */
172				if ((len = r - locale) > ENCODING_LEN) {
173					errno = EINVAL;
174					UNLOCK_AND_RETURN (NULL);
175				}
176				(void)strlcpy(new_categories[i], locale,
177					      len + 1);
178				i++;
179				while (*r == '/')
180					r++;
181				locale = r;
182				while (*r && *r != '/')
183					r++;
184			} while (*locale);
185			while (i < _LC_LAST) {
186				(void)strcpy(new_categories[i],
187					     new_categories[i-1]);
188				i++;
189			}
190		}
191	}
192
193	if (category != LC_ALL)
194		UNLOCK_AND_RETURN (loadlocale(category));
195
196	save__numeric_fp_cvt = __global_locale.__numeric_fp_cvt;
197	save__lc_numeric_loc = __global_locale.__lc_numeric_loc;
198	XL_RETAIN(save__lc_numeric_loc);
199	for (i = 1; i < _LC_LAST; ++i) {
200		(void)strcpy(saved_categories[i], current_categories[i]);
201		if (loadlocale(i) == NULL) {
202			saverr = errno;
203			for (j = 1; j < i; j++) {
204				(void)strcpy(new_categories[j],
205					     saved_categories[j]);
206				if (loadlocale(j) == NULL) {
207					(void)strcpy(new_categories[j], "C");
208					(void)loadlocale(j);
209				}
210			}
211			__global_locale.__numeric_fp_cvt = save__numeric_fp_cvt;
212			__global_locale.__lc_numeric_loc = save__lc_numeric_loc;
213			XL_RELEASE(save__lc_numeric_loc);
214			errno = saverr;
215			UNLOCK_AND_RETURN (NULL);
216		}
217	}
218	XL_RELEASE(save__lc_numeric_loc);
219	UNLOCK_AND_RETURN (currentlocale());
220}
221
222static char *
223currentlocale()
224{
225	int i;
226
227	size_t bufsiz = _LC_LAST * (ENCODING_LEN + 1/*"/"*/ + 1);
228	static char *current_locale_string = NULL;
229
230	if (current_locale_string == NULL) {
231		current_locale_string = malloc(bufsiz);
232		if (current_locale_string == NULL) {
233			return NULL;
234		}
235	}
236
237	(void)strlcpy(current_locale_string, current_categories[1], bufsiz);
238
239	for (i = 2; i < _LC_LAST; ++i)
240		if (strcmp(current_categories[1], current_categories[i])) {
241			for (i = 2; i < _LC_LAST; ++i) {
242				(void)strcat(current_locale_string, "/");
243				(void)strcat(current_locale_string,
244					     current_categories[i]);
245			}
246			break;
247		}
248	return (current_locale_string);
249}
250
251static char *
252loadlocale(category)
253	int category;
254{
255	char *new = new_categories[category];
256	char *old = current_categories[category];
257	int (*func)(const char *, locale_t);
258	int saved_errno;
259
260	if ((new[0] == '.' &&
261	     (new[1] == '\0' || (new[1] == '.' && new[2] == '\0'))) ||
262	    strchr(new, '/') != NULL) {
263		errno = EINVAL;
264		return (NULL);
265	}
266
267	saved_errno = errno;
268	errno = __detect_path_locale();
269	if (errno != 0)
270		return (NULL);
271	errno = saved_errno;
272
273	switch (category) {
274	case LC_CTYPE:
275		func = __wrap_setrunelocale;
276		break;
277	case LC_COLLATE:
278		func = __collate_load_tables;
279		break;
280	case LC_TIME:
281		func = __time_load_locale;
282		break;
283	case LC_NUMERIC:
284		func = __numeric_load_locale;
285		break;
286	case LC_MONETARY:
287		func = __monetary_load_locale;
288		break;
289	case LC_MESSAGES:
290		func = __messages_load_locale;
291		break;
292	default:
293		errno = EINVAL;
294		return (NULL);
295	}
296
297	if (strcmp(new, old) == 0)
298		return (old);
299
300	if (func(new, &__global_locale) != _LDP_ERROR) {
301		(void)strcpy(old, new);
302		switch (category) {
303		case LC_CTYPE:
304			if (__global_locale.__numeric_fp_cvt == LC_NUMERIC_FP_SAME_LOCALE)
305				__global_locale.__numeric_fp_cvt = LC_NUMERIC_FP_UNINITIALIZED;
306			break;
307		case LC_NUMERIC:
308			__global_locale.__numeric_fp_cvt = LC_NUMERIC_FP_UNINITIALIZED;
309			XL_RELEASE(__global_locale.__lc_numeric_loc);
310			__global_locale.__lc_numeric_loc = NULL;
311			break;
312		}
313		return (old);
314	}
315
316	return (NULL);
317}
318
319__private_extern__ const char *
320__get_locale_env(category)
321        int category;
322{
323        const char *env;
324
325        /* 1. check LC_ALL. */
326        env = getenv(categories[0]);
327
328        /* 2. check LC_* */
329	if (env == NULL || !*env)
330                env = getenv(categories[category]);
331
332        /* 3. check LANG */
333	if (env == NULL || !*env)
334                env = getenv("LANG");
335
336        /* 4. if none is set, fall to "C" */
337	if (env == NULL || !*env)
338                env = "C";
339
340	return (env);
341}
342
343/*
344 * Detect locale storage location and store its value to _PathLocale variable
345 */
346__private_extern__ int
347__detect_path_locale(void)
348{
349	if (_PathLocale == NULL) {
350		char *p = getenv("PATH_LOCALE");
351
352		if (p != NULL && !issetugid()) {
353			if (strlen(p) + 1/*"/"*/ + ENCODING_LEN +
354			    1/*"/"*/ + CATEGORY_LEN >= PATH_MAX)
355				return (ENAMETOOLONG);
356			_PathLocale = strdup(p);
357			if (_PathLocale == NULL)
358				return (errno == 0 ? ENOMEM : errno);
359		} else
360			_PathLocale = _PATH_LOCALE;
361	}
362	return (0);
363}
364
365