1 /* Copyright (C) 2002 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 "simple-hash.h"
47 #include "localedef.h"
49 extern const char *output_prefix
;
51 #define ARCHIVE_NAME LOCALEDIR "/locale-archive"
53 static const char *locnames
[] =
55 #define DEFINE_CATEGORY(category, category_name, items, a) \
56 [category] = category_name,
57 #include "categories.def"
58 #undef DEFINE_CATEGORY
62 /* Size of the initial archive header. */
63 #define INITIAL_NUM_NAMES 450
64 #define INITIAL_SIZE_STRINGS 3500
65 #define INITIAL_NUM_LOCREC 350
66 #define INITIAL_NUM_SUMS 2000
70 create_archive (const char *archivefname
, struct locarhandle
*ah
)
73 char fname
[strlen (archivefname
) + sizeof (".XXXXXX")];
74 struct locarhead head
;
78 strcpy (stpcpy (fname
, archivefname
), ".XXXXXX");
80 /* Create a temporary file in the correct directory. */
83 error (EXIT_FAILURE
, errno
, _("cannot create temporary file"));
85 /* Create the initial content of the archive. */
86 head
.magic
= AR_MAGIC
;
87 head
.namehash_offset
= sizeof (struct locarhead
);
88 head
.namehash_used
= 0;
89 head
.namehash_size
= next_prime (INITIAL_NUM_NAMES
);
91 head
.string_offset
= (head
.namehash_offset
92 + head
.namehash_size
* sizeof (struct namehashent
));
94 head
.string_size
= INITIAL_SIZE_STRINGS
;
96 head
.locrectab_offset
= head
.string_offset
+ head
.string_size
;
97 head
.locrectab_used
= 0;
98 head
.locrectab_size
= INITIAL_NUM_LOCREC
;
100 head
.sumhash_offset
= (head
.locrectab_offset
101 + head
.locrectab_size
* sizeof (struct locrecent
));
102 head
.sumhash_used
= 0;
103 head
.sumhash_size
= next_prime (INITIAL_NUM_SUMS
);
105 total
= head
.sumhash_offset
+ head
.sumhash_size
* sizeof (struct sumhashent
);
107 /* Write out the header and create room for the other data structures. */
108 if (TEMP_FAILURE_RETRY (write (fd
, &head
, sizeof (head
))) != sizeof (head
))
112 error (EXIT_FAILURE
, errval
, _("cannot initialize archive file"));
115 if (ftruncate64 (fd
, total
) != 0)
119 error (EXIT_FAILURE
, errval
, _("cannot resize archive file"));
122 /* Map the header and all the administration data structures. */
123 p
= mmap64 (NULL
, total
, PROT_READ
| PROT_WRITE
, MAP_SHARED
, fd
, 0);
128 error (EXIT_FAILURE
, errval
, _("cannot map archive header"));
131 /* Now try to rename it. We don't use the rename function since
132 this would overwrite a file which has been created in
134 if (link (fname
, archivefname
) == -1)
138 /* We cannot use the just created file. */
142 if (errval
== EEXIST
)
144 /* There is already an archive. Must have been a localedef run
145 which happened in parallel. Simply open this file then. */
146 open_archive (ah
, false);
150 error (EXIT_FAILURE
, errval
, _("failed to create new locale archive"));
153 /* Remove the temporary name. */
156 /* Make the file globally readable. */
157 if (fchmod (fd
, S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
) == -1)
160 unlink (archivefname
);
161 error (EXIT_FAILURE
, errval
,
162 _("cannot change mode of new locale archive"));
171 /* This structure and qsort comparator function are used below to sort an
172 old archive's locrec table in order of data position in the file. */
176 struct locrecent
*locrec
;
180 oldlocrecentcmp (const void *a
, const void *b
)
182 struct locrecent
*la
= ((const struct oldlocrecent
*) a
)->locrec
;
183 struct locrecent
*lb
= ((const struct oldlocrecent
*) b
)->locrec
;
184 uint32_t start_a
= -1, end_a
= 0;
185 uint32_t start_b
= -1, end_b
= 0;
188 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
191 if (la
->record
[cnt
].offset
< start_a
)
192 start_a
= la
->record
[cnt
].offset
;
193 if (la
->record
[cnt
].offset
+ la
->record
[cnt
].len
> end_a
)
194 end_a
= la
->record
[cnt
].offset
+ la
->record
[cnt
].len
;
196 assert (start_a
!= (uint32_t)-1);
199 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
202 if (lb
->record
[cnt
].offset
< start_b
)
203 start_b
= lb
->record
[cnt
].offset
;
204 if (lb
->record
[cnt
].offset
+ lb
->record
[cnt
].len
> end_b
)
205 end_b
= lb
->record
[cnt
].offset
+ lb
->record
[cnt
].len
;
207 assert (start_b
!= (uint32_t)-1);
210 if (start_a
!= start_b
)
211 return (int)start_a
- (int)start_b
;
212 return (int)end_a
- (int)end_b
;
216 /* forward decl for below */
217 static uint32_t add_locale (struct locarhandle
*ah
, const char *name
,
218 locale_data_t data
, bool replace
);
221 enlarge_archive (struct locarhandle
*ah
, const struct locarhead
*head
)
225 struct locarhead newhead
;
228 unsigned int cnt
, loccnt
;
229 struct namehashent
*oldnamehashtab
;
230 struct locrecent
*oldlocrectab
;
231 struct locarhandle new_ah
;
232 size_t prefix_len
= output_prefix
? strlen (output_prefix
) : 0;
233 char archivefname
[prefix_len
+ sizeof (ARCHIVE_NAME
)];
234 char fname
[prefix_len
+ sizeof (ARCHIVE_NAME
) + sizeof (".XXXXXX") - 1];
235 struct oldlocrecent
*oldlocrecarray
;
238 memcpy (archivefname
, output_prefix
, prefix_len
);
239 strcpy (archivefname
+ prefix_len
, ARCHIVE_NAME
);
240 strcpy (stpcpy (fname
, archivefname
), ".XXXXXX");
242 /* Not all of the old file has to be mapped. Change this now this
243 we will have to access the whole content. */
244 if (fstat64 (ah
->fd
, &st
) != 0
245 || (ah
->addr
= mmap64 (NULL
, st
.st_size
, PROT_READ
| PROT_WRITE
,
246 MAP_SHARED
, ah
->fd
, 0)) == MAP_FAILED
)
247 error (EXIT_FAILURE
, errno
, _("cannot map locale archive file"));
248 ah
->len
= st
.st_size
;
250 /* Create a temporary file in the correct directory. */
251 fd
= mkstemp (fname
);
253 error (EXIT_FAILURE
, errno
, _("cannot create temporary file"));
255 /* Copy the existing head information. */
258 /* Create the new archive header. The sizes of the various tables
259 should be double from what is currently used. */
260 newhead
.namehash_size
= MAX (next_prime (2 * newhead
.namehash_used
),
261 newhead
.namehash_size
);
263 printf ("name: size: %u, used: %d, new: size: %u\n",
264 head
->namehash_size
, head
->namehash_used
, newhead
.namehash_size
);
266 newhead
.string_offset
= (newhead
.namehash_offset
267 + (newhead
.namehash_size
268 * sizeof (struct namehashent
)));
269 newhead
.string_size
= MAX (2 * newhead
.string_used
, newhead
.string_size
);
271 newhead
.locrectab_offset
= newhead
.string_offset
+ newhead
.string_size
;
272 newhead
.locrectab_size
= MAX (2 * newhead
.locrectab_used
,
273 newhead
.locrectab_size
);
275 newhead
.sumhash_offset
= (newhead
.locrectab_offset
276 + (newhead
.locrectab_size
277 * sizeof (struct locrecent
)));
278 newhead
.sumhash_size
= MAX (next_prime (2 * newhead
.sumhash_used
),
279 newhead
.sumhash_size
);
281 total
= (newhead
.sumhash_offset
282 + newhead
.sumhash_size
* sizeof (struct sumhashent
));
284 /* The new file is empty now. */
285 newhead
.namehash_used
= 0;
286 newhead
.string_used
= 0;
287 newhead
.locrectab_used
= 0;
288 newhead
.sumhash_used
= 0;
290 /* Write out the header and create room for the other data structures. */
291 if (TEMP_FAILURE_RETRY (write (fd
, &newhead
, sizeof (newhead
)))
296 error (EXIT_FAILURE
, errval
, _("cannot initialize archive file"));
299 if (ftruncate64 (fd
, total
) != 0)
303 error (EXIT_FAILURE
, errval
, _("cannot resize archive file"));
306 /* Map the header and all the administration data structures. */
307 p
= mmap64 (NULL
, total
, PROT_READ
| PROT_WRITE
, MAP_SHARED
, fd
, 0);
312 error (EXIT_FAILURE
, errval
, _("cannot map archive header"));
315 /* Lock the new file. */
316 if (lockf64 (fd
, F_LOCK
, total
) != 0)
320 error (EXIT_FAILURE
, errval
, _("cannot lock new archive"));
327 /* Walk through the hash name hash table to find out what data is
328 still referenced and transfer it into the new file. */
329 oldnamehashtab
= (struct namehashent
*) ((char *) ah
->addr
330 + head
->namehash_offset
);
331 oldlocrectab
= (struct locrecent
*) ((char *) ah
->addr
332 + head
->locrectab_offset
);
334 /* Sort the old locrec table in order of data position. */
335 oldlocrecarray
= (struct oldlocrecent
*)
336 alloca (head
->namehash_size
337 * sizeof (struct oldlocrecent
));
338 for (cnt
= 0, loccnt
= 0; cnt
< head
->namehash_size
; ++cnt
)
339 if (oldnamehashtab
[cnt
].locrec_offset
!= 0)
341 oldlocrecarray
[loccnt
].cnt
= cnt
;
342 oldlocrecarray
[loccnt
++].locrec
343 = (struct locrecent
*) ((char *) ah
->addr
344 + oldnamehashtab
[cnt
].locrec_offset
);
346 qsort (oldlocrecarray
, loccnt
, sizeof (struct oldlocrecent
),
349 for (cnt
= 0; cnt
< loccnt
; ++cnt
)
351 /* Insert this entry in the new hash table. */
352 locale_data_t old_data
;
354 struct locrecent
*oldlocrec
= oldlocrecarray
[cnt
].locrec
;
356 for (idx
= 0; idx
< __LC_LAST
; ++idx
)
359 old_data
[idx
].size
= oldlocrec
->record
[idx
].len
;
361 = ((char *) ah
->addr
+ oldlocrec
->record
[idx
].offset
);
363 __md5_buffer (old_data
[idx
].addr
, old_data
[idx
].size
,
367 if (add_locale (&new_ah
,
369 + oldnamehashtab
[oldlocrecarray
[cnt
].cnt
].name_offset
),
371 error (EXIT_FAILURE
, 0, _("cannot extend locale archive file"));
374 /* Make the file globally readable. */
375 if (fchmod (fd
, S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
) == -1)
379 error (EXIT_FAILURE
, errval
,
380 _("cannot change mode of resized locale archive"));
383 /* Rename the new file. */
384 if (rename (fname
, archivefname
) != 0)
388 error (EXIT_FAILURE
, errval
, _("cannot rename new archive"));
391 /* Close the old file. */
394 /* Add the information for the new one. */
400 open_archive (struct locarhandle
*ah
, bool readonly
)
405 struct locarhead head
;
407 size_t prefix_len
= output_prefix
? strlen (output_prefix
) : 0;
408 char archivefname
[prefix_len
+ sizeof (ARCHIVE_NAME
)];
411 memcpy (archivefname
, output_prefix
, prefix_len
);
412 strcpy (archivefname
+ prefix_len
, ARCHIVE_NAME
);
415 /* Open the archive. We must have exclusive write access. */
416 fd
= open64 (archivefname
, readonly
? O_RDONLY
: O_RDWR
);
419 /* Maybe the file does not yet exist. */
424 static const struct locarhead nullhead
=
427 .namehash_offset
= 0,
431 ah
->addr
= (void *) &nullhead
;
435 create_archive (archivefname
, ah
);
440 error (EXIT_FAILURE
, errno
, _("cannot open locale archive \"%s\""),
444 if (fstat64 (fd
, &st
) < 0)
445 error (EXIT_FAILURE
, errno
, _("cannot stat locale archive \"%s\""),
448 if (!readonly
&& lockf64 (fd
, F_LOCK
, sizeof (struct locarhead
)) == -1)
452 if (retry
++ < max_locarchive_open_retry
)
456 /* Wait for a bit. */
458 req
.tv_nsec
= 1000000 * (random () % 500 + 1);
459 (void) nanosleep (&req
, NULL
);
464 error (EXIT_FAILURE
, errno
, _("cannot lock locale archive \"%s\""),
468 /* One more check. Maybe another process replaced the archive file
469 with a new, larger one since we opened the file. */
470 if (stat64 (archivefname
, &st2
) == -1
471 || st
.st_dev
!= st2
.st_dev
472 || st
.st_ino
!= st2
.st_ino
)
474 (void) lockf64 (fd
, F_ULOCK
, sizeof (struct locarhead
));
479 /* Read the header. */
480 if (TEMP_FAILURE_RETRY (read (fd
, &head
, sizeof (head
))) != sizeof (head
))
482 (void) lockf64 (fd
, F_ULOCK
, sizeof (struct locarhead
));
483 error (EXIT_FAILURE
, errno
, _("cannot read archive header"));
487 ah
->len
= (head
.sumhash_offset
488 + head
.sumhash_size
* sizeof (struct sumhashent
));
490 /* Now we know how large the administrative information part is.
492 ah
->addr
= mmap64 (NULL
, ah
->len
, PROT_READ
| (readonly
? 0 : PROT_WRITE
),
494 if (ah
->addr
== MAP_FAILED
)
496 (void) lockf64 (fd
, F_ULOCK
, sizeof (struct locarhead
));
497 error (EXIT_FAILURE
, errno
, _("cannot map archive header"));
503 close_archive (struct locarhandle
*ah
)
507 munmap (ah
->addr
, ah
->len
);
512 #include "../../intl/explodename.c"
513 #include "../../intl/l10nflist.c"
515 static struct namehashent
*
516 insert_name (struct locarhandle
*ah
,
517 const char *name
, size_t name_len
, bool replace
)
519 const struct locarhead
*const head
= ah
->addr
;
520 struct namehashent
*namehashtab
521 = (struct namehashent
*) ((char *) ah
->addr
+ head
->namehash_offset
);
522 unsigned int insert_idx
, idx
, incr
;
524 /* Hash value of the locale name. */
525 uint32_t hval
= compute_hashval (name
, name_len
);
528 idx
= hval
% head
->namehash_size
;
529 incr
= 1 + hval
% (head
->namehash_size
- 2);
531 /* If the name_offset field is zero this means this is a
532 deleted entry and therefore no entry can be found. */
533 while (namehashtab
[idx
].name_offset
!= 0)
535 if (namehashtab
[idx
].hashval
== hval
537 (char *) ah
->addr
+ namehashtab
[idx
].name_offset
) == 0)
539 /* Found the entry. */
540 if (namehashtab
[idx
].locrec_offset
!= 0 && ! replace
)
543 error (0, 0, _("locale '%s' already exists"), name
);
550 if (namehashtab
[idx
].hashval
== hval
&& ! be_quiet
)
552 error (0, 0, "hash collision (%u) %s, %s",
553 hval
, name
, (char *) ah
->addr
+ namehashtab
[idx
].name_offset
);
556 /* Remember the first place we can insert the new entry. */
557 if (namehashtab
[idx
].locrec_offset
== 0 && insert_idx
== -1)
561 if (idx
>= head
->namehash_size
)
562 idx
-= head
->namehash_size
;
565 /* Add as early as possible. */
566 if (insert_idx
!= -1)
569 namehashtab
[idx
].hashval
= hval
; /* no-op if replacing an old entry. */
570 return &namehashtab
[idx
];
574 add_alias (struct locarhandle
*ah
, const char *alias
, bool replace
,
575 const char *oldname
, uint32_t locrec_offset
)
577 struct locarhead
*head
= ah
->addr
;
578 const size_t name_len
= strlen (alias
);
579 struct namehashent
*namehashent
= insert_name (ah
, alias
, strlen (alias
),
581 if (namehashent
== NULL
&& ! replace
)
584 if (namehashent
->name_offset
== 0)
586 /* We are adding a new hash entry for this alias.
587 Determine whether we have to resize the file. */
588 if (head
->string_used
+ name_len
+ 1 > head
->string_size
589 || 100 * head
->namehash_used
> 75 * head
->namehash_size
)
591 /* The current archive is not large enough. */
592 enlarge_archive (ah
, head
);
594 /* The locrecent might have moved, so we have to look up
595 the old name afresh. */
596 namehashent
= insert_name (ah
, oldname
, strlen (oldname
), true);
597 assert (namehashent
->name_offset
!= 0);
598 assert (namehashent
->locrec_offset
!= 0);
599 locrec_offset
= namehashent
->locrec_offset
;
601 /* Tail call to try the whole thing again. */
602 add_alias (ah
, alias
, replace
, oldname
, locrec_offset
);
606 /* Add the name string. */
607 memcpy (ah
->addr
+ head
->string_offset
+ head
->string_used
,
608 alias
, name_len
+ 1);
609 namehashent
->name_offset
= head
->string_offset
+ head
->string_used
;
610 head
->string_used
+= name_len
+ 1;
612 ++head
->namehash_used
;
615 if (namehashent
->locrec_offset
!= 0)
617 /* Replacing an existing entry.
618 Mark that we are no longer using the old locrecent. */
619 struct locrecent
*locrecent
620 = (struct locrecent
*) ((char *) ah
->addr
621 + namehashent
->locrec_offset
);
625 /* Point this entry at the locrecent installed for the main name. */
626 namehashent
->locrec_offset
= locrec_offset
;
629 static int /* qsort comparator used below */
630 cmpcategorysize (const void *a
, const void *b
)
632 if (*(const void **) a
== NULL
)
634 if (*(const void **) b
== NULL
)
636 return ((*(const struct locale_category_data
**) a
)->size
637 - (*(const struct locale_category_data
**) b
)->size
);
640 /* Check the content of the archive for duplicates. Add the content
641 of the files if necessary. Returns the locrec_offset. */
643 add_locale (struct locarhandle
*ah
,
644 const char *name
, locale_data_t data
, bool replace
)
646 /* First look for the name. If it already exists and we are not
647 supposed to replace it don't do anything. If it does not exist
648 we have to allocate a new locale record. */
649 size_t name_len
= strlen (name
);
650 uint32_t file_offsets
[__LC_LAST
];
651 unsigned int num_new_offsets
= 0;
652 struct sumhashent
*sumhashtab
;
654 unsigned int cnt
, idx
;
655 struct locarhead
*head
;
656 struct namehashent
*namehashent
;
658 struct locrecent
*locrecent
;
661 struct locale_category_data
*size_order
[__LC_LAST
];
662 const size_t pagesz
= getpagesize ();
666 sumhashtab
= (struct sumhashent
*) ((char *) ah
->addr
667 + head
->sumhash_offset
);
669 memset (file_offsets
, 0, sizeof (file_offsets
));
671 size_order
[LC_ALL
] = NULL
;
672 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
674 size_order
[cnt
] = &data
[cnt
];
676 /* Sort the array in ascending order of data size. */
677 qsort (size_order
, __LC_LAST
, sizeof size_order
[0], cmpcategorysize
);
680 data
[LC_ALL
].size
= 0;
681 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
682 if (size_order
[cnt
] != NULL
)
684 const size_t rounded_size
= (size_order
[cnt
]->size
+ 15) & -16;
685 if (data
[LC_ALL
].size
+ rounded_size
> 2 * pagesz
)
687 /* This category makes the small-categories block
688 stop being small, so this is the end of the road. */
690 size_order
[cnt
++] = NULL
;
691 while (cnt
< __LC_LAST
);
694 data
[LC_ALL
].size
+= rounded_size
;
695 small_mask
|= 1 << (size_order
[cnt
] - data
);
698 /* Copy the data for all the small categories into the LC_ALL
701 data
[LC_ALL
].addr
= alloca (data
[LC_ALL
].size
);
702 memset (data
[LC_ALL
].addr
, 0, data
[LC_ALL
].size
);
704 ptr
= data
[LC_ALL
].addr
;
705 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
706 if (small_mask
& (1 << cnt
))
708 memcpy (ptr
, data
[cnt
].addr
, data
[cnt
].size
);
709 ptr
+= (data
[cnt
].size
+ 15) & -16;
711 __md5_buffer (data
[LC_ALL
].addr
, data
[LC_ALL
].size
, data
[LC_ALL
].sum
);
713 /* For each locale category data set determine whether the same data
714 is already somewhere in the archive. */
715 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
716 if (small_mask
== 0 ? cnt
!= LC_ALL
: !(small_mask
& (1 << cnt
)))
720 /* Compute the hash value of the checksum to determine a
721 starting point for the search in the MD5 hash value
723 hval
= compute_hashval (data
[cnt
].sum
, 16);
725 idx
= hval
% head
->sumhash_size
;
726 incr
= 1 + hval
% (head
->sumhash_size
- 2);
728 while (sumhashtab
[idx
].file_offset
!= 0)
730 if (memcmp (data
[cnt
].sum
, sumhashtab
[idx
].sum
, 16) == 0)
733 file_offsets
[cnt
] = sumhashtab
[idx
].file_offset
;
739 if (idx
>= head
->sumhash_size
)
740 idx
-= head
->sumhash_size
;
744 /* Find a slot for the locale name in the hash table. */
745 namehashent
= insert_name (ah
, name
, name_len
, replace
);
746 if (namehashent
== NULL
) /* Already exists and !REPLACE. */
749 /* Determine whether we have to resize the file. */
750 if (100 * (head
->sumhash_used
+ num_new_offsets
) > 75 * head
->sumhash_size
751 || (namehashent
->locrec_offset
== 0
752 && (head
->locrectab_used
== head
->locrectab_size
753 || head
->string_used
+ name_len
+ 1 > head
->string_size
754 || 100 * head
->namehash_used
> 75 * head
->namehash_size
)))
756 /* The current archive is not large enough. */
757 enlarge_archive (ah
, head
);
758 return add_locale (ah
, name
, data
, replace
);
761 /* Add the locale data which is not yet in the archive. */
762 for (cnt
= 0, lastoffset
= 0; cnt
< __LC_LAST
; ++cnt
)
763 if ((small_mask
== 0 ? cnt
!= LC_ALL
: !(small_mask
& (1 << cnt
)))
764 && file_offsets
[cnt
] == 0)
766 /* The data for this section is not yet available in the
767 archive. Append it. */
771 lastpos
= lseek64 (ah
->fd
, 0, SEEK_END
);
772 if (lastpos
== (off64_t
) -1)
773 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
775 /* If block of small categories would cross page boundary,
776 align it unless it immediately follows a large category. */
777 if (cnt
== LC_ALL
&& lastoffset
!= lastpos
778 && ((((lastpos
& (pagesz
- 1)) + data
[cnt
].size
+ pagesz
- 1)
780 > ((data
[cnt
].size
+ pagesz
- 1) & -pagesz
)))
782 size_t sz
= pagesz
- (lastpos
& (pagesz
- 1));
783 char *zeros
= alloca (sz
);
785 memset (zeros
, 0, sz
);
786 if (TEMP_FAILURE_RETRY (write (ah
->fd
, zeros
, sz
) != sz
))
787 error (EXIT_FAILURE
, errno
,
788 _("cannot add to locale archive"));
793 /* Align all data to a 16 byte boundary. */
794 if ((lastpos
& 15) != 0)
796 static const char zeros
[15] = { 0, };
798 if (TEMP_FAILURE_RETRY (write (ah
->fd
, zeros
, 16 - (lastpos
& 15)))
799 != 16 - (lastpos
& 15))
800 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
802 lastpos
+= 16 - (lastpos
& 15);
805 /* Remember the position. */
806 file_offsets
[cnt
] = lastpos
;
807 lastoffset
= lastpos
+ data
[cnt
].size
;
809 /* Write the data. */
810 if (TEMP_FAILURE_RETRY (write (ah
->fd
, data
[cnt
].addr
, data
[cnt
].size
))
812 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
814 /* Add the hash value to the hash table. */
815 md5hval
= compute_hashval (data
[cnt
].sum
, 16);
817 idx
= md5hval
% head
->sumhash_size
;
818 incr
= 1 + md5hval
% (head
->sumhash_size
- 2);
820 while (sumhashtab
[idx
].file_offset
!= 0)
823 if (idx
>= head
->sumhash_size
)
824 idx
-= head
->sumhash_size
;
827 memcpy (sumhashtab
[idx
].sum
, data
[cnt
].sum
, 16);
828 sumhashtab
[idx
].file_offset
= file_offsets
[cnt
];
830 ++head
->sumhash_used
;
833 lastoffset
= file_offsets
[LC_ALL
];
834 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
835 if (small_mask
& (1 << cnt
))
837 file_offsets
[cnt
] = lastoffset
;
838 lastoffset
+= (data
[cnt
].size
+ 15) & -16;
841 if (namehashent
->name_offset
== 0)
843 /* Add the name string. */
844 memcpy ((char *) ah
->addr
+ head
->string_offset
+ head
->string_used
,
846 namehashent
->name_offset
= head
->string_offset
+ head
->string_used
;
847 head
->string_used
+= name_len
+ 1;
848 ++head
->namehash_used
;
851 if (namehashent
->locrec_offset
== 0)
853 /* Allocate a name location record. */
854 namehashent
->locrec_offset
= (head
->locrectab_offset
855 + (head
->locrectab_used
++
856 * sizeof (struct locrecent
)));
857 locrecent
= (struct locrecent
*) ((char *) ah
->addr
858 + namehashent
->locrec_offset
);
863 /* If there are other aliases pointing to this locrecent,
864 we still need a new one. If not, reuse the old one. */
866 locrecent
= (struct locrecent
*) ((char *) ah
->addr
867 + namehashent
->locrec_offset
);
868 if (locrecent
->refs
> 1)
871 namehashent
->locrec_offset
= (head
->locrectab_offset
872 + (head
->locrectab_used
++
873 * sizeof (struct locrecent
)));
874 locrecent
= (struct locrecent
*) ((char *) ah
->addr
875 + namehashent
->locrec_offset
);
880 /* Fill in the table with the locations of the locale data. */
881 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
883 locrecent
->record
[cnt
].offset
= file_offsets
[cnt
];
884 locrecent
->record
[cnt
].len
= data
[cnt
].size
;
887 return namehashent
->locrec_offset
;
891 /* Check the content of the archive for duplicates. Add the content
892 of the files if necessary. Add all the names, possibly overwriting
895 add_locale_to_archive (ah
, name
, data
, replace
)
896 struct locarhandle
*ah
;
901 char *normalized_name
= NULL
;
902 uint32_t locrec_offset
;
904 /* First analyze the name to decide how to archive it. */
905 const char *language
;
906 const char *modifier
;
907 const char *territory
;
909 const char *normalized_codeset
;
910 int mask
= _nl_explode_name (strdupa (name
),
911 &language
, &modifier
, &territory
,
912 &codeset
, &normalized_codeset
);
914 if (mask
& XPG_NORM_CODESET
)
915 /* This name contains a codeset in unnormalized form.
916 We will store it in the archive with a normalized name. */
917 asprintf (&normalized_name
, "%s%s%s.%s%s%s",
918 language
, territory
== NULL
? "" : "_", territory
?: "",
919 (mask
& XPG_NORM_CODESET
) ? normalized_codeset
: codeset
,
920 modifier
== NULL
? "" : "@", modifier
?: "");
922 /* This call does the main work. */
923 locrec_offset
= add_locale (ah
, normalized_name
?: name
, data
, replace
);
924 free (normalized_name
);
925 if (locrec_offset
== 0)
927 if (mask
& XPG_NORM_CODESET
)
928 free ((char *) normalized_codeset
);
932 if ((mask
& XPG_CODESET
) == 0)
934 /* This name lacks a codeset, so determine the locale's codeset and
935 add an alias for its name with normalized codeset appended. */
940 unsigned int nstrings
;
941 unsigned int strindex
[0];
942 } *filedata
= data
[LC_CTYPE
].addr
;
943 codeset
= (char *) filedata
944 + filedata
->strindex
[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME
)];
946 normalized_codeset
= _nl_normalize_codeset (codeset
, strlen (codeset
));
947 mask
|= XPG_NORM_CODESET
;
949 asprintf (&normalized_name
, "%s%s%s.%s%s%s",
950 language
, territory
== NULL
? "" : "_", territory
?: "",
952 modifier
== NULL
? "" : "@", modifier
?: "");
954 add_alias (ah
, normalized_name
, replace
, name
, locrec_offset
);
955 free (normalized_name
);
958 /* Now read the locale.alias files looking for lines whose
959 right hand side matches our name after normalization. */
960 if (alias_file
!= NULL
)
963 fp
= fopen (alias_file
, "r");
965 error (1, errno
, _("locale alias file `%s' not found"),
968 /* No threads present. */
969 __fsetlocking (fp
, FSETLOCKING_BYCALLER
);
971 while (! feof_unlocked (fp
))
973 /* It is a reasonable approach to use a fix buffer here
975 a) we are only interested in the first two fields
976 b) these fields must be usable as file names and so must
983 if (fgets_unlocked (buf
, BUFSIZ
, fp
) == NULL
)
988 /* Ignore leading white space. */
989 while (isspace (cp
[0]) && cp
[0] != '\n')
992 /* A leading '#' signals a comment line. */
993 if (cp
[0] != '\0' && cp
[0] != '#' && cp
[0] != '\n')
996 while (cp
[0] != '\0' && !isspace (cp
[0]))
998 /* Terminate alias name. */
1002 /* Now look for the beginning of the value. */
1003 while (isspace (cp
[0]))
1009 while (cp
[0] != '\0' && !isspace (cp
[0]))
1011 /* Terminate value. */
1014 /* This has to be done to make the following
1015 test for the end of line possible. We are
1016 looking for the terminating '\n' which do not
1021 else if (cp
[0] != '\0')
1024 /* Does this alias refer to our locale? We will
1025 normalize the right hand side and compare the
1026 elements of the normalized form. */
1028 const char *rhs_language
;
1029 const char *rhs_modifier
;
1030 const char *rhs_territory
;
1031 const char *rhs_codeset
;
1032 const char *rhs_normalized_codeset
;
1033 int rhs_mask
= _nl_explode_name (value
,
1038 &rhs_normalized_codeset
);
1039 if (!strcmp (language
, rhs_language
)
1040 && ((rhs_mask
& XPG_CODESET
)
1041 /* He has a codeset, it must match normalized. */
1042 ? !strcmp ((mask
& XPG_NORM_CODESET
)
1043 ? normalized_codeset
: codeset
,
1044 (rhs_mask
& XPG_NORM_CODESET
)
1045 ? rhs_normalized_codeset
: rhs_codeset
)
1046 /* He has no codeset, we must also have none. */
1047 : (mask
& XPG_CODESET
) == 0)
1048 /* Codeset (or lack thereof) matches. */
1049 && !strcmp (territory
?: "", rhs_territory
?: "")
1050 && !strcmp (modifier
?: "", rhs_modifier
?: ""))
1051 /* We have a winner. */
1052 add_alias (ah
, alias
, replace
,
1053 normalized_name
?: name
, locrec_offset
);
1054 if (rhs_mask
& XPG_NORM_CODESET
)
1055 free ((char *) rhs_normalized_codeset
);
1060 /* Possibly not the whole line fits into the buffer.
1061 Ignore the rest of the line. */
1062 while (strchr (cp
, '\n') == NULL
)
1065 if (fgets_unlocked (buf
, BUFSIZ
, fp
) == NULL
)
1066 /* Make sure the inner loop will be left. The outer
1067 loop will exit at the `feof' test. */
1075 if (mask
& XPG_NORM_CODESET
)
1076 free ((char *) normalized_codeset
);
1083 add_locales_to_archive (nlist
, list
, replace
)
1088 struct locarhandle ah
;
1091 /* Open the archive. This call never returns if we cannot
1092 successfully open the archive. */
1093 open_archive (&ah
, false);
1097 const char *fname
= *list
++;
1098 size_t fnamelen
= strlen (fname
);
1107 printf (_("Adding %s\n"), fname
);
1109 /* First see whether this really is a directory and whether it
1110 contains all the require locale category files. */
1111 if (stat64 (fname
, &st
) < 0)
1113 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname
,
1117 if (!S_ISDIR (st
.st_mode
))
1119 error (0, 0, _("\"%s\" is no directory; ignored"), fname
);
1123 dirp
= opendir (fname
);
1126 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1127 fname
, strerror (errno
));
1132 while ((d
= readdir64 (dirp
)) != NULL
)
1134 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1136 if (strcmp (d
->d_name
, locnames
[cnt
]) == 0)
1138 unsigned char d_type
;
1140 /* We have an object of the required name. If it's
1141 a directory we have to look at a file with the
1142 prefix "SYS_". Otherwise we have found what we
1144 #ifdef _DIRENT_HAVE_D_TYPE
1147 if (d_type
!= DT_REG
)
1150 char fullname
[fnamelen
+ 2 * strlen (d
->d_name
) + 7];
1152 #ifdef _DIRENT_HAVE_D_TYPE
1153 if (d_type
== DT_UNKNOWN
)
1156 strcpy (stpcpy (stpcpy (fullname
, fname
), "/"),
1159 if (stat64 (fullname
, &st
) == -1)
1160 /* We cannot stat the file, ignore it. */
1163 d_type
= IFTODT (st
.st_mode
);
1166 if (d_type
== DT_DIR
)
1168 /* We have to do more tests. The file is a
1169 directory and it therefore must contain a
1170 regular file with the same name except a
1172 char *t
= stpcpy (stpcpy (fullname
, fname
), "/");
1173 strcpy (stpcpy (stpcpy (t
, d
->d_name
), "/SYS_"),
1176 if (stat64 (fullname
, &st
) == -1)
1177 /* There is no SYS_* file or we cannot
1181 d_type
= IFTODT (st
.st_mode
);
1185 /* If we found a regular file (eventually after
1186 following a symlink) we are successful. */
1187 if (d_type
== DT_REG
)
1195 if (seen
!= __LC_LAST
- 1)
1197 /* We don't have all locale category files. Ignore the name. */
1198 error (0, 0, _("incomplete set of locale files in \"%s\""),
1203 /* Add the files to the archive. To do this we first compute
1204 sizes and the MD5 sums of all the files. */
1205 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1208 char fullname
[fnamelen
+ 2 * strlen (locnames
[cnt
]) + 7];
1211 strcpy (stpcpy (stpcpy (fullname
, fname
), "/"), locnames
[cnt
]);
1212 fd
= open64 (fullname
, O_RDONLY
);
1213 if (fd
== -1 || fstat64 (fd
, &st
) == -1)
1215 /* Cannot read the file. */
1221 if (S_ISDIR (st
.st_mode
))
1225 t
= stpcpy (stpcpy (fullname
, fname
), "/");
1226 strcpy (stpcpy (stpcpy (t
, locnames
[cnt
]), "/SYS_"),
1229 fd
= open64 (fullname
, O_RDONLY
);
1230 if (fd
== -1 || fstat64 (fd
, &st
) == -1
1231 || !S_ISREG (st
.st_mode
))
1240 data
[cnt
].addr
= mmap64 (NULL
, st
.st_size
, PROT_READ
, MAP_SHARED
,
1242 if (data
[cnt
].addr
== MAP_FAILED
)
1244 /* Cannot map it. */
1249 data
[cnt
].size
= st
.st_size
;
1250 __md5_buffer (data
[cnt
].addr
, st
.st_size
, data
[cnt
].sum
);
1252 /* We don't need the file descriptor anymore. */
1256 if (cnt
!= __LC_LAST
)
1260 munmap (data
[cnt
].addr
, data
[cnt
].size
);
1262 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname
);
1267 result
|= add_locale_to_archive (&ah
, basename (fname
), data
, replace
);
1269 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1271 munmap (data
[cnt
].addr
, data
[cnt
].size
);
1275 close_archive (&ah
);
1282 delete_locales_from_archive (nlist
, list
)
1286 struct locarhandle ah
;
1287 struct locarhead
*head
;
1288 struct namehashent
*namehashtab
;
1290 /* Open the archive. This call never returns if we cannot
1291 successfully open the archive. */
1292 open_archive (&ah
, false);
1295 namehashtab
= (struct namehashent
*) ((char *) ah
.addr
1296 + head
->namehash_offset
);
1300 const char *locname
= *list
++;
1305 /* Search for this locale in the archive. */
1306 hval
= compute_hashval (locname
, strlen (locname
));
1308 idx
= hval
% head
->namehash_size
;
1309 incr
= 1 + hval
% (head
->namehash_size
- 2);
1311 /* If the name_offset field is zero this means this is no
1312 deleted entry and therefore no entry can be found. */
1313 while (namehashtab
[idx
].name_offset
!= 0)
1315 if (namehashtab
[idx
].hashval
== hval
1316 && (strcmp (locname
,
1317 (char *) ah
.addr
+ namehashtab
[idx
].name_offset
)
1320 /* Found the entry. Now mark it as removed by zero-ing
1321 the reference to the locale record. */
1322 namehashtab
[idx
].locrec_offset
= 0;
1327 if (idx
>= head
->namehash_size
)
1328 idx
-= head
->namehash_size
;
1331 if (namehashtab
[idx
].name_offset
== 0 && ! be_quiet
)
1332 error (0, 0, _("locale \"%s\" not in archive"), locname
);
1335 close_archive (&ah
);
1344 uint32_t locrec_offset
;
1350 const unsigned char *sum
;
1351 uint32_t file_offset
;
1357 nameentcmp (const void *a
, const void *b
)
1359 return strcmp (((const struct nameent
*) a
)->name
,
1360 ((const struct nameent
*) b
)->name
);
1365 dataentcmp (const void *a
, const void *b
)
1367 if (((const struct dataent
*) a
)->file_offset
1368 < ((const struct dataent
*) b
)->file_offset
)
1371 if (((const struct dataent
*) a
)->file_offset
1372 > ((const struct dataent
*) b
)->file_offset
)
1380 show_archive_content (int verbose
)
1382 struct locarhandle ah
;
1383 struct locarhead
*head
;
1384 struct namehashent
*namehashtab
;
1385 struct nameent
*names
;
1389 /* Open the archive. This call never returns if we cannot
1390 successfully open the archive. */
1391 open_archive (&ah
, true);
1395 names
= (struct nameent
*) xmalloc (head
->namehash_used
1396 * sizeof (struct nameent
));
1398 namehashtab
= (struct namehashent
*) ((char *) ah
.addr
1399 + head
->namehash_offset
);
1400 for (cnt
= used
= 0; cnt
< head
->namehash_size
; ++cnt
)
1401 if (namehashtab
[cnt
].locrec_offset
!= 0)
1403 assert (used
< head
->namehash_used
);
1404 names
[used
].name
= ah
.addr
+ namehashtab
[cnt
].name_offset
;
1405 names
[used
++].locrec_offset
= namehashtab
[cnt
].locrec_offset
;
1408 /* Sort the names. */
1409 qsort (names
, used
, sizeof (struct nameent
), nameentcmp
);
1413 struct dataent
*files
;
1414 struct sumhashent
*sumhashtab
;
1417 files
= (struct dataent
*) xmalloc (head
->sumhash_used
1418 * sizeof (struct sumhashent
));
1420 sumhashtab
= (struct sumhashent
*) ((char *) ah
.addr
1421 + head
->sumhash_offset
);
1422 for (cnt
= sumused
= 0; cnt
< head
->sumhash_size
; ++cnt
)
1423 if (sumhashtab
[cnt
].file_offset
!= 0)
1425 assert (sumused
< head
->sumhash_used
);
1426 files
[sumused
].sum
= (const unsigned char *) sumhashtab
[cnt
].sum
;
1427 files
[sumused
].file_offset
= sumhashtab
[cnt
].file_offset
;
1428 files
[sumused
++].nlink
= 0;
1431 /* Sort by file locations. */
1432 qsort (files
, sumused
, sizeof (struct dataent
), dataentcmp
);
1434 /* Compute nlink fields. */
1435 for (cnt
= 0; cnt
< used
; ++cnt
)
1437 struct locrecent
*locrec
;
1440 locrec
= (struct locrecent
*) ((char *) ah
.addr
1441 + names
[cnt
].locrec_offset
);
1442 for (idx
= 0; idx
< __LC_LAST
; ++idx
)
1443 if (locrec
->record
[LC_ALL
].offset
!= 0
1445 || (locrec
->record
[idx
].offset
1446 < locrec
->record
[LC_ALL
].offset
)
1447 || (locrec
->record
[idx
].offset
+ locrec
->record
[idx
].len
1448 > (locrec
->record
[LC_ALL
].offset
1449 + locrec
->record
[LC_ALL
].len
)))
1452 struct dataent
*data
, dataent
;
1454 dataent
.file_offset
= locrec
->record
[idx
].offset
;
1455 data
= (struct dataent
*) bsearch (&dataent
, files
, sumused
,
1456 sizeof (struct dataent
),
1458 assert (data
!= NULL
);
1464 for (cnt
= 0; cnt
< used
; ++cnt
)
1466 struct locrecent
*locrec
;
1469 locrec
= (struct locrecent
*) ((char *) ah
.addr
1470 + names
[cnt
].locrec_offset
);
1471 for (idx
= 0; idx
< __LC_LAST
; ++idx
)
1474 struct dataent
*data
, dataent
;
1476 dataent
.file_offset
= locrec
->record
[idx
].offset
;
1477 if (locrec
->record
[LC_ALL
].offset
!= 0
1478 && dataent
.file_offset
>= locrec
->record
[LC_ALL
].offset
1479 && (dataent
.file_offset
+ locrec
->record
[idx
].len
1480 <= (locrec
->record
[LC_ALL
].offset
1481 + locrec
->record
[LC_ALL
].len
)))
1482 dataent
.file_offset
= locrec
->record
[LC_ALL
].offset
;
1484 data
= (struct dataent
*) bsearch (&dataent
, files
, sumused
,
1485 sizeof (struct dataent
),
1487 printf ("%6d %7x %3d%c ",
1488 locrec
->record
[idx
].len
, locrec
->record
[idx
].offset
,
1490 dataent
.file_offset
== locrec
->record
[LC_ALL
].offset
1492 for (i
= 0; i
< 16; i
+= 4)
1493 printf ("%02x%02x%02x%02x",
1494 data
->sum
[i
], data
->sum
[i
+ 1],
1495 data
->sum
[i
+ 2], data
->sum
[i
+ 3]);
1496 printf (" %s/%s\n", names
[cnt
].name
,
1497 idx
== LC_MESSAGES
? "LC_MESSAGES/SYS_LC_MESSAGES"
1503 for (cnt
= 0; cnt
< used
; ++cnt
)
1504 puts (names
[cnt
].name
);
1506 close_archive (&ah
);
1508 exit (EXIT_SUCCESS
);