diff options
Diffstat (limited to 'REORG.TODO/locale/programs/locarchive.c')
-rw-r--r-- | REORG.TODO/locale/programs/locarchive.c | 1757 |
1 files changed, 1757 insertions, 0 deletions
diff --git a/REORG.TODO/locale/programs/locarchive.c b/REORG.TODO/locale/programs/locarchive.c new file mode 100644 index 0000000000..f67b7b8d99 --- /dev/null +++ b/REORG.TODO/locale/programs/locarchive.c @@ -0,0 +1,1757 @@ +/* Copyright (C) 2002-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2002. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <assert.h> +#include <dirent.h> +#include <errno.h> +#include <error.h> +#include <fcntl.h> +#include <inttypes.h> +#include <libintl.h> +#include <locale.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/param.h> +#include <sys/shm.h> +#include <sys/stat.h> + +#include <libc-mmap.h> +#include <libc-pointer-arith.h> +#include "../../crypt/md5.h" +#include "../localeinfo.h" +#include "../locarchive.h" +#include "localedef.h" +#include "locfile.h" + +/* Define the hash function. We define the function as static inline. + We must change the name so as not to conflict with simple-hash.h. */ +#define compute_hashval static archive_hashval +#define hashval_t uint32_t +#include "hashval.h" +#undef compute_hashval + +extern const char *output_prefix; + +#define ARCHIVE_NAME COMPLOCALEDIR "/locale-archive" + +static const char *locnames[] = + { +#define DEFINE_CATEGORY(category, category_name, items, a) \ + [category] = category_name, +#include "categories.def" +#undef DEFINE_CATEGORY + }; + + +/* Size of the initial archive header. */ +#define INITIAL_NUM_NAMES 900 +#define INITIAL_SIZE_STRINGS 7500 +#define INITIAL_NUM_LOCREC 420 +#define INITIAL_NUM_SUMS 2000 + + +/* Get and set values (possibly endian-swapped) in structures mapped + from or written directly to locale archives. */ +#define GET(FIELD) maybe_swap_uint32 (FIELD) +#define SET(FIELD, VALUE) ((FIELD) = maybe_swap_uint32 (VALUE)) +#define INC(FIELD, INCREMENT) SET (FIELD, GET (FIELD) + (INCREMENT)) + + +/* Size of the reserved address space area. */ +#define RESERVE_MMAP_SIZE 512 * 1024 * 1024 + +/* To prepare for enlargements of the mmaped area reserve some address + space. On some machines, being a file mapping rather than an anonymous + mapping affects the address selection. So do this mapping from the + actual file, even though it's only a dummy to reserve address space. */ +static void * +prepare_address_space (int fd, size_t total, size_t *reserved, int *xflags, + void **mmap_base, size_t *mmap_len) +{ + if (total < RESERVE_MMAP_SIZE) + { + void *p = mmap64 (NULL, RESERVE_MMAP_SIZE, PROT_NONE, MAP_SHARED, fd, 0); + if (p != MAP_FAILED) + { + void *aligned_p = PTR_ALIGN_UP (p, MAP_FIXED_ALIGNMENT); + size_t align_adjust = aligned_p - p; + *mmap_base = p; + *mmap_len = RESERVE_MMAP_SIZE; + assert (align_adjust < RESERVE_MMAP_SIZE); + *reserved = RESERVE_MMAP_SIZE - align_adjust; + *xflags = MAP_FIXED; + return aligned_p; + } + } + + *reserved = total; + *xflags = 0; + *mmap_base = NULL; + *mmap_len = 0; + return NULL; +} + + +static void +create_archive (const char *archivefname, struct locarhandle *ah) +{ + int fd; + char fname[strlen (archivefname) + sizeof (".XXXXXX")]; + struct locarhead head; + size_t total; + + strcpy (stpcpy (fname, archivefname), ".XXXXXX"); + + /* Create a temporary file in the correct directory. */ + fd = mkstemp (fname); + if (fd == -1) + error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname); + + /* Create the initial content of the archive. */ + SET (head.magic, AR_MAGIC); + SET (head.serial, 0); + SET (head.namehash_offset, sizeof (struct locarhead)); + SET (head.namehash_used, 0); + SET (head.namehash_size, next_prime (INITIAL_NUM_NAMES)); + + SET (head.string_offset, + (GET (head.namehash_offset) + + GET (head.namehash_size) * sizeof (struct namehashent))); + SET (head.string_used, 0); + SET (head.string_size, INITIAL_SIZE_STRINGS); + + SET (head.locrectab_offset, + GET (head.string_offset) + GET (head.string_size)); + SET (head.locrectab_used, 0); + SET (head.locrectab_size, INITIAL_NUM_LOCREC); + + SET (head.sumhash_offset, + (GET (head.locrectab_offset) + + GET (head.locrectab_size) * sizeof (struct locrecent))); + SET (head.sumhash_used, 0); + SET (head.sumhash_size, next_prime (INITIAL_NUM_SUMS)); + + total = (GET (head.sumhash_offset) + + GET (head.sumhash_size) * sizeof (struct sumhashent)); + + /* Write out the header and create room for the other data structures. */ + if (TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head))) != sizeof (head)) + { + int errval = errno; + unlink (fname); + error (EXIT_FAILURE, errval, _("cannot initialize archive file")); + } + + if (ftruncate64 (fd, total) != 0) + { + int errval = errno; + unlink (fname); + error (EXIT_FAILURE, errval, _("cannot resize archive file")); + } + + size_t reserved, mmap_len; + int xflags; + void *mmap_base; + void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base, + &mmap_len); + + /* Map the header and all the administration data structures. */ + p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0); + if (p == MAP_FAILED) + { + int errval = errno; + unlink (fname); + error (EXIT_FAILURE, errval, _("cannot map archive header")); + } + + /* Now try to rename it. We don't use the rename function since + this would overwrite a file which has been created in + parallel. */ + if (link (fname, archivefname) == -1) + { + int errval = errno; + + /* We cannot use the just created file. */ + close (fd); + unlink (fname); + + if (errval == EEXIST) + { + /* There is already an archive. Must have been a localedef run + which happened in parallel. Simply open this file then. */ + open_archive (ah, false); + return; + } + + error (EXIT_FAILURE, errval, _("failed to create new locale archive")); + } + + /* Remove the temporary name. */ + unlink (fname); + + /* Make the file globally readable. */ + if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1) + { + int errval = errno; + unlink (archivefname); + error (EXIT_FAILURE, errval, + _("cannot change mode of new locale archive")); + } + + ah->fname = NULL; + ah->fd = fd; + ah->mmap_base = mmap_base; + ah->mmap_len = mmap_len; + ah->addr = p; + ah->mmaped = total; + ah->reserved = reserved; +} + + +/* This structure and qsort comparator function are used below to sort an + old archive's locrec table in order of data position in the file. */ +struct oldlocrecent +{ + unsigned int cnt; + struct locrecent *locrec; +}; + +static int +oldlocrecentcmp (const void *a, const void *b) +{ + struct locrecent *la = ((const struct oldlocrecent *) a)->locrec; + struct locrecent *lb = ((const struct oldlocrecent *) b)->locrec; + uint32_t start_a = -1, end_a = 0; + uint32_t start_b = -1, end_b = 0; + int cnt; + + for (cnt = 0; cnt < __LC_LAST; ++cnt) + if (cnt != LC_ALL) + { + if (GET (la->record[cnt].offset) < start_a) + start_a = GET (la->record[cnt].offset); + if (GET (la->record[cnt].offset) + GET (la->record[cnt].len) > end_a) + end_a = GET (la->record[cnt].offset) + GET (la->record[cnt].len); + } + assert (start_a != (uint32_t)-1); + assert (end_a != 0); + + for (cnt = 0; cnt < __LC_LAST; ++cnt) + if (cnt != LC_ALL) + { + if (GET (lb->record[cnt].offset) < start_b) + start_b = GET (lb->record[cnt].offset); + if (GET (lb->record[cnt].offset) + GET (lb->record[cnt].len) > end_b) + end_b = GET (lb->record[cnt].offset) + GET (lb->record[cnt].len); + } + assert (start_b != (uint32_t)-1); + assert (end_b != 0); + + if (start_a != start_b) + return (int)start_a - (int)start_b; + return (int)end_a - (int)end_b; +} + + +/* forward decls for below */ +static uint32_t add_locale (struct locarhandle *ah, const char *name, + locale_data_t data, bool replace); +static void add_alias (struct locarhandle *ah, const char *alias, + bool replace, const char *oldname, + uint32_t *locrec_offset_p); + + +static bool +file_data_available_p (struct locarhandle *ah, uint32_t offset, uint32_t size) +{ + if (offset < ah->mmaped && offset + size <= ah->mmaped) + return true; + + struct stat64 st; + if (fstat64 (ah->fd, &st) != 0) + return false; + + if (st.st_size > ah->reserved) + return false; + + size_t start = ALIGN_DOWN (ah->mmaped, MAP_FIXED_ALIGNMENT); + void *p = mmap64 (ah->addr + start, st.st_size - start, + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, + ah->fd, start); + if (p == MAP_FAILED) + { + ah->mmaped = start; + return false; + } + + ah->mmaped = st.st_size; + return true; +} + + +static int +compare_from_file (struct locarhandle *ah, void *p1, uint32_t offset2, + uint32_t size) +{ + void *p2 = xmalloc (size); + if (pread (ah->fd, p2, size, offset2) != size) + WITH_CUR_LOCALE (error (4, errno, + _("cannot read data from locale archive"))); + + int res = memcmp (p1, p2, size); + free (p2); + return res; +} + + +static void +enlarge_archive (struct locarhandle *ah, const struct locarhead *head) +{ + struct stat64 st; + int fd; + struct locarhead newhead; + size_t total; + unsigned int cnt, loccnt; + struct namehashent *oldnamehashtab; + struct locarhandle new_ah; + size_t prefix_len = output_prefix ? strlen (output_prefix) : 0; + char archivefname[prefix_len + sizeof (ARCHIVE_NAME)]; + char fname[prefix_len + sizeof (ARCHIVE_NAME) + sizeof (".XXXXXX") - 1]; + + if (output_prefix) + memcpy (archivefname, output_prefix, prefix_len); + strcpy (archivefname + prefix_len, ARCHIVE_NAME); + strcpy (stpcpy (fname, archivefname), ".XXXXXX"); + + /* Not all of the old file has to be mapped. Change this now this + we will have to access the whole content. */ + if (fstat64 (ah->fd, &st) != 0) + enomap: + error (EXIT_FAILURE, errno, _("cannot map locale archive file")); + + if (st.st_size < ah->reserved) + ah->addr = mmap64 (ah->addr, st.st_size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FIXED, ah->fd, 0); + else + { + if (ah->mmap_base) + munmap (ah->mmap_base, ah->mmap_len); + else + munmap (ah->addr, ah->reserved); + ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE, + MAP_SHARED, ah->fd, 0); + ah->reserved = st.st_size; + ah->mmap_base = NULL; + ah->mmap_len = 0; + head = ah->addr; + } + if (ah->addr == MAP_FAILED) + goto enomap; + ah->mmaped = st.st_size; + + /* Create a temporary file in the correct directory. */ + fd = mkstemp (fname); + if (fd == -1) + error (EXIT_FAILURE, errno, _("cannot create temporary file: %s"), fname); + + /* Copy the existing head information. */ + newhead = *head; + + /* Create the new archive header. The sizes of the various tables + should be double from what is currently used. */ + SET (newhead.namehash_size, + MAX (next_prime (2 * GET (newhead.namehash_used)), + GET (newhead.namehash_size))); + if (verbose) + printf ("name: size: %u, used: %d, new: size: %u\n", + GET (head->namehash_size), + GET (head->namehash_used), GET (newhead.namehash_size)); + + SET (newhead.string_offset, (GET (newhead.namehash_offset) + + (GET (newhead.namehash_size) + * sizeof (struct namehashent)))); + /* Keep the string table size aligned to 4 bytes, so that + all the struct { uint32_t } types following are happy. */ + SET (newhead.string_size, MAX ((2 * GET (newhead.string_used) + 3) & -4, + GET (newhead.string_size))); + + SET (newhead.locrectab_offset, + GET (newhead.string_offset) + GET (newhead.string_size)); + SET (newhead.locrectab_size, MAX (2 * GET (newhead.locrectab_used), + GET (newhead.locrectab_size))); + + SET (newhead.sumhash_offset, (GET (newhead.locrectab_offset) + + (GET (newhead.locrectab_size) + * sizeof (struct locrecent)))); + SET (newhead.sumhash_size, + MAX (next_prime (2 * GET (newhead.sumhash_used)), + GET (newhead.sumhash_size))); + + total = (GET (newhead.sumhash_offset) + + GET (newhead.sumhash_size) * sizeof (struct sumhashent)); + + /* The new file is empty now. */ + SET (newhead.namehash_used, 0); + SET (newhead.string_used, 0); + SET (newhead.locrectab_used, 0); + SET (newhead.sumhash_used, 0); + + /* Write out the header and create room for the other data structures. */ + if (TEMP_FAILURE_RETRY (write (fd, &newhead, sizeof (newhead))) + != sizeof (newhead)) + { + int errval = errno; + unlink (fname); + error (EXIT_FAILURE, errval, _("cannot initialize archive file")); + } + + if (ftruncate64 (fd, total) != 0) + { + int errval = errno; + unlink (fname); + error (EXIT_FAILURE, errval, _("cannot resize archive file")); + } + + size_t reserved, mmap_len; + int xflags; + void *mmap_base; + void *p = prepare_address_space (fd, total, &reserved, &xflags, &mmap_base, + &mmap_len); + + /* Map the header and all the administration data structures. */ + p = mmap64 (p, total, PROT_READ | PROT_WRITE, MAP_SHARED | xflags, fd, 0); + if (p == MAP_FAILED) + { + int errval = errno; + unlink (fname); + error (EXIT_FAILURE, errval, _("cannot map archive header")); + } + + /* Lock the new file. */ + if (lockf64 (fd, F_LOCK, total) != 0) + { + int errval = errno; + unlink (fname); + error (EXIT_FAILURE, errval, _("cannot lock new archive")); + } + + new_ah.mmaped = total; + new_ah.mmap_base = mmap_base; + new_ah.mmap_len = mmap_len; + new_ah.addr = p; + new_ah.fd = fd; + new_ah.reserved = reserved; + + /* Walk through the hash name hash table to find out what data is + still referenced and transfer it into the new file. */ + oldnamehashtab = (struct namehashent *) ((char *) ah->addr + + GET (head->namehash_offset)); + + /* Sort the old locrec table in order of data position. */ + struct oldlocrecent oldlocrecarray[GET (head->namehash_size)]; + for (cnt = 0, loccnt = 0; cnt < GET (head->namehash_size); ++cnt) + if (GET (oldnamehashtab[cnt].locrec_offset) != 0) + { + oldlocrecarray[loccnt].cnt = cnt; + oldlocrecarray[loccnt++].locrec + = (struct locrecent *) ((char *) ah->addr + + GET (oldnamehashtab[cnt].locrec_offset)); + } + qsort (oldlocrecarray, loccnt, sizeof (struct oldlocrecent), + oldlocrecentcmp); + + uint32_t last_locrec_offset = 0; + for (cnt = 0; cnt < loccnt; ++cnt) + { + /* Insert this entry in the new hash table. */ + locale_data_t old_data; + unsigned int idx; + struct locrecent *oldlocrec = oldlocrecarray[cnt].locrec; + + for (idx = 0; idx < __LC_LAST; ++idx) + if (idx != LC_ALL) + { + old_data[idx].size = GET (oldlocrec->record[idx].len); + old_data[idx].addr + = ((char *) ah->addr + GET (oldlocrec->record[idx].offset)); + + __md5_buffer (old_data[idx].addr, old_data[idx].size, + old_data[idx].sum); + } + + if (cnt > 0 && oldlocrecarray[cnt - 1].locrec == oldlocrec) + { + const char *oldname + = ((char *) ah->addr + + GET (oldnamehashtab[oldlocrecarray[cnt + - 1].cnt].name_offset)); + + add_alias + (&new_ah, + ((char *) ah->addr + + GET (oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset)), + 0, oldname, &last_locrec_offset); + continue; + } + + last_locrec_offset = + add_locale + (&new_ah, + ((char *) ah->addr + + GET (oldnamehashtab[oldlocrecarray[cnt].cnt].name_offset)), + old_data, 0); + if (last_locrec_offset == 0) + error (EXIT_FAILURE, 0, _("cannot extend locale archive file")); + } + + /* Make the file globally readable. */ + if (fchmod (fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1) + { + int errval = errno; + unlink (fname); + error (EXIT_FAILURE, errval, + _("cannot change mode of resized locale archive")); + } + + /* Rename the new file. */ + if (rename (fname, archivefname) != 0) + { + int errval = errno; + unlink (fname); + error (EXIT_FAILURE, errval, _("cannot rename new archive")); + } + + /* Close the old file. */ + close_archive (ah); + + /* Add the information for the new one. */ + *ah = new_ah; +} + + +void +open_archive (struct locarhandle *ah, bool readonly) +{ + struct stat64 st; + struct stat64 st2; + int fd; + struct locarhead head; + int retry = 0; + size_t prefix_len = output_prefix ? strlen (output_prefix) : 0; + char default_fname[prefix_len + sizeof (ARCHIVE_NAME)]; + const char *archivefname = ah->fname; + + /* If ah has a non-NULL fname open that otherwise open the default. */ + if (archivefname == NULL) + { + archivefname = default_fname; + if (output_prefix) + memcpy (default_fname, output_prefix, prefix_len); + strcpy (default_fname + prefix_len, ARCHIVE_NAME); + } + + while (1) + { + /* Open the archive. We must have exclusive write access. */ + fd = open64 (archivefname, readonly ? O_RDONLY : O_RDWR); + if (fd == -1) + { + /* Maybe the file does not yet exist? If we are opening + the default locale archive we ignore the failure and + list an empty archive, otherwise we print an error + and exit. */ + if (errno == ENOENT && archivefname == default_fname) + { + if (readonly) + { + static const struct locarhead nullhead = + { + .namehash_used = 0, + .namehash_offset = 0, + .namehash_size = 0 + }; + + ah->addr = (void *) &nullhead; + ah->fd = -1; + } + else + create_archive (archivefname, ah); + + return; + } + else + error (EXIT_FAILURE, errno, _("cannot open locale archive \"%s\""), + archivefname); + } + + if (fstat64 (fd, &st) < 0) + error (EXIT_FAILURE, errno, _("cannot stat locale archive \"%s\""), + archivefname); + + if (!readonly && lockf64 (fd, F_LOCK, sizeof (struct locarhead)) == -1) + { + close (fd); + + if (retry++ < max_locarchive_open_retry) + { + struct timespec req; + + /* Wait for a bit. */ + req.tv_sec = 0; + req.tv_nsec = 1000000 * (random () % 500 + 1); + (void) nanosleep (&req, NULL); + + continue; + } + + error (EXIT_FAILURE, errno, _("cannot lock locale archive \"%s\""), + archivefname); + } + + /* One more check. Maybe another process replaced the archive file + with a new, larger one since we opened the file. */ + if (stat64 (archivefname, &st2) == -1 + || st.st_dev != st2.st_dev + || st.st_ino != st2.st_ino) + { + (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead)); + close (fd); + continue; + } + + /* Leave the loop. */ + break; + } + + /* Read the header. */ + if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head)) + { + (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead)); + error (EXIT_FAILURE, errno, _("cannot read archive header")); + } + + ah->fd = fd; + ah->mmaped = st.st_size; + + size_t reserved, mmap_len; + int xflags; + void *mmap_base; + void *p = prepare_address_space (fd, st.st_size, &reserved, &xflags, + &mmap_base, &mmap_len); + + /* Map the entire file. We might need to compare the category data + in the file with the newly added data. */ + ah->addr = mmap64 (p, st.st_size, PROT_READ | (readonly ? 0 : PROT_WRITE), + MAP_SHARED | xflags, fd, 0); + if (ah->addr == MAP_FAILED) + { + (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead)); + error (EXIT_FAILURE, errno, _("cannot map archive header")); + } + ah->reserved = reserved; + ah->mmap_base = mmap_base; + ah->mmap_len = mmap_len; +} + + +void +close_archive (struct locarhandle *ah) +{ + if (ah->fd != -1) + { + if (ah->mmap_base) + munmap (ah->mmap_base, ah->mmap_len); + else + munmap (ah->addr, ah->reserved); + close (ah->fd); + } +} + +#include "../../intl/explodename.c" +#include "../../intl/l10nflist.c" + +static struct namehashent * +insert_name (struct locarhandle *ah, + const char *name, size_t name_len, bool replace) +{ + const struct locarhead *const head = ah->addr; + struct namehashent *namehashtab + = (struct namehashent *) ((char *) ah->addr + + GET (head->namehash_offset)); + unsigned int insert_idx, idx, incr; + + /* Hash value of the locale name. */ + uint32_t hval = archive_hashval (name, name_len); + + insert_idx = -1; + idx = hval % GET (head->namehash_size); + incr = 1 + hval % (GET (head->namehash_size) - 2); + + /* If the name_offset field is zero this means this is a + deleted entry and therefore no entry can be found. */ + while (GET (namehashtab[idx].name_offset) != 0) + { + if (GET (namehashtab[idx].hashval) == hval + && (strcmp (name, + (char *) ah->addr + GET (namehashtab[idx].name_offset)) + == 0)) + { + /* Found the entry. */ + if (GET (namehashtab[idx].locrec_offset) != 0 && ! replace) + { + if (! be_quiet) + error (0, 0, _("locale '%s' already exists"), name); + return NULL; + } + + break; + } + + if (GET (namehashtab[idx].hashval) == hval && ! be_quiet) + { + error (0, 0, "hash collision (%u) %s, %s", + hval, name, + (char *) ah->addr + GET (namehashtab[idx].name_offset)); + } + + /* Remember the first place we can insert the new entry. */ + if (GET (namehashtab[idx].locrec_offset) == 0 && insert_idx == -1) + insert_idx = idx; + + idx += incr; + if (idx >= GET (head->namehash_size)) + idx -= GET (head->namehash_size); + } + + /* Add as early as possible. */ + if (insert_idx != -1) + idx = insert_idx; + + SET (namehashtab[idx].hashval, hval); /* no-op if replacing an old entry. */ + return &namehashtab[idx]; +} + +static void +add_alias (struct locarhandle *ah, const char *alias, bool replace, + const char *oldname, uint32_t *locrec_offset_p) +{ + uint32_t locrec_offset = *locrec_offset_p; + struct locarhead *head = ah->addr; + const size_t name_len = strlen (alias); + struct namehashent *namehashent = insert_name (ah, alias, strlen (alias), + replace); + if (namehashent == NULL && ! replace) + return; + + if (GET (namehashent->name_offset) == 0) + { + /* We are adding a new hash entry for this alias. + Determine whether we have to resize the file. */ + if (GET (head->string_used) + name_len + 1 > GET (head->string_size) + || (100 * GET (head->namehash_used) + > 75 * GET (head->namehash_size))) + { + /* The current archive is not large enough. */ + enlarge_archive (ah, head); + + /* The locrecent might have moved, so we have to look up + the old name afresh. */ + namehashent = insert_name (ah, oldname, strlen (oldname), true); + assert (GET (namehashent->name_offset) != 0); + assert (GET (namehashent->locrec_offset) != 0); + *locrec_offset_p = GET (namehashent->locrec_offset); + + /* Tail call to try the whole thing again. */ + add_alias (ah, alias, replace, oldname, locrec_offset_p); + return; + } + + /* Add the name string. */ + memcpy (ah->addr + GET (head->string_offset) + GET (head->string_used), + alias, name_len + 1); + SET (namehashent->name_offset, + GET (head->string_offset) + GET (head->string_used)); + INC (head->string_used, name_len + 1); + + INC (head->namehash_used, 1); + } + + if (GET (namehashent->locrec_offset) != 0) + { + /* Replacing an existing entry. + Mark that we are no longer using the old locrecent. */ + struct locrecent *locrecent + = (struct locrecent *) ((char *) ah->addr + + GET (namehashent->locrec_offset)); + INC (locrecent->refs, -1); + } + + /* Point this entry at the locrecent installed for the main name. */ + SET (namehashent->locrec_offset, locrec_offset); +} + +static int /* qsort comparator used below */ +cmpcategorysize (const void *a, const void *b) +{ + if (*(const void **) a == NULL) + return 1; + if (*(const void **) b == NULL) + return -1; + return ((*(const struct locale_category_data **) a)->size + - (*(const struct locale_category_data **) b)->size); +} + +/* Check the content of the archive for duplicates. Add the content + of the files if necessary. Returns the locrec_offset. */ +static uint32_t +add_locale (struct locarhandle *ah, + const char *name, locale_data_t data, bool replace) +{ + /* First look for the name. If it already exists and we are not + supposed to replace it don't do anything. If it does not exist + we have to allocate a new locale record. */ + size_t name_len = strlen (name); + uint32_t file_offsets[__LC_LAST]; + unsigned int num_new_offsets = 0; + struct sumhashent *sumhashtab; + uint32_t hval; + unsigned int cnt, idx; + struct locarhead *head; + struct namehashent *namehashent; + unsigned int incr; + struct locrecent *locrecent; + off64_t lastoffset; + char *ptr; + struct locale_category_data *size_order[__LC_LAST]; + /* Page size alignment is a minor optimization for locality; use a + common value here rather than making the localedef output depend + on the page size of the system on which localedef is run. See + <https://sourceware.org/glibc/wiki/Development_Todo/Master#Locale_archive_alignment> + for more discussion. */ + const size_t pagesz = 4096; + int small_mask; + + head = ah->addr; + sumhashtab = (struct sumhashent *) ((char *) ah->addr + + GET (head->sumhash_offset)); + + memset (file_offsets, 0, sizeof (file_offsets)); + + size_order[LC_ALL] = NULL; + for (cnt = 0; cnt < __LC_LAST; ++cnt) + if (cnt != LC_ALL) + size_order[cnt] = &data[cnt]; + + /* Sort the array in ascending order of data size. */ + qsort (size_order, __LC_LAST, sizeof size_order[0], cmpcategorysize); + + small_mask = 0; + data[LC_ALL].size = 0; + for (cnt = 0; cnt < __LC_LAST; ++cnt) + if (size_order[cnt] != NULL) + { + const size_t rounded_size = (size_order[cnt]->size + 15) & -16; + if (data[LC_ALL].size + rounded_size > 2 * pagesz) + { + /* This category makes the small-categories block + stop being small, so this is the end of the road. */ + do + size_order[cnt++] = NULL; + while (cnt < __LC_LAST); + break; + } + data[LC_ALL].size += rounded_size; + small_mask |= 1 << (size_order[cnt] - data); + } + + /* Copy the data for all the small categories into the LC_ALL + pseudo-category. */ + + data[LC_ALL].addr = alloca (data[LC_ALL].size); + memset (data[LC_ALL].addr, 0, data[LC_ALL].size); + + ptr = data[LC_ALL].addr; + for (cnt = 0; cnt < __LC_LAST; ++cnt) + if (small_mask & (1 << cnt)) + { + memcpy (ptr, data[cnt].addr, data[cnt].size); + ptr += (data[cnt].size + 15) & -16; + } + __md5_buffer (data[LC_ALL].addr, data[LC_ALL].size, data[LC_ALL].sum); + + /* For each locale category data set determine whether the same data + is already somewhere in the archive. */ + for (cnt = 0; cnt < __LC_LAST; ++cnt) + if (small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt))) + { + ++num_new_offsets; + + /* Compute the hash value of the checksum to determine a + starting point for the search in the MD5 hash value + table. */ + hval = archive_hashval (data[cnt].sum, 16); + + idx = hval % GET (head->sumhash_size); + incr = 1 + hval % (GET (head->sumhash_size) - 2); + + while (GET (sumhashtab[idx].file_offset) != 0) + { + if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0) + { + /* Check the content, there could be a collision of + the hash sum. + + Unfortunately the sumhashent record does not include + the size of the stored data. So we have to search for + it. */ + locrecent + = (struct locrecent *) ((char *) ah->addr + + GET (head->locrectab_offset)); + size_t iloc; + for (iloc = 0; iloc < GET (head->locrectab_used); ++iloc) + if (GET (locrecent[iloc].refs) != 0 + && (GET (locrecent[iloc].record[cnt].offset) + == GET (sumhashtab[idx].file_offset))) + break; + + if (iloc != GET (head->locrectab_used) + && data[cnt].size == GET (locrecent[iloc].record[cnt].len) + /* We have to compare the content. Either we can + have the data mmaped or we have to read from + the file. */ + && (file_data_available_p + (ah, GET (sumhashtab[idx].file_offset), + data[cnt].size) + ? memcmp (data[cnt].addr, + (char *) ah->addr + + GET (sumhashtab[idx].file_offset), + data[cnt].size) == 0 + : compare_from_file (ah, data[cnt].addr, + GET (sumhashtab[idx].file_offset), + data[cnt].size) == 0)) + { + /* Found it. */ + file_offsets[cnt] = GET (sumhashtab[idx].file_offset); + --num_new_offsets; + break; + } + } + + idx += incr; + if (idx >= GET (head->sumhash_size)) + idx -= GET (head->sumhash_size); + } + } + + /* Find a slot for the locale name in the hash table. */ + namehashent = insert_name (ah, name, name_len, replace); + if (namehashent == NULL) /* Already exists and !REPLACE. */ + return 0; + + /* Determine whether we have to resize the file. */ + if ((100 * (GET (head->sumhash_used) + num_new_offsets) + > 75 * GET (head->sumhash_size)) + || (GET (namehashent->locrec_offset) == 0 + && (GET (head->locrectab_used) == GET (head->locrectab_size) + || (GET (head->string_used) + name_len + 1 + > GET (head->string_size)) + || (100 * GET (head->namehash_used) + > 75 * GET (head->namehash_size))))) + { + /* The current archive is not large enough. */ + enlarge_archive (ah, head); + return add_locale (ah, name, data, replace); + } + + /* Add the locale data which is not yet in the archive. */ + for (cnt = 0, lastoffset = 0; cnt < __LC_LAST; ++cnt) + if ((small_mask == 0 ? cnt != LC_ALL : !(small_mask & (1 << cnt))) + && file_offsets[cnt] == 0) + { + /* The data for this section is not yet available in the + archive. Append it. */ + off64_t lastpos; + uint32_t md5hval; + + lastpos = lseek64 (ah->fd, 0, SEEK_END); + if (lastpos == (off64_t) -1) + error (EXIT_FAILURE, errno, _("cannot add to locale archive")); + + /* If block of small categories would cross page boundary, + align it unless it immediately follows a large category. */ + if (cnt == LC_ALL && lastoffset != lastpos + && ((((lastpos & (pagesz - 1)) + data[cnt].size + pagesz - 1) + & -pagesz) + > ((data[cnt].size + pagesz - 1) & -pagesz))) + { + size_t sz = pagesz - (lastpos & (pagesz - 1)); + char *zeros = alloca (sz); + + memset (zeros, 0, sz); + if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, sz) != sz)) + error (EXIT_FAILURE, errno, + _("cannot add to locale archive")); + + lastpos += sz; + } + + /* Align all data to a 16 byte boundary. */ + if ((lastpos & 15) != 0) + { + static const char zeros[15] = { 0, }; + + if (TEMP_FAILURE_RETRY (write (ah->fd, zeros, 16 - (lastpos & 15))) + != 16 - (lastpos & 15)) + error (EXIT_FAILURE, errno, _("cannot add to locale archive")); + + lastpos += 16 - (lastpos & 15); + } + + /* Remember the position. */ + file_offsets[cnt] = lastpos; + lastoffset = lastpos + data[cnt].size; + + /* Write the data. */ + if (TEMP_FAILURE_RETRY (write (ah->fd, data[cnt].addr, data[cnt].size)) + != data[cnt].size) + error (EXIT_FAILURE, errno, _("cannot add to locale archive")); + + /* Add the hash value to the hash table. */ + md5hval = archive_hashval (data[cnt].sum, 16); + + idx = md5hval % GET (head->sumhash_size); + incr = 1 + md5hval % (GET (head->sumhash_size) - 2); + + while (GET (sumhashtab[idx].file_offset) != 0) + { + idx += incr; + if (idx >= GET (head->sumhash_size)) + idx -= GET (head->sumhash_size); + } + + memcpy (sumhashtab[idx].sum, data[cnt].sum, 16); + SET (sumhashtab[idx].file_offset, file_offsets[cnt]); + + INC (head->sumhash_used, 1); + } + + lastoffset = file_offsets[LC_ALL]; + for (cnt = 0; cnt < __LC_LAST; ++cnt) + if (small_mask & (1 << cnt)) + { + file_offsets[cnt] = lastoffset; + lastoffset += (data[cnt].size + 15) & -16; + } + + if (GET (namehashent->name_offset) == 0) + { + /* Add the name string. */ + memcpy ((char *) ah->addr + GET (head->string_offset) + + GET (head->string_used), + name, name_len + 1); + SET (namehashent->name_offset, + GET (head->string_offset) + GET (head->string_used)); + INC (head->string_used, name_len + 1); + INC (head->namehash_used, 1); + } + + if (GET (namehashent->locrec_offset == 0)) + { + /* Allocate a name location record. */ + SET (namehashent->locrec_offset, (GET (head->locrectab_offset) + + (GET (head->locrectab_used) + * sizeof (struct locrecent)))); + INC (head->locrectab_used, 1); + locrecent = (struct locrecent *) ((char *) ah->addr + + GET (namehashent->locrec_offset)); + SET (locrecent->refs, 1); + } + else + { + /* If there are other aliases pointing to this locrecent, + we still need a new one. If not, reuse the old one. */ + + locrecent = (struct locrecent *) ((char *) ah->addr + + GET (namehashent->locrec_offset)); + if (GET (locrecent->refs) > 1) + { + INC (locrecent->refs, -1); + SET (namehashent->locrec_offset, (GET (head->locrectab_offset) + + (GET (head->locrectab_used) + * sizeof (struct locrecent)))); + INC (head->locrectab_used, 1); + locrecent + = (struct locrecent *) ((char *) ah->addr + + GET (namehashent->locrec_offset)); + SET (locrecent->refs, 1); + } + } + + /* Fill in the table with the locations of the locale data. */ + for (cnt = 0; cnt < __LC_LAST; ++cnt) + { + SET (locrecent->record[cnt].offset, file_offsets[cnt]); + SET (locrecent->record[cnt].len, data[cnt].size); + } + + return GET (namehashent->locrec_offset); +} + + +/* Check the content of the archive for duplicates. Add the content + of the files if necessary. Add all the names, possibly overwriting + old files. */ +int +add_locale_to_archive (struct locarhandle *ah, const char *name, + locale_data_t data, bool replace) +{ + char *normalized_name = NULL; + uint32_t locrec_offset; + + /* First analyze the name to decide how to archive it. */ + const char *language; + const char *modifier; + const char *territory; + const char *codeset; + const char *normalized_codeset; + int mask = _nl_explode_name (strdupa (name), + &language, &modifier, &territory, + &codeset, &normalized_codeset); + if (mask == -1) + return -1; + + if (mask & XPG_NORM_CODESET) + /* This name contains a codeset in unnormalized form. + We will store it in the archive with a normalized name. */ + asprintf (&normalized_name, "%s%s%s.%s%s%s", + language, territory == NULL ? "" : "_", territory ?: "", + (mask & XPG_NORM_CODESET) ? normalized_codeset : codeset, + modifier == NULL ? "" : "@", modifier ?: ""); + + /* This call does the main work. */ + locrec_offset = add_locale (ah, normalized_name ?: name, data, replace); + if (locrec_offset == 0) + { + free (normalized_name); + if (mask & XPG_NORM_CODESET) + free ((char *) normalized_codeset); + return -1; + } + + if ((mask & XPG_CODESET) == 0) + { + /* This name lacks a codeset, so determine the locale's codeset and + add an alias for its name with normalized codeset appended. */ + + const struct + { + unsigned int magic; + unsigned int nstrings; + unsigned int strindex[0]; + } *filedata = data[LC_CTYPE].addr; + codeset = (char *) filedata + + maybe_swap_uint32 (filedata->strindex[_NL_ITEM_INDEX + (_NL_CTYPE_CODESET_NAME)]); + char *normalized_codeset_name = NULL; + + normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset)); + mask |= XPG_NORM_CODESET; + + asprintf (&normalized_codeset_name, "%s%s%s.%s%s%s", + language, territory == NULL ? "" : "_", territory ?: "", + normalized_codeset, + modifier == NULL ? "" : "@", modifier ?: ""); + + add_alias (ah, normalized_codeset_name, replace, + normalized_name ?: name, &locrec_offset); + free (normalized_codeset_name); + } + + /* Now read the locale.alias files looking for lines whose + right hand side matches our name after normalization. */ + int result = 0; + if (alias_file != NULL) + { + FILE *fp; + fp = fopen (alias_file, "rm"); + if (fp == NULL) + error (1, errno, _("locale alias file `%s' not found"), + alias_file); + + /* No threads present. */ + __fsetlocking (fp, FSETLOCKING_BYCALLER); + + while (! feof_unlocked (fp)) + { + /* It is a reasonable approach to use a fix buffer here + because + a) we are only interested in the first two fields + b) these fields must be usable as file names and so must + not be that long */ + char buf[BUFSIZ]; + char *alias; + char *value; + char *cp; + + if (fgets_unlocked (buf, BUFSIZ, fp) == NULL) + /* EOF reached. */ + break; + + cp = buf; + /* Ignore leading white space. */ + while (isspace (cp[0]) && cp[0] != '\n') + ++cp; + + /* A leading '#' signals a comment line. */ + if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n') + { + alias = cp++; + while (cp[0] != '\0' && !isspace (cp[0])) + ++cp; + /* Terminate alias name. */ + if (cp[0] != '\0') + *cp++ = '\0'; + + /* Now look for the beginning of the value. */ + while (isspace (cp[0])) + ++cp; + + if (cp[0] != '\0') + { + value = cp++; + while (cp[0] != '\0' && !isspace (cp[0])) + ++cp; + /* Terminate value. */ + if (cp[0] == '\n') + { + /* This has to be done to make the following + test for the end of line possible. We are + looking for the terminating '\n' which do not + overwrite here. */ + *cp++ = '\0'; + *cp = '\n'; + } + else if (cp[0] != '\0') + *cp++ = '\0'; + + /* Does this alias refer to our locale? We will + normalize the right hand side and compare the + elements of the normalized form. */ + { + const char *rhs_language; + const char *rhs_modifier; + const char *rhs_territory; + const char *rhs_codeset; + const char *rhs_normalized_codeset; + int rhs_mask = _nl_explode_name (value, + &rhs_language, + &rhs_modifier, + &rhs_territory, + &rhs_codeset, + &rhs_normalized_codeset); + if (rhs_mask == -1) + { + result = -1; + goto out; + } + if (!strcmp (language, rhs_language) + && ((rhs_mask & XPG_CODESET) + /* He has a codeset, it must match normalized. */ + ? !strcmp ((mask & XPG_NORM_CODESET) + ? normalized_codeset : codeset, + (rhs_mask & XPG_NORM_CODESET) + ? rhs_normalized_codeset : rhs_codeset) + /* He has no codeset, we must also have none. */ + : (mask & XPG_CODESET) == 0) + /* Codeset (or lack thereof) matches. */ + && !strcmp (territory ?: "", rhs_territory ?: "") + && !strcmp (modifier ?: "", rhs_modifier ?: "")) + /* We have a winner. */ + add_alias (ah, alias, replace, + normalized_name ?: name, &locrec_offset); + if (rhs_mask & XPG_NORM_CODESET) + free ((char *) rhs_normalized_codeset); + } + } + } + + /* Possibly not the whole line fits into the buffer. + Ignore the rest of the line. */ + while (strchr (cp, '\n') == NULL) + { + cp = buf; + if (fgets_unlocked (buf, BUFSIZ, fp) == NULL) + /* Make sure the inner loop will be left. The outer + loop will exit at the `feof' test. */ + *cp = '\n'; + } + } + + out: + fclose (fp); + } + + free (normalized_name); + + if (mask & XPG_NORM_CODESET) + free ((char *) normalized_codeset); + + return result; +} + + +int +add_locales_to_archive (size_t nlist, char *list[], bool replace) +{ + struct locarhandle ah; + int result = 0; + + /* Open the archive. This call never returns if we cannot + successfully open the archive. */ + ah.fname = NULL; + open_archive (&ah, false); + + while (nlist-- > 0) + { + const char *fname = *list++; + size_t fnamelen = strlen (fname); + struct stat64 st; + DIR *dirp; + struct dirent64 *d; + int seen; + locale_data_t data; + int cnt; + + if (! be_quiet) + printf (_("Adding %s\n"), fname); + + /* First see whether this really is a directory and whether it + contains all the require locale category files. */ + if (stat64 (fname, &st) < 0) + { + error (0, 0, _("stat of \"%s\" failed: %s: ignored"), fname, + strerror (errno)); + continue; + } + if (!S_ISDIR (st.st_mode)) + { + error (0, 0, _("\"%s\" is no directory; ignored"), fname); + continue; + } + + dirp = opendir (fname); + if (dirp == NULL) + { + error (0, 0, _("cannot open directory \"%s\": %s: ignored"), + fname, strerror (errno)); + continue; + } + + seen = 0; + while ((d = readdir64 (dirp)) != NULL) + { + for (cnt = 0; cnt < __LC_LAST; ++cnt) + if (cnt != LC_ALL) + if (strcmp (d->d_name, locnames[cnt]) == 0) + { + unsigned char d_type; + + /* We have an object of the required name. If it's + a directory we have to look at a file with the + prefix "SYS_". Otherwise we have found what we + are looking for. */ +#ifdef _DIRENT_HAVE_D_TYPE + d_type = d->d_type; + + if (d_type != DT_REG) +#endif + { + char fullname[fnamelen + 2 * strlen (d->d_name) + 7]; + +#ifdef _DIRENT_HAVE_D_TYPE + if (d_type == DT_UNKNOWN) +#endif + { + strcpy (stpcpy (stpcpy (fullname, fname), "/"), + d->d_name); + + if (stat64 (fullname, &st) == -1) + /* We cannot stat the file, ignore it. */ + break; + + d_type = IFTODT (st.st_mode); + } + + if (d_type == DT_DIR) + { + /* We have to do more tests. The file is a + directory and it therefore must contain a + regular file with the same name except a + "SYS_" prefix. */ + char *t = stpcpy (stpcpy (fullname, fname), "/"); + strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"), + d->d_name); + + if (stat64 (fullname, &st) == -1) + /* There is no SYS_* file or we cannot + access it. */ + break; + + d_type = IFTODT (st.st_mode); + } + } + + /* If we found a regular file (eventually after + following a symlink) we are successful. */ + if (d_type == DT_REG) + ++seen; + break; + } + } + + closedir (dirp); + + if (seen != __LC_LAST - 1) + { + /* We don't have all locale category files. Ignore the name. */ + error (0, 0, _("incomplete set of locale files in \"%s\""), + fname); + continue; + } + + /* Add the files to the archive. To do this we first compute + sizes and the MD5 sums of all the files. */ + for (cnt = 0; cnt < __LC_LAST; ++cnt) + if (cnt != LC_ALL) + { + char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7]; + int fd; + + strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]); + fd = open64 (fullname, O_RDONLY); + if (fd == -1 || fstat64 (fd, &st) == -1) + { + /* Cannot read the file. */ + if (fd != -1) + close (fd); + break; + } + + if (S_ISDIR (st.st_mode)) + { + char *t; + close (fd); + t = stpcpy (stpcpy (fullname, fname), "/"); + strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"), + locnames[cnt]); + + fd = open64 (fullname, O_RDONLY); + if (fd == -1 || fstat64 (fd, &st) == -1 + || !S_ISREG (st.st_mode)) + { + if (fd != -1) + close (fd); + break; + } + } + + /* Map the file. */ + data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED, + fd, 0); + if (data[cnt].addr == MAP_FAILED) + { + /* Cannot map it. */ + close (fd); + break; + } + + data[cnt].size = st.st_size; + __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum); + + /* We don't need the file descriptor anymore. */ + close (fd); + } + + if (cnt != __LC_LAST) + { + while (cnt-- > 0) + if (cnt != LC_ALL) + munmap (data[cnt].addr, data[cnt].size); + + error (0, 0, _("cannot read all files in \"%s\": ignored"), fname); + + continue; + } + + result |= add_locale_to_archive (&ah, basename (fname), data, replace); + + for (cnt = 0; cnt < __LC_LAST; ++cnt) + if (cnt != LC_ALL) + munmap (data[cnt].addr, data[cnt].size); + } + + /* We are done. */ + close_archive (&ah); + + return result; +} + + +int +delete_locales_from_archive (size_t nlist, char *list[]) +{ + struct locarhandle ah; + struct locarhead *head; + struct namehashent *namehashtab; + + /* Open the archive. This call never returns if we cannot + successfully open the archive. */ + ah.fname = NULL; + open_archive (&ah, false); + + head = ah.addr; + namehashtab = (struct namehashent *) ((char *) ah.addr + + GET (head->namehash_offset)); + + while (nlist-- > 0) + { + const char *locname = *list++; + uint32_t hval; + unsigned int idx; + unsigned int incr; + + /* Search for this locale in the archive. */ + hval = archive_hashval (locname, strlen (locname)); + + idx = hval % GET (head->namehash_size); + incr = 1 + hval % (GET (head->namehash_size) - 2); + + /* If the name_offset field is zero this means this is no + deleted entry and therefore no entry can be found. */ + while (GET (namehashtab[idx].name_offset) != 0) + { + if (GET (namehashtab[idx].hashval) == hval + && (strcmp (locname, + ((char *) ah.addr + + GET (namehashtab[idx].name_offset))) + == 0)) + { + /* Found the entry. Now mark it as removed by zero-ing + the reference to the locale record. */ + SET (namehashtab[idx].locrec_offset, 0); + break; + } + + idx += incr; + if (idx >= GET (head->namehash_size)) + idx -= GET (head->namehash_size); + } + + if (GET (namehashtab[idx].name_offset) == 0 && ! be_quiet) + error (0, 0, _("locale \"%s\" not in archive"), locname); + } + + close_archive (&ah); + + return 0; +} + + +struct nameent +{ + char *name; + uint32_t locrec_offset; +}; + + +struct dataent +{ + const unsigned char *sum; + uint32_t file_offset; + uint32_t nlink; +}; + + +static int +nameentcmp (const void *a, const void *b) +{ + return strcmp (((const struct nameent *) a)->name, + ((const struct nameent *) b)->name); +} + + +static int +dataentcmp (const void *a, const void *b) +{ + if (((const struct dataent *) a)->file_offset + < ((const struct dataent *) b)->file_offset) + return -1; + + if (((const struct dataent *) a)->file_offset + > ((const struct dataent *) b)->file_offset) + return 1; + + return 0; +} + + +void +show_archive_content (const char *fname, int verbose) +{ + struct locarhandle ah; + struct locarhead *head; + struct namehashent *namehashtab; + struct nameent *names; + size_t cnt, used; + + /* Open the archive. This call never returns if we cannot + successfully open the archive. */ + ah.fname = fname; + open_archive (&ah, true); + + head = ah.addr; + + names = (struct nameent *) xmalloc (GET (head->namehash_used) + * sizeof (struct nameent)); + + namehashtab = (struct namehashent *) ((char *) ah.addr + + GET (head->namehash_offset)); + for (cnt = used = 0; cnt < GET (head->namehash_size); ++cnt) + if (GET (namehashtab[cnt].locrec_offset) != 0) + { + assert (used < GET (head->namehash_used)); + names[used].name = ah.addr + GET (namehashtab[cnt].name_offset); + names[used++].locrec_offset = GET (namehashtab[cnt].locrec_offset); + } + + /* Sort the names. */ + qsort (names, used, sizeof (struct nameent), nameentcmp); + + if (verbose) + { + struct dataent *files; + struct sumhashent *sumhashtab; + int sumused; + + files = (struct dataent *) xmalloc (GET (head->sumhash_used) + * sizeof (struct dataent)); + + sumhashtab = (struct sumhashent *) ((char *) ah.addr + + GET (head->sumhash_offset)); + for (cnt = sumused = 0; cnt < GET (head->sumhash_size); ++cnt) + if (GET (sumhashtab[cnt].file_offset) != 0) + { + assert (sumused < GET (head->sumhash_used)); + files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum; + files[sumused].file_offset = GET (sumhashtab[cnt].file_offset); + files[sumused++].nlink = 0; + } + + /* Sort by file locations. */ + qsort (files, sumused, sizeof (struct dataent), dataentcmp); + + /* Compute nlink fields. */ + for (cnt = 0; cnt < used; ++cnt) + { + struct locrecent *locrec; + int idx; + + locrec = (struct locrecent *) ((char *) ah.addr + + names[cnt].locrec_offset); + for (idx = 0; idx < __LC_LAST; ++idx) + if (GET (locrec->record[LC_ALL].offset) != 0 + ? (idx == LC_ALL + || (GET (locrec->record[idx].offset) + < GET (locrec->record[LC_ALL].offset)) + || ((GET (locrec->record[idx].offset) + + GET (locrec->record[idx].len)) + > (GET (locrec->record[LC_ALL].offset) + + GET (locrec->record[LC_ALL].len)))) + : idx != LC_ALL) + { + struct dataent *data, dataent; + + dataent.file_offset = GET (locrec->record[idx].offset); + data = (struct dataent *) bsearch (&dataent, files, sumused, + sizeof (struct dataent), + dataentcmp); + assert (data != NULL); + ++data->nlink; + } + } + + /* Print it. */ + for (cnt = 0; cnt < used; ++cnt) + { + struct locrecent *locrec; + int idx, i; + + locrec = (struct locrecent *) ((char *) ah.addr + + names[cnt].locrec_offset); + for (idx = 0; idx < __LC_LAST; ++idx) + if (idx != LC_ALL) + { + struct dataent *data, dataent; + + dataent.file_offset = GET (locrec->record[idx].offset); + if (GET (locrec->record[LC_ALL].offset) != 0 + && (dataent.file_offset + >= GET (locrec->record[LC_ALL].offset)) + && (dataent.file_offset + GET (locrec->record[idx].len) + <= (GET (locrec->record[LC_ALL].offset) + + GET (locrec->record[LC_ALL].len)))) + dataent.file_offset = GET (locrec->record[LC_ALL].offset); + + data = (struct dataent *) bsearch (&dataent, files, sumused, + sizeof (struct dataent), + dataentcmp); + printf ("%6d %7x %3d%c ", + GET (locrec->record[idx].len), + GET (locrec->record[idx].offset), + data->nlink, + (dataent.file_offset + == GET (locrec->record[LC_ALL].offset)) + ? '+' : ' '); + for (i = 0; i < 16; i += 4) + printf ("%02x%02x%02x%02x", + data->sum[i], data->sum[i + 1], + data->sum[i + 2], data->sum[i + 3]); + printf (" %s/%s\n", names[cnt].name, + idx == LC_MESSAGES ? "LC_MESSAGES/SYS_LC_MESSAGES" + : locnames[idx]); + } + } + } + else + for (cnt = 0; cnt < used; ++cnt) + puts (names[cnt].name); + + close_archive (&ah); + + exit (EXIT_SUCCESS); +} |