| commit b44ac4f4c7a8bbe5eaa2701aa9452eaf2c96e1dd |
| Author: Florian Weimer <fweimer@redhat.com> |
| Date: Fri Dec 4 09:13:43 2020 +0100 |
| |
| elf: Process glibc-hwcaps subdirectories in ldconfig |
| |
| Libraries from these subdirectories are added to the cache |
| with a special hwcap bit DL_CACHE_HWCAP_EXTENSION, so that |
| they are ignored by older dynamic loaders. |
| |
| Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org> |
| |
| diff --git a/elf/cache.c b/elf/cache.c |
| index f773cacacf26db1c..dde3d7fefa4105f9 100644 |
| |
| |
| @@ -40,6 +40,105 @@ |
| /* Used to store library names, paths, and other strings. */ |
| static struct stringtable strings; |
| |
| +/* Keeping track of "glibc-hwcaps" subdirectories. During cache |
| + construction, a linear search by name is performed to deduplicate |
| + entries. */ |
| +struct glibc_hwcaps_subdirectory |
| +{ |
| + struct glibc_hwcaps_subdirectory *next; |
| + |
| + /* Interned string with the subdirectory name. */ |
| + struct stringtable_entry *name; |
| + |
| + /* Array index in the cache_extension_tag_glibc_hwcaps section in |
| + the stored cached file. This is computed after all the |
| + subdirectories have been processed, so that subdirectory names in |
| + the extension section can be sorted. */ |
| + uint32_t section_index; |
| + |
| + /* True if the subdirectory is actually used for anything. */ |
| + bool used; |
| +}; |
| + |
| +const char * |
| +glibc_hwcaps_subdirectory_name (const struct glibc_hwcaps_subdirectory *dir) |
| +{ |
| + return dir->name->string; |
| +} |
| + |
| +/* Linked list of known hwcaps subdirecty names. */ |
| +static struct glibc_hwcaps_subdirectory *hwcaps; |
| + |
| +struct glibc_hwcaps_subdirectory * |
| +new_glibc_hwcaps_subdirectory (const char *name) |
| +{ |
| + struct stringtable_entry *name_interned = stringtable_add (&strings, name); |
| + for (struct glibc_hwcaps_subdirectory *p = hwcaps; p != NULL; p = p->next) |
| + if (p->name == name_interned) |
| + return p; |
| + struct glibc_hwcaps_subdirectory *p = xmalloc (sizeof (*p)); |
| + p->next = hwcaps; |
| + p->name = name_interned; |
| + p->section_index = 0; |
| + p->used = false; |
| + hwcaps = p; |
| + return p; |
| +} |
| + |
| +/* Helper for sorting struct glibc_hwcaps_subdirectory elements by |
| + name. */ |
| +static int |
| +assign_glibc_hwcaps_indices_compare (const void *l, const void *r) |
| +{ |
| + const struct glibc_hwcaps_subdirectory *left |
| + = *(struct glibc_hwcaps_subdirectory **)l; |
| + const struct glibc_hwcaps_subdirectory *right |
| + = *(struct glibc_hwcaps_subdirectory **)r; |
| + return strcmp (glibc_hwcaps_subdirectory_name (left), |
| + glibc_hwcaps_subdirectory_name (right)); |
| +} |
| + |
| +/* Count the number of hwcaps subdirectories which are actually |
| + used. */ |
| +static size_t |
| +glibc_hwcaps_count (void) |
| +{ |
| + size_t count = 0; |
| + for (struct glibc_hwcaps_subdirectory *p = hwcaps; p != NULL; p = p->next) |
| + if (p->used) |
| + ++count; |
| + return count; |
| +} |
| + |
| +/* Compute the section_index fields for all */ |
| +static void |
| +assign_glibc_hwcaps_indices (void) |
| +{ |
| + /* Convert the linked list into an array, so that we can use qsort. |
| + Only copy the subdirectories which are actually used. */ |
| + size_t count = glibc_hwcaps_count (); |
| + struct glibc_hwcaps_subdirectory **array |
| + = xmalloc (sizeof (*array) * count); |
| + { |
| + size_t i = 0; |
| + for (struct glibc_hwcaps_subdirectory *p = hwcaps; p != NULL; p = p->next) |
| + if (p->used) |
| + { |
| + array[i] = p; |
| + ++i; |
| + } |
| + assert (i == count); |
| + } |
| + |
| + qsort (array, count, sizeof (*array), assign_glibc_hwcaps_indices_compare); |
| + |
| + /* Assign the array indices. */ |
| + for (size_t i = 0; i < count; ++i) |
| + array[i]->section_index = i; |
| + |
| + free (array); |
| +} |
| + |
| struct cache_entry |
| { |
| struct stringtable_entry *lib; /* Library name. */ |
| @@ -48,6 +147,10 @@ struct cache_entry |
| unsigned int osversion; /* Required OS version. */ |
| uint64_t hwcap; /* Important hardware capabilities. */ |
| int bits_hwcap; /* Number of bits set in hwcap. */ |
| + |
| + /* glibc-hwcaps subdirectory. If not NULL, hwcap must be zero. */ |
| + struct glibc_hwcaps_subdirectory *hwcaps; |
| + |
| struct cache_entry *next; /* Next entry in list. */ |
| }; |
| |
| @@ -60,7 +163,7 @@ static const char *flag_descr[] = |
| /* Print a single entry. */ |
| static void |
| print_entry (const char *lib, int flag, unsigned int osversion, |
| - uint64_t hwcap, const char *key) |
| + uint64_t hwcap, const char *hwcap_string, const char *key) |
| { |
| printf ("\t%s (", lib); |
| switch (flag & FLAG_TYPE_MASK) |
| @@ -132,7 +235,9 @@ print_entry (const char *lib, int flag, unsigned int osversion, |
| printf (",%d", flag & FLAG_REQUIRED_MASK); |
| break; |
| } |
| - if (hwcap != 0) |
| + if (hwcap_string != NULL) |
| + printf (", hwcap: \"%s\"", hwcap_string); |
| + else if (hwcap != 0) |
| printf (", hwcap: %#.16" PRIx64, hwcap); |
| if (osversion != 0) |
| { |
| @@ -158,6 +263,29 @@ print_entry (const char *lib, int flag, unsigned int osversion, |
| printf (") => %s\n", key); |
| } |
| |
| +/* Returns the string with the name of the glibcs-hwcaps subdirectory |
| + associated with ENTRY->hwcap. file_base must be the base address |
| + for string table indices. */ |
| +static const char * |
| +glibc_hwcaps_string (struct cache_extension_all_loaded *ext, |
| + const void *file_base, size_t file_size, |
| + struct file_entry_new *entry) |
| +{ |
| + const uint32_t *hwcaps_array |
| + = ext->sections[cache_extension_tag_glibc_hwcaps].base; |
| + if (dl_cache_hwcap_extension (entry) && hwcaps_array != NULL) |
| + { |
| + uint32_t index = (uint32_t) entry->hwcap; |
| + if (index < ext->sections[cache_extension_tag_glibc_hwcaps].size / 4) |
| + { |
| + uint32_t string_table_index = hwcaps_array[index]; |
| + if (string_table_index < file_size) |
| + return file_base + string_table_index; |
| + } |
| + } |
| + return NULL; |
| +} |
| + |
| /* Print an error and exit if the new-file cache is internally |
| inconsistent. */ |
| static void |
| @@ -167,9 +295,7 @@ check_new_cache (struct cache_file_new *cache) |
| error (EXIT_FAILURE, 0, _("Cache file has wrong endianness.\n")); |
| } |
| |
| -/* Print the extension information at the cache at start address |
| - FILE_BASE, of length FILE_SIZE bytes. The new-format cache header |
| - is at CACHE, and the file name for diagnostics is CACHE_NAME. */ |
| +/* Print the extension information in *EXT. */ |
| static void |
| print_extensions (struct cache_extension_all_loaded *ext) |
| { |
| @@ -266,7 +392,7 @@ print_cache (const char *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->libs[i].flags, 0, 0, NULL, |
| cache_data + cache->libs[i].value); |
| } |
| else if (format == 1) |
| @@ -281,11 +407,16 @@ print_cache (const char *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); |
| + { |
| + const char *hwcaps_string |
| + = glibc_hwcaps_string (&ext, cache, cache_size, |
| + &cache_new->libs[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, hwcaps_string, |
| + cache_data + cache_new->libs[i].value); |
| + } |
| print_extensions (&ext); |
| } |
| /* Cleanup. */ |
| @@ -311,8 +442,23 @@ compare (const struct cache_entry *e1, const struct cache_entry *e2) |
| return 1; |
| else if (e1->flags > e2->flags) |
| return -1; |
| + /* Keep the glibc-hwcaps extension entries before the regular |
| + entries, and sort them by their names. search_cache in |
| + dl-cache.c stops searching once the first non-extension entry |
| + is found, so the extension entries need to come first. */ |
| + else if (e1->hwcaps != NULL && e2->hwcaps == NULL) |
| + return -1; |
| + else if (e1->hwcaps == NULL && e2->hwcaps != NULL) |
| + return 1; |
| + else if (e1->hwcaps != NULL && e2->hwcaps != NULL) |
| + { |
| + res = strcmp (glibc_hwcaps_subdirectory_name (e1->hwcaps), |
| + glibc_hwcaps_subdirectory_name (e2->hwcaps)); |
| + if (res != 0) |
| + return res; |
| + } |
| /* Sort by most specific hwcap. */ |
| - else if (e2->bits_hwcap > e1->bits_hwcap) |
| + if (e2->bits_hwcap > e1->bits_hwcap) |
| return 1; |
| else if (e2->bits_hwcap < e1->bits_hwcap) |
| return -1; |
| @@ -337,30 +483,65 @@ enum |
| * sizeof (struct cache_extension_section))) |
| }; |
| |
| -/* Write the cache extensions to FD. The extension directory is |
| - assumed to be located at CACHE_EXTENSION_OFFSET. */ |
| +/* Write the cache extensions to FD. The string table is shifted by |
| + STRING_TABLE_OFFSET. The extension directory is assumed to be |
| + located at CACHE_EXTENSION_OFFSET. assign_glibc_hwcaps_indices |
| + must have been called. */ |
| static void |
| -write_extensions (int fd, uint32_t cache_extension_offset) |
| +write_extensions (int fd, uint32_t str_offset, |
| + uint32_t cache_extension_offset) |
| { |
| assert ((cache_extension_offset % 4) == 0); |
| |
| + /* The length and contents of the glibc-hwcaps section. */ |
| + uint32_t hwcaps_count = glibc_hwcaps_count (); |
| + uint32_t hwcaps_offset = cache_extension_offset + cache_extension_size; |
| + uint32_t hwcaps_size = hwcaps_count * sizeof (uint32_t); |
| + uint32_t *hwcaps_array = xmalloc (hwcaps_size); |
| + for (struct glibc_hwcaps_subdirectory *p = hwcaps; p != NULL; p = p->next) |
| + if (p->used) |
| + hwcaps_array[p->section_index] = str_offset + p->name->offset; |
| + |
| + /* This is the offset of the generator string. */ |
| + uint32_t generator_offset = hwcaps_offset; |
| + if (hwcaps_count == 0) |
| + /* There is no section for the hwcaps subdirectories. */ |
| + generator_offset -= sizeof (struct cache_extension_section); |
| + else |
| + /* The string table indices for the hwcaps subdirectories shift |
| + the generator string backwards. */ |
| + generator_offset += hwcaps_size; |
| + |
| struct cache_extension *ext = xmalloc (cache_extension_size); |
| ext->magic = cache_extension_magic; |
| - ext->count = cache_extension_count; |
| |
| - for (int i = 0; i < cache_extension_count; ++i) |
| - { |
| - ext->sections[i].tag = i; |
| - ext->sections[i].flags = 0; |
| - } |
| + /* Extension index current being filled. */ |
| + size_t xid = 0; |
| |
| const char *generator |
| = "ldconfig " PKGVERSION RELEASE " release version " VERSION; |
| - ext->sections[cache_extension_tag_generator].offset |
| - = cache_extension_offset + cache_extension_size; |
| - ext->sections[cache_extension_tag_generator].size = strlen (generator); |
| + ext->sections[xid].tag = cache_extension_tag_generator; |
| + ext->sections[xid].flags = 0; |
| + ext->sections[xid].offset = generator_offset; |
| + ext->sections[xid].size = strlen (generator); |
| + |
| + if (hwcaps_count > 0) |
| + { |
| + ++xid; |
| + ext->sections[xid].tag = cache_extension_tag_glibc_hwcaps; |
| + ext->sections[xid].flags = 0; |
| + ext->sections[xid].offset = hwcaps_offset; |
| + ext->sections[xid].size = hwcaps_size; |
| + } |
| + |
| + ++xid; |
| + ext->count = xid; |
| + assert (xid <= cache_extension_count); |
| |
| - if (write (fd, ext, cache_extension_size) != cache_extension_size |
| + size_t ext_size = (offsetof (struct cache_extension, sections) |
| + + xid * sizeof (struct cache_extension_section)); |
| + if (write (fd, ext, ext_size) != ext_size |
| + || write (fd, hwcaps_array, hwcaps_size) != hwcaps_size |
| || write (fd, generator, strlen (generator)) != strlen (generator)) |
| error (EXIT_FAILURE, errno, _("Writing of cache extension data failed")); |
| |
| @@ -373,6 +554,8 @@ save_cache (const char *cache_name) |
| { |
| /* The cache entries are sorted already, save them in this order. */ |
| |
| + assign_glibc_hwcaps_indices (); |
| + |
| struct cache_entry *entry; |
| /* Number of cache entries. */ |
| int cache_entry_count = 0; |
| @@ -474,7 +657,11 @@ save_cache (const char *cache_name) |
| 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; |
| + if (entry->hwcaps == NULL) |
| + file_entries_new->libs[idx_new].hwcap = entry->hwcap; |
| + else |
| + file_entries_new->libs[idx_new].hwcap |
| + = DL_CACHE_HWCAP_EXTENSION | entry->hwcaps->section_index; |
| file_entries_new->libs[idx_new].key |
| = str_offset + entry->lib->offset; |
| file_entries_new->libs[idx_new].value |
| @@ -554,7 +741,7 @@ save_cache (const char *cache_name) |
| /* Align file position to 4. */ |
| off64_t old_offset = lseek64 (fd, extension_offset, SEEK_SET); |
| assert ((unsigned long long int) (extension_offset - old_offset) < 4); |
| - write_extensions (fd, extension_offset); |
| + write_extensions (fd, str_offset, extension_offset); |
| } |
| |
| /* Make sure user can always read cache file */ |
| @@ -588,27 +775,35 @@ save_cache (const char *cache_name) |
| |
| /* Add one library to the cache. */ |
| void |
| -add_to_cache (const char *path, const char *lib, int flags, |
| - unsigned int osversion, uint64_t hwcap) |
| +add_to_cache (const char *path, const char *filename, const char *soname, |
| + int flags, unsigned int osversion, uint64_t hwcap, |
| + struct glibc_hwcaps_subdirectory *hwcaps) |
| { |
| struct cache_entry *new_entry = xmalloc (sizeof (*new_entry)); |
| |
| struct stringtable_entry *path_interned; |
| { |
| char *p; |
| - if (asprintf (&p, "%s/%s", path, lib) < 0) |
| + if (asprintf (&p, "%s/%s", path, filename) < 0) |
| error (EXIT_FAILURE, errno, _("Could not create library path")); |
| path_interned = stringtable_add (&strings, p); |
| free (p); |
| } |
| |
| - new_entry->lib = stringtable_add (&strings, lib); |
| + new_entry->lib = stringtable_add (&strings, soname); |
| new_entry->path = path_interned; |
| new_entry->flags = flags; |
| new_entry->osversion = osversion; |
| new_entry->hwcap = hwcap; |
| + new_entry->hwcaps = hwcaps; |
| new_entry->bits_hwcap = 0; |
| |
| + if (hwcaps != NULL) |
| + { |
| + assert (hwcap == 0); |
| + hwcaps->used = true; |
| + } |
| + |
| /* 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) |
| diff --git a/elf/ldconfig.c b/elf/ldconfig.c |
| index 0fa5aef83f9cd86c..8c66d7e5426d8cc4 100644 |
| |
| |
| @@ -16,6 +16,7 @@ |
| along with this program; if not, see <http://www.gnu.org/licenses/>. */ |
| |
| #define PROCINFO_CLASS static |
| +#include <assert.h> |
| #include <alloca.h> |
| #include <argp.h> |
| #include <dirent.h> |
| @@ -41,6 +42,7 @@ |
| |
| #include <ldconfig.h> |
| #include <dl-cache.h> |
| +#include <dl-hwcaps.h> |
| |
| #include <dl-procinfo.h> |
| |
| @@ -85,6 +87,10 @@ struct dir_entry |
| dev_t dev; |
| const char *from_file; |
| int from_line; |
| + |
| + /* Non-NULL for subdirectories under a glibc-hwcaps subdirectory. */ |
| + struct glibc_hwcaps_subdirectory *hwcaps; |
| + |
| struct dir_entry *next; |
| }; |
| |
| @@ -338,17 +344,20 @@ new_sub_entry (const struct dir_entry *entry, const char *path, |
| new_entry->from_line = entry->from_line; |
| new_entry->path = xstrdup (path); |
| new_entry->flag = entry->flag; |
| + new_entry->hwcaps = NULL; |
| new_entry->next = NULL; |
| new_entry->ino = st->st_ino; |
| new_entry->dev = st->st_dev; |
| return new_entry; |
| } |
| |
| -/* Add a single directory entry. */ |
| -static void |
| +/* Add a single directory entry. Return true if the directory is |
| + actually added (because it is not a duplicate). */ |
| +static bool |
| add_single_dir (struct dir_entry *entry, int verbose) |
| { |
| struct dir_entry *ptr, *prev; |
| + bool added = true; |
| |
| ptr = dir_entries; |
| prev = ptr; |
| @@ -368,6 +377,7 @@ add_single_dir (struct dir_entry *entry, int verbose) |
| ptr->flag = entry->flag; |
| free (entry->path); |
| free (entry); |
| + added = false; |
| break; |
| } |
| prev = ptr; |
| @@ -378,6 +388,73 @@ add_single_dir (struct dir_entry *entry, int verbose) |
| dir_entries = entry; |
| else if (ptr == NULL) |
| prev->next = entry; |
| + return added; |
| +} |
| + |
| +/* Check if PATH contains a "glibc-hwcaps" subdirectory. If so, queue |
| + its subdirectories for glibc-hwcaps processing. */ |
| +static void |
| +add_glibc_hwcaps_subdirectories (struct dir_entry *entry, const char *path) |
| +{ |
| + /* glibc-hwcaps subdirectories do not nest. */ |
| + assert (entry->hwcaps == NULL); |
| + |
| + char *glibc_hwcaps; |
| + if (asprintf (&glibc_hwcaps, "%s/" GLIBC_HWCAPS_SUBDIRECTORY, path) < 0) |
| + error (EXIT_FAILURE, errno, _("Could not form glibc-hwcaps path")); |
| + |
| + DIR *dir = opendir (glibc_hwcaps); |
| + if (dir != NULL) |
| + { |
| + while (true) |
| + { |
| + errno = 0; |
| + struct dirent64 *e = readdir64 (dir); |
| + if (e == NULL) |
| + { |
| + if (errno == 0) |
| + break; |
| + else |
| + error (EXIT_FAILURE, errno, _("Listing directory %s"), path); |
| + } |
| + |
| + /* Ignore hidden subdirectories, including "." and "..", and |
| + regular files. File names containing a ':' cannot be |
| + looked up by the dynamic loader, so skip those as |
| + well. */ |
| + if (e->d_name[0] == '.' || e->d_type == DT_REG |
| + || strchr (e->d_name, ':') != NULL) |
| + continue; |
| + |
| + /* See if this entry eventually resolves to a directory. */ |
| + struct stat64 st; |
| + if (fstatat64 (dirfd (dir), e->d_name, &st, 0) < 0) |
| + /* Ignore unreadable entries. */ |
| + continue; |
| + |
| + if (S_ISDIR (st.st_mode)) |
| + { |
| + /* This is a directory, so it needs to be scanned for |
| + libraries, associated with the hwcaps implied by the |
| + subdirectory name. */ |
| + char *new_path; |
| + if (asprintf (&new_path, "%s/" GLIBC_HWCAPS_SUBDIRECTORY "/%s", |
| + /* Use non-canonicalized path here. */ |
| + entry->path, e->d_name) < 0) |
| + error (EXIT_FAILURE, errno, |
| + _("Could not form glibc-hwcaps path")); |
| + struct dir_entry *new_entry = new_sub_entry (entry, new_path, |
| + &st); |
| + free (new_path); |
| + new_entry->hwcaps = new_glibc_hwcaps_subdirectory (e->d_name); |
| + add_single_dir (new_entry, 0); |
| + } |
| + } |
| + |
| + closedir (dir); |
| + } |
| + |
| + free (glibc_hwcaps); |
| } |
| |
| /* Add one directory to the list of directories to process. */ |
| @@ -386,6 +463,7 @@ add_dir_1 (const char *line, const char *from_file, int from_line) |
| { |
| unsigned int i; |
| struct dir_entry *entry = xmalloc (sizeof (struct dir_entry)); |
| + entry->hwcaps = NULL; |
| entry->next = NULL; |
| |
| entry->from_file = strdup (from_file); |
| @@ -443,7 +521,9 @@ add_dir_1 (const char *line, const char *from_file, int from_line) |
| entry->ino = stat_buf.st_ino; |
| entry->dev = stat_buf.st_dev; |
| |
| - add_single_dir (entry, 1); |
| + if (add_single_dir (entry, 1)) |
| + /* Add glibc-hwcaps subdirectories if present. */ |
| + add_glibc_hwcaps_subdirectories (entry, path); |
| } |
| |
| if (opt_chroot) |
| @@ -695,15 +775,27 @@ struct dlib_entry |
| static void |
| search_dir (const struct dir_entry *entry) |
| { |
| - uint64_t hwcap = path_hwcap (entry->path); |
| - if (opt_verbose) |
| + uint64_t hwcap; |
| + if (entry->hwcaps == NULL) |
| { |
| - if (hwcap != 0) |
| - printf ("%s: (hwcap: %#.16" PRIx64 ")", entry->path, hwcap); |
| - else |
| - printf ("%s:", entry->path); |
| - printf (_(" (from %s:%d)\n"), entry->from_file, entry->from_line); |
| + hwcap = path_hwcap (entry->path); |
| + if (opt_verbose) |
| + { |
| + if (hwcap != 0) |
| + printf ("%s: (hwcap: %#.16" PRIx64 ")", entry->path, hwcap); |
| + else |
| + printf ("%s:", entry->path); |
| + } |
| } |
| + else |
| + { |
| + hwcap = 0; |
| + if (opt_verbose) |
| + printf ("%s: (hwcap: \"%s\")", entry->path, |
| + glibc_hwcaps_subdirectory_name (entry->hwcaps)); |
| + } |
| + if (opt_verbose) |
| + printf (_(" (from %s:%d)\n"), entry->from_file, entry->from_line); |
| |
| char *dir_name; |
| char *real_file_name; |
| @@ -745,13 +837,15 @@ search_dir (const struct dir_entry *entry) |
| && direntry->d_type != DT_DIR) |
| continue; |
| /* Does this file look like a shared library or is it a hwcap |
| - subdirectory? The dynamic linker is also considered as |
| + subdirectory (if not already processing a glibc-hwcaps |
| + subdirectory)? The dynamic linker is also considered as |
| shared library. */ |
| if (((strncmp (direntry->d_name, "lib", 3) != 0 |
| && strncmp (direntry->d_name, "ld-", 3) != 0) |
| || strstr (direntry->d_name, ".so") == NULL) |
| && (direntry->d_type == DT_REG |
| - || !is_hwcap_platform (direntry->d_name))) |
| + || (entry->hwcaps == NULL |
| + && !is_hwcap_platform (direntry->d_name)))) |
| continue; |
| |
| size_t len = strlen (direntry->d_name); |
| @@ -799,7 +893,7 @@ search_dir (const struct dir_entry *entry) |
| } |
| |
| struct stat64 stat_buf; |
| - int is_dir; |
| + bool is_dir; |
| int is_link = S_ISLNK (lstat_buf.st_mode); |
| if (is_link) |
| { |
| @@ -837,7 +931,10 @@ search_dir (const struct dir_entry *entry) |
| else |
| is_dir = S_ISDIR (lstat_buf.st_mode); |
| |
| - if (is_dir && is_hwcap_platform (direntry->d_name)) |
| + /* No descending into subdirectories if this directory is a |
| + glibc-hwcaps subdirectory (which are not recursive). */ |
| + if (entry->hwcaps == NULL |
| + && is_dir && is_hwcap_platform (direntry->d_name)) |
| { |
| if (!is_link |
| && direntry->d_type != DT_UNKNOWN |
| @@ -1028,13 +1125,31 @@ search_dir (const struct dir_entry *entry) |
| struct dlib_entry *dlib_ptr; |
| for (dlib_ptr = dlibs; dlib_ptr != NULL; dlib_ptr = dlib_ptr->next) |
| { |
| - /* Don't create links to links. */ |
| - if (dlib_ptr->is_link == 0) |
| - create_links (dir_name, entry->path, dlib_ptr->name, |
| - dlib_ptr->soname); |
| + /* The cached file name is the soname for non-glibc-hwcaps |
| + subdirectories (relying on symbolic links; this helps with |
| + library updates that change the file name), and the actual |
| + file for glibc-hwcaps subdirectories. */ |
| + const char *filename; |
| + if (entry->hwcaps == NULL) |
| + { |
| + /* Don't create links to links. */ |
| + if (dlib_ptr->is_link == 0) |
| + create_links (dir_name, entry->path, dlib_ptr->name, |
| + dlib_ptr->soname); |
| + filename = dlib_ptr->soname; |
| + } |
| + else |
| + { |
| + /* Do not create links in glibc-hwcaps subdirectories, but |
| + still log the cache addition. */ |
| + if (opt_verbose) |
| + printf ("\t%s -> %s\n", dlib_ptr->soname, dlib_ptr->name); |
| + filename = dlib_ptr->name; |
| + } |
| if (opt_build_cache) |
| - add_to_cache (entry->path, dlib_ptr->soname, dlib_ptr->flag, |
| - dlib_ptr->osversion, hwcap); |
| + add_to_cache (entry->path, filename, dlib_ptr->soname, |
| + dlib_ptr->flag, dlib_ptr->osversion, |
| + hwcap, entry->hwcaps); |
| } |
| |
| /* Free all resources. */ |
| diff --git a/sysdeps/generic/dl-cache.h b/sysdeps/generic/dl-cache.h |
| index 259e843724531630..6adbe3c79a32a4ec 100644 |
| |
| |
| @@ -99,6 +99,23 @@ struct file_entry_new |
| uint64_t hwcap; /* Hwcap entry. */ |
| }; |
| |
| +/* This bit in the hwcap field of struct file_entry_new indicates that |
| + the lower 32 bits contain an index into the |
| + cache_extension_tag_glibc_hwcaps section. Older glibc versions do |
| + not know about this HWCAP bit, so they will ignore these |
| + entries. */ |
| +#define DL_CACHE_HWCAP_EXTENSION (1ULL << 62) |
| + |
| +/* Return true if the ENTRY->hwcap value indicates that |
| + DL_CACHE_HWCAP_EXTENSION is used. */ |
| +static inline bool |
| +dl_cache_hwcap_extension (struct file_entry_new *entry) |
| +{ |
| + /* If DL_CACHE_HWCAP_EXTENSION is set, but other bits as well, this |
| + is a different kind of extension. */ |
| + return (entry->hwcap >> 32) == (DL_CACHE_HWCAP_EXTENSION >> 32); |
| +} |
| + |
| /* See flags member of struct cache_file_new below. */ |
| enum |
| { |
| @@ -182,6 +199,17 @@ enum cache_extension_tag |
| cache file. */ |
| cache_extension_tag_generator, |
| |
| + /* glibc-hwcaps subdirectory information. An array of uint32_t |
| + values, which are indices into the string table. The strings |
| + are sorted lexicographically (according to strcmp). The extra |
| + level of indirection (instead of using string table indices |
| + directly) allows the dynamic loader to compute the preference |
| + order of the hwcaps names more efficiently. |
| + |
| + For this section, 4-byte alignment is required, and the section |
| + size must be a multiple of 4. */ |
| + cache_extension_tag_glibc_hwcaps, |
| + |
| /* Total number of known cache extension tags. */ |
| cache_extension_count |
| }; |
| @@ -236,6 +264,27 @@ struct cache_extension_all_loaded |
| struct cache_extension_loaded sections[cache_extension_count]; |
| }; |
| |
| +/* Performs basic data validation based on section tag, and removes |
| + the sections which are invalid. */ |
| +static void |
| +cache_extension_verify (struct cache_extension_all_loaded *loaded) |
| +{ |
| + { |
| + /* Section must not be empty, it must be aligned at 4 bytes, and |
| + the size must be a multiple of 4. */ |
| + struct cache_extension_loaded *hwcaps |
| + = &loaded->sections[cache_extension_tag_glibc_hwcaps]; |
| + if (hwcaps->size == 0 |
| + || ((uintptr_t) hwcaps->base % 4) != 0 |
| + || (hwcaps->size % 4) != 0) |
| + { |
| + hwcaps->base = NULL; |
| + hwcaps->size = 0; |
| + hwcaps->flags = 0; |
| + } |
| + } |
| +} |
| + |
| static bool __attribute__ ((unused)) |
| cache_extension_load (const struct cache_file_new *cache, |
| const void *file_base, size_t file_size, |
| @@ -282,6 +331,7 @@ cache_extension_load (const struct cache_file_new *cache, |
| loaded->sections[tag].size = ext->sections[i].size; |
| loaded->sections[tag].flags = ext->sections[i].flags; |
| } |
| + cache_extension_verify (loaded); |
| return true; |
| } |
| |
| diff --git a/sysdeps/generic/ldconfig.h b/sysdeps/generic/ldconfig.h |
| index b15b142511829436..a8d22f143f867a3e 100644 |
| |
| |
| @@ -57,8 +57,22 @@ extern void init_cache (void); |
| |
| extern void save_cache (const char *cache_name); |
| |
| -extern void add_to_cache (const char *path, const char *lib, int flags, |
| - unsigned int osversion, uint64_t hwcap); |
| +struct glibc_hwcaps_subdirectory; |
| + |
| +/* Return a struct describing the subdirectory for NAME. Reuse an |
| + existing struct if it exists. */ |
| +struct glibc_hwcaps_subdirectory *new_glibc_hwcaps_subdirectory |
| + (const char *name); |
| + |
| +/* Returns the name that was specified when |
| + add_glibc_hwcaps_subdirectory was called. */ |
| +const char *glibc_hwcaps_subdirectory_name |
| + (const struct glibc_hwcaps_subdirectory *); |
| + |
| +extern void add_to_cache (const char *path, const char *filename, |
| + const char *soname, |
| + int flags, unsigned int osversion, uint64_t hwcap, |
| + struct glibc_hwcaps_subdirectory *); |
| |
| extern void init_aux_cache (void); |
| |