]> git.ipfire.org Git - thirdparty/glibc.git/blob - locale/programs/locarchive.c
* elf/ldconfig.c: Allow GPLv2 or any later version.
[thirdparty/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 }