These patches introduce a module 'getlocalename_l-simple', that implement
the simple case (category != LC_ALL) of the POSIX:2024 getlocalename_l
function.

Most of the code was already present in the 'localename-unsafe' module
and is merely moved to a different source file. New code is only added
for glibc, NetBSD, OpenBSD, Android.


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

        getlocalename_l-simple: Add tests.
        * tests/test-getlocalename_l.c: New file.
        * modules/getlocalename_l-simple-tests: New file.

        getlocalename_l-simple: New module.
        * lib/locale.in.h (newlocale, duplocale, freelocale): Now enabled by
        module 'getlocalename_l-simple'.
        (getlocalename_l): New declaration.
        * lib/getlocalename_l.c: New file, based on lib/localename-unsafe.c,
        with modifications for glibc systems, NetBSD, OpenBSD, Android.
        * lib/localename-table.h (LCMIN): New macro.
        * lib/struniq.h: Update a comment.
        * lib/localename-unsafe.c: Don't define the
        LOCALENAME_ENHANCE_LOCALE_FUNCS overrides here. Moved to
        lib/getlocalename_l.c.
        (gl_locale_name_thread_unsafe): Invoke getlocalename_l. Previous code
        moved to lib/getlocalename_l.c.
        * m4/intl-thread-locale.m4 (gt_INTL_THREAD_LOCALE_NAME): Improve support
        for OpenBSD: Set gt_nameless_locales to yes and
        gt_localename_enhances_locale_funcs to yes also when $gt_fake_locales is
        yes.
        * m4/getlocalename_l.m4: New file, based on m4/localename.m4.
        * m4/localename.m4 (gl_LOCALENAME_UNSAFE, gl_LOCALENAME_UNSAFE_LIMITED):
        Remove code that was moved to m4/getlocalename_l.m4.
        * m4/locale_h.m4 (gl_LOCALE_H): Test whether getlocalename_l is
        declared.
        (gl_LOCALE_H_REQUIRE_DEFAULTS): Initialize GNULIB_GETLOCALENAME_L.
        (gl_LOCALE_H_DEFAULTS): Initialize HAVE_GETLOCALENAME_L.
        * modules/locale-h (Makefile.am): Substitute GNULIB_GETLOCALENAME_L,
        HAVE_GETLOCALENAME_L.
        * modules/getlocalename_l-simple: New file.
        * modules/localename-unsafe (Files): Remove lib/localename-table.h,
        lib/localename-table.c, lib/struniq.h.
        (Depends-on): Add getlocalename_l-simple. Remove bool, flexmember,
        free-posix, langinfo-h, thread-optim.
        (Makefile.am): Don't compile localename-table.c.
        * modules/localename-unsafe-limited (Depends-on): Add
        getlocalename_l-simple.
        * modules/newlocale (Link): New section.
        * modules/duplocale (Link): Link with $(GETLOCALENAME_L_LIB).
        * modules/freelocale (Link): New section.
        * modules/newlocale-tests (Makefile.am): Link the test program with
        $(GETLOCALENAME_L_LIB).
        * modules/duplocale-tests (Makefile.am): Likewise.
        * modules/freelocale-tests (Makefile.am): Likewise.
        * modules/is*_l-tests (Makefile.am): Likewise.
        * modules/tolower_l-tests (Makefile.am): Likewise.
        * modules/toupper_l-tests (Makefile.am): Likewise.
        * modules/strcasecmp_l-tests (Makefile.am): Likewise.
        * modules/strncasecmp_l-tests (Makefile.am): Likewise.
        * modules/strerror_l-tests (Makefile.am): Likewise.
        * doc/posix-functions/getlocalename_l.texi: Mention the new module.

From 706178a93b611cc47d305c92c9844e56503e6d5a Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 21 Feb 2025 11:25:51 +0100
Subject: [PATCH 1/2] getlocalename_l-simple: New module.

* lib/locale.in.h (newlocale, duplocale, freelocale): Now enabled by
module 'getlocalename_l-simple'.
(getlocalename_l): New declaration.
* lib/getlocalename_l.c: New file, based on lib/localename-unsafe.c,
with modifications for glibc systems, NetBSD, OpenBSD, Android.
* lib/localename-table.h (LCMIN): New macro.
* lib/struniq.h: Update a comment.
* lib/localename-unsafe.c: Don't define the
LOCALENAME_ENHANCE_LOCALE_FUNCS overrides here. Moved to
lib/getlocalename_l.c.
(gl_locale_name_thread_unsafe): Invoke getlocalename_l. Previous code
moved to lib/getlocalename_l.c.
* m4/intl-thread-locale.m4 (gt_INTL_THREAD_LOCALE_NAME): Improve support
for OpenBSD: Set gt_nameless_locales to yes and
gt_localename_enhances_locale_funcs to yes also when $gt_fake_locales is
yes.
* m4/getlocalename_l.m4: New file, based on m4/localename.m4.
* m4/localename.m4 (gl_LOCALENAME_UNSAFE, gl_LOCALENAME_UNSAFE_LIMITED):
Remove code that was moved to m4/getlocalename_l.m4.
* m4/locale_h.m4 (gl_LOCALE_H): Test whether getlocalename_l is
declared.
(gl_LOCALE_H_REQUIRE_DEFAULTS): Initialize GNULIB_GETLOCALENAME_L.
(gl_LOCALE_H_DEFAULTS): Initialize HAVE_GETLOCALENAME_L.
* modules/locale-h (Makefile.am): Substitute GNULIB_GETLOCALENAME_L,
HAVE_GETLOCALENAME_L.
* modules/getlocalename_l-simple: New file.
* modules/localename-unsafe (Files): Remove lib/localename-table.h,
lib/localename-table.c, lib/struniq.h.
(Depends-on): Add getlocalename_l-simple. Remove bool, flexmember,
free-posix, langinfo-h, thread-optim.
(Makefile.am): Don't compile localename-table.c.
* modules/localename-unsafe-limited (Depends-on): Add
getlocalename_l-simple.
* modules/newlocale (Link): New section.
* modules/duplocale (Link): Link with $(GETLOCALENAME_L_LIB).
* modules/freelocale (Link): New section.
* modules/newlocale-tests (Makefile.am): Link the test program with
$(GETLOCALENAME_L_LIB).
* modules/duplocale-tests (Makefile.am): Likewise.
* modules/freelocale-tests (Makefile.am): Likewise.
* modules/is*_l-tests (Makefile.am): Likewise.
* modules/tolower_l-tests (Makefile.am): Likewise.
* modules/toupper_l-tests (Makefile.am): Likewise.
* modules/strcasecmp_l-tests (Makefile.am): Likewise.
* modules/strncasecmp_l-tests (Makefile.am): Likewise.
* modules/strerror_l-tests (Makefile.am): Likewise.
* doc/posix-functions/getlocalename_l.texi: Mention the new module.
---
 ChangeLog                                |  51 ++
 doc/posix-functions/getlocalename_l.texi |  11 +-
 lib/getlocalename_l.c                    | 660 +++++++++++++++++++++++
 lib/locale.in.h                          |  24 +-
 lib/localename-table.h                   |   5 +-
 lib/localename-unsafe.c                  | 570 +-------------------
 lib/struniq.h                            |   2 +-
 m4/getlocalename_l.m4                    |  76 +++
 m4/intl-thread-locale.m4                 |  23 +-
 m4/locale_h.m4                           |   6 +-
 m4/localename.m4                         |  46 +-
 modules/duplocale                        |   2 +-
 modules/duplocale-tests                  |   2 +-
 modules/freelocale                       |   3 +
 modules/freelocale-tests                 |   1 +
 modules/getlocalename_l-simple           |  47 ++
 modules/isalnum_l-tests                  |   1 +
 modules/isalpha_l-tests                  |   1 +
 modules/isblank_l-tests                  |   1 +
 modules/iscntrl_l-tests                  |   1 +
 modules/isdigit_l-tests                  |   1 +
 modules/isgraph_l-tests                  |   1 +
 modules/islower_l-tests                  |   1 +
 modules/isprint_l-tests                  |   1 +
 modules/ispunct_l-tests                  |   1 +
 modules/isspace_l-tests                  |   1 +
 modules/isupper_l-tests                  |   1 +
 modules/isxdigit_l-tests                 |   1 +
 modules/locale-h                         |   2 +
 modules/localename-unsafe                |  11 +-
 modules/localename-unsafe-limited        |   1 +
 modules/newlocale                        |   3 +
 modules/newlocale-tests                  |   1 +
 modules/strcasecmp_l-tests               |   1 +
 modules/strerror_l-tests                 |   1 +
 modules/strncasecmp_l-tests              |   1 +
 modules/tolower_l-tests                  |   1 +
 modules/toupper_l-tests                  |   1 +
 38 files changed, 917 insertions(+), 647 deletions(-)
 create mode 100644 lib/getlocalename_l.c
 create mode 100644 m4/getlocalename_l.m4
 create mode 100644 modules/getlocalename_l-simple

diff --git a/ChangeLog b/ChangeLog
index 14546b8227..cc692df338 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,54 @@
+2025-02-21  Bruno Haible  <br...@clisp.org>
+
+	getlocalename_l-simple: New module.
+	* lib/locale.in.h (newlocale, duplocale, freelocale): Now enabled by
+	module 'getlocalename_l-simple'.
+	(getlocalename_l): New declaration.
+	* lib/getlocalename_l.c: New file, based on lib/localename-unsafe.c,
+	with modifications for glibc systems, NetBSD, OpenBSD, Android.
+	* lib/localename-table.h (LCMIN): New macro.
+	* lib/struniq.h: Update a comment.
+	* lib/localename-unsafe.c: Don't define the
+	LOCALENAME_ENHANCE_LOCALE_FUNCS overrides here. Moved to
+	lib/getlocalename_l.c.
+	(gl_locale_name_thread_unsafe): Invoke getlocalename_l. Previous code
+	moved to lib/getlocalename_l.c.
+	* m4/intl-thread-locale.m4 (gt_INTL_THREAD_LOCALE_NAME): Improve support
+	for OpenBSD: Set gt_nameless_locales to yes and
+	gt_localename_enhances_locale_funcs to yes also when $gt_fake_locales is
+	yes.
+	* m4/getlocalename_l.m4: New file, based on m4/localename.m4.
+	* m4/localename.m4 (gl_LOCALENAME_UNSAFE, gl_LOCALENAME_UNSAFE_LIMITED):
+	Remove code that was moved to m4/getlocalename_l.m4.
+	* m4/locale_h.m4 (gl_LOCALE_H): Test whether getlocalename_l is
+	declared.
+	(gl_LOCALE_H_REQUIRE_DEFAULTS): Initialize GNULIB_GETLOCALENAME_L.
+	(gl_LOCALE_H_DEFAULTS): Initialize HAVE_GETLOCALENAME_L.
+	* modules/locale-h (Makefile.am): Substitute GNULIB_GETLOCALENAME_L,
+	HAVE_GETLOCALENAME_L.
+	* modules/getlocalename_l-simple: New file.
+	* modules/localename-unsafe (Files): Remove lib/localename-table.h,
+	lib/localename-table.c, lib/struniq.h.
+	(Depends-on): Add getlocalename_l-simple. Remove bool, flexmember,
+	free-posix, langinfo-h, thread-optim.
+	(Makefile.am): Don't compile localename-table.c.
+	* modules/localename-unsafe-limited (Depends-on): Add
+	getlocalename_l-simple.
+	* modules/newlocale (Link): New section.
+	* modules/duplocale (Link): Link with $(GETLOCALENAME_L_LIB).
+	* modules/freelocale (Link): New section.
+	* modules/newlocale-tests (Makefile.am): Link the test program with
+	$(GETLOCALENAME_L_LIB).
+	* modules/duplocale-tests (Makefile.am): Likewise.
+	* modules/freelocale-tests (Makefile.am): Likewise.
+	* modules/is*_l-tests (Makefile.am): Likewise.
+	* modules/tolower_l-tests (Makefile.am): Likewise.
+	* modules/toupper_l-tests (Makefile.am): Likewise.
+	* modules/strcasecmp_l-tests (Makefile.am): Likewise.
+	* modules/strncasecmp_l-tests (Makefile.am): Likewise.
+	* modules/strerror_l-tests (Makefile.am): Likewise.
+	* doc/posix-functions/getlocalename_l.texi: Mention the new module.
+
 2025-02-21  Bruno Haible  <br...@clisp.org>
 
 	setlocale-messages: New module.
diff --git a/doc/posix-functions/getlocalename_l.texi b/doc/posix-functions/getlocalename_l.texi
index 26f8f2ab42..dc7b3ed94c 100644
--- a/doc/posix-functions/getlocalename_l.texi
+++ b/doc/posix-functions/getlocalename_l.texi
@@ -4,15 +4,18 @@
 
 POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9799919799/functions/getlocalename_l.html}
 
-Gnulib module: ---
+Gnulib module: getlocalename_l-simple
+@mindex getlocalename_l-simple
 
 Portability problems fixed by Gnulib:
 @itemize
+@item
+This function is missing on many platforms:
+glibc 2.41, macOS 14, FreeBSD 14.0, NetBSD 10.0, OpenBSD 7.5, Minix 3.3.0, AIX 7.3.1, HP-UX 11.31, Solaris 11.4, Cygwin 3.5.x, mingw, MSVC 14, Android 9.0.
+But the gnulib replacement supports only a single locale category:
+the argument @code{LC_ALL} is unsupported.
 @end itemize
 
 Portability problems not fixed by Gnulib:
 @itemize
-@item
-This function is missing on many platforms:
-glibc 2.41, macOS 14, FreeBSD 14.0, NetBSD 10.0, OpenBSD 7.5, Minix 3.3.0, AIX 7.3.1, HP-UX 11.31, Solaris 11.4, Cygwin 3.5.x, mingw, MSVC 14, Android 9.0.
 @end itemize
diff --git a/lib/getlocalename_l.c b/lib/getlocalename_l.c
new file mode 100644
index 0000000000..4cc63a5a94
--- /dev/null
+++ b/lib/getlocalename_l.c
@@ -0,0 +1,660 @@
+/* Return name of a single locale category.
+   Copyright (C) 1995-2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <locale.h>
+
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "setlocale-messages.h"
+#include "setlocale_null.h"
+
+#if (__GLIBC__ >= 2 && !defined __UCLIBC__) || (defined __linux__ && HAVE_LANGINFO_H) || defined __CYGWIN__
+# include <langinfo.h>
+#endif
+#if defined __sun
+# if HAVE_SOLARIS114_LOCALES
+#  include <sys/localedef.h>
+# endif
+#endif
+#if HAVE_NAMELESS_LOCALES
+# include "localename-table.h"
+#endif
+#if defined __HAIKU__
+# include <dlfcn.h>
+#endif
+
+#include "flexmember.h"
+#include "glthread/lock.h"
+#include "thread-optim.h"
+
+
+/* Define a local struniq() function.  */
+#include "struniq.h"
+
+#if LOCALENAME_ENHANCE_LOCALE_FUNCS
+
+/* The 'locale_t' object does not contain the names of the locale categories.
+   We have to associate them with the object through a hash table.
+   The hash table is defined in localename-table.[hc].  */
+
+/* Returns the name of a given locale category in a given locale_t object,
+   allocated as a string with indefinite extent.  */
+static const char *
+get_locale_t_name (int category, locale_t locale)
+{
+  if (category == LC_ALL)
+    /* Invalid argument.  */
+    abort ();
+  if (locale == LC_GLOBAL_LOCALE)
+    {
+      /* Query the global locale.  */
+      const char *name = setlocale_null (category);
+      if (name != NULL)
+        return struniq (name);
+      else
+        /* Should normally not happen.  */
+        return "";
+    }
+  else
+    {
+# if HAVE_AIX72_LOCALES
+      if (category == LC_MESSAGES)
+        {
+          const char *name = ((__locale_t) locale)->locale_name;
+          if (name != NULL)
+            return struniq (name);
+        }
+# endif
+      /* Look up the names in the hash table.  */
+      size_t hashcode = locale_hash_function (locale);
+      size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE;
+      /* If the locale was not found in the table, return "".  This can
+         happen if the application uses the original newlocale()/duplocale()
+         functions instead of the overridden ones.  */
+      const char *name = "";
+      struct locale_hash_node *p;
+      /* Lock while looking up the hash node.  */
+      gl_rwlock_rdlock (locale_lock);
+      for (p = locale_hash_table[slot]; p != NULL; p = p->next)
+        if (p->locale == locale)
+          {
+            name = p->names.category_name[category - LCMIN];
+            break;
+          }
+      gl_rwlock_unlock (locale_lock);
+      return name;
+    }
+}
+
+# if !(defined newlocale && defined duplocale && defined freelocale)
+#  error "newlocale, duplocale, freelocale not being replaced as expected!"
+# endif
+
+/* newlocale() override.  */
+locale_t
+newlocale (int category_mask, const char *name, locale_t base)
+#undef newlocale
+{
+  struct locale_categories_names names;
+  struct locale_hash_node *node;
+  locale_t result;
+
+  /* Make sure name has indefinite extent.  */
+  if (((LC_CTYPE_MASK | LC_NUMERIC_MASK | LC_TIME_MASK | LC_COLLATE_MASK
+        | LC_MONETARY_MASK | LC_MESSAGES_MASK)
+       & category_mask) != 0)
+    name = struniq (name);
+
+  /* Determine the category names of the result.  */
+  if (((LC_CTYPE_MASK | LC_NUMERIC_MASK | LC_TIME_MASK | LC_COLLATE_MASK
+        | LC_MONETARY_MASK | LC_MESSAGES_MASK)
+       & ~category_mask) == 0)
+    {
+      /* Use name, ignore base.  */
+      int i;
+
+      name = struniq (name);
+      for (i = 0; i < 6; i++)
+        names.category_name[i] = name;
+    }
+  else
+    {
+      /* Use base, possibly also name.  */
+      if (base == NULL)
+        {
+          int i;
+
+          for (i = 0; i < 6; i++)
+            {
+              int category = i + LCMIN;
+              int mask;
+
+              switch (category)
+                {
+                case LC_CTYPE:
+                  mask = LC_CTYPE_MASK;
+                  break;
+                case LC_NUMERIC:
+                  mask = LC_NUMERIC_MASK;
+                  break;
+                case LC_TIME:
+                  mask = LC_TIME_MASK;
+                  break;
+                case LC_COLLATE:
+                  mask = LC_COLLATE_MASK;
+                  break;
+                case LC_MONETARY:
+                  mask = LC_MONETARY_MASK;
+                  break;
+                case LC_MESSAGES:
+                  mask = LC_MESSAGES_MASK;
+                  break;
+                default:
+                  abort ();
+                }
+              names.category_name[i] =
+                ((mask & category_mask) != 0 ? name : "C");
+            }
+        }
+      else if (base == LC_GLOBAL_LOCALE)
+        {
+          int i;
+
+          for (i = 0; i < 6; i++)
+            {
+              int category = i + LCMIN;
+              int mask;
+
+              switch (category)
+                {
+                case LC_CTYPE:
+                  mask = LC_CTYPE_MASK;
+                  break;
+                case LC_NUMERIC:
+                  mask = LC_NUMERIC_MASK;
+                  break;
+                case LC_TIME:
+                  mask = LC_TIME_MASK;
+                  break;
+                case LC_COLLATE:
+                  mask = LC_COLLATE_MASK;
+                  break;
+                case LC_MONETARY:
+                  mask = LC_MONETARY_MASK;
+                  break;
+                case LC_MESSAGES:
+                  mask = LC_MESSAGES_MASK;
+                  break;
+                default:
+                  abort ();
+                }
+              names.category_name[i] =
+                ((mask & category_mask) != 0
+                 ? name
+                 : get_locale_t_name (category, LC_GLOBAL_LOCALE));
+            }
+        }
+      else
+        {
+          /* Look up the names of base in the hash table.  Like multiple calls
+             of get_locale_t_name, but locking only once.  */
+          struct locale_hash_node *p;
+
+          /* Lock while looking up the hash node.  */
+          gl_rwlock_rdlock (locale_lock);
+          for (p = locale_hash_table[locale_hash_function (base) % LOCALE_HASH_TABLE_SIZE];
+               p != NULL;
+               p = p->next)
+            if (p->locale == base)
+              break;
+
+          int i;
+          for (i = 0; i < 6; i++)
+            {
+              int category = i + LCMIN;
+              int mask;
+
+              switch (category)
+                {
+                case LC_CTYPE:
+                  mask = LC_CTYPE_MASK;
+                  break;
+                case LC_NUMERIC:
+                  mask = LC_NUMERIC_MASK;
+                  break;
+                case LC_TIME:
+                  mask = LC_TIME_MASK;
+                  break;
+                case LC_COLLATE:
+                  mask = LC_COLLATE_MASK;
+                  break;
+                case LC_MONETARY:
+                  mask = LC_MONETARY_MASK;
+                  break;
+                case LC_MESSAGES:
+                  mask = LC_MESSAGES_MASK;
+                  break;
+                default:
+                  abort ();
+                }
+              names.category_name[i] =
+                ((mask & category_mask) != 0
+                 ? name
+                 : (p != NULL ? p->names.category_name[i] : ""));
+            }
+
+          gl_rwlock_unlock (locale_lock);
+        }
+    }
+
+  node = (struct locale_hash_node *) malloc (sizeof (struct locale_hash_node));
+  if (node == NULL)
+    /* errno is set to ENOMEM.  */
+    return NULL;
+
+  result = newlocale (category_mask, name, base);
+  if (result == NULL)
+    {
+      free (node);
+      return NULL;
+    }
+
+  /* Fill the hash node.  */
+  node->locale = result;
+  node->names = names;
+
+  /* Insert it in the hash table.  */
+  {
+    size_t hashcode = locale_hash_function (result);
+    size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE;
+    struct locale_hash_node *p;
+
+    /* Lock while inserting the new node.  */
+    gl_rwlock_wrlock (locale_lock);
+    for (p = locale_hash_table[slot]; p != NULL; p = p->next)
+      if (p->locale == result)
+        {
+          /* This can happen if the application uses the original freelocale()
+             function instead of the overridden one.  */
+          p->names = node->names;
+          break;
+        }
+    if (p == NULL)
+      {
+        node->next = locale_hash_table[slot];
+        locale_hash_table[slot] = node;
+      }
+
+    gl_rwlock_unlock (locale_lock);
+
+    if (p != NULL)
+      free (node);
+  }
+
+  return result;
+}
+
+/* duplocale() override.  */
+locale_t
+duplocale (locale_t locale)
+#undef duplocale
+{
+  struct locale_hash_node *node;
+  locale_t result;
+
+  if (locale == NULL)
+    /* Invalid argument.  */
+    abort ();
+
+  node = (struct locale_hash_node *) malloc (sizeof (struct locale_hash_node));
+  if (node == NULL)
+    /* errno is set to ENOMEM.  */
+    return NULL;
+
+  result = duplocale (locale);
+  if (result == NULL)
+    {
+      free (node);
+      return NULL;
+    }
+
+  /* Fill the hash node.  */
+  node->locale = result;
+  if (locale == LC_GLOBAL_LOCALE)
+    {
+      int i;
+
+      for (i = 0; i < 6; i++)
+        {
+          int category = i + LCMIN;
+          node->names.category_name[i] =
+            get_locale_t_name (category, LC_GLOBAL_LOCALE);
+        }
+
+      /* Lock before inserting the new node.  */
+      gl_rwlock_wrlock (locale_lock);
+    }
+  else
+    {
+      struct locale_hash_node *p;
+
+      /* Lock once, for the lookup and the insertion.  */
+      gl_rwlock_wrlock (locale_lock);
+
+      for (p = locale_hash_table[locale_hash_function (locale) % LOCALE_HASH_TABLE_SIZE];
+           p != NULL;
+           p = p->next)
+        if (p->locale == locale)
+          break;
+      if (p != NULL)
+        node->names = p->names;
+      else
+        {
+          /* This can happen if the application uses the original
+             newlocale()/duplocale() functions instead of the overridden
+             ones.  */
+          int i;
+
+          for (i = 0; i < 6; i++)
+            node->names.category_name[i] = "";
+        }
+    }
+
+  /* Insert it in the hash table.  */
+  {
+    size_t hashcode = locale_hash_function (result);
+    size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE;
+    struct locale_hash_node *p;
+
+    for (p = locale_hash_table[slot]; p != NULL; p = p->next)
+      if (p->locale == result)
+        {
+          /* This can happen if the application uses the original freelocale()
+             function instead of the overridden one.  */
+          p->names = node->names;
+          break;
+        }
+    if (p == NULL)
+      {
+        node->next = locale_hash_table[slot];
+        locale_hash_table[slot] = node;
+      }
+
+    gl_rwlock_unlock (locale_lock);
+
+    if (p != NULL)
+      free (node);
+  }
+
+  return result;
+}
+
+/* freelocale() override.  */
+void
+freelocale (locale_t locale)
+#undef freelocale
+{
+  if (locale == NULL || locale == LC_GLOBAL_LOCALE)
+    /* Invalid argument.  */
+    abort ();
+
+  {
+    size_t hashcode = locale_hash_function (locale);
+    size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE;
+    struct locale_hash_node *found;
+    struct locale_hash_node **p;
+
+    found = NULL;
+    /* Lock while removing the hash node.  */
+    gl_rwlock_wrlock (locale_lock);
+    for (p = &locale_hash_table[slot]; *p != NULL; p = &(*p)->next)
+      if ((*p)->locale == locale)
+        {
+          found = *p;
+          *p = (*p)->next;
+          break;
+        }
+    gl_rwlock_unlock (locale_lock);
+    free (found);
+  }
+
+  freelocale (locale);
+}
+
+#endif
+
+
+const char *
+getlocalename_l (int category, locale_t locale)
+{
+  if (category == LC_ALL)
+    /* Unsupported in this simple implementation.  */
+    abort ();
+
+  if (locale != LC_GLOBAL_LOCALE)
+    {
+#if GNULIB_defined_locale_t
+      struct gl_locale_category_t *plc =
+        &locale->category[gl_log2_lcmask_to_index (gl_log2_lc_mask (category))];
+      return plc->name;
+#elif __GLIBC__ >= 2 && !defined __UCLIBC__
+      /* Work around an incorrect definition of the _NL_LOCALE_NAME macro in
+         glibc < 2.12.
+         See <https://sourceware.org/bugzilla/show_bug.cgi?id=10968>.  */
+      const char *name =
+        nl_langinfo_l (_NL_ITEM ((category), _NL_ITEM_INDEX (-1)), locale);
+      if (name[0] == '\0')
+        /* Fallback code for glibc < 2.4, which did not implement
+           nl_langinfo_l (_NL_LOCALE_NAME (category), locale).  */
+        name = locale->__names[category];
+      return name;
+#elif defined __linux__ && HAVE_LANGINFO_H && defined NL_LOCALE_NAME
+      /* musl libc */
+      return nl_langinfo_l (NL_LOCALE_NAME (category), locale);
+#elif (defined __FreeBSD__ || defined __DragonFly__) || (defined __APPLE__ && defined __MACH__)
+      /* FreeBSD >= 9.1, Mac OS X */
+      int mask;
+
+      switch (category)
+        {
+        case LC_CTYPE:
+          mask = LC_CTYPE_MASK;
+          break;
+        case LC_NUMERIC:
+          mask = LC_NUMERIC_MASK;
+          break;
+        case LC_TIME:
+          mask = LC_TIME_MASK;
+          break;
+        case LC_COLLATE:
+          mask = LC_COLLATE_MASK;
+          break;
+        case LC_MONETARY:
+          mask = LC_MONETARY_MASK;
+          break;
+        case LC_MESSAGES:
+          mask = LC_MESSAGES_MASK;
+          break;
+        default: /* We shouldn't get here.  */
+          return "";
+        }
+      return querylocale (mask, locale);
+#elif defined __NetBSD__
+      /* NetBSD >= 7.0 */
+      #define _LOCALENAME_LEN_MAX 33
+      struct _locale {
+        void *cache;
+        char query[_LOCALENAME_LEN_MAX * 6];
+        const char *part_name[7];
+      };
+      return ((struct _locale *) locale)->part_name[category];
+#elif defined __sun
+# if HAVE_SOLARIS114_LOCALES
+      /* Solaris >= 11.4.  */
+      void *lcp = (*locale)->core.data->lcp;
+      if (lcp != NULL)
+        switch (category)
+          {
+          case LC_CTYPE:
+          case LC_NUMERIC:
+          case LC_TIME:
+          case LC_COLLATE:
+          case LC_MONETARY:
+          case LC_MESSAGES:
+            return ((const char * const *) lcp)[category];
+          default: /* We shouldn't get here.  */
+            return "";
+          }
+# else
+      /* Solaris 11 OpenIndiana.
+         For the internal structure of locale objects, see
+         https://github.com/OpenIndiana/illumos-gate/blob/master/usr/src/lib/libc/port/locale/localeimpl.h  */
+      switch (category)
+        {
+        case LC_CTYPE:
+        case LC_NUMERIC:
+        case LC_TIME:
+        case LC_COLLATE:
+        case LC_MONETARY:
+        case LC_MESSAGES:
+          return ((const char * const *) locale)[category];
+        default: /* We shouldn't get here.  */
+          return "";
+        }
+# endif
+#elif HAVE_NAMELESS_LOCALES
+      /* OpenBSD >= 6.2, AIX >= 7.1 */
+      return get_locale_t_name (category, locale);
+#elif defined __OpenBSD__ && HAVE_FAKE_LOCALES
+      /* OpenBSD >= 6.2 has only fake locales.  */
+      if (locale == (locale_t) 2)
+        return "C.UTF-8";
+      return "C";
+#elif defined __CYGWIN__
+      /* Cygwin >= 2.6.
+         Cygwin <= 2.6.1 lacks NL_LOCALE_NAME, requiring peeking inside
+         an opaque struct.  */
+# ifdef NL_LOCALE_NAME
+      return nl_langinfo_l (NL_LOCALE_NAME (category), locale);
+# else
+      /* FIXME: Remove when we can assume new-enough Cygwin.  */
+      struct __locale_t {
+        char categories[7][32];
+      };
+      return ((struct __locale_t *) locale)->categories[category];
+# endif
+#elif defined __HAIKU__
+      /* Since 2022, Haiku has per-thread locales.  locale_t is 'void *',
+         but in fact a 'LocaleBackendData *'.  */
+      struct LocaleBackendData {
+        int magic;
+        void /*BPrivate::Libroot::LocaleBackend*/ *backend;
+        void /*BPrivate::Libroot::LocaleDataBridge*/ *databridge;
+      };
+      void *locale_backend =
+        ((struct LocaleBackendData *) locale)->backend;
+      if (locale_backend != NULL)
+        {
+          /* The only existing concrete subclass of
+             BPrivate::Libroot::LocaleBackend is
+             BPrivate::Libroot::ICULocaleBackend.
+             Invoke the (non-virtual) method
+             BPrivate::Libroot::ICULocaleBackend::_QueryLocale on it.
+             This method is located in a separate shared library,
+             libroot-addon-icu.so.  */
+          static void * volatile querylocale_method /* = NULL */;
+          static int volatile querylocale_found /* = 0 */;
+          /* Attempt to open this shared library, the first time we get
+             here.  */
+          if (querylocale_found == 0)
+            {
+              void *handle =
+                dlopen ("/boot/system/lib/libroot-addon-icu.so", 0);
+              if (handle != NULL)
+                {
+                  void *sym =
+                    dlsym (handle, "_ZN8BPrivate7Libroot16ICULocaleBackend12_QueryLocaleEi");
+                  if (sym != NULL)
+                    {
+                      querylocale_method = sym;
+                      querylocale_found = 1;
+                    }
+                  else
+                    /* Could not find the symbol.  */
+                    querylocale_found = -1;
+                }
+              else
+                /* Could not open the separate shared library.  */
+                querylocale_found = -1;
+            }
+          if (querylocale_found > 0)
+            {
+              /* The _QueryLocale method is a non-static C++ method with
+                 parameters (int category) and return type 'const char *'.
+                 See
+                   haiku/headers/private/libroot/locale/ICULocaleBackend.h
+                   haiku/src/system/libroot/add-ons/icu/ICULocaleBackend.cpp
+                 This is the same as a C function with parameters
+                   (BPrivate::Libroot::LocaleBackend* this, int category)
+                 and return type 'const char *'.  Invoke it.  */
+              const char * (*querylocale_func) (void *, int) =
+                (const char * (*) (void *, int)) querylocale_method;
+              return querylocale_func (locale_backend, category);
+            }
+        }
+      else
+        /* It's the "C" or "POSIX" locale.  */
+        return "C";
+#elif defined __ANDROID__
+      /* Android API level >= 21 */
+      struct __locale_t {
+        size_t mb_cur_max;
+      };
+      return ((struct __locale_t *) locale)->mb_cur_max == 4 ? "C.UTF-8" : "C";
+#else
+ #error "Please port gnulib getlocalename_l.c to your platform! Report this to bug-gnulib."
+#endif
+    }
+  else
+    {
+      /* Query the global locale.  */
+      const char *name;
+#if LC_MESSAGES == 1729
+      if (category == LC_MESSAGES)
+        name = setlocale_messages_null ();
+      else
+#endif
+        name = setlocale_null (category);
+      if (name != NULL)
+        /* Return the result as a string of indefinite extent.
+           This is required because POSIX says that
+             "the returned string pointer might be invalidated or the string
+              content might be overwritten by a subsequent call in the same
+              thread to getlocalename_l() with LC_GLOBAL_LOCALE"
+           and a setlocale_null call in a different thread would also invalidate
+           the result.  */
+        return struniq (name);
+      else
+        /* Should normally not happen.  */
+        return "";
+    }
+}
diff --git a/lib/locale.in.h b/lib/locale.in.h
index 7c35b283de..2b10a6b95d 100644
--- a/lib/locale.in.h
+++ b/lib/locale.in.h
@@ -300,7 +300,7 @@ _GL_WARN_ON_USE (setlocale, "setlocale works differently on native Windows - "
 # include "setlocale_null.h"
 #endif
 
-#if @GNULIB_NEWLOCALE@ || (@GNULIB_LOCALENAME_UNSAFE@ && @LOCALENAME_ENHANCE_LOCALE_FUNCS@ && @HAVE_NEWLOCALE@)
+#if @GNULIB_NEWLOCALE@ || (@GNULIB_GETLOCALENAME_L@ && @LOCALENAME_ENHANCE_LOCALE_FUNCS@ && @HAVE_NEWLOCALE@)
 # if @REPLACE_NEWLOCALE@
 #  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
 #   undef newlocale
@@ -331,7 +331,7 @@ _GL_WARN_ON_USE (newlocale, "newlocale is not portable");
 # endif
 #endif
 
-#if @GNULIB_DUPLOCALE@ || (@GNULIB_LOCALENAME_UNSAFE@ && @LOCALENAME_ENHANCE_LOCALE_FUNCS@ && @HAVE_DUPLOCALE@)
+#if @GNULIB_DUPLOCALE@ || (@GNULIB_GETLOCALENAME_L@ && @LOCALENAME_ENHANCE_LOCALE_FUNCS@ && @HAVE_DUPLOCALE@)
 # if @REPLACE_DUPLOCALE@
 #  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
 #   undef duplocale
@@ -357,7 +357,7 @@ _GL_WARN_ON_USE (duplocale, "duplocale is unportable and buggy on some glibc sys
 # endif
 #endif
 
-#if @GNULIB_FREELOCALE@ || (@GNULIB_LOCALENAME_UNSAFE@ && @LOCALENAME_ENHANCE_LOCALE_FUNCS@ && @HAVE_FREELOCALE@)
+#if @GNULIB_FREELOCALE@ || (@GNULIB_GETLOCALENAME_L@ && @LOCALENAME_ENHANCE_LOCALE_FUNCS@ && @HAVE_FREELOCALE@)
 # if @REPLACE_FREELOCALE@
 #  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
 #   undef freelocale
@@ -384,6 +384,24 @@ _GL_WARN_ON_USE (freelocale, "freelocale is not portable");
 # endif
 #endif
 
+#if @GNULIB_GETLOCALENAME_L@
+# if !@HAVE_GETLOCALENAME_L@
+_GL_FUNCDECL_SYS (getlocalename_l, const char *,
+                  (int category, locale_t locale),
+                  _GL_ARG_NONNULL ((2)));
+# endif
+_GL_CXXALIAS_SYS (getlocalename_l, const char *,
+                  (int category, locale_t locale));
+# if __GLIBC__ >= 2
+_GL_CXXALIASWARN (getlocalename_l);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef getlocalename_l
+# if HAVE_RAW_DECL_GETLOCALENAME_L
+_GL_WARN_ON_USE (getlocalename_l, "getlocalename_l is not portable");
+# endif
+#endif
+
 #endif /* _@GUARD_PREFIX@_LOCALE_H */
 #endif /* _@GUARD_PREFIX@_LOCALE_H */
 #endif /* !(__need_locale_t || _@GUARD_PREFIX@_ALREADY_INCLUDING_LOCALE_H) */
diff --git a/lib/localename-table.h b/lib/localename-table.h
index 3fd11fadbd..c9982be128 100644
--- a/lib/localename-table.h
+++ b/lib/localename-table.h
@@ -23,9 +23,12 @@
 
 # include "glthread/lock.h"
 
+/* Smallest among the six LC_* category values.  */
+# define LCMIN (LC_ALL == 0 ? 1 : 0)
+
 struct locale_categories_names
   {
-    /* Locale category -> name (allocated with indefinite extent).  */
+    /* (Locale category - LCMIN) -> name (allocated with indefinite extent).  */
     const char *category_name[6];
   };
 
diff --git a/lib/localename-unsafe.c b/lib/localename-unsafe.c
index 5b34aaf906..569bf9ba99 100644
--- a/lib/localename-unsafe.c
+++ b/lib/localename-unsafe.c
@@ -29,7 +29,6 @@
 /* Specification.  */
 #include "localename.h"
 
-#include <limits.h>
 #include <stddef.h>
 #include <stdlib.h>
 #include <locale.h>
@@ -42,20 +41,6 @@
 # if defined __APPLE__ && defined __MACH__
 #  include <xlocale.h>
 # endif
-# if (__GLIBC__ >= 2 && !defined __UCLIBC__) || (defined __linux__ && HAVE_LANGINFO_H) || defined __CYGWIN__
-#  include <langinfo.h>
-# endif
-# if defined __sun
-#  if HAVE_SOLARIS114_LOCALES
-#   include <sys/localedef.h>
-#  endif
-# endif
-# if HAVE_NAMELESS_LOCALES
-#  include "localename-table.h"
-# endif
-# if defined __HAIKU__
-#  include <dlfcn.h>
-# endif
 #endif
 
 #if HAVE_CFPREFERENCESCOPYAPPVALUE
@@ -68,12 +53,6 @@
 # include "glthread/lock.h"
 #endif
 
-#if LOCALENAME_ENHANCE_LOCALE_FUNCS
-# include "flexmember.h"
-# include "glthread/lock.h"
-# include "thread-optim.h"
-#endif
-
 #if defined WINDOWS_NATIVE || defined __CYGWIN__ /* Native Windows or Cygwin */
 # define WIN32_LEAN_AND_MEAN
 # include <windows.h>
@@ -2633,396 +2612,6 @@ get_lcid (const char *locale_name)
 #endif
 
 
-#if LOCALENAME_ENHANCE_LOCALE_FUNCS
-
-/* Define a local struniq() function.  */
-# include "struniq.h"
-
-/* The 'locale_t' object does not contain the names of the locale categories.
-   We have to associate them with the object through a hash table.
-   The hash table is defined in localename-table.[hc].  */
-
-/* Returns the name of a given locale category in a given locale_t object,
-   allocated as a string with indefinite extent.  */
-static const char *
-get_locale_t_name (int category, locale_t locale)
-{
-  if (category == LC_ALL)
-    /* Invalid argument.  */
-    abort ();
-  if (locale == LC_GLOBAL_LOCALE)
-    {
-      /* Query the global locale.  */
-      const char *name = setlocale_null (category);
-      if (name != NULL)
-        return struniq (name);
-      else
-        /* Should normally not happen.  */
-        return "";
-    }
-  else
-    {
-# if HAVE_AIX72_LOCALES
-      if (category == LC_MESSAGES)
-        {
-          const char *name = ((__locale_t) locale)->locale_name;
-          if (name != NULL)
-            return struniq (name);
-        }
-# endif
-      /* Look up the names in the hash table.  */
-      size_t hashcode = locale_hash_function (locale);
-      size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE;
-      /* If the locale was not found in the table, return "".  This can
-         happen if the application uses the original newlocale()/duplocale()
-         functions instead of the overridden ones.  */
-      const char *name = "";
-      struct locale_hash_node *p;
-      /* Lock while looking up the hash node.  */
-      gl_rwlock_rdlock (locale_lock);
-      for (p = locale_hash_table[slot]; p != NULL; p = p->next)
-        if (p->locale == locale)
-          {
-            name = p->names.category_name[category];
-            break;
-          }
-      gl_rwlock_unlock (locale_lock);
-      return name;
-    }
-}
-
-# if !(defined newlocale && defined duplocale && defined freelocale)
-#  error "newlocale, duplocale, freelocale not being replaced as expected!"
-# endif
-
-/* newlocale() override.  */
-locale_t
-newlocale (int category_mask, const char *name, locale_t base)
-#undef newlocale
-{
-  struct locale_categories_names names;
-  struct locale_hash_node *node;
-  locale_t result;
-
-  /* Make sure name has indefinite extent.  */
-  if (((LC_CTYPE_MASK | LC_NUMERIC_MASK | LC_TIME_MASK | LC_COLLATE_MASK
-        | LC_MONETARY_MASK | LC_MESSAGES_MASK)
-       & category_mask) != 0)
-    name = struniq (name);
-
-  /* Determine the category names of the result.  */
-  if (((LC_CTYPE_MASK | LC_NUMERIC_MASK | LC_TIME_MASK | LC_COLLATE_MASK
-        | LC_MONETARY_MASK | LC_MESSAGES_MASK)
-       & ~category_mask) == 0)
-    {
-      /* Use name, ignore base.  */
-      int category;
-
-      name = struniq (name);
-      for (category = 0; category < 6; category++)
-        names.category_name[category] = name;
-    }
-  else
-    {
-      /* Use base, possibly also name.  */
-      if (base == NULL)
-        {
-          int category;
-
-          for (category = 0; category < 6; category++)
-            {
-              int mask;
-
-              switch (category)
-                {
-                case LC_CTYPE:
-                  mask = LC_CTYPE_MASK;
-                  break;
-                case LC_NUMERIC:
-                  mask = LC_NUMERIC_MASK;
-                  break;
-                case LC_TIME:
-                  mask = LC_TIME_MASK;
-                  break;
-                case LC_COLLATE:
-                  mask = LC_COLLATE_MASK;
-                  break;
-                case LC_MONETARY:
-                  mask = LC_MONETARY_MASK;
-                  break;
-                case LC_MESSAGES:
-                  mask = LC_MESSAGES_MASK;
-                  break;
-                default:
-                  abort ();
-                }
-              names.category_name[category] =
-                ((mask & category_mask) != 0 ? name : "C");
-            }
-        }
-      else if (base == LC_GLOBAL_LOCALE)
-        {
-          int category;
-
-          for (category = 0; category < 6; category++)
-            {
-              int mask;
-
-              switch (category)
-                {
-                case LC_CTYPE:
-                  mask = LC_CTYPE_MASK;
-                  break;
-                case LC_NUMERIC:
-                  mask = LC_NUMERIC_MASK;
-                  break;
-                case LC_TIME:
-                  mask = LC_TIME_MASK;
-                  break;
-                case LC_COLLATE:
-                  mask = LC_COLLATE_MASK;
-                  break;
-                case LC_MONETARY:
-                  mask = LC_MONETARY_MASK;
-                  break;
-                case LC_MESSAGES:
-                  mask = LC_MESSAGES_MASK;
-                  break;
-                default:
-                  abort ();
-                }
-              names.category_name[category] =
-                ((mask & category_mask) != 0
-                 ? name
-                 : get_locale_t_name (category, LC_GLOBAL_LOCALE));
-            }
-        }
-      else
-        {
-          /* Look up the names of base in the hash table.  Like multiple calls
-             of get_locale_t_name, but locking only once.  */
-          struct locale_hash_node *p;
-          int category;
-
-          /* Lock while looking up the hash node.  */
-          gl_rwlock_rdlock (locale_lock);
-          for (p = locale_hash_table[locale_hash_function (base) % LOCALE_HASH_TABLE_SIZE];
-               p != NULL;
-               p = p->next)
-            if (p->locale == base)
-              break;
-
-          for (category = 0; category < 6; category++)
-            {
-              int mask;
-
-              switch (category)
-                {
-                case LC_CTYPE:
-                  mask = LC_CTYPE_MASK;
-                  break;
-                case LC_NUMERIC:
-                  mask = LC_NUMERIC_MASK;
-                  break;
-                case LC_TIME:
-                  mask = LC_TIME_MASK;
-                  break;
-                case LC_COLLATE:
-                  mask = LC_COLLATE_MASK;
-                  break;
-                case LC_MONETARY:
-                  mask = LC_MONETARY_MASK;
-                  break;
-                case LC_MESSAGES:
-                  mask = LC_MESSAGES_MASK;
-                  break;
-                default:
-                  abort ();
-                }
-              names.category_name[category] =
-                ((mask & category_mask) != 0
-                 ? name
-                 : (p != NULL ? p->names.category_name[category] : ""));
-            }
-
-          gl_rwlock_unlock (locale_lock);
-        }
-    }
-
-  node = (struct locale_hash_node *) malloc (sizeof (struct locale_hash_node));
-  if (node == NULL)
-    /* errno is set to ENOMEM.  */
-    return NULL;
-
-  result = newlocale (category_mask, name, base);
-  if (result == NULL)
-    {
-      free (node);
-      return NULL;
-    }
-
-  /* Fill the hash node.  */
-  node->locale = result;
-  node->names = names;
-
-  /* Insert it in the hash table.  */
-  {
-    size_t hashcode = locale_hash_function (result);
-    size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE;
-    struct locale_hash_node *p;
-
-    /* Lock while inserting the new node.  */
-    gl_rwlock_wrlock (locale_lock);
-    for (p = locale_hash_table[slot]; p != NULL; p = p->next)
-      if (p->locale == result)
-        {
-          /* This can happen if the application uses the original freelocale()
-             function instead of the overridden one.  */
-          p->names = node->names;
-          break;
-        }
-    if (p == NULL)
-      {
-        node->next = locale_hash_table[slot];
-        locale_hash_table[slot] = node;
-      }
-
-    gl_rwlock_unlock (locale_lock);
-
-    if (p != NULL)
-      free (node);
-  }
-
-  return result;
-}
-
-/* duplocale() override.  */
-locale_t
-duplocale (locale_t locale)
-#undef duplocale
-{
-  struct locale_hash_node *node;
-  locale_t result;
-
-  if (locale == NULL)
-    /* Invalid argument.  */
-    abort ();
-
-  node = (struct locale_hash_node *) malloc (sizeof (struct locale_hash_node));
-  if (node == NULL)
-    /* errno is set to ENOMEM.  */
-    return NULL;
-
-  result = duplocale (locale);
-  if (result == NULL)
-    {
-      free (node);
-      return NULL;
-    }
-
-  /* Fill the hash node.  */
-  node->locale = result;
-  if (locale == LC_GLOBAL_LOCALE)
-    {
-      int category;
-
-      for (category = 0; category < 6; category++)
-        node->names.category_name[category] =
-          get_locale_t_name (category, LC_GLOBAL_LOCALE);
-
-      /* Lock before inserting the new node.  */
-      gl_rwlock_wrlock (locale_lock);
-    }
-  else
-    {
-      struct locale_hash_node *p;
-
-      /* Lock once, for the lookup and the insertion.  */
-      gl_rwlock_wrlock (locale_lock);
-
-      for (p = locale_hash_table[locale_hash_function (locale) % LOCALE_HASH_TABLE_SIZE];
-           p != NULL;
-           p = p->next)
-        if (p->locale == locale)
-          break;
-      if (p != NULL)
-        node->names = p->names;
-      else
-        {
-          /* This can happen if the application uses the original
-             newlocale()/duplocale() functions instead of the overridden
-             ones.  */
-          int category;
-
-          for (category = 0; category < 6; category++)
-            node->names.category_name[category] = "";
-        }
-    }
-
-  /* Insert it in the hash table.  */
-  {
-    size_t hashcode = locale_hash_function (result);
-    size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE;
-    struct locale_hash_node *p;
-
-    for (p = locale_hash_table[slot]; p != NULL; p = p->next)
-      if (p->locale == result)
-        {
-          /* This can happen if the application uses the original freelocale()
-             function instead of the overridden one.  */
-          p->names = node->names;
-          break;
-        }
-    if (p == NULL)
-      {
-        node->next = locale_hash_table[slot];
-        locale_hash_table[slot] = node;
-      }
-
-    gl_rwlock_unlock (locale_lock);
-
-    if (p != NULL)
-      free (node);
-  }
-
-  return result;
-}
-
-/* freelocale() override.  */
-void
-freelocale (locale_t locale)
-#undef freelocale
-{
-  if (locale == NULL || locale == LC_GLOBAL_LOCALE)
-    /* Invalid argument.  */
-    abort ();
-
-  {
-    size_t hashcode = locale_hash_function (locale);
-    size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE;
-    struct locale_hash_node *found;
-    struct locale_hash_node **p;
-
-    found = NULL;
-    /* Lock while removing the hash node.  */
-    gl_rwlock_wrlock (locale_lock);
-    for (p = &locale_hash_table[slot]; *p != NULL; p = &(*p)->next)
-      if ((*p)->locale == locale)
-        {
-          found = *p;
-          *p = (*p)->next;
-          break;
-        }
-    gl_rwlock_unlock (locale_lock);
-    free (found);
-  }
-
-  freelocale (locale);
-}
-
-#endif
-
-
 const char *
 gl_locale_name_thread_unsafe (int category, _GL_UNUSED const char *categoryname)
 {
@@ -3033,164 +2622,7 @@ gl_locale_name_thread_unsafe (int category, _GL_UNUSED const char *categoryname)
   {
     locale_t thread_locale = uselocale (NULL);
     if (thread_locale != LC_GLOBAL_LOCALE)
-      {
-# if __GLIBC__ >= 2 && !defined __UCLIBC__
-        /* Work around an incorrect definition of the _NL_LOCALE_NAME macro in
-           glibc < 2.12.
-           See <https://sourceware.org/bugzilla/show_bug.cgi?id=10968>.  */
-        const char *name =
-          nl_langinfo (_NL_ITEM ((category), _NL_ITEM_INDEX (-1)));
-        if (name[0] == '\0')
-          /* Fallback code for glibc < 2.4, which did not implement
-             nl_langinfo (_NL_LOCALE_NAME (category)).  */
-          name = thread_locale->__names[category];
-        return name;
-# elif defined __linux__ && HAVE_LANGINFO_H && defined NL_LOCALE_NAME
-        /* musl libc */
-        return nl_langinfo_l (NL_LOCALE_NAME (category), thread_locale);
-# elif (defined __FreeBSD__ || defined __DragonFly__) || (defined __APPLE__ && defined __MACH__)
-        /* FreeBSD, Mac OS X */
-        int mask;
-
-        switch (category)
-          {
-          case LC_CTYPE:
-            mask = LC_CTYPE_MASK;
-            break;
-          case LC_NUMERIC:
-            mask = LC_NUMERIC_MASK;
-            break;
-          case LC_TIME:
-            mask = LC_TIME_MASK;
-            break;
-          case LC_COLLATE:
-            mask = LC_COLLATE_MASK;
-            break;
-          case LC_MONETARY:
-            mask = LC_MONETARY_MASK;
-            break;
-          case LC_MESSAGES:
-            mask = LC_MESSAGES_MASK;
-            break;
-          default: /* We shouldn't get here.  */
-            return "";
-          }
-        return querylocale (mask, thread_locale);
-# elif defined __sun
-#  if HAVE_SOLARIS114_LOCALES
-        /* Solaris >= 11.4.  */
-        void *lcp = (*thread_locale)->core.data->lcp;
-        if (lcp != NULL)
-          switch (category)
-            {
-            case LC_CTYPE:
-            case LC_NUMERIC:
-            case LC_TIME:
-            case LC_COLLATE:
-            case LC_MONETARY:
-            case LC_MESSAGES:
-              return ((const char * const *) lcp)[category];
-            default: /* We shouldn't get here.  */
-              return "";
-            }
-#  else
-        /* Solaris 11 OpenIndiana.
-           For the internal structure of locale objects, see
-           https://github.com/OpenIndiana/illumos-gate/blob/master/usr/src/lib/libc/port/locale/localeimpl.h  */
-        switch (category)
-          {
-          case LC_CTYPE:
-          case LC_NUMERIC:
-          case LC_TIME:
-          case LC_COLLATE:
-          case LC_MONETARY:
-          case LC_MESSAGES:
-            return ((const char * const *) thread_locale)[category];
-          default: /* We shouldn't get here.  */
-            return "";
-          }
-#  endif
-# elif defined _AIX && HAVE_NAMELESS_LOCALES
-        return get_locale_t_name (category, thread_locale);
-# elif defined __CYGWIN__
-        /* Cygwin < 2.6 lacks uselocale and thread-local locales altogether.
-           Cygwin <= 2.6.1 lacks NL_LOCALE_NAME, requiring peeking inside
-           an opaque struct.  */
-#  ifdef NL_LOCALE_NAME
-        return nl_langinfo_l (NL_LOCALE_NAME (category), thread_locale);
-#  else
-        /* FIXME: Remove when we can assume new-enough Cygwin.  */
-        struct __locale_t {
-          char categories[7][32];
-        };
-        return ((struct __locale_t *) thread_locale)->categories[category];
-#  endif
-# elif defined __HAIKU__
-        /* Since 2022, Haiku has per-thread locales.  locale_t is 'void *',
-           but in fact a 'LocaleBackendData *'.  */
-        struct LocaleBackendData {
-          int magic;
-          void /*BPrivate::Libroot::LocaleBackend*/ *backend;
-          void /*BPrivate::Libroot::LocaleDataBridge*/ *databridge;
-        };
-        void *thread_locale_backend =
-          ((struct LocaleBackendData *) thread_locale)->backend;
-        if (thread_locale_backend != NULL)
-          {
-            /* The only existing concrete subclass of
-               BPrivate::Libroot::LocaleBackend is
-               BPrivate::Libroot::ICULocaleBackend.
-               Invoke the (non-virtual) method
-               BPrivate::Libroot::ICULocaleBackend::_QueryLocale on it.
-               This method is located in a separate shared library,
-               libroot-addon-icu.so.  */
-            static void * volatile querylocale_method /* = NULL */;
-            static int volatile querylocale_found /* = 0 */;
-            /* Attempt to open this shared library, the first time we get
-               here.  */
-            if (querylocale_found == 0)
-              {
-                void *handle =
-                  dlopen ("/boot/system/lib/libroot-addon-icu.so", 0);
-                if (handle != NULL)
-                  {
-                    void *sym =
-                      dlsym (handle, "_ZN8BPrivate7Libroot16ICULocaleBackend12_QueryLocaleEi");
-                    if (sym != NULL)
-                      {
-                        querylocale_method = sym;
-                        querylocale_found = 1;
-                      }
-                    else
-                      /* Could not find the symbol.  */
-                      querylocale_found = -1;
-                  }
-                else
-                  /* Could not open the separate shared library.  */
-                  querylocale_found = -1;
-              }
-            if (querylocale_found > 0)
-              {
-                /* The _QueryLocale method is a non-static C++ method with
-                   parameters (int category) and return type 'const char *'.
-                   See
-                     haiku/headers/private/libroot/locale/ICULocaleBackend.h
-                     haiku/src/system/libroot/add-ons/icu/ICULocaleBackend.cpp
-                   This is the same as a C function with parameters
-                     (BPrivate::Libroot::LocaleBackend* this, int category)
-                   and return type 'const char *'.  Invoke it.  */
-                const char * (*querylocale_func) (void *, int) =
-                  (const char * (*) (void *, int)) querylocale_method;
-                return querylocale_func (thread_locale_backend, category);
-              }
-          }
-        else
-          /* It's the "C" or "POSIX" locale.  */
-          return "C";
-# elif defined __ANDROID__
-        return MB_CUR_MAX == 4 ? "C.UTF-8" : "C";
-# endif
-      }
+      return getlocalename_l (category, thread_locale);
   }
 #endif
   /* On WINDOWS_NATIVE, don't use GetThreadLocale() here, because when
diff --git a/lib/struniq.h b/lib/struniq.h
index f3c50de820..7bc77d1cc9 100644
--- a/lib/struniq.h
+++ b/lib/struniq.h
@@ -30,7 +30,7 @@
 
      flexmember
      lock
-     stdbool
+     bool
      thread-optim
  */
 
diff --git a/m4/getlocalename_l.m4 b/m4/getlocalename_l.m4
new file mode 100644
index 0000000000..e007b24b3c
--- /dev/null
+++ b/m4/getlocalename_l.m4
@@ -0,0 +1,76 @@
+# getlocalename_l.m4
+# serial 1
+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,
+dnl with or without modifications, as long as this notice is preserved.
+dnl This file is offered as-is, without any warranty.
+
+AC_DEFUN([gl_FUNC_GETLOCALENAME_L_SIMPLE],
+[
+  AC_REQUIRE([gl_LOCALE_H_DEFAULTS])
+
+  dnl Persuade glibc <locale.h> to declare getlocalename_l().
+  AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
+
+  AC_REQUIRE([gl_FUNC_SETLOCALE_NULL])
+  AC_CHECK_FUNCS_ONCE([getlocalename_l])
+  if test $ac_cv_func_getlocalename_l = yes; then
+    GETLOCALENAME_L_LIB=
+  else
+    HAVE_GETLOCALENAME_L=0
+    GETLOCALENAME_L_LIB="$SETLOCALE_NULL_LIB"
+  fi
+  dnl GETLOCALENAME_L_LIB is expected to be '-pthread' or '-lpthread' on AIX
+  dnl with gcc or xlc, and empty otherwise.
+  AC_SUBST([GETLOCALENAME_L_LIB])
+])
+
+# Prerequisites of lib/getlocalename_l.c.
+AC_DEFUN([gl_PREREQ_GETLOCALENAME_L_SIMPLE],
+[
+  AC_REQUIRE([gl_LOCALE_H_DEFAULTS])
+  AC_REQUIRE([gl_LOCALE_T])
+  AC_REQUIRE([gt_INTL_THREAD_LOCALE_NAME])
+  AC_CHECK_HEADERS_ONCE([langinfo.h])
+  if test $HAVE_LOCALE_T = 1; then
+    gl_CHECK_FUNCS_ANDROID([newlocale], [[#include <locale.h>]])
+    gl_CHECK_FUNCS_ANDROID([duplocale], [[#include <locale.h>]])
+    gl_CHECK_FUNCS_ANDROID([freelocale], [[#include <locale.h>]])
+    gl_func_newlocale="$ac_cv_func_newlocale"
+    gl_func_duplocale="$ac_cv_func_duplocale"
+    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_newlocale='future OS version'
+    gl_cv_onwards_func_duplocale='future OS version'
+    gl_cv_onwards_func_freelocale='future OS version'
+    gl_func_newlocale=no
+    gl_func_duplocale=no
+    gl_func_freelocale=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
+  if test $gl_func_duplocale != yes; then
+    HAVE_DUPLOCALE=0
+    case "$gl_cv_onwards_func_duplocale" in
+      future*) REPLACE_DUPLOCALE=1 ;;
+    esac
+  fi
+  if test $gl_func_freelocale != yes; then
+    HAVE_FREELOCALE=0
+    case "$gl_cv_onwards_func_freelocale" in
+      future*) REPLACE_FREELOCALE=1 ;;
+    esac
+  fi
+  if test $gt_localename_enhances_locale_funcs = yes; then
+    REPLACE_NEWLOCALE=1
+    REPLACE_DUPLOCALE=1
+    REPLACE_FREELOCALE=1
+  fi
+])
diff --git a/m4/intl-thread-locale.m4 b/m4/intl-thread-locale.m4
index aff83a7ad6..084eacf14a 100644
--- a/m4/intl-thread-locale.m4
+++ b/m4/intl-thread-locale.m4
@@ -1,5 +1,5 @@
 # intl-thread-locale.m4
-# serial 13
+# serial 14
 dnl Copyright (C) 2015-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,
@@ -38,8 +38,9 @@ AC_DEFUN([gt_INTL_THREAD_LOCALE_NAME]
   dnl OpenBSD base system, store complete information about the global locale,
   dnl such that third-party software can access it"), but for uselocale()
   dnl they did not think about the programs.
-  dnl In this situation, even the HAVE_NAMELESS_LOCALES support does not work.
-  dnl So, define HAVE_FAKE_LOCALES and disable all locale_t support.
+  dnl In this situation, even the HAVE_NAMELESS_LOCALES support cannot make
+  dnl uselocale() work.
+  dnl So, define HAVE_FAKE_LOCALES and disable all per-thread locale support.
   dnl Expected result: HAVE_FAKE_LOCALES is defined on OpenBSD ??? 6.2.
   case "$gt_cv_func_uselocale_works" in
     *yes)
@@ -123,16 +124,14 @@ AC_DEFUN([gt_INTL_THREAD_LOCALE_NAME]
   dnl requires the gnulib overrides of 'newlocale', 'duplocale', 'freelocale',
   dnl which is a problem for GNU libunistring.  Therefore try hard to avoid
   dnl enabling this code!
-  dnl Expected result: HAVE_NAMELESS_LOCALES is defined on AIX,
+  dnl Expected result: HAVE_NAMELESS_LOCALES is defined on OpenBSD ??? 6.2, AIX,
   dnl and              HAVE_AIX72_LOCALES is defined on AIX ??? 7.2.
-  gt_nameless_locales=no
+  gt_nameless_locales=$gt_fake_locales
   case "$host_os" in
     dnl It's needed on AIX 7.2.
     aix*)
       gt_nameless_locales=yes
-      AC_DEFINE([HAVE_NAMELESS_LOCALES], [1],
-        [Define if the locale_t type does not contain the name of each locale category.])
-      dnl In AIX ??? 7.2, a locale contains at least the name of the LC_MESSSAGES
+      dnl In AIX ??? 7.2, a locale contains at least the name of the LC_MESSAGES
       dnl category (fix of defect 823926).
       AC_CACHE_CHECK([for AIX locales with LC_MESSAGES name],
         [gt_cv_locale_aix72],
@@ -153,6 +152,10 @@ AC_DEFUN([gt_INTL_THREAD_LOCALE_NAME]
       fi
       ;;
   esac
+  if test $gt_nameless_locales = yes; then
+    AC_DEFINE([HAVE_NAMELESS_LOCALES], [1],
+      [Define if the locale_t type does not contain the name of each locale category.])
+  fi
 
   dnl We cannot support uselocale() on platforms where the locale_t type is
   dnl fake.  So, set
@@ -172,8 +175,8 @@ AC_DEFUN([gt_INTL_THREAD_LOCALE_NAME]
   dnl overrides newlocale(), duplocale(), freelocale() to keep track of locale
   dnl names.
   dnl Expected result: LOCALENAME_ENHANCE_LOCALE_FUNCS is defined on
-  dnl AIX 7.1, AIX ??? 7.3.
-  if test $gt_good_uselocale = yes && test $gt_nameless_locales = yes; then
+  dnl OpenBSD ??? 6.2, AIX 7.1, AIX ??? 7.3.
+  if test $gt_working_uselocale = yes && test $gt_nameless_locales = yes; then
     gt_localename_enhances_locale_funcs=yes
     LOCALENAME_ENHANCE_LOCALE_FUNCS=1
     AC_DEFINE([LOCALENAME_ENHANCE_LOCALE_FUNCS], [1],
diff --git a/m4/locale_h.m4 b/m4/locale_h.m4
index 1e3cc11bf2..0b6b6f182d 100644
--- a/m4/locale_h.m4
+++ b/m4/locale_h.m4
@@ -1,5 +1,5 @@
 # locale_h.m4
-# serial 34
+# serial 35
 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,
@@ -107,7 +107,7 @@ AC_DEFUN_ONCE([gl_LOCALE_H]
 # include <xlocale.h>
 #endif
     ]],
-    [setlocale newlocale duplocale freelocale])
+    [setlocale newlocale duplocale freelocale getlocalename_l])
 ])
 
 dnl Checks to determine whether the system has the locale_t type,
@@ -179,6 +179,7 @@ AC_DEFUN([gl_LOCALE_H_REQUIRE_DEFAULTS]
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_NEWLOCALE])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_DUPLOCALE])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_FREELOCALE])
+    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_GETLOCALENAME_L])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_LOCALENAME_UNSAFE])
   ])
   m4_require(GL_MODULE_INDICATOR_PREFIX[_LOCALE_H_MODULE_INDICATOR_DEFAULTS])
@@ -191,6 +192,7 @@ AC_DEFUN([gl_LOCALE_H_DEFAULTS]
   HAVE_NEWLOCALE=1;       AC_SUBST([HAVE_NEWLOCALE])
   HAVE_DUPLOCALE=1;       AC_SUBST([HAVE_DUPLOCALE])
   HAVE_FREELOCALE=1;      AC_SUBST([HAVE_FREELOCALE])
+  HAVE_GETLOCALENAME_L=1; AC_SUBST([HAVE_GETLOCALENAME_L])
   REPLACE_LOCALECONV=0;   AC_SUBST([REPLACE_LOCALECONV])
   REPLACE_SETLOCALE=0;    AC_SUBST([REPLACE_SETLOCALE])
   REPLACE_NEWLOCALE=0;    AC_SUBST([REPLACE_NEWLOCALE])
diff --git a/m4/localename.m4 b/m4/localename.m4
index af94411b23..b5677d8944 100644
--- a/m4/localename.m4
+++ b/m4/localename.m4
@@ -1,5 +1,5 @@
 # localename.m4
-# serial 13
+# serial 14
 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,
@@ -9,57 +9,13 @@
 AC_DEFUN([gl_LOCALENAME_UNSAFE],
 [
   AC_REQUIRE([gl_LOCALE_H_DEFAULTS])
-  AC_REQUIRE([gl_LOCALE_T])
   AC_REQUIRE([gt_LC_MESSAGES])
-  AC_REQUIRE([gt_INTL_THREAD_LOCALE_NAME])
   AC_REQUIRE([gt_INTL_MACOSX])
-  AC_CHECK_HEADERS_ONCE([langinfo.h])
-  if test $HAVE_LOCALE_T = 1; then
-    gl_CHECK_FUNCS_ANDROID([newlocale], [[#include <locale.h>]])
-    gl_CHECK_FUNCS_ANDROID([duplocale], [[#include <locale.h>]])
-    gl_CHECK_FUNCS_ANDROID([freelocale], [[#include <locale.h>]])
-    gl_func_newlocale="$ac_cv_func_newlocale"
-    gl_func_duplocale="$ac_cv_func_duplocale"
-    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_newlocale='future OS version'
-    gl_cv_onwards_func_duplocale='future OS version'
-    gl_cv_onwards_func_freelocale='future OS version'
-    gl_func_newlocale=no
-    gl_func_duplocale=no
-    gl_func_freelocale=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
-  if test $gl_func_duplocale != yes; then
-    HAVE_DUPLOCALE=0
-    case "$gl_cv_onwards_func_duplocale" in
-      future*) REPLACE_DUPLOCALE=1 ;;
-    esac
-  fi
-  if test $gl_func_freelocale != yes; then
-    HAVE_FREELOCALE=0
-    case "$gl_cv_onwards_func_freelocale" in
-      future*) REPLACE_FREELOCALE=1 ;;
-    esac
-  fi
-  if test $gt_localename_enhances_locale_funcs = yes; then
-    REPLACE_NEWLOCALE=1
-    REPLACE_DUPLOCALE=1
-    REPLACE_FREELOCALE=1
-  fi
 ])
 
 AC_DEFUN([gl_LOCALENAME_UNSAFE_LIMITED],
 [
   AC_REQUIRE([gt_LC_MESSAGES])
-  AC_REQUIRE([gt_INTL_THREAD_LOCALE_NAME])
 ])
 
 AC_DEFUN([gl_LOCALENAME_ENVIRON],
diff --git a/modules/duplocale b/modules/duplocale
index 8d34e9476a..e4fce0ed04 100644
--- a/modules/duplocale
+++ b/modules/duplocale
@@ -29,7 +29,7 @@ Include:
 <locale.h>
 
 Link:
-$(DUPLOCALE_LIB)
+$(DUPLOCALE_LIB) $(GETLOCALENAME_L_LIB)
 
 License:
 LGPLv2+
diff --git a/modules/duplocale-tests b/modules/duplocale-tests
index 395d252dc8..bcc8ea1d44 100644
--- a/modules/duplocale-tests
+++ b/modules/duplocale-tests
@@ -17,4 +17,4 @@ gt_FUNC_USELOCALE
 Makefile.am:
 TESTS += test-duplocale
 check_PROGRAMS += test-duplocale
-test_duplocale_LDADD = $(LDADD) $(SETLOCALE_LIB) @DUPLOCALE_LIB@
+test_duplocale_LDADD = $(LDADD) $(SETLOCALE_LIB) @DUPLOCALE_LIB@ $(GETLOCALENAME_L_LIB)
diff --git a/modules/freelocale b/modules/freelocale
index fe1faaae59..4c99c4b5c4 100644
--- a/modules/freelocale
+++ b/modules/freelocale
@@ -24,6 +24,9 @@ endif
 Include:
 <locale.h>
 
+Link:
+$(GETLOCALENAME_L_LIB)
+
 License:
 LGPLv2+
 
diff --git a/modules/freelocale-tests b/modules/freelocale-tests
index 9c4c322e38..6cb7463323 100644
--- a/modules/freelocale-tests
+++ b/modules/freelocale-tests
@@ -11,3 +11,4 @@ configure.ac:
 Makefile.am:
 TESTS += test-freelocale
 check_PROGRAMS += test-freelocale
+test_freelocale_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/getlocalename_l-simple b/modules/getlocalename_l-simple
new file mode 100644
index 0000000000..e169721fd5
--- /dev/null
+++ b/modules/getlocalename_l-simple
@@ -0,0 +1,47 @@
+Description:
+getlocalename_l() function: return name of a single locale category.
+
+Files:
+lib/getlocalename_l.c
+lib/localename-table.h
+lib/localename-table.c
+lib/struniq.h
+m4/getlocalename_l.m4
+m4/intl-thread-locale.m4
+
+Depends-on:
+locale-h
+extensions
+flexmember         [test $HAVE_GETLOCALENAME_L = 0]
+lock               [test $HAVE_GETLOCALENAME_L = 0]
+bool               [test $HAVE_GETLOCALENAME_L = 0]
+thread-optim       [test $HAVE_GETLOCALENAME_L = 0]
+setlocale-messages [test $HAVE_GETLOCALENAME_L = 0]
+setlocale-null     [test $HAVE_GETLOCALENAME_L = 0]
+free-posix         [test $HAVE_GETLOCALENAME_L = 0]
+
+configure.ac:
+gl_FUNC_GETLOCALENAME_L_SIMPLE
+gl_CONDITIONAL([GL_COND_OBJ_GETLOCALENAME_L], [test $HAVE_GETLOCALENAME_L = 0])
+AM_COND_IF([GL_COND_OBJ_GETLOCALENAME_L], [
+  gl_PREREQ_GETLOCALENAME_L_SIMPLE
+])
+gl_MODULE_INDICATOR([getlocalename_l])
+gl_LOCALE_MODULE_INDICATOR([getlocalename_l])
+
+Makefile.am:
+if GL_COND_OBJ_GETLOCALENAME_L
+lib_SOURCES += getlocalename_l.c localename-table.c
+endif
+
+Include:
+<locale.h>
+
+Link:
+$(GETLOCALENAME_L_LIB)
+
+License:
+LGPLv2+
+
+Maintainer:
+all
diff --git a/modules/isalnum_l-tests b/modules/isalnum_l-tests
index 2fab4fd61f..c84fa3581f 100644
--- a/modules/isalnum_l-tests
+++ b/modules/isalnum_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-isalnum_l
 check_PROGRAMS += test-isalnum_l
+test_isalnum_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/isalpha_l-tests b/modules/isalpha_l-tests
index 14fe1a0804..9ea4f90ec8 100644
--- a/modules/isalpha_l-tests
+++ b/modules/isalpha_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-isalpha_l
 check_PROGRAMS += test-isalpha_l
+test_isalpha_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/isblank_l-tests b/modules/isblank_l-tests
index f6511a3262..be0dd7962a 100644
--- a/modules/isblank_l-tests
+++ b/modules/isblank_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-isblank_l
 check_PROGRAMS += test-isblank_l
+test_isblank_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/iscntrl_l-tests b/modules/iscntrl_l-tests
index b35b8264b2..c1542350d4 100644
--- a/modules/iscntrl_l-tests
+++ b/modules/iscntrl_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-iscntrl_l
 check_PROGRAMS += test-iscntrl_l
+test_iscntrl_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/isdigit_l-tests b/modules/isdigit_l-tests
index 78d479b4c1..971238df99 100644
--- a/modules/isdigit_l-tests
+++ b/modules/isdigit_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-isdigit_l
 check_PROGRAMS += test-isdigit_l
+test_isdigit_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/isgraph_l-tests b/modules/isgraph_l-tests
index 519e40b623..01b422515b 100644
--- a/modules/isgraph_l-tests
+++ b/modules/isgraph_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-isgraph_l
 check_PROGRAMS += test-isgraph_l
+test_isgraph_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/islower_l-tests b/modules/islower_l-tests
index 8df465f55f..91ca010b48 100644
--- a/modules/islower_l-tests
+++ b/modules/islower_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-islower_l
 check_PROGRAMS += test-islower_l
+test_islower_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/isprint_l-tests b/modules/isprint_l-tests
index 317d88fbfe..2babfa40c3 100644
--- a/modules/isprint_l-tests
+++ b/modules/isprint_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-isprint_l
 check_PROGRAMS += test-isprint_l
+test_isprint_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/ispunct_l-tests b/modules/ispunct_l-tests
index d74a6baf56..e405d5079b 100644
--- a/modules/ispunct_l-tests
+++ b/modules/ispunct_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-ispunct_l
 check_PROGRAMS += test-ispunct_l
+test_ispunct_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/isspace_l-tests b/modules/isspace_l-tests
index 4e0e2384a3..2a02fadc96 100644
--- a/modules/isspace_l-tests
+++ b/modules/isspace_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-isspace_l
 check_PROGRAMS += test-isspace_l
+test_isspace_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/isupper_l-tests b/modules/isupper_l-tests
index 488b6b2e5a..605363fc3a 100644
--- a/modules/isupper_l-tests
+++ b/modules/isupper_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-isupper_l
 check_PROGRAMS += test-isupper_l
+test_isupper_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/isxdigit_l-tests b/modules/isxdigit_l-tests
index 8565a6dff1..25d82c4c44 100644
--- a/modules/isxdigit_l-tests
+++ b/modules/isxdigit_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-isxdigit_l
 check_PROGRAMS += test-isxdigit_l
+test_isxdigit_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/locale-h b/modules/locale-h
index 52f456ea86..42eb9b5fbc 100644
--- a/modules/locale-h
+++ b/modules/locale-h
@@ -41,10 +41,12 @@ locale.h: locale.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H
 	      -e 's/@''GNULIB_NEWLOCALE''@/$(GNULIB_NEWLOCALE)/g' \
 	      -e 's/@''GNULIB_DUPLOCALE''@/$(GNULIB_DUPLOCALE)/g' \
 	      -e 's/@''GNULIB_FREELOCALE''@/$(GNULIB_FREELOCALE)/g' \
+	      -e 's/@''GNULIB_GETLOCALENAME_L''@/$(GNULIB_GETLOCALENAME_L)/g' \
 	      -e 's/@''GNULIB_LOCALENAME_UNSAFE''@/$(GNULIB_LOCALENAME_UNSAFE)/g' \
 	      -e 's|@''HAVE_NEWLOCALE''@|$(HAVE_NEWLOCALE)|g' \
 	      -e 's|@''HAVE_DUPLOCALE''@|$(HAVE_DUPLOCALE)|g' \
 	      -e 's|@''HAVE_FREELOCALE''@|$(HAVE_FREELOCALE)|g' \
+	      -e 's|@''HAVE_GETLOCALENAME_L''@|$(HAVE_GETLOCALENAME_L)|g' \
 	      -e 's|@''HAVE_XLOCALE_H''@|$(HAVE_XLOCALE_H)|g' \
 	      -e 's|@''REPLACE_LOCALECONV''@|$(REPLACE_LOCALECONV)|g' \
 	      -e 's|@''REPLACE_SETLOCALE''@|$(REPLACE_SETLOCALE)|g' \
diff --git a/modules/localename-unsafe b/modules/localename-unsafe
index 18fa5433bd..b7d073deea 100644
--- a/modules/localename-unsafe
+++ b/modules/localename-unsafe
@@ -5,9 +5,6 @@ in thread-local (unsafe) storage.
 Files:
 lib/localename.h
 lib/localename-unsafe.c
-lib/localename-table.h
-lib/localename-table.c
-lib/struniq.h
 m4/localename.m4
 m4/intl-thread-locale.m4
 m4/intlmacosx.m4
@@ -18,16 +15,11 @@ Depends-on:
 localename-unsafe-limited
 localename-environ
 extensions
-bool
 locale-h
-flexmember
-free-posix
 strdup
 lock
-langinfo-h
-setlocale-null
+getlocalename_l-simple
 setlocale-null-unlocked
-thread-optim
 
 configure.ac:
 gl_LOCALENAME_UNSAFE
@@ -38,7 +30,6 @@ Makefile.am:
 if !GL_COND_OBJ_LOCALENAME_UNSAFE_LIMITED
 lib_SOURCES += localename-unsafe.c
 endif
-lib_SOURCES += localename-table.c
 
 Include:
 "localename.h"
diff --git a/modules/localename-unsafe-limited b/modules/localename-unsafe-limited
index 74bf0d5697..51e8b1e882 100644
--- a/modules/localename-unsafe-limited
+++ b/modules/localename-unsafe-limited
@@ -13,6 +13,7 @@ m4/lcmessage.m4
 Depends-on:
 extensions
 locale-h
+getlocalename_l-simple
 setlocale-null-unlocked
 
 configure.ac:
diff --git a/modules/newlocale b/modules/newlocale
index 04f6df28f9..c48ff2b8cd 100644
--- a/modules/newlocale
+++ b/modules/newlocale
@@ -26,6 +26,9 @@ endif
 Include:
 <locale.h>
 
+Link:
+$(GETLOCALENAME_L_LIB)
+
 License:
 LGPLv2+
 
diff --git a/modules/newlocale-tests b/modules/newlocale-tests
index 6876456784..d9b32f78bf 100644
--- a/modules/newlocale-tests
+++ b/modules/newlocale-tests
@@ -11,3 +11,4 @@ gl_CHECK_FUNCS_ANDROID([nl_langinfo_l], [[#include <langinfo.h>]])
 Makefile.am:
 TESTS += test-newlocale
 check_PROGRAMS += test-newlocale
+test_newlocale_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/strcasecmp_l-tests b/modules/strcasecmp_l-tests
index b096df6fb8..d99f610172 100644
--- a/modules/strcasecmp_l-tests
+++ b/modules/strcasecmp_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-strcasecmp_l
 check_PROGRAMS += test-strcasecmp_l
+test_strcasecmp_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/strerror_l-tests b/modules/strerror_l-tests
index 07ef0cd21a..356788852e 100644
--- a/modules/strerror_l-tests
+++ b/modules/strerror_l-tests
@@ -13,3 +13,4 @@ configure.ac:
 Makefile.am:
 TESTS += test-strerror_l
 check_PROGRAMS += test-strerror_l
+test_strerror_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/strncasecmp_l-tests b/modules/strncasecmp_l-tests
index 909d11701e..12c99d1419 100644
--- a/modules/strncasecmp_l-tests
+++ b/modules/strncasecmp_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-strncasecmp_l
 check_PROGRAMS += test-strncasecmp_l
+test_strncasecmp_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/tolower_l-tests b/modules/tolower_l-tests
index 5a08826b1f..4897f9229a 100644
--- a/modules/tolower_l-tests
+++ b/modules/tolower_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-tolower_l
 check_PROGRAMS += test-tolower_l
+test_tolower_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
diff --git a/modules/toupper_l-tests b/modules/toupper_l-tests
index bfd92effdd..eea338d5a5 100644
--- a/modules/toupper_l-tests
+++ b/modules/toupper_l-tests
@@ -14,3 +14,4 @@ gl_MUSL_LIBC
 Makefile.am:
 TESTS += test-toupper_l
 check_PROGRAMS += test-toupper_l
+test_toupper_l_LDADD = $(LDADD) $(GETLOCALENAME_L_LIB)
-- 
2.43.0

>From e3466db8e26caa34d025fb9a2624c6afba1d00aa Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 21 Feb 2025 11:25:54 +0100
Subject: [PATCH 2/2] getlocalename_l-simple: Add tests.

* tests/test-getlocalename_l.c: New file.
* modules/getlocalename_l-simple-tests: New file.
---
 ChangeLog                            |   4 +
 modules/getlocalename_l-simple-tests |  16 ++
 tests/test-getlocalename_l.c         | 368 +++++++++++++++++++++++++++
 3 files changed, 388 insertions(+)
 create mode 100644 modules/getlocalename_l-simple-tests
 create mode 100644 tests/test-getlocalename_l.c

diff --git a/ChangeLog b/ChangeLog
index cc692df338..cf4cebea9e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2025-02-21  Bruno Haible  <br...@clisp.org>
 
+	getlocalename_l-simple: Add tests.
+	* tests/test-getlocalename_l.c: New file.
+	* modules/getlocalename_l-simple-tests: New file.
+
 	getlocalename_l-simple: New module.
 	* lib/locale.in.h (newlocale, duplocale, freelocale): Now enabled by
 	module 'getlocalename_l-simple'.
diff --git a/modules/getlocalename_l-simple-tests b/modules/getlocalename_l-simple-tests
new file mode 100644
index 0000000000..dfbc3af7e7
--- /dev/null
+++ b/modules/getlocalename_l-simple-tests
@@ -0,0 +1,16 @@
+Files:
+tests/test-getlocalename_l.c
+tests/signature.h
+tests/macros.h
+
+Depends-on:
+newlocale
+freelocale
+setlocale
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-getlocalename_l
+check_PROGRAMS += test-getlocalename_l
+test_getlocalename_l_LDADD = $(LDADD) @GETLOCALENAME_L_LIB@ $(SETLOCALE_LIB)
diff --git a/tests/test-getlocalename_l.c b/tests/test-getlocalename_l.c
new file mode 100644
index 0000000000..77ebf4614b
--- /dev/null
+++ b/tests/test-getlocalename_l.c
@@ -0,0 +1,368 @@
+/* Test of getlocalename_l function.
+   Copyright (C) 2025 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 <br...@clisp.org>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <locale.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (getlocalename_l, const char *, (int, locale_t));
+
+#include <string.h>
+
+#include "macros.h"
+
+#ifdef __HAIKU__
+/* Work around Haiku bug <https://dev.haiku-os.org/ticket/18344>.  */
+# define freelocale(loc) ((void) (loc))
+#endif
+
+/* The name that setlocale(,NULL) returns for the "C" locale.  */
+#ifdef __HAIKU__
+# define C_CANONICALIZED "POSIX"
+#else
+# define C_CANONICALIZED "C"
+#endif
+
+#if defined _WIN32 && !defined __CYGWIN__
+
+# define ENGLISH  "English_United States"
+# define FRENCH   "French_France"
+# define ENCODING ".1252"
+
+# define LOCALE1 ENGLISH ENCODING
+# define LOCALE2 FRENCH ENCODING
+
+#else
+
+# define LOCALE1 "en_US.UTF-8"
+# define LOCALE2 "fr_FR.UTF-8"
+
+#endif
+
+int
+main ()
+{
+  /* Test the "C" locale.  */
+  {
+    const char *ret;
+    locale_t locale = newlocale (LC_ALL_MASK, "C", NULL);
+    ASSERT (locale != NULL);
+
+    ret = getlocalename_l (LC_COLLATE, locale);
+    ASSERT (strcmp (ret, "C") == 0);
+
+    ret = getlocalename_l (LC_CTYPE, locale);
+    ASSERT (strcmp (ret, "C") == 0);
+
+    ret = getlocalename_l (LC_MESSAGES, locale);
+    ASSERT (strcmp (ret, "C") == 0);
+
+    ret = getlocalename_l (LC_MONETARY, locale);
+    ASSERT (strcmp (ret, "C") == 0);
+
+    ret = getlocalename_l (LC_NUMERIC, locale);
+    ASSERT (strcmp (ret, "C") == 0);
+
+    ret = getlocalename_l (LC_TIME, locale);
+    ASSERT (strcmp (ret, "C") == 0);
+
+    freelocale (locale);
+  }
+
+  /* Test an English locale.  */
+  {
+    locale_t locale1 = newlocale (LC_ALL_MASK, LOCALE1, NULL);
+    if (locale1 != NULL)
+      {
+        const char *ret;
+
+        ret = getlocalename_l (LC_COLLATE, locale1);
+        ASSERT (strcmp (ret, LOCALE1) == 0);
+
+        ret = getlocalename_l (LC_CTYPE, locale1);
+        ASSERT (strcmp (ret, LOCALE1) == 0);
+
+        ret = getlocalename_l (LC_MESSAGES, locale1);
+        ASSERT (strcmp (ret, LOCALE1) == 0);
+
+        ret = getlocalename_l (LC_MONETARY, locale1);
+        ASSERT (strcmp (ret, LOCALE1) == 0);
+
+        ret = getlocalename_l (LC_NUMERIC, locale1);
+        ASSERT (strcmp (ret, LOCALE1) == 0);
+
+        ret = getlocalename_l (LC_TIME, locale1);
+        ASSERT (strcmp (ret, LOCALE1) == 0);
+
+        freelocale (locale1);
+      }
+  }
+
+  /* Test a mixed locale.  */
+  {
+    locale_t locale1 = newlocale (LC_ALL_MASK, LOCALE1, NULL);
+    if (locale1 != NULL)
+      {
+        locale_t locale2 = newlocale (LC_COLLATE_MASK, LOCALE2, locale1);
+        if (locale2 != NULL)
+          {
+            const char *ret;
+
+            ret = getlocalename_l (LC_COLLATE, locale2);
+            ASSERT (strcmp (ret, LOCALE2) == 0);
+
+            ret = getlocalename_l (LC_CTYPE, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_MESSAGES, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_MONETARY, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_NUMERIC, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_TIME, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            freelocale (locale2);
+          }
+        else
+          freelocale (locale1);
+      }
+  }
+  {
+    locale_t locale1 = newlocale (LC_ALL_MASK, LOCALE1, NULL);
+    if (locale1 != NULL)
+      {
+        locale_t locale2 = newlocale (LC_CTYPE_MASK, LOCALE2, locale1);
+        if (locale2 != NULL)
+          {
+            const char *ret;
+
+            ret = getlocalename_l (LC_COLLATE, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_CTYPE, locale2);
+            ASSERT (strcmp (ret, LOCALE2) == 0);
+
+            ret = getlocalename_l (LC_MESSAGES, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_MONETARY, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_NUMERIC, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_TIME, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            freelocale (locale2);
+          }
+        else
+          freelocale (locale1);
+      }
+  }
+  {
+    locale_t locale1 = newlocale (LC_ALL_MASK, LOCALE1, NULL);
+    if (locale1 != NULL)
+      {
+        locale_t locale2 = newlocale (LC_MESSAGES_MASK, LOCALE2, locale1);
+        if (locale2 != NULL)
+          {
+            const char *ret;
+
+            ret = getlocalename_l (LC_COLLATE, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_CTYPE, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_MESSAGES, locale2);
+            ASSERT (strcmp (ret, LOCALE2) == 0);
+
+            ret = getlocalename_l (LC_MONETARY, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_NUMERIC, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_TIME, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            freelocale (locale2);
+          }
+        else
+          freelocale (locale1);
+      }
+  }
+  {
+    locale_t locale1 = newlocale (LC_ALL_MASK, LOCALE1, NULL);
+    if (locale1 != NULL)
+      {
+        locale_t locale2 = newlocale (LC_MONETARY_MASK, LOCALE2, locale1);
+        if (locale2 != NULL)
+          {
+            const char *ret;
+
+            ret = getlocalename_l (LC_COLLATE, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_CTYPE, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_MESSAGES, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_MONETARY, locale2);
+            ASSERT (strcmp (ret, LOCALE2) == 0);
+
+            ret = getlocalename_l (LC_NUMERIC, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_TIME, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            freelocale (locale2);
+          }
+        else
+          freelocale (locale1);
+      }
+  }
+  {
+    locale_t locale1 = newlocale (LC_ALL_MASK, LOCALE1, NULL);
+    if (locale1 != NULL)
+      {
+        locale_t locale2 = newlocale (LC_NUMERIC_MASK, LOCALE2, locale1);
+        if (locale2 != NULL)
+          {
+            const char *ret;
+
+            ret = getlocalename_l (LC_COLLATE, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_CTYPE, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_MESSAGES, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_MONETARY, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_NUMERIC, locale2);
+            ASSERT (strcmp (ret, LOCALE2) == 0);
+
+            ret = getlocalename_l (LC_TIME, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            freelocale (locale2);
+          }
+        else
+          freelocale (locale1);
+      }
+  }
+  {
+    locale_t locale1 = newlocale (LC_ALL_MASK, LOCALE1, NULL);
+    if (locale1 != NULL)
+      {
+        locale_t locale2 = newlocale (LC_TIME_MASK, LOCALE2, locale1);
+        if (locale2 != NULL)
+          {
+            const char *ret;
+
+            ret = getlocalename_l (LC_COLLATE, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_CTYPE, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_MESSAGES, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_MONETARY, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_NUMERIC, locale2);
+            ASSERT (strcmp (ret, LOCALE1) == 0);
+
+            ret = getlocalename_l (LC_TIME, locale2);
+            ASSERT (strcmp (ret, LOCALE2) == 0);
+
+            freelocale (locale2);
+          }
+        else
+          freelocale (locale1);
+      }
+  }
+
+  /* Test the global locale.  */
+  {
+    const char *ret;
+
+    ret = getlocalename_l (LC_COLLATE, LC_GLOBAL_LOCALE);
+    ASSERT (strcmp (ret, C_CANONICALIZED) == 0);
+
+    ret = getlocalename_l (LC_CTYPE, LC_GLOBAL_LOCALE);
+    ASSERT (strcmp (ret, C_CANONICALIZED) == 0);
+
+    ret = getlocalename_l (LC_MESSAGES, LC_GLOBAL_LOCALE);
+    ASSERT (strcmp (ret, C_CANONICALIZED) == 0);
+
+    ret = getlocalename_l (LC_MONETARY, LC_GLOBAL_LOCALE);
+    ASSERT (strcmp (ret, C_CANONICALIZED) == 0);
+
+    ret = getlocalename_l (LC_NUMERIC, LC_GLOBAL_LOCALE);
+    ASSERT (strcmp (ret, C_CANONICALIZED) == 0);
+
+    ret = getlocalename_l (LC_TIME, LC_GLOBAL_LOCALE);
+    ASSERT (strcmp (ret, C_CANONICALIZED) == 0);
+  }
+
+  /* Skip this part on OpenBSD <= 6.1.  */
+#if !(defined __OpenBSD__ && !HAVE_NEWLOCALE)
+  if (setlocale (LC_ALL, LOCALE1) != NULL)
+    {
+      const char *ret;
+
+      ret = getlocalename_l (LC_COLLATE, LC_GLOBAL_LOCALE);
+      ASSERT (strcmp (ret, LOCALE1) == 0);
+
+      ret = getlocalename_l (LC_CTYPE, LC_GLOBAL_LOCALE);
+      ASSERT (strcmp (ret, LOCALE1) == 0);
+
+      ret = getlocalename_l (LC_MESSAGES, LC_GLOBAL_LOCALE);
+      ASSERT (strcmp (ret, LOCALE1) == 0);
+
+      ret = getlocalename_l (LC_MONETARY, LC_GLOBAL_LOCALE);
+      ASSERT (strcmp (ret, LOCALE1) == 0);
+
+      ret = getlocalename_l (LC_NUMERIC, LC_GLOBAL_LOCALE);
+      ASSERT (strcmp (ret, LOCALE1) == 0);
+
+      ret = getlocalename_l (LC_TIME, LC_GLOBAL_LOCALE);
+      ASSERT (strcmp (ret, LOCALE1) == 0);
+    }
+#endif
+
+  return 0;
+}
-- 
2.43.0

Reply via email to