xref: /haiku/src/system/libroot/posix/locale/locale_t.cpp (revision 445d4fd926c569e7b9ae28017da86280aaecbae2)
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 		}
145 	} else {
146 		if (category_mask == LC_ALL_MASK) {
147 			locales[LC_ALL] = locale;
148 		}
149 		for (int lc = 1; lc <= LC_LAST; ++lc) {
150 			if (category_mask & (1 << (lc - 1))) {
151 				locales[lc] = locale;
152 			}
153 		}
154 	}
155 
156 	if (backend == NULL) {
157 		// for any locale other than POSIX/C, we try to activate the ICU
158 		// backend
159 		bool needBackend = false;
160 		for (int lc = 0; lc <= LC_LAST; lc++) {
161 			if (locales[lc] != NULL && strcasecmp(locales[lc], "POSIX") != 0
162 					&& strcasecmp(locales[lc], "C") != 0) {
163 				needBackend = true;
164 				break;
165 			}
166 		}
167 		if (needBackend) {
168 			status_t status = LocaleBackend::CreateBackend(backend);
169 			if (backend == NULL) {
170 				errno = status;
171 				if (newObject) {
172 					delete localeObject;
173 				}
174 				return (locale_t)0;
175 			}
176 			databridge = new (std::nothrow) LocaleDataBridge(false);
177 			if (databridge == NULL) {
178 				errno = ENOMEM;
179 				LocaleBackend::DestroyBackend(backend);
180 				if (newObject) {
181 					delete localeObject;
182 				}
183 				return (locale_t)0;
184 			}
185 			backend->Initialize(databridge);
186 		}
187 	}
188 
189 	BPrivate::ErrnoMaintainer errnoMaintainer;
190 
191 	if (backend != NULL) {
192 		for (int lc = 0; lc <= LC_LAST; lc++) {
193 			if (locales[lc] != NULL) {
194 				locale = backend->SetLocale(lc, locales[lc]);
195 				if (lc == LC_ALL) {
196 					// skip the rest, LC_ALL overrides
197 					break;
198 				}
199 			}
200 		}
201 	}
202 
203 	return (locale_t)localeObject;
204 }
205 
206 
207 extern "C" locale_t
208 uselocale(locale_t newLoc)
209 {
210     locale_t oldLoc = (locale_t)GetCurrentLocaleInfo();
211     if (oldLoc == NULL) {
212         oldLoc = LC_GLOBAL_LOCALE;
213     }
214 
215     if (newLoc != (locale_t)0) {
216         // Avoid expensive TLS reads with a local variable.
217         locale_t appliedLoc = oldLoc;
218 
219         if (newLoc == LC_GLOBAL_LOCALE) {
220             appliedLoc = NULL;
221         } else {
222             if (((LocaleBackendData*)newLoc)->magic != LOCALE_T_MAGIC) {
223                 errno = EINVAL;
224                 return (locale_t)0;
225             }
226             appliedLoc = newLoc;
227         }
228 
229         SetCurrentLocaleInfo((LocaleBackendData*)appliedLoc);
230 
231         if (appliedLoc != NULL) {
232 			LocaleDataBridge*& databridge = ((LocaleBackendData*)appliedLoc)->databridge;
233 			// Happens when appliedLoc represents the C locale.
234 			if (databridge == NULL) {
235 				LocaleBackend*& backend = ((LocaleBackendData*)appliedLoc)->backend;
236 				status_t status = LocaleBackend::CreateBackend(backend);
237 				if (backend == NULL) {
238 					if (status == B_MISSING_LIBRARY) {
239 						// This means libroot-addon-icu is not available.
240 						// Therefore, the global locale is still the C locale
241 						// and cannot be set to any other locale. Do nothing.
242 						return oldLoc;
243 					}
244 					errno = status;
245 					return (locale_t)0;
246 				}
247 
248 				databridge = new (std::nothrow) LocaleDataBridge(false);
249 				if (databridge == NULL) {
250 					LocaleBackend::DestroyBackend(backend);
251 					errno = ENOMEM;
252 					return (locale_t)0;
253 				}
254 
255 				backend->Initialize(databridge);
256 			}
257 			databridge->ApplyToCurrentThread();
258         } else {
259             gGlobalLocaleDataBridge.ApplyToCurrentThread();
260         }
261     }
262 
263     return oldLoc;
264 }
265 
266 
267 extern "C" locale_t
268 __current_locale_t()
269 {
270 	locale_t locale = (locale_t)GetCurrentLocaleInfo();
271 	if (locale == NULL) {
272 		static LocaleBackendData global_locale_t;
273 		global_locale_t.backend = gGlobalLocaleBackend;
274 		global_locale_t.databridge = &gGlobalLocaleDataBridge;
275 		return (locale_t)&global_locale_t;
276 	}
277 
278 	return locale;
279 }
280