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