/* 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 . */
#include
/* Specification. */
#include "getlocalename_l-unsafe.h"
#include
#include
#include
#include
#include
#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
#endif
#if defined __sun
# if HAVE_SOLARIS114_LOCALES
# include
# endif
#endif
#if HAVE_NAMELESS_LOCALES
# include "localename-table.h"
#endif
#if defined __HAIKU__
# include
#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 . */
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.
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
#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 };
}
}