(root)/
glibc-2.38/
locale/
programs/
locarchive.c
       1  /* Copyright (C) 2002-2023 Free Software Foundation, Inc.
       2     This file is part of the GNU C Library.
       3  
       4     This program is free software; you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published
       6     by the Free Software Foundation; version 2 of the License, or
       7     (at your option) any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program; if not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  #ifdef HAVE_CONFIG_H
      18  # include <config.h>
      19  #endif
      20  
      21  #include <assert.h>
      22  #include <dirent.h>
      23  #include <errno.h>
      24  #include <error.h>
      25  #include <fcntl.h>
      26  #include <inttypes.h>
      27  #include <libintl.h>
      28  #include <locale.h>
      29  #include <stdbool.h>
      30  #include <stdio.h>
      31  #include <stdio_ext.h>
      32  #include <stdlib.h>
      33  #include <string.h>
      34  #include <time.h>
      35  #include <unistd.h>
      36  #include <stdint.h>
      37  #include <sys/mman.h>
      38  #include <sys/param.h>
      39  #include <sys/shm.h>
      40  #include <sys/stat.h>
      41  
      42  #include <libc-mmap.h>
      43  #include <libc-pointer-arith.h>
      44  #include "../../crypt/md5.h"
      45  #include "../localeinfo.h"
      46  #include "../locarchive.h"
      47  #include "localedef.h"
      48  #include "locfile.h"
      49  
      50  /* Define the hash function.  We define the function as static inline.
      51     We must change the name so as not to conflict with simple-hash.h.  */
      52  #define compute_hashval static archive_hashval
      53  #define hashval_t uint32_t
      54  #include "hashval.h"
      55  #undef compute_hashval
      56  
      57  extern const char *output_prefix;
      58  
      59  #define ARCHIVE_NAME COMPLOCALEDIR "/locale-archive"
      60  
      61  static const char *locnames[] =
      62    {
      63  #define DEFINE_CATEGORY(category, category_name, items, a) \
      64    [category] = category_name,
      65  #include "categories.def"
      66  #undef  DEFINE_CATEGORY
      67    };
      68  
      69  
      70  /* Size of the initial archive header.  */
      71  #define INITIAL_NUM_NAMES	900
      72  #define INITIAL_SIZE_STRINGS	7500
      73  #define INITIAL_NUM_LOCREC	420
      74  #define INITIAL_NUM_SUMS	2000
      75  
      76  
      77  /* Get and set values (possibly endian-swapped) in structures mapped
      78     from or written directly to locale archives.  */
      79  #define GET(FIELD)	maybe_swap_uint32 (FIELD)
      80  #define SET(FIELD, VALUE)	((FIELD) = maybe_swap_uint32 (VALUE))
      81  #define INC(FIELD, INCREMENT)	SET (FIELD, GET (FIELD) + (INCREMENT))
      82  
      83  
      84  /* Size of the reserved address space area.  */
      85  #define RESERVE_MMAP_SIZE	512 * 1024 * 1024
      86  
      87  /* To prepare for enlargements of the mmaped area reserve some address
      88     space.  On some machines, being a file mapping rather than an anonymous
      89     mapping affects the address selection.  So do this mapping from the
      90     actual file, even though it's only a dummy to reserve address space.  */
      91  static void *
      92  prepare_address_space (int fd, size_t total, size_t *reserved, int *xflags,
      93  		       void **mmap_base, size_t *mmap_len)
      94  {
      95    if (total < RESERVE_MMAP_SIZE)
      96      {
      97        void *p = mmap64 (NULL, RESERVE_MMAP_SIZE, PROT_NONE, MAP_SHARED, fd, 0);
      98        if (p != MAP_FAILED)
      99  	{
     100  	  void *aligned_p = PTR_ALIGN_UP (p, MAP_FIXED_ALIGNMENT);
     101  	  size_t align_adjust = aligned_p - p;
     102  	  *mmap_base = p;
     103  	  *mmap_len = RESERVE_MMAP_SIZE;
     104  	  assert (align_adjust < RESERVE_MMAP_SIZE);
     105  	  *reserved = RESERVE_MMAP_SIZE - align_adjust;
     106  	  *xflags = MAP_FIXED;
     107  	  return aligned_p;
     108  	}
     109      }
     110  
     111    *reserved = total;
     112    *xflags = 0;
     113    *mmap_base = NULL;
     114    *mmap_len = 0;
     115    return NULL;
     116  }
     117  
     118  
     119  static void
     120  create_archive (const char *archivefname, struct locarhandle *ah)
     121  {
     122    int fd;
     123    char fname[strlen (archivefname) + sizeof (".XXXXXX")];
     124    struct locarhead head;
     125    size_t total;
     126  
     127    strcpy (stpcpy (fname, archivefname), ".XXXXXX");
     128  
     129    /* Create a temporary file in the correct directory.  */
     130    fd = mkstemp (fname);
     131    if (fd == -1)
     132      error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname);
     133  
     134    /* Create the initial content of the archive.  */
     135    SET (head.magic, AR_MAGIC);
     136    SET (head.serial, 0);
     137    SET (head.namehash_offset, sizeof (struct locarhead));
     138    SET (head.namehash_used, 0);
     139    SET (head.namehash_size, next_prime (INITIAL_NUM_NAMES));
     140  
     141    SET (head.string_offset,
     142         (GET (head.namehash_offset)
     143  	+ GET (head.namehash_size) * sizeof (struct namehashent)));
     144    SET (head.string_used, 0);
     145    SET (head.string_size, INITIAL_SIZE_STRINGS);
     146  
     147    SET (head.locrectab_offset,
     148         GET (head.string_offset) + GET (head.string_size));
     149    SET (head.locrectab_used, 0);
     150    SET (head.locrectab_size, INITIAL_NUM_LOCREC);
     151  
     152    SET (head.sumhash_offset,
     153         (GET (head.locrectab_offset)
     154  	+ GET (head.locrectab_size) * sizeof (struct locrecent)));
     155    SET (head.sumhash_used, 0);
     156    SET (head.sumhash_size, next_prime (INITIAL_NUM_SUMS));
     157  
     158    total = (GET (head.sumhash_offset)
     159  	   + GET (head.sumhash_size) * sizeof (struct sumhashent));
     160  
     161    /* Write out the header and create room for the other data structures.  */
     162    if (TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head))) != sizeof (head))
     163      {
     164        int errval = errno;
     165        unlink (fname);
     166        error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
     167      }
     168  
     169    if (ftruncate64 (fd, total) != 0)
     170      {
     171        int errval = errno;
     172        unlink (fname);
     173        error (EXIT_FAILURE, errval, _("cannot resize archive file"));
     174      }
     175  
     176    size_t reserved, mmap_len;
     177    int xflags;
     178    void *mmap_base;
     179    void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base,
     180  				   &mmap_len);
     181  
     182    /* Map the header and all the administration data structures.  */
     183    p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
     184    if (p == MAP_FAILED)
     185      {
     186        int errval = errno;
     187        unlink (fname);
     188        error (EXIT_FAILURE, errval, _("cannot map archive header"));
     189      }
     190  
     191    /* Now try to rename it.  We don't use the rename function since
     192       this would overwrite a file which has been created in
     193       parallel.  */
     194    if (link (fname, archivefname) == -1)
     195      {
     196        int errval = errno;
     197  
     198        /* We cannot use the just created file.  */
     199        close (fd);
     200        unlink (fname);
     201  
     202        if (errval == EEXIST)
     203  	{
     204  	  /* There is already an archive.  Must have been a localedef run
     205  	     which happened in parallel.  Simply open this file then.  */
     206  	  open_archive (ah, false);
     207  	  return;
     208  	}
     209  
     210        error (EXIT_FAILURE, errval, _("failed to create new locale archive"));
     211      }
     212  
     213    /* Remove the temporary name.  */
     214    unlink (fname);
     215  
     216    /* Make the file globally readable.  */
     217    if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
     218      {
     219        int errval = errno;
     220        unlink (archivefname);
     221        error (EXIT_FAILURE, errval,
     222  	     _("cannot change mode of new locale archive"));
     223      }
     224  
     225    ah->fname = NULL;
     226    ah->fd = fd;
     227    ah->mmap_base = mmap_base;
     228    ah->mmap_len = mmap_len;
     229    ah->addr = p;
     230    ah->mmaped = total;
     231    ah->reserved = reserved;
     232  }
     233  
     234  
     235  /* This structure and qsort comparator function are used below to sort an
     236     old archive's locrec table in order of data position in the file.  */
     237  struct oldlocrecent
     238  {
     239    unsigned int cnt;
     240    struct locrecent *locrec;
     241  };
     242  
     243  static int
     244  oldlocrecentcmp (const void *a, const void *b)
     245  {
     246    struct locrecent *la = ((const struct oldlocrecent *) a)->locrec;
     247    struct locrecent *lb = ((const struct oldlocrecent *) b)->locrec;
     248    uint32_t start_a = -1, end_a = 0;
     249    uint32_t start_b = -1, end_b = 0;
     250    int cnt;
     251  
     252    for (cnt = 0; cnt < __LC_LAST; ++cnt)
     253      if (cnt != LC_ALL)
     254        {
     255  	if (GET (la->record[cnt].offset) < start_a)
     256  	  start_a = GET (la->record[cnt].offset);
     257  	if (GET (la->record[cnt].offset) + GET (la->record[cnt].len) > end_a)
     258  	  end_a = GET (la->record[cnt].offset) + GET (la->record[cnt].len);
     259        }
     260    assert (start_a != (uint32_t)-1);
     261    assert (end_a != 0);
     262  
     263    for (cnt = 0; cnt < __LC_LAST; ++cnt)
     264      if (cnt != LC_ALL)
     265        {
     266  	if (GET (lb->record[cnt].offset) < start_b)
     267  	  start_b = GET (lb->record[cnt].offset);
     268  	if (GET (lb->record[cnt].offset) + GET (lb->record[cnt].len) > end_b)
     269  	  end_b = GET (lb->record[cnt].offset) + GET (lb->record[cnt].len);
     270        }
     271    assert (start_b != (uint32_t)-1);
     272    assert (end_b != 0);
     273  
     274    if (start_a != start_b)
     275      return (int)start_a - (int)start_b;
     276    return (int)end_a - (int)end_b;
     277  }
     278  
     279  
     280  /* forward decls for below */
     281  static uint32_t add_locale (struct locarhandle *ah, const char *name,
     282  			    locale_data_t data, bool replace);
     283  static void add_alias (struct locarhandle *ah, const char *alias,
     284  		       bool replace, const char *oldname,
     285  		       uint32_t *locrec_offset_p);
     286  
     287  
     288  static bool
     289  file_data_available_p (struct locarhandle *ah, uint32_t offset, uint32_t size)
     290  {
     291    if (offset < ah->mmaped && offset + size <= ah->mmaped)
     292      return true;
     293  
     294    struct stat64 st;
     295    if (fstat64 (ah->fd, &st) != 0)
     296      return false;
     297  
     298    if (st.st_size > ah->reserved)
     299      return false;
     300  
     301    size_t start = ALIGN_DOWN (ah->mmaped, MAP_FIXED_ALIGNMENT);
     302    void *p = mmap64 (ah->addr + start, st.st_size - start,
     303  		    PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED,
     304  		    ah->fd, start);
     305    if (p == MAP_FAILED)
     306      {
     307        ah->mmaped = start;
     308        return false;
     309      }
     310  
     311    ah->mmaped = st.st_size;
     312    return true;
     313  }
     314  
     315  
     316  static int
     317  compare_from_file (struct locarhandle *ah, void *p1, uint32_t offset2,
     318  		   uint32_t size)
     319  {
     320    void *p2 = xmalloc (size);
     321    if (pread (ah->fd, p2, size, offset2) != size)
     322      record_error (4, errno,
     323  		  _("cannot read data from locale archive"));
     324  
     325    int res = memcmp (p1, p2, size);
     326    free (p2);
     327    return res;
     328  }
     329  
     330  
     331  static void
     332  enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
     333  {
     334    struct stat64 st;
     335    int fd;
     336    struct locarhead newhead;
     337    size_t total;
     338    unsigned int cnt, loccnt;
     339    struct namehashent *oldnamehashtab;
     340    struct locarhandle new_ah;
     341    size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
     342    char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
     343    char fname[prefix_len + sizeof (ARCHIVE_NAME) + sizeof (".XXXXXX") - 1];
     344  
     345    if (output_prefix)
     346      memcpy (archivefname, output_prefix, prefix_len);
     347    strcpy (archivefname + prefix_len, ARCHIVE_NAME);
     348    strcpy (stpcpy (fname, archivefname), ".XXXXXX");
     349  
     350    /* Not all of the old file has to be mapped.  Change this now this
     351       we will have to access the whole content.  */
     352    if (fstat64 (ah->fd, &st) != 0)
     353    enomap:
     354      error (EXIT_FAILURE, errno, _("cannot map locale archive file"));
     355  
     356    if (st.st_size < ah->reserved)
     357      ah->addr = mmap64 (ah->addr, st.st_size, PROT_READ | PROT_WRITE,
     358  		       MAP_SHARED | MAP_FIXED, ah->fd, 0);
     359    else
     360      {
     361        if (ah->mmap_base)
     362  	munmap (ah->mmap_base, ah->mmap_len);
     363        else
     364  	munmap (ah->addr, ah->reserved);
     365        ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE,
     366  			 MAP_SHARED, ah->fd, 0);
     367        ah->reserved = st.st_size;
     368        ah->mmap_base = NULL;
     369        ah->mmap_len = 0;
     370        head = ah->addr;
     371      }
     372    if (ah->addr == MAP_FAILED)
     373      goto enomap;
     374    ah->mmaped = st.st_size;
     375  
     376    /* Create a temporary file in the correct directory.  */
     377    fd = mkstemp (fname);
     378    if (fd == -1)
     379      error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname);
     380  
     381    /* Copy the existing head information.  */
     382    newhead = *head;
     383  
     384    /* Create the new archive header.  The sizes of the various tables
     385       should be double from what is currently used.  */
     386    SET (newhead.namehash_size,
     387         MAX (next_prime (2 * GET (newhead.namehash_used)),
     388  	    GET (newhead.namehash_size)));
     389    if (verbose)
     390      printf ("name: size: %u, used: %d, new: size: %u\n",
     391  	    GET (head->namehash_size),
     392  	    GET (head->namehash_used), GET (newhead.namehash_size));
     393  
     394    SET (newhead.string_offset, (GET (newhead.namehash_offset)
     395  			       + (GET (newhead.namehash_size)
     396  				  * sizeof (struct namehashent))));
     397    /* Keep the string table size aligned to 4 bytes, so that
     398       all the struct { uint32_t } types following are happy.  */
     399    SET (newhead.string_size, MAX ((2 * GET (newhead.string_used) + 3) & -4,
     400  				 GET (newhead.string_size)));
     401  
     402    SET (newhead.locrectab_offset,
     403         GET (newhead.string_offset) + GET (newhead.string_size));
     404    SET (newhead.locrectab_size, MAX (2 * GET (newhead.locrectab_used),
     405  				    GET (newhead.locrectab_size)));
     406  
     407    SET (newhead.sumhash_offset, (GET (newhead.locrectab_offset)
     408  				+ (GET (newhead.locrectab_size)
     409  				   * sizeof (struct locrecent))));
     410    SET (newhead.sumhash_size,
     411         MAX (next_prime (2 * GET (newhead.sumhash_used)),
     412  	    GET (newhead.sumhash_size)));
     413  
     414    total = (GET (newhead.sumhash_offset)
     415  	   + GET (newhead.sumhash_size) * sizeof (struct sumhashent));
     416  
     417    /* The new file is empty now.  */
     418    SET (newhead.namehash_used, 0);
     419    SET (newhead.string_used, 0);
     420    SET (newhead.locrectab_used, 0);
     421    SET (newhead.sumhash_used, 0);
     422  
     423    /* Write out the header and create room for the other data structures.  */
     424    if (TEMP_FAILURE_RETRY (write (fd, &newhead, sizeof (newhead)))
     425        != sizeof (newhead))
     426      {
     427        int errval = errno;
     428        unlink (fname);
     429        error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
     430      }
     431  
     432    if (ftruncate64 (fd, total) != 0)
     433      {
     434        int errval = errno;
     435        unlink (fname);
     436        error (EXIT_FAILURE, errval, _("cannot resize archive file"));
     437      }
     438  
     439    size_t reserved, mmap_len;
     440    int xflags;
     441    void *mmap_base;
     442    void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base,
     443  				   &mmap_len);
     444  
     445    /* Map the header and all the administration data structures.  */
     446    p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
     447    if (p == MAP_FAILED)
     448      {
     449        int errval = errno;
     450        unlink (fname);
     451        error (EXIT_FAILURE, errval, _("cannot map archive header"));
     452      }
     453  
     454    /* Lock the new file.  */
     455    if (lockf64 (fd, F_LOCK, total) != 0)
     456      {
     457        int errval = errno;
     458        unlink (fname);
     459        error (EXIT_FAILURE, errval, _("cannot lock new archive"));
     460      }
     461  
     462    new_ah.mmaped = total;
     463    new_ah.mmap_base = mmap_base;
     464    new_ah.mmap_len = mmap_len;
     465    new_ah.addr = p;
     466    new_ah.fd = fd;
     467    new_ah.reserved = reserved;
     468  
     469    /* Walk through the hash name hash table to find out what data is
     470       still referenced and transfer it into the new file.  */
     471    oldnamehashtab = (struct namehashent *) ((char *) ah->addr
     472  					   + GET (head->namehash_offset));
     473  
     474    /* Sort the old locrec table in order of data position.  */
     475    struct oldlocrecent oldlocrecarray[GET (head->namehash_size)];
     476    for (cnt = 0, loccnt = 0; cnt < GET (head->namehash_size); ++cnt)
     477      if (GET (oldnamehashtab[cnt].locrec_offset) != 0)
     478        {
     479  	oldlocrecarray[loccnt].cnt = cnt;
     480  	oldlocrecarray[loccnt++].locrec
     481  	  = (struct locrecent *) ((char *) ah->addr
     482  				  + GET (oldnamehashtab[cnt].locrec_offset));
     483        }
     484    qsort (oldlocrecarray, loccnt, sizeof (struct oldlocrecent),
     485  	 oldlocrecentcmp);
     486  
     487    uint32_t last_locrec_offset = 0;
     488    for (cnt = 0; cnt < loccnt; ++cnt)
     489      {
     490        /* Insert this entry in the new hash table.  */
     491        locale_data_t old_data;
     492        unsigned int idx;
     493        struct locrecent *oldlocrec = oldlocrecarray[cnt].locrec;
     494  
     495        for (idx = 0; idx < __LC_LAST; ++idx)
     496  	if (idx != LC_ALL)
     497  	  {
     498  	    old_data[idx].size = GET (oldlocrec->record[idx].len);
     499  	    old_data[idx].addr
     500  	      = ((char *) ah->addr + GET (oldlocrec->record[idx].offset));
     501  
     502  	    __md5_buffer (old_data[idx].addr, old_data[idx].size,
     503  			  old_data[idx].sum);
     504  	  }
     505  
     506        if (cnt > 0 && oldlocrecarray[cnt - 1].locrec == oldlocrec)
     507  	{
     508  	  const char *oldname
     509  	    = ((char *) ah->addr
     510  	       + GET (oldnamehashtab[oldlocrecarray[cnt
     511  						    - 1].cnt].name_offset));
     512  
     513  	  add_alias
     514  	    (&new_ah,
     515  	     ((char *) ah->addr
     516  	      + GET (oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset)),
     517  	     0, oldname, &last_locrec_offset);
     518  	  continue;
     519  	}
     520  
     521        last_locrec_offset =
     522  	add_locale
     523  	(&new_ah,
     524  	 ((char *) ah->addr
     525  	  + GET (oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset)),
     526  	 old_data, 0);
     527        if (last_locrec_offset == 0)
     528  	error (EXIT_FAILURE, 0, _("cannot extend locale archive file"));
     529      }
     530  
     531    /* Make the file globally readable.  */
     532    if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
     533      {
     534        int errval = errno;
     535        unlink (fname);
     536        error (EXIT_FAILURE, errval,
     537  	     _("cannot change mode of resized locale archive"));
     538      }
     539  
     540    /* Rename the new file.  */
     541    if (rename (fname, archivefname) != 0)
     542      {
     543        int errval = errno;
     544        unlink (fname);
     545        error (EXIT_FAILURE, errval, _("cannot rename new archive"));
     546      }
     547  
     548    /* Close the old file.  */
     549    close_archive (ah);
     550  
     551    /* Add the information for the new one.  */
     552    *ah = new_ah;
     553  }
     554  
     555  
     556  void
     557  open_archive (struct locarhandle *ah, bool readonly)
     558  {
     559    struct stat64 st;
     560    struct stat64 st2;
     561    int fd;
     562    struct locarhead head;
     563    int retry = 0;
     564    size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
     565    char default_fname[prefix_len + sizeof (ARCHIVE_NAME)];
     566    const char *archivefname = ah->fname;
     567  
     568    /* If ah has a non-NULL fname open that otherwise open the default.  */
     569    if (archivefname == NULL)
     570      {
     571        archivefname = default_fname;
     572        if (output_prefix)
     573          memcpy (default_fname, output_prefix, prefix_len);
     574        strcpy (default_fname + prefix_len, ARCHIVE_NAME);
     575      }
     576  
     577    while (1)
     578      {
     579        /* Open the archive.  We must have exclusive write access.  */
     580        fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
     581        if (fd == -1)
     582  	{
     583  	  /* Maybe the file does not yet exist? If we are opening
     584  	     the default locale archive we ignore the failure and
     585  	     list an empty archive, otherwise we print an error
     586  	     and exit.  */
     587  	  if (errno == ENOENT && archivefname == default_fname)
     588  	    {
     589  	      if (readonly)
     590  		{
     591  		  static const struct locarhead nullhead =
     592  		    {
     593  		      .namehash_used = 0,
     594  		      .namehash_offset = 0,
     595  		      .namehash_size = 0
     596  		    };
     597  
     598  		  ah->addr = (void *) &nullhead;
     599  		  ah->fd = -1;
     600  		}
     601  	      else
     602  		create_archive (archivefname, ah);
     603  
     604  	      return;
     605  	    }
     606  	  else
     607  	    error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
     608  		   archivefname);
     609  	}
     610  
     611        if (fstat64 (fd, &st) < 0)
     612  	error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""),
     613  	       archivefname);
     614  
     615        if (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1)
     616  	{
     617  	  close (fd);
     618  
     619  	  if (retry++ < max_locarchive_open_retry)
     620  	    {
     621  	      struct timespec req;
     622  
     623  	      /* Wait for a bit.  */
     624  	      req.tv_sec = 0;
     625  	      req.tv_nsec = 1000000 * (random () % 500 + 1);
     626  	      (void) nanosleep (&req, NULL);
     627  
     628  	      continue;
     629  	    }
     630  
     631  	  error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
     632  		 archivefname);
     633  	}
     634  
     635        /* One more check.  Maybe another process replaced the archive file
     636  	 with a new, larger one since we opened the file.  */
     637        if (stat64 (archivefname, &st2) == -1
     638  	  || st.st_dev != st2.st_dev
     639  	  || st.st_ino != st2.st_ino)
     640  	{
     641  	  (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
     642  	  close (fd);
     643  	  continue;
     644  	}
     645  
     646        /* Leave the loop.  */
     647        break;
     648      }
     649  
     650    /* Read the header.  */
     651    if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
     652      {
     653        (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
     654        error (EXIT_FAILURE, errno, _("cannot read archive header"));
     655      }
     656  
     657    /* Check the magic value */
     658    if (GET (head.magic) != AR_MAGIC)
     659      {
     660        (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
     661        error (EXIT_FAILURE, 0, _("bad magic value in archive header"));
     662      }
     663  
     664    ah->fd = fd;
     665    ah->mmaped = st.st_size;
     666  
     667    size_t reserved, mmap_len;
     668    int xflags;
     669    void *mmap_base;
     670    void *p = prepare_address_space (fd, st.st_size, &reserved, &xflags,
     671  				   &mmap_base, &mmap_len);
     672  
     673    /* Map the entire file.  We might need to compare the category data
     674       in the file with the newly added data.  */
     675    ah->addr = mmap64 (p, st.st_size, PROT_READ | (readonly ? 0 : PROT_WRITE),
     676  		     MAP_SHARED | xflags, fd, 0);
     677    if (ah->addr == MAP_FAILED)
     678      {
     679        (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
     680        error (EXIT_FAILURE, errno, _("cannot map archive header"));
     681      }
     682    ah->reserved = reserved;
     683    ah->mmap_base = mmap_base;
     684    ah->mmap_len = mmap_len;
     685  }
     686  
     687  
     688  void
     689  close_archive (struct locarhandle *ah)
     690  {
     691    if (ah->fd != -1)
     692      {
     693        if (ah->mmap_base)
     694  	munmap (ah->mmap_base, ah->mmap_len);
     695        else
     696  	munmap (ah->addr, ah->reserved);
     697        close (ah->fd);
     698      }
     699  }
     700  
     701  #include "../../intl/explodename.c"
     702  #include "../../intl/l10nflist.c"
     703  
     704  static struct namehashent *
     705  insert_name (struct locarhandle *ah,
     706  	     const char *name, size_t name_len, bool replace)
     707  {
     708    const struct locarhead *const head = ah->addr;
     709    struct namehashent *namehashtab
     710      = (struct namehashent *) ((char *) ah->addr
     711  			      + GET (head->namehash_offset));
     712    unsigned int insert_idx, idx, incr;
     713  
     714    /* Hash value of the locale name.  */
     715    uint32_t hval = archive_hashval (name, name_len);
     716  
     717    insert_idx = -1;
     718    idx = hval % GET (head->namehash_size);
     719    incr = 1 + hval % (GET (head->namehash_size) - 2);
     720  
     721    /* If the name_offset field is zero this means this is a
     722       deleted entry and therefore no entry can be found.  */
     723    while (GET (namehashtab[idx].name_offset) != 0)
     724      {
     725        if (GET (namehashtab[idx].hashval) == hval
     726  	  && (strcmp (name,
     727  		      (char *) ah->addr + GET (namehashtab[idx].name_offset))
     728  	      == 0))
     729  	{
     730  	  /* Found the entry.  */
     731  	  if (GET (namehashtab[idx].locrec_offset) != 0 && ! replace)
     732  	    {
     733  	      if (! be_quiet)
     734  		error (0, 0, _("locale '%s' already exists"), name);
     735  	      return NULL;
     736  	    }
     737  
     738  	  break;
     739  	}
     740  
     741        if (GET (namehashtab[idx].hashval) == hval && ! be_quiet)
     742  	{
     743  	  error (0, 0, "hash collision (%u) %s, %s",
     744  		 hval, name,
     745  		 (char *) ah->addr + GET (namehashtab[idx].name_offset));
     746  	}
     747  
     748        /* Remember the first place we can insert the new entry.  */
     749        if (GET (namehashtab[idx].locrec_offset) == 0 && insert_idx == -1)
     750  	insert_idx = idx;
     751  
     752        idx += incr;
     753        if (idx >= GET (head->namehash_size))
     754  	idx -= GET (head->namehash_size);
     755      }
     756  
     757    /* Add as early as possible.  */
     758    if (insert_idx != -1)
     759      idx = insert_idx;
     760  
     761    SET (namehashtab[idx].hashval, hval); /* no-op if replacing an old entry.  */
     762    return &namehashtab[idx];
     763  }
     764  
     765  static void
     766  add_alias (struct locarhandle *ah, const char *alias, bool replace,
     767  	   const char *oldname, uint32_t *locrec_offset_p)
     768  {
     769    uint32_t locrec_offset = *locrec_offset_p;
     770    struct locarhead *head = ah->addr;
     771    const size_t name_len = strlen (alias);
     772    struct namehashent *namehashent = insert_name (ah, alias, strlen (alias),
     773  						 replace);
     774    if (namehashent == NULL && ! replace)
     775      return;
     776  
     777    if (GET (namehashent->name_offset) == 0)
     778      {
     779        /* We are adding a new hash entry for this alias.
     780  	 Determine whether we have to resize the file.  */
     781        if (GET (head->string_used) + name_len + 1 > GET (head->string_size)
     782  	  || (100 * GET (head->namehash_used)
     783  	      > 75 * GET (head->namehash_size)))
     784  	{
     785  	  /* The current archive is not large enough.  */
     786  	  enlarge_archive (ah, head);
     787  
     788  	  /* The locrecent might have moved, so we have to look up
     789  	     the old name afresh.  */
     790  	  namehashent = insert_name (ah, oldname, strlen (oldname), true);
     791  	  assert (GET (namehashent->name_offset) != 0);
     792  	  assert (GET (namehashent->locrec_offset) != 0);
     793  	  *locrec_offset_p = GET (namehashent->locrec_offset);
     794  
     795  	  /* Tail call to try the whole thing again.  */
     796  	  add_alias (ah, alias, replace, oldname, locrec_offset_p);
     797  	  return;
     798  	}
     799  
     800        /* Add the name string.  */
     801        memcpy (ah->addr + GET (head->string_offset) + GET (head->string_used),
     802  	      alias, name_len + 1);
     803        SET (namehashent->name_offset,
     804  	   GET (head->string_offset) + GET (head->string_used));
     805        INC (head->string_used, name_len + 1);
     806  
     807        INC (head->namehash_used, 1);
     808      }
     809  
     810    if (GET (namehashent->locrec_offset) != 0)
     811      {
     812        /* Replacing an existing entry.
     813  	 Mark that we are no longer using the old locrecent.  */
     814        struct locrecent *locrecent
     815  	= (struct locrecent *) ((char *) ah->addr
     816  				+ GET (namehashent->locrec_offset));
     817        INC (locrecent->refs, -1);
     818      }
     819  
     820    /* Point this entry at the locrecent installed for the main name.  */
     821    SET (namehashent->locrec_offset, locrec_offset);
     822  }
     823  
     824  static int			/* qsort comparator used below */
     825  cmpcategorysize (const void *a, const void *b)
     826  {
     827    if (*(const void **) a == NULL)
     828      return 1;
     829    if (*(const void **) b == NULL)
     830      return -1;
     831    return ((*(const struct locale_category_data **) a)->size
     832  	  - (*(const struct locale_category_data **) b)->size);
     833  }
     834  
     835  /* Check the content of the archive for duplicates.  Add the content
     836     of the files if necessary.  Returns the locrec_offset.  */
     837  static uint32_t
     838  add_locale (struct locarhandle *ah,
     839  	    const char *name, locale_data_t data, bool replace)
     840  {
     841    /* First look for the name.  If it already exists and we are not
     842       supposed to replace it don't do anything.  If it does not exist
     843       we have to allocate a new locale record.  */
     844    size_t name_len = strlen (name);
     845    uint32_t file_offsets[__LC_LAST];
     846    unsigned int num_new_offsets = 0;
     847    struct sumhashent *sumhashtab;
     848    uint32_t hval;
     849    unsigned int cnt, idx;
     850    struct locarhead *head;
     851    struct namehashent *namehashent;
     852    unsigned int incr;
     853    struct locrecent *locrecent;
     854    off64_t lastoffset;
     855    char *ptr;
     856    struct locale_category_data *size_order[__LC_LAST];
     857    /* Page size alignment is a minor optimization for locality; use a
     858       common value here rather than making the localedef output depend
     859       on the page size of the system on which localedef is run.  See
     860       <https://sourceware.org/glibc/wiki/Development_Todo/Master#Locale_archive_alignment>
     861       for more discussion.  */
     862    const size_t pagesz = 4096;
     863    int small_mask;
     864  
     865    head = ah->addr;
     866    sumhashtab = (struct sumhashent *) ((char *) ah->addr
     867  				      + GET (head->sumhash_offset));
     868  
     869    memset (file_offsets, 0, sizeof (file_offsets));
     870  
     871    size_order[LC_ALL] = NULL;
     872    for (cnt = 0; cnt < __LC_LAST; ++cnt)
     873      if (cnt != LC_ALL)
     874        size_order[cnt] = &data[cnt];
     875  
     876    /* Sort the array in ascending order of data size.  */
     877    qsort (size_order, __LC_LAST, sizeof size_order[0], cmpcategorysize);
     878  
     879    small_mask = 0;
     880    data[LC_ALL].size = 0;
     881    for (cnt = 0; cnt < __LC_LAST; ++cnt)
     882      if (size_order[cnt] != NULL)
     883        {
     884  	const size_t rounded_size = (size_order[cnt]->size + 15) & -16;
     885  	if (data[LC_ALL].size + rounded_size > 2 * pagesz)
     886  	  {
     887  	    /* This category makes the small-categories block
     888  	       stop being small, so this is the end of the road.  */
     889  	    do
     890  	      size_order[cnt++] = NULL;
     891  	    while (cnt < __LC_LAST);
     892  	    break;
     893  	  }
     894  	data[LC_ALL].size += rounded_size;
     895  	small_mask |= 1 << (size_order[cnt] - data);
     896        }
     897  
     898    /* Copy the data for all the small categories into the LC_ALL
     899       pseudo-category.  */
     900  
     901    data[LC_ALL].addr = alloca (data[LC_ALL].size);
     902    memset (data[LC_ALL].addr, 0, data[LC_ALL].size);
     903  
     904    ptr = data[LC_ALL].addr;
     905    for (cnt = 0; cnt < __LC_LAST; ++cnt)
     906      if (small_mask & (1 << cnt))
     907        {
     908  	memcpy (ptr, data[cnt].addr, data[cnt].size);
     909  	ptr += (data[cnt].size + 15) & -16;
     910        }
     911    __md5_buffer (data[LC_ALL].addr, data[LC_ALL].size, data[LC_ALL].sum);
     912  
     913    /* For each locale category data set determine whether the same data
     914       is already somewhere in the archive.  */
     915    for (cnt = 0; cnt < __LC_LAST; ++cnt)
     916      if (small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
     917        {
     918  	++num_new_offsets;
     919  
     920  	/* Compute the hash value of the checksum to determine a
     921  	   starting point for the search in the MD5 hash value
     922  	   table.  */
     923  	hval = archive_hashval (data[cnt].sum, 16);
     924  
     925  	idx = hval % GET (head->sumhash_size);
     926  	incr = 1 + hval % (GET (head->sumhash_size) - 2);
     927  
     928  	while (GET (sumhashtab[idx].file_offset) != 0)
     929  	  {
     930  	    if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
     931  	      {
     932  		/* Check the content, there could be a collision of
     933  		   the hash sum.
     934  
     935  		   Unfortunately the sumhashent record does not include
     936  		   the size of the stored data.  So we have to search for
     937  		   it.  */
     938  		locrecent
     939  		  = (struct locrecent *) ((char *) ah->addr
     940  					  + GET (head->locrectab_offset));
     941  		size_t iloc;
     942  		for (iloc = 0; iloc < GET (head->locrectab_used); ++iloc)
     943  		  if (GET (locrecent[iloc].refs) != 0
     944  		      && (GET (locrecent[iloc].record[cnt].offset)
     945  			  == GET (sumhashtab[idx].file_offset)))
     946  		    break;
     947  
     948  		if (iloc != GET (head->locrectab_used)
     949  		    && data[cnt].size == GET (locrecent[iloc].record[cnt].len)
     950  		    /* We have to compare the content.  Either we can
     951  		       have the data mmaped or we have to read from
     952  		       the file.  */
     953  		    && (file_data_available_p
     954  			(ah, GET (sumhashtab[idx].file_offset),
     955  			 data[cnt].size)
     956  			? memcmp (data[cnt].addr,
     957  				  (char *) ah->addr
     958  				  + GET (sumhashtab[idx].file_offset),
     959  				  data[cnt].size) == 0
     960  			: compare_from_file (ah, data[cnt].addr,
     961  					     GET (sumhashtab[idx].file_offset),
     962  					     data[cnt].size) == 0))
     963  		  {
     964  		    /* Found it.  */
     965  		    file_offsets[cnt] = GET (sumhashtab[idx].file_offset);
     966  		    --num_new_offsets;
     967  		    break;
     968  		  }
     969  	      }
     970  
     971  	    idx += incr;
     972  	    if (idx >= GET (head->sumhash_size))
     973  	      idx -= GET (head->sumhash_size);
     974  	  }
     975        }
     976  
     977    /* Find a slot for the locale name in the hash table.  */
     978    namehashent = insert_name (ah, name, name_len, replace);
     979    if (namehashent == NULL)	/* Already exists and !REPLACE.  */
     980      return 0;
     981  
     982    /* Determine whether we have to resize the file.  */
     983    if ((100 * (GET (head->sumhash_used) + num_new_offsets)
     984         > 75 * GET (head->sumhash_size))
     985        || (GET (namehashent->locrec_offset) == 0
     986  	  && (GET (head->locrectab_used) == GET (head->locrectab_size)
     987  	      || (GET (head->string_used) + name_len + 1
     988  		  > GET (head->string_size))
     989  	      || (100 * GET (head->namehash_used)
     990  		  > 75 * GET (head->namehash_size)))))
     991      {
     992        /* The current archive is not large enough.  */
     993        enlarge_archive (ah, head);
     994        return add_locale (ah, name, data, replace);
     995      }
     996  
     997    /* Add the locale data which is not yet in the archive.  */
     998    for (cnt = 0, lastoffset = 0; cnt < __LC_LAST; ++cnt)
     999      if ((small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
    1000  	&& file_offsets[cnt] == 0)
    1001        {
    1002  	/* The data for this section is not yet available in the
    1003  	   archive.  Append it.  */
    1004  	off64_t lastpos;
    1005  	uint32_t md5hval;
    1006  
    1007  	lastpos = lseek64 (ah->fd, 0, SEEK_END);
    1008  	if (lastpos == (off64_t) -1)
    1009  	  error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
    1010  
    1011  	/* If block of small categories would cross page boundary,
    1012  	   align it unless it immediately follows a large category.  */
    1013  	if (cnt == LC_ALL && lastoffset != lastpos
    1014  	    && ((((lastpos & (pagesz - 1)) + data[cnt].size + pagesz - 1)
    1015  		 & -pagesz)
    1016  		> ((data[cnt].size + pagesz - 1) & -pagesz)))
    1017  	  {
    1018  	    size_t sz = pagesz - (lastpos & (pagesz - 1));
    1019  	    char *zeros = alloca (sz);
    1020  
    1021  	    memset (zeros, 0, sz);
    1022  	    if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, sz) != sz))
    1023  	      error (EXIT_FAILURE, errno,
    1024  		     _("cannot add to locale archive"));
    1025  
    1026  	    lastpos += sz;
    1027  	  }
    1028  
    1029  	/* Align all data to a 16 byte boundary.  */
    1030  	if ((lastpos & 15) != 0)
    1031  	  {
    1032  	    static const char zeros[15] = { 0, };
    1033  
    1034  	    if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, 16 - (lastpos & 15)))
    1035  		!= 16 - (lastpos & 15))
    1036  	      error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
    1037  
    1038  	    lastpos += 16 - (lastpos & 15);
    1039  	  }
    1040  
    1041  	/* Remember the position.  */
    1042  	file_offsets[cnt] = lastpos;
    1043  	lastoffset = lastpos + data[cnt].size;
    1044  
    1045  	/* Write the data.  */
    1046  	if (TEMP_FAILURE_RETRY (write (ah->fd, data[cnt].addr, data[cnt].size))
    1047  	    != data[cnt].size)
    1048  	  error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
    1049  
    1050  	/* Add the hash value to the hash table.  */
    1051  	md5hval = archive_hashval (data[cnt].sum, 16);
    1052  
    1053  	idx = md5hval % GET (head->sumhash_size);
    1054  	incr = 1 + md5hval % (GET (head->sumhash_size) - 2);
    1055  
    1056  	while (GET (sumhashtab[idx].file_offset) != 0)
    1057  	  {
    1058  	    idx += incr;
    1059  	    if (idx >= GET (head->sumhash_size))
    1060  	      idx -= GET (head->sumhash_size);
    1061  	  }
    1062  
    1063  	memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
    1064  	SET (sumhashtab[idx].file_offset, file_offsets[cnt]);
    1065  
    1066  	INC (head->sumhash_used, 1);
    1067        }
    1068  
    1069    lastoffset = file_offsets[LC_ALL];
    1070    for (cnt = 0; cnt < __LC_LAST; ++cnt)
    1071      if (small_mask & (1 << cnt))
    1072        {
    1073  	file_offsets[cnt] = lastoffset;
    1074  	lastoffset += (data[cnt].size + 15) & -16;
    1075        }
    1076  
    1077    if (GET (namehashent->name_offset) == 0)
    1078      {
    1079        /* Add the name string.  */
    1080        memcpy ((char *) ah->addr + GET (head->string_offset)
    1081  	      + GET (head->string_used),
    1082  	      name, name_len + 1);
    1083        SET (namehashent->name_offset,
    1084  	   GET (head->string_offset) + GET (head->string_used));
    1085        INC (head->string_used, name_len + 1);
    1086        INC (head->namehash_used, 1);
    1087      }
    1088  
    1089    if (GET (namehashent->locrec_offset == 0))
    1090      {
    1091        /* Allocate a name location record.  */
    1092        SET (namehashent->locrec_offset, (GET (head->locrectab_offset)
    1093  					+ (GET (head->locrectab_used)
    1094  					   * sizeof (struct locrecent))));
    1095        INC (head->locrectab_used, 1);
    1096        locrecent = (struct locrecent *) ((char *) ah->addr
    1097  					+ GET (namehashent->locrec_offset));
    1098        SET (locrecent->refs, 1);
    1099      }
    1100    else
    1101      {
    1102        /* If there are other aliases pointing to this locrecent,
    1103  	 we still need a new one.  If not, reuse the old one.  */
    1104  
    1105        locrecent = (struct locrecent *) ((char *) ah->addr
    1106  					+ GET (namehashent->locrec_offset));
    1107        if (GET (locrecent->refs) > 1)
    1108  	{
    1109  	  INC (locrecent->refs, -1);
    1110  	  SET (namehashent->locrec_offset, (GET (head->locrectab_offset)
    1111  					    + (GET (head->locrectab_used)
    1112  					       * sizeof (struct locrecent))));
    1113  	  INC (head->locrectab_used, 1);
    1114  	  locrecent
    1115  	    = (struct locrecent *) ((char *) ah->addr
    1116  				    + GET (namehashent->locrec_offset));
    1117  	  SET (locrecent->refs, 1);
    1118  	}
    1119      }
    1120  
    1121    /* Fill in the table with the locations of the locale data.  */
    1122    for (cnt = 0; cnt < __LC_LAST; ++cnt)
    1123      {
    1124        SET (locrecent->record[cnt].offset, file_offsets[cnt]);
    1125        SET (locrecent->record[cnt].len, data[cnt].size);
    1126      }
    1127  
    1128    return GET (namehashent->locrec_offset);
    1129  }
    1130  
    1131  
    1132  /* Check the content of the archive for duplicates.  Add the content
    1133     of the files if necessary.  Add all the names, possibly overwriting
    1134     old files.  */
    1135  int
    1136  add_locale_to_archive (struct locarhandle *ah, const char *name,
    1137  		       locale_data_t data, bool replace)
    1138  {
    1139    char *normalized_name = NULL;
    1140    uint32_t locrec_offset;
    1141  
    1142    /* First analyze the name to decide how to archive it.  */
    1143    const char *language;
    1144    const char *modifier;
    1145    const char *territory;
    1146    const char *codeset;
    1147    const char *normalized_codeset;
    1148    int mask = _nl_explode_name (strdupa (name),
    1149  			       &language, &modifier, &territory,
    1150  			       &codeset, &normalized_codeset);
    1151    if (mask == -1)
    1152      return -1;
    1153  
    1154    if (mask & XPG_NORM_CODESET)
    1155      /* This name contains a codeset in unnormalized form.
    1156         We will store it in the archive with a normalized name.  */
    1157      if (asprintf (&normalized_name, "%s%s%s.%s%s%s",
    1158  		  language, territory == NULL ? "" : "_", territory ?: "",
    1159  		  normalized_codeset,
    1160  		  modifier == NULL ? "" : "@", modifier ?: "") < 0)
    1161        {
    1162  	free ((char *) normalized_codeset);
    1163  	return -1;
    1164        }
    1165  
    1166    /* This call does the main work.  */
    1167    locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
    1168    if (locrec_offset == 0)
    1169      {
    1170        free (normalized_name);
    1171        if (mask & XPG_NORM_CODESET)
    1172  	free ((char *) normalized_codeset);
    1173        return -1;
    1174      }
    1175  
    1176    if ((mask & XPG_CODESET) == 0)
    1177      {
    1178        /* This name lacks a codeset, so determine the locale's codeset and
    1179  	 add an alias for its name with normalized codeset appended.  */
    1180  
    1181        const struct
    1182        {
    1183  	unsigned int magic;
    1184  	unsigned int nstrings;
    1185  	unsigned int strindex[0];
    1186        } *filedata = data[LC_CTYPE].addr;
    1187        codeset = (char *) filedata
    1188  	+ maybe_swap_uint32 (filedata->strindex[_NL_ITEM_INDEX
    1189  						(_NL_CTYPE_CODESET_NAME)]);
    1190        char *normalized_codeset_name = NULL;
    1191  
    1192        normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
    1193        mask |= XPG_NORM_CODESET;
    1194  
    1195        if (asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
    1196  		    language, territory == NULL ? "" : "_", territory ?: "",
    1197  		    normalized_codeset,
    1198  		    modifier == NULL ? "" : "@", modifier ?: "") < 0)
    1199  	{
    1200  	  free ((char *) normalized_codeset);
    1201  	  return -1;
    1202  	}
    1203  
    1204        add_alias (ah, normalized_codeset_name, replace,
    1205  		 normalized_name ?: name, &locrec_offset);
    1206        free (normalized_codeset_name);
    1207      }
    1208  
    1209    /* Now read the locale.alias files looking for lines whose
    1210       right hand side matches our name after normalization.  */
    1211    int result = 0;
    1212    if (alias_file != NULL)
    1213      {
    1214        FILE *fp;
    1215        fp = fopen (alias_file, "rm");
    1216        if (fp == NULL)
    1217  	error (1, errno, _("locale alias file `%s' not found"),
    1218  	       alias_file);
    1219  
    1220        /* No threads present.  */
    1221        __fsetlocking (fp, FSETLOCKING_BYCALLER);
    1222  
    1223        while (! feof_unlocked (fp))
    1224  	{
    1225  	  /* It is a reasonable approach to use a fix buffer here
    1226  	     because
    1227  	     a) we are only interested in the first two fields
    1228  	     b) these fields must be usable as file names and so must
    1229  	     not be that long  */
    1230  	  char buf[BUFSIZ];
    1231  	  char *alias;
    1232  	  char *value;
    1233  	  char *cp;
    1234  
    1235  	  if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
    1236  	    /* EOF reached.  */
    1237  	    break;
    1238  
    1239  	  cp = buf;
    1240  	  /* Ignore leading white space.  */
    1241  	  while (isspace (cp[0]) && cp[0] != '\n')
    1242  	    ++cp;
    1243  
    1244  	  /* A leading '#' signals a comment line.  */
    1245  	  if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
    1246  	    {
    1247  	      alias = cp++;
    1248  	      while (cp[0] != '\0' && !isspace (cp[0]))
    1249  		++cp;
    1250  	      /* Terminate alias name.  */
    1251  	      if (cp[0] != '\0')
    1252  		*cp++ = '\0';
    1253  
    1254  	      /* Now look for the beginning of the value.  */
    1255  	      while (isspace (cp[0]))
    1256  		++cp;
    1257  
    1258  	      if (cp[0] != '\0')
    1259  		{
    1260  		  value = cp++;
    1261  		  while (cp[0] != '\0' && !isspace (cp[0]))
    1262  		    ++cp;
    1263  		  /* Terminate value.  */
    1264  		  if (cp[0] == '\n')
    1265  		    {
    1266  		      /* This has to be done to make the following
    1267  			 test for the end of line possible.  We are
    1268  			 looking for the terminating '\n' which do not
    1269  			 overwrite here.  */
    1270  		      *cp++ = '\0';
    1271  		      *cp = '\n';
    1272  		    }
    1273  		  else if (cp[0] != '\0')
    1274  		    *cp++ = '\0';
    1275  
    1276  		  /* Does this alias refer to our locale?  We will
    1277  		     normalize the right hand side and compare the
    1278  		     elements of the normalized form.  */
    1279  		  {
    1280  		    const char *rhs_language;
    1281  		    const char *rhs_modifier;
    1282  		    const char *rhs_territory;
    1283  		    const char *rhs_codeset;
    1284  		    const char *rhs_normalized_codeset;
    1285  		    int rhs_mask = _nl_explode_name (value,
    1286  						     &rhs_language,
    1287  						     &rhs_modifier,
    1288  						     &rhs_territory,
    1289  						     &rhs_codeset,
    1290  						     &rhs_normalized_codeset);
    1291  		    if (rhs_mask == -1)
    1292  		      {
    1293  			result = -1;
    1294  			goto out;
    1295  		      }
    1296  		    if (!strcmp (language, rhs_language)
    1297  			&& ((rhs_mask & XPG_CODESET)
    1298  			    /* He has a codeset, it must match normalized.  */
    1299  			    ? !strcmp ((mask & XPG_NORM_CODESET)
    1300  				       ? normalized_codeset : codeset,
    1301  				       (rhs_mask & XPG_NORM_CODESET)
    1302  				       ? rhs_normalized_codeset : rhs_codeset)
    1303  			    /* He has no codeset, we must also have none.  */
    1304  			    : (mask & XPG_CODESET) == 0)
    1305  			/* Codeset (or lack thereof) matches.  */
    1306  			&& !strcmp (territory ?: "", rhs_territory ?: "")
    1307  			&& !strcmp (modifier ?: "", rhs_modifier ?: ""))
    1308  		      /* We have a winner.  */
    1309  		      add_alias (ah, alias, replace,
    1310  				 normalized_name ?: name, &locrec_offset);
    1311  		    if (rhs_mask & XPG_NORM_CODESET)
    1312  		      free ((char *) rhs_normalized_codeset);
    1313  		  }
    1314  		}
    1315  	    }
    1316  
    1317  	  /* Possibly not the whole line fits into the buffer.
    1318  	     Ignore the rest of the line.  */
    1319  	  while (strchr (cp, '\n') == NULL)
    1320  	    {
    1321  	      cp = buf;
    1322  	      if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
    1323  		/* Make sure the inner loop will be left.  The outer
    1324  		   loop will exit at the `feof' test.  */
    1325  		*cp = '\n';
    1326  	    }
    1327  	}
    1328  
    1329      out:
    1330        fclose (fp);
    1331      }
    1332  
    1333    free (normalized_name);
    1334  
    1335    if (mask & XPG_NORM_CODESET)
    1336      free ((char *) normalized_codeset);
    1337  
    1338    return result;
    1339  }
    1340  
    1341  
    1342  int
    1343  add_locales_to_archive (size_t nlist, char *list[], bool replace)
    1344  {
    1345    struct locarhandle ah;
    1346    int result = 0;
    1347  
    1348    /* Open the archive.  This call never returns if we cannot
    1349       successfully open the archive.  */
    1350    ah.fname = NULL;
    1351    open_archive (&ah, false);
    1352  
    1353    while (nlist-- > 0)
    1354      {
    1355        const char *fname = *list++;
    1356        size_t fnamelen = strlen (fname);
    1357        struct stat64 st;
    1358        DIR *dirp;
    1359        struct dirent64 *d;
    1360        int seen;
    1361        locale_data_t data;
    1362        int cnt;
    1363  
    1364        if (! be_quiet)
    1365  	printf (_("Adding %s\n"), fname);
    1366  
    1367        /* First see whether this really is a directory and whether it
    1368  	 contains all the require locale category files.  */
    1369        if (stat64 (fname, &st) < 0)
    1370  	{
    1371  	  error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
    1372  		 strerror (errno));
    1373  	  continue;
    1374  	}
    1375        if (!S_ISDIR (st.st_mode))
    1376  	{
    1377  	  error (0, 0, _("\"%s\" is no directory; ignored"), fname);
    1378  	  continue;
    1379  	}
    1380  
    1381        dirp = opendir (fname);
    1382        if (dirp == NULL)
    1383  	{
    1384  	  error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
    1385  		 fname, strerror (errno));
    1386  	  continue;
    1387  	}
    1388  
    1389        seen = 0;
    1390        while ((d = readdir64 (dirp)) != NULL)
    1391  	{
    1392  	  for (cnt = 0; cnt < __LC_LAST; ++cnt)
    1393  	    if (cnt != LC_ALL)
    1394  	      if (strcmp (d->d_name, locnames[cnt]) == 0)
    1395  		{
    1396  		  unsigned char d_type;
    1397  
    1398  		  /* We have an object of the required name.  If it's
    1399  		     a directory we have to look at a file with the
    1400  		     prefix "SYS_".  Otherwise we have found what we
    1401  		     are looking for.  */
    1402  		  d_type = d->d_type;
    1403  
    1404  		  if (d_type != DT_REG)
    1405  		    {
    1406  		      char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
    1407  
    1408  		      if (d_type == DT_UNKNOWN || d_type == DT_LNK)
    1409  			{
    1410  			  strcpy (stpcpy (stpcpy (fullname, fname), "/"),
    1411  				  d->d_name);
    1412  
    1413  			  if (stat64 (fullname, &st) == -1)
    1414  			    /* We cannot stat the file, ignore it.  */
    1415  			    break;
    1416  
    1417  			  d_type = IFTODT (st.st_mode);
    1418  			}
    1419  
    1420  		      if (d_type == DT_DIR)
    1421  			{
    1422  			  /* We have to do more tests.  The file is a
    1423  			     directory and it therefore must contain a
    1424  			     regular file with the same name except a
    1425  			     "SYS_" prefix.  */
    1426  			  char *t = stpcpy (stpcpy (fullname, fname), "/");
    1427  			  strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
    1428  				  d->d_name);
    1429  
    1430  			  if (stat64 (fullname, &st) == -1)
    1431  			    /* There is no SYS_* file or we cannot
    1432  			       access it.  */
    1433  			    break;
    1434  
    1435  			  d_type = IFTODT (st.st_mode);
    1436  			}
    1437  		    }
    1438  
    1439  		  /* If we found a regular file (eventually after
    1440  		     following a symlink) we are successful.  */
    1441  		  if (d_type == DT_REG)
    1442  		    ++seen;
    1443  		  break;
    1444  		}
    1445  	}
    1446  
    1447        closedir (dirp);
    1448  
    1449        if (seen != __LC_LAST - 1)
    1450  	{
    1451  	  /* We don't have all locale category files.  Ignore the name.  */
    1452  	  error (0, 0, _("incomplete set of locale files in \"%s\""),
    1453  		 fname);
    1454  	  continue;
    1455  	}
    1456  
    1457        /* Add the files to the archive.  To do this we first compute
    1458  	 sizes and the MD5 sums of all the files.  */
    1459        for (cnt = 0; cnt < __LC_LAST; ++cnt)
    1460  	if (cnt != LC_ALL)
    1461  	  {
    1462  	    char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
    1463  	    int fd;
    1464  
    1465  	    strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
    1466  	    fd = open64 (fullname, O_RDONLY);
    1467  	    if (fd == -1 || fstat64 (fd, &st) == -1)
    1468  	      {
    1469  		/* Cannot read the file.  */
    1470  		if (fd != -1)
    1471  		  close (fd);
    1472  		break;
    1473  	      }
    1474  
    1475  	    if (S_ISDIR (st.st_mode))
    1476  	      {
    1477  		char *t;
    1478  		close (fd);
    1479  		t = stpcpy (stpcpy (fullname, fname), "/");
    1480  		strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
    1481  			locnames[cnt]);
    1482  
    1483  		fd = open64 (fullname, O_RDONLY);
    1484  		if (fd == -1 || fstat64 (fd, &st) == -1
    1485  		    || !S_ISREG (st.st_mode))
    1486  		  {
    1487  		    if (fd != -1)
    1488  		      close (fd);
    1489  		    break;
    1490  		  }
    1491  	      }
    1492  
    1493  	    /* Map the file.  */
    1494  	    data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
    1495  				     fd, 0);
    1496  	    if (data[cnt].addr == MAP_FAILED)
    1497  	      {
    1498  		/* Cannot map it.  */
    1499  		close (fd);
    1500  		break;
    1501  	      }
    1502  
    1503  	    data[cnt].size = st.st_size;
    1504  	    __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
    1505  
    1506  	    /* We don't need the file descriptor anymore.  */
    1507  	    close (fd);
    1508  	  }
    1509  
    1510        if (cnt != __LC_LAST)
    1511  	{
    1512  	  while (cnt-- > 0)
    1513  	    if (cnt != LC_ALL)
    1514  	      munmap (data[cnt].addr, data[cnt].size);
    1515  
    1516  	  error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);
    1517  
    1518  	  continue;
    1519  	}
    1520  
    1521        result |= add_locale_to_archive (&ah, basename (fname), data, replace);
    1522  
    1523        for (cnt = 0; cnt < __LC_LAST; ++cnt)
    1524  	if (cnt != LC_ALL)
    1525  	  munmap (data[cnt].addr, data[cnt].size);
    1526      }
    1527  
    1528    /* We are done.  */
    1529    close_archive (&ah);
    1530  
    1531    return result;
    1532  }
    1533  
    1534  
    1535  int
    1536  delete_locales_from_archive (size_t nlist, char *list[])
    1537  {
    1538    struct locarhandle ah;
    1539    struct locarhead *head;
    1540    struct namehashent *namehashtab;
    1541  
    1542    /* Open the archive.  This call never returns if we cannot
    1543       successfully open the archive.  */
    1544    ah.fname = NULL;
    1545    open_archive (&ah, false);
    1546  
    1547    head = ah.addr;
    1548    namehashtab = (struct namehashent *) ((char *) ah.addr
    1549  					+ GET (head->namehash_offset));
    1550  
    1551    while (nlist-- > 0)
    1552      {
    1553        const char *locname = *list++;
    1554        uint32_t hval;
    1555        unsigned int idx;
    1556        unsigned int incr;
    1557  
    1558        /* Search for this locale in the archive.  */
    1559        hval = archive_hashval (locname, strlen (locname));
    1560  
    1561        idx = hval % GET (head->namehash_size);
    1562        incr = 1 + hval % (GET (head->namehash_size) - 2);
    1563  
    1564        /* If the name_offset field is zero this means this is no
    1565  	 deleted entry and therefore no entry can be found.  */
    1566        while (GET (namehashtab[idx].name_offset) != 0)
    1567  	{
    1568  	  if (GET (namehashtab[idx].hashval) == hval
    1569  	      && (strcmp (locname,
    1570  			  ((char *) ah.addr
    1571  			   + GET (namehashtab[idx].name_offset)))
    1572  		  == 0))
    1573  	    {
    1574  	      /* Found the entry.  Now mark it as removed by zero-ing
    1575  		 the reference to the locale record.  */
    1576  	      SET (namehashtab[idx].locrec_offset, 0);
    1577  	      break;
    1578  	    }
    1579  
    1580  	  idx += incr;
    1581  	  if (idx >= GET (head->namehash_size))
    1582  	    idx -= GET (head->namehash_size);
    1583  	}
    1584  
    1585        if (GET (namehashtab[idx].name_offset) == 0 && ! be_quiet)
    1586  	error (0, 0, _("locale \"%s\" not in archive"), locname);
    1587      }
    1588  
    1589    close_archive (&ah);
    1590  
    1591    return 0;
    1592  }
    1593  
    1594  
    1595  struct nameent
    1596  {
    1597    char *name;
    1598    uint32_t locrec_offset;
    1599  };
    1600  
    1601  
    1602  struct dataent
    1603  {
    1604    const unsigned char *sum;
    1605    uint32_t file_offset;
    1606    uint32_t nlink;
    1607  };
    1608  
    1609  
    1610  static int
    1611  nameentcmp (const void *a, const void *b)
    1612  {
    1613    return strcmp (((const struct nameent *) a)->name,
    1614  		 ((const struct nameent *) b)->name);
    1615  }
    1616  
    1617  
    1618  static int
    1619  dataentcmp (const void *a, const void *b)
    1620  {
    1621    if (((const struct dataent *) a)->file_offset
    1622        < ((const struct dataent *) b)->file_offset)
    1623      return -1;
    1624  
    1625    if (((const struct dataent *) a)->file_offset
    1626        > ((const struct dataent *) b)->file_offset)
    1627      return 1;
    1628  
    1629    return 0;
    1630  }
    1631  
    1632  
    1633  void
    1634  show_archive_content (const char *fname, int verbose)
    1635  {
    1636    struct locarhandle ah;
    1637    struct locarhead *head;
    1638    struct namehashent *namehashtab;
    1639    struct nameent *names;
    1640    size_t cnt, used;
    1641  
    1642    /* Open the archive.  This call never returns if we cannot
    1643       successfully open the archive.  */
    1644    ah.fname = fname;
    1645    open_archive (&ah, true);
    1646  
    1647    head = ah.addr;
    1648  
    1649    names = (struct nameent *) xmalloc (GET (head->namehash_used)
    1650  				      * sizeof (struct nameent));
    1651  
    1652    namehashtab = (struct namehashent *) ((char *) ah.addr
    1653  					+ GET (head->namehash_offset));
    1654    for (cnt = used = 0; cnt < GET (head->namehash_size); ++cnt)
    1655      if (GET (namehashtab[cnt].locrec_offset) != 0)
    1656        {
    1657  	assert (used < GET (head->namehash_used));
    1658  	names[used].name = ah.addr + GET (namehashtab[cnt].name_offset);
    1659  	names[used++].locrec_offset = GET (namehashtab[cnt].locrec_offset);
    1660        }
    1661  
    1662    /* Sort the names.  */
    1663    qsort (names, used, sizeof (struct nameent), nameentcmp);
    1664  
    1665    if (verbose)
    1666      {
    1667        struct dataent *files;
    1668        struct sumhashent *sumhashtab;
    1669        int sumused;
    1670  
    1671        files = (struct dataent *) xmalloc (GET (head->sumhash_used)
    1672  					  * sizeof (struct dataent));
    1673  
    1674        sumhashtab = (struct sumhashent *) ((char *) ah.addr
    1675  					  + GET (head->sumhash_offset));
    1676        for (cnt = sumused = 0; cnt < GET (head->sumhash_size); ++cnt)
    1677  	if (GET (sumhashtab[cnt].file_offset) != 0)
    1678  	  {
    1679  	    assert (sumused < GET (head->sumhash_used));
    1680  	    files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
    1681  	    files[sumused].file_offset = GET (sumhashtab[cnt].file_offset);
    1682  	    files[sumused++].nlink = 0;
    1683  	  }
    1684  
    1685        /* Sort by file locations.  */
    1686        qsort (files, sumused, sizeof (struct dataent), dataentcmp);
    1687  
    1688        /* Compute nlink fields.  */
    1689        for (cnt = 0; cnt < used; ++cnt)
    1690  	{
    1691  	  struct locrecent *locrec;
    1692  	  int idx;
    1693  
    1694  	  locrec = (struct locrecent *) ((char *) ah.addr
    1695  					 + names[cnt].locrec_offset);
    1696  	  for (idx = 0; idx < __LC_LAST; ++idx)
    1697  	    if (GET (locrec->record[LC_ALL].offset) != 0
    1698  		? (idx == LC_ALL
    1699  		   || (GET (locrec->record[idx].offset)
    1700  		       < GET (locrec->record[LC_ALL].offset))
    1701  		   || ((GET (locrec->record[idx].offset)
    1702  			+ GET (locrec->record[idx].len))
    1703  		       > (GET (locrec->record[LC_ALL].offset)
    1704  			  + GET (locrec->record[LC_ALL].len))))
    1705  		: idx != LC_ALL)
    1706  	      {
    1707  		struct dataent *data, dataent;
    1708  
    1709  		dataent.file_offset = GET (locrec->record[idx].offset);
    1710  		data = (struct dataent *) bsearch (&dataent, files, sumused,
    1711  						   sizeof (struct dataent),
    1712  						   dataentcmp);
    1713  		assert (data != NULL);
    1714  		++data->nlink;
    1715  	      }
    1716  	}
    1717  
    1718        /* Print it.  */
    1719        for (cnt = 0; cnt < used; ++cnt)
    1720  	{
    1721  	  struct locrecent *locrec;
    1722  	  int idx, i;
    1723  
    1724  	  locrec = (struct locrecent *) ((char *) ah.addr
    1725  					 + names[cnt].locrec_offset);
    1726  	  for (idx = 0; idx < __LC_LAST; ++idx)
    1727  	    if (idx != LC_ALL)
    1728  	      {
    1729  		struct dataent *data, dataent;
    1730  
    1731  		dataent.file_offset = GET (locrec->record[idx].offset);
    1732  		if (GET (locrec->record[LC_ALL].offset) != 0
    1733  		    && (dataent.file_offset
    1734  			>= GET (locrec->record[LC_ALL].offset))
    1735  		    && (dataent.file_offset + GET (locrec->record[idx].len)
    1736  			<= (GET (locrec->record[LC_ALL].offset)
    1737  			    + GET (locrec->record[LC_ALL].len))))
    1738  		  dataent.file_offset = GET (locrec->record[LC_ALL].offset);
    1739  
    1740  		data = (struct dataent *) bsearch (&dataent, files, sumused,
    1741  						   sizeof (struct dataent),
    1742  						   dataentcmp);
    1743  		printf ("%6d %7x %3d%c ",
    1744  			GET (locrec->record[idx].len),
    1745  			GET (locrec->record[idx].offset),
    1746  			data->nlink,
    1747  			(dataent.file_offset
    1748  			 == GET (locrec->record[LC_ALL].offset))
    1749  			? '+' : ' ');
    1750  		for (i = 0; i < 16; i += 4)
    1751  		    printf ("%02x%02x%02x%02x",
    1752  			    data->sum[i], data->sum[i + 1],
    1753  			    data->sum[i + 2], data->sum[i + 3]);
    1754  		printf (" %s/%s\n", names[cnt].name,
    1755  			idx == LC_MESSAGES ? "LC_MESSAGES/SYS_LC_MESSAGES"
    1756  			: locnames[idx]);
    1757  	      }
    1758  	}
    1759        free (files);
    1760      }
    1761    else
    1762      for (cnt = 0; cnt < used; ++cnt)
    1763        puts (names[cnt].name);
    1764  
    1765    close_archive (&ah);
    1766  
    1767    exit (EXIT_SUCCESS);
    1768  }