[27/28] elf: Process glibc-hwcaps subdirectories in ldconfig

Message ID ffef62d3152bb2565849c002550ac533ab809249.1601569371.git.fweimer@redhat.com
State Superseded
Headers show
Series
  • glibc-hwcaps support
Related show

Commit Message

Florian Weimer via Libc-alpha Oct. 1, 2020, 4:34 p.m.
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.
---
 elf/cache.c                | 258 ++++++++++++++++++++++++++++++++-----
 elf/ldconfig.c             | 153 +++++++++++++++++++---
 sysdeps/generic/dl-cache.h |  51 +++++++-
 sysdeps/generic/ldconfig.h |  18 ++-
 4 files changed, 426 insertions(+), 54 deletions(-)

-- 
Red Hat GmbH, https://de.redhat.com/ , Registered seat: Grasbrunn,
Commercial register: Amtsgericht Muenchen, HRB 153243,
Managing Directors: Charles Cachera, Brian Klemm, Laurie Krebs, Michael O'Neill

Comments

Florian Weimer via Libc-alpha Oct. 27, 2020, 5:28 p.m. | #1
On 01/10/2020 13:34, Florian Weimer via Libc-alpha wrote:
> 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.


Looks goods in general, some comments below.


> ---

>  elf/cache.c                | 258 ++++++++++++++++++++++++++++++++-----

>  elf/ldconfig.c             | 153 +++++++++++++++++++---

>  sysdeps/generic/dl-cache.h |  51 +++++++-

>  sysdeps/generic/ldconfig.h |  18 ++-

>  4 files changed, 426 insertions(+), 54 deletions(-)

> 

> diff --git a/elf/cache.c b/elf/cache.c

> index eda3da98a7..7ce4ca9870 100644

> --- a/elf/cache.c

> +++ b/elf/cache.c

> @@ -40,6 +40,105 @@

>  /* Used to store library names, paths, and other strings.  */

>  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;

> +};

> +


Ok.

> +const char *

> +glibc_hwcaps_subdirectory_name (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_intern (&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;

> +}

> +


Ok.

> +/* 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 (left->name->string, right->name->string);

> +}

> +


Maybe:

  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;

> +}

> +


Ok.

> +/* 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);


Do we need this assert? I think it would make sense if hwcaps is modified
concurrently, which does not seem the case. 

> +  }

> +

> +  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);

> +}

> +


Ok.

>  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.  */

>  };

>  


Ok.

> @@ -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)

>      {


Ok.

> @@ -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;

> +}

> +


Ok.

>  /* 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 ltength 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)


Ok.

> @@ -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.  */


Ok.

> @@ -311,8 +442,22 @@ 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 (e1->hwcaps->name->string, e2->hwcaps->name->string);


Maybe:

  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;


Ok.

> @@ -337,30 +482,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_count * sizeof (uint32_t);


Maybe 

  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;

> -    }


Ok, although maybe you could refactor the 'elf: Add extension mechanism to ld.so.cache'
to avoid add such code.

> +  /* 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);


Would it make more sense to reference the index directly using the
enumeration instead or add an assert to check if the index is within
the expected size?

>  

> -  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));


So here we could just use cache_extension_count instead of 'xid' (with
the advantage that we certify at compile-time that only know
cache_extension_count will be written on the file).

> +  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 +553,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;


Ok.

> @@ -474,7 +656,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


Ok.

> @@ -554,7 +740,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 */


Ok.

> @@ -588,8 +774,9 @@ 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));

>  

> @@ -597,11 +784,11 @@ add_to_cache (const char *path, const char *lib, int flags,

>    {

>      /* Use a small, on-stack buffer in most cases.  */

>      char buf[200];

> -    int ret = snprintf (buf, sizeof (buf), "%s/%s", path, lib);

> +    int ret = snprintf (buf, sizeof (buf), "%s/%s", path, filename);

>      if (ret < 0 || ret >= sizeof (buf) - 1)

>        {

>  	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_intern (&strings, p);

>  	free (p);


Ok.

> @@ -610,13 +797,20 @@ add_to_cache (const char *path, const char *lib, int flags,

>        path_interned = stringtable_intern (&strings, buf);

>    }

>  

> -  new_entry->lib = stringtable_intern (&strings, lib);

> +  new_entry->lib = stringtable_intern (&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)


Ok.

> diff --git a/elf/ldconfig.c b/elf/ldconfig.c

> index 3768267bac..3136601de7 100644

> --- a/elf/ldconfig.c

> +++ b/elf/ldconfig.c

> @@ -16,6 +16,7 @@

>     along with this program; if not, see <https://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;

>  };

>  


Ok.

> @@ -339,17 +345,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;

> @@ -369,6 +378,7 @@ add_single_dir (struct dir_entry *entry, int verbose)

>  	  ptr->flag = entry->flag;

>  	  free (entry->path);

>  	  free (entry);

> +	  added = false;

>  	  break;

>  	}

>        prev = ptr;

> @@ -379,6 +389,73 @@ add_single_dir (struct dir_entry *entry, int verbose)

>      dir_entries = entry;

>    else if (ptr == NULL)

>      prev->next = entry;

> +  return added;

> +}

> +


Ok.

> +/* 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.  */


Ok.

> @@ -387,6 +464,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);

> @@ -444,7 +522,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)


Ok.

> @@ -696,15 +776,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;


Ok.

> @@ -746,13 +838,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);


Ok.

> @@ -838,7 +932,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


is_dir is an 'int', maybe make it a boolean?

> @@ -1029,13 +1126,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.  */


Ok.

> diff --git a/sysdeps/generic/dl-cache.h b/sysdeps/generic/dl-cache.h

> index fec209509d..66b0312ac1 100644

> --- a/sysdeps/generic/dl-cache.h

> +++ b/sysdeps/generic/dl-cache.h

> @@ -81,7 +81,6 @@ struct cache_file

>  #define CACHE_VERSION "1.1"

>  #define CACHEMAGIC_VERSION_NEW CACHEMAGIC_NEW CACHE_VERSION

>  

> -


Spurious line removal.

>  struct file_entry_new

>  {

>    union

> @@ -99,6 +98,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

>    {


Ok.

> @@ -161,6 +177,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

>    };


Ok.

> @@ -215,6 +242,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;

> +      }

> +  }

> +}

> +


Ok.

>  static bool __attribute__ ((unused))

>  cache_extension_load (const struct cache_file_new *cache,

>  		      const void *file_base, size_t file_size,

> @@ -261,6 +309,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;

>  }

>  


Ok.

> diff --git a/sysdeps/generic/ldconfig.h b/sysdeps/generic/ldconfig.h

> index b64aab0064..30a76481aa 100644

> --- a/sysdeps/generic/ldconfig.h

> +++ b/sysdeps/generic/ldconfig.h

> @@ -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

> +  (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);

>  

> 


Ok.
Florian Weimer via Libc-alpha Nov. 4, 2020, 11:57 a.m. | #2
* Adhemerval Zanella via Libc-alpha:

>> +/* 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 (left->name->string, right->name->string);

>> +}

>> +

>

> Maybe:

>

>   strcmp (glibc_hwcaps_subdirectory_name (left),

>           glibc_hwcaps_subdirectory_name (right));


Fixed.

>> +/* 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);

>

> Do we need this assert? I think it would make sense if hwcaps is modified

> concurrently, which does not seem the case.


It documents that the loop processed the entire array, consistent with
glibc_hwcaps_count.  Right now, the function is defined immediately
above, but that could change, and I think it is reasonable to capture
this dependency.

>> @@ -311,8 +442,22 @@ 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 (e1->hwcaps->name->string, e2->hwcaps->name->string);

>

> Maybe:

>

>   res = strcmp (glibc_hwcaps_subdirectory_name (e1->hwcaps),

>                 glibc_hwcaps_subdirectory_name (e2->hwcaps));


Fixed.

>> -/* 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_count * sizeof (uint32_t);

>

> Maybe 

>

>   generator_offset += hwcaps_size;


Fixed.

>>    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;

>> -    }

>

> Ok, although maybe you could refactor the 'elf: Add extension

> mechanism to ld.so.cache' to avoid add such code.


This is still quite ad-hoc.  I expect that the code will change again
when we have additional extensions and a common pattern emerges.

>> +  /* 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);

>

> Would it make more sense to reference the index directly using the

> enumeration instead or add an assert to check if the index is within

> the expected size?


In the future, the index does not necessarily equal the tag value.  We
don't write the glibc-hwcaps extension if no such subdirectories exist.

>> -  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));

>

> So here we could just use cache_extension_count instead of 'xid' (with

> the advantage that we certify at compile-time that only know

> cache_extension_count will be written on the file).


I think we shouldn't write extensions that aren't used.  It will help to
make sure that the loader code is tolerant of extensions.

>> @@ -838,7 +932,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

>

> is_dir is an 'int', maybe make it a boolean?


Fixed.

>> diff --git a/sysdeps/generic/dl-cache.h b/sysdeps/generic/dl-cache.h

>> index fec209509d..66b0312ac1 100644

>> --- a/sysdeps/generic/dl-cache.h

>> +++ b/sysdeps/generic/dl-cache.h

>> @@ -81,7 +81,6 @@ struct cache_file

>>  #define CACHE_VERSION "1.1"

>>  #define CACHEMAGIC_VERSION_NEW CACHEMAGIC_NEW CACHE_VERSION

>>  

>> -

>

> Spurious line removal.


Fixed.

Thanks,
Florian
-- 
Red Hat GmbH, https://de.redhat.com/ , Registered seat: Grasbrunn,
Commercial register: Amtsgericht Muenchen, HRB 153243,
Managing Directors: Charles Cachera, Brian Klemm, Laurie Krebs, Michael O'Neill

Patch

diff --git a/elf/cache.c b/elf/cache.c
index eda3da98a7..7ce4ca9870 100644
--- a/elf/cache.c
+++ b/elf/cache.c
@@ -40,6 +40,105 @@ 
 /* Used to store library names, paths, and other strings.  */
 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 (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_intern (&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 (left->name->string, right->name->string);
+}
+
+/* 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 ltength 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,22 @@  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 (e1->hwcaps->name->string, e2->hwcaps->name->string);
+	  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 +482,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_count * sizeof (uint32_t);
+
   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 +553,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 +656,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 +740,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,8 +774,9 @@  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));
 
@@ -597,11 +784,11 @@  add_to_cache (const char *path, const char *lib, int flags,
   {
     /* Use a small, on-stack buffer in most cases.  */
     char buf[200];
-    int ret = snprintf (buf, sizeof (buf), "%s/%s", path, lib);
+    int ret = snprintf (buf, sizeof (buf), "%s/%s", path, filename);
     if (ret < 0 || ret >= sizeof (buf) - 1)
       {
 	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_intern (&strings, p);
 	free (p);
@@ -610,13 +797,20 @@  add_to_cache (const char *path, const char *lib, int flags,
       path_interned = stringtable_intern (&strings, buf);
   }
 
-  new_entry->lib = stringtable_intern (&strings, lib);
+  new_entry->lib = stringtable_intern (&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 3768267bac..3136601de7 100644
--- a/elf/ldconfig.c
+++ b/elf/ldconfig.c
@@ -16,6 +16,7 @@ 
    along with this program; if not, see <https://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;
 };
 
@@ -339,17 +345,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;
@@ -369,6 +378,7 @@  add_single_dir (struct dir_entry *entry, int verbose)
 	  ptr->flag = entry->flag;
 	  free (entry->path);
 	  free (entry);
+	  added = false;
 	  break;
 	}
       prev = ptr;
@@ -379,6 +389,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.  */
@@ -387,6 +464,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);
@@ -444,7 +522,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)
@@ -696,15 +776,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;
@@ -746,13 +838,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);
@@ -838,7 +932,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
@@ -1029,13 +1126,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 fec209509d..66b0312ac1 100644
--- a/sysdeps/generic/dl-cache.h
+++ b/sysdeps/generic/dl-cache.h
@@ -81,7 +81,6 @@  struct cache_file
 #define CACHE_VERSION "1.1"
 #define CACHEMAGIC_VERSION_NEW CACHEMAGIC_NEW CACHE_VERSION
 
-
 struct file_entry_new
 {
   union
@@ -99,6 +98,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
   {
@@ -161,6 +177,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
   };
@@ -215,6 +242,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,
@@ -261,6 +309,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 b64aab0064..30a76481aa 100644
--- a/sysdeps/generic/ldconfig.h
+++ b/sysdeps/generic/ldconfig.h
@@ -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
+  (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);