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
18using BPrivate::Libroot::gGlobalLocaleBackend;
19using BPrivate::Libroot::gGlobalLocaleDataBridge;
20using BPrivate::Libroot::GetCurrentLocaleInfo;
21using BPrivate::Libroot::GetLocalesFromEnvironment;
22using BPrivate::Libroot::LocaleBackend;
23using BPrivate::Libroot::LocaleBackendData;
24using BPrivate::Libroot::LocaleDataBridge;
25
26
27extern "C" locale_t
28duplocale(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
88extern "C" void
89freelocale(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
102extern "C" locale_t
103newlocale(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
203extern "C" locale_t
204uselocale(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
263extern "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
278extern "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