xref: /haiku/src/system/libroot/posix/locale/locale_t.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
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     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 		}
143 	} else {
144 		if (category_mask == LC_ALL_MASK) {
145 			locales[LC_ALL] = locale;
146 		}
147 		for (int lc = 1; lc <= LC_LAST; ++lc) {
148 			if (category_mask & (1 << (lc - 1))) {
149 				locales[lc] = locale;
150 			}
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