Functions for locate archive creation.
authordrepper <drepper>
Thu, 18 Apr 2002 07:55:47 +0000 (07:55 +0000)
committerdrepper <drepper>
Thu, 18 Apr 2002 07:55:47 +0000 (07:55 +0000)
locale/programs/locarchive.c [new file with mode: 0644]

diff --git a/locale/programs/locarchive.c b/locale/programs/locarchive.c
new file mode 100644 (file)
index 0000000..861dd5b
--- /dev/null
@@ -0,0 +1,931 @@
+/* Copyright (C) 2002 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.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+#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 <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include "../../crypt/md5.h"
+#include "../localeinfo.h"
+#include "../locarchive.h"
+#include "simple-hash.h"
+#include "localedef.h"
+
+
+static const char archivefname[] = LOCALEDIR "/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_NANES      450
+#define INITIAL_SIZE_STRINGS   3500
+#define INITIAL_NUM_LOCREC     350
+#define INITIAL_NUM_SUMS       2000
+
+
+static void
+create_archive (struct locarhandle *ah)
+{
+  int fd;
+  char fname[] = LOCALEDIR "/locale-archive.XXXXXX";
+  struct locarhead head;
+  void *p;
+  size_t total;
+
+  /* Create a temporary file in the correct directory.  */
+  fd = mkstemp (fname);
+  if (fd == -1)
+    error (EXIT_FAILURE, errno, _("cannot create temporary file"));
+
+  /* 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);
+
+  /* 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"));
+    }
+
+  /* Map the header and all the administration data structures.  */
+  p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, 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);
+         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->fd = fd;
+  ah->addr = p;
+  ah->len = total;
+}
+
+
+static void
+enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
+{
+  struct stat64 st;
+  int fd;
+  char fname[] = LOCALEDIR "/locale-archive.XXXXXX";
+  struct locarhead newhead;
+  size_t total;
+  void *p;
+  unsigned int cnt;
+  struct namehashent *oldnamehashtab;
+  struct locrecent *oldlocrectab;
+  struct locarhandle new_ah;
+
+  /* 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)
+    error (EXIT_FAILURE, errno, _("cannot map locale archive file"));
+  ah->len = 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"));
+
+  /* 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));
+
+  /* The new file is empty now.  */
+  newhead.namehash_used = 0;
+  newhead.string_used = 0;
+  newhead.locrectab_used = 0;
+  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"));
+    }
+
+  /* Map the header and all the administration data structures.  */
+  p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, 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.len = total;
+  new_ah.addr = p;
+  new_ah.fd = fd;
+
+  /* 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)
+      {
+       /* Insert this entry in the new hash table.  */
+       locale_data_t old_data;
+       unsigned int idx;
+       struct locrecent *oldlocrec;
+
+       oldlocrec = (struct locrecent *) ((char *) ah->addr
+                                         + oldnamehashtab[cnt].locrec_offset);
+
+       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);
+
+             __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"));
+      }
+
+
+  /* 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)
+{
+  struct stat64 st;
+  struct stat64 st2;
+  int fd;
+  struct locarhead head;
+  int retry = 0;
+
+ again:
+  /* Open the archive.  We must have exclusive write access.  */
+  fd = open64 (archivefname, O_RDWR);
+  if (fd == -1)
+    {
+      /* Maybe the file does not yet exist.  */
+      if (errno == ENOENT)
+       {
+         create_archive (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 (lockf64 (fd, F_LOCK, st.st_size) == -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);
+
+         goto again;
+       }
+
+      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)
+    {
+      close (fd);
+      goto again;
+    }
+
+  /* Read the header.  */
+  if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
+    error (EXIT_FAILURE, errno, _("cannot read archive header"));
+
+  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 | PROT_WRITE, MAP_SHARED, fd, 0);
+  if (ah->addr == MAP_FAILED)
+    error (EXIT_FAILURE, errno, _("cannot map archive header"));
+}
+
+
+void
+close_archive (struct locarhandle *ah)
+{
+  munmap (ah->addr, ah->len);
+  close (ah->fd);
+}
+
+
+/* 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;
+{
+  /* 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;
+  unsigned int idx;
+  unsigned int insert_idx;
+  struct locarhead *head;
+  struct namehashent *namehashtab;
+  struct namehashent *namehashent;
+  unsigned int incr;
+  struct locrecent *locrecent;
+
+  head = ah->addr;
+  sumhashtab = (struct sumhashent *) ((char *) ah->addr
+                                     + head->sumhash_offset);
+  namehashtab = (struct namehashent *) ((char *) ah->addr
+                                       + head->namehash_offset);
+
+
+  /* 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)
+      {
+       /* 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);
+
+       idx = hval % head->sumhash_size;
+       incr = 1 + hval % (head->sumhash_size - 2);
+
+       while (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;
+             }
+
+           idx += incr;
+           if (idx >= head->sumhash_size)
+             idx -= 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];
+
+  /* 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)))
+    {
+      /* The current archive is not large enough.  */
+      enlarge_archive (ah, head);
+      return add_locale_to_archive (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)
+      {
+       /* 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"));
+
+       /* 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;
+
+       /* 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 = compute_hashval (data[cnt].sum, 16);
+
+       idx = md5hval % head->sumhash_size;
+       incr = 1 + md5hval % (head->sumhash_size - 2);
+
+       while (sumhashtab[idx].file_offset != 0)
+         {
+           idx += incr;
+           if (idx >= head->sumhash_size)
+             idx -= head->sumhash_size;
+         }
+
+       memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
+       sumhashtab[idx].file_offset = file_offsets[cnt];
+
+       ++head->sumhash_used;
+      }
+
+
+  if (namehashent->locrec_offset == 0)
+    {
+      /* Add the name string.  */
+      memcpy ((char *) ah->addr + head->string_offset + head->string_used,
+             name, name_len + 1);
+      namehashent->name_offset = head->string_offset + head->string_used;
+      head->string_used += name_len + 1;
+
+      /* Allocate a name location record.  */
+      namehashent->locrec_offset = (head->locrectab_offset
+                                   + (head->locrectab_used++
+                                      * sizeof (struct locrecent)));
+
+      namehashent->hashval = hval;
+
+      ++head->namehash_used;
+    }
+
+
+  /* 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)
+      {
+       locrecent->record[cnt].offset = file_offsets[cnt];
+       locrecent->record[cnt].len = data[cnt].size;
+      }
+
+
+  /* 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.  */
+
+
+  return 0;
+}
+
+
+int
+add_locales_to_archive (nlist, list, replace)
+     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.  */
+  open_archive (&ah);
+
+  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.  */
+                         strcpy (stpcpy (stpcpy (stpcpy (stpcpy (fullname,
+                                                                 fname),
+                                                         "/"),
+                                                 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))
+             {
+               close (fd);
+               strcpy (stpcpy (stpcpy (stpcpy (stpcpy (fullname, fname),
+                                               "/"),
+                                       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 (nlist, list)
+     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.  */
+  open_archive (&ah);
+
+  head = ah.addr;
+  namehashtab = (struct namehashent *) ((char *) ah.addr
+                                       + 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 = compute_hashval (locname, strlen (locname));
+
+      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 (locname,
+                         (char *) ah.addr + 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;
+             break;
+           }
+
+         idx += incr;
+         if (idx >= head->namehash_size)
+           idx -= head->namehash_size;
+       }
+
+      if (namehashtab[idx].name_offset == 0 && ! be_quiet)
+       error (0, 0, _("locale \"%s\" not in archive"), locname);
+    }
+
+  close_archive (&ah);
+
+  return 0;
+}
+
+
+static int
+xstrcmp (const void *a, const void *b)
+{
+  return strcmp (*(const char **) a, *(const char **) b);
+}
+
+
+void
+show_archive_content (void)
+{
+  struct locarhandle ah;
+  struct locarhead *head;
+  struct namehashent *namehashtab;
+  int cnt;
+  char **names;
+  int used;
+
+  /* Open the archive.  This call never returns if we cannot
+     successfully open the archive.  */
+  open_archive (&ah);
+
+  head = ah.addr;
+
+  names = (char **) xmalloc (head->namehash_used * sizeof (char *));
+
+  namehashtab = (struct namehashent *) ((char *) ah.addr
+                                       + head->namehash_offset);
+  for (cnt = used = 0; cnt < head->namehash_size; ++cnt)
+    if (namehashtab[cnt].locrec_offset != 0)
+      {
+       assert (used < head->namehash_used);
+       names[used++] = ah.addr + namehashtab[cnt].name_offset;
+      }
+
+  /* Sort the names.  */
+  qsort (names, used, sizeof (char *), xstrcmp);
+
+  for (cnt = 0; cnt < used; ++cnt)
+    puts (names[cnt]);
+
+  close_archive (&ah);
+
+  exit (EXIT_SUCCESS);
+}