MIPS: IEEE 754-2008 NaN encoding support
[platform/upstream/glibc.git] / elf / ldconfig.c
index e1c3f84..c7b9eb9 100644 (file)
@@ -1,22 +1,21 @@
-/* Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
+/* Copyright (C) 1999-2013 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Andreas Jaeger <aj@suse.de>, 1999.
 
-   The GNU C Library is free software; you can redistribute it and/or
-   modify it under the terms of the GNU Lesser General Public
-   License as published by the Free Software Foundation; either
-   version 2.1 of the License, or (at your option) any later version.
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published
+   by the Free Software Foundation; version 2 of the License, or
+   (at your option) any later version.
 
-   The GNU C Library is distributed in the hope that it will be useful,
+   This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-   Lesser General Public License for more details.
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
-   You should have received a copy of the GNU Lesser General Public
-   License along with the GNU C Library; if not, write to the Free
-   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
-   02111-1307 USA.  */
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
+#define PROCINFO_CLASS static
 #include <alloca.h>
 #include <argp.h>
 #include <dirent.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <libintl.h>
+#include <locale.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <stdint.h>
 #include <sys/fcntl.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <glob.h>
+#include <libgen.h>
 
-#include "ldconfig.h"
-#include "dl-cache.h"
+#include <ldconfig.h>
+#include <dl-cache.h>
 
-#include "dl-procinfo.h"
+#include <dl-procinfo.h>
+
+#ifdef _DL_FIRST_PLATFORM
+# define _DL_FIRST_EXTRA (_DL_FIRST_PLATFORM + _DL_PLATFORMS_COUNT)
+#else
+# define _DL_FIRST_EXTRA _DL_HWCAP_COUNT
+#endif
 
 #ifndef LD_SO_CONF
 # define LD_SO_CONF SYSCONFDIR "/ld.so.conf"
@@ -102,6 +112,9 @@ static char *opt_chroot;
 /* Manually link given shared libraries.  */
 static int opt_manual_link;
 
+/* Should we ignore an old auxiliary cache file?  */
+static int opt_ignore_aux_cache;
+
 /* Cache file to use.  */
 static char *cache_file;
 
@@ -111,11 +124,17 @@ static const char *config_file;
 /* Mask to use for important hardware capabilities.  */
 static unsigned long int hwcap_mask = HWCAP_IMPORTANT;
 
+/* Configuration-defined capabilities defined in kernel vDSOs.  */
+static const char *hwcap_extra[64 - _DL_FIRST_EXTRA];
+
 /* Name and version of program.  */
 static void print_version (FILE *stream, struct argp_state *state);
 void (*argp_program_version_hook) (FILE *, struct argp_state *)
      = print_version;
 
+/* Function to print some extra text in the help message.  */
+static char *more_help (int key, const char *text, void *input);
+
 /* Definitions of arguments for argp functions.  */
 static const struct argp_option options[] =
 {
@@ -123,15 +142,19 @@ static const struct argp_option options[] =
   { "verbose", 'v', NULL, 0, N_("Generate verbose messages"), 0},
   { NULL, 'N', NULL, 0, N_("Don't build cache"), 0},
   { NULL, 'X', NULL, 0, N_("Don't generate links"), 0},
-  { NULL, 'r', "ROOT", 0, N_("Change to and use ROOT as root directory"), 0},
-  { NULL, 'C', "CACHE", 0, N_("Use CACHE as cache file"), 0},
-  { NULL, 'f', "CONF", 0, N_("Use CONF as configuration file"), 0},
+  { NULL, 'r', N_("ROOT"), 0, N_("Change to and use ROOT as root directory"), 0},
+  { NULL, 'C', N_("CACHE"), 0, N_("Use CACHE as cache file"), 0},
+  { NULL, 'f', N_("CONF"), 0, N_("Use CONF as configuration file"), 0},
   { NULL, 'n', NULL, 0, N_("Only process directories specified on the command line.  Don't build cache."), 0},
   { NULL, 'l', NULL, 0, N_("Manually link individual libraries."), 0},
-  { "format", 'c', "FORMAT", 0, N_("Format to use: new, old or compat (default)"), 0},
+  { "format", 'c', N_("FORMAT"), 0, N_("Format to use: new, old or compat (default)"), 0},
+  { "ignore-aux-cache", 'i', NULL, 0, N_("Ignore auxiliary cache file"), 0},
   { NULL, 0, NULL, 0, NULL, 0 }
 };
 
+#define PROCINFO_CLASS static
+#include <dl-procinfo.c>
+
 /* Short description of program.  */
 static const char doc[] = N_("Configure Dynamic Linker Run Time Bindings.");
 
@@ -141,7 +164,7 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state);
 /* Data structure to communicate with argp functions.  */
 static struct argp argp =
 {
-  options, parse_opt, NULL, doc, NULL, NULL, NULL
+  options, parse_opt, NULL, doc, NULL, more_help, NULL
 };
 
 /* Check if string corresponds to an important hardware capability or
@@ -151,13 +174,22 @@ is_hwcap_platform (const char *name)
 {
   int hwcap_idx = _dl_string_hwcap (name);
 
+  /* Is this a normal hwcap for the machine like "fpu?"  */
   if (hwcap_idx != -1 && ((1 << hwcap_idx) & hwcap_mask))
     return 1;
 
+  /* Is this a platform pseudo-hwcap like "i686?"  */
   hwcap_idx = _dl_string_platform (name);
   if (hwcap_idx != -1)
     return 1;
 
+  /* Is this one of the extra pseudo-hwcaps that we map beyond
+     _DL_FIRST_EXTRA like "tls", or "nosegneg?"  */
+  for (hwcap_idx = _DL_FIRST_EXTRA; hwcap_idx < 64; ++hwcap_idx)
+    if (hwcap_extra[hwcap_idx - _DL_FIRST_EXTRA] != NULL
+       && !strcmp (name, hwcap_extra[hwcap_idx - _DL_FIRST_EXTRA]))
+      return 1;
+
   return 0;
 }
 
@@ -190,7 +222,14 @@ path_hwcap (const char *path)
        {
          h = _dl_string_platform (ptr + 1);
          if (h == (uint64_t) -1)
-           break;
+           {
+             for (h = _DL_FIRST_EXTRA; h < 64; ++h)
+               if (hwcap_extra[h - _DL_FIRST_EXTRA] != NULL
+                   && !strcmp (ptr + 1, hwcap_extra[h - _DL_FIRST_EXTRA]))
+                 break;
+             if (h == 64)
+               break;
+           }
        }
       hwcap += 1ULL << h;
 
@@ -210,10 +249,15 @@ parse_opt (int key, char *arg, struct argp_state *state)
     {
     case 'C':
       cache_file = arg;
+      /* Ignore auxiliary cache since we use non-standard cache.  */
+      opt_ignore_aux_cache = 1;
       break;
     case 'f':
       config_file = arg;
       break;
+    case 'i':
+      opt_ignore_aux_cache = 1;
+      break;
     case 'l':
       opt_manual_link = 1;
       break;
@@ -251,16 +295,36 @@ parse_opt (int key, char *arg, struct argp_state *state)
   return 0;
 }
 
+/* Print bug-reporting information in the help message.  */
+static char *
+more_help (int key, const char *text, void *input)
+{
+  char *tp = NULL;
+  switch (key)
+    {
+    case ARGP_KEY_HELP_EXTRA:
+      /* We print some extra information.  */
+      if (asprintf (&tp, gettext ("\
+For bug reporting instructions, please see:\n\
+%s.\n"), REPORT_BUGS_TO) < 0)
+       return NULL;
+      return tp;
+    default:
+      break;
+    }
+  return (char *) text;
+}
+
 /* Print the version information.  */
 static void
 print_version (FILE *stream, struct argp_state *state)
 {
-  fprintf (stream, "ldconfig (GNU %s) %s\n", PACKAGE, VERSION);
+  fprintf (stream, "ldconfig %s%s\n", PKGVERSION, VERSION);
   fprintf (stream, gettext ("\
 Copyright (C) %s Free Software Foundation, Inc.\n\
 This is free software; see the source for copying conditions.  There is NO\n\
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
-"), "2001");
+"), "2013");
   fprintf (stream, gettext ("Written by %s.\n"),
           "Andreas Jaeger");
 }
@@ -300,17 +364,13 @@ add_single_dir (struct dir_entry *entry, int verbose)
 static void
 add_dir (const char *line)
 {
-  char *equal_sign;
-  struct dir_entry *entry;
   unsigned int i;
-  struct stat64 stat_buf;
-
-  entry = xmalloc (sizeof (struct dir_entry));
+  struct dir_entry *entry = xmalloc (sizeof (struct dir_entry));
   entry->next = NULL;
 
   /* Search for an '=' sign.  */
   entry->path = xstrdup (line);
-  equal_sign = strchr (entry->path, '=');
+  char *equal_sign = strchr (entry->path, '=');
   if (equal_sign)
     {
       *equal_sign = '\0';
@@ -330,27 +390,41 @@ add_dir (const char *line)
       entry->flag = FLAG_ANY;
     }
 
-  /* Canonify path: for now only remove trailing slashes.  */
-  i = strlen (entry->path) - 1;
-  while (entry->path[i] == '/' && i > 0)
-    {
-      entry->path[i] = '\0';
-      --i;
-    }
+  /* Canonify path: for now only remove leading and trailing
+     whitespace and the trailing slashes.  */
+  i = strlen (entry->path);
+
+  while (i > 0 && isspace (entry->path[i - 1]))
+    entry->path[--i] = '\0';
+
+  while (i > 0 && entry->path[i - 1] == '/')
+    entry->path[--i] = '\0';
+
+  if (i == 0)
+    return;
+
+  char *path = entry->path;
+  if (opt_chroot)
+    path = chroot_canon (opt_chroot, path);
 
-  if (stat64 (entry->path, &stat_buf))
+  struct stat64 stat_buf;
+  if (path == NULL || stat64 (path, &stat_buf))
     {
       if (opt_verbose)
        error (0, errno, _("Can't stat %s"), entry->path);
       free (entry->path);
       free (entry);
-      return;
     }
+  else
+    {
+      entry->ino = stat_buf.st_ino;
+      entry->dev = stat_buf.st_dev;
 
-  entry->ino = stat_buf.st_ino;
-  entry->dev = stat_buf.st_dev;
+      add_single_dir (entry, 1);
+    }
 
- add_single_dir (entry, 1);
+  if (opt_chroot)
+    free (path);
 }
 
 
@@ -483,7 +557,7 @@ manual_link (char *library)
   if (libname)
     {
       /* Successfully split names.  Check if path is just "/" to avoid
-         an empty path.  */
+        an empty path.  */
       if (libname == path)
        {
          libname = library + 1;
@@ -525,7 +599,7 @@ manual_link (char *library)
   /* Do some sanity checks first.  */
   if (lstat64 (real_library, &stat_buf))
     {
-      error (0, errno, _("Can't lstat %s"), library);
+      error (0, errno, _("Cannot lstat %s"), library);
       free (path);
       return;
     }
@@ -537,14 +611,17 @@ manual_link (char *library)
       free (path);
       return;
     }
+
   if (process_file (real_library, library, libname, &flag, &osversion,
-                   &soname, 0))
+                   &soname, 0, &stat_buf))
     {
       error (0, 0, _("No link created since soname could not be found for %s"),
             library);
       free (path);
       return;
     }
+  if (soname == NULL)
+    soname = implicit_soname (libname, flag);
   create_links (real_path, path, libname, soname);
   free (soname);
   free (path);
@@ -590,31 +667,20 @@ struct dlib_entry
 static void
 search_dir (const struct dir_entry *entry)
 {
-  DIR *dir;
-  struct dirent *direntry;
-  char *file_name, *dir_name, *real_file_name, *real_name;
-  int file_name_len, real_file_name_len, len;
-  char *soname;
-  struct dlib_entry *dlibs;
-  struct dlib_entry *dlib_ptr;
-  struct stat64 lstat_buf, stat_buf;
-  int is_link, is_dir;
   uint64_t hwcap = path_hwcap (entry->path);
-  unsigned int osversion;
-
-  file_name_len = PATH_MAX;
-  file_name = alloca (file_name_len);
-
-  dlibs = NULL;
-
   if (opt_verbose)
     {
       if (hwcap != 0)
-       printf ("%s: (hwcap: 0x%" PRIx64 ")\n", entry->path, hwcap);
+       printf ("%s: (hwcap: %#.16" PRIx64 ")\n", entry->path, hwcap);
       else
        printf ("%s:\n", entry->path);
     }
 
+  char *dir_name;
+  char *real_file_name;
+  size_t real_file_name_len;
+  size_t file_name_len = PATH_MAX;
+  char *file_name = alloca (file_name_len);
   if (opt_chroot)
     {
       dir_name = chroot_canon (opt_chroot, entry->path);
@@ -628,6 +694,7 @@ search_dir (const struct dir_entry *entry)
       real_file_name = file_name;
     }
 
+  DIR *dir;
   if (dir_name == NULL || (dir = opendir (dir_name)) == NULL)
     {
       if (opt_verbose)
@@ -637,7 +704,9 @@ search_dir (const struct dir_entry *entry)
       return;
     }
 
-  while ((direntry = readdir (dir)) != NULL)
+  struct dirent64 *direntry;
+  struct dlib_entry *dlibs = NULL;
+  while ((direntry = readdir64 (dir)) != NULL)
     {
       int flag;
 #ifdef _DIRENT_HAVE_D_TYPE
@@ -654,12 +723,30 @@ search_dir (const struct dir_entry *entry)
       if (((strncmp (direntry->d_name, "lib", 3) != 0
            && strncmp (direntry->d_name, "ld-", 3) != 0)
           || strstr (direntry->d_name, ".so") == NULL)
-         && !is_hwcap_platform (direntry->d_name))
+         && (
+#ifdef _DIRENT_HAVE_D_TYPE
+             direntry->d_type == DT_REG ||
+#endif
+             !is_hwcap_platform (direntry->d_name)))
        continue;
-      len = strlen (entry->path) + strlen (direntry->d_name);
+
+      size_t len = strlen (direntry->d_name);
+      /* Skip temporary files created by the prelink program.  Files with
+        names like these are never really DSOs we want to look at.  */
+      if (len >= sizeof (".#prelink#") - 1)
+       {
+         if (strcmp (direntry->d_name + len - sizeof (".#prelink#") + 1,
+                     ".#prelink#") == 0)
+           continue;
+         if (len >= sizeof (".#prelink#.XXXXXX") - 1
+             && memcmp (direntry->d_name + len - sizeof (".#prelink#.XXXXXX")
+                        + 1, ".#prelink#.", sizeof (".#prelink#.") - 1) == 0)
+           continue;
+       }
+      len += strlen (entry->path) + 2;
       if (len > file_name_len)
        {
-         file_name_len = len + 1;
+         file_name_len = len;
          file_name = alloca (file_name_len);
          if (!opt_chroot)
            real_file_name = file_name;
@@ -667,37 +754,63 @@ search_dir (const struct dir_entry *entry)
       sprintf (file_name, "%s/%s", entry->path, direntry->d_name);
       if (opt_chroot)
        {
-         len = strlen (dir_name) + strlen (direntry->d_name);
+         len = strlen (dir_name) + strlen (direntry->d_name) + 2;
          if (len > real_file_name_len)
            {
-             real_file_name_len = len + 1;
+             real_file_name_len = len;
              real_file_name = alloca (real_file_name_len);
            }
          sprintf (real_file_name, "%s/%s", dir_name, direntry->d_name);
        }
+
+      struct stat64 lstat_buf;
 #ifdef _DIRENT_HAVE_D_TYPE
+      /* We optimize and try to do the lstat call only if needed.  */
       if (direntry->d_type != DT_UNKNOWN)
        lstat_buf.st_mode = DTTOIF (direntry->d_type);
       else
 #endif
-       if (lstat64 (real_file_name, &lstat_buf))
+       if (__builtin_expect (lstat64 (real_file_name, &lstat_buf), 0))
          {
-           error (0, errno, _("Can't lstat %s"), file_name);
+           error (0, errno, _("Cannot lstat %s"), file_name);
            continue;
          }
 
-      is_link = S_ISLNK (lstat_buf.st_mode);
+      struct stat64 stat_buf;
+      int is_dir;
+      int is_link = S_ISLNK (lstat_buf.st_mode);
       if (is_link)
-        {
+       {
          /* In case of symlink, we check if the symlink refers to
             a directory. */
-         if (stat64 (real_file_name, &stat_buf))
+         char *target_name = real_file_name;
+         if (opt_chroot)
+           {
+             target_name = chroot_canon (opt_chroot, file_name);
+             if (target_name == NULL)
+               {
+                 if (strstr (file_name, ".so") == NULL)
+                   error (0, 0, _("Input file %s not found.\n"), file_name);
+                 continue;
+               }
+           }
+         if (__builtin_expect (stat64 (target_name, &stat_buf), 0))
            {
              if (opt_verbose)
-               error (0, errno, _("Can't stat %s"), file_name);
+               error (0, errno, _("Cannot stat %s"), file_name);
+
+             /* Remove stale symlinks.  */
+             if (strstr (direntry->d_name, ".so."))
+               unlink (real_file_name);
              continue;
            }
          is_dir = S_ISDIR (stat_buf.st_mode);
+
+         /* lstat_buf is later stored, update contents.  */
+         lstat_buf.st_dev = stat_buf.st_dev;
+         lstat_buf.st_ino = stat_buf.st_ino;
+         lstat_buf.st_size = stat_buf.st_size;
+         lstat_buf.st_ctime = stat_buf.st_ctime;
        }
       else
        is_dir = S_ISDIR (lstat_buf.st_mode);
@@ -711,22 +824,28 @@ search_dir (const struct dir_entry *entry)
          new_entry->path = xstrdup (file_name);
          new_entry->flag = entry->flag;
          new_entry->next = NULL;
-         if (is_link)
-           {
-             new_entry->ino = stat_buf.st_ino;
-             new_entry->dev = stat_buf.st_dev;
-           }
-         else
+#ifdef _DIRENT_HAVE_D_TYPE
+         /* We have filled in lstat only #ifndef
+            _DIRENT_HAVE_D_TYPE.  Fill it in if needed.  */
+         if (!is_link
+             && direntry->d_type != DT_UNKNOWN
+             && __builtin_expect (lstat64 (real_file_name, &lstat_buf), 0))
            {
-             new_entry->ino = lstat_buf.st_ino;
-             new_entry->dev = lstat_buf.st_dev;
+             error (0, errno, _("Cannot lstat %s"), file_name);
+             free (new_entry->path);
+             free (new_entry);
+             continue;
            }
+#endif
+         new_entry->ino = lstat_buf.st_ino;
+         new_entry->dev = lstat_buf.st_dev;
          add_single_dir (new_entry, 0);
          continue;
        }
       else if (!S_ISREG (lstat_buf.st_mode) && !is_link)
        continue;
 
+      char *real_name;
       if (opt_chroot && is_link)
        {
          real_name = chroot_canon (opt_chroot, file_name);
@@ -740,18 +859,57 @@ search_dir (const struct dir_entry *entry)
       else
        real_name = real_file_name;
 
-      if (process_file (real_name, file_name, direntry->d_name, &flag,
-                       &osversion, &soname, is_link))
+#ifdef _DIRENT_HAVE_D_TYPE
+      /* Call lstat64 if not done yet.  */
+      if (!is_link
+         && direntry->d_type != DT_UNKNOWN
+         && __builtin_expect (lstat64 (real_file_name, &lstat_buf), 0))
        {
-         if (real_name != real_file_name)
-           free (real_name);
+         error (0, errno, _("Cannot lstat %s"), file_name);
          continue;
        }
+#endif
+
+      /* First search whether the auxiliary cache contains this
+        library already and it's not changed.  */
+      char *soname;
+      unsigned int osversion;
+      if (!search_aux_cache (&lstat_buf, &flag, &osversion, &soname))
+       {
+         if (process_file (real_name, file_name, direntry->d_name, &flag,
+                           &osversion, &soname, is_link, &lstat_buf))
+           {
+             if (real_name != real_file_name)
+               free (real_name);
+             continue;
+           }
+         else if (opt_build_cache)
+           add_to_aux_cache (&lstat_buf, flag, osversion, soname);
+       }
+
+      if (soname == NULL)
+       soname = implicit_soname (direntry->d_name, flag);
+
+      /* A link may just point to itself.  */
+      if (is_link)
+       {
+         /* If the path the link points to isn't its soname and it is not
+            .so symlink for ld(1) only, we treat it as a normal file.  */
+         const char *real_base_name = basename (real_file_name);
+
+         if (strcmp (real_base_name, soname) != 0)
+           {
+             len = strlen (real_base_name);
+             if (len < strlen (".so")
+                 || strcmp (real_base_name + len - strlen (".so"), ".so") != 0
+                 || strncmp (real_base_name, soname, len) != 0)
+               is_link = 0;
+           }
+       }
 
       if (real_name != real_file_name)
        free (real_name);
 
-      /* Links will just point to itself.  */
       if (is_link)
        {
          free (soname);
@@ -762,6 +920,7 @@ search_dir (const struct dir_entry *entry)
          && (entry->flag == FLAG_ELF_LIBC5
              || entry->flag == FLAG_ELF_LIBC6))
        flag = entry->flag;
+
       /* Some sanity checks to print warnings.  */
       if (opt_verbose)
        {
@@ -777,6 +936,7 @@ search_dir (const struct dir_entry *entry)
        }
 
       /* Add library to list.  */
+      struct dlib_entry *dlib_ptr;
       for (dlib_ptr = dlibs; dlib_ptr != NULL; dlib_ptr = dlib_ptr->next)
        {
          /* Is soname already in list?  */
@@ -801,12 +961,13 @@ search_dir (const struct dir_entry *entry)
                        dlib_ptr->flag = flag;
                      else
                        error (0, 0, _("libraries %s and %s in directory %s have same soname but different type."),
-                              dlib_ptr->name, direntry->d_name, entry->path);
+                              dlib_ptr->name, direntry->d_name,
+                              entry->path);
                    }
                  free (dlib_ptr->name);
-                 dlib_ptr->osversion = osversion;
                  dlib_ptr->name = xstrdup (direntry->d_name);
                  dlib_ptr->is_link = is_link;
+                 dlib_ptr->osversion = osversion;
                }
              /* Don't add this library, abort loop.  */
              /* Also free soname, since it's dynamically allocated.  */
@@ -819,10 +980,10 @@ search_dir (const struct dir_entry *entry)
        {
          dlib_ptr = (struct dlib_entry *)xmalloc (sizeof (struct dlib_entry));
          dlib_ptr->name = xstrdup (direntry->d_name);
-         dlib_ptr->flag = flag;
-         dlib_ptr->osversion = osversion;
          dlib_ptr->soname = soname;
+         dlib_ptr->flag = flag;
          dlib_ptr->is_link = is_link;
+         dlib_ptr->osversion = osversion;
          /* Add at head of list.  */
          dlib_ptr->next = dlibs;
          dlibs = dlib_ptr;
@@ -833,6 +994,7 @@ search_dir (const struct dir_entry *entry)
 
   /* Now dlibs contains a list of all libs - add those to the cache
      and created all symbolic links.  */
+  struct dlib_entry *dlib_ptr;
   for (dlib_ptr = dlibs; dlib_ptr != NULL; dlib_ptr = dlib_ptr->next)
     {
       /* Don't create links to links.  */
@@ -878,16 +1040,20 @@ search_dirs (void)
 }
 
 
+static void parse_conf_include (const char *config_file, unsigned int lineno,
+                               bool do_chroot, const char *pattern);
+
 /* Parse configuration file.  */
 static void
-parse_conf (const char *filename)
+parse_conf (const char *filename, bool do_chroot)
 {
   FILE *file = NULL;
   char *line = NULL;
   const char *canon;
   size_t len = 0;
+  unsigned int lineno;
 
-  if (opt_chroot)
+  if (do_chroot && opt_chroot)
     {
       canon = chroot_canon (opt_chroot, filename);
       if (canon)
@@ -903,7 +1069,9 @@ parse_conf (const char *filename)
 
   if (file == NULL)
     {
-      error (0, errno, _("Can't open configuration file %s"), canon);
+      error (0, errno, _("\
+Warning: ignoring configuration file that cannot be opened: %s"),
+            canon);
       if (canon != filename)
        free ((char *) canon);
       return;
@@ -915,12 +1083,14 @@ parse_conf (const char *filename)
   if (canon != filename)
     free ((char *) canon);
 
+  lineno = 0;
   do
     {
       ssize_t n = getline (&line, &len, file);
       if (n < 0)
        break;
 
+      ++lineno;
       if (line[n - 1] == '\n')
        line[n - 1] = '\0';
 
@@ -929,18 +1099,139 @@ parse_conf (const char *filename)
         make it terminating the line.  */
       *strchrnul (line, '#') = '\0';
 
+      /* Remove leading whitespace.  NUL is no whitespace character.  */
+      char *cp = line;
+      while (isspace (*cp))
+       ++cp;
+
       /* If the line is blank it is ignored.  */
-      if (line[0] == '\0')
+      if (cp[0] == '\0')
        continue;
 
-      add_dir (line);
-    } while (!feof (file));
+      if (!strncmp (cp, "include", 7) && isblank (cp[7]))
+       {
+         char *dir;
+         cp += 8;
+         while ((dir = strsep (&cp, " \t")) != NULL)
+           if (dir[0] != '\0')
+             parse_conf_include (filename, lineno, do_chroot, dir);
+       }
+      else if (!strncasecmp (cp, "hwcap", 5) && isblank (cp[5]))
+       {
+         cp += 6;
+         char *p, *name = NULL;
+         unsigned long int n = strtoul (cp, &cp, 0);
+         if (cp != NULL && isblank (*cp))
+           while ((p = strsep (&cp, " \t")) != NULL)
+             if (p[0] != '\0')
+               {
+                 if (name == NULL)
+                   name = p;
+                 else
+                   {
+                     name = NULL;
+                     break;
+                   }
+               }
+         if (name == NULL)
+           {
+             error (EXIT_FAILURE, 0, _("%s:%u: bad syntax in hwcap line"),
+                    filename, lineno);
+             break;
+           }
+         if (n >= (64 - _DL_FIRST_EXTRA))
+           error (EXIT_FAILURE, 0,
+                  _("%s:%u: hwcap index %lu above maximum %u"),
+                  filename, lineno, n, 64 - _DL_FIRST_EXTRA - 1);
+         if (hwcap_extra[n] == NULL)
+           {
+             for (unsigned long int h = 0; h < (64 - _DL_FIRST_EXTRA); ++h)
+               if (hwcap_extra[h] != NULL && !strcmp (name, hwcap_extra[h]))
+                 error (EXIT_FAILURE, 0,
+                        _("%s:%u: hwcap index %lu already defined as %s"),
+                        filename, lineno, h, name);
+             hwcap_extra[n] = xstrdup (name);
+           }
+         else
+           {
+             if (strcmp (name, hwcap_extra[n]))
+               error (EXIT_FAILURE, 0,
+                      _("%s:%u: hwcap index %lu already defined as %s"),
+                      filename, lineno, n, hwcap_extra[n]);
+             if (opt_verbose)
+               error (0, 0, _("%s:%u: duplicate hwcap %lu %s"),
+                      filename, lineno, n, name);
+           }
+       }
+      else
+       add_dir (cp);
+    }
+  while (!feof_unlocked (file));
 
   /* Free buffer and close file.  */
   free (line);
   fclose (file);
 }
 
+/* Handle one word in an `include' line, a glob pattern of additional
+   config files to read.  */
+static void
+parse_conf_include (const char *config_file, unsigned int lineno,
+                   bool do_chroot, const char *pattern)
+{
+  if (opt_chroot && pattern[0] != '/')
+    error (EXIT_FAILURE, 0,
+          _("need absolute file name for configuration file when using -r"));
+
+  char *copy = NULL;
+  if (pattern[0] != '/' && strchr (config_file, '/') != NULL)
+    {
+      if (asprintf (&copy, "%s/%s", dirname (strdupa (config_file)),
+                   pattern) < 0)
+       error (EXIT_FAILURE, 0, _("memory exhausted"));
+      pattern = copy;
+    }
+
+  glob64_t gl;
+  int result;
+  if (do_chroot && opt_chroot)
+    {
+      char *canon = chroot_canon (opt_chroot, pattern);
+      if (canon == NULL)
+       return;
+      result = glob64 (canon, 0, NULL, &gl);
+      free (canon);
+    }
+  else
+    result = glob64 (pattern, 0, NULL, &gl);
+
+  switch (result)
+    {
+    case 0:
+      for (size_t i = 0; i < gl.gl_pathc; ++i)
+       parse_conf (gl.gl_pathv[i], false);
+      globfree64 (&gl);
+      break;
+
+    case GLOB_NOMATCH:
+      break;
+
+    case GLOB_NOSPACE:
+      errno = ENOMEM;
+    case GLOB_ABORTED:
+      if (opt_verbose)
+       error (0, errno, _("%s:%u: cannot read directory %s"),
+              config_file, lineno, pattern);
+      break;
+
+    default:
+      abort ();
+      break;
+    }
+
+  free (copy);
+}
+
 /* Honour LD_HWCAP_MASK.  */
 static void
 set_hwcap (void)
@@ -955,26 +1246,42 @@ set_hwcap (void)
 int
 main (int argc, char **argv)
 {
-  int remaining;
+  /* Set locale via LC_ALL.  */
+  setlocale (LC_ALL, "");
+
+  /* Set the text message domain.  */
+  textdomain (_libc_intl_domainname);
 
   /* Parse and process arguments.  */
+  int remaining;
   argp_parse (&argp, argc, argv, 0, &remaining, NULL);
 
-  /* Remaining arguments are additional libraries if opt_manual_link
+  /* Remaining arguments are additional directories if opt_manual_link
      is not set.  */
   if (remaining != argc && !opt_manual_link)
     {
       int i;
       for (i = remaining; i < argc; ++i)
-       add_dir (argv[i]);
+       if (opt_build_cache && argv[i][0] != '/')
+         error (EXIT_FAILURE, 0,
+                _("relative path `%s' used to build cache"),
+                argv[i]);
+       else
+         add_dir (argv[i]);
     }
 
+  /* The last entry in hwcap_extra is reserved for the "tls" pseudo-hwcap which
+     indicates support for TLS.  This pseudo-hwcap is only used by old versions
+     under which TLS support was optional.  The entry is no longer needed, but
+     must remain for compatibility.  */
+  hwcap_extra[63 - _DL_FIRST_EXTRA] = "tls";
+
   set_hwcap ();
 
   if (opt_chroot)
     {
       /* Normalize the path a bit, we might need it for printing later.  */
-      char *endp = strchr (opt_chroot, '\0');
+      char *endp = rawmemchr (opt_chroot, '\0');
       while (endp > opt_chroot && endp[-1] == '/')
        --endp;
       *endp = '\0';
@@ -1021,17 +1328,15 @@ main (int argc, char **argv)
   if (opt_chroot)
     {
       /* Canonicalize the directory name of cache_file, not cache_file,
-         because we'll rename a temporary cache file to it.  */
+        because we'll rename a temporary cache file to it.  */
       char *p = strrchr (cache_file, '/');
       char *canon = chroot_canon (opt_chroot,
                                  p ? (*p = '\0', cache_file) : "/");
 
       if (canon == NULL)
-       {
-         error (EXIT_FAILURE, errno,
-                _("Can't open cache file directory %s\n"),
-                p ? cache_file : "/");
-       }
+       error (EXIT_FAILURE, errno,
+              _("Can't open cache file directory %s\n"),
+              p ? cache_file : "/");
 
       if (p)
        ++p;
@@ -1060,18 +1365,31 @@ main (int argc, char **argv)
 
   if (!opt_only_cline)
     {
+      parse_conf (config_file, true);
+
       /* Always add the standard search paths.  */
-      add_dir (SLIBDIR);
+      add_system_dir (SLIBDIR);
       if (strcmp (SLIBDIR, LIBDIR))
-       add_dir (LIBDIR);
-
-      parse_conf (config_file);
+       add_system_dir (LIBDIR);
     }
 
+  const char *aux_cache_file = _PATH_LDCONFIG_AUX_CACHE;
+  if (opt_chroot)
+    aux_cache_file = chroot_canon (opt_chroot, aux_cache_file);
+
+  if (! opt_ignore_aux_cache && aux_cache_file)
+    load_aux_cache (aux_cache_file);
+  else
+    init_aux_cache ();
+
   search_dirs ();
 
   if (opt_build_cache)
-    save_cache (cache_file);
+    {
+      save_cache (cache_file);
+      if (aux_cache_file)
+       save_aux_cache (aux_cache_file);
+    }
 
   return 0;
 }