/* Duplicate a locale object.
   Copyright (C) 2009-2026 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 <bruno@clisp.org>, 2007.  */

#include <config.h>

/* Specification.  */
#include <locale.h>

#include <errno.h>
#include <stdlib.h>
#include <string.h>

#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))

locale_t
duplocale (locale_t locale)
#undef duplocale
{
  /* Implement duplocale(LC_GLOBAL_LOCALE) on platforms without locale_t.
     Also, work around crash in the duplocale function in glibc < 2.12.
     See <https://sourceware.org/PR10969>.
     Also, on AIX 7.1, duplocale(LC_GLOBAL_LOCALE) returns (locale_t)0 with
     errno set to EINVAL.
     Also, on NetBSD 7.0, duplocale(LC_GLOBAL_LOCALE) returns a locale that
     corresponds to the C locale.  */
  if (locale == LC_GLOBAL_LOCALE)
    {
      /* Create a copy of the locale by fetching the name of each locale
         category, starting with LC_CTYPE.  */
      static struct { int cat; int mask; } const categories[] =
        {
            { LC_NUMERIC,        LC_NUMERIC_MASK },
            { LC_TIME,           LC_TIME_MASK },
            { LC_COLLATE,        LC_COLLATE_MASK },
            { LC_MONETARY,       LC_MONETARY_MASK },
            { LC_MESSAGES,       LC_MESSAGES_MASK }
#ifdef LC_PAPER
          , { LC_PAPER,          LC_PAPER_MASK }
#endif
#ifdef LC_NAME
          , { LC_NAME,           LC_NAME_MASK }
#endif
#ifdef LC_ADDRESS
          , { LC_ADDRESS,        LC_ADDRESS_MASK }
#endif
#ifdef LC_TELEPHONE
          , { LC_TELEPHONE,      LC_TELEPHONE_MASK }
#endif
#ifdef LC_MEASUREMENT
          , { LC_MEASUREMENT,    LC_MEASUREMENT_MASK }
#endif
#ifdef LC_IDENTIFICATION
          , { LC_IDENTIFICATION, LC_IDENTIFICATION_MASK }
#endif
        };
      char base_name[SETLOCALE_NULL_MAX];

      {
        int err = setlocale_null_r (LC_CTYPE, base_name, sizeof (base_name));
        if (err)
          {
            errno = err;
            return NULL;
          }
      }
      locale_t base_copy = newlocale (LC_ALL_MASK, base_name, NULL);
      if (base_copy == NULL)
        return NULL;

      for (unsigned int i = 0; i < SIZEOF (categories); i++)
        {
          int category = categories[i].cat;
          int category_mask = categories[i].mask;
          char name[SETLOCALE_NULL_MAX];

          {
            int err = setlocale_null_r (category, name, sizeof (name));
            if (err)
              {
                errno = err;
                return NULL;
              }
          }
          if (!streq (name, base_name))
            {
              locale_t copy = newlocale (category_mask, name, base_copy);
              if (copy == NULL)
                {
                  int saved_errno = errno;
                  freelocale (base_copy);
                  errno = saved_errno;
                  return NULL;
                }
              /* No need to call freelocale (base_copy) if copy != base_copy;
                 the newlocale function already takes care of doing it.  */
              base_copy = copy;
            }
        }

      return base_copy;
    }

#if GNULIB_defined_locale_t

  locale_t result = (struct gl_locale_t *) malloc (sizeof (struct gl_locale_t));
  if (result == NULL)
    {
      errno = ENOMEM;
      return NULL;
    }

  for (int i = 0; i < 6; i++)
    {
      int log2_lcmask = gl_index_to_log2_lcmask (i);
      int err;

      result->category[i].name = strdup (locale->category[i].name);
      if (result->category[i].name == NULL)
        {
          err = ENOMEM;
          goto fail_with_err;
        }
      result->category[i].is_c_locale = locale->category[i].is_c_locale;
# if HAVE_WINDOWS_LOCALE_T
      if (log2_lcmask == gl_log2_lc_mask (LC_MESSAGES)
          || result->category[i].is_c_locale)
        {
          /* Just to initialize it.  */
          result->category[i].system_locale = NULL;
        }
      else
        {
          int cat = log2_lcmask;
          (void) cat;
          /* Documentation:
             <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/create-locale-wcreate-locale>  */
          result->category[i].system_locale =
            _create_locale (LC_ALL /* or cat */, result->category[i].name);
          if (result->category[i].system_locale == NULL)
            {
              free (result->category[i].name);
              err = ENOENT;
              goto fail_with_err;
            }
        }
# endif

      if (0)
       fail_with_err:
        {
          while (--i >= 0)
            {
# if HAVE_WINDOWS_LOCALE_T
              if (!(i == gl_log2_lcmask_to_index (gl_log2_lc_mask (LC_MESSAGES))
                    || result->category[i].is_c_locale))
                /* Documentation:
                   <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/free-locale>  */
                _free_locale (result->category[i].system_locale);
# endif
              free (result->category[i].name);
            }
          free (result);
          errno = err;
          return NULL;
        }
    }

  /* Success.  */
  return result;


#else

  return duplocale (locale);

#endif
}
