These patches work around two platform-specific bugs of newlocale().

2025-02-14  Bruno Haible  <br...@clisp.org>

        newlocale: Work around NetBSD bug.
        * lib/newlocale.c (newlocale) [NetBSD]: Test whether the locale name is
        valid; fail with error ENOENT if not.
        * doc/posix-functions/newlocale.texi: Mention the NetBSD bug.

2025-02-14  Bruno Haible  <br...@clisp.org>

        newlocale: Work around macOS, NetBSD, Solaris 11 OpenIndiana bug.
        * m4/newlocale.m4 (gl_FUNC_NEWLOCALE): Test for the "null base" bug.
        Set REPLACE_NEWLOCALE to 1 if it has the bug.
        * lib/newlocale.c (newlocale): Add alternative implementation that uses
        the system's newlocale().
        * modules/newlocale (configure.ac): Consider REPLACE_NEWLOCALE.
        * tests/test-newlocale.c: Include <langinfo.h>.
        (main): Verify fix for the "null base" bug.
        * modules/newlocale-tests (configure.ac): Test for nl_langinfo_l.
        * doc/posix-functions/newlocale.texi: Mention the "null base" bug.

2025-02-14  Bruno Haible  <br...@clisp.org>

        newlocale, freelocale: Tweak configuration.
        * m4/newlocale.m4 (gl_FUNC_NEWLOCALE): Make consistent with
        m4/localename.m4.
        * m4/freelocale.m4 (gl_FUNC_FREELOCALE): Likewise.

>From e8618f66fa25932f1121632b52044abb27a6ecec Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 14 Feb 2025 14:32:30 +0100
Subject: [PATCH 1/6] newlocale, freelocale: Tweak configuration.

* m4/newlocale.m4 (gl_FUNC_NEWLOCALE): Make consistent with
m4/localename.m4.
* m4/freelocale.m4 (gl_FUNC_FREELOCALE): Likewise.
---
 ChangeLog        |  7 +++++++
 m4/freelocale.m4 | 18 +++++++++++++++---
 m4/newlocale.m4  | 18 +++++++++++++++---
 3 files changed, 37 insertions(+), 6 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 50b6a1daf3..03cc2df88b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2025-02-14  Bruno Haible  <br...@clisp.org>
+
+	newlocale, freelocale: Tweak configuration.
+	* m4/newlocale.m4 (gl_FUNC_NEWLOCALE): Make consistent with
+	m4/localename.m4.
+	* m4/freelocale.m4 (gl_FUNC_FREELOCALE): Likewise.
+
 2025-02-14  Bruno Haible  <br...@clisp.org>
 
 	duplocale: Support all platforms.
diff --git a/m4/freelocale.m4 b/m4/freelocale.m4
index 61fc0a93bd..5c6a144a74 100644
--- a/m4/freelocale.m4
+++ b/m4/freelocale.m4
@@ -1,5 +1,5 @@
 # freelocale.m4
-# serial 1
+# serial 2
 dnl Copyright (C) 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,
@@ -9,9 +9,21 @@
 AC_DEFUN([gl_FUNC_FREELOCALE],
 [
   AC_REQUIRE([gl_LOCALE_H_DEFAULTS])
-  gl_CHECK_FUNCS_ANDROID([freelocale], [[#include <locale.h>]])
-  if test $ac_cv_func_freelocale = no; then
+  AC_REQUIRE([gl_LOCALE_T])
+  if test $HAVE_LOCALE_T = 1; then
+    gl_CHECK_FUNCS_ANDROID([freelocale], [[#include <locale.h>]])
+    gl_func_freelocale="$ac_cv_func_freelocale"
+  else
+    dnl In 2019, some versions of z/OS lack the locale_t type and have broken
+    dnl newlocale, duplocale, freelocale functions.
+    gl_cv_onwards_func_freelocale='future OS version'
+    gl_func_freelocale=no
+  fi
+  if test $gl_func_freelocale != yes; then
     HAVE_FREELOCALE=0
+    case "$gl_cv_onwards_func_freelocale" in
+      future*) REPLACE_FREELOCALE=1 ;;
+    esac
   fi
 ])
 
diff --git a/m4/newlocale.m4 b/m4/newlocale.m4
index b2475eb16f..13df13808e 100644
--- a/m4/newlocale.m4
+++ b/m4/newlocale.m4
@@ -1,5 +1,5 @@
 # newlocale.m4
-# serial 1
+# serial 2
 dnl Copyright (C) 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,
@@ -9,9 +9,21 @@
 AC_DEFUN([gl_FUNC_NEWLOCALE],
 [
   AC_REQUIRE([gl_LOCALE_H_DEFAULTS])
-  gl_CHECK_FUNCS_ANDROID([newlocale], [[#include <locale.h>]])
-  if test $ac_cv_func_newlocale = no; then
+  AC_REQUIRE([gl_LOCALE_T])
+  if test $HAVE_LOCALE_T = 1; then
+    gl_CHECK_FUNCS_ANDROID([newlocale], [[#include <locale.h>]])
+    gl_func_newlocale="$ac_cv_func_newlocale"
+  else
+    dnl In 2019, some versions of z/OS lack the locale_t type and have broken
+    dnl newlocale, duplocale, freelocale functions.
+    gl_cv_onwards_func_newlocale='future OS version'
+    gl_func_newlocale=no
+  fi
+  if test $gl_func_newlocale != yes; then
     HAVE_NEWLOCALE=0
+    case "$gl_cv_onwards_func_newlocale" in
+      future*) REPLACE_NEWLOCALE=1 ;;
+    esac
   fi
 ])
 
-- 
2.43.0

>From bc2d70ee57c62739cb15d5d06aad6982306d05ff Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 14 Feb 2025 15:24:54 +0100
Subject: [PATCH 2/6] newlocale: Work around macOS, NetBSD, Solaris 11
 OpenIndiana bug.

* m4/newlocale.m4 (gl_FUNC_NEWLOCALE): Test for the "null base" bug.
Set REPLACE_NEWLOCALE to 1 if it has the bug.
* lib/newlocale.c (newlocale): Add alternative implementation that uses
the system's newlocale().
* modules/newlocale (configure.ac): Consider REPLACE_NEWLOCALE.
* tests/test-newlocale.c: Include <langinfo.h>.
(main): Verify fix for the "null base" bug.
* modules/newlocale-tests (configure.ac): Test for nl_langinfo_l.
* doc/posix-functions/newlocale.texi: Mention the "null base" bug.
---
 ChangeLog                          | 13 +++++++
 doc/posix-functions/newlocale.texi |  4 +++
 lib/newlocale.c                    | 55 ++++++++++++++++++++++--------
 m4/newlocale.m4                    | 54 +++++++++++++++++++++++++++--
 modules/newlocale                  |  3 +-
 modules/newlocale-tests            |  1 +
 tests/test-newlocale.c             | 35 ++++++++++++++++---
 7 files changed, 143 insertions(+), 22 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 03cc2df88b..5c52985015 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2025-02-14  Bruno Haible  <br...@clisp.org>
+
+	newlocale: Work around macOS, NetBSD, Solaris 11 OpenIndiana bug.
+	* m4/newlocale.m4 (gl_FUNC_NEWLOCALE): Test for the "null base" bug.
+	Set REPLACE_NEWLOCALE to 1 if it has the bug.
+	* lib/newlocale.c (newlocale): Add alternative implementation that uses
+	the system's newlocale().
+	* modules/newlocale (configure.ac): Consider REPLACE_NEWLOCALE.
+	* tests/test-newlocale.c: Include <langinfo.h>.
+	(main): Verify fix for the "null base" bug.
+	* modules/newlocale-tests (configure.ac): Test for nl_langinfo_l.
+	* doc/posix-functions/newlocale.texi: Mention the "null base" bug.
+
 2025-02-14  Bruno Haible  <br...@clisp.org>
 
 	newlocale, freelocale: Tweak configuration.
diff --git a/doc/posix-functions/newlocale.texi b/doc/posix-functions/newlocale.texi
index a305c3821b..b4dc3661bd 100644
--- a/doc/posix-functions/newlocale.texi
+++ b/doc/posix-functions/newlocale.texi
@@ -16,6 +16,10 @@
 This function is useless because the @code{locale_t} type is not defined
 on some platforms:
 z/OS.
+@item
+When the third argument is NULL, this function uses locale category data
+from the current locale instead of from the "C" locale on some platforms:
+macOS, NetBSD 10.0, Solaris 11 OpenIndiana.
 @end itemize
 
 Portability problems not fixed by Gnulib:
diff --git a/lib/newlocale.c b/lib/newlocale.c
index 01bc2f3953..d5f902b8fb 100644
--- a/lib/newlocale.c
+++ b/lib/newlocale.c
@@ -22,10 +22,33 @@
 #include <locale.h>
 
 #include <errno.h>
-#include <stdlib.h>
-#include <string.h>
 
-#include "localename.h"
+#if HAVE_NEWLOCALE
+/* Only provide workarounds.  */
+
+locale_t
+newlocale (int category_mask, const char *name, locale_t base)
+# undef newlocale
+{
+  if ((category_mask & ~LC_ALL_MASK) != 0)
+    {
+      errno = EINVAL;
+      return NULL;
+    }
+
+  if (category_mask != LC_ALL_MASK && base == NULL)
+    base = newlocale (LC_ALL_MASK, "C", NULL);
+
+  return newlocale (category_mask, name, base);
+}
+
+#else
+/* Implement from scratch.  */
+
+# include <stdlib.h>
+# include <string.h>
+
+# include "localename.h"
 
 locale_t
 newlocale (int category_mask, const char *name, locale_t base)
@@ -57,7 +80,7 @@ newlocale (int category_mask, const char *name, locale_t base)
   if (strcmp (name, "POSIX") == 0)
     name = "C";
 
-#if !HAVE_WINDOWS_LOCALE_T
+# if !HAVE_WINDOWS_LOCALE_T
   /* In this case, the only NAMEs that we support are "C" and (equivalently)
      "POSIX".  */
   if (category_mask != 0 && strcmp (name, "C") != 0)
@@ -65,7 +88,7 @@ newlocale (int category_mask, const char *name, locale_t base)
       errno = ENOENT;
       return NULL;
     }
-#endif
+# endif
 
   int i;
   int err;
@@ -111,15 +134,15 @@ newlocale (int category_mask, const char *name, locale_t base)
           if (strcmp (lcname, "C") == 0)
             {
               result->category[i].is_c_locale = true;
-#if HAVE_WINDOWS_LOCALE_T
+# if HAVE_WINDOWS_LOCALE_T
               /* Just to initialize it.  */
               result->category[i].system_locale = NULL;
-#endif
+# endif
             }
           else
             {
               result->category[i].is_c_locale = false;
-#if HAVE_WINDOWS_LOCALE_T
+# if HAVE_WINDOWS_LOCALE_T
               if (log2_lcmask == gl_log2_lc_mask (LC_MESSAGES))
                 result->category[i].system_locale = NULL;
               else
@@ -137,7 +160,7 @@ newlocale (int category_mask, const char *name, locale_t base)
                       goto fail_with_err;
                     }
                 }
-#endif
+# endif
             }
         }
       else
@@ -151,10 +174,10 @@ newlocale (int category_mask, const char *name, locale_t base)
                   goto fail_with_err;
                 }
               result->category[i].is_c_locale = true;
-#if HAVE_WINDOWS_LOCALE_T
+# if HAVE_WINDOWS_LOCALE_T
               /* Just to initialize it.  */
               result->category[i].system_locale = NULL;
-#endif
+# endif
             }
         }
     }
@@ -168,13 +191,13 @@ newlocale (int category_mask, const char *name, locale_t base)
           int log2_lcmask = gl_index_to_log2_lcmask (i);
           if ((category_mask & (1 << log2_lcmask)) != 0)
             {
-#if HAVE_WINDOWS_LOCALE_T
+# if HAVE_WINDOWS_LOCALE_T
               if (!(i == gl_log2_lcmask_to_index (gl_log2_lc_mask (LC_MESSAGES))
                     || base->category[i].is_c_locale))
                 /* Documentation:
                    <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/free-locale>  */
                 _free_locale (base->category[i].system_locale);
-#endif
+# endif
               free (base->category[i].name);
 
               base->category[i] = result->category[i];
@@ -188,13 +211,13 @@ newlocale (int category_mask, const char *name, locale_t base)
  fail_with_err:
   while (--i >= 0)
     {
-#if HAVE_WINDOWS_LOCALE_T
+# if HAVE_WINDOWS_LOCALE_T
       if (!(i == gl_log2_lcmask_to_index (gl_log2_lc_mask (LC_MESSAGES))
             || result->category[i].is_c_locale))
         /* Documentation:
            <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/free-locale>  */
         _free_locale (result->category[i].system_locale);
-#endif
+# endif
       free (result->category[i].name);
     }
   if (base == NULL)
@@ -202,3 +225,5 @@ newlocale (int category_mask, const char *name, locale_t base)
   errno = err;
   return NULL;
 }
+
+#endif
diff --git a/m4/newlocale.m4 b/m4/newlocale.m4
index 13df13808e..2775609ee6 100644
--- a/m4/newlocale.m4
+++ b/m4/newlocale.m4
@@ -1,5 +1,5 @@
 # newlocale.m4
-# serial 2
+# serial 3
 dnl Copyright (C) 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,
@@ -19,7 +19,57 @@ AC_DEFUN([gl_FUNC_NEWLOCALE]
     gl_cv_onwards_func_newlocale='future OS version'
     gl_func_newlocale=no
   fi
-  if test $gl_func_newlocale != yes; then
+  if test $gl_func_newlocale = yes; then
+    dnl Check against the macOS, NetBSD, Solaris 11 OpenIndiana bug:
+    dnl When the third argument is NULL, newlocale() uses locale category data
+    dnl from the current locale instead of from the "C" locale.
+    gl_CHECK_FUNCS_ANDROID([nl_langinfo_l], [[#include <langinfo.h>]])
+    if test $ac_cv_func_nl_langinfo_l = yes; then
+      AC_REQUIRE([AC_CANONICAL_HOST])
+      AC_CACHE_CHECK([whether newlocale with a null base works],
+        [gl_cv_func_newlocale_works],
+        [dnl Prepare a guess, used when cross-compiling or when specific locales
+         dnl are not available.
+         case "$host_os" in
+           darwin* | netbsd* | solaris*)
+             gl_cv_func_newlocale_works="guessing no" ;;
+           *)
+             gl_cv_func_newlocale_works="guessing yes" ;;
+         esac
+         AC_RUN_IFELSE(
+           [AC_LANG_SOURCE([[
+#include <locale.h>
+#if HAVE_XLOCALE_H
+# include <xlocale.h>
+#endif
+#include <langinfo.h>
+int main ()
+{
+  locale_t l1 = newlocale (LC_TIME_MASK, "en_US.UTF-8", NULL);
+  if (l1 != NULL)
+    {
+      if (setlocale (LC_ALL, "fr_FR.UTF-8") != NULL)
+        {
+          locale_t l1a = newlocale (LC_TIME_MASK, "en_US.UTF-8", NULL);
+          const char *radixchar1a = nl_langinfo_l (RADIXCHAR, l1a);
+          return (*radixchar1a != '.');
+        }
+    }
+  return 2;
+}]])],
+           [gl_cv_func_newlocale_works=yes],
+           [if test $? = 1; then
+              gl_cv_func_newlocale_works=no
+            fi
+           ],
+           [])
+        ])
+      case "$gl_cv_func_newlocale_works" in
+        *yes) ;;
+        *) REPLACE_NEWLOCALE=1 ;;
+      esac
+    fi
+  else
     HAVE_NEWLOCALE=0
     case "$gl_cv_onwards_func_newlocale" in
       future*) REPLACE_NEWLOCALE=1 ;;
diff --git a/modules/newlocale b/modules/newlocale
index 78a10436fa..04f6df28f9 100644
--- a/modules/newlocale
+++ b/modules/newlocale
@@ -11,7 +11,8 @@ localename-environ
 
 configure.ac:
 gl_FUNC_NEWLOCALE
-gl_CONDITIONAL([GL_COND_OBJ_NEWLOCALE], [test $HAVE_LOCALE_T = 0])
+gl_CONDITIONAL([GL_COND_OBJ_NEWLOCALE],
+               [test $HAVE_LOCALE_T = 0 || { test $REPLACE_NEWLOCALE = 1 && test "$gt_localename_enhances_locale_funcs" != yes; }])
 AM_COND_IF([GL_COND_OBJ_NEWLOCALE], [
   gl_PREREQ_NEWLOCALE
 ])
diff --git a/modules/newlocale-tests b/modules/newlocale-tests
index 706003b4f2..6876456784 100644
--- a/modules/newlocale-tests
+++ b/modules/newlocale-tests
@@ -6,6 +6,7 @@ tests/macros.h
 Depends-on:
 
 configure.ac:
+gl_CHECK_FUNCS_ANDROID([nl_langinfo_l], [[#include <langinfo.h>]])
 
 Makefile.am:
 TESTS += test-newlocale
diff --git a/tests/test-newlocale.c b/tests/test-newlocale.c
index fa21f8b697..a6829d7154 100644
--- a/tests/test-newlocale.c
+++ b/tests/test-newlocale.c
@@ -23,6 +23,10 @@
 #include "signature.h"
 SIGNATURE_CHECK (newlocale, locale_t, (int, const char *, locale_t));
 
+#if HAVE_NL_LANGINFO_L
+# include <langinfo.h>
+#endif
+
 #include "macros.h"
 
 #if defined _WIN32 && !defined __CYGWIN__
@@ -47,10 +51,33 @@ SIGNATURE_CHECK (newlocale, locale_t, (int, const char *, locale_t));
 int
 main ()
 {
-  locale_t l1 = newlocale (LC_TIME_MASK, LOCALE1, NULL);
-  locale_t l2 = newlocale (LC_MESSAGES_MASK, LOCALE2, l1);
-  locale_t l3 = newlocale (LC_TIME_MASK, LOCALE3, l2);
-  (void) l3;
+  {
+    locale_t l1 = newlocale (LC_TIME_MASK, LOCALE1, NULL);
+    locale_t l2 = newlocale (LC_MESSAGES_MASK, LOCALE2, l1);
+    locale_t l3 = newlocale (LC_TIME_MASK, LOCALE3, l2);
+    (void) l3;
+  }
+
+#if HAVE_NL_LANGINFO_L
+  /* Verify that when the base argument is NULL, "the data for all sections
+     not requested by category_mask shall be taken from the POSIX locale".  */
+  {
+    locale_t l1 = newlocale (LC_TIME_MASK, LOCALE1, NULL);
+    if (l1 != NULL)
+      {
+        const char *radixchar1 = nl_langinfo_l (RADIXCHAR, l1);
+        ASSERT (*radixchar1 == '.');
+        if (setlocale (LC_ALL, LOCALE2) != NULL)
+          {
+            radixchar1 = nl_langinfo_l (RADIXCHAR, l1);
+            ASSERT (*radixchar1 == '.');
+            locale_t l1a = newlocale (LC_TIME_MASK, LOCALE1, NULL);
+            const char *radixchar1a = nl_langinfo_l (RADIXCHAR, l1a);
+            ASSERT (*radixchar1a == '.');
+          }
+      }
+  }
+#endif
 
   return test_exit_status;
 }
-- 
2.43.0

>From 49564bc5410d4480a847a772628eec176fc68582 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 14 Feb 2025 15:42:22 +0100
Subject: [PATCH 3/6] newlocale: Work around NetBSD bug.

* lib/newlocale.c (newlocale) [NetBSD]: Test whether the locale name is
valid; fail with error ENOENT if not.
* doc/posix-functions/newlocale.texi: Mention the NetBSD bug.
---
 ChangeLog                          |  7 +++++++
 doc/posix-functions/newlocale.texi |  4 ++++
 lib/newlocale.c                    | 30 ++++++++++++++++++++++++++++++
 3 files changed, 41 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index 5c52985015..3d8d3bb3f0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2025-02-14  Bruno Haible  <br...@clisp.org>
+
+	newlocale: Work around NetBSD bug.
+	* lib/newlocale.c (newlocale) [NetBSD]: Test whether the locale name is
+	valid; fail with error ENOENT if not.
+	* doc/posix-functions/newlocale.texi: Mention the NetBSD bug.
+
 2025-02-14  Bruno Haible  <br...@clisp.org>
 
 	newlocale: Work around macOS, NetBSD, Solaris 11 OpenIndiana bug.
diff --git a/doc/posix-functions/newlocale.texi b/doc/posix-functions/newlocale.texi
index b4dc3661bd..54af40a76f 100644
--- a/doc/posix-functions/newlocale.texi
+++ b/doc/posix-functions/newlocale.texi
@@ -20,6 +20,10 @@
 When the third argument is NULL, this function uses locale category data
 from the current locale instead of from the "C" locale on some platforms:
 macOS, NetBSD 10.0, Solaris 11 OpenIndiana.
+@item
+When the second argument is an invalid or unsupported locale name,
+this function uses the "C" locale instead of failing on some platforms:
+NetBSD 10.0.
 @end itemize
 
 Portability problems not fixed by Gnulib:
diff --git a/lib/newlocale.c b/lib/newlocale.c
index d5f902b8fb..ccce715852 100644
--- a/lib/newlocale.c
+++ b/lib/newlocale.c
@@ -26,6 +26,9 @@
 #if HAVE_NEWLOCALE
 /* Only provide workarounds.  */
 
+# include <sys/types.h>
+# include <sys/stat.h>
+
 locale_t
 newlocale (int category_mask, const char *name, locale_t base)
 # undef newlocale
@@ -36,6 +39,33 @@ newlocale (int category_mask, const char *name, locale_t base)
       return NULL;
     }
 
+# if defined __NetBSD__
+  /* Work around a NetBSD bug: newlocale does not fail (unlike setlocale)
+     when NAME is an invalid locale name.  */
+  if (category_mask != 0)
+    {
+      /* Test whether NAME is valid.  */
+      if (!(strcmp (name, "C") == 0 || strcmp (name, "POSIX") == 0))
+        {
+          char *filename = (char *) malloc (18 + strlen (name) + 9 + 1);
+          if (filename == NULL)
+            {
+              errno = ENOMEM;
+              return NULL;
+            }
+          sprintf (filename, "/usr/share/locale/%s/LC_CTYPE", name);
+          struct stat statbuf;
+          if (stat (filename, &statbuf) < 0)
+            {
+              free (filename);
+              errno = ENOENT;
+              return NULL;
+            }
+          free (filename);
+        }
+    }
+# endif
+
   if (category_mask != LC_ALL_MASK && base == NULL)
     base = newlocale (LC_ALL_MASK, "C", NULL);
 
-- 
2.43.0

Reply via email to