]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - locale/programs/locarchive.c
Prefer https to http for gnu.org and fsf.org URLs
[thirdparty/glibc.git] / locale / programs / locarchive.c
index 9ef373a2e5694d3946c602579fd5b3db5c837c2c..88e9ce1a126864027c4c657011c796bfa2101096 100644 (file)
@@ -1,21 +1,19 @@
-/* Copyright (C) 2002 Free Software Foundation, Inc.
+/* Copyright (C) 2002-2019 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
 
-   The GNU C Library is free software; you can redistribute it and/or
-   modify it under the terms of the GNU Lesser General Public
-   License as published by the Free Software Foundation; either
-   version 2.1 of the License, or (at your option) any later version.
+   This 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.
 
-   The GNU C Library is distributed in the hope that it will be useful,
+   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
-   Lesser General Public License for more details.
+   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 Lesser General Public
-   License along with the GNU C Library; if not, write to the Free
-   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
-   02111-1307 USA.  */
+   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>
 #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 "simple-hash.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 LOCALEDIR "/locale-archive"
+#define ARCHIVE_NAME COMPLOCALEDIR "/locale-archive"
 
 static const char *locnames[] =
   {
@@ -59,19 +69,60 @@ static const char *locnames[] =
 
 
 /* Size of the initial archive header.  */
-#define INITIAL_NUM_NANES      450
-#define INITIAL_SIZE_STRINGS   3500
-#define INITIAL_NUM_LOCREC     350
+#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;
-  void *p;
   size_t total;
 
   strcpy (stpcpy (fname, archivefname), ".XXXXXX");
@@ -79,29 +130,34 @@ create_archive (const char *archivefname, struct locarhandle *ah)
   /* Create a temporary file in the correct directory.  */
   fd = mkstemp (fname);
   if (fd == -1)
-    error (EXIT_FAILURE, errno, _("cannot create temporary file"));
+    error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname);
 
   /* Create the initial content of the archive.  */
-  head.magic = AR_MAGIC;
-  head.namehash_offset = sizeof (struct locarhead);
-  head.namehash_used = 0;
-  head.namehash_size = next_prime (INITIAL_NUM_NANES);
-
-  head.string_offset = (head.namehash_offset
-                       + head.namehash_size * sizeof (struct namehashent));
-  head.string_used = 0;
-  head.string_size = INITIAL_SIZE_STRINGS;
-
-  head.locrectab_offset = head.string_offset + head.string_size;
-  head.locrectab_used = 0;
-  head.locrectab_size = INITIAL_NUM_LOCREC;
-
-  head.sumhash_offset = (head.locrectab_offset
-                        + head.locrectab_size * sizeof (struct locrecent));
-  head.sumhash_used = 0;
-  head.sumhash_size = next_prime (INITIAL_NUM_SUMS);
-
-  total = head.sumhash_offset + head.sumhash_size * sizeof (struct sumhashent);
+  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))
@@ -118,8 +174,14 @@ create_archive (const char *archivefname, struct locarhandle *ah)
       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 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+  p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
   if (p == MAP_FAILED)
     {
       int errval = errno;
@@ -161,9 +223,109 @@ create_archive (const char *archivefname, struct locarhandle *ah)
             _("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->len = total;
+  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;
 }
 
 
@@ -174,10 +336,8 @@ enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
   int fd;
   struct locarhead newhead;
   size_t total;
-  void *p;
-  unsigned int cnt;
+  unsigned int cnt, loccnt;
   struct namehashent *oldnamehashtab;
-  struct locrecent *oldlocrectab;
   struct locarhandle new_ah;
   size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
   char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
@@ -190,50 +350,76 @@ enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
 
   /* 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
-      || (ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE,
-                            MAP_SHARED, ah->fd, 0)) == MAP_FAILED)
+  if (fstat64 (ah->fd, &st) != 0)
+  enomap:
     error (EXIT_FAILURE, errno, _("cannot map locale archive file"));
-  ah->len = st.st_size;
+
+  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"));
+    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.  */
-  newhead.namehash_size = MAX (next_prime (2 * newhead.namehash_used),
-                              newhead.namehash_size);
-  printf ("name: size: %u, used: %d, new: size: %u\n",
-         head->namehash_size, head->namehash_used, newhead.namehash_size);
-
-  newhead.string_offset = (newhead.namehash_offset
-                          + (newhead.namehash_size
-                             * sizeof (struct namehashent)));
-  newhead.string_size = MAX (2 * newhead.string_used, newhead.string_size);
-
-  newhead.locrectab_offset = newhead.string_offset + newhead.string_size;
-  newhead.locrectab_size = MAX (2 * newhead.locrectab_used,
-                               newhead.locrectab_size);
-
-  newhead.sumhash_offset = (newhead.locrectab_offset
-                           + (newhead.locrectab_size
-                              * sizeof (struct locrecent)));
-  newhead.sumhash_size = MAX (next_prime (2 * newhead.sumhash_used),
-                             newhead.sumhash_size);
-
-  total = (newhead.sumhash_offset
-          + newhead.sumhash_size * sizeof (struct sumhashent));
+  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.  */
-  newhead.namehash_used = 0;
-  newhead.string_used = 0;
-  newhead.locrectab_used = 0;
-  newhead.sumhash_used = 0;
+  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)))
@@ -251,8 +437,14 @@ enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
       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 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+  p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
   if (p == MAP_FAILED)
     {
       int errval = errno;
@@ -268,45 +460,74 @@ enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
       error (EXIT_FAILURE, errval, _("cannot lock new archive"));
     }
 
-  new_ah.len = total;
+  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
-                                          + head->namehash_offset);
-  oldlocrectab = (struct locrecent *) ((char *) ah->addr
-                                      + head->locrectab_offset);
-  for (cnt = 0; cnt < head->namehash_size; ++cnt)
-    if (oldnamehashtab[cnt].locrec_offset != 0)
+                                          + 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)
       {
-       /* Insert this entry in the new hash table.  */
-       locale_data_t old_data;
-       unsigned int idx;
-       struct locrecent *oldlocrec;
+       oldlocrecarray[loccnt].cnt = cnt;
+       oldlocrecarray[loccnt++].locrec
+         = (struct locrecent *) ((char *) ah->addr
+                                 + GET (oldnamehashtab[cnt].locrec_offset));
+      }
+  qsort (oldlocrecarray, loccnt, sizeof (struct oldlocrecent),
+        oldlocrecentcmp);
 
-       oldlocrec = (struct locrecent *) ((char *) ah->addr
-                                         + oldnamehashtab[cnt].locrec_offset);
+  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 = oldlocrec->record[idx].len;
-             old_data[idx].addr
-               = ((char *) ah->addr + oldlocrec->record[idx].offset);
+      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);
-           }
+           __md5_buffer (old_data[idx].addr, old_data[idx].size,
+                         old_data[idx].sum);
+         }
 
-       if (add_locale_to_archive (&new_ah,
-                                  ((char *) ah->addr
-                                   + oldnamehashtab[cnt].name_offset),
-                                  old_data, 0) != 0)
-         error (EXIT_FAILURE, 0, _("cannot extend locale archive file"));
-      }
+      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)
@@ -342,75 +563,89 @@ open_archive (struct locarhandle *ah, bool readonly)
   struct locarhead head;
   int retry = 0;
   size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
-  char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
+  char default_fname[prefix_len + sizeof (ARCHIVE_NAME)];
+  const char *archivefname = ah->fname;
 
-  if (output_prefix)
-    memcpy (archivefname, output_prefix, prefix_len);
-  strcpy (archivefname + prefix_len, ARCHIVE_NAME);
+  /* 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);
+    }
 
- again:
-  /* Open the archive.  We must have exclusive write access.  */
-  fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
-  if (fd == -1)
+  while (1)
     {
-      /* Maybe the file does not yet exist.  */
-      if (errno == ENOENT)
+      /* Open the archive.  We must have exclusive write access.  */
+      fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
+      if (fd == -1)
        {
-         if (readonly)
+         /* 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)
            {
-             static const struct locarhead nullhead =
+             if (readonly)
                {
-                 .namehash_used = 0,
-                 .namehash_offset = 0,
-                 .namehash_size = 0
-               };
+                 static const struct locarhead nullhead =
+                   {
+                     .namehash_used = 0,
+                     .namehash_offset = 0,
+                     .namehash_size = 0
+                   };
 
-             ah->addr = (void *) &nullhead;
-             ah->fd = -1;
+                 ah->addr = (void *) &nullhead;
+                 ah->fd = -1;
+               }
+             else
+               create_archive (archivefname, ah);
+
+             return;
            }
          else
-           create_archive (archivefname, ah);
-
-         return;
+           error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
+                  archivefname);
        }
-      else
-       error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
+
+      if (fstat64 (fd, &st) < 0)
+       error (EXIT_FAILURE, errno, _("cannot stat 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 (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1)
-    {
-      close (fd);
+         if (retry++ < max_locarchive_open_retry)
+           {
+             struct timespec req;
 
-      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);
 
-         /* Wait for a bit.  */
-         req.tv_sec = 0;
-         req.tv_nsec = 1000000 * (random () % 500 + 1);
-         (void) nanosleep (&req, NULL);
+             continue;
+           }
 
-         goto again;
+         error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
+                archivefname);
        }
 
-      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;
+       }
 
-  /* 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);
-      goto again;
+      /* Leave the loop.  */
+      break;
     }
 
   /* Read the header.  */
@@ -421,18 +656,26 @@ open_archive (struct locarhandle *ah, bool readonly)
     }
 
   ah->fd = fd;
-  ah->len = (head.sumhash_offset
-            + head.sumhash_size * sizeof (struct sumhashent));
-
-  /* Now we know how large the administrative information part is.
-     Map all of it.  */
-  ah->addr = mmap64 (NULL, ah->len, PROT_READ | (readonly ? 0 : PROT_WRITE),
-                    MAP_SHARED, fd, 0);
+  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;
 }
 
 
@@ -441,21 +684,153 @@ close_archive (struct locarhandle *ah)
 {
   if (ah->fd != -1)
     {
-      munmap (ah->addr, ah->len);
+      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.  Add all the names, possibly overwriting
-   old files.  */
-int
-add_locale_to_archive (ah, name, data, replace)
-     struct locarhandle *ah;
-     const char *name;
-     locale_data_t data;
-     bool replace;
+   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
@@ -465,112 +840,158 @@ add_locale_to_archive (ah, name, data, replace)
   unsigned int num_new_offsets = 0;
   struct sumhashent *sumhashtab;
   uint32_t hval;
-  unsigned int cnt;
-  unsigned int idx;
-  unsigned int insert_idx;
+  unsigned int cnt, idx;
   struct locarhead *head;
-  struct namehashent *namehashtab;
   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
-                                     + head->sumhash_offset);
-  namehashtab = (struct namehashent *) ((char *) ah->addr
-                                       + head->namehash_offset);
+                                     + 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 (cnt != LC_ALL)
+    if (small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
       {
-       /* By default signal that we have no data.  */
-       file_offsets[cnt] = 0;
        ++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 = compute_hashval (data[cnt].sum, 16);
+       hval = archive_hashval (data[cnt].sum, 16);
 
-       idx = hval % head->sumhash_size;
-       incr = 1 + hval % (head->sumhash_size - 2);
+       idx = hval % GET (head->sumhash_size);
+       incr = 1 + hval % (GET (head->sumhash_size) - 2);
 
-       while (sumhashtab[idx].file_offset != 0)
+       while (GET (sumhashtab[idx].file_offset) != 0)
          {
            if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
              {
-               /* Found it.  */
-               file_offsets[cnt] = sumhashtab[idx].file_offset;
-               --num_new_offsets;
-               break;
+               /* 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 >= head->sumhash_size)
-             idx -= head->sumhash_size;
+           if (idx >= GET (head->sumhash_size))
+             idx -= GET (head->sumhash_size);
          }
       }
 
-
-  /* Hash value of the locale name.  */
-  hval = compute_hashval (name, name_len);
-
-  insert_idx = -1;
-  idx = hval % head->namehash_size;
-  incr = 1 + hval % (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 (namehashtab[idx].name_offset != 0)
-    {
-      if (namehashtab[idx].hashval == hval
-         && strcmp (name,
-                    (char *) ah->addr + namehashtab[idx].name_offset) == 0)
-       {
-         /* Found the entry.  */
-         if (! replace)
-           {
-             if (! be_quiet)
-               error (0, 0, _("locale '%s' already exists"), name);
-             return 1;
-           }
-
-         break;
-       }
-
-      /* Remember the first place we can insert the new entry.  */
-      if (namehashtab[idx].locrec_offset == 0 && insert_idx == -1)
-       insert_idx = idx;
-
-      idx += incr;
-      if (idx >= head->namehash_size)
-       idx -= head->namehash_size;
-    }
-
-  /* Add as early as possible.  */
-  if (insert_idx != -1)
-    idx = insert_idx;
-
-  namehashent = &namehashtab[idx];
+  /* 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 * (head->sumhash_used + num_new_offsets) > 75 * head->sumhash_size
-      || (namehashent->locrec_offset == 0
-         && (head->locrectab_used == head->locrectab_size
-             || head->string_used + name_len + 1 > head->string_size
-             || 100 * head->namehash_used > 75 * head->namehash_size)))
+  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_to_archive (ah, name, data, replace);
+      return add_locale (ah, name, data, replace);
     }
 
   /* Add the locale data which is not yet in the archive.  */
-  for (cnt = 0; cnt < __LC_LAST; ++cnt)
-    if (cnt != LC_ALL && file_offsets[cnt] == 0)
+  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.  */
@@ -581,6 +1002,24 @@ add_locale_to_archive (ah, name, data, replace)
        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)
          {
@@ -595,6 +1034,7 @@ add_locale_to_archive (ah, name, data, replace)
 
        /* 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))
@@ -602,77 +1042,298 @@ add_locale_to_archive (ah, name, data, replace)
          error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
 
        /* Add the hash value to the hash table.  */
-       md5hval = compute_hashval (data[cnt].sum, 16);
+       md5hval = archive_hashval (data[cnt].sum, 16);
 
-       idx = md5hval % head->sumhash_size;
-       incr = 1 + md5hval % (head->sumhash_size - 2);
+       idx = md5hval % GET (head->sumhash_size);
+       incr = 1 + md5hval % (GET (head->sumhash_size) - 2);
 
-       while (sumhashtab[idx].file_offset != 0)
+       while (GET (sumhashtab[idx].file_offset) != 0)
          {
            idx += incr;
-           if (idx >= head->sumhash_size)
-             idx -= head->sumhash_size;
+           if (idx >= GET (head->sumhash_size))
+             idx -= GET (head->sumhash_size);
          }
 
        memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
-       sumhashtab[idx].file_offset = file_offsets[cnt];
+       SET (sumhashtab[idx].file_offset, file_offsets[cnt]);
 
-       ++head->sumhash_used;
+       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 (namehashent->locrec_offset == 0)
+  if (GET (namehashent->name_offset) == 0)
     {
       /* Add the name string.  */
-      memcpy ((char *) ah->addr + head->string_offset + head->string_used,
+      memcpy ((char *) ah->addr + GET (head->string_offset)
+             + GET (head->string_used),
              name, name_len + 1);
-      namehashent->name_offset = head->string_offset + head->string_used;
-      head->string_used += 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.  */
-      namehashent->locrec_offset = (head->locrectab_offset
-                                   + (head->locrectab_used++
-                                      * sizeof (struct locrecent)));
-
-      namehashent->hashval = hval;
-
-      ++head->namehash_used;
+      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.  */
-  locrecent = (struct locrecent *) ((char *) ah->addr
-                                   + namehashent->locrec_offset);
   for (cnt = 0; cnt < __LC_LAST; ++cnt)
-    if (cnt != LC_ALL)
+    {
+      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
       {
-       locrecent->record[cnt].offset = file_offsets[cnt];
-       locrecent->record[cnt].len = data[cnt].size;
-      }
+       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);
 
-  /* Read the locale.alias file to see whether any matching record is
-     found.  If an entry is available check whether it is already in
-     the archive.  If this is the case check whether the new locale's
-     name is more specific than the one currently referred to by the
-     alias.  */
+      /* 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';
 
-  return 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 (nlist, list, replace)
-     size_t nlist;
-     char *list[];
-     bool replace;
+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)
@@ -724,17 +1385,13 @@ add_locales_to_archive (nlist, list, replace)
                     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);
@@ -862,9 +1519,7 @@ add_locales_to_archive (nlist, list, replace)
 
 
 int
-delete_locales_from_archive (nlist, list)
-     size_t nlist;
-     char *list[];
+delete_locales_from_archive (size_t nlist, char *list[])
 {
   struct locarhandle ah;
   struct locarhead *head;
@@ -872,11 +1527,12 @@ delete_locales_from_archive (nlist, list)
 
   /* 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
-                                       + head->namehash_offset);
+                                       + GET (head->namehash_offset));
 
   while (nlist-- > 0)
     {
@@ -886,33 +1542,33 @@ delete_locales_from_archive (nlist, list)
       unsigned int incr;
 
       /* Search for this locale in the archive.  */
-      hval = compute_hashval (locname, strlen (locname));
+      hval = archive_hashval (locname, strlen (locname));
 
-      idx = hval % head->namehash_size;
-      incr = 1 + hval % (head->namehash_size - 2);
+      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 (namehashtab[idx].name_offset != 0)
+      while (GET (namehashtab[idx].name_offset) != 0)
        {
-         if (namehashtab[idx].hashval == hval
+         if (GET (namehashtab[idx].hashval) == hval
              && (strcmp (locname,
-                         (char *) ah.addr + namehashtab[idx].name_offset)
+                         ((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.  */
-             namehashtab[idx].locrec_offset = 0;
-             --head->namehash_used;
+             SET (namehashtab[idx].locrec_offset, 0);
              break;
            }
 
          idx += incr;
-         if (idx >= head->namehash_size)
-           idx -= head->namehash_size;
+         if (idx >= GET (head->namehash_size))
+           idx -= GET (head->namehash_size);
        }
 
-      if (namehashtab[idx].name_offset == 0 && ! be_quiet)
+      if (GET (namehashtab[idx].name_offset) == 0 && ! be_quiet)
        error (0, 0, _("locale \"%s\" not in archive"), locname);
     }
 
@@ -961,32 +1617,32 @@ dataentcmp (const void *a, const void *b)
 
 
 void
-show_archive_content (int verbose)
+show_archive_content (const char *fname, int verbose)
 {
   struct locarhandle ah;
   struct locarhead *head;
   struct namehashent *namehashtab;
   struct nameent *names;
-  int cnt;
-  int used;
+  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 (head->namehash_used
+  names = (struct nameent *) xmalloc (GET (head->namehash_used)
                                      * sizeof (struct nameent));
 
   namehashtab = (struct namehashent *) ((char *) ah.addr
-                                       + head->namehash_offset);
-  for (cnt = used = 0; cnt < head->namehash_size; ++cnt)
-    if (namehashtab[cnt].locrec_offset != 0)
+                                       + GET (head->namehash_offset));
+  for (cnt = used = 0; cnt < GET (head->namehash_size); ++cnt)
+    if (GET (namehashtab[cnt].locrec_offset) != 0)
       {
-       assert (used < head->namehash_used);
-       names[used].name = ah.addr + namehashtab[cnt].name_offset;
-       names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
+       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.  */
@@ -998,17 +1654,17 @@ show_archive_content (int verbose)
       struct sumhashent *sumhashtab;
       int sumused;
 
-      files = (struct dataent *) xmalloc (head->sumhash_used
-                                         * sizeof (struct sumhashent));
+      files = (struct dataent *) xmalloc (GET (head->sumhash_used)
+                                         * sizeof (struct dataent));
 
       sumhashtab = (struct sumhashent *) ((char *) ah.addr
-                                         + head->sumhash_offset);
-      for (cnt = sumused = 0; cnt < head->sumhash_size; ++cnt)
-       if (sumhashtab[cnt].file_offset != 0)
+                                         + GET (head->sumhash_offset));
+      for (cnt = sumused = 0; cnt < GET (head->sumhash_size); ++cnt)
+       if (GET (sumhashtab[cnt].file_offset) != 0)
          {
-           assert (sumused < head->sumhash_used);
+           assert (sumused < GET (head->sumhash_used));
            files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
-           files[sumused].file_offset = sumhashtab[cnt].file_offset;
+           files[sumused].file_offset = GET (sumhashtab[cnt].file_offset);
            files[sumused++].nlink = 0;
          }
 
@@ -1024,11 +1680,19 @@ show_archive_content (int verbose)
          locrec = (struct locrecent *) ((char *) ah.addr
                                         + names[cnt].locrec_offset);
          for (idx = 0; idx < __LC_LAST; ++idx)
-           if (idx != LC_ALL)
+           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 = locrec->record[idx].offset;
+               dataent.file_offset = GET (locrec->record[idx].offset);
                data = (struct dataent *) bsearch (&dataent, files, sumused,
                                                   sizeof (struct dataent),
                                                   dataentcmp);
@@ -1050,13 +1714,25 @@ show_archive_content (int verbose)
              {
                struct dataent *data, dataent;
 
-               dataent.file_offset = locrec->record[idx].offset;
+               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 ",
-                       locrec->record[idx].len, locrec->record[idx].offset,
-                       data->nlink);
+               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],