diff options
Diffstat (limited to 'elf/cache.c')
-rw-r--r-- | elf/cache.c | 460 |
1 files changed, 370 insertions, 90 deletions
diff --git a/elf/cache.c b/elf/cache.c index 6dbd5a6..180adea 100644 --- a/elf/cache.c +++ b/elf/cache.c @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2003,2005,2006 Free Software Foundation, Inc. +/* Copyright (C) 1999-2003,2005,2006,2007 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Andreas Jaeger <aj@suse.de>, 1999. @@ -20,6 +20,7 @@ #include <error.h> #include <dirent.h> #include <inttypes.h> +#include <libgen.h> #include <libintl.h> #include <stdio.h> #include <stdlib.h> @@ -80,16 +81,16 @@ print_entry (const char *lib, int flag, unsigned int osversion, fputs (",x86-64", stdout); break; case FLAG_S390_LIB64: - fputs(",64bit", stdout); + fputs (",64bit", stdout); break; case FLAG_POWERPC_LIB64: - fputs(",64bit", stdout); + fputs (",64bit", stdout); break; case FLAG_MIPS64_LIBN32: - fputs(",N32", stdout); + fputs (",N32", stdout); break; case FLAG_MIPS64_LIBN64: - fputs(",64bit", stdout); + fputs (",64bit", stdout); case 0: break; default: @@ -128,19 +129,11 @@ print_entry (const char *lib, int flag, unsigned int osversion, void print_cache (const char *cache_name) { - size_t cache_size; - struct stat64 st; - int fd; - unsigned int i; - struct cache_file *cache; - struct cache_file_new *cache_new = NULL; - const char *cache_data; - int format = 0; - - fd = open (cache_name, O_RDONLY); + 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) @@ -149,14 +142,19 @@ print_cache (const char *cache_name) return; } - cache = mmap (0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + 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")); - cache_size = st.st_size; + 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. */ @@ -201,7 +199,7 @@ print_cache (const char *cache_name) printf (_("%d libs found in cache `%s'\n"), cache->nlibs, cache_name); /* Print everything. */ - for (i = 0; i < cache->nlibs; i++) + 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); @@ -212,7 +210,7 @@ print_cache (const char *cache_name) cache_new->nlibs, cache_name); /* Print everything. */ - for (i = 0; i < cache_new->nlibs; i++) + 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, @@ -231,15 +229,11 @@ init_cache (void) entries = NULL; } - - -static -int compare (const struct cache_entry *e1, const struct cache_entry *e2) +static int +compare (const struct cache_entry *e1, const struct cache_entry *e2) { - int res; - /* We need to swap entries here to get the correct sort order. */ - res = _dl_cache_libcmp (e2->lib, e1->lib); + int res = _dl_cache_libcmp (e2->lib, e1->lib); if (res == 0) { if (e1->flags < e2->flags) @@ -267,29 +261,19 @@ int compare (const struct cache_entry *e1, const struct cache_entry *e2) void save_cache (const char *cache_name) { - struct cache_entry *entry; - int fd, idx_old, idx_new; - size_t total_strlen, len; - char *strings, *str, *temp_name; - struct cache_file *file_entries = NULL; - struct cache_file_new *file_entries_new = NULL; - size_t file_entries_size = 0; - size_t file_entries_new_size = 0; - unsigned int str_offset; - /* Number of cache entries. */ - int cache_entry_count = 0; - /* Number of normal cache entries. */ - int cache_entry_old_count = 0; - /* Pad for alignment of cache_file_new. */ - size_t pad; - /* 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. */ - total_strlen = 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. */ @@ -300,8 +284,8 @@ save_cache (const char *cache_name) } /* Create the on disk cache structure. */ - /* First an array for all strings. */ - strings = (char *)xmalloc (total_strlen); + struct cache_file *file_entries = NULL; + size_t file_entries_size = 0; if (opt_format != 2) { @@ -315,25 +299,27 @@ save_cache (const char *cache_name) /* 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 = (struct cache_file *) xmalloc (file_entries_size); + file_entries = xmalloc (file_entries_size); /* Fill in the header. */ - memset (file_entries, 0, sizeof (struct cache_file)); + 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 = - (struct cache_file_new *) xmalloc (file_entries_new_size); + file_entries_new = xmalloc (file_entries_new_size); /* Fill in the header. */ - memset (file_entries_new, 0, sizeof (struct cache_file_new)); + 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, @@ -343,17 +329,24 @@ save_cache (const char *cache_name) file_entries_new->len_strings = total_strlen; } - pad = ALIGN_CACHE (file_entries_size) - file_entries_size; + /* 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; - str = strings; + /* 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) { @@ -375,21 +368,18 @@ save_cache (const char *cache_name) file_entries_new->libs[idx_new].hwcap = entry->hwcap; file_entries_new->libs[idx_new].key = str_offset; } - len = strlen (entry->lib); - str = stpcpy (str, entry->lib); - /* Account the final NUL. */ - ++str; - str_offset += len + 1; + + 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); - str = stpcpy (str, entry->path); - /* Account the final NUL. */ - ++str; - str_offset += len + 1; + 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; @@ -403,16 +393,12 @@ save_cache (const char *cache_name) /* Write out the cache. */ /* Write cache first to a temporary file and rename it later. */ - temp_name = xmalloc (strlen (cache_name) + 2); + char *temp_name = xmalloc (strlen (cache_name) + 2); sprintf (temp_name, "%s~", cache_name); - /* First remove an old copy if it exists. */ - if (unlink (temp_name) && errno != ENOENT) - error (EXIT_FAILURE, errno, _("Can't remove old temporary cache file %s"), - temp_name); /* Create file. */ - fd = open (temp_name, O_CREAT|O_WRONLY|O_TRUNC|O_NOFOLLOW, - S_IROTH|S_IRGRP|S_IRUSR|S_IWUSR); + 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); @@ -439,11 +425,10 @@ save_cache (const char *cache_name) error (EXIT_FAILURE, errno, _("Writing of cache data failed")); } - if (write (fd, strings, total_strlen) != (ssize_t) total_strlen) + if (write (fd, strings, total_strlen) != (ssize_t) total_strlen + || close (fd)) error (EXIT_FAILURE, errno, _("Writing of cache data failed")); - close (fd); - /* Make sure user can always read cache file */ if (chmod (temp_name, S_IROTH|S_IRGRP|S_IRUSR|S_IWUSR)) error (EXIT_FAILURE, errno, @@ -463,8 +448,6 @@ save_cache (const char *cache_name) while (entries) { entry = entries; - free (entry->path); - free (entry->lib); entries = entries->next; free (entry); } @@ -476,33 +459,29 @@ void add_to_cache (const char *path, const char *lib, int flags, unsigned int osversion, uint64_t hwcap) { - struct cache_entry *new_entry, *ptr, *prev; - char *full_path; - size_t len, i; - - new_entry = (struct cache_entry *) xmalloc (sizeof (struct cache_entry)); - - len = strlen (lib) + strlen (path) + 2; - - full_path = (char *) xmalloc (len); - snprintf (full_path, len, "%s/%s", path, lib); - - new_entry->lib = xstrdup (lib); - new_entry->path = full_path; + 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 (i = 0; (~((1ULL << i) - 1) & hwcap) != 0 && i < 8 * sizeof (hwcap); ++i) + 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. */ - ptr = entries; - prev = entries; + struct cache_entry *ptr = entries; + struct cache_entry *prev = entries; while (ptr != NULL) { if (compare (ptr, new_entry) > 0) @@ -522,3 +501,304 @@ add_to_cache (const char *path, const char *lib, int flags, 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 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 < 0 + || 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 (file_entries); +} |