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