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);




Reply via email to