Several platforms have added locale_t based API to their libc in ca. 2010 to 2012. The purpose of these APIs is 1) to have multithreaded, multi-locale applications. For example, web servers which use a different locale for each HTTP request that they process. 2) to be able to use the "C" locale in specific places that require "C" locale behaviour (e.g. floating-point number I/O in JSON parsers or writers).
But few applications are using this API so far. Now, POSIX:2024 standardizes the type and a few *_l functions that have a 'locale_t' parameter. Still, applications will be shy to use it if there are portability problems. The main problem is that the locale_t type is missing on: - mingw, MSVC 14, - FreeBSD 9.0, NetBSD 6.1, OpenBSD 6.1, Solaris 11.3, Android 4.4. This patch adds a 'locale_t' type. - On native Windows from 2019, it is fully functional, making use of the '_create_locale' function, see <https://learn.microsoft.com/en-us/cpp/c-runtime-library/locale>, that was also added to mingw in 2019, cf. <https://sourceforge.net/p/mingw-w64/mailman/message/36794595/>. - On the other platforms (old native Windows, FreeBSD ≤ 9.0, NetBSD ≤ 6.1, OpenBSD ≤ 6.1, Solaris ≤ 11.3, Android 4.4), it supports the use-case 2 above, but not the use-case 1. For the use-case 1, some programs (e.g. Guile) use setlocale() with locking as workaround. But this is not possible in Gnulib, since there can be invocations of locale-sensitive functions (e.g. fprintf()) anywhere in the application, and we cannot force a lock protection on them. Since this affects only older platforms, this limitation should be acceptable. 2025-02-13 Bruno Haible <br...@clisp.org> locale-h: Ensure locale_t type. * lib/locale.in.h (gl_log2_lc_mask, gl_log2_lcmask_to_index, gl_index_to_log2_lcmask): New macros. (LC_COLLATE_MASK, LC_CTYPE_MASK, LC_MESSAGES_MASK, LC_MONETARY_MASK, LC_NUMERIC_MASK, LC_TIME_MASK, LC_ALL_MASK): New macros. (struct gl_locale_category_t, struct gl_locale_t, locale_t): New types. (LC_GLOBAL_LOCALE, GNULIB_defined_locale_t): New macros. * m4/locale_h.m4 (gl_LOCALE_H): Set and define HAVE_WINDOWS_LOCALE_T. (gl_LOCALE_T): Prepare for substituting HAVE_LOCALE_T. * modules/locale-h (Depends-on): Add bool. (Makefile.am): Substitute HAVE_LOCALE_T, HAVE_WINDOWS_LOCALE_T. * tests/test-locale-h.c: Check that the LC_*_MASK macros and locale_t are defined. * doc/posix-headers/locale.texi: Document the change. diff --git a/doc/posix-headers/locale.texi b/doc/posix-headers/locale.texi index 0ba1826832..4a99931c27 100644 --- a/doc/posix-headers/locale.texi +++ b/doc/posix-headers/locale.texi @@ -14,7 +14,7 @@ @item The @code{locale_t} type is not defined on some platforms: -glibc 2.11, macOS 11.1. +glibc 2.11, macOS 11.1, FreeBSD 9.0, NetBSD 6.1, OpenBSD 6.1, Solaris 11.3, mingw, MSVC 14, Android 4.4. @item The @code{struct lconv} type does not contain any members on some platforms: diff --git a/lib/locale.in.h b/lib/locale.in.h index 000f382831..389ff26124 100644 --- a/lib/locale.in.h +++ b/lib/locale.in.h @@ -69,6 +69,85 @@ # define LC_MESSAGES 1729 #endif +#if !@HAVE_LOCALE_T@ +# if !defined GNULIB_defined_locale_t +/* The values of the POSIX-standardized LC_* macros are: + + LC_COLLATE LC_CTYPE LC_MESSAGES LC_MONETARY LC_NUMERIC LC_TIME + + glibc, Solaris, 3 0 5 4 1 2 + Android + macOS, *BSD 1 2 6 3 4 5 + native Windows 1 2 1729 3 4 5 + + We map these to the log2(LC_*_MASK) values, chosen to be compatible with + later releases of the same operating system. */ +# if defined __APPLE__ && defined __MACH__ /* macOS */ +/* LC_COLLATE LC_CTYPE LC_MESSAGES LC_MONETARY LC_NUMERIC LC_TIME + + category 1 2 6 3 4 5 + log2(LC_*_MASK) 0 1 2 3 4 5 + */ +# define gl_log2_lc_mask(category) ((0x2543100 >> (4 * (category))) & 0xf) +# elif defined __FreeBSD__ || defined __DragonFly__ /* FreeBSD */ +/* LC_COLLATE LC_CTYPE LC_MESSAGES LC_MONETARY LC_NUMERIC LC_TIME + + category 1 2 6 3 4 5 + log2(LC_*_MASK) 0 1 5 2 3 4 + */ +# define gl_log2_lc_mask(category) ((category) - 1) +# elif defined _WIN32 && !defined __CYGWIN__ /* native Windows */ +# define gl_log2_lc_mask(category) \ + ((category) == LC_MESSAGES ? 0 : (category)) +# else /* glibc, Solaris, Android, NetBSD, OpenBSD */ +# define gl_log2_lc_mask(category) (category) +# endif +/* From there we map them to array indices 0..5. */ +# if (gl_log2_lc_mask (LC_COLLATE) == 0 || gl_log2_lc_mask (LC_CTYPE) == 0 \ + || gl_log2_lc_mask (LC_MESSAGES) == 0) + /* glibc, Solaris, Android, macOS, FreeBSD, native Windows */ +# define gl_log2_lcmask_to_index(c) (c) +# define gl_index_to_log2_lcmask(i) (i) +# else + /* NetBSD, OpenBSD */ +# define gl_log2_lcmask_to_index(c) ((c) - 1) +# define gl_index_to_log2_lcmask(i) ((i) + 1) +# endif +/* Define the LC_*_MASK macros. */ +# define LC_COLLATE_MASK (1 << gl_log2_lc_mask (LC_COLLATE)) +# define LC_CTYPE_MASK (1 << gl_log2_lc_mask (LC_CTYPE)) +# define LC_MESSAGES_MASK (1 << gl_log2_lc_mask (LC_MESSAGES)) +# define LC_MONETARY_MASK (1 << gl_log2_lc_mask (LC_MONETARY)) +# define LC_NUMERIC_MASK (1 << gl_log2_lc_mask (LC_NUMERIC)) +# define LC_TIME_MASK (1 << gl_log2_lc_mask (LC_TIME)) +# define LC_ALL_MASK \ + (LC_COLLATE_MASK | LC_CTYPE_MASK | LC_MESSAGES_MASK | LC_MONETARY_MASK \ + | LC_NUMERIC_MASK | LC_TIME_MASK) +/* Now define the locale_t type. */ +struct gl_locale_category_t +{ + char *name; + bool is_c_locale; +# if @HAVE_WINDOWS_LOCALE_T@ + /* Use the native Windows '_locale_t' type. + Documentation: + <https://learn.microsoft.com/en-us/cpp/c-runtime-library/locale> + This field is NULL if is_c_locale is true. But don't use this NULL value, + since for the native Windows *_l functions a null _locale_t means to use + the global locale. */ + _locale_t system_locale; +# endif +}; +struct gl_locale_t +{ + struct gl_locale_category_t category[6]; +}; +typedef struct gl_locale_t *locale_t; +# define LC_GLOBAL_LOCALE ((locale_t)(-1)) +# define GNULIB_defined_locale_t 1 +# endif +#endif + /* On native Windows with MSVC, 'struct lconv' lacks the members int_p_* and int_n_*. Instead of overriding 'struct lconv', merely define these member names as macros. This avoids trouble in C++ mode. */ diff --git a/m4/locale_h.m4 b/m4/locale_h.m4 index 486b5a1396..9300062194 100644 --- a/m4/locale_h.m4 +++ b/m4/locale_h.m4 @@ -1,5 +1,5 @@ # locale_h.m4 -# serial 31 +# serial 32 dnl Copyright (C) 2007, 2009-2025 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -20,6 +20,26 @@ AC_DEFUN_ONCE([gl_LOCALE_H] AC_REQUIRE([gl_STDDEF_H]) AC_REQUIRE([gl_LOCALE_T]) + dnl On native Windows, there is a type '_locale_t' that can be used to + dnl define locale_t. + AC_CACHE_CHECK([whether locale.h defines _locale_t], + [gl_cv_header_locale_has_windows_locale_t], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[#include <locale.h> + _locale_t x;]], + [[]])], + [gl_cv_header_locale_has_windows_locale_t=yes], + [gl_cv_header_locale_has_windows_locale_t=no]) + ]) + if test $gl_cv_header_locale_has_windows_locale_t = yes; then + HAVE_WINDOWS_LOCALE_T=1 + AC_DEFINE([HAVE_WINDOWS_LOCALE_T], [1], + [Define to 1 if <locale.h> defines the _locale_t type.]) + else + HAVE_WINDOWS_LOCALE_T=0 + fi + AC_SUBST([HAVE_WINDOWS_LOCALE_T]) dnl Solaris 11.0 defines the int_p_*, int_n_* members of 'struct lconv' dnl only if _LCONV_C99 is defined. @@ -131,6 +151,7 @@ AC_DEFUN([gl_LOCALE_T] fi fi AC_SUBST([HAVE_XLOCALE_H]) + AC_SUBST([HAVE_LOCALE_T]) ]) # gl_LOCALE_MODULE_INDICATOR([modulename]) diff --git a/modules/locale-h b/modules/locale-h index c81bd908d7..5dab6ab42a 100644 --- a/modules/locale-h +++ b/modules/locale-h @@ -13,6 +13,7 @@ snippet/arg-nonnull snippet/c++defs snippet/warn-on-use stddef-h +bool configure.ac: gl_LOCALE_H @@ -32,6 +33,8 @@ locale.h: locale.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H -e 's|@''PRAGMA_SYSTEM_HEADER''@|@PRAGMA_SYSTEM_HEADER@|g' \ -e 's|@''PRAGMA_COLUMNS''@|@PRAGMA_COLUMNS@|g' \ -e 's|@''NEXT_LOCALE_H''@|$(NEXT_LOCALE_H)|g' \ + -e 's|@''HAVE_LOCALE_T''@|$(HAVE_LOCALE_T)|g' \ + -e 's|@''HAVE_WINDOWS_LOCALE_T''@|$(HAVE_WINDOWS_LOCALE_T)|g' \ -e 's/@''GNULIB_LOCALECONV''@/$(GNULIB_LOCALECONV)/g' \ -e 's/@''GNULIB_SETLOCALE''@/$(GNULIB_SETLOCALE)/g' \ -e 's/@''GNULIB_SETLOCALE_NULL''@/$(GNULIB_SETLOCALE_NULL)/g' \ diff --git a/tests/test-locale-h.c b/tests/test-locale-h.c index ba5bdf0ef1..201cd12525 100644 --- a/tests/test-locale-h.c +++ b/tests/test-locale-h.c @@ -30,9 +30,21 @@ int a[] = LC_NUMERIC, LC_TIME }; +int m[] = + { + LC_ALL_MASK, + LC_COLLATE_MASK, + LC_CTYPE_MASK, + LC_MESSAGES_MASK, + LC_MONETARY_MASK, + LC_NUMERIC_MASK, + LC_TIME_MASK + }; /* Check that the 'struct lconv' type is defined. */ struct lconv l; +/* Check that the 'locale_t' type is defined. */ +locale_t lt; int ls; /* Check that NULL can be passed through varargs as a pointer type, @@ -42,11 +54,9 @@ static_assert (sizeof NULL == sizeof (void *)); int main () { -#if HAVE_WORKING_NEWLOCALE /* Check that the locale_t type and the LC_GLOBAL_LOCALE macro are defined. */ locale_t b = LC_GLOBAL_LOCALE; (void) b; -#endif /* Check that 'struct lconv' has the ISO C and POSIX specified members. */ ls += sizeof (*l.decimal_point);