/* Copyright (C) 2002 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Ulrich Drepper , 2002. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../crypt/md5.h" #include "../localeinfo.h" #include "../locarchive.h" #include "simple-hash.h" #include "localedef.h" extern const char *output_prefix; #define ARCHIVE_NAME LOCALEDIR "/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_NANES 450 #define INITIAL_SIZE_STRINGS 3500 #define INITIAL_NUM_LOCREC 350 #define INITIAL_NUM_SUMS 2000 static void create_archive (const char *archivefname, struct locarhandle *ah) { int fd; char fname[strlen (archivefname) + sizeof (".XXXXXX")]; struct locarhead head; void *p; 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")); /* Create the initial content of the archive. */ head.magic = AR_MAGIC; head.namehash_offset = sizeof (struct locarhead); head.namehash_used = 0; head.namehash_size = next_prime (INITIAL_NUM_NANES); head.string_offset = (head.namehash_offset + head.namehash_size * sizeof (struct namehashent)); head.string_used = 0; head.string_size = INITIAL_SIZE_STRINGS; head.locrectab_offset = head.string_offset + head.string_size; head.locrectab_used = 0; head.locrectab_size = INITIAL_NUM_LOCREC; head.sumhash_offset = (head.locrectab_offset + head.locrectab_size * sizeof (struct locrecent)); head.sumhash_used = 0; head.sumhash_size = next_prime (INITIAL_NUM_SUMS); total = head.sumhash_offset + 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")); } /* Map the header and all the administration data structures. */ p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, 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->fd = fd; ah->addr = p; ah->len = total; } static void enlarge_archive (struct locarhandle *ah, const struct locarhead *head) { struct stat64 st; int fd; struct locarhead newhead; size_t total; void *p; unsigned int cnt; struct namehashent *oldnamehashtab; struct locrecent *oldlocrectab; 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 || (ah->addr = mmap64 (NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, ah->fd, 0)) == MAP_FAILED) error (EXIT_FAILURE, errno, _("cannot map locale archive file")); ah->len = 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")); /* 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. */ newhead.namehash_size = MAX (next_prime (2 * newhead.namehash_used), newhead.namehash_size); printf ("name: size: %u, used: %d, new: size: %u\n", head->namehash_size, head->namehash_used, newhead.namehash_size); newhead.string_offset = (newhead.namehash_offset + (newhead.namehash_size * sizeof (struct namehashent))); newhead.string_size = MAX (2 * newhead.string_used, newhead.string_size); newhead.locrectab_offset = newhead.string_offset + newhead.string_size; newhead.locrectab_size = MAX (2 * newhead.locrectab_used, newhead.locrectab_size); newhead.sumhash_offset = (newhead.locrectab_offset + (newhead.locrectab_size * sizeof (struct locrecent))); newhead.sumhash_size = MAX (next_prime (2 * newhead.sumhash_used), newhead.sumhash_size); total = (newhead.sumhash_offset + newhead.sumhash_size * sizeof (struct sumhashent)); /* The new file is empty now. */ newhead.namehash_used = 0; newhead.string_used = 0; newhead.locrectab_used = 0; 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")); } /* Map the header and all the administration data structures. */ p = mmap64 (NULL, total, PROT_READ | PROT_WRITE, MAP_SHARED, 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.len = total; new_ah.addr = p; new_ah.fd = fd; /* 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 + head->namehash_offset); oldlocrectab = (struct locrecent *) ((char *) ah->addr + head->locrectab_offset); for (cnt = 0; cnt < head->namehash_size; ++cnt) if (oldnamehashtab[cnt].locrec_offset != 0) { /* Insert this entry in the new hash table. */ locale_data_t old_data; unsigned int idx; struct locrecent *oldlocrec; oldlocrec = (struct locrecent *) ((char *) ah->addr + oldnamehashtab[cnt].locrec_offset); for (idx = 0; idx < __LC_LAST; ++idx) if (idx != LC_ALL) { old_data[idx].size = oldlocrec->record[idx].len; old_data[idx].addr = ((char *) ah->addr + oldlocrec->record[idx].offset); __md5_buffer (old_data[idx].addr, old_data[idx].size, old_data[idx].sum); } if (add_locale_to_archive (&new_ah, ((char *) ah->addr + oldnamehashtab[cnt].name_offset), old_data, 0) != 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 archivefname[prefix_len + sizeof (ARCHIVE_NAME)]; if (output_prefix) memcpy (archivefname, output_prefix, prefix_len); strcpy (archivefname + prefix_len, ARCHIVE_NAME); again: /* 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 (errno == ENOENT) { 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); goto again; } 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); goto again; } /* 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->len = (head.sumhash_offset + head.sumhash_size * sizeof (struct sumhashent)); /* Now we know how large the administrative information part is. Map all of it. */ ah->addr = mmap64 (NULL, ah->len, PROT_READ | (readonly ? 0 : PROT_WRITE), MAP_SHARED, fd, 0); if (ah->addr == MAP_FAILED) { (void) lockf64 (fd, F_ULOCK, sizeof (struct locarhead)); error (EXIT_FAILURE, errno, _("cannot map archive header")); } } void close_archive (struct locarhandle *ah) { if (ah->fd != -1) { munmap (ah->addr, ah->len); close (ah->fd); } } /* 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 (ah, name, data, replace) 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; unsigned int idx; unsigned int insert_idx; struct locarhead *head; struct namehashent *namehashtab; struct namehashent *namehashent; unsigned int incr; struct locrecent *locrecent; head = ah->addr; sumhashtab = (struct sumhashent *) ((char *) ah->addr + head->sumhash_offset); namehashtab = (struct namehashent *) ((char *) ah->addr + head->namehash_offset); /* 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 (cnt != LC_ALL) { /* By default signal that we have no data. */ file_offsets[cnt] = 0; ++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 = compute_hashval (data[cnt].sum, 16); idx = hval % head->sumhash_size; incr = 1 + hval % (head->sumhash_size - 2); while (sumhashtab[idx].file_offset != 0) { if (memcmp (data[cnt].sum, sumhashtab[idx].sum, 16) == 0) { /* Found it. */ file_offsets[cnt] = sumhashtab[idx].file_offset; --num_new_offsets; break; } idx += incr; if (idx >= head->sumhash_size) idx -= head->sumhash_size; } } /* Hash value of the locale name. */ hval = compute_hashval (name, name_len); insert_idx = -1; idx = hval % head->namehash_size; incr = 1 + hval % (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 (namehashtab[idx].name_offset != 0) { if (namehashtab[idx].hashval == hval && strcmp (name, (char *) ah->addr + namehashtab[idx].name_offset) == 0) { /* Found the entry. */ if (namehashtab[idx].locrec_offset != 0 && ! replace) { if (! be_quiet) error (0, 0, _("locale '%s' already exists"), name); return 1; } break; } /* Remember the first place we can insert the new entry. */ if (namehashtab[idx].locrec_offset == 0 && insert_idx == -1) insert_idx = idx; idx += incr; if (idx >= head->namehash_size) idx -= head->namehash_size; } /* Add as early as possible. */ if (insert_idx != -1) idx = insert_idx; namehashent = &namehashtab[idx]; /* Determine whether we have to resize the file. */ if (100 * (head->sumhash_used + num_new_offsets) > 75 * head->sumhash_size || (namehashent->locrec_offset == 0 && (head->locrectab_used == head->locrectab_size || head->string_used + name_len + 1 > head->string_size || 100 * head->namehash_used > 75 * head->namehash_size))) { /* The current archive is not large enough. */ enlarge_archive (ah, head); return add_locale_to_archive (ah, name, data, replace); } /* Add the locale data which is not yet in the archive. */ for (cnt = 0; cnt < __LC_LAST; ++cnt) if (cnt != LC_ALL && 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")); /* 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; /* 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 = compute_hashval (data[cnt].sum, 16); idx = md5hval % head->sumhash_size; incr = 1 + md5hval % (head->sumhash_size - 2); while (sumhashtab[idx].file_offset != 0) { idx += incr; if (idx >= head->sumhash_size) idx -= head->sumhash_size; } memcpy (sumhashtab[idx].sum, data[cnt].sum, 16); sumhashtab[idx].file_offset = file_offsets[cnt]; ++head->sumhash_used; } if (namehashent->locrec_offset == 0) { /* Add the name string. */ memcpy ((char *) ah->addr + head->string_offset + head->string_used, name, name_len + 1); namehashent->name_offset = head->string_offset + head->string_used; head->string_used += name_len + 1; /* Allocate a name location record. */ namehashent->locrec_offset = (head->locrectab_offset + (head->locrectab_used++ * sizeof (struct locrecent))); namehashent->hashval = hval; ++head->namehash_used; } /* Fill in the table with the locations of the locale data. */ locrecent = (struct locrecent *) ((char *) ah->addr + namehashent->locrec_offset); for (cnt = 0; cnt < __LC_LAST; ++cnt) if (cnt != LC_ALL) { locrecent->record[cnt].offset = file_offsets[cnt]; locrecent->record[cnt].len = data[cnt].size; } /* Read the locale.alias file to see whether any matching record is found. If an entry is available check whether it is already in the archive. If this is the case check whether the new locale's name is more specific than the one currently referred to by the alias. */ return 0; } int add_locales_to_archive (nlist, list, replace) 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. */ 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 (nlist, list) 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. */ open_archive (&ah, false); head = ah.addr; namehashtab = (struct namehashent *) ((char *) ah.addr + 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 = compute_hashval (locname, strlen (locname)); idx = hval % head->namehash_size; incr = 1 + hval % (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 (namehashtab[idx].name_offset != 0) { if (namehashtab[idx].hashval == hval && (strcmp (locname, (char *) ah.addr + namehashtab[idx].name_offset) == 0)) { /* Found the entry. Now mark it as removed by zero-ing the reference to the locale record. */ namehashtab[idx].locrec_offset = 0; --head->namehash_used; break; } idx += incr; if (idx >= head->namehash_size) idx -= head->namehash_size; } if (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 (int verbose) { struct locarhandle ah; struct locarhead *head; struct namehashent *namehashtab; struct nameent *names; int cnt; int used; /* Open the archive. This call never returns if we cannot successfully open the archive. */ open_archive (&ah, true); head = ah.addr; names = (struct nameent *) xmalloc (head->namehash_used * sizeof (struct nameent)); namehashtab = (struct namehashent *) ((char *) ah.addr + head->namehash_offset); for (cnt = used = 0; cnt < head->namehash_size; ++cnt) if (namehashtab[cnt].locrec_offset != 0) { assert (used < head->namehash_used); names[used].name = ah.addr + namehashtab[cnt].name_offset; names[used++].locrec_offset = 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 (head->sumhash_used * sizeof (struct sumhashent)); sumhashtab = (struct sumhashent *) ((char *) ah.addr + head->sumhash_offset); for (cnt = sumused = 0; cnt < head->sumhash_size; ++cnt) if (sumhashtab[cnt].file_offset != 0) { assert (sumused < head->sumhash_used); files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum; files[sumused].file_offset = 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 (idx != LC_ALL) { struct dataent *data, dataent; dataent.file_offset = 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 = locrec->record[idx].offset; data = (struct dataent *) bsearch (&dataent, files, sumused, sizeof (struct dataent), dataentcmp); printf ("%6d %7x %3d ", locrec->record[idx].len, locrec->record[idx].offset, data->nlink); 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); }