(open_archive): One more adjustment for the new readonly parameter.
[kopensolaris-gnu/glibc.git] / locale / programs / locarchive.c
1 /* Copyright (C) 2002 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    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    The GNU C Library 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 GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, write to the Free
17    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18    02111-1307 USA.  */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include <assert.h>
25 #include <dirent.h>
26 #include <errno.h>
27 #include <error.h>
28 #include <fcntl.h>
29 #include <inttypes.h>
30 #include <libintl.h>
31 #include <locale.h>
32 #include <stdbool.h>
33 #include <stdio.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 "simple-hash.h"
46 #include "localedef.h"
47
48 extern const char *output_prefix;
49
50 #define ARCHIVE_NAME LOCALEDIR "/locale-archive"
51
52 static const char *locnames[] =
53   {
54 #define DEFINE_CATEGORY(category, category_name, items, a) \
55   [category] = category_name,
56 #include "categories.def"
57 #undef  DEFINE_CATEGORY
58   };
59
60
61 /* Size of the initial archive header.  */
62 #define INITIAL_NUM_NANES       450
63 #define INITIAL_SIZE_STRINGS    3500
64 #define INITIAL_NUM_LOCREC      350
65 #define INITIAL_NUM_SUMS        2000
66
67
68 static void
69 create_archive (const char *archivefname, struct locarhandle *ah)
70 {
71   int fd;
72   char fname[strlen (archivefname) + sizeof (".XXXXXX")];
73   struct locarhead head;
74   void *p;
75   size_t total;
76
77   strcpy (stpcpy (fname, archivefname), ".XXXXXX");
78
79   /* Create a temporary file in the correct directory.  */
80   fd = mkstemp (fname);
81   if (fd == -1)
82     error (EXIT_FAILURE, errno, _("cannot create temporary file"));
83
84   /* Create the initial content of the archive.  */
85   head.magic = AR_MAGIC;
86   head.namehash_offset = sizeof (struct locarhead);
87   head.namehash_used = 0;
88   head.namehash_size = next_prime (INITIAL_NUM_NANES);
89
90   head.string_offset = (head.namehash_offset
91                         + head.namehash_size * sizeof (struct namehashent));
92   head.string_used = 0;
93   head.string_size = INITIAL_SIZE_STRINGS;
94
95   head.locrectab_offset = head.string_offset + head.string_size;
96   head.locrectab_used = 0;
97   head.locrectab_size = INITIAL_NUM_LOCREC;
98
99   head.sumhash_offset = (head.locrectab_offset
100                          + head.locrectab_size * sizeof (struct locrecent));
101   head.sumhash_used = 0;
102   head.sumhash_size = next_prime (INITIAL_NUM_SUMS);
103
104   total = head.sumhash_offset + head.sumhash_size * sizeof (struct sumhashent);
105
106   /* Write out the header and create room for the other data structures.  */
107   if (TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head))) != sizeof (head))
108     {
109       int errval = errno;
110       unlink (fname);
111       error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
112     }
113
114   if (ftruncate64 (fd, total) != 0)
115     {
116       int errval = errno;
117       unlink (fname);
118       error (EXIT_FAILURE, errval, _("cannot resize archive file"));
119     }
120
121   /* Map the header and all the administration data structures.  */
122   p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
123   if (p == MAP_FAILED)
124     {
125       int errval = errno;
126       unlink (fname);
127       error (EXIT_FAILURE, errval, _("cannot map archive header"));
128     }
129
130   /* Now try to rename it.  We don't use the rename function since
131      this would overwrite a file which has been created in
132      parallel.  */
133   if (link (fname, archivefname) == -1)
134     {
135       int errval = errno;
136
137       /* We cannot use the just created file.  */
138       close (fd);
139       unlink (fname);
140
141       if (errval == EEXIST)
142         {
143           /* There is already an archive.  Must have been a localedef run
144              which happened in parallel.  Simply open this file then.  */
145           open_archive (ah, false);
146           return;
147         }
148
149       error (EXIT_FAILURE, errval, _("failed to create new locale archive"));
150     }
151
152   /* Remove the temporary name.  */
153   unlink (fname);
154
155   /* Make the file globally readable.  */
156   if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
157     {
158       int errval = errno;
159       unlink (archivefname);
160       error (EXIT_FAILURE, errval,
161              _("cannot change mode of new locale archive"));
162     }
163
164   ah->fd = fd;
165   ah->addr = p;
166   ah->len = total;
167 }
168
169
170 static void
171 enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
172 {
173   struct stat64 st;
174   int fd;
175   struct locarhead newhead;
176   size_t total;
177   void *p;
178   unsigned int cnt;
179   struct namehashent *oldnamehashtab;
180   struct locrecent *oldlocrectab;
181   struct locarhandle new_ah;
182   size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
183   char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
184   char fname[prefix_len + sizeof (ARCHIVE_NAME) + sizeof (".XXXXXX") - 1];
185
186   if (output_prefix)
187     memcpy (archivefname, output_prefix, prefix_len);
188   strcpy (archivefname + prefix_len, ARCHIVE_NAME);
189   strcpy (stpcpy (fname, archivefname), ".XXXXXX");
190
191   /* Not all of the old file has to be mapped.  Change this now this
192      we will have to access the whole content.  */
193   if (fstat64 (ah->fd, &st) != 0
194       || (ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE,
195                              MAP_SHARED, ah->fd, 0)) == MAP_FAILED)
196     error (EXIT_FAILURE, errno, _("cannot map locale archive file"));
197   ah->len = st.st_size;
198
199   /* Create a temporary file in the correct directory.  */
200   fd = mkstemp (fname);
201   if (fd == -1)
202     error (EXIT_FAILURE, errno, _("cannot create temporary file"));
203
204   /* Copy the existing head information.  */
205   newhead = *head;
206
207   /* Create the new archive header.  The sizes of the various tables
208      should be double from what is currently used.  */
209   newhead.namehash_size = MAX (next_prime (2 * newhead.namehash_used),
210                                newhead.namehash_size);
211   printf ("name: size: %u, used: %d, new: size: %u\n",
212           head->namehash_size, head->namehash_used, newhead.namehash_size);
213
214   newhead.string_offset = (newhead.namehash_offset
215                            + (newhead.namehash_size
216                               * sizeof (struct namehashent)));
217   newhead.string_size = MAX (2 * newhead.string_used, newhead.string_size);
218
219   newhead.locrectab_offset = newhead.string_offset + newhead.string_size;
220   newhead.locrectab_size = MAX (2 * newhead.locrectab_used,
221                                 newhead.locrectab_size);
222
223   newhead.sumhash_offset = (newhead.locrectab_offset
224                             + (newhead.locrectab_size
225                                * sizeof (struct locrecent)));
226   newhead.sumhash_size = MAX (next_prime (2 * newhead.sumhash_used),
227                               newhead.sumhash_size);
228
229   total = (newhead.sumhash_offset
230            + newhead.sumhash_size * sizeof (struct sumhashent));
231
232   /* The new file is empty now.  */
233   newhead.namehash_used = 0;
234   newhead.string_used = 0;
235   newhead.locrectab_used = 0;
236   newhead.sumhash_used = 0;
237
238   /* Write out the header and create room for the other data structures.  */
239   if (TEMP_FAILURE_RETRY (write (fd, &newhead, sizeof (newhead)))
240       != sizeof (newhead))
241     {
242       int errval = errno;
243       unlink (fname);
244       error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
245     }
246
247   if (ftruncate64 (fd, total) != 0)
248     {
249       int errval = errno;
250       unlink (fname);
251       error (EXIT_FAILURE, errval, _("cannot resize archive file"));
252     }
253
254   /* Map the header and all the administration data structures.  */
255   p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
256   if (p == MAP_FAILED)
257     {
258       int errval = errno;
259       unlink (fname);
260       error (EXIT_FAILURE, errval, _("cannot map archive header"));
261     }
262
263   /* Lock the new file.  */
264   if (lockf64 (fd, F_LOCK, total) != 0)
265     {
266       int errval = errno;
267       unlink (fname);
268       error (EXIT_FAILURE, errval, _("cannot lock new archive"));
269     }
270
271   new_ah.len = total;
272   new_ah.addr = p;
273   new_ah.fd = fd;
274
275   /* Walk through the hash name hash table to find out what data is
276      still referenced and transfer it into the new file.  */
277   oldnamehashtab = (struct namehashent *) ((char *) ah->addr
278                                            + head->namehash_offset);
279   oldlocrectab = (struct locrecent *) ((char *) ah->addr
280                                        + head->locrectab_offset);
281   for (cnt = 0; cnt < head->namehash_size; ++cnt)
282     if (oldnamehashtab[cnt].locrec_offset != 0)
283       {
284         /* Insert this entry in the new hash table.  */
285         locale_data_t old_data;
286         unsigned int idx;
287         struct locrecent *oldlocrec;
288
289         oldlocrec = (struct locrecent *) ((char *) ah->addr
290                                           + oldnamehashtab[cnt].locrec_offset);
291
292         for (idx = 0; idx < __LC_LAST; ++idx)
293           if (idx != LC_ALL)
294             {
295               old_data[idx].size = oldlocrec->record[idx].len;
296               old_data[idx].addr
297                 = ((char *) ah->addr + oldlocrec->record[idx].offset);
298
299               __md5_buffer (old_data[idx].addr, old_data[idx].size,
300                             old_data[idx].sum);
301             }
302
303         if (add_locale_to_archive (&new_ah,
304                                    ((char *) ah->addr
305                                     + oldnamehashtab[cnt].name_offset),
306                                    old_data, 0) != 0)
307           error (EXIT_FAILURE, 0, _("cannot extend locale archive file"));
308       }
309
310
311   /* Make the file globally readable.  */
312   if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
313     {
314       int errval = errno;
315       unlink (fname);
316       error (EXIT_FAILURE, errval,
317              _("cannot change mode of resized locale archive"));
318     }
319
320   /* Rename the new file.  */
321   if (rename (fname, archivefname) != 0)
322     {
323       int errval = errno;
324       unlink (fname);
325       error (EXIT_FAILURE, errval, _("cannot rename new archive"));
326     }
327
328   /* Close the old file.  */
329   close_archive (ah);
330
331   /* Add the information for the new one.  */
332   *ah = new_ah;
333 }
334
335
336 void
337 open_archive (struct locarhandle *ah, bool readonly)
338 {
339   struct stat64 st;
340   struct stat64 st2;
341   int fd;
342   struct locarhead head;
343   int retry = 0;
344   size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
345   char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
346
347   if (output_prefix)
348     memcpy (archivefname, output_prefix, prefix_len);
349   strcpy (archivefname + prefix_len, ARCHIVE_NAME);
350
351  again:
352   /* Open the archive.  We must have exclusive write access.  */
353   fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
354   if (fd == -1)
355     {
356       /* Maybe the file does not yet exist.  */
357       if (errno == ENOENT)
358         {
359           if (readonly)
360             {
361               static const struct locarhead nullhead =
362                 {
363                   .namehash_used = 0,
364                   .namehash_offset = 0,
365                   .namehash_size = 0
366                 };
367
368               ah->addr = (void *) &nullhead;
369               ah->fd = -1;
370             }
371           else
372             create_archive (archivefname, ah);
373
374           return;
375         }
376       else
377         error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
378                archivefname);
379     }
380
381   if (fstat64 (fd, &st) < 0)
382     error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""),
383            archivefname);
384
385   if (lockf64 (fd, F_LOCK, st.st_size) == -1)
386     {
387       close (fd);
388
389       if (retry++ < max_locarchive_open_retry)
390         {
391           struct timespec req;
392
393           /* Wait for a bit.  */
394           req.tv_sec = 0;
395           req.tv_nsec = 1000000 * (random () % 500 + 1);
396           (void) nanosleep (&req, NULL);
397
398           goto again;
399         }
400
401       error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
402              archivefname);
403     }
404
405   /* One more check.  Maybe another process replaced the archive file
406      with a new, larger one since we opened the file.  */
407   if (stat64 (archivefname, &st2) == -1
408       || st.st_dev != st2.st_dev
409       || st.st_ino != st2.st_ino)
410     {
411       close (fd);
412       goto again;
413     }
414
415   /* Read the header.  */
416   if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
417     error (EXIT_FAILURE, errno, _("cannot read archive header"));
418
419   ah->fd = fd;
420   ah->len = (head.sumhash_offset
421              + head.sumhash_size * sizeof (struct sumhashent));
422
423   /* Now we know how large the administrative information part is.
424      Map all of it.  */
425   ah->addr = mmap64 (NULL, ah->len, PROT_READ | (readonly ? 0 : PROT_WRITE),
426                      MAP_SHARED, fd, 0);
427   if (ah->addr == MAP_FAILED)
428     error (EXIT_FAILURE, errno, _("cannot map archive header"));
429 }
430
431
432 void
433 close_archive (struct locarhandle *ah)
434 {
435   if (ah->fd != -1)
436     {
437       munmap (ah->addr, ah->len);
438       close (ah->fd);
439     }
440 }
441
442
443 /* Check the content of the archive for duplicates.  Add the content
444    of the files if necessary.  Add all the names, possibly overwriting
445    old files.  */
446 int
447 add_locale_to_archive (ah, name, data, replace)
448      struct locarhandle *ah;
449      const char *name;
450      locale_data_t data;
451      bool replace;
452 {
453   /* First look for the name.  If it already exists and we are not
454      supposed to replace it don't do anything.  If it does not exist
455      we have to allocate a new locale record.  */
456   size_t name_len = strlen (name);
457   uint32_t file_offsets[__LC_LAST];
458   unsigned int num_new_offsets = 0;
459   struct sumhashent *sumhashtab;
460   uint32_t hval;
461   unsigned int cnt;
462   unsigned int idx;
463   unsigned int insert_idx;
464   struct locarhead *head;
465   struct namehashent *namehashtab;
466   struct namehashent *namehashent;
467   unsigned int incr;
468   struct locrecent *locrecent;
469
470   head = ah->addr;
471   sumhashtab = (struct sumhashent *) ((char *) ah->addr
472                                       + head->sumhash_offset);
473   namehashtab = (struct namehashent *) ((char *) ah->addr
474                                         + head->namehash_offset);
475
476
477   /* For each locale category data set determine whether the same data
478      is already somewhere in the archive.  */
479   for (cnt = 0; cnt < __LC_LAST; ++cnt)
480     if (cnt != LC_ALL)
481       {
482         /* By default signal that we have no data.  */
483         file_offsets[cnt] = 0;
484         ++num_new_offsets;
485
486         /* Compute the hash value of the checksum to determine a
487            starting point for the search in the MD5 hash value
488            table.  */
489         hval = compute_hashval (data[cnt].sum, 16);
490
491         idx = hval % head->sumhash_size;
492         incr = 1 + hval % (head->sumhash_size - 2);
493
494         while (sumhashtab[idx].file_offset != 0)
495           {
496             if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
497               {
498                 /* Found it.  */
499                 file_offsets[cnt] = sumhashtab[idx].file_offset;
500                 --num_new_offsets;
501                 break;
502               }
503
504             idx += incr;
505             if (idx >= head->sumhash_size)
506               idx -= head->sumhash_size;
507           }
508       }
509
510
511   /* Hash value of the locale name.  */
512   hval = compute_hashval (name, name_len);
513
514   insert_idx = -1;
515   idx = hval % head->namehash_size;
516   incr = 1 + hval % (head->namehash_size - 2);
517
518   /* If the name_offset field is zero this means this is no
519      deleted entry and therefore no entry can be found.  */
520   while (namehashtab[idx].name_offset != 0)
521     {
522       if (namehashtab[idx].hashval == hval
523           && strcmp (name,
524                      (char *) ah->addr + namehashtab[idx].name_offset) == 0)
525         {
526           /* Found the entry.  */
527           if (! replace)
528             {
529               if (! be_quiet)
530                 error (0, 0, _("locale '%s' already exists"), name);
531               return 1;
532             }
533
534           break;
535         }
536
537       /* Remember the first place we can insert the new entry.  */
538       if (namehashtab[idx].locrec_offset == 0 && insert_idx == -1)
539         insert_idx = idx;
540
541       idx += incr;
542       if (idx >= head->namehash_size)
543         idx -= head->namehash_size;
544     }
545
546   /* Add as early as possible.  */
547   if (insert_idx != -1)
548     idx = insert_idx;
549
550   namehashent = &namehashtab[idx];
551
552   /* Determine whether we have to resize the file.  */
553   if (100 * (head->sumhash_used + num_new_offsets) > 75 * head->sumhash_size
554       || (namehashent->locrec_offset == 0
555           && (head->locrectab_used == head->locrectab_size
556               || head->string_used + name_len + 1 > head->string_size
557               || 100 * head->namehash_used > 75 * head->namehash_size)))
558     {
559       /* The current archive is not large enough.  */
560       enlarge_archive (ah, head);
561       return add_locale_to_archive (ah, name, data, replace);
562     }
563
564   /* Add the locale data which is not yet in the archive.  */
565   for (cnt = 0; cnt < __LC_LAST; ++cnt)
566     if (cnt != LC_ALL && file_offsets[cnt] == 0)
567       {
568         /* The data for this section is not yet available in the
569            archive.  Append it.  */
570         off64_t lastpos;
571         uint32_t md5hval;
572
573         lastpos = lseek64 (ah->fd, 0, SEEK_END);
574         if (lastpos == (off64_t) -1)
575           error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
576
577         /* Align all data to a 16 byte boundary.  */
578         if ((lastpos & 15) != 0)
579           {
580             static const char zeros[15] = { 0, };
581
582             if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, 16 - (lastpos & 15)))
583                 != 16 - (lastpos & 15))
584               error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
585
586             lastpos += 16 - (lastpos & 15);
587           }
588
589         /* Remember the position.  */
590         file_offsets[cnt] = lastpos;
591
592         /* Write the data.  */
593         if (TEMP_FAILURE_RETRY (write (ah->fd, data[cnt].addr, data[cnt].size))
594             != data[cnt].size)
595           error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
596
597         /* Add the hash value to the hash table.  */
598         md5hval = compute_hashval (data[cnt].sum, 16);
599
600         idx = md5hval % head->sumhash_size;
601         incr = 1 + md5hval % (head->sumhash_size - 2);
602
603         while (sumhashtab[idx].file_offset != 0)
604           {
605             idx += incr;
606             if (idx >= head->sumhash_size)
607               idx -= head->sumhash_size;
608           }
609
610         memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
611         sumhashtab[idx].file_offset = file_offsets[cnt];
612
613         ++head->sumhash_used;
614       }
615
616
617   if (namehashent->locrec_offset == 0)
618     {
619       /* Add the name string.  */
620       memcpy ((char *) ah->addr + head->string_offset + head->string_used,
621               name, name_len + 1);
622       namehashent->name_offset = head->string_offset + head->string_used;
623       head->string_used += name_len + 1;
624
625       /* Allocate a name location record.  */
626       namehashent->locrec_offset = (head->locrectab_offset
627                                     + (head->locrectab_used++
628                                        * sizeof (struct locrecent)));
629
630       namehashent->hashval = hval;
631
632       ++head->namehash_used;
633     }
634
635
636   /* Fill in the table with the locations of the locale data.  */
637   locrecent = (struct locrecent *) ((char *) ah->addr
638                                     + namehashent->locrec_offset);
639   for (cnt = 0; cnt < __LC_LAST; ++cnt)
640     if (cnt != LC_ALL)
641       {
642         locrecent->record[cnt].offset = file_offsets[cnt];
643         locrecent->record[cnt].len = data[cnt].size;
644       }
645
646
647   /* Read the locale.alias file to see whether any matching record is
648      found.  If an entry is available check whether it is already in
649      the archive.  If this is the case check whether the new locale's
650      name is more specific than the one currently referred to by the
651      alias.  */
652
653
654   return 0;
655 }
656
657
658 int
659 add_locales_to_archive (nlist, list, replace)
660      size_t nlist;
661      char *list[];
662      bool replace;
663 {
664   struct locarhandle ah;
665   int result = 0;
666
667   /* Open the archive.  This call never returns if we cannot
668      successfully open the archive.  */
669   open_archive (&ah, false);
670
671   while (nlist-- > 0)
672     {
673       const char *fname = *list++;
674       size_t fnamelen = strlen (fname);
675       struct stat64 st;
676       DIR *dirp;
677       struct dirent64 *d;
678       int seen;
679       locale_data_t data;
680       int cnt;
681
682       if (! be_quiet)
683         printf (_("Adding %s\n"), fname);
684
685       /* First see whether this really is a directory and whether it
686          contains all the require locale category files.  */
687       if (stat64 (fname, &st) < 0)
688         {
689           error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
690                  strerror (errno));
691           continue;
692         }
693       if (!S_ISDIR (st.st_mode))
694         {
695           error (0, 0, _("\"%s\" is no directory; ignored"), fname);
696           continue;
697         }
698
699       dirp = opendir (fname);
700       if (dirp == NULL)
701         {
702           error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
703                  fname, strerror (errno));
704           continue;
705         }
706
707       seen = 0;
708       while ((d = readdir64 (dirp)) != NULL)
709         {
710           for (cnt = 0; cnt < __LC_LAST; ++cnt)
711             if (cnt != LC_ALL)
712               if (strcmp (d->d_name, locnames[cnt]) == 0)
713                 {
714                   unsigned char d_type;
715
716                   /* We have an object of the required name.  If it's
717                      a directory we have to look at a file with the
718                      prefix "SYS_".  Otherwise we have found what we
719                      are looking for.  */
720 #ifdef _DIRENT_HAVE_D_TYPE
721                   d_type = d->d_type;
722
723                   if (d_type != DT_REG)
724 #endif
725                     {
726                       char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
727
728 #ifdef _DIRENT_HAVE_D_TYPE
729                       if (d_type == DT_UNKNOWN)
730 #endif
731                         {
732                           strcpy (stpcpy (stpcpy (fullname, fname), "/"),
733                                   d->d_name);
734
735                           if (stat64 (fullname, &st) == -1)
736                             /* We cannot stat the file, ignore it.  */
737                             break;
738
739                           d_type = IFTODT (st.st_mode);
740                         }
741
742                       if (d_type == DT_DIR)
743                         {
744                           /* We have to do more tests.  The file is a
745                              directory and it therefore must contain a
746                              regular file with the same name except a
747                              "SYS_" prefix.  */
748                           char *t = stpcpy (stpcpy (fullname, fname), "/");
749                           strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
750                                   d->d_name);
751
752                           if (stat64 (fullname, &st) == -1)
753                             /* There is no SYS_* file or we cannot
754                                access it.  */
755                             break;
756
757                           d_type = IFTODT (st.st_mode);
758                         }
759                     }
760
761                   /* If we found a regular file (eventually after
762                      following a symlink) we are successful.  */
763                   if (d_type == DT_REG)
764                     ++seen;
765                   break;
766                 }
767         }
768
769       closedir (dirp);
770
771       if (seen != __LC_LAST - 1)
772         {
773           /* We don't have all locale category files.  Ignore the name.  */
774           error (0, 0, _("incomplete set of locale files in \"%s\""),
775                  fname);
776           continue;
777         }
778
779       /* Add the files to the archive.  To do this we first compute
780          sizes and the MD5 sums of all the files.  */
781       for (cnt = 0; cnt < __LC_LAST; ++cnt)
782         if (cnt != LC_ALL)
783           {
784             char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
785             int fd;
786
787             strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
788             fd = open64 (fullname, O_RDONLY);
789             if (fd == -1 || fstat64 (fd, &st) == -1)
790               {
791                 /* Cannot read the file.  */
792                 if (fd != -1)
793                   close (fd);
794                 break;
795               }
796
797             if (S_ISDIR (st.st_mode))
798               {
799                 char *t;
800                 close (fd);
801                 t = stpcpy (stpcpy (fullname, fname), "/");
802                 strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
803                         locnames[cnt]);
804
805                 fd = open64 (fullname, O_RDONLY);
806                 if (fd == -1 || fstat64 (fd, &st) == -1
807                     || !S_ISREG (st.st_mode))
808                   {
809                     if (fd != -1)
810                       close (fd);
811                     break;
812                   }
813               }
814
815             /* Map the file.  */
816             data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
817                                      fd, 0);
818             if (data[cnt].addr == MAP_FAILED)
819               {
820                 /* Cannot map it.  */
821                 close (fd);
822                 break;
823               }
824
825             data[cnt].size = st.st_size;
826             __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
827
828             /* We don't need the file descriptor anymore.  */
829             close (fd);
830           }
831
832       if (cnt != __LC_LAST)
833         {
834           while (cnt-- > 0)
835             if (cnt != LC_ALL)
836               munmap (data[cnt].addr, data[cnt].size);
837
838           error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);
839
840           continue;
841         }
842
843       result |= add_locale_to_archive (&ah, basename (fname), data, replace);
844
845       for (cnt = 0; cnt < __LC_LAST; ++cnt)
846         if (cnt != LC_ALL)
847           munmap (data[cnt].addr, data[cnt].size);
848     }
849
850   /* We are done.  */
851   close_archive (&ah);
852
853   return result;
854 }
855
856
857 int
858 delete_locales_from_archive (nlist, list)
859      size_t nlist;
860      char *list[];
861 {
862   struct locarhandle ah;
863   struct locarhead *head;
864   struct namehashent *namehashtab;
865
866   /* Open the archive.  This call never returns if we cannot
867      successfully open the archive.  */
868   open_archive (&ah, false);
869
870   head = ah.addr;
871   namehashtab = (struct namehashent *) ((char *) ah.addr
872                                         + head->namehash_offset);
873
874   while (nlist-- > 0)
875     {
876       const char *locname = *list++;
877       uint32_t hval;
878       unsigned int idx;
879       unsigned int incr;
880
881       /* Search for this locale in the archive.  */
882       hval = compute_hashval (locname, strlen (locname));
883
884       idx = hval % head->namehash_size;
885       incr = 1 + hval % (head->namehash_size - 2);
886
887       /* If the name_offset field is zero this means this is no
888          deleted entry and therefore no entry can be found.  */
889       while (namehashtab[idx].name_offset != 0)
890         {
891           if (namehashtab[idx].hashval == hval
892               && (strcmp (locname,
893                           (char *) ah.addr + namehashtab[idx].name_offset)
894                   == 0))
895             {
896               /* Found the entry.  Now mark it as removed by zero-ing
897                  the reference to the locale record.  */
898               namehashtab[idx].locrec_offset = 0;
899               --head->namehash_used;
900               break;
901             }
902
903           idx += incr;
904           if (idx >= head->namehash_size)
905             idx -= head->namehash_size;
906         }
907
908       if (namehashtab[idx].name_offset == 0 && ! be_quiet)
909         error (0, 0, _("locale \"%s\" not in archive"), locname);
910     }
911
912   close_archive (&ah);
913
914   return 0;
915 }
916
917
918 static int
919 xstrcmp (const void *a, const void *b)
920 {
921   return strcmp (*(const char **) a, *(const char **) b);
922 }
923
924
925 void
926 show_archive_content (void)
927 {
928   struct locarhandle ah;
929   struct locarhead *head;
930   struct namehashent *namehashtab;
931   int cnt;
932   char **names;
933   int used;
934
935   /* Open the archive.  This call never returns if we cannot
936      successfully open the archive.  */
937   open_archive (&ah, true);
938
939   head = ah.addr;
940
941   names = (char **) xmalloc (head->namehash_used * sizeof (char *));
942
943   namehashtab = (struct namehashent *) ((char *) ah.addr
944                                         + head->namehash_offset);
945   for (cnt = used = 0; cnt < head->namehash_size; ++cnt)
946     if (namehashtab[cnt].locrec_offset != 0)
947       {
948         assert (used < head->namehash_used);
949         names[used++] = ah.addr + namehashtab[cnt].name_offset;
950       }
951
952   /* Sort the names.  */
953   qsort (names, used, sizeof (char *), xstrcmp);
954
955   for (cnt = 0; cnt < used; ++cnt)
956     puts (names[cnt]);
957
958   close_archive (&ah);
959
960   exit (EXIT_SUCCESS);
961 }