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