This patch implements a portable duplocale() function, that matches the
just-added newlocale() function. Part of the code was already there,
for old glibc and old AIX platforms.


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

        duplocale: Support all platforms.
        * lib/locale.in.h (duplocale): Declare also on platforms that don't
        already have a duplocale function. Don't define HAVE_WORKING_DUPLOCALE.
        * lib/duplocale.c: Include <stdlib.h>.
        (duplocale): Renamed from rpl_duplocale. Add implementation for
        platforms without native locale_t.
        * modules/duplocale (Depends-on): Add newlocale, freelocale.
        (configure.ac): Compile also on platforms without native locale_t.
        * tests/test-duplocale.c: Ignore HAVE_WORKING_DUPLOCALE.
        * tests/test-locale-h-c++.cc: Likewise.
        * doc/posix-functions/duplocale.texi: Mention the change.

>From a5ce928de09dad7d429f96d5e23ce59c93bc2039 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 14 Feb 2025 05:56:55 +0100
Subject: [PATCH] duplocale: Support all platforms.

* lib/locale.in.h (duplocale): Declare also on platforms that don't
already have a duplocale function. Don't define HAVE_WORKING_DUPLOCALE.
* lib/duplocale.c: Include <stdlib.h>.
(duplocale): Renamed from rpl_duplocale. Add implementation for
platforms without native locale_t.
* modules/duplocale (Depends-on): Add newlocale, freelocale.
(configure.ac): Compile also on platforms without native locale_t.
* tests/test-duplocale.c: Ignore HAVE_WORKING_DUPLOCALE.
* tests/test-locale-h-c++.cc: Likewise.
* doc/posix-functions/duplocale.texi: Mention the change.
---
 ChangeLog                          | 14 ++++++
 doc/posix-functions/duplocale.texi | 14 +++---
 lib/duplocale.c                    | 79 ++++++++++++++++++++++++++++--
 lib/locale.in.h                    | 28 +++++------
 modules/duplocale                  |  6 ++-
 tests/test-duplocale.c             | 15 ------
 tests/test-locale-h-c++.cc         |  2 +-
 7 files changed, 113 insertions(+), 45 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 712f90be04..50b6a1daf3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2025-02-14  Bruno Haible  <br...@clisp.org>
+
+	duplocale: Support all platforms.
+	* lib/locale.in.h (duplocale): Declare also on platforms that don't
+	already have a duplocale function. Don't define HAVE_WORKING_DUPLOCALE.
+	* lib/duplocale.c: Include <stdlib.h>.
+	(duplocale): Renamed from rpl_duplocale. Add implementation for
+	platforms without native locale_t.
+	* modules/duplocale (Depends-on): Add newlocale, freelocale.
+	(configure.ac): Compile also on platforms without native locale_t.
+	* tests/test-duplocale.c: Ignore HAVE_WORKING_DUPLOCALE.
+	* tests/test-locale-h-c++.cc: Likewise.
+	* doc/posix-functions/duplocale.texi: Mention the change.
+
 2025-02-14  Bruno Haible  <br...@clisp.org>
 
 	freelocale: Fix typo.
diff --git a/doc/posix-functions/duplocale.texi b/doc/posix-functions/duplocale.texi
index 4d5e919b52..efcb847ca6 100644
--- a/doc/posix-functions/duplocale.texi
+++ b/doc/posix-functions/duplocale.texi
@@ -10,6 +10,13 @@
 Portability problems fixed by Gnulib:
 @itemize
 @item
+This function is missing on many platforms:
+FreeBSD 9.0, NetBSD 6.1, OpenBSD 6.1, Minix 3.1.8, AIX 6.1, HP-UX 11, Solaris 11.3, Cygwin 2.5.x, mingw, MSVC 14, Android 4.4.
+@item
+This function is useless because the @code{locale_t} type is not defined
+on some platforms:
+z/OS.
+@item
 The argument @code{LC_GLOBAL_LOCALE} is not supported on some platforms:
 glibc 2.11, AIX 7.1.
 @item
@@ -21,13 +28,6 @@
 Portability problems not fixed by Gnulib:
 @itemize
 @item
-This function is missing on many platforms:
-FreeBSD 9.0, NetBSD 5.0, OpenBSD 6.1, Minix 3.1.8, AIX 6.1, HP-UX 11, Solaris 11.3, Cygwin 2.5.x, mingw, MSVC 14, Android 4.4.
-@item
-This function is useless because the @code{locale_t} type is not defined
-on some platforms:
-z/OS.
-@item
 With the argument @code{LC_GLOBAL_LOCALE}, this function returns a wrong result
 on some platforms:
 @c https://dev.haiku-os.org/ticket/18345
diff --git a/lib/duplocale.c b/lib/duplocale.c
index 933c4bbf38..47a626e1e6 100644
--- a/lib/duplocale.c
+++ b/lib/duplocale.c
@@ -22,16 +22,17 @@
 #include <locale.h>
 
 #include <errno.h>
+#include <stdlib.h>
 #include <string.h>
 
 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
 
-#undef duplocale
-
 locale_t
-rpl_duplocale (locale_t locale)
+duplocale (locale_t locale)
+#undef duplocale
 {
-  /* Work around crash in the duplocale function in glibc < 2.12.
+  /* Implement duplocale(LC_GLOBAL_LOCALE) on platforms without locale_t.
+     Also, work around crash in the duplocale function in glibc < 2.12.
      See <https://sourceware.org/bugzilla/show_bug.cgi?id=10969>.
      Also, on AIX 7.1, duplocale(LC_GLOBAL_LOCALE) returns (locale_t)0 with
      errno set to EINVAL.
@@ -113,5 +114,75 @@ rpl_duplocale (locale_t locale)
       return base_copy;
     }
 
+#if GNULIB_defined_locale_t
+
+  locale_t result = (struct gl_locale_t *) malloc (sizeof (struct gl_locale_t));
+  if (result == NULL)
+    {
+      errno = ENOMEM;
+      return NULL;
+    }
+
+  int i;
+  int err;
+  for (i = 0; i < 6; i++)
+    {
+      int log2_lcmask = gl_index_to_log2_lcmask (i);
+
+      result->category[i].name = strdup (locale->category[i].name);
+      if (result->category[i].name == NULL)
+        {
+          err = ENOMEM;
+          goto fail_with_err;
+        }
+      result->category[i].is_c_locale = locale->category[i].is_c_locale;
+# if HAVE_WINDOWS_LOCALE_T
+      if (log2_lcmask == gl_log2_lc_mask (LC_MESSAGES)
+          || result->category[i].is_c_locale)
+        {
+          /* Just to initialize it.  */
+          result->category[i].system_locale = NULL;
+        }
+      else
+        {
+          int cat = log2_lcmask;
+          (void) cat;
+          /* Documentation:
+             <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/create-locale-wcreate-locale>  */
+          result->category[i].system_locale =
+            _create_locale (LC_ALL /* or cat */, result->category[i].name);
+          if (result->category[i].system_locale == NULL)
+            {
+              free (result->category[i].name);
+              err = ENOENT;
+              goto fail_with_err;
+            }
+        }
+# endif
+    }
+
+  /* Success.  */
+  return result;
+
+ fail_with_err:
+  while (--i >= 0)
+    {
+# 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
+      free (result->category[i].name);
+    }
+  free (result);
+  errno = err;
+  return NULL;
+
+#else
+
   return duplocale (locale);
+
+#endif
 }
diff --git a/lib/locale.in.h b/lib/locale.in.h
index 3d4f34f579..7c35b283de 100644
--- a/lib/locale.in.h
+++ b/lib/locale.in.h
@@ -332,31 +332,27 @@ _GL_WARN_ON_USE (newlocale, "newlocale is not portable");
 #endif
 
 #if @GNULIB_DUPLOCALE@ || (@GNULIB_LOCALENAME_UNSAFE@ && @LOCALENAME_ENHANCE_LOCALE_FUNCS@ && @HAVE_DUPLOCALE@)
-# if @HAVE_DUPLOCALE@ /* locale_t may be undefined if !@HAVE_DUPLOCALE@.  */
-#  if @REPLACE_DUPLOCALE@
-#   if !(defined __cplusplus && defined GNULIB_NAMESPACE)
-#    undef duplocale
-#    define duplocale rpl_duplocale
-#    define GNULIB_defined_duplocale 1
-#   endif
+# if @REPLACE_DUPLOCALE@
+#  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+#   undef duplocale
+#   define duplocale rpl_duplocale
+#   define GNULIB_defined_duplocale 1
+#  endif
 _GL_FUNCDECL_RPL (duplocale, locale_t, (locale_t locale), _GL_ARG_NONNULL ((1)));
 _GL_CXXALIAS_RPL (duplocale, locale_t, (locale_t locale));
-#  else
-_GL_CXXALIAS_SYS (duplocale, locale_t, (locale_t locale));
+# else
+#  if !@HAVE_DUPLOCALE@
+_GL_FUNCDECL_SYS (duplocale, locale_t, (locale_t locale), _GL_ARG_NONNULL ((1)));
 #  endif
+_GL_CXXALIAS_SYS (duplocale, locale_t, (locale_t locale));
 # endif
-# if __GLIBC__ >= 2 && @HAVE_DUPLOCALE@
+# if __GLIBC__ >= 2
 _GL_CXXALIASWARN (duplocale);
 # endif
-# if @HAVE_DUPLOCALE@
-#  ifndef HAVE_WORKING_DUPLOCALE
-#   define HAVE_WORKING_DUPLOCALE 1
-#  endif
-# endif
 #elif defined GNULIB_POSIXCHECK
 # undef duplocale
 # if HAVE_RAW_DECL_DUPLOCALE
-_GL_WARN_ON_USE (duplocale, "duplocale is buggy on some glibc systems - "
+_GL_WARN_ON_USE (duplocale, "duplocale is unportable and buggy on some glibc systems - "
                  "use gnulib module duplocale for portability");
 # endif
 #endif
diff --git a/modules/duplocale b/modules/duplocale
index 8ccc964a43..8d34e9476a 100644
--- a/modules/duplocale
+++ b/modules/duplocale
@@ -7,12 +7,14 @@ m4/duplocale.m4
 
 Depends-on:
 locale-h
-setlocale-null  [test $HAVE_DUPLOCALE = 1 && test $REPLACE_DUPLOCALE = 1]
+freelocale      [test $HAVE_LOCALE_T = 0 || { test $HAVE_DUPLOCALE = 1 && test $REPLACE_DUPLOCALE = 1; }]
+newlocale       [test $HAVE_LOCALE_T = 0 || { test $HAVE_DUPLOCALE = 1 && test $REPLACE_DUPLOCALE = 1; }]
+setlocale-null  [test $HAVE_LOCALE_T = 0 || { test $HAVE_DUPLOCALE = 1 && test $REPLACE_DUPLOCALE = 1; }]
 
 configure.ac:
 gl_FUNC_DUPLOCALE
 gl_CONDITIONAL([GL_COND_OBJ_DUPLOCALE],
-               [test $HAVE_DUPLOCALE = 1 && test $REPLACE_DUPLOCALE = 1])
+               [test $HAVE_LOCALE_T = 0 || { test $HAVE_DUPLOCALE = 1 && test $REPLACE_DUPLOCALE = 1; }])
 AM_COND_IF([GL_COND_OBJ_DUPLOCALE], [
   gl_PREREQ_DUPLOCALE
 ])
diff --git a/tests/test-duplocale.c b/tests/test-duplocale.c
index bce2491c0d..cc348441e4 100644
--- a/tests/test-duplocale.c
+++ b/tests/test-duplocale.c
@@ -20,8 +20,6 @@
 
 #include <locale.h>
 
-#if HAVE_WORKING_DUPLOCALE
-
 #include "signature.h"
 SIGNATURE_CHECK (duplocale, locale_t, (locale_t));
 
@@ -234,16 +232,3 @@ main ()
 
   return test_exit_status;
 }
-
-#else
-
-#include <stdio.h>
-
-int
-main ()
-{
-  fprintf (stderr, "Skipping test: function duplocale not available\n");
-  return 77;
-}
-
-#endif
diff --git a/tests/test-locale-h-c++.cc b/tests/test-locale-h-c++.cc
index f2bcfaa71d..5a6a2a195b 100644
--- a/tests/test-locale-h-c++.cc
+++ b/tests/test-locale-h-c++.cc
@@ -36,7 +36,7 @@ SIGNATURE_CHECK (GNULIB_NAMESPACE::setlocale, char *, (int, const char *));
 SIGNATURE_CHECK (GNULIB_NAMESPACE::newlocale, locale_t, (int, const char *, locale_t));
 #endif
 
-#if GNULIB_TEST_DUPLOCALE && HAVE_WORKING_DUPLOCALE
+#if GNULIB_TEST_DUPLOCALE
 SIGNATURE_CHECK (GNULIB_NAMESPACE::duplocale, locale_t, (locale_t));
 #endif
 
-- 
2.43.0

Reply via email to