xref: /openbsd/lib/libc/locale/setlocale.c (revision 7b36286a)
1 /*	$OpenBSD: setlocale.c,v 1.17 2007/11/28 10:24:38 chl Exp $	*/
2 /*
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  * 3. 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 #include <sys/localedef.h>
35 #include <locale.h>
36 #include <limits.h>
37 #include <paths.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #include "rune.h"
44 #include "rune_local.h"
45 /*
46  * Category names for getenv()
47  */
48 static char *categories[_LC_LAST] = {
49     "LC_ALL",
50     "LC_COLLATE",
51     "LC_CTYPE",
52     "LC_MONETARY",
53     "LC_NUMERIC",
54     "LC_TIME",
55     "LC_MESSAGES"
56 };
57 
58 /*
59  * Current locales for each category
60  */
61 static char current_categories[_LC_LAST][32] = {
62     "C",
63     "C",
64     "C",
65     "C",
66     "C",
67     "C",
68     "C"
69 };
70 
71 /*
72  * The locales we are going to try and load
73  */
74 static char new_categories[_LC_LAST][32];
75 
76 static char current_locale_string[_LC_LAST * 33];
77 
78 static char	*currentlocale(void);
79 static void revert_to_default(int);
80 static int load_locale_sub(int, const char *, int);
81 static char	*loadlocale(int);
82 static const char *__get_locale_env(int);
83 
84 char *
85 setlocale(int category, const char *locale)
86 {
87 	int i, loadlocale_success;
88 	size_t len;
89 	const char *env, *r;
90 
91 	if (issetugid() != 0 ||
92 	    ((!_PathLocale && !(_PathLocale = getenv("PATH_LOCALE"))) ||
93 	     !*_PathLocale))
94 		_PathLocale = _PATH_LOCALE;
95 
96 	if (category < 0 || category >= _LC_LAST)
97 		return (NULL);
98 
99 	if (!locale)
100 		return (category ?
101 		    current_categories[category] : currentlocale());
102 
103 	/*
104 	 * Default to the current locale for everything.
105 	 */
106 	for (i = 1; i < _LC_LAST; ++i)
107 		(void)strlcpy(new_categories[i], current_categories[i],
108 		    sizeof(new_categories[i]));
109 
110 	/*
111 	 * Now go fill up new_categories from the locale argument
112 	 */
113 	if (!*locale) {
114 		if (category == LC_ALL) {
115 			for (i = 1; i < _LC_LAST; ++i) {
116 				env = __get_locale_env(i);
117 				(void)strlcpy(new_categories[i], env,
118 				    sizeof(new_categories[i]));
119 			}
120 		}
121 		else {
122 			env = __get_locale_env(category);
123 			(void)strlcpy(new_categories[category], env,
124 				sizeof(new_categories[category]));
125 		}
126 	} else if (category) {
127 		(void)strlcpy(new_categories[category], locale,
128 		    sizeof(new_categories[category]));
129 	} else {
130 		if ((r = strchr(locale, '/')) == 0) {
131 			for (i = 1; i < _LC_LAST; ++i) {
132 				(void)strlcpy(new_categories[i], locale,
133 				    sizeof(new_categories[i]));
134 			}
135 		} else {
136 			for (i = 1;;) {
137 				if (*locale == '/')
138 					return (NULL);	/* invalid format. */
139 				len = r - locale;
140 				if (len + 1 > sizeof(new_categories[i]))
141 					return (NULL);	/* too long */
142 				(void)memcpy(new_categories[i], locale, len);
143 				new_categories[i][len] = '\0';
144 				if (*r == 0)
145 					break;
146 				if (*(locale = ++r) == 0)
147 					/* slash followed by NUL */
148 					return (NULL);
149 				/* skip until NUL or '/' */
150 				while (*r && *r != '/')
151 					r++;
152 				if (++i == _LC_LAST)
153 					return (NULL);	/* too many slashes. */
154 			}
155 			if (i + 1 != _LC_LAST)
156 				return (NULL);	/* too few slashes. */
157 		}
158 	}
159 
160 	if (category)
161 		return (loadlocale(category));
162 
163 	loadlocale_success = 0;
164 	for (i = 1; i < _LC_LAST; ++i) {
165 		if (loadlocale(i) != NULL)
166 			loadlocale_success = 1;
167 	}
168 
169 	/*
170 	 * If all categories failed, return NULL; we don't need to back
171 	 * changes off, since none happened.
172 	 */
173 	if (!loadlocale_success)
174 		return NULL;
175 
176 	return (currentlocale());
177 }
178 
179 static char *
180 currentlocale(void)
181 {
182 	int i;
183 
184 	(void)strlcpy(current_locale_string, current_categories[1],
185 	    sizeof(current_locale_string));
186 
187 	for (i = 2; i < _LC_LAST; ++i)
188 		if (strcmp(current_categories[1], current_categories[i])) {
189 			(void)snprintf(current_locale_string,
190 			    sizeof(current_locale_string), "%s/%s/%s/%s/%s/%s",
191 			    current_categories[1], current_categories[2],
192 			    current_categories[3], current_categories[4],
193 			    current_categories[5], current_categories[6]);
194 			break;
195 		}
196 	return (current_locale_string);
197 }
198 
199 static void
200 revert_to_default(int category)
201 {
202 	switch (category) {
203 	case LC_CTYPE:
204 		(void)_xpg4_setrunelocale("C");
205 		__install_currentrunelocale_ctype();
206 		break;
207 	case LC_MESSAGES:
208 	case LC_COLLATE:
209 	case LC_MONETARY:
210 	case LC_NUMERIC:
211 	case LC_TIME:
212 		break;
213 	}
214 }
215 
216 static int
217 load_locale_sub(int category, const char *locname, int isspecial)
218 {
219 	char name[PATH_MAX];
220 	int len;
221 
222 	/* check for the default locales */
223 	if (!strcmp(new_categories[category], "C") ||
224 	    !strcmp(new_categories[category], "POSIX")) {
225 		revert_to_default(category);
226 		return 0;
227 	}
228 
229 	/* sanity check */
230 	if (strchr(locname, '/') != NULL)
231 		return -1;
232 
233 	len = snprintf(name, sizeof(name), "%s/%s/%s",
234 		       _PathLocale, locname, categories[category]);
235 	if (len < 0 || len >= sizeof(name))
236 		return -1;
237 
238 	switch (category) {
239 	case LC_CTYPE:
240 		if (_xpg4_setrunelocale(locname))
241 			return -1;
242 		__install_currentrunelocale_ctype();
243 		break;
244 
245 	case LC_MESSAGES:
246 	case LC_COLLATE:
247 	case LC_MONETARY:
248 	case LC_NUMERIC:
249 	case LC_TIME:
250 		return -1;
251 	}
252 
253 	return 0;
254 }
255 
256 static char *
257 loadlocale(int category)
258 {
259 	if (strcmp(new_categories[category],
260 	    current_categories[category]) == 0)
261 		return (current_categories[category]);
262 
263 	if (!load_locale_sub(category, new_categories[category], 0)) {
264 		(void)strlcpy(current_categories[category],
265 		    new_categories[category], sizeof(current_categories[category]));
266 		return current_categories[category];
267 	} else {
268 		return NULL;
269 	}
270 }
271 
272 static const char *
273 __get_locale_env(int category)
274 {
275 	const char *env;
276 
277 	/* 1. check LC_ALL. */
278 	env = getenv(categories[0]);
279 
280 	/* 2. check LC_* */
281 	if (!env || !*env)
282 		env = getenv(categories[category]);
283 
284 	/* 3. check LANG */
285 	if (!env || !*env)
286 		env = getenv("LANG");
287 
288 	/* 4. if none is set, fall to "C" */
289 	if (!env || !*env || strchr(env, '/'))
290 		env = "C";
291 
292 	return env;
293 }
294