1 /* Copyright (C) 2002-2022 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 *
prepare_address_space(int fd,size_t total,size_t * reserved,int * xflags,void ** mmap_base,size_t * mmap_len)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
create_archive(const char * archivefname,struct locarhandle * ah)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
oldlocrecentcmp(const void * a,const void * b)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
file_data_available_p(struct locarhandle * ah,uint32_t offset,uint32_t size)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
compare_from_file(struct locarhandle * ah,void * p1,uint32_t offset2,uint32_t size)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
enlarge_archive(struct locarhandle * ah,const struct locarhead * head)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
open_archive(struct locarhandle * ah,bool readonly)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
close_archive(struct locarhandle * ah)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 *
insert_name(struct locarhandle * ah,const char * name,size_t name_len,bool replace)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
add_alias(struct locarhandle * ah,const char * alias,bool replace,const char * oldname,uint32_t * locrec_offset_p)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 */
cmpcategorysize(const void * a,const void * b)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
add_locale(struct locarhandle * ah,const char * name,locale_data_t data,bool replace)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
add_locale_to_archive(struct locarhandle * ah,const char * name,locale_data_t data,bool replace)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     asprintf (&normalized_name, "%s%s%s.%s%s%s",
1158 	      language, territory == NULL ? "" : "_", territory ?: "",
1159 	      (mask & XPG_NORM_CODESET) ? normalized_codeset : codeset,
1160 	      modifier == NULL ? "" : "@", modifier ?: "");
1161 
1162   /* This call does the main work.  */
1163   locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
1164   if (locrec_offset == 0)
1165     {
1166       free (normalized_name);
1167       if (mask & XPG_NORM_CODESET)
1168 	free ((char *) normalized_codeset);
1169       return -1;
1170     }
1171 
1172   if ((mask & XPG_CODESET) == 0)
1173     {
1174       /* This name lacks a codeset, so determine the locale's codeset and
1175 	 add an alias for its name with normalized codeset appended.  */
1176 
1177       const struct
1178       {
1179 	unsigned int magic;
1180 	unsigned int nstrings;
1181 	unsigned int strindex[0];
1182       } *filedata = data[LC_CTYPE].addr;
1183       codeset = (char *) filedata
1184 	+ maybe_swap_uint32 (filedata->strindex[_NL_ITEM_INDEX
1185 						(_NL_CTYPE_CODESET_NAME)]);
1186       char *normalized_codeset_name = NULL;
1187 
1188       normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
1189       mask |= XPG_NORM_CODESET;
1190 
1191       asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
1192 		language, territory == NULL ? "" : "_", territory ?: "",
1193 		normalized_codeset,
1194 		modifier == NULL ? "" : "@", modifier ?: "");
1195 
1196       add_alias (ah, normalized_codeset_name, replace,
1197 		 normalized_name ?: name, &locrec_offset);
1198       free (normalized_codeset_name);
1199     }
1200 
1201   /* Now read the locale.alias files looking for lines whose
1202      right hand side matches our name after normalization.  */
1203   int result = 0;
1204   if (alias_file != NULL)
1205     {
1206       FILE *fp;
1207       fp = fopen (alias_file, "rm");
1208       if (fp == NULL)
1209 	error (1, errno, _("locale alias file `%s' not found"),
1210 	       alias_file);
1211 
1212       /* No threads present.  */
1213       __fsetlocking (fp, FSETLOCKING_BYCALLER);
1214 
1215       while (! feof_unlocked (fp))
1216 	{
1217 	  /* It is a reasonable approach to use a fix buffer here
1218 	     because
1219 	     a) we are only interested in the first two fields
1220 	     b) these fields must be usable as file names and so must
1221 	     not be that long  */
1222 	  char buf[BUFSIZ];
1223 	  char *alias;
1224 	  char *value;
1225 	  char *cp;
1226 
1227 	  if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1228 	    /* EOF reached.  */
1229 	    break;
1230 
1231 	  cp = buf;
1232 	  /* Ignore leading white space.  */
1233 	  while (isspace (cp[0]) && cp[0] != '\n')
1234 	    ++cp;
1235 
1236 	  /* A leading '#' signals a comment line.  */
1237 	  if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
1238 	    {
1239 	      alias = cp++;
1240 	      while (cp[0] != '\0' && !isspace (cp[0]))
1241 		++cp;
1242 	      /* Terminate alias name.  */
1243 	      if (cp[0] != '\0')
1244 		*cp++ = '\0';
1245 
1246 	      /* Now look for the beginning of the value.  */
1247 	      while (isspace (cp[0]))
1248 		++cp;
1249 
1250 	      if (cp[0] != '\0')
1251 		{
1252 		  value = cp++;
1253 		  while (cp[0] != '\0' && !isspace (cp[0]))
1254 		    ++cp;
1255 		  /* Terminate value.  */
1256 		  if (cp[0] == '\n')
1257 		    {
1258 		      /* This has to be done to make the following
1259 			 test for the end of line possible.  We are
1260 			 looking for the terminating '\n' which do not
1261 			 overwrite here.  */
1262 		      *cp++ = '\0';
1263 		      *cp = '\n';
1264 		    }
1265 		  else if (cp[0] != '\0')
1266 		    *cp++ = '\0';
1267 
1268 		  /* Does this alias refer to our locale?  We will
1269 		     normalize the right hand side and compare the
1270 		     elements of the normalized form.  */
1271 		  {
1272 		    const char *rhs_language;
1273 		    const char *rhs_modifier;
1274 		    const char *rhs_territory;
1275 		    const char *rhs_codeset;
1276 		    const char *rhs_normalized_codeset;
1277 		    int rhs_mask = _nl_explode_name (value,
1278 						     &rhs_language,
1279 						     &rhs_modifier,
1280 						     &rhs_territory,
1281 						     &rhs_codeset,
1282 						     &rhs_normalized_codeset);
1283 		    if (rhs_mask == -1)
1284 		      {
1285 			result = -1;
1286 			goto out;
1287 		      }
1288 		    if (!strcmp (language, rhs_language)
1289 			&& ((rhs_mask & XPG_CODESET)
1290 			    /* He has a codeset, it must match normalized.  */
1291 			    ? !strcmp ((mask & XPG_NORM_CODESET)
1292 				       ? normalized_codeset : codeset,
1293 				       (rhs_mask & XPG_NORM_CODESET)
1294 				       ? rhs_normalized_codeset : rhs_codeset)
1295 			    /* He has no codeset, we must also have none.  */
1296 			    : (mask & XPG_CODESET) == 0)
1297 			/* Codeset (or lack thereof) matches.  */
1298 			&& !strcmp (territory ?: "", rhs_territory ?: "")
1299 			&& !strcmp (modifier ?: "", rhs_modifier ?: ""))
1300 		      /* We have a winner.  */
1301 		      add_alias (ah, alias, replace,
1302 				 normalized_name ?: name, &locrec_offset);
1303 		    if (rhs_mask & XPG_NORM_CODESET)
1304 		      free ((char *) rhs_normalized_codeset);
1305 		  }
1306 		}
1307 	    }
1308 
1309 	  /* Possibly not the whole line fits into the buffer.
1310 	     Ignore the rest of the line.  */
1311 	  while (strchr (cp, '\n') == NULL)
1312 	    {
1313 	      cp = buf;
1314 	      if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1315 		/* Make sure the inner loop will be left.  The outer
1316 		   loop will exit at the `feof' test.  */
1317 		*cp = '\n';
1318 	    }
1319 	}
1320 
1321     out:
1322       fclose (fp);
1323     }
1324 
1325   free (normalized_name);
1326 
1327   if (mask & XPG_NORM_CODESET)
1328     free ((char *) normalized_codeset);
1329 
1330   return result;
1331 }
1332 
1333 
1334 int
add_locales_to_archive(size_t nlist,char * list[],bool replace)1335 add_locales_to_archive (size_t nlist, char *list[], bool replace)
1336 {
1337   struct locarhandle ah;
1338   int result = 0;
1339 
1340   /* Open the archive.  This call never returns if we cannot
1341      successfully open the archive.  */
1342   ah.fname = NULL;
1343   open_archive (&ah, false);
1344 
1345   while (nlist-- > 0)
1346     {
1347       const char *fname = *list++;
1348       size_t fnamelen = strlen (fname);
1349       struct stat64 st;
1350       DIR *dirp;
1351       struct dirent64 *d;
1352       int seen;
1353       locale_data_t data;
1354       int cnt;
1355 
1356       if (! be_quiet)
1357 	printf (_("Adding %s\n"), fname);
1358 
1359       /* First see whether this really is a directory and whether it
1360 	 contains all the require locale category files.  */
1361       if (stat64 (fname, &st) < 0)
1362 	{
1363 	  error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
1364 		 strerror (errno));
1365 	  continue;
1366 	}
1367       if (!S_ISDIR (st.st_mode))
1368 	{
1369 	  error (0, 0, _("\"%s\" is no directory; ignored"), fname);
1370 	  continue;
1371 	}
1372 
1373       dirp = opendir (fname);
1374       if (dirp == NULL)
1375 	{
1376 	  error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1377 		 fname, strerror (errno));
1378 	  continue;
1379 	}
1380 
1381       seen = 0;
1382       while ((d = readdir64 (dirp)) != NULL)
1383 	{
1384 	  for (cnt = 0; cnt < __LC_LAST; ++cnt)
1385 	    if (cnt != LC_ALL)
1386 	      if (strcmp (d->d_name, locnames[cnt]) == 0)
1387 		{
1388 		  unsigned char d_type;
1389 
1390 		  /* We have an object of the required name.  If it's
1391 		     a directory we have to look at a file with the
1392 		     prefix "SYS_".  Otherwise we have found what we
1393 		     are looking for.  */
1394 		  d_type = d->d_type;
1395 
1396 		  if (d_type != DT_REG)
1397 		    {
1398 		      char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
1399 
1400 		      if (d_type == DT_UNKNOWN || d_type == DT_LNK)
1401 			{
1402 			  strcpy (stpcpy (stpcpy (fullname, fname), "/"),
1403 				  d->d_name);
1404 
1405 			  if (stat64 (fullname, &st) == -1)
1406 			    /* We cannot stat the file, ignore it.  */
1407 			    break;
1408 
1409 			  d_type = IFTODT (st.st_mode);
1410 			}
1411 
1412 		      if (d_type == DT_DIR)
1413 			{
1414 			  /* We have to do more tests.  The file is a
1415 			     directory and it therefore must contain a
1416 			     regular file with the same name except a
1417 			     "SYS_" prefix.  */
1418 			  char *t = stpcpy (stpcpy (fullname, fname), "/");
1419 			  strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
1420 				  d->d_name);
1421 
1422 			  if (stat64 (fullname, &st) == -1)
1423 			    /* There is no SYS_* file or we cannot
1424 			       access it.  */
1425 			    break;
1426 
1427 			  d_type = IFTODT (st.st_mode);
1428 			}
1429 		    }
1430 
1431 		  /* If we found a regular file (eventually after
1432 		     following a symlink) we are successful.  */
1433 		  if (d_type == DT_REG)
1434 		    ++seen;
1435 		  break;
1436 		}
1437 	}
1438 
1439       closedir (dirp);
1440 
1441       if (seen != __LC_LAST - 1)
1442 	{
1443 	  /* We don't have all locale category files.  Ignore the name.  */
1444 	  error (0, 0, _("incomplete set of locale files in \"%s\""),
1445 		 fname);
1446 	  continue;
1447 	}
1448 
1449       /* Add the files to the archive.  To do this we first compute
1450 	 sizes and the MD5 sums of all the files.  */
1451       for (cnt = 0; cnt < __LC_LAST; ++cnt)
1452 	if (cnt != LC_ALL)
1453 	  {
1454 	    char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
1455 	    int fd;
1456 
1457 	    strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
1458 	    fd = open64 (fullname, O_RDONLY);
1459 	    if (fd == -1 || fstat64 (fd, &st) == -1)
1460 	      {
1461 		/* Cannot read the file.  */
1462 		if (fd != -1)
1463 		  close (fd);
1464 		break;
1465 	      }
1466 
1467 	    if (S_ISDIR (st.st_mode))
1468 	      {
1469 		char *t;
1470 		close (fd);
1471 		t = stpcpy (stpcpy (fullname, fname), "/");
1472 		strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
1473 			locnames[cnt]);
1474 
1475 		fd = open64 (fullname, O_RDONLY);
1476 		if (fd == -1 || fstat64 (fd, &st) == -1
1477 		    || !S_ISREG (st.st_mode))
1478 		  {
1479 		    if (fd != -1)
1480 		      close (fd);
1481 		    break;
1482 		  }
1483 	      }
1484 
1485 	    /* Map the file.  */
1486 	    data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
1487 				     fd, 0);
1488 	    if (data[cnt].addr == MAP_FAILED)
1489 	      {
1490 		/* Cannot map it.  */
1491 		close (fd);
1492 		break;
1493 	      }
1494 
1495 	    data[cnt].size = st.st_size;
1496 	    __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
1497 
1498 	    /* We don't need the file descriptor anymore.  */
1499 	    close (fd);
1500 	  }
1501 
1502       if (cnt != __LC_LAST)
1503 	{
1504 	  while (cnt-- > 0)
1505 	    if (cnt != LC_ALL)
1506 	      munmap (data[cnt].addr, data[cnt].size);
1507 
1508 	  error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);
1509 
1510 	  continue;
1511 	}
1512 
1513       result |= add_locale_to_archive (&ah, basename (fname), data, replace);
1514 
1515       for (cnt = 0; cnt < __LC_LAST; ++cnt)
1516 	if (cnt != LC_ALL)
1517 	  munmap (data[cnt].addr, data[cnt].size);
1518     }
1519 
1520   /* We are done.  */
1521   close_archive (&ah);
1522 
1523   return result;
1524 }
1525 
1526 
1527 int
delete_locales_from_archive(size_t nlist,char * list[])1528 delete_locales_from_archive (size_t nlist, char *list[])
1529 {
1530   struct locarhandle ah;
1531   struct locarhead *head;
1532   struct namehashent *namehashtab;
1533 
1534   /* Open the archive.  This call never returns if we cannot
1535      successfully open the archive.  */
1536   ah.fname = NULL;
1537   open_archive (&ah, false);
1538 
1539   head = ah.addr;
1540   namehashtab = (struct namehashent *) ((char *) ah.addr
1541 					+ GET (head->namehash_offset));
1542 
1543   while (nlist-- > 0)
1544     {
1545       const char *locname = *list++;
1546       uint32_t hval;
1547       unsigned int idx;
1548       unsigned int incr;
1549 
1550       /* Search for this locale in the archive.  */
1551       hval = archive_hashval (locname, strlen (locname));
1552 
1553       idx = hval % GET (head->namehash_size);
1554       incr = 1 + hval % (GET (head->namehash_size) - 2);
1555 
1556       /* If the name_offset field is zero this means this is no
1557 	 deleted entry and therefore no entry can be found.  */
1558       while (GET (namehashtab[idx].name_offset) != 0)
1559 	{
1560 	  if (GET (namehashtab[idx].hashval) == hval
1561 	      && (strcmp (locname,
1562 			  ((char *) ah.addr
1563 			   + GET (namehashtab[idx].name_offset)))
1564 		  == 0))
1565 	    {
1566 	      /* Found the entry.  Now mark it as removed by zero-ing
1567 		 the reference to the locale record.  */
1568 	      SET (namehashtab[idx].locrec_offset, 0);
1569 	      break;
1570 	    }
1571 
1572 	  idx += incr;
1573 	  if (idx >= GET (head->namehash_size))
1574 	    idx -= GET (head->namehash_size);
1575 	}
1576 
1577       if (GET (namehashtab[idx].name_offset) == 0 && ! be_quiet)
1578 	error (0, 0, _("locale \"%s\" not in archive"), locname);
1579     }
1580 
1581   close_archive (&ah);
1582 
1583   return 0;
1584 }
1585 
1586 
1587 struct nameent
1588 {
1589   char *name;
1590   uint32_t locrec_offset;
1591 };
1592 
1593 
1594 struct dataent
1595 {
1596   const unsigned char *sum;
1597   uint32_t file_offset;
1598   uint32_t nlink;
1599 };
1600 
1601 
1602 static int
nameentcmp(const void * a,const void * b)1603 nameentcmp (const void *a, const void *b)
1604 {
1605   return strcmp (((const struct nameent *) a)->name,
1606 		 ((const struct nameent *) b)->name);
1607 }
1608 
1609 
1610 static int
dataentcmp(const void * a,const void * b)1611 dataentcmp (const void *a, const void *b)
1612 {
1613   if (((const struct dataent *) a)->file_offset
1614       < ((const struct dataent *) b)->file_offset)
1615     return -1;
1616 
1617   if (((const struct dataent *) a)->file_offset
1618       > ((const struct dataent *) b)->file_offset)
1619     return 1;
1620 
1621   return 0;
1622 }
1623 
1624 
1625 void
show_archive_content(const char * fname,int verbose)1626 show_archive_content (const char *fname, int verbose)
1627 {
1628   struct locarhandle ah;
1629   struct locarhead *head;
1630   struct namehashent *namehashtab;
1631   struct nameent *names;
1632   size_t cnt, used;
1633 
1634   /* Open the archive.  This call never returns if we cannot
1635      successfully open the archive.  */
1636   ah.fname = fname;
1637   open_archive (&ah, true);
1638 
1639   head = ah.addr;
1640 
1641   names = (struct nameent *) xmalloc (GET (head->namehash_used)
1642 				      * sizeof (struct nameent));
1643 
1644   namehashtab = (struct namehashent *) ((char *) ah.addr
1645 					+ GET (head->namehash_offset));
1646   for (cnt = used = 0; cnt < GET (head->namehash_size); ++cnt)
1647     if (GET (namehashtab[cnt].locrec_offset) != 0)
1648       {
1649 	assert (used < GET (head->namehash_used));
1650 	names[used].name = ah.addr + GET (namehashtab[cnt].name_offset);
1651 	names[used++].locrec_offset = GET (namehashtab[cnt].locrec_offset);
1652       }
1653 
1654   /* Sort the names.  */
1655   qsort (names, used, sizeof (struct nameent), nameentcmp);
1656 
1657   if (verbose)
1658     {
1659       struct dataent *files;
1660       struct sumhashent *sumhashtab;
1661       int sumused;
1662 
1663       files = (struct dataent *) xmalloc (GET (head->sumhash_used)
1664 					  * sizeof (struct dataent));
1665 
1666       sumhashtab = (struct sumhashent *) ((char *) ah.addr
1667 					  + GET (head->sumhash_offset));
1668       for (cnt = sumused = 0; cnt < GET (head->sumhash_size); ++cnt)
1669 	if (GET (sumhashtab[cnt].file_offset) != 0)
1670 	  {
1671 	    assert (sumused < GET (head->sumhash_used));
1672 	    files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
1673 	    files[sumused].file_offset = GET (sumhashtab[cnt].file_offset);
1674 	    files[sumused++].nlink = 0;
1675 	  }
1676 
1677       /* Sort by file locations.  */
1678       qsort (files, sumused, sizeof (struct dataent), dataentcmp);
1679 
1680       /* Compute nlink fields.  */
1681       for (cnt = 0; cnt < used; ++cnt)
1682 	{
1683 	  struct locrecent *locrec;
1684 	  int idx;
1685 
1686 	  locrec = (struct locrecent *) ((char *) ah.addr
1687 					 + names[cnt].locrec_offset);
1688 	  for (idx = 0; idx < __LC_LAST; ++idx)
1689 	    if (GET (locrec->record[LC_ALL].offset) != 0
1690 		? (idx == LC_ALL
1691 		   || (GET (locrec->record[idx].offset)
1692 		       < GET (locrec->record[LC_ALL].offset))
1693 		   || ((GET (locrec->record[idx].offset)
1694 			+ GET (locrec->record[idx].len))
1695 		       > (GET (locrec->record[LC_ALL].offset)
1696 			  + GET (locrec->record[LC_ALL].len))))
1697 		: idx != LC_ALL)
1698 	      {
1699 		struct dataent *data, dataent;
1700 
1701 		dataent.file_offset = GET (locrec->record[idx].offset);
1702 		data = (struct dataent *) bsearch (&dataent, files, sumused,
1703 						   sizeof (struct dataent),
1704 						   dataentcmp);
1705 		assert (data != NULL);
1706 		++data->nlink;
1707 	      }
1708 	}
1709 
1710       /* Print it.  */
1711       for (cnt = 0; cnt < used; ++cnt)
1712 	{
1713 	  struct locrecent *locrec;
1714 	  int idx, i;
1715 
1716 	  locrec = (struct locrecent *) ((char *) ah.addr
1717 					 + names[cnt].locrec_offset);
1718 	  for (idx = 0; idx < __LC_LAST; ++idx)
1719 	    if (idx != LC_ALL)
1720 	      {
1721 		struct dataent *data, dataent;
1722 
1723 		dataent.file_offset = GET (locrec->record[idx].offset);
1724 		if (GET (locrec->record[LC_ALL].offset) != 0
1725 		    && (dataent.file_offset
1726 			>= GET (locrec->record[LC_ALL].offset))
1727 		    && (dataent.file_offset + GET (locrec->record[idx].len)
1728 			<= (GET (locrec->record[LC_ALL].offset)
1729 			    + GET (locrec->record[LC_ALL].len))))
1730 		  dataent.file_offset = GET (locrec->record[LC_ALL].offset);
1731 
1732 		data = (struct dataent *) bsearch (&dataent, files, sumused,
1733 						   sizeof (struct dataent),
1734 						   dataentcmp);
1735 		printf ("%6d %7x %3d%c ",
1736 			GET (locrec->record[idx].len),
1737 			GET (locrec->record[idx].offset),
1738 			data->nlink,
1739 			(dataent.file_offset
1740 			 == GET (locrec->record[LC_ALL].offset))
1741 			? '+' : ' ');
1742 		for (i = 0; i < 16; i += 4)
1743 		    printf ("%02x%02x%02x%02x",
1744 			    data->sum[i], data->sum[i + 1],
1745 			    data->sum[i + 2], data->sum[i + 3]);
1746 		printf (" %s/%s\n", names[cnt].name,
1747 			idx == LC_MESSAGES ? "LC_MESSAGES/SYS_LC_MESSAGES"
1748 			: locnames[idx]);
1749 	      }
1750 	}
1751       free (files);
1752     }
1753   else
1754     for (cnt = 0; cnt < used; ++cnt)
1755       puts (names[cnt].name);
1756 
1757   close_archive (&ah);
1758 
1759   exit (EXIT_SUCCESS);
1760 }
1761