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

Reply via email to