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