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
)
552 error (0, 0, "hash collision (%u) %s, %s",
553 hval
, name
, (char *) ah
->addr
+ namehashtab
[idx
].name_offset
);
557 /* Remember the first place we can insert the new entry. */
558 if (namehashtab
[idx
].locrec_offset
== 0 && insert_idx
== -1)
562 if (idx
>= head
->namehash_size
)
563 idx
-= head
->namehash_size
;
566 /* Add as early as possible. */
567 if (insert_idx
!= -1)
570 namehashtab
[idx
].hashval
= hval
; /* no-op if replacing an old entry. */
571 return &namehashtab
[idx
];
575 add_alias (struct locarhandle
*ah
, const char *alias
, bool replace
,
576 const char *oldname
, uint32_t locrec_offset
)
578 struct locarhead
*head
= ah
->addr
;
579 const size_t name_len
= strlen (alias
);
580 struct namehashent
*namehashent
= insert_name (ah
, alias
, strlen (alias
),
582 if (namehashent
== NULL
&& ! replace
)
585 if (namehashent
->name_offset
== 0)
587 /* We are adding a new hash entry for this alias.
588 Determine whether we have to resize the file. */
589 if (head
->string_used
+ name_len
+ 1 > head
->string_size
590 || 100 * head
->namehash_used
> 75 * head
->namehash_size
)
592 /* The current archive is not large enough. */
593 enlarge_archive (ah
, head
);
595 /* The locrecent might have moved, so we have to look up
596 the old name afresh. */
597 namehashent
= insert_name (ah
, oldname
, strlen (oldname
), true);
598 assert (namehashent
->name_offset
!= 0);
599 assert (namehashent
->locrec_offset
!= 0);
600 locrec_offset
= namehashent
->locrec_offset
;
602 /* Tail call to try the whole thing again. */
603 add_alias (ah
, alias
, replace
, oldname
, locrec_offset
);
607 /* Add the name string. */
608 memcpy (ah
->addr
+ head
->string_offset
+ head
->string_used
,
609 alias
, name_len
+ 1);
610 namehashent
->name_offset
= head
->string_offset
+ head
->string_used
;
611 head
->string_used
+= name_len
+ 1;
613 ++head
->namehash_used
;
616 if (namehashent
->locrec_offset
!= 0)
618 /* Replacing an existing entry.
619 Mark that we are no longer using the old locrecent. */
620 struct locrecent
*locrecent
621 = (struct locrecent
*) ((char *) ah
->addr
622 + namehashent
->locrec_offset
);
626 /* Point this entry at the locrecent installed for the main name. */
627 namehashent
->locrec_offset
= locrec_offset
;
630 static int /* qsort comparator used below */
631 cmpcategorysize (const void *a
, const void *b
)
633 if (*(const void **) a
== NULL
)
635 if (*(const void **) b
== NULL
)
637 return ((*(const struct locale_category_data
**) a
)->size
638 - (*(const struct locale_category_data
**) b
)->size
);
641 /* Check the content of the archive for duplicates. Add the content
642 of the files if necessary. Returns the locrec_offset. */
644 add_locale (struct locarhandle
*ah
,
645 const char *name
, locale_data_t data
, bool replace
)
647 /* First look for the name. If it already exists and we are not
648 supposed to replace it don't do anything. If it does not exist
649 we have to allocate a new locale record. */
650 size_t name_len
= strlen (name
);
651 uint32_t file_offsets
[__LC_LAST
];
652 unsigned int num_new_offsets
= 0;
653 struct sumhashent
*sumhashtab
;
655 unsigned int cnt
, idx
;
656 struct locarhead
*head
;
657 struct namehashent
*namehashent
;
659 struct locrecent
*locrecent
;
662 struct locale_category_data
*size_order
[__LC_LAST
];
663 const size_t pagesz
= getpagesize ();
667 sumhashtab
= (struct sumhashent
*) ((char *) ah
->addr
668 + head
->sumhash_offset
);
670 memset (file_offsets
, 0, sizeof (file_offsets
));
672 size_order
[LC_ALL
] = NULL
;
673 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
675 size_order
[cnt
] = &data
[cnt
];
677 /* Sort the array in ascending order of data size. */
678 qsort (size_order
, __LC_LAST
, sizeof size_order
[0], cmpcategorysize
);
681 data
[LC_ALL
].size
= 0;
682 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
683 if (size_order
[cnt
] != NULL
)
685 const size_t rounded_size
= (size_order
[cnt
]->size
+ 15) & -16;
686 if (data
[LC_ALL
].size
+ rounded_size
> 2 * pagesz
)
688 /* This category makes the small-categories block
689 stop being small, so this is the end of the road. */
691 size_order
[cnt
++] = NULL
;
692 while (cnt
< __LC_LAST
);
695 data
[LC_ALL
].size
+= rounded_size
;
696 small_mask
|= 1 << (size_order
[cnt
] - data
);
699 /* Copy the data for all the small categories into the LC_ALL
702 data
[LC_ALL
].addr
= alloca (data
[LC_ALL
].size
);
703 memset (data
[LC_ALL
].addr
, 0, data
[LC_ALL
].size
);
705 ptr
= data
[LC_ALL
].addr
;
706 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
707 if (small_mask
& (1 << cnt
))
709 memcpy (ptr
, data
[cnt
].addr
, data
[cnt
].size
);
710 ptr
+= (data
[cnt
].size
+ 15) & -16;
712 __md5_buffer (data
[LC_ALL
].addr
, data
[LC_ALL
].size
, data
[LC_ALL
].sum
);
714 /* For each locale category data set determine whether the same data
715 is already somewhere in the archive. */
716 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
717 if (small_mask
== 0 ? cnt
!= LC_ALL
: !(small_mask
& (1 << cnt
)))
721 /* Compute the hash value of the checksum to determine a
722 starting point for the search in the MD5 hash value
724 hval
= compute_hashval (data
[cnt
].sum
, 16);
726 idx
= hval
% head
->sumhash_size
;
727 incr
= 1 + hval
% (head
->sumhash_size
- 2);
729 while (sumhashtab
[idx
].file_offset
!= 0)
731 if (memcmp (data
[cnt
].sum
, sumhashtab
[idx
].sum
, 16) == 0)
734 file_offsets
[cnt
] = sumhashtab
[idx
].file_offset
;
740 if (idx
>= head
->sumhash_size
)
741 idx
-= head
->sumhash_size
;
745 /* Find a slot for the locale name in the hash table. */
746 namehashent
= insert_name (ah
, name
, name_len
, replace
);
747 if (namehashent
== NULL
) /* Already exists and !REPLACE. */
750 /* Determine whether we have to resize the file. */
751 if (100 * (head
->sumhash_used
+ num_new_offsets
) > 75 * head
->sumhash_size
752 || (namehashent
->locrec_offset
== 0
753 && (head
->locrectab_used
== head
->locrectab_size
754 || head
->string_used
+ name_len
+ 1 > head
->string_size
755 || 100 * head
->namehash_used
> 75 * head
->namehash_size
)))
757 /* The current archive is not large enough. */
758 enlarge_archive (ah
, head
);
759 return add_locale (ah
, name
, data
, replace
);
762 /* Add the locale data which is not yet in the archive. */
763 for (cnt
= 0, lastoffset
= 0; cnt
< __LC_LAST
; ++cnt
)
764 if ((small_mask
== 0 ? cnt
!= LC_ALL
: !(small_mask
& (1 << cnt
)))
765 && file_offsets
[cnt
] == 0)
767 /* The data for this section is not yet available in the
768 archive. Append it. */
772 lastpos
= lseek64 (ah
->fd
, 0, SEEK_END
);
773 if (lastpos
== (off64_t
) -1)
774 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
776 /* If block of small categories would cross page boundary,
777 align it unless it immediately follows a large category. */
778 if (cnt
== LC_ALL
&& lastoffset
!= lastpos
779 && ((((lastpos
& (pagesz
- 1)) + data
[cnt
].size
+ pagesz
- 1)
781 > ((data
[cnt
].size
+ pagesz
- 1) & -pagesz
)))
783 size_t sz
= pagesz
- (lastpos
& (pagesz
- 1));
784 char *zeros
= alloca (sz
);
786 memset (zeros
, 0, sz
);
787 if (TEMP_FAILURE_RETRY (write (ah
->fd
, zeros
, sz
) != sz
))
788 error (EXIT_FAILURE
, errno
,
789 _("cannot add to locale archive"));
794 /* Align all data to a 16 byte boundary. */
795 if ((lastpos
& 15) != 0)
797 static const char zeros
[15] = { 0, };
799 if (TEMP_FAILURE_RETRY (write (ah
->fd
, zeros
, 16 - (lastpos
& 15)))
800 != 16 - (lastpos
& 15))
801 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
803 lastpos
+= 16 - (lastpos
& 15);
806 /* Remember the position. */
807 file_offsets
[cnt
] = lastpos
;
808 lastoffset
= lastpos
+ data
[cnt
].size
;
810 /* Write the data. */
811 if (TEMP_FAILURE_RETRY (write (ah
->fd
, data
[cnt
].addr
, data
[cnt
].size
))
813 error (EXIT_FAILURE
, errno
, _("cannot add to locale archive"));
815 /* Add the hash value to the hash table. */
816 md5hval
= compute_hashval (data
[cnt
].sum
, 16);
818 idx
= md5hval
% head
->sumhash_size
;
819 incr
= 1 + md5hval
% (head
->sumhash_size
- 2);
821 while (sumhashtab
[idx
].file_offset
!= 0)
824 if (idx
>= head
->sumhash_size
)
825 idx
-= head
->sumhash_size
;
828 memcpy (sumhashtab
[idx
].sum
, data
[cnt
].sum
, 16);
829 sumhashtab
[idx
].file_offset
= file_offsets
[cnt
];
831 ++head
->sumhash_used
;
834 lastoffset
= file_offsets
[LC_ALL
];
835 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
836 if (small_mask
& (1 << cnt
))
838 file_offsets
[cnt
] = lastoffset
;
839 lastoffset
+= (data
[cnt
].size
+ 15) & -16;
842 if (namehashent
->name_offset
== 0)
844 /* Add the name string. */
845 memcpy ((char *) ah
->addr
+ head
->string_offset
+ head
->string_used
,
847 namehashent
->name_offset
= head
->string_offset
+ head
->string_used
;
848 head
->string_used
+= name_len
+ 1;
849 ++head
->namehash_used
;
852 if (namehashent
->locrec_offset
== 0)
854 /* Allocate a name location record. */
855 namehashent
->locrec_offset
= (head
->locrectab_offset
856 + (head
->locrectab_used
++
857 * sizeof (struct locrecent
)));
858 locrecent
= (struct locrecent
*) ((char *) ah
->addr
859 + namehashent
->locrec_offset
);
864 /* If there are other aliases pointing to this locrecent,
865 we still need a new one. If not, reuse the old one. */
867 locrecent
= (struct locrecent
*) ((char *) ah
->addr
868 + namehashent
->locrec_offset
);
869 if (locrecent
->refs
> 1)
872 namehashent
->locrec_offset
= (head
->locrectab_offset
873 + (head
->locrectab_used
++
874 * sizeof (struct locrecent
)));
875 locrecent
= (struct locrecent
*) ((char *) ah
->addr
876 + namehashent
->locrec_offset
);
881 /* Fill in the table with the locations of the locale data. */
882 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
884 locrecent
->record
[cnt
].offset
= file_offsets
[cnt
];
885 locrecent
->record
[cnt
].len
= data
[cnt
].size
;
888 return namehashent
->locrec_offset
;
892 /* Check the content of the archive for duplicates. Add the content
893 of the files if necessary. Add all the names, possibly overwriting
896 add_locale_to_archive (ah
, name
, data
, replace
)
897 struct locarhandle
*ah
;
902 char *normalized_name
= NULL
;
903 uint32_t locrec_offset
;
905 /* First analyze the name to decide how to archive it. */
906 const char *language
;
907 const char *modifier
;
908 const char *territory
;
910 const char *normalized_codeset
;
911 int mask
= _nl_explode_name (strdupa (name
),
912 &language
, &modifier
, &territory
,
913 &codeset
, &normalized_codeset
);
915 if (mask
& XPG_NORM_CODESET
)
916 /* This name contains a codeset in unnormalized form.
917 We will store it in the archive with a normalized name. */
918 asprintf (&normalized_name
, "%s%s%s.%s%s%s",
919 language
, territory
== NULL
? "" : "_", territory
?: "",
920 (mask
& XPG_NORM_CODESET
) ? normalized_codeset
: codeset
,
921 modifier
== NULL
? "" : "@", modifier
?: "");
923 /* This call does the main work. */
924 locrec_offset
= add_locale (ah
, normalized_name
?: name
, data
, replace
);
925 free (normalized_name
);
926 if (locrec_offset
== 0)
928 if (mask
& XPG_NORM_CODESET
)
929 free ((char *) normalized_codeset
);
933 if ((mask
& XPG_CODESET
) == 0)
935 /* This name lacks a codeset, so determine the locale's codeset and
936 add an alias for its name with normalized codeset appended. */
941 unsigned int nstrings
;
942 unsigned int strindex
[0];
943 } *filedata
= data
[LC_CTYPE
].addr
;
944 codeset
= (char *) filedata
945 + filedata
->strindex
[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME
)];
947 normalized_codeset
= _nl_normalize_codeset (codeset
, strlen (codeset
));
948 mask
|= XPG_NORM_CODESET
;
950 asprintf (&normalized_name
, "%s%s%s.%s%s%s",
951 language
, territory
== NULL
? "" : "_", territory
?: "",
953 modifier
== NULL
? "" : "@", modifier
?: "");
955 add_alias (ah
, normalized_name
, replace
, name
, locrec_offset
);
956 free (normalized_name
);
959 /* Now read the locale.alias files looking for lines whose
960 right hand side matches our name after normalization. */
961 if (alias_file
!= NULL
)
964 fp
= fopen (alias_file
, "r");
966 error (1, errno
, _("locale alias file `%s' not found"),
969 /* No threads present. */
970 __fsetlocking (fp
, FSETLOCKING_BYCALLER
);
972 while (! feof_unlocked (fp
))
974 /* It is a reasonable approach to use a fix buffer here
976 a) we are only interested in the first two fields
977 b) these fields must be usable as file names and so must
984 if (fgets_unlocked (buf
, BUFSIZ
, fp
) == NULL
)
989 /* Ignore leading white space. */
990 while (isspace (cp
[0]) && cp
[0] != '\n')
993 /* A leading '#' signals a comment line. */
994 if (cp
[0] != '\0' && cp
[0] != '#' && cp
[0] != '\n')
997 while (cp
[0] != '\0' && !isspace (cp
[0]))
999 /* Terminate alias name. */
1003 /* Now look for the beginning of the value. */
1004 while (isspace (cp
[0]))
1010 while (cp
[0] != '\0' && !isspace (cp
[0]))
1012 /* Terminate value. */
1015 /* This has to be done to make the following
1016 test for the end of line possible. We are
1017 looking for the terminating '\n' which do not
1022 else if (cp
[0] != '\0')
1025 /* Does this alias refer to our locale? We will
1026 normalize the right hand side and compare the
1027 elements of the normalized form. */
1029 const char *rhs_language
;
1030 const char *rhs_modifier
;
1031 const char *rhs_territory
;
1032 const char *rhs_codeset
;
1033 const char *rhs_normalized_codeset
;
1034 int rhs_mask
= _nl_explode_name (value
,
1039 &rhs_normalized_codeset
);
1040 if (!strcmp (language
, rhs_language
)
1041 && ((rhs_mask
& XPG_CODESET
)
1042 /* He has a codeset, it must match normalized. */
1043 ? !strcmp ((mask
& XPG_NORM_CODESET
)
1044 ? normalized_codeset
: codeset
,
1045 (rhs_mask
& XPG_NORM_CODESET
)
1046 ? rhs_normalized_codeset
: rhs_codeset
)
1047 /* He has no codeset, we must also have none. */
1048 : (mask
& XPG_CODESET
) == 0)
1049 /* Codeset (or lack thereof) matches. */
1050 && !strcmp (territory
?: "", rhs_territory
?: "")
1051 && !strcmp (modifier
?: "", rhs_modifier
?: ""))
1052 /* We have a winner. */
1053 add_alias (ah
, alias
, replace
,
1054 normalized_name
?: name
, locrec_offset
);
1055 if (rhs_mask
& XPG_NORM_CODESET
)
1056 free ((char *) rhs_normalized_codeset
);
1061 /* Possibly not the whole line fits into the buffer.
1062 Ignore the rest of the line. */
1063 while (strchr (cp
, '\n') == NULL
)
1066 if (fgets_unlocked (buf
, BUFSIZ
, fp
) == NULL
)
1067 /* Make sure the inner loop will be left. The outer
1068 loop will exit at the `feof' test. */
1076 if (mask
& XPG_NORM_CODESET
)
1077 free ((char *) normalized_codeset
);
1084 add_locales_to_archive (nlist
, list
, replace
)
1089 struct locarhandle ah
;
1092 /* Open the archive. This call never returns if we cannot
1093 successfully open the archive. */
1094 open_archive (&ah
, false);
1098 const char *fname
= *list
++;
1099 size_t fnamelen
= strlen (fname
);
1108 printf (_("Adding %s\n"), fname
);
1110 /* First see whether this really is a directory and whether it
1111 contains all the require locale category files. */
1112 if (stat64 (fname
, &st
) < 0)
1114 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname
,
1118 if (!S_ISDIR (st
.st_mode
))
1120 error (0, 0, _("\"%s\" is no directory; ignored"), fname
);
1124 dirp
= opendir (fname
);
1127 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1128 fname
, strerror (errno
));
1133 while ((d
= readdir64 (dirp
)) != NULL
)
1135 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1137 if (strcmp (d
->d_name
, locnames
[cnt
]) == 0)
1139 unsigned char d_type
;
1141 /* We have an object of the required name. If it's
1142 a directory we have to look at a file with the
1143 prefix "SYS_". Otherwise we have found what we
1145 #ifdef _DIRENT_HAVE_D_TYPE
1148 if (d_type
!= DT_REG
)
1151 char fullname
[fnamelen
+ 2 * strlen (d
->d_name
) + 7];
1153 #ifdef _DIRENT_HAVE_D_TYPE
1154 if (d_type
== DT_UNKNOWN
)
1157 strcpy (stpcpy (stpcpy (fullname
, fname
), "/"),
1160 if (stat64 (fullname
, &st
) == -1)
1161 /* We cannot stat the file, ignore it. */
1164 d_type
= IFTODT (st
.st_mode
);
1167 if (d_type
== DT_DIR
)
1169 /* We have to do more tests. The file is a
1170 directory and it therefore must contain a
1171 regular file with the same name except a
1173 char *t
= stpcpy (stpcpy (fullname
, fname
), "/");
1174 strcpy (stpcpy (stpcpy (t
, d
->d_name
), "/SYS_"),
1177 if (stat64 (fullname
, &st
) == -1)
1178 /* There is no SYS_* file or we cannot
1182 d_type
= IFTODT (st
.st_mode
);
1186 /* If we found a regular file (eventually after
1187 following a symlink) we are successful. */
1188 if (d_type
== DT_REG
)
1196 if (seen
!= __LC_LAST
- 1)
1198 /* We don't have all locale category files. Ignore the name. */
1199 error (0, 0, _("incomplete set of locale files in \"%s\""),
1204 /* Add the files to the archive. To do this we first compute
1205 sizes and the MD5 sums of all the files. */
1206 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1209 char fullname
[fnamelen
+ 2 * strlen (locnames
[cnt
]) + 7];
1212 strcpy (stpcpy (stpcpy (fullname
, fname
), "/"), locnames
[cnt
]);
1213 fd
= open64 (fullname
, O_RDONLY
);
1214 if (fd
== -1 || fstat64 (fd
, &st
) == -1)
1216 /* Cannot read the file. */
1222 if (S_ISDIR (st
.st_mode
))
1226 t
= stpcpy (stpcpy (fullname
, fname
), "/");
1227 strcpy (stpcpy (stpcpy (t
, locnames
[cnt
]), "/SYS_"),
1230 fd
= open64 (fullname
, O_RDONLY
);
1231 if (fd
== -1 || fstat64 (fd
, &st
) == -1
1232 || !S_ISREG (st
.st_mode
))
1241 data
[cnt
].addr
= mmap64 (NULL
, st
.st_size
, PROT_READ
, MAP_SHARED
,
1243 if (data
[cnt
].addr
== MAP_FAILED
)
1245 /* Cannot map it. */
1250 data
[cnt
].size
= st
.st_size
;
1251 __md5_buffer (data
[cnt
].addr
, st
.st_size
, data
[cnt
].sum
);
1253 /* We don't need the file descriptor anymore. */
1257 if (cnt
!= __LC_LAST
)
1261 munmap (data
[cnt
].addr
, data
[cnt
].size
);
1263 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname
);
1268 result
|= add_locale_to_archive (&ah
, basename (fname
), data
, replace
);
1270 for (cnt
= 0; cnt
< __LC_LAST
; ++cnt
)
1272 munmap (data
[cnt
].addr
, data
[cnt
].size
);
1276 close_archive (&ah
);
1283 delete_locales_from_archive (nlist
, list
)
1287 struct locarhandle ah
;
1288 struct locarhead
*head
;
1289 struct namehashent
*namehashtab
;
1291 /* Open the archive. This call never returns if we cannot
1292 successfully open the archive. */
1293 open_archive (&ah
, false);
1296 namehashtab
= (struct namehashent
*) ((char *) ah
.addr
1297 + head
->namehash_offset
);
1301 const char *locname
= *list
++;
1306 /* Search for this locale in the archive. */
1307 hval
= compute_hashval (locname
, strlen (locname
));
1309 idx
= hval
% head
->namehash_size
;
1310 incr
= 1 + hval
% (head
->namehash_size
- 2);
1312 /* If the name_offset field is zero this means this is no
1313 deleted entry and therefore no entry can be found. */
1314 while (namehashtab
[idx
].name_offset
!= 0)
1316 if (namehashtab
[idx
].hashval
== hval
1317 && (strcmp (locname
,
1318 (char *) ah
.addr
+ namehashtab
[idx
].name_offset
)
1321 /* Found the entry. Now mark it as removed by zero-ing
1322 the reference to the locale record. */
1323 namehashtab
[idx
].locrec_offset
= 0;
1328 if (idx
>= head
->namehash_size
)
1329 idx
-= head
->namehash_size
;
1332 if (namehashtab
[idx
].name_offset
== 0 && ! be_quiet
)
1333 error (0, 0, _("locale \"%s\" not in archive"), locname
);
1336 close_archive (&ah
);
1345 uint32_t locrec_offset
;
1351 const unsigned char *sum
;
1352 uint32_t file_offset
;
1358 nameentcmp (const void *a
, const void *b
)
1360 return strcmp (((const struct nameent
*) a
)->name
,
1361 ((const struct nameent
*) b
)->name
);
1366 dataentcmp (const void *a
, const void *b
)
1368 if (((const struct dataent
*) a
)->file_offset
1369 < ((const struct dataent
*) b
)->file_offset
)
1372 if (((const struct dataent
*) a
)->file_offset
1373 > ((const struct dataent
*) b
)->file_offset
)
1381 show_archive_content (int verbose
)
1383 struct locarhandle ah
;
1384 struct locarhead
*head
;
1385 struct namehashent
*namehashtab
;
1386 struct nameent
*names
;
1390 /* Open the archive. This call never returns if we cannot
1391 successfully open the archive. */
1392 open_archive (&ah
, true);
1396 names
= (struct nameent
*) xmalloc (head
->namehash_used
1397 * sizeof (struct nameent
));
1399 namehashtab
= (struct namehashent
*) ((char *) ah
.addr
1400 + head
->namehash_offset
);
1401 for (cnt
= used
= 0; cnt
< head
->namehash_size
; ++cnt
)
1402 if (namehashtab
[cnt
].locrec_offset
!= 0)
1404 assert (used
< head
->namehash_used
);
1405 names
[used
].name
= ah
.addr
+ namehashtab
[cnt
].name_offset
;
1406 names
[used
++].locrec_offset
= namehashtab
[cnt
].locrec_offset
;
1409 /* Sort the names. */
1410 qsort (names
, used
, sizeof (struct nameent
), nameentcmp
);
1414 struct dataent
*files
;
1415 struct sumhashent
*sumhashtab
;
1418 files
= (struct dataent
*) xmalloc (head
->sumhash_used
1419 * sizeof (struct sumhashent
));
1421 sumhashtab
= (struct sumhashent
*) ((char *) ah
.addr
1422 + head
->sumhash_offset
);
1423 for (cnt
= sumused
= 0; cnt
< head
->sumhash_size
; ++cnt
)
1424 if (sumhashtab
[cnt
].file_offset
!= 0)
1426 assert (sumused
< head
->sumhash_used
);
1427 files
[sumused
].sum
= (const unsigned char *) sumhashtab
[cnt
].sum
;
1428 files
[sumused
].file_offset
= sumhashtab
[cnt
].file_offset
;
1429 files
[sumused
++].nlink
= 0;
1432 /* Sort by file locations. */
1433 qsort (files
, sumused
, sizeof (struct dataent
), dataentcmp
);
1435 /* Compute nlink fields. */
1436 for (cnt
= 0; cnt
< used
; ++cnt
)
1438 struct locrecent
*locrec
;
1441 locrec
= (struct locrecent
*) ((char *) ah
.addr
1442 + names
[cnt
].locrec_offset
);
1443 for (idx
= 0; idx
< __LC_LAST
; ++idx
)
1444 if (locrec
->record
[LC_ALL
].offset
!= 0
1446 || (locrec
->record
[idx
].offset
1447 < locrec
->record
[LC_ALL
].offset
)
1448 || (locrec
->record
[idx
].offset
+ locrec
->record
[idx
].len
1449 > (locrec
->record
[LC_ALL
].offset
1450 + locrec
->record
[LC_ALL
].len
)))
1453 struct dataent
*data
, dataent
;
1455 dataent
.file_offset
= locrec
->record
[idx
].offset
;
1456 data
= (struct dataent
*) bsearch (&dataent
, files
, sumused
,
1457 sizeof (struct dataent
),
1459 assert (data
!= NULL
);
1465 for (cnt
= 0; cnt
< used
; ++cnt
)
1467 struct locrecent
*locrec
;
1470 locrec
= (struct locrecent
*) ((char *) ah
.addr
1471 + names
[cnt
].locrec_offset
);
1472 for (idx
= 0; idx
< __LC_LAST
; ++idx
)
1475 struct dataent
*data
, dataent
;
1477 dataent
.file_offset
= locrec
->record
[idx
].offset
;
1478 if (locrec
->record
[LC_ALL
].offset
!= 0
1479 && dataent
.file_offset
>= locrec
->record
[LC_ALL
].offset
1480 && (dataent
.file_offset
+ locrec
->record
[idx
].len
1481 <= (locrec
->record
[LC_ALL
].offset
1482 + locrec
->record
[LC_ALL
].len
)))
1483 dataent
.file_offset
= locrec
->record
[LC_ALL
].offset
;
1485 data
= (struct dataent
*) bsearch (&dataent
, files
, sumused
,
1486 sizeof (struct dataent
),
1488 printf ("%6d %7x %3d%c ",
1489 locrec
->record
[idx
].len
, locrec
->record
[idx
].offset
,
1491 dataent
.file_offset
== locrec
->record
[LC_ALL
].offset
1493 for (i
= 0; i
< 16; i
+= 4)
1494 printf ("%02x%02x%02x%02x",
1495 data
->sum
[i
], data
->sum
[i
+ 1],
1496 data
->sum
[i
+ 2], data
->sum
[i
+ 3]);
1497 printf (" %s/%s\n", names
[cnt
].name
,
1498 idx
== LC_MESSAGES
? "LC_MESSAGES/SYS_LC_MESSAGES"
1504 for (cnt
= 0; cnt
< used
; ++cnt
)
1505 puts (names
[cnt
].name
);
1507 close_archive (&ah
);
1509 exit (EXIT_SUCCESS
);