2.5-18.1
[glibc.git] / locale / programs / locarchive.c
blob5c0d5f18ce94aae764cbb81a704bf792cd9d9603
1 /* Copyright (C) 2002, 2003, 2005 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. */
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
22 #include <assert.h>
23 #include <dirent.h>
24 #include <errno.h>
25 #include <error.h>
26 #include <fcntl.h>
27 #include <inttypes.h>
28 #include <libintl.h>
29 #include <locale.h>
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <stdio_ext.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
37 #include <sys/mman.h>
38 #include <sys/param.h>
39 #include <sys/stat.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
50 #include "hashval.h"
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 450
68 #define INITIAL_SIZE_STRINGS 3500
69 #define INITIAL_NUM_LOCREC 350
70 #define INITIAL_NUM_SUMS 2000
73 static void
74 create_archive (const char *archivefname, struct locarhandle *ah)
76 int fd;
77 char fname[strlen (archivefname) + sizeof (".XXXXXX")];
78 struct locarhead head;
79 void *p;
80 size_t total;
82 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
84 /* Create a temporary file in the correct directory. */
85 fd = mkstemp (fname);
86 if (fd == -1)
87 error (EXIT_FAILURE, errno, _("cannot create temporary file"));
89 /* Create the initial content of the archive. */
90 head.magic = AR_MAGIC;
91 head.namehash_offset = sizeof (struct locarhead);
92 head.namehash_used = 0;
93 head.namehash_size = next_prime (INITIAL_NUM_NAMES);
95 head.string_offset = (head.namehash_offset
96 + head.namehash_size * sizeof (struct namehashent));
97 head.string_used = 0;
98 head.string_size = INITIAL_SIZE_STRINGS;
100 head.locrectab_offset = head.string_offset + head.string_size;
101 head.locrectab_used = 0;
102 head.locrectab_size = INITIAL_NUM_LOCREC;
104 head.sumhash_offset = (head.locrectab_offset
105 + head.locrectab_size * sizeof (struct locrecent));
106 head.sumhash_used = 0;
107 head.sumhash_size = next_prime (INITIAL_NUM_SUMS);
109 total = head.sumhash_offset + head.sumhash_size * sizeof (struct sumhashent);
111 /* Write out the header and create room for the other data structures. */
112 if (TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head))) != sizeof (head))
114 int errval = errno;
115 unlink (fname);
116 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
119 if (ftruncate64 (fd, total) != 0)
121 int errval = errno;
122 unlink (fname);
123 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
126 /* Map the header and all the administration data structures. */
127 p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
128 if (p == MAP_FAILED)
130 int errval = errno;
131 unlink (fname);
132 error (EXIT_FAILURE, errval, _("cannot map archive header"));
135 /* Now try to rename it. We don't use the rename function since
136 this would overwrite a file which has been created in
137 parallel. */
138 if (link (fname, archivefname) == -1)
140 int errval = errno;
142 /* We cannot use the just created file. */
143 close (fd);
144 unlink (fname);
146 if (errval == EEXIST)
148 /* There is already an archive. Must have been a localedef run
149 which happened in parallel. Simply open this file then. */
150 open_archive (ah, false);
151 return;
154 error (EXIT_FAILURE, errval, _("failed to create new locale archive"));
157 /* Remove the temporary name. */
158 unlink (fname);
160 /* Make the file globally readable. */
161 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
163 int errval = errno;
164 unlink (archivefname);
165 error (EXIT_FAILURE, errval,
166 _("cannot change mode of new locale archive"));
169 ah->fd = fd;
170 ah->addr = p;
171 ah->len = total;
175 /* This structure and qsort comparator function are used below to sort an
176 old archive's locrec table in order of data position in the file. */
177 struct oldlocrecent
179 unsigned int cnt;
180 struct locrecent *locrec;
183 static int
184 oldlocrecentcmp (const void *a, const void *b)
186 struct locrecent *la = ((const struct oldlocrecent *) a)->locrec;
187 struct locrecent *lb = ((const struct oldlocrecent *) b)->locrec;
188 uint32_t start_a = -1, end_a = 0;
189 uint32_t start_b = -1, end_b = 0;
190 int cnt;
192 for (cnt = 0; cnt < __LC_LAST; ++cnt)
193 if (cnt != LC_ALL)
195 if (la->record[cnt].offset < start_a)
196 start_a = la->record[cnt].offset;
197 if (la->record[cnt].offset + la->record[cnt].len > end_a)
198 end_a = la->record[cnt].offset + la->record[cnt].len;
200 assert (start_a != (uint32_t)-1);
201 assert (end_a != 0);
203 for (cnt = 0; cnt < __LC_LAST; ++cnt)
204 if (cnt != LC_ALL)
206 if (lb->record[cnt].offset < start_b)
207 start_b = lb->record[cnt].offset;
208 if (lb->record[cnt].offset + lb->record[cnt].len > end_b)
209 end_b = lb->record[cnt].offset + lb->record[cnt].len;
211 assert (start_b != (uint32_t)-1);
212 assert (end_b != 0);
214 if (start_a != start_b)
215 return (int)start_a - (int)start_b;
216 return (int)end_a - (int)end_b;
220 /* forward decl for below */
221 static uint32_t add_locale (struct locarhandle *ah, const char *name,
222 locale_data_t data, bool replace);
224 static void
225 enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
227 struct stat64 st;
228 int fd;
229 struct locarhead newhead;
230 size_t total;
231 void *p;
232 unsigned int cnt, loccnt;
233 struct namehashent *oldnamehashtab;
234 struct locrecent *oldlocrectab;
235 struct locarhandle new_ah;
236 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
237 char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
238 char fname[prefix_len + sizeof (ARCHIVE_NAME) + sizeof (".XXXXXX") - 1];
240 if (output_prefix)
241 memcpy (archivefname, output_prefix, prefix_len);
242 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
243 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
245 /* Not all of the old file has to be mapped. Change this now this
246 we will have to access the whole content. */
247 if (fstat64 (ah->fd, &st) != 0
248 || (ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE,
249 MAP_SHARED, ah->fd, 0)) == MAP_FAILED)
250 error (EXIT_FAILURE, errno, _("cannot map locale archive file"));
251 ah->len = st.st_size;
253 /* Create a temporary file in the correct directory. */
254 fd = mkstemp (fname);
255 if (fd == -1)
256 error (EXIT_FAILURE, errno, _("cannot create temporary file"));
258 /* Copy the existing head information. */
259 newhead = *head;
261 /* Create the new archive header. The sizes of the various tables
262 should be double from what is currently used. */
263 newhead.namehash_size = MAX (next_prime (2 * newhead.namehash_used),
264 newhead.namehash_size);
265 if (verbose)
266 printf ("name: size: %u, used: %d, new: size: %u\n",
267 head->namehash_size, head->namehash_used, newhead.namehash_size);
269 newhead.string_offset = (newhead.namehash_offset
270 + (newhead.namehash_size
271 * sizeof (struct namehashent)));
272 /* Keep the string table size aligned to 4 bytes, so that
273 all the struct { uint32_t } types following are happy. */
274 newhead.string_size = MAX ((2 * newhead.string_used + 3) & -4,
275 newhead.string_size);
277 newhead.locrectab_offset = newhead.string_offset + newhead.string_size;
278 newhead.locrectab_size = MAX (2 * newhead.locrectab_used,
279 newhead.locrectab_size);
281 newhead.sumhash_offset = (newhead.locrectab_offset
282 + (newhead.locrectab_size
283 * sizeof (struct locrecent)));
284 newhead.sumhash_size = MAX (next_prime (2 * newhead.sumhash_used),
285 newhead.sumhash_size);
287 total = (newhead.sumhash_offset
288 + newhead.sumhash_size * sizeof (struct sumhashent));
290 /* The new file is empty now. */
291 newhead.namehash_used = 0;
292 newhead.string_used = 0;
293 newhead.locrectab_used = 0;
294 newhead.sumhash_used = 0;
296 /* Write out the header and create room for the other data structures. */
297 if (TEMP_FAILURE_RETRY (write (fd, &newhead, sizeof (newhead)))
298 != sizeof (newhead))
300 int errval = errno;
301 unlink (fname);
302 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
305 if (ftruncate64 (fd, total) != 0)
307 int errval = errno;
308 unlink (fname);
309 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
312 /* Map the header and all the administration data structures. */
313 p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
314 if (p == MAP_FAILED)
316 int errval = errno;
317 unlink (fname);
318 error (EXIT_FAILURE, errval, _("cannot map archive header"));
321 /* Lock the new file. */
322 if (lockf64 (fd, F_LOCK, total) != 0)
324 int errval = errno;
325 unlink (fname);
326 error (EXIT_FAILURE, errval, _("cannot lock new archive"));
329 new_ah.len = total;
330 new_ah.addr = p;
331 new_ah.fd = fd;
333 /* Walk through the hash name hash table to find out what data is
334 still referenced and transfer it into the new file. */
335 oldnamehashtab = (struct namehashent *) ((char *) ah->addr
336 + head->namehash_offset);
337 oldlocrectab = (struct locrecent *) ((char *) ah->addr
338 + head->locrectab_offset);
340 /* Sort the old locrec table in order of data position. */
341 struct oldlocrecent oldlocrecarray[head->namehash_size];
342 for (cnt = 0, loccnt = 0; cnt < head->namehash_size; ++cnt)
343 if (oldnamehashtab[cnt].locrec_offset != 0)
345 oldlocrecarray[loccnt].cnt = cnt;
346 oldlocrecarray[loccnt++].locrec
347 = (struct locrecent *) ((char *) ah->addr
348 + oldnamehashtab[cnt].locrec_offset);
350 qsort (oldlocrecarray, loccnt, sizeof (struct oldlocrecent),
351 oldlocrecentcmp);
353 for (cnt = 0; cnt < loccnt; ++cnt)
355 /* Insert this entry in the new hash table. */
356 locale_data_t old_data;
357 unsigned int idx;
358 struct locrecent *oldlocrec = oldlocrecarray[cnt].locrec;
360 for (idx = 0; idx < __LC_LAST; ++idx)
361 if (idx != LC_ALL)
363 old_data[idx].size = oldlocrec->record[idx].len;
364 old_data[idx].addr
365 = ((char *) ah->addr + oldlocrec->record[idx].offset);
367 __md5_buffer (old_data[idx].addr, old_data[idx].size,
368 old_data[idx].sum);
371 if (add_locale (&new_ah,
372 ((char *) ah->addr
373 + oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset),
374 old_data, 0) == 0)
375 error (EXIT_FAILURE, 0, _("cannot extend locale archive file"));
378 /* Make the file globally readable. */
379 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
381 int errval = errno;
382 unlink (fname);
383 error (EXIT_FAILURE, errval,
384 _("cannot change mode of resized locale archive"));
387 /* Rename the new file. */
388 if (rename (fname, archivefname) != 0)
390 int errval = errno;
391 unlink (fname);
392 error (EXIT_FAILURE, errval, _("cannot rename new archive"));
395 /* Close the old file. */
396 close_archive (ah);
398 /* Add the information for the new one. */
399 *ah = new_ah;
403 void
404 open_archive (struct locarhandle *ah, bool readonly)
406 struct stat64 st;
407 struct stat64 st2;
408 int fd;
409 struct locarhead head;
410 int retry = 0;
411 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
412 char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
414 if (output_prefix)
415 memcpy (archivefname, output_prefix, prefix_len);
416 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
418 while (1)
420 /* Open the archive. We must have exclusive write access. */
421 fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
422 if (fd == -1)
424 /* Maybe the file does not yet exist. */
425 if (errno == ENOENT)
427 if (readonly)
429 static const struct locarhead nullhead =
431 .namehash_used = 0,
432 .namehash_offset = 0,
433 .namehash_size = 0
436 ah->addr = (void *) &nullhead;
437 ah->fd = -1;
439 else
440 create_archive (archivefname, ah);
442 return;
444 else
445 error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
446 archivefname);
449 if (fstat64 (fd, &st) < 0)
450 error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""),
451 archivefname);
453 if (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1)
455 close (fd);
457 if (retry++ < max_locarchive_open_retry)
459 struct timespec req;
461 /* Wait for a bit. */
462 req.tv_sec = 0;
463 req.tv_nsec = 1000000 * (random () % 500 + 1);
464 (void) nanosleep (&req, NULL);
466 continue;
469 error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
470 archivefname);
473 /* One more check. Maybe another process replaced the archive file
474 with a new, larger one since we opened the file. */
475 if (stat64 (archivefname, &st2) == -1
476 || st.st_dev != st2.st_dev
477 || st.st_ino != st2.st_ino)
479 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
480 close (fd);
481 continue;
484 /* Leave the loop. */
485 break;
488 /* Read the header. */
489 if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
491 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
492 error (EXIT_FAILURE, errno, _("cannot read archive header"));
495 ah->fd = fd;
496 ah->len = (head.sumhash_offset
497 + head.sumhash_size * sizeof (struct sumhashent));
499 /* Now we know how large the administrative information part is.
500 Map all of it. */
501 ah->addr = mmap64 (NULL, ah->len, PROT_READ | (readonly ? 0 : PROT_WRITE),
502 MAP_SHARED, fd, 0);
503 if (ah->addr == MAP_FAILED)
505 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
506 error (EXIT_FAILURE, errno, _("cannot map archive header"));
511 void
512 close_archive (struct locarhandle *ah)
514 if (ah->fd != -1)
516 munmap (ah->addr, ah->len);
517 close (ah->fd);
521 #include "../../intl/explodename.c"
522 #include "../../intl/l10nflist.c"
524 static struct namehashent *
525 insert_name (struct locarhandle *ah,
526 const char *name, size_t name_len, bool replace)
528 const struct locarhead *const head = ah->addr;
529 struct namehashent *namehashtab
530 = (struct namehashent *) ((char *) ah->addr + head->namehash_offset);
531 unsigned int insert_idx, idx, incr;
533 /* Hash value of the locale name. */
534 uint32_t hval = archive_hashval (name, name_len);
536 insert_idx = -1;
537 idx = hval % head->namehash_size;
538 incr = 1 + hval % (head->namehash_size - 2);
540 /* If the name_offset field is zero this means this is a
541 deleted entry and therefore no entry can be found. */
542 while (namehashtab[idx].name_offset != 0)
544 if (namehashtab[idx].hashval == hval
545 && strcmp (name,
546 (char *) ah->addr + namehashtab[idx].name_offset) == 0)
548 /* Found the entry. */
549 if (namehashtab[idx].locrec_offset != 0 && ! replace)
551 if (! be_quiet)
552 error (0, 0, _("locale '%s' already exists"), name);
553 return NULL;
556 break;
559 if (namehashtab[idx].hashval == hval && ! be_quiet)
561 error (0, 0, "hash collision (%u) %s, %s",
562 hval, name, (char *) ah->addr + namehashtab[idx].name_offset);
565 /* Remember the first place we can insert the new entry. */
566 if (namehashtab[idx].locrec_offset == 0 && insert_idx == -1)
567 insert_idx = idx;
569 idx += incr;
570 if (idx >= head->namehash_size)
571 idx -= head->namehash_size;
574 /* Add as early as possible. */
575 if (insert_idx != -1)
576 idx = insert_idx;
578 namehashtab[idx].hashval = hval; /* no-op if replacing an old entry. */
579 return &namehashtab[idx];
582 static void
583 add_alias (struct locarhandle *ah, const char *alias, bool replace,
584 const char *oldname, uint32_t *locrec_offset_p)
586 uint32_t locrec_offset = *locrec_offset_p;
587 struct locarhead *head = ah->addr;
588 const size_t name_len = strlen (alias);
589 struct namehashent *namehashent = insert_name (ah, alias, strlen (alias),
590 replace);
591 if (namehashent == NULL && ! replace)
592 return;
594 if (namehashent->name_offset == 0)
596 /* We are adding a new hash entry for this alias.
597 Determine whether we have to resize the file. */
598 if (head->string_used + name_len + 1 > head->string_size
599 || 100 * head->namehash_used > 75 * head->namehash_size)
601 /* The current archive is not large enough. */
602 enlarge_archive (ah, head);
604 /* The locrecent might have moved, so we have to look up
605 the old name afresh. */
606 namehashent = insert_name (ah, oldname, strlen (oldname), true);
607 assert (namehashent->name_offset != 0);
608 assert (namehashent->locrec_offset != 0);
609 *locrec_offset_p = namehashent->locrec_offset;
611 /* Tail call to try the whole thing again. */
612 add_alias (ah, alias, replace, oldname, locrec_offset_p);
613 return;
616 /* Add the name string. */
617 memcpy (ah->addr + head->string_offset + head->string_used,
618 alias, name_len + 1);
619 namehashent->name_offset = head->string_offset + head->string_used;
620 head->string_used += name_len + 1;
622 ++head->namehash_used;
625 if (namehashent->locrec_offset != 0)
627 /* Replacing an existing entry.
628 Mark that we are no longer using the old locrecent. */
629 struct locrecent *locrecent
630 = (struct locrecent *) ((char *) ah->addr
631 + namehashent->locrec_offset);
632 --locrecent->refs;
635 /* Point this entry at the locrecent installed for the main name. */
636 namehashent->locrec_offset = locrec_offset;
639 static int /* qsort comparator used below */
640 cmpcategorysize (const void *a, const void *b)
642 if (*(const void **) a == NULL)
643 return 1;
644 if (*(const void **) b == NULL)
645 return -1;
646 return ((*(const struct locale_category_data **) a)->size
647 - (*(const struct locale_category_data **) b)->size);
650 /* Check the content of the archive for duplicates. Add the content
651 of the files if necessary. Returns the locrec_offset. */
652 static uint32_t
653 add_locale (struct locarhandle *ah,
654 const char *name, locale_data_t data, bool replace)
656 /* First look for the name. If it already exists and we are not
657 supposed to replace it don't do anything. If it does not exist
658 we have to allocate a new locale record. */
659 size_t name_len = strlen (name);
660 uint32_t file_offsets[__LC_LAST];
661 unsigned int num_new_offsets = 0;
662 struct sumhashent *sumhashtab;
663 uint32_t hval;
664 unsigned int cnt, idx;
665 struct locarhead *head;
666 struct namehashent *namehashent;
667 unsigned int incr;
668 struct locrecent *locrecent;
669 off64_t lastoffset;
670 char *ptr;
671 struct locale_category_data *size_order[__LC_LAST];
672 const size_t pagesz = getpagesize ();
673 int small_mask;
675 head = ah->addr;
676 sumhashtab = (struct sumhashent *) ((char *) ah->addr
677 + head->sumhash_offset);
679 memset (file_offsets, 0, sizeof (file_offsets));
681 size_order[LC_ALL] = NULL;
682 for (cnt = 0; cnt < __LC_LAST; ++cnt)
683 if (cnt != LC_ALL)
684 size_order[cnt] = &data[cnt];
686 /* Sort the array in ascending order of data size. */
687 qsort (size_order, __LC_LAST, sizeof size_order[0], cmpcategorysize);
689 small_mask = 0;
690 data[LC_ALL].size = 0;
691 for (cnt = 0; cnt < __LC_LAST; ++cnt)
692 if (size_order[cnt] != NULL)
694 const size_t rounded_size = (size_order[cnt]->size + 15) & -16;
695 if (data[LC_ALL].size + rounded_size > 2 * pagesz)
697 /* This category makes the small-categories block
698 stop being small, so this is the end of the road. */
700 size_order[cnt++] = NULL;
701 while (cnt < __LC_LAST);
702 break;
704 data[LC_ALL].size += rounded_size;
705 small_mask |= 1 << (size_order[cnt] - data);
708 /* Copy the data for all the small categories into the LC_ALL
709 pseudo-category. */
711 data[LC_ALL].addr = alloca (data[LC_ALL].size);
712 memset (data[LC_ALL].addr, 0, data[LC_ALL].size);
714 ptr = data[LC_ALL].addr;
715 for (cnt = 0; cnt < __LC_LAST; ++cnt)
716 if (small_mask & (1 << cnt))
718 memcpy (ptr, data[cnt].addr, data[cnt].size);
719 ptr += (data[cnt].size + 15) & -16;
721 __md5_buffer (data[LC_ALL].addr, data[LC_ALL].size, data[LC_ALL].sum);
723 /* For each locale category data set determine whether the same data
724 is already somewhere in the archive. */
725 for (cnt = 0; cnt < __LC_LAST; ++cnt)
726 if (small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
728 ++num_new_offsets;
730 /* Compute the hash value of the checksum to determine a
731 starting point for the search in the MD5 hash value
732 table. */
733 hval = archive_hashval (data[cnt].sum, 16);
735 idx = hval % head->sumhash_size;
736 incr = 1 + hval % (head->sumhash_size - 2);
738 while (sumhashtab[idx].file_offset != 0)
740 if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
742 /* Found it. */
743 file_offsets[cnt] = sumhashtab[idx].file_offset;
744 --num_new_offsets;
745 break;
748 idx += incr;
749 if (idx >= head->sumhash_size)
750 idx -= head->sumhash_size;
754 /* Find a slot for the locale name in the hash table. */
755 namehashent = insert_name (ah, name, name_len, replace);
756 if (namehashent == NULL) /* Already exists and !REPLACE. */
757 return 0;
759 /* Determine whether we have to resize the file. */
760 if (100 * (head->sumhash_used + num_new_offsets) > 75 * head->sumhash_size
761 || (namehashent->locrec_offset == 0
762 && (head->locrectab_used == head->locrectab_size
763 || head->string_used + name_len + 1 > head->string_size
764 || 100 * head->namehash_used > 75 * head->namehash_size)))
766 /* The current archive is not large enough. */
767 enlarge_archive (ah, head);
768 return add_locale (ah, name, data, replace);
771 /* Add the locale data which is not yet in the archive. */
772 for (cnt = 0, lastoffset = 0; cnt < __LC_LAST; ++cnt)
773 if ((small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
774 && file_offsets[cnt] == 0)
776 /* The data for this section is not yet available in the
777 archive. Append it. */
778 off64_t lastpos;
779 uint32_t md5hval;
781 lastpos = lseek64 (ah->fd, 0, SEEK_END);
782 if (lastpos == (off64_t) -1)
783 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
785 /* If block of small categories would cross page boundary,
786 align it unless it immediately follows a large category. */
787 if (cnt == LC_ALL && lastoffset != lastpos
788 && ((((lastpos & (pagesz - 1)) + data[cnt].size + pagesz - 1)
789 & -pagesz)
790 > ((data[cnt].size + pagesz - 1) & -pagesz)))
792 size_t sz = pagesz - (lastpos & (pagesz - 1));
793 char *zeros = alloca (sz);
795 memset (zeros, 0, sz);
796 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, sz) != sz))
797 error (EXIT_FAILURE, errno,
798 _("cannot add to locale archive"));
800 lastpos += sz;
803 /* Align all data to a 16 byte boundary. */
804 if ((lastpos & 15) != 0)
806 static const char zeros[15] = { 0, };
808 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, 16 - (lastpos & 15)))
809 != 16 - (lastpos & 15))
810 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
812 lastpos += 16 - (lastpos & 15);
815 /* Remember the position. */
816 file_offsets[cnt] = lastpos;
817 lastoffset = lastpos + data[cnt].size;
819 /* Write the data. */
820 if (TEMP_FAILURE_RETRY (write (ah->fd, data[cnt].addr, data[cnt].size))
821 != data[cnt].size)
822 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
824 /* Add the hash value to the hash table. */
825 md5hval = archive_hashval (data[cnt].sum, 16);
827 idx = md5hval % head->sumhash_size;
828 incr = 1 + md5hval % (head->sumhash_size - 2);
830 while (sumhashtab[idx].file_offset != 0)
832 idx += incr;
833 if (idx >= head->sumhash_size)
834 idx -= head->sumhash_size;
837 memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
838 sumhashtab[idx].file_offset = file_offsets[cnt];
840 ++head->sumhash_used;
843 lastoffset = file_offsets[LC_ALL];
844 for (cnt = 0; cnt < __LC_LAST; ++cnt)
845 if (small_mask & (1 << cnt))
847 file_offsets[cnt] = lastoffset;
848 lastoffset += (data[cnt].size + 15) & -16;
851 if (namehashent->name_offset == 0)
853 /* Add the name string. */
854 memcpy ((char *) ah->addr + head->string_offset + head->string_used,
855 name, name_len + 1);
856 namehashent->name_offset = head->string_offset + head->string_used;
857 head->string_used += name_len + 1;
858 ++head->namehash_used;
861 if (namehashent->locrec_offset == 0)
863 /* Allocate a name location record. */
864 namehashent->locrec_offset = (head->locrectab_offset
865 + (head->locrectab_used++
866 * sizeof (struct locrecent)));
867 locrecent = (struct locrecent *) ((char *) ah->addr
868 + namehashent->locrec_offset);
869 locrecent->refs = 1;
871 else
873 /* If there are other aliases pointing to this locrecent,
874 we still need a new one. If not, reuse the old one. */
876 locrecent = (struct locrecent *) ((char *) ah->addr
877 + namehashent->locrec_offset);
878 if (locrecent->refs > 1)
880 --locrecent->refs;
881 namehashent->locrec_offset = (head->locrectab_offset
882 + (head->locrectab_used++
883 * sizeof (struct locrecent)));
884 locrecent = (struct locrecent *) ((char *) ah->addr
885 + namehashent->locrec_offset);
886 locrecent->refs = 1;
890 /* Fill in the table with the locations of the locale data. */
891 for (cnt = 0; cnt < __LC_LAST; ++cnt)
893 locrecent->record[cnt].offset = file_offsets[cnt];
894 locrecent->record[cnt].len = data[cnt].size;
897 return namehashent->locrec_offset;
901 /* Check the content of the archive for duplicates. Add the content
902 of the files if necessary. Add all the names, possibly overwriting
903 old files. */
905 add_locale_to_archive (ah, name, data, replace)
906 struct locarhandle *ah;
907 const char *name;
908 locale_data_t data;
909 bool replace;
911 char *normalized_name = NULL;
912 uint32_t locrec_offset;
914 /* First analyze the name to decide how to archive it. */
915 const char *language;
916 const char *modifier;
917 const char *territory;
918 const char *codeset;
919 const char *normalized_codeset;
920 int mask = _nl_explode_name (strdupa (name),
921 &language, &modifier, &territory,
922 &codeset, &normalized_codeset);
924 if (mask & XPG_NORM_CODESET)
925 /* This name contains a codeset in unnormalized form.
926 We will store it in the archive with a normalized name. */
927 asprintf (&normalized_name, "%s%s%s.%s%s%s",
928 language, territory == NULL ? "" : "_", territory ?: "",
929 (mask & XPG_NORM_CODESET) ? normalized_codeset : codeset,
930 modifier == NULL ? "" : "@", modifier ?: "");
932 /* This call does the main work. */
933 locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
934 if (locrec_offset == 0)
936 free (normalized_name);
937 if (mask & XPG_NORM_CODESET)
938 free ((char *) normalized_codeset);
939 return -1;
942 if ((mask & XPG_CODESET) == 0)
944 /* This name lacks a codeset, so determine the locale's codeset and
945 add an alias for its name with normalized codeset appended. */
947 const struct
949 unsigned int magic;
950 unsigned int nstrings;
951 unsigned int strindex[0];
952 } *filedata = data[LC_CTYPE].addr;
953 codeset = (char *) filedata
954 + filedata->strindex[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME)];
955 char *normalized_codeset_name = NULL;
957 normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
958 mask |= XPG_NORM_CODESET;
960 asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
961 language, territory == NULL ? "" : "_", territory ?: "",
962 normalized_codeset,
963 modifier == NULL ? "" : "@", modifier ?: "");
965 add_alias (ah, normalized_codeset_name, replace,
966 normalized_name ?: name, &locrec_offset);
967 free (normalized_codeset_name);
970 /* Now read the locale.alias files looking for lines whose
971 right hand side matches our name after normalization. */
972 if (alias_file != NULL)
974 FILE *fp;
975 fp = fopen (alias_file, "rm");
976 if (fp == NULL)
977 error (1, errno, _("locale alias file `%s' not found"),
978 alias_file);
980 /* No threads present. */
981 __fsetlocking (fp, FSETLOCKING_BYCALLER);
983 while (! feof_unlocked (fp))
985 /* It is a reasonable approach to use a fix buffer here
986 because
987 a) we are only interested in the first two fields
988 b) these fields must be usable as file names and so must
989 not be that long */
990 char buf[BUFSIZ];
991 char *alias;
992 char *value;
993 char *cp;
995 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
996 /* EOF reached. */
997 break;
999 cp = buf;
1000 /* Ignore leading white space. */
1001 while (isspace (cp[0]) && cp[0] != '\n')
1002 ++cp;
1004 /* A leading '#' signals a comment line. */
1005 if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
1007 alias = cp++;
1008 while (cp[0] != '\0' && !isspace (cp[0]))
1009 ++cp;
1010 /* Terminate alias name. */
1011 if (cp[0] != '\0')
1012 *cp++ = '\0';
1014 /* Now look for the beginning of the value. */
1015 while (isspace (cp[0]))
1016 ++cp;
1018 if (cp[0] != '\0')
1020 value = cp++;
1021 while (cp[0] != '\0' && !isspace (cp[0]))
1022 ++cp;
1023 /* Terminate value. */
1024 if (cp[0] == '\n')
1026 /* This has to be done to make the following
1027 test for the end of line possible. We are
1028 looking for the terminating '\n' which do not
1029 overwrite here. */
1030 *cp++ = '\0';
1031 *cp = '\n';
1033 else if (cp[0] != '\0')
1034 *cp++ = '\0';
1036 /* Does this alias refer to our locale? We will
1037 normalize the right hand side and compare the
1038 elements of the normalized form. */
1040 const char *rhs_language;
1041 const char *rhs_modifier;
1042 const char *rhs_territory;
1043 const char *rhs_codeset;
1044 const char *rhs_normalized_codeset;
1045 int rhs_mask = _nl_explode_name (value,
1046 &rhs_language,
1047 &rhs_modifier,
1048 &rhs_territory,
1049 &rhs_codeset,
1050 &rhs_normalized_codeset);
1051 if (!strcmp (language, rhs_language)
1052 && ((rhs_mask & XPG_CODESET)
1053 /* He has a codeset, it must match normalized. */
1054 ? !strcmp ((mask & XPG_NORM_CODESET)
1055 ? normalized_codeset : codeset,
1056 (rhs_mask & XPG_NORM_CODESET)
1057 ? rhs_normalized_codeset : rhs_codeset)
1058 /* He has no codeset, we must also have none. */
1059 : (mask & XPG_CODESET) == 0)
1060 /* Codeset (or lack thereof) matches. */
1061 && !strcmp (territory ?: "", rhs_territory ?: "")
1062 && !strcmp (modifier ?: "", rhs_modifier ?: ""))
1063 /* We have a winner. */
1064 add_alias (ah, alias, replace,
1065 normalized_name ?: name, &locrec_offset);
1066 if (rhs_mask & XPG_NORM_CODESET)
1067 free ((char *) rhs_normalized_codeset);
1072 /* Possibly not the whole line fits into the buffer.
1073 Ignore the rest of the line. */
1074 while (strchr (cp, '\n') == NULL)
1076 cp = buf;
1077 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1078 /* Make sure the inner loop will be left. The outer
1079 loop will exit at the `feof' test. */
1080 *cp = '\n';
1084 fclose (fp);
1087 free (normalized_name);
1089 if (mask & XPG_NORM_CODESET)
1090 free ((char *) normalized_codeset);
1092 return 0;
1097 add_locales_to_archive (nlist, list, replace)
1098 size_t nlist;
1099 char *list[];
1100 bool replace;
1102 struct locarhandle ah;
1103 int result = 0;
1105 /* Open the archive. This call never returns if we cannot
1106 successfully open the archive. */
1107 open_archive (&ah, false);
1109 while (nlist-- > 0)
1111 const char *fname = *list++;
1112 size_t fnamelen = strlen (fname);
1113 struct stat64 st;
1114 DIR *dirp;
1115 struct dirent64 *d;
1116 int seen;
1117 locale_data_t data;
1118 int cnt;
1120 if (! be_quiet)
1121 printf (_("Adding %s\n"), fname);
1123 /* First see whether this really is a directory and whether it
1124 contains all the require locale category files. */
1125 if (stat64 (fname, &st) < 0)
1127 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
1128 strerror (errno));
1129 continue;
1131 if (!S_ISDIR (st.st_mode))
1133 error (0, 0, _("\"%s\" is no directory; ignored"), fname);
1134 continue;
1137 dirp = opendir (fname);
1138 if (dirp == NULL)
1140 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1141 fname, strerror (errno));
1142 continue;
1145 seen = 0;
1146 while ((d = readdir64 (dirp)) != NULL)
1148 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1149 if (cnt != LC_ALL)
1150 if (strcmp (d->d_name, locnames[cnt]) == 0)
1152 unsigned char d_type;
1154 /* We have an object of the required name. If it's
1155 a directory we have to look at a file with the
1156 prefix "SYS_". Otherwise we have found what we
1157 are looking for. */
1158 #ifdef _DIRENT_HAVE_D_TYPE
1159 d_type = d->d_type;
1161 if (d_type != DT_REG)
1162 #endif
1164 char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
1166 #ifdef _DIRENT_HAVE_D_TYPE
1167 if (d_type == DT_UNKNOWN)
1168 #endif
1170 strcpy (stpcpy (stpcpy (fullname, fname), "/"),
1171 d->d_name);
1173 if (stat64 (fullname, &st) == -1)
1174 /* We cannot stat the file, ignore it. */
1175 break;
1177 d_type = IFTODT (st.st_mode);
1180 if (d_type == DT_DIR)
1182 /* We have to do more tests. The file is a
1183 directory and it therefore must contain a
1184 regular file with the same name except a
1185 "SYS_" prefix. */
1186 char *t = stpcpy (stpcpy (fullname, fname), "/");
1187 strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
1188 d->d_name);
1190 if (stat64 (fullname, &st) == -1)
1191 /* There is no SYS_* file or we cannot
1192 access it. */
1193 break;
1195 d_type = IFTODT (st.st_mode);
1199 /* If we found a regular file (eventually after
1200 following a symlink) we are successful. */
1201 if (d_type == DT_REG)
1202 ++seen;
1203 break;
1207 closedir (dirp);
1209 if (seen != __LC_LAST - 1)
1211 /* We don't have all locale category files. Ignore the name. */
1212 error (0, 0, _("incomplete set of locale files in \"%s\""),
1213 fname);
1214 continue;
1217 /* Add the files to the archive. To do this we first compute
1218 sizes and the MD5 sums of all the files. */
1219 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1220 if (cnt != LC_ALL)
1222 char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
1223 int fd;
1225 strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
1226 fd = open64 (fullname, O_RDONLY);
1227 if (fd == -1 || fstat64 (fd, &st) == -1)
1229 /* Cannot read the file. */
1230 if (fd != -1)
1231 close (fd);
1232 break;
1235 if (S_ISDIR (st.st_mode))
1237 char *t;
1238 close (fd);
1239 t = stpcpy (stpcpy (fullname, fname), "/");
1240 strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
1241 locnames[cnt]);
1243 fd = open64 (fullname, O_RDONLY);
1244 if (fd == -1 || fstat64 (fd, &st) == -1
1245 || !S_ISREG (st.st_mode))
1247 if (fd != -1)
1248 close (fd);
1249 break;
1253 /* Map the file. */
1254 data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
1255 fd, 0);
1256 if (data[cnt].addr == MAP_FAILED)
1258 /* Cannot map it. */
1259 close (fd);
1260 break;
1263 data[cnt].size = st.st_size;
1264 __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
1266 /* We don't need the file descriptor anymore. */
1267 close (fd);
1270 if (cnt != __LC_LAST)
1272 while (cnt-- > 0)
1273 if (cnt != LC_ALL)
1274 munmap (data[cnt].addr, data[cnt].size);
1276 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);
1278 continue;
1281 result |= add_locale_to_archive (&ah, basename (fname), data, replace);
1283 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1284 if (cnt != LC_ALL)
1285 munmap (data[cnt].addr, data[cnt].size);
1288 /* We are done. */
1289 close_archive (&ah);
1291 return result;
1296 delete_locales_from_archive (nlist, list)
1297 size_t nlist;
1298 char *list[];
1300 struct locarhandle ah;
1301 struct locarhead *head;
1302 struct namehashent *namehashtab;
1304 /* Open the archive. This call never returns if we cannot
1305 successfully open the archive. */
1306 open_archive (&ah, false);
1308 head = ah.addr;
1309 namehashtab = (struct namehashent *) ((char *) ah.addr
1310 + head->namehash_offset);
1312 while (nlist-- > 0)
1314 const char *locname = *list++;
1315 uint32_t hval;
1316 unsigned int idx;
1317 unsigned int incr;
1319 /* Search for this locale in the archive. */
1320 hval = archive_hashval (locname, strlen (locname));
1322 idx = hval % head->namehash_size;
1323 incr = 1 + hval % (head->namehash_size - 2);
1325 /* If the name_offset field is zero this means this is no
1326 deleted entry and therefore no entry can be found. */
1327 while (namehashtab[idx].name_offset != 0)
1329 if (namehashtab[idx].hashval == hval
1330 && (strcmp (locname,
1331 (char *) ah.addr + namehashtab[idx].name_offset)
1332 == 0))
1334 /* Found the entry. Now mark it as removed by zero-ing
1335 the reference to the locale record. */
1336 namehashtab[idx].locrec_offset = 0;
1337 break;
1340 idx += incr;
1341 if (idx >= head->namehash_size)
1342 idx -= head->namehash_size;
1345 if (namehashtab[idx].name_offset == 0 && ! be_quiet)
1346 error (0, 0, _("locale \"%s\" not in archive"), locname);
1349 close_archive (&ah);
1351 return 0;
1355 struct nameent
1357 char *name;
1358 uint32_t locrec_offset;
1362 struct dataent
1364 const unsigned char *sum;
1365 uint32_t file_offset;
1366 uint32_t nlink;
1370 static int
1371 nameentcmp (const void *a, const void *b)
1373 return strcmp (((const struct nameent *) a)->name,
1374 ((const struct nameent *) b)->name);
1378 static int
1379 dataentcmp (const void *a, const void *b)
1381 if (((const struct dataent *) a)->file_offset
1382 < ((const struct dataent *) b)->file_offset)
1383 return -1;
1385 if (((const struct dataent *) a)->file_offset
1386 > ((const struct dataent *) b)->file_offset)
1387 return 1;
1389 return 0;
1393 void
1394 show_archive_content (int verbose)
1396 struct locarhandle ah;
1397 struct locarhead *head;
1398 struct namehashent *namehashtab;
1399 struct nameent *names;
1400 size_t cnt, used;
1402 /* Open the archive. This call never returns if we cannot
1403 successfully open the archive. */
1404 open_archive (&ah, true);
1406 head = ah.addr;
1408 names = (struct nameent *) xmalloc (head->namehash_used
1409 * sizeof (struct nameent));
1411 namehashtab = (struct namehashent *) ((char *) ah.addr
1412 + head->namehash_offset);
1413 for (cnt = used = 0; cnt < head->namehash_size; ++cnt)
1414 if (namehashtab[cnt].locrec_offset != 0)
1416 assert (used < head->namehash_used);
1417 names[used].name = ah.addr + namehashtab[cnt].name_offset;
1418 names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
1421 /* Sort the names. */
1422 qsort (names, used, sizeof (struct nameent), nameentcmp);
1424 if (verbose)
1426 struct dataent *files;
1427 struct sumhashent *sumhashtab;
1428 int sumused;
1430 files = (struct dataent *) xmalloc (head->sumhash_used
1431 * sizeof (struct sumhashent));
1433 sumhashtab = (struct sumhashent *) ((char *) ah.addr
1434 + head->sumhash_offset);
1435 for (cnt = sumused = 0; cnt < head->sumhash_size; ++cnt)
1436 if (sumhashtab[cnt].file_offset != 0)
1438 assert (sumused < head->sumhash_used);
1439 files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
1440 files[sumused].file_offset = sumhashtab[cnt].file_offset;
1441 files[sumused++].nlink = 0;
1444 /* Sort by file locations. */
1445 qsort (files, sumused, sizeof (struct dataent), dataentcmp);
1447 /* Compute nlink fields. */
1448 for (cnt = 0; cnt < used; ++cnt)
1450 struct locrecent *locrec;
1451 int idx;
1453 locrec = (struct locrecent *) ((char *) ah.addr
1454 + names[cnt].locrec_offset);
1455 for (idx = 0; idx < __LC_LAST; ++idx)
1456 if (locrec->record[LC_ALL].offset != 0
1457 ? (idx == LC_ALL
1458 || (locrec->record[idx].offset
1459 < locrec->record[LC_ALL].offset)
1460 || (locrec->record[idx].offset + locrec->record[idx].len
1461 > (locrec->record[LC_ALL].offset
1462 + locrec->record[LC_ALL].len)))
1463 : idx != LC_ALL)
1465 struct dataent *data, dataent;
1467 dataent.file_offset = locrec->record[idx].offset;
1468 data = (struct dataent *) bsearch (&dataent, files, sumused,
1469 sizeof (struct dataent),
1470 dataentcmp);
1471 assert (data != NULL);
1472 ++data->nlink;
1476 /* Print it. */
1477 for (cnt = 0; cnt < used; ++cnt)
1479 struct locrecent *locrec;
1480 int idx, i;
1482 locrec = (struct locrecent *) ((char *) ah.addr
1483 + names[cnt].locrec_offset);
1484 for (idx = 0; idx < __LC_LAST; ++idx)
1485 if (idx != LC_ALL)
1487 struct dataent *data, dataent;
1489 dataent.file_offset = locrec->record[idx].offset;
1490 if (locrec->record[LC_ALL].offset != 0
1491 && dataent.file_offset >= locrec->record[LC_ALL].offset
1492 && (dataent.file_offset + locrec->record[idx].len
1493 <= (locrec->record[LC_ALL].offset
1494 + locrec->record[LC_ALL].len)))
1495 dataent.file_offset = locrec->record[LC_ALL].offset;
1497 data = (struct dataent *) bsearch (&dataent, files, sumused,
1498 sizeof (struct dataent),
1499 dataentcmp);
1500 printf ("%6d %7x %3d%c ",
1501 locrec->record[idx].len, locrec->record[idx].offset,
1502 data->nlink,
1503 dataent.file_offset == locrec->record[LC_ALL].offset
1504 ? '+' : ' ');
1505 for (i = 0; i < 16; i += 4)
1506 printf ("%02x%02x%02x%02x",
1507 data->sum[i], data->sum[i + 1],
1508 data->sum[i + 2], data->sum[i + 3]);
1509 printf (" %s/%s\n", names[cnt].name,
1510 idx == LC_MESSAGES ? "LC_MESSAGES/SYS_LC_MESSAGES"
1511 : locnames[idx]);
1515 else
1516 for (cnt = 0; cnt < used; ++cnt)
1517 puts (names[cnt].name);
1519 close_archive (&ah);
1521 exit (EXIT_SUCCESS);