1/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; version 2 of the License, or
8 (at your option) any later version.
9
10 This program 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
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, see <https://www.gnu.org/licenses/>. */
17
18#ifdef HAVE_CONFIG_H
19# include <config.h>
20#endif
21
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 <stdint.h>
38#include <sys/mman.h>
39#include <sys/param.h>
40#include <sys/shm.h>
41#include <sys/stat.h>
42
43#include <libc-mmap.h>
44#include <libc-pointer-arith.h>
45#include "../../crypt/md5.h"
46#include "../localeinfo.h"
47#include "../locarchive.h"
48#include "localedef.h"
49#include "locfile.h"
50
51/* Define the hash function. We define the function as static inline.
52 We must change the name so as not to conflict with simple-hash.h. */
53#define compute_hashval static archive_hashval
54#define hashval_t uint32_t
55#include "hashval.h"
56#undef compute_hashval
57
58extern const char *output_prefix;
59
60#define ARCHIVE_NAME COMPLOCALEDIR "/locale-archive"
61
62static const char *locnames[] =
63 {
64#define DEFINE_CATEGORY(category, category_name, items, a) \
65 [category] = category_name,
66#include "categories.def"
67#undef DEFINE_CATEGORY
68 };
69
70
71/* Size of the initial archive header. */
72#define INITIAL_NUM_NAMES 900
73#define INITIAL_SIZE_STRINGS 7500
74#define INITIAL_NUM_LOCREC 420
75#define INITIAL_NUM_SUMS 2000
76
77
78/* Get and set values (possibly endian-swapped) in structures mapped
79 from or written directly to locale archives. */
80#define GET(FIELD) maybe_swap_uint32 (FIELD)
81#define SET(FIELD, VALUE) ((FIELD) = maybe_swap_uint32 (VALUE))
82#define INC(FIELD, INCREMENT) SET (FIELD, GET (FIELD) + (INCREMENT))
83
84
85/* Size of the reserved address space area. */
86#define RESERVE_MMAP_SIZE 512 * 1024 * 1024
87
88/* To prepare for enlargements of the mmaped area reserve some address
89 space. On some machines, being a file mapping rather than an anonymous
90 mapping affects the address selection. So do this mapping from the
91 actual file, even though it's only a dummy to reserve address space. */
92static void *
93prepare_address_space (int fd, size_t total, size_t *reserved, int *xflags,
94 void **mmap_base, size_t *mmap_len)
95{
96 if (total < RESERVE_MMAP_SIZE)
97 {
98 void *p = mmap64 (NULL, RESERVE_MMAP_SIZE, PROT_NONE, MAP_SHARED, fd, 0);
99 if (p != MAP_FAILED)
100 {
101 void *aligned_p = PTR_ALIGN_UP (p, MAP_FIXED_ALIGNMENT);
102 size_t align_adjust = aligned_p - p;
103 *mmap_base = p;
104 *mmap_len = RESERVE_MMAP_SIZE;
105 assert (align_adjust < RESERVE_MMAP_SIZE);
106 *reserved = RESERVE_MMAP_SIZE - align_adjust;
107 *xflags = MAP_FIXED;
108 return aligned_p;
109 }
110 }
111
112 *reserved = total;
113 *xflags = 0;
114 *mmap_base = NULL;
115 *mmap_len = 0;
116 return NULL;
117}
118
119
120static void
121create_archive (const char *archivefname, struct locarhandle *ah)
122{
123 int fd;
124 char fname[strlen (archivefname) + sizeof (".XXXXXX")];
125 struct locarhead head;
126 size_t total;
127
128 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
129
130 /* Create a temporary file in the correct directory. */
131 fd = mkstemp (fname);
132 if (fd == -1)
133 error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname);
134
135 /* Create the initial content of the archive. */
136 SET (head.magic, AR_MAGIC);
137 SET (head.serial, 0);
138 SET (head.namehash_offset, sizeof (struct locarhead));
139 SET (head.namehash_used, 0);
140 SET (head.namehash_size, next_prime (INITIAL_NUM_NAMES));
141
142 SET (head.string_offset,
143 (GET (head.namehash_offset)
144 + GET (head.namehash_size) * sizeof (struct namehashent)));
145 SET (head.string_used, 0);
146 SET (head.string_size, INITIAL_SIZE_STRINGS);
147
148 SET (head.locrectab_offset,
149 GET (head.string_offset) + GET (head.string_size));
150 SET (head.locrectab_used, 0);
151 SET (head.locrectab_size, INITIAL_NUM_LOCREC);
152
153 SET (head.sumhash_offset,
154 (GET (head.locrectab_offset)
155 + GET (head.locrectab_size) * sizeof (struct locrecent)));
156 SET (head.sumhash_used, 0);
157 SET (head.sumhash_size, next_prime (INITIAL_NUM_SUMS));
158
159 total = (GET (head.sumhash_offset)
160 + GET (head.sumhash_size) * sizeof (struct sumhashent));
161
162 /* Write out the header and create room for the other data structures. */
163 if (TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head))) != sizeof (head))
164 {
165 int errval = errno;
166 unlink (fname);
167 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
168 }
169
170 if (ftruncate64 (fd, total) != 0)
171 {
172 int errval = errno;
173 unlink (fname);
174 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
175 }
176
177 size_t reserved, mmap_len;
178 int xflags;
179 void *mmap_base;
180 void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base,
181 &mmap_len);
182
183 /* Map the header and all the administration data structures. */
184 p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
185 if (p == MAP_FAILED)
186 {
187 int errval = errno;
188 unlink (fname);
189 error (EXIT_FAILURE, errval, _("cannot map archive header"));
190 }
191
192 /* Now try to rename it. We don't use the rename function since
193 this would overwrite a file which has been created in
194 parallel. */
195 if (link (fname, archivefname) == -1)
196 {
197 int errval = errno;
198
199 /* We cannot use the just created file. */
200 close (fd);
201 unlink (fname);
202
203 if (errval == EEXIST)
204 {
205 /* There is already an archive. Must have been a localedef run
206 which happened in parallel. Simply open this file then. */
207 open_archive (ah, false);
208 return;
209 }
210
211 error (EXIT_FAILURE, errval, _("failed to create new locale archive"));
212 }
213
214 /* Remove the temporary name. */
215 unlink (fname);
216
217 /* Make the file globally readable. */
218 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
219 {
220 int errval = errno;
221 unlink (archivefname);
222 error (EXIT_FAILURE, errval,
223 _("cannot change mode of new locale archive"));
224 }
225
226 ah->fname = NULL;
227 ah->fd = fd;
228 ah->mmap_base = mmap_base;
229 ah->mmap_len = mmap_len;
230 ah->addr = p;
231 ah->mmaped = total;
232 ah->reserved = reserved;
233}
234
235
236/* This structure and qsort comparator function are used below to sort an
237 old archive's locrec table in order of data position in the file. */
238struct oldlocrecent
239{
240 unsigned int cnt;
241 struct locrecent *locrec;
242};
243
244static int
245oldlocrecentcmp (const void *a, const void *b)
246{
247 struct locrecent *la = ((const struct oldlocrecent *) a)->locrec;
248 struct locrecent *lb = ((const struct oldlocrecent *) b)->locrec;
249 uint32_t start_a = -1, end_a = 0;
250 uint32_t start_b = -1, end_b = 0;
251 int cnt;
252
253 for (cnt = 0; cnt < __LC_LAST; ++cnt)
254 if (cnt != LC_ALL)
255 {
256 if (GET (la->record[cnt].offset) < start_a)
257 start_a = GET (la->record[cnt].offset);
258 if (GET (la->record[cnt].offset) + GET (la->record[cnt].len) > end_a)
259 end_a = GET (la->record[cnt].offset) + GET (la->record[cnt].len);
260 }
261 assert (start_a != (uint32_t)-1);
262 assert (end_a != 0);
263
264 for (cnt = 0; cnt < __LC_LAST; ++cnt)
265 if (cnt != LC_ALL)
266 {
267 if (GET (lb->record[cnt].offset) < start_b)
268 start_b = GET (lb->record[cnt].offset);
269 if (GET (lb->record[cnt].offset) + GET (lb->record[cnt].len) > end_b)
270 end_b = GET (lb->record[cnt].offset) + GET (lb->record[cnt].len);
271 }
272 assert (start_b != (uint32_t)-1);
273 assert (end_b != 0);
274
275 if (start_a != start_b)
276 return (int)start_a - (int)start_b;
277 return (int)end_a - (int)end_b;
278}
279
280
281/* forward decls for below */
282static uint32_t add_locale (struct locarhandle *ah, const char *name,
283 locale_data_t data, bool replace);
284static void add_alias (struct locarhandle *ah, const char *alias,
285 bool replace, const char *oldname,
286 uint32_t *locrec_offset_p);
287
288
289static bool
290file_data_available_p (struct locarhandle *ah, uint32_t offset, uint32_t size)
291{
292 if (offset < ah->mmaped && offset + size <= ah->mmaped)
293 return true;
294
295 struct stat64 st;
296 if (fstat64 (ah->fd, &st) != 0)
297 return false;
298
299 if (st.st_size > ah->reserved)
300 return false;
301
302 size_t start = ALIGN_DOWN (ah->mmaped, MAP_FIXED_ALIGNMENT);
303 void *p = mmap64 (ah->addr + start, st.st_size - start,
304 PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED,
305 ah->fd, start);
306 if (p == MAP_FAILED)
307 {
308 ah->mmaped = start;
309 return false;
310 }
311
312 ah->mmaped = st.st_size;
313 return true;
314}
315
316
317static int
318compare_from_file (struct locarhandle *ah, void *p1, uint32_t offset2,
319 uint32_t size)
320{
321 void *p2 = xmalloc (size);
322 if (pread (ah->fd, p2, size, offset2) != size)
323 record_error (4, errno,
324 _("cannot read data from locale archive"));
325
326 int res = memcmp (p1, p2, size);
327 free (p2);
328 return res;
329}
330
331
332static void
333enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
334{
335 struct stat64 st;
336 int fd;
337 struct locarhead newhead;
338 size_t total;
339 unsigned int cnt, loccnt;
340 struct namehashent *oldnamehashtab;
341 struct locarhandle new_ah;
342 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
343 char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
344 char fname[prefix_len + sizeof (ARCHIVE_NAME) + sizeof (".XXXXXX") - 1];
345
346 if (output_prefix)
347 memcpy (archivefname, output_prefix, prefix_len);
348 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
349 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
350
351 /* Not all of the old file has to be mapped. Change this now this
352 we will have to access the whole content. */
353 if (fstat64 (ah->fd, &st) != 0)
354 enomap:
355 error (EXIT_FAILURE, errno, _("cannot map locale archive file"));
356
357 if (st.st_size < ah->reserved)
358 ah->addr = mmap64 (ah->addr, st.st_size, PROT_READ | PROT_WRITE,
359 MAP_SHARED | MAP_FIXED, ah->fd, 0);
360 else
361 {
362 if (ah->mmap_base)
363 munmap (ah->mmap_base, ah->mmap_len);
364 else
365 munmap (ah->addr, ah->reserved);
366 ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE,
367 MAP_SHARED, ah->fd, 0);
368 ah->reserved = st.st_size;
369 ah->mmap_base = NULL;
370 ah->mmap_len = 0;
371 head = ah->addr;
372 }
373 if (ah->addr == MAP_FAILED)
374 goto enomap;
375 ah->mmaped = st.st_size;
376
377 /* Create a temporary file in the correct directory. */
378 fd = mkstemp (fname);
379 if (fd == -1)
380 error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname);
381
382 /* Copy the existing head information. */
383 newhead = *head;
384
385 /* Create the new archive header. The sizes of the various tables
386 should be double from what is currently used. */
387 SET (newhead.namehash_size,
388 MAX (next_prime (2 * GET (newhead.namehash_used)),
389 GET (newhead.namehash_size)));
390 if (verbose)
391 printf ("name: size: %u, used: %d, new: size: %u\n",
392 GET (head->namehash_size),
393 GET (head->namehash_used), GET (newhead.namehash_size));
394
395 SET (newhead.string_offset, (GET (newhead.namehash_offset)
396 + (GET (newhead.namehash_size)
397 * sizeof (struct namehashent))));
398 /* Keep the string table size aligned to 4 bytes, so that
399 all the struct { uint32_t } types following are happy. */
400 SET (newhead.string_size, MAX ((2 * GET (newhead.string_used) + 3) & -4,
401 GET (newhead.string_size)));
402
403 SET (newhead.locrectab_offset,
404 GET (newhead.string_offset) + GET (newhead.string_size));
405 SET (newhead.locrectab_size, MAX (2 * GET (newhead.locrectab_used),
406 GET (newhead.locrectab_size)));
407
408 SET (newhead.sumhash_offset, (GET (newhead.locrectab_offset)
409 + (GET (newhead.locrectab_size)
410 * sizeof (struct locrecent))));
411 SET (newhead.sumhash_size,
412 MAX (next_prime (2 * GET (newhead.sumhash_used)),
413 GET (newhead.sumhash_size)));
414
415 total = (GET (newhead.sumhash_offset)
416 + GET (newhead.sumhash_size) * sizeof (struct sumhashent));
417
418 /* The new file is empty now. */
419 SET (newhead.namehash_used, 0);
420 SET (newhead.string_used, 0);
421 SET (newhead.locrectab_used, 0);
422 SET (newhead.sumhash_used, 0);
423
424 /* Write out the header and create room for the other data structures. */
425 if (TEMP_FAILURE_RETRY (write (fd, &newhead, sizeof (newhead)))
426 != sizeof (newhead))
427 {
428 int errval = errno;
429 unlink (fname);
430 error (EXIT_FAILURE, errval, _("cannot initialize archive file"));
431 }
432
433 if (ftruncate64 (fd, total) != 0)
434 {
435 int errval = errno;
436 unlink (fname);
437 error (EXIT_FAILURE, errval, _("cannot resize archive file"));
438 }
439
440 size_t reserved, mmap_len;
441 int xflags;
442 void *mmap_base;
443 void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base,
444 &mmap_len);
445
446 /* Map the header and all the administration data structures. */
447 p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
448 if (p == MAP_FAILED)
449 {
450 int errval = errno;
451 unlink (fname);
452 error (EXIT_FAILURE, errval, _("cannot map archive header"));
453 }
454
455 /* Lock the new file. */
456 if (lockf64 (fd, F_LOCK, total) != 0)
457 {
458 int errval = errno;
459 unlink (fname);
460 error (EXIT_FAILURE, errval, _("cannot lock new archive"));
461 }
462
463 new_ah.mmaped = total;
464 new_ah.mmap_base = mmap_base;
465 new_ah.mmap_len = mmap_len;
466 new_ah.addr = p;
467 new_ah.fd = fd;
468 new_ah.reserved = reserved;
469
470 /* Walk through the hash name hash table to find out what data is
471 still referenced and transfer it into the new file. */
472 oldnamehashtab = (struct namehashent *) ((char *) ah->addr
473 + GET (head->namehash_offset));
474
475 /* Sort the old locrec table in order of data position. */
476 struct oldlocrecent oldlocrecarray[GET (head->namehash_size)];
477 for (cnt = 0, loccnt = 0; cnt < GET (head->namehash_size); ++cnt)
478 if (GET (oldnamehashtab[cnt].locrec_offset) != 0)
479 {
480 oldlocrecarray[loccnt].cnt = cnt;
481 oldlocrecarray[loccnt++].locrec
482 = (struct locrecent *) ((char *) ah->addr
483 + GET (oldnamehashtab[cnt].locrec_offset));
484 }
485 qsort (oldlocrecarray, loccnt, sizeof (struct oldlocrecent),
486 oldlocrecentcmp);
487
488 uint32_t last_locrec_offset = 0;
489 for (cnt = 0; cnt < loccnt; ++cnt)
490 {
491 /* Insert this entry in the new hash table. */
492 locale_data_t old_data;
493 unsigned int idx;
494 struct locrecent *oldlocrec = oldlocrecarray[cnt].locrec;
495
496 for (idx = 0; idx < __LC_LAST; ++idx)
497 if (idx != LC_ALL)
498 {
499 old_data[idx].size = GET (oldlocrec->record[idx].len);
500 old_data[idx].addr
501 = ((char *) ah->addr + GET (oldlocrec->record[idx].offset));
502
503 __md5_buffer (old_data[idx].addr, old_data[idx].size,
504 old_data[idx].sum);
505 }
506
507 if (cnt > 0 && oldlocrecarray[cnt - 1].locrec == oldlocrec)
508 {
509 const char *oldname
510 = ((char *) ah->addr
511 + GET (oldnamehashtab[oldlocrecarray[cnt
512 - 1].cnt].name_offset));
513
514 add_alias
515 (&new_ah,
516 ((char *) ah->addr
517 + GET (oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset)),
518 0, oldname, &last_locrec_offset);
519 continue;
520 }
521
522 last_locrec_offset =
523 add_locale
524 (&new_ah,
525 ((char *) ah->addr
526 + GET (oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset)),
527 old_data, 0);
528 if (last_locrec_offset == 0)
529 error (EXIT_FAILURE, 0, _("cannot extend locale archive file"));
530 }
531
532 /* Make the file globally readable. */
533 if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1)
534 {
535 int errval = errno;
536 unlink (fname);
537 error (EXIT_FAILURE, errval,
538 _("cannot change mode of resized locale archive"));
539 }
540
541 /* Rename the new file. */
542 if (rename (fname, archivefname) != 0)
543 {
544 int errval = errno;
545 unlink (fname);
546 error (EXIT_FAILURE, errval, _("cannot rename new archive"));
547 }
548
549 /* Close the old file. */
550 close_archive (ah);
551
552 /* Add the information for the new one. */
553 *ah = new_ah;
554}
555
556
557void
558open_archive (struct locarhandle *ah, bool readonly)
559{
560 struct stat64 st;
561 struct stat64 st2;
562 int fd;
563 struct locarhead head;
564 int retry = 0;
565 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
566 char default_fname[prefix_len + sizeof (ARCHIVE_NAME)];
567 const char *archivefname = ah->fname;
568
569 /* If ah has a non-NULL fname open that otherwise open the default. */
570 if (archivefname == NULL)
571 {
572 archivefname = default_fname;
573 if (output_prefix)
574 memcpy (default_fname, output_prefix, prefix_len);
575 strcpy (default_fname + prefix_len, ARCHIVE_NAME);
576 }
577
578 while (1)
579 {
580 /* Open the archive. We must have exclusive write access. */
581 fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
582 if (fd == -1)
583 {
584 /* Maybe the file does not yet exist? If we are opening
585 the default locale archive we ignore the failure and
586 list an empty archive, otherwise we print an error
587 and exit. */
588 if (errno == ENOENT && archivefname == default_fname)
589 {
590 if (readonly)
591 {
592 static const struct locarhead nullhead =
593 {
594 .namehash_used = 0,
595 .namehash_offset = 0,
596 .namehash_size = 0
597 };
598
599 ah->addr = (void *) &nullhead;
600 ah->fd = -1;
601 }
602 else
603 create_archive (archivefname, ah);
604
605 return;
606 }
607 else
608 error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
609 archivefname);
610 }
611
612 if (fstat64 (fd, &st) < 0)
613 error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""),
614 archivefname);
615
616 if (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1)
617 {
618 close (fd);
619
620 if (retry++ < max_locarchive_open_retry)
621 {
622 struct timespec req;
623
624 /* Wait for a bit. */
625 req.tv_sec = 0;
626 req.tv_nsec = 1000000 * (random () % 500 + 1);
627 (void) nanosleep (&req, NULL);
628
629 continue;
630 }
631
632 error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
633 archivefname);
634 }
635
636 /* One more check. Maybe another process replaced the archive file
637 with a new, larger one since we opened the file. */
638 if (stat64 (archivefname, &st2) == -1
639 || st.st_dev != st2.st_dev
640 || st.st_ino != st2.st_ino)
641 {
642 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
643 close (fd);
644 continue;
645 }
646
647 /* Leave the loop. */
648 break;
649 }
650
651 /* Read the header. */
652 if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
653 {
654 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
655 error (EXIT_FAILURE, errno, _("cannot read archive header"));
656 }
657
658 ah->fd = fd;
659 ah->mmaped = st.st_size;
660
661 size_t reserved, mmap_len;
662 int xflags;
663 void *mmap_base;
664 void *p = prepare_address_space (fd, st.st_size, &reserved, &xflags,
665 &mmap_base, &mmap_len);
666
667 /* Map the entire file. We might need to compare the category data
668 in the file with the newly added data. */
669 ah->addr = mmap64 (p, st.st_size, PROT_READ | (readonly ? 0 : PROT_WRITE),
670 MAP_SHARED | xflags, fd, 0);
671 if (ah->addr == MAP_FAILED)
672 {
673 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
674 error (EXIT_FAILURE, errno, _("cannot map archive header"));
675 }
676 ah->reserved = reserved;
677 ah->mmap_base = mmap_base;
678 ah->mmap_len = mmap_len;
679}
680
681
682void
683close_archive (struct locarhandle *ah)
684{
685 if (ah->fd != -1)
686 {
687 if (ah->mmap_base)
688 munmap (ah->mmap_base, ah->mmap_len);
689 else
690 munmap (ah->addr, ah->reserved);
691 close (ah->fd);
692 }
693}
694
695#include "../../intl/explodename.c"
696#include "../../intl/l10nflist.c"
697
698static struct namehashent *
699insert_name (struct locarhandle *ah,
700 const char *name, size_t name_len, bool replace)
701{
702 const struct locarhead *const head = ah->addr;
703 struct namehashent *namehashtab
704 = (struct namehashent *) ((char *) ah->addr
705 + GET (head->namehash_offset));
706 unsigned int insert_idx, idx, incr;
707
708 /* Hash value of the locale name. */
709 uint32_t hval = archive_hashval (name, name_len);
710
711 insert_idx = -1;
712 idx = hval % GET (head->namehash_size);
713 incr = 1 + hval % (GET (head->namehash_size) - 2);
714
715 /* If the name_offset field is zero this means this is a
716 deleted entry and therefore no entry can be found. */
717 while (GET (namehashtab[idx].name_offset) != 0)
718 {
719 if (GET (namehashtab[idx].hashval) == hval
720 && (strcmp (name,
721 (char *) ah->addr + GET (namehashtab[idx].name_offset))
722 == 0))
723 {
724 /* Found the entry. */
725 if (GET (namehashtab[idx].locrec_offset) != 0 && ! replace)
726 {
727 if (! be_quiet)
728 error (0, 0, _("locale '%s' already exists"), name);
729 return NULL;
730 }
731
732 break;
733 }
734
735 if (GET (namehashtab[idx].hashval) == hval && ! be_quiet)
736 {
737 error (0, 0, "hash collision (%u) %s, %s",
738 hval, name,
739 (char *) ah->addr + GET (namehashtab[idx].name_offset));
740 }
741
742 /* Remember the first place we can insert the new entry. */
743 if (GET (namehashtab[idx].locrec_offset) == 0 && insert_idx == -1)
744 insert_idx = idx;
745
746 idx += incr;
747 if (idx >= GET (head->namehash_size))
748 idx -= GET (head->namehash_size);
749 }
750
751 /* Add as early as possible. */
752 if (insert_idx != -1)
753 idx = insert_idx;
754
755 SET (namehashtab[idx].hashval, hval); /* no-op if replacing an old entry. */
756 return &namehashtab[idx];
757}
758
759static void
760add_alias (struct locarhandle *ah, const char *alias, bool replace,
761 const char *oldname, uint32_t *locrec_offset_p)
762{
763 uint32_t locrec_offset = *locrec_offset_p;
764 struct locarhead *head = ah->addr;
765 const size_t name_len = strlen (alias);
766 struct namehashent *namehashent = insert_name (ah, alias, strlen (alias),
767 replace);
768 if (namehashent == NULL && ! replace)
769 return;
770
771 if (GET (namehashent->name_offset) == 0)
772 {
773 /* We are adding a new hash entry for this alias.
774 Determine whether we have to resize the file. */
775 if (GET (head->string_used) + name_len + 1 > GET (head->string_size)
776 || (100 * GET (head->namehash_used)
777 > 75 * GET (head->namehash_size)))
778 {
779 /* The current archive is not large enough. */
780 enlarge_archive (ah, head);
781
782 /* The locrecent might have moved, so we have to look up
783 the old name afresh. */
784 namehashent = insert_name (ah, oldname, strlen (oldname), true);
785 assert (GET (namehashent->name_offset) != 0);
786 assert (GET (namehashent->locrec_offset) != 0);
787 *locrec_offset_p = GET (namehashent->locrec_offset);
788
789 /* Tail call to try the whole thing again. */
790 add_alias (ah, alias, replace, oldname, locrec_offset_p);
791 return;
792 }
793
794 /* Add the name string. */
795 memcpy (ah->addr + GET (head->string_offset) + GET (head->string_used),
796 alias, name_len + 1);
797 SET (namehashent->name_offset,
798 GET (head->string_offset) + GET (head->string_used));
799 INC (head->string_used, name_len + 1);
800
801 INC (head->namehash_used, 1);
802 }
803
804 if (GET (namehashent->locrec_offset) != 0)
805 {
806 /* Replacing an existing entry.
807 Mark that we are no longer using the old locrecent. */
808 struct locrecent *locrecent
809 = (struct locrecent *) ((char *) ah->addr
810 + GET (namehashent->locrec_offset));
811 INC (locrecent->refs, -1);
812 }
813
814 /* Point this entry at the locrecent installed for the main name. */
815 SET (namehashent->locrec_offset, locrec_offset);
816}
817
818static int /* qsort comparator used below */
819cmpcategorysize (const void *a, const void *b)
820{
821 if (*(const void **) a == NULL)
822 return 1;
823 if (*(const void **) b == NULL)
824 return -1;
825 return ((*(const struct locale_category_data **) a)->size
826 - (*(const struct locale_category_data **) b)->size);
827}
828
829/* Check the content of the archive for duplicates. Add the content
830 of the files if necessary. Returns the locrec_offset. */
831static uint32_t
832add_locale (struct locarhandle *ah,
833 const char *name, locale_data_t data, bool replace)
834{
835 /* First look for the name. If it already exists and we are not
836 supposed to replace it don't do anything. If it does not exist
837 we have to allocate a new locale record. */
838 size_t name_len = strlen (name);
839 uint32_t file_offsets[__LC_LAST];
840 unsigned int num_new_offsets = 0;
841 struct sumhashent *sumhashtab;
842 uint32_t hval;
843 unsigned int cnt, idx;
844 struct locarhead *head;
845 struct namehashent *namehashent;
846 unsigned int incr;
847 struct locrecent *locrecent;
848 off64_t lastoffset;
849 char *ptr;
850 struct locale_category_data *size_order[__LC_LAST];
851 /* Page size alignment is a minor optimization for locality; use a
852 common value here rather than making the localedef output depend
853 on the page size of the system on which localedef is run. See
854 <https://sourceware.org/glibc/wiki/Development_Todo/Master#Locale_archive_alignment>
855 for more discussion. */
856 const size_t pagesz = 4096;
857 int small_mask;
858
859 head = ah->addr;
860 sumhashtab = (struct sumhashent *) ((char *) ah->addr
861 + GET (head->sumhash_offset));
862
863 memset (file_offsets, 0, sizeof (file_offsets));
864
865 size_order[LC_ALL] = NULL;
866 for (cnt = 0; cnt < __LC_LAST; ++cnt)
867 if (cnt != LC_ALL)
868 size_order[cnt] = &data[cnt];
869
870 /* Sort the array in ascending order of data size. */
871 qsort (size_order, __LC_LAST, sizeof size_order[0], cmpcategorysize);
872
873 small_mask = 0;
874 data[LC_ALL].size = 0;
875 for (cnt = 0; cnt < __LC_LAST; ++cnt)
876 if (size_order[cnt] != NULL)
877 {
878 const size_t rounded_size = (size_order[cnt]->size + 15) & -16;
879 if (data[LC_ALL].size + rounded_size > 2 * pagesz)
880 {
881 /* This category makes the small-categories block
882 stop being small, so this is the end of the road. */
883 do
884 size_order[cnt++] = NULL;
885 while (cnt < __LC_LAST);
886 break;
887 }
888 data[LC_ALL].size += rounded_size;
889 small_mask |= 1 << (size_order[cnt] - data);
890 }
891
892 /* Copy the data for all the small categories into the LC_ALL
893 pseudo-category. */
894
895 data[LC_ALL].addr = alloca (data[LC_ALL].size);
896 memset (data[LC_ALL].addr, 0, data[LC_ALL].size);
897
898 ptr = data[LC_ALL].addr;
899 for (cnt = 0; cnt < __LC_LAST; ++cnt)
900 if (small_mask & (1 << cnt))
901 {
902 memcpy (ptr, data[cnt].addr, data[cnt].size);
903 ptr += (data[cnt].size + 15) & -16;
904 }
905 __md5_buffer (data[LC_ALL].addr, data[LC_ALL].size, data[LC_ALL].sum);
906
907 /* For each locale category data set determine whether the same data
908 is already somewhere in the archive. */
909 for (cnt = 0; cnt < __LC_LAST; ++cnt)
910 if (small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
911 {
912 ++num_new_offsets;
913
914 /* Compute the hash value of the checksum to determine a
915 starting point for the search in the MD5 hash value
916 table. */
917 hval = archive_hashval (data[cnt].sum, 16);
918
919 idx = hval % GET (head->sumhash_size);
920 incr = 1 + hval % (GET (head->sumhash_size) - 2);
921
922 while (GET (sumhashtab[idx].file_offset) != 0)
923 {
924 if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
925 {
926 /* Check the content, there could be a collision of
927 the hash sum.
928
929 Unfortunately the sumhashent record does not include
930 the size of the stored data. So we have to search for
931 it. */
932 locrecent
933 = (struct locrecent *) ((char *) ah->addr
934 + GET (head->locrectab_offset));
935 size_t iloc;
936 for (iloc = 0; iloc < GET (head->locrectab_used); ++iloc)
937 if (GET (locrecent[iloc].refs) != 0
938 && (GET (locrecent[iloc].record[cnt].offset)
939 == GET (sumhashtab[idx].file_offset)))
940 break;
941
942 if (iloc != GET (head->locrectab_used)
943 && data[cnt].size == GET (locrecent[iloc].record[cnt].len)
944 /* We have to compare the content. Either we can
945 have the data mmaped or we have to read from
946 the file. */
947 && (file_data_available_p
948 (ah, GET (sumhashtab[idx].file_offset),
949 data[cnt].size)
950 ? memcmp (data[cnt].addr,
951 (char *) ah->addr
952 + GET (sumhashtab[idx].file_offset),
953 data[cnt].size) == 0
954 : compare_from_file (ah, data[cnt].addr,
955 GET (sumhashtab[idx].file_offset),
956 data[cnt].size) == 0))
957 {
958 /* Found it. */
959 file_offsets[cnt] = GET (sumhashtab[idx].file_offset);
960 --num_new_offsets;
961 break;
962 }
963 }
964
965 idx += incr;
966 if (idx >= GET (head->sumhash_size))
967 idx -= GET (head->sumhash_size);
968 }
969 }
970
971 /* Find a slot for the locale name in the hash table. */
972 namehashent = insert_name (ah, name, name_len, replace);
973 if (namehashent == NULL) /* Already exists and !REPLACE. */
974 return 0;
975
976 /* Determine whether we have to resize the file. */
977 if ((100 * (GET (head->sumhash_used) + num_new_offsets)
978 > 75 * GET (head->sumhash_size))
979 || (GET (namehashent->locrec_offset) == 0
980 && (GET (head->locrectab_used) == GET (head->locrectab_size)
981 || (GET (head->string_used) + name_len + 1
982 > GET (head->string_size))
983 || (100 * GET (head->namehash_used)
984 > 75 * GET (head->namehash_size)))))
985 {
986 /* The current archive is not large enough. */
987 enlarge_archive (ah, head);
988 return add_locale (ah, name, data, replace);
989 }
990
991 /* Add the locale data which is not yet in the archive. */
992 for (cnt = 0, lastoffset = 0; cnt < __LC_LAST; ++cnt)
993 if ((small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
994 && file_offsets[cnt] == 0)
995 {
996 /* The data for this section is not yet available in the
997 archive. Append it. */
998 off64_t lastpos;
999 uint32_t md5hval;
1000
1001 lastpos = lseek64 (ah->fd, 0, SEEK_END);
1002 if (lastpos == (off64_t) -1)
1003 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
1004
1005 /* If block of small categories would cross page boundary,
1006 align it unless it immediately follows a large category. */
1007 if (cnt == LC_ALL && lastoffset != lastpos
1008 && ((((lastpos & (pagesz - 1)) + data[cnt].size + pagesz - 1)
1009 & -pagesz)
1010 > ((data[cnt].size + pagesz - 1) & -pagesz)))
1011 {
1012 size_t sz = pagesz - (lastpos & (pagesz - 1));
1013 char *zeros = alloca (sz);
1014
1015 memset (zeros, 0, sz);
1016 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, sz) != sz))
1017 error (EXIT_FAILURE, errno,
1018 _("cannot add to locale archive"));
1019
1020 lastpos += sz;
1021 }
1022
1023 /* Align all data to a 16 byte boundary. */
1024 if ((lastpos & 15) != 0)
1025 {
1026 static const char zeros[15] = { 0, };
1027
1028 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, 16 - (lastpos & 15)))
1029 != 16 - (lastpos & 15))
1030 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
1031
1032 lastpos += 16 - (lastpos & 15);
1033 }
1034
1035 /* Remember the position. */
1036 file_offsets[cnt] = lastpos;
1037 lastoffset = lastpos + data[cnt].size;
1038
1039 /* Write the data. */
1040 if (TEMP_FAILURE_RETRY (write (ah->fd, data[cnt].addr, data[cnt].size))
1041 != data[cnt].size)
1042 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
1043
1044 /* Add the hash value to the hash table. */
1045 md5hval = archive_hashval (data[cnt].sum, 16);
1046
1047 idx = md5hval % GET (head->sumhash_size);
1048 incr = 1 + md5hval % (GET (head->sumhash_size) - 2);
1049
1050 while (GET (sumhashtab[idx].file_offset) != 0)
1051 {
1052 idx += incr;
1053 if (idx >= GET (head->sumhash_size))
1054 idx -= GET (head->sumhash_size);
1055 }
1056
1057 memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
1058 SET (sumhashtab[idx].file_offset, file_offsets[cnt]);
1059
1060 INC (head->sumhash_used, 1);
1061 }
1062
1063 lastoffset = file_offsets[LC_ALL];
1064 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1065 if (small_mask & (1 << cnt))
1066 {
1067 file_offsets[cnt] = lastoffset;
1068 lastoffset += (data[cnt].size + 15) & -16;
1069 }
1070
1071 if (GET (namehashent->name_offset) == 0)
1072 {
1073 /* Add the name string. */
1074 memcpy ((char *) ah->addr + GET (head->string_offset)
1075 + GET (head->string_used),
1076 name, name_len + 1);
1077 SET (namehashent->name_offset,
1078 GET (head->string_offset) + GET (head->string_used));
1079 INC (head->string_used, name_len + 1);
1080 INC (head->namehash_used, 1);
1081 }
1082
1083 if (GET (namehashent->locrec_offset == 0))
1084 {
1085 /* Allocate a name location record. */
1086 SET (namehashent->locrec_offset, (GET (head->locrectab_offset)
1087 + (GET (head->locrectab_used)
1088 * sizeof (struct locrecent))));
1089 INC (head->locrectab_used, 1);
1090 locrecent = (struct locrecent *) ((char *) ah->addr
1091 + GET (namehashent->locrec_offset));
1092 SET (locrecent->refs, 1);
1093 }
1094 else
1095 {
1096 /* If there are other aliases pointing to this locrecent,
1097 we still need a new one. If not, reuse the old one. */
1098
1099 locrecent = (struct locrecent *) ((char *) ah->addr
1100 + GET (namehashent->locrec_offset));
1101 if (GET (locrecent->refs) > 1)
1102 {
1103 INC (locrecent->refs, -1);
1104 SET (namehashent->locrec_offset, (GET (head->locrectab_offset)
1105 + (GET (head->locrectab_used)
1106 * sizeof (struct locrecent))));
1107 INC (head->locrectab_used, 1);
1108 locrecent
1109 = (struct locrecent *) ((char *) ah->addr
1110 + GET (namehashent->locrec_offset));
1111 SET (locrecent->refs, 1);
1112 }
1113 }
1114
1115 /* Fill in the table with the locations of the locale data. */
1116 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1117 {
1118 SET (locrecent->record[cnt].offset, file_offsets[cnt]);
1119 SET (locrecent->record[cnt].len, data[cnt].size);
1120 }
1121
1122 return GET (namehashent->locrec_offset);
1123}
1124
1125
1126/* Check the content of the archive for duplicates. Add the content
1127 of the files if necessary. Add all the names, possibly overwriting
1128 old files. */
1129int
1130add_locale_to_archive (struct locarhandle *ah, const char *name,
1131 locale_data_t data, bool replace)
1132{
1133 char *normalized_name = NULL;
1134 uint32_t locrec_offset;
1135
1136 /* First analyze the name to decide how to archive it. */
1137 const char *language;
1138 const char *modifier;
1139 const char *territory;
1140 const char *codeset;
1141 const char *normalized_codeset;
1142 int mask = _nl_explode_name (strdupa (name),
1143 &language, &modifier, &territory,
1144 &codeset, &normalized_codeset);
1145 if (mask == -1)
1146 return -1;
1147
1148 if (mask & XPG_NORM_CODESET)
1149 /* This name contains a codeset in unnormalized form.
1150 We will store it in the archive with a normalized name. */
1151 asprintf (&normalized_name, "%s%s%s.%s%s%s",
1152 language, territory == NULL ? "" : "_", territory ?: "",
1153 (mask & XPG_NORM_CODESET) ? normalized_codeset : codeset,
1154 modifier == NULL ? "" : "@", modifier ?: "");
1155
1156 /* This call does the main work. */
1157 locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
1158 if (locrec_offset == 0)
1159 {
1160 free (normalized_name);
1161 if (mask & XPG_NORM_CODESET)
1162 free ((char *) normalized_codeset);
1163 return -1;
1164 }
1165
1166 if ((mask & XPG_CODESET) == 0)
1167 {
1168 /* This name lacks a codeset, so determine the locale's codeset and
1169 add an alias for its name with normalized codeset appended. */
1170
1171 const struct
1172 {
1173 unsigned int magic;
1174 unsigned int nstrings;
1175 unsigned int strindex[0];
1176 } *filedata = data[LC_CTYPE].addr;
1177 codeset = (char *) filedata
1178 + maybe_swap_uint32 (filedata->strindex[_NL_ITEM_INDEX
1179 (_NL_CTYPE_CODESET_NAME)]);
1180 char *normalized_codeset_name = NULL;
1181
1182 normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
1183 mask |= XPG_NORM_CODESET;
1184
1185 asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
1186 language, territory == NULL ? "" : "_", territory ?: "",
1187 normalized_codeset,
1188 modifier == NULL ? "" : "@", modifier ?: "");
1189
1190 add_alias (ah, normalized_codeset_name, replace,
1191 normalized_name ?: name, &locrec_offset);
1192 free (normalized_codeset_name);
1193 }
1194
1195 /* Now read the locale.alias files looking for lines whose
1196 right hand side matches our name after normalization. */
1197 int result = 0;
1198 if (alias_file != NULL)
1199 {
1200 FILE *fp;
1201 fp = fopen (alias_file, "rm");
1202 if (fp == NULL)
1203 error (1, errno, _("locale alias file `%s' not found"),
1204 alias_file);
1205
1206 /* No threads present. */
1207 __fsetlocking (fp, FSETLOCKING_BYCALLER);
1208
1209 while (! feof_unlocked (fp))
1210 {
1211 /* It is a reasonable approach to use a fix buffer here
1212 because
1213 a) we are only interested in the first two fields
1214 b) these fields must be usable as file names and so must
1215 not be that long */
1216 char buf[BUFSIZ];
1217 char *alias;
1218 char *value;
1219 char *cp;
1220
1221 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1222 /* EOF reached. */
1223 break;
1224
1225 cp = buf;
1226 /* Ignore leading white space. */
1227 while (isspace (cp[0]) && cp[0] != '\n')
1228 ++cp;
1229
1230 /* A leading '#' signals a comment line. */
1231 if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
1232 {
1233 alias = cp++;
1234 while (cp[0] != '\0' && !isspace (cp[0]))
1235 ++cp;
1236 /* Terminate alias name. */
1237 if (cp[0] != '\0')
1238 *cp++ = '\0';
1239
1240 /* Now look for the beginning of the value. */
1241 while (isspace (cp[0]))
1242 ++cp;
1243
1244 if (cp[0] != '\0')
1245 {
1246 value = cp++;
1247 while (cp[0] != '\0' && !isspace (cp[0]))
1248 ++cp;
1249 /* Terminate value. */
1250 if (cp[0] == '\n')
1251 {
1252 /* This has to be done to make the following
1253 test for the end of line possible. We are
1254 looking for the terminating '\n' which do not
1255 overwrite here. */
1256 *cp++ = '\0';
1257 *cp = '\n';
1258 }
1259 else if (cp[0] != '\0')
1260 *cp++ = '\0';
1261
1262 /* Does this alias refer to our locale? We will
1263 normalize the right hand side and compare the
1264 elements of the normalized form. */
1265 {
1266 const char *rhs_language;
1267 const char *rhs_modifier;
1268 const char *rhs_territory;
1269 const char *rhs_codeset;
1270 const char *rhs_normalized_codeset;
1271 int rhs_mask = _nl_explode_name (value,
1272 &rhs_language,
1273 &rhs_modifier,
1274 &rhs_territory,
1275 &rhs_codeset,
1276 &rhs_normalized_codeset);
1277 if (rhs_mask == -1)
1278 {
1279 result = -1;
1280 goto out;
1281 }
1282 if (!strcmp (language, rhs_language)
1283 && ((rhs_mask & XPG_CODESET)
1284 /* He has a codeset, it must match normalized. */
1285 ? !strcmp ((mask & XPG_NORM_CODESET)
1286 ? normalized_codeset : codeset,
1287 (rhs_mask & XPG_NORM_CODESET)
1288 ? rhs_normalized_codeset : rhs_codeset)
1289 /* He has no codeset, we must also have none. */
1290 : (mask & XPG_CODESET) == 0)
1291 /* Codeset (or lack thereof) matches. */
1292 && !strcmp (territory ?: "", rhs_territory ?: "")
1293 && !strcmp (modifier ?: "", rhs_modifier ?: ""))
1294 /* We have a winner. */
1295 add_alias (ah, alias, replace,
1296 normalized_name ?: name, &locrec_offset);
1297 if (rhs_mask & XPG_NORM_CODESET)
1298 free ((char *) rhs_normalized_codeset);
1299 }
1300 }
1301 }
1302
1303 /* Possibly not the whole line fits into the buffer.
1304 Ignore the rest of the line. */
1305 while (strchr (cp, '\n') == NULL)
1306 {
1307 cp = buf;
1308 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1309 /* Make sure the inner loop will be left. The outer
1310 loop will exit at the `feof' test. */
1311 *cp = '\n';
1312 }
1313 }
1314
1315 out:
1316 fclose (fp);
1317 }
1318
1319 free (normalized_name);
1320
1321 if (mask & XPG_NORM_CODESET)
1322 free ((char *) normalized_codeset);
1323
1324 return result;
1325}
1326
1327
1328int
1329add_locales_to_archive (size_t nlist, char *list[], bool replace)
1330{
1331 struct locarhandle ah;
1332 int result = 0;
1333
1334 /* Open the archive. This call never returns if we cannot
1335 successfully open the archive. */
1336 ah.fname = NULL;
1337 open_archive (&ah, false);
1338
1339 while (nlist-- > 0)
1340 {
1341 const char *fname = *list++;
1342 size_t fnamelen = strlen (fname);
1343 struct stat64 st;
1344 DIR *dirp;
1345 struct dirent64 *d;
1346 int seen;
1347 locale_data_t data;
1348 int cnt;
1349
1350 if (! be_quiet)
1351 printf (_("Adding %s\n"), fname);
1352
1353 /* First see whether this really is a directory and whether it
1354 contains all the require locale category files. */
1355 if (stat64 (fname, &st) < 0)
1356 {
1357 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
1358 strerror (errno));
1359 continue;
1360 }
1361 if (!S_ISDIR (st.st_mode))
1362 {
1363 error (0, 0, _("\"%s\" is no directory; ignored"), fname);
1364 continue;
1365 }
1366
1367 dirp = opendir (fname);
1368 if (dirp == NULL)
1369 {
1370 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1371 fname, strerror (errno));
1372 continue;
1373 }
1374
1375 seen = 0;
1376 while ((d = readdir64 (dirp)) != NULL)
1377 {
1378 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1379 if (cnt != LC_ALL)
1380 if (strcmp (d->d_name, locnames[cnt]) == 0)
1381 {
1382 unsigned char d_type;
1383
1384 /* We have an object of the required name. If it's
1385 a directory we have to look at a file with the
1386 prefix "SYS_". Otherwise we have found what we
1387 are looking for. */
1388 d_type = d->d_type;
1389
1390 if (d_type != DT_REG)
1391 {
1392 char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
1393
1394 if (d_type == DT_UNKNOWN)
1395 {
1396 strcpy (stpcpy (stpcpy (fullname, fname), "/"),
1397 d->d_name);
1398
1399 if (stat64 (fullname, &st) == -1)
1400 /* We cannot stat the file, ignore it. */
1401 break;
1402
1403 d_type = IFTODT (st.st_mode);
1404 }
1405
1406 if (d_type == DT_DIR)
1407 {
1408 /* We have to do more tests. The file is a
1409 directory and it therefore must contain a
1410 regular file with the same name except a
1411 "SYS_" prefix. */
1412 char *t = stpcpy (stpcpy (fullname, fname), "/");
1413 strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
1414 d->d_name);
1415
1416 if (stat64 (fullname, &st) == -1)
1417 /* There is no SYS_* file or we cannot
1418 access it. */
1419 break;
1420
1421 d_type = IFTODT (st.st_mode);
1422 }
1423 }
1424
1425 /* If we found a regular file (eventually after
1426 following a symlink) we are successful. */
1427 if (d_type == DT_REG)
1428 ++seen;
1429 break;
1430 }
1431 }
1432
1433 closedir (dirp);
1434
1435 if (seen != __LC_LAST - 1)
1436 {
1437 /* We don't have all locale category files. Ignore the name. */
1438 error (0, 0, _("incomplete set of locale files in \"%s\""),
1439 fname);
1440 continue;
1441 }
1442
1443 /* Add the files to the archive. To do this we first compute
1444 sizes and the MD5 sums of all the files. */
1445 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1446 if (cnt != LC_ALL)
1447 {
1448 char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
1449 int fd;
1450
1451 strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
1452 fd = open64 (fullname, O_RDONLY);
1453 if (fd == -1 || fstat64 (fd, &st) == -1)
1454 {
1455 /* Cannot read the file. */
1456 if (fd != -1)
1457 close (fd);
1458 break;
1459 }
1460
1461 if (S_ISDIR (st.st_mode))
1462 {
1463 char *t;
1464 close (fd);
1465 t = stpcpy (stpcpy (fullname, fname), "/");
1466 strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
1467 locnames[cnt]);
1468
1469 fd = open64 (fullname, O_RDONLY);
1470 if (fd == -1 || fstat64 (fd, &st) == -1
1471 || !S_ISREG (st.st_mode))
1472 {
1473 if (fd != -1)
1474 close (fd);
1475 break;
1476 }
1477 }
1478
1479 /* Map the file. */
1480 data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
1481 fd, 0);
1482 if (data[cnt].addr == MAP_FAILED)
1483 {
1484 /* Cannot map it. */
1485 close (fd);
1486 break;
1487 }
1488
1489 data[cnt].size = st.st_size;
1490 __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
1491
1492 /* We don't need the file descriptor anymore. */
1493 close (fd);
1494 }
1495
1496 if (cnt != __LC_LAST)
1497 {
1498 while (cnt-- > 0)
1499 if (cnt != LC_ALL)
1500 munmap (data[cnt].addr, data[cnt].size);
1501
1502 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);
1503
1504 continue;
1505 }
1506
1507 result |= add_locale_to_archive (&ah, basename (fname), data, replace);
1508
1509 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1510 if (cnt != LC_ALL)
1511 munmap (data[cnt].addr, data[cnt].size);
1512 }
1513
1514 /* We are done. */
1515 close_archive (&ah);
1516
1517 return result;
1518}
1519
1520
1521int
1522delete_locales_from_archive (size_t nlist, char *list[])
1523{
1524 struct locarhandle ah;
1525 struct locarhead *head;
1526 struct namehashent *namehashtab;
1527
1528 /* Open the archive. This call never returns if we cannot
1529 successfully open the archive. */
1530 ah.fname = NULL;
1531 open_archive (&ah, false);
1532
1533 head = ah.addr;
1534 namehashtab = (struct namehashent *) ((char *) ah.addr
1535 + GET (head->namehash_offset));
1536
1537 while (nlist-- > 0)
1538 {
1539 const char *locname = *list++;
1540 uint32_t hval;
1541 unsigned int idx;
1542 unsigned int incr;
1543
1544 /* Search for this locale in the archive. */
1545 hval = archive_hashval (locname, strlen (locname));
1546
1547 idx = hval % GET (head->namehash_size);
1548 incr = 1 + hval % (GET (head->namehash_size) - 2);
1549
1550 /* If the name_offset field is zero this means this is no
1551 deleted entry and therefore no entry can be found. */
1552 while (GET (namehashtab[idx].name_offset) != 0)
1553 {
1554 if (GET (namehashtab[idx].hashval) == hval
1555 && (strcmp (locname,
1556 ((char *) ah.addr
1557 + GET (namehashtab[idx].name_offset)))
1558 == 0))
1559 {
1560 /* Found the entry. Now mark it as removed by zero-ing
1561 the reference to the locale record. */
1562 SET (namehashtab[idx].locrec_offset, 0);
1563 break;
1564 }
1565
1566 idx += incr;
1567 if (idx >= GET (head->namehash_size))
1568 idx -= GET (head->namehash_size);
1569 }
1570
1571 if (GET (namehashtab[idx].name_offset) == 0 && ! be_quiet)
1572 error (0, 0, _("locale \"%s\" not in archive"), locname);
1573 }
1574
1575 close_archive (&ah);
1576
1577 return 0;
1578}
1579
1580
1581struct nameent
1582{
1583 char *name;
1584 uint32_t locrec_offset;
1585};
1586
1587
1588struct dataent
1589{
1590 const unsigned char *sum;
1591 uint32_t file_offset;
1592 uint32_t nlink;
1593};
1594
1595
1596static int
1597nameentcmp (const void *a, const void *b)
1598{
1599 return strcmp (((const struct nameent *) a)->name,
1600 ((const struct nameent *) b)->name);
1601}
1602
1603
1604static int
1605dataentcmp (const void *a, const void *b)
1606{
1607 if (((const struct dataent *) a)->file_offset
1608 < ((const struct dataent *) b)->file_offset)
1609 return -1;
1610
1611 if (((const struct dataent *) a)->file_offset
1612 > ((const struct dataent *) b)->file_offset)
1613 return 1;
1614
1615 return 0;
1616}
1617
1618
1619void
1620show_archive_content (const char *fname, int verbose)
1621{
1622 struct locarhandle ah;
1623 struct locarhead *head;
1624 struct namehashent *namehashtab;
1625 struct nameent *names;
1626 size_t cnt, used;
1627
1628 /* Open the archive. This call never returns if we cannot
1629 successfully open the archive. */
1630 ah.fname = fname;
1631 open_archive (&ah, true);
1632
1633 head = ah.addr;
1634
1635 names = (struct nameent *) xmalloc (GET (head->namehash_used)
1636 * sizeof (struct nameent));
1637
1638 namehashtab = (struct namehashent *) ((char *) ah.addr
1639 + GET (head->namehash_offset));
1640 for (cnt = used = 0; cnt < GET (head->namehash_size); ++cnt)
1641 if (GET (namehashtab[cnt].locrec_offset) != 0)
1642 {
1643 assert (used < GET (head->namehash_used));
1644 names[used].name = ah.addr + GET (namehashtab[cnt].name_offset);
1645 names[used++].locrec_offset = GET (namehashtab[cnt].locrec_offset);
1646 }
1647
1648 /* Sort the names. */
1649 qsort (names, used, sizeof (struct nameent), nameentcmp);
1650
1651 if (verbose)
1652 {
1653 struct dataent *files;
1654 struct sumhashent *sumhashtab;
1655 int sumused;
1656
1657 files = (struct dataent *) xmalloc (GET (head->sumhash_used)
1658 * sizeof (struct dataent));
1659
1660 sumhashtab = (struct sumhashent *) ((char *) ah.addr
1661 + GET (head->sumhash_offset));
1662 for (cnt = sumused = 0; cnt < GET (head->sumhash_size); ++cnt)
1663 if (GET (sumhashtab[cnt].file_offset) != 0)
1664 {
1665 assert (sumused < GET (head->sumhash_used));
1666 files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
1667 files[sumused].file_offset = GET (sumhashtab[cnt].file_offset);
1668 files[sumused++].nlink = 0;
1669 }
1670
1671 /* Sort by file locations. */
1672 qsort (files, sumused, sizeof (struct dataent), dataentcmp);
1673
1674 /* Compute nlink fields. */
1675 for (cnt = 0; cnt < used; ++cnt)
1676 {
1677 struct locrecent *locrec;
1678 int idx;
1679
1680 locrec = (struct locrecent *) ((char *) ah.addr
1681 + names[cnt].locrec_offset);
1682 for (idx = 0; idx < __LC_LAST; ++idx)
1683 if (GET (locrec->record[LC_ALL].offset) != 0
1684 ? (idx == LC_ALL
1685 || (GET (locrec->record[idx].offset)
1686 < GET (locrec->record[LC_ALL].offset))
1687 || ((GET (locrec->record[idx].offset)
1688 + GET (locrec->record[idx].len))
1689 > (GET (locrec->record[LC_ALL].offset)
1690 + GET (locrec->record[LC_ALL].len))))
1691 : idx != LC_ALL)
1692 {
1693 struct dataent *data, dataent;
1694
1695 dataent.file_offset = GET (locrec->record[idx].offset);
1696 data = (struct dataent *) bsearch (&dataent, files, sumused,
1697 sizeof (struct dataent),
1698 dataentcmp);
1699 assert (data != NULL);
1700 ++data->nlink;
1701 }
1702 }
1703
1704 /* Print it. */
1705 for (cnt = 0; cnt < used; ++cnt)
1706 {
1707 struct locrecent *locrec;
1708 int idx, i;
1709
1710 locrec = (struct locrecent *) ((char *) ah.addr
1711 + names[cnt].locrec_offset);
1712 for (idx = 0; idx < __LC_LAST; ++idx)
1713 if (idx != LC_ALL)
1714 {
1715 struct dataent *data, dataent;
1716
1717 dataent.file_offset = GET (locrec->record[idx].offset);
1718 if (GET (locrec->record[LC_ALL].offset) != 0
1719 && (dataent.file_offset
1720 >= GET (locrec->record[LC_ALL].offset))
1721 && (dataent.file_offset + GET (locrec->record[idx].len)
1722 <= (GET (locrec->record[LC_ALL].offset)
1723 + GET (locrec->record[LC_ALL].len))))
1724 dataent.file_offset = GET (locrec->record[LC_ALL].offset);
1725
1726 data = (struct dataent *) bsearch (&dataent, files, sumused,
1727 sizeof (struct dataent),
1728 dataentcmp);
1729 printf ("%6d %7x %3d%c ",
1730 GET (locrec->record[idx].len),
1731 GET (locrec->record[idx].offset),
1732 data->nlink,
1733 (dataent.file_offset
1734 == GET (locrec->record[LC_ALL].offset))
1735 ? '+' : ' ');
1736 for (i = 0; i < 16; i += 4)
1737 printf ("%02x%02x%02x%02x",
1738 data->sum[i], data->sum[i + 1],
1739 data->sum[i + 2], data->sum[i + 3]);
1740 printf (" %s/%s\n", names[cnt].name,
1741 idx == LC_MESSAGES ? "LC_MESSAGES/SYS_LC_MESSAGES"
1742 : locnames[idx]);
1743 }
1744 }
1745 }
1746 else
1747 for (cnt = 0; cnt < used; ++cnt)
1748 puts (names[cnt].name);
1749
1750 close_archive (&ah);
1751
1752 exit (EXIT_SUCCESS);
1753}
1754