* sysdeps/i386/tls.h (THREAD_GSCOPE_RESET_FLAG): Use explicit
[glibc.git] / locale / programs / locarchive.c
blob91ea8e24c73958d293922421f8d9a7ef0278487e
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 version 2 as
7 published by the Free Software Foundation.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
22 #include <assert.h>
23 #include <dirent.h>
24 #include <errno.h>
25 #include <error.h>
26 #include <fcntl.h>
27 #include <inttypes.h>
28 #include <libintl.h>
29 #include <locale.h>
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <stdio_ext.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
37 #include <sys/mman.h>
38 #include <sys/param.h>
39 #include <sys/stat.h>
41 #include "../../crypt/md5.h"
42 #include "../localeinfo.h"
43 #include "../locarchive.h"
44 #include "localedef.h"
46 /* Define the hash function. We define the function as static inline.
47 We must change the name so as not to conflict with simple-hash.h. */
48 #define compute_hashval static inline archive_hashval
49 #define hashval_t uint32_t
50 #include "hashval.h"
51 #undef compute_hashval
53 extern const char *output_prefix;
55 #define ARCHIVE_NAME LOCALEDIR "/locale-archive"
57 static const char *locnames[] =
59 #define DEFINE_CATEGORY(category, category_name, items, a) \
60 [category] = category_name,
61 #include "categories.def"
62 #undef DEFINE_CATEGORY
66 /* Size of the initial archive header. */
67 #define INITIAL_NUM_NAMES 900
68 #define INITIAL_SIZE_STRINGS 7500
69 #define INITIAL_NUM_LOCREC 420
70 #define INITIAL_NUM_SUMS 2000
73 static void
74 create_archive (const char *archivefname, struct locarhandle *ah)
76 int fd;
77 char fname[strlen (archivefname) + sizeof (".XXXXXX")];
78 struct locarhead head;
79 void *p;
80 size_t total;
82 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
84 /* Create a temporary file in the correct directory. */
85 fd = mkstemp (fname);
86 if (fd == -1)
87 error (EXIT_FAILURE, errno, _("cannot create temporary file"));
89 /* Create the initial content of the archive. */
90 head.magic = AR_MAGIC;
91 head.serial = 0;
92 head.namehash_offset = sizeof (struct locarhead);
93 head.namehash_used = 0;
94 head.namehash_size = next_prime (INITIAL_NUM_NAMES);
96 head.string_offset = (head.namehash_offset
97 + head.namehash_size * sizeof (struct namehashent));
98 head.string_used = 0;
99 head.string_size = INITIAL_SIZE_STRINGS;
101 head.locrectab_offset = head.string_offset + head.string_size;
102 head.locrectab_used = 0;
103 head.locrectab_size = INITIAL_NUM_LOCREC;
105 head.sumhash_offset = (head.locrectab_offset
106 + head.locrectab_size * sizeof (struct locrecent));
107 head.sumhash_used = 0;
108 head.sumhash_size = next_prime (INITIAL_NUM_SUMS);
110 total = head.sumhash_offset + head.sumhash_size * sizeof (struct sumhashent);
112 /* Write out the header and create room for the other data structures. */
113 if (TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head))) != sizeof (head))
115 int errval = errno;
116 unlink (fname);
117 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
120 if (ftruncate64 (fd, total) != 0)
122 int errval = errno;
123 unlink (fname);
124 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
127 /* Map the header and all the administration data structures. */
128 p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
129 if (p == MAP_FAILED)
131 int errval = errno;
132 unlink (fname);
133 error (EXIT_FAILURE, errval, _("cannot map archive header"));
136 /* Now try to rename it. We don't use the rename function since
137 this would overwrite a file which has been created in
138 parallel. */
139 if (link (fname, archivefname) == -1)
141 int errval = errno;
143 /* We cannot use the just created file. */
144 close (fd);
145 unlink (fname);
147 if (errval == EEXIST)
149 /* There is already an archive. Must have been a localedef run
150 which happened in parallel. Simply open this file then. */
151 open_archive (ah, false);
152 return;
155 error (EXIT_FAILURE, errval, _("failed to create new locale archive"));
158 /* Remove the temporary name. */
159 unlink (fname);
161 /* Make the file globally readable. */
162 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
164 int errval = errno;
165 unlink (archivefname);
166 error (EXIT_FAILURE, errval,
167 _("cannot change mode of new locale archive"));
170 ah->fd = fd;
171 ah->addr = p;
172 ah->len = total;
176 /* This structure and qsort comparator function are used below to sort an
177 old archive's locrec table in order of data position in the file. */
178 struct oldlocrecent
180 unsigned int cnt;
181 struct locrecent *locrec;
184 static int
185 oldlocrecentcmp (const void *a, const void *b)
187 struct locrecent *la = ((const struct oldlocrecent *) a)->locrec;
188 struct locrecent *lb = ((const struct oldlocrecent *) b)->locrec;
189 uint32_t start_a = -1, end_a = 0;
190 uint32_t start_b = -1, end_b = 0;
191 int cnt;
193 for (cnt = 0; cnt < __LC_LAST; ++cnt)
194 if (cnt != LC_ALL)
196 if (la->record[cnt].offset < start_a)
197 start_a = la->record[cnt].offset;
198 if (la->record[cnt].offset + la->record[cnt].len > end_a)
199 end_a = la->record[cnt].offset + la->record[cnt].len;
201 assert (start_a != (uint32_t)-1);
202 assert (end_a != 0);
204 for (cnt = 0; cnt < __LC_LAST; ++cnt)
205 if (cnt != LC_ALL)
207 if (lb->record[cnt].offset < start_b)
208 start_b = lb->record[cnt].offset;
209 if (lb->record[cnt].offset + lb->record[cnt].len > end_b)
210 end_b = lb->record[cnt].offset + lb->record[cnt].len;
212 assert (start_b != (uint32_t)-1);
213 assert (end_b != 0);
215 if (start_a != start_b)
216 return (int)start_a - (int)start_b;
217 return (int)end_a - (int)end_b;
221 /* forward decls for below */
222 static uint32_t add_locale (struct locarhandle *ah, const char *name,
223 locale_data_t data, bool replace);
224 static void add_alias (struct locarhandle *ah, const char *alias,
225 bool replace, const char *oldname,
226 uint32_t *locrec_offset_p);
228 static void
229 enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
231 struct stat64 st;
232 int fd;
233 struct locarhead newhead;
234 size_t total;
235 void *p;
236 unsigned int cnt, loccnt;
237 struct namehashent *oldnamehashtab;
238 struct locrecent *oldlocrectab;
239 struct locarhandle new_ah;
240 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
241 char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
242 char fname[prefix_len + sizeof (ARCHIVE_NAME) + sizeof (".XXXXXX") - 1];
244 if (output_prefix)
245 memcpy (archivefname, output_prefix, prefix_len);
246 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
247 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
249 /* Not all of the old file has to be mapped. Change this now this
250 we will have to access the whole content. */
251 if (fstat64 (ah->fd, &st) != 0
252 || (ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE,
253 MAP_SHARED, ah->fd, 0)) == MAP_FAILED)
254 error (EXIT_FAILURE, errno, _("cannot map locale archive file"));
255 ah->len = st.st_size;
257 /* Create a temporary file in the correct directory. */
258 fd = mkstemp (fname);
259 if (fd == -1)
260 error (EXIT_FAILURE, errno, _("cannot create temporary file"));
262 /* Copy the existing head information. */
263 newhead = *head;
265 /* Create the new archive header. The sizes of the various tables
266 should be double from what is currently used. */
267 newhead.namehash_size = MAX (next_prime (2 * newhead.namehash_used),
268 newhead.namehash_size);
269 if (verbose)
270 printf ("name: size: %u, used: %d, new: size: %u\n",
271 head->namehash_size, head->namehash_used, newhead.namehash_size);
273 newhead.string_offset = (newhead.namehash_offset
274 + (newhead.namehash_size
275 * sizeof (struct namehashent)));
276 /* Keep the string table size aligned to 4 bytes, so that
277 all the struct { uint32_t } types following are happy. */
278 newhead.string_size = MAX ((2 * newhead.string_used + 3) & -4,
279 newhead.string_size);
281 newhead.locrectab_offset = newhead.string_offset + newhead.string_size;
282 newhead.locrectab_size = MAX (2 * newhead.locrectab_used,
283 newhead.locrectab_size);
285 newhead.sumhash_offset = (newhead.locrectab_offset
286 + (newhead.locrectab_size
287 * sizeof (struct locrecent)));
288 newhead.sumhash_size = MAX (next_prime (2 * newhead.sumhash_used),
289 newhead.sumhash_size);
291 total = (newhead.sumhash_offset
292 + newhead.sumhash_size * sizeof (struct sumhashent));
294 /* The new file is empty now. */
295 newhead.namehash_used = 0;
296 newhead.string_used = 0;
297 newhead.locrectab_used = 0;
298 newhead.sumhash_used = 0;
300 /* Write out the header and create room for the other data structures. */
301 if (TEMP_FAILURE_RETRY (write (fd, &newhead, sizeof (newhead)))
302 != sizeof (newhead))
304 int errval = errno;
305 unlink (fname);
306 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
309 if (ftruncate64 (fd, total) != 0)
311 int errval = errno;
312 unlink (fname);
313 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
316 /* Map the header and all the administration data structures. */
317 p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
318 if (p == MAP_FAILED)
320 int errval = errno;
321 unlink (fname);
322 error (EXIT_FAILURE, errval, _("cannot map archive header"));
325 /* Lock the new file. */
326 if (lockf64 (fd, F_LOCK, total) != 0)
328 int errval = errno;
329 unlink (fname);
330 error (EXIT_FAILURE, errval, _("cannot lock new archive"));
333 new_ah.len = total;
334 new_ah.addr = p;
335 new_ah.fd = fd;
337 /* Walk through the hash name hash table to find out what data is
338 still referenced and transfer it into the new file. */
339 oldnamehashtab = (struct namehashent *) ((char *) ah->addr
340 + head->namehash_offset);
341 oldlocrectab = (struct locrecent *) ((char *) ah->addr
342 + head->locrectab_offset);
344 /* Sort the old locrec table in order of data position. */
345 struct oldlocrecent oldlocrecarray[head->namehash_size];
346 for (cnt = 0, loccnt = 0; cnt < head->namehash_size; ++cnt)
347 if (oldnamehashtab[cnt].locrec_offset != 0)
349 oldlocrecarray[loccnt].cnt = cnt;
350 oldlocrecarray[loccnt++].locrec
351 = (struct locrecent *) ((char *) ah->addr
352 + oldnamehashtab[cnt].locrec_offset);
354 qsort (oldlocrecarray, loccnt, sizeof (struct oldlocrecent),
355 oldlocrecentcmp);
357 uint32_t last_locrec_offset = 0;
358 for (cnt = 0; cnt < loccnt; ++cnt)
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;
365 for (idx = 0; idx < __LC_LAST; ++idx)
366 if (idx != LC_ALL)
368 old_data[idx].size = oldlocrec->record[idx].len;
369 old_data[idx].addr
370 = ((char *) ah->addr + oldlocrec->record[idx].offset);
372 __md5_buffer (old_data[idx].addr, old_data[idx].size,
373 old_data[idx].sum);
376 if (cnt > 0 && oldlocrecarray[cnt - 1].locrec == oldlocrec)
378 const char *oldname
379 = ((char *) ah->addr
380 + oldnamehashtab[oldlocrecarray[cnt - 1].cnt].name_offset);
382 add_alias (&new_ah,
383 ((char *) ah->addr
384 + oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset),
385 0, oldname, &last_locrec_offset);
386 continue;
389 last_locrec_offset =
390 add_locale (&new_ah,
391 ((char *) ah->addr
392 + oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset),
393 old_data, 0);
394 if (last_locrec_offset == 0)
395 error (EXIT_FAILURE, 0, _("cannot extend locale archive file"));
398 /* Make the file globally readable. */
399 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
401 int errval = errno;
402 unlink (fname);
403 error (EXIT_FAILURE, errval,
404 _("cannot change mode of resized locale archive"));
407 /* Rename the new file. */
408 if (rename (fname, archivefname) != 0)
410 int errval = errno;
411 unlink (fname);
412 error (EXIT_FAILURE, errval, _("cannot rename new archive"));
415 /* Close the old file. */
416 close_archive (ah);
418 /* Add the information for the new one. */
419 *ah = new_ah;
423 void
424 open_archive (struct locarhandle *ah, bool readonly)
426 struct stat64 st;
427 struct stat64 st2;
428 int fd;
429 struct locarhead head;
430 int retry = 0;
431 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
432 char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
434 if (output_prefix)
435 memcpy (archivefname, output_prefix, prefix_len);
436 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
438 while (1)
440 /* Open the archive. We must have exclusive write access. */
441 fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
442 if (fd == -1)
444 /* Maybe the file does not yet exist. */
445 if (errno == ENOENT)
447 if (readonly)
449 static const struct locarhead nullhead =
451 .namehash_used = 0,
452 .namehash_offset = 0,
453 .namehash_size = 0
456 ah->addr = (void *) &nullhead;
457 ah->fd = -1;
459 else
460 create_archive (archivefname, ah);
462 return;
464 else
465 error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
466 archivefname);
469 if (fstat64 (fd, &st) < 0)
470 error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""),
471 archivefname);
473 if (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1)
475 close (fd);
477 if (retry++ < max_locarchive_open_retry)
479 struct timespec req;
481 /* Wait for a bit. */
482 req.tv_sec = 0;
483 req.tv_nsec = 1000000 * (random () % 500 + 1);
484 (void) nanosleep (&req, NULL);
486 continue;
489 error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
490 archivefname);
493 /* One more check. Maybe another process replaced the archive file
494 with a new, larger one since we opened the file. */
495 if (stat64 (archivefname, &st2) == -1
496 || st.st_dev != st2.st_dev
497 || st.st_ino != st2.st_ino)
499 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
500 close (fd);
501 continue;
504 /* Leave the loop. */
505 break;
508 /* Read the header. */
509 if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
511 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
512 error (EXIT_FAILURE, errno, _("cannot read archive header"));
515 ah->fd = fd;
516 ah->len = (head.sumhash_offset
517 + head.sumhash_size * sizeof (struct sumhashent));
519 /* Now we know how large the administrative information part is.
520 Map all of it. */
521 ah->addr = mmap64 (NULL, ah->len, PROT_READ | (readonly ? 0 : PROT_WRITE),
522 MAP_SHARED, fd, 0);
523 if (ah->addr == MAP_FAILED)
525 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
526 error (EXIT_FAILURE, errno, _("cannot map archive header"));
531 void
532 close_archive (struct locarhandle *ah)
534 if (ah->fd != -1)
536 munmap (ah->addr, ah->len);
537 close (ah->fd);
541 #include "../../intl/explodename.c"
542 #include "../../intl/l10nflist.c"
544 static struct namehashent *
545 insert_name (struct locarhandle *ah,
546 const char *name, size_t name_len, bool replace)
548 const struct locarhead *const head = ah->addr;
549 struct namehashent *namehashtab
550 = (struct namehashent *) ((char *) ah->addr + head->namehash_offset);
551 unsigned int insert_idx, idx, incr;
553 /* Hash value of the locale name. */
554 uint32_t hval = archive_hashval (name, name_len);
556 insert_idx = -1;
557 idx = hval % head->namehash_size;
558 incr = 1 + hval % (head->namehash_size - 2);
560 /* If the name_offset field is zero this means this is a
561 deleted entry and therefore no entry can be found. */
562 while (namehashtab[idx].name_offset != 0)
564 if (namehashtab[idx].hashval == hval
565 && strcmp (name,
566 (char *) ah->addr + namehashtab[idx].name_offset) == 0)
568 /* Found the entry. */
569 if (namehashtab[idx].locrec_offset != 0 && ! replace)
571 if (! be_quiet)
572 error (0, 0, _("locale '%s' already exists"), name);
573 return NULL;
576 break;
579 if (namehashtab[idx].hashval == hval && ! be_quiet)
581 error (0, 0, "hash collision (%u) %s, %s",
582 hval, name, (char *) ah->addr + namehashtab[idx].name_offset);
585 /* Remember the first place we can insert the new entry. */
586 if (namehashtab[idx].locrec_offset == 0 && insert_idx == -1)
587 insert_idx = idx;
589 idx += incr;
590 if (idx >= head->namehash_size)
591 idx -= head->namehash_size;
594 /* Add as early as possible. */
595 if (insert_idx != -1)
596 idx = insert_idx;
598 namehashtab[idx].hashval = hval; /* no-op if replacing an old entry. */
599 return &namehashtab[idx];
602 static void
603 add_alias (struct locarhandle *ah, const char *alias, bool replace,
604 const char *oldname, uint32_t *locrec_offset_p)
606 uint32_t locrec_offset = *locrec_offset_p;
607 struct locarhead *head = ah->addr;
608 const size_t name_len = strlen (alias);
609 struct namehashent *namehashent = insert_name (ah, alias, strlen (alias),
610 replace);
611 if (namehashent == NULL && ! replace)
612 return;
614 if (namehashent->name_offset == 0)
616 /* We are adding a new hash entry for this alias.
617 Determine whether we have to resize the file. */
618 if (head->string_used + name_len + 1 > head->string_size
619 || 100 * head->namehash_used > 75 * head->namehash_size)
621 /* The current archive is not large enough. */
622 enlarge_archive (ah, head);
624 /* The locrecent might have moved, so we have to look up
625 the old name afresh. */
626 namehashent = insert_name (ah, oldname, strlen (oldname), true);
627 assert (namehashent->name_offset != 0);
628 assert (namehashent->locrec_offset != 0);
629 *locrec_offset_p = namehashent->locrec_offset;
631 /* Tail call to try the whole thing again. */
632 add_alias (ah, alias, replace, oldname, locrec_offset_p);
633 return;
636 /* Add the name string. */
637 memcpy (ah->addr + head->string_offset + head->string_used,
638 alias, name_len + 1);
639 namehashent->name_offset = head->string_offset + head->string_used;
640 head->string_used += name_len + 1;
642 ++head->namehash_used;
645 if (namehashent->locrec_offset != 0)
647 /* Replacing an existing entry.
648 Mark that we are no longer using the old locrecent. */
649 struct locrecent *locrecent
650 = (struct locrecent *) ((char *) ah->addr
651 + namehashent->locrec_offset);
652 --locrecent->refs;
655 /* Point this entry at the locrecent installed for the main name. */
656 namehashent->locrec_offset = locrec_offset;
659 static int /* qsort comparator used below */
660 cmpcategorysize (const void *a, const void *b)
662 if (*(const void **) a == NULL)
663 return 1;
664 if (*(const void **) b == NULL)
665 return -1;
666 return ((*(const struct locale_category_data **) a)->size
667 - (*(const struct locale_category_data **) b)->size);
670 /* Check the content of the archive for duplicates. Add the content
671 of the files if necessary. Returns the locrec_offset. */
672 static uint32_t
673 add_locale (struct locarhandle *ah,
674 const char *name, locale_data_t data, bool replace)
676 /* First look for the name. If it already exists and we are not
677 supposed to replace it don't do anything. If it does not exist
678 we have to allocate a new locale record. */
679 size_t name_len = strlen (name);
680 uint32_t file_offsets[__LC_LAST];
681 unsigned int num_new_offsets = 0;
682 struct sumhashent *sumhashtab;
683 uint32_t hval;
684 unsigned int cnt, idx;
685 struct locarhead *head;
686 struct namehashent *namehashent;
687 unsigned int incr;
688 struct locrecent *locrecent;
689 off64_t lastoffset;
690 char *ptr;
691 struct locale_category_data *size_order[__LC_LAST];
692 const size_t pagesz = getpagesize ();
693 int small_mask;
695 head = ah->addr;
696 sumhashtab = (struct sumhashent *) ((char *) ah->addr
697 + head->sumhash_offset);
699 memset (file_offsets, 0, sizeof (file_offsets));
701 size_order[LC_ALL] = NULL;
702 for (cnt = 0; cnt < __LC_LAST; ++cnt)
703 if (cnt != LC_ALL)
704 size_order[cnt] = &data[cnt];
706 /* Sort the array in ascending order of data size. */
707 qsort (size_order, __LC_LAST, sizeof size_order[0], cmpcategorysize);
709 small_mask = 0;
710 data[LC_ALL].size = 0;
711 for (cnt = 0; cnt < __LC_LAST; ++cnt)
712 if (size_order[cnt] != NULL)
714 const size_t rounded_size = (size_order[cnt]->size + 15) & -16;
715 if (data[LC_ALL].size + rounded_size > 2 * pagesz)
717 /* This category makes the small-categories block
718 stop being small, so this is the end of the road. */
720 size_order[cnt++] = NULL;
721 while (cnt < __LC_LAST);
722 break;
724 data[LC_ALL].size += rounded_size;
725 small_mask |= 1 << (size_order[cnt] - data);
728 /* Copy the data for all the small categories into the LC_ALL
729 pseudo-category. */
731 data[LC_ALL].addr = alloca (data[LC_ALL].size);
732 memset (data[LC_ALL].addr, 0, data[LC_ALL].size);
734 ptr = data[LC_ALL].addr;
735 for (cnt = 0; cnt < __LC_LAST; ++cnt)
736 if (small_mask & (1 << cnt))
738 memcpy (ptr, data[cnt].addr, data[cnt].size);
739 ptr += (data[cnt].size + 15) & -16;
741 __md5_buffer (data[LC_ALL].addr, data[LC_ALL].size, data[LC_ALL].sum);
743 /* For each locale category data set determine whether the same data
744 is already somewhere in the archive. */
745 for (cnt = 0; cnt < __LC_LAST; ++cnt)
746 if (small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
748 ++num_new_offsets;
750 /* Compute the hash value of the checksum to determine a
751 starting point for the search in the MD5 hash value
752 table. */
753 hval = archive_hashval (data[cnt].sum, 16);
755 idx = hval % head->sumhash_size;
756 incr = 1 + hval % (head->sumhash_size - 2);
758 while (sumhashtab[idx].file_offset != 0)
760 if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
762 /* Found it. */
763 file_offsets[cnt] = sumhashtab[idx].file_offset;
764 --num_new_offsets;
765 break;
768 idx += incr;
769 if (idx >= head->sumhash_size)
770 idx -= head->sumhash_size;
774 /* Find a slot for the locale name in the hash table. */
775 namehashent = insert_name (ah, name, name_len, replace);
776 if (namehashent == NULL) /* Already exists and !REPLACE. */
777 return 0;
779 /* Determine whether we have to resize the file. */
780 if (100 * (head->sumhash_used + num_new_offsets) > 75 * head->sumhash_size
781 || (namehashent->locrec_offset == 0
782 && (head->locrectab_used == head->locrectab_size
783 || head->string_used + name_len + 1 > head->string_size
784 || 100 * head->namehash_used > 75 * head->namehash_size)))
786 /* The current archive is not large enough. */
787 enlarge_archive (ah, head);
788 return add_locale (ah, name, data, replace);
791 /* Add the locale data which is not yet in the archive. */
792 for (cnt = 0, lastoffset = 0; cnt < __LC_LAST; ++cnt)
793 if ((small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
794 && file_offsets[cnt] == 0)
796 /* The data for this section is not yet available in the
797 archive. Append it. */
798 off64_t lastpos;
799 uint32_t md5hval;
801 lastpos = lseek64 (ah->fd, 0, SEEK_END);
802 if (lastpos == (off64_t) -1)
803 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
805 /* If block of small categories would cross page boundary,
806 align it unless it immediately follows a large category. */
807 if (cnt == LC_ALL && lastoffset != lastpos
808 && ((((lastpos & (pagesz - 1)) + data[cnt].size + pagesz - 1)
809 & -pagesz)
810 > ((data[cnt].size + pagesz - 1) & -pagesz)))
812 size_t sz = pagesz - (lastpos & (pagesz - 1));
813 char *zeros = alloca (sz);
815 memset (zeros, 0, sz);
816 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, sz) != sz))
817 error (EXIT_FAILURE, errno,
818 _("cannot add to locale archive"));
820 lastpos += sz;
823 /* Align all data to a 16 byte boundary. */
824 if ((lastpos & 15) != 0)
826 static const char zeros[15] = { 0, };
828 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, 16 - (lastpos & 15)))
829 != 16 - (lastpos & 15))
830 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
832 lastpos += 16 - (lastpos & 15);
835 /* Remember the position. */
836 file_offsets[cnt] = lastpos;
837 lastoffset = lastpos + data[cnt].size;
839 /* Write the data. */
840 if (TEMP_FAILURE_RETRY (write (ah->fd, data[cnt].addr, data[cnt].size))
841 != data[cnt].size)
842 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
844 /* Add the hash value to the hash table. */
845 md5hval = archive_hashval (data[cnt].sum, 16);
847 idx = md5hval % head->sumhash_size;
848 incr = 1 + md5hval % (head->sumhash_size - 2);
850 while (sumhashtab[idx].file_offset != 0)
852 idx += incr;
853 if (idx >= head->sumhash_size)
854 idx -= head->sumhash_size;
857 memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
858 sumhashtab[idx].file_offset = file_offsets[cnt];
860 ++head->sumhash_used;
863 lastoffset = file_offsets[LC_ALL];
864 for (cnt = 0; cnt < __LC_LAST; ++cnt)
865 if (small_mask & (1 << cnt))
867 file_offsets[cnt] = lastoffset;
868 lastoffset += (data[cnt].size + 15) & -16;
871 if (namehashent->name_offset == 0)
873 /* Add the name string. */
874 memcpy ((char *) ah->addr + head->string_offset + head->string_used,
875 name, name_len + 1);
876 namehashent->name_offset = head->string_offset + head->string_used;
877 head->string_used += name_len + 1;
878 ++head->namehash_used;
881 if (namehashent->locrec_offset == 0)
883 /* Allocate a name location record. */
884 namehashent->locrec_offset = (head->locrectab_offset
885 + (head->locrectab_used++
886 * sizeof (struct locrecent)));
887 locrecent = (struct locrecent *) ((char *) ah->addr
888 + namehashent->locrec_offset);
889 locrecent->refs = 1;
891 else
893 /* If there are other aliases pointing to this locrecent,
894 we still need a new one. If not, reuse the old one. */
896 locrecent = (struct locrecent *) ((char *) ah->addr
897 + namehashent->locrec_offset);
898 if (locrecent->refs > 1)
900 --locrecent->refs;
901 namehashent->locrec_offset = (head->locrectab_offset
902 + (head->locrectab_used++
903 * sizeof (struct locrecent)));
904 locrecent = (struct locrecent *) ((char *) ah->addr
905 + namehashent->locrec_offset);
906 locrecent->refs = 1;
910 /* Fill in the table with the locations of the locale data. */
911 for (cnt = 0; cnt < __LC_LAST; ++cnt)
913 locrecent->record[cnt].offset = file_offsets[cnt];
914 locrecent->record[cnt].len = data[cnt].size;
917 return namehashent->locrec_offset;
921 /* Check the content of the archive for duplicates. Add the content
922 of the files if necessary. Add all the names, possibly overwriting
923 old files. */
925 add_locale_to_archive (ah, name, data, replace)
926 struct locarhandle *ah;
927 const char *name;
928 locale_data_t data;
929 bool replace;
931 char *normalized_name = NULL;
932 uint32_t locrec_offset;
934 /* First analyze the name to decide how to archive it. */
935 const char *language;
936 const char *modifier;
937 const char *territory;
938 const char *codeset;
939 const char *normalized_codeset;
940 int mask = _nl_explode_name (strdupa (name),
941 &language, &modifier, &territory,
942 &codeset, &normalized_codeset);
944 if (mask & XPG_NORM_CODESET)
945 /* This name contains a codeset in unnormalized form.
946 We will store it in the archive with a normalized name. */
947 asprintf (&normalized_name, "%s%s%s.%s%s%s",
948 language, territory == NULL ? "" : "_", territory ?: "",
949 (mask & XPG_NORM_CODESET) ? normalized_codeset : codeset,
950 modifier == NULL ? "" : "@", modifier ?: "");
952 /* This call does the main work. */
953 locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
954 if (locrec_offset == 0)
956 free (normalized_name);
957 if (mask & XPG_NORM_CODESET)
958 free ((char *) normalized_codeset);
959 return -1;
962 if ((mask & XPG_CODESET) == 0)
964 /* This name lacks a codeset, so determine the locale's codeset and
965 add an alias for its name with normalized codeset appended. */
967 const struct
969 unsigned int magic;
970 unsigned int nstrings;
971 unsigned int strindex[0];
972 } *filedata = data[LC_CTYPE].addr;
973 codeset = (char *) filedata
974 + filedata->strindex[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME)];
975 char *normalized_codeset_name = NULL;
977 normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
978 mask |= XPG_NORM_CODESET;
980 asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
981 language, territory == NULL ? "" : "_", territory ?: "",
982 normalized_codeset,
983 modifier == NULL ? "" : "@", modifier ?: "");
985 add_alias (ah, normalized_codeset_name, replace,
986 normalized_name ?: name, &locrec_offset);
987 free (normalized_codeset_name);
990 /* Now read the locale.alias files looking for lines whose
991 right hand side matches our name after normalization. */
992 if (alias_file != NULL)
994 FILE *fp;
995 fp = fopen (alias_file, "rm");
996 if (fp == NULL)
997 error (1, errno, _("locale alias file `%s' not found"),
998 alias_file);
1000 /* No threads present. */
1001 __fsetlocking (fp, FSETLOCKING_BYCALLER);
1003 while (! feof_unlocked (fp))
1005 /* It is a reasonable approach to use a fix buffer here
1006 because
1007 a) we are only interested in the first two fields
1008 b) these fields must be usable as file names and so must
1009 not be that long */
1010 char buf[BUFSIZ];
1011 char *alias;
1012 char *value;
1013 char *cp;
1015 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1016 /* EOF reached. */
1017 break;
1019 cp = buf;
1020 /* Ignore leading white space. */
1021 while (isspace (cp[0]) && cp[0] != '\n')
1022 ++cp;
1024 /* A leading '#' signals a comment line. */
1025 if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
1027 alias = cp++;
1028 while (cp[0] != '\0' && !isspace (cp[0]))
1029 ++cp;
1030 /* Terminate alias name. */
1031 if (cp[0] != '\0')
1032 *cp++ = '\0';
1034 /* Now look for the beginning of the value. */
1035 while (isspace (cp[0]))
1036 ++cp;
1038 if (cp[0] != '\0')
1040 value = cp++;
1041 while (cp[0] != '\0' && !isspace (cp[0]))
1042 ++cp;
1043 /* Terminate value. */
1044 if (cp[0] == '\n')
1046 /* This has to be done to make the following
1047 test for the end of line possible. We are
1048 looking for the terminating '\n' which do not
1049 overwrite here. */
1050 *cp++ = '\0';
1051 *cp = '\n';
1053 else if (cp[0] != '\0')
1054 *cp++ = '\0';
1056 /* Does this alias refer to our locale? We will
1057 normalize the right hand side and compare the
1058 elements of the normalized form. */
1060 const char *rhs_language;
1061 const char *rhs_modifier;
1062 const char *rhs_territory;
1063 const char *rhs_codeset;
1064 const char *rhs_normalized_codeset;
1065 int rhs_mask = _nl_explode_name (value,
1066 &rhs_language,
1067 &rhs_modifier,
1068 &rhs_territory,
1069 &rhs_codeset,
1070 &rhs_normalized_codeset);
1071 if (!strcmp (language, rhs_language)
1072 && ((rhs_mask & XPG_CODESET)
1073 /* He has a codeset, it must match normalized. */
1074 ? !strcmp ((mask & XPG_NORM_CODESET)
1075 ? normalized_codeset : codeset,
1076 (rhs_mask & XPG_NORM_CODESET)
1077 ? rhs_normalized_codeset : rhs_codeset)
1078 /* He has no codeset, we must also have none. */
1079 : (mask & XPG_CODESET) == 0)
1080 /* Codeset (or lack thereof) matches. */
1081 && !strcmp (territory ?: "", rhs_territory ?: "")
1082 && !strcmp (modifier ?: "", rhs_modifier ?: ""))
1083 /* We have a winner. */
1084 add_alias (ah, alias, replace,
1085 normalized_name ?: name, &locrec_offset);
1086 if (rhs_mask & XPG_NORM_CODESET)
1087 free ((char *) rhs_normalized_codeset);
1092 /* Possibly not the whole line fits into the buffer.
1093 Ignore the rest of the line. */
1094 while (strchr (cp, '\n') == NULL)
1096 cp = buf;
1097 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1098 /* Make sure the inner loop will be left. The outer
1099 loop will exit at the `feof' test. */
1100 *cp = '\n';
1104 fclose (fp);
1107 free (normalized_name);
1109 if (mask & XPG_NORM_CODESET)
1110 free ((char *) normalized_codeset);
1112 return 0;
1117 add_locales_to_archive (nlist, list, replace)
1118 size_t nlist;
1119 char *list[];
1120 bool replace;
1122 struct locarhandle ah;
1123 int result = 0;
1125 /* Open the archive. This call never returns if we cannot
1126 successfully open the archive. */
1127 open_archive (&ah, false);
1129 while (nlist-- > 0)
1131 const char *fname = *list++;
1132 size_t fnamelen = strlen (fname);
1133 struct stat64 st;
1134 DIR *dirp;
1135 struct dirent64 *d;
1136 int seen;
1137 locale_data_t data;
1138 int cnt;
1140 if (! be_quiet)
1141 printf (_("Adding %s\n"), fname);
1143 /* First see whether this really is a directory and whether it
1144 contains all the require locale category files. */
1145 if (stat64 (fname, &st) < 0)
1147 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
1148 strerror (errno));
1149 continue;
1151 if (!S_ISDIR (st.st_mode))
1153 error (0, 0, _("\"%s\" is no directory; ignored"), fname);
1154 continue;
1157 dirp = opendir (fname);
1158 if (dirp == NULL)
1160 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1161 fname, strerror (errno));
1162 continue;
1165 seen = 0;
1166 while ((d = readdir64 (dirp)) != NULL)
1168 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1169 if (cnt != LC_ALL)
1170 if (strcmp (d->d_name, locnames[cnt]) == 0)
1172 unsigned char d_type;
1174 /* We have an object of the required name. If it's
1175 a directory we have to look at a file with the
1176 prefix "SYS_". Otherwise we have found what we
1177 are looking for. */
1178 #ifdef _DIRENT_HAVE_D_TYPE
1179 d_type = d->d_type;
1181 if (d_type != DT_REG)
1182 #endif
1184 char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
1186 #ifdef _DIRENT_HAVE_D_TYPE
1187 if (d_type == DT_UNKNOWN)
1188 #endif
1190 strcpy (stpcpy (stpcpy (fullname, fname), "/"),
1191 d->d_name);
1193 if (stat64 (fullname, &st) == -1)
1194 /* We cannot stat the file, ignore it. */
1195 break;
1197 d_type = IFTODT (st.st_mode);
1200 if (d_type == DT_DIR)
1202 /* We have to do more tests. The file is a
1203 directory and it therefore must contain a
1204 regular file with the same name except a
1205 "SYS_" prefix. */
1206 char *t = stpcpy (stpcpy (fullname, fname), "/");
1207 strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
1208 d->d_name);
1210 if (stat64 (fullname, &st) == -1)
1211 /* There is no SYS_* file or we cannot
1212 access it. */
1213 break;
1215 d_type = IFTODT (st.st_mode);
1219 /* If we found a regular file (eventually after
1220 following a symlink) we are successful. */
1221 if (d_type == DT_REG)
1222 ++seen;
1223 break;
1227 closedir (dirp);
1229 if (seen != __LC_LAST - 1)
1231 /* We don't have all locale category files. Ignore the name. */
1232 error (0, 0, _("incomplete set of locale files in \"%s\""),
1233 fname);
1234 continue;
1237 /* Add the files to the archive. To do this we first compute
1238 sizes and the MD5 sums of all the files. */
1239 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1240 if (cnt != LC_ALL)
1242 char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
1243 int fd;
1245 strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
1246 fd = open64 (fullname, O_RDONLY);
1247 if (fd == -1 || fstat64 (fd, &st) == -1)
1249 /* Cannot read the file. */
1250 if (fd != -1)
1251 close (fd);
1252 break;
1255 if (S_ISDIR (st.st_mode))
1257 char *t;
1258 close (fd);
1259 t = stpcpy (stpcpy (fullname, fname), "/");
1260 strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
1261 locnames[cnt]);
1263 fd = open64 (fullname, O_RDONLY);
1264 if (fd == -1 || fstat64 (fd, &st) == -1
1265 || !S_ISREG (st.st_mode))
1267 if (fd != -1)
1268 close (fd);
1269 break;
1273 /* Map the file. */
1274 data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
1275 fd, 0);
1276 if (data[cnt].addr == MAP_FAILED)
1278 /* Cannot map it. */
1279 close (fd);
1280 break;
1283 data[cnt].size = st.st_size;
1284 __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
1286 /* We don't need the file descriptor anymore. */
1287 close (fd);
1290 if (cnt != __LC_LAST)
1292 while (cnt-- > 0)
1293 if (cnt != LC_ALL)
1294 munmap (data[cnt].addr, data[cnt].size);
1296 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);
1298 continue;
1301 result |= add_locale_to_archive (&ah, basename (fname), data, replace);
1303 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1304 if (cnt != LC_ALL)
1305 munmap (data[cnt].addr, data[cnt].size);
1308 /* We are done. */
1309 close_archive (&ah);
1311 return result;
1316 delete_locales_from_archive (nlist, list)
1317 size_t nlist;
1318 char *list[];
1320 struct locarhandle ah;
1321 struct locarhead *head;
1322 struct namehashent *namehashtab;
1324 /* Open the archive. This call never returns if we cannot
1325 successfully open the archive. */
1326 open_archive (&ah, false);
1328 head = ah.addr;
1329 namehashtab = (struct namehashent *) ((char *) ah.addr
1330 + head->namehash_offset);
1332 while (nlist-- > 0)
1334 const char *locname = *list++;
1335 uint32_t hval;
1336 unsigned int idx;
1337 unsigned int incr;
1339 /* Search for this locale in the archive. */
1340 hval = archive_hashval (locname, strlen (locname));
1342 idx = hval % head->namehash_size;
1343 incr = 1 + hval % (head->namehash_size - 2);
1345 /* If the name_offset field is zero this means this is no
1346 deleted entry and therefore no entry can be found. */
1347 while (namehashtab[idx].name_offset != 0)
1349 if (namehashtab[idx].hashval == hval
1350 && (strcmp (locname,
1351 (char *) ah.addr + namehashtab[idx].name_offset)
1352 == 0))
1354 /* Found the entry. Now mark it as removed by zero-ing
1355 the reference to the locale record. */
1356 namehashtab[idx].locrec_offset = 0;
1357 break;
1360 idx += incr;
1361 if (idx >= head->namehash_size)
1362 idx -= head->namehash_size;
1365 if (namehashtab[idx].name_offset == 0 && ! be_quiet)
1366 error (0, 0, _("locale \"%s\" not in archive"), locname);
1369 close_archive (&ah);
1371 return 0;
1375 struct nameent
1377 char *name;
1378 uint32_t locrec_offset;
1382 struct dataent
1384 const unsigned char *sum;
1385 uint32_t file_offset;
1386 uint32_t nlink;
1390 static int
1391 nameentcmp (const void *a, const void *b)
1393 return strcmp (((const struct nameent *) a)->name,
1394 ((const struct nameent *) b)->name);
1398 static int
1399 dataentcmp (const void *a, const void *b)
1401 if (((const struct dataent *) a)->file_offset
1402 < ((const struct dataent *) b)->file_offset)
1403 return -1;
1405 if (((const struct dataent *) a)->file_offset
1406 > ((const struct dataent *) b)->file_offset)
1407 return 1;
1409 return 0;
1413 void
1414 show_archive_content (int verbose)
1416 struct locarhandle ah;
1417 struct locarhead *head;
1418 struct namehashent *namehashtab;
1419 struct nameent *names;
1420 size_t cnt, used;
1422 /* Open the archive. This call never returns if we cannot
1423 successfully open the archive. */
1424 open_archive (&ah, true);
1426 head = ah.addr;
1428 names = (struct nameent *) xmalloc (head->namehash_used
1429 * sizeof (struct nameent));
1431 namehashtab = (struct namehashent *) ((char *) ah.addr
1432 + head->namehash_offset);
1433 for (cnt = used = 0; cnt < head->namehash_size; ++cnt)
1434 if (namehashtab[cnt].locrec_offset != 0)
1436 assert (used < head->namehash_used);
1437 names[used].name = ah.addr + namehashtab[cnt].name_offset;
1438 names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
1441 /* Sort the names. */
1442 qsort (names, used, sizeof (struct nameent), nameentcmp);
1444 if (verbose)
1446 struct dataent *files;
1447 struct sumhashent *sumhashtab;
1448 int sumused;
1450 files = (struct dataent *) xmalloc (head->sumhash_used
1451 * sizeof (struct dataent));
1453 sumhashtab = (struct sumhashent *) ((char *) ah.addr
1454 + head->sumhash_offset);
1455 for (cnt = sumused = 0; cnt < head->sumhash_size; ++cnt)
1456 if (sumhashtab[cnt].file_offset != 0)
1458 assert (sumused < head->sumhash_used);
1459 files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
1460 files[sumused].file_offset = sumhashtab[cnt].file_offset;
1461 files[sumused++].nlink = 0;
1464 /* Sort by file locations. */
1465 qsort (files, sumused, sizeof (struct dataent), dataentcmp);
1467 /* Compute nlink fields. */
1468 for (cnt = 0; cnt < used; ++cnt)
1470 struct locrecent *locrec;
1471 int idx;
1473 locrec = (struct locrecent *) ((char *) ah.addr
1474 + names[cnt].locrec_offset);
1475 for (idx = 0; idx < __LC_LAST; ++idx)
1476 if (locrec->record[LC_ALL].offset != 0
1477 ? (idx == LC_ALL
1478 || (locrec->record[idx].offset
1479 < locrec->record[LC_ALL].offset)
1480 || (locrec->record[idx].offset + locrec->record[idx].len
1481 > (locrec->record[LC_ALL].offset
1482 + locrec->record[LC_ALL].len)))
1483 : idx != LC_ALL)
1485 struct dataent *data, dataent;
1487 dataent.file_offset = locrec->record[idx].offset;
1488 data = (struct dataent *) bsearch (&dataent, files, sumused,
1489 sizeof (struct dataent),
1490 dataentcmp);
1491 assert (data != NULL);
1492 ++data->nlink;
1496 /* Print it. */
1497 for (cnt = 0; cnt < used; ++cnt)
1499 struct locrecent *locrec;
1500 int idx, i;
1502 locrec = (struct locrecent *) ((char *) ah.addr
1503 + names[cnt].locrec_offset);
1504 for (idx = 0; idx < __LC_LAST; ++idx)
1505 if (idx != LC_ALL)
1507 struct dataent *data, dataent;
1509 dataent.file_offset = locrec->record[idx].offset;
1510 if (locrec->record[LC_ALL].offset != 0
1511 && dataent.file_offset >= locrec->record[LC_ALL].offset
1512 && (dataent.file_offset + locrec->record[idx].len
1513 <= (locrec->record[LC_ALL].offset
1514 + locrec->record[LC_ALL].len)))
1515 dataent.file_offset = locrec->record[LC_ALL].offset;
1517 data = (struct dataent *) bsearch (&dataent, files, sumused,
1518 sizeof (struct dataent),
1519 dataentcmp);
1520 printf ("%6d %7x %3d%c ",
1521 locrec->record[idx].len, locrec->record[idx].offset,
1522 data->nlink,
1523 dataent.file_offset == locrec->record[LC_ALL].offset
1524 ? '+' : ' ');
1525 for (i = 0; i < 16; i += 4)
1526 printf ("%02x%02x%02x%02x",
1527 data->sum[i], data->sum[i + 1],
1528 data->sum[i + 2], data->sum[i + 3]);
1529 printf (" %s/%s\n", names[cnt].name,
1530 idx == LC_MESSAGES ? "LC_MESSAGES/SYS_LC_MESSAGES"
1531 : locnames[idx]);
1535 else
1536 for (cnt = 0; cnt < used; ++cnt)
1537 puts (names[cnt].name);
1539 close_archive (&ah);
1541 exit (EXIT_SUCCESS);