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
59static const char *
60setlocale_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
90static int
91setlocale_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
186extern __declspec(dllimport) CRITICAL_SECTION *gl_get_setlocale_null_lock (void);
187
188static int
189setlocale_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
203extern
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
225static int
226setlocale_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
247extern mtx_t *gl_get_setlocale_null_lock (void);
248
249static int
250setlocale_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
268int
269setlocale_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
300const char *
301setlocale_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