]> git.ipfire.org Git - thirdparty/glibc.git/blame - locale/programs/locarchive.c
Update copyright dates with scripts/update-copyrights
[thirdparty/glibc.git] / locale / programs / locarchive.c
CommitLineData
dff8da6b 1/* Copyright (C) 2002-2024 Free Software Foundation, Inc.
a7b65cdc 2 This file is part of the GNU C Library.
a7b65cdc 3
43bc8ac6 4 This program is free software; you can redistribute it and/or modify
2e2efe65
RM
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.
a7b65cdc 8
43bc8ac6 9 This program is distributed in the hope that it will be useful,
a7b65cdc 10 but WITHOUT ANY WARRANTY; without even the implied warranty of
43bc8ac6
UD
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
a7b65cdc 13
43bc8ac6 14 You should have received a copy of the GNU General Public License
5a82c748 15 along with this program; if not, see <https://www.gnu.org/licenses/>. */
a7b65cdc
UD
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>
cb09a2cd 31#include <stdio_ext.h>
a7b65cdc
UD
32#include <stdlib.h>
33#include <string.h>
34#include <time.h>
35#include <unistd.h>
e054f494 36#include <stdint.h>
a7b65cdc
UD
37#include <sys/mman.h>
38#include <sys/param.h>
17db6e8d 39#include <sys/shm.h>
a7b65cdc
UD
40#include <sys/stat.h>
41
17db6e8d 42#include <libc-mmap.h>
9090848d 43#include <libc-pointer-arith.h>
e6e3c666 44#include "md5.h"
a7b65cdc
UD
45#include "../localeinfo.h"
46#include "../locarchive.h"
a7b65cdc 47#include "localedef.h"
6055173a 48#include "locfile.h"
a7b65cdc 49
a3f9038c
RM
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. */
f1d70dad 52#define compute_hashval static archive_hashval
a3f9038c
RM
53#define hashval_t uint32_t
54#include "hashval.h"
55#undef compute_hashval
56
531bafd8 57extern const char *output_prefix;
a7b65cdc 58
90fe682d 59#define ARCHIVE_NAME COMPLOCALEDIR "/locale-archive"
a7b65cdc
UD
60
61static 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. */
c14f245c
UD
71#define INITIAL_NUM_NAMES 900
72#define INITIAL_SIZE_STRINGS 7500
73#define INITIAL_NUM_LOCREC 420
a7b65cdc
UD
74#define INITIAL_NUM_SUMS 2000
75
76
6055173a
JM
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
705341a9
UD
84/* Size of the reserved address space area. */
85#define RESERVE_MMAP_SIZE 512 * 1024 * 1024
86
110946e4
RM
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. */
91static void *
17db6e8d
MF
92prepare_address_space (int fd, size_t total, size_t *reserved, int *xflags,
93 void **mmap_base, size_t *mmap_len)
110946e4
RM
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)
17db6e8d
MF
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 }
110946e4
RM
109 }
110
111 *reserved = total;
112 *xflags = 0;
17db6e8d
MF
113 *mmap_base = NULL;
114 *mmap_len = 0;
110946e4
RM
115 return NULL;
116}
117
705341a9 118
a7b65cdc 119static void
531bafd8 120create_archive (const char *archivefname, struct locarhandle *ah)
a7b65cdc
UD
121{
122 int fd;
531bafd8 123 char fname[strlen (archivefname) + sizeof (".XXXXXX")];
a7b65cdc 124 struct locarhead head;
a7b65cdc
UD
125 size_t total;
126
531bafd8
UD
127 strcpy (stpcpy (fname, archivefname), ".XXXXXX");
128
a7b65cdc
UD
129 /* Create a temporary file in the correct directory. */
130 fd = mkstemp (fname);
131 if (fd == -1)
0e60d68e 132 error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname);
a7b65cdc
UD
133
134 /* Create the initial content of the archive. */
6055173a
JM
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));
a7b65cdc
UD
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
17db6e8d 176 size_t reserved, mmap_len;
110946e4 177 int xflags;
17db6e8d
MF
178 void *mmap_base;
179 void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base,
180 &mmap_len);
705341a9 181
a7b65cdc 182 /* Map the header and all the administration data structures. */
705341a9 183 p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
a7b65cdc
UD
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. */
b2bffca2 206 open_archive (ah, false);
a7b65cdc
UD
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
484c12fb 225 ah->fname = NULL;
a7b65cdc 226 ah->fd = fd;
17db6e8d
MF
227 ah->mmap_base = mmap_base;
228 ah->mmap_len = mmap_len;
a7b65cdc 229 ah->addr = p;
705341a9
UD
230 ah->mmaped = total;
231 ah->reserved = reserved;
a7b65cdc
UD
232}
233
ac8f8c53 234
31ff2aa3
RM
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. */
ac8f8c53
RM
237struct oldlocrecent
238{
239 unsigned int cnt;
240 struct locrecent *locrec;
241};
242
243static int
244oldlocrecentcmp (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;
31ff2aa3
RM
248 uint32_t start_a = -1, end_a = 0;
249 uint32_t start_b = -1, end_b = 0;
250 int cnt;
ac8f8c53 251
31ff2aa3
RM
252 for (cnt = 0; cnt < __LC_LAST; ++cnt)
253 if (cnt != LC_ALL)
254 {
6055173a
JM
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);
31ff2aa3
RM
259 }
260 assert (start_a != (uint32_t)-1);
261 assert (end_a != 0);
ac8f8c53 262
31ff2aa3
RM
263 for (cnt = 0; cnt < __LC_LAST; ++cnt)
264 if (cnt != LC_ALL)
265 {
6055173a
JM
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);
31ff2aa3
RM
270 }
271 assert (start_b != (uint32_t)-1);
272 assert (end_b != 0);
ac8f8c53 273
31ff2aa3
RM
274 if (start_a != start_b)
275 return (int)start_a - (int)start_b;
276 return (int)end_a - (int)end_b;
ac8f8c53
RM
277}
278
279
c14f245c 280/* forward decls for below */
cb09a2cd
RM
281static uint32_t add_locale (struct locarhandle *ah, const char *name,
282 locale_data_t data, bool replace);
c14f245c
UD
283static void add_alias (struct locarhandle *ah, const char *alias,
284 bool replace, const char *oldname,
285 uint32_t *locrec_offset_p);
a7b65cdc 286
705341a9
UD
287
288static bool
289file_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
17db6e8d 301 size_t start = ALIGN_DOWN (ah->mmaped, MAP_FIXED_ALIGNMENT);
6284c9f6
UD
302 void *p = mmap64 (ah->addr + start, st.st_size - start,
303 PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED,
304 ah->fd, start);
705341a9 305 if (p == MAP_FAILED)
6284c9f6
UD
306 {
307 ah->mmaped = start;
308 return false;
309 }
705341a9
UD
310
311 ah->mmaped = st.st_size;
312 return true;
313}
314
315
316static int
317compare_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)
f16491eb
CD
322 record_error (4, errno,
323 _("cannot read data from locale archive"));
705341a9
UD
324
325 int res = memcmp (p1, p2, size);
326 free (p2);
327 return res;
328}
329
330
a7b65cdc
UD
331static void
332enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
333{
334 struct stat64 st;
335 int fd;
a7b65cdc
UD
336 struct locarhead newhead;
337 size_t total;
ac8f8c53 338 unsigned int cnt, loccnt;
a7b65cdc 339 struct namehashent *oldnamehashtab;
a7b65cdc 340 struct locarhandle new_ah;
531bafd8
UD
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");
a7b65cdc
UD
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. */
c9edc889
UD
352 if (fstat64 (ah->fd, &st) != 0)
353 enomap:
a7b65cdc 354 error (EXIT_FAILURE, errno, _("cannot map locale archive file"));
c9edc889
UD
355
356 if (st.st_size < ah->reserved)
6284c9f6
UD
357 ah->addr = mmap64 (ah->addr, st.st_size, PROT_READ | PROT_WRITE,
358 MAP_SHARED | MAP_FIXED, ah->fd, 0);
c9edc889
UD
359 else
360 {
17db6e8d
MF
361 if (ah->mmap_base)
362 munmap (ah->mmap_base, ah->mmap_len);
363 else
364 munmap (ah->addr, ah->reserved);
c9edc889
UD
365 ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE,
366 MAP_SHARED, ah->fd, 0);
367 ah->reserved = st.st_size;
17db6e8d
MF
368 ah->mmap_base = NULL;
369 ah->mmap_len = 0;
6284c9f6 370 head = ah->addr;
c9edc889
UD
371 }
372 if (ah->addr == MAP_FAILED)
373 goto enomap;
705341a9 374 ah->mmaped = st.st_size;
a7b65cdc
UD
375
376 /* Create a temporary file in the correct directory. */
377 fd = mkstemp (fname);
378 if (fd == -1)
0e60d68e 379 error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname);
a7b65cdc
UD
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. */
6055173a
JM
386 SET (newhead.namehash_size,
387 MAX (next_prime (2 * GET (newhead.namehash_used)),
388 GET (newhead.namehash_size)));
ea08adbf 389 if (verbose)
69f6a804 390 printf ("name: size: %u, used: %d, new: size: %u\n",
6055173a
JM
391 GET (head->namehash_size),
392 GET (head->namehash_used), GET (newhead.namehash_size));
a7b65cdc 393
6055173a
JM
394 SET (newhead.string_offset, (GET (newhead.namehash_offset)
395 + (GET (newhead.namehash_size)
396 * sizeof (struct namehashent))));
5e922099
RM
397 /* Keep the string table size aligned to 4 bytes, so that
398 all the struct { uint32_t } types following are happy. */
6055173a
JM
399 SET (newhead.string_size, MAX ((2 * GET (newhead.string_used) + 3) & -4,
400 GET (newhead.string_size)));
a7b65cdc 401
6055173a
JM
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)));
a7b65cdc 406
6055173a
JM
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)));
a7b65cdc 413
6055173a
JM
414 total = (GET (newhead.sumhash_offset)
415 + GET (newhead.sumhash_size) * sizeof (struct sumhashent));
a7b65cdc
UD
416
417 /* The new file is empty now. */
6055173a
JM
418 SET (newhead.namehash_used, 0);
419 SET (newhead.string_used, 0);
420 SET (newhead.locrectab_used, 0);
421 SET (newhead.sumhash_used, 0);
a7b65cdc
UD
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
17db6e8d 439 size_t reserved, mmap_len;
110946e4 440 int xflags;
17db6e8d
MF
441 void *mmap_base;
442 void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base,
443 &mmap_len);
6284c9f6 444
a7b65cdc 445 /* Map the header and all the administration data structures. */
6284c9f6 446 p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0);
a7b65cdc
UD
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
705341a9 462 new_ah.mmaped = total;
17db6e8d
MF
463 new_ah.mmap_base = mmap_base;
464 new_ah.mmap_len = mmap_len;
a7b65cdc
UD
465 new_ah.addr = p;
466 new_ah.fd = fd;
6284c9f6 467 new_ah.reserved = reserved;
a7b65cdc
UD
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
6055173a 472 + GET (head->namehash_offset));
31ff2aa3
RM
473
474 /* Sort the old locrec table in order of data position. */
6055173a
JM
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)
a7b65cdc 478 {
ac8f8c53
RM
479 oldlocrecarray[loccnt].cnt = cnt;
480 oldlocrecarray[loccnt++].locrec
481 = (struct locrecent *) ((char *) ah->addr
6055173a 482 + GET (oldnamehashtab[cnt].locrec_offset));
ac8f8c53 483 }
ac8f8c53
RM
484 qsort (oldlocrecarray, loccnt, sizeof (struct oldlocrecent),
485 oldlocrecentcmp);
a7b65cdc 486
c14f245c 487 uint32_t last_locrec_offset = 0;
ac8f8c53
RM
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;
a7b65cdc 494
ac8f8c53
RM
495 for (idx = 0; idx < __LC_LAST; ++idx)
496 if (idx != LC_ALL)
497 {
6055173a 498 old_data[idx].size = GET (oldlocrec->record[idx].len);
ac8f8c53 499 old_data[idx].addr
6055173a 500 = ((char *) ah->addr + GET (oldlocrec->record[idx].offset));
a7b65cdc 501
ac8f8c53
RM
502 __md5_buffer (old_data[idx].addr, old_data[idx].size,
503 old_data[idx].sum);
504 }
a7b65cdc 505
c14f245c
UD
506 if (cnt > 0 && oldlocrecarray[cnt - 1].locrec == oldlocrec)
507 {
508 const char *oldname
509 = ((char *) ah->addr
6055173a
JM
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);
c14f245c
UD
518 continue;
519 }
520
521 last_locrec_offset =
6055173a
JM
522 add_locale
523 (&new_ah,
524 ((char *) ah->addr
525 + GET (oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset)),
526 old_data, 0);
c14f245c 527 if (last_locrec_offset == 0)
ac8f8c53
RM
528 error (EXIT_FAILURE, 0, _("cannot extend locale archive file"));
529 }
a7b65cdc
UD
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
556void
b2bffca2 557open_archive (struct locarhandle *ah, bool readonly)
a7b65cdc
UD
558{
559 struct stat64 st;
560 struct stat64 st2;
561 int fd;
562 struct locarhead head;
563 int retry = 0;
531bafd8 564 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
484c12fb 565 char default_fname[prefix_len + sizeof (ARCHIVE_NAME)];
4712799f 566 const char *archivefname = ah->fname;
531bafd8 567
484c12fb
CD
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)
4712799f
SP
573 memcpy (default_fname, output_prefix, prefix_len);
574 strcpy (default_fname + prefix_len, ARCHIVE_NAME);
484c12fb 575 }
a7b65cdc 576
10996249 577 while (1)
a7b65cdc 578 {
10996249
UD
579 /* Open the archive. We must have exclusive write access. */
580 fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
581 if (fd == -1)
a7b65cdc 582 {
484c12fb
CD
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)
b2bffca2 588 {
10996249 589 if (readonly)
b2bffca2 590 {
10996249
UD
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;
b2bffca2
UD
605 }
606 else
10996249
UD
607 error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
608 archivefname);
a7b65cdc 609 }
10996249
UD
610
611 if (fstat64 (fd, &st) < 0)
612 error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""),
a7b65cdc 613 archivefname);
a7b65cdc 614
10996249
UD
615 if (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1)
616 {
617 close (fd);
a7b65cdc 618
10996249
UD
619 if (retry++ < max_locarchive_open_retry)
620 {
621 struct timespec req;
a7b65cdc 622
10996249
UD
623 /* Wait for a bit. */
624 req.tv_sec = 0;
625 req.tv_nsec = 1000000 * (random () % 500 + 1);
626 (void) nanosleep (&req, NULL);
a7b65cdc 627
10996249
UD
628 continue;
629 }
a7b65cdc 630
10996249
UD
631 error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
632 archivefname);
a7b65cdc
UD
633 }
634
10996249
UD
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 }
a7b65cdc 645
10996249
UD
646 /* Leave the loop. */
647 break;
a7b65cdc
UD
648 }
649
650 /* Read the header. */
651 if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
b2bffca2
UD
652 {
653 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
654 error (EXIT_FAILURE, errno, _("cannot read archive header"));
655 }
a7b65cdc 656
cbab7f72
AJ
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
a7b65cdc 664 ah->fd = fd;
705341a9
UD
665 ah->mmaped = st.st_size;
666
17db6e8d 667 size_t reserved, mmap_len;
110946e4 668 int xflags;
17db6e8d
MF
669 void *mmap_base;
670 void *p = prepare_address_space (fd, st.st_size, &reserved, &xflags,
671 &mmap_base, &mmap_len);
a7b65cdc 672
59a7162b
UD
673 /* Map the entire file. We might need to compare the category data
674 in the file with the newly added data. */
705341a9
UD
675 ah->addr = mmap64 (p, st.st_size, PROT_READ | (readonly ? 0 : PROT_WRITE),
676 MAP_SHARED | xflags, fd, 0);
a7b65cdc 677 if (ah->addr == MAP_FAILED)
b2bffca2
UD
678 {
679 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
680 error (EXIT_FAILURE, errno, _("cannot map archive header"));
681 }
705341a9 682 ah->reserved = reserved;
17db6e8d
MF
683 ah->mmap_base = mmap_base;
684 ah->mmap_len = mmap_len;
a7b65cdc
UD
685}
686
687
688void
689close_archive (struct locarhandle *ah)
690{
b2bffca2
UD
691 if (ah->fd != -1)
692 {
17db6e8d
MF
693 if (ah->mmap_base)
694 munmap (ah->mmap_base, ah->mmap_len);
695 else
696 munmap (ah->addr, ah->reserved);
b2bffca2
UD
697 close (ah->fd);
698 }
a7b65cdc
UD
699}
700
cb09a2cd
RM
701#include "../../intl/explodename.c"
702#include "../../intl/l10nflist.c"
703
704static struct namehashent *
705insert_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
6055173a
JM
710 = (struct namehashent *) ((char *) ah->addr
711 + GET (head->namehash_offset));
cb09a2cd
RM
712 unsigned int insert_idx, idx, incr;
713
714 /* Hash value of the locale name. */
a3f9038c 715 uint32_t hval = archive_hashval (name, name_len);
cb09a2cd
RM
716
717 insert_idx = -1;
6055173a
JM
718 idx = hval % GET (head->namehash_size);
719 incr = 1 + hval % (GET (head->namehash_size) - 2);
cb09a2cd
RM
720
721 /* If the name_offset field is zero this means this is a
722 deleted entry and therefore no entry can be found. */
6055173a 723 while (GET (namehashtab[idx].name_offset) != 0)
cb09a2cd 724 {
6055173a
JM
725 if (GET (namehashtab[idx].hashval) == hval
726 && (strcmp (name,
727 (char *) ah->addr + GET (namehashtab[idx].name_offset))
728 == 0))
cb09a2cd
RM
729 {
730 /* Found the entry. */
6055173a 731 if (GET (namehashtab[idx].locrec_offset) != 0 && ! replace)
cb09a2cd
RM
732 {
733 if (! be_quiet)
734 error (0, 0, _("locale '%s' already exists"), name);
735 return NULL;
736 }
737
738 break;
739 }
740
6055173a 741 if (GET (namehashtab[idx].hashval) == hval && ! be_quiet)
ac8f8c53
RM
742 {
743 error (0, 0, "hash collision (%u) %s, %s",
6055173a
JM
744 hval, name,
745 (char *) ah->addr + GET (namehashtab[idx].name_offset));
ac8f8c53
RM
746 }
747
cb09a2cd 748 /* Remember the first place we can insert the new entry. */
6055173a 749 if (GET (namehashtab[idx].locrec_offset) == 0 && insert_idx == -1)
cb09a2cd
RM
750 insert_idx = idx;
751
752 idx += incr;
6055173a
JM
753 if (idx >= GET (head->namehash_size))
754 idx -= GET (head->namehash_size);
cb09a2cd
RM
755 }
756
757 /* Add as early as possible. */
758 if (insert_idx != -1)
759 idx = insert_idx;
760
6055173a 761 SET (namehashtab[idx].hashval, hval); /* no-op if replacing an old entry. */
cb09a2cd
RM
762 return &namehashtab[idx];
763}
764
765static void
766add_alias (struct locarhandle *ah, const char *alias, bool replace,
bd6daf3b 767 const char *oldname, uint32_t *locrec_offset_p)
cb09a2cd 768{
bd6daf3b 769 uint32_t locrec_offset = *locrec_offset_p;
cb09a2cd
RM
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
6055173a 777 if (GET (namehashent->name_offset) == 0)
cb09a2cd
RM
778 {
779 /* We are adding a new hash entry for this alias.
780 Determine whether we have to resize the file. */
6055173a
JM
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)))
cb09a2cd
RM
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);
6055173a
JM
791 assert (GET (namehashent->name_offset) != 0);
792 assert (GET (namehashent->locrec_offset) != 0);
793 *locrec_offset_p = GET (namehashent->locrec_offset);
cb09a2cd
RM
794
795 /* Tail call to try the whole thing again. */
bd6daf3b 796 add_alias (ah, alias, replace, oldname, locrec_offset_p);
cb09a2cd
RM
797 return;
798 }
799
800 /* Add the name string. */
6055173a 801 memcpy (ah->addr + GET (head->string_offset) + GET (head->string_used),
cb09a2cd 802 alias, name_len + 1);
6055173a
JM
803 SET (namehashent->name_offset,
804 GET (head->string_offset) + GET (head->string_used));
805 INC (head->string_used, name_len + 1);
cb09a2cd 806
6055173a 807 INC (head->namehash_used, 1);
cb09a2cd
RM
808 }
809
6055173a 810 if (GET (namehashent->locrec_offset) != 0)
cb09a2cd
RM
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
6055173a
JM
816 + GET (namehashent->locrec_offset));
817 INC (locrecent->refs, -1);
cb09a2cd
RM
818 }
819
820 /* Point this entry at the locrecent installed for the main name. */
6055173a 821 SET (namehashent->locrec_offset, locrec_offset);
cb09a2cd
RM
822}
823
ac8f8c53
RM
824static int /* qsort comparator used below */
825cmpcategorysize (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}
a7b65cdc
UD
834
835/* Check the content of the archive for duplicates. Add the content
cb09a2cd
RM
836 of the files if necessary. Returns the locrec_offset. */
837static uint32_t
838add_locale (struct locarhandle *ah,
839 const char *name, locale_data_t data, bool replace)
a7b65cdc
UD
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;
ac8f8c53 849 unsigned int cnt, idx;
a7b65cdc 850 struct locarhead *head;
a7b65cdc
UD
851 struct namehashent *namehashent;
852 unsigned int incr;
853 struct locrecent *locrecent;
ac8f8c53
RM
854 off64_t lastoffset;
855 char *ptr;
856 struct locale_category_data *size_order[__LC_LAST];
d3d23756
JM
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;
ac8f8c53 863 int small_mask;
a7b65cdc
UD
864
865 head = ah->addr;
866 sumhashtab = (struct sumhashent *) ((char *) ah->addr
6055173a 867 + GET (head->sumhash_offset));
a7b65cdc 868
ac8f8c53
RM
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);
a7b65cdc
UD
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)
ac8f8c53 916 if (small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
a7b65cdc 917 {
a7b65cdc
UD
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. */
a3f9038c 923 hval = archive_hashval (data[cnt].sum, 16);
a7b65cdc 924
6055173a
JM
925 idx = hval % GET (head->sumhash_size);
926 incr = 1 + hval % (GET (head->sumhash_size) - 2);
a7b65cdc 927
6055173a 928 while (GET (sumhashtab[idx].file_offset) != 0)
a7b65cdc
UD
929 {
930 if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
931 {
59a7162b
UD
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. */
6055173a
JM
938 locrecent
939 = (struct locrecent *) ((char *) ah->addr
940 + GET (head->locrectab_offset));
59a7162b 941 size_t iloc;
6055173a
JM
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)))
59a7162b
UD
946 break;
947
6055173a
JM
948 if (iloc != GET (head->locrectab_used)
949 && data[cnt].size == GET (locrecent[iloc].record[cnt].len)
705341a9
UD
950 /* We have to compare the content. Either we can
951 have the data mmaped or we have to read from
952 the file. */
6055173a
JM
953 && (file_data_available_p
954 (ah, GET (sumhashtab[idx].file_offset),
955 data[cnt].size)
705341a9
UD
956 ? memcmp (data[cnt].addr,
957 (char *) ah->addr
6055173a 958 + GET (sumhashtab[idx].file_offset),
705341a9
UD
959 data[cnt].size) == 0
960 : compare_from_file (ah, data[cnt].addr,
6055173a 961 GET (sumhashtab[idx].file_offset),
705341a9 962 data[cnt].size) == 0))
59a7162b
UD
963 {
964 /* Found it. */
6055173a 965 file_offsets[cnt] = GET (sumhashtab[idx].file_offset);
59a7162b
UD
966 --num_new_offsets;
967 break;
968 }
a7b65cdc
UD
969 }
970
971 idx += incr;
6055173a
JM
972 if (idx >= GET (head->sumhash_size))
973 idx -= GET (head->sumhash_size);
a7b65cdc
UD
974 }
975 }
976
cb09a2cd
RM
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;
a7b65cdc
UD
981
982 /* Determine whether we have to resize the file. */
6055173a
JM
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)))))
a7b65cdc
UD
991 {
992 /* The current archive is not large enough. */
993 enlarge_archive (ah, head);
cb09a2cd 994 return add_locale (ah, name, data, replace);
a7b65cdc
UD
995 }
996
997 /* Add the locale data which is not yet in the archive. */
ac8f8c53
RM
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)
a7b65cdc
UD
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
ac8f8c53
RM
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
a7b65cdc
UD
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;
ac8f8c53 1043 lastoffset = lastpos + data[cnt].size;
a7b65cdc
UD
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. */
a3f9038c 1051 md5hval = archive_hashval (data[cnt].sum, 16);
a7b65cdc 1052
6055173a
JM
1053 idx = md5hval % GET (head->sumhash_size);
1054 incr = 1 + md5hval % (GET (head->sumhash_size) - 2);
a7b65cdc 1055
6055173a 1056 while (GET (sumhashtab[idx].file_offset) != 0)
a7b65cdc
UD
1057 {
1058 idx += incr;
6055173a
JM
1059 if (idx >= GET (head->sumhash_size))
1060 idx -= GET (head->sumhash_size);
a7b65cdc
UD
1061 }
1062
1063 memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
6055173a 1064 SET (sumhashtab[idx].file_offset, file_offsets[cnt]);
a7b65cdc 1065
6055173a 1066 INC (head->sumhash_used, 1);
a7b65cdc
UD
1067 }
1068
ac8f8c53
RM
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
6055173a 1077 if (GET (namehashent->name_offset) == 0)
a7b65cdc
UD
1078 {
1079 /* Add the name string. */
6055173a
JM
1080 memcpy ((char *) ah->addr + GET (head->string_offset)
1081 + GET (head->string_used),
a7b65cdc 1082 name, name_len + 1);
6055173a
JM
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);
cb09a2cd 1087 }
a7b65cdc 1088
6055173a 1089 if (GET (namehashent->locrec_offset == 0))
cb09a2cd 1090 {
a7b65cdc 1091 /* Allocate a name location record. */
6055173a
JM
1092 SET (namehashent->locrec_offset, (GET (head->locrectab_offset)
1093 + (GET (head->locrectab_used)
1094 * sizeof (struct locrecent))));
1095 INC (head->locrectab_used, 1);
cb09a2cd 1096 locrecent = (struct locrecent *) ((char *) ah->addr
6055173a
JM
1097 + GET (namehashent->locrec_offset));
1098 SET (locrecent->refs, 1);
a7b65cdc 1099 }
cb09a2cd
RM
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. */
a7b65cdc 1104
cb09a2cd 1105 locrecent = (struct locrecent *) ((char *) ah->addr
6055173a
JM
1106 + GET (namehashent->locrec_offset));
1107 if (GET (locrecent->refs) > 1)
cb09a2cd 1108 {
6055173a
JM
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);
cb09a2cd
RM
1118 }
1119 }
a7b65cdc
UD
1120
1121 /* Fill in the table with the locations of the locale data. */
a7b65cdc 1122 for (cnt = 0; cnt < __LC_LAST; ++cnt)
ac8f8c53 1123 {
6055173a
JM
1124 SET (locrecent->record[cnt].offset, file_offsets[cnt]);
1125 SET (locrecent->record[cnt].len, data[cnt].size);
ac8f8c53 1126 }
a7b65cdc 1127
6055173a 1128 return GET (namehashent->locrec_offset);
cb09a2cd
RM
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. */
1135int
9dd346ff
JM
1136add_locale_to_archive (struct locarhandle *ah, const char *name,
1137 locale_data_t data, bool replace)
cb09a2cd
RM
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);
4f031072
UD
1151 if (mask == -1)
1152 return -1;
cb09a2cd
RM
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. */
d4ad86a0
FB
1157 if (asprintf (&normalized_name, "%s%s%s.%s%s%s",
1158 language, territory == NULL ? "" : "_", territory ?: "",
1159 normalized_codeset,
1160 modifier == NULL ? "" : "@", modifier ?: "") < 0)
1161 {
1162 free ((char *) normalized_codeset);
1163 return -1;
1164 }
cb09a2cd
RM
1165
1166 /* This call does the main work. */
1167 locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
cb09a2cd
RM
1168 if (locrec_offset == 0)
1169 {
bd6daf3b 1170 free (normalized_name);
cb09a2cd
RM
1171 if (mask & XPG_NORM_CODESET)
1172 free ((char *) normalized_codeset);
1173 return -1;
1174 }
1175
1176 if ((mask & XPG_CODESET) == 0)
1177 {
1178 /* This name lacks a codeset, so determine the locale's codeset and
1179 add an alias for its name with normalized codeset appended. */
1180
1181 const struct
1182 {
1183 unsigned int magic;
1184 unsigned int nstrings;
1185 unsigned int strindex[0];
1186 } *filedata = data[LC_CTYPE].addr;
1187 codeset = (char *) filedata
6055173a
JM
1188 + maybe_swap_uint32 (filedata->strindex[_NL_ITEM_INDEX
1189 (_NL_CTYPE_CODESET_NAME)]);
bd6daf3b 1190 char *normalized_codeset_name = NULL;
cb09a2cd
RM
1191
1192 normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
1193 mask |= XPG_NORM_CODESET;
1194
d4ad86a0
FB
1195 if (asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
1196 language, territory == NULL ? "" : "_", territory ?: "",
1197 normalized_codeset,
1198 modifier == NULL ? "" : "@", modifier ?: "") < 0)
1199 {
1200 free ((char *) normalized_codeset);
1201 return -1;
1202 }
cb09a2cd 1203
bd6daf3b
RM
1204 add_alias (ah, normalized_codeset_name, replace,
1205 normalized_name ?: name, &locrec_offset);
1206 free (normalized_codeset_name);
cb09a2cd
RM
1207 }
1208
1209 /* Now read the locale.alias files looking for lines whose
1210 right hand side matches our name after normalization. */
4f031072 1211 int result = 0;
cb09a2cd
RM
1212 if (alias_file != NULL)
1213 {
1214 FILE *fp;
2e2dc1a5 1215 fp = fopen (alias_file, "rm");
cb09a2cd
RM
1216 if (fp == NULL)
1217 error (1, errno, _("locale alias file `%s' not found"),
1218 alias_file);
1219
1220 /* No threads present. */
1221 __fsetlocking (fp, FSETLOCKING_BYCALLER);
1222
1223 while (! feof_unlocked (fp))
1224 {
1225 /* It is a reasonable approach to use a fix buffer here
1226 because
1227 a) we are only interested in the first two fields
1228 b) these fields must be usable as file names and so must
1229 not be that long */
1230 char buf[BUFSIZ];
1231 char *alias;
1232 char *value;
1233 char *cp;
1234
1235 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1236 /* EOF reached. */
1237 break;
1238
1239 cp = buf;
1240 /* Ignore leading white space. */
1241 while (isspace (cp[0]) && cp[0] != '\n')
1242 ++cp;
1243
1244 /* A leading '#' signals a comment line. */
1245 if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
1246 {
1247 alias = cp++;
1248 while (cp[0] != '\0' && !isspace (cp[0]))
1249 ++cp;
1250 /* Terminate alias name. */
1251 if (cp[0] != '\0')
1252 *cp++ = '\0';
1253
1254 /* Now look for the beginning of the value. */
1255 while (isspace (cp[0]))
1256 ++cp;
1257
1258 if (cp[0] != '\0')
1259 {
1260 value = cp++;
1261 while (cp[0] != '\0' && !isspace (cp[0]))
1262 ++cp;
1263 /* Terminate value. */
1264 if (cp[0] == '\n')
1265 {
1266 /* This has to be done to make the following
1267 test for the end of line possible. We are
1268 looking for the terminating '\n' which do not
1269 overwrite here. */
1270 *cp++ = '\0';
1271 *cp = '\n';
1272 }
1273 else if (cp[0] != '\0')
1274 *cp++ = '\0';
1275
1276 /* Does this alias refer to our locale? We will
1277 normalize the right hand side and compare the
1278 elements of the normalized form. */
1279 {
1280 const char *rhs_language;
1281 const char *rhs_modifier;
1282 const char *rhs_territory;
1283 const char *rhs_codeset;
1284 const char *rhs_normalized_codeset;
1285 int rhs_mask = _nl_explode_name (value,
1286 &rhs_language,
1287 &rhs_modifier,
1288 &rhs_territory,
1289 &rhs_codeset,
1290 &rhs_normalized_codeset);
13f1ab36 1291 if (rhs_mask == -1)
4f031072
UD
1292 {
1293 result = -1;
1294 goto out;
1295 }
cb09a2cd
RM
1296 if (!strcmp (language, rhs_language)
1297 && ((rhs_mask & XPG_CODESET)
1298 /* He has a codeset, it must match normalized. */
1299 ? !strcmp ((mask & XPG_NORM_CODESET)
1300 ? normalized_codeset : codeset,
1301 (rhs_mask & XPG_NORM_CODESET)
1302 ? rhs_normalized_codeset : rhs_codeset)
1303 /* He has no codeset, we must also have none. */
1304 : (mask & XPG_CODESET) == 0)
1305 /* Codeset (or lack thereof) matches. */
1306 && !strcmp (territory ?: "", rhs_territory ?: "")
1307 && !strcmp (modifier ?: "", rhs_modifier ?: ""))
1308 /* We have a winner. */
1309 add_alias (ah, alias, replace,
bd6daf3b 1310 normalized_name ?: name, &locrec_offset);
cb09a2cd
RM
1311 if (rhs_mask & XPG_NORM_CODESET)
1312 free ((char *) rhs_normalized_codeset);
1313 }
1314 }
1315 }
1316
1317 /* Possibly not the whole line fits into the buffer.
1318 Ignore the rest of the line. */
1319 while (strchr (cp, '\n') == NULL)
1320 {
1321 cp = buf;
1322 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1323 /* Make sure the inner loop will be left. The outer
1324 loop will exit at the `feof' test. */
1325 *cp = '\n';
1326 }
1327 }
a7b65cdc 1328
4f031072 1329 out:
cb09a2cd
RM
1330 fclose (fp);
1331 }
a7b65cdc 1332
bd6daf3b
RM
1333 free (normalized_name);
1334
cb09a2cd
RM
1335 if (mask & XPG_NORM_CODESET)
1336 free ((char *) normalized_codeset);
a7b65cdc 1337
4f031072 1338 return result;
a7b65cdc
UD
1339}
1340
1341
1342int
9dd346ff 1343add_locales_to_archive (size_t nlist, char *list[], bool replace)
a7b65cdc
UD
1344{
1345 struct locarhandle ah;
1346 int result = 0;
1347
1348 /* Open the archive. This call never returns if we cannot
1349 successfully open the archive. */
484c12fb 1350 ah.fname = NULL;
b2bffca2 1351 open_archive (&ah, false);
a7b65cdc
UD
1352
1353 while (nlist-- > 0)
1354 {
1355 const char *fname = *list++;
1356 size_t fnamelen = strlen (fname);
1357 struct stat64 st;
1358 DIR *dirp;
1359 struct dirent64 *d;
1360 int seen;
1361 locale_data_t data;
1362 int cnt;
1363
1364 if (! be_quiet)
1365 printf (_("Adding %s\n"), fname);
1366
1367 /* First see whether this really is a directory and whether it
1368 contains all the require locale category files. */
1369 if (stat64 (fname, &st) < 0)
1370 {
1371 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
1372 strerror (errno));
1373 continue;
1374 }
1375 if (!S_ISDIR (st.st_mode))
1376 {
1377 error (0, 0, _("\"%s\" is no directory; ignored"), fname);
1378 continue;
1379 }
1380
1381 dirp = opendir (fname);
1382 if (dirp == NULL)
1383 {
1384 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1385 fname, strerror (errno));
1386 continue;
1387 }
1388
1389 seen = 0;
1390 while ((d = readdir64 (dirp)) != NULL)
1391 {
1392 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1393 if (cnt != LC_ALL)
1394 if (strcmp (d->d_name, locnames[cnt]) == 0)
1395 {
1396 unsigned char d_type;
1397
1398 /* We have an object of the required name. If it's
1399 a directory we have to look at a file with the
1400 prefix "SYS_". Otherwise we have found what we
1401 are looking for. */
a7b65cdc
UD
1402 d_type = d->d_type;
1403
1404 if (d_type != DT_REG)
a7b65cdc
UD
1405 {
1406 char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
1407
ea89d5bb 1408 if (d_type == DT_UNKNOWN || d_type == DT_LNK)
a7b65cdc
UD
1409 {
1410 strcpy (stpcpy (stpcpy (fullname, fname), "/"),
1411 d->d_name);
1412
1413 if (stat64 (fullname, &st) == -1)
1414 /* We cannot stat the file, ignore it. */
1415 break;
1416
1417 d_type = IFTODT (st.st_mode);
1418 }
1419
1420 if (d_type == DT_DIR)
1421 {
1422 /* We have to do more tests. The file is a
1423 directory and it therefore must contain a
1424 regular file with the same name except a
1425 "SYS_" prefix. */
531bafd8
UD
1426 char *t = stpcpy (stpcpy (fullname, fname), "/");
1427 strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
a7b65cdc
UD
1428 d->d_name);
1429
1430 if (stat64 (fullname, &st) == -1)
1431 /* There is no SYS_* file or we cannot
1432 access it. */
1433 break;
1434
1435 d_type = IFTODT (st.st_mode);
1436 }
1437 }
1438
1439 /* If we found a regular file (eventually after
1440 following a symlink) we are successful. */
1441 if (d_type == DT_REG)
1442 ++seen;
1443 break;
1444 }
1445 }
1446
1447 closedir (dirp);
1448
1449 if (seen != __LC_LAST - 1)
1450 {
1451 /* We don't have all locale category files. Ignore the name. */
1452 error (0, 0, _("incomplete set of locale files in \"%s\""),
1453 fname);
1454 continue;
1455 }
1456
1457 /* Add the files to the archive. To do this we first compute
1458 sizes and the MD5 sums of all the files. */
1459 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1460 if (cnt != LC_ALL)
1461 {
1462 char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
1463 int fd;
1464
1465 strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
1466 fd = open64 (fullname, O_RDONLY);
1467 if (fd == -1 || fstat64 (fd, &st) == -1)
1468 {
1469 /* Cannot read the file. */
1470 if (fd != -1)
1471 close (fd);
1472 break;
1473 }
1474
1475 if (S_ISDIR (st.st_mode))
1476 {
531bafd8 1477 char *t;
a7b65cdc 1478 close (fd);
531bafd8
UD
1479 t = stpcpy (stpcpy (fullname, fname), "/");
1480 strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
a7b65cdc
UD
1481 locnames[cnt]);
1482
1483 fd = open64 (fullname, O_RDONLY);
1484 if (fd == -1 || fstat64 (fd, &st) == -1
1485 || !S_ISREG (st.st_mode))
1486 {
1487 if (fd != -1)
1488 close (fd);
1489 break;
1490 }
1491 }
1492
1493 /* Map the file. */
1494 data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
1495 fd, 0);
1496 if (data[cnt].addr == MAP_FAILED)
1497 {
1498 /* Cannot map it. */
1499 close (fd);
1500 break;
1501 }
1502
1503 data[cnt].size = st.st_size;
1504 __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
1505
1506 /* We don't need the file descriptor anymore. */
1507 close (fd);
1508 }
1509
1510 if (cnt != __LC_LAST)
1511 {
1512 while (cnt-- > 0)
1513 if (cnt != LC_ALL)
1514 munmap (data[cnt].addr, data[cnt].size);
1515
1516 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);
1517
1518 continue;
1519 }
1520
1521 result |= add_locale_to_archive (&ah, basename (fname), data, replace);
1522
1523 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1524 if (cnt != LC_ALL)
1525 munmap (data[cnt].addr, data[cnt].size);
1526 }
1527
1528 /* We are done. */
1529 close_archive (&ah);
1530
1531 return result;
1532}
1533
1534
1535int
9dd346ff 1536delete_locales_from_archive (size_t nlist, char *list[])
a7b65cdc
UD
1537{
1538 struct locarhandle ah;
1539 struct locarhead *head;
1540 struct namehashent *namehashtab;
1541
1542 /* Open the archive. This call never returns if we cannot
1543 successfully open the archive. */
484c12fb 1544 ah.fname = NULL;
b2bffca2 1545 open_archive (&ah, false);
a7b65cdc
UD
1546
1547 head = ah.addr;
1548 namehashtab = (struct namehashent *) ((char *) ah.addr
6055173a 1549 + GET (head->namehash_offset));
a7b65cdc
UD
1550
1551 while (nlist-- > 0)
1552 {
1553 const char *locname = *list++;
1554 uint32_t hval;
1555 unsigned int idx;
1556 unsigned int incr;
1557
1558 /* Search for this locale in the archive. */
a3f9038c 1559 hval = archive_hashval (locname, strlen (locname));
a7b65cdc 1560
6055173a
JM
1561 idx = hval % GET (head->namehash_size);
1562 incr = 1 + hval % (GET (head->namehash_size) - 2);
a7b65cdc
UD
1563
1564 /* If the name_offset field is zero this means this is no
1565 deleted entry and therefore no entry can be found. */
6055173a 1566 while (GET (namehashtab[idx].name_offset) != 0)
a7b65cdc 1567 {
6055173a 1568 if (GET (namehashtab[idx].hashval) == hval
a7b65cdc 1569 && (strcmp (locname,
6055173a
JM
1570 ((char *) ah.addr
1571 + GET (namehashtab[idx].name_offset)))
a7b65cdc
UD
1572 == 0))
1573 {
1574 /* Found the entry. Now mark it as removed by zero-ing
1575 the reference to the locale record. */
6055173a 1576 SET (namehashtab[idx].locrec_offset, 0);
a7b65cdc
UD
1577 break;
1578 }
1579
1580 idx += incr;
6055173a
JM
1581 if (idx >= GET (head->namehash_size))
1582 idx -= GET (head->namehash_size);
a7b65cdc
UD
1583 }
1584
6055173a 1585 if (GET (namehashtab[idx].name_offset) == 0 && ! be_quiet)
a7b65cdc
UD
1586 error (0, 0, _("locale \"%s\" not in archive"), locname);
1587 }
1588
1589 close_archive (&ah);
1590
1591 return 0;
1592}
1593
1594
b2bffca2
UD
1595struct nameent
1596{
1597 char *name;
1598 uint32_t locrec_offset;
1599};
1600
1601
1602struct dataent
1603{
1604 const unsigned char *sum;
1605 uint32_t file_offset;
1606 uint32_t nlink;
1607};
1608
1609
1610static int
1611nameentcmp (const void *a, const void *b)
1612{
1613 return strcmp (((const struct nameent *) a)->name,
1614 ((const struct nameent *) b)->name);
1615}
1616
1617
a7b65cdc 1618static int
b2bffca2 1619dataentcmp (const void *a, const void *b)
a7b65cdc 1620{
b2bffca2
UD
1621 if (((const struct dataent *) a)->file_offset
1622 < ((const struct dataent *) b)->file_offset)
1623 return -1;
1624
1625 if (((const struct dataent *) a)->file_offset
1626 > ((const struct dataent *) b)->file_offset)
1627 return 1;
1628
1629 return 0;
a7b65cdc
UD
1630}
1631
1632
1633void
484c12fb 1634show_archive_content (const char *fname, int verbose)
a7b65cdc
UD
1635{
1636 struct locarhandle ah;
1637 struct locarhead *head;
1638 struct namehashent *namehashtab;
b2bffca2 1639 struct nameent *names;
638bb1f3 1640 size_t cnt, used;
a7b65cdc
UD
1641
1642 /* Open the archive. This call never returns if we cannot
1643 successfully open the archive. */
484c12fb 1644 ah.fname = fname;
b2bffca2 1645 open_archive (&ah, true);
a7b65cdc
UD
1646
1647 head = ah.addr;
1648
6055173a 1649 names = (struct nameent *) xmalloc (GET (head->namehash_used)
b2bffca2 1650 * sizeof (struct nameent));
a7b65cdc
UD
1651
1652 namehashtab = (struct namehashent *) ((char *) ah.addr
6055173a
JM
1653 + GET (head->namehash_offset));
1654 for (cnt = used = 0; cnt < GET (head->namehash_size); ++cnt)
1655 if (GET (namehashtab[cnt].locrec_offset) != 0)
a7b65cdc 1656 {
6055173a
JM
1657 assert (used < GET (head->namehash_used));
1658 names[used].name = ah.addr + GET (namehashtab[cnt].name_offset);
1659 names[used++].locrec_offset = GET (namehashtab[cnt].locrec_offset);
a7b65cdc
UD
1660 }
1661
1662 /* Sort the names. */
b2bffca2
UD
1663 qsort (names, used, sizeof (struct nameent), nameentcmp);
1664
1665 if (verbose)
1666 {
1667 struct dataent *files;
1668 struct sumhashent *sumhashtab;
1669 int sumused;
1670
6055173a 1671 files = (struct dataent *) xmalloc (GET (head->sumhash_used)
07358add 1672 * sizeof (struct dataent));
b2bffca2
UD
1673
1674 sumhashtab = (struct sumhashent *) ((char *) ah.addr
6055173a
JM
1675 + GET (head->sumhash_offset));
1676 for (cnt = sumused = 0; cnt < GET (head->sumhash_size); ++cnt)
1677 if (GET (sumhashtab[cnt].file_offset) != 0)
b2bffca2 1678 {
6055173a 1679 assert (sumused < GET (head->sumhash_used));
b2bffca2 1680 files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
6055173a 1681 files[sumused].file_offset = GET (sumhashtab[cnt].file_offset);
b2bffca2
UD
1682 files[sumused++].nlink = 0;
1683 }
1684
1685 /* Sort by file locations. */
1686 qsort (files, sumused, sizeof (struct dataent), dataentcmp);
1687
1688 /* Compute nlink fields. */
1689 for (cnt = 0; cnt < used; ++cnt)
1690 {
1691 struct locrecent *locrec;
1692 int idx;
1693
1694 locrec = (struct locrecent *) ((char *) ah.addr
1695 + names[cnt].locrec_offset);
1696 for (idx = 0; idx < __LC_LAST; ++idx)
6055173a 1697 if (GET (locrec->record[LC_ALL].offset) != 0
ac8f8c53 1698 ? (idx == LC_ALL
6055173a
JM
1699 || (GET (locrec->record[idx].offset)
1700 < GET (locrec->record[LC_ALL].offset))
1701 || ((GET (locrec->record[idx].offset)
1702 + GET (locrec->record[idx].len))
1703 > (GET (locrec->record[LC_ALL].offset)
1704 + GET (locrec->record[LC_ALL].len))))
ac8f8c53 1705 : idx != LC_ALL)
b2bffca2
UD
1706 {
1707 struct dataent *data, dataent;
1708
6055173a 1709 dataent.file_offset = GET (locrec->record[idx].offset);
b2bffca2
UD
1710 data = (struct dataent *) bsearch (&dataent, files, sumused,
1711 sizeof (struct dataent),
1712 dataentcmp);
1713 assert (data != NULL);
1714 ++data->nlink;
1715 }
1716 }
a7b65cdc 1717
b2bffca2
UD
1718 /* Print it. */
1719 for (cnt = 0; cnt < used; ++cnt)
1720 {
1721 struct locrecent *locrec;
1722 int idx, i;
1723
1724 locrec = (struct locrecent *) ((char *) ah.addr
1725 + names[cnt].locrec_offset);
1726 for (idx = 0; idx < __LC_LAST; ++idx)
1727 if (idx != LC_ALL)
1728 {
1729 struct dataent *data, dataent;
1730
6055173a
JM
1731 dataent.file_offset = GET (locrec->record[idx].offset);
1732 if (GET (locrec->record[LC_ALL].offset) != 0
1733 && (dataent.file_offset
1734 >= GET (locrec->record[LC_ALL].offset))
1735 && (dataent.file_offset + GET (locrec->record[idx].len)
1736 <= (GET (locrec->record[LC_ALL].offset)
1737 + GET (locrec->record[LC_ALL].len))))
1738 dataent.file_offset = GET (locrec->record[LC_ALL].offset);
ac8f8c53 1739
b2bffca2
UD
1740 data = (struct dataent *) bsearch (&dataent, files, sumused,
1741 sizeof (struct dataent),
1742 dataentcmp);
ac8f8c53 1743 printf ("%6d %7x %3d%c ",
6055173a
JM
1744 GET (locrec->record[idx].len),
1745 GET (locrec->record[idx].offset),
ac8f8c53 1746 data->nlink,
6055173a
JM
1747 (dataent.file_offset
1748 == GET (locrec->record[LC_ALL].offset))
ac8f8c53 1749 ? '+' : ' ');
b2bffca2
UD
1750 for (i = 0; i < 16; i += 4)
1751 printf ("%02x%02x%02x%02x",
1752 data->sum[i], data->sum[i + 1],
1753 data->sum[i + 2], data->sum[i + 3]);
1754 printf (" %s/%s\n", names[cnt].name,
1755 idx == LC_MESSAGES ? "LC_MESSAGES/SYS_LC_MESSAGES"
1756 : locnames[idx]);
1757 }
1758 }
23171016 1759 free (files);
b2bffca2
UD
1760 }
1761 else
1762 for (cnt = 0; cnt < used; ++cnt)
1763 puts (names[cnt].name);
a7b65cdc
UD
1764
1765 close_archive (&ah);
1766
1767 exit (EXIT_SUCCESS);
1768}