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