* elf/ldconfig.c: Allow GPLv2 or any later version.
[glibc.git] / locale / programs / locarchive.c
blob42e661846e13c9dd7d070850831b7114d3ef351a
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.
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, write to the Free Software Foundation,
17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
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>
42 #include "../../crypt/md5.h"
43 #include "../localeinfo.h"
44 #include "../locarchive.h"
45 #include "localedef.h"
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
54 extern const char *output_prefix;
56 #define ARCHIVE_NAME LOCALEDIR "/locale-archive"
58 static const char *locnames[] =
60 #define DEFINE_CATEGORY(category, category_name, items, a) \
61 [category] = category_name,
62 #include "categories.def"
63 #undef DEFINE_CATEGORY
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
74 static void
75 create_archive (const char *archivefname, struct locarhandle *ah)
77 int fd;
78 char fname[strlen (archivefname) + sizeof (".XXXXXX")];
79 struct locarhead head;
80 void *p;
81 size_t total;
83 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
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"));
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);
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;
102 head.locrectab_offset = head.string_offset + head.string_size;
103 head.locrectab_used = 0;
104 head.locrectab_size = INITIAL_NUM_LOCREC;
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);
111 total = head.sumhash_offset + head.sumhash_size * sizeof (struct sumhashent);
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))
116 int errval = errno;
117 unlink (fname);
118 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
121 if (ftruncate64 (fd, total) != 0)
123 int errval = errno;
124 unlink (fname);
125 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
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)
132 int errval = errno;
133 unlink (fname);
134 error (EXIT_FAILURE, errval, _("cannot map archive header"));
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)
142 int errval = errno;
144 /* We cannot use the just created file. */
145 close (fd);
146 unlink (fname);
148 if (errval == EEXIST)
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;
156 error (EXIT_FAILURE, errval, _("failed to create new locale archive"));
159 /* Remove the temporary name. */
160 unlink (fname);
162 /* Make the file globally readable. */
163 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
165 int errval = errno;
166 unlink (archivefname);
167 error (EXIT_FAILURE, errval,
168 _("cannot change mode of new locale archive"));
171 ah->fd = fd;
172 ah->addr = p;
173 ah->len = total;
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
181 unsigned int cnt;
182 struct locrecent *locrec;
185 static int
186 oldlocrecentcmp (const void *a, const void *b)
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;
194 for (cnt = 0; cnt < __LC_LAST; ++cnt)
195 if (cnt != LC_ALL)
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;
202 assert (start_a != (uint32_t)-1);
203 assert (end_a != 0);
205 for (cnt = 0; cnt < __LC_LAST; ++cnt)
206 if (cnt != LC_ALL)
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;
213 assert (start_b != (uint32_t)-1);
214 assert (end_b != 0);
216 if (start_a != start_b)
217 return (int)start_a - (int)start_b;
218 return (int)end_a - (int)end_b;
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);
229 static void
230 enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
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];
245 if (output_prefix)
246 memcpy (archivefname, output_prefix, prefix_len);
247 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
248 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
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;
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"));
263 /* Copy the existing head information. */
264 newhead = *head;
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);
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);
282 newhead.locrectab_offset = newhead.string_offset + newhead.string_size;
283 newhead.locrectab_size = MAX (2 * newhead.locrectab_used,
284 newhead.locrectab_size);
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);
292 total = (newhead.sumhash_offset
293 + newhead.sumhash_size * sizeof (struct sumhashent));
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;
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))
305 int errval = errno;
306 unlink (fname);
307 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
310 if (ftruncate64 (fd, total) != 0)
312 int errval = errno;
313 unlink (fname);
314 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
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)
321 int errval = errno;
322 unlink (fname);
323 error (EXIT_FAILURE, errval, _("cannot map archive header"));
326 /* Lock the new file. */
327 if (lockf64 (fd, F_LOCK, total) != 0)
329 int errval = errno;
330 unlink (fname);
331 error (EXIT_FAILURE, errval, _("cannot lock new archive"));
334 new_ah.len = total;
335 new_ah.addr = p;
336 new_ah.fd = fd;
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);
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)
350 oldlocrecarray[loccnt].cnt = cnt;
351 oldlocrecarray[loccnt++].locrec
352 = (struct locrecent *) ((char *) ah->addr
353 + oldnamehashtab[cnt].locrec_offset);
355 qsort (oldlocrecarray, loccnt, sizeof (struct oldlocrecent),
356 oldlocrecentcmp);
358 uint32_t last_locrec_offset = 0;
359 for (cnt = 0; cnt < loccnt; ++cnt)
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;
366 for (idx = 0; idx < __LC_LAST; ++idx)
367 if (idx != LC_ALL)
369 old_data[idx].size = oldlocrec->record[idx].len;
370 old_data[idx].addr
371 = ((char *) ah->addr + oldlocrec->record[idx].offset);
373 __md5_buffer (old_data[idx].addr, old_data[idx].size,
374 old_data[idx].sum);
377 if (cnt > 0 && oldlocrecarray[cnt - 1].locrec == oldlocrec)
379 const char *oldname
380 = ((char *) ah->addr
381 + oldnamehashtab[oldlocrecarray[cnt - 1].cnt].name_offset);
383 add_alias (&new_ah,
384 ((char *) ah->addr
385 + oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset),
386 0, oldname, &last_locrec_offset);
387 continue;
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"));
399 /* Make the file globally readable. */
400 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
402 int errval = errno;
403 unlink (fname);
404 error (EXIT_FAILURE, errval,
405 _("cannot change mode of resized locale archive"));
408 /* Rename the new file. */
409 if (rename (fname, archivefname) != 0)
411 int errval = errno;
412 unlink (fname);
413 error (EXIT_FAILURE, errval, _("cannot rename new archive"));
416 /* Close the old file. */
417 close_archive (ah);
419 /* Add the information for the new one. */
420 *ah = new_ah;
424 void
425 open_archive (struct locarhandle *ah, bool readonly)
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)];
435 if (output_prefix)
436 memcpy (archivefname, output_prefix, prefix_len);
437 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
439 while (1)
441 /* Open the archive. We must have exclusive write access. */
442 fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
443 if (fd == -1)
445 /* Maybe the file does not yet exist. */
446 if (errno == ENOENT)
448 if (readonly)
450 static const struct locarhead nullhead =
452 .namehash_used = 0,
453 .namehash_offset = 0,
454 .namehash_size = 0
457 ah->addr = (void *) &nullhead;
458 ah->fd = -1;
460 else
461 create_archive (archivefname, ah);
463 return;
465 else
466 error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
467 archivefname);
470 if (fstat64 (fd, &st) < 0)
471 error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""),
472 archivefname);
474 if (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1)
476 close (fd);
478 if (retry++ < max_locarchive_open_retry)
480 struct timespec req;
482 /* Wait for a bit. */
483 req.tv_sec = 0;
484 req.tv_nsec = 1000000 * (random () % 500 + 1);
485 (void) nanosleep (&req, NULL);
487 continue;
490 error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
491 archivefname);
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)
500 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
501 close (fd);
502 continue;
505 /* Leave the loop. */
506 break;
509 /* Read the header. */
510 if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
512 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
513 error (EXIT_FAILURE, errno, _("cannot read archive header"));
516 ah->fd = fd;
517 ah->len = (head.sumhash_offset
518 + head.sumhash_size * sizeof (struct sumhashent));
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)
526 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
527 error (EXIT_FAILURE, errno, _("cannot map archive header"));
532 void
533 close_archive (struct locarhandle *ah)
535 if (ah->fd != -1)
537 munmap (ah->addr, ah->len);
538 close (ah->fd);
542 #include "../../intl/explodename.c"
543 #include "../../intl/l10nflist.c"
545 static struct namehashent *
546 insert_name (struct locarhandle *ah,
547 const char *name, size_t name_len, bool replace)
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;
554 /* Hash value of the locale name. */
555 uint32_t hval = archive_hashval (name, name_len);
557 insert_idx = -1;
558 idx = hval % head->namehash_size;
559 incr = 1 + hval % (head->namehash_size - 2);
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)
565 if (namehashtab[idx].hashval == hval
566 && strcmp (name,
567 (char *) ah->addr + namehashtab[idx].name_offset) == 0)
569 /* Found the entry. */
570 if (namehashtab[idx].locrec_offset != 0 && ! replace)
572 if (! be_quiet)
573 error (0, 0, _("locale '%s' already exists"), name);
574 return NULL;
577 break;
580 if (namehashtab[idx].hashval == hval && ! be_quiet)
582 error (0, 0, "hash collision (%u) %s, %s",
583 hval, name, (char *) ah->addr + namehashtab[idx].name_offset);
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;
590 idx += incr;
591 if (idx >= head->namehash_size)
592 idx -= head->namehash_size;
595 /* Add as early as possible. */
596 if (insert_idx != -1)
597 idx = insert_idx;
599 namehashtab[idx].hashval = hval; /* no-op if replacing an old entry. */
600 return &namehashtab[idx];
603 static void
604 add_alias (struct locarhandle *ah, const char *alias, bool replace,
605 const char *oldname, uint32_t *locrec_offset_p)
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;
615 if (namehashent->name_offset == 0)
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)
622 /* The current archive is not large enough. */
623 enlarge_archive (ah, head);
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;
632 /* Tail call to try the whole thing again. */
633 add_alias (ah, alias, replace, oldname, locrec_offset_p);
634 return;
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;
643 ++head->namehash_used;
646 if (namehashent->locrec_offset != 0)
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;
656 /* Point this entry at the locrecent installed for the main name. */
657 namehashent->locrec_offset = locrec_offset;
660 static int /* qsort comparator used below */
661 cmpcategorysize (const void *a, const void *b)
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);
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)
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;
696 head = ah->addr;
697 sumhashtab = (struct sumhashent *) ((char *) ah->addr
698 + head->sumhash_offset);
700 memset (file_offsets, 0, sizeof (file_offsets));
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];
707 /* Sort the array in ascending order of data size. */
708 qsort (size_order, __LC_LAST, sizeof size_order[0], cmpcategorysize);
710 small_mask = 0;
711 data[LC_ALL].size = 0;
712 for (cnt = 0; cnt < __LC_LAST; ++cnt)
713 if (size_order[cnt] != NULL)
715 const size_t rounded_size = (size_order[cnt]->size + 15) & -16;
716 if (data[LC_ALL].size + rounded_size > 2 * pagesz)
718 /* This category makes the small-categories block
719 stop being small, so this is the end of the road. */
721 size_order[cnt++] = NULL;
722 while (cnt < __LC_LAST);
723 break;
725 data[LC_ALL].size += rounded_size;
726 small_mask |= 1 << (size_order[cnt] - data);
729 /* Copy the data for all the small categories into the LC_ALL
730 pseudo-category. */
732 data[LC_ALL].addr = alloca (data[LC_ALL].size);
733 memset (data[LC_ALL].addr, 0, data[LC_ALL].size);
735 ptr = data[LC_ALL].addr;
736 for (cnt = 0; cnt < __LC_LAST; ++cnt)
737 if (small_mask & (1 << cnt))
739 memcpy (ptr, data[cnt].addr, data[cnt].size);
740 ptr += (data[cnt].size + 15) & -16;
742 __md5_buffer (data[LC_ALL].addr, data[LC_ALL].size, data[LC_ALL].sum);
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)))
749 ++num_new_offsets;
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);
756 idx = hval % head->sumhash_size;
757 incr = 1 + hval % (head->sumhash_size - 2);
759 while (sumhashtab[idx].file_offset != 0)
761 if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
763 /* Found it. */
764 file_offsets[cnt] = sumhashtab[idx].file_offset;
765 --num_new_offsets;
766 break;
769 idx += incr;
770 if (idx >= head->sumhash_size)
771 idx -= head->sumhash_size;
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;
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)))
787 /* The current archive is not large enough. */
788 enlarge_archive (ah, head);
789 return add_locale (ah, name, data, replace);
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)
797 /* The data for this section is not yet available in the
798 archive. Append it. */
799 off64_t lastpos;
800 uint32_t md5hval;
802 lastpos = lseek64 (ah->fd, 0, SEEK_END);
803 if (lastpos == (off64_t) -1)
804 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
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)))
813 size_t sz = pagesz - (lastpos & (pagesz - 1));
814 char *zeros = alloca (sz);
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"));
821 lastpos += sz;
824 /* Align all data to a 16 byte boundary. */
825 if ((lastpos & 15) != 0)
827 static const char zeros[15] = { 0, };
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"));
833 lastpos += 16 - (lastpos & 15);
836 /* Remember the position. */
837 file_offsets[cnt] = lastpos;
838 lastoffset = lastpos + data[cnt].size;
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"));
845 /* Add the hash value to the hash table. */
846 md5hval = archive_hashval (data[cnt].sum, 16);
848 idx = md5hval % head->sumhash_size;
849 incr = 1 + md5hval % (head->sumhash_size - 2);
851 while (sumhashtab[idx].file_offset != 0)
853 idx += incr;
854 if (idx >= head->sumhash_size)
855 idx -= head->sumhash_size;
858 memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
859 sumhashtab[idx].file_offset = file_offsets[cnt];
861 ++head->sumhash_used;
864 lastoffset = file_offsets[LC_ALL];
865 for (cnt = 0; cnt < __LC_LAST; ++cnt)
866 if (small_mask & (1 << cnt))
868 file_offsets[cnt] = lastoffset;
869 lastoffset += (data[cnt].size + 15) & -16;
872 if (namehashent->name_offset == 0)
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;
882 if (namehashent->locrec_offset == 0)
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;
892 else
894 /* If there are other aliases pointing to this locrecent,
895 we still need a new one. If not, reuse the old one. */
897 locrecent = (struct locrecent *) ((char *) ah->addr
898 + namehashent->locrec_offset);
899 if (locrecent->refs > 1)
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;
911 /* Fill in the table with the locations of the locale data. */
912 for (cnt = 0; cnt < __LC_LAST; ++cnt)
914 locrecent->record[cnt].offset = file_offsets[cnt];
915 locrecent->record[cnt].len = data[cnt].size;
918 return namehashent->locrec_offset;
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. */
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;
932 char *normalized_name = NULL;
933 uint32_t locrec_offset;
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);
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 ?: "");
953 /* This call does the main work. */
954 locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
955 if (locrec_offset == 0)
957 free (normalized_name);
958 if (mask & XPG_NORM_CODESET)
959 free ((char *) normalized_codeset);
960 return -1;
963 if ((mask & XPG_CODESET) == 0)
965 /* This name lacks a codeset, so determine the locale's codeset and
966 add an alias for its name with normalized codeset appended. */
968 const struct
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;
978 normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
979 mask |= XPG_NORM_CODESET;
981 asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
982 language, territory == NULL ? "" : "_", territory ?: "",
983 normalized_codeset,
984 modifier == NULL ? "" : "@", modifier ?: "");
986 add_alias (ah, normalized_codeset_name, replace,
987 normalized_name ?: name, &locrec_offset);
988 free (normalized_codeset_name);
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)
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);
1001 /* No threads present. */
1002 __fsetlocking (fp, FSETLOCKING_BYCALLER);
1004 while (! feof_unlocked (fp))
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;
1016 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1017 /* EOF reached. */
1018 break;
1020 cp = buf;
1021 /* Ignore leading white space. */
1022 while (isspace (cp[0]) && cp[0] != '\n')
1023 ++cp;
1025 /* A leading '#' signals a comment line. */
1026 if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
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';
1035 /* Now look for the beginning of the value. */
1036 while (isspace (cp[0]))
1037 ++cp;
1039 if (cp[0] != '\0')
1041 value = cp++;
1042 while (cp[0] != '\0' && !isspace (cp[0]))
1043 ++cp;
1044 /* Terminate value. */
1045 if (cp[0] == '\n')
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';
1054 else if (cp[0] != '\0')
1055 *cp++ = '\0';
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. */
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);
1093 /* Possibly not the whole line fits into the buffer.
1094 Ignore the rest of the line. */
1095 while (strchr (cp, '\n') == NULL)
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';
1105 fclose (fp);
1108 free (normalized_name);
1110 if (mask & XPG_NORM_CODESET)
1111 free ((char *) normalized_codeset);
1113 return 0;
1118 add_locales_to_archive (nlist, list, replace)
1119 size_t nlist;
1120 char *list[];
1121 bool replace;
1123 struct locarhandle ah;
1124 int result = 0;
1126 /* Open the archive. This call never returns if we cannot
1127 successfully open the archive. */
1128 open_archive (&ah, false);
1130 while (nlist-- > 0)
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;
1141 if (! be_quiet)
1142 printf (_("Adding %s\n"), fname);
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)
1148 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
1149 strerror (errno));
1150 continue;
1152 if (!S_ISDIR (st.st_mode))
1154 error (0, 0, _("\"%s\" is no directory; ignored"), fname);
1155 continue;
1158 dirp = opendir (fname);
1159 if (dirp == NULL)
1161 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1162 fname, strerror (errno));
1163 continue;
1166 seen = 0;
1167 while ((d = readdir64 (dirp)) != NULL)
1169 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1170 if (cnt != LC_ALL)
1171 if (strcmp (d->d_name, locnames[cnt]) == 0)
1173 unsigned char d_type;
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;
1182 if (d_type != DT_REG)
1183 #endif
1185 char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
1187 #ifdef _DIRENT_HAVE_D_TYPE
1188 if (d_type == DT_UNKNOWN)
1189 #endif
1191 strcpy (stpcpy (stpcpy (fullname, fname), "/"),
1192 d->d_name);
1194 if (stat64 (fullname, &st) == -1)
1195 /* We cannot stat the file, ignore it. */
1196 break;
1198 d_type = IFTODT (st.st_mode);
1201 if (d_type == DT_DIR)
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);
1211 if (stat64 (fullname, &st) == -1)
1212 /* There is no SYS_* file or we cannot
1213 access it. */
1214 break;
1216 d_type = IFTODT (st.st_mode);
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;
1228 closedir (dirp);
1230 if (seen != __LC_LAST - 1)
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;
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)
1243 char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
1244 int fd;
1246 strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
1247 fd = open64 (fullname, O_RDONLY);
1248 if (fd == -1 || fstat64 (fd, &st) == -1)
1250 /* Cannot read the file. */
1251 if (fd != -1)
1252 close (fd);
1253 break;
1256 if (S_ISDIR (st.st_mode))
1258 char *t;
1259 close (fd);
1260 t = stpcpy (stpcpy (fullname, fname), "/");
1261 strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
1262 locnames[cnt]);
1264 fd = open64 (fullname, O_RDONLY);
1265 if (fd == -1 || fstat64 (fd, &st) == -1
1266 || !S_ISREG (st.st_mode))
1268 if (fd != -1)
1269 close (fd);
1270 break;
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)
1279 /* Cannot map it. */
1280 close (fd);
1281 break;
1284 data[cnt].size = st.st_size;
1285 __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
1287 /* We don't need the file descriptor anymore. */
1288 close (fd);
1291 if (cnt != __LC_LAST)
1293 while (cnt-- > 0)
1294 if (cnt != LC_ALL)
1295 munmap (data[cnt].addr, data[cnt].size);
1297 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);
1299 continue;
1302 result |= add_locale_to_archive (&ah, basename (fname), data, replace);
1304 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1305 if (cnt != LC_ALL)
1306 munmap (data[cnt].addr, data[cnt].size);
1309 /* We are done. */
1310 close_archive (&ah);
1312 return result;
1317 delete_locales_from_archive (nlist, list)
1318 size_t nlist;
1319 char *list[];
1321 struct locarhandle ah;
1322 struct locarhead *head;
1323 struct namehashent *namehashtab;
1325 /* Open the archive. This call never returns if we cannot
1326 successfully open the archive. */
1327 open_archive (&ah, false);
1329 head = ah.addr;
1330 namehashtab = (struct namehashent *) ((char *) ah.addr
1331 + head->namehash_offset);
1333 while (nlist-- > 0)
1335 const char *locname = *list++;
1336 uint32_t hval;
1337 unsigned int idx;
1338 unsigned int incr;
1340 /* Search for this locale in the archive. */
1341 hval = archive_hashval (locname, strlen (locname));
1343 idx = hval % head->namehash_size;
1344 incr = 1 + hval % (head->namehash_size - 2);
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)
1350 if (namehashtab[idx].hashval == hval
1351 && (strcmp (locname,
1352 (char *) ah.addr + namehashtab[idx].name_offset)
1353 == 0))
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;
1361 idx += incr;
1362 if (idx >= head->namehash_size)
1363 idx -= head->namehash_size;
1366 if (namehashtab[idx].name_offset == 0 && ! be_quiet)
1367 error (0, 0, _("locale \"%s\" not in archive"), locname);
1370 close_archive (&ah);
1372 return 0;
1376 struct nameent
1378 char *name;
1379 uint32_t locrec_offset;
1383 struct dataent
1385 const unsigned char *sum;
1386 uint32_t file_offset;
1387 uint32_t nlink;
1391 static int
1392 nameentcmp (const void *a, const void *b)
1394 return strcmp (((const struct nameent *) a)->name,
1395 ((const struct nameent *) b)->name);
1399 static int
1400 dataentcmp (const void *a, const void *b)
1402 if (((const struct dataent *) a)->file_offset
1403 < ((const struct dataent *) b)->file_offset)
1404 return -1;
1406 if (((const struct dataent *) a)->file_offset
1407 > ((const struct dataent *) b)->file_offset)
1408 return 1;
1410 return 0;
1414 void
1415 show_archive_content (int verbose)
1417 struct locarhandle ah;
1418 struct locarhead *head;
1419 struct namehashent *namehashtab;
1420 struct nameent *names;
1421 size_t cnt, used;
1423 /* Open the archive. This call never returns if we cannot
1424 successfully open the archive. */
1425 open_archive (&ah, true);
1427 head = ah.addr;
1429 names = (struct nameent *) xmalloc (head->namehash_used
1430 * sizeof (struct nameent));
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)
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;
1442 /* Sort the names. */
1443 qsort (names, used, sizeof (struct nameent), nameentcmp);
1445 if (verbose)
1447 struct dataent *files;
1448 struct sumhashent *sumhashtab;
1449 int sumused;
1451 files = (struct dataent *) xmalloc (head->sumhash_used
1452 * sizeof (struct dataent));
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)
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;
1465 /* Sort by file locations. */
1466 qsort (files, sumused, sizeof (struct dataent), dataentcmp);
1468 /* Compute nlink fields. */
1469 for (cnt = 0; cnt < used; ++cnt)
1471 struct locrecent *locrec;
1472 int idx;
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)
1486 struct dataent *data, dataent;
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;
1497 /* Print it. */
1498 for (cnt = 0; cnt < used; ++cnt)
1500 struct locrecent *locrec;
1501 int idx, i;
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)
1508 struct dataent *data, dataent;
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;
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]);
1536 else
1537 for (cnt = 0; cnt < used; ++cnt)
1538 puts (names[cnt].name);
1540 close_archive (&ah);
1542 exit (EXIT_SUCCESS);