xref: /openbsd/lib/libc/locale/setlocale.c (revision 4cfece93)
1 /*	$OpenBSD: setlocale.c,v 1.30 2019/07/03 03:24:04 deraadt Exp $	*/
2 /*
3  * Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <locale.h>
19 #include <stdlib.h>
20 #include <string.h>
21 
22 #include "rune.h"
23 
24 static void
25 freegl(char **oldgl)
26 {
27 	int ic;
28 
29 	if (oldgl == NULL)
30 		return;
31 	for (ic = LC_ALL; ic < _LC_LAST; ic++)
32 		free(oldgl[ic]);
33 	free(oldgl);
34 }
35 
36 static char **
37 dupgl(char **oldgl)
38 {
39 	char **newgl;
40 	int ic;
41 
42 	if ((newgl = calloc(_LC_LAST, sizeof(*newgl))) == NULL)
43 		return NULL;
44 	for (ic = LC_ALL; ic < _LC_LAST; ic++) {
45 		if ((newgl[ic] = strdup(ic == LC_ALL ? "" :
46 		    oldgl == NULL ? "C" : oldgl[ic])) == NULL) {
47 			freegl(newgl);
48 			return NULL;
49 		}
50 	}
51 	return newgl;
52 }
53 
54 static int
55 changegl(int category, const char *locname, char **gl)
56 {
57 	char *cp;
58 
59 	if ((locname = _get_locname(category, locname)) == NULL ||
60 	    (cp = strdup(locname)) == NULL)
61 		return -1;
62 
63 	free(gl[category]);
64 	gl[category] = cp;
65 	return 0;
66 }
67 
68 char *
69 setlocale(int category, const char *locname)
70 {
71 	/*
72 	 * Even though only LC_CTYPE has any effect in the OpenBSD
73 	 * base system, store complete information about the global
74 	 * locale, such that third-party software can access it,
75 	 * both via setlocale(3) and via locale(1).
76 	 */
77 	static char	  global_locname[256];
78 	static char	**global_locale;
79 
80 	char **newgl, *firstname, *nextname;
81 	int ic;
82 
83 	if (category < LC_ALL || category >= _LC_LAST)
84 		return NULL;
85 
86 	/*
87 	 * Change the global locale.
88 	 */
89 	if (locname != NULL) {
90 		if ((newgl = dupgl(global_locale)) == NULL)
91 			return NULL;
92 		if (category == LC_ALL && strchr(locname, '/') != NULL) {
93 
94 			/* One value for each category. */
95 			if ((firstname = strdup(locname)) == NULL) {
96 				freegl(newgl);
97 				return NULL;
98 			}
99 			nextname = firstname;
100 			for (ic = 1; ic < _LC_LAST; ic++)
101 				if (nextname == NULL || changegl(ic,
102 				    strsep(&nextname, "/"), newgl) == -1)
103 					break;
104 			free(firstname);
105 			if (ic < _LC_LAST || nextname != NULL) {
106 				freegl(newgl);
107 				return NULL;
108 			}
109 		} else {
110 
111 			/* One value only. */
112 			if (changegl(category, locname, newgl) == -1) {
113 				freegl(newgl);
114 				return NULL;
115 			}
116 
117 			/* One common value for all categories. */
118 			if (category == LC_ALL) {
119 				for (ic = 1; ic < _LC_LAST; ic++) {
120 					if (changegl(ic, locname,
121 					    newgl) == -1) {
122 						freegl(newgl);
123 						return NULL;
124 					}
125 				}
126 			}
127 		}
128 	} else
129 		newgl = global_locale;
130 
131 	/*
132 	 * Assemble a string representation of the globale locale.
133 	 */
134 
135 	/* setlocale(3) was never called with a non-NULL argument. */
136 	if (newgl == NULL) {
137 		(void)strlcpy(global_locname, "C", sizeof(global_locname));
138 		goto done;
139 	}
140 
141 	/* Individual category, or LC_ALL uniformly set. */
142 	if (category > LC_ALL || newgl[LC_ALL][0] != '\0') {
143 		if (strlcpy(global_locname, newgl[category],
144 		    sizeof(global_locname)) >= sizeof(global_locname))
145 			global_locname[0] = '\0';
146 		goto done;
147 	}
148 
149 	/*
150 	 * Check whether all categories agree and return either
151 	 * the single common name for all categories or a string
152 	 * listing the names for all categories.
153 	 */
154 	for (ic = 2; ic < _LC_LAST; ic++)
155 		if (strcmp(newgl[ic], newgl[1]) != 0)
156 			break;
157 	if (ic == _LC_LAST) {
158 		if (strlcpy(global_locname, newgl[1],
159 		    sizeof(global_locname)) >= sizeof(global_locname))
160 			global_locname[0] = '\0';
161 	} else {
162 		ic = snprintf(global_locname, sizeof(global_locname),
163 		    "%s/%s/%s/%s/%s/%s", newgl[1], newgl[2], newgl[3],
164 		    newgl[4], newgl[5], newgl[6]);
165 		if (ic < 0 || ic >= sizeof(global_locname))
166 			global_locname[0] = '\0';
167 	}
168 
169 done:
170 	if (locname != NULL) {
171 		/*
172 		 * We can't replace the global locale earlier
173 		 * because we first have to make sure that we
174 		 * also have the memory required to report success.
175 		 */
176 		if (global_locname[0] != '\0') {
177 			freegl(global_locale);
178 			global_locale = newgl;
179 			if (category == LC_ALL || category == LC_CTYPE)
180 				_GlobalRuneLocale =
181 				    strchr(newgl[LC_CTYPE], '.') == NULL ?
182 				    &_DefaultRuneLocale : _Utf8RuneLocale;
183 		} else {
184 			freegl(newgl);
185 			return NULL;
186 		}
187 	}
188 	return global_locname;
189 }
190 DEF_STRONG(setlocale);
191