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