| /* Copyright (C) 1999-2014 Free Software Foundation, Inc. |
| This file is part of the GNU C Library. |
| Contributed by Andreas Jaeger <aj@suse.de>, 1999. |
| |
| 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/>. */ |
| |
| #include <errno.h> |
| #include <error.h> |
| #include <dirent.h> |
| #include <inttypes.h> |
| #include <libgen.h> |
| #include <libintl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <stdint.h> |
| #include <sys/fcntl.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <ldconfig.h> |
| #include <dl-cache.h> |
| |
| struct cache_entry |
| { |
| char *lib; /* Library name. */ |
| char *path; /* Path to find library. */ |
| int flags; /* Flags to indicate kind of library. */ |
| unsigned int osversion; /* Required OS version. */ |
| uint64_t hwcap; /* Important hardware capabilities. */ |
| int bits_hwcap; /* Number of bits set in hwcap. */ |
| struct cache_entry *next; /* Next entry in list. */ |
| }; |
| |
| /* List of all cache entries. */ |
| static struct cache_entry *entries; |
| |
| static const char *flag_descr[] = |
| { "libc4", "ELF", "libc5", "libc6"}; |
| |
| /* Print a single entry. */ |
| static void |
| print_entry (const char *lib, int flag, unsigned int osversion, |
| uint64_t hwcap, const char *key) |
| { |
| printf ("\t%s (", lib); |
| switch (flag & FLAG_TYPE_MASK) |
| { |
| case FLAG_LIBC4: |
| case FLAG_ELF: |
| case FLAG_ELF_LIBC5: |
| case FLAG_ELF_LIBC6: |
| fputs (flag_descr[flag & FLAG_TYPE_MASK], stdout); |
| break; |
| default: |
| fputs (_("unknown"), stdout); |
| break; |
| } |
| switch (flag & FLAG_REQUIRED_MASK) |
| { |
| case FLAG_SPARC_LIB64: |
| fputs (",64bit", stdout); |
| break; |
| case FLAG_IA64_LIB64: |
| fputs (",IA-64", stdout); |
| break; |
| case FLAG_X8664_LIB64: |
| fputs (",x86-64", stdout); |
| break; |
| case FLAG_S390_LIB64: |
| fputs (",64bit", stdout); |
| break; |
| case FLAG_POWERPC_LIB64: |
| fputs (",64bit", stdout); |
| break; |
| case FLAG_MIPS64_LIBN32: |
| fputs (",N32", stdout); |
| break; |
| case FLAG_MIPS64_LIBN64: |
| fputs (",64bit", stdout); |
| break; |
| case FLAG_X8664_LIBX32: |
| fputs (",x32", stdout); |
| break; |
| case FLAG_ARM_LIBHF: |
| fputs (",hard-float", stdout); |
| break; |
| case FLAG_AARCH64_LIB64: |
| fputs (",AArch64", stdout); |
| break; |
| /* Uses the ARM soft-float ABI. */ |
| case FLAG_ARM_LIBSF: |
| fputs (",soft-float", stdout); |
| break; |
| case FLAG_MIPS_LIB32_NAN2008: |
| fputs (",nan2008", stdout); |
| break; |
| case FLAG_MIPS64_LIBN32_NAN2008: |
| fputs (",N32,nan2008", stdout); |
| break; |
| case FLAG_MIPS64_LIBN64_NAN2008: |
| fputs (",64bit,nan2008", stdout); |
| break; |
| case 0: |
| break; |
| default: |
| printf (",%d", flag & FLAG_REQUIRED_MASK); |
| break; |
| } |
| if (hwcap != 0) |
| printf (", hwcap: %#.16" PRIx64, hwcap); |
| if (osversion != 0) |
| { |
| static const char *const abi_tag_os[] = |
| { |
| [0] = "Linux", |
| [1] = "Hurd", |
| [2] = "Solaris", |
| [3] = "FreeBSD", |
| [4] = "kNetBSD", |
| [5] = "Syllable", |
| [6] = N_("Unknown OS") |
| }; |
| #define MAXTAG (sizeof abi_tag_os / sizeof abi_tag_os[0] - 1) |
| unsigned int os = osversion >> 24; |
| |
| printf (_(", OS ABI: %s %d.%d.%d"), |
| _(abi_tag_os[os > MAXTAG ? MAXTAG : os]), |
| (osversion >> 16) & 0xff, |
| (osversion >> 8) & 0xff, |
| osversion & 0xff); |
| } |
| printf (") => %s\n", key); |
| } |
| |
| |
| /* Print the whole cache file, if a file contains the new cache format |
| hidden in the old one, print the contents of the new format. */ |
| void |
| print_cache (const char *cache_name) |
| { |
| int fd = open (cache_name, O_RDONLY); |
| if (fd < 0) |
| error (EXIT_FAILURE, errno, _("Can't open cache file %s\n"), cache_name); |
| |
| struct stat64 st; |
| if (fstat64 (fd, &st) < 0 |
| /* No need to map the file if it is empty. */ |
| || st.st_size == 0) |
| { |
| close (fd); |
| return; |
| } |
| |
| struct cache_file *cache |
| = mmap (NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); |
| if (cache == MAP_FAILED) |
| error (EXIT_FAILURE, errno, _("mmap of cache file failed.\n")); |
| |
| size_t cache_size = st.st_size; |
| if (cache_size < sizeof (struct cache_file)) |
| error (EXIT_FAILURE, 0, _("File is not a cache file.\n")); |
| |
| struct cache_file_new *cache_new = NULL; |
| const char *cache_data; |
| int format = 0; |
| |
| if (memcmp (cache->magic, CACHEMAGIC, sizeof CACHEMAGIC - 1)) |
| { |
| /* This can only be the new format without the old one. */ |
| cache_new = (struct cache_file_new *) cache; |
| |
| if (memcmp (cache_new->magic, CACHEMAGIC_NEW, sizeof CACHEMAGIC_NEW - 1) |
| || memcmp (cache_new->version, CACHE_VERSION, |
| sizeof CACHE_VERSION - 1)) |
| error (EXIT_FAILURE, 0, _("File is not a cache file.\n")); |
| format = 1; |
| /* This is where the strings start. */ |
| cache_data = (const char *) cache_new; |
| } |
| else |
| { |
| size_t offset = ALIGN_CACHE (sizeof (struct cache_file) |
| + (cache->nlibs |
| * sizeof (struct file_entry))); |
| /* This is where the strings start. */ |
| cache_data = (const char *) &cache->libs[cache->nlibs]; |
| |
| /* Check for a new cache embedded in the old format. */ |
| if (cache_size > |
| (offset + sizeof (struct cache_file_new))) |
| { |
| |
| cache_new = (struct cache_file_new *) ((void *)cache + offset); |
| |
| if (memcmp (cache_new->magic, CACHEMAGIC_NEW, |
| sizeof CACHEMAGIC_NEW - 1) == 0 |
| && memcmp (cache_new->version, CACHE_VERSION, |
| sizeof CACHE_VERSION - 1) == 0) |
| { |
| cache_data = (const char *) cache_new; |
| format = 1; |
| } |
| } |
| } |
| |
| if (format == 0) |
| { |
| printf (_("%d libs found in cache `%s'\n"), cache->nlibs, cache_name); |
| |
| /* Print everything. */ |
| for (unsigned int i = 0; i < cache->nlibs; i++) |
| print_entry (cache_data + cache->libs[i].key, |
| cache->libs[i].flags, 0, 0, |
| cache_data + cache->libs[i].value); |
| } |
| else if (format == 1) |
| { |
| printf (_("%d libs found in cache `%s'\n"), |
| cache_new->nlibs, cache_name); |
| |
| /* Print everything. */ |
| for (unsigned int i = 0; i < cache_new->nlibs; i++) |
| print_entry (cache_data + cache_new->libs[i].key, |
| cache_new->libs[i].flags, |
| cache_new->libs[i].osversion, |
| cache_new->libs[i].hwcap, |
| cache_data + cache_new->libs[i].value); |
| } |
| /* Cleanup. */ |
| munmap (cache, cache_size); |
| close (fd); |
| } |
| |
| /* Initialize cache data structures. */ |
| void |
| init_cache (void) |
| { |
| entries = NULL; |
| } |
| |
| static int |
| compare (const struct cache_entry *e1, const struct cache_entry *e2) |
| { |
| /* We need to swap entries here to get the correct sort order. */ |
| int res = _dl_cache_libcmp (e2->lib, e1->lib); |
| if (res == 0) |
| { |
| if (e1->flags < e2->flags) |
| return 1; |
| else if (e1->flags > e2->flags) |
| return -1; |
| /* Sort by most specific hwcap. */ |
| else if (e2->bits_hwcap > e1->bits_hwcap) |
| return 1; |
| else if (e2->bits_hwcap < e1->bits_hwcap) |
| return -1; |
| else if (e2->hwcap > e1->hwcap) |
| return 1; |
| else if (e2->hwcap < e1->hwcap) |
| return -1; |
| if (e2->osversion > e1->osversion) |
| return 1; |
| if (e2->osversion < e1->osversion) |
| return -1; |
| } |
| return res; |
| } |
| |
| /* Save the contents of the cache. */ |
| void |
| save_cache (const char *cache_name) |
| { |
| /* The cache entries are sorted already, save them in this order. */ |
| |
| /* Count the length of all strings. */ |
| /* The old format doesn't contain hwcap entries and doesn't contain |
| libraries in subdirectories with hwcaps entries. Count therefore |
| also all entries with hwcap == 0. */ |
| size_t total_strlen = 0; |
| struct cache_entry *entry; |
| /* Number of cache entries. */ |
| int cache_entry_count = 0; |
| /* Number of normal cache entries. */ |
| int cache_entry_old_count = 0; |
| |
| for (entry = entries; entry != NULL; entry = entry->next) |
| { |
| /* Account the final NULs. */ |
| total_strlen += strlen (entry->lib) + strlen (entry->path) + 2; |
| ++cache_entry_count; |
| if (entry->hwcap == 0) |
| ++cache_entry_old_count; |
| } |
| |
| /* Create the on disk cache structure. */ |
| struct cache_file *file_entries = NULL; |
| size_t file_entries_size = 0; |
| |
| if (opt_format != 2) |
| { |
| /* struct cache_file_new is 64-bit aligned on some arches while |
| only 32-bit aligned on other arches. Duplicate last old |
| cache entry so that new cache in ld.so.cache can be used by |
| both. */ |
| if (opt_format != 0) |
| cache_entry_old_count = (cache_entry_old_count + 1) & ~1; |
| |
| /* And the list of all entries in the old format. */ |
| file_entries_size = sizeof (struct cache_file) |
| + cache_entry_old_count * sizeof (struct file_entry); |
| file_entries = xmalloc (file_entries_size); |
| |
| /* Fill in the header. */ |
| memset (file_entries, '\0', sizeof (struct cache_file)); |
| memcpy (file_entries->magic, CACHEMAGIC, sizeof CACHEMAGIC - 1); |
| |
| file_entries->nlibs = cache_entry_old_count; |
| } |
| |
| struct cache_file_new *file_entries_new = NULL; |
| size_t file_entries_new_size = 0; |
| |
| if (opt_format != 0) |
| { |
| /* And the list of all entries in the new format. */ |
| file_entries_new_size = sizeof (struct cache_file_new) |
| + cache_entry_count * sizeof (struct file_entry_new); |
| file_entries_new = xmalloc (file_entries_new_size); |
| |
| /* Fill in the header. */ |
| memset (file_entries_new, '\0', sizeof (struct cache_file_new)); |
| memcpy (file_entries_new->magic, CACHEMAGIC_NEW, |
| sizeof CACHEMAGIC_NEW - 1); |
| memcpy (file_entries_new->version, CACHE_VERSION, |
| sizeof CACHE_VERSION - 1); |
| |
| file_entries_new->nlibs = cache_entry_count; |
| file_entries_new->len_strings = total_strlen; |
| } |
| |
| /* Pad for alignment of cache_file_new. */ |
| size_t pad = ALIGN_CACHE (file_entries_size) - file_entries_size; |
| |
| /* If we have both formats, we hide the new format in the strings |
| table, we have to adjust all string indices for this so that |
| old libc5/glibc 2 dynamic linkers just ignore them. */ |
| unsigned int str_offset; |
| if (opt_format != 0) |
| str_offset = file_entries_new_size; |
| else |
| str_offset = 0; |
| |
| /* An array for all strings. */ |
| char *strings = xmalloc (total_strlen); |
| char *str = strings; |
| int idx_old; |
| int idx_new; |
| |
| for (idx_old = 0, idx_new = 0, entry = entries; entry != NULL; |
| entry = entry->next, ++idx_new) |
| { |
| /* First the library. */ |
| if (opt_format != 2 && entry->hwcap == 0) |
| { |
| file_entries->libs[idx_old].flags = entry->flags; |
| /* XXX: Actually we can optimize here and remove duplicates. */ |
| file_entries->libs[idx_old].key = str_offset + pad; |
| } |
| if (opt_format != 0) |
| { |
| /* We could subtract file_entries_new_size from str_offset - |
| not doing so makes the code easier, the string table |
| always begins at the beginning of the new cache |
| struct. */ |
| file_entries_new->libs[idx_new].flags = entry->flags; |
| file_entries_new->libs[idx_new].osversion = entry->osversion; |
| file_entries_new->libs[idx_new].hwcap = entry->hwcap; |
| file_entries_new->libs[idx_new].key = str_offset; |
| } |
| |
| size_t len = strlen (entry->lib) + 1; |
| str = mempcpy (str, entry->lib, len); |
| str_offset += len; |
| /* Then the path. */ |
| if (opt_format != 2 && entry->hwcap == 0) |
| file_entries->libs[idx_old].value = str_offset + pad; |
| if (opt_format != 0) |
| file_entries_new->libs[idx_new].value = str_offset; |
| len = strlen (entry->path) + 1; |
| str = mempcpy (str, entry->path, len); |
| str_offset += len; |
| /* Ignore entries with hwcap for old format. */ |
| if (entry->hwcap == 0) |
| ++idx_old; |
| } |
| |
| /* Duplicate last old cache entry if needed. */ |
| if (opt_format != 2 |
| && idx_old < cache_entry_old_count) |
| file_entries->libs[idx_old] = file_entries->libs[idx_old - 1]; |
| |
| /* Write out the cache. */ |
| |
| /* Write cache first to a temporary file and rename it later. */ |
| char *temp_name = xmalloc (strlen (cache_name) + 2); |
| sprintf (temp_name, "%s~", cache_name); |
| |
| /* Create file. */ |
| int fd = open (temp_name, O_CREAT|O_WRONLY|O_TRUNC|O_NOFOLLOW, |
| S_IRUSR|S_IWUSR); |
| if (fd < 0) |
| error (EXIT_FAILURE, errno, _("Can't create temporary cache file %s"), |
| temp_name); |
| |
| /* Write contents. */ |
| if (opt_format != 2) |
| { |
| if (write (fd, file_entries, file_entries_size) |
| != (ssize_t) file_entries_size) |
| error (EXIT_FAILURE, errno, _("Writing of cache data failed")); |
| } |
| if (opt_format != 0) |
| { |
| /* Align cache. */ |
| if (opt_format != 2) |
| { |
| char zero[pad]; |
| memset (zero, '\0', pad); |
| if (write (fd, zero, pad) != (ssize_t) pad) |
| error (EXIT_FAILURE, errno, _("Writing of cache data failed")); |
| } |
| if (write (fd, file_entries_new, file_entries_new_size) |
| != (ssize_t) file_entries_new_size) |
| error (EXIT_FAILURE, errno, _("Writing of cache data failed")); |
| } |
| |
| if (write (fd, strings, total_strlen) != (ssize_t) total_strlen |
| || close (fd)) |
| error (EXIT_FAILURE, errno, _("Writing of cache data failed")); |
| |
| /* Make sure user can always read cache file */ |
| if (chmod (temp_name, S_IROTH|S_IRGRP|S_IRUSR|S_IWUSR)) |
| error (EXIT_FAILURE, errno, |
| _("Changing access rights of %s to %#o failed"), temp_name, |
| S_IROTH|S_IRGRP|S_IRUSR|S_IWUSR); |
| |
| /* Move temporary to its final location. */ |
| if (rename (temp_name, cache_name)) |
| error (EXIT_FAILURE, errno, _("Renaming of %s to %s failed"), temp_name, |
| cache_name); |
| |
| /* Free all allocated memory. */ |
| free (file_entries_new); |
| free (file_entries); |
| free (strings); |
| |
| while (entries) |
| { |
| entry = entries; |
| entries = entries->next; |
| free (entry); |
| } |
| } |
| |
| |
| /* Add one library to the cache. */ |
| void |
| add_to_cache (const char *path, const char *lib, int flags, |
| unsigned int osversion, uint64_t hwcap) |
| { |
| size_t liblen = strlen (lib) + 1; |
| size_t len = liblen + strlen (path) + 1; |
| struct cache_entry *new_entry |
| = xmalloc (sizeof (struct cache_entry) + liblen + len); |
| |
| new_entry->lib = memcpy ((char *) (new_entry + 1), lib, liblen); |
| new_entry->path = new_entry->lib + liblen; |
| snprintf (new_entry->path, len, "%s/%s", path, lib); |
| new_entry->flags = flags; |
| new_entry->osversion = osversion; |
| new_entry->hwcap = hwcap; |
| new_entry->bits_hwcap = 0; |
| |
| /* Count the number of bits set in the masked value. */ |
| for (size_t i = 0; |
| (~((1ULL << i) - 1) & hwcap) != 0 && i < 8 * sizeof (hwcap); ++i) |
| if ((hwcap & (1ULL << i)) != 0) |
| ++new_entry->bits_hwcap; |
| |
| |
| /* Keep the list sorted - search for right place to insert. */ |
| struct cache_entry *ptr = entries; |
| struct cache_entry *prev = entries; |
| while (ptr != NULL) |
| { |
| if (compare (ptr, new_entry) > 0) |
| break; |
| prev = ptr; |
| ptr = ptr->next; |
| } |
| /* Is this the first entry? */ |
| if (ptr == entries) |
| { |
| new_entry->next = entries; |
| entries = new_entry; |
| } |
| else |
| { |
| new_entry->next = prev->next; |
| prev->next = new_entry; |
| } |
| } |
| |
| |
| /* Auxiliary cache. */ |
| |
| struct aux_cache_entry_id |
| { |
| uint64_t ino; |
| uint64_t ctime; |
| uint64_t size; |
| uint64_t dev; |
| }; |
| |
| struct aux_cache_entry |
| { |
| struct aux_cache_entry_id id; |
| int flags; |
| unsigned int osversion; |
| int used; |
| char *soname; |
| struct aux_cache_entry *next; |
| }; |
| |
| #define AUX_CACHEMAGIC "glibc-ld.so.auxcache-1.0" |
| |
| struct aux_cache_file_entry |
| { |
| struct aux_cache_entry_id id; /* Unique id of entry. */ |
| int32_t flags; /* This is 1 for an ELF library. */ |
| uint32_t soname; /* String table indice. */ |
| uint32_t osversion; /* Required OS version. */ |
| int32_t pad; |
| }; |
| |
| /* ldconfig maintains an auxiliary cache file that allows |
| only reading those libraries that have changed since the last iteration. |
| For this for each library some information is cached in the auxiliary |
| cache. */ |
| struct aux_cache_file |
| { |
| char magic[sizeof AUX_CACHEMAGIC - 1]; |
| uint32_t nlibs; /* Number of entries. */ |
| uint32_t len_strings; /* Size of string table. */ |
| struct aux_cache_file_entry libs[0]; /* Entries describing libraries. */ |
| /* After this the string table of size len_strings is found. */ |
| }; |
| |
| static const unsigned int primes[] = |
| { |
| 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, |
| 524287, 1048573, 2097143, 4194301, 8388593, 16777213, 33554393, |
| 67108859, 134217689, 268435399, 536870909, 1073741789, 2147483647 |
| }; |
| |
| static size_t aux_hash_size; |
| static struct aux_cache_entry **aux_hash; |
| |
| /* Simplistic hash function for aux_cache_entry_id. */ |
| static unsigned int |
| aux_cache_entry_id_hash (struct aux_cache_entry_id *id) |
| { |
| uint64_t ret = ((id->ino * 11 + id->ctime) * 11 + id->size) * 11 + id->dev; |
| return ret ^ (ret >> 32); |
| } |
| |
| static size_t nextprime (size_t x) |
| { |
| for (unsigned int i = 0; i < sizeof (primes) / sizeof (primes[0]); ++i) |
| if (primes[i] >= x) |
| return primes[i]; |
| return x; |
| } |
| |
| void |
| init_aux_cache (void) |
| { |
| aux_hash_size = primes[3]; |
| aux_hash = xcalloc (aux_hash_size, sizeof (struct aux_cache_entry *)); |
| } |
| |
| int |
| search_aux_cache (struct stat64 *stat_buf, int *flags, |
| unsigned int *osversion, char **soname) |
| { |
| struct aux_cache_entry_id id; |
| id.ino = (uint64_t) stat_buf->st_ino; |
| id.ctime = (uint64_t) stat_buf->st_ctime; |
| id.size = (uint64_t) stat_buf->st_size; |
| id.dev = (uint64_t) stat_buf->st_dev; |
| |
| unsigned int hash = aux_cache_entry_id_hash (&id); |
| struct aux_cache_entry *entry; |
| for (entry = aux_hash[hash % aux_hash_size]; entry; entry = entry->next) |
| if (id.ino == entry->id.ino |
| && id.ctime == entry->id.ctime |
| && id.size == entry->id.size |
| && id.dev == entry->id.dev) |
| { |
| *flags = entry->flags; |
| *osversion = entry->osversion; |
| if (entry->soname != NULL) |
| *soname = xstrdup (entry->soname); |
| else |
| *soname = NULL; |
| entry->used = 1; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| insert_to_aux_cache (struct aux_cache_entry_id *id, int flags, |
| unsigned int osversion, const char *soname, int used) |
| { |
| size_t hash = aux_cache_entry_id_hash (id) % aux_hash_size; |
| struct aux_cache_entry *entry; |
| for (entry = aux_hash[hash]; entry; entry = entry->next) |
| if (id->ino == entry->id.ino |
| && id->ctime == entry->id.ctime |
| && id->size == entry->id.size |
| && id->dev == entry->id.dev) |
| abort (); |
| |
| size_t len = soname ? strlen (soname) + 1 : 0; |
| entry = xmalloc (sizeof (struct aux_cache_entry) + len); |
| entry->id = *id; |
| entry->flags = flags; |
| entry->osversion = osversion; |
| entry->used = used; |
| if (soname != NULL) |
| entry->soname = memcpy ((char *) (entry + 1), soname, len); |
| else |
| entry->soname = NULL; |
| entry->next = aux_hash[hash]; |
| aux_hash[hash] = entry; |
| } |
| |
| void |
| add_to_aux_cache (struct stat64 *stat_buf, int flags, |
| unsigned int osversion, const char *soname) |
| { |
| struct aux_cache_entry_id id; |
| id.ino = (uint64_t) stat_buf->st_ino; |
| id.ctime = (uint64_t) stat_buf->st_ctime; |
| id.size = (uint64_t) stat_buf->st_size; |
| id.dev = (uint64_t) stat_buf->st_dev; |
| insert_to_aux_cache (&id, flags, osversion, soname, 1); |
| } |
| |
| /* Load auxiliary cache to search for unchanged entries. */ |
| void |
| load_aux_cache (const char *aux_cache_name) |
| { |
| int fd = open (aux_cache_name, O_RDONLY); |
| if (fd < 0) |
| { |
| init_aux_cache (); |
| return; |
| } |
| |
| struct stat64 st; |
| if (fstat64 (fd, &st) < 0 || st.st_size < sizeof (struct aux_cache_file)) |
| { |
| close (fd); |
| init_aux_cache (); |
| return; |
| } |
| |
| size_t aux_cache_size = st.st_size; |
| struct aux_cache_file *aux_cache |
| = mmap (NULL, aux_cache_size, PROT_READ, MAP_PRIVATE, fd, 0); |
| if (aux_cache == MAP_FAILED |
| || aux_cache_size < sizeof (struct aux_cache_file) |
| || memcmp (aux_cache->magic, AUX_CACHEMAGIC, sizeof AUX_CACHEMAGIC - 1) |
| || aux_cache->nlibs >= aux_cache_size) |
| { |
| close (fd); |
| init_aux_cache (); |
| return; |
| } |
| |
| aux_hash_size = nextprime (aux_cache->nlibs); |
| aux_hash = xcalloc (aux_hash_size, sizeof (struct aux_cache_entry *)); |
| |
| const char *aux_cache_data |
| = (const char *) &aux_cache->libs[aux_cache->nlibs]; |
| for (unsigned int i = 0; i < aux_cache->nlibs; ++i) |
| insert_to_aux_cache (&aux_cache->libs[i].id, |
| aux_cache->libs[i].flags, |
| aux_cache->libs[i].osversion, |
| aux_cache->libs[i].soname == 0 |
| ? NULL : aux_cache_data + aux_cache->libs[i].soname, |
| 0); |
| |
| munmap (aux_cache, aux_cache_size); |
| close (fd); |
| } |
| |
| /* Save the contents of the auxiliary cache. */ |
| void |
| save_aux_cache (const char *aux_cache_name) |
| { |
| /* Count the length of all sonames. We start with empty string. */ |
| size_t total_strlen = 1; |
| /* Number of cache entries. */ |
| int cache_entry_count = 0; |
| |
| for (size_t i = 0; i < aux_hash_size; ++i) |
| for (struct aux_cache_entry *entry = aux_hash[i]; |
| entry != NULL; entry = entry->next) |
| if (entry->used) |
| { |
| ++cache_entry_count; |
| if (entry->soname != NULL) |
| total_strlen += strlen (entry->soname) + 1; |
| } |
| |
| /* Auxiliary cache. */ |
| size_t file_entries_size |
| = sizeof (struct aux_cache_file) |
| + cache_entry_count * sizeof (struct aux_cache_file_entry); |
| struct aux_cache_file *file_entries |
| = xmalloc (file_entries_size + total_strlen); |
| |
| /* Fill in the header of the auxiliary cache. */ |
| memset (file_entries, '\0', sizeof (struct aux_cache_file)); |
| memcpy (file_entries->magic, AUX_CACHEMAGIC, sizeof AUX_CACHEMAGIC - 1); |
| |
| file_entries->nlibs = cache_entry_count; |
| file_entries->len_strings = total_strlen; |
| |
| /* Initial String offset for auxiliary cache is always after the |
| special empty string. */ |
| unsigned int str_offset = 1; |
| |
| /* An array for all strings. */ |
| char *str = (char *) file_entries + file_entries_size; |
| *str++ = '\0'; |
| |
| size_t idx = 0; |
| for (size_t i = 0; i < aux_hash_size; ++i) |
| for (struct aux_cache_entry *entry = aux_hash[i]; |
| entry != NULL; entry = entry->next) |
| if (entry->used) |
| { |
| file_entries->libs[idx].id = entry->id; |
| file_entries->libs[idx].flags = entry->flags; |
| if (entry->soname == NULL) |
| file_entries->libs[idx].soname = 0; |
| else |
| { |
| file_entries->libs[idx].soname = str_offset; |
| |
| size_t len = strlen (entry->soname) + 1; |
| str = mempcpy (str, entry->soname, len); |
| str_offset += len; |
| } |
| file_entries->libs[idx].osversion = entry->osversion; |
| file_entries->libs[idx++].pad = 0; |
| } |
| |
| /* Write out auxiliary cache file. */ |
| /* Write auxiliary cache first to a temporary file and rename it later. */ |
| |
| char *temp_name = xmalloc (strlen (aux_cache_name) + 2); |
| sprintf (temp_name, "%s~", aux_cache_name); |
| |
| /* Check that directory exists and create if needed. */ |
| char *dir = strdupa (aux_cache_name); |
| dir = dirname (dir); |
| |
| struct stat64 st; |
| if (stat64 (dir, &st) < 0) |
| { |
| if (mkdir (dir, 0700) < 0) |
| goto out_fail; |
| } |
| |
| /* Create file. */ |
| int fd = open (temp_name, O_CREAT|O_WRONLY|O_TRUNC|O_NOFOLLOW, |
| S_IRUSR|S_IWUSR); |
| if (fd < 0) |
| goto out_fail; |
| |
| if (write (fd, file_entries, file_entries_size + total_strlen) |
| != (ssize_t) (file_entries_size + total_strlen) |
| || close (fd)) |
| { |
| unlink (temp_name); |
| goto out_fail; |
| } |
| |
| /* Move temporary to its final location. */ |
| if (rename (temp_name, aux_cache_name)) |
| unlink (temp_name); |
| |
| out_fail: |
| /* Free allocated memory. */ |
| free (temp_name); |
| free (file_entries); |
| } |