It is the usual expectation that use of setlocale() to change the global locale is not multithread-safe (of course), but that setlocale(...,NULL) to query the name of the global locale is.
However, POSIX does not guarantee it: <https://pubs.opengroup.org/onlinepubs/9699919799/functions/setlocale.html> says "The returned string pointer might be invalidated or the string content might be overwritten by a subsequent call to setlocale()." We need to distinguish two cases: * querying the name of one category of the global locale A unit test shows that this is not MT-safe on OpenBSD, AIX. * querying the name of the global locale (LC_ALL case) A unit test shows that this is not MT-safe on musl libc, macOS, FreeBSD, NetBSD, OpenBSD, AIX, Haiku, Cygwin. This info, together with a test program that runs setlocale(...,NULL) in different threads at different times, provides the following answer to the question "Where is the return value of setlocale (..., NULL)?": simple category LC_ALL glibc in the category of the locale in the locale musl libc in the category of the locale in a global NOT MT-SAFE! macOS in the category (global) in a global NOT MT-SAFE! FreeBSD in the category (global) in a global NOT MT-SAFE! NetBSD in the category of the locale in a global NOT MT-SAFE! OpenBSD in a global NOT MT-SAFE! in a global NOT MT-SAFE! AIX in a global NOT MT-SAFE! in a global NOT MT-SAFE! HP-UX in the thread in the thread IRIX in the category (global) in a global actually MT-SAFE Solaris 10 in the locale / thread in the locale Solaris 11.0 in the category of the locale in the locale Solaris 11.4 in the category of the locale in the locale Solaris OpenIndiana in the category of the locale in the locale Haiku in the category (global) in a global NOT MT-SAFE! Cygwin in the category (global) in a global NOT MT-SAFE! mingw in the category of the locale in the thread MSVC in the category of the locale? in the thread The only way to fix this is to introduce a lock around the calls to setlocale(...,NULL), since in particular - in OpenBSD, code inspection shows that there is no way to fetch the infos directly in a multithread-safe way, - in AIX, there is no documented API for fetching the infos either. How can the API look like? (a) We could override setlocale(), so that setlocale (..., NULL) returns a string in a buffer in thread-local storage. (b) We could introduce a new API, that takes a caller-provided buffer as argument. Like the functions getlogin_r, ttyname_r, ptsname_r do. I decided to go with approach (b), for the following reasons: * Returning a string in thread-local storage is technically complex (1. get a pointer to thread-local storage, 2. store a malloc()ed buffer in it, resize it when needed, 3. make sure the buffer gets freed when the thread exits [easy with POSIX and ISO C threads, but hard with Windows threads]). Really, it is better to store ALL thread-local data in the thread's stack. Isn't that what a stack is for? * We already have a setlocale() override, and having the ability to activate one override or the other or both together could lead to complex code. In approach (b), the setlocale() override will use the new API - simple. The attached patches implement this. 2019-12-15 Bruno Haible <br...@clisp.org> setlocale-null: New module. * lib/locale.in.h (SETLOCALE_NULL_MAX, SETLOCALE_NULL_ALL_MAX, setlocale_null): New declarations. * lib/setlocale_null.c: New file. * lib/setlocale-lock.c: New file. * m4/threadlib.m4 (gl_PTHREADLIB_BODY): Define C macro HAVE_PTHREAD_API. * m4/setlocale_null.m4: New file. * m4/locale_h.m4 (gl_LOCALE_H_DEFAULTS): Initialize GNULIB_SETLOCALE_NULL. * modules/locale (Makefile.am): Substitute GNULIB_SETLOCALE_NULL. * modules/setlocale-null: New file. * doc/posix-functions/setlocale.texi: Mention the new module. setlocale-null: Add tests. * tests/test-setlocale_null.c: New file. * tests/test-setlocale_null-one.c: New file. * tests/test-setlocale_null-all.c: New file. * modules/setlocale-null-tests: New file.
>From 567591f3b194927a4d7eefa71007be2709796c4e Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Sun, 15 Dec 2019 21:39:55 +0100 Subject: [PATCH 1/2] setlocale-null: New module. * lib/locale.in.h (SETLOCALE_NULL_MAX, SETLOCALE_NULL_ALL_MAX, setlocale_null): New declarations. * lib/setlocale_null.c: New file. * lib/setlocale-lock.c: New file. * m4/threadlib.m4 (gl_PTHREADLIB_BODY): Define C macro HAVE_PTHREAD_API. * m4/setlocale_null.m4: New file. * m4/locale_h.m4 (gl_LOCALE_H_DEFAULTS): Initialize GNULIB_SETLOCALE_NULL. * modules/locale (Makefile.am): Substitute GNULIB_SETLOCALE_NULL. * modules/setlocale-null: New file. * doc/posix-functions/setlocale.texi: Mention the new module. --- ChangeLog | 15 +++ doc/posix-functions/setlocale.texi | 14 ++- lib/locale.in.h | 39 +++++++ lib/setlocale-lock.c | 124 ++++++++++++++++++++++ lib/setlocale_null.c | 204 +++++++++++++++++++++++++++++++++++++ m4/locale_h.m4 | 11 +- m4/setlocale_null.m4 | 84 +++++++++++++++ m4/threadlib.m4 | 8 +- modules/locale | 1 + modules/setlocale-null | 34 +++++++ 10 files changed, 526 insertions(+), 8 deletions(-) create mode 100644 lib/setlocale-lock.c create mode 100644 lib/setlocale_null.c create mode 100644 m4/setlocale_null.m4 create mode 100644 modules/setlocale-null diff --git a/ChangeLog b/ChangeLog index 39ba464..51f367c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,20 @@ 2019-12-15 Bruno Haible <br...@clisp.org> + setlocale-null: New module. + * lib/locale.in.h (SETLOCALE_NULL_MAX, SETLOCALE_NULL_ALL_MAX, + setlocale_null): New declarations. + * lib/setlocale_null.c: New file. + * lib/setlocale-lock.c: New file. + * m4/threadlib.m4 (gl_PTHREADLIB_BODY): Define C macro HAVE_PTHREAD_API. + * m4/setlocale_null.m4: New file. + * m4/locale_h.m4 (gl_LOCALE_H_DEFAULTS): Initialize + GNULIB_SETLOCALE_NULL. + * modules/locale (Makefile.am): Substitute GNULIB_SETLOCALE_NULL. + * modules/setlocale-null: New file. + * doc/posix-functions/setlocale.texi: Mention the new module. + +2019-12-15 Bruno Haible <br...@clisp.org> + lock tests: Skip test when no multithreading is enabled. * tests/test-rwlock1.c: Skip the test when no multithreading is enabled. diff --git a/doc/posix-functions/setlocale.texi b/doc/posix-functions/setlocale.texi index 0678ac9..cd172c0 100644 --- a/doc/posix-functions/setlocale.texi +++ b/doc/posix-functions/setlocale.texi @@ -4,9 +4,9 @@ POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9699919799/functions/setlocale.html} -Gnulib module: setlocale +Gnulib module: setlocale, setlocale-null -Portability problems fixed by Gnulib: +Portability problems fixed by Gnulib module @code{setlocale}: @itemize @item On Windows platforms (excluding Cygwin), @code{setlocale(@var{category},NULL)} @@ -26,6 +26,16 @@ always fails. The replacement, however, supports only the locale names @code{"C"} and @code{"POSIX"}. @end itemize +Portability problems fixed by Gnulib module @code{setlocale-null}: +@itemize +@item +Invocations of @code{setlocale (..., NULL)} are not multithread-safe on some +platforms: +musl libc, macOS, FreeBSD, NetBSD, OpenBSD, AIX, Haiku, Cygwin. +To make these invocations multithread-safe, you need to change the code to +invoke @code{setlocale_null} instead. +@end itemize + Portability problems not fixed by Gnulib: @itemize @item diff --git a/lib/locale.in.h b/lib/locale.in.h index 9e897a3..5986683 100644 --- a/lib/locale.in.h +++ b/lib/locale.in.h @@ -206,6 +206,45 @@ _GL_WARN_ON_USE (setlocale, "setlocale works differently on native Windows - " # endif #endif +#if @GNULIB_SETLOCALE_NULL@ +/* Recommended size of a buffer for a locale name for a single category. + On glibc systems, you can have locale names that are relative file names; + assume a maximum length 256. + In native Windows, in 2018 the longest locale name was of length 58 + ("FYRO Macedonian_Former Yugoslav Republic of Macedonia.1251"). */ +# define SETLOCALE_NULL_MAX (256+1) +/* Recommended size of a buffer for a locale name with all categories. + On glibc systems, you can have locale names that are relative file names; + assume maximum length 256 for each. There are 12 categories; so, the + maximum total length is 148+12*256. + In native Windows, there are 5 categories, and the maximum total length is + 55+5*58. */ +# define SETLOCALE_NULL_ALL_MAX (148+12*256+1) +/* setlocale_null (CATEGORY, BUF, BUFSIZE) is like setlocale (CATEGORY, NULL), + except that + - it is guaranteed to be multithread-safe, + - it returns the resulting locale category name or locale name in the + user-supplied buffer BUF, which must be BUFSIZE bytes long. + The recommended minimum buffer size is + - SETLOCALE_NULL_MAX for CATEGORY != LC_ALL, and + - SETLOCALE_NULL_ALL_MAX for CATEGORY == LC_ALL. + The return value is an error code: 0 if the call is successful, ERANGE if + BUFSIZE is smaller than the length needed size (including the trailing NUL + byte). In the latter case, a truncated result is returned in BUF, but + still NUL-terminated if BUFSIZE > 0. + For this call to be multithread-safe, *all* calls to + setlocale (CATEGORY, NULL) in all other threads must have been converted + to use setlocale_null as well, and the other threads must not make other + setlocale invocations (since changing the global locale has side effects + on all threads). */ +_GL_FUNCDECL_SYS (setlocale_null, int, + (int category, char *buf, size_t bufsize) + _GL_ARG_NONNULL ((2))); +_GL_CXXALIAS_SYS (setlocale_null, int, + (int category, char *buf, size_t bufsize)); +_GL_CXXALIASWARN (setlocale_null); +#endif + #if /*@GNULIB_NEWLOCALE@ ||*/ (@GNULIB_LOCALENAME@ && @HAVE_NEWLOCALE@) # if @REPLACE_NEWLOCALE@ # if !(defined __cplusplus && defined GNULIB_NAMESPACE) diff --git a/lib/setlocale-lock.c b/lib/setlocale-lock.c new file mode 100644 index 0000000..ed6ab9f --- /dev/null +++ b/lib/setlocale-lock.c @@ -0,0 +1,124 @@ +/* Return the internal lock used by setlocale_null. + Copyright (C) 2019 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 <br...@clisp.org>, 2019. */ + +#include <config.h> + +/* This file defines the internal lock used by setlocale_null. + It is a separate compilation unit, so that only one copy of it is + present when linking statically. */ + +/* Prohibit renaming this symbol. */ +#undef gl_get_setlocale_null_lock + +#if defined _WIN32 && !defined __CYGWIN__ + +# define WIN32_LEAN_AND_MEAN /* avoid including junk */ +# include <windows.h> + +# include "windows-initguard.h" + +/* The return type is a 'CRITICAL_SECTION *', not a 'glwthread_mutex_t *', + because the latter is not guaranteed to be a stable ABI in the future. */ + +/* Make sure the function gets exported from DLLs. */ +__declspec(dllexport) CRITICAL_SECTION *gl_get_setlocale_null_lock (void); + +static glwthread_initguard_t guard = GLWTHREAD_INITGUARD_INIT; +static CRITICAL_SECTION lock; + +/* Returns the internal lock used by setlocale_null. */ +CRITICAL_SECTION * +gl_get_setlocale_null_lock (void) +{ + if (!guard.done) + { + if (InterlockedIncrement (&guard.started) == 0) + { + /* This thread is the first one to need the lock. Initialize it. */ + InitializeCriticalSection (&lock); + guard.done = 1; + } + else + { + /* Don't let guard.started grow and wrap around. */ + InterlockedDecrement (&guard.started); + /* Yield the CPU while waiting for another thread to finish + initializing this mutex. */ + while (!guard.done) + Sleep (0); + } + } + return &lock; +} + +#elif HAVE_PTHREAD_API + +# include <pthread.h> + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +# if defined _WIN32 || defined __CYGWIN__ +/* Make sure the function gets exported from DLLs. */ +__declspec(dllexport) pthread_mutex_t *gl_get_setlocale_null_lock (void); +# endif + +/* Returns the internal lock used by setlocale_null. */ +pthread_mutex_t * +gl_get_setlocale_null_lock (void) +{ + return &mutex; +} + +#elif HAVE_THREADS_H + +# include <threads.h> +# include <stdlib.h> + +static int volatile init_needed = 1; +static once_flag init_once = ONCE_FLAG_INIT; +static mtx_t mutex; + +static void +atomic_init (void) +{ + if (mtx_init (&mutex, mtx_plain) != thrd_success) + abort (); + init_needed = 0; +} + +/* Returns the internal lock used by setlocale_null. */ +mtx_t * +gl_get_setlocale_null_lock (void) +{ + if (init_needed) + call_once (&init_once, atomic_init); + return &mutex; +} + +#endif + +#if defined _WIN32 || defined __CYGWIN__ +/* Make sure the '__declspec(dllimport)' in setlocale_null.c does not cause + a link failure when no DLLs are involved. */ +# if defined _WIN64 || defined _LP64 +# define IMP(x) __imp_##x +# else +# define IMP(x) _imp__##x +# endif +void * IMP(gl_get_setlocale_null_lock) = &gl_get_setlocale_null_lock; +#endif diff --git a/lib/setlocale_null.c b/lib/setlocale_null.c new file mode 100644 index 0000000..b0506b9 --- /dev/null +++ b/lib/setlocale_null.c @@ -0,0 +1,204 @@ +/* Query the name of the current global locale. + Copyright (C) 2019 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 <br...@clisp.org>, 2019. */ + +#include <config.h> + +/* Specification. */ +#include <locale.h> + +#include <errno.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> +# elif HAVE_THREADS_H +# include <threads.h> +# endif +#endif + +/* Use the system's setlocale() function, not the gnulib override, here. */ +#undef setlocale + +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); + 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 (category, NULL); + 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) + +/* 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 + +extern +# if defined _WIN32 || defined __CYGWIN__ + __declspec(dllimport) +# endif + pthread_mutex_t *gl_get_setlocale_null_lock (void); + +static int +setlocale_null_with_lock (int category, char *buf, size_t bufsize) +{ + 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; +} + +# 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 (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 +} diff --git a/m4/locale_h.m4 b/m4/locale_h.m4 index c0ec3fc..fd7de42 100644 --- a/m4/locale_h.m4 +++ b/m4/locale_h.m4 @@ -1,4 +1,4 @@ -# locale_h.m4 serial 23 +# locale_h.m4 serial 24 dnl Copyright (C) 2007, 2009-2019 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -140,10 +140,11 @@ AC_DEFUN([gl_LOCALE_MODULE_INDICATOR], AC_DEFUN([gl_LOCALE_H_DEFAULTS], [ - GNULIB_LOCALECONV=0; AC_SUBST([GNULIB_LOCALECONV]) - GNULIB_SETLOCALE=0; AC_SUBST([GNULIB_SETLOCALE]) - GNULIB_DUPLOCALE=0; AC_SUBST([GNULIB_DUPLOCALE]) - GNULIB_LOCALENAME=0; AC_SUBST([GNULIB_LOCALENAME]) + GNULIB_LOCALECONV=0; AC_SUBST([GNULIB_LOCALECONV]) + GNULIB_SETLOCALE=0; AC_SUBST([GNULIB_SETLOCALE]) + GNULIB_SETLOCALE_NULL=0; AC_SUBST([GNULIB_SETLOCALE_NULL]) + GNULIB_DUPLOCALE=0; AC_SUBST([GNULIB_DUPLOCALE]) + GNULIB_LOCALENAME=0; AC_SUBST([GNULIB_LOCALENAME]) dnl Assume proper GNU behavior unless another module says otherwise. HAVE_NEWLOCALE=1; AC_SUBST([HAVE_NEWLOCALE]) HAVE_DUPLOCALE=1; AC_SUBST([HAVE_DUPLOCALE]) diff --git a/m4/setlocale_null.m4 b/m4/setlocale_null.m4 new file mode 100644 index 0000000..5c69a3f --- /dev/null +++ b/m4/setlocale_null.m4 @@ -0,0 +1,84 @@ +# setlocale_null.m4 serial 1 +dnl Copyright (C) 2019 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_FUNC_SETLOCALE_NULL], +[ + AC_REQUIRE([AC_CANONICAL_HOST]) + AC_REQUIRE([gl_PTHREADLIB]) + AC_CHECK_HEADERS_ONCE([threads.h]) + + AC_CACHE_CHECK([whether setlocale (LC_ALL, NULL) is multithread-safe], + [gl_cv_func_setlocale_null_all_mtsafe], + [case "$host_os" in + # Guess no on musl libc, macOS, FreeBSD, NetBSD, OpenBSD, AIX, Haiku, Cygwin. + *-musl* | darwin* | freebsd* | netbsd* | openbsd* | aix* | haiku* | cygwin*) + gl_cv_func_setlocale_null_all_mtsafe=no ;; + # Guess yes on glibc, HP-UX, IRIX, Solaris, native Windows. + *-gnu* | gnu* | hpux* | irix* | solaris* | mingw*) + gl_cv_func_setlocale_null_all_mtsafe=yes ;; + # If we don't know, obey --enable-cross-guesses. + *) + gl_cv_func_setlocale_null_all_mtsafe="$gl_cross_guess_normal" ;; + esac + ]) + dnl On platforms without multithreading, there is no issue. + case "$host_os" in + mingw*) ;; + *) + if test $gl_pthread_api = no && test $ac_cv_header_threads_h = no; then + gl_cv_func_setlocale_null_all_mtsafe="trivially yes" + fi + ;; + esac + case "$gl_cv_func_setlocale_null_all_mtsafe" in + *yes) SETLOCALE_NULL_ALL_MTSAFE=1 ;; + *) SETLOCALE_NULL_ALL_MTSAFE=0 ;; + esac + AC_DEFINE_UNQUOTED([SETLOCALE_NULL_ALL_MTSAFE], [$SETLOCALE_NULL_ALL_MTSAFE], + [Define to 1 if setlocale (LC_ALL, NULL) is multithread-safe.]) + + dnl This is about a single category (not LC_ALL). + AC_CACHE_CHECK([whether setlocale (category, NULL) is multithread-safe], + [gl_cv_func_setlocale_null_one_mtsafe], + [case "$host_os" in + # Guess no on OpenBSD, AIX. + openbsd* | aix*) + gl_cv_func_setlocale_null_one_mtsafe=no ;; + # Guess yes on glibc, musl libc, macOS, FreeBSD, NetBSD, HP-UX, IRIX, Solaris, Haiku, Cygwin, native Windows. + *-gnu* | gnu* | *-musl* | darwin* | freebsd* | netbsd* | hpux* | irix* | solaris* | haiku* | cygwin* | mingw*) + gl_cv_func_setlocale_null_one_mtsafe=yes ;; + # If we don't know, obey --enable-cross-guesses. + *) + gl_cv_func_setlocale_null_one_mtsafe="$gl_cross_guess_normal" ;; + esac + ]) + dnl On platforms without multithreading, there is no issue. + case "$host_os" in + mingw*) ;; + *) + if test $gl_pthread_api = no && test $ac_cv_header_threads_h = no; then + gl_cv_func_setlocale_null_one_mtsafe="trivially yes" + fi + ;; + esac + case "$gl_cv_func_setlocale_null_one_mtsafe" in + *yes) SETLOCALE_NULL_ONE_MTSAFE=1 ;; + *) SETLOCALE_NULL_ONE_MTSAFE=0 ;; + esac + AC_DEFINE_UNQUOTED([SETLOCALE_NULL_ONE_MTSAFE], [$SETLOCALE_NULL_ONE_MTSAFE], + [Define to 1 if setlocale (category, NULL) is multithread-safe.]) + + dnl Determine link dependencies of lib/setlocale_null.c and lib/setlocale-lock.c. + if test $SETLOCALE_NULL_ALL_MTSAFE = 0 || test $SETLOCALE_NULL_ONE_MTSAFE = 0; then + case "$host_os" in + mingw*) LIB_SETLOCALE_NULL= ;; + *) LIB_SETLOCALE_NULL="$LIBPTHREAD" ;; + esac + else + LIB_SETLOCALE_NULL= + fi + AC_SUBST([LIB_SETLOCALE_NULL]) +]) diff --git a/m4/threadlib.m4 b/m4/threadlib.m4 index d22e0d6..d6e6bf9 100644 --- a/m4/threadlib.m4 +++ b/m4/threadlib.m4 @@ -1,4 +1,4 @@ -# threadlib.m4 serial 22 +# threadlib.m4 serial 23 dnl Copyright (C) 2005-2019 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -18,6 +18,8 @@ dnl LIBPMULTITHREAD is that on platforms supporting weak symbols, typically dnl LIBPTHREAD is empty whereas LIBPMULTITHREAD is not. dnl Adds to CPPFLAGS the flag -D_REENTRANT or -D_THREAD_SAFE if needed for dnl multithread-safe programs. +dnl Defines the C macro HAVE_PTHREAD_API if (at least parts of) the POSIX +dnl threads API is available. dnl gl_THREADLIB dnl ------------ @@ -220,6 +222,10 @@ AC_DEFUN([gl_PTHREADLIB_BODY], AC_MSG_RESULT([$gl_pthread_api]) AC_SUBST([LIBPTHREAD]) AC_SUBST([LIBPMULTITHREAD]) + if test $gl_pthread_api = yes; then + AC_DEFINE([HAVE_PTHREAD_API], [1], + [Define if you have the <pthread.h> header and the POSIX threads API.]) + fi gl_threadlib_body_done=done fi ]) diff --git a/modules/locale b/modules/locale index 488e437..aac463c 100644 --- a/modules/locale +++ b/modules/locale @@ -31,6 +31,7 @@ locale.h: locale.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H -e 's|@''NEXT_LOCALE_H''@|$(NEXT_LOCALE_H)|g' \ -e 's/@''GNULIB_LOCALECONV''@/$(GNULIB_LOCALECONV)/g' \ -e 's/@''GNULIB_SETLOCALE''@/$(GNULIB_SETLOCALE)/g' \ + -e 's/@''GNULIB_SETLOCALE_NULL''@/$(GNULIB_SETLOCALE_NULL)/g' \ -e 's/@''GNULIB_DUPLOCALE''@/$(GNULIB_DUPLOCALE)/g' \ -e 's/@''GNULIB_LOCALENAME''@/$(GNULIB_LOCALENAME)/g' \ -e 's|@''HAVE_NEWLOCALE''@|$(HAVE_NEWLOCALE)|g' \ diff --git a/modules/setlocale-null b/modules/setlocale-null new file mode 100644 index 0000000..17e8d9d --- /dev/null +++ b/modules/setlocale-null @@ -0,0 +1,34 @@ +Description: +setlocale_null() function: query the name of the current global locale. + +Files: +lib/setlocale_null.c +lib/setlocale-lock.c +lib/windows-initguard.h +m4/setlocale_null.m4 +m4/threadlib.m4 + +Depends-on: +locale + +configure.ac: +gl_FUNC_SETLOCALE_NULL +if test $SETLOCALE_NULL_ALL_MTSAFE = 0 || test $SETLOCALE_NULL_ONE_MTSAFE = 0; then + AC_LIBOBJ([setlocale-lock]) +fi +gl_LOCALE_MODULE_INDICATOR([setlocale_null]) + +Makefile.am: +lib_SOURCES += setlocale_null.c + +Include: +<locale.h> + +Link: +$(LIB_SETLOCALE_NULL) + +License: +LGPLv2+ + +Maintainer: +Bruno Haible -- 2.7.4
>From 4110c01cb60ea810e5f4abb38224eac0b1560462 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Sun, 15 Dec 2019 21:48:05 +0100 Subject: [PATCH 2/2] setlocale-null: Add tests. * tests/test-setlocale_null.c: New file. * tests/test-setlocale_null-one.c: New file. * tests/test-setlocale_null-all.c: New file. * modules/setlocale-null-tests: New file. --- ChangeLog | 6 ++ modules/setlocale-null-tests | 23 +++++++ tests/test-setlocale_null-all.c | 148 ++++++++++++++++++++++++++++++++++++++++ tests/test-setlocale_null-one.c | 148 ++++++++++++++++++++++++++++++++++++++++ tests/test-setlocale_null.c | 32 +++++++++ 5 files changed, 357 insertions(+) create mode 100644 modules/setlocale-null-tests create mode 100644 tests/test-setlocale_null-all.c create mode 100644 tests/test-setlocale_null-one.c create mode 100644 tests/test-setlocale_null.c diff --git a/ChangeLog b/ChangeLog index 51f367c..af1d5bf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2019-12-15 Bruno Haible <br...@clisp.org> + setlocale-null: Add tests. + * tests/test-setlocale_null.c: New file. + * tests/test-setlocale_null-one.c: New file. + * tests/test-setlocale_null-all.c: New file. + * modules/setlocale-null-tests: New file. + setlocale-null: New module. * lib/locale.in.h (SETLOCALE_NULL_MAX, SETLOCALE_NULL_ALL_MAX, setlocale_null): New declarations. diff --git a/modules/setlocale-null-tests b/modules/setlocale-null-tests new file mode 100644 index 0000000..2114848 --- /dev/null +++ b/modules/setlocale-null-tests @@ -0,0 +1,23 @@ +Files: +tests/test-setlocale_null.c +tests/test-setlocale_null-one.c +tests/test-setlocale_null-all.c + +Depends-on: +thread +nanosleep + +configure.ac: + +Makefile.am: +TESTS += \ + test-setlocale_null \ + test-setlocale_null-one \ + test-setlocale_null-all +check_PROGRAMS += \ + test-setlocale_null \ + test-setlocale_null-one \ + test-setlocale_null-all +test_setlocale_null_LDADD = $(LDADD) @LIB_SETLOCALE_NULL@ +test_setlocale_null_one_LDADD = $(LDADD) @LIB_SETLOCALE_NULL@ $(LIBMULTITHREAD) $(LIB_NANOSLEEP) +test_setlocale_null_all_LDADD = $(LDADD) @LIB_SETLOCALE_NULL@ $(LIBMULTITHREAD) $(LIB_NANOSLEEP) diff --git a/tests/test-setlocale_null-all.c b/tests/test-setlocale_null-all.c new file mode 100644 index 0000000..b3ebb13 --- /dev/null +++ b/tests/test-setlocale_null-all.c @@ -0,0 +1,148 @@ +/* Multithread-safety test for setlocale_null (LC_ALL, ...). + Copyright (C) 2019 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 <br...@clisp.org>, 2019. */ + +#include <config.h> + +/* Specification. */ +#include <locale.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "glthread/thread.h" + + +/* Some common locale names. */ + +#if defined _WIN32 && !defined __CYGWIN__ +# define ENGLISH "English_United States" +# define GERMAN "German_Germany" +# define FRENCH "French_France" +# define ENCODING ".1252" +#else +# define ENGLISH "en_US" +# define GERMAN "de_DE" +# define FRENCH "fr_FR" +# if defined __sgi +# define ENCODING ".ISO8859-15" +# elif defined __hpux +# define ENCODING ".utf8" +# else +# define ENCODING ".UTF-8" +# endif +#endif + +static const char LOCALE1[] = ENGLISH ENCODING; +static const char LOCALE2[] = GERMAN ENCODING; +static const char LOCALE3[] = FRENCH ENCODING; + +static char *expected; + +static void * +thread1_func (void *arg) +{ + for (;;) + { + char buf[SETLOCALE_NULL_ALL_MAX]; + + if (setlocale_null (LC_ALL, buf, sizeof (buf))) + abort (); + if (strcmp (expected, buf) != 0) + { + fprintf (stderr, "thread1 disturbed by thread2!\n"); fflush (stderr); + abort (); + } + } + + /*NOTREACHED*/ + return NULL; +} + +static void * +thread2_func (void *arg) +{ + for (;;) + { + char buf[SETLOCALE_NULL_ALL_MAX]; + + setlocale_null (LC_NUMERIC, buf, sizeof (buf)); + setlocale_null (LC_ALL, buf, sizeof (buf)); + } + + /*NOTREACHED*/ + return NULL; +} + +int +main (int argc, char *argv[]) +{ + if (setlocale (LC_ALL, LOCALE1) == NULL) + { + fprintf (stderr, "Skipping test: LOCALE1 not recognized\n"); + return 77; + } + if (setlocale (LC_NUMERIC, LOCALE2) == NULL) + { + fprintf (stderr, "Skipping test: LOCALE2 not recognized\n"); + return 77; + } + if (setlocale (LC_TIME, LOCALE3) == NULL) + { + fprintf (stderr, "Skipping test: LOCALE3 not recognized\n"); + return 77; + } + + expected = strdup (setlocale (LC_ALL, NULL)); + + /* Create the two threads. */ + gl_thread_create (thread1_func, NULL); + gl_thread_create (thread2_func, NULL); + + /* Let them run for 5 seconds. */ + { + struct timespec duration; + duration.tv_sec = 5; + duration.tv_nsec = 0; + + nanosleep (&duration, NULL); + } + + return 0; +} + +/* Without locking, the results of this test would be: +glibc OK +musl libc crash < 10 sec +macOS crash < 1 sec +FreeBSD crash < 1 sec +NetBSD crash < 2 sec +OpenBSD crash < 1 sec +AIX crash < 2 sec +HP-UX OK +IRIX OK +Solaris 10 OK +Solaris 11.0 OK +Solaris 11.4 OK +Solaris OpenIndiana OK +Haiku crash < 1 sec +Cygwin crash < 1 sec +mingw OK +MSVC OK (assuming compiler option /MD !) +*/ diff --git a/tests/test-setlocale_null-one.c b/tests/test-setlocale_null-one.c new file mode 100644 index 0000000..1aa5eaa --- /dev/null +++ b/tests/test-setlocale_null-one.c @@ -0,0 +1,148 @@ +/* Multithread-safety test for setlocale_null (LC_xxx, ...). + Copyright (C) 2019 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 <br...@clisp.org>, 2019. */ + +#include <config.h> + +/* Specification. */ +#include <locale.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "glthread/thread.h" + + +/* Some common locale names. */ + +#if defined _WIN32 && !defined __CYGWIN__ +# define ENGLISH "English_United States" +# define GERMAN "German_Germany" +# define FRENCH "French_France" +# define ENCODING ".1252" +#else +# define ENGLISH "en_US" +# define GERMAN "de_DE" +# define FRENCH "fr_FR" +# if defined __sgi +# define ENCODING ".ISO8859-15" +# elif defined __hpux +# define ENCODING ".utf8" +# else +# define ENCODING ".UTF-8" +# endif +#endif + +static const char LOCALE1[] = ENGLISH ENCODING; +static const char LOCALE2[] = GERMAN ENCODING; +static const char LOCALE3[] = FRENCH ENCODING; + +static char *expected; + +static void * +thread1_func (void *arg) +{ + for (;;) + { + char buf[SETLOCALE_NULL_MAX]; + + if (setlocale_null (LC_NUMERIC, buf, sizeof (buf))) + abort (); + if (strcmp (expected, buf) != 0) + { + fprintf (stderr, "thread1 disturbed by thread2!\n"); fflush (stderr); + abort (); + } + } + + /*NOTREACHED*/ + return NULL; +} + +static void * +thread2_func (void *arg) +{ + for (;;) + { + char buf[SETLOCALE_NULL_MAX]; + + setlocale_null (LC_NUMERIC, buf, sizeof (buf)); + setlocale_null (LC_TIME, buf, sizeof (buf)); + } + + /*NOTREACHED*/ + return NULL; +} + +int +main (int argc, char *argv[]) +{ + if (setlocale (LC_ALL, LOCALE1) == NULL) + { + fprintf (stderr, "Skipping test: LOCALE1 not recognized\n"); + return 77; + } + if (setlocale (LC_NUMERIC, LOCALE2) == NULL) + { + fprintf (stderr, "Skipping test: LOCALE2 not recognized\n"); + return 77; + } + if (setlocale (LC_TIME, LOCALE3) == NULL) + { + fprintf (stderr, "Skipping test: LOCALE3 not recognized\n"); + return 77; + } + + expected = strdup (setlocale (LC_NUMERIC, NULL)); + + /* Create the two threads. */ + gl_thread_create (thread1_func, NULL); + gl_thread_create (thread2_func, NULL); + + /* Let them run for 2 seconds. */ + { + struct timespec duration; + duration.tv_sec = 2; + duration.tv_nsec = 0; + + nanosleep (&duration, NULL); + } + + return 0; +} + +/* Without locking, the results of this test would be: +glibc OK +musl libc OK +macOS OK +FreeBSD OK +NetBSD OK +OpenBSD crash < 1 sec +AIX crash < 2 sec +HP-UX OK +IRIX OK +Solaris 10 OK +Solaris 11.0 OK +Solaris 11.4 OK +Solaris OpenIndiana OK +Haiku OK +Cygwin OK +mingw OK +MSVC OK (assuming compiler option /MD !) +*/ diff --git a/tests/test-setlocale_null.c b/tests/test-setlocale_null.c new file mode 100644 index 0000000..003cfc9 --- /dev/null +++ b/tests/test-setlocale_null.c @@ -0,0 +1,32 @@ +/* Test of setlocale_null function. + Copyright (C) 2019 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 <br...@clisp.org>, 2019. */ + +#include <config.h> + +/* Specification. */ +#include <locale.h> + +/* Check that SETLOCALE_NULL_ALL_MAX is a constant expression. */ +static char buf[SETLOCALE_NULL_ALL_MAX]; + +int +main () +{ + /* Check that setlocale_null () can be used with $(LIB_SETLOCALE_NULL). */ + return setlocale_null (LC_ALL, buf, sizeof (buf)) != 0; +} -- 2.7.4