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