1 /*
2  * Copyright 2010 Nexenta Systems, Inc.  All rights reserved.
3  * Copyright (c) 1996 - 2002 FreeBSD Project
4  * Copyright (c) 1991, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Paul Borman at Krystal Technologies.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 4. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include "lint.h"
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <errno.h>
39 #include <limits.h>
40 #include <locale.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <alloca.h>
45 #include <stdio.h>
46 #include "collate.h"
47 #include "lmonetary.h"	/* for __monetary_load_locale() */
48 #include "lnumeric.h"	/* for __numeric_load_locale() */
49 #include "lmessages.h"	/* for __messages_load_locale() */
50 #include "setlocale.h"
51 #include "ldpart.h"
52 #include "timelocal.h" /* for __time_load_locale() */
53 #include "../i18n/_loc_path.h"
54 
55 /*
56  * Category names for getenv()  Note that this was modified
57  * for Solaris.  See <iso/locale_iso.h>.
58  */
59 #define	NUM_CATS	7
60 static char *categories[7] = {
61 	"LC_CTYPE",
62 	"LC_NUMERIC",
63 	"LC_TIME",
64 	"LC_COLLATE",
65 	"LC_MONETARY",
66 	"LC_MESSAGES",
67 	"LC_ALL",
68 };
69 
70 /*
71  * Current locales for each category
72  */
73 static char current_categories[NUM_CATS][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.  See ../i18n/_loc_path.h
85  */
86 char	*_PathLocale = _DFLT_LOC_PATH;
87 
88 /*
89  * The locales we are going to try and load
90  */
91 static char new_categories[NUM_CATS][ENCODING_LEN + 1];
92 static char saved_categories[NUM_CATS][ENCODING_LEN + 1];
93 static char current_locale_string[NUM_CATS * (ENCODING_LEN + 1 + 1)];
94 
95 static char	*currentlocale(void);
96 static char	*loadlocale(int);
97 static const char *__get_locale_env(int);
98 
99 char *
100 setlocale(int category, const char *locale)
101 {
102 	int i, j, saverr;
103 	const char *env, *r;
104 
105 	if (category < 0 || category >= NUM_CATS) {
106 		errno = EINVAL;
107 		return (NULL);
108 	}
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 = 0; i < NUM_CATS; ++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 = 0; i < NUM_CATS; ++i) {
126 				if (i == LC_ALL)
127 					continue;
128 				env = __get_locale_env(i);
129 				if (strlen(env) > ENCODING_LEN) {
130 					errno = EINVAL;
131 					return (NULL);
132 				}
133 				(void) strcpy(new_categories[i], env);
134 			}
135 		} else {
136 			env = __get_locale_env(category);
137 			if (strlen(env) > ENCODING_LEN) {
138 				errno = EINVAL;
139 				return (NULL);
140 			}
141 			(void) strcpy(new_categories[category], env);
142 		}
143 	} else if (category != LC_ALL) {
144 		if (strlen(locale) > ENCODING_LEN) {
145 			errno = EINVAL;
146 			return (NULL);
147 		}
148 		(void) strcpy(new_categories[category], locale);
149 	} else {
150 		if ((r = strchr(locale, '/')) == NULL) {
151 			if (strlen(locale) > ENCODING_LEN) {
152 				errno = EINVAL;
153 				return (NULL);
154 			}
155 			for (i = 0; i < NUM_CATS; ++i)
156 				(void) strcpy(new_categories[i], locale);
157 		} else {
158 			char	*buf;
159 			char	*save;
160 
161 			buf = alloca(strlen(locale) + 1);
162 			(void) strcpy(buf, locale);
163 
164 			save = NULL;
165 			r = strtok_r(buf, "/", &save);
166 			for (i = 0;  i < NUM_CATS; i++) {
167 				if (i == LC_ALL)
168 					continue;
169 				if (r == NULL) {
170 					/*
171 					 * Composite Locale is inadequately
172 					 * specified!   (Or with empty fields.)
173 					 * The old code would fill fields
174 					 * out from the last one, but I think
175 					 * this is suboptimal.
176 					 */
177 					errno = EINVAL;
178 					return (NULL);
179 				}
180 				(void) strlcpy(new_categories[i], r,
181 				    ENCODING_LEN);
182 				r = strtok_r(NULL, "/", &save);
183 			}
184 			if (r != NULL) {
185 				/*
186 				 * Too many components - we had left over
187 				 * data in the LC_ALL.  It is malformed.
188 				 */
189 				errno = EINVAL;
190 				return (NULL);
191 			}
192 		}
193 	}
194 
195 	if (category != LC_ALL)
196 		return (loadlocale(category));
197 
198 	for (i = 0; i < NUM_CATS; ++i) {
199 		(void) strcpy(saved_categories[i], current_categories[i]);
200 		if (i == LC_ALL)
201 			continue;
202 		if (loadlocale(i) == NULL) {
203 			saverr = errno;
204 			for (j = 0; j < i; j++) {
205 				(void) strcpy(new_categories[j],
206 				    saved_categories[j]);
207 				if (i == LC_ALL)
208 					continue;
209 				if (loadlocale(j) == NULL) {
210 					(void) strcpy(new_categories[j], "C");
211 					(void) loadlocale(j);
212 				}
213 			}
214 			errno = saverr;
215 			return (NULL);
216 		}
217 	}
218 	return (currentlocale());
219 }
220 
221 static char *
222 currentlocale(void)
223 {
224 	int i;
225 	int composite = 0;
226 
227 	/* Look to see if any category is different */
228 	for (i = 1; i < NUM_CATS; ++i) {
229 		if (i == LC_ALL)
230 			continue;
231 		if (strcmp(current_categories[0], current_categories[i])) {
232 			composite = 1;
233 			break;
234 		}
235 	}
236 
237 	if (composite) {
238 		/*
239 		 * Note ordering of these follows the numeric order,
240 		 * if the order is changed, then setlocale() will need
241 		 * to be changed as well.
242 		 */
243 		(void) snprintf(current_locale_string,
244 		    sizeof (current_locale_string),
245 		    "%s/%s/%s/%s/%s/%s",
246 		    current_categories[LC_CTYPE],
247 		    current_categories[LC_NUMERIC],
248 		    current_categories[LC_TIME],
249 		    current_categories[LC_COLLATE],
250 		    current_categories[LC_MONETARY],
251 		    current_categories[LC_MESSAGES]);
252 	} else {
253 		(void) strlcpy(current_locale_string, current_categories[0],
254 		    sizeof (current_locale_string));
255 	}
256 	return (current_locale_string);
257 }
258 
259 static char *
260 loadlocale(int category)
261 {
262 	char *new = new_categories[category];
263 	char *old = current_categories[category];
264 	int (*func)(const char *);
265 
266 	if ((new[0] == '.' &&
267 	    (new[1] == '\0' || (new[1] == '.' && new[2] == '\0'))) ||
268 	    strchr(new, '/') != NULL) {
269 		errno = EINVAL;
270 		return (NULL);
271 	}
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) != _LDP_ERROR) {
301 		(void) strcpy(old, new);
302 		return (old);
303 	}
304 
305 	return (NULL);
306 }
307 
308 static const char *
309 __get_locale_env(int category)
310 {
311 	const char *env;
312 
313 	/* 1. check LC_ALL. */
314 	env = getenv(categories[LC_ALL]);
315 
316 	/* 2. check LC_* */
317 	if (env == NULL || !*env)
318 		env = getenv(categories[category]);
319 
320 	/* 3. check LANG */
321 	if (env == NULL || !*env)
322 		env = getenv("LANG");
323 
324 	/* 4. if none is set, fall to "C" */
325 	if (env == NULL || !*env)
326 		env = "C";
327 
328 	return (env);
329 }
330