about summary refs log tree commit diff
path: root/REORG.TODO/locale/programs/locarchive.c
diff options
context:
space:
mode:
Diffstat (limited to 'REORG.TODO/locale/programs/locarchive.c')
-rw-r--r--REORG.TODO/locale/programs/locarchive.c1757
1 files changed, 1757 insertions, 0 deletions
diff --git a/REORG.TODO/locale/programs/locarchive.c b/REORG.TODO/locale/programs/locarchive.c
new file mode 100644
index 0000000000..f67b7b8d99
--- /dev/null
+++ b/REORG.TODO/locale/programs/locarchive.c
@@ -0,0 +1,1757 @@
+/* Copyright (C) 2002-2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
+
+   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 <http://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 "../../crypt/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)
+    WITH_CUR_LOCALE (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)
+	{
+	  (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
+	  close (fd);
+	  continue;
+	}
+
+      /* Leave the loop.  */
+      break;
+    }
+
+  /* Read the header.  */
+  if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
+    {
+      (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
+      error (EXIT_FAILURE, errno, _("cannot read 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)
+    {
+      (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
+      error (EXIT_FAILURE, errno, _("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.  */
+    asprintf (&normalized_name, "%s%s%s.%s%s%s",
+	      language, territory == NULL ? "" : "_", territory ?: "",
+	      (mask & XPG_NORM_CODESET) ? normalized_codeset : codeset,
+	      modifier == NULL ? "" : "@", modifier ?: "");
+
+  /* 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;
+
+      asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
+		language, territory == NULL ? "" : "_", territory ?: "",
+		normalized_codeset,
+		modifier == NULL ? "" : "@", modifier ?: "");
+
+      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.  */
+#ifdef _DIRENT_HAVE_D_TYPE
+		  d_type = d->d_type;
+
+		  if (d_type != DT_REG)
+#endif
+		    {
+		      char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
+
+#ifdef _DIRENT_HAVE_D_TYPE
+		      if (d_type == DT_UNKNOWN)
+#endif
+			{
+			  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]);
+	      }
+	}
+    }
+  else
+    for (cnt = 0; cnt < used; ++cnt)
+      puts (names[cnt].name);
+
+  close_archive (&ah);
+
+  exit (EXIT_SUCCESS);
+}