/* 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 = "";
      /* Lock while looking up the hash node.  */
      gl_rwlock_rdlock (locale_lock);
      for (struct locale_hash_node *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
{
  /* 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.  */
  struct locale_categories_names names;
  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.  */
      name = struniq (name);
      for (int i = 0; i < 6; i++)
        names.category_name[i] = name;
    }
  else
    {
      /* Use base, possibly also name.  */
      if (base == NULL)
        {
          for (int 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)
        {
          for (int 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.  */

          /* Lock while looking up the hash node.  */
          gl_rwlock_rdlock (locale_lock);
          struct locale_hash_node *p;
          for (p = locale_hash_table[locale_hash_function (base) % LOCALE_HASH_TABLE_SIZE];
               p != NULL;
               p = p->next)
            if (p->locale == base)
              break;

          for (int 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);
        }
    }

  struct locale_hash_node *node =
    (struct locale_hash_node *) malloc (sizeof (struct locale_hash_node));
  if (node == NULL)
    /* errno is set to ENOMEM.  */
    return NULL;

  locale_t 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;

    /* Lock while inserting the new node.  */
    gl_rwlock_wrlock (locale_lock);
    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;
}

/* duplocale() override.  */
locale_t
duplocale (locale_t locale)
#undef duplocale
{
  if (locale == NULL)
    /* Invalid argument.  */
    abort ();

  struct locale_hash_node *node =
    (struct locale_hash_node *) malloc (sizeof (struct locale_hash_node));
  if (node == NULL)
    /* errno is set to ENOMEM.  */
    return NULL;

  locale_t result = duplocale (locale);
  if (result == NULL)
    {
      free (node);
      return NULL;
    }

  /* Fill the hash node.  */
  node->locale = result;
  if (locale == LC_GLOBAL_LOCALE)
    {
      for (int 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
    {
      /* Lock once, for the lookup and the insertion.  */
      gl_rwlock_wrlock (locale_lock);

      struct locale_hash_node *p;
      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.  */
          for (int 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 = NULL;
    /* Lock while removing the hash node.  */
    gl_rwlock_wrlock (locale_lock);
    for (struct locale_hash_node **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 };
    }
}
