xref: /haiku/src/system/libroot/posix/locale/locale_t.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
1 /*
2  * Copyright 2022, Trung Nguyen, trungnt282910@gmail.com
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <new>
8 #include <errno.h>
9 #include <locale.h>
10 #include <strings.h>
11 
12 #include <ErrnoMaintainer.h>
13 
14 #include "LocaleBackend.h"
15 #include "LocaleInternal.h"
16 
17 
18 using BPrivate::Libroot::gGlobalLocaleBackend;
19 using BPrivate::Libroot::gGlobalLocaleDataBridge;
20 using BPrivate::Libroot::GetCurrentLocaleInfo;
21 using BPrivate::Libroot::GetLocalesFromEnvironment;
22 using BPrivate::Libroot::LocaleBackend;
23 using BPrivate::Libroot::LocaleBackendData;
24 using BPrivate::Libroot::LocaleDataBridge;
25 
26 
27 extern "C" locale_t
28 duplocale(locale_t l)
29 {
30 	LocaleBackendData* locObj = (LocaleBackendData*)l;
31 
32 	LocaleBackendData* newObj = new (std::nothrow) LocaleBackendData;
33 	if (newObj == NULL) {
34 		errno = ENOMEM;
35 		return (locale_t)0;
36 	}
37 	newObj->magic = LOCALE_T_MAGIC;
38 	newObj->backend = NULL;
39 	newObj->databridge = NULL;
40 
41 	LocaleBackend* backend = (l == LC_GLOBAL_LOCALE) ?
42 		gGlobalLocaleBackend : (LocaleBackend*)locObj->backend;
43 
44 	if (backend == NULL)
45 		return (locale_t)newObj;
46 
47 	// Check if everything is set to "C" or "POSIX",
48 	// and avoid making a backend.
49 	const char* localeDescription = backend->SetLocale(LC_ALL, NULL);
50 
51 	if ((strcasecmp(localeDescription, "POSIX") == 0)
52 			|| (strcasecmp(localeDescription, "C") == 0)) {
53 		return (locale_t)newObj;
54 	}
55 
56 	LocaleBackend*& newBackend = newObj->backend;
57 	LocaleDataBridge*& newDataBridge = newObj->databridge;
58 
59 	status_t status = LocaleBackend::CreateBackend(newBackend);
60 
61 	if (newBackend == NULL) {
62 		errno = status;
63 		delete newObj;
64 		return (locale_t)0;
65 	}
66 
67 	newDataBridge = new (std::nothrow) LocaleDataBridge(false);
68 
69 	if (newDataBridge == NULL) {
70 		errno = ENOMEM;
71 		LocaleBackend::DestroyBackend(newBackend);
72 		delete newObj;
73 		return (locale_t)0;
74 	}
75 
76 	newBackend->Initialize(newDataBridge);
77 
78 	// Skipping LC_ALL. Asking for LC_ALL would force the backend
79 	// to query each other value once, anyway.
80 	for (int lc = 1; lc <= LC_LAST; ++lc) {
81 		newBackend->SetLocale(lc, backend->SetLocale(lc, NULL));
82 	}
83 
84 	return (locale_t)newObj;
85 }
86 
87 
88 extern "C" void
89 freelocale(locale_t l)
90 {
91 	LocaleBackendData* locobj = (LocaleBackendData*)l;
92 
93 	if (locobj->backend) {
94 		LocaleBackend::DestroyBackend(locobj->backend);
95 		LocaleDataBridge* databridge = locobj->databridge;
96 		delete databridge;
97 	}
98 	delete locobj;
99 }
100 
101 
102 extern "C" locale_t
103 newlocale(int category_mask, const char* locale, locale_t base)
104 {
105 	if (((category_mask | LC_ALL_MASK) != LC_ALL_MASK) || (locale == NULL)) {
106 		errno = EINVAL;
107 		return (locale_t)0;
108 	}
109 
110 	bool newObject = false;
111 	LocaleBackendData* localeObject = (LocaleBackendData*)base;
112 
113 	if (localeObject == NULL) {
114 		localeObject = new (std::nothrow) LocaleBackendData;
115 		if (localeObject == NULL) {
116 			errno = ENOMEM;
117 			return (locale_t)0;
118 		}
119 		localeObject->magic = LOCALE_T_MAGIC;
120 		localeObject->backend = NULL;
121 		localeObject->databridge = NULL;
122 
123 		newObject = true;
124 	}
125 
126 	LocaleBackend*& backend = localeObject->backend;
127 	LocaleDataBridge*& databridge = localeObject->databridge;
128 
129 	const char* locales[LC_LAST + 1];
130 	for (int lc = 0; lc <= LC_LAST; lc++)
131 		locales[lc] = NULL;
132 
133 	if (*locale == '\0') {
134 		if (category_mask == LC_ALL_MASK) {
135 			GetLocalesFromEnvironment(LC_ALL, locales);
136 		} else {
137 			for (int lc = 1; lc <= LC_LAST; ++lc) {
138 				if (category_mask & (1 << (lc - 1)))
139 					GetLocalesFromEnvironment(lc, locales);
140 			}
141 		}
142 	} else {
143 		if (category_mask == LC_ALL_MASK) {
144 			locales[LC_ALL] = locale;
145 		}
146 		for (int lc = 1; lc <= LC_LAST; ++lc) {
147 			if (category_mask & (1 << (lc - 1)))
148 				locales[lc] = locale;
149 		}
150 	}
151 
152 	if (backend == NULL) {
153 		// for any locale other than POSIX/C, we try to activate the ICU
154 		// backend
155 		bool needBackend = false;
156 		for (int lc = 0; lc <= LC_LAST; lc++) {
157 			if (locales[lc] != NULL && strcasecmp(locales[lc], "POSIX") != 0
158 					&& strcasecmp(locales[lc], "C") != 0) {
159 				needBackend = true;
160 				break;
161 			}
162 		}
163 		if (needBackend) {
164 			status_t status = LocaleBackend::CreateBackend(backend);
165 			if (backend == NULL) {
166 				errno = status;
167 				if (newObject) {
168 					delete localeObject;
169 				}
170 				return (locale_t)0;
171 			}
172 			databridge = new (std::nothrow) LocaleDataBridge(false);
173 			if (databridge == NULL) {
174 				errno = ENOMEM;
175 				LocaleBackend::DestroyBackend(backend);
176 				if (newObject) {
177 					delete localeObject;
178 				}
179 				return (locale_t)0;
180 			}
181 			backend->Initialize(databridge);
182 		}
183 	}
184 
185 	BPrivate::ErrnoMaintainer errnoMaintainer;
186 
187 	if (backend != NULL) {
188 		for (int lc = 0; lc <= LC_LAST; lc++) {
189 			if (locales[lc] != NULL) {
190 				locale = backend->SetLocale(lc, locales[lc]);
191 				if (lc == LC_ALL) {
192 					// skip the rest, LC_ALL overrides
193 					break;
194 				}
195 			}
196 		}
197 	}
198 
199 	return (locale_t)localeObject;
200 }
201 
202 
203 extern "C" locale_t
204 uselocale(locale_t newLoc)
205 {
206 	locale_t oldLoc = (locale_t)GetCurrentLocaleInfo();
207 	if (oldLoc == NULL) {
208 		oldLoc = LC_GLOBAL_LOCALE;
209 	}
210 
211 	if (newLoc != (locale_t)0) {
212 		// Avoid expensive TLS reads with a local variable.
213 		locale_t appliedLoc = oldLoc;
214 
215 		if (newLoc == LC_GLOBAL_LOCALE) {
216 			appliedLoc = NULL;
217 		} else {
218 			if (((LocaleBackendData*)newLoc)->magic != LOCALE_T_MAGIC) {
219 				errno = EINVAL;
220 				return (locale_t)0;
221 			}
222 			appliedLoc = newLoc;
223 		}
224 
225 		SetCurrentLocaleInfo((LocaleBackendData*)appliedLoc);
226 
227 		if (appliedLoc != NULL) {
228 			LocaleDataBridge*& databridge = ((LocaleBackendData*)appliedLoc)->databridge;
229 			// Happens when appliedLoc represents the C locale.
230 			if (databridge == NULL) {
231 				LocaleBackend*& backend = ((LocaleBackendData*)appliedLoc)->backend;
232 				status_t status = LocaleBackend::CreateBackend(backend);
233 				if (backend == NULL) {
234 					if (status == B_MISSING_LIBRARY) {
235 						// This means libroot-addon-icu is not available.
236 						// Therefore, the global locale is still the C locale
237 						// and cannot be set to any other locale. Do nothing.
238 						return oldLoc;
239 					}
240 					errno = status;
241 					return (locale_t)0;
242 				}
243 
244 				databridge = new (std::nothrow) LocaleDataBridge(false);
245 				if (databridge == NULL) {
246 					LocaleBackend::DestroyBackend(backend);
247 					errno = ENOMEM;
248 					return (locale_t)0;
249 				}
250 
251 				backend->Initialize(databridge);
252 			}
253 			databridge->ApplyToCurrentThread();
254 		} else {
255 			gGlobalLocaleDataBridge.ApplyToCurrentThread();
256 		}
257 	}
258 
259 	return oldLoc;
260 }
261 
262 
263 extern "C" locale_t
264 __current_locale_t()
265 {
266 	locale_t locale = (locale_t)GetCurrentLocaleInfo();
267 	if (locale == NULL) {
268 		static LocaleBackendData global_locale_t;
269 		global_locale_t.backend = gGlobalLocaleBackend;
270 		global_locale_t.databridge = &gGlobalLocaleDataBridge;
271 		return (locale_t)&global_locale_t;
272 	}
273 
274 	return locale;
275 }
276 
277 
278 extern "C" locale_t
279 __posix_locale_t()
280 {
281 	static LocaleBackendData posix_locale_t;
282 	posix_locale_t.backend = NULL;
283 	posix_locale_t.databridge = NULL;
284 	return &posix_locale_t;
285 }
286