Commit 567591f3 authored by Bruno Haible's avatar Bruno Haible
Browse files

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.
parent fdff8bd0
2019-12-15 Bruno Haible <bruno@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 <bruno@clisp.org>
 
lock tests: Skip test when no multithreading is enabled.
......@@ -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
......
......@@ -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)
......
/* 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 <bruno@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
/* 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 <bruno@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
}
# 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])
......
# 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])
])
# 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
])
......
......@@ -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' \
......
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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment