xref: /dragonfly/lib/libc/locale/setlocale.c (revision e65bc1c3)
1 /*	$NetBSD: src/lib/libc/locale/setlocale.c,v 1.47 2004/07/21 20:27:46 tshiozak Exp $	*/
2 
3 /*
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  * 3. 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 #define _CTYPE_PRIVATE
36 
37 #include <sys/types.h>
38 #include <sys/localedef.h>
39 #include <sys/stat.h>
40 #include <assert.h>
41 #include <ctype.h>
42 #include <limits.h>
43 #include <locale.h>
44 #include <paths.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include "collate.h"
50 #include "rune.h"
51 #include "rune_local.h"
52 
53 #include "../citrus/citrus_namespace.h"
54 #include "../citrus/citrus_region.h"
55 #include "../citrus/citrus_lookup.h"
56 #include "../citrus/citrus_bcs.h"
57 
58 #define _LOCALE_ALIAS_NAME	"locale.alias"
59 #define _LOCALE_SYM_FORCE	"/force"
60 
61 static char	*currentlocale(void);
62 static void	revert_to_default(int);
63 static int	force_locale_enable(int);
64 static int	load_locale_sub(int, const char *, int);
65 static char	*loadlocale(int);
66 static const char *__get_locale_env(int);
67 
68 static void	revert_collate(void);
69 static int	load_ctype(const char *);
70 static void	revert_ctype(void);
71 static int	load_messages(const char *);
72 
73 static const struct  {
74 	const char *name;
75 	int (*load_function)(const char *);
76 	void (*revert_function)(void);
77 } categories[] = {
78 	{ "LC_ALL", NULL, NULL },
79 	{ "LC_COLLATE", __collate_load_tables, revert_collate },
80 	{ "LC_CTYPE", load_ctype, revert_ctype },
81 	{ "LC_MONETARY", NULL, NULL },
82 	{ "LC_NUMERIC", NULL, NULL },
83 	{ "LC_TIME", NULL, NULL },
84 	{ "LC_MESSAGES", load_messages, NULL }
85 };
86 
87 /*
88  * Current locales for each category
89  */
90 static char current_categories[_LC_LAST][32] = {
91 	"C",
92         "C",
93         "C",
94         "C",
95         "C",
96 	"C",
97         "C"
98 };
99 
100 /*
101  * The locales we are going to try and load
102  */
103 static char new_categories[_LC_LAST][32];
104 
105 static char current_locale_string[_LC_LAST * 33];
106 const char *_PathLocale;
107 
108 static int
109 load_ctype(const char *locale)
110 {
111 	if (_xpg4_setrunelocale(locale))
112 		return(-1);
113 	if (__runetable_to_netbsd_ctype(locale)) {
114 		/* very unfortunate, but need to go to "C" locale */
115 		revert_ctype();
116 		return(-1);
117 	}
118 
119 	return(0);
120 }
121 
122 static void
123 revert_ctype(void)
124 {
125 	_xpg4_setrunelocale("C");
126 	__runetable_to_netbsd_ctype("C");
127 }
128 
129 static void
130 revert_collate(void)
131 {
132 	__collate_load_tables("C");
133 }
134 
135 static int
136 load_messages(const char *locale)
137 {
138 	char name[PATH_MAX];
139 	struct stat st;
140 
141 	/*
142 	 * XXX we don't have LC_MESSAGES support yet,
143 	 * but catopen may use the value of LC_MESSAGES category.
144 	 * so return successfully if locale directory is present.
145 	 */
146 	snprintf(name, sizeof(name), "%s/%s", _PathLocale, locale);
147 
148 	if (stat(name, &st) < 0)
149 		return(-1);
150 	if (!S_ISDIR(st.st_mode))
151 		return(-1);
152 	return(0);
153 }
154 
155 char *
156 setlocale(int category, const char *locale)
157 {
158 	int i, loadlocale_success;
159 	size_t len;
160 	const char *env, *r;
161 
162 	__mb_len_max_runtime = 32;
163 
164 	if (issetugid() ||
165 	    (!_PathLocale && !(_PathLocale = getenv("PATH_LOCALE"))))
166 		_PathLocale = _PATH_LOCALE;
167 
168 	if (category < 0 || category >= (int)__arysize(categories))
169 		return(NULL);
170 
171 	if (locale == NULL)
172 		return(category ?
173 		    current_categories[category] : currentlocale());
174 
175 	/*
176 	 * Default to the current locale for everything.
177 	 */
178 	for (i = 1; i < _LC_LAST; ++i)
179 		strlcpy(new_categories[i], current_categories[i],
180 			sizeof(new_categories[i]));
181 
182 	/*
183 	 * Now go fill up new_categories from the locale argument
184 	 */
185 	if (*locale == '\0') {
186 		if (category == LC_ALL) {
187 			for (i = 1; i < _LC_LAST; ++i) {
188 				env = __get_locale_env(i);
189 				strlcpy(new_categories[i], env,
190 				    sizeof(new_categories[i]));
191 			}
192 		}
193 		else {
194 			env = __get_locale_env(category);
195 			strlcpy(new_categories[category], env,
196 				sizeof(new_categories[category]));
197 		}
198 	} else if (category) {
199 		strlcpy(new_categories[category], locale,
200 		        sizeof(new_categories[category]));
201 	} else {
202 		if ((r = strchr(locale, '/')) == NULL) {
203 			for (i = 1; i < _LC_LAST; ++i) {
204 				strlcpy(new_categories[i], locale,
205 				        sizeof(new_categories[i]));
206 			}
207 		} else {
208 			for (i = 1;;) {
209 				_DIAGASSERT(*r == '/' || *r == 0);
210 				_DIAGASSERT(*locale != 0);
211 				if (*locale == '/')
212 					return(NULL);	/* invalid format. */
213 				len = r - locale;
214 				if (len + 1 > sizeof(new_categories[i]))
215 					return(NULL);	/* too long */
216 				memcpy(new_categories[i], locale, len);
217 				new_categories[i][len] = '\0';
218 				if (*r == 0)
219 					break;
220 				_DIAGASSERT(*r == '/');
221 				if (*(locale = ++r) == 0)
222 					/* slash followed by NUL */
223 					return(NULL);
224 				/* skip until NUL or '/' */
225 				while (*r && *r != '/')
226 					r++;
227 				if (++i == _LC_LAST)
228 					return(NULL);	/* too many slashes. */
229 			}
230 			if (i + 1 != _LC_LAST)
231 				return(NULL);	/* too few slashes. */
232 		}
233 	}
234 
235 	if (category)
236 		return(loadlocale(category));
237 
238 	loadlocale_success = 0;
239 	for (i = 1; i < _LC_LAST; ++i) {
240 		if (loadlocale(i) != NULL)
241 			loadlocale_success = 1;
242 	}
243 
244 	/*
245 	 * If all categories failed, return NULL; we don't need to back
246 	 * changes off, since none happened.
247 	 */
248 	if (!loadlocale_success)
249 		return(NULL);
250 
251 	return(currentlocale());
252 }
253 
254 static char *
255 currentlocale(void)
256 {
257 	int i;
258 
259 	strlcpy(current_locale_string, current_categories[1],
260 		sizeof(current_locale_string));
261 
262 	for (i = 2; i < _LC_LAST; ++i)
263 		if (strcmp(current_categories[1], current_categories[i])) {
264 			snprintf(current_locale_string,
265 			    sizeof(current_locale_string), "%s/%s/%s/%s/%s/%s",
266 			    current_categories[1], current_categories[2],
267 			    current_categories[3], current_categories[4],
268 			    current_categories[5], current_categories[6]);
269 			break;
270 		}
271 	return(current_locale_string);
272 }
273 
274 static void
275 revert_to_default(int category)
276 {
277 	_DIAGASSERT(category >= 0 && category < _LC_LAST);
278 
279 	if (categories[category].revert_function != NULL)
280 		categories[category].revert_function();
281 }
282 
283 static int
284 force_locale_enable(int category)
285 {
286 	revert_to_default(category);
287 
288 	return(0);
289 }
290 
291 static int
292 load_locale_sub(int category, const char *locname, int isspecial)
293 {
294 	char name[PATH_MAX];
295 
296 	/* check for the default locales */
297 	if (!strcmp(new_categories[category], "C") ||
298 	    !strcmp(new_categories[category], "POSIX")) {
299 		revert_to_default(category);
300 		return(0);
301 	}
302 
303 	/* check whether special symbol */
304 	if (isspecial && _bcs_strcasecmp(locname, _LOCALE_SYM_FORCE) == 0)
305 		return(force_locale_enable(category));
306 
307 	/* sanity check */
308 	if (strchr(locname, '/') != NULL)
309 		return(-1);
310 
311 	snprintf(name, sizeof(name), "%s/%s/%s", _PathLocale, locname,
312 		 categories[category].name);
313 
314 	if (category > 0 && category < (int)__arysize(categories) &&
315 	    categories[category].load_function != NULL)
316 		return(categories[category].load_function(locname));
317 
318 	return(0);
319 }
320 
321 static char *
322 loadlocale(int category)
323 {
324 	char aliaspath[PATH_MAX], loccat[PATH_MAX], buf[PATH_MAX];
325 	const char *alias;
326 
327 	_DIAGASSERT(0 < category && category < __arysize(categories));
328 
329 	if (strcmp(new_categories[category], current_categories[category]) == 0)
330 		return(current_categories[category]);
331 
332 	/* (1) non-aliased file */
333 	if (!load_locale_sub(category, new_categories[category], 0))
334 		goto success;
335 
336 	/* (2) lookup locname/catname type alias */
337 	snprintf(aliaspath, sizeof(aliaspath), "%s/" _LOCALE_ALIAS_NAME,
338 		 _PathLocale);
339 	snprintf(loccat, sizeof(loccat), "%s/%s", new_categories[category],
340 		 categories[category].name);
341 	alias = _lookup_alias(aliaspath, loccat, buf, sizeof(buf),
342 			      _LOOKUP_CASE_SENSITIVE);
343 	if (!load_locale_sub(category, alias, 1))
344 		goto success;
345 
346 	/* (3) lookup locname type alias */
347 	alias = _lookup_alias(aliaspath, new_categories[category],
348 			      buf, sizeof(buf), _LOOKUP_CASE_SENSITIVE);
349 	if (!load_locale_sub(category, alias, 1))
350 		goto success;
351 
352 	return(NULL);
353 
354 success:
355 	strlcpy(current_categories[category], new_categories[category],
356 		sizeof(current_categories[category]));
357 	return(current_categories[category]);
358 }
359 
360 static const char *
361 __get_locale_env(int category)
362 {
363 	const char *env;
364 
365 	_DIAGASSERT(category != LC_ALL);
366 
367 	/* 1. check LC_ALL. */
368 	env = getenv(categories[0].name);
369 
370 	/* 2. check LC_* */
371 	if (env == NULL || *env == '\0')
372 		env = getenv(categories[category].name);
373 
374 	/* 3. check LANG */
375 	if (env == NULL || *env == '\0')
376 		env = getenv("LANG");
377 
378 	/* 4. if none is set, fall to "C" */
379 	if (env == NULL || *env == '\0' || strchr(env, '/'))
380 		env = "C";
381 
382 	return(env);
383 }
384