Fix missing libc-internal.h include.
[glibc.git] / locale / programs / locarchive.c
blobd8df39a8e6472eafe7822258d17844d7a3b93723
1 /* Copyright (C) 2002-2013 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
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.
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.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, see <http://www.gnu.org/licenses/>. */
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
22 #include <assert.h>
23 #include <dirent.h>
24 #include <errno.h>
25 #include <error.h>
26 #include <fcntl.h>
27 #include <inttypes.h>
28 #include <libintl.h>
29 #include <locale.h>
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <stdio_ext.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
37 #include <stdint.h>
38 #include <sys/mman.h>
39 #include <sys/param.h>
40 #include <sys/shm.h>
41 #include <sys/stat.h>
43 #include <libc-internal.h>
44 #include <libc-mmap.h>
45 #include "../../crypt/md5.h"
46 #include "../localeinfo.h"
47 #include "../locarchive.h"
48 #include "localedef.h"
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
57 extern const char *output_prefix;
59 #define ARCHIVE_NAME LOCALEDIR "/locale-archive"
61 static const char *locnames[] =
63 #define DEFINE_CATEGORY(category, category_name, items, a) \
64 [category] = category_name,
65 #include "categories.def"
66 #undef DEFINE_CATEGORY
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
77 /* Size of the reserved address space area. */
78 #define RESERVE_MMAP_SIZE 512 * 1024 * 1024
80 /* To prepare for enlargements of the mmaped area reserve some address
81 space. On some machines, being a file mapping rather than an anonymous
82 mapping affects the address selection. So do this mapping from the
83 actual file, even though it's only a dummy to reserve address space. */
84 static void *
85 prepare_address_space (int fd, size_t total, size_t *reserved, int *xflags,
86 void **mmap_base, size_t *mmap_len)
88 if (total < RESERVE_MMAP_SIZE)
90 void *p = mmap64 (NULL, RESERVE_MMAP_SIZE, PROT_NONE, MAP_SHARED, fd, 0);
91 if (p != MAP_FAILED)
93 void *aligned_p = PTR_ALIGN_UP (p, MAP_FIXED_ALIGNMENT);
94 size_t align_adjust = aligned_p - p;
95 *mmap_base = p;
96 *mmap_len = RESERVE_MMAP_SIZE;
97 assert (align_adjust < RESERVE_MMAP_SIZE);
98 *reserved = RESERVE_MMAP_SIZE - align_adjust;
99 *xflags = MAP_FIXED;
100 return aligned_p;
104 *reserved = total;
105 *xflags = 0;
106 *mmap_base = NULL;
107 *mmap_len = 0;
108 return NULL;
112 static void
113 create_archive (const char *archivefname, struct locarhandle *ah)
115 int fd;
116 char fname[strlen (archivefname) + sizeof (".XXXXXX")];
117 struct locarhead head;
118 size_t total;
120 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
122 /* Create a temporary file in the correct directory. */
123 fd = mkstemp (fname);
124 if (fd == -1)
125 error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname);
127 /* Create the initial content of the archive. */
128 head.magic = AR_MAGIC;
129 head.serial = 0;
130 head.namehash_offset = sizeof (struct locarhead);
131 head.namehash_used = 0;
132 head.namehash_size = next_prime (INITIAL_NUM_NAMES);
134 head.string_offset = (head.namehash_offset
135 + head.namehash_size * sizeof (struct namehashent));
136 head.string_used = 0;
137 head.string_size = INITIAL_SIZE_STRINGS;
139 head.locrectab_offset = head.string_offset + head.string_size;
140 head.locrectab_used = 0;
141 head.locrectab_size = INITIAL_NUM_LOCREC;
143 head.sumhash_offset = (head.locrectab_offset
144 + head.locrectab_size * sizeof (struct locrecent));
145 head.sumhash_used = 0;
146 head.sumhash_size = next_prime (INITIAL_NUM_SUMS);
148 total = head.sumhash_offset + head.sumhash_size * sizeof (struct sumhashent);
150 /* Write out the header and create room for the other data structures. */
151 if (TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head))) != sizeof (head))
153 int errval = errno;
154 unlink (fname);
155 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
158 if (ftruncate64 (fd, total) != 0)
160 int errval = errno;
161 unlink (fname);
162 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
165 size_t reserved, mmap_len;
166 int xflags;
167 void *mmap_base;
168 void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base,
169 &mmap_len);
171 /* Map the header and all the administration data structures. */
172 p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
173 if (p == MAP_FAILED)
175 int errval = errno;
176 unlink (fname);
177 error (EXIT_FAILURE, errval, _("cannot map archive header"));
180 /* Now try to rename it. We don't use the rename function since
181 this would overwrite a file which has been created in
182 parallel. */
183 if (link (fname, archivefname) == -1)
185 int errval = errno;
187 /* We cannot use the just created file. */
188 close (fd);
189 unlink (fname);
191 if (errval == EEXIST)
193 /* There is already an archive. Must have been a localedef run
194 which happened in parallel. Simply open this file then. */
195 open_archive (ah, false);
196 return;
199 error (EXIT_FAILURE, errval, _("failed to create new locale archive"));
202 /* Remove the temporary name. */
203 unlink (fname);
205 /* Make the file globally readable. */
206 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
208 int errval = errno;
209 unlink (archivefname);
210 error (EXIT_FAILURE, errval,
211 _("cannot change mode of new locale archive"));
214 ah->fd = fd;
215 ah->mmap_base = mmap_base;
216 ah->mmap_len = mmap_len;
217 ah->addr = p;
218 ah->mmaped = total;
219 ah->reserved = reserved;
223 /* This structure and qsort comparator function are used below to sort an
224 old archive's locrec table in order of data position in the file. */
225 struct oldlocrecent
227 unsigned int cnt;
228 struct locrecent *locrec;
231 static int
232 oldlocrecentcmp (const void *a, const void *b)
234 struct locrecent *la = ((const struct oldlocrecent *) a)->locrec;
235 struct locrecent *lb = ((const struct oldlocrecent *) b)->locrec;
236 uint32_t start_a = -1, end_a = 0;
237 uint32_t start_b = -1, end_b = 0;
238 int cnt;
240 for (cnt = 0; cnt < __LC_LAST; ++cnt)
241 if (cnt != LC_ALL)
243 if (la->record[cnt].offset < start_a)
244 start_a = la->record[cnt].offset;
245 if (la->record[cnt].offset + la->record[cnt].len > end_a)
246 end_a = la->record[cnt].offset + la->record[cnt].len;
248 assert (start_a != (uint32_t)-1);
249 assert (end_a != 0);
251 for (cnt = 0; cnt < __LC_LAST; ++cnt)
252 if (cnt != LC_ALL)
254 if (lb->record[cnt].offset < start_b)
255 start_b = lb->record[cnt].offset;
256 if (lb->record[cnt].offset + lb->record[cnt].len > end_b)
257 end_b = lb->record[cnt].offset + lb->record[cnt].len;
259 assert (start_b != (uint32_t)-1);
260 assert (end_b != 0);
262 if (start_a != start_b)
263 return (int)start_a - (int)start_b;
264 return (int)end_a - (int)end_b;
268 /* forward decls for below */
269 static uint32_t add_locale (struct locarhandle *ah, const char *name,
270 locale_data_t data, bool replace);
271 static void add_alias (struct locarhandle *ah, const char *alias,
272 bool replace, const char *oldname,
273 uint32_t *locrec_offset_p);
276 static bool
277 file_data_available_p (struct locarhandle *ah, uint32_t offset, uint32_t size)
279 if (offset < ah->mmaped && offset + size <= ah->mmaped)
280 return true;
282 struct stat64 st;
283 if (fstat64 (ah->fd, &st) != 0)
284 return false;
286 if (st.st_size > ah->reserved)
287 return false;
289 size_t start = ALIGN_DOWN (ah->mmaped, MAP_FIXED_ALIGNMENT);
290 void *p = mmap64 (ah->addr + start, st.st_size - start,
291 PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED,
292 ah->fd, start);
293 if (p == MAP_FAILED)
295 ah->mmaped = start;
296 return false;
299 ah->mmaped = st.st_size;
300 return true;
304 static int
305 compare_from_file (struct locarhandle *ah, void *p1, uint32_t offset2,
306 uint32_t size)
308 void *p2 = xmalloc (size);
309 if (pread (ah->fd, p2, size, offset2) != size)
310 WITH_CUR_LOCALE (error (4, errno,
311 _("cannot read data from locale archive")));
313 int res = memcmp (p1, p2, size);
314 free (p2);
315 return res;
319 static void
320 enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
322 struct stat64 st;
323 int fd;
324 struct locarhead newhead;
325 size_t total;
326 unsigned int cnt, loccnt;
327 struct namehashent *oldnamehashtab;
328 struct locarhandle new_ah;
329 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
330 char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
331 char fname[prefix_len + sizeof (ARCHIVE_NAME) + sizeof (".XXXXXX") - 1];
333 if (output_prefix)
334 memcpy (archivefname, output_prefix, prefix_len);
335 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
336 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
338 /* Not all of the old file has to be mapped. Change this now this
339 we will have to access the whole content. */
340 if (fstat64 (ah->fd, &st) != 0)
341 enomap:
342 error (EXIT_FAILURE, errno, _("cannot map locale archive file"));
344 if (st.st_size < ah->reserved)
345 ah->addr = mmap64 (ah->addr, st.st_size, PROT_READ | PROT_WRITE,
346 MAP_SHARED | MAP_FIXED, ah->fd, 0);
347 else
349 if (ah->mmap_base)
350 munmap (ah->mmap_base, ah->mmap_len);
351 else
352 munmap (ah->addr, ah->reserved);
353 ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE,
354 MAP_SHARED, ah->fd, 0);
355 ah->reserved = st.st_size;
356 ah->mmap_base = NULL;
357 ah->mmap_len = 0;
358 head = ah->addr;
360 if (ah->addr == MAP_FAILED)
361 goto enomap;
362 ah->mmaped = st.st_size;
364 /* Create a temporary file in the correct directory. */
365 fd = mkstemp (fname);
366 if (fd == -1)
367 error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname);
369 /* Copy the existing head information. */
370 newhead = *head;
372 /* Create the new archive header. The sizes of the various tables
373 should be double from what is currently used. */
374 newhead.namehash_size = MAX (next_prime (2 * newhead.namehash_used),
375 newhead.namehash_size);
376 if (verbose)
377 printf ("name: size: %u, used: %d, new: size: %u\n",
378 head->namehash_size, head->namehash_used, newhead.namehash_size);
380 newhead.string_offset = (newhead.namehash_offset
381 + (newhead.namehash_size
382 * sizeof (struct namehashent)));
383 /* Keep the string table size aligned to 4 bytes, so that
384 all the struct { uint32_t } types following are happy. */
385 newhead.string_size = MAX ((2 * newhead.string_used + 3) & -4,
386 newhead.string_size);
388 newhead.locrectab_offset = newhead.string_offset + newhead.string_size;
389 newhead.locrectab_size = MAX (2 * newhead.locrectab_used,
390 newhead.locrectab_size);
392 newhead.sumhash_offset = (newhead.locrectab_offset
393 + (newhead.locrectab_size
394 * sizeof (struct locrecent)));
395 newhead.sumhash_size = MAX (next_prime (2 * newhead.sumhash_used),
396 newhead.sumhash_size);
398 total = (newhead.sumhash_offset
399 + newhead.sumhash_size * sizeof (struct sumhashent));
401 /* The new file is empty now. */
402 newhead.namehash_used = 0;
403 newhead.string_used = 0;
404 newhead.locrectab_used = 0;
405 newhead.sumhash_used = 0;
407 /* Write out the header and create room for the other data structures. */
408 if (TEMP_FAILURE_RETRY (write (fd, &newhead, sizeof (newhead)))
409 != sizeof (newhead))
411 int errval = errno;
412 unlink (fname);
413 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
416 if (ftruncate64 (fd, total) != 0)
418 int errval = errno;
419 unlink (fname);
420 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
423 size_t reserved, mmap_len;
424 int xflags;
425 void *mmap_base;
426 void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base,
427 &mmap_len);
429 /* Map the header and all the administration data structures. */
430 p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
431 if (p == MAP_FAILED)
433 int errval = errno;
434 unlink (fname);
435 error (EXIT_FAILURE, errval, _("cannot map archive header"));
438 /* Lock the new file. */
439 if (lockf64 (fd, F_LOCK, total) != 0)
441 int errval = errno;
442 unlink (fname);
443 error (EXIT_FAILURE, errval, _("cannot lock new archive"));
446 new_ah.mmaped = total;
447 new_ah.mmap_base = mmap_base;
448 new_ah.mmap_len = mmap_len;
449 new_ah.addr = p;
450 new_ah.fd = fd;
451 new_ah.reserved = reserved;
453 /* Walk through the hash name hash table to find out what data is
454 still referenced and transfer it into the new file. */
455 oldnamehashtab = (struct namehashent *) ((char *) ah->addr
456 + head->namehash_offset);
458 /* Sort the old locrec table in order of data position. */
459 struct oldlocrecent oldlocrecarray[head->namehash_size];
460 for (cnt = 0, loccnt = 0; cnt < head->namehash_size; ++cnt)
461 if (oldnamehashtab[cnt].locrec_offset != 0)
463 oldlocrecarray[loccnt].cnt = cnt;
464 oldlocrecarray[loccnt++].locrec
465 = (struct locrecent *) ((char *) ah->addr
466 + oldnamehashtab[cnt].locrec_offset);
468 qsort (oldlocrecarray, loccnt, sizeof (struct oldlocrecent),
469 oldlocrecentcmp);
471 uint32_t last_locrec_offset = 0;
472 for (cnt = 0; cnt < loccnt; ++cnt)
474 /* Insert this entry in the new hash table. */
475 locale_data_t old_data;
476 unsigned int idx;
477 struct locrecent *oldlocrec = oldlocrecarray[cnt].locrec;
479 for (idx = 0; idx < __LC_LAST; ++idx)
480 if (idx != LC_ALL)
482 old_data[idx].size = oldlocrec->record[idx].len;
483 old_data[idx].addr
484 = ((char *) ah->addr + oldlocrec->record[idx].offset);
486 __md5_buffer (old_data[idx].addr, old_data[idx].size,
487 old_data[idx].sum);
490 if (cnt > 0 && oldlocrecarray[cnt - 1].locrec == oldlocrec)
492 const char *oldname
493 = ((char *) ah->addr
494 + oldnamehashtab[oldlocrecarray[cnt - 1].cnt].name_offset);
496 add_alias (&new_ah,
497 ((char *) ah->addr
498 + oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset),
499 0, oldname, &last_locrec_offset);
500 continue;
503 last_locrec_offset =
504 add_locale (&new_ah,
505 ((char *) ah->addr
506 + oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset),
507 old_data, 0);
508 if (last_locrec_offset == 0)
509 error (EXIT_FAILURE, 0, _("cannot extend locale archive file"));
512 /* Make the file globally readable. */
513 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
515 int errval = errno;
516 unlink (fname);
517 error (EXIT_FAILURE, errval,
518 _("cannot change mode of resized locale archive"));
521 /* Rename the new file. */
522 if (rename (fname, archivefname) != 0)
524 int errval = errno;
525 unlink (fname);
526 error (EXIT_FAILURE, errval, _("cannot rename new archive"));
529 /* Close the old file. */
530 close_archive (ah);
532 /* Add the information for the new one. */
533 *ah = new_ah;
537 void
538 open_archive (struct locarhandle *ah, bool readonly)
540 struct stat64 st;
541 struct stat64 st2;
542 int fd;
543 struct locarhead head;
544 int retry = 0;
545 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
546 char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
548 if (output_prefix)
549 memcpy (archivefname, output_prefix, prefix_len);
550 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
552 while (1)
554 /* Open the archive. We must have exclusive write access. */
555 fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
556 if (fd == -1)
558 /* Maybe the file does not yet exist. */
559 if (errno == ENOENT)
561 if (readonly)
563 static const struct locarhead nullhead =
565 .namehash_used = 0,
566 .namehash_offset = 0,
567 .namehash_size = 0
570 ah->addr = (void *) &nullhead;
571 ah->fd = -1;
573 else
574 create_archive (archivefname, ah);
576 return;
578 else
579 error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
580 archivefname);
583 if (fstat64 (fd, &st) < 0)
584 error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""),
585 archivefname);
587 if (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1)
589 close (fd);
591 if (retry++ < max_locarchive_open_retry)
593 struct timespec req;
595 /* Wait for a bit. */
596 req.tv_sec = 0;
597 req.tv_nsec = 1000000 * (random () % 500 + 1);
598 (void) nanosleep (&req, NULL);
600 continue;
603 error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
604 archivefname);
607 /* One more check. Maybe another process replaced the archive file
608 with a new, larger one since we opened the file. */
609 if (stat64 (archivefname, &st2) == -1
610 || st.st_dev != st2.st_dev
611 || st.st_ino != st2.st_ino)
613 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
614 close (fd);
615 continue;
618 /* Leave the loop. */
619 break;
622 /* Read the header. */
623 if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
625 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
626 error (EXIT_FAILURE, errno, _("cannot read archive header"));
629 ah->fd = fd;
630 ah->mmaped = st.st_size;
632 size_t reserved, mmap_len;
633 int xflags;
634 void *mmap_base;
635 void *p = prepare_address_space (fd, st.st_size, &reserved, &xflags,
636 &mmap_base, &mmap_len);
638 /* Map the entire file. We might need to compare the category data
639 in the file with the newly added data. */
640 ah->addr = mmap64 (p, st.st_size, PROT_READ | (readonly ? 0 : PROT_WRITE),
641 MAP_SHARED | xflags, fd, 0);
642 if (ah->addr == MAP_FAILED)
644 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
645 error (EXIT_FAILURE, errno, _("cannot map archive header"));
647 ah->reserved = reserved;
648 ah->mmap_base = mmap_base;
649 ah->mmap_len = mmap_len;
653 void
654 close_archive (struct locarhandle *ah)
656 if (ah->fd != -1)
658 if (ah->mmap_base)
659 munmap (ah->mmap_base, ah->mmap_len);
660 else
661 munmap (ah->addr, ah->reserved);
662 close (ah->fd);
666 #include "../../intl/explodename.c"
667 #include "../../intl/l10nflist.c"
669 static struct namehashent *
670 insert_name (struct locarhandle *ah,
671 const char *name, size_t name_len, bool replace)
673 const struct locarhead *const head = ah->addr;
674 struct namehashent *namehashtab
675 = (struct namehashent *) ((char *) ah->addr + head->namehash_offset);
676 unsigned int insert_idx, idx, incr;
678 /* Hash value of the locale name. */
679 uint32_t hval = archive_hashval (name, name_len);
681 insert_idx = -1;
682 idx = hval % head->namehash_size;
683 incr = 1 + hval % (head->namehash_size - 2);
685 /* If the name_offset field is zero this means this is a
686 deleted entry and therefore no entry can be found. */
687 while (namehashtab[idx].name_offset != 0)
689 if (namehashtab[idx].hashval == hval
690 && strcmp (name,
691 (char *) ah->addr + namehashtab[idx].name_offset) == 0)
693 /* Found the entry. */
694 if (namehashtab[idx].locrec_offset != 0 && ! replace)
696 if (! be_quiet)
697 error (0, 0, _("locale '%s' already exists"), name);
698 return NULL;
701 break;
704 if (namehashtab[idx].hashval == hval && ! be_quiet)
706 error (0, 0, "hash collision (%u) %s, %s",
707 hval, name, (char *) ah->addr + namehashtab[idx].name_offset);
710 /* Remember the first place we can insert the new entry. */
711 if (namehashtab[idx].locrec_offset == 0 && insert_idx == -1)
712 insert_idx = idx;
714 idx += incr;
715 if (idx >= head->namehash_size)
716 idx -= head->namehash_size;
719 /* Add as early as possible. */
720 if (insert_idx != -1)
721 idx = insert_idx;
723 namehashtab[idx].hashval = hval; /* no-op if replacing an old entry. */
724 return &namehashtab[idx];
727 static void
728 add_alias (struct locarhandle *ah, const char *alias, bool replace,
729 const char *oldname, uint32_t *locrec_offset_p)
731 uint32_t locrec_offset = *locrec_offset_p;
732 struct locarhead *head = ah->addr;
733 const size_t name_len = strlen (alias);
734 struct namehashent *namehashent = insert_name (ah, alias, strlen (alias),
735 replace);
736 if (namehashent == NULL && ! replace)
737 return;
739 if (namehashent->name_offset == 0)
741 /* We are adding a new hash entry for this alias.
742 Determine whether we have to resize the file. */
743 if (head->string_used + name_len + 1 > head->string_size
744 || 100 * head->namehash_used > 75 * head->namehash_size)
746 /* The current archive is not large enough. */
747 enlarge_archive (ah, head);
749 /* The locrecent might have moved, so we have to look up
750 the old name afresh. */
751 namehashent = insert_name (ah, oldname, strlen (oldname), true);
752 assert (namehashent->name_offset != 0);
753 assert (namehashent->locrec_offset != 0);
754 *locrec_offset_p = namehashent->locrec_offset;
756 /* Tail call to try the whole thing again. */
757 add_alias (ah, alias, replace, oldname, locrec_offset_p);
758 return;
761 /* Add the name string. */
762 memcpy (ah->addr + head->string_offset + head->string_used,
763 alias, name_len + 1);
764 namehashent->name_offset = head->string_offset + head->string_used;
765 head->string_used += name_len + 1;
767 ++head->namehash_used;
770 if (namehashent->locrec_offset != 0)
772 /* Replacing an existing entry.
773 Mark that we are no longer using the old locrecent. */
774 struct locrecent *locrecent
775 = (struct locrecent *) ((char *) ah->addr
776 + namehashent->locrec_offset);
777 --locrecent->refs;
780 /* Point this entry at the locrecent installed for the main name. */
781 namehashent->locrec_offset = locrec_offset;
784 static int /* qsort comparator used below */
785 cmpcategorysize (const void *a, const void *b)
787 if (*(const void **) a == NULL)
788 return 1;
789 if (*(const void **) b == NULL)
790 return -1;
791 return ((*(const struct locale_category_data **) a)->size
792 - (*(const struct locale_category_data **) b)->size);
795 /* Check the content of the archive for duplicates. Add the content
796 of the files if necessary. Returns the locrec_offset. */
797 static uint32_t
798 add_locale (struct locarhandle *ah,
799 const char *name, locale_data_t data, bool replace)
801 /* First look for the name. If it already exists and we are not
802 supposed to replace it don't do anything. If it does not exist
803 we have to allocate a new locale record. */
804 size_t name_len = strlen (name);
805 uint32_t file_offsets[__LC_LAST];
806 unsigned int num_new_offsets = 0;
807 struct sumhashent *sumhashtab;
808 uint32_t hval;
809 unsigned int cnt, idx;
810 struct locarhead *head;
811 struct namehashent *namehashent;
812 unsigned int incr;
813 struct locrecent *locrecent;
814 off64_t lastoffset;
815 char *ptr;
816 struct locale_category_data *size_order[__LC_LAST];
817 const size_t pagesz = getpagesize ();
818 int small_mask;
820 head = ah->addr;
821 sumhashtab = (struct sumhashent *) ((char *) ah->addr
822 + head->sumhash_offset);
824 memset (file_offsets, 0, sizeof (file_offsets));
826 size_order[LC_ALL] = NULL;
827 for (cnt = 0; cnt < __LC_LAST; ++cnt)
828 if (cnt != LC_ALL)
829 size_order[cnt] = &data[cnt];
831 /* Sort the array in ascending order of data size. */
832 qsort (size_order, __LC_LAST, sizeof size_order[0], cmpcategorysize);
834 small_mask = 0;
835 data[LC_ALL].size = 0;
836 for (cnt = 0; cnt < __LC_LAST; ++cnt)
837 if (size_order[cnt] != NULL)
839 const size_t rounded_size = (size_order[cnt]->size + 15) & -16;
840 if (data[LC_ALL].size + rounded_size > 2 * pagesz)
842 /* This category makes the small-categories block
843 stop being small, so this is the end of the road. */
845 size_order[cnt++] = NULL;
846 while (cnt < __LC_LAST);
847 break;
849 data[LC_ALL].size += rounded_size;
850 small_mask |= 1 << (size_order[cnt] - data);
853 /* Copy the data for all the small categories into the LC_ALL
854 pseudo-category. */
856 data[LC_ALL].addr = alloca (data[LC_ALL].size);
857 memset (data[LC_ALL].addr, 0, data[LC_ALL].size);
859 ptr = data[LC_ALL].addr;
860 for (cnt = 0; cnt < __LC_LAST; ++cnt)
861 if (small_mask & (1 << cnt))
863 memcpy (ptr, data[cnt].addr, data[cnt].size);
864 ptr += (data[cnt].size + 15) & -16;
866 __md5_buffer (data[LC_ALL].addr, data[LC_ALL].size, data[LC_ALL].sum);
868 /* For each locale category data set determine whether the same data
869 is already somewhere in the archive. */
870 for (cnt = 0; cnt < __LC_LAST; ++cnt)
871 if (small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
873 ++num_new_offsets;
875 /* Compute the hash value of the checksum to determine a
876 starting point for the search in the MD5 hash value
877 table. */
878 hval = archive_hashval (data[cnt].sum, 16);
880 idx = hval % head->sumhash_size;
881 incr = 1 + hval % (head->sumhash_size - 2);
883 while (sumhashtab[idx].file_offset != 0)
885 if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
887 /* Check the content, there could be a collision of
888 the hash sum.
890 Unfortunately the sumhashent record does not include
891 the size of the stored data. So we have to search for
892 it. */
893 locrecent = (struct locrecent *) ((char *) ah->addr
894 + head->locrectab_offset);
895 size_t iloc;
896 for (iloc = 0; iloc < head->locrectab_used; ++iloc)
897 if (locrecent[iloc].refs != 0
898 && (locrecent[iloc].record[cnt].offset
899 == sumhashtab[idx].file_offset))
900 break;
902 if (iloc != head->locrectab_used
903 && data[cnt].size == locrecent[iloc].record[cnt].len
904 /* We have to compare the content. Either we can
905 have the data mmaped or we have to read from
906 the file. */
907 && (file_data_available_p (ah, sumhashtab[idx].file_offset,
908 data[cnt].size)
909 ? memcmp (data[cnt].addr,
910 (char *) ah->addr
911 + sumhashtab[idx].file_offset,
912 data[cnt].size) == 0
913 : compare_from_file (ah, data[cnt].addr,
914 sumhashtab[idx].file_offset,
915 data[cnt].size) == 0))
917 /* Found it. */
918 file_offsets[cnt] = sumhashtab[idx].file_offset;
919 --num_new_offsets;
920 break;
924 idx += incr;
925 if (idx >= head->sumhash_size)
926 idx -= head->sumhash_size;
930 /* Find a slot for the locale name in the hash table. */
931 namehashent = insert_name (ah, name, name_len, replace);
932 if (namehashent == NULL) /* Already exists and !REPLACE. */
933 return 0;
935 /* Determine whether we have to resize the file. */
936 if (100 * (head->sumhash_used + num_new_offsets) > 75 * head->sumhash_size
937 || (namehashent->locrec_offset == 0
938 && (head->locrectab_used == head->locrectab_size
939 || head->string_used + name_len + 1 > head->string_size
940 || 100 * head->namehash_used > 75 * head->namehash_size)))
942 /* The current archive is not large enough. */
943 enlarge_archive (ah, head);
944 return add_locale (ah, name, data, replace);
947 /* Add the locale data which is not yet in the archive. */
948 for (cnt = 0, lastoffset = 0; cnt < __LC_LAST; ++cnt)
949 if ((small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
950 && file_offsets[cnt] == 0)
952 /* The data for this section is not yet available in the
953 archive. Append it. */
954 off64_t lastpos;
955 uint32_t md5hval;
957 lastpos = lseek64 (ah->fd, 0, SEEK_END);
958 if (lastpos == (off64_t) -1)
959 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
961 /* If block of small categories would cross page boundary,
962 align it unless it immediately follows a large category. */
963 if (cnt == LC_ALL && lastoffset != lastpos
964 && ((((lastpos & (pagesz - 1)) + data[cnt].size + pagesz - 1)
965 & -pagesz)
966 > ((data[cnt].size + pagesz - 1) & -pagesz)))
968 size_t sz = pagesz - (lastpos & (pagesz - 1));
969 char *zeros = alloca (sz);
971 memset (zeros, 0, sz);
972 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, sz) != sz))
973 error (EXIT_FAILURE, errno,
974 _("cannot add to locale archive"));
976 lastpos += sz;
979 /* Align all data to a 16 byte boundary. */
980 if ((lastpos & 15) != 0)
982 static const char zeros[15] = { 0, };
984 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, 16 - (lastpos & 15)))
985 != 16 - (lastpos & 15))
986 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
988 lastpos += 16 - (lastpos & 15);
991 /* Remember the position. */
992 file_offsets[cnt] = lastpos;
993 lastoffset = lastpos + data[cnt].size;
995 /* Write the data. */
996 if (TEMP_FAILURE_RETRY (write (ah->fd, data[cnt].addr, data[cnt].size))
997 != data[cnt].size)
998 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
1000 /* Add the hash value to the hash table. */
1001 md5hval = archive_hashval (data[cnt].sum, 16);
1003 idx = md5hval % head->sumhash_size;
1004 incr = 1 + md5hval % (head->sumhash_size - 2);
1006 while (sumhashtab[idx].file_offset != 0)
1008 idx += incr;
1009 if (idx >= head->sumhash_size)
1010 idx -= head->sumhash_size;
1013 memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
1014 sumhashtab[idx].file_offset = file_offsets[cnt];
1016 ++head->sumhash_used;
1019 lastoffset = file_offsets[LC_ALL];
1020 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1021 if (small_mask & (1 << cnt))
1023 file_offsets[cnt] = lastoffset;
1024 lastoffset += (data[cnt].size + 15) & -16;
1027 if (namehashent->name_offset == 0)
1029 /* Add the name string. */
1030 memcpy ((char *) ah->addr + head->string_offset + head->string_used,
1031 name, name_len + 1);
1032 namehashent->name_offset = head->string_offset + head->string_used;
1033 head->string_used += name_len + 1;
1034 ++head->namehash_used;
1037 if (namehashent->locrec_offset == 0)
1039 /* Allocate a name location record. */
1040 namehashent->locrec_offset = (head->locrectab_offset
1041 + (head->locrectab_used++
1042 * sizeof (struct locrecent)));
1043 locrecent = (struct locrecent *) ((char *) ah->addr
1044 + namehashent->locrec_offset);
1045 locrecent->refs = 1;
1047 else
1049 /* If there are other aliases pointing to this locrecent,
1050 we still need a new one. If not, reuse the old one. */
1052 locrecent = (struct locrecent *) ((char *) ah->addr
1053 + namehashent->locrec_offset);
1054 if (locrecent->refs > 1)
1056 --locrecent->refs;
1057 namehashent->locrec_offset = (head->locrectab_offset
1058 + (head->locrectab_used++
1059 * sizeof (struct locrecent)));
1060 locrecent = (struct locrecent *) ((char *) ah->addr
1061 + namehashent->locrec_offset);
1062 locrecent->refs = 1;
1066 /* Fill in the table with the locations of the locale data. */
1067 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1069 locrecent->record[cnt].offset = file_offsets[cnt];
1070 locrecent->record[cnt].len = data[cnt].size;
1073 return namehashent->locrec_offset;
1077 /* Check the content of the archive for duplicates. Add the content
1078 of the files if necessary. Add all the names, possibly overwriting
1079 old files. */
1081 add_locale_to_archive (ah, name, data, replace)
1082 struct locarhandle *ah;
1083 const char *name;
1084 locale_data_t data;
1085 bool replace;
1087 char *normalized_name = NULL;
1088 uint32_t locrec_offset;
1090 /* First analyze the name to decide how to archive it. */
1091 const char *language;
1092 const char *modifier;
1093 const char *territory;
1094 const char *codeset;
1095 const char *normalized_codeset;
1096 int mask = _nl_explode_name (strdupa (name),
1097 &language, &modifier, &territory,
1098 &codeset, &normalized_codeset);
1099 if (mask == -1)
1100 return -1;
1102 if (mask & XPG_NORM_CODESET)
1103 /* This name contains a codeset in unnormalized form.
1104 We will store it in the archive with a normalized name. */
1105 asprintf (&normalized_name, "%s%s%s.%s%s%s",
1106 language, territory == NULL ? "" : "_", territory ?: "",
1107 (mask & XPG_NORM_CODESET) ? normalized_codeset : codeset,
1108 modifier == NULL ? "" : "@", modifier ?: "");
1110 /* This call does the main work. */
1111 locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
1112 if (locrec_offset == 0)
1114 free (normalized_name);
1115 if (mask & XPG_NORM_CODESET)
1116 free ((char *) normalized_codeset);
1117 return -1;
1120 if ((mask & XPG_CODESET) == 0)
1122 /* This name lacks a codeset, so determine the locale's codeset and
1123 add an alias for its name with normalized codeset appended. */
1125 const struct
1127 unsigned int magic;
1128 unsigned int nstrings;
1129 unsigned int strindex[0];
1130 } *filedata = data[LC_CTYPE].addr;
1131 codeset = (char *) filedata
1132 + filedata->strindex[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME)];
1133 char *normalized_codeset_name = NULL;
1135 normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
1136 mask |= XPG_NORM_CODESET;
1138 asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
1139 language, territory == NULL ? "" : "_", territory ?: "",
1140 normalized_codeset,
1141 modifier == NULL ? "" : "@", modifier ?: "");
1143 add_alias (ah, normalized_codeset_name, replace,
1144 normalized_name ?: name, &locrec_offset);
1145 free (normalized_codeset_name);
1148 /* Now read the locale.alias files looking for lines whose
1149 right hand side matches our name after normalization. */
1150 int result = 0;
1151 if (alias_file != NULL)
1153 FILE *fp;
1154 fp = fopen (alias_file, "rm");
1155 if (fp == NULL)
1156 error (1, errno, _("locale alias file `%s' not found"),
1157 alias_file);
1159 /* No threads present. */
1160 __fsetlocking (fp, FSETLOCKING_BYCALLER);
1162 while (! feof_unlocked (fp))
1164 /* It is a reasonable approach to use a fix buffer here
1165 because
1166 a) we are only interested in the first two fields
1167 b) these fields must be usable as file names and so must
1168 not be that long */
1169 char buf[BUFSIZ];
1170 char *alias;
1171 char *value;
1172 char *cp;
1174 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1175 /* EOF reached. */
1176 break;
1178 cp = buf;
1179 /* Ignore leading white space. */
1180 while (isspace (cp[0]) && cp[0] != '\n')
1181 ++cp;
1183 /* A leading '#' signals a comment line. */
1184 if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
1186 alias = cp++;
1187 while (cp[0] != '\0' && !isspace (cp[0]))
1188 ++cp;
1189 /* Terminate alias name. */
1190 if (cp[0] != '\0')
1191 *cp++ = '\0';
1193 /* Now look for the beginning of the value. */
1194 while (isspace (cp[0]))
1195 ++cp;
1197 if (cp[0] != '\0')
1199 value = cp++;
1200 while (cp[0] != '\0' && !isspace (cp[0]))
1201 ++cp;
1202 /* Terminate value. */
1203 if (cp[0] == '\n')
1205 /* This has to be done to make the following
1206 test for the end of line possible. We are
1207 looking for the terminating '\n' which do not
1208 overwrite here. */
1209 *cp++ = '\0';
1210 *cp = '\n';
1212 else if (cp[0] != '\0')
1213 *cp++ = '\0';
1215 /* Does this alias refer to our locale? We will
1216 normalize the right hand side and compare the
1217 elements of the normalized form. */
1219 const char *rhs_language;
1220 const char *rhs_modifier;
1221 const char *rhs_territory;
1222 const char *rhs_codeset;
1223 const char *rhs_normalized_codeset;
1224 int rhs_mask = _nl_explode_name (value,
1225 &rhs_language,
1226 &rhs_modifier,
1227 &rhs_territory,
1228 &rhs_codeset,
1229 &rhs_normalized_codeset);
1230 if (rhs_mask == -1)
1232 result = -1;
1233 goto out;
1235 if (!strcmp (language, rhs_language)
1236 && ((rhs_mask & XPG_CODESET)
1237 /* He has a codeset, it must match normalized. */
1238 ? !strcmp ((mask & XPG_NORM_CODESET)
1239 ? normalized_codeset : codeset,
1240 (rhs_mask & XPG_NORM_CODESET)
1241 ? rhs_normalized_codeset : rhs_codeset)
1242 /* He has no codeset, we must also have none. */
1243 : (mask & XPG_CODESET) == 0)
1244 /* Codeset (or lack thereof) matches. */
1245 && !strcmp (territory ?: "", rhs_territory ?: "")
1246 && !strcmp (modifier ?: "", rhs_modifier ?: ""))
1247 /* We have a winner. */
1248 add_alias (ah, alias, replace,
1249 normalized_name ?: name, &locrec_offset);
1250 if (rhs_mask & XPG_NORM_CODESET)
1251 free ((char *) rhs_normalized_codeset);
1256 /* Possibly not the whole line fits into the buffer.
1257 Ignore the rest of the line. */
1258 while (strchr (cp, '\n') == NULL)
1260 cp = buf;
1261 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1262 /* Make sure the inner loop will be left. The outer
1263 loop will exit at the `feof' test. */
1264 *cp = '\n';
1268 out:
1269 fclose (fp);
1272 free (normalized_name);
1274 if (mask & XPG_NORM_CODESET)
1275 free ((char *) normalized_codeset);
1277 return result;
1282 add_locales_to_archive (nlist, list, replace)
1283 size_t nlist;
1284 char *list[];
1285 bool replace;
1287 struct locarhandle ah;
1288 int result = 0;
1290 /* Open the archive. This call never returns if we cannot
1291 successfully open the archive. */
1292 open_archive (&ah, false);
1294 while (nlist-- > 0)
1296 const char *fname = *list++;
1297 size_t fnamelen = strlen (fname);
1298 struct stat64 st;
1299 DIR *dirp;
1300 struct dirent64 *d;
1301 int seen;
1302 locale_data_t data;
1303 int cnt;
1305 if (! be_quiet)
1306 printf (_("Adding %s\n"), fname);
1308 /* First see whether this really is a directory and whether it
1309 contains all the require locale category files. */
1310 if (stat64 (fname, &st) < 0)
1312 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
1313 strerror (errno));
1314 continue;
1316 if (!S_ISDIR (st.st_mode))
1318 error (0, 0, _("\"%s\" is no directory; ignored"), fname);
1319 continue;
1322 dirp = opendir (fname);
1323 if (dirp == NULL)
1325 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1326 fname, strerror (errno));
1327 continue;
1330 seen = 0;
1331 while ((d = readdir64 (dirp)) != NULL)
1333 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1334 if (cnt != LC_ALL)
1335 if (strcmp (d->d_name, locnames[cnt]) == 0)
1337 unsigned char d_type;
1339 /* We have an object of the required name. If it's
1340 a directory we have to look at a file with the
1341 prefix "SYS_". Otherwise we have found what we
1342 are looking for. */
1343 #ifdef _DIRENT_HAVE_D_TYPE
1344 d_type = d->d_type;
1346 if (d_type != DT_REG)
1347 #endif
1349 char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
1351 #ifdef _DIRENT_HAVE_D_TYPE
1352 if (d_type == DT_UNKNOWN)
1353 #endif
1355 strcpy (stpcpy (stpcpy (fullname, fname), "/"),
1356 d->d_name);
1358 if (stat64 (fullname, &st) == -1)
1359 /* We cannot stat the file, ignore it. */
1360 break;
1362 d_type = IFTODT (st.st_mode);
1365 if (d_type == DT_DIR)
1367 /* We have to do more tests. The file is a
1368 directory and it therefore must contain a
1369 regular file with the same name except a
1370 "SYS_" prefix. */
1371 char *t = stpcpy (stpcpy (fullname, fname), "/");
1372 strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
1373 d->d_name);
1375 if (stat64 (fullname, &st) == -1)
1376 /* There is no SYS_* file or we cannot
1377 access it. */
1378 break;
1380 d_type = IFTODT (st.st_mode);
1384 /* If we found a regular file (eventually after
1385 following a symlink) we are successful. */
1386 if (d_type == DT_REG)
1387 ++seen;
1388 break;
1392 closedir (dirp);
1394 if (seen != __LC_LAST - 1)
1396 /* We don't have all locale category files. Ignore the name. */
1397 error (0, 0, _("incomplete set of locale files in \"%s\""),
1398 fname);
1399 continue;
1402 /* Add the files to the archive. To do this we first compute
1403 sizes and the MD5 sums of all the files. */
1404 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1405 if (cnt != LC_ALL)
1407 char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
1408 int fd;
1410 strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
1411 fd = open64 (fullname, O_RDONLY);
1412 if (fd == -1 || fstat64 (fd, &st) == -1)
1414 /* Cannot read the file. */
1415 if (fd != -1)
1416 close (fd);
1417 break;
1420 if (S_ISDIR (st.st_mode))
1422 char *t;
1423 close (fd);
1424 t = stpcpy (stpcpy (fullname, fname), "/");
1425 strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
1426 locnames[cnt]);
1428 fd = open64 (fullname, O_RDONLY);
1429 if (fd == -1 || fstat64 (fd, &st) == -1
1430 || !S_ISREG (st.st_mode))
1432 if (fd != -1)
1433 close (fd);
1434 break;
1438 /* Map the file. */
1439 data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
1440 fd, 0);
1441 if (data[cnt].addr == MAP_FAILED)
1443 /* Cannot map it. */
1444 close (fd);
1445 break;
1448 data[cnt].size = st.st_size;
1449 __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
1451 /* We don't need the file descriptor anymore. */
1452 close (fd);
1455 if (cnt != __LC_LAST)
1457 while (cnt-- > 0)
1458 if (cnt != LC_ALL)
1459 munmap (data[cnt].addr, data[cnt].size);
1461 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);
1463 continue;
1466 result |= add_locale_to_archive (&ah, basename (fname), data, replace);
1468 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1469 if (cnt != LC_ALL)
1470 munmap (data[cnt].addr, data[cnt].size);
1473 /* We are done. */
1474 close_archive (&ah);
1476 return result;
1481 delete_locales_from_archive (nlist, list)
1482 size_t nlist;
1483 char *list[];
1485 struct locarhandle ah;
1486 struct locarhead *head;
1487 struct namehashent *namehashtab;
1489 /* Open the archive. This call never returns if we cannot
1490 successfully open the archive. */
1491 open_archive (&ah, false);
1493 head = ah.addr;
1494 namehashtab = (struct namehashent *) ((char *) ah.addr
1495 + head->namehash_offset);
1497 while (nlist-- > 0)
1499 const char *locname = *list++;
1500 uint32_t hval;
1501 unsigned int idx;
1502 unsigned int incr;
1504 /* Search for this locale in the archive. */
1505 hval = archive_hashval (locname, strlen (locname));
1507 idx = hval % head->namehash_size;
1508 incr = 1 + hval % (head->namehash_size - 2);
1510 /* If the name_offset field is zero this means this is no
1511 deleted entry and therefore no entry can be found. */
1512 while (namehashtab[idx].name_offset != 0)
1514 if (namehashtab[idx].hashval == hval
1515 && (strcmp (locname,
1516 (char *) ah.addr + namehashtab[idx].name_offset)
1517 == 0))
1519 /* Found the entry. Now mark it as removed by zero-ing
1520 the reference to the locale record. */
1521 namehashtab[idx].locrec_offset = 0;
1522 break;
1525 idx += incr;
1526 if (idx >= head->namehash_size)
1527 idx -= head->namehash_size;
1530 if (namehashtab[idx].name_offset == 0 && ! be_quiet)
1531 error (0, 0, _("locale \"%s\" not in archive"), locname);
1534 close_archive (&ah);
1536 return 0;
1540 struct nameent
1542 char *name;
1543 uint32_t locrec_offset;
1547 struct dataent
1549 const unsigned char *sum;
1550 uint32_t file_offset;
1551 uint32_t nlink;
1555 static int
1556 nameentcmp (const void *a, const void *b)
1558 return strcmp (((const struct nameent *) a)->name,
1559 ((const struct nameent *) b)->name);
1563 static int
1564 dataentcmp (const void *a, const void *b)
1566 if (((const struct dataent *) a)->file_offset
1567 < ((const struct dataent *) b)->file_offset)
1568 return -1;
1570 if (((const struct dataent *) a)->file_offset
1571 > ((const struct dataent *) b)->file_offset)
1572 return 1;
1574 return 0;
1578 void
1579 show_archive_content (int verbose)
1581 struct locarhandle ah;
1582 struct locarhead *head;
1583 struct namehashent *namehashtab;
1584 struct nameent *names;
1585 size_t cnt, used;
1587 /* Open the archive. This call never returns if we cannot
1588 successfully open the archive. */
1589 open_archive (&ah, true);
1591 head = ah.addr;
1593 names = (struct nameent *) xmalloc (head->namehash_used
1594 * sizeof (struct nameent));
1596 namehashtab = (struct namehashent *) ((char *) ah.addr
1597 + head->namehash_offset);
1598 for (cnt = used = 0; cnt < head->namehash_size; ++cnt)
1599 if (namehashtab[cnt].locrec_offset != 0)
1601 assert (used < head->namehash_used);
1602 names[used].name = ah.addr + namehashtab[cnt].name_offset;
1603 names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
1606 /* Sort the names. */
1607 qsort (names, used, sizeof (struct nameent), nameentcmp);
1609 if (verbose)
1611 struct dataent *files;
1612 struct sumhashent *sumhashtab;
1613 int sumused;
1615 files = (struct dataent *) xmalloc (head->sumhash_used
1616 * sizeof (struct dataent));
1618 sumhashtab = (struct sumhashent *) ((char *) ah.addr
1619 + head->sumhash_offset);
1620 for (cnt = sumused = 0; cnt < head->sumhash_size; ++cnt)
1621 if (sumhashtab[cnt].file_offset != 0)
1623 assert (sumused < head->sumhash_used);
1624 files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
1625 files[sumused].file_offset = sumhashtab[cnt].file_offset;
1626 files[sumused++].nlink = 0;
1629 /* Sort by file locations. */
1630 qsort (files, sumused, sizeof (struct dataent), dataentcmp);
1632 /* Compute nlink fields. */
1633 for (cnt = 0; cnt < used; ++cnt)
1635 struct locrecent *locrec;
1636 int idx;
1638 locrec = (struct locrecent *) ((char *) ah.addr
1639 + names[cnt].locrec_offset);
1640 for (idx = 0; idx < __LC_LAST; ++idx)
1641 if (locrec->record[LC_ALL].offset != 0
1642 ? (idx == LC_ALL
1643 || (locrec->record[idx].offset
1644 < locrec->record[LC_ALL].offset)
1645 || (locrec->record[idx].offset + locrec->record[idx].len
1646 > (locrec->record[LC_ALL].offset
1647 + locrec->record[LC_ALL].len)))
1648 : idx != LC_ALL)
1650 struct dataent *data, dataent;
1652 dataent.file_offset = locrec->record[idx].offset;
1653 data = (struct dataent *) bsearch (&dataent, files, sumused,
1654 sizeof (struct dataent),
1655 dataentcmp);
1656 assert (data != NULL);
1657 ++data->nlink;
1661 /* Print it. */
1662 for (cnt = 0; cnt < used; ++cnt)
1664 struct locrecent *locrec;
1665 int idx, i;
1667 locrec = (struct locrecent *) ((char *) ah.addr
1668 + names[cnt].locrec_offset);
1669 for (idx = 0; idx < __LC_LAST; ++idx)
1670 if (idx != LC_ALL)
1672 struct dataent *data, dataent;
1674 dataent.file_offset = locrec->record[idx].offset;
1675 if (locrec->record[LC_ALL].offset != 0
1676 && dataent.file_offset >= locrec->record[LC_ALL].offset
1677 && (dataent.file_offset + locrec->record[idx].len
1678 <= (locrec->record[LC_ALL].offset
1679 + locrec->record[LC_ALL].len)))
1680 dataent.file_offset = locrec->record[LC_ALL].offset;
1682 data = (struct dataent *) bsearch (&dataent, files, sumused,
1683 sizeof (struct dataent),
1684 dataentcmp);
1685 printf ("%6d %7x %3d%c ",
1686 locrec->record[idx].len, locrec->record[idx].offset,
1687 data->nlink,
1688 dataent.file_offset == locrec->record[LC_ALL].offset
1689 ? '+' : ' ');
1690 for (i = 0; i < 16; i += 4)
1691 printf ("%02x%02x%02x%02x",
1692 data->sum[i], data->sum[i + 1],
1693 data->sum[i + 2], data->sum[i + 3]);
1694 printf (" %s/%s\n", names[cnt].name,
1695 idx == LC_MESSAGES ? "LC_MESSAGES/SYS_LC_MESSAGES"
1696 : locnames[idx]);
1700 else
1701 for (cnt = 0; cnt < used; ++cnt)
1702 puts (names[cnt].name);
1704 close_archive (&ah);
1706 exit (EXIT_SUCCESS);