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