xref: /openbsd-src/lib/libc/locale/setlocale.c (revision 66e5b30f07118dc27f73db4347326bf0321b41fe)
1 /*	$OpenBSD: setlocale.c,v 1.31 2024/08/18 02:20:29 guenther 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 <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "rune.h"
24 
25 static void
26 freegl(char **oldgl)
27 {
28 	int ic;
29 
30 	if (oldgl == NULL)
31 		return;
32 	for (ic = LC_ALL; ic < _LC_LAST; ic++)
33 		free(oldgl[ic]);
34 	free(oldgl);
35 }
36 
37 static char **
38 dupgl(char **oldgl)
39 {
40 	char **newgl;
41 	int ic;
42 
43 	if ((newgl = calloc(_LC_LAST, sizeof(*newgl))) == NULL)
44 		return NULL;
45 	for (ic = LC_ALL; ic < _LC_LAST; ic++) {
46 		if ((newgl[ic] = strdup(ic == LC_ALL ? "" :
47 		    oldgl == NULL ? "C" : oldgl[ic])) == NULL) {
48 			freegl(newgl);
49 			return NULL;
50 		}
51 	}
52 	return newgl;
53 }
54 
55 static int
56 changegl(int category, const char *locname, char **gl)
57 {
58 	char *cp;
59 
60 	if ((locname = _get_locname(category, locname)) == NULL ||
61 	    (cp = strdup(locname)) == NULL)
62 		return -1;
63 
64 	free(gl[category]);
65 	gl[category] = cp;
66 	return 0;
67 }
68 
69 char *
70 setlocale(int category, const char *locname)
71 {
72 	/*
73 	 * Even though only LC_CTYPE has any effect in the OpenBSD
74 	 * base system, store complete information about the global
75 	 * locale, such that third-party software can access it,
76 	 * both via setlocale(3) and via locale(1).
77 	 */
78 	static char	  global_locname[256];
79 	static char	**global_locale;
80 
81 	char **newgl, *firstname, *nextname;
82 	int ic;
83 
84 	if (category < LC_ALL || category >= _LC_LAST)
85 		return NULL;
86 
87 	/*
88 	 * Change the global locale.
89 	 */
90 	if (locname != NULL) {
91 		if ((newgl = dupgl(global_locale)) == NULL)
92 			return NULL;
93 		if (category == LC_ALL && strchr(locname, '/') != NULL) {
94 
95 			/* One value for each category. */
96 			if ((firstname = strdup(locname)) == NULL) {
97 				freegl(newgl);
98 				return NULL;
99 			}
100 			nextname = firstname;
101 			for (ic = 1; ic < _LC_LAST; ic++)
102 				if (nextname == NULL || changegl(ic,
103 				    strsep(&nextname, "/"), newgl) == -1)
104 					break;
105 			free(firstname);
106 			if (ic < _LC_LAST || nextname != NULL) {
107 				freegl(newgl);
108 				return NULL;
109 			}
110 		} else {
111 
112 			/* One value only. */
113 			if (changegl(category, locname, newgl) == -1) {
114 				freegl(newgl);
115 				return NULL;
116 			}
117 
118 			/* One common value for all categories. */
119 			if (category == LC_ALL) {
120 				for (ic = 1; ic < _LC_LAST; ic++) {
121 					if (changegl(ic, locname,
122 					    newgl) == -1) {
123 						freegl(newgl);
124 						return NULL;
125 					}
126 				}
127 			}
128 		}
129 	} else
130 		newgl = global_locale;
131 
132 	/*
133 	 * Assemble a string representation of the globale locale.
134 	 */
135 
136 	/* setlocale(3) was never called with a non-NULL argument. */
137 	if (newgl == NULL) {
138 		(void)strlcpy(global_locname, "C", sizeof(global_locname));
139 		goto done;
140 	}
141 
142 	/* Individual category, or LC_ALL uniformly set. */
143 	if (category > LC_ALL || newgl[LC_ALL][0] != '\0') {
144 		if (strlcpy(global_locname, newgl[category],
145 		    sizeof(global_locname)) >= sizeof(global_locname))
146 			global_locname[0] = '\0';
147 		goto done;
148 	}
149 
150 	/*
151 	 * Check whether all categories agree and return either
152 	 * the single common name for all categories or a string
153 	 * listing the names for all categories.
154 	 */
155 	for (ic = 2; ic < _LC_LAST; ic++)
156 		if (strcmp(newgl[ic], newgl[1]) != 0)
157 			break;
158 	if (ic == _LC_LAST) {
159 		if (strlcpy(global_locname, newgl[1],
160 		    sizeof(global_locname)) >= sizeof(global_locname))
161 			global_locname[0] = '\0';
162 	} else {
163 		ic = snprintf(global_locname, sizeof(global_locname),
164 		    "%s/%s/%s/%s/%s/%s", newgl[1], newgl[2], newgl[3],
165 		    newgl[4], newgl[5], newgl[6]);
166 		if (ic < 0 || ic >= sizeof(global_locname))
167 			global_locname[0] = '\0';
168 	}
169 
170 done:
171 	if (locname != NULL) {
172 		/*
173 		 * We can't replace the global locale earlier
174 		 * because we first have to make sure that we
175 		 * also have the memory required to report success.
176 		 */
177 		if (global_locname[0] != '\0') {
178 			freegl(global_locale);
179 			global_locale = newgl;
180 			if (category == LC_ALL || category == LC_CTYPE)
181 				_GlobalRuneLocale =
182 				    strchr(newgl[LC_CTYPE], '.') == NULL ?
183 				    &_DefaultRuneLocale : _Utf8RuneLocale;
184 		} else {
185 			freegl(newgl);
186 			return NULL;
187 		}
188 	}
189 	return global_locname;
190 }
191 DEF_STRONG(setlocale);
192