Paul Eggert wrote:
> I tried to update GNU diffutils to the latest Gnulib, and found that 
> this dragged in a bunch of stuff about multithreading that diffutils 
> doesn't need.

That's nothing new. The latest release already contains some multithreading
support already:

$ ls -1 diffutils-3.11/m4/*thread*
diffutils-3.11/m4/intl-thread-locale.m4
diffutils-3.11/m4/pthread-cond.m4
diffutils-3.11/m4/pthread_h.m4
diffutils-3.11/m4/pthread-mutex.m4
diffutils-3.11/m4/pthread_mutex_timedlock.m4
diffutils-3.11/m4/pthread-once.m4
diffutils-3.11/m4/pthread-rwlock.m4
diffutils-3.11/m4/pthread_rwlock_rdlock.m4
diffutils-3.11/m4/pthread_sigmask.m4
diffutils-3.11/m4/pthread-spin.m4
diffutils-3.11/m4/pthread-thread.m4
diffutils-3.11/m4/threadlib.m4
diffutils-3.11/m4/thread.m4
$ ls -1 diffutils-3.11/lib/*thread*
diffutils-3.11/lib/pthread.in.h
diffutils-3.11/lib/pthread-once.c

diffutils-3.11/lib/glthread:
...

> The first function I found was hard_locale. What's the 
> recommended way to use hard_locale in a single-threaded app, without 
> pulling in the thread library part of Gnulib?

'hard-locale' relies on 'setlocale-null'. There are 3 aspects:

* Runtime speed optimization. The documentation
  
<https://www.gnu.org/software/gnulib/manual/html_node/Multithreading-Optimizations.html>
  lists optimizations for several modules. There is no
  SETLOCALE_NULL_SINGLE_THREAD or SETLOCALE_NULL_SINGLE_FLAG that would
  have an effect. We could introduce one if relevant.
  But since on glibc systems, setlocale-null does not do any locking anyway
  (because SETLOCALE_NULL_ALL_MTSAFE and SETLOCALE_NULL_ONE_MTSAFE are 1
  on glibc systems), possible optimizations would be relevant only for
  other platforms.
  Use of the 'thread-optim' module in setlocale-null is not needed, for the
  same reason.

* Omitting any multithreading code. Users can already force it by configuring
  with --disable-threads. As a package maintainer, you can make this the
  default by adding this line to configure.ac:
    AC_DEFUN([gl_THREADLIB_DEFAULT_NO], [])
  See m4/threadlib.m4 for how it works.

* Other than that, added files in the tarball are harmless. As long as you
  can see (with "ldd") that the package's binaries don't make use of libpthread
  and (with "nm | grep ' U '") that the package's binaries don't use
  multithreading functions, there is no reason to forcibly remove source files
  from the tarball.
  In other words, maintaining two versions of each module, one multithread-safe
  and one with reduced dependencies for single-threaded packages, is not worth
  the effort.

Still, anyone can investigate where dependencies come from. For example,
diffutils-3.11 contains lib/pthread-once.c, whereas diffutils-3.10 doesn't.
Where does it come from?
  $ ./gnulib-tool --find lib/pthread-once.c 
  pthread-once
Which are the modules with an indirect dependency on it?
  $ ./gnulib-tool --extract-recursive-dependents pthread-once | grep -v tests
When you intersect the resulting list with the gnulib_modules defined in
diffutils/bootstrap.conf, the result is:
  exclude
  nstrftime
  regex
In detail, we have dependency chains
  exclude -> regex -> lock -> once -> pthread-once
  nstrftime -> localename-unsafe-limited -> getlocalename_l-simple -> lock -> 
once -> pthread-once
  regex -> lock -> once -> pthread-once

Here, in fact, the dependency chain
  localename-unsafe-limited -> getlocalename_l-simple -> lock
can be reduced, by introducing a function getlocalename_l_unsafe
and modules 'getlocalename_l-unsafe' and 'getlocalename_l-unsafe-limited'.
Done through the patches below.

> Also, a couple of the multithread-based Gnulib tests failed in the 
> updated diffutils, but only there - they don't fail standalone. I assume 
> this is because diffutils is excluding some modules (based on what it 
> needed to do with old Gnulib to avoid the threading stuff), so I doubt 
> whether this rabbit hole is worth going down.

Yes, when you --avoid'ed some multithreading modules, no wonder that some
tests fail.


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

        localename-unsafe-limited: Use getlocalename_l-unsafe-limited.
        * modules/localename-unsafe-limited (Depends-on): Add
        getlocalename_l-unsafe-limited. Remove getlocalename_l-unsafe.

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

        getlocalename_l-unsafe-limited: New module.
        * modules/getlocalename_l-unsafe-limited: New file.
        * modules/getlocalename_l-unsafe (Depends-on): Add
        getlocalename_l-unsafe-limited.
        (Makefile.am): Don't compile getlocalename_l-unsafe.c if already
        compiled as part of module 'getlocalename_l-unsafe-limited'.

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

        localename-unsafe: Use getlocalename_l-unsafe.
        * lib/localename-unsafe.c: Include getlocalename_l-unsafe.h.
        (gl_locale_name_thread_unsafe): Invoke getlocalename_l_unsafe instead of
        getlocalename_l.
        * modules/localename-unsafe (Depends-on): Add getlocalename_l-unsafe.
        Remove getlocalename_l-simple.
        * modules/localename-unsafe-limited (Depends-on): Likewise.

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

        getlocalename_l-unsafe: New module.
        * lib/getlocalename_l-unsafe.h: New file, based on
        lib/getlocalename_l.c.
        * lib/getlocalename_l-unsafe.c: New file.
        * lib/getlocalename_l.c: Most code moved to
        lib/getlocalename_l-unsafe.c.
        (getlocalename_l): Implement based on getlocalename_l_unsafe.
        * m4/getlocalename_l.m4 (gl_FUNC_GETLOCALENAME_L_UNSAFE,
        gl_PREREQ_GETLOCALENAME_L_UNSAFE): New macros.
        (gl_FUNC_GETLOCALENAME_L_SIMPLE): Require
        gl_FUNC_GETLOCALENAME_L_UNSAFE.
        (gl_PREREQ_GETLOCALENAME_L_SIMPLE): Now empty.
        * modules/getlocalename_l-unsafe: New file.
        * modules/getlocalename_l-simple (Files): Remove
        lib/localename-table.h, lib/localename-table.c,
        m4/intl-thread-locale.m4.
        (Depends-on): Add getlocalename_l-unsafe. Remove setlocale-messages,
        setlocale-null, free-posix.
        (Makefile.am): Don't compile localename-table.c.

>From 81ee5a4ef374c2c2232ba6f5b7ebcf9515c4dba7 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 22 Feb 2025 18:11:38 +0100
Subject: [PATCH 1/4] getlocalename_l-unsafe: New module.

* lib/getlocalename_l-unsafe.h: New file, based on
lib/getlocalename_l.c.
* lib/getlocalename_l-unsafe.c: New file.
* lib/getlocalename_l.c: Most code moved to
lib/getlocalename_l-unsafe.c.
(getlocalename_l): Implement based on getlocalename_l_unsafe.
* m4/getlocalename_l.m4 (gl_FUNC_GETLOCALENAME_L_UNSAFE,
gl_PREREQ_GETLOCALENAME_L_UNSAFE): New macros.
(gl_FUNC_GETLOCALENAME_L_SIMPLE): Require
gl_FUNC_GETLOCALENAME_L_UNSAFE.
(gl_PREREQ_GETLOCALENAME_L_SIMPLE): Now empty.
* modules/getlocalename_l-unsafe: New file.
* modules/getlocalename_l-simple (Files): Remove
lib/localename-table.h, lib/localename-table.c,
m4/intl-thread-locale.m4.
(Depends-on): Add getlocalename_l-unsafe. Remove setlocale-messages,
setlocale-null, free-posix.
(Makefile.am): Don't compile localename-table.c.
---
 ChangeLog                      |  22 ++
 lib/getlocalename_l-unsafe.c   | 678 +++++++++++++++++++++++++++++++++
 lib/getlocalename_l-unsafe.h   |  67 ++++
 lib/getlocalename_l.c          | 633 +-----------------------------
 m4/getlocalename_l.m4          |  17 +-
 modules/getlocalename_l-simple |  17 +-
 modules/getlocalename_l-unsafe |  40 ++
 7 files changed, 841 insertions(+), 633 deletions(-)
 create mode 100644 lib/getlocalename_l-unsafe.c
 create mode 100644 lib/getlocalename_l-unsafe.h
 create mode 100644 modules/getlocalename_l-unsafe

diff --git a/ChangeLog b/ChangeLog
index 9e389ec0b4..dd437a98b7 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,25 @@
+2025-02-22  Bruno Haible  <br...@clisp.org>
+
+	getlocalename_l-unsafe: New module.
+	* lib/getlocalename_l-unsafe.h: New file, based on
+	lib/getlocalename_l.c.
+	* lib/getlocalename_l-unsafe.c: New file.
+	* lib/getlocalename_l.c: Most code moved to
+	lib/getlocalename_l-unsafe.c.
+	(getlocalename_l): Implement based on getlocalename_l_unsafe.
+	* m4/getlocalename_l.m4 (gl_FUNC_GETLOCALENAME_L_UNSAFE,
+	gl_PREREQ_GETLOCALENAME_L_UNSAFE): New macros.
+	(gl_FUNC_GETLOCALENAME_L_SIMPLE): Require
+	gl_FUNC_GETLOCALENAME_L_UNSAFE.
+	(gl_PREREQ_GETLOCALENAME_L_SIMPLE): Now empty.
+	* modules/getlocalename_l-unsafe: New file.
+	* modules/getlocalename_l-simple (Files): Remove
+	lib/localename-table.h, lib/localename-table.c,
+	m4/intl-thread-locale.m4.
+	(Depends-on): Add getlocalename_l-unsafe. Remove setlocale-messages,
+	setlocale-null, free-posix.
+	(Makefile.am): Don't compile localename-table.c.
+
 2025-02-21  Bruno Haible  <br...@clisp.org>
 
 	langinfo-h, nl_langinfo: Support abbreviated alternative month names.
diff --git a/lib/getlocalename_l-unsafe.c b/lib/getlocalename_l-unsafe.c
new file mode 100644
index 0000000000..627434fc24
--- /dev/null
+++ b/lib/getlocalename_l-unsafe.c
@@ -0,0 +1,678 @@
+/* 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 "getlocalename_l-unsafe.h"
+
+#include <locale.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if LC_MESSAGES == 1729
+# include "setlocale-messages.h"
+#endif
+#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
+
+
+#if LOCALENAME_ENHANCE_LOCALE_FUNCS
+
+# include "flexmember.h"
+# include "glthread/lock.h"
+# include "thread-optim.h"
+
+/* 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.  */
+static struct string_with_storage
+get_locale_t_name_unsafe (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 (struct string_with_storage) { name, STORAGE_GLOBAL };
+      else
+        /* Should normally not happen.  */
+        return (struct string_with_storage) { "", STORAGE_INDEFINITE };
+    }
+  else
+    {
+# if HAVE_AIX72_LOCALES
+      if (category == LC_MESSAGES)
+        {
+          const char *name = ((__locale_t) locale)->locale_name;
+          if (name != NULL)
+            return (struct string_with_storage) { name, STORAGE_OBJECT };
+        }
+# 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 (struct string_with_storage) { name, STORAGE_INDEFINITE };
+    }
+}
+
+/* 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)
+{
+  struct string_with_storage ret = get_locale_t_name_unsafe (category, locale);
+  return (ret.storage != STORAGE_INDEFINITE
+          ? struniq (ret.value)
+          : ret.value);
+}
+
+# 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
+
+
+struct string_with_storage
+getlocalename_l_unsafe (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 (struct string_with_storage) { plc->name, STORAGE_OBJECT };
+#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 (struct string_with_storage) { name, STORAGE_OBJECT };
+#elif defined __linux__ && HAVE_LANGINFO_H && defined NL_LOCALE_NAME
+      /* musl libc */
+      const char *name = nl_langinfo_l (NL_LOCALE_NAME (category), locale);
+      return (struct string_with_storage) { name, STORAGE_OBJECT };
+#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 (struct string_with_storage) { "", STORAGE_INDEFINITE };
+        }
+      const char *name = querylocale (mask, locale);
+      return (struct string_with_storage) { name, STORAGE_OBJECT };
+#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];
+      };
+      const char *name = ((struct _locale *) locale)->part_name[category];
+      return (struct string_with_storage) { name, STORAGE_OBJECT };
+#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:
+            {
+              const char *name = ((const char * const *) lcp)[category];
+              return (struct string_with_storage) { name, STORAGE_OBJECT };
+            }
+          default: /* We shouldn't get here.  */
+            return (struct string_with_storage) { "", STORAGE_INDEFINITE };
+          }
+# 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:
+          {
+            const char *name = ((const char * const *) locale)[category];
+            return (struct string_with_storage) { name, STORAGE_OBJECT };
+          }
+        default: /* We shouldn't get here.  */
+          return (struct string_with_storage) { "", STORAGE_INDEFINITE };
+        }
+# endif
+#elif HAVE_NAMELESS_LOCALES
+      /* OpenBSD >= 6.2, AIX >= 7.1 */
+      return get_locale_t_name_unsafe (category, locale);
+#elif defined __OpenBSD__ && HAVE_FAKE_LOCALES
+      /* OpenBSD >= 6.2 has only fake locales.  */
+      if (locale == (locale_t) 2)
+        return (struct string_with_storage) { "C.UTF-8", STORAGE_INDEFINITE };
+      return (struct string_with_storage) { "C", STORAGE_INDEFINITE };
+#elif defined __CYGWIN__
+      /* Cygwin >= 2.6.
+         Cygwin <= 2.6.1 lacks NL_LOCALE_NAME, requiring peeking inside
+         an opaque struct.  */
+# ifdef NL_LOCALE_NAME
+      const char *name = 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];
+      };
+      const char *name = ((struct __locale_t *) locale)->categories[category];
+# endif
+      return (struct string_with_storage) { name, STORAGE_OBJECT };
+#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;
+              const char *name = querylocale_func (locale_backend, category);
+              return (struct string_with_storage) { name, STORAGE_OBJECT };
+            }
+        }
+      else
+        /* It's the "C" or "POSIX" locale.  */
+        return (struct string_with_storage) { "C", STORAGE_INDEFINITE };
+#elif defined __ANDROID__
+      /* Android API level >= 21 */
+      struct __locale_t {
+        size_t mb_cur_max;
+      };
+      const char *name = ((struct __locale_t *) locale)->mb_cur_max == 4 ? "C.UTF-8" : "C";
+      return (struct string_with_storage) { name, STORAGE_INDEFINITE };
+#else
+ #error "Please port gnulib getlocalename_l-unsafe.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 (struct string_with_storage) { name, STORAGE_GLOBAL };
+      else
+        /* Should normally not happen.  */
+        return (struct string_with_storage) { "", STORAGE_INDEFINITE };
+    }
+}
diff --git a/lib/getlocalename_l-unsafe.h b/lib/getlocalename_l-unsafe.h
new file mode 100644
index 0000000000..d56b113ab8
--- /dev/null
+++ b/lib/getlocalename_l-unsafe.h
@@ -0,0 +1,67 @@
+/* Return name of a single locale category.
+   Copyright (C) 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/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2025.  */
+
+#ifndef _GETLOCALENAME_L_UNSAFE_H
+#define _GETLOCALENAME_L_UNSAFE_H
+
+#include <locale.h>
+
+#include "arg-nonnull.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* This enumeration describes the storage of the return value.  */
+enum storage
+{
+  /* Storage with indefinite extent.  */
+  STORAGE_INDEFINITE,
+  /* Storage in a thread-independent location, valid until the next setlocale()
+     call.  */
+  STORAGE_GLOBAL,
+  /* Storage in a thread-local buffer, valid until the next
+     getlocalename_l_unlocked() call in the same thread.  */
+  STORAGE_THREAD,
+  /* Storage in or attached to the locale_t object, valid until the next call
+     to newlocale() with that object as base or until the next freelocale() call
+     for that object.  */
+  STORAGE_OBJECT
+};
+
+/* Return type of getlocalename_l_unlocked.  */
+struct string_with_storage
+{
+  const char *value;
+  enum storage storage;
+};
+
+/* Returns the name of the locale category CATEGORY in the given LOCALE object
+   or, if LOCALE is LC_GLOBAL_LOCALE, in the global locale.
+   CATEGORY must be != LC_ALL.  */
+extern struct string_with_storage
+       getlocalename_l_unsafe (int category, locale_t locale)
+                              _GL_ARG_NONNULL ((2));
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _GETLOCALENAME_L_UNSAFE_H */
diff --git a/lib/getlocalename_l.c b/lib/getlocalename_l.c
index 4cc63a5a94..f3fd2d2875 100644
--- a/lib/getlocalename_l.c
+++ b/lib/getlocalename_l.c
@@ -20,429 +20,18 @@
 #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 "getlocalename_l-unsafe.h"
 
 #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)
 {
@@ -450,211 +39,17 @@ getlocalename_l (int category, locale_t locale)
     /* 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 "";
-    }
+  struct string_with_storage ret = getlocalename_l_unsafe (category, locale);
+  /* Return the result as a string of indefinite extent.
+     In the case (ret.storage == STORAGE_GLOBAL) 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.  For the other cases, it's a convenience to avoid undefined
+     behaviour in the calling program.  */
+  return (ret.storage != STORAGE_INDEFINITE
+          ? struniq (ret.value)
+          : ret.value);
 }
diff --git a/m4/getlocalename_l.m4 b/m4/getlocalename_l.m4
index e007b24b3c..e434dff5f3 100644
--- a/m4/getlocalename_l.m4
+++ b/m4/getlocalename_l.m4
@@ -1,5 +1,5 @@
 # getlocalename_l.m4
-# serial 1
+# serial 2
 dnl Copyright (C) 2025 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -13,6 +13,17 @@ AC_DEFUN([gl_FUNC_GETLOCALENAME_L_SIMPLE]
   dnl Persuade glibc <locale.h> to declare getlocalename_l().
   AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
 
+  AC_REQUIRE([gl_FUNC_GETLOCALENAME_L_UNSAFE])
+])
+
+# Prerequisites of lib/getlocalename_l.c.
+AC_DEFUN([gl_PREREQ_GETLOCALENAME_L_SIMPLE],
+[
+  :
+])
+
+AC_DEFUN_ONCE([gl_FUNC_GETLOCALENAME_L_UNSAFE],
+[
   AC_REQUIRE([gl_FUNC_SETLOCALE_NULL])
   AC_CHECK_FUNCS_ONCE([getlocalename_l])
   if test $ac_cv_func_getlocalename_l = yes; then
@@ -26,8 +37,8 @@ AC_DEFUN([gl_FUNC_GETLOCALENAME_L_SIMPLE]
   AC_SUBST([GETLOCALENAME_L_LIB])
 ])
 
-# Prerequisites of lib/getlocalename_l.c.
-AC_DEFUN([gl_PREREQ_GETLOCALENAME_L_SIMPLE],
+# Prerequisites of lib/getlocalename_l-unsafe.c.
+AC_DEFUN([gl_PREREQ_GETLOCALENAME_L_UNSAFE],
 [
   AC_REQUIRE([gl_LOCALE_H_DEFAULTS])
   AC_REQUIRE([gl_LOCALE_T])
diff --git a/modules/getlocalename_l-simple b/modules/getlocalename_l-simple
index e169721fd5..f750146d97 100644
--- a/modules/getlocalename_l-simple
+++ b/modules/getlocalename_l-simple
@@ -3,22 +3,17 @@ 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]
+getlocalename_l-unsafe [test $HAVE_GETLOCALENAME_L = 0]
+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]
 
 configure.ac:
 gl_FUNC_GETLOCALENAME_L_SIMPLE
@@ -31,7 +26,7 @@ gl_LOCALE_MODULE_INDICATOR([getlocalename_l])
 
 Makefile.am:
 if GL_COND_OBJ_GETLOCALENAME_L
-lib_SOURCES += getlocalename_l.c localename-table.c
+lib_SOURCES += getlocalename_l.c
 endif
 
 Include:
diff --git a/modules/getlocalename_l-unsafe b/modules/getlocalename_l-unsafe
new file mode 100644
index 0000000000..937d443bef
--- /dev/null
+++ b/modules/getlocalename_l-unsafe
@@ -0,0 +1,40 @@
+Description:
+getlocalename_l_unsafe() function: return name of a single locale category.
+
+Files:
+lib/getlocalename_l-unsafe.h
+lib/getlocalename_l-unsafe.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
+flexmember
+lock
+bool
+thread-optim
+setlocale-messages
+setlocale-null
+free-posix
+
+configure.ac:
+gl_FUNC_GETLOCALENAME_L_UNSAFE
+gl_PREREQ_GETLOCALENAME_L_UNSAFE
+
+Makefile.am:
+lib_SOURCES += getlocalename_l-unsafe.c localename-table.c
+
+Include:
+"getlocalename_l-unsafe.h"
+
+Link:
+$(GETLOCALENAME_L_LIB)
+
+License:
+LGPLv2+
+
+Maintainer:
+all
-- 
2.43.0

>From 6e463f6c031d0f0dfbfb6e48496d2ef585247071 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 22 Feb 2025 18:18:53 +0100
Subject: [PATCH 2/4] localename-unsafe: Use getlocalename_l-unsafe.

* lib/localename-unsafe.c: Include getlocalename_l-unsafe.h.
(gl_locale_name_thread_unsafe): Invoke getlocalename_l_unsafe instead of
getlocalename_l.
* modules/localename-unsafe (Depends-on): Add getlocalename_l-unsafe.
Remove getlocalename_l-simple.
* modules/localename-unsafe-limited (Depends-on): Likewise.
---
 ChangeLog                         | 10 ++++++++++
 lib/localename-unsafe.c           |  7 ++++++-
 modules/localename-unsafe         |  2 +-
 modules/localename-unsafe-limited |  2 +-
 4 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index dd437a98b7..3f9a7bb618 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2025-02-22  Bruno Haible  <br...@clisp.org>
+
+	localename-unsafe: Use getlocalename_l-unsafe.
+	* lib/localename-unsafe.c: Include getlocalename_l-unsafe.h.
+	(gl_locale_name_thread_unsafe): Invoke getlocalename_l_unsafe instead of
+	getlocalename_l.
+	* modules/localename-unsafe (Depends-on): Add getlocalename_l-unsafe.
+	Remove getlocalename_l-simple.
+	* modules/localename-unsafe-limited (Depends-on): Likewise.
+
 2025-02-22  Bruno Haible  <br...@clisp.org>
 
 	getlocalename_l-unsafe: New module.
diff --git a/lib/localename-unsafe.c b/lib/localename-unsafe.c
index 569bf9ba99..a058281f5b 100644
--- a/lib/localename-unsafe.c
+++ b/lib/localename-unsafe.c
@@ -34,6 +34,7 @@
 #include <locale.h>
 #include <string.h>
 
+#include "getlocalename_l-unsafe.h"
 #include "setlocale_null.h"
 
 #if HAVE_GOOD_USELOCALE
@@ -2622,7 +2623,11 @@ gl_locale_name_thread_unsafe (int category, _GL_UNUSED const char *categoryname)
   {
     locale_t thread_locale = uselocale (NULL);
     if (thread_locale != LC_GLOBAL_LOCALE)
-      return getlocalename_l (category, thread_locale);
+      {
+        struct string_with_storage ret =
+          getlocalename_l_unsafe (category, thread_locale);
+        return ret.value;
+      }
   }
 #endif
   /* On WINDOWS_NATIVE, don't use GetThreadLocale() here, because when
diff --git a/modules/localename-unsafe b/modules/localename-unsafe
index b7d073deea..01ab5a42d0 100644
--- a/modules/localename-unsafe
+++ b/modules/localename-unsafe
@@ -18,7 +18,7 @@ extensions
 locale-h
 strdup
 lock
-getlocalename_l-simple
+getlocalename_l-unsafe
 setlocale-null-unlocked
 
 configure.ac:
diff --git a/modules/localename-unsafe-limited b/modules/localename-unsafe-limited
index 51e8b1e882..9ca367e48c 100644
--- a/modules/localename-unsafe-limited
+++ b/modules/localename-unsafe-limited
@@ -13,7 +13,7 @@ m4/lcmessage.m4
 Depends-on:
 extensions
 locale-h
-getlocalename_l-simple
+getlocalename_l-unsafe
 setlocale-null-unlocked
 
 configure.ac:
-- 
2.43.0

>From 05721dc8382d42238bac7b0323c122979473e500 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 22 Feb 2025 18:35:37 +0100
Subject: [PATCH 3/4] getlocalename_l-unsafe-limited: New module.

* modules/getlocalename_l-unsafe-limited: New file.
* modules/getlocalename_l-unsafe (Depends-on): Add
getlocalename_l-unsafe-limited.
(Makefile.am): Don't compile getlocalename_l-unsafe.c if already
compiled as part of module 'getlocalename_l-unsafe-limited'.
---
 ChangeLog                              |  9 ++++++++
 modules/getlocalename_l-unsafe         |  6 ++++-
 modules/getlocalename_l-unsafe-limited | 31 ++++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 1 deletion(-)
 create mode 100644 modules/getlocalename_l-unsafe-limited

diff --git a/ChangeLog b/ChangeLog
index 3f9a7bb618..b2caa4e01a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2025-02-22  Bruno Haible  <br...@clisp.org>
+
+	getlocalename_l-unsafe-limited: New module.
+	* modules/getlocalename_l-unsafe-limited: New file.
+	* modules/getlocalename_l-unsafe (Depends-on): Add
+	getlocalename_l-unsafe-limited.
+	(Makefile.am): Don't compile getlocalename_l-unsafe.c if already
+	compiled as part of module 'getlocalename_l-unsafe-limited'.
+
 2025-02-22  Bruno Haible  <br...@clisp.org>
 
 	localename-unsafe: Use getlocalename_l-unsafe.
diff --git a/modules/getlocalename_l-unsafe b/modules/getlocalename_l-unsafe
index 937d443bef..5a37d46b85 100644
--- a/modules/getlocalename_l-unsafe
+++ b/modules/getlocalename_l-unsafe
@@ -12,6 +12,7 @@ m4/intl-thread-locale.m4
 
 Depends-on:
 locale-h
+getlocalename_l-unsafe-limited
 flexmember
 lock
 bool
@@ -25,7 +26,10 @@ gl_FUNC_GETLOCALENAME_L_UNSAFE
 gl_PREREQ_GETLOCALENAME_L_UNSAFE
 
 Makefile.am:
-lib_SOURCES += getlocalename_l-unsafe.c localename-table.c
+if !GL_COND_OBJ_GETLOCALENAME_L_UNSAFE_LIMITED
+lib_SOURCES += getlocalename_l-unsafe.c
+endif
+lib_SOURCES += localename-table.c
 
 Include:
 "getlocalename_l-unsafe.h"
diff --git a/modules/getlocalename_l-unsafe-limited b/modules/getlocalename_l-unsafe-limited
new file mode 100644
index 0000000000..dd91b7a1b6
--- /dev/null
+++ b/modules/getlocalename_l-unsafe-limited
@@ -0,0 +1,31 @@
+Description:
+getlocalename_l_unsafe() function: return name of a single locale category.
+Only works on a limited set of platforms: on NetBSD and Solaris.
+
+Files:
+lib/getlocalename_l-unsafe.h
+lib/getlocalename_l-unsafe.c
+m4/getlocalename_l.m4
+
+Depends-on:
+locale-h
+setlocale-null
+
+configure.ac:
+AC_REQUIRE([AC_CANONICAL_HOST])
+gl_CONDITIONAL([GL_COND_OBJ_GETLOCALENAME_L_UNSAFE_LIMITED],
+               [case "$host_os" in netbsd* | solaris*) true;; *) false;; esac])
+
+Makefile.am:
+if GL_COND_OBJ_GETLOCALENAME_L_UNSAFE_LIMITED
+lib_SOURCES += getlocalename_l-unsafe.c
+endif
+
+Include:
+"getlocalename_l-unsafe.h"
+
+License:
+LGPLv2+
+
+Maintainer:
+all
-- 
2.43.0

>From b8f9df907427f2ad8fcc2bbed3398be27f49a7da Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 22 Feb 2025 18:39:21 +0100
Subject: [PATCH 4/4] localename-unsafe-limited: Use
 getlocalename_l-unsafe-limited.

* modules/localename-unsafe-limited (Depends-on): Add
getlocalename_l-unsafe-limited. Remove getlocalename_l-unsafe.
---
 ChangeLog                         | 6 ++++++
 modules/localename-unsafe-limited | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index b2caa4e01a..e2432dfc04 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2025-02-22  Bruno Haible  <br...@clisp.org>
+
+	localename-unsafe-limited: Use getlocalename_l-unsafe-limited.
+	* modules/localename-unsafe-limited (Depends-on): Add
+	getlocalename_l-unsafe-limited. Remove getlocalename_l-unsafe.
+
 2025-02-22  Bruno Haible  <br...@clisp.org>
 
 	getlocalename_l-unsafe-limited: New module.
diff --git a/modules/localename-unsafe-limited b/modules/localename-unsafe-limited
index 9ca367e48c..6737c0101f 100644
--- a/modules/localename-unsafe-limited
+++ b/modules/localename-unsafe-limited
@@ -13,7 +13,7 @@ m4/lcmessage.m4
 Depends-on:
 extensions
 locale-h
-getlocalename_l-unsafe
+getlocalename_l-unsafe-limited
 setlocale-null-unlocked
 
 configure.ac:
-- 
2.43.0

Reply via email to