1 /* Copyright (C) 2002, 2003, 2005, 2007, 2009 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. */
33 #include <stdio_ext.h>
39 #include <sys/param.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
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 /* Size of the reserved address space area. */
75 #define RESERVE_MMAP_SIZE 512 * 1024 * 1024
79 create_archive (const char *archivefname
, struct locarhandle
*ah
)
82 char fname
[strlen (archivefname
) + sizeof (".XXXXXX")];
83 struct locarhead head
;
87 strcpy (stpcpy (fname
, archivefname
), ".XXXXXX");
89 /* Create a temporary file in the correct directory. */
92 error (EXIT_FAILURE
, errno
, _("cannot create temporary file"));
94 /* Create the initial content of the archive. */
95 head
.magic
= AR_MAGIC
;
97 head
.namehash_offset
= sizeof (struct locarhead
);
98 head
.namehash_used
= 0;
99 head
.namehash_size
= next_prime (INITIAL_NUM_NAMES
);
101 head
.string_offset
= (head
.namehash_offset
102 + head
.namehash_size
* sizeof (struct namehashent
));
103 head
.string_used
= 0;
104 head
.string_size
= INITIAL_SIZE_STRINGS
;
106 head
.locrectab_offset
= head
.string_offset
+ head
.string_size
;
107 head
.locrectab_used
= 0;
108 head
.locrectab_size
= INITIAL_NUM_LOCREC
;
110 head
.sumhash_offset
= (head
.locrectab_offset
111 + head
.locrectab_size
* sizeof (struct locrecent
));
112 head
.sumhash_used
= 0;
113 head
.sumhash_size
= next_prime (INITIAL_NUM_SUMS
);
115 total
= head
.sumhash_offset
+ head
.sumhash_size
* sizeof (struct sumhashent
);
117 /* Write out the header and create room for the other data structures. */
118 if (TEMP_FAILURE_RETRY (write (fd
, &head
, sizeof (head
))) != sizeof (head
))
122 error (EXIT_FAILURE
, errval
, _("cannot initialize archive file"));
125 if (ftruncate64 (fd
, total
) != 0)
129 error (EXIT_FAILURE
, errval
, _("cannot resize archive file"));
132 /* To prepare for enlargements of the mmaped area reserve some
134 size_t reserved
= RESERVE_MMAP_SIZE
;
137 && ((p
= mmap64 (NULL
, reserved
, PROT_NONE
, MAP_ANON
, -1, 0))
146 /* Map the header and all the administration data structures. */
147 p
= mmap64 (p
, total
, PROT_READ
| PROT_WRITE
, MAP_SHARED
| xflags
, fd
, 0);
152 error (EXIT_FAILURE
, errval
, _("cannot map archive header"));
155 /* Now try to rename it. We don't use the rename function since
156 this would overwrite a file which has been created in
158 if (link (fname
, archivefname
) == -1)
162 /* We cannot use the just created file. */
166 if (errval
== EEXIST
)
168 /* There is already an archive. Must have been a localedef run
169 which happened in parallel. Simply open this file then. */
170 open_archive (ah
, false);
174 error (EXIT_FAILURE
, errval
, _("failed to create new locale archive"));
177 /* Remove the temporary name. */
180 /* Make the file globally readable. */
181 if (fchmod (fd
, S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
) == -1)
184 unlink (archivefname
);
185 error (EXIT_FAILURE
, errval
,
186 _("cannot change mode of new locale archive"));
192 ah
->reserved
= reserved
;
196 /* This structure and qsort comparator function are used below to sort an
197 old archive's locrec table in order of data position in the file. */
201 struct locrecent
*locrec
;
205 oldlocrecentcmp (const void *a
, const void *b
)
207 struct locrecent
*la
= ((const struct oldlocrecent
*) a
)->locrec
;
208 struct locrecent
*lb
= ((const struct oldlocrecent
*) b
)->locrec
;
209 uint32_t start_a
= -1, end_a
= 0;
210 uint32_t start_b
= -1, end_b
= 0;
213 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
216 if (la
->record
[cnt
].offset
< start_a
)
217 start_a
= la
->record
[cnt
].offset
;
218 if (la
->record
[cnt
].offset
+ la
->record
[cnt
].len
> end_a
)
219 end_a
= la
->record
[cnt
].offset
+ la
->record
[cnt
].len
;
221 assert (start_a
!= (uint32_t)-1);
224 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
227 if (lb
->record
[cnt
].offset
< start_b
)
228 start_b
= lb
->record
[cnt
].offset
;
229 if (lb
->record
[cnt
].offset
+ lb
->record
[cnt
].len
> end_b
)
230 end_b
= lb
->record
[cnt
].offset
+ lb
->record
[cnt
].len
;
232 assert (start_b
!= (uint32_t)-1);
235 if (start_a
!= start_b
)
236 return (int)start_a
- (int)start_b
;
237 return (int)end_a
- (int)end_b
;
241 /* forward decls for below */
242 static uint32_t add_locale (struct locarhandle
*ah
, const char *name
,
243 locale_data_t data
, bool replace
);
244 void add_alias (struct locarhandle
*ah
, const char *alias
,
245 bool replace
, const char *oldname
,
246 uint32_t *locrec_offset_p
);
250 file_data_available_p (struct locarhandle
*ah
, uint32_t offset
, uint32_t size
)
252 if (offset
< ah
->mmaped
&& offset
+ size
<= ah
->mmaped
)
256 if (fstat64 (ah
->fd
, &st
) != 0)
259 if (st
.st_size
> ah
->reserved
)
262 void *p
= mremap (ah
->addr
, ah
->mmaped
, st
.st_size
,
263 MREMAP_FIXED
| MREMAP_MAYMOVE
, ah
->addr
);
267 ah
->mmaped
= st
.st_size
;
273 compare_from_file (struct locarhandle
*ah
, void *p1
, uint32_t offset2
,
276 void *p2
= xmalloc (size
);
277 if (pread (ah
->fd
, p2
, size
, offset2
) != size
)
278 WITH_CUR_LOCALE (error (4, errno
,
279 _("cannot read data from locale archive")));
281 int res
= memcmp (p1
, p2
, size
);
288 enlarge_archive (struct locarhandle
*ah
, const struct locarhead
*head
)
292 struct locarhead newhead
;
295 unsigned int cnt
, loccnt
;
296 struct namehashent
*oldnamehashtab
;
297 struct locrecent
*oldlocrectab
;
298 struct locarhandle new_ah
;
299 size_t prefix_len
= output_prefix
? strlen (output_prefix
) : 0;
300 char archivefname
[prefix_len
+ sizeof (ARCHIVE_NAME
)];
301 char fname
[prefix_len
+ sizeof (ARCHIVE_NAME
) + sizeof (".XXXXXX") - 1];
304 memcpy (archivefname
, output_prefix
, prefix_len
);
305 strcpy (archivefname
+ prefix_len
, ARCHIVE_NAME
);
306 strcpy (stpcpy (fname
, archivefname
), ".XXXXXX");
308 /* Not all of the old file has to be mapped. Change this now this
309 we will have to access the whole content. */
310 if (fstat64 (ah
->fd
, &st
) != 0)
312 error (EXIT_FAILURE
, errno
, _("cannot map locale archive file"));
314 if (st
.st_size
< ah
->reserved
)
315 ah
->addr
= mremap (ah
->addr
, ah
->mmaped
, st
.st_size
,
316 MREMAP_MAYMOVE
| MREMAP_FIXED
, ah
->addr
);
319 munmap (ah
->addr
, ah
->reserved
);
320 ah
->addr
= mmap64 (NULL
, st
.st_size
, PROT_READ
| PROT_WRITE
,
321 MAP_SHARED
, ah
->fd
, 0);
322 ah
->reserved
= st
.st_size
;
324 if (ah
->addr
== MAP_FAILED
)
326 ah
->mmaped
= st
.st_size
;
328 /* Create a temporary file in the correct directory. */
329 fd
= mkstemp (fname
);
331 error (EXIT_FAILURE
, errno
, _("cannot create temporary file"));
333 /* Copy the existing head information. */
336 /* Create the new archive header. The sizes of the various tables
337 should be double from what is currently used. */
338 newhead
.namehash_size
= MAX (next_prime (2 * newhead
.namehash_used
),
339 newhead
.namehash_size
);
341 printf ("name: size: %u, used: %d, new: size: %u\n",
342 head
->namehash_size
, head
->namehash_used
, newhead
.namehash_size
);
344 newhead
.string_offset
= (newhead
.namehash_offset
345 + (newhead
.namehash_size
346 * sizeof (struct namehashent
)));
347 /* Keep the string table size aligned to 4 bytes, so that
348 all the struct { uint32_t } types following are happy. */
349 newhead
.string_size
= MAX ((2 * newhead
.string_used
+ 3) & -4,
350 newhead
.string_size
);
352 newhead
.locrectab_offset
= newhead
.string_offset
+ newhead
.string_size
;
353 newhead
.locrectab_size
= MAX (2 * newhead
.locrectab_used
,
354 newhead
.locrectab_size
);
356 newhead
.sumhash_offset
= (newhead
.locrectab_offset
357 + (newhead
.locrectab_size
358 * sizeof (struct locrecent
)));
359 newhead
.sumhash_size
= MAX (next_prime (2 * newhead
.sumhash_used
),
360 newhead
.sumhash_size
);
362 total
= (newhead
.sumhash_offset
363 + newhead
.sumhash_size
* sizeof (struct sumhashent
));
365 /* The new file is empty now. */
366 newhead
.namehash_used
= 0;
367 newhead
.string_used
= 0;
368 newhead
.locrectab_used
= 0;
369 newhead
.sumhash_used
= 0;
371 /* Write out the header and create room for the other data structures. */
372 if (TEMP_FAILURE_RETRY (write (fd
, &newhead
, sizeof (newhead
)))
377 error (EXIT_FAILURE
, errval
, _("cannot initialize archive file"));
380 if (ftruncate64 (fd
, total
) != 0)
384 error (EXIT_FAILURE
, errval
, _("cannot resize archive file"));
387 /* Map the header and all the administration data structures. */
388 p
= mmap64 (NULL
, total
, PROT_READ
| PROT_WRITE
, MAP_SHARED
, fd
, 0);
393 error (EXIT_FAILURE
, errval
, _("cannot map archive header"));
396 /* Lock the new file. */
397 if (lockf64 (fd
, F_LOCK
, total
) != 0)
401 error (EXIT_FAILURE
, errval
, _("cannot lock new archive"));
404 new_ah
.mmaped
= total
;
407 new_ah
.reserved
= total
;
409 /* Walk through the hash name hash table to find out what data is
410 still referenced and transfer it into the new file. */
411 oldnamehashtab
= (struct namehashent
*) ((char *) ah
->addr
412 + head
->namehash_offset
);
413 oldlocrectab
= (struct locrecent
*) ((char *) ah
->addr
414 + head
->locrectab_offset
);
416 /* Sort the old locrec table in order of data position. */
417 struct oldlocrecent oldlocrecarray
[head
->namehash_size
];
418 for (cnt
= 0, loccnt
= 0; cnt
< head
->namehash_size
; ++cnt
)
419 if (oldnamehashtab
[cnt
].locrec_offset
!= 0)
421 oldlocrecarray
[loccnt
].cnt
= cnt
;
422 oldlocrecarray
[loccnt
++].locrec
423 = (struct locrecent
*) ((char *) ah
->addr
424 + oldnamehashtab
[cnt
].locrec_offset
);
426 qsort (oldlocrecarray
, loccnt
, sizeof (struct oldlocrecent
),
429 uint32_t last_locrec_offset
= 0;
430 for (cnt
= 0; cnt
< loccnt
; ++cnt
)
432 /* Insert this entry in the new hash table. */
433 locale_data_t old_data
;
435 struct locrecent
*oldlocrec
= oldlocrecarray
[cnt
].locrec
;
437 for (idx
= 0; idx
< __LC_LAST
; ++idx
)
440 old_data
[idx
].size
= oldlocrec
->record
[idx
].len
;
442 = ((char *) ah
->addr
+ oldlocrec
->record
[idx
].offset
);
444 __md5_buffer (old_data
[idx
].addr
, old_data
[idx
].size
,
448 if (cnt
> 0 && oldlocrecarray
[cnt
- 1].locrec
== oldlocrec
)
452 + oldnamehashtab
[oldlocrecarray
[cnt
- 1].cnt
].name_offset
);
456 + oldnamehashtab
[oldlocrecarray
[cnt
].cnt
].name_offset
),
457 0, oldname
, &last_locrec_offset
);
464 + oldnamehashtab
[oldlocrecarray
[cnt
].cnt
].name_offset
),
466 if (last_locrec_offset
== 0)
467 error (EXIT_FAILURE
, 0, _("cannot extend locale archive file"));
470 /* Make the file globally readable. */
471 if (fchmod (fd
, S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
) == -1)
475 error (EXIT_FAILURE
, errval
,
476 _("cannot change mode of resized locale archive"));
479 /* Rename the new file. */
480 if (rename (fname
, archivefname
) != 0)
484 error (EXIT_FAILURE
, errval
, _("cannot rename new archive"));
487 /* Close the old file. */
490 /* Add the information for the new one. */
496 open_archive (struct locarhandle
*ah
, bool readonly
)
501 struct locarhead head
;
503 size_t prefix_len
= output_prefix
? strlen (output_prefix
) : 0;
504 char archivefname
[prefix_len
+ sizeof (ARCHIVE_NAME
)];
507 memcpy (archivefname
, output_prefix
, prefix_len
);
508 strcpy (archivefname
+ prefix_len
, ARCHIVE_NAME
);
512 /* Open the archive. We must have exclusive write access. */
513 fd
= open64 (archivefname
, readonly
? O_RDONLY
: O_RDWR
);
516 /* Maybe the file does not yet exist. */
521 static const struct locarhead nullhead
=
524 .namehash_offset
= 0,
528 ah
->addr
= (void *) &nullhead
;
532 create_archive (archivefname
, ah
);
537 error (EXIT_FAILURE
, errno
, _("cannot open locale archive \"%s\""),
541 if (fstat64 (fd
, &st
) < 0)
542 error (EXIT_FAILURE
, errno
, _("cannot stat locale archive \"%s\""),
545 if (!readonly
&& lockf64 (fd
, F_LOCK
, sizeof (struct locarhead
)) == -1)
549 if (retry
++ < max_locarchive_open_retry
)
553 /* Wait for a bit. */
555 req
.tv_nsec
= 1000000 * (random () % 500 + 1);
556 (void) nanosleep (&req
, NULL
);
561 error (EXIT_FAILURE
, errno
, _("cannot lock locale archive \"%s\""),
565 /* One more check. Maybe another process replaced the archive file
566 with a new, larger one since we opened the file. */
567 if (stat64 (archivefname
, &st2
) == -1
568 || st
.st_dev
!= st2
.st_dev
569 || st
.st_ino
!= st2
.st_ino
)
571 (void) lockf64 (fd
, F_ULOCK
, sizeof (struct locarhead
));
576 /* Leave the loop. */
580 /* Read the header. */
581 if (TEMP_FAILURE_RETRY (read (fd
, &head
, sizeof (head
))) != sizeof (head
))
583 (void) lockf64 (fd
, F_ULOCK
, sizeof (struct locarhead
));
584 error (EXIT_FAILURE
, errno
, _("cannot read archive header"));
588 ah
->mmaped
= st
.st_size
;
590 /* To prepare for enlargements of the mmaped area reserve some
592 size_t reserved
= RESERVE_MMAP_SIZE
;
595 if (st
.st_size
< reserved
596 && ((p
= mmap64 (NULL
, RESERVE_MMAP_SIZE
, PROT_NONE
, MAP_ANON
, -1, 0))
602 reserved
= st
.st_size
;
605 /* Map the entire file. We might need to compare the category data
606 in the file with the newly added data. */
607 ah
->addr
= mmap64 (p
, st
.st_size
, PROT_READ
| (readonly
? 0 : PROT_WRITE
),
608 MAP_SHARED
| xflags
, fd
, 0);
609 if (ah
->addr
== MAP_FAILED
)
611 (void) lockf64 (fd
, F_ULOCK
, sizeof (struct locarhead
));
612 error (EXIT_FAILURE
, errno
, _("cannot map archive header"));
614 ah
->reserved
= reserved
;
619 close_archive (struct locarhandle
*ah
)
623 munmap (ah
->addr
, ah
->reserved
);
628 #include "../../intl/explodename.c"
629 #include "../../intl/l10nflist.c"
632 insert_name (struct locarhandle
*ah
,
633 const char *name
, size_t name_len
, bool replace
)
635 const struct locarhead
*const head
= ah
->addr
;
636 struct namehashent
*namehashtab
637 = (struct namehashent
*) ((char *) ah
->addr
+ head
->namehash_offset
);
638 unsigned int insert_idx
, idx
, incr
;
640 /* Hash value of the locale name. */
641 uint32_t hval
= archive_hashval (name
, name_len
);
644 idx
= hval
% head
->namehash_size
;
645 incr
= 1 + hval
% (head
->namehash_size
- 2);
647 /* If the name_offset field is zero this means this is a
648 deleted entry and therefore no entry can be found. */
649 while (namehashtab
[idx
].name_offset
!= 0)
651 if (namehashtab
[idx
].hashval
== hval
653 (char *) ah
->addr
+ namehashtab
[idx
].name_offset
) == 0)
655 /* Found the entry. */
656 if (namehashtab
[idx
].locrec_offset
!= 0 && ! replace
)
659 error (0, 0, _("locale '%s' already exists"), name
);
666 if (namehashtab
[idx
].hashval
== hval
&& ! be_quiet
)
668 error (0, 0, "hash collision (%u) %s, %s",
669 hval
, name
, (char *) ah
->addr
+ namehashtab
[idx
].name_offset
);
672 /* Remember the first place we can insert the new entry. */
673 if (namehashtab
[idx
].locrec_offset
== 0 && insert_idx
== -1)
677 if (idx
>= head
->namehash_size
)
678 idx
-= head
->namehash_size
;
681 /* Add as early as possible. */
682 if (insert_idx
!= -1)
685 namehashtab
[idx
].hashval
= hval
; /* no-op if replacing an old entry. */
686 return &namehashtab
[idx
];
690 add_alias (struct locarhandle
*ah
, const char *alias
, bool replace
,
691 const char *oldname
, uint32_t *locrec_offset_p
)
693 uint32_t locrec_offset
= *locrec_offset_p
;
694 struct locarhead
*head
= ah
->addr
;
695 const size_t name_len
= strlen (alias
);
696 struct namehashent
*namehashent
= insert_name (ah
, alias
, strlen (alias
),
698 if (namehashent
== NULL
&& ! replace
)
701 if (namehashent
->name_offset
== 0)
703 /* We are adding a new hash entry for this alias.
704 Determine whether we have to resize the file. */
705 if (head
->string_used
+ name_len
+ 1 > head
->string_size
706 || 100 * head
->namehash_used
> 75 * head
->namehash_size
)
708 /* The current archive is not large enough. */
709 enlarge_archive (ah
, head
);
711 /* The locrecent might have moved, so we have to look up
712 the old name afresh. */
713 namehashent
= insert_name (ah
, oldname
, strlen (oldname
), true);
714 assert (namehashent
->name_offset
!= 0);
715 assert (namehashent
->locrec_offset
!= 0);
716 *locrec_offset_p
= namehashent
->locrec_offset
;
718 /* Tail call to try the whole thing again. */
719 add_alias (ah
, alias
, replace
, oldname
, locrec_offset_p
);
723 /* Add the name string. */
724 memcpy (ah
->addr
+ head
->string_offset
+ head
->string_used
,
725 alias
, name_len
+ 1);
726 namehashent
->name_offset
= head
->string_offset
+ head
->string_used
;
727 head
->string_used
+= name_len
+ 1;
729 ++head
->namehash_used
;
732 if (namehashent
->locrec_offset
!= 0)
734 /* Replacing an existing entry.
735 Mark that we are no longer using the old locrecent. */
736 struct locrecent
*locrecent
737 = (struct locrecent
*) ((char *) ah
->addr
738 + namehashent
->locrec_offset
);
742 /* Point this entry at the locrecent installed for the main name. */
743 namehashent
->locrec_offset
= locrec_offset
;
746 static int /* qsort comparator used below */
747 cmpcategorysize (const void *a
, const void *b
)
749 if (*(const void **) a
== NULL
)
751 if (*(const void **) b
== NULL
)
753 return ((*(const struct locale_category_data
**) a
)->size
754 - (*(const struct locale_category_data
**) b
)->size
);
757 /* Check the content of the archive for duplicates. Add the content
758 of the files if necessary. Returns the locrec_offset. */
760 add_locale (struct locarhandle
*ah
,
761 const char *name
, locale_data_t data
, bool replace
)
763 /* First look for the name. If it already exists and we are not
764 supposed to replace it don't do anything. If it does not exist
765 we have to allocate a new locale record. */
766 size_t name_len
= strlen (name
);
767 uint32_t file_offsets
[__LC_LAST
];
768 unsigned int num_new_offsets
= 0;
769 struct sumhashent
*sumhashtab
;
771 unsigned int cnt
, idx
;
772 struct locarhead
*head
;
773 struct namehashent
*namehashent
;
775 struct locrecent
*locrecent
;
778 struct locale_category_data
*size_order
[__LC_LAST
];
779 const size_t pagesz
= getpagesize ();
783 sumhashtab
= (struct sumhashent
*) ((char *) ah
->addr
784 + head
->sumhash_offset
);
786 memset (file_offsets
, 0, sizeof (file_offsets
));
788 size_order
[LC_ALL
] = NULL
;
789 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
791 size_order
[cnt
] = &data
[cnt
];
793 /* Sort the array in ascending order of data size. */
794 qsort (size_order
, __LC_LAST
, sizeof size_order
[0], cmpcategorysize
);
797 data
[LC_ALL
].size
= 0;
798 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
799 if (size_order
[cnt
] != NULL
)
801 const size_t rounded_size
= (size_order
[cnt
]->size
+ 15) & -16;
802 if (data
[LC_ALL
].size
+ rounded_size
> 2 * pagesz
)
804 /* This category makes the small-categories block
805 stop being small, so this is the end of the road. */
807 size_order
[cnt
++] = NULL
;
808 while (cnt
< __LC_LAST
);
811 data
[LC_ALL
].size
+= rounded_size
;
812 small_mask
|= 1 << (size_order
[cnt
] - data
);
815 /* Copy the data for all the small categories into the LC_ALL
818 data
[LC_ALL
].addr
= alloca (data
[LC_ALL
].size
);
819 memset (data
[LC_ALL
].addr
, 0, data
[LC_ALL
].size
);
821 ptr
= data
[LC_ALL
].addr
;
822 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
823 if (small_mask
& (1 << cnt
))
825 memcpy (ptr
, data
[cnt
].addr
, data
[cnt
].size
);
826 ptr
+= (data
[cnt
].size
+ 15) & -16;
828 __md5_buffer (data
[LC_ALL
].addr
, data
[LC_ALL
].size
, data
[LC_ALL
].sum
);
830 /* For each locale category data set determine whether the same data
831 is already somewhere in the archive. */
832 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
833 if (small_mask
== 0 ? cnt
!= LC_ALL
: !(small_mask
& (1 << cnt
)))
837 /* Compute the hash value of the checksum to determine a
838 starting point for the search in the MD5 hash value
840 hval
= archive_hashval (data
[cnt
].sum
, 16);
842 idx
= hval
% head
->sumhash_size
;
843 incr
= 1 + hval
% (head
->sumhash_size
- 2);
845 while (sumhashtab
[idx
].file_offset
!= 0)
847 if (memcmp (data
[cnt
].sum
, sumhashtab
[idx
].sum
, 16) == 0)
849 /* Check the content, there could be a collision of
852 Unfortunately the sumhashent record does not include
853 the size of the stored data. So we have to search for
855 locrecent
= (struct locrecent
*) ((char *) ah
->addr
856 + head
->locrectab_offset
);
858 for (iloc
= 0; iloc
< head
->locrectab_used
; ++iloc
)
859 if (locrecent
[iloc
].refs
!= 0
860 && (locrecent
[iloc
].record
[cnt
].offset
861 == sumhashtab
[idx
].file_offset
))
864 if (iloc
!= head
->locrectab_used
865 && data
[cnt
].size
== locrecent
[iloc
].record
[cnt
].len
866 /* We have to compare the content. Either we can
867 have the data mmaped or we have to read from
869 && (file_data_available_p (ah
, sumhashtab
[idx
].file_offset
,
871 ? memcmp (data
[cnt
].addr
,
873 + sumhashtab
[idx
].file_offset
,
875 : compare_from_file (ah
, data
[cnt
].addr
,
876 sumhashtab
[idx
].file_offset
,
877 data
[cnt
].size
) == 0))
880 file_offsets
[cnt
] = sumhashtab
[idx
].file_offset
;
887 if (idx
>= head
->sumhash_size
)
888 idx
-= head
->sumhash_size
;
892 /* Find a slot for the locale name in the hash table. */
893 namehashent
= insert_name (ah
, name
, name_len
, replace
);
894 if (namehashent
== NULL
) /* Already exists and !REPLACE. */
897 /* Determine whether we have to resize the file. */
898 if (100 * (head
->sumhash_used
+ num_new_offsets
) > 75 * head
->sumhash_size
899 || (namehashent
->locrec_offset
== 0
900 && (head
->locrectab_used
== head
->locrectab_size
901 || head
->string_used
+ name_len
+ 1 > head
->string_size
902 || 100 * head
->namehash_used
> 75 * head
->namehash_size
)))
904 /* The current archive is not large enough. */
905 enlarge_archive (ah
, head
);
906 return add_locale (ah
, name
, data
, replace
);
909 /* Add the locale data which is not yet in the archive. */
910 for (cnt
= 0, lastoffset
= 0; cnt
< __LC_LAST
; ++cnt
)
911 if ((small_mask
== 0 ? cnt
!= LC_ALL
: !(small_mask
& (1 << cnt
)))
912 && file_offsets
[cnt
] == 0)
914 /* The data for this section is not yet available in the
915 archive. Append it. */
919 lastpos
= lseek64 (ah
->fd
, 0, SEEK_END
);
920 if (lastpos
== (off64_t
) -1)
921 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
923 /* If block of small categories would cross page boundary,
924 align it unless it immediately follows a large category. */
925 if (cnt
== LC_ALL
&& lastoffset
!= lastpos
926 && ((((lastpos
& (pagesz
- 1)) + data
[cnt
].size
+ pagesz
- 1)
928 > ((data
[cnt
].size
+ pagesz
- 1) & -pagesz
)))
930 size_t sz
= pagesz
- (lastpos
& (pagesz
- 1));
931 char *zeros
= alloca (sz
);
933 memset (zeros
, 0, sz
);
934 if (TEMP_FAILURE_RETRY (write (ah
->fd
, zeros
, sz
) != sz
))
935 error (EXIT_FAILURE
, errno
,
936 _("cannot add to locale archive"));
941 /* Align all data to a 16 byte boundary. */
942 if ((lastpos
& 15) != 0)
944 static const char zeros
[15] = { 0, };
946 if (TEMP_FAILURE_RETRY (write (ah
->fd
, zeros
, 16 - (lastpos
& 15)))
947 != 16 - (lastpos
& 15))
948 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
950 lastpos
+= 16 - (lastpos
& 15);
953 /* Remember the position. */
954 file_offsets
[cnt
] = lastpos
;
955 lastoffset
= lastpos
+ data
[cnt
].size
;
957 /* Write the data. */
958 if (TEMP_FAILURE_RETRY (write (ah
->fd
, data
[cnt
].addr
, data
[cnt
].size
))
960 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
962 /* Add the hash value to the hash table. */
963 md5hval
= archive_hashval (data
[cnt
].sum
, 16);
965 idx
= md5hval
% head
->sumhash_size
;
966 incr
= 1 + md5hval
% (head
->sumhash_size
- 2);
968 while (sumhashtab
[idx
].file_offset
!= 0)
971 if (idx
>= head
->sumhash_size
)
972 idx
-= head
->sumhash_size
;
975 memcpy (sumhashtab
[idx
].sum
, data
[cnt
].sum
, 16);
976 sumhashtab
[idx
].file_offset
= file_offsets
[cnt
];
978 ++head
->sumhash_used
;
981 lastoffset
= file_offsets
[LC_ALL
];
982 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
983 if (small_mask
& (1 << cnt
))
985 file_offsets
[cnt
] = lastoffset
;
986 lastoffset
+= (data
[cnt
].size
+ 15) & -16;
989 if (namehashent
->name_offset
== 0)
991 /* Add the name string. */
992 memcpy ((char *) ah
->addr
+ head
->string_offset
+ head
->string_used
,
994 namehashent
->name_offset
= head
->string_offset
+ head
->string_used
;
995 head
->string_used
+= name_len
+ 1;
996 ++head
->namehash_used
;
999 if (namehashent
->locrec_offset
== 0)
1001 /* Allocate a name location record. */
1002 namehashent
->locrec_offset
= (head
->locrectab_offset
1003 + (head
->locrectab_used
++
1004 * sizeof (struct locrecent
)));
1005 locrecent
= (struct locrecent
*) ((char *) ah
->addr
1006 + namehashent
->locrec_offset
);
1007 locrecent
->refs
= 1;
1011 /* If there are other aliases pointing to this locrecent,
1012 we still need a new one. If not, reuse the old one. */
1014 locrecent
= (struct locrecent
*) ((char *) ah
->addr
1015 + namehashent
->locrec_offset
);
1016 if (locrecent
->refs
> 1)
1019 namehashent
->locrec_offset
= (head
->locrectab_offset
1020 + (head
->locrectab_used
++
1021 * sizeof (struct locrecent
)));
1022 locrecent
= (struct locrecent
*) ((char *) ah
->addr
1023 + namehashent
->locrec_offset
);
1024 locrecent
->refs
= 1;
1028 /* Fill in the table with the locations of the locale data. */
1029 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1031 locrecent
->record
[cnt
].offset
= file_offsets
[cnt
];
1032 locrecent
->record
[cnt
].len
= data
[cnt
].size
;
1035 return namehashent
->locrec_offset
;
1039 /* Check the content of the archive for duplicates. Add the content
1040 of the files if necessary. Add all the names, possibly overwriting
1043 add_locale_to_archive (ah
, name
, data
, replace
)
1044 struct locarhandle
*ah
;
1049 char *normalized_name
= NULL
;
1050 uint32_t locrec_offset
;
1052 /* First analyze the name to decide how to archive it. */
1053 const char *language
;
1054 const char *modifier
;
1055 const char *territory
;
1056 const char *codeset
;
1057 const char *normalized_codeset
;
1058 int mask
= _nl_explode_name (strdupa (name
),
1059 &language
, &modifier
, &territory
,
1060 &codeset
, &normalized_codeset
);
1062 if (mask
& XPG_NORM_CODESET
)
1063 /* This name contains a codeset in unnormalized form.
1064 We will store it in the archive with a normalized name. */
1065 asprintf (&normalized_name
, "%s%s%s.%s%s%s",
1066 language
, territory
== NULL
? "" : "_", territory
?: "",
1067 (mask
& XPG_NORM_CODESET
) ? normalized_codeset
: codeset
,
1068 modifier
== NULL
? "" : "@", modifier
?: "");
1070 /* This call does the main work. */
1071 locrec_offset
= add_locale (ah
, normalized_name
?: name
, data
, replace
);
1072 if (locrec_offset
== 0)
1074 free (normalized_name
);
1075 if (mask
& XPG_NORM_CODESET
)
1076 free ((char *) normalized_codeset
);
1080 if ((mask
& XPG_CODESET
) == 0)
1082 /* This name lacks a codeset, so determine the locale's codeset and
1083 add an alias for its name with normalized codeset appended. */
1088 unsigned int nstrings
;
1089 unsigned int strindex
[0];
1090 } *filedata
= data
[LC_CTYPE
].addr
;
1091 codeset
= (char *) filedata
1092 + filedata
->strindex
[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME
)];
1093 char *normalized_codeset_name
= NULL
;
1095 normalized_codeset
= _nl_normalize_codeset (codeset
, strlen (codeset
));
1096 mask
|= XPG_NORM_CODESET
;
1098 asprintf (&normalized_codeset_name
, "%s%s%s.%s%s%s",
1099 language
, territory
== NULL
? "" : "_", territory
?: "",
1101 modifier
== NULL
? "" : "@", modifier
?: "");
1103 add_alias (ah
, normalized_codeset_name
, replace
,
1104 normalized_name
?: name
, &locrec_offset
);
1105 free (normalized_codeset_name
);
1108 /* Now read the locale.alias files looking for lines whose
1109 right hand side matches our name after normalization. */
1110 if (alias_file
!= NULL
)
1113 fp
= fopen (alias_file
, "rm");
1115 error (1, errno
, _("locale alias file `%s' not found"),
1118 /* No threads present. */
1119 __fsetlocking (fp
, FSETLOCKING_BYCALLER
);
1121 while (! feof_unlocked (fp
))
1123 /* It is a reasonable approach to use a fix buffer here
1125 a) we are only interested in the first two fields
1126 b) these fields must be usable as file names and so must
1133 if (fgets_unlocked (buf
, BUFSIZ
, fp
) == NULL
)
1138 /* Ignore leading white space. */
1139 while (isspace (cp
[0]) && cp
[0] != '\n')
1142 /* A leading '#' signals a comment line. */
1143 if (cp
[0] != '\0' && cp
[0] != '#' && cp
[0] != '\n')
1146 while (cp
[0] != '\0' && !isspace (cp
[0]))
1148 /* Terminate alias name. */
1152 /* Now look for the beginning of the value. */
1153 while (isspace (cp
[0]))
1159 while (cp
[0] != '\0' && !isspace (cp
[0]))
1161 /* Terminate value. */
1164 /* This has to be done to make the following
1165 test for the end of line possible. We are
1166 looking for the terminating '\n' which do not
1171 else if (cp
[0] != '\0')
1174 /* Does this alias refer to our locale? We will
1175 normalize the right hand side and compare the
1176 elements of the normalized form. */
1178 const char *rhs_language
;
1179 const char *rhs_modifier
;
1180 const char *rhs_territory
;
1181 const char *rhs_codeset
;
1182 const char *rhs_normalized_codeset
;
1183 int rhs_mask
= _nl_explode_name (value
,
1188 &rhs_normalized_codeset
);
1189 if (!strcmp (language
, rhs_language
)
1190 && ((rhs_mask
& XPG_CODESET
)
1191 /* He has a codeset, it must match normalized. */
1192 ? !strcmp ((mask
& XPG_NORM_CODESET
)
1193 ? normalized_codeset
: codeset
,
1194 (rhs_mask
& XPG_NORM_CODESET
)
1195 ? rhs_normalized_codeset
: rhs_codeset
)
1196 /* He has no codeset, we must also have none. */
1197 : (mask
& XPG_CODESET
) == 0)
1198 /* Codeset (or lack thereof) matches. */
1199 && !strcmp (territory
?: "", rhs_territory
?: "")
1200 && !strcmp (modifier
?: "", rhs_modifier
?: ""))
1201 /* We have a winner. */
1202 add_alias (ah
, alias
, replace
,
1203 normalized_name
?: name
, &locrec_offset
);
1204 if (rhs_mask
& XPG_NORM_CODESET
)
1205 free ((char *) rhs_normalized_codeset
);
1210 /* Possibly not the whole line fits into the buffer.
1211 Ignore the rest of the line. */
1212 while (strchr (cp
, '\n') == NULL
)
1215 if (fgets_unlocked (buf
, BUFSIZ
, fp
) == NULL
)
1216 /* Make sure the inner loop will be left. The outer
1217 loop will exit at the `feof' test. */
1225 free (normalized_name
);
1227 if (mask
& XPG_NORM_CODESET
)
1228 free ((char *) normalized_codeset
);
1235 add_locales_to_archive (nlist
, list
, replace
)
1240 struct locarhandle ah
;
1243 /* Open the archive. This call never returns if we cannot
1244 successfully open the archive. */
1245 open_archive (&ah
, false);
1249 const char *fname
= *list
++;
1250 size_t fnamelen
= strlen (fname
);
1259 printf (_("Adding %s\n"), fname
);
1261 /* First see whether this really is a directory and whether it
1262 contains all the require locale category files. */
1263 if (stat64 (fname
, &st
) < 0)
1265 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname
,
1269 if (!S_ISDIR (st
.st_mode
))
1271 error (0, 0, _("\"%s\" is no directory; ignored"), fname
);
1275 dirp
= opendir (fname
);
1278 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1279 fname
, strerror (errno
));
1284 while ((d
= readdir64 (dirp
)) != NULL
)
1286 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1288 if (strcmp (d
->d_name
, locnames
[cnt
]) == 0)
1290 unsigned char d_type
;
1292 /* We have an object of the required name. If it's
1293 a directory we have to look at a file with the
1294 prefix "SYS_". Otherwise we have found what we
1296 #ifdef _DIRENT_HAVE_D_TYPE
1299 if (d_type
!= DT_REG
)
1302 char fullname
[fnamelen
+ 2 * strlen (d
->d_name
) + 7];
1304 #ifdef _DIRENT_HAVE_D_TYPE
1305 if (d_type
== DT_UNKNOWN
)
1308 strcpy (stpcpy (stpcpy (fullname
, fname
), "/"),
1311 if (stat64 (fullname
, &st
) == -1)
1312 /* We cannot stat the file, ignore it. */
1315 d_type
= IFTODT (st
.st_mode
);
1318 if (d_type
== DT_DIR
)
1320 /* We have to do more tests. The file is a
1321 directory and it therefore must contain a
1322 regular file with the same name except a
1324 char *t
= stpcpy (stpcpy (fullname
, fname
), "/");
1325 strcpy (stpcpy (stpcpy (t
, d
->d_name
), "/SYS_"),
1328 if (stat64 (fullname
, &st
) == -1)
1329 /* There is no SYS_* file or we cannot
1333 d_type
= IFTODT (st
.st_mode
);
1337 /* If we found a regular file (eventually after
1338 following a symlink) we are successful. */
1339 if (d_type
== DT_REG
)
1347 if (seen
!= __LC_LAST
- 1)
1349 /* We don't have all locale category files. Ignore the name. */
1350 error (0, 0, _("incomplete set of locale files in \"%s\""),
1355 /* Add the files to the archive. To do this we first compute
1356 sizes and the MD5 sums of all the files. */
1357 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1360 char fullname
[fnamelen
+ 2 * strlen (locnames
[cnt
]) + 7];
1363 strcpy (stpcpy (stpcpy (fullname
, fname
), "/"), locnames
[cnt
]);
1364 fd
= open64 (fullname
, O_RDONLY
);
1365 if (fd
== -1 || fstat64 (fd
, &st
) == -1)
1367 /* Cannot read the file. */
1373 if (S_ISDIR (st
.st_mode
))
1377 t
= stpcpy (stpcpy (fullname
, fname
), "/");
1378 strcpy (stpcpy (stpcpy (t
, locnames
[cnt
]), "/SYS_"),
1381 fd
= open64 (fullname
, O_RDONLY
);
1382 if (fd
== -1 || fstat64 (fd
, &st
) == -1
1383 || !S_ISREG (st
.st_mode
))
1392 data
[cnt
].addr
= mmap64 (NULL
, st
.st_size
, PROT_READ
, MAP_SHARED
,
1394 if (data
[cnt
].addr
== MAP_FAILED
)
1396 /* Cannot map it. */
1401 data
[cnt
].size
= st
.st_size
;
1402 __md5_buffer (data
[cnt
].addr
, st
.st_size
, data
[cnt
].sum
);
1404 /* We don't need the file descriptor anymore. */
1408 if (cnt
!= __LC_LAST
)
1412 munmap (data
[cnt
].addr
, data
[cnt
].size
);
1414 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname
);
1419 result
|= add_locale_to_archive (&ah
, basename (fname
), data
, replace
);
1421 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1423 munmap (data
[cnt
].addr
, data
[cnt
].size
);
1427 close_archive (&ah
);
1434 delete_locales_from_archive (nlist
, list
)
1438 struct locarhandle ah
;
1439 struct locarhead
*head
;
1440 struct namehashent
*namehashtab
;
1442 /* Open the archive. This call never returns if we cannot
1443 successfully open the archive. */
1444 open_archive (&ah
, false);
1447 namehashtab
= (struct namehashent
*) ((char *) ah
.addr
1448 + head
->namehash_offset
);
1452 const char *locname
= *list
++;
1457 /* Search for this locale in the archive. */
1458 hval
= archive_hashval (locname
, strlen (locname
));
1460 idx
= hval
% head
->namehash_size
;
1461 incr
= 1 + hval
% (head
->namehash_size
- 2);
1463 /* If the name_offset field is zero this means this is no
1464 deleted entry and therefore no entry can be found. */
1465 while (namehashtab
[idx
].name_offset
!= 0)
1467 if (namehashtab
[idx
].hashval
== hval
1468 && (strcmp (locname
,
1469 (char *) ah
.addr
+ namehashtab
[idx
].name_offset
)
1472 /* Found the entry. Now mark it as removed by zero-ing
1473 the reference to the locale record. */
1474 namehashtab
[idx
].locrec_offset
= 0;
1479 if (idx
>= head
->namehash_size
)
1480 idx
-= head
->namehash_size
;
1483 if (namehashtab
[idx
].name_offset
== 0 && ! be_quiet
)
1484 error (0, 0, _("locale \"%s\" not in archive"), locname
);
1487 close_archive (&ah
);
1496 uint32_t locrec_offset
;
1502 const unsigned char *sum
;
1503 uint32_t file_offset
;
1509 nameentcmp (const void *a
, const void *b
)
1511 return strcmp (((const struct nameent
*) a
)->name
,
1512 ((const struct nameent
*) b
)->name
);
1517 dataentcmp (const void *a
, const void *b
)
1519 if (((const struct dataent
*) a
)->file_offset
1520 < ((const struct dataent
*) b
)->file_offset
)
1523 if (((const struct dataent
*) a
)->file_offset
1524 > ((const struct dataent
*) b
)->file_offset
)
1532 show_archive_content (int verbose
)
1534 struct locarhandle ah
;
1535 struct locarhead
*head
;
1536 struct namehashent
*namehashtab
;
1537 struct nameent
*names
;
1540 /* Open the archive. This call never returns if we cannot
1541 successfully open the archive. */
1542 open_archive (&ah
, true);
1546 names
= (struct nameent
*) xmalloc (head
->namehash_used
1547 * sizeof (struct nameent
));
1549 namehashtab
= (struct namehashent
*) ((char *) ah
.addr
1550 + head
->namehash_offset
);
1551 for (cnt
= used
= 0; cnt
< head
->namehash_size
; ++cnt
)
1552 if (namehashtab
[cnt
].locrec_offset
!= 0)
1554 assert (used
< head
->namehash_used
);
1555 names
[used
].name
= ah
.addr
+ namehashtab
[cnt
].name_offset
;
1556 names
[used
++].locrec_offset
= namehashtab
[cnt
].locrec_offset
;
1559 /* Sort the names. */
1560 qsort (names
, used
, sizeof (struct nameent
), nameentcmp
);
1564 struct dataent
*files
;
1565 struct sumhashent
*sumhashtab
;
1568 files
= (struct dataent
*) xmalloc (head
->sumhash_used
1569 * sizeof (struct dataent
));
1571 sumhashtab
= (struct sumhashent
*) ((char *) ah
.addr
1572 + head
->sumhash_offset
);
1573 for (cnt
= sumused
= 0; cnt
< head
->sumhash_size
; ++cnt
)
1574 if (sumhashtab
[cnt
].file_offset
!= 0)
1576 assert (sumused
< head
->sumhash_used
);
1577 files
[sumused
].sum
= (const unsigned char *) sumhashtab
[cnt
].sum
;
1578 files
[sumused
].file_offset
= sumhashtab
[cnt
].file_offset
;
1579 files
[sumused
++].nlink
= 0;
1582 /* Sort by file locations. */
1583 qsort (files
, sumused
, sizeof (struct dataent
), dataentcmp
);
1585 /* Compute nlink fields. */
1586 for (cnt
= 0; cnt
< used
; ++cnt
)
1588 struct locrecent
*locrec
;
1591 locrec
= (struct locrecent
*) ((char *) ah
.addr
1592 + names
[cnt
].locrec_offset
);
1593 for (idx
= 0; idx
< __LC_LAST
; ++idx
)
1594 if (locrec
->record
[LC_ALL
].offset
!= 0
1596 || (locrec
->record
[idx
].offset
1597 < locrec
->record
[LC_ALL
].offset
)
1598 || (locrec
->record
[idx
].offset
+ locrec
->record
[idx
].len
1599 > (locrec
->record
[LC_ALL
].offset
1600 + locrec
->record
[LC_ALL
].len
)))
1603 struct dataent
*data
, dataent
;
1605 dataent
.file_offset
= locrec
->record
[idx
].offset
;
1606 data
= (struct dataent
*) bsearch (&dataent
, files
, sumused
,
1607 sizeof (struct dataent
),
1609 assert (data
!= NULL
);
1615 for (cnt
= 0; cnt
< used
; ++cnt
)
1617 struct locrecent
*locrec
;
1620 locrec
= (struct locrecent
*) ((char *) ah
.addr
1621 + names
[cnt
].locrec_offset
);
1622 for (idx
= 0; idx
< __LC_LAST
; ++idx
)
1625 struct dataent
*data
, dataent
;
1627 dataent
.file_offset
= locrec
->record
[idx
].offset
;
1628 if (locrec
->record
[LC_ALL
].offset
!= 0
1629 && dataent
.file_offset
>= locrec
->record
[LC_ALL
].offset
1630 && (dataent
.file_offset
+ locrec
->record
[idx
].len
1631 <= (locrec
->record
[LC_ALL
].offset
1632 + locrec
->record
[LC_ALL
].len
)))
1633 dataent
.file_offset
= locrec
->record
[LC_ALL
].offset
;
1635 data
= (struct dataent
*) bsearch (&dataent
, files
, sumused
,
1636 sizeof (struct dataent
),
1638 printf ("%6d %7x %3d%c ",
1639 locrec
->record
[idx
].len
, locrec
->record
[idx
].offset
,
1641 dataent
.file_offset
== locrec
->record
[LC_ALL
].offset
1643 for (i
= 0; i
< 16; i
+= 4)
1644 printf ("%02x%02x%02x%02x",
1645 data
->sum
[i
], data
->sum
[i
+ 1],
1646 data
->sum
[i
+ 2], data
->sum
[i
+ 3]);
1647 printf (" %s/%s\n", names
[cnt
].name
,
1648 idx
== LC_MESSAGES
? "LC_MESSAGES/SYS_LC_MESSAGES"
1654 for (cnt
= 0; cnt
< used
; ++cnt
)
1655 puts (names
[cnt
].name
);
1657 close_archive (&ah
);
1659 exit (EXIT_SUCCESS
);