xref: /haiku/src/system/libroot/posix/locale/locale_t.cpp (revision 2705bc6bdf64dbf0eb0985ccffe0468ab10120b0)
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 
38 	LocaleBackend* backend = (l == LC_GLOBAL_LOCALE) ?
39 		gGlobalLocaleBackend : (LocaleBackend*)locObj->backend;
40 
41 	if (backend == NULL) {
42 		newObj->backend = NULL;
43 		return (locale_t)newObj;
44 	}
45 
46 	// Check if everything is set to "C" or "POSIX",
47 	// and avoid making a backend.
48 	const char* localeDescription = backend->SetLocale(LC_ALL, NULL);
49 
50 	if ((strcasecmp(localeDescription, "POSIX") == 0)
51 			|| (strcasecmp(localeDescription, "C") == 0)) {
52 		newObj->backend = NULL;
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 	newObj->magic = LOCALE_T_MAGIC;
85 
86 	return (locale_t)newObj;
87 }
88 
89 
90 extern "C" void
91 freelocale(locale_t l)
92 {
93 	LocaleBackendData* locobj = (LocaleBackendData*)l;
94 
95 	if (locobj->backend) {
96 		LocaleBackend::DestroyBackend(locobj->backend);
97 		LocaleDataBridge* databridge = locobj->databridge;
98 		delete databridge;
99 	}
100 	delete locobj;
101 }
102 
103 
104 extern "C" locale_t
105 newlocale(int category_mask, const char* locale, locale_t base)
106 {
107 	if (((category_mask | LC_ALL_MASK) != LC_ALL_MASK) || (locale == NULL)) {
108 		errno = EINVAL;
109 		return (locale_t)0;
110 	}
111 
112 	bool newObject = false;
113 	LocaleBackendData* localeObject = (LocaleBackendData*)base;
114 
115 	if (localeObject == NULL) {
116 		localeObject = new (std::nothrow) LocaleBackendData;
117 		if (localeObject == NULL) {
118 			errno = ENOMEM;
119 			return (locale_t)0;
120 		}
121 		localeObject->magic = LOCALE_T_MAGIC;
122 		localeObject->backend = NULL;
123 		localeObject->databridge = NULL;
124 
125 		newObject = true;
126 	}
127 
128 	LocaleBackend*& backend = localeObject->backend;
129 	LocaleDataBridge*& databridge = localeObject->databridge;
130 
131 	const char* locales[LC_LAST + 1];
132 	for (int lc = 0; lc <= LC_LAST; lc++)
133 		locales[lc] = NULL;
134 
135 	if (*locale == '\0') {
136 		if (category_mask == LC_ALL_MASK) {
137 			GetLocalesFromEnvironment(LC_ALL, locales);
138 		} else {
139 			for (int lc = 1; lc <= LC_LAST; ++lc) {
140 				if (category_mask & (1 << (lc - 1)))
141 					GetLocalesFromEnvironment(lc, locales);
142 			}
143 		}
144 	} else {
145 		if (category_mask == LC_ALL_MASK) {
146 			locales[LC_ALL] = locale;
147 		}
148 		for (int lc = 1; lc <= LC_LAST; ++lc) {
149 			if (category_mask & (1 << (lc - 1)))
150 				locales[lc] = locale;
151 		}
152 	}
153 
154 	if (backend == NULL) {
155 		// for any locale other than POSIX/C, we try to activate the ICU
156 		// backend
157 		bool needBackend = false;
158 		for (int lc = 0; lc <= LC_LAST; lc++) {
159 			if (locales[lc] != NULL && strcasecmp(locales[lc], "POSIX") != 0
160 					&& strcasecmp(locales[lc], "C") != 0) {
161 				needBackend = true;
162 				break;
163 			}
164 		}
165 		if (needBackend) {
166 			status_t status = LocaleBackend::CreateBackend(backend);
167 			if (backend == NULL) {
168 				errno = status;
169 				if (newObject) {
170 					delete localeObject;
171 				}
172 				return (locale_t)0;
173 			}
174 			databridge = new (std::nothrow) LocaleDataBridge(false);
175 			if (databridge == NULL) {
176 				errno = ENOMEM;
177 				LocaleBackend::DestroyBackend(backend);
178 				if (newObject) {
179 					delete localeObject;
180 				}
181 				return (locale_t)0;
182 			}
183 			backend->Initialize(databridge);
184 		}
185 	}
186 
187 	BPrivate::ErrnoMaintainer errnoMaintainer;
188 
189 	if (backend != NULL) {
190 		for (int lc = 0; lc <= LC_LAST; lc++) {
191 			if (locales[lc] != NULL) {
192 				locale = backend->SetLocale(lc, locales[lc]);
193 				if (lc == LC_ALL) {
194 					// skip the rest, LC_ALL overrides
195 					break;
196 				}
197 			}
198 		}
199 	}
200 
201 	return (locale_t)localeObject;
202 }
203 
204 
205 extern "C" locale_t
206 uselocale(locale_t newLoc)
207 {
208 	locale_t oldLoc = (locale_t)GetCurrentLocaleInfo();
209 	if (oldLoc == NULL) {
210 		oldLoc = LC_GLOBAL_LOCALE;
211 	}
212 
213 	if (newLoc != (locale_t)0) {
214 		// Avoid expensive TLS reads with a local variable.
215 		locale_t appliedLoc = oldLoc;
216 
217 		if (newLoc == LC_GLOBAL_LOCALE) {
218 			appliedLoc = NULL;
219 		} else {
220 			if (((LocaleBackendData*)newLoc)->magic != LOCALE_T_MAGIC) {
221 				errno = EINVAL;
222 				return (locale_t)0;
223 			}
224 			appliedLoc = newLoc;
225 		}
226 
227 		SetCurrentLocaleInfo((LocaleBackendData*)appliedLoc);
228 
229 		if (appliedLoc != NULL) {
230 			LocaleDataBridge*& databridge = ((LocaleBackendData*)appliedLoc)->databridge;
231 			// Happens when appliedLoc represents the C locale.
232 			if (databridge == NULL) {
233 				LocaleBackend*& backend = ((LocaleBackendData*)appliedLoc)->backend;
234 				status_t status = LocaleBackend::CreateBackend(backend);
235 				if (backend == NULL) {
236 					if (status == B_MISSING_LIBRARY) {
237 						// This means libroot-addon-icu is not available.
238 						// Therefore, the global locale is still the C locale
239 						// and cannot be set to any other locale. Do nothing.
240 						return oldLoc;
241 					}
242 					errno = status;
243 					return (locale_t)0;
244 				}
245 
246 				databridge = new (std::nothrow) LocaleDataBridge(false);
247 				if (databridge == NULL) {
248 					LocaleBackend::DestroyBackend(backend);
249 					errno = ENOMEM;
250 					return (locale_t)0;
251 				}
252 
253 				backend->Initialize(databridge);
254 			}
255 			databridge->ApplyToCurrentThread();
256 		} else {
257 			gGlobalLocaleDataBridge.ApplyToCurrentThread();
258 		}
259 	}
260 
261 	return oldLoc;
262 }
263 
264 
265 extern "C" locale_t
266 __current_locale_t()
267 {
268 	locale_t locale = (locale_t)GetCurrentLocaleInfo();
269 	if (locale == NULL) {
270 		static LocaleBackendData global_locale_t;
271 		global_locale_t.backend = gGlobalLocaleBackend;
272 		global_locale_t.databridge = &gGlobalLocaleDataBridge;
273 		return (locale_t)&global_locale_t;
274 	}
275 
276 	return locale;
277 }
278