xref: /dragonfly/lib/libc/locale/setlocale.c (revision 1bf4b486)
1 /*	$NetBSD: src/lib/libc/locale/setlocale.c,v 1.47 2004/07/21 20:27:46 tshiozak Exp $	*/
2 /*	$DragonFly: src/lib/libc/locale/setlocale.c,v 1.3 2005/04/21 16:36:34 joerg Exp $ */
3 
4 /*
5  * Copyright (c) 1991, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * Paul Borman at Krystal Technologies.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #define _CTYPE_PRIVATE
37 
38 #include <sys/types.h>
39 #include <sys/localedef.h>
40 #include <sys/stat.h>
41 #include <assert.h>
42 #include <ctype.h>
43 #include <limits.h>
44 #include <locale.h>
45 #include <paths.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <unistd.h>
50 #include "collate.h"
51 #include "rune.h"
52 #include "rune_local.h"
53 
54 #include "../citrus/citrus_namespace.h"
55 #include "../citrus/citrus_region.h"
56 #include "../citrus/citrus_lookup.h"
57 #include "../citrus/citrus_bcs.h"
58 
59 #define _LOCALE_ALIAS_NAME	"locale.alias"
60 #define _LOCALE_SYM_FORCE	"/force"
61 
62 static char	*currentlocale(void);
63 static void	revert_to_default(int);
64 static int	force_locale_enable(int);
65 static int	load_locale_sub(int, const char *, int);
66 static char	*loadlocale(int);
67 static const char *__get_locale_env(int);
68 
69 static void	revert_collate(void);
70 static int	load_ctype(const char *);
71 static void	revert_ctype(void);
72 static int	load_messages(const char *);
73 
74 static const struct  {
75 	const char *name;
76 	int (*load_function)(const char *);
77 	void (*revert_function)(void);
78 } categories[] = {
79 	{ "LC_ALL", NULL, NULL },
80 	{ "LC_COLLATE", __collate_load_tables, revert_collate },
81 	{ "LC_CTYPE", load_ctype, revert_ctype },
82 	{ "LC_MONETARY", NULL, NULL },
83 	{ "LC_NUMERIC", NULL, NULL },
84 	{ "LC_TIME", NULL, NULL },
85 	{ "LC_MESSAGES", load_messages, NULL }
86 };
87 
88 /*
89  * Current locales for each category
90  */
91 static char current_categories[_LC_LAST][32] = {
92 	"C",
93         "C",
94         "C",
95         "C",
96         "C",
97 	"C",
98         "C"
99 };
100 
101 /*
102  * The locales we are going to try and load
103  */
104 static char new_categories[_LC_LAST][32];
105 
106 static char current_locale_string[_LC_LAST * 33];
107 const char *_PathLocale;
108 
109 static int
110 load_ctype(const char *locale)
111 {
112 	if (_xpg4_setrunelocale(locale))
113 		return(-1);
114 	if (__runetable_to_netbsd_ctype(locale)) {
115 		/* very unfortunate, but need to go to "C" locale */
116 		revert_ctype();
117 		return(-1);
118 	}
119 
120 	return(0);
121 }
122 
123 static void
124 revert_ctype(void)
125 {
126 	_xpg4_setrunelocale("C");
127 	__runetable_to_netbsd_ctype("C");
128 }
129 
130 static void
131 revert_collate(void)
132 {
133 	__collate_load_tables("C");
134 }
135 
136 static int
137 load_messages(const char *locale)
138 {
139 	char name[PATH_MAX];
140 	struct stat st;
141 
142 	/*
143 	 * XXX we don't have LC_MESSAGES support yet,
144 	 * but catopen may use the value of LC_MESSAGES category.
145 	 * so return successfully if locale directory is present.
146 	 */
147 	snprintf(name, sizeof(name), "%s/%s", _PathLocale, locale);
148 
149 	if (stat(name, &st) < 0)
150 		return(-1);
151 	if (!S_ISDIR(st.st_mode))
152 		return(-1);
153 	return(0);
154 }
155 
156 char *
157 setlocale(int category, const char *locale)
158 {
159 	int i, loadlocale_success;
160 	size_t len;
161 	const char *env, *r;
162 
163 	__mb_len_max_runtime = 32;
164 
165 	if (issetugid() ||
166 	    (!_PathLocale && !(_PathLocale = getenv("PATH_LOCALE"))))
167 		_PathLocale = _PATH_LOCALE;
168 
169 	if (category < 0 || category >= (int)__arysize(categories))
170 		return(NULL);
171 
172 	if (locale == NULL)
173 		return(category ?
174 		    current_categories[category] : currentlocale());
175 
176 	/*
177 	 * Default to the current locale for everything.
178 	 */
179 	for (i = 1; i < _LC_LAST; ++i)
180 		strlcpy(new_categories[i], current_categories[i],
181 			sizeof(new_categories[i]));
182 
183 	/*
184 	 * Now go fill up new_categories from the locale argument
185 	 */
186 	if (*locale == '\0') {
187 		if (category == LC_ALL) {
188 			for (i = 1; i < _LC_LAST; ++i) {
189 				env = __get_locale_env(i);
190 				strlcpy(new_categories[i], env,
191 				    sizeof(new_categories[i]));
192 			}
193 		}
194 		else {
195 			env = __get_locale_env(category);
196 			strlcpy(new_categories[category], env,
197 				sizeof(new_categories[category]));
198 		}
199 	} else if (category) {
200 		strlcpy(new_categories[category], locale,
201 		        sizeof(new_categories[category]));
202 	} else {
203 		if ((r = strchr(locale, '/')) == 0) {
204 			for (i = 1; i < _LC_LAST; ++i) {
205 				strlcpy(new_categories[i], locale,
206 				        sizeof(new_categories[i]));
207 			}
208 		} else {
209 			for (i = 1;;) {
210 				_DIAGASSERT(*r == '/' || *r == 0);
211 				_DIAGASSERT(*locale != 0);
212 				if (*locale == '/')
213 					return(NULL);	/* invalid format. */
214 				len = r - locale;
215 				if (len + 1 > sizeof(new_categories[i]))
216 					return(NULL);	/* too long */
217 				memcpy(new_categories[i], locale, len);
218 				new_categories[i][len] = '\0';
219 				if (*r == 0)
220 					break;
221 				_DIAGASSERT(*r == '/');
222 				if (*(locale = ++r) == 0)
223 					/* slash followed by NUL */
224 					return(NULL);
225 				/* skip until NUL or '/' */
226 				while (*r && *r != '/')
227 					r++;
228 				if (++i == _LC_LAST)
229 					return(NULL);	/* too many slashes. */
230 			}
231 			if (i + 1 != _LC_LAST)
232 				return(NULL);	/* too few slashes. */
233 		}
234 	}
235 
236 	if (category)
237 		return(loadlocale(category));
238 
239 	loadlocale_success = 0;
240 	for (i = 1; i < _LC_LAST; ++i) {
241 		if (loadlocale(i) != NULL)
242 			loadlocale_success = 1;
243 	}
244 
245 	/*
246 	 * If all categories failed, return NULL; we don't need to back
247 	 * changes off, since none happened.
248 	 */
249 	if (!loadlocale_success)
250 		return(NULL);
251 
252 	return(currentlocale());
253 }
254 
255 static char *
256 currentlocale(void)
257 {
258 	int i;
259 
260 	strlcpy(current_locale_string, current_categories[1],
261 		sizeof(current_locale_string));
262 
263 	for (i = 2; i < _LC_LAST; ++i)
264 		if (strcmp(current_categories[1], current_categories[i])) {
265 			snprintf(current_locale_string,
266 			    sizeof(current_locale_string), "%s/%s/%s/%s/%s/%s",
267 			    current_categories[1], current_categories[2],
268 			    current_categories[3], current_categories[4],
269 			    current_categories[5], current_categories[6]);
270 			break;
271 		}
272 	return(current_locale_string);
273 }
274 
275 static void
276 revert_to_default(int category)
277 {
278 	_DIAGASSERT(category >= 0 && category < _LC_LAST);
279 
280 	if (categories[category].revert_function != NULL)
281 		categories[category].revert_function();
282 }
283 
284 static int
285 force_locale_enable(int category)
286 {
287 	revert_to_default(category);
288 
289 	return(0);
290 }
291 
292 static int
293 load_locale_sub(int category, const char *locname, int isspecial)
294 {
295 	char name[PATH_MAX];
296 
297 	/* check for the default locales */
298 	if (!strcmp(new_categories[category], "C") ||
299 	    !strcmp(new_categories[category], "POSIX")) {
300 		revert_to_default(category);
301 		return(0);
302 	}
303 
304 	/* check whether special symbol */
305 	if (isspecial && _bcs_strcasecmp(locname, _LOCALE_SYM_FORCE) == 0)
306 		return(force_locale_enable(category));
307 
308 	/* sanity check */
309 	if (strchr(locname, '/') != NULL)
310 		return(-1);
311 
312 	snprintf(name, sizeof(name), "%s/%s/%s", _PathLocale, locname,
313 		 categories[category].name);
314 
315 	if (category > 0 && category < (int)__arysize(categories) &&
316 	    categories[category].load_function != NULL)
317 		return(categories[category].load_function(locname));
318 
319 	return(0);
320 }
321 
322 static char *
323 loadlocale(int category)
324 {
325 	char aliaspath[PATH_MAX], loccat[PATH_MAX], buf[PATH_MAX];
326 	const char *alias;
327 
328 	_DIAGASSERT(0 < category && category < __arysize(categories));
329 
330 	if (strcmp(new_categories[category], current_categories[category]) == 0)
331 		return(current_categories[category]);
332 
333 	/* (1) non-aliased file */
334 	if (!load_locale_sub(category, new_categories[category], 0))
335 		goto success;
336 
337 	/* (2) lookup locname/catname type alias */
338 	snprintf(aliaspath, sizeof(aliaspath), "%s/" _LOCALE_ALIAS_NAME,
339 		 _PathLocale);
340 	snprintf(loccat, sizeof(loccat), "%s/%s", new_categories[category],
341 		 categories[category].name);
342 	alias = _lookup_alias(aliaspath, loccat, buf, sizeof(buf),
343 			      _LOOKUP_CASE_SENSITIVE);
344 	if (!load_locale_sub(category, alias, 1))
345 		goto success;
346 
347 	/* (3) lookup locname type alias */
348 	alias = _lookup_alias(aliaspath, new_categories[category],
349 			      buf, sizeof(buf), _LOOKUP_CASE_SENSITIVE);
350 	if (!load_locale_sub(category, alias, 1))
351 		goto success;
352 
353 	return(NULL);
354 
355 success:
356 	strlcpy(current_categories[category], new_categories[category],
357 		sizeof(current_categories[category]));
358 	return(current_categories[category]);
359 }
360 
361 static const char *
362 __get_locale_env(int category)
363 {
364 	const char *env;
365 
366 	_DIAGASSERT(category != LC_ALL);
367 
368 	/* 1. check LC_ALL. */
369 	env = getenv(categories[0].name);
370 
371 	/* 2. check LC_* */
372 	if (env == NULL || *env == '\0')
373 		env = getenv(categories[category].name);
374 
375 	/* 3. check LANG */
376 	if (env == NULL || *env == '\0')
377 		env = getenv("LANG");
378 
379 	/* 4. if none is set, fall to "C" */
380 	if (env == NULL || *env == '\0' || strchr(env, '/'))
381 		env = "C";
382 
383 	return(env);
384 }
385