| /* Query the name of the current global locale. |
| Copyright (C) 2019-2021 Free Software Foundation, Inc. |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program 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 General Public License for more details. |
| |
| You should have received a copy of the GNU 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 |
| } |