14297a3b0SGarrett D'Amore /*
24297a3b0SGarrett D'Amore  * Copyright (c) 1996 - 2002 FreeBSD Project
34297a3b0SGarrett D'Amore  * Copyright (c) 1991, 1993
44297a3b0SGarrett D'Amore  *	The Regents of the University of California.  All rights reserved.
54297a3b0SGarrett D'Amore  *
64297a3b0SGarrett D'Amore  * This code is derived from software contributed to Berkeley by
74297a3b0SGarrett D'Amore  * Paul Borman at Krystal Technologies.
84297a3b0SGarrett D'Amore  *
94297a3b0SGarrett D'Amore  * Redistribution and use in source and binary forms, with or without
104297a3b0SGarrett D'Amore  * modification, are permitted provided that the following conditions
114297a3b0SGarrett D'Amore  * are met:
124297a3b0SGarrett D'Amore  * 1. Redistributions of source code must retain the above copyright
134297a3b0SGarrett D'Amore  *    notice, this list of conditions and the following disclaimer.
144297a3b0SGarrett D'Amore  * 2. Redistributions in binary form must reproduce the above copyright
154297a3b0SGarrett D'Amore  *    notice, this list of conditions and the following disclaimer in the
164297a3b0SGarrett D'Amore  *    documentation and/or other materials provided with the distribution.
174297a3b0SGarrett D'Amore  * 4. Neither the name of the University nor the names of its contributors
184297a3b0SGarrett D'Amore  *    may be used to endorse or promote products derived from this software
194297a3b0SGarrett D'Amore  *    without specific prior written permission.
204297a3b0SGarrett D'Amore  *
214297a3b0SGarrett D'Amore  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
224297a3b0SGarrett D'Amore  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
234297a3b0SGarrett D'Amore  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
244297a3b0SGarrett D'Amore  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
254297a3b0SGarrett D'Amore  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
264297a3b0SGarrett D'Amore  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
274297a3b0SGarrett D'Amore  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
284297a3b0SGarrett D'Amore  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
294297a3b0SGarrett D'Amore  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
304297a3b0SGarrett D'Amore  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
314297a3b0SGarrett D'Amore  * SUCH DAMAGE.
324297a3b0SGarrett D'Amore  */
334297a3b0SGarrett D'Amore 
344297a3b0SGarrett D'Amore /*
354297a3b0SGarrett D'Amore  * Copyright 2010 Nexenta Systems, Inc.  All rights reserved.
364297a3b0SGarrett D'Amore  */
374297a3b0SGarrett D'Amore 
384297a3b0SGarrett D'Amore #include "lint.h"
394297a3b0SGarrett D'Amore #include <sys/types.h>
404297a3b0SGarrett D'Amore #include <sys/stat.h>
414297a3b0SGarrett D'Amore #include <errno.h>
424297a3b0SGarrett D'Amore #include <limits.h>
434297a3b0SGarrett D'Amore #include <locale.h>
444297a3b0SGarrett D'Amore #include <stdlib.h>
454297a3b0SGarrett D'Amore #include <string.h>
464297a3b0SGarrett D'Amore #include <unistd.h>
474297a3b0SGarrett D'Amore #include <alloca.h>
48*5f5fdac7SGarrett D'Amore #include <stdio.h>
494297a3b0SGarrett D'Amore #include "collate.h"
504297a3b0SGarrett D'Amore #include "lmonetary.h"	/* for __monetary_load_locale() */
514297a3b0SGarrett D'Amore #include "lnumeric.h"	/* for __numeric_load_locale() */
524297a3b0SGarrett D'Amore #include "lmessages.h"	/* for __messages_load_locale() */
534297a3b0SGarrett D'Amore #include "setlocale.h"
544297a3b0SGarrett D'Amore #include "ldpart.h"
554297a3b0SGarrett D'Amore #include "timelocal.h" /* for __time_load_locale() */
564297a3b0SGarrett D'Amore #include "../i18n/_loc_path.h"
574297a3b0SGarrett D'Amore 
584297a3b0SGarrett D'Amore /*
594297a3b0SGarrett D'Amore  * Category names for getenv()  Note that this was modified
604297a3b0SGarrett D'Amore  * for Solaris.  See <iso/locale_iso.h>.
614297a3b0SGarrett D'Amore  */
62*5f5fdac7SGarrett D'Amore #define NUM_CATS	7
63*5f5fdac7SGarrett D'Amore static char *categories[7] = {
644297a3b0SGarrett D'Amore 	"LC_CTYPE",
654297a3b0SGarrett D'Amore 	"LC_NUMERIC",
664297a3b0SGarrett D'Amore 	"LC_TIME",
674297a3b0SGarrett D'Amore 	"LC_COLLATE",
684297a3b0SGarrett D'Amore 	"LC_MONETARY",
694297a3b0SGarrett D'Amore 	"LC_MESSAGES",
704297a3b0SGarrett D'Amore 	"LC_ALL",
714297a3b0SGarrett D'Amore };
724297a3b0SGarrett D'Amore 
734297a3b0SGarrett D'Amore /*
744297a3b0SGarrett D'Amore  * Current locales for each category
754297a3b0SGarrett D'Amore  */
764297a3b0SGarrett D'Amore static char current_categories[NUM_CATS][ENCODING_LEN + 1] = {
774297a3b0SGarrett D'Amore 	"C",
784297a3b0SGarrett D'Amore 	"C",
794297a3b0SGarrett D'Amore 	"C",
804297a3b0SGarrett D'Amore 	"C",
814297a3b0SGarrett D'Amore 	"C",
824297a3b0SGarrett D'Amore 	"C",
834297a3b0SGarrett D'Amore 	"C",
844297a3b0SGarrett D'Amore };
854297a3b0SGarrett D'Amore 
864297a3b0SGarrett D'Amore /*
874297a3b0SGarrett D'Amore  * Path to locale storage directory.  See ../i18n/_loc_path.h
884297a3b0SGarrett D'Amore  */
894297a3b0SGarrett D'Amore char	*_PathLocale = _DFLT_LOC_PATH;
904297a3b0SGarrett D'Amore 
914297a3b0SGarrett D'Amore /*
924297a3b0SGarrett D'Amore  * The locales we are going to try and load
934297a3b0SGarrett D'Amore  */
944297a3b0SGarrett D'Amore static char new_categories[NUM_CATS][ENCODING_LEN + 1];
954297a3b0SGarrett D'Amore static char saved_categories[NUM_CATS][ENCODING_LEN + 1];
964297a3b0SGarrett D'Amore static char current_locale_string[NUM_CATS * (ENCODING_LEN + 1 + 1)];
974297a3b0SGarrett D'Amore 
984297a3b0SGarrett D'Amore static char	*currentlocale(void);
994297a3b0SGarrett D'Amore static char	*loadlocale(int);
1004297a3b0SGarrett D'Amore static const char *__get_locale_env(int);
1014297a3b0SGarrett D'Amore 
1024297a3b0SGarrett D'Amore char *
1034297a3b0SGarrett D'Amore setlocale(int category, const char *locale)
1044297a3b0SGarrett D'Amore {
1054297a3b0SGarrett D'Amore 	int i, j, saverr;
1064297a3b0SGarrett D'Amore 	const char *env, *r;
1074297a3b0SGarrett D'Amore 
1084297a3b0SGarrett D'Amore 	if (category < 0 || category >= NUM_CATS) {
1094297a3b0SGarrett D'Amore 		errno = EINVAL;
1104297a3b0SGarrett D'Amore 		return (NULL);
1114297a3b0SGarrett D'Amore 	}
1124297a3b0SGarrett D'Amore 
1134297a3b0SGarrett D'Amore 	if (locale == NULL)
1144297a3b0SGarrett D'Amore 		return (category != LC_ALL ?
1154297a3b0SGarrett D'Amore 		    current_categories[category] : currentlocale());
1164297a3b0SGarrett D'Amore 
1174297a3b0SGarrett D'Amore 	/*
1184297a3b0SGarrett D'Amore 	 * Default to the current locale for everything.
1194297a3b0SGarrett D'Amore 	 */
1204297a3b0SGarrett D'Amore 	for (i = 0; i < NUM_CATS; ++i)
1214297a3b0SGarrett D'Amore 		(void) strcpy(new_categories[i], current_categories[i]);
1224297a3b0SGarrett D'Amore 
1234297a3b0SGarrett D'Amore 	/*
1244297a3b0SGarrett D'Amore 	 * Now go fill up new_categories from the locale argument
1254297a3b0SGarrett D'Amore 	 */
1264297a3b0SGarrett D'Amore 	if (!*locale) {
1274297a3b0SGarrett D'Amore 		if (category == LC_ALL) {
1284297a3b0SGarrett D'Amore 			for (i = 0; i < NUM_CATS; ++i) {
129*5f5fdac7SGarrett D'Amore 				if (i == LC_ALL)
130*5f5fdac7SGarrett D'Amore 					continue;
1314297a3b0SGarrett D'Amore 				env = __get_locale_env(i);
1324297a3b0SGarrett D'Amore 				if (strlen(env) > ENCODING_LEN) {
1334297a3b0SGarrett D'Amore 					errno = EINVAL;
1344297a3b0SGarrett D'Amore 					return (NULL);
1354297a3b0SGarrett D'Amore 				}
1364297a3b0SGarrett D'Amore 				(void) strcpy(new_categories[i], env);
1374297a3b0SGarrett D'Amore 			}
1384297a3b0SGarrett D'Amore 		} else {
1394297a3b0SGarrett D'Amore 			env = __get_locale_env(category);
1404297a3b0SGarrett D'Amore 			if (strlen(env) > ENCODING_LEN) {
1414297a3b0SGarrett D'Amore 				errno = EINVAL;
1424297a3b0SGarrett D'Amore 				return (NULL);
1434297a3b0SGarrett D'Amore 			}
1444297a3b0SGarrett D'Amore 			(void) strcpy(new_categories[category], env);
1454297a3b0SGarrett D'Amore 		}
1464297a3b0SGarrett D'Amore 	} else if (category != LC_ALL) {
1474297a3b0SGarrett D'Amore 		if (strlen(locale) > ENCODING_LEN) {
1484297a3b0SGarrett D'Amore 			errno = EINVAL;
1494297a3b0SGarrett D'Amore 			return (NULL);
1504297a3b0SGarrett D'Amore 		}
1514297a3b0SGarrett D'Amore 		(void) strcpy(new_categories[category], locale);
1524297a3b0SGarrett D'Amore 	} else {
1534297a3b0SGarrett D'Amore 		if ((r = strchr(locale, '/')) == NULL) {
1544297a3b0SGarrett D'Amore 			if (strlen(locale) > ENCODING_LEN) {
1554297a3b0SGarrett D'Amore 				errno = EINVAL;
1564297a3b0SGarrett D'Amore 				return (NULL);
1574297a3b0SGarrett D'Amore 			}
158*5f5fdac7SGarrett D'Amore 			for (i = 0; i < NUM_CATS; ++i)
1594297a3b0SGarrett D'Amore 				(void) strcpy(new_categories[i], locale);
1604297a3b0SGarrett D'Amore 		} else {
1614297a3b0SGarrett D'Amore 			char	*buf;
1624297a3b0SGarrett D'Amore 			char	*save;
1634297a3b0SGarrett D'Amore 
1644297a3b0SGarrett D'Amore 			buf = alloca(strlen(locale) + 1);
165*5f5fdac7SGarrett D'Amore 			(void) strcpy(buf, locale);
1664297a3b0SGarrett D'Amore 
167*5f5fdac7SGarrett D'Amore 			save = NULL;
168*5f5fdac7SGarrett D'Amore 			r = strtok_r(buf, "/", &save);
169*5f5fdac7SGarrett D'Amore 			for (i = 0;  i < NUM_CATS; i++) {
170*5f5fdac7SGarrett D'Amore 				if (i == LC_ALL)
171*5f5fdac7SGarrett D'Amore 					continue;
1724297a3b0SGarrett D'Amore 				if (r == NULL) {
1734297a3b0SGarrett D'Amore 					/*
1744297a3b0SGarrett D'Amore 					 * Composite Locale is inadequately
1754297a3b0SGarrett D'Amore 					 * specified!   (Or with empty fields.)
1764297a3b0SGarrett D'Amore 					 * The old code would fill fields
1774297a3b0SGarrett D'Amore 					 * out from the last one, but I think
1784297a3b0SGarrett D'Amore 					 * this is suboptimal.
1794297a3b0SGarrett D'Amore 					 */
1804297a3b0SGarrett D'Amore 					errno = EINVAL;
1814297a3b0SGarrett D'Amore 					return (NULL);
1824297a3b0SGarrett D'Amore 				}
1834297a3b0SGarrett D'Amore 				(void) strlcpy(new_categories[i], r,
1844297a3b0SGarrett D'Amore 				    ENCODING_LEN);
185*5f5fdac7SGarrett D'Amore 				r = strtok_r(NULL, "/", &save);
186*5f5fdac7SGarrett D'Amore 			}
187*5f5fdac7SGarrett D'Amore 			if (r != NULL) {
188*5f5fdac7SGarrett D'Amore 				/*
189*5f5fdac7SGarrett D'Amore 				 * Too many components - we had left over
190*5f5fdac7SGarrett D'Amore 				 * data in the LC_ALL.  It is malformed.
191*5f5fdac7SGarrett D'Amore 				 */
192*5f5fdac7SGarrett D'Amore 				errno = EINVAL;
193*5f5fdac7SGarrett D'Amore 				return (NULL);
1944297a3b0SGarrett D'Amore 			}
1954297a3b0SGarrett D'Amore 		}
1964297a3b0SGarrett D'Amore 	}
1974297a3b0SGarrett D'Amore 
1984297a3b0SGarrett D'Amore 	if (category != LC_ALL)
1994297a3b0SGarrett D'Amore 		return (loadlocale(category));
2004297a3b0SGarrett D'Amore 
201*5f5fdac7SGarrett D'Amore 	for (i = 0; i < NUM_CATS; ++i) {
2024297a3b0SGarrett D'Amore 		(void) strcpy(saved_categories[i], current_categories[i]);
203*5f5fdac7SGarrett D'Amore 		if (i == LC_ALL)
204*5f5fdac7SGarrett D'Amore 			continue;
2054297a3b0SGarrett D'Amore 		if (loadlocale(i) == NULL) {
2064297a3b0SGarrett D'Amore 			saverr = errno;
207*5f5fdac7SGarrett D'Amore 			for (j = 0; j < i; j++) {
2084297a3b0SGarrett D'Amore 				(void) strcpy(new_categories[j],
2094297a3b0SGarrett D'Amore 				    saved_categories[j]);
210*5f5fdac7SGarrett D'Amore 				if (i == LC_ALL)
211*5f5fdac7SGarrett D'Amore 					continue;
2124297a3b0SGarrett D'Amore 				if (loadlocale(j) == NULL) {
2134297a3b0SGarrett D'Amore 					(void) strcpy(new_categories[j], "C");
2144297a3b0SGarrett D'Amore 					(void) loadlocale(j);
2154297a3b0SGarrett D'Amore 				}
2164297a3b0SGarrett D'Amore 			}
2174297a3b0SGarrett D'Amore 			errno = saverr;
2184297a3b0SGarrett D'Amore 			return (NULL);
2194297a3b0SGarrett D'Amore 		}
2204297a3b0SGarrett D'Amore 	}
2214297a3b0SGarrett D'Amore 	return (currentlocale());
2224297a3b0SGarrett D'Amore }
2234297a3b0SGarrett D'Amore 
2244297a3b0SGarrett D'Amore static char *
2254297a3b0SGarrett D'Amore currentlocale(void)
2264297a3b0SGarrett D'Amore {
2274297a3b0SGarrett D'Amore 	int i;
228*5f5fdac7SGarrett D'Amore 	int composite = 0;
2294297a3b0SGarrett D'Amore 
230*5f5fdac7SGarrett D'Amore 	/* Look to see if any category is different */
231*5f5fdac7SGarrett D'Amore 	for (i = 1; i < NUM_CATS; ++i) {
232*5f5fdac7SGarrett D'Amore 		if (i == LC_ALL)
233*5f5fdac7SGarrett D'Amore 			continue;
234*5f5fdac7SGarrett D'Amore 		if (strcmp(current_categories[0], current_categories[i])) {
235*5f5fdac7SGarrett D'Amore 			composite = 1;
2364297a3b0SGarrett D'Amore 			break;
2374297a3b0SGarrett D'Amore 		}
238*5f5fdac7SGarrett D'Amore 	}
239*5f5fdac7SGarrett D'Amore 
240*5f5fdac7SGarrett D'Amore 	if (composite) {
241*5f5fdac7SGarrett D'Amore 		/*
242*5f5fdac7SGarrett D'Amore 		 * Note ordering of these follows the numeric order,
243*5f5fdac7SGarrett D'Amore 		 * if the order is changed, then setlocale() will need
244*5f5fdac7SGarrett D'Amore 		 * to be changed as well.
245*5f5fdac7SGarrett D'Amore 		 */
246*5f5fdac7SGarrett D'Amore 		(void) snprintf(current_locale_string,
247*5f5fdac7SGarrett D'Amore 		    sizeof (current_locale_string),
248*5f5fdac7SGarrett D'Amore 		    "%s/%s/%s/%s/%s/%s",
249*5f5fdac7SGarrett D'Amore 		    current_categories[LC_CTYPE],
250*5f5fdac7SGarrett D'Amore 		    current_categories[LC_NUMERIC],
251*5f5fdac7SGarrett D'Amore 		    current_categories[LC_TIME],
252*5f5fdac7SGarrett D'Amore 		    current_categories[LC_COLLATE],
253*5f5fdac7SGarrett D'Amore 		    current_categories[LC_MONETARY],
254*5f5fdac7SGarrett D'Amore 		    current_categories[LC_MESSAGES]);
255*5f5fdac7SGarrett D'Amore 	} else {
256*5f5fdac7SGarrett D'Amore 		(void) strlcpy(current_locale_string, current_categories[0],
257*5f5fdac7SGarrett D'Amore 		    sizeof (current_locale_string));
258*5f5fdac7SGarrett D'Amore 	}
2594297a3b0SGarrett D'Amore 	return (current_locale_string);
2604297a3b0SGarrett D'Amore }
2614297a3b0SGarrett D'Amore 
2624297a3b0SGarrett D'Amore static char *
2634297a3b0SGarrett D'Amore loadlocale(int category)
2644297a3b0SGarrett D'Amore {
2654297a3b0SGarrett D'Amore 	char *new = new_categories[category];
2664297a3b0SGarrett D'Amore 	char *old = current_categories[category];
2674297a3b0SGarrett D'Amore 	int (*func)(const char *);
2684297a3b0SGarrett D'Amore 
2694297a3b0SGarrett D'Amore 	if ((new[0] == '.' &&
2704297a3b0SGarrett D'Amore 	    (new[1] == '\0' || (new[1] == '.' && new[2] == '\0'))) ||
2714297a3b0SGarrett D'Amore 	    strchr(new, '/') != NULL) {
2724297a3b0SGarrett D'Amore 		errno = EINVAL;
2734297a3b0SGarrett D'Amore 		return (NULL);
2744297a3b0SGarrett D'Amore 	}
2754297a3b0SGarrett D'Amore 
2764297a3b0SGarrett D'Amore 	switch (category) {
2774297a3b0SGarrett D'Amore 	case LC_CTYPE:
2784297a3b0SGarrett D'Amore 		func = __wrap_setrunelocale;
2794297a3b0SGarrett D'Amore 		break;
2804297a3b0SGarrett D'Amore 	case LC_COLLATE:
2814297a3b0SGarrett D'Amore 		func = __collate_load_tables;
2824297a3b0SGarrett D'Amore 		break;
2834297a3b0SGarrett D'Amore 	case LC_TIME:
2844297a3b0SGarrett D'Amore 		func = __time_load_locale;
2854297a3b0SGarrett D'Amore 		break;
2864297a3b0SGarrett D'Amore 	case LC_NUMERIC:
2874297a3b0SGarrett D'Amore 		func = __numeric_load_locale;
2884297a3b0SGarrett D'Amore 		break;
2894297a3b0SGarrett D'Amore 	case LC_MONETARY:
2904297a3b0SGarrett D'Amore 		func = __monetary_load_locale;
2914297a3b0SGarrett D'Amore 		break;
2924297a3b0SGarrett D'Amore 	case LC_MESSAGES:
2934297a3b0SGarrett D'Amore 		func = __messages_load_locale;
2944297a3b0SGarrett D'Amore 		break;
2954297a3b0SGarrett D'Amore 	default:
2964297a3b0SGarrett D'Amore 		errno = EINVAL;
2974297a3b0SGarrett D'Amore 		return (NULL);
2984297a3b0SGarrett D'Amore 	}
2994297a3b0SGarrett D'Amore 
3004297a3b0SGarrett D'Amore 	if (strcmp(new, old) == 0)
3014297a3b0SGarrett D'Amore 		return (old);
3024297a3b0SGarrett D'Amore 
3034297a3b0SGarrett D'Amore 	if (func(new) != _LDP_ERROR) {
3044297a3b0SGarrett D'Amore 		(void) strcpy(old, new);
3054297a3b0SGarrett D'Amore 		return (old);
3064297a3b0SGarrett D'Amore 	}
3074297a3b0SGarrett D'Amore 
3084297a3b0SGarrett D'Amore 	return (NULL);
3094297a3b0SGarrett D'Amore }
3104297a3b0SGarrett D'Amore 
3114297a3b0SGarrett D'Amore static const char *
3124297a3b0SGarrett D'Amore __get_locale_env(int category)
3134297a3b0SGarrett D'Amore {
3144297a3b0SGarrett D'Amore 	const char *env;
3154297a3b0SGarrett D'Amore 
3164297a3b0SGarrett D'Amore 	/* 1. check LC_ALL. */
317*5f5fdac7SGarrett D'Amore 	env = getenv(categories[LC_ALL]);
3184297a3b0SGarrett D'Amore 
3194297a3b0SGarrett D'Amore 	/* 2. check LC_* */
3204297a3b0SGarrett D'Amore 	if (env == NULL || !*env)
3214297a3b0SGarrett D'Amore 		env = getenv(categories[category]);
3224297a3b0SGarrett D'Amore 
3234297a3b0SGarrett D'Amore 	/* 3. check LANG */
3244297a3b0SGarrett D'Amore 	if (env == NULL || !*env)
3254297a3b0SGarrett D'Amore 		env = getenv("LANG");
3264297a3b0SGarrett D'Amore 
3274297a3b0SGarrett D'Amore 	/* 4. if none is set, fall to "C" */
3284297a3b0SGarrett D'Amore 	if (env == NULL || !*env)
3294297a3b0SGarrett D'Amore 		env = "C";
3304297a3b0SGarrett D'Amore 
3314297a3b0SGarrett D'Amore 	return (env);
3324297a3b0SGarrett D'Amore }
333