1 /* Query the name of the current global locale.
2 Copyright (C) 2019-2023 Free Software Foundation, Inc.
3
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of the
7 License, or (at your option) any later version.
8
9 This file is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17 /* Written by Bruno Haible <bruno@clisp.org>, 2019. */
18
19 #include <config.h>
20
21 /* Specification. */
22 #include "setlocale_null.h"
23
24 #include <errno.h>
25 #include <locale.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #if defined _WIN32 && !defined __CYGWIN__
29 # include <wchar.h>
30 #endif
31
32 #if !(SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE)
33
34 # if AVOID_ANY_THREADS
35
36 /* The option '--disable-threads' explicitly requests no locking. */
37
38 # elif defined _WIN32 && !defined __CYGWIN__
39
40 # define WIN32_LEAN_AND_MEAN /* avoid including junk */
41 # include <windows.h>
42
43 # elif HAVE_PTHREAD_API
44
45 # include <pthread.h>
46 # if HAVE_THREADS_H && HAVE_WEAK_SYMBOLS
47 # include <threads.h>
48 # pragma weak thrd_exit
49 # define c11_threads_in_use() (thrd_exit != NULL)
50 # else
51 # define c11_threads_in_use() 0
52 # endif
53
54 # elif HAVE_THREADS_H
55
56 # include <threads.h>
57
58 # endif
59
60 #endif
61
62 /* Use the system's setlocale() function, not the gnulib override, here. */
63 #undef setlocale
64
65 static const char *
66 setlocale_null_androidfix (int category)
67 {
68 const char *result = setlocale (category, NULL);
69
70 #ifdef __ANDROID__
71 if (result == NULL)
72 switch (category)
73 {
74 case LC_CTYPE:
75 case LC_NUMERIC:
76 case LC_TIME:
77 case LC_COLLATE:
78 case LC_MONETARY:
79 case LC_MESSAGES:
80 case LC_ALL:
81 case LC_PAPER:
82 case LC_NAME:
83 case LC_ADDRESS:
84 case LC_TELEPHONE:
85 case LC_MEASUREMENT:
86 result = "C";
87 break;
88 default:
89 break;
90 }
91 #endif
92
93 return result;
94 }
95
96 static int
97 setlocale_null_unlocked (int category, char *buf, size_t bufsize)
98 {
99 #if defined _WIN32 && !defined __CYGWIN__ && defined _MSC_VER
100 /* On native Windows, nowadays, the setlocale() implementation is based
101 on _wsetlocale() and uses malloc() for the result. We are better off
102 using _wsetlocale() directly. */
103 const wchar_t *result = _wsetlocale (category, NULL);
104
105 if (result == NULL)
106 {
107 /* CATEGORY is invalid. */
108 if (bufsize > 0)
109 /* Return an empty string in BUF.
110 This is a convenience for callers that don't want to write explicit
111 code for handling EINVAL. */
112 buf[0] = '\0';
113 return EINVAL;
114 }
115 else
116 {
117 size_t length = wcslen (result);
118 if (length < bufsize)
119 {
120 size_t i;
121
122 /* Convert wchar_t[] -> char[], assuming plain ASCII. */
123 for (i = 0; i <= length; i++)
124 buf[i] = result[i];
125
126 return 0;
127 }
128 else
129 {
130 if (bufsize > 0)
131 {
132 /* Return a truncated result in BUF.
133 This is a convenience for callers that don't want to write
134 explicit code for handling ERANGE. */
135 size_t i;
136
137 /* Convert wchar_t[] -> char[], assuming plain ASCII. */
138 for (i = 0; i < bufsize; i++)
139 buf[i] = result[i];
140 buf[bufsize - 1] = '\0';
141 }
142 return ERANGE;
143 }
144 }
145 #else
146 const char *result = setlocale_null_androidfix (category);
147
148 if (result == NULL)
149 {
150 /* CATEGORY is invalid. */
151 if (bufsize > 0)
152 /* Return an empty string in BUF.
153 This is a convenience for callers that don't want to write explicit
154 code for handling EINVAL. */
155 buf[0] = '\0';
156 return EINVAL;
157 }
158 else
159 {
160 size_t length = strlen (result);
161 if (length < bufsize)
162 {
163 memcpy (buf, result, length + 1);
164 return 0;
165 }
166 else
167 {
168 if (bufsize > 0)
169 {
170 /* Return a truncated result in BUF.
171 This is a convenience for callers that don't want to write
172 explicit code for handling ERANGE. */
173 memcpy (buf, result, bufsize - 1);
174 buf[bufsize - 1] = '\0';
175 }
176 return ERANGE;
177 }
178 }
179 #endif
180 }
181
182 #if !(SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE) /* musl libc, macOS, FreeBSD, NetBSD, OpenBSD, AIX, Haiku, Cygwin < 3.4.6 */
183
184 /* Use a lock, so that no two threads can invoke setlocale_null_unlocked
185 at the same time. */
186
187 /* Prohibit renaming this symbol. */
188 # undef gl_get_setlocale_null_lock
189
190 # if AVOID_ANY_THREADS
191
192 /* The option '--disable-threads' explicitly requests no locking. */
193 # define setlocale_null_with_lock setlocale_null_unlocked
194
195 # elif defined _WIN32 && !defined __CYGWIN__
196
197 extern __declspec(dllimport) CRITICAL_SECTION *gl_get_setlocale_null_lock (void);
198
199 static int
200 setlocale_null_with_lock (int category, char *buf, size_t bufsize)
201 {
202 CRITICAL_SECTION *lock = gl_get_setlocale_null_lock ();
203 int ret;
204
205 EnterCriticalSection (lock);
206 ret = setlocale_null_unlocked (category, buf, bufsize);
207 LeaveCriticalSection (lock);
208
209 return ret;
210 }
211
212 # elif HAVE_PTHREAD_API /* musl libc, macOS, FreeBSD, NetBSD, OpenBSD, AIX, Haiku, Cygwin < 3.4.6 */
213
214 extern
215 # if defined _WIN32 || defined __CYGWIN__
216 __declspec(dllimport)
217 # endif
218 pthread_mutex_t *gl_get_setlocale_null_lock (void);
219
220 # if HAVE_WEAK_SYMBOLS /* musl libc, FreeBSD, NetBSD, OpenBSD, Haiku */
221
222 /* Avoid the need to link with '-lpthread'. */
223 # pragma weak pthread_mutex_lock
224 # pragma weak pthread_mutex_unlock
225
226 /* Determine whether libpthread is in use. */
227 # pragma weak pthread_mutexattr_gettype
228 /* See the comments in lock.h. */
229 # define pthread_in_use() \
230 (pthread_mutexattr_gettype != NULL || c11_threads_in_use ())
231
232 # else
233 # define pthread_in_use() 1
234 # endif
235
236 static int
237 setlocale_null_with_lock (int category, char *buf, size_t bufsize)
238 {
239 if (pthread_in_use())
240 {
241 pthread_mutex_t *lock = gl_get_setlocale_null_lock ();
242 int ret;
243
244 if (pthread_mutex_lock (lock))
245 abort ();
246 ret = setlocale_null_unlocked (category, buf, bufsize);
247 if (pthread_mutex_unlock (lock))
248 abort ();
249
250 return ret;
251 }
252 else
253 return setlocale_null_unlocked (category, buf, bufsize);
254 }
255
256 # elif HAVE_THREADS_H
257
258 extern mtx_t *gl_get_setlocale_null_lock (void);
259
260 static int
261 setlocale_null_with_lock (int category, char *buf, size_t bufsize)
262 {
263 mtx_t *lock = gl_get_setlocale_null_lock ();
264 int ret;
265
266 if (mtx_lock (lock) != thrd_success)
267 abort ();
268 ret = setlocale_null_unlocked (category, buf, bufsize);
269 if (mtx_unlock (lock) != thrd_success)
270 abort ();
271
272 return ret;
273 }
274
275 # endif
276
277 #endif
278
279 int
280 setlocale_null_r (int category, char *buf, size_t bufsize)
281 {
282 #if SETLOCALE_NULL_ALL_MTSAFE
283 # if SETLOCALE_NULL_ONE_MTSAFE
284
285 return setlocale_null_unlocked (category, buf, bufsize);
286
287 # else
288
289 if (category == LC_ALL)
290 return setlocale_null_unlocked (category, buf, bufsize);
291 else
292 return setlocale_null_with_lock (category, buf, bufsize);
293
294 # endif
295 #else
296 # if SETLOCALE_NULL_ONE_MTSAFE
297
298 if (category == LC_ALL)
299 return setlocale_null_with_lock (category, buf, bufsize);
300 else
301 return setlocale_null_unlocked (category, buf, bufsize);
302
303 # else
304
305 return setlocale_null_with_lock (category, buf, bufsize);
306
307 # endif
308 #endif
309 }
310
311 const char *
312 setlocale_null (int category)
313 {
314 #if SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE
315 return setlocale_null_androidfix (category);
316 #else
317
318 /* This call must be multithread-safe. To achieve this without using
319 thread-local storage:
320 1. We use a specific static buffer for each possible CATEGORY
321 argument. So that different threads can call setlocale_mtsafe
322 with different CATEGORY arguments, without interfering.
323 2. We use a simple strcpy or memcpy to fill this static buffer.
324 Filling it through, for example, strcpy + strcat would not be
325 guaranteed to leave the buffer's contents intact if another thread
326 is currently accessing it. If necessary, the contents is first
327 assembled in a stack-allocated buffer. */
328 if (category == LC_ALL)
329 {
330 # if SETLOCALE_NULL_ALL_MTSAFE
331 return setlocale_null_androidfix (LC_ALL);
332 # else
333 char buf[SETLOCALE_NULL_ALL_MAX];
334 static char resultbuf[SETLOCALE_NULL_ALL_MAX];
335
336 if (setlocale_null_r (LC_ALL, buf, sizeof (buf)))
337 return "C";
338 strcpy (resultbuf, buf);
339 return resultbuf;
340 # endif
341 }
342 else
343 {
344 # if SETLOCALE_NULL_ONE_MTSAFE
345 return setlocale_null_androidfix (category);
346 # else
347 enum
348 {
349 LC_CTYPE_INDEX,
350 LC_NUMERIC_INDEX,
351 LC_TIME_INDEX,
352 LC_COLLATE_INDEX,
353 LC_MONETARY_INDEX,
354 LC_MESSAGES_INDEX,
355 # ifdef LC_PAPER
356 LC_PAPER_INDEX,
357 # endif
358 # ifdef LC_NAME
359 LC_NAME_INDEX,
360 # endif
361 # ifdef LC_ADDRESS
362 LC_ADDRESS_INDEX,
363 # endif
364 # ifdef LC_TELEPHONE
365 LC_TELEPHONE_INDEX,
366 # endif
367 # ifdef LC_MEASUREMENT
368 LC_MEASUREMENT_INDEX,
369 # endif
370 # ifdef LC_IDENTIFICATION
371 LC_IDENTIFICATION_INDEX,
372 # endif
373 LC_INDICES_COUNT
374 }
375 i;
376 char buf[SETLOCALE_NULL_MAX];
377 static char resultbuf[LC_INDICES_COUNT][SETLOCALE_NULL_MAX];
378 int err;
379
380 err = setlocale_null_r (category, buf, sizeof (buf));
381 if (err == EINVAL)
382 return NULL;
383 if (err)
384 return "C";
385
386 switch (category)
387 {
388 case LC_CTYPE: i = LC_CTYPE_INDEX; break;
389 case LC_NUMERIC: i = LC_NUMERIC_INDEX; break;
390 case LC_TIME: i = LC_TIME_INDEX; break;
391 case LC_COLLATE: i = LC_COLLATE_INDEX; break;
392 case LC_MONETARY: i = LC_MONETARY_INDEX; break;
393 case LC_MESSAGES: i = LC_MESSAGES_INDEX; break;
394 # ifdef LC_PAPER
395 case LC_PAPER: i = LC_PAPER_INDEX; break;
396 # endif
397 # ifdef LC_NAME
398 case LC_NAME: i = LC_NAME_INDEX; break;
399 # endif
400 # ifdef LC_ADDRESS
401 case LC_ADDRESS: i = LC_ADDRESS_INDEX; break;
402 # endif
403 # ifdef LC_TELEPHONE
404 case LC_TELEPHONE: i = LC_TELEPHONE_INDEX; break;
405 # endif
406 # ifdef LC_MEASUREMENT
407 case LC_MEASUREMENT: i = LC_MEASUREMENT_INDEX; break;
408 # endif
409 # ifdef LC_IDENTIFICATION
410 case LC_IDENTIFICATION: i = LC_IDENTIFICATION_INDEX; break;
411 # endif
412 default:
413 /* If you get here, a #ifdef LC_xxx is missing. */
414 abort ();
415 }
416
417 strcpy (resultbuf[i], buf);
418 return resultbuf[i];
419 # endif
420 }
421 #endif
422 }