/* Copyright (C) 2002-2025 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published
   by the Free Software Foundation; version 2 of the License, or
   (at your option) any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, see <https://www.gnu.org/licenses/>.  */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <inttypes.h>
#include <libintl.h>
#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/shm.h>
#include <sys/stat.h>

#include <libc-mmap.h>
#include <libc-pointer-arith.h>
#include "md5.h"
#include "../localeinfo.h"
#include "../locarchive.h"
#include "localedef.h"
#include "locfile.h"

/* Define the hash function.  We define the function as static inline.
   We must change the name so as not to conflict with simple-hash.h.  */
#define compute_hashval static archive_hashval
#define hashval_t uint32_t
#include "hashval.h"
#undef compute_hashval

extern const char *output_prefix;

#define ARCHIVE_NAME COMPLOCALEDIR "/locale-archive"

static const char *locnames[] =
  {
#define DEFINE_CATEGORY(category, category_name, items, a) \
  [category] = category_name,
#include "categories.def"
#undef  DEFINE_CATEGORY
  };


/* Size of the initial archive header.  */
#define INITIAL_NUM_NAMES	900
#define INITIAL_SIZE_STRINGS	7500
#define INITIAL_NUM_LOCREC	420
#define INITIAL_NUM_SUMS	2000


/* Get and set values (possibly endian-swapped) in structures mapped
   from or written directly to locale archives.  */
#define GET(FIELD)	maybe_swap_uint32 (FIELD)
#define SET(FIELD, VALUE)	((FIELD) = maybe_swap_uint32 (VALUE))
#define INC(FIELD, INCREMENT)	SET (FIELD, GET (FIELD) + (INCREMENT))


/* Size of the reserved address space area.  */
#define RESERVE_MMAP_SIZE	512 * 1024 * 1024

/* To prepare for enlargements of the mmaped area reserve some address
   space.  On some machines, being a file mapping rather than an anonymous
   mapping affects the address selection.  So do this mapping from the
   actual file, even though it's only a dummy to reserve address space.  */
static void *
prepare_address_space (int fd, size_t total, size_t *reserved, int *xflags,
		       void **mmap_base, size_t *mmap_len)
{
  if (total < RESERVE_MMAP_SIZE)
    {
      void *p = mmap64 (NULL, RESERVE_MMAP_SIZE, PROT_NONE, MAP_SHARED, fd, 0);
      if (p != MAP_FAILED)
	{
	  void *aligned_p = PTR_ALIGN_UP (p, MAP_FIXED_ALIGNMENT);
	  size_t align_adjust = aligned_p - p;
	  *mmap_base = p;
	  *mmap_len = RESERVE_MMAP_SIZE;
	  assert (align_adjust < RESERVE_MMAP_SIZE);
	  *reserved = RESERVE_MMAP_SIZE - align_adjust;
	  *xflags = MAP_FIXED;
	  return aligned_p;
	}
    }

  *reserved = total;
  *xflags = 0;
  *mmap_base = NULL;
  *mmap_len = 0;
  return NULL;
}


static void
create_archive (const char *archivefname, struct locarhandle *ah)
{
  int fd;
  char fname[strlen (archivefname) + sizeof (".XXXXXX")];
  struct locarhead head;
  size_t total;

  strcpy (stpcpy (fname, archivefname), ".XXXXXX");

  /* Create a temporary file in the correct directory.  */
  fd = mkstemp (fname);
  if (fd == -1)
    error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname);

  /* Create the initial content of the archive.  */
  SET (head.magic, AR_MAGIC);
  SET (head.serial, 0);
  SET (head.namehash_offset, sizeof (struct locarhead));
  SET (head.namehash_used, 0);
  SET (head.namehash_size, next_prime (INITIAL_NUM_NAMES));

  SET (head.string_offset,
       (GET (head.namehash_offset)
	+ GET (head.namehash_size) * sizeof (struct namehashent)));
  SET (head.string_used, 0);
  SET (head.string_size, INITIAL_SIZE_STRINGS);

  SET (head.locrectab_offset,
       GET (head.string_offset) + GET (head.string_size));
  SET (head.locrectab_used, 0);
  SET (head.locrectab_size, INITIAL_NUM_LOCREC);

  SET (head.sumhash_offset,
       (GET (head.locrectab_offset)
	+ GET (head.locrectab_size) * sizeof (struct locrecent)));
  SET (head.sumhash_used, 0);
  SET (head.sumhash_size, next_prime (INITIAL_NUM_SUMS));

  total = (GET (head.sumhash_offset)
	   + GET (head.sumhash_size) * sizeof (struct sumhashent));

  /* Write out the header and create room for the other data structures.  */
  if (TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head))) != sizeof (head))
    {
      int errval = errno;
      unlink (fname);
      error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
    }

  if (ftruncate64 (fd, total) != 0)
    {
      int errval = errno;
      unlink (fname);
      error (EXIT_FAILURE, errval, _("cannot resize archive file"));
    }

  size_t reserved, mmap_len;
  int xflags;
  void *mmap_base;
  void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base,
				   &mmap_len);

  /* Map the header and all the administration data structures.  */
  p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
  if (p == MAP_FAILED)
    {
      int errval = errno;
      unlink (fname);
      error (EXIT_FAILURE, errval, _("cannot map archive header"));
    }

  /* Now try to rename it.  We don't use the rename function since
     this would overwrite a file which has been created in
     parallel.  */
  if (link (fname, archivefname) == -1)
    {
      int errval = errno;

      /* We cannot use the just created file.  */
      close (fd);
      unlink (fname);

      if (errval == EEXIST)
	{
	  /* There is already an archive.  Must have been a localedef run
	     which happened in parallel.  Simply open this file then.  */
	  open_archive (ah, false);
	  return;
	}

      error (EXIT_FAILURE, errval, _("failed to create new locale archive"));
    }

  /* Remove the temporary name.  */
  unlink (fname);

  /* Make the file globally readable.  */
  if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
    {
      int errval = errno;
      unlink (archivefname);
      error (EXIT_FAILURE, errval,
	     _("cannot change mode of new locale archive"));
    }

  ah->fname = NULL;
  ah->fd = fd;
  ah->mmap_base = mmap_base;
  ah->mmap_len = mmap_len;
  ah->addr = p;
  ah->mmaped = total;
  ah->reserved = reserved;
}


/* This structure and qsort comparator function are used below to sort an
   old archive's locrec table in order of data position in the file.  */
struct oldlocrecent
{
  unsigned int cnt;
  struct locrecent *locrec;
};

static int
oldlocrecentcmp (const void *a, const void *b)
{
  struct locrecent *la = ((const struct oldlocrecent *) a)->locrec;
  struct locrecent *lb = ((const struct oldlocrecent *) b)->locrec;
  uint32_t start_a = -1, end_a = 0;
  uint32_t start_b = -1, end_b = 0;
  int cnt;

  for (cnt = 0; cnt < __LC_LAST; ++cnt)
    if (cnt != LC_ALL)
      {
	if (GET (la->record[cnt].offset) < start_a)
	  start_a = GET (la->record[cnt].offset);
	if (GET (la->record[cnt].offset) + GET (la->record[cnt].len) > end_a)
	  end_a = GET (la->record[cnt].offset) + GET (la->record[cnt].len);
      }
  assert (start_a != (uint32_t)-1);
  assert (end_a != 0);

  for (cnt = 0; cnt < __LC_LAST; ++cnt)
    if (cnt != LC_ALL)
      {
	if (GET (lb->record[cnt].offset) < start_b)
	  start_b = GET (lb->record[cnt].offset);
	if (GET (lb->record[cnt].offset) + GET (lb->record[cnt].len) > end_b)
	  end_b = GET (lb->record[cnt].offset) + GET (lb->record[cnt].len);
      }
  assert (start_b != (uint32_t)-1);
  assert (end_b != 0);

  if (start_a != start_b)
    return (int)start_a - (int)start_b;
  return (int)end_a - (int)end_b;
}


/* forward decls for below */
static uint32_t add_locale (struct locarhandle *ah, const char *name,
			    locale_data_t data, bool replace);
static void add_alias (struct locarhandle *ah, const char *alias,
		       bool replace, const char *oldname,
		       uint32_t *locrec_offset_p);


static bool
file_data_available_p (struct locarhandle *ah, uint32_t offset, uint32_t size)
{
  if (offset < ah->mmaped && offset + size <= ah->mmaped)
    return true;

  struct stat64 st;
  if (fstat64 (ah->fd, &st) != 0)
    return false;

  if (st.st_size > ah->reserved)
    return false;

  size_t start = ALIGN_DOWN (ah->mmaped, MAP_FIXED_ALIGNMENT);
  void *p = mmap64 (ah->addr + start, st.st_size - start,
		    PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED,
		    ah->fd, start);
  if (p == MAP_FAILED)
    {
      ah->mmaped = start;
      return false;
    }

  ah->mmaped = st.st_size;
  return true;
}


static int
compare_from_file (struct locarhandle *ah, void *p1, uint32_t offset2,
		   uint32_t size)
{
  void *p2 = xmalloc (size);
  if (pread (ah->fd, p2, size, offset2) != size)
    record_error (4, errno,
		  _("cannot read data from locale archive"));

  int res = memcmp (p1, p2, size);
  free (p2);
  return res;
}


static void
enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
{
  struct stat64 st;
  int fd;
  struct locarhead newhead;
  size_t total;
  unsigned int cnt, loccnt;
  struct namehashent *oldnamehashtab;
  struct locarhandle new_ah;
  size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
  char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
  char fname[prefix_len + sizeof (ARCHIVE_NAME) + sizeof (".XXXXXX") - 1];

  if (output_prefix)
    memcpy (archivefname, output_prefix, prefix_len);
  strcpy (archivefname + prefix_len, ARCHIVE_NAME);
  strcpy (stpcpy (fname, archivefname), ".XXXXXX");

  /* Not all of the old file has to be mapped.  Change this now this
     we will have to access the whole content.  */
  if (fstat64 (ah->fd, &st) != 0)
  enomap:
    error (EXIT_FAILURE, errno, _("cannot map locale archive file"));

  if (st.st_size < ah->reserved)
    ah->addr = mmap64 (ah->addr, st.st_size, PROT_READ | PROT_WRITE,
		       MAP_SHARED | MAP_FIXED, ah->fd, 0);
  else
    {
      if (ah->mmap_base)
	munmap (ah->mmap_base, ah->mmap_len);
      else
	munmap (ah->addr, ah->reserved);
      ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE,
			 MAP_SHARED, ah->fd, 0);
      ah->reserved = st.st_size;
      ah->mmap_base = NULL;
      ah->mmap_len = 0;
      head = ah->addr;
    }
  if (ah->addr == MAP_FAILED)
    goto enomap;
  ah->mmaped = st.st_size;

  /* Create a temporary file in the correct directory.  */
  fd = mkstemp (fname);
  if (fd == -1)
    error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname);

  /* Copy the existing head information.  */
  newhead = *head;

  /* Create the new archive header.  The sizes of the various tables
     should be double from what is currently used.  */
  SET (newhead.namehash_size,
       MAX (next_prime (2 * GET (newhead.namehash_used)),
	    GET (newhead.namehash_size)));
  if (verbose)
    printf ("name: size: %u, used: %d, new: size: %u\n",
	    GET (head->namehash_size),
	    GET (head->namehash_used), GET (newhead.namehash_size));

  SET (newhead.string_offset, (GET (newhead.namehash_offset)
			       + (GET (newhead.namehash_size)
				  * sizeof (struct namehashent))));
  /* Keep the string table size aligned to 4 bytes, so that
     all the struct { uint32_t } types following are happy.  */
  SET (newhead.string_size, MAX ((2 * GET (newhead.string_used) + 3) & -4,
				 GET (newhead.string_size)));

  SET (newhead.locrectab_offset,
       GET (newhead.string_offset) + GET (newhead.string_size));
  SET (newhead.locrectab_size, MAX (2 * GET (newhead.locrectab_used),
				    GET (newhead.locrectab_size)));

  SET (newhead.sumhash_offset, (GET (newhead.locrectab_offset)
				+ (GET (newhead.locrectab_size)
				   * sizeof (struct locrecent))));
  SET (newhead.sumhash_size,
       MAX (next_prime (2 * GET (newhead.sumhash_used)),
	    GET (newhead.sumhash_size)));

  total = (GET (newhead.sumhash_offset)
	   + GET (newhead.sumhash_size) * sizeof (struct sumhashent));

  /* The new file is empty now.  */
  SET (newhead.namehash_used, 0);
  SET (newhead.string_used, 0);
  SET (newhead.locrectab_used, 0);
  SET (newhead.sumhash_used, 0);

  /* Write out the header and create room for the other data structures.  */
  if (TEMP_FAILURE_RETRY (write (fd, &newhead, sizeof (newhead)))
      != sizeof (newhead))
    {
      int errval = errno;
      unlink (fname);
      error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
    }

  if (ftruncate64 (fd, total) != 0)
    {
      int errval = errno;
      unlink (fname);
      error (EXIT_FAILURE, errval, _("cannot resize archive file"));
    }

  size_t reserved, mmap_len;
  int xflags;
  void *mmap_base;
  void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base,
				   &mmap_len);

  /* Map the header and all the administration data structures.  */
  p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
  if (p == MAP_FAILED)
    {
      int errval = errno;
      unlink (fname);
      error (EXIT_FAILURE, errval, _("cannot map archive header"));
    }

  /* Lock the new file.  */
  if (lockf64 (fd, F_LOCK, total) != 0)
    {
      int errval = errno;
      unlink (fname);
      error (EXIT_FAILURE, errval, _("cannot lock new archive"));
    }

  new_ah.mmaped = total;
  new_ah.mmap_base = mmap_base;
  new_ah.mmap_len = mmap_len;
  new_ah.addr = p;
  new_ah.fd = fd;
  new_ah.reserved = reserved;

  /* Walk through the hash name hash table to find out what data is
     still referenced and transfer it into the new file.  */
  oldnamehashtab = (struct namehashent *) ((char *) ah->addr
					   + GET (head->namehash_offset));

  /* Sort the old locrec table in order of data position.  */
  struct oldlocrecent oldlocrecarray[GET (head->namehash_size)];
  for (cnt = 0, loccnt = 0; cnt < GET (head->namehash_size); ++cnt)
    if (GET (oldnamehashtab[cnt].locrec_offset) != 0)
      {
	oldlocrecarray[loccnt].cnt = cnt;
	oldlocrecarray[loccnt++].locrec
	  = (struct locrecent *) ((char *) ah->addr
				  + GET (oldnamehashtab[cnt].locrec_offset));
      }
  qsort (oldlocrecarray, loccnt, sizeof (struct oldlocrecent),
	 oldlocrecentcmp);

  uint32_t last_locrec_offset = 0;
  for (cnt = 0; cnt < loccnt; ++cnt)
    {
      /* Insert this entry in the new hash table.  */
      locale_data_t old_data;
      unsigned int idx;
      struct locrecent *oldlocrec = oldlocrecarray[cnt].locrec;

      for (idx = 0; idx < __LC_LAST; ++idx)
	if (idx != LC_ALL)
	  {
	    old_data[idx].size = GET (oldlocrec->record[idx].len);
	    old_data[idx].addr
	      = ((char *) ah->addr + GET (oldlocrec->record[idx].offset));

	    __md5_buffer (old_data[idx].addr, old_data[idx].size,
			  old_data[idx].sum);
	  }

      if (cnt > 0 && oldlocrecarray[cnt - 1].locrec == oldlocrec)
	{
	  const char *oldname
	    = ((char *) ah->addr
	       + GET (oldnamehashtab[oldlocrecarray[cnt
						    - 1].cnt].name_offset));

	  add_alias
	    (&new_ah,
	     ((char *) ah->addr
	      + GET (oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset)),
	     0, oldname, &last_locrec_offset);
	  continue;
	}

      last_locrec_offset =
	add_locale
	(&new_ah,
	 ((char *) ah->addr
	  + GET (oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset)),
	 old_data, 0);
      if (last_locrec_offset == 0)
	error (EXIT_FAILURE, 0, _("cannot extend locale archive file"));
    }

  /* Make the file globally readable.  */
  if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
    {
      int errval = errno;
      unlink (fname);
      error (EXIT_FAILURE, errval,
	     _("cannot change mode of resized locale archive"));
    }

  /* Rename the new file.  */
  if (rename (fname, archivefname) != 0)
    {
      int errval = errno;
      unlink (fname);
      error (EXIT_FAILURE, errval, _("cannot rename new archive"));
    }

  /* Close the old file.  */
  close_archive (ah);

  /* Add the information for the new one.  */
  *ah = new_ah;
}


void
open_archive (struct locarhandle *ah, bool readonly)
{
  struct stat64 st;
  struct stat64 st2;
  int fd;
  struct locarhead head;
  int retry = 0;
  size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
  char default_fname[prefix_len + sizeof (ARCHIVE_NAME)];
  const char *archivefname = ah->fname;

  /* If ah has a non-NULL fname open that otherwise open the default.  */
  if (archivefname == NULL)
    {
      archivefname = default_fname;
      if (output_prefix)
        memcpy (default_fname, output_prefix, prefix_len);
      strcpy (default_fname + prefix_len, ARCHIVE_NAME);
    }

  while (1)
    {
      /* Open the archive.  We must have exclusive write access.  */
      fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
      if (fd == -1)
	{
	  /* Maybe the file does not yet exist? If we are opening
	     the default locale archive we ignore the failure and
	     list an empty archive, otherwise we print an error
	     and exit.  */
	  if (errno == ENOENT && archivefname == default_fname)
	    {
	      if (readonly)
		{
		  static const struct locarhead nullhead =
		    {
		      .namehash_used = 0,
		      .namehash_offset = 0,
		      .namehash_size = 0
		    };

		  ah->addr = (void *) &nullhead;
		  ah->fd = -1;
		}
	      else
		create_archive (archivefname, ah);

	      return;
	    }
	  else
	    error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
		   archivefname);
	}

      if (fstat64 (fd, &st) < 0)
	error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""),
	       archivefname);

      if (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1)
	{
	  close (fd);

	  if (retry++ < max_locarchive_open_retry)
	    {
	      struct timespec req;

	      /* Wait for a bit.  */
	      req.tv_sec = 0;
	      req.tv_nsec = 1000000 * (random () % 500 + 1);
	      (void) nanosleep (&req, NULL);

	      continue;
	    }

	  error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
		 archivefname);
	}

      /* One more check.  Maybe another process replaced the archive file
	 with a new, larger one since we opened the file.  */
      if (stat64 (archivefname, &st2) == -1
	  || st.st_dev != st2.st_dev
	  || st.st_ino != st2.st_ino)
	{
	  if (lockf64 (fd, F_ULOCK, sizeof (struct locarhead)) != 0)
	    error (EXIT_FAILURE, errno, _("cannot unlock archive header"));
	  close (fd);
	  continue;
	}

      /* Leave the loop.  */
      break;
    }

  /* Read the header.  */
  if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
    {
      int errval = errno;
      if (lockf64 (fd, F_ULOCK, sizeof (struct locarhead)) != 0)
	error (EXIT_FAILURE, errno, _("cannot unlock archive header"));
      error (EXIT_FAILURE, errval, _("cannot read archive header"));
    }

  /* Check the magic value */
  if (GET (head.magic) != AR_MAGIC)
    {
      if (lockf64 (fd, F_ULOCK, sizeof (struct locarhead)) != 0)
	error (EXIT_FAILURE, errno, _("cannot unlock archive header"));
      error (EXIT_FAILURE, 0, _("bad magic value in archive header"));
    }

  ah->fd = fd;
  ah->mmaped = st.st_size;

  size_t reserved, mmap_len;
  int xflags;
  void *mmap_base;
  void *p = prepare_address_space (fd, st.st_size, &reserved, &xflags,
				   &mmap_base, &mmap_len);

  /* Map the entire file.  We might need to compare the category data
     in the file with the newly added data.  */
  ah->addr = mmap64 (p, st.st_size, PROT_READ | (readonly ? 0 : PROT_WRITE),
		     MAP_SHARED | xflags, fd, 0);
  if (ah->addr == MAP_FAILED)
    {
      int errval = errno;
      if (lockf64 (fd, F_ULOCK, sizeof (struct locarhead)) != 0)
	error (EXIT_FAILURE, errno, _("cannot unlock archive header"));
      error (EXIT_FAILURE, errval, _("cannot map archive header"));
    }
  ah->reserved = reserved;
  ah->mmap_base = mmap_base;
  ah->mmap_len = mmap_len;
}


void
close_archive (struct locarhandle *ah)
{
  if (ah->fd != -1)
    {
      if (ah->mmap_base)
	munmap (ah->mmap_base, ah->mmap_len);
      else
	munmap (ah->addr, ah->reserved);
      close (ah->fd);
    }
}

#include "../../intl/explodename.c"
#include "../../intl/l10nflist.c"

static struct namehashent *
insert_name (struct locarhandle *ah,
	     const char *name, size_t name_len, bool replace)
{
  const struct locarhead *const head = ah->addr;
  struct namehashent *namehashtab
    = (struct namehashent *) ((char *) ah->addr
			      + GET (head->namehash_offset));
  unsigned int insert_idx, idx, incr;

  /* Hash value of the locale name.  */
  uint32_t hval = archive_hashval (name, name_len);

  insert_idx = -1;
  idx = hval % GET (head->namehash_size);
  incr = 1 + hval % (GET (head->namehash_size) - 2);

  /* If the name_offset field is zero this means this is a
     deleted entry and therefore no entry can be found.  */
  while (GET (namehashtab[idx].name_offset) != 0)
    {
      if (GET (namehashtab[idx].hashval) == hval
	  && (strcmp (name,
		      (char *) ah->addr + GET (namehashtab[idx].name_offset))
	      == 0))
	{
	  /* Found the entry.  */
	  if (GET (namehashtab[idx].locrec_offset) != 0 && ! replace)
	    {
	      if (! be_quiet)
		error (0, 0, _("locale '%s' already exists"), name);
	      return NULL;
	    }

	  break;
	}

      if (GET (namehashtab[idx].hashval) == hval && ! be_quiet)
	{
	  error (0, 0, "hash collision (%u) %s, %s",
		 hval, name,
		 (char *) ah->addr + GET (namehashtab[idx].name_offset));
	}

      /* Remember the first place we can insert the new entry.  */
      if (GET (namehashtab[idx].locrec_offset) == 0 && insert_idx == -1)
	insert_idx = idx;

      idx += incr;
      if (idx >= GET (head->namehash_size))
	idx -= GET (head->namehash_size);
    }

  /* Add as early as possible.  */
  if (insert_idx != -1)
    idx = insert_idx;

  SET (namehashtab[idx].hashval, hval); /* no-op if replacing an old entry.  */
  return &namehashtab[idx];
}

static void
add_alias (struct locarhandle *ah, const char *alias, bool replace,
	   const char *oldname, uint32_t *locrec_offset_p)
{
  uint32_t locrec_offset = *locrec_offset_p;
  struct locarhead *head = ah->addr;
  const size_t name_len = strlen (alias);
  struct namehashent *namehashent = insert_name (ah, alias, strlen (alias),
						 replace);
  if (namehashent == NULL && ! replace)
    return;

  if (GET (namehashent->name_offset) == 0)
    {
      /* We are adding a new hash entry for this alias.
	 Determine whether we have to resize the file.  */
      if (GET (head->string_used) + name_len + 1 > GET (head->string_size)
	  || (100 * GET (head->namehash_used)
	      > 75 * GET (head->namehash_size)))
	{
	  /* The current archive is not large enough.  */
	  enlarge_archive (ah, head);

	  /* The locrecent might have moved, so we have to look up
	     the old name afresh.  */
	  namehashent = insert_name (ah, oldname, strlen (oldname), true);
	  assert (GET (namehashent->name_offset) != 0);
	  assert (GET (namehashent->locrec_offset) != 0);
	  *locrec_offset_p = GET (namehashent->locrec_offset);

	  /* Tail call to try the whole thing again.  */
	  add_alias (ah, alias, replace, oldname, locrec_offset_p);
	  return;
	}

      /* Add the name string.  */
      memcpy (ah->addr + GET (head->string_offset) + GET (head->string_used),
	      alias, name_len + 1);
      SET (namehashent->name_offset,
	   GET (head->string_offset) + GET (head->string_used));
      INC (head->string_used, name_len + 1);

      INC (head->namehash_used, 1);
    }

  if (GET (namehashent->locrec_offset) != 0)
    {
      /* Replacing an existing entry.
	 Mark that we are no longer using the old locrecent.  */
      struct locrecent *locrecent
	= (struct locrecent *) ((char *) ah->addr
				+ GET (namehashent->locrec_offset));
      INC (locrecent->refs, -1);
    }

  /* Point this entry at the locrecent installed for the main name.  */
  SET (namehashent->locrec_offset, locrec_offset);
}

static int			/* qsort comparator used below */
cmpcategorysize (const void *a, const void *b)
{
  if (*(const void **) a == NULL)
    return 1;
  if (*(const void **) b == NULL)
    return -1;
  return ((*(const struct locale_category_data **) a)->size
	  - (*(const struct locale_category_data **) b)->size);
}

/* Check the content of the archive for duplicates.  Add the content
   of the files if necessary.  Returns the locrec_offset.  */
static uint32_t
add_locale (struct locarhandle *ah,
	    const char *name, locale_data_t data, bool replace)
{
  /* First look for the name.  If it already exists and we are not
     supposed to replace it don't do anything.  If it does not exist
     we have to allocate a new locale record.  */
  size_t name_len = strlen (name);
  uint32_t file_offsets[__LC_LAST];
  unsigned int num_new_offsets = 0;
  struct sumhashent *sumhashtab;
  uint32_t hval;
  unsigned int cnt, idx;
  struct locarhead *head;
  struct namehashent *namehashent;
  unsigned int incr;
  struct locrecent *locrecent;
  off64_t lastoffset;
  char *ptr;
  struct locale_category_data *size_order[__LC_LAST];
  /* Page size alignment is a minor optimization for locality; use a
     common value here rather than making the localedef output depend
     on the page size of the system on which localedef is run.  See
     <https://sourceware.org/glibc/wiki/Development_Todo/Master#Locale_archive_alignment>
     for more discussion.  */
  const size_t pagesz = 4096;
  int small_mask;

  head = ah->addr;
  sumhashtab = (struct sumhashent *) ((char *) ah->addr
				      + GET (head->sumhash_offset));

  memset (file_offsets, 0, sizeof (file_offsets));

  size_order[LC_ALL] = NULL;
  for (cnt = 0; cnt < __LC_LAST; ++cnt)
    if (cnt != LC_ALL)
      size_order[cnt] = &data[cnt];

  /* Sort the array in ascending order of data size.  */
  qsort (size_order, __LC_LAST, sizeof size_order[0], cmpcategorysize);

  small_mask = 0;
  data[LC_ALL].size = 0;
  for (cnt = 0; cnt < __LC_LAST; ++cnt)
    if (size_order[cnt] != NULL)
      {
	const size_t rounded_size = (size_order[cnt]->size + 15) & -16;
	if (data[LC_ALL].size + rounded_size > 2 * pagesz)
	  {
	    /* This category makes the small-categories block
	       stop being small, so this is the end of the road.  */
	    do
	      size_order[cnt++] = NULL;
	    while (cnt < __LC_LAST);
	    break;
	  }
	data[LC_ALL].size += rounded_size;
	small_mask |= 1 << (size_order[cnt] - data);
      }

  /* Copy the data for all the small categories into the LC_ALL
     pseudo-category.  */

  data[LC_ALL].addr = alloca (data[LC_ALL].size);
  memset (data[LC_ALL].addr, 0, data[LC_ALL].size);

  ptr = data[LC_ALL].addr;
  for (cnt = 0; cnt < __LC_LAST; ++cnt)
    if (small_mask & (1 << cnt))
      {
	memcpy (ptr, data[cnt].addr, data[cnt].size);
	ptr += (data[cnt].size + 15) & -16;
      }
  __md5_buffer (data[LC_ALL].addr, data[LC_ALL].size, data[LC_ALL].sum);

  /* For each locale category data set determine whether the same data
     is already somewhere in the archive.  */
  for (cnt = 0; cnt < __LC_LAST; ++cnt)
    if (small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
      {
	++num_new_offsets;

	/* Compute the hash value of the checksum to determine a
	   starting point for the search in the MD5 hash value
	   table.  */
	hval = archive_hashval (data[cnt].sum, 16);

	idx = hval % GET (head->sumhash_size);
	incr = 1 + hval % (GET (head->sumhash_size) - 2);

	while (GET (sumhashtab[idx].file_offset) != 0)
	  {
	    if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
	      {
		/* Check the content, there could be a collision of
		   the hash sum.

		   Unfortunately the sumhashent record does not include
		   the size of the stored data.  So we have to search for
		   it.  */
		locrecent
		  = (struct locrecent *) ((char *) ah->addr
					  + GET (head->locrectab_offset));
		size_t iloc;
		for (iloc = 0; iloc < GET (head->locrectab_used); ++iloc)
		  if (GET (locrecent[iloc].refs) != 0
		      && (GET (locrecent[iloc].record[cnt].offset)
			  == GET (sumhashtab[idx].file_offset)))
		    break;

		if (iloc != GET (head->locrectab_used)
		    && data[cnt].size == GET (locrecent[iloc].record[cnt].len)
		    /* We have to compare the content.  Either we can
		       have the data mmaped or we have to read from
		       the file.  */
		    && (file_data_available_p
			(ah, GET (sumhashtab[idx].file_offset),
			 data[cnt].size)
			? memcmp (data[cnt].addr,
				  (char *) ah->addr
				  + GET (sumhashtab[idx].file_offset),
				  data[cnt].size) == 0
			: compare_from_file (ah, data[cnt].addr,
					     GET (sumhashtab[idx].file_offset),
					     data[cnt].size) == 0))
		  {
		    /* Found it.  */
		    file_offsets[cnt] = GET (sumhashtab[idx].file_offset);
		    --num_new_offsets;
		    break;
		  }
	      }

	    idx += incr;
	    if (idx >= GET (head->sumhash_size))
	      idx -= GET (head->sumhash_size);
	  }
      }

  /* Find a slot for the locale name in the hash table.  */
  namehashent = insert_name (ah, name, name_len, replace);
  if (namehashent == NULL)	/* Already exists and !REPLACE.  */
    return 0;

  /* Determine whether we have to resize the file.  */
  if ((100 * (GET (head->sumhash_used) + num_new_offsets)
       > 75 * GET (head->sumhash_size))
      || (GET (namehashent->locrec_offset) == 0
	  && (GET (head->locrectab_used) == GET (head->locrectab_size)
	      || (GET (head->string_used) + name_len + 1
		  > GET (head->string_size))
	      || (100 * GET (head->namehash_used)
		  > 75 * GET (head->namehash_size)))))
    {
      /* The current archive is not large enough.  */
      enlarge_archive (ah, head);
      return add_locale (ah, name, data, replace);
    }

  /* Add the locale data which is not yet in the archive.  */
  for (cnt = 0, lastoffset = 0; cnt < __LC_LAST; ++cnt)
    if ((small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
	&& file_offsets[cnt] == 0)
      {
	/* The data for this section is not yet available in the
	   archive.  Append it.  */
	off64_t lastpos;
	uint32_t md5hval;

	lastpos = lseek64 (ah->fd, 0, SEEK_END);
	if (lastpos == (off64_t) -1)
	  error (EXIT_FAILURE, errno, _("cannot add to locale archive"));

	/* If block of small categories would cross page boundary,
	   align it unless it immediately follows a large category.  */
	if (cnt == LC_ALL && lastoffset != lastpos
	    && ((((lastpos & (pagesz - 1)) + data[cnt].size + pagesz - 1)
		 & -pagesz)
		> ((data[cnt].size + pagesz - 1) & -pagesz)))
	  {
	    size_t sz = pagesz - (lastpos & (pagesz - 1));
	    char *zeros = alloca (sz);

	    memset (zeros, 0, sz);
	    if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, sz) != sz))
	      error (EXIT_FAILURE, errno,
		     _("cannot add to locale archive"));

	    lastpos += sz;
	  }

	/* Align all data to a 16 byte boundary.  */
	if ((lastpos & 15) != 0)
	  {
	    static const char zeros[15] = { 0, };

	    if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, 16 - (lastpos & 15)))
		!= 16 - (lastpos & 15))
	      error (EXIT_FAILURE, errno, _("cannot add to locale archive"));

	    lastpos += 16 - (lastpos & 15);
	  }

	/* Remember the position.  */
	file_offsets[cnt] = lastpos;
	lastoffset = lastpos + data[cnt].size;

	/* Write the data.  */
	if (TEMP_FAILURE_RETRY (write (ah->fd, data[cnt].addr, data[cnt].size))
	    != data[cnt].size)
	  error (EXIT_FAILURE, errno, _("cannot add to locale archive"));

	/* Add the hash value to the hash table.  */
	md5hval = archive_hashval (data[cnt].sum, 16);

	idx = md5hval % GET (head->sumhash_size);
	incr = 1 + md5hval % (GET (head->sumhash_size) - 2);

	while (GET (sumhashtab[idx].file_offset) != 0)
	  {
	    idx += incr;
	    if (idx >= GET (head->sumhash_size))
	      idx -= GET (head->sumhash_size);
	  }

	memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
	SET (sumhashtab[idx].file_offset, file_offsets[cnt]);

	INC (head->sumhash_used, 1);
      }

  lastoffset = file_offsets[LC_ALL];
  for (cnt = 0; cnt < __LC_LAST; ++cnt)
    if (small_mask & (1 << cnt))
      {
	file_offsets[cnt] = lastoffset;
	lastoffset += (data[cnt].size + 15) & -16;
      }

  if (GET (namehashent->name_offset) == 0)
    {
      /* Add the name string.  */
      memcpy ((char *) ah->addr + GET (head->string_offset)
	      + GET (head->string_used),
	      name, name_len + 1);
      SET (namehashent->name_offset,
	   GET (head->string_offset) + GET (head->string_used));
      INC (head->string_used, name_len + 1);
      INC (head->namehash_used, 1);
    }

  if (GET (namehashent->locrec_offset == 0))
    {
      /* Allocate a name location record.  */
      SET (namehashent->locrec_offset, (GET (head->locrectab_offset)
					+ (GET (head->locrectab_used)
					   * sizeof (struct locrecent))));
      INC (head->locrectab_used, 1);
      locrecent = (struct locrecent *) ((char *) ah->addr
					+ GET (namehashent->locrec_offset));
      SET (locrecent->refs, 1);
    }
  else
    {
      /* If there are other aliases pointing to this locrecent,
	 we still need a new one.  If not, reuse the old one.  */

      locrecent = (struct locrecent *) ((char *) ah->addr
					+ GET (namehashent->locrec_offset));
      if (GET (locrecent->refs) > 1)
	{
	  INC (locrecent->refs, -1);
	  SET (namehashent->locrec_offset, (GET (head->locrectab_offset)
					    + (GET (head->locrectab_used)
					       * sizeof (struct locrecent))));
	  INC (head->locrectab_used, 1);
	  locrecent
	    = (struct locrecent *) ((char *) ah->addr
				    + GET (namehashent->locrec_offset));
	  SET (locrecent->refs, 1);
	}
    }

  /* Fill in the table with the locations of the locale data.  */
  for (cnt = 0; cnt < __LC_LAST; ++cnt)
    {
      SET (locrecent->record[cnt].offset, file_offsets[cnt]);
      SET (locrecent->record[cnt].len, data[cnt].size);
    }

  return GET (namehashent->locrec_offset);
}


/* Check the content of the archive for duplicates.  Add the content
   of the files if necessary.  Add all the names, possibly overwriting
   old files.  */
int
add_locale_to_archive (struct locarhandle *ah, const char *name,
		       locale_data_t data, bool replace)
{
  char *normalized_name = NULL;
  uint32_t locrec_offset;

  /* First analyze the name to decide how to archive it.  */
  const char *language;
  const char *modifier;
  const char *territory;
  const char *codeset;
  const char *normalized_codeset;
  int mask = _nl_explode_name (strdupa (name),
			       &language, &modifier, &territory,
			       &codeset, &normalized_codeset);
  if (mask == -1)
    return -1;

  if (mask & XPG_NORM_CODESET)
    /* This name contains a codeset in unnormalized form.
       We will store it in the archive with a normalized name.  */
    if (asprintf (&normalized_name, "%s%s%s.%s%s%s",
		  language, territory == NULL ? "" : "_", territory ?: "",
		  normalized_codeset,
		  modifier == NULL ? "" : "@", modifier ?: "") < 0)
      {
	free ((char *) normalized_codeset);
	return -1;
      }

  /* This call does the main work.  */
  locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
  if (locrec_offset == 0)
    {
      free (normalized_name);
      if (mask & XPG_NORM_CODESET)
	free ((char *) normalized_codeset);
      return -1;
    }

  if ((mask & XPG_CODESET) == 0)
    {
      /* This name lacks a codeset, so determine the locale's codeset and
	 add an alias for its name with normalized codeset appended.  */

      const struct
      {
	unsigned int magic;
	unsigned int nstrings;
	unsigned int strindex[0];
      } *filedata = data[LC_CTYPE].addr;
      codeset = (char *) filedata
	+ maybe_swap_uint32 (filedata->strindex[_NL_ITEM_INDEX
						(_NL_CTYPE_CODESET_NAME)]);
      char *normalized_codeset_name = NULL;

      normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
      mask |= XPG_NORM_CODESET;

      if (asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
		    language, territory == NULL ? "" : "_", territory ?: "",
		    normalized_codeset,
		    modifier == NULL ? "" : "@", modifier ?: "") < 0)
	{
	  free ((char *) normalized_codeset);
	  return -1;
	}

      add_alias (ah, normalized_codeset_name, replace,
		 normalized_name ?: name, &locrec_offset);
      free (normalized_codeset_name);
    }

  /* Now read the locale.alias files looking for lines whose
     right hand side matches our name after normalization.  */
  int result = 0;
  if (alias_file != NULL)
    {
      FILE *fp;
      fp = fopen (alias_file, "rm");
      if (fp == NULL)
	error (1, errno, _("locale alias file `%s' not found"),
	       alias_file);

      /* No threads present.  */
      __fsetlocking (fp, FSETLOCKING_BYCALLER);

      while (! feof_unlocked (fp))
	{
	  /* It is a reasonable approach to use a fix buffer here
	     because
	     a) we are only interested in the first two fields
	     b) these fields must be usable as file names and so must
	     not be that long  */
	  char buf[BUFSIZ];
	  char *alias;
	  char *value;
	  char *cp;

	  if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
	    /* EOF reached.  */
	    break;

	  cp = buf;
	  /* Ignore leading white space.  */
	  while (isspace (cp[0]) && cp[0] != '\n')
	    ++cp;

	  /* A leading '#' signals a comment line.  */
	  if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
	    {
	      alias = cp++;
	      while (cp[0] != '\0' && !isspace (cp[0]))
		++cp;
	      /* Terminate alias name.  */
	      if (cp[0] != '\0')
		*cp++ = '\0';

	      /* Now look for the beginning of the value.  */
	      while (isspace (cp[0]))
		++cp;

	      if (cp[0] != '\0')
		{
		  value = cp++;
		  while (cp[0] != '\0' && !isspace (cp[0]))
		    ++cp;
		  /* Terminate value.  */
		  if (cp[0] == '\n')
		    {
		      /* This has to be done to make the following
			 test for the end of line possible.  We are
			 looking for the terminating '\n' which do not
			 overwrite here.  */
		      *cp++ = '\0';
		      *cp = '\n';
		    }
		  else if (cp[0] != '\0')
		    *cp++ = '\0';

		  /* Does this alias refer to our locale?  We will
		     normalize the right hand side and compare the
		     elements of the normalized form.  */
		  {
		    const char *rhs_language;
		    const char *rhs_modifier;
		    const char *rhs_territory;
		    const char *rhs_codeset;
		    const char *rhs_normalized_codeset;
		    int rhs_mask = _nl_explode_name (value,
						     &rhs_language,
						     &rhs_modifier,
						     &rhs_territory,
						     &rhs_codeset,
						     &rhs_normalized_codeset);
		    if (rhs_mask == -1)
		      {
			result = -1;
			goto out;
		      }
		    if (!strcmp (language, rhs_language)
			&& ((rhs_mask & XPG_CODESET)
			    /* He has a codeset, it must match normalized.  */
			    ? !strcmp ((mask & XPG_NORM_CODESET)
				       ? normalized_codeset : codeset,
				       (rhs_mask & XPG_NORM_CODESET)
				       ? rhs_normalized_codeset : rhs_codeset)
			    /* He has no codeset, we must also have none.  */
			    : (mask & XPG_CODESET) == 0)
			/* Codeset (or lack thereof) matches.  */
			&& !strcmp (territory ?: "", rhs_territory ?: "")
			&& !strcmp (modifier ?: "", rhs_modifier ?: ""))
		      /* We have a winner.  */
		      add_alias (ah, alias, replace,
				 normalized_name ?: name, &locrec_offset);
		    if (rhs_mask & XPG_NORM_CODESET)
		      free ((char *) rhs_normalized_codeset);
		  }
		}
	    }

	  /* Possibly not the whole line fits into the buffer.
	     Ignore the rest of the line.  */
	  while (strchr (cp, '\n') == NULL)
	    {
	      cp = buf;
	      if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
		/* Make sure the inner loop will be left.  The outer
		   loop will exit at the `feof' test.  */
		*cp = '\n';
	    }
	}

    out:
      fclose (fp);
    }

  free (normalized_name);

  if (mask & XPG_NORM_CODESET)
    free ((char *) normalized_codeset);

  return result;
}


int
add_locales_to_archive (size_t nlist, char *list[], bool replace)
{
  struct locarhandle ah;
  int result = 0;

  /* Open the archive.  This call never returns if we cannot
     successfully open the archive.  */
  ah.fname = NULL;
  open_archive (&ah, false);

  while (nlist-- > 0)
    {
      const char *fname = *list++;
      size_t fnamelen = strlen (fname);
      struct stat64 st;
      DIR *dirp;
      struct dirent64 *d;
      int seen;
      locale_data_t data;
      int cnt;

      if (! be_quiet)
	printf (_("Adding %s\n"), fname);

      /* First see whether this really is a directory and whether it
	 contains all the require locale category files.  */
      if (stat64 (fname, &st) < 0)
	{
	  error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
		 strerror (errno));
	  continue;
	}
      if (!S_ISDIR (st.st_mode))
	{
	  error (0, 0, _("\"%s\" is no directory; ignored"), fname);
	  continue;
	}

      dirp = opendir (fname);
      if (dirp == NULL)
	{
	  error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
		 fname, strerror (errno));
	  continue;
	}

      seen = 0;
      while ((d = readdir64 (dirp)) != NULL)
	{
	  for (cnt = 0; cnt < __LC_LAST; ++cnt)
	    if (cnt != LC_ALL)
	      if (strcmp (d->d_name, locnames[cnt]) == 0)
		{
		  unsigned char d_type;

		  /* We have an object of the required name.  If it's
		     a directory we have to look at a file with the
		     prefix "SYS_".  Otherwise we have found what we
		     are looking for.  */
		  d_type = d->d_type;

		  if (d_type != DT_REG)
		    {
		      char fullname[fnamelen + 2 * strlen (d->d_name) + 7];

		      if (d_type == DT_UNKNOWN || d_type == DT_LNK)
			{
			  strcpy (stpcpy (stpcpy (fullname, fname), "/"),
				  d->d_name);

			  if (stat64 (fullname, &st) == -1)
			    /* We cannot stat the file, ignore it.  */
			    break;

			  d_type = IFTODT (st.st_mode);
			}

		      if (d_type == DT_DIR)
			{
			  /* We have to do more tests.  The file is a
			     directory and it therefore must contain a
			     regular file with the same name except a
			     "SYS_" prefix.  */
			  char *t = stpcpy (stpcpy (fullname, fname), "/");
			  strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
				  d->d_name);

			  if (stat64 (fullname, &st) == -1)
			    /* There is no SYS_* file or we cannot
			       access it.  */
			    break;

			  d_type = IFTODT (st.st_mode);
			}
		    }

		  /* If we found a regular file (eventually after
		     following a symlink) we are successful.  */
		  if (d_type == DT_REG)
		    ++seen;
		  break;
		}
	}

      closedir (dirp);

      if (seen != __LC_LAST - 1)
	{
	  /* We don't have all locale category files.  Ignore the name.  */
	  error (0, 0, _("incomplete set of locale files in \"%s\""),
		 fname);
	  continue;
	}

      /* Add the files to the archive.  To do this we first compute
	 sizes and the MD5 sums of all the files.  */
      for (cnt = 0; cnt < __LC_LAST; ++cnt)
	if (cnt != LC_ALL)
	  {
	    char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
	    int fd;

	    strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
	    fd = open64 (fullname, O_RDONLY);
	    if (fd == -1 || fstat64 (fd, &st) == -1)
	      {
		/* Cannot read the file.  */
		if (fd != -1)
		  close (fd);
		break;
	      }

	    if (S_ISDIR (st.st_mode))
	      {
		char *t;
		close (fd);
		t = stpcpy (stpcpy (fullname, fname), "/");
		strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
			locnames[cnt]);

		fd = open64 (fullname, O_RDONLY);
		if (fd == -1 || fstat64 (fd, &st) == -1
		    || !S_ISREG (st.st_mode))
		  {
		    if (fd != -1)
		      close (fd);
		    break;
		  }
	      }

	    /* Map the file.  */
	    data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
				     fd, 0);
	    if (data[cnt].addr == MAP_FAILED)
	      {
		/* Cannot map it.  */
		close (fd);
		break;
	      }

	    data[cnt].size = st.st_size;
	    __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);

	    /* We don't need the file descriptor anymore.  */
	    close (fd);
	  }

      if (cnt != __LC_LAST)
	{
	  while (cnt-- > 0)
	    if (cnt != LC_ALL)
	      munmap (data[cnt].addr, data[cnt].size);

	  error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);

	  continue;
	}

      result |= add_locale_to_archive (&ah, basename (fname), data, replace);

      for (cnt = 0; cnt < __LC_LAST; ++cnt)
	if (cnt != LC_ALL)
	  munmap (data[cnt].addr, data[cnt].size);
    }

  /* We are done.  */
  close_archive (&ah);

  return result;
}


int
delete_locales_from_archive (size_t nlist, char *list[])
{
  struct locarhandle ah;
  struct locarhead *head;
  struct namehashent *namehashtab;

  /* Open the archive.  This call never returns if we cannot
     successfully open the archive.  */
  ah.fname = NULL;
  open_archive (&ah, false);

  head = ah.addr;
  namehashtab = (struct namehashent *) ((char *) ah.addr
					+ GET (head->namehash_offset));

  while (nlist-- > 0)
    {
      const char *locname = *list++;
      uint32_t hval;
      unsigned int idx;
      unsigned int incr;

      /* Search for this locale in the archive.  */
      hval = archive_hashval (locname, strlen (locname));

      idx = hval % GET (head->namehash_size);
      incr = 1 + hval % (GET (head->namehash_size) - 2);

      /* If the name_offset field is zero this means this is no
	 deleted entry and therefore no entry can be found.  */
      while (GET (namehashtab[idx].name_offset) != 0)
	{
	  if (GET (namehashtab[idx].hashval) == hval
	      && (strcmp (locname,
			  ((char *) ah.addr
			   + GET (namehashtab[idx].name_offset)))
		  == 0))
	    {
	      /* Found the entry.  Now mark it as removed by zero-ing
		 the reference to the locale record.  */
	      SET (namehashtab[idx].locrec_offset, 0);
	      break;
	    }

	  idx += incr;
	  if (idx >= GET (head->namehash_size))
	    idx -= GET (head->namehash_size);
	}

      if (GET (namehashtab[idx].name_offset) == 0 && ! be_quiet)
	error (0, 0, _("locale \"%s\" not in archive"), locname);
    }

  close_archive (&ah);

  return 0;
}


struct nameent
{
  char *name;
  uint32_t locrec_offset;
};


struct dataent
{
  const unsigned char *sum;
  uint32_t file_offset;
  uint32_t nlink;
};


static int
nameentcmp (const void *a, const void *b)
{
  return strcmp (((const struct nameent *) a)->name,
		 ((const struct nameent *) b)->name);
}


static int
dataentcmp (const void *a, const void *b)
{
  if (((const struct dataent *) a)->file_offset
      < ((const struct dataent *) b)->file_offset)
    return -1;

  if (((const struct dataent *) a)->file_offset
      > ((const struct dataent *) b)->file_offset)
    return 1;

  return 0;
}


void
show_archive_content (const char *fname, int verbose)
{
  struct locarhandle ah;
  struct locarhead *head;
  struct namehashent *namehashtab;
  struct nameent *names;
  size_t cnt, used;

  /* Open the archive.  This call never returns if we cannot
     successfully open the archive.  */
  ah.fname = fname;
  open_archive (&ah, true);

  head = ah.addr;

  names = (struct nameent *) xmalloc (GET (head->namehash_used)
				      * sizeof (struct nameent));

  namehashtab = (struct namehashent *) ((char *) ah.addr
					+ GET (head->namehash_offset));
  for (cnt = used = 0; cnt < GET (head->namehash_size); ++cnt)
    if (GET (namehashtab[cnt].locrec_offset) != 0)
      {
	assert (used < GET (head->namehash_used));
	names[used].name = ah.addr + GET (namehashtab[cnt].name_offset);
	names[used++].locrec_offset = GET (namehashtab[cnt].locrec_offset);
      }

  /* Sort the names.  */
  qsort (names, used, sizeof (struct nameent), nameentcmp);

  if (verbose)
    {
      struct dataent *files;
      struct sumhashent *sumhashtab;
      int sumused;

      files = (struct dataent *) xmalloc (GET (head->sumhash_used)
					  * sizeof (struct dataent));

      sumhashtab = (struct sumhashent *) ((char *) ah.addr
					  + GET (head->sumhash_offset));
      for (cnt = sumused = 0; cnt < GET (head->sumhash_size); ++cnt)
	if (GET (sumhashtab[cnt].file_offset) != 0)
	  {
	    assert (sumused < GET (head->sumhash_used));
	    files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
	    files[sumused].file_offset = GET (sumhashtab[cnt].file_offset);
	    files[sumused++].nlink = 0;
	  }

      /* Sort by file locations.  */
      qsort (files, sumused, sizeof (struct dataent), dataentcmp);

      /* Compute nlink fields.  */
      for (cnt = 0; cnt < used; ++cnt)
	{
	  struct locrecent *locrec;
	  int idx;

	  locrec = (struct locrecent *) ((char *) ah.addr
					 + names[cnt].locrec_offset);
	  for (idx = 0; idx < __LC_LAST; ++idx)
	    if (GET (locrec->record[LC_ALL].offset) != 0
		? (idx == LC_ALL
		   || (GET (locrec->record[idx].offset)
		       < GET (locrec->record[LC_ALL].offset))
		   || ((GET (locrec->record[idx].offset)
			+ GET (locrec->record[idx].len))
		       > (GET (locrec->record[LC_ALL].offset)
			  + GET (locrec->record[LC_ALL].len))))
		: idx != LC_ALL)
	      {
		struct dataent *data, dataent;

		dataent.file_offset = GET (locrec->record[idx].offset);
		data = (struct dataent *) bsearch (&dataent, files, sumused,
						   sizeof (struct dataent),
						   dataentcmp);
		assert (data != NULL);
		++data->nlink;
	      }
	}

      /* Print it.  */
      for (cnt = 0; cnt < used; ++cnt)
	{
	  struct locrecent *locrec;
	  int idx, i;

	  locrec = (struct locrecent *) ((char *) ah.addr
					 + names[cnt].locrec_offset);
	  for (idx = 0; idx < __LC_LAST; ++idx)
	    if (idx != LC_ALL)
	      {
		struct dataent *data, dataent;

		dataent.file_offset = GET (locrec->record[idx].offset);
		if (GET (locrec->record[LC_ALL].offset) != 0
		    && (dataent.file_offset
			>= GET (locrec->record[LC_ALL].offset))
		    && (dataent.file_offset + GET (locrec->record[idx].len)
			<= (GET (locrec->record[LC_ALL].offset)
			    + GET (locrec->record[LC_ALL].len))))
		  dataent.file_offset = GET (locrec->record[LC_ALL].offset);

		data = (struct dataent *) bsearch (&dataent, files, sumused,
						   sizeof (struct dataent),
						   dataentcmp);
		printf ("%6d %7x %3d%c ",
			GET (locrec->record[idx].len),
			GET (locrec->record[idx].offset),
			data->nlink,
			(dataent.file_offset
			 == GET (locrec->record[LC_ALL].offset))
			? '+' : ' ');
		for (i = 0; i < 16; i += 4)
		    printf ("%02x%02x%02x%02x",
			    data->sum[i], data->sum[i + 1],
			    data->sum[i + 2], data->sum[i + 3]);
		printf (" %s/%s\n", names[cnt].name,
			idx == LC_MESSAGES ? "LC_MESSAGES/SYS_LC_MESSAGES"
			: locnames[idx]);
	      }
	}
      free (files);
    }
  else
    for (cnt = 0; cnt < used; ++cnt)
      puts (names[cnt].name);

  close_archive (&ah);

  exit (EXIT_SUCCESS);
}
