changeset 19104:4e436f948972

glob: match dangling symlinks This fixes a bug I inadvertently introduced to Gnulib when I merged glibc glob back into gnulib on 2007-10-16. This fix is inspired by a patch proposed for glibc by Adhemerval Zanella in: https://sourceware.org/ml/libc-alpha/2017-08/msg00446.html * doc/posix-functions/glob.texi: Update list of affected platforms. * lib/glob.c (__lstat64): New macro. (is_dir): New function. (glob, glob_in_dir): Match symlinks even if they are dangling. (link_stat, link_exists_p): Remove. All uses removed. * lib/glob.in.h (__attribute_noinline__): Remove; no longer used. * m4/glob.m4 (gl_PREREQ_GLOB): Do not check for fstatat. * modules/glob (Depends-on): Remove dirfd. * modules/glob-tests (Depends-on): Add symlink. * tests/test-glob.c: Include errno.h, unistd.h. (BASE): New macro. (main): Test dangling symlinks, if symlinks are supported.
author Paul Eggert <eggert@cs.ucla.edu>
date Thu, 31 Aug 2017 14:34:25 -0700
parents d2497bce0489
children 346b97ee61a1
files ChangeLog doc/posix-functions/glob.texi lib/glob.c lib/glob.in.h m4/glob.m4 modules/glob modules/glob-tests tests/test-glob.c
diffstat 8 files changed, 124 insertions(+), 162 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Aug 31 14:34:24 2017 -0700
+++ b/ChangeLog	Thu Aug 31 14:34:25 2017 -0700
@@ -1,5 +1,22 @@
 2017-08-31  Paul Eggert  <eggert@cs.ucla.edu>
 
+	glob: match dangling symlinks
+	This fixes a bug I inadvertently introduced to Gnulib when I
+	merged glibc glob back into gnulib on 2007-10-16.  This fix is
+	inspired by a patch proposed for glibc by Adhemerval Zanella in:
+	https://sourceware.org/ml/libc-alpha/2017-08/msg00446.html
+	* doc/posix-functions/glob.texi: Update list of affected platforms.
+	* lib/glob.c (__lstat64): New macro.
+	(is_dir): New function.
+	(glob, glob_in_dir): Match symlinks even if they are dangling.
+	(link_stat, link_exists_p): Remove.  All uses removed.
+	* lib/glob.in.h (__attribute_noinline__): Remove; no longer used.
+	* m4/glob.m4 (gl_PREREQ_GLOB): Do not check for fstatat.
+	* modules/glob-tests (Depends-on): Add symlink.
+	* tests/test-glob.c: Include errno.h, unistd.h.
+	(BASE): New macro.
+	(main): Test dangling symlinks, if symlinks are supported.
+
 	glob, backupfile: inode 0 is a valid inode number
 	* doc/posix-functions/readdir.texi (readdir):
 	* doc/posix-headers/dirent.texi (dirent.h):
--- a/doc/posix-functions/glob.texi	Thu Aug 31 14:34:24 2017 -0700
+++ b/doc/posix-functions/glob.texi	Thu Aug 31 14:34:25 2017 -0700
@@ -14,7 +14,7 @@
 @item
 This function does not list symbolic links to nonexistent files among the results,
 on some platforms:
-glibc 2.14, AIX 7.1, HP-UX 11, Solaris 11 2011-11.
+glibc 2.26, AIX 7.2, HP-UX 11, Solaris 11 2011-11.
 @item
 On platforms where @code{off_t} is a 32-bit type, this function may not
 work correctly on huge directories larger than 2 GB.
--- a/lib/glob.c	Thu Aug 31 14:34:24 2017 -0700
+++ b/lib/glob.c	Thu Aug 31 14:34:25 2017 -0700
@@ -57,6 +57,9 @@
 # define readdir(str) __readdir64 (str)
 # define getpwnam_r(name, bufp, buf, len, res) \
     __getpwnam_r (name, bufp, buf, len, res)
+# ifndef __lstat64
+#  define __lstat64(fname, buf) __lxstat64 (_STAT_VER, fname, buf)
+# endif
 # ifndef __stat64
 #  define __stat64(fname, buf) __xstat64 (_STAT_VER, fname, buf)
 # endif
@@ -64,6 +67,7 @@
 # define FLEXIBLE_ARRAY_MEMBER
 #else /* !_LIBC */
 # define __getlogin_r(buf, len) getlogin_r (buf, len)
+# define __lstat64(fname, buf)  lstat (fname, buf)
 # define __stat64(fname, buf)   stat (fname, buf)
 # define __fxstatat64(_, d, f, st, flag) fstatat (d, f, st, flag)
 # define struct_stat64          struct stat
@@ -226,6 +230,18 @@
 static int collated_compare (const void *, const void *) __THROWNL;
 
 
+/* Return true if FILENAME is a directory or a symbolic link to a directory.
+   Use FLAGS and PGLOB to resolve the filename.  */
+static bool
+is_dir (char const *filename, int flags, glob_t const *pglob)
+{
+  struct stat st;
+  struct_stat64 st64;
+  return (__glibc_unlikely (flags & GLOB_ALTDIRFUNC)
+          ? pglob->gl_stat (filename, &st) == 0 && S_ISDIR (st.st_mode)
+          : __stat64 (filename, &st64) == 0 && S_ISDIR (st64.st_mode));
+}
+
 /* Find the end of the sub-pattern in a brace expression.  */
 static const char *
 next_brace_sub (const char *cp, int flags)
@@ -976,68 +992,53 @@
      can give the answer now.  */
   if (filename == NULL)
     {
-      struct stat st;
-      struct_stat64 st64;
+      size_t newcount = pglob->gl_pathc + pglob->gl_offs;
+      char **new_gl_pathv;
+
+      if (newcount > SIZE_MAX / sizeof (char *) - 2)
+        {
+        nospace:
+          free (pglob->gl_pathv);
+          pglob->gl_pathv = NULL;
+          pglob->gl_pathc = 0;
+          retval = GLOB_NOSPACE;
+          goto out;
+        }
 
-      /* Return the directory if we don't check for error or if it exists.  */
-      if ((flags & GLOB_NOCHECK)
-          || (((__builtin_expect (flags & GLOB_ALTDIRFUNC, 0))
-               ? ((*pglob->gl_stat) (dirname, &st) == 0
-                  && S_ISDIR (st.st_mode))
-               : (__stat64 (dirname, &st64) == 0 && S_ISDIR (st64.st_mode)))))
+      new_gl_pathv = realloc (pglob->gl_pathv,
+                              (newcount + 2) * sizeof (char *));
+      if (new_gl_pathv == NULL)
+        goto nospace;
+      pglob->gl_pathv = new_gl_pathv;
+
+      if (flags & GLOB_MARK && is_dir (dirname, flags, pglob))
         {
-          size_t newcount = pglob->gl_pathc + pglob->gl_offs;
-          char **new_gl_pathv;
-
-          if (newcount > SIZE_MAX / sizeof (char *) - 2)
+          char *p;
+          pglob->gl_pathv[newcount] = malloc (dirlen + 2);
+          if (pglob->gl_pathv[newcount] == NULL)
+            goto nospace;
+          p = mempcpy (pglob->gl_pathv[newcount], dirname, dirlen);
+          p[0] = '/';
+          p[1] = '\0';
+          if (__glibc_unlikely (malloc_dirname))
+            free (dirname);
+        }
+      else
+        {
+          if (__glibc_unlikely (malloc_dirname))
+            pglob->gl_pathv[newcount] = dirname;
+          else
             {
-            nospace:
-              free (pglob->gl_pathv);
-              pglob->gl_pathv = NULL;
-              pglob->gl_pathc = 0;
-              retval = GLOB_NOSPACE;
-              goto out;
-            }
-
-          new_gl_pathv = realloc (pglob->gl_pathv,
-                                  (newcount + 2) * sizeof (char *));
-          if (new_gl_pathv == NULL)
-            goto nospace;
-          pglob->gl_pathv = new_gl_pathv;
-
-          if (flags & GLOB_MARK)
-            {
-              char *p;
-              pglob->gl_pathv[newcount] = malloc (dirlen + 2);
+              pglob->gl_pathv[newcount] = strdup (dirname);
               if (pglob->gl_pathv[newcount] == NULL)
                 goto nospace;
-              p = mempcpy (pglob->gl_pathv[newcount], dirname, dirlen);
-              p[0] = '/';
-              p[1] = '\0';
-              if (__glibc_unlikely (malloc_dirname))
-                free (dirname);
             }
-          else
-            {
-              if (__glibc_unlikely (malloc_dirname))
-                pglob->gl_pathv[newcount] = dirname;
-              else
-                {
-                  pglob->gl_pathv[newcount] = strdup (dirname);
-                  if (pglob->gl_pathv[newcount] == NULL)
-                    goto nospace;
-                }
-            }
-          pglob->gl_pathv[++newcount] = NULL;
-          ++pglob->gl_pathc;
-          pglob->gl_flags = flags;
+        }
+      pglob->gl_pathv[++newcount] = NULL;
+      ++pglob->gl_pathc;
+      pglob->gl_flags = flags;
 
-          return 0;
-        }
-
-      /* Not found.  */
-      retval = GLOB_NOMATCH;
-      goto out;
+      return 0;
     }
 
   meta = __glob_pattern_type (dirname, !(flags & GLOB_NOESCAPE));
@@ -1245,15 +1246,9 @@
     {
       /* Append slashes to directory names.  */
       size_t i;
-      struct stat st;
-      struct_stat64 st64;
 
       for (i = oldcount; i < pglob->gl_pathc + pglob->gl_offs; ++i)
-        if ((__builtin_expect (flags & GLOB_ALTDIRFUNC, 0)
-             ? ((*pglob->gl_stat) (pglob->gl_pathv[i], &st) == 0
-                && S_ISDIR (st.st_mode))
-             : (__stat64 (pglob->gl_pathv[i], &st64) == 0
-                && S_ISDIR (st64.st_mode))))
+        if (is_dir (pglob->gl_pathv[i], flags, pglob))
           {
             size_t len = strlen (pglob->gl_pathv[i]) + 2;
             char *new = realloc (pglob->gl_pathv[i], len);
@@ -1359,56 +1354,6 @@
   return 0;
 }
 
-/* We put this in a separate function mainly to allow the memory
-   allocated with alloca to be recycled.  */
-static int
-__attribute_noinline__
-link_stat (const char *dir, size_t dirlen, const char *fname,
-           glob_t *pglob
-# if !defined _LIBC && !HAVE_FSTATAT
-           , int flags
-# endif
-           )
-{
-  size_t fnamelen = strlen (fname);
-  char *fullname = __alloca (dirlen + 1 + fnamelen + 1);
-  struct stat st;
-
-  mempcpy (mempcpy (mempcpy (fullname, dir, dirlen), "/", 1),
-           fname, fnamelen + 1);
-
-# if !defined _LIBC && !HAVE_FSTATAT
-  if (__builtin_expect ((flags & GLOB_ALTDIRFUNC) == 0, 1))
-    {
-      struct_stat64 st64;
-      return __stat64 (fullname, &st64);
-    }
-# endif
-  return (*pglob->gl_stat) (fullname, &st);
-}
-
-/* Return true if DIR/FNAME exists.  */
-static int
-link_exists_p (int dfd, const char *dir, size_t dirlen, const char *fname,
-               glob_t *pglob, int flags)
-{
-  int status;
-# if defined _LIBC || HAVE_FSTATAT
-  if (__builtin_expect (flags & GLOB_ALTDIRFUNC, 0))
-    status = link_stat (dir, dirlen, fname, pglob);
-  else
-    {
-      /* dfd cannot be -1 here, because dirfd never returns -1 on
-         glibc, or on hosts that have fstatat.  */
-      struct_stat64 st64;
-      status = __fxstatat64 (_STAT_VER, dfd, fname, &st64, 0);
-    }
-# else
-  status = link_stat (dir, dirlen, fname, pglob, flags);
-# endif
-  return status == 0 || errno == EOVERFLOW;
-}
-
 /* Like 'glob', but PATTERN is a final pathname component,
    and matches are searched for in DIRECTORY.
    The GLOB_NOSORT bit in FLAGS is ignored.  No sorting is ever done.
@@ -1450,8 +1395,6 @@
     }
   else if (meta == 0)
     {
-      /* Since we use the normal file functions we can also use stat()
-         to verify the file is there.  */
       union
       {
         struct stat st;
@@ -1476,8 +1419,8 @@
                         "/", 1),
                pattern, patlen + 1);
       if (((__builtin_expect (flags & GLOB_ALTDIRFUNC, 0)
-            ? (*pglob->gl_stat) (fullname, &ust.st)
-            : __stat64 (fullname, &ust.st64))
+            ? (*pglob->gl_lstat) (fullname, &ust.st)
+            : __lstat64 (fullname, &ust.st64))
            == 0)
           || errno == EOVERFLOW)
         /* We found this file to be existing.  Now tell the rest
@@ -1501,8 +1444,6 @@
         }
       else
         {
-          int dfd = (__builtin_expect (flags & GLOB_ALTDIRFUNC, 0)
-                     ? -1 : dirfd ((DIR *) stream));
           int fnm_flags = ((!(flags & GLOB_PERIOD) ? FNM_PERIOD : 0)
                            | ((flags & GLOB_NOESCAPE) ? FNM_NOESCAPE : 0));
           flags |= GLOB_MAGCHAR;
@@ -1536,42 +1477,34 @@
 
               if (fnmatch (pattern, d.name, fnm_flags) == 0)
                 {
-                  /* If the file we found is a symlink we have to
-                     make sure the target file exists.  */
-                  dirent_type type = readdir_result_type (d);
-                  if (! (type == DT_LNK || type == DT_UNKNOWN)
-                      || link_exists_p (dfd, directory, dirlen, d.name,
-                                        pglob, flags))
+                  if (cur == names->count)
                     {
-                      if (cur == names->count)
-                        {
-                          struct globnames *newnames;
-                          size_t count = names->count * 2;
-                          size_t nameoff = offsetof (struct globnames, name);
-                          size_t size = FLEXSIZEOF (struct globnames, name,
-                                                    count * sizeof (char *));
-                          if ((SIZE_MAX - nameoff) / 2 / sizeof (char *)
-                              < names->count)
-                            goto memory_error;
-                          if (glob_use_alloca (alloca_used, size))
-                            newnames = names_alloca
-                              = alloca_account (size, alloca_used);
-                          else if ((newnames = malloc (size))
-                                   == NULL)
-                            goto memory_error;
-                          newnames->count = count;
-                          newnames->next = names;
-                          names = newnames;
-                          cur = 0;
-                        }
-                      names->name[cur] = strdup (d.name);
-                      if (names->name[cur] == NULL)
+                      struct globnames *newnames;
+                      size_t count = names->count * 2;
+                      size_t nameoff = offsetof (struct globnames, name);
+                      size_t size = FLEXSIZEOF (struct globnames, name,
+                                                count * sizeof (char *));
+                      if ((SIZE_MAX - nameoff) / 2 / sizeof (char *)
+                          < names->count)
                         goto memory_error;
-                      ++cur;
-                      ++nfound;
-                      if (SIZE_MAX - pglob->gl_offs <= nfound)
+                      if (glob_use_alloca (alloca_used, size))
+                        newnames = names_alloca
+                          = alloca_account (size, alloca_used);
+                      else if ((newnames = malloc (size))
+                               == NULL)
                         goto memory_error;
+                      newnames->count = count;
+                      newnames->next = names;
+                      names = newnames;
+                      cur = 0;
                     }
+                  names->name[cur] = strdup (d.name);
+                  if (names->name[cur] == NULL)
+                    goto memory_error;
+                  ++cur;
+                  ++nfound;
+                  if (SIZE_MAX - pglob->gl_offs <= nfound)
+                    goto memory_error;
                 }
             }
         }
--- a/lib/glob.in.h	Thu Aug 31 14:34:24 2017 -0700
+++ b/lib/glob.in.h	Thu Aug 31 14:34:25 2017 -0700
@@ -49,14 +49,6 @@
 
 #define attribute_hidden
 
-#ifndef __attribute_noinline__
-# if 3 < __GNUC__ + (1 <= __GNUC_MINOR__)
-#  define __attribute_noinline__ __attribute__ ((__noinline__))
-#else
-#  define __attribute_noinline__ /* Ignore */
-# endif
-#endif
-
 #if __GNUC__ < 3
 # define __glibc_unlikely(cond) (cond)
 #else
--- a/m4/glob.m4	Thu Aug 31 14:34:24 2017 -0700
+++ b/m4/glob.m4	Thu Aug 31 14:34:25 2017 -0700
@@ -1,4 +1,4 @@
-# glob.m4 serial 14
+# glob.m4 serial 15
 dnl Copyright (C) 2005-2007, 2009-2017 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -72,5 +72,5 @@
     HAVE_SYS_CDEFS_H=0
   fi
   AC_SUBST([HAVE_SYS_CDEFS_H])
-  AC_CHECK_FUNCS_ONCE([fstatat getlogin_r getpwnam_r])dnl
+  AC_CHECK_FUNCS_ONCE([getlogin_r getpwnam_r])dnl
 ])
--- a/modules/glob	Thu Aug 31 14:34:24 2017 -0700
+++ b/modules/glob	Thu Aug 31 14:34:25 2017 -0700
@@ -21,7 +21,6 @@
 builtin-expect  [test -n "$GLOB_H"]
 closedir        [test -n "$GLOB_H"]
 d-type          [test -n "$GLOB_H"]
-dirfd           [test -n "$GLOB_H"]
 flexmember      [test -n "$GLOB_H"]
 fnmatch         [test -n "$GLOB_H"]
 getlogin_r      [test -n "$GLOB_H"]
--- a/modules/glob-tests	Thu Aug 31 14:34:24 2017 -0700
+++ b/modules/glob-tests	Thu Aug 31 14:34:25 2017 -0700
@@ -5,6 +5,7 @@
 
 Depends-on:
 glob-c++-tests
+symlink
 
 configure.ac:
 
--- a/tests/test-glob.c	Thu Aug 31 14:34:24 2017 -0700
+++ b/tests/test-glob.c	Thu Aug 31 14:34:25 2017 -0700
@@ -20,6 +20,9 @@
 
 #include <glob.h>
 
+#include <errno.h>
+#include <unistd.h>
+
 #include "signature.h"
 SIGNATURE_CHECK (glob, int, (char const *, int, int (*) (char const *, int),
                              glob_t *));
@@ -29,6 +32,7 @@
 
 #include "macros.h"
 
+#define BASE "test-glob.t"
 #define GL_NO_SUCH_FILE "/gnulib-magic-does-not-exist"
 
 int
@@ -73,5 +77,21 @@
   ASSERT (strcmp (g.gl_pathv[0], GL_NO_SUCH_FILE) == 0);
   globfree (&g);
 
+  if ((symlink (GL_NO_SUCH_FILE, BASE "globlink1") == 0 || errno == EEXIST)
+      && (symlink (".", BASE "globlink2") == 0 || errno == EEXIST))
+    {
+      res = glob (BASE "globlink[12]", 0, NULL, &g);
+      ASSERT (res == 0 && g.gl_pathc == 2);
+      ASSERT (strcmp (g.gl_pathv[0], BASE "globlink1") == 0);
+      ASSERT (strcmp (g.gl_pathv[1], BASE "globlink2") == 0);
+      globfree (&g);
+
+      res = glob (BASE "globlink[12]", GLOB_MARK, NULL, &g);
+      ASSERT (res == 0 && g.gl_pathc == 2);
+      ASSERT (strcmp (g.gl_pathv[0], BASE "globlink1") == 0);
+      ASSERT (strcmp (g.gl_pathv[1], BASE "globlink2/") == 0);
+      globfree (&g);
+    }
+
   return 0;
 }