1192595Sdes/*
292559Sdes * Copyright (c) 1996 - 2002 FreeBSD Project
365668Skris * Copyright (c) 1991, 1993
465668Skris *	The Regents of the University of California.  All rights reserved.
565668Skris *
665668Skris * This code is derived from software contributed to Berkeley by
765668Skris * Paul Borman at Krystal Technologies.
865668Skris *
965668Skris * Redistribution and use in source and binary forms, with or without
1065668Skris * modification, are permitted provided that the following conditions
1165668Skris * are met:
1265668Skris * 1. Redistributions of source code must retain the above copyright
1365668Skris *    notice, this list of conditions and the following disclaimer.
1465668Skris * 2. Redistributions in binary form must reproduce the above copyright
1592559Sdes *    notice, this list of conditions and the following disclaimer in the
1665668Skris *    documentation and/or other materials provided with the distribution.
1765668Skris * 4. Neither the name of the University nor the names of its contributors
1865668Skris *    may be used to endorse or promote products derived from this software
1965668Skris *    without specific prior written permission.
2065668Skris *
2165668Skris * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2265668Skris * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2365668Skris * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2465668Skris * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2565668Skris * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2665668Skris * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2765668Skris * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2865668Skris * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2965668Skris * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3065668Skris * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3165668Skris * SUCH DAMAGE.
3265668Skris */
3365668Skris
3465668Skris#if defined(LIBC_SCCS) && !defined(lint)
3565668Skrisstatic char sccsid[] = "@(#)setlocale.c	8.1 (Berkeley) 7/4/93";
3665668Skris#endif /* LIBC_SCCS and not lint */
3757429Smarkm#include <sys/cdefs.h>
3892559Sdes__FBSDID("$FreeBSD: releng/10.2/lib/libc/locale/setlocale.c 228921 2011-12-27 23:28:01Z jilles $");
3992559Sdes
4057429Smarkm#include <sys/types.h>
4157429Smarkm#include <sys/stat.h>
4257429Smarkm#include <errno.h>
4357429Smarkm#include <limits.h>
4457429Smarkm#include <locale.h>
4557429Smarkm#include <paths.h>	/* for _PATH_LOCALE */
4657429Smarkm#include <stdlib.h>
4760573Skris#include <string.h>
4860573Skris#include <unistd.h>
4960573Skris#include "collate.h"
5060573Skris#include "lmonetary.h"	/* for __monetary_load_locale() */
5160573Skris#include "lnumeric.h"	/* for __numeric_load_locale() */
5276262Sgreen#include "lmessages.h"	/* for __messages_load_locale() */
5376262Sgreen#include "setlocale.h"
5476262Sgreen#include "ldpart.h"
5592559Sdes#include "../stdtime/timelocal.h" /* for __time_load_locale() */
5692559Sdes
5757429Smarkm/*
5865668Skris * Category names for getenv()
5965668Skris */
6065668Skrisstatic const char categories[_LC_LAST][12] = {
6192559Sdes    "LC_ALL",
62157019Sdes    "LC_COLLATE",
63181111Sdes    "LC_CTYPE",
64157019Sdes    "LC_MONETARY",
6557429Smarkm    "LC_NUMERIC",
66181111Sdes    "LC_TIME",
67181111Sdes    "LC_MESSAGES",
68181111Sdes};
69181111Sdes
70181111Sdes/*
71181111Sdes * Current locales for each category
72181111Sdes */
73181111Sdesstatic char current_categories[_LC_LAST][ENCODING_LEN + 1] = {
74181111Sdes    "C",
75181111Sdes    "C",
76181111Sdes    "C",
77181111Sdes    "C",
78181111Sdes    "C",
79181111Sdes    "C",
80181111Sdes    "C",
81181111Sdes};
82181111Sdes
83181111Sdes/*
8465668Skris * Path to locale storage directory
8557429Smarkm */
8657429Smarkmchar	*_PathLocale;
8757429Smarkm
8892559Sdes/*
8992559Sdes * The locales we are going to try and load
9060573Skris */
9160573Skrisstatic char new_categories[_LC_LAST][ENCODING_LEN + 1];
9260573Skrisstatic char saved_categories[_LC_LAST][ENCODING_LEN + 1];
9360573Skris
9460573Skrisstatic char current_locale_string[_LC_LAST * (ENCODING_LEN + 1/*"/"*/ + 1)];
95137019Sdes
9674500Sgreenstatic char	*currentlocale(void);
97106130Sdesstatic char	*loadlocale(int);
98147005Sdesconst char *__get_locale_env(int);
9992559Sdes
10092559Sdeschar *
10157429Smarkmsetlocale(category, locale)
10257429Smarkm	int category;
10357429Smarkm	const char *locale;
10457429Smarkm{
10560573Skris	int i, j, len, saverr;
106192595Sdes        const char *env, *r;
10792559Sdes
10857429Smarkm	if (category < LC_ALL || category >= _LC_LAST) {
10957429Smarkm		errno = EINVAL;
11057429Smarkm		return (NULL);
11160573Skris	}
11299063Sdes
11399063Sdes	if (locale == NULL)
11499063Sdes		return (category != LC_ALL ?
11599063Sdes		    current_categories[category] : currentlocale());
11699063Sdes
11799063Sdes	/*
11860573Skris	 * Default to the current locale for everything.
11992559Sdes	 */
12060573Skris	for (i = 1; i < _LC_LAST; ++i)
12160573Skris		(void)strcpy(new_categories[i], current_categories[i]);
12260573Skris
12360573Skris	/*
124181111Sdes	 * Now go fill up new_categories from the locale argument
125181111Sdes	 */
12692559Sdes	if (!*locale) {
127157019Sdes		if (category == LC_ALL) {
128181111Sdes			for (i = 1; i < _LC_LAST; ++i) {
12960573Skris				env = __get_locale_env(i);
13065668Skris				if (strlen(env) > ENCODING_LEN) {
131157019Sdes					errno = EINVAL;
132157019Sdes					return (NULL);
133181111Sdes				}
134181111Sdes				(void)strcpy(new_categories[i], env);
135157019Sdes			}
136181111Sdes		} else {
137181111Sdes			env = __get_locale_env(category);
138181111Sdes			if (strlen(env) > ENCODING_LEN) {
139181111Sdes				errno = EINVAL;
140181111Sdes				return (NULL);
14165668Skris			}
14265668Skris			(void)strcpy(new_categories[category], env);
14360573Skris		}
14460573Skris	} else if (category != LC_ALL) {
14560573Skris		if (strlen(locale) > ENCODING_LEN) {
14660573Skris			errno = EINVAL;
14765668Skris			return (NULL);
14892559Sdes		}
149181111Sdes		(void)strcpy(new_categories[category], locale);
15092559Sdes	} else {
151181111Sdes		if ((r = strchr(locale, '/')) == NULL) {
15292559Sdes			if (strlen(locale) > ENCODING_LEN) {
15392559Sdes				errno = EINVAL;
15465668Skris				return (NULL);
15592559Sdes			}
15692559Sdes			for (i = 1; i < _LC_LAST; ++i)
15792559Sdes				(void)strcpy(new_categories[i], locale);
15892559Sdes		} else {
15992559Sdes			for (i = 1; r[1] == '/'; ++r)
16065668Skris				;
16192559Sdes			if (!r[1]) {
16292559Sdes				errno = EINVAL;
16392559Sdes				return (NULL);	/* Hmm, just slashes... */
16492559Sdes			}
16592559Sdes			do {
16660573Skris				if (i == _LC_LAST)
16792559Sdes					break;  /* Too many slashes... */
16892559Sdes				if ((len = r - locale) > ENCODING_LEN) {
16998684Sdes					errno = EINVAL;
17098684Sdes					return (NULL);
17160573Skris				}
172157019Sdes				(void)strlcpy(new_categories[i], locale,
173157019Sdes					      len + 1);
17498684Sdes				i++;
17598684Sdes				while (*r == '/')
17698684Sdes					r++;
17798684Sdes				locale = r;
17898684Sdes				while (*r && *r != '/')
17998684Sdes					r++;
18098684Sdes			} while (*locale);
181149753Sdes			while (i < _LC_LAST) {
18298684Sdes				(void)strcpy(new_categories[i],
18398684Sdes					     new_categories[i-1]);
18492559Sdes				i++;
18560573Skris			}
186157019Sdes		}
18792559Sdes	}
18899063Sdes
189181111Sdes	if (category != LC_ALL)
19092559Sdes		return (loadlocale(category));
19192559Sdes
19292559Sdes	for (i = 1; i < _LC_LAST; ++i) {
19369587Sgreen		(void)strcpy(saved_categories[i], current_categories[i]);
19492559Sdes		if (loadlocale(i) == NULL) {
19592559Sdes			saverr = errno;
196157019Sdes			for (j = 1; j < i; j++) {
197181111Sdes				(void)strcpy(new_categories[j],
198181111Sdes					     saved_categories[j]);
199181111Sdes				if (loadlocale(j) == NULL) {
200181111Sdes					(void)strcpy(new_categories[j], "C");
201181111Sdes					(void)loadlocale(j);
20292559Sdes				}
20392559Sdes			}
204137019Sdes			errno = saverr;
20560573Skris			return (NULL);
20692559Sdes		}
20760573Skris	}
20892559Sdes	return (currentlocale());
20992559Sdes}
21092559Sdes
21192559Sdesstatic char *
21292559Sdescurrentlocale()
21392559Sdes{
21492559Sdes	int i;
21592559Sdes
21692559Sdes	(void)strcpy(current_locale_string, current_categories[1]);
21792559Sdes
218181111Sdes	for (i = 2; i < _LC_LAST; ++i)
21960573Skris		if (strcmp(current_categories[1], current_categories[i])) {
22092559Sdes			for (i = 2; i < _LC_LAST; ++i) {
22160573Skris				(void)strcat(current_locale_string, "/");
222137019Sdes				(void)strcat(current_locale_string,
22392559Sdes					     current_categories[i]);
22492559Sdes			}
22560573Skris			break;
22692559Sdes		}
22792559Sdes	return (current_locale_string);
22892559Sdes}
22992559Sdes
23092559Sdesstatic char *
23160573Skrisloadlocale(category)
23292559Sdes	int category;
23392559Sdes{
23492559Sdes	char *new = new_categories[category];
23592559Sdes	char *old = current_categories[category];
236162856Sdes	int (*func)(const char *);
23792559Sdes	int saved_errno;
238162856Sdes
239181111Sdes	if ((new[0] == '.' &&
240162856Sdes	     (new[1] == '\0' || (new[1] == '.' && new[2] == '\0'))) ||
241181111Sdes	    strchr(new, '/') != NULL) {
242181111Sdes		errno = EINVAL;
243162856Sdes		return (NULL);
244147005Sdes	}
245147005Sdes
246147005Sdes	saved_errno = errno;
247147005Sdes	errno = __detect_path_locale();
248192595Sdes	if (errno != 0)
249137019Sdes		return (NULL);
25060573Skris	errno = saved_errno;
25192559Sdes
25260573Skris	switch (category) {
25392559Sdes	case LC_CTYPE:
254149753Sdes		func = __wrap_setrunelocale;
25592559Sdes		break;
256149753Sdes	case LC_COLLATE:
257181111Sdes		func = __collate_load_tables;
25892559Sdes		break;
25960573Skris	case LC_TIME:
26092559Sdes		func = __time_load_locale;
26160573Skris		break;
26292559Sdes	case LC_NUMERIC:
26360573Skris		func = __numeric_load_locale;
26492559Sdes		break;
26560573Skris	case LC_MONETARY:
26692559Sdes		func = __monetary_load_locale;
26792559Sdes		break;
26860573Skris	case LC_MESSAGES:
26992559Sdes		func = __messages_load_locale;
27060573Skris		break;
27192559Sdes	default:
272181111Sdes		errno = EINVAL;
27392559Sdes		return (NULL);
27492559Sdes	}
27576262Sgreen
27692559Sdes	if (strcmp(new, old) == 0)
27792559Sdes		return (old);
27892559Sdes
27976262Sgreen	if (func(new) != _LDP_ERROR) {
28057429Smarkm		(void)strcpy(old, new);
281		(void)strcpy(__xlocale_global_locale.components[category-1]->locale, new);
282		return (old);
283	}
284
285	return (NULL);
286}
287
288const char *
289__get_locale_env(category)
290        int category;
291{
292        const char *env;
293
294        /* 1. check LC_ALL. */
295        env = getenv(categories[0]);
296
297        /* 2. check LC_* */
298	if (env == NULL || !*env)
299                env = getenv(categories[category]);
300
301        /* 3. check LANG */
302	if (env == NULL || !*env)
303                env = getenv("LANG");
304
305        /* 4. if none is set, fall to "C" */
306	if (env == NULL || !*env)
307                env = "C";
308
309	return (env);
310}
311
312/*
313 * Detect locale storage location and store its value to _PathLocale variable
314 */
315int
316__detect_path_locale(void)
317{
318	if (_PathLocale == NULL) {
319		char *p = getenv("PATH_LOCALE");
320
321		if (p != NULL && !issetugid()) {
322			if (strlen(p) + 1/*"/"*/ + ENCODING_LEN +
323			    1/*"/"*/ + CATEGORY_LEN >= PATH_MAX)
324				return (ENAMETOOLONG);
325			_PathLocale = strdup(p);
326			if (_PathLocale == NULL)
327				return (errno == 0 ? ENOMEM : errno);
328		} else
329			_PathLocale = _PATH_LOCALE;
330	}
331	return (0);
332}
333
334