]> git.ipfire.org Git - thirdparty/glibc.git/blame - locale/programs/locarchive.c
Remove locale file dependence on int32_t alignment.
[thirdparty/glibc.git] / locale / programs / locarchive.c
CommitLineData
568035b7 1/* Copyright (C) 2002-2013 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
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)
322 WITH_CUR_LOCALE (error (4, errno,
323 _("cannot read data from locale archive")));
324
325 int res = memcmp (p1, p2, size);
326 free (p2);
327 return res;
328}
329
330
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
UD
564 size_t prefix_len = output_prefix ? strlen (output_prefix) : 0;
565 char archivefname[prefix_len + sizeof (ARCHIVE_NAME)];
566
567 if (output_prefix)
568 memcpy (archivefname, output_prefix, prefix_len);
569 strcpy (archivefname + prefix_len, ARCHIVE_NAME);
a7b65cdc 570
10996249 571 while (1)
a7b65cdc 572 {
10996249
UD
573 /* Open the archive. We must have exclusive write access. */
574 fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR);
575 if (fd == -1)
a7b65cdc 576 {
10996249
UD
577 /* Maybe the file does not yet exist. */
578 if (errno == ENOENT)
b2bffca2 579 {
10996249 580 if (readonly)
b2bffca2 581 {
10996249
UD
582 static const struct locarhead nullhead =
583 {
584 .namehash_used = 0,
585 .namehash_offset = 0,
586 .namehash_size = 0
587 };
588
589 ah->addr = (void *) &nullhead;
590 ah->fd = -1;
591 }
592 else
593 create_archive (archivefname, ah);
594
595 return;
b2bffca2
UD
596 }
597 else
10996249
UD
598 error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""),
599 archivefname);
a7b65cdc 600 }
10996249
UD
601
602 if (fstat64 (fd, &st) < 0)
603 error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""),
a7b65cdc 604 archivefname);
a7b65cdc 605
10996249
UD
606 if (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1)
607 {
608 close (fd);
a7b65cdc 609
10996249
UD
610 if (retry++ < max_locarchive_open_retry)
611 {
612 struct timespec req;
a7b65cdc 613
10996249
UD
614 /* Wait for a bit. */
615 req.tv_sec = 0;
616 req.tv_nsec = 1000000 * (random () % 500 + 1);
617 (void) nanosleep (&req, NULL);
a7b65cdc 618
10996249
UD
619 continue;
620 }
a7b65cdc 621
10996249
UD
622 error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""),
623 archivefname);
a7b65cdc
UD
624 }
625
10996249
UD
626 /* One more check. Maybe another process replaced the archive file
627 with a new, larger one since we opened the file. */
628 if (stat64 (archivefname, &st2) == -1
629 || st.st_dev != st2.st_dev
630 || st.st_ino != st2.st_ino)
631 {
632 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
633 close (fd);
634 continue;
635 }
a7b65cdc 636
10996249
UD
637 /* Leave the loop. */
638 break;
a7b65cdc
UD
639 }
640
641 /* Read the header. */
642 if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
b2bffca2
UD
643 {
644 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
645 error (EXIT_FAILURE, errno, _("cannot read archive header"));
646 }
a7b65cdc
UD
647
648 ah->fd = fd;
705341a9
UD
649 ah->mmaped = st.st_size;
650
17db6e8d 651 size_t reserved, mmap_len;
110946e4 652 int xflags;
17db6e8d
MF
653 void *mmap_base;
654 void *p = prepare_address_space (fd, st.st_size, &reserved, &xflags,
655 &mmap_base, &mmap_len);
a7b65cdc 656
59a7162b
UD
657 /* Map the entire file. We might need to compare the category data
658 in the file with the newly added data. */
705341a9
UD
659 ah->addr = mmap64 (p, st.st_size, PROT_READ | (readonly ? 0 : PROT_WRITE),
660 MAP_SHARED | xflags, fd, 0);
a7b65cdc 661 if (ah->addr == MAP_FAILED)
b2bffca2
UD
662 {
663 (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead));
664 error (EXIT_FAILURE, errno, _("cannot map archive header"));
665 }
705341a9 666 ah->reserved = reserved;
17db6e8d
MF
667 ah->mmap_base = mmap_base;
668 ah->mmap_len = mmap_len;
a7b65cdc
UD
669}
670
671
672void
673close_archive (struct locarhandle *ah)
674{
b2bffca2
UD
675 if (ah->fd != -1)
676 {
17db6e8d
MF
677 if (ah->mmap_base)
678 munmap (ah->mmap_base, ah->mmap_len);
679 else
680 munmap (ah->addr, ah->reserved);
b2bffca2
UD
681 close (ah->fd);
682 }
a7b65cdc
UD
683}
684
cb09a2cd
RM
685#include "../../intl/explodename.c"
686#include "../../intl/l10nflist.c"
687
688static struct namehashent *
689insert_name (struct locarhandle *ah,
690 const char *name, size_t name_len, bool replace)
691{
692 const struct locarhead *const head = ah->addr;
693 struct namehashent *namehashtab
6055173a
JM
694 = (struct namehashent *) ((char *) ah->addr
695 + GET (head->namehash_offset));
cb09a2cd
RM
696 unsigned int insert_idx, idx, incr;
697
698 /* Hash value of the locale name. */
a3f9038c 699 uint32_t hval = archive_hashval (name, name_len);
cb09a2cd
RM
700
701 insert_idx = -1;
6055173a
JM
702 idx = hval % GET (head->namehash_size);
703 incr = 1 + hval % (GET (head->namehash_size) - 2);
cb09a2cd
RM
704
705 /* If the name_offset field is zero this means this is a
706 deleted entry and therefore no entry can be found. */
6055173a 707 while (GET (namehashtab[idx].name_offset) != 0)
cb09a2cd 708 {
6055173a
JM
709 if (GET (namehashtab[idx].hashval) == hval
710 && (strcmp (name,
711 (char *) ah->addr + GET (namehashtab[idx].name_offset))
712 == 0))
cb09a2cd
RM
713 {
714 /* Found the entry. */
6055173a 715 if (GET (namehashtab[idx].locrec_offset) != 0 && ! replace)
cb09a2cd
RM
716 {
717 if (! be_quiet)
718 error (0, 0, _("locale '%s' already exists"), name);
719 return NULL;
720 }
721
722 break;
723 }
724
6055173a 725 if (GET (namehashtab[idx].hashval) == hval && ! be_quiet)
ac8f8c53
RM
726 {
727 error (0, 0, "hash collision (%u) %s, %s",
6055173a
JM
728 hval, name,
729 (char *) ah->addr + GET (namehashtab[idx].name_offset));
ac8f8c53
RM
730 }
731
cb09a2cd 732 /* Remember the first place we can insert the new entry. */
6055173a 733 if (GET (namehashtab[idx].locrec_offset) == 0 && insert_idx == -1)
cb09a2cd
RM
734 insert_idx = idx;
735
736 idx += incr;
6055173a
JM
737 if (idx >= GET (head->namehash_size))
738 idx -= GET (head->namehash_size);
cb09a2cd
RM
739 }
740
741 /* Add as early as possible. */
742 if (insert_idx != -1)
743 idx = insert_idx;
744
6055173a 745 SET (namehashtab[idx].hashval, hval); /* no-op if replacing an old entry. */
cb09a2cd
RM
746 return &namehashtab[idx];
747}
748
749static void
750add_alias (struct locarhandle *ah, const char *alias, bool replace,
bd6daf3b 751 const char *oldname, uint32_t *locrec_offset_p)
cb09a2cd 752{
bd6daf3b 753 uint32_t locrec_offset = *locrec_offset_p;
cb09a2cd
RM
754 struct locarhead *head = ah->addr;
755 const size_t name_len = strlen (alias);
756 struct namehashent *namehashent = insert_name (ah, alias, strlen (alias),
757 replace);
758 if (namehashent == NULL && ! replace)
759 return;
760
6055173a 761 if (GET (namehashent->name_offset) == 0)
cb09a2cd
RM
762 {
763 /* We are adding a new hash entry for this alias.
764 Determine whether we have to resize the file. */
6055173a
JM
765 if (GET (head->string_used) + name_len + 1 > GET (head->string_size)
766 || (100 * GET (head->namehash_used)
767 > 75 * GET (head->namehash_size)))
cb09a2cd
RM
768 {
769 /* The current archive is not large enough. */
770 enlarge_archive (ah, head);
771
772 /* The locrecent might have moved, so we have to look up
773 the old name afresh. */
774 namehashent = insert_name (ah, oldname, strlen (oldname), true);
6055173a
JM
775 assert (GET (namehashent->name_offset) != 0);
776 assert (GET (namehashent->locrec_offset) != 0);
777 *locrec_offset_p = GET (namehashent->locrec_offset);
cb09a2cd
RM
778
779 /* Tail call to try the whole thing again. */
bd6daf3b 780 add_alias (ah, alias, replace, oldname, locrec_offset_p);
cb09a2cd
RM
781 return;
782 }
783
784 /* Add the name string. */
6055173a 785 memcpy (ah->addr + GET (head->string_offset) + GET (head->string_used),
cb09a2cd 786 alias, name_len + 1);
6055173a
JM
787 SET (namehashent->name_offset,
788 GET (head->string_offset) + GET (head->string_used));
789 INC (head->string_used, name_len + 1);
cb09a2cd 790
6055173a 791 INC (head->namehash_used, 1);
cb09a2cd
RM
792 }
793
6055173a 794 if (GET (namehashent->locrec_offset) != 0)
cb09a2cd
RM
795 {
796 /* Replacing an existing entry.
797 Mark that we are no longer using the old locrecent. */
798 struct locrecent *locrecent
799 = (struct locrecent *) ((char *) ah->addr
6055173a
JM
800 + GET (namehashent->locrec_offset));
801 INC (locrecent->refs, -1);
cb09a2cd
RM
802 }
803
804 /* Point this entry at the locrecent installed for the main name. */
6055173a 805 SET (namehashent->locrec_offset, locrec_offset);
cb09a2cd
RM
806}
807
ac8f8c53
RM
808static int /* qsort comparator used below */
809cmpcategorysize (const void *a, const void *b)
810{
811 if (*(const void **) a == NULL)
812 return 1;
813 if (*(const void **) b == NULL)
814 return -1;
815 return ((*(const struct locale_category_data **) a)->size
816 - (*(const struct locale_category_data **) b)->size);
817}
a7b65cdc
UD
818
819/* Check the content of the archive for duplicates. Add the content
cb09a2cd
RM
820 of the files if necessary. Returns the locrec_offset. */
821static uint32_t
822add_locale (struct locarhandle *ah,
823 const char *name, locale_data_t data, bool replace)
a7b65cdc
UD
824{
825 /* First look for the name. If it already exists and we are not
826 supposed to replace it don't do anything. If it does not exist
827 we have to allocate a new locale record. */
828 size_t name_len = strlen (name);
829 uint32_t file_offsets[__LC_LAST];
830 unsigned int num_new_offsets = 0;
831 struct sumhashent *sumhashtab;
832 uint32_t hval;
ac8f8c53 833 unsigned int cnt, idx;
a7b65cdc 834 struct locarhead *head;
a7b65cdc
UD
835 struct namehashent *namehashent;
836 unsigned int incr;
837 struct locrecent *locrecent;
ac8f8c53
RM
838 off64_t lastoffset;
839 char *ptr;
840 struct locale_category_data *size_order[__LC_LAST];
841 const size_t pagesz = getpagesize ();
842 int small_mask;
a7b65cdc
UD
843
844 head = ah->addr;
845 sumhashtab = (struct sumhashent *) ((char *) ah->addr
6055173a 846 + GET (head->sumhash_offset));
a7b65cdc 847
ac8f8c53
RM
848 memset (file_offsets, 0, sizeof (file_offsets));
849
850 size_order[LC_ALL] = NULL;
851 for (cnt = 0; cnt < __LC_LAST; ++cnt)
852 if (cnt != LC_ALL)
853 size_order[cnt] = &data[cnt];
854
855 /* Sort the array in ascending order of data size. */
856 qsort (size_order, __LC_LAST, sizeof size_order[0], cmpcategorysize);
857
858 small_mask = 0;
859 data[LC_ALL].size = 0;
860 for (cnt = 0; cnt < __LC_LAST; ++cnt)
861 if (size_order[cnt] != NULL)
862 {
863 const size_t rounded_size = (size_order[cnt]->size + 15) & -16;
864 if (data[LC_ALL].size + rounded_size > 2 * pagesz)
865 {
866 /* This category makes the small-categories block
867 stop being small, so this is the end of the road. */
868 do
869 size_order[cnt++] = NULL;
870 while (cnt < __LC_LAST);
871 break;
872 }
873 data[LC_ALL].size += rounded_size;
874 small_mask |= 1 << (size_order[cnt] - data);
875 }
876
877 /* Copy the data for all the small categories into the LC_ALL
878 pseudo-category. */
879
880 data[LC_ALL].addr = alloca (data[LC_ALL].size);
881 memset (data[LC_ALL].addr, 0, data[LC_ALL].size);
882
883 ptr = data[LC_ALL].addr;
884 for (cnt = 0; cnt < __LC_LAST; ++cnt)
885 if (small_mask & (1 << cnt))
886 {
887 memcpy (ptr, data[cnt].addr, data[cnt].size);
888 ptr += (data[cnt].size + 15) & -16;
889 }
890 __md5_buffer (data[LC_ALL].addr, data[LC_ALL].size, data[LC_ALL].sum);
a7b65cdc
UD
891
892 /* For each locale category data set determine whether the same data
893 is already somewhere in the archive. */
894 for (cnt = 0; cnt < __LC_LAST; ++cnt)
ac8f8c53 895 if (small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
a7b65cdc 896 {
a7b65cdc
UD
897 ++num_new_offsets;
898
899 /* Compute the hash value of the checksum to determine a
900 starting point for the search in the MD5 hash value
901 table. */
a3f9038c 902 hval = archive_hashval (data[cnt].sum, 16);
a7b65cdc 903
6055173a
JM
904 idx = hval % GET (head->sumhash_size);
905 incr = 1 + hval % (GET (head->sumhash_size) - 2);
a7b65cdc 906
6055173a 907 while (GET (sumhashtab[idx].file_offset) != 0)
a7b65cdc
UD
908 {
909 if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0)
910 {
59a7162b
UD
911 /* Check the content, there could be a collision of
912 the hash sum.
913
914 Unfortunately the sumhashent record does not include
915 the size of the stored data. So we have to search for
916 it. */
6055173a
JM
917 locrecent
918 = (struct locrecent *) ((char *) ah->addr
919 + GET (head->locrectab_offset));
59a7162b 920 size_t iloc;
6055173a
JM
921 for (iloc = 0; iloc < GET (head->locrectab_used); ++iloc)
922 if (GET (locrecent[iloc].refs) != 0
923 && (GET (locrecent[iloc].record[cnt].offset)
924 == GET (sumhashtab[idx].file_offset)))
59a7162b
UD
925 break;
926
6055173a
JM
927 if (iloc != GET (head->locrectab_used)
928 && data[cnt].size == GET (locrecent[iloc].record[cnt].len)
705341a9
UD
929 /* We have to compare the content. Either we can
930 have the data mmaped or we have to read from
931 the file. */
6055173a
JM
932 && (file_data_available_p
933 (ah, GET (sumhashtab[idx].file_offset),
934 data[cnt].size)
705341a9
UD
935 ? memcmp (data[cnt].addr,
936 (char *) ah->addr
6055173a 937 + GET (sumhashtab[idx].file_offset),
705341a9
UD
938 data[cnt].size) == 0
939 : compare_from_file (ah, data[cnt].addr,
6055173a 940 GET (sumhashtab[idx].file_offset),
705341a9 941 data[cnt].size) == 0))
59a7162b
UD
942 {
943 /* Found it. */
6055173a 944 file_offsets[cnt] = GET (sumhashtab[idx].file_offset);
59a7162b
UD
945 --num_new_offsets;
946 break;
947 }
a7b65cdc
UD
948 }
949
950 idx += incr;
6055173a
JM
951 if (idx >= GET (head->sumhash_size))
952 idx -= GET (head->sumhash_size);
a7b65cdc
UD
953 }
954 }
955
cb09a2cd
RM
956 /* Find a slot for the locale name in the hash table. */
957 namehashent = insert_name (ah, name, name_len, replace);
958 if (namehashent == NULL) /* Already exists and !REPLACE. */
959 return 0;
a7b65cdc
UD
960
961 /* Determine whether we have to resize the file. */
6055173a
JM
962 if ((100 * (GET (head->sumhash_used) + num_new_offsets)
963 > 75 * GET (head->sumhash_size))
964 || (GET (namehashent->locrec_offset) == 0
965 && (GET (head->locrectab_used) == GET (head->locrectab_size)
966 || (GET (head->string_used) + name_len + 1
967 > GET (head->string_size))
968 || (100 * GET (head->namehash_used)
969 > 75 * GET (head->namehash_size)))))
a7b65cdc
UD
970 {
971 /* The current archive is not large enough. */
972 enlarge_archive (ah, head);
cb09a2cd 973 return add_locale (ah, name, data, replace);
a7b65cdc
UD
974 }
975
976 /* Add the locale data which is not yet in the archive. */
ac8f8c53
RM
977 for (cnt = 0, lastoffset = 0; cnt < __LC_LAST; ++cnt)
978 if ((small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt)))
979 && file_offsets[cnt] == 0)
a7b65cdc
UD
980 {
981 /* The data for this section is not yet available in the
982 archive. Append it. */
983 off64_t lastpos;
984 uint32_t md5hval;
985
986 lastpos = lseek64 (ah->fd, 0, SEEK_END);
987 if (lastpos == (off64_t) -1)
988 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
989
ac8f8c53
RM
990 /* If block of small categories would cross page boundary,
991 align it unless it immediately follows a large category. */
992 if (cnt == LC_ALL && lastoffset != lastpos
993 && ((((lastpos & (pagesz - 1)) + data[cnt].size + pagesz - 1)
994 & -pagesz)
995 > ((data[cnt].size + pagesz - 1) & -pagesz)))
996 {
997 size_t sz = pagesz - (lastpos & (pagesz - 1));
998 char *zeros = alloca (sz);
999
1000 memset (zeros, 0, sz);
1001 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, sz) != sz))
1002 error (EXIT_FAILURE, errno,
1003 _("cannot add to locale archive"));
1004
1005 lastpos += sz;
1006 }
1007
a7b65cdc
UD
1008 /* Align all data to a 16 byte boundary. */
1009 if ((lastpos & 15) != 0)
1010 {
1011 static const char zeros[15] = { 0, };
1012
1013 if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, 16 - (lastpos & 15)))
1014 != 16 - (lastpos & 15))
1015 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
1016
1017 lastpos += 16 - (lastpos & 15);
1018 }
1019
1020 /* Remember the position. */
1021 file_offsets[cnt] = lastpos;
ac8f8c53 1022 lastoffset = lastpos + data[cnt].size;
a7b65cdc
UD
1023
1024 /* Write the data. */
1025 if (TEMP_FAILURE_RETRY (write (ah->fd, data[cnt].addr, data[cnt].size))
1026 != data[cnt].size)
1027 error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
1028
1029 /* Add the hash value to the hash table. */
a3f9038c 1030 md5hval = archive_hashval (data[cnt].sum, 16);
a7b65cdc 1031
6055173a
JM
1032 idx = md5hval % GET (head->sumhash_size);
1033 incr = 1 + md5hval % (GET (head->sumhash_size) - 2);
a7b65cdc 1034
6055173a 1035 while (GET (sumhashtab[idx].file_offset) != 0)
a7b65cdc
UD
1036 {
1037 idx += incr;
6055173a
JM
1038 if (idx >= GET (head->sumhash_size))
1039 idx -= GET (head->sumhash_size);
a7b65cdc
UD
1040 }
1041
1042 memcpy (sumhashtab[idx].sum, data[cnt].sum, 16);
6055173a 1043 SET (sumhashtab[idx].file_offset, file_offsets[cnt]);
a7b65cdc 1044
6055173a 1045 INC (head->sumhash_used, 1);
a7b65cdc
UD
1046 }
1047
ac8f8c53
RM
1048 lastoffset = file_offsets[LC_ALL];
1049 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1050 if (small_mask & (1 << cnt))
1051 {
1052 file_offsets[cnt] = lastoffset;
1053 lastoffset += (data[cnt].size + 15) & -16;
1054 }
1055
6055173a 1056 if (GET (namehashent->name_offset) == 0)
a7b65cdc
UD
1057 {
1058 /* Add the name string. */
6055173a
JM
1059 memcpy ((char *) ah->addr + GET (head->string_offset)
1060 + GET (head->string_used),
a7b65cdc 1061 name, name_len + 1);
6055173a
JM
1062 SET (namehashent->name_offset,
1063 GET (head->string_offset) + GET (head->string_used));
1064 INC (head->string_used, name_len + 1);
1065 INC (head->namehash_used, 1);
cb09a2cd 1066 }
a7b65cdc 1067
6055173a 1068 if (GET (namehashent->locrec_offset == 0))
cb09a2cd 1069 {
a7b65cdc 1070 /* Allocate a name location record. */
6055173a
JM
1071 SET (namehashent->locrec_offset, (GET (head->locrectab_offset)
1072 + (GET (head->locrectab_used)
1073 * sizeof (struct locrecent))));
1074 INC (head->locrectab_used, 1);
cb09a2cd 1075 locrecent = (struct locrecent *) ((char *) ah->addr
6055173a
JM
1076 + GET (namehashent->locrec_offset));
1077 SET (locrecent->refs, 1);
a7b65cdc 1078 }
cb09a2cd
RM
1079 else
1080 {
1081 /* If there are other aliases pointing to this locrecent,
1082 we still need a new one. If not, reuse the old one. */
a7b65cdc 1083
cb09a2cd 1084 locrecent = (struct locrecent *) ((char *) ah->addr
6055173a
JM
1085 + GET (namehashent->locrec_offset));
1086 if (GET (locrecent->refs) > 1)
cb09a2cd 1087 {
6055173a
JM
1088 INC (locrecent->refs, -1);
1089 SET (namehashent->locrec_offset, (GET (head->locrectab_offset)
1090 + (GET (head->locrectab_used)
1091 * sizeof (struct locrecent))));
1092 INC (head->locrectab_used, 1);
1093 locrecent
1094 = (struct locrecent *) ((char *) ah->addr
1095 + GET (namehashent->locrec_offset));
1096 SET (locrecent->refs, 1);
cb09a2cd
RM
1097 }
1098 }
a7b65cdc
UD
1099
1100 /* Fill in the table with the locations of the locale data. */
a7b65cdc 1101 for (cnt = 0; cnt < __LC_LAST; ++cnt)
ac8f8c53 1102 {
6055173a
JM
1103 SET (locrecent->record[cnt].offset, file_offsets[cnt]);
1104 SET (locrecent->record[cnt].len, data[cnt].size);
ac8f8c53 1105 }
a7b65cdc 1106
6055173a 1107 return GET (namehashent->locrec_offset);
cb09a2cd
RM
1108}
1109
1110
1111/* Check the content of the archive for duplicates. Add the content
1112 of the files if necessary. Add all the names, possibly overwriting
1113 old files. */
1114int
1115add_locale_to_archive (ah, name, data, replace)
1116 struct locarhandle *ah;
1117 const char *name;
1118 locale_data_t data;
1119 bool replace;
1120{
1121 char *normalized_name = NULL;
1122 uint32_t locrec_offset;
1123
1124 /* First analyze the name to decide how to archive it. */
1125 const char *language;
1126 const char *modifier;
1127 const char *territory;
1128 const char *codeset;
1129 const char *normalized_codeset;
1130 int mask = _nl_explode_name (strdupa (name),
1131 &language, &modifier, &territory,
1132 &codeset, &normalized_codeset);
4f031072
UD
1133 if (mask == -1)
1134 return -1;
cb09a2cd
RM
1135
1136 if (mask & XPG_NORM_CODESET)
1137 /* This name contains a codeset in unnormalized form.
1138 We will store it in the archive with a normalized name. */
1139 asprintf (&normalized_name, "%s%s%s.%s%s%s",
1140 language, territory == NULL ? "" : "_", territory ?: "",
1141 (mask & XPG_NORM_CODESET) ? normalized_codeset : codeset,
1142 modifier == NULL ? "" : "@", modifier ?: "");
1143
1144 /* This call does the main work. */
1145 locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
cb09a2cd
RM
1146 if (locrec_offset == 0)
1147 {
bd6daf3b 1148 free (normalized_name);
cb09a2cd
RM
1149 if (mask & XPG_NORM_CODESET)
1150 free ((char *) normalized_codeset);
1151 return -1;
1152 }
1153
1154 if ((mask & XPG_CODESET) == 0)
1155 {
1156 /* This name lacks a codeset, so determine the locale's codeset and
1157 add an alias for its name with normalized codeset appended. */
1158
1159 const struct
1160 {
1161 unsigned int magic;
1162 unsigned int nstrings;
1163 unsigned int strindex[0];
1164 } *filedata = data[LC_CTYPE].addr;
1165 codeset = (char *) filedata
6055173a
JM
1166 + maybe_swap_uint32 (filedata->strindex[_NL_ITEM_INDEX
1167 (_NL_CTYPE_CODESET_NAME)]);
bd6daf3b 1168 char *normalized_codeset_name = NULL;
cb09a2cd
RM
1169
1170 normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
1171 mask |= XPG_NORM_CODESET;
1172
bd6daf3b 1173 asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s",
cb09a2cd
RM
1174 language, territory == NULL ? "" : "_", territory ?: "",
1175 normalized_codeset,
1176 modifier == NULL ? "" : "@", modifier ?: "");
1177
bd6daf3b
RM
1178 add_alias (ah, normalized_codeset_name, replace,
1179 normalized_name ?: name, &locrec_offset);
1180 free (normalized_codeset_name);
cb09a2cd
RM
1181 }
1182
1183 /* Now read the locale.alias files looking for lines whose
1184 right hand side matches our name after normalization. */
4f031072 1185 int result = 0;
cb09a2cd
RM
1186 if (alias_file != NULL)
1187 {
1188 FILE *fp;
2e2dc1a5 1189 fp = fopen (alias_file, "rm");
cb09a2cd
RM
1190 if (fp == NULL)
1191 error (1, errno, _("locale alias file `%s' not found"),
1192 alias_file);
1193
1194 /* No threads present. */
1195 __fsetlocking (fp, FSETLOCKING_BYCALLER);
1196
1197 while (! feof_unlocked (fp))
1198 {
1199 /* It is a reasonable approach to use a fix buffer here
1200 because
1201 a) we are only interested in the first two fields
1202 b) these fields must be usable as file names and so must
1203 not be that long */
1204 char buf[BUFSIZ];
1205 char *alias;
1206 char *value;
1207 char *cp;
1208
1209 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1210 /* EOF reached. */
1211 break;
1212
1213 cp = buf;
1214 /* Ignore leading white space. */
1215 while (isspace (cp[0]) && cp[0] != '\n')
1216 ++cp;
1217
1218 /* A leading '#' signals a comment line. */
1219 if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
1220 {
1221 alias = cp++;
1222 while (cp[0] != '\0' && !isspace (cp[0]))
1223 ++cp;
1224 /* Terminate alias name. */
1225 if (cp[0] != '\0')
1226 *cp++ = '\0';
1227
1228 /* Now look for the beginning of the value. */
1229 while (isspace (cp[0]))
1230 ++cp;
1231
1232 if (cp[0] != '\0')
1233 {
1234 value = cp++;
1235 while (cp[0] != '\0' && !isspace (cp[0]))
1236 ++cp;
1237 /* Terminate value. */
1238 if (cp[0] == '\n')
1239 {
1240 /* This has to be done to make the following
1241 test for the end of line possible. We are
1242 looking for the terminating '\n' which do not
1243 overwrite here. */
1244 *cp++ = '\0';
1245 *cp = '\n';
1246 }
1247 else if (cp[0] != '\0')
1248 *cp++ = '\0';
1249
1250 /* Does this alias refer to our locale? We will
1251 normalize the right hand side and compare the
1252 elements of the normalized form. */
1253 {
1254 const char *rhs_language;
1255 const char *rhs_modifier;
1256 const char *rhs_territory;
1257 const char *rhs_codeset;
1258 const char *rhs_normalized_codeset;
1259 int rhs_mask = _nl_explode_name (value,
1260 &rhs_language,
1261 &rhs_modifier,
1262 &rhs_territory,
1263 &rhs_codeset,
1264 &rhs_normalized_codeset);
13f1ab36 1265 if (rhs_mask == -1)
4f031072
UD
1266 {
1267 result = -1;
1268 goto out;
1269 }
cb09a2cd
RM
1270 if (!strcmp (language, rhs_language)
1271 && ((rhs_mask & XPG_CODESET)
1272 /* He has a codeset, it must match normalized. */
1273 ? !strcmp ((mask & XPG_NORM_CODESET)
1274 ? normalized_codeset : codeset,
1275 (rhs_mask & XPG_NORM_CODESET)
1276 ? rhs_normalized_codeset : rhs_codeset)
1277 /* He has no codeset, we must also have none. */
1278 : (mask & XPG_CODESET) == 0)
1279 /* Codeset (or lack thereof) matches. */
1280 && !strcmp (territory ?: "", rhs_territory ?: "")
1281 && !strcmp (modifier ?: "", rhs_modifier ?: ""))
1282 /* We have a winner. */
1283 add_alias (ah, alias, replace,
bd6daf3b 1284 normalized_name ?: name, &locrec_offset);
cb09a2cd
RM
1285 if (rhs_mask & XPG_NORM_CODESET)
1286 free ((char *) rhs_normalized_codeset);
1287 }
1288 }
1289 }
1290
1291 /* Possibly not the whole line fits into the buffer.
1292 Ignore the rest of the line. */
1293 while (strchr (cp, '\n') == NULL)
1294 {
1295 cp = buf;
1296 if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
1297 /* Make sure the inner loop will be left. The outer
1298 loop will exit at the `feof' test. */
1299 *cp = '\n';
1300 }
1301 }
a7b65cdc 1302
4f031072 1303 out:
cb09a2cd
RM
1304 fclose (fp);
1305 }
a7b65cdc 1306
bd6daf3b
RM
1307 free (normalized_name);
1308
cb09a2cd
RM
1309 if (mask & XPG_NORM_CODESET)
1310 free ((char *) normalized_codeset);
a7b65cdc 1311
4f031072 1312 return result;
a7b65cdc
UD
1313}
1314
1315
1316int
1317add_locales_to_archive (nlist, list, replace)
1318 size_t nlist;
1319 char *list[];
1320 bool replace;
1321{
1322 struct locarhandle ah;
1323 int result = 0;
1324
1325 /* Open the archive. This call never returns if we cannot
1326 successfully open the archive. */
b2bffca2 1327 open_archive (&ah, false);
a7b65cdc
UD
1328
1329 while (nlist-- > 0)
1330 {
1331 const char *fname = *list++;
1332 size_t fnamelen = strlen (fname);
1333 struct stat64 st;
1334 DIR *dirp;
1335 struct dirent64 *d;
1336 int seen;
1337 locale_data_t data;
1338 int cnt;
1339
1340 if (! be_quiet)
1341 printf (_("Adding %s\n"), fname);
1342
1343 /* First see whether this really is a directory and whether it
1344 contains all the require locale category files. */
1345 if (stat64 (fname, &st) < 0)
1346 {
1347 error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname,
1348 strerror (errno));
1349 continue;
1350 }
1351 if (!S_ISDIR (st.st_mode))
1352 {
1353 error (0, 0, _("\"%s\" is no directory; ignored"), fname);
1354 continue;
1355 }
1356
1357 dirp = opendir (fname);
1358 if (dirp == NULL)
1359 {
1360 error (0, 0, _("cannot open directory \"%s\": %s: ignored"),
1361 fname, strerror (errno));
1362 continue;
1363 }
1364
1365 seen = 0;
1366 while ((d = readdir64 (dirp)) != NULL)
1367 {
1368 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1369 if (cnt != LC_ALL)
1370 if (strcmp (d->d_name, locnames[cnt]) == 0)
1371 {
1372 unsigned char d_type;
1373
1374 /* We have an object of the required name. If it's
1375 a directory we have to look at a file with the
1376 prefix "SYS_". Otherwise we have found what we
1377 are looking for. */
1378#ifdef _DIRENT_HAVE_D_TYPE
1379 d_type = d->d_type;
1380
1381 if (d_type != DT_REG)
1382#endif
1383 {
1384 char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
1385
1386#ifdef _DIRENT_HAVE_D_TYPE
1387 if (d_type == DT_UNKNOWN)
1388#endif
1389 {
1390 strcpy (stpcpy (stpcpy (fullname, fname), "/"),
1391 d->d_name);
1392
1393 if (stat64 (fullname, &st) == -1)
1394 /* We cannot stat the file, ignore it. */
1395 break;
1396
1397 d_type = IFTODT (st.st_mode);
1398 }
1399
1400 if (d_type == DT_DIR)
1401 {
1402 /* We have to do more tests. The file is a
1403 directory and it therefore must contain a
1404 regular file with the same name except a
1405 "SYS_" prefix. */
531bafd8
UD
1406 char *t = stpcpy (stpcpy (fullname, fname), "/");
1407 strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
a7b65cdc
UD
1408 d->d_name);
1409
1410 if (stat64 (fullname, &st) == -1)
1411 /* There is no SYS_* file or we cannot
1412 access it. */
1413 break;
1414
1415 d_type = IFTODT (st.st_mode);
1416 }
1417 }
1418
1419 /* If we found a regular file (eventually after
1420 following a symlink) we are successful. */
1421 if (d_type == DT_REG)
1422 ++seen;
1423 break;
1424 }
1425 }
1426
1427 closedir (dirp);
1428
1429 if (seen != __LC_LAST - 1)
1430 {
1431 /* We don't have all locale category files. Ignore the name. */
1432 error (0, 0, _("incomplete set of locale files in \"%s\""),
1433 fname);
1434 continue;
1435 }
1436
1437 /* Add the files to the archive. To do this we first compute
1438 sizes and the MD5 sums of all the files. */
1439 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1440 if (cnt != LC_ALL)
1441 {
1442 char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
1443 int fd;
1444
1445 strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
1446 fd = open64 (fullname, O_RDONLY);
1447 if (fd == -1 || fstat64 (fd, &st) == -1)
1448 {
1449 /* Cannot read the file. */
1450 if (fd != -1)
1451 close (fd);
1452 break;
1453 }
1454
1455 if (S_ISDIR (st.st_mode))
1456 {
531bafd8 1457 char *t;
a7b65cdc 1458 close (fd);
531bafd8
UD
1459 t = stpcpy (stpcpy (fullname, fname), "/");
1460 strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
a7b65cdc
UD
1461 locnames[cnt]);
1462
1463 fd = open64 (fullname, O_RDONLY);
1464 if (fd == -1 || fstat64 (fd, &st) == -1
1465 || !S_ISREG (st.st_mode))
1466 {
1467 if (fd != -1)
1468 close (fd);
1469 break;
1470 }
1471 }
1472
1473 /* Map the file. */
1474 data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
1475 fd, 0);
1476 if (data[cnt].addr == MAP_FAILED)
1477 {
1478 /* Cannot map it. */
1479 close (fd);
1480 break;
1481 }
1482
1483 data[cnt].size = st.st_size;
1484 __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
1485
1486 /* We don't need the file descriptor anymore. */
1487 close (fd);
1488 }
1489
1490 if (cnt != __LC_LAST)
1491 {
1492 while (cnt-- > 0)
1493 if (cnt != LC_ALL)
1494 munmap (data[cnt].addr, data[cnt].size);
1495
1496 error (0, 0, _("cannot read all files in \"%s\": ignored"), fname);
1497
1498 continue;
1499 }
1500
1501 result |= add_locale_to_archive (&ah, basename (fname), data, replace);
1502
1503 for (cnt = 0; cnt < __LC_LAST; ++cnt)
1504 if (cnt != LC_ALL)
1505 munmap (data[cnt].addr, data[cnt].size);
1506 }
1507
1508 /* We are done. */
1509 close_archive (&ah);
1510
1511 return result;
1512}
1513
1514
1515int
1516delete_locales_from_archive (nlist, list)
1517 size_t nlist;
1518 char *list[];
1519{
1520 struct locarhandle ah;
1521 struct locarhead *head;
1522 struct namehashent *namehashtab;
1523
1524 /* Open the archive. This call never returns if we cannot
1525 successfully open the archive. */
b2bffca2 1526 open_archive (&ah, false);
a7b65cdc
UD
1527
1528 head = ah.addr;
1529 namehashtab = (struct namehashent *) ((char *) ah.addr
6055173a 1530 + GET (head->namehash_offset));
a7b65cdc
UD
1531
1532 while (nlist-- > 0)
1533 {
1534 const char *locname = *list++;
1535 uint32_t hval;
1536 unsigned int idx;
1537 unsigned int incr;
1538
1539 /* Search for this locale in the archive. */
a3f9038c 1540 hval = archive_hashval (locname, strlen (locname));
a7b65cdc 1541
6055173a
JM
1542 idx = hval % GET (head->namehash_size);
1543 incr = 1 + hval % (GET (head->namehash_size) - 2);
a7b65cdc
UD
1544
1545 /* If the name_offset field is zero this means this is no
1546 deleted entry and therefore no entry can be found. */
6055173a 1547 while (GET (namehashtab[idx].name_offset) != 0)
a7b65cdc 1548 {
6055173a 1549 if (GET (namehashtab[idx].hashval) == hval
a7b65cdc 1550 && (strcmp (locname,
6055173a
JM
1551 ((char *) ah.addr
1552 + GET (namehashtab[idx].name_offset)))
a7b65cdc
UD
1553 == 0))
1554 {
1555 /* Found the entry. Now mark it as removed by zero-ing
1556 the reference to the locale record. */
6055173a 1557 SET (namehashtab[idx].locrec_offset, 0);
a7b65cdc
UD
1558 break;
1559 }
1560
1561 idx += incr;
6055173a
JM
1562 if (idx >= GET (head->namehash_size))
1563 idx -= GET (head->namehash_size);
a7b65cdc
UD
1564 }
1565
6055173a 1566 if (GET (namehashtab[idx].name_offset) == 0 && ! be_quiet)
a7b65cdc
UD
1567 error (0, 0, _("locale \"%s\" not in archive"), locname);
1568 }
1569
1570 close_archive (&ah);
1571
1572 return 0;
1573}
1574
1575
b2bffca2
UD
1576struct nameent
1577{
1578 char *name;
1579 uint32_t locrec_offset;
1580};
1581
1582
1583struct dataent
1584{
1585 const unsigned char *sum;
1586 uint32_t file_offset;
1587 uint32_t nlink;
1588};
1589
1590
1591static int
1592nameentcmp (const void *a, const void *b)
1593{
1594 return strcmp (((const struct nameent *) a)->name,
1595 ((const struct nameent *) b)->name);
1596}
1597
1598
a7b65cdc 1599static int
b2bffca2 1600dataentcmp (const void *a, const void *b)
a7b65cdc 1601{
b2bffca2
UD
1602 if (((const struct dataent *) a)->file_offset
1603 < ((const struct dataent *) b)->file_offset)
1604 return -1;
1605
1606 if (((const struct dataent *) a)->file_offset
1607 > ((const struct dataent *) b)->file_offset)
1608 return 1;
1609
1610 return 0;
a7b65cdc
UD
1611}
1612
1613
1614void
b2bffca2 1615show_archive_content (int verbose)
a7b65cdc
UD
1616{
1617 struct locarhandle ah;
1618 struct locarhead *head;
1619 struct namehashent *namehashtab;
b2bffca2 1620 struct nameent *names;
638bb1f3 1621 size_t cnt, used;
a7b65cdc
UD
1622
1623 /* Open the archive. This call never returns if we cannot
1624 successfully open the archive. */
b2bffca2 1625 open_archive (&ah, true);
a7b65cdc
UD
1626
1627 head = ah.addr;
1628
6055173a 1629 names = (struct nameent *) xmalloc (GET (head->namehash_used)
b2bffca2 1630 * sizeof (struct nameent));
a7b65cdc
UD
1631
1632 namehashtab = (struct namehashent *) ((char *) ah.addr
6055173a
JM
1633 + GET (head->namehash_offset));
1634 for (cnt = used = 0; cnt < GET (head->namehash_size); ++cnt)
1635 if (GET (namehashtab[cnt].locrec_offset) != 0)
a7b65cdc 1636 {
6055173a
JM
1637 assert (used < GET (head->namehash_used));
1638 names[used].name = ah.addr + GET (namehashtab[cnt].name_offset);
1639 names[used++].locrec_offset = GET (namehashtab[cnt].locrec_offset);
a7b65cdc
UD
1640 }
1641
1642 /* Sort the names. */
b2bffca2
UD
1643 qsort (names, used, sizeof (struct nameent), nameentcmp);
1644
1645 if (verbose)
1646 {
1647 struct dataent *files;
1648 struct sumhashent *sumhashtab;
1649 int sumused;
1650
6055173a 1651 files = (struct dataent *) xmalloc (GET (head->sumhash_used)
07358add 1652 * sizeof (struct dataent));
b2bffca2
UD
1653
1654 sumhashtab = (struct sumhashent *) ((char *) ah.addr
6055173a
JM
1655 + GET (head->sumhash_offset));
1656 for (cnt = sumused = 0; cnt < GET (head->sumhash_size); ++cnt)
1657 if (GET (sumhashtab[cnt].file_offset) != 0)
b2bffca2 1658 {
6055173a 1659 assert (sumused < GET (head->sumhash_used));
b2bffca2 1660 files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
6055173a 1661 files[sumused].file_offset = GET (sumhashtab[cnt].file_offset);
b2bffca2
UD
1662 files[sumused++].nlink = 0;
1663 }
1664
1665 /* Sort by file locations. */
1666 qsort (files, sumused, sizeof (struct dataent), dataentcmp);
1667
1668 /* Compute nlink fields. */
1669 for (cnt = 0; cnt < used; ++cnt)
1670 {
1671 struct locrecent *locrec;
1672 int idx;
1673
1674 locrec = (struct locrecent *) ((char *) ah.addr
1675 + names[cnt].locrec_offset);
1676 for (idx = 0; idx < __LC_LAST; ++idx)
6055173a 1677 if (GET (locrec->record[LC_ALL].offset) != 0
ac8f8c53 1678 ? (idx == LC_ALL
6055173a
JM
1679 || (GET (locrec->record[idx].offset)
1680 < GET (locrec->record[LC_ALL].offset))
1681 || ((GET (locrec->record[idx].offset)
1682 + GET (locrec->record[idx].len))
1683 > (GET (locrec->record[LC_ALL].offset)
1684 + GET (locrec->record[LC_ALL].len))))
ac8f8c53 1685 : idx != LC_ALL)
b2bffca2
UD
1686 {
1687 struct dataent *data, dataent;
1688
6055173a 1689 dataent.file_offset = GET (locrec->record[idx].offset);
b2bffca2
UD
1690 data = (struct dataent *) bsearch (&dataent, files, sumused,
1691 sizeof (struct dataent),
1692 dataentcmp);
1693 assert (data != NULL);
1694 ++data->nlink;
1695 }
1696 }
a7b65cdc 1697
b2bffca2
UD
1698 /* Print it. */
1699 for (cnt = 0; cnt < used; ++cnt)
1700 {
1701 struct locrecent *locrec;
1702 int idx, i;
1703
1704 locrec = (struct locrecent *) ((char *) ah.addr
1705 + names[cnt].locrec_offset);
1706 for (idx = 0; idx < __LC_LAST; ++idx)
1707 if (idx != LC_ALL)
1708 {
1709 struct dataent *data, dataent;
1710
6055173a
JM
1711 dataent.file_offset = GET (locrec->record[idx].offset);
1712 if (GET (locrec->record[LC_ALL].offset) != 0
1713 && (dataent.file_offset
1714 >= GET (locrec->record[LC_ALL].offset))
1715 && (dataent.file_offset + GET (locrec->record[idx].len)
1716 <= (GET (locrec->record[LC_ALL].offset)
1717 + GET (locrec->record[LC_ALL].len))))
1718 dataent.file_offset = GET (locrec->record[LC_ALL].offset);
ac8f8c53 1719
b2bffca2
UD
1720 data = (struct dataent *) bsearch (&dataent, files, sumused,
1721 sizeof (struct dataent),
1722 dataentcmp);
ac8f8c53 1723 printf ("%6d %7x %3d%c ",
6055173a
JM
1724 GET (locrec->record[idx].len),
1725 GET (locrec->record[idx].offset),
ac8f8c53 1726 data->nlink,
6055173a
JM
1727 (dataent.file_offset
1728 == GET (locrec->record[LC_ALL].offset))
ac8f8c53 1729 ? '+' : ' ');
b2bffca2
UD
1730 for (i = 0; i < 16; i += 4)
1731 printf ("%02x%02x%02x%02x",
1732 data->sum[i], data->sum[i + 1],
1733 data->sum[i + 2], data->sum[i + 3]);
1734 printf (" %s/%s\n", names[cnt].name,
1735 idx == LC_MESSAGES ? "LC_MESSAGES/SYS_LC_MESSAGES"
1736 : locnames[idx]);
1737 }
1738 }
1739 }
1740 else
1741 for (cnt = 0; cnt < used; ++cnt)
1742 puts (names[cnt].name);
a7b65cdc
UD
1743
1744 close_archive (&ah);
1745
1746 exit (EXIT_SUCCESS);
1747}