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
24static void
25freegl(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
36static char **
37dupgl(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
54static int
55changegl(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
68char *
69setlocale(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
169done:
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}
190DEF_STRONG(setlocale);
191