* sysdeps/i386/fpu/s_fdim.S (__fdim): Handle +inf/+inf.
[glibc.git] / locale / programs / locarchive.c
blob1a1c7701d1236eb4cba2c0ced178871fdd40ca69
1 /* Copyright (C) 2002, 2003 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, write to the Free
17 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18 02111-1307 USA. */
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
24 #include <assert.h>
25 #include <dirent.h>
26 #include <errno.h>
27 #include <error.h>
28 #include <fcntl.h>
29 #include <inttypes.h>
30 #include <libintl.h>
31 #include <locale.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <stdio_ext.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <time.h>
38 #include <unistd.h>
39 #include <sys/mman.h>
40 #include <sys/param.h>
41 #include <sys/stat.h>
43 #include "../../crypt/md5.h"
44 #include "../localeinfo.h"
45 #include "../locarchive.h"
46 #include "localedef.h"
48 /* Define the hash function. We define the function as static inline.
49 We must change the name so as not to conflict with simple-hash.h. */
50 #define compute_hashval static inline archive_hashval
51 #define hashval_t uint32_t
52 #include "hashval.h"
53 #undef compute_hashval
55 extern const char *output_prefix;
57 #define ARCHIVE_NAME LOCALEDIR "/locale-archive"
59 static const char *locnames[] =
61 #define DEFINE_CATEGORY(category, category_name, items, a) \
62 [category] = category_name,
63 #include "categories.def"
64 #undef DEFINE_CATEGORY
68 /* Size of the initial archive header. */
69 #define INITIAL_NUM_NAMES 450
70 #define INITIAL_SIZE_STRINGS 3500
71 #define INITIAL_NUM_LOCREC 350
72 #define INITIAL_NUM_SUMS 2000
75 static void
76 create_archive (const char *archivefname, struct locarhandle *ah)
78 int fd;
79 char fname[strlen (archivefname) + sizeof (".XXXXXX")];
80 struct locarhead head;
81 void *p;
82 size_t total;
84 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
86 /* Create a temporary file in the correct directory. */
87 fd = mkstemp (fname);
88 if (fd == -1)
89 error (EXIT_FAILURE, errno, _("cannot create temporary file"));
91 /* Create the initial content of the archive. */
92 head.magic = AR_MAGIC;
93 head.namehash_offset = sizeof (struct locarhead);
94 head.namehash_used = 0;
95 head.namehash_size = next_prime (INITIAL_NUM_NAMES);
97 head.string_offset = (head.namehash_offset
98 + head.namehash_size * sizeof (struct namehashent));
99 head.string_used = 0;
100 head.string_size = INITIAL_SIZE_STRINGS;
102 head.locrectab_offset = head.string_offset + head.string_size;
103 head.locrectab_used = 0;
104 head.locrectab_size = INITIAL_NUM_LOCREC;
106 head.sumhash_offset = (head.locrectab_offset
107 + head.locrectab_size * sizeof (struct locrecent));
108 head.sumhash_used = 0;
109 head.sumhash_size = next_prime (INITIAL_NUM_SUMS);
111 total = head.sumhash_offset + head.sumhash_size * sizeof (struct sumhashent);
113 /* Write out the header and create room for the other data structures. */
114 if (TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head))) != sizeof (head))
116 int errval = errno;
117 unlink (fname);
118 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
121 if (ftruncate64 (fd, total) != 0)
123 int errval = errno;
124 unlink (fname);
125 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
128 /* Map the header and all the administration data structures. */
129 p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
130 if (p == MAP_FAILED)
132 int errval = errno;
133 unlink (fname);
134 error (EXIT_FAILURE, errval, _("cannot map archive header"));
137 /* Now try to rename it. We don't use the rename function since
138 this would overwrite a file which has been created in
139 parallel. */
140 if (link (fname, archivefname) == -1)
142 int errval = errno;
144 /* We cannot use the just created file. */
145 close (fd);
146 unlink (fname);
148 if (errval == EEXIST)
150 /* There is already an archive. Must have been a localedef run
151 which happened in parallel. Simply open this file then. */
152 open_archive (ah, false);
153 return;
156 error (EXIT_FAILURE, errval, _("failed to create new locale archive"));
159 /* Remove the temporary name. */
160 unlink (fname);
162 /* Make the file globally readable. */
163 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
165 int errval = errno;
166 unlink (archivefname);
167 error (EXIT_FAILURE, errval,
168 _("cannot change mode of new locale archive"));
171 ah->fd = fd;
172 ah->addr = p;
173 ah->len = total;
177 /* This structure and qsort comparator function are used below to sort an
178 old archive's locrec table in order of data position in the file. */
179 struct oldlocrecent
181 unsigned int cnt;
182 struct locrecent *locrec;
185 static int
186 oldlocrecentcmp (const void *a, const void *b)
188 struct locrecent *la = ((const struct oldlocrecent *) a)->locrec;
189 struct locrecent *lb = ((const struct oldlocrecent *) b)->locrec;
190 uint32_t start_a = -1, end_a = 0;
191 uint32_t start_b = -1, end_b = 0;
192 int cnt;
194 for (cnt = 0; cnt < __LC_LAST; ++cnt)
195 if (cnt != LC_ALL)
197 if (la->record[cnt].offset < start_a)
198 start_a = la->record[cnt].offset;
199 if (la->record[cnt].offset + la->record[cnt].len > end_a)
200 end_a = la->record[cnt].offset + la->record[cnt].len;
202 assert (start_a != (uint32_t)-1);
203 assert (end_a != 0);
205 for (cnt = 0; cnt < __LC_LAST; ++cnt)
206 if (cnt != LC_ALL)
208 if (lb->record[cnt].offset < start_b)
209 start_b = lb->record[cnt].offset;
210 if (lb->record[cnt].offset + lb->record[cnt].len > end_b)
211 end_b = lb->record[cnt].offset + lb->record[cnt].len;
213 assert (start_b != (uint32_t)-1);
214 assert (end_b != 0);
216 if (start_a != start_b)
217 return (int)start_a - (int)start_b;
218 return (int)end_a - (int)end_b;
222 /* forward decl for below */
223 static uint32_t add_locale (struct locarhandle *ah, const char *name,
224 locale_data_t data, bool replace);
226 static void
227 enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
229 struct stat64 st;
230 int fd;
231 struct locarhead newhead;
232 size_t total;
233 void *p;
234 unsigned int cnt, loccnt;
235 struct namehashent *oldnamehashtab;
236 struct locrecent *oldlocrectab;
237 struct locarhandle new_ah;
238 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
239 char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
240 char fname[prefix_len + sizeof (ARCHIVE_NAME) + sizeof (".XXXXXX") - 1];
242 if (output_prefix)
243 memcpy (archivefname, output_prefix, prefix_len);
244 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
245 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
247 /* Not all of the old file has to be mapped. Change this now this
248 we will have to access the whole content. */
249 if (fstat64 (ah->fd, &st) != 0
250 || (ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE,
251 MAP_SHARED, ah->fd, 0)) == MAP_FAILED)
252 error (EXIT_FAILURE, errno, _("cannot map locale archive file"));
253 ah->len = st.st_size;
255 /* Create a temporary file in the correct directory. */
256 fd = mkstemp (fname);
257 if (fd == -1)
258 error (EXIT_FAILURE, errno, _("cannot create temporary file"));
260 /* Copy the existing head information. */
261 newhead = *head;
263 /* Create the new archive header. The sizes of the various tables
264 should be double from what is currently used. */
265 newhead.namehash_size = MAX (next_prime (2 * newhead.namehash_used),
266 newhead.namehash_size);
267 if (verbose)
268 printf ("name: size: %u, used: %d, new: size: %u\n",
269 head->namehash_size, head->namehash_used, newhead.namehash_size);
271 newhead.string_offset = (newhead.namehash_offset
272 + (newhead.namehash_size
273 * sizeof (struct namehashent)));
274 /* Keep the string table size aligned to 4 bytes, so that
275 all the struct { uint32_t } types following are happy. */
276 newhead.string_size = MAX ((2 * newhead.string_used + 3) & -4,
277 newhead.string_size);
279 newhead.locrectab_offset = newhead.string_offset + newhead.string_size;
280 newhead.locrectab_size = MAX (2 * newhead.locrectab_used,
281 newhead.locrectab_size);
283 newhead.sumhash_offset = (newhead.locrectab_offset
284 + (newhead.locrectab_size
285 * sizeof (struct locrecent)));
286 newhead.sumhash_size = MAX (next_prime (2 * newhead.sumhash_used),
287 newhead.sumhash_size);
289 total = (newhead.sumhash_offset
290 + newhead.sumhash_size * sizeof (struct sumhashent));
292 /* The new file is empty now. */
293 newhead.namehash_used = 0;
294 newhead.string_used = 0;
295 newhead.locrectab_used = 0;
296 newhead.sumhash_used = 0;
298 /* Write out the header and create room for the other data structures. */
299 if (TEMP_FAILURE_RETRY (write (fd, &newhead, sizeof (newhead)))
300 != sizeof (newhead))
302 int errval = errno;
303 unlink (fname);
304 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
307 if (ftruncate64 (fd, total) != 0)
309 int errval = errno;
310 unlink (fname);
311 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
314 /* Map the header and all the administration data structures. */
315 p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
316 if (p == MAP_FAILED)
318 int errval = errno;
319 unlink (fname);
320 error (EXIT_FAILURE, errval, _("cannot map archive header"));
323 /* Lock the new file. */
324 if (lockf64 (fd, F_LOCK, total) != 0)
326 int errval = errno;
327 unlink (fname);
328 error (EXIT_FAILURE, errval, _("cannot lock new archive"));
331 new_ah.len = total;
332 new_ah.addr = p;
333 new_ah.fd = fd;
335 /* Walk through the hash name hash table to find out what data is
336 still referenced and transfer it into the new file. */
337 oldnamehashtab = (struct namehashent *) ((char *) ah->addr
338 + head->namehash_offset);
339 oldlocrectab = (struct locrecent *) ((char *) ah->addr
340 + head->locrectab_offset);
342 /* Sort the old locrec table in order of data position. */
343 struct oldlocrecent oldlocrecarray[head->namehash_size];
344 for (cnt = 0, loccnt = 0; cnt < head->namehash_size; ++cnt)
345 if (oldnamehashtab[cnt].locrec_offset != 0)
347 oldlocrecarray[loccnt].cnt = cnt;
348 oldlocrecarray[loccnt++].locrec
349 = (struct locrecent *) ((char *) ah->addr
350 + oldnamehashtab[cnt].locrec_offset);
352 qsort (oldlocrecarray, loccnt, sizeof (struct oldlocrecent),
353 oldlocrecentcmp);
355 for (cnt = 0; cnt < loccnt; ++cnt)
357 /* Insert this entry in the new hash table. */
358 locale_data_t old_data;
359 unsigned int idx;
360 struct locrecent *oldlocrec = oldlocrecarray[cnt].locrec;
362 for (idx = 0; idx < __LC_LAST; ++idx)
363 if (idx != LC_ALL)
365 old_data[idx].size = oldlocrec->record[idx].len;
366 old_data[idx].addr
367 = ((char *) ah->addr + oldlocrec->record[idx].offset);
369 __md5_buffer (old_data[idx].addr, old_data[idx].size,
370 old_data[idx].sum);
373 if (add_locale (&new_ah,
374 ((char *) ah->addr
375 + oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset),
376 old_data, 0) == 0)
377 error (EXIT_FAILURE, 0, _("cannot extend locale archive file"));
380 /* Make the file globally readable. */
381 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
383 int errval = errno;
384 unlink (fname);
385 error (EXIT_FAILURE, errval,
386 _("cannot change mode of resized locale archive"));
389 /* Rename the new file. */
390 if (rename (fname, archivefname) != 0)
392 int errval = errno;
393 unlink (fname);
394 error (EXIT_FAILURE, errval, _("cannot rename new archive"));
397 /* Close the old file. */
398 close_archive (ah);
400 /* Add the information for the new one. */
401 *ah = new_ah;
405 void
406 open_archive (struct locarhandle *ah, bool readonly)
408 struct stat64 st;
409 struct stat64 st2;
410 int fd;
411 struct locarhead head;
412 int retry = 0;
413 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
414 char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
416 if (output_prefix)
417 memcpy (archivefname, output_prefix, prefix_len);
418 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
420 while (1)
422 /* Open the archive. We must have exclusive write access. */
423 fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
424 if (fd == -1)
426 /* Maybe the file does not yet exist. */
427 if (errno == ENOENT)
429 if (readonly)
431 static const struct locarhead nullhead =
433 .namehash_used = 0,
434 .namehash_offset = 0,
435 .namehash_size = 0
438 ah->addr = (void *) &nullhead;
439 ah->fd = -1;
441 else
442 create_archive (archivefname, ah);
444 return;
446 else
447 error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
448 archivefname);
451 if (fstat64 (fd, &st) < 0)
452 error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""),
453 archivefname);
455 if (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1)
457 close (fd);
459 if (retry++ < max_locarchive_open_retry)
461 struct timespec req;
463 /* Wait for a bit. */
464 req.tv_sec = 0;
465 req.tv_nsec = 1000000 * (random () % 500 + 1);
466 (void) nanosleep (&req, NULL);
468 continue;
471 error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
472 archivefname);
475 /* One more check. Maybe another process replaced the archive file
476 with a new, larger one since we opened the file. */
477 if (stat64 (archivefname, &st2) == -1
478 || st.st_dev != st2.st_dev
479 || st.st_ino != st2.st_ino)
481 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
482 close (fd);
483 continue;
486 /* Leave the loop. */
487 break;
490 /* Read the header. */
491 if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
493 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
494 error (EXIT_FAILURE, errno, _("cannot read archive header"));
497 ah->fd = fd;
498 ah->len = (head.sumhash_offset
499 + head.sumhash_size * sizeof (struct sumhashent));
501 /* Now we know how large the administrative information part is.
502 Map all of it. */
503 ah->addr = mmap64 (NULL, ah->len, PROT_READ | (readonly ? 0 : PROT_WRITE),
504 MAP_SHARED, fd, 0);
505 if (ah->addr == MAP_FAILED)
507 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
508 error (EXIT_FAILURE, errno, _("cannot map archive header"));
513 void
514 close_archive (struct locarhandle *ah)
516 if (ah->fd != -1)
518 munmap (ah->addr, ah->len);
519 close (ah->fd);
523 #include "../../intl/explodename.c"
524 #include "../../intl/l10nflist.c"
526 static struct namehashent *
527 insert_name (struct locarhandle *ah,
528 const char *name, size_t name_len, bool replace)
530 const struct locarhead *const head = ah->addr;
531 struct namehashent *namehashtab
532 = (struct namehashent *) ((char *) ah->addr + head->namehash_offset);
533 unsigned int insert_idx, idx, incr;
535 /* Hash value of the locale name. */
536 uint32_t hval = archive_hashval (name, name_len);
538 insert_idx = -1;
539 idx = hval % head->namehash_size;
540 incr = 1 + hval % (head->namehash_size - 2);
542 /* If the name_offset field is zero this means this is a
543 deleted entry and therefore no entry can be found. */
544 while (namehashtab[idx].name_offset != 0)
546 if (namehashtab[idx].hashval == hval
547 && strcmp (name,
548 (char *) ah->addr + namehashtab[idx].name_offset) == 0)
550 /* Found the entry. */
551 if (namehashtab[idx].locrec_offset != 0 && ! replace)
553 if (! be_quiet)
554 error (0, 0, _("locale '%s' already exists"), name);
555 return NULL;
558 break;
561 if (namehashtab[idx].hashval == hval && ! be_quiet)
563 error (0, 0, "hash collision (%u) %s, %s",
564 hval, name, (char *) ah->addr + namehashtab[idx].name_offset);
567 /* Remember the first place we can insert the new entry. */
568 if (namehashtab[idx].locrec_offset == 0 && insert_idx == -1)
569 insert_idx = idx;
571 idx += incr;
572 if (idx >= head->namehash_size)
573 idx -= head->namehash_size;
576 /* Add as early as possible. */
577 if (insert_idx != -1)
578 idx = insert_idx;
580 namehashtab[idx].hashval = hval; /* no-op if replacing an old entry. */
581 return &namehashtab[idx];
584 static void
585 add_alias (struct locarhandle *ah, const char *alias, bool replace,
586 const char *oldname, uint32_t *locrec_offset_p)
588 uint32_t locrec_offset = *locrec_offset_p;
589 struct locarhead *head = ah->addr;
590 const size_t name_len = strlen (alias);
591 struct namehashent *namehashent = insert_name (ah, alias, strlen (alias),
592 replace);
593 if (namehashent == NULL && ! replace)
594 return;
596 if (namehashent->name_offset == 0)
598 /* We are adding a new hash entry for this alias.
599 Determine whether we have to resize the file. */
600 if (head->string_used + name_len + 1 > head->string_size
601 || 100 * head->namehash_used > 75 * head->namehash_size)
603 /* The current archive is not large enough. */
604 enlarge_archive (ah, head);
606 /* The locrecent might have moved, so we have to look up
607 the old name afresh. */
608 namehashent = insert_name (ah, oldname, strlen (oldname), true);
609 assert (namehashent->name_offset != 0);
610 assert (namehashent->locrec_offset != 0);
611 *locrec_offset_p = namehashent->locrec_offset;
613 /* Tail call to try the whole thing again. */
614 add_alias (ah, alias, replace, oldname, locrec_offset_p);
615 return;
618 /* Add the name string. */
619 memcpy (ah->addr + head->string_offset + head->string_used,
620 alias, name_len + 1);
621 namehashent->name_offset = head->string_offset + head->string_used;
622 head->string_used += name_len + 1;
624 ++head->namehash_used;
627 if (namehashent->locrec_offset != 0)
629 /* Replacing an existing entry.
630 Mark that we are no longer using the old locrecent. */
631 struct locrecent *locrecent
632 = (struct locrecent *) ((char *) ah->addr
633 + namehashent->locrec_offset);
634 --locrecent->refs;
637 /* Point this entry at the locrecent installed for the main name. */
638 namehashent->locrec_offset = locrec_offset;
641 static int /* qsort comparator used below */
642 cmpcategorysize (const void *a, const void *b)
644 if (*(const void **) a == NULL)
645 return 1;
646 if (*(const void **) b == NULL)
647 return -1;
648 return ((*(const struct locale_category_data **) a)->size
649 - (*(const struct locale_category_data **) b)->size);
652 /* Check the content of the archive for duplicates. Add the content
653 of the files if necessary. Returns the locrec_offset. */
654 static uint32_t
655 add_locale (struct locarhandle *ah,
656 const char *name, locale_data_t data, bool replace)
658 /* First look for the name. If it already exists and we are not
659 supposed to replace it don't do anything. If it does not exist
660 we have to allocate a new locale record. */
661 size_t name_len = strlen (name);
662 uint32_t file_offsets[__LC_LAST];
663 unsigned int num_new_offsets = 0;
664 struct sumhashent *sumhashtab;
665 uint32_t hval;
666 unsigned int cnt, idx;
667 struct locarhead *head;
668 struct namehashent *namehashent;
669 unsigned int incr;
670 struct locrecent *locrecent;
671 off64_t lastoffset;
672 char *ptr;
673 struct locale_category_data *size_order[__LC_LAST];
674 const size_t pagesz = getpagesize ();
675 int small_mask;
677 head = ah->addr;
678 sumhashtab = (struct sumhashent *) ((char *) ah->addr
679 + head->sumhash_offset);
681 memset (file_offsets, 0, sizeof (file_offsets));
683 size_order[LC_ALL] = NULL;
684 for (cnt = 0; cnt < __LC_LAST; ++cnt)
685 if (cnt != LC_ALL)
686 size_order[cnt] = &data[cnt];
688 /* Sort the array in ascending order of data size. */
689 qsort (size_order, __LC_LAST, sizeof size_order[0], cmpcategorysize);
691 small_mask = 0;
692 data[LC_ALL].size = 0;
693 for (cnt = 0; cnt < __LC_LAST; ++cnt)
694 if (size_order[cnt] != NULL)
696 const size_t rounded_size = (size_order[cnt]->size + 15) & -16;
697 if (data[LC_ALL].size + rounded_size > 2 * pagesz)
699 /* This category makes the small-categories block
700 stop being small, so this is the end of the road. */
702 size_order[cnt++] = NULL;
703 while (cnt < __LC_LAST);
704 break;
706 data[LC_ALL].size += rounded_size;
707 small_mask |= 1 << (size_order[cnt] - data);
710 /* Copy the data for all the small categories into the LC_ALL
711 pseudo-category. */
713 data[LC_ALL].addr = alloca (data[LC_ALL].size);
714 memset (data[LC_ALL].addr, 0, data[LC_ALL].size);
716 ptr = data[LC_ALL].addr;
717 for (cnt = 0; cnt < __LC_LAST; ++cnt)
718 if (small_mask & (1 << cnt))
720 memcpy (ptr, data[cnt].addr, data[cnt].size);
721 ptr += (data[cnt].size + 15) & -16;
723 __md5_buffer (data[LC_ALL].addr, data[LC_ALL].size, data[LC_ALL].sum);
725 /* For each locale category data set determine whether the same data
726 is already somewhere in the archive. */
727 for (cnt = 0; cnt < __LC_LAST; ++cnt)
728 if (small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
730 ++num_new_offsets;
732 /* Compute the hash value of the checksum to determine a
733 starting point for the search in the MD5 hash value
734 table. */
735 hval = archive_hashval (data[cnt].sum, 16);
737 idx = hval % head->sumhash_size;
738 incr = 1 + hval % (head->sumhash_size - 2);
740 while (sumhashtab[idx].file_offset != 0)
742 if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
744 /* Found it. */
745 file_offsets[cnt] = sumhashtab[idx].file_offset;
746 --num_new_offsets;
747 break;
750 idx += incr;
751 if (idx >= head->sumhash_size)
752 idx -= head->sumhash_size;
756 /* Find a slot for the locale name in the hash table. */
757 namehashent = insert_name (ah, name, name_len, replace);
758 if (namehashent == NULL) /* Already exists and !REPLACE. */
759 return 0;
761 /* Determine whether we have to resize the file. */
762 if (100 * (head->sumhash_used + num_new_offsets) > 75 * head->sumhash_size
763 || (namehashent->locrec_offset == 0
764 && (head->locrectab_used == head->locrectab_size
765 || head->string_used + name_len + 1 > head->string_size
766 || 100 * head->namehash_used > 75 * head->namehash_size)))
768 /* The current archive is not large enough. */
769 enlarge_archive (ah, head);
770 return add_locale (ah, name, data, replace);
773 /* Add the locale data which is not yet in the archive. */
774 for (cnt = 0, lastoffset = 0; cnt < __LC_LAST; ++cnt)
775 if ((small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
776 && file_offsets[cnt] == 0)
778 /* The data for this section is not yet available in the
779 archive. Append it. */
780 off64_t lastpos;
781 uint32_t md5hval;
783 lastpos = lseek64 (ah->fd, 0, SEEK_END);
784 if (lastpos == (off64_t) -1)
785 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
787 /* If block of small categories would cross page boundary,
788 align it unless it immediately follows a large category. */
789 if (cnt == LC_ALL && lastoffset != lastpos
790 && ((((lastpos & (pagesz - 1)) + data[cnt].size + pagesz - 1)
791 & -pagesz)
792 > ((data[cnt].size + pagesz - 1) & -pagesz)))
794 size_t sz = pagesz - (lastpos & (pagesz - 1));
795 char *zeros = alloca (sz);
797 memset (zeros, 0, sz);
798 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, sz) != sz))
799 error (EXIT_FAILURE, errno,
800 _("cannot add to locale archive"));
802 lastpos += sz;
805 /* Align all data to a 16 byte boundary. */
806 if ((lastpos & 15) != 0)
808 static const char zeros[15] = { 0, };
810 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, 16 - (lastpos & 15)))
811 != 16 - (lastpos & 15))
812 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
814 lastpos += 16 - (lastpos & 15);
817 /* Remember the position. */
818 file_offsets[cnt] = lastpos;
819 lastoffset = lastpos + data[cnt].size;
821 /* Write the data. */
822 if (TEMP_FAILURE_RETRY (write (ah->fd, data[cnt].addr, data[cnt].size))
823 != data[cnt].size)
824 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
826 /* Add the hash value to the hash table. */
827 md5hval = archive_hashval (data[cnt].sum, 16);
829 idx = md5hval % head->sumhash_size;
830 incr = 1 + md5hval % (head->sumhash_size - 2);
832 while (sumhashtab[idx].file_offset != 0)
834 idx += incr;
835 if (idx >= head->sumhash_size)
836 idx -= head->sumhash_size;
839 memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
840 sumhashtab[idx].file_offset = file_offsets[cnt];
842 ++head->sumhash_used;
845 lastoffset = file_offsets[LC_ALL];
846 for (cnt = 0; cnt < __LC_LAST; ++cnt)
847 if (small_mask & (1 << cnt))
849 file_offsets[cnt] = lastoffset;
850 lastoffset += (data[cnt].size + 15) & -16;
853 if (namehashent->name_offset == 0)
855 /* Add the name string. */
856 memcpy ((char *) ah->addr + head->string_offset + head->string_used,
857 name, name_len + 1);
858 namehashent->name_offset = head->string_offset + head->string_used;
859 head->string_used += name_len + 1;
860 ++head->namehash_used;
863 if (namehashent->locrec_offset == 0)
865 /* Allocate a name location record. */
866 namehashent->locrec_offset = (head->locrectab_offset
867 + (head->locrectab_used++
868 * sizeof (struct locrecent)));
869 locrecent = (struct locrecent *) ((char *) ah->addr
870 + namehashent->locrec_offset);
871 locrecent->refs = 1;
873 else
875 /* If there are other aliases pointing to this locrecent,
876 we still need a new one. If not, reuse the old one. */
878 locrecent = (struct locrecent *) ((char *) ah->addr
879 + namehashent->locrec_offset);
880 if (locrecent->refs > 1)
882 --locrecent->refs;
883 namehashent->locrec_offset = (head->locrectab_offset
884 + (head->locrectab_used++
885 * sizeof (struct locrecent)));
886 locrecent = (struct locrecent *) ((char *) ah->addr
887 + namehashent->locrec_offset);
888 locrecent->refs = 1;
892 /* Fill in the table with the locations of the locale data. */
893 for (cnt = 0; cnt < __LC_LAST; ++cnt)
895 locrecent->record[cnt].offset = file_offsets[cnt];
896 locrecent->record[cnt].len = data[cnt].size;
899 return namehashent->locrec_offset;
903 /* Check the content of the archive for duplicates. Add the content
904 of the files if necessary. Add all the names, possibly overwriting
905 old files. */
907 add_locale_to_archive (ah, name, data, replace)
908 struct locarhandle *ah;
909 const char *name;
910 locale_data_t data;
911 bool replace;
913 char *normalized_name = NULL;
914 uint32_t locrec_offset;
916 /* First analyze the name to decide how to archive it. */
917 const char *language;
918 const char *modifier;
919 const char *territory;
920 const char *codeset;
921 const char *normalized_codeset;
922 int mask = _nl_explode_name (strdupa (name),
923 &language, &modifier, &territory,
924 &codeset, &normalized_codeset);
926 if (mask & XPG_NORM_CODESET)
927 /* This name contains a codeset in unnormalized form.
928 We will store it in the archive with a normalized name. */
929 asprintf (&normalized_name, "%s%s%s.%s%s%s",
930 language, territory == NULL ? "" : "_", territory ?: "",
931 (mask & XPG_NORM_CODESET) ? normalized_codeset : codeset,
932 modifier == NULL ? "" : "@", modifier ?: "");
934 /* This call does the main work. */
935 locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
936 if (locrec_offset == 0)
938 free (normalized_name);
939 if (mask & XPG_NORM_CODESET)
940 free ((char *) normalized_codeset);
941 return -1;
944 if ((mask & XPG_CODESET) == 0)
946 /* This name lacks a codeset, so determine the locale's codeset and
947 add an alias for its name with normalized codeset appended. */
949 const struct
951 unsigned int magic;
952 unsigned int nstrings;
953 unsigned int strindex[0];
954 } *filedata = data[LC_CTYPE].addr;
955 codeset = (char *) filedata
956 + filedata->strindex[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME)];
957 char *normalized_codeset_name = NULL;
959 normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
960 mask |= XPG_NORM_CODESET;
962 asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
963 language, territory == NULL ? "" : "_", territory ?: "",
964 normalized_codeset,
965 modifier == NULL ? "" : "@", modifier ?: "");
967 add_alias (ah, normalized_codeset_name, replace,
968 normalized_name ?: name, &locrec_offset);
969 free (normalized_codeset_name);
972 /* Now read the locale.alias files looking for lines whose
973 right hand side matches our name after normalization. */
974 if (alias_file != NULL)
976 FILE *fp;
977 fp = fopen (alias_file, "rm");
978 if (fp == NULL)
979 error (1, errno, _("locale alias file `%s' not found"),
980 alias_file);
982 /* No threads present. */
983 __fsetlocking (fp, FSETLOCKING_BYCALLER);
985 while (! feof_unlocked (fp))
987 /* It is a reasonable approach to use a fix buffer here
988 because
989 a) we are only interested in the first two fields
990 b) these fields must be usable as file names and so must
991 not be that long */
992 char buf[BUFSIZ];
993 char *alias;
994 char *value;
995 char *cp;
997 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
998 /* EOF reached. */
999 break;
1001 cp = buf;
1002 /* Ignore leading white space. */
1003 while (isspace (cp[0]) && cp[0] != '\n')
1004 ++cp;
1006 /* A leading '#' signals a comment line. */
1007 if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
1009 alias = cp++;
1010 while (cp[0] != '\0' && !isspace (cp[0]))
1011 ++cp;
1012 /* Terminate alias name. */
1013 if (cp[0] != '\0')
1014 *cp++ = '\0';
1016 /* Now look for the beginning of the value. */
1017 while (isspace (cp[0]))
1018 ++cp;
1020 if (cp[0] != '\0')
1022 value = cp++;
1023 while (cp[0] != '\0' && !isspace (cp[0]))
1024 ++cp;
1025 /* Terminate value. */
1026 if (cp[0] == '\n')
1028 /* This has to be done to make the following
1029 test for the end of line possible. We are
1030 looking for the terminating '\n' which do not
1031 overwrite here. */
1032 *cp++ = '\0';
1033 *cp = '\n';
1035 else if (cp[0] != '\0')
1036 *cp++ = '\0';
1038 /* Does this alias refer to our locale? We will
1039 normalize the right hand side and compare the
1040 elements of the normalized form. */
1042 const char *rhs_language;
1043 const char *rhs_modifier;
1044 const char *rhs_territory;
1045 const char *rhs_codeset;
1046 const char *rhs_normalized_codeset;
1047 int rhs_mask = _nl_explode_name (value,
1048 &rhs_language,
1049 &rhs_modifier,
1050 &rhs_territory,
1051 &rhs_codeset,
1052 &rhs_normalized_codeset);
1053 if (!strcmp (language, rhs_language)
1054 && ((rhs_mask & XPG_CODESET)
1055 /* He has a codeset, it must match normalized. */
1056 ? !strcmp ((mask & XPG_NORM_CODESET)
1057 ? normalized_codeset : codeset,
1058 (rhs_mask & XPG_NORM_CODESET)
1059 ? rhs_normalized_codeset : rhs_codeset)
1060 /* He has no codeset, we must also have none. */
1061 : (mask & XPG_CODESET) == 0)
1062 /* Codeset (or lack thereof) matches. */
1063 && !strcmp (territory ?: "", rhs_territory ?: "")
1064 && !strcmp (modifier ?: "", rhs_modifier ?: ""))
1065 /* We have a winner. */
1066 add_alias (ah, alias, replace,
1067 normalized_name ?: name, &locrec_offset);
1068 if (rhs_mask & XPG_NORM_CODESET)
1069 free ((char *) rhs_normalized_codeset);
1074 /* Possibly not the whole line fits into the buffer.
1075 Ignore the rest of the line. */
1076 while (strchr (cp, '\n') == NULL)
1078 cp = buf;
1079 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1080 /* Make sure the inner loop will be left. The outer
1081 loop will exit at the `feof' test. */
1082 *cp = '\n';
1086 fclose (fp);
1089 free (normalized_name);
1091 if (mask & XPG_NORM_CODESET)
1092 free ((char *) normalized_codeset);
1094 return 0;
1099 add_locales_to_archive (nlist, list, replace)
1100 size_t nlist;
1101 char *list[];
1102 bool replace;
1104 struct locarhandle ah;
1105 int result = 0;
1107 /* Open the archive. This call never returns if we cannot
1108 successfully open the archive. */
1109 open_archive (&ah, false);
1111 while (nlist-- > 0)
1113 const char *fname = *list++;
1114 size_t fnamelen = strlen (fname);
1115 struct stat64 st;
1116 DIR *dirp;
1117 struct dirent64 *d;
1118 int seen;
1119 locale_data_t data;
1120 int cnt;
1122 if (! be_quiet)
1123 printf (_("Adding %s\n"), fname);
1125 /* First see whether this really is a directory and whether it
1126 contains all the require locale category files. */
1127 if (stat64 (fname, &st) < 0)
1129 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
1130 strerror (errno));
1131 continue;
1133 if (!S_ISDIR (st.st_mode))
1135 error (0, 0, _("\"%s\" is no directory; ignored"), fname);
1136 continue;
1139 dirp = opendir (fname);
1140 if (dirp == NULL)
1142 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1143 fname, strerror (errno));
1144 continue;
1147 seen = 0;
1148 while ((d = readdir64 (dirp)) != NULL)
1150 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1151 if (cnt != LC_ALL)
1152 if (strcmp (d->d_name, locnames[cnt]) == 0)
1154 unsigned char d_type;
1156 /* We have an object of the required name. If it's
1157 a directory we have to look at a file with the
1158 prefix "SYS_". Otherwise we have found what we
1159 are looking for. */
1160 #ifdef _DIRENT_HAVE_D_TYPE
1161 d_type = d->d_type;
1163 if (d_type != DT_REG)
1164 #endif
1166 char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
1168 #ifdef _DIRENT_HAVE_D_TYPE
1169 if (d_type == DT_UNKNOWN)
1170 #endif
1172 strcpy (stpcpy (stpcpy (fullname, fname), "/"),
1173 d->d_name);
1175 if (stat64 (fullname, &st) == -1)
1176 /* We cannot stat the file, ignore it. */
1177 break;
1179 d_type = IFTODT (st.st_mode);
1182 if (d_type == DT_DIR)
1184 /* We have to do more tests. The file is a
1185 directory and it therefore must contain a
1186 regular file with the same name except a
1187 "SYS_" prefix. */
1188 char *t = stpcpy (stpcpy (fullname, fname), "/");
1189 strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
1190 d->d_name);
1192 if (stat64 (fullname, &st) == -1)
1193 /* There is no SYS_* file or we cannot
1194 access it. */
1195 break;
1197 d_type = IFTODT (st.st_mode);
1201 /* If we found a regular file (eventually after
1202 following a symlink) we are successful. */
1203 if (d_type == DT_REG)
1204 ++seen;
1205 break;
1209 closedir (dirp);
1211 if (seen != __LC_LAST - 1)
1213 /* We don't have all locale category files. Ignore the name. */
1214 error (0, 0, _("incomplete set of locale files in \"%s\""),
1215 fname);
1216 continue;
1219 /* Add the files to the archive. To do this we first compute
1220 sizes and the MD5 sums of all the files. */
1221 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1222 if (cnt != LC_ALL)
1224 char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
1225 int fd;
1227 strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
1228 fd = open64 (fullname, O_RDONLY);
1229 if (fd == -1 || fstat64 (fd, &st) == -1)
1231 /* Cannot read the file. */
1232 if (fd != -1)
1233 close (fd);
1234 break;
1237 if (S_ISDIR (st.st_mode))
1239 char *t;
1240 close (fd);
1241 t = stpcpy (stpcpy (fullname, fname), "/");
1242 strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
1243 locnames[cnt]);
1245 fd = open64 (fullname, O_RDONLY);
1246 if (fd == -1 || fstat64 (fd, &st) == -1
1247 || !S_ISREG (st.st_mode))
1249 if (fd != -1)
1250 close (fd);
1251 break;
1255 /* Map the file. */
1256 data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
1257 fd, 0);
1258 if (data[cnt].addr == MAP_FAILED)
1260 /* Cannot map it. */
1261 close (fd);
1262 break;
1265 data[cnt].size = st.st_size;
1266 __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
1268 /* We don't need the file descriptor anymore. */
1269 close (fd);
1272 if (cnt != __LC_LAST)
1274 while (cnt-- > 0)
1275 if (cnt != LC_ALL)
1276 munmap (data[cnt].addr, data[cnt].size);
1278 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);
1280 continue;
1283 result |= add_locale_to_archive (&ah, basename (fname), data, replace);
1285 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1286 if (cnt != LC_ALL)
1287 munmap (data[cnt].addr, data[cnt].size);
1290 /* We are done. */
1291 close_archive (&ah);
1293 return result;
1298 delete_locales_from_archive (nlist, list)
1299 size_t nlist;
1300 char *list[];
1302 struct locarhandle ah;
1303 struct locarhead *head;
1304 struct namehashent *namehashtab;
1306 /* Open the archive. This call never returns if we cannot
1307 successfully open the archive. */
1308 open_archive (&ah, false);
1310 head = ah.addr;
1311 namehashtab = (struct namehashent *) ((char *) ah.addr
1312 + head->namehash_offset);
1314 while (nlist-- > 0)
1316 const char *locname = *list++;
1317 uint32_t hval;
1318 unsigned int idx;
1319 unsigned int incr;
1321 /* Search for this locale in the archive. */
1322 hval = archive_hashval (locname, strlen (locname));
1324 idx = hval % head->namehash_size;
1325 incr = 1 + hval % (head->namehash_size - 2);
1327 /* If the name_offset field is zero this means this is no
1328 deleted entry and therefore no entry can be found. */
1329 while (namehashtab[idx].name_offset != 0)
1331 if (namehashtab[idx].hashval == hval
1332 && (strcmp (locname,
1333 (char *) ah.addr + namehashtab[idx].name_offset)
1334 == 0))
1336 /* Found the entry. Now mark it as removed by zero-ing
1337 the reference to the locale record. */
1338 namehashtab[idx].locrec_offset = 0;
1339 break;
1342 idx += incr;
1343 if (idx >= head->namehash_size)
1344 idx -= head->namehash_size;
1347 if (namehashtab[idx].name_offset == 0 && ! be_quiet)
1348 error (0, 0, _("locale \"%s\" not in archive"), locname);
1351 close_archive (&ah);
1353 return 0;
1357 struct nameent
1359 char *name;
1360 uint32_t locrec_offset;
1364 struct dataent
1366 const unsigned char *sum;
1367 uint32_t file_offset;
1368 uint32_t nlink;
1372 static int
1373 nameentcmp (const void *a, const void *b)
1375 return strcmp (((const struct nameent *) a)->name,
1376 ((const struct nameent *) b)->name);
1380 static int
1381 dataentcmp (const void *a, const void *b)
1383 if (((const struct dataent *) a)->file_offset
1384 < ((const struct dataent *) b)->file_offset)
1385 return -1;
1387 if (((const struct dataent *) a)->file_offset
1388 > ((const struct dataent *) b)->file_offset)
1389 return 1;
1391 return 0;
1395 void
1396 show_archive_content (int verbose)
1398 struct locarhandle ah;
1399 struct locarhead *head;
1400 struct namehashent *namehashtab;
1401 struct nameent *names;
1402 size_t cnt, used;
1404 /* Open the archive. This call never returns if we cannot
1405 successfully open the archive. */
1406 open_archive (&ah, true);
1408 head = ah.addr;
1410 names = (struct nameent *) xmalloc (head->namehash_used
1411 * sizeof (struct nameent));
1413 namehashtab = (struct namehashent *) ((char *) ah.addr
1414 + head->namehash_offset);
1415 for (cnt = used = 0; cnt < head->namehash_size; ++cnt)
1416 if (namehashtab[cnt].locrec_offset != 0)
1418 assert (used < head->namehash_used);
1419 names[used].name = ah.addr + namehashtab[cnt].name_offset;
1420 names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
1423 /* Sort the names. */
1424 qsort (names, used, sizeof (struct nameent), nameentcmp);
1426 if (verbose)
1428 struct dataent *files;
1429 struct sumhashent *sumhashtab;
1430 int sumused;
1432 files = (struct dataent *) xmalloc (head->sumhash_used
1433 * sizeof (struct sumhashent));
1435 sumhashtab = (struct sumhashent *) ((char *) ah.addr
1436 + head->sumhash_offset);
1437 for (cnt = sumused = 0; cnt < head->sumhash_size; ++cnt)
1438 if (sumhashtab[cnt].file_offset != 0)
1440 assert (sumused < head->sumhash_used);
1441 files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
1442 files[sumused].file_offset = sumhashtab[cnt].file_offset;
1443 files[sumused++].nlink = 0;
1446 /* Sort by file locations. */
1447 qsort (files, sumused, sizeof (struct dataent), dataentcmp);
1449 /* Compute nlink fields. */
1450 for (cnt = 0; cnt < used; ++cnt)
1452 struct locrecent *locrec;
1453 int idx;
1455 locrec = (struct locrecent *) ((char *) ah.addr
1456 + names[cnt].locrec_offset);
1457 for (idx = 0; idx < __LC_LAST; ++idx)
1458 if (locrec->record[LC_ALL].offset != 0
1459 ? (idx == LC_ALL
1460 || (locrec->record[idx].offset
1461 < locrec->record[LC_ALL].offset)
1462 || (locrec->record[idx].offset + locrec->record[idx].len
1463 > (locrec->record[LC_ALL].offset
1464 + locrec->record[LC_ALL].len)))
1465 : idx != LC_ALL)
1467 struct dataent *data, dataent;
1469 dataent.file_offset = locrec->record[idx].offset;
1470 data = (struct dataent *) bsearch (&dataent, files, sumused,
1471 sizeof (struct dataent),
1472 dataentcmp);
1473 assert (data != NULL);
1474 ++data->nlink;
1478 /* Print it. */
1479 for (cnt = 0; cnt < used; ++cnt)
1481 struct locrecent *locrec;
1482 int idx, i;
1484 locrec = (struct locrecent *) ((char *) ah.addr
1485 + names[cnt].locrec_offset);
1486 for (idx = 0; idx < __LC_LAST; ++idx)
1487 if (idx != LC_ALL)
1489 struct dataent *data, dataent;
1491 dataent.file_offset = locrec->record[idx].offset;
1492 if (locrec->record[LC_ALL].offset != 0
1493 && dataent.file_offset >= locrec->record[LC_ALL].offset
1494 && (dataent.file_offset + locrec->record[idx].len
1495 <= (locrec->record[LC_ALL].offset
1496 + locrec->record[LC_ALL].len)))
1497 dataent.file_offset = locrec->record[LC_ALL].offset;
1499 data = (struct dataent *) bsearch (&dataent, files, sumused,
1500 sizeof (struct dataent),
1501 dataentcmp);
1502 printf ("%6d %7x %3d%c ",
1503 locrec->record[idx].len, locrec->record[idx].offset,
1504 data->nlink,
1505 dataent.file_offset == locrec->record[LC_ALL].offset
1506 ? '+' : ' ');
1507 for (i = 0; i < 16; i += 4)
1508 printf ("%02x%02x%02x%02x",
1509 data->sum[i], data->sum[i + 1],
1510 data->sum[i + 2], data->sum[i + 3]);
1511 printf (" %s/%s\n", names[cnt].name,
1512 idx == LC_MESSAGES ? "LC_MESSAGES/SYS_LC_MESSAGES"
1513 : locnames[idx]);
1517 else
1518 for (cnt = 0; cnt < used; ++cnt)
1519 puts (names[cnt].name);
1521 close_archive (&ah);
1523 exit (EXIT_SUCCESS);