| /* Query the name of the current global locale. | 
 |    Copyright (C) 2019-2022 Free Software Foundation, Inc. | 
 |  | 
 |    This file is free software: you can redistribute it and/or modify | 
 |    it under the terms of the GNU Lesser General Public License as | 
 |    published by the Free Software Foundation; either version 2.1 of the | 
 |    License, or (at your option) any later version. | 
 |  | 
 |    This file is distributed in the hope that it will be useful, | 
 |    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |    GNU Lesser General Public License for more details. | 
 |  | 
 |    You should have received a copy of the GNU Lesser General Public License | 
 |    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */ | 
 |  | 
 | /* Written by Bruno Haible <bruno@clisp.org>, 2019.  */ | 
 |  | 
 | #include <config.h> | 
 |  | 
 | /* Specification.  */ | 
 | #include "setlocale_null.h" | 
 |  | 
 | #include <errno.h> | 
 | #include <locale.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #if defined _WIN32 && !defined __CYGWIN__ | 
 | # include <wchar.h> | 
 | #endif | 
 |  | 
 | #if !(SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE) | 
 | # if defined _WIN32 && !defined __CYGWIN__ | 
 |  | 
 | #  define WIN32_LEAN_AND_MEAN  /* avoid including junk */ | 
 | #  include <windows.h> | 
 |  | 
 | # elif HAVE_PTHREAD_API | 
 |  | 
 | #  include <pthread.h> | 
 | #  if HAVE_THREADS_H && HAVE_WEAK_SYMBOLS | 
 | #   include <threads.h> | 
 | #   pragma weak thrd_exit | 
 | #   define c11_threads_in_use() (thrd_exit != NULL) | 
 | #  else | 
 | #   define c11_threads_in_use() 0 | 
 | #  endif | 
 |  | 
 | # elif HAVE_THREADS_H | 
 |  | 
 | #  include <threads.h> | 
 |  | 
 | # endif | 
 | #endif | 
 |  | 
 | /* Use the system's setlocale() function, not the gnulib override, here.  */ | 
 | #undef setlocale | 
 |  | 
 | static const char * | 
 | setlocale_null_androidfix (int category) | 
 | { | 
 |   const char *result = setlocale (category, NULL); | 
 |  | 
 | #ifdef __ANDROID__ | 
 |   if (result == NULL) | 
 |     switch (category) | 
 |       { | 
 |       case LC_CTYPE: | 
 |       case LC_NUMERIC: | 
 |       case LC_TIME: | 
 |       case LC_COLLATE: | 
 |       case LC_MONETARY: | 
 |       case LC_MESSAGES: | 
 |       case LC_ALL: | 
 |       case LC_PAPER: | 
 |       case LC_NAME: | 
 |       case LC_ADDRESS: | 
 |       case LC_TELEPHONE: | 
 |       case LC_MEASUREMENT: | 
 |         result = "C"; | 
 |         break; | 
 |       default: | 
 |         break; | 
 |       } | 
 | #endif | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | static int | 
 | setlocale_null_unlocked (int category, char *buf, size_t bufsize) | 
 | { | 
 | #if defined _WIN32 && !defined __CYGWIN__ && defined _MSC_VER | 
 |   /* On native Windows, nowadays, the setlocale() implementation is based | 
 |      on _wsetlocale() and uses malloc() for the result.  We are better off | 
 |      using _wsetlocale() directly.  */ | 
 |   const wchar_t *result = _wsetlocale (category, NULL); | 
 |  | 
 |   if (result == NULL) | 
 |     { | 
 |       /* CATEGORY is invalid.  */ | 
 |       if (bufsize > 0) | 
 |         /* Return an empty string in BUF. | 
 |            This is a convenience for callers that don't want to write explicit | 
 |            code for handling EINVAL.  */ | 
 |         buf[0] = '\0'; | 
 |       return EINVAL; | 
 |     } | 
 |   else | 
 |     { | 
 |       size_t length = wcslen (result); | 
 |       if (length < bufsize) | 
 |         { | 
 |           size_t i; | 
 |  | 
 |           /* Convert wchar_t[] -> char[], assuming plain ASCII.  */ | 
 |           for (i = 0; i <= length; i++) | 
 |             buf[i] = result[i]; | 
 |  | 
 |           return 0; | 
 |         } | 
 |       else | 
 |         { | 
 |           if (bufsize > 0) | 
 |             { | 
 |               /* Return a truncated result in BUF. | 
 |                  This is a convenience for callers that don't want to write | 
 |                  explicit code for handling ERANGE.  */ | 
 |               size_t i; | 
 |  | 
 |               /* Convert wchar_t[] -> char[], assuming plain ASCII.  */ | 
 |               for (i = 0; i < bufsize; i++) | 
 |                 buf[i] = result[i]; | 
 |               buf[bufsize - 1] = '\0'; | 
 |             } | 
 |           return ERANGE; | 
 |         } | 
 |     } | 
 | #else | 
 |   const char *result = setlocale_null_androidfix (category); | 
 |  | 
 |   if (result == NULL) | 
 |     { | 
 |       /* CATEGORY is invalid.  */ | 
 |       if (bufsize > 0) | 
 |         /* Return an empty string in BUF. | 
 |            This is a convenience for callers that don't want to write explicit | 
 |            code for handling EINVAL.  */ | 
 |         buf[0] = '\0'; | 
 |       return EINVAL; | 
 |     } | 
 |   else | 
 |     { | 
 |       size_t length = strlen (result); | 
 |       if (length < bufsize) | 
 |         { | 
 |           memcpy (buf, result, length + 1); | 
 |           return 0; | 
 |         } | 
 |       else | 
 |         { | 
 |           if (bufsize > 0) | 
 |             { | 
 |               /* Return a truncated result in BUF. | 
 |                  This is a convenience for callers that don't want to write | 
 |                  explicit code for handling ERANGE.  */ | 
 |               memcpy (buf, result, bufsize - 1); | 
 |               buf[bufsize - 1] = '\0'; | 
 |             } | 
 |           return ERANGE; | 
 |         } | 
 |     } | 
 | #endif | 
 | } | 
 |  | 
 | #if !(SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE) /* musl libc, macOS, FreeBSD, NetBSD, OpenBSD, AIX, Haiku, Cygwin */ | 
 |  | 
 | /* Use a lock, so that no two threads can invoke setlocale_null_unlocked | 
 |    at the same time.  */ | 
 |  | 
 | /* Prohibit renaming this symbol.  */ | 
 | # undef gl_get_setlocale_null_lock | 
 |  | 
 | # if defined _WIN32 && !defined __CYGWIN__ | 
 |  | 
 | extern __declspec(dllimport) CRITICAL_SECTION *gl_get_setlocale_null_lock (void); | 
 |  | 
 | static int | 
 | setlocale_null_with_lock (int category, char *buf, size_t bufsize) | 
 | { | 
 |   CRITICAL_SECTION *lock = gl_get_setlocale_null_lock (); | 
 |   int ret; | 
 |  | 
 |   EnterCriticalSection (lock); | 
 |   ret = setlocale_null_unlocked (category, buf, bufsize); | 
 |   LeaveCriticalSection (lock); | 
 |  | 
 |   return ret; | 
 | } | 
 |  | 
 | # elif HAVE_PTHREAD_API /* musl libc, macOS, FreeBSD, NetBSD, OpenBSD, AIX, Haiku, Cygwin */ | 
 |  | 
 | extern | 
 | #  if defined _WIN32 || defined __CYGWIN__ | 
 |   __declspec(dllimport) | 
 | #  endif | 
 |   pthread_mutex_t *gl_get_setlocale_null_lock (void); | 
 |  | 
 | #  if HAVE_WEAK_SYMBOLS /* musl libc, FreeBSD, NetBSD, OpenBSD, Haiku */ | 
 |  | 
 |     /* Avoid the need to link with '-lpthread'.  */ | 
 | #   pragma weak pthread_mutex_lock | 
 | #   pragma weak pthread_mutex_unlock | 
 |  | 
 |     /* Determine whether libpthread is in use.  */ | 
 | #   pragma weak pthread_mutexattr_gettype | 
 |     /* See the comments in lock.h.  */ | 
 | #   define pthread_in_use() \ | 
 |       (pthread_mutexattr_gettype != NULL || c11_threads_in_use ()) | 
 |  | 
 | #  else | 
 | #   define pthread_in_use() 1 | 
 | #  endif | 
 |  | 
 | static int | 
 | setlocale_null_with_lock (int category, char *buf, size_t bufsize) | 
 | { | 
 |   if (pthread_in_use()) | 
 |     { | 
 |       pthread_mutex_t *lock = gl_get_setlocale_null_lock (); | 
 |       int ret; | 
 |  | 
 |       if (pthread_mutex_lock (lock)) | 
 |         abort (); | 
 |       ret = setlocale_null_unlocked (category, buf, bufsize); | 
 |       if (pthread_mutex_unlock (lock)) | 
 |         abort (); | 
 |  | 
 |       return ret; | 
 |     } | 
 |   else | 
 |     return setlocale_null_unlocked (category, buf, bufsize); | 
 | } | 
 |  | 
 | # elif HAVE_THREADS_H | 
 |  | 
 | extern mtx_t *gl_get_setlocale_null_lock (void); | 
 |  | 
 | static int | 
 | setlocale_null_with_lock (int category, char *buf, size_t bufsize) | 
 | { | 
 |   mtx_t *lock = gl_get_setlocale_null_lock (); | 
 |   int ret; | 
 |  | 
 |   if (mtx_lock (lock) != thrd_success) | 
 |     abort (); | 
 |   ret = setlocale_null_unlocked (category, buf, bufsize); | 
 |   if (mtx_unlock (lock) != thrd_success) | 
 |     abort (); | 
 |  | 
 |   return ret; | 
 | } | 
 |  | 
 | # endif | 
 |  | 
 | #endif | 
 |  | 
 | int | 
 | setlocale_null_r (int category, char *buf, size_t bufsize) | 
 | { | 
 | #if SETLOCALE_NULL_ALL_MTSAFE | 
 | # if SETLOCALE_NULL_ONE_MTSAFE | 
 |  | 
 |   return setlocale_null_unlocked (category, buf, bufsize); | 
 |  | 
 | # else | 
 |  | 
 |   if (category == LC_ALL) | 
 |     return setlocale_null_unlocked (category, buf, bufsize); | 
 |   else | 
 |     return setlocale_null_with_lock (category, buf, bufsize); | 
 |  | 
 | # endif | 
 | #else | 
 | # if SETLOCALE_NULL_ONE_MTSAFE | 
 |  | 
 |   if (category == LC_ALL) | 
 |     return setlocale_null_with_lock (category, buf, bufsize); | 
 |   else | 
 |     return setlocale_null_unlocked (category, buf, bufsize); | 
 |  | 
 | # else | 
 |  | 
 |   return setlocale_null_with_lock (category, buf, bufsize); | 
 |  | 
 | # endif | 
 | #endif | 
 | } | 
 |  | 
 | const char * | 
 | setlocale_null (int category) | 
 | { | 
 | #if SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE | 
 |   return setlocale_null_androidfix (category); | 
 | #else | 
 |  | 
 |   /* This call must be multithread-safe.  To achieve this without using | 
 |      thread-local storage: | 
 |        1. We use a specific static buffer for each possible CATEGORY | 
 |           argument.  So that different threads can call setlocale_mtsafe | 
 |           with different CATEGORY arguments, without interfering. | 
 |        2. We use a simple strcpy or memcpy to fill this static buffer. | 
 |           Filling it through, for example, strcpy + strcat would not be | 
 |           guaranteed to leave the buffer's contents intact if another thread | 
 |           is currently accessing it.  If necessary, the contents is first | 
 |           assembled in a stack-allocated buffer.  */ | 
 |   if (category == LC_ALL) | 
 |     { | 
 | # if SETLOCALE_NULL_ALL_MTSAFE | 
 |       return setlocale_null_androidfix (LC_ALL); | 
 | # else | 
 |       char buf[SETLOCALE_NULL_ALL_MAX]; | 
 |       static char resultbuf[SETLOCALE_NULL_ALL_MAX]; | 
 |  | 
 |       if (setlocale_null_r (LC_ALL, buf, sizeof (buf))) | 
 |         return "C"; | 
 |       strcpy (resultbuf, buf); | 
 |       return resultbuf; | 
 | # endif | 
 |     } | 
 |   else | 
 |     { | 
 | # if SETLOCALE_NULL_ONE_MTSAFE | 
 |       return setlocale_null_androidfix (category); | 
 | # else | 
 |       enum | 
 |         { | 
 |           LC_CTYPE_INDEX, | 
 |           LC_NUMERIC_INDEX, | 
 |           LC_TIME_INDEX, | 
 |           LC_COLLATE_INDEX, | 
 |           LC_MONETARY_INDEX, | 
 |           LC_MESSAGES_INDEX, | 
 | #  ifdef LC_PAPER | 
 |           LC_PAPER_INDEX, | 
 | #  endif | 
 | #  ifdef LC_NAME | 
 |           LC_NAME_INDEX, | 
 | #  endif | 
 | #  ifdef LC_ADDRESS | 
 |           LC_ADDRESS_INDEX, | 
 | #  endif | 
 | #  ifdef LC_TELEPHONE | 
 |           LC_TELEPHONE_INDEX, | 
 | #  endif | 
 | #  ifdef LC_MEASUREMENT | 
 |           LC_MEASUREMENT_INDEX, | 
 | #  endif | 
 | #  ifdef LC_IDENTIFICATION | 
 |           LC_IDENTIFICATION_INDEX, | 
 | #  endif | 
 |           LC_INDICES_COUNT | 
 |         } | 
 |         i; | 
 |       char buf[SETLOCALE_NULL_MAX]; | 
 |       static char resultbuf[LC_INDICES_COUNT][SETLOCALE_NULL_MAX]; | 
 |       int err; | 
 |  | 
 |       err = setlocale_null_r (category, buf, sizeof (buf)); | 
 |       if (err == EINVAL) | 
 |         return NULL; | 
 |       if (err) | 
 |         return "C"; | 
 |  | 
 |       switch (category) | 
 |         { | 
 |         case LC_CTYPE:          i = LC_CTYPE_INDEX;          break; | 
 |         case LC_NUMERIC:        i = LC_NUMERIC_INDEX;        break; | 
 |         case LC_TIME:           i = LC_TIME_INDEX;           break; | 
 |         case LC_COLLATE:        i = LC_COLLATE_INDEX;        break; | 
 |         case LC_MONETARY:       i = LC_MONETARY_INDEX;       break; | 
 |         case LC_MESSAGES:       i = LC_MESSAGES_INDEX;       break; | 
 | #  ifdef LC_PAPER | 
 |         case LC_PAPER:          i = LC_PAPER_INDEX;          break; | 
 | #  endif | 
 | #  ifdef LC_NAME | 
 |         case LC_NAME:           i = LC_NAME_INDEX;           break; | 
 | #  endif | 
 | #  ifdef LC_ADDRESS | 
 |         case LC_ADDRESS:        i = LC_ADDRESS_INDEX;        break; | 
 | #  endif | 
 | #  ifdef LC_TELEPHONE | 
 |         case LC_TELEPHONE:      i = LC_TELEPHONE_INDEX;      break; | 
 | #  endif | 
 | #  ifdef LC_MEASUREMENT | 
 |         case LC_MEASUREMENT:    i = LC_MEASUREMENT_INDEX;    break; | 
 | #  endif | 
 | #  ifdef LC_IDENTIFICATION | 
 |         case LC_IDENTIFICATION: i = LC_IDENTIFICATION_INDEX; break; | 
 | #  endif | 
 |         default: | 
 |           /* If you get here, a #ifdef LC_xxx is missing.  */ | 
 |           abort (); | 
 |         } | 
 |  | 
 |       strcpy (resultbuf[i], buf); | 
 |       return resultbuf[i]; | 
 | # endif | 
 |     } | 
 | #endif | 
 | } |