1 /* Copyright (C) 2002, 2003 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, write to the Free
17 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
34 #include <stdio_ext.h>
40 #include <sys/param.h>
43 #include "../../crypt/md5.h"
44 #include "../localeinfo.h"
45 #include "../locarchive.h"
46 #include "localedef.h"
48 /* Define the hash function. We define the function as static inline.
49 We must change the name so as not to conflict with simple-hash.h. */
50 #define compute_hashval static inline archive_hashval
51 #define hashval_t uint32_t
53 #undef compute_hashval
55 extern const char *output_prefix
;
57 #define ARCHIVE_NAME LOCALEDIR "/locale-archive"
59 static const char *locnames
[] =
61 #define DEFINE_CATEGORY(category, category_name, items, a) \
62 [category] = category_name,
63 #include "categories.def"
64 #undef DEFINE_CATEGORY
68 /* Size of the initial archive header. */
69 #define INITIAL_NUM_NAMES 450
70 #define INITIAL_SIZE_STRINGS 3500
71 #define INITIAL_NUM_LOCREC 350
72 #define INITIAL_NUM_SUMS 2000
76 create_archive (const char *archivefname
, struct locarhandle
*ah
)
79 char fname
[strlen (archivefname
) + sizeof (".XXXXXX")];
80 struct locarhead head
;
84 strcpy (stpcpy (fname
, archivefname
), ".XXXXXX");
86 /* Create a temporary file in the correct directory. */
89 error (EXIT_FAILURE
, errno
, _("cannot create temporary file"));
91 /* Create the initial content of the archive. */
92 head
.magic
= AR_MAGIC
;
93 head
.namehash_offset
= sizeof (struct locarhead
);
94 head
.namehash_used
= 0;
95 head
.namehash_size
= next_prime (INITIAL_NUM_NAMES
);
97 head
.string_offset
= (head
.namehash_offset
98 + head
.namehash_size
* sizeof (struct namehashent
));
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
))
118 error (EXIT_FAILURE
, errval
, _("cannot initialize archive file"));
121 if (ftruncate64 (fd
, total
) != 0)
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);
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
140 if (link (fname
, archivefname
) == -1)
144 /* We cannot use the just created file. */
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);
156 error (EXIT_FAILURE
, errval
, _("failed to create new locale archive"));
159 /* Remove the temporary name. */
162 /* Make the file globally readable. */
163 if (fchmod (fd
, S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
) == -1)
166 unlink (archivefname
);
167 error (EXIT_FAILURE
, errval
,
168 _("cannot change mode of new locale archive"));
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. */
182 struct locrecent
*locrec
;
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;
194 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
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);
205 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
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);
216 if (start_a
!= start_b
)
217 return (int)start_a
- (int)start_b
;
218 return (int)end_a
- (int)end_b
;
222 /* forward decl for below */
223 static uint32_t add_locale (struct locarhandle
*ah
, const char *name
,
224 locale_data_t data
, bool replace
);
227 enlarge_archive (struct locarhandle
*ah
, const struct locarhead
*head
)
231 struct locarhead newhead
;
234 unsigned int cnt
, loccnt
;
235 struct namehashent
*oldnamehashtab
;
236 struct locrecent
*oldlocrectab
;
237 struct locarhandle new_ah
;
238 size_t prefix_len
= output_prefix
? strlen (output_prefix
) : 0;
239 char archivefname
[prefix_len
+ sizeof (ARCHIVE_NAME
)];
240 char fname
[prefix_len
+ sizeof (ARCHIVE_NAME
) + sizeof (".XXXXXX") - 1];
241 struct oldlocrecent
*oldlocrecarray
;
244 memcpy (archivefname
, output_prefix
, prefix_len
);
245 strcpy (archivefname
+ prefix_len
, ARCHIVE_NAME
);
246 strcpy (stpcpy (fname
, archivefname
), ".XXXXXX");
248 /* Not all of the old file has to be mapped. Change this now this
249 we will have to access the whole content. */
250 if (fstat64 (ah
->fd
, &st
) != 0
251 || (ah
->addr
= mmap64 (NULL
, st
.st_size
, PROT_READ
| PROT_WRITE
,
252 MAP_SHARED
, ah
->fd
, 0)) == MAP_FAILED
)
253 error (EXIT_FAILURE
, errno
, _("cannot map locale archive file"));
254 ah
->len
= st
.st_size
;
256 /* Create a temporary file in the correct directory. */
257 fd
= mkstemp (fname
);
259 error (EXIT_FAILURE
, errno
, _("cannot create temporary file"));
261 /* Copy the existing head information. */
264 /* Create the new archive header. The sizes of the various tables
265 should be double from what is currently used. */
266 newhead
.namehash_size
= MAX (next_prime (2 * newhead
.namehash_used
),
267 newhead
.namehash_size
);
269 printf ("name: size: %u, used: %d, new: size: %u\n",
270 head
->namehash_size
, head
->namehash_used
, newhead
.namehash_size
);
272 newhead
.string_offset
= (newhead
.namehash_offset
273 + (newhead
.namehash_size
274 * sizeof (struct namehashent
)));
275 /* Keep the string table size aligned to 4 bytes, so that
276 all the struct { uint32_t } types following are happy. */
277 newhead
.string_size
= MAX ((2 * newhead
.string_used
+ 3) & -4,
278 newhead
.string_size
);
280 newhead
.locrectab_offset
= newhead
.string_offset
+ newhead
.string_size
;
281 newhead
.locrectab_size
= MAX (2 * newhead
.locrectab_used
,
282 newhead
.locrectab_size
);
284 newhead
.sumhash_offset
= (newhead
.locrectab_offset
285 + (newhead
.locrectab_size
286 * sizeof (struct locrecent
)));
287 newhead
.sumhash_size
= MAX (next_prime (2 * newhead
.sumhash_used
),
288 newhead
.sumhash_size
);
290 total
= (newhead
.sumhash_offset
291 + newhead
.sumhash_size
* sizeof (struct sumhashent
));
293 /* The new file is empty now. */
294 newhead
.namehash_used
= 0;
295 newhead
.string_used
= 0;
296 newhead
.locrectab_used
= 0;
297 newhead
.sumhash_used
= 0;
299 /* Write out the header and create room for the other data structures. */
300 if (TEMP_FAILURE_RETRY (write (fd
, &newhead
, sizeof (newhead
)))
305 error (EXIT_FAILURE
, errval
, _("cannot initialize archive file"));
308 if (ftruncate64 (fd
, total
) != 0)
312 error (EXIT_FAILURE
, errval
, _("cannot resize archive file"));
315 /* Map the header and all the administration data structures. */
316 p
= mmap64 (NULL
, total
, PROT_READ
| PROT_WRITE
, MAP_SHARED
, fd
, 0);
321 error (EXIT_FAILURE
, errval
, _("cannot map archive header"));
324 /* Lock the new file. */
325 if (lockf64 (fd
, F_LOCK
, total
) != 0)
329 error (EXIT_FAILURE
, errval
, _("cannot lock new archive"));
336 /* Walk through the hash name hash table to find out what data is
337 still referenced and transfer it into the new file. */
338 oldnamehashtab
= (struct namehashent
*) ((char *) ah
->addr
339 + head
->namehash_offset
);
340 oldlocrectab
= (struct locrecent
*) ((char *) ah
->addr
341 + head
->locrectab_offset
);
343 /* Sort the old locrec table in order of data position. */
344 oldlocrecarray
= (struct oldlocrecent
*)
345 alloca (head
->namehash_size
346 * sizeof (struct oldlocrecent
));
347 for (cnt
= 0, loccnt
= 0; cnt
< head
->namehash_size
; ++cnt
)
348 if (oldnamehashtab
[cnt
].locrec_offset
!= 0)
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
),
358 for (cnt
= 0; cnt
< loccnt
; ++cnt
)
360 /* Insert this entry in the new hash table. */
361 locale_data_t old_data
;
363 struct locrecent
*oldlocrec
= oldlocrecarray
[cnt
].locrec
;
365 for (idx
= 0; idx
< __LC_LAST
; ++idx
)
368 old_data
[idx
].size
= oldlocrec
->record
[idx
].len
;
370 = ((char *) ah
->addr
+ oldlocrec
->record
[idx
].offset
);
372 __md5_buffer (old_data
[idx
].addr
, old_data
[idx
].size
,
376 if (add_locale (&new_ah
,
378 + oldnamehashtab
[oldlocrecarray
[cnt
].cnt
].name_offset
),
380 error (EXIT_FAILURE
, 0, _("cannot extend locale archive file"));
383 /* Make the file globally readable. */
384 if (fchmod (fd
, S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
) == -1)
388 error (EXIT_FAILURE
, errval
,
389 _("cannot change mode of resized locale archive"));
392 /* Rename the new file. */
393 if (rename (fname
, archivefname
) != 0)
397 error (EXIT_FAILURE
, errval
, _("cannot rename new archive"));
400 /* Close the old file. */
403 /* Add the information for the new one. */
409 open_archive (struct locarhandle
*ah
, bool readonly
)
414 struct locarhead head
;
416 size_t prefix_len
= output_prefix
? strlen (output_prefix
) : 0;
417 char archivefname
[prefix_len
+ sizeof (ARCHIVE_NAME
)];
420 memcpy (archivefname
, output_prefix
, prefix_len
);
421 strcpy (archivefname
+ prefix_len
, ARCHIVE_NAME
);
425 /* Open the archive. We must have exclusive write access. */
426 fd
= open64 (archivefname
, readonly
? O_RDONLY
: O_RDWR
);
429 /* Maybe the file does not yet exist. */
434 static const struct locarhead nullhead
=
437 .namehash_offset
= 0,
441 ah
->addr
= (void *) &nullhead
;
445 create_archive (archivefname
, ah
);
450 error (EXIT_FAILURE
, errno
, _("cannot open locale archive \"%s\""),
454 if (fstat64 (fd
, &st
) < 0)
455 error (EXIT_FAILURE
, errno
, _("cannot stat locale archive \"%s\""),
458 if (!readonly
&& lockf64 (fd
, F_LOCK
, sizeof (struct locarhead
)) == -1)
462 if (retry
++ < max_locarchive_open_retry
)
466 /* Wait for a bit. */
468 req
.tv_nsec
= 1000000 * (random () % 500 + 1);
469 (void) nanosleep (&req
, NULL
);
474 error (EXIT_FAILURE
, errno
, _("cannot lock locale archive \"%s\""),
478 /* One more check. Maybe another process replaced the archive file
479 with a new, larger one since we opened the file. */
480 if (stat64 (archivefname
, &st2
) == -1
481 || st
.st_dev
!= st2
.st_dev
482 || st
.st_ino
!= st2
.st_ino
)
484 (void) lockf64 (fd
, F_ULOCK
, sizeof (struct locarhead
));
489 /* Leave the loop. */
493 /* Read the header. */
494 if (TEMP_FAILURE_RETRY (read (fd
, &head
, sizeof (head
))) != sizeof (head
))
496 (void) lockf64 (fd
, F_ULOCK
, sizeof (struct locarhead
));
497 error (EXIT_FAILURE
, errno
, _("cannot read archive header"));
501 ah
->len
= (head
.sumhash_offset
502 + head
.sumhash_size
* sizeof (struct sumhashent
));
504 /* Now we know how large the administrative information part is.
506 ah
->addr
= mmap64 (NULL
, ah
->len
, PROT_READ
| (readonly
? 0 : PROT_WRITE
),
508 if (ah
->addr
== MAP_FAILED
)
510 (void) lockf64 (fd
, F_ULOCK
, sizeof (struct locarhead
));
511 error (EXIT_FAILURE
, errno
, _("cannot map archive header"));
517 close_archive (struct locarhandle
*ah
)
521 munmap (ah
->addr
, ah
->len
);
526 #include "../../intl/explodename.c"
527 #include "../../intl/l10nflist.c"
529 static struct namehashent
*
530 insert_name (struct locarhandle
*ah
,
531 const char *name
, size_t name_len
, bool replace
)
533 const struct locarhead
*const head
= ah
->addr
;
534 struct namehashent
*namehashtab
535 = (struct namehashent
*) ((char *) ah
->addr
+ head
->namehash_offset
);
536 unsigned int insert_idx
, idx
, incr
;
538 /* Hash value of the locale name. */
539 uint32_t hval
= archive_hashval (name
, name_len
);
542 idx
= hval
% head
->namehash_size
;
543 incr
= 1 + hval
% (head
->namehash_size
- 2);
545 /* If the name_offset field is zero this means this is a
546 deleted entry and therefore no entry can be found. */
547 while (namehashtab
[idx
].name_offset
!= 0)
549 if (namehashtab
[idx
].hashval
== hval
551 (char *) ah
->addr
+ namehashtab
[idx
].name_offset
) == 0)
553 /* Found the entry. */
554 if (namehashtab
[idx
].locrec_offset
!= 0 && ! replace
)
557 error (0, 0, _("locale '%s' already exists"), name
);
564 if (namehashtab
[idx
].hashval
== hval
&& ! be_quiet
)
566 error (0, 0, "hash collision (%u) %s, %s",
567 hval
, name
, (char *) ah
->addr
+ namehashtab
[idx
].name_offset
);
570 /* Remember the first place we can insert the new entry. */
571 if (namehashtab
[idx
].locrec_offset
== 0 && insert_idx
== -1)
575 if (idx
>= head
->namehash_size
)
576 idx
-= head
->namehash_size
;
579 /* Add as early as possible. */
580 if (insert_idx
!= -1)
583 namehashtab
[idx
].hashval
= hval
; /* no-op if replacing an old entry. */
584 return &namehashtab
[idx
];
588 add_alias (struct locarhandle
*ah
, const char *alias
, bool replace
,
589 const char *oldname
, uint32_t *locrec_offset_p
)
591 uint32_t locrec_offset
= *locrec_offset_p
;
592 struct locarhead
*head
= ah
->addr
;
593 const size_t name_len
= strlen (alias
);
594 struct namehashent
*namehashent
= insert_name (ah
, alias
, strlen (alias
),
596 if (namehashent
== NULL
&& ! replace
)
599 if (namehashent
->name_offset
== 0)
601 /* We are adding a new hash entry for this alias.
602 Determine whether we have to resize the file. */
603 if (head
->string_used
+ name_len
+ 1 > head
->string_size
604 || 100 * head
->namehash_used
> 75 * head
->namehash_size
)
606 /* The current archive is not large enough. */
607 enlarge_archive (ah
, head
);
609 /* The locrecent might have moved, so we have to look up
610 the old name afresh. */
611 namehashent
= insert_name (ah
, oldname
, strlen (oldname
), true);
612 assert (namehashent
->name_offset
!= 0);
613 assert (namehashent
->locrec_offset
!= 0);
614 *locrec_offset_p
= namehashent
->locrec_offset
;
616 /* Tail call to try the whole thing again. */
617 add_alias (ah
, alias
, replace
, oldname
, locrec_offset_p
);
621 /* Add the name string. */
622 memcpy (ah
->addr
+ head
->string_offset
+ head
->string_used
,
623 alias
, name_len
+ 1);
624 namehashent
->name_offset
= head
->string_offset
+ head
->string_used
;
625 head
->string_used
+= name_len
+ 1;
627 ++head
->namehash_used
;
630 if (namehashent
->locrec_offset
!= 0)
632 /* Replacing an existing entry.
633 Mark that we are no longer using the old locrecent. */
634 struct locrecent
*locrecent
635 = (struct locrecent
*) ((char *) ah
->addr
636 + namehashent
->locrec_offset
);
640 /* Point this entry at the locrecent installed for the main name. */
641 namehashent
->locrec_offset
= locrec_offset
;
644 static int /* qsort comparator used below */
645 cmpcategorysize (const void *a
, const void *b
)
647 if (*(const void **) a
== NULL
)
649 if (*(const void **) b
== NULL
)
651 return ((*(const struct locale_category_data
**) a
)->size
652 - (*(const struct locale_category_data
**) b
)->size
);
655 /* Check the content of the archive for duplicates. Add the content
656 of the files if necessary. Returns the locrec_offset. */
658 add_locale (struct locarhandle
*ah
,
659 const char *name
, locale_data_t data
, bool replace
)
661 /* First look for the name. If it already exists and we are not
662 supposed to replace it don't do anything. If it does not exist
663 we have to allocate a new locale record. */
664 size_t name_len
= strlen (name
);
665 uint32_t file_offsets
[__LC_LAST
];
666 unsigned int num_new_offsets
= 0;
667 struct sumhashent
*sumhashtab
;
669 unsigned int cnt
, idx
;
670 struct locarhead
*head
;
671 struct namehashent
*namehashent
;
673 struct locrecent
*locrecent
;
676 struct locale_category_data
*size_order
[__LC_LAST
];
677 const size_t pagesz
= getpagesize ();
681 sumhashtab
= (struct sumhashent
*) ((char *) ah
->addr
682 + head
->sumhash_offset
);
684 memset (file_offsets
, 0, sizeof (file_offsets
));
686 size_order
[LC_ALL
] = NULL
;
687 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
689 size_order
[cnt
] = &data
[cnt
];
691 /* Sort the array in ascending order of data size. */
692 qsort (size_order
, __LC_LAST
, sizeof size_order
[0], cmpcategorysize
);
695 data
[LC_ALL
].size
= 0;
696 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
697 if (size_order
[cnt
] != NULL
)
699 const size_t rounded_size
= (size_order
[cnt
]->size
+ 15) & -16;
700 if (data
[LC_ALL
].size
+ rounded_size
> 2 * pagesz
)
702 /* This category makes the small-categories block
703 stop being small, so this is the end of the road. */
705 size_order
[cnt
++] = NULL
;
706 while (cnt
< __LC_LAST
);
709 data
[LC_ALL
].size
+= rounded_size
;
710 small_mask
|= 1 << (size_order
[cnt
] - data
);
713 /* Copy the data for all the small categories into the LC_ALL
716 data
[LC_ALL
].addr
= alloca (data
[LC_ALL
].size
);
717 memset (data
[LC_ALL
].addr
, 0, data
[LC_ALL
].size
);
719 ptr
= data
[LC_ALL
].addr
;
720 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
721 if (small_mask
& (1 << cnt
))
723 memcpy (ptr
, data
[cnt
].addr
, data
[cnt
].size
);
724 ptr
+= (data
[cnt
].size
+ 15) & -16;
726 __md5_buffer (data
[LC_ALL
].addr
, data
[LC_ALL
].size
, data
[LC_ALL
].sum
);
728 /* For each locale category data set determine whether the same data
729 is already somewhere in the archive. */
730 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
731 if (small_mask
== 0 ? cnt
!= LC_ALL
: !(small_mask
& (1 << cnt
)))
735 /* Compute the hash value of the checksum to determine a
736 starting point for the search in the MD5 hash value
738 hval
= archive_hashval (data
[cnt
].sum
, 16);
740 idx
= hval
% head
->sumhash_size
;
741 incr
= 1 + hval
% (head
->sumhash_size
- 2);
743 while (sumhashtab
[idx
].file_offset
!= 0)
745 if (memcmp (data
[cnt
].sum
, sumhashtab
[idx
].sum
, 16) == 0)
748 file_offsets
[cnt
] = sumhashtab
[idx
].file_offset
;
754 if (idx
>= head
->sumhash_size
)
755 idx
-= head
->sumhash_size
;
759 /* Find a slot for the locale name in the hash table. */
760 namehashent
= insert_name (ah
, name
, name_len
, replace
);
761 if (namehashent
== NULL
) /* Already exists and !REPLACE. */
764 /* Determine whether we have to resize the file. */
765 if (100 * (head
->sumhash_used
+ num_new_offsets
) > 75 * head
->sumhash_size
766 || (namehashent
->locrec_offset
== 0
767 && (head
->locrectab_used
== head
->locrectab_size
768 || head
->string_used
+ name_len
+ 1 > head
->string_size
769 || 100 * head
->namehash_used
> 75 * head
->namehash_size
)))
771 /* The current archive is not large enough. */
772 enlarge_archive (ah
, head
);
773 return add_locale (ah
, name
, data
, replace
);
776 /* Add the locale data which is not yet in the archive. */
777 for (cnt
= 0, lastoffset
= 0; cnt
< __LC_LAST
; ++cnt
)
778 if ((small_mask
== 0 ? cnt
!= LC_ALL
: !(small_mask
& (1 << cnt
)))
779 && file_offsets
[cnt
] == 0)
781 /* The data for this section is not yet available in the
782 archive. Append it. */
786 lastpos
= lseek64 (ah
->fd
, 0, SEEK_END
);
787 if (lastpos
== (off64_t
) -1)
788 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
790 /* If block of small categories would cross page boundary,
791 align it unless it immediately follows a large category. */
792 if (cnt
== LC_ALL
&& lastoffset
!= lastpos
793 && ((((lastpos
& (pagesz
- 1)) + data
[cnt
].size
+ pagesz
- 1)
795 > ((data
[cnt
].size
+ pagesz
- 1) & -pagesz
)))
797 size_t sz
= pagesz
- (lastpos
& (pagesz
- 1));
798 char *zeros
= alloca (sz
);
800 memset (zeros
, 0, sz
);
801 if (TEMP_FAILURE_RETRY (write (ah
->fd
, zeros
, sz
) != sz
))
802 error (EXIT_FAILURE
, errno
,
803 _("cannot add to locale archive"));
808 /* Align all data to a 16 byte boundary. */
809 if ((lastpos
& 15) != 0)
811 static const char zeros
[15] = { 0, };
813 if (TEMP_FAILURE_RETRY (write (ah
->fd
, zeros
, 16 - (lastpos
& 15)))
814 != 16 - (lastpos
& 15))
815 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
817 lastpos
+= 16 - (lastpos
& 15);
820 /* Remember the position. */
821 file_offsets
[cnt
] = lastpos
;
822 lastoffset
= lastpos
+ data
[cnt
].size
;
824 /* Write the data. */
825 if (TEMP_FAILURE_RETRY (write (ah
->fd
, data
[cnt
].addr
, data
[cnt
].size
))
827 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
829 /* Add the hash value to the hash table. */
830 md5hval
= archive_hashval (data
[cnt
].sum
, 16);
832 idx
= md5hval
% head
->sumhash_size
;
833 incr
= 1 + md5hval
% (head
->sumhash_size
- 2);
835 while (sumhashtab
[idx
].file_offset
!= 0)
838 if (idx
>= head
->sumhash_size
)
839 idx
-= head
->sumhash_size
;
842 memcpy (sumhashtab
[idx
].sum
, data
[cnt
].sum
, 16);
843 sumhashtab
[idx
].file_offset
= file_offsets
[cnt
];
845 ++head
->sumhash_used
;
848 lastoffset
= file_offsets
[LC_ALL
];
849 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
850 if (small_mask
& (1 << cnt
))
852 file_offsets
[cnt
] = lastoffset
;
853 lastoffset
+= (data
[cnt
].size
+ 15) & -16;
856 if (namehashent
->name_offset
== 0)
858 /* Add the name string. */
859 memcpy ((char *) ah
->addr
+ head
->string_offset
+ head
->string_used
,
861 namehashent
->name_offset
= head
->string_offset
+ head
->string_used
;
862 head
->string_used
+= name_len
+ 1;
863 ++head
->namehash_used
;
866 if (namehashent
->locrec_offset
== 0)
868 /* Allocate a name location record. */
869 namehashent
->locrec_offset
= (head
->locrectab_offset
870 + (head
->locrectab_used
++
871 * sizeof (struct locrecent
)));
872 locrecent
= (struct locrecent
*) ((char *) ah
->addr
873 + namehashent
->locrec_offset
);
878 /* If there are other aliases pointing to this locrecent,
879 we still need a new one. If not, reuse the old one. */
881 locrecent
= (struct locrecent
*) ((char *) ah
->addr
882 + namehashent
->locrec_offset
);
883 if (locrecent
->refs
> 1)
886 namehashent
->locrec_offset
= (head
->locrectab_offset
887 + (head
->locrectab_used
++
888 * sizeof (struct locrecent
)));
889 locrecent
= (struct locrecent
*) ((char *) ah
->addr
890 + namehashent
->locrec_offset
);
895 /* Fill in the table with the locations of the locale data. */
896 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
898 locrecent
->record
[cnt
].offset
= file_offsets
[cnt
];
899 locrecent
->record
[cnt
].len
= data
[cnt
].size
;
902 return namehashent
->locrec_offset
;
906 /* Check the content of the archive for duplicates. Add the content
907 of the files if necessary. Add all the names, possibly overwriting
910 add_locale_to_archive (ah
, name
, data
, replace
)
911 struct locarhandle
*ah
;
916 char *normalized_name
= NULL
;
917 uint32_t locrec_offset
;
919 /* First analyze the name to decide how to archive it. */
920 const char *language
;
921 const char *modifier
;
922 const char *territory
;
924 const char *normalized_codeset
;
925 int mask
= _nl_explode_name (strdupa (name
),
926 &language
, &modifier
, &territory
,
927 &codeset
, &normalized_codeset
);
929 if (mask
& XPG_NORM_CODESET
)
930 /* This name contains a codeset in unnormalized form.
931 We will store it in the archive with a normalized name. */
932 asprintf (&normalized_name
, "%s%s%s.%s%s%s",
933 language
, territory
== NULL
? "" : "_", territory
?: "",
934 (mask
& XPG_NORM_CODESET
) ? normalized_codeset
: codeset
,
935 modifier
== NULL
? "" : "@", modifier
?: "");
937 /* This call does the main work. */
938 locrec_offset
= add_locale (ah
, normalized_name
?: name
, data
, replace
);
939 if (locrec_offset
== 0)
941 free (normalized_name
);
942 if (mask
& XPG_NORM_CODESET
)
943 free ((char *) normalized_codeset
);
947 if ((mask
& XPG_CODESET
) == 0)
949 /* This name lacks a codeset, so determine the locale's codeset and
950 add an alias for its name with normalized codeset appended. */
955 unsigned int nstrings
;
956 unsigned int strindex
[0];
957 } *filedata
= data
[LC_CTYPE
].addr
;
958 codeset
= (char *) filedata
959 + filedata
->strindex
[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME
)];
960 char *normalized_codeset_name
= NULL
;
962 normalized_codeset
= _nl_normalize_codeset (codeset
, strlen (codeset
));
963 mask
|= XPG_NORM_CODESET
;
965 asprintf (&normalized_codeset_name
, "%s%s%s.%s%s%s",
966 language
, territory
== NULL
? "" : "_", territory
?: "",
968 modifier
== NULL
? "" : "@", modifier
?: "");
970 add_alias (ah
, normalized_codeset_name
, replace
,
971 normalized_name
?: name
, &locrec_offset
);
972 free (normalized_codeset_name
);
975 /* Now read the locale.alias files looking for lines whose
976 right hand side matches our name after normalization. */
977 if (alias_file
!= NULL
)
980 fp
= fopen (alias_file
, "rm");
982 error (1, errno
, _("locale alias file `%s' not found"),
985 /* No threads present. */
986 __fsetlocking (fp
, FSETLOCKING_BYCALLER
);
988 while (! feof_unlocked (fp
))
990 /* It is a reasonable approach to use a fix buffer here
992 a) we are only interested in the first two fields
993 b) these fields must be usable as file names and so must
1000 if (fgets_unlocked (buf
, BUFSIZ
, fp
) == NULL
)
1005 /* Ignore leading white space. */
1006 while (isspace (cp
[0]) && cp
[0] != '\n')
1009 /* A leading '#' signals a comment line. */
1010 if (cp
[0] != '\0' && cp
[0] != '#' && cp
[0] != '\n')
1013 while (cp
[0] != '\0' && !isspace (cp
[0]))
1015 /* Terminate alias name. */
1019 /* Now look for the beginning of the value. */
1020 while (isspace (cp
[0]))
1026 while (cp
[0] != '\0' && !isspace (cp
[0]))
1028 /* Terminate value. */
1031 /* This has to be done to make the following
1032 test for the end of line possible. We are
1033 looking for the terminating '\n' which do not
1038 else if (cp
[0] != '\0')
1041 /* Does this alias refer to our locale? We will
1042 normalize the right hand side and compare the
1043 elements of the normalized form. */
1045 const char *rhs_language
;
1046 const char *rhs_modifier
;
1047 const char *rhs_territory
;
1048 const char *rhs_codeset
;
1049 const char *rhs_normalized_codeset
;
1050 int rhs_mask
= _nl_explode_name (value
,
1055 &rhs_normalized_codeset
);
1056 if (!strcmp (language
, rhs_language
)
1057 && ((rhs_mask
& XPG_CODESET
)
1058 /* He has a codeset, it must match normalized. */
1059 ? !strcmp ((mask
& XPG_NORM_CODESET
)
1060 ? normalized_codeset
: codeset
,
1061 (rhs_mask
& XPG_NORM_CODESET
)
1062 ? rhs_normalized_codeset
: rhs_codeset
)
1063 /* He has no codeset, we must also have none. */
1064 : (mask
& XPG_CODESET
) == 0)
1065 /* Codeset (or lack thereof) matches. */
1066 && !strcmp (territory
?: "", rhs_territory
?: "")
1067 && !strcmp (modifier
?: "", rhs_modifier
?: ""))
1068 /* We have a winner. */
1069 add_alias (ah
, alias
, replace
,
1070 normalized_name
?: name
, &locrec_offset
);
1071 if (rhs_mask
& XPG_NORM_CODESET
)
1072 free ((char *) rhs_normalized_codeset
);
1077 /* Possibly not the whole line fits into the buffer.
1078 Ignore the rest of the line. */
1079 while (strchr (cp
, '\n') == NULL
)
1082 if (fgets_unlocked (buf
, BUFSIZ
, fp
) == NULL
)
1083 /* Make sure the inner loop will be left. The outer
1084 loop will exit at the `feof' test. */
1092 free (normalized_name
);
1094 if (mask
& XPG_NORM_CODESET
)
1095 free ((char *) normalized_codeset
);
1102 add_locales_to_archive (nlist
, list
, replace
)
1107 struct locarhandle ah
;
1110 /* Open the archive. This call never returns if we cannot
1111 successfully open the archive. */
1112 open_archive (&ah
, false);
1116 const char *fname
= *list
++;
1117 size_t fnamelen
= strlen (fname
);
1126 printf (_("Adding %s\n"), fname
);
1128 /* First see whether this really is a directory and whether it
1129 contains all the require locale category files. */
1130 if (stat64 (fname
, &st
) < 0)
1132 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname
,
1136 if (!S_ISDIR (st
.st_mode
))
1138 error (0, 0, _("\"%s\" is no directory; ignored"), fname
);
1142 dirp
= opendir (fname
);
1145 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1146 fname
, strerror (errno
));
1151 while ((d
= readdir64 (dirp
)) != NULL
)
1153 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1155 if (strcmp (d
->d_name
, locnames
[cnt
]) == 0)
1157 unsigned char d_type
;
1159 /* We have an object of the required name. If it's
1160 a directory we have to look at a file with the
1161 prefix "SYS_". Otherwise we have found what we
1163 #ifdef _DIRENT_HAVE_D_TYPE
1166 if (d_type
!= DT_REG
)
1169 char fullname
[fnamelen
+ 2 * strlen (d
->d_name
) + 7];
1171 #ifdef _DIRENT_HAVE_D_TYPE
1172 if (d_type
== DT_UNKNOWN
)
1175 strcpy (stpcpy (stpcpy (fullname
, fname
), "/"),
1178 if (stat64 (fullname
, &st
) == -1)
1179 /* We cannot stat the file, ignore it. */
1182 d_type
= IFTODT (st
.st_mode
);
1185 if (d_type
== DT_DIR
)
1187 /* We have to do more tests. The file is a
1188 directory and it therefore must contain a
1189 regular file with the same name except a
1191 char *t
= stpcpy (stpcpy (fullname
, fname
), "/");
1192 strcpy (stpcpy (stpcpy (t
, d
->d_name
), "/SYS_"),
1195 if (stat64 (fullname
, &st
) == -1)
1196 /* There is no SYS_* file or we cannot
1200 d_type
= IFTODT (st
.st_mode
);
1204 /* If we found a regular file (eventually after
1205 following a symlink) we are successful. */
1206 if (d_type
== DT_REG
)
1214 if (seen
!= __LC_LAST
- 1)
1216 /* We don't have all locale category files. Ignore the name. */
1217 error (0, 0, _("incomplete set of locale files in \"%s\""),
1222 /* Add the files to the archive. To do this we first compute
1223 sizes and the MD5 sums of all the files. */
1224 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1227 char fullname
[fnamelen
+ 2 * strlen (locnames
[cnt
]) + 7];
1230 strcpy (stpcpy (stpcpy (fullname
, fname
), "/"), locnames
[cnt
]);
1231 fd
= open64 (fullname
, O_RDONLY
);
1232 if (fd
== -1 || fstat64 (fd
, &st
) == -1)
1234 /* Cannot read the file. */
1240 if (S_ISDIR (st
.st_mode
))
1244 t
= stpcpy (stpcpy (fullname
, fname
), "/");
1245 strcpy (stpcpy (stpcpy (t
, locnames
[cnt
]), "/SYS_"),
1248 fd
= open64 (fullname
, O_RDONLY
);
1249 if (fd
== -1 || fstat64 (fd
, &st
) == -1
1250 || !S_ISREG (st
.st_mode
))
1259 data
[cnt
].addr
= mmap64 (NULL
, st
.st_size
, PROT_READ
, MAP_SHARED
,
1261 if (data
[cnt
].addr
== MAP_FAILED
)
1263 /* Cannot map it. */
1268 data
[cnt
].size
= st
.st_size
;
1269 __md5_buffer (data
[cnt
].addr
, st
.st_size
, data
[cnt
].sum
);
1271 /* We don't need the file descriptor anymore. */
1275 if (cnt
!= __LC_LAST
)
1279 munmap (data
[cnt
].addr
, data
[cnt
].size
);
1281 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname
);
1286 result
|= add_locale_to_archive (&ah
, basename (fname
), data
, replace
);
1288 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1290 munmap (data
[cnt
].addr
, data
[cnt
].size
);
1294 close_archive (&ah
);
1301 delete_locales_from_archive (nlist
, list
)
1305 struct locarhandle ah
;
1306 struct locarhead
*head
;
1307 struct namehashent
*namehashtab
;
1309 /* Open the archive. This call never returns if we cannot
1310 successfully open the archive. */
1311 open_archive (&ah
, false);
1314 namehashtab
= (struct namehashent
*) ((char *) ah
.addr
1315 + head
->namehash_offset
);
1319 const char *locname
= *list
++;
1324 /* Search for this locale in the archive. */
1325 hval
= archive_hashval (locname
, strlen (locname
));
1327 idx
= hval
% head
->namehash_size
;
1328 incr
= 1 + hval
% (head
->namehash_size
- 2);
1330 /* If the name_offset field is zero this means this is no
1331 deleted entry and therefore no entry can be found. */
1332 while (namehashtab
[idx
].name_offset
!= 0)
1334 if (namehashtab
[idx
].hashval
== hval
1335 && (strcmp (locname
,
1336 (char *) ah
.addr
+ namehashtab
[idx
].name_offset
)
1339 /* Found the entry. Now mark it as removed by zero-ing
1340 the reference to the locale record. */
1341 namehashtab
[idx
].locrec_offset
= 0;
1346 if (idx
>= head
->namehash_size
)
1347 idx
-= head
->namehash_size
;
1350 if (namehashtab
[idx
].name_offset
== 0 && ! be_quiet
)
1351 error (0, 0, _("locale \"%s\" not in archive"), locname
);
1354 close_archive (&ah
);
1363 uint32_t locrec_offset
;
1369 const unsigned char *sum
;
1370 uint32_t file_offset
;
1376 nameentcmp (const void *a
, const void *b
)
1378 return strcmp (((const struct nameent
*) a
)->name
,
1379 ((const struct nameent
*) b
)->name
);
1384 dataentcmp (const void *a
, const void *b
)
1386 if (((const struct dataent
*) a
)->file_offset
1387 < ((const struct dataent
*) b
)->file_offset
)
1390 if (((const struct dataent
*) a
)->file_offset
1391 > ((const struct dataent
*) b
)->file_offset
)
1399 show_archive_content (int verbose
)
1401 struct locarhandle ah
;
1402 struct locarhead
*head
;
1403 struct namehashent
*namehashtab
;
1404 struct nameent
*names
;
1407 /* Open the archive. This call never returns if we cannot
1408 successfully open the archive. */
1409 open_archive (&ah
, true);
1413 names
= (struct nameent
*) xmalloc (head
->namehash_used
1414 * sizeof (struct nameent
));
1416 namehashtab
= (struct namehashent
*) ((char *) ah
.addr
1417 + head
->namehash_offset
);
1418 for (cnt
= used
= 0; cnt
< head
->namehash_size
; ++cnt
)
1419 if (namehashtab
[cnt
].locrec_offset
!= 0)
1421 assert (used
< head
->namehash_used
);
1422 names
[used
].name
= ah
.addr
+ namehashtab
[cnt
].name_offset
;
1423 names
[used
++].locrec_offset
= namehashtab
[cnt
].locrec_offset
;
1426 /* Sort the names. */
1427 qsort (names
, used
, sizeof (struct nameent
), nameentcmp
);
1431 struct dataent
*files
;
1432 struct sumhashent
*sumhashtab
;
1435 files
= (struct dataent
*) xmalloc (head
->sumhash_used
1436 * sizeof (struct sumhashent
));
1438 sumhashtab
= (struct sumhashent
*) ((char *) ah
.addr
1439 + head
->sumhash_offset
);
1440 for (cnt
= sumused
= 0; cnt
< head
->sumhash_size
; ++cnt
)
1441 if (sumhashtab
[cnt
].file_offset
!= 0)
1443 assert (sumused
< head
->sumhash_used
);
1444 files
[sumused
].sum
= (const unsigned char *) sumhashtab
[cnt
].sum
;
1445 files
[sumused
].file_offset
= sumhashtab
[cnt
].file_offset
;
1446 files
[sumused
++].nlink
= 0;
1449 /* Sort by file locations. */
1450 qsort (files
, sumused
, sizeof (struct dataent
), dataentcmp
);
1452 /* Compute nlink fields. */
1453 for (cnt
= 0; cnt
< used
; ++cnt
)
1455 struct locrecent
*locrec
;
1458 locrec
= (struct locrecent
*) ((char *) ah
.addr
1459 + names
[cnt
].locrec_offset
);
1460 for (idx
= 0; idx
< __LC_LAST
; ++idx
)
1461 if (locrec
->record
[LC_ALL
].offset
!= 0
1463 || (locrec
->record
[idx
].offset
1464 < locrec
->record
[LC_ALL
].offset
)
1465 || (locrec
->record
[idx
].offset
+ locrec
->record
[idx
].len
1466 > (locrec
->record
[LC_ALL
].offset
1467 + locrec
->record
[LC_ALL
].len
)))
1470 struct dataent
*data
, dataent
;
1472 dataent
.file_offset
= locrec
->record
[idx
].offset
;
1473 data
= (struct dataent
*) bsearch (&dataent
, files
, sumused
,
1474 sizeof (struct dataent
),
1476 assert (data
!= NULL
);
1482 for (cnt
= 0; cnt
< used
; ++cnt
)
1484 struct locrecent
*locrec
;
1487 locrec
= (struct locrecent
*) ((char *) ah
.addr
1488 + names
[cnt
].locrec_offset
);
1489 for (idx
= 0; idx
< __LC_LAST
; ++idx
)
1492 struct dataent
*data
, dataent
;
1494 dataent
.file_offset
= locrec
->record
[idx
].offset
;
1495 if (locrec
->record
[LC_ALL
].offset
!= 0
1496 && dataent
.file_offset
>= locrec
->record
[LC_ALL
].offset
1497 && (dataent
.file_offset
+ locrec
->record
[idx
].len
1498 <= (locrec
->record
[LC_ALL
].offset
1499 + locrec
->record
[LC_ALL
].len
)))
1500 dataent
.file_offset
= locrec
->record
[LC_ALL
].offset
;
1502 data
= (struct dataent
*) bsearch (&dataent
, files
, sumused
,
1503 sizeof (struct dataent
),
1505 printf ("%6d %7x %3d%c ",
1506 locrec
->record
[idx
].len
, locrec
->record
[idx
].offset
,
1508 dataent
.file_offset
== locrec
->record
[LC_ALL
].offset
1510 for (i
= 0; i
< 16; i
+= 4)
1511 printf ("%02x%02x%02x%02x",
1512 data
->sum
[i
], data
->sum
[i
+ 1],
1513 data
->sum
[i
+ 2], data
->sum
[i
+ 3]);
1514 printf (" %s/%s\n", names
[cnt
].name
,
1515 idx
== LC_MESSAGES
? "LC_MESSAGES/SYS_LC_MESSAGES"
1521 for (cnt
= 0; cnt
< used
; ++cnt
)
1522 puts (names
[cnt
].name
);
1524 close_archive (&ah
);
1526 exit (EXIT_SUCCESS
);