summaryrefslogtreecommitdiff
path: root/lib/getlocalename_l-unsafe.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/getlocalename_l-unsafe.c')
-rw-r--r--lib/getlocalename_l-unsafe.c695
1 files changed, 695 insertions, 0 deletions
diff --git a/lib/getlocalename_l-unsafe.c b/lib/getlocalename_l-unsafe.c
new file mode 100644
index 00000000..e2738829
--- /dev/null
+++ b/lib/getlocalename_l-unsafe.c
@@ -0,0 +1,695 @@
+/* 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/>. */
+
+/* Don't use __attribute__ __nonnull__ in this compilation unit. Otherwise gcc
+ optimizes away the locale == NULL tests below. */
+#define _GL_ARG_NONNULL(params)
+
+#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 || defined __ANDROID__
+# include "setlocale-fixes.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/PR10968>. */
+ 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 };
+ }
+ /* We shouldn't get here. */
+ return (struct string_with_storage) { "", STORAGE_INDEFINITE };
+# else
+ /* Solaris 11 OpenIndiana or Solaris 11 OmniOS. */
+# if HAVE_GETLOCALENAME_L
+ /* illumos after April 2025. */
+# undef getlocalename_l
+ const char *name = getlocalename_l (category, locale);
+ return (struct string_with_storage) { name, STORAGE_OBJECT };
+# else
+ /* 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
+# 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
+#if defined __ANDROID__
+ name = setlocale_fixed_null (category);
+#else
+ name = setlocale_null (category);
+#endif
+ if (name != NULL)
+ return (struct string_with_storage) { name, STORAGE_GLOBAL };
+ else
+ /* Should normally not happen. */
+ return (struct string_with_storage) { "", STORAGE_INDEFINITE };
+ }
+}