[04/18] posix: Allow glob to match dangling symlinks [BZ #866]

Message ID 1502463044-4042-5-git-send-email-adhemerval.zanella@linaro.org
State New
Headers show
Series
  • Untitled series #3286
Related show

Commit Message

Adhemerval Zanella Aug. 11, 2017, 2:50 p.m.
This patch makes glob match dangling symlinks.  Compared to other glob
implementation (*BSD, bash, musl, and other shells as well), GLIBC seems
the be the only one that does not match dangling symlinks.  As for
comment #5 in BZ #866, POSIX does not have any strict specification for
dangling symlinks match and it is reasonable that trying to glob everything
in a path should return all types of files (such as for a 'rm *').  Also,
comment #7 shows even more example where GLIBC current behavior is
unexepected.

I avoided adding another GNU specific flag to set this behavior and
instead make it the default.  Although this change the semanthic from
previous implementation, I think adding another compat symbol to be
really unecessary as from aforementioned reasons (current behavior not
defined in any standard, general idea of different implementation is
to list dangling symbols).

Checked on x86_64-linux-gnu.

	* posix/Makefile (tests): Add tst-glob_symlinks and remove tst-glob3.
	* posix/bug-glob1.c: Remove file.
	* posix/glob.c (glob): Match dangling symlinks.
	(link_exists2_p): Remove function.
	(link_exists_p): Likewise.
	* posix/tst-glob_symlinks.c: New file.
	* sysdeps/gnu/glob64.c (__stat): Redefine to __lstat.
	* sysdeps/unix/sysv/linux/i386/glob64.c (__stat): Likewise.
---
 posix/Makefile            |   3 +-
 posix/bug-glob1.c         |  88 ------------------------------
 posix/glob.c              | 124 +++++++++++-------------------------------
 posix/tst-glob_symlinks.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 165 insertions(+), 183 deletions(-)
 delete mode 100644 posix/bug-glob1.c
 create mode 100644 posix/tst-glob_symlinks.c

-- 
2.7.4

Comments

Paul Eggert Aug. 31, 2017, 10:11 p.m. | #1
Thanks for working to clear up this longstanding problem in glibc glob. 
Here are the issues I found with the proposed patch:

* glob_in_dir calls gl_stat, which is a typo; it should call gl_lstat.

* The GLOB_MARK checks should use stat not lstat, since symlinks to 
directories should be marked.

* For GLOB_TILDE and GLOB_TILDE_CHECK, the revised code still tests for 
existence of the home directory as a directory. Other glob 
implementations merely expand the ~ or ~foo and treat the result as a 
literal string, and glibc should be consistent here.  This is simpler 
and avoids a stat call.

* __lstat64 needs to be defined in the !_LIBC case too.

* While looking into this I noticed that glob ignores directory entries 
with d_ino == 0. Although currently GNU/Linux cannot return such 
entries, POSIX allows d_ino == 0 so this assumption is not portable, and 
this bug is in the neighborhood so we might as well fix it in a 
separated patch (which simplifies the code).

* commit message says "remove tst-glob3" but the patch actually removes 
bug-glob1-ARGS.

I fixed these problems (except for the last one) while merging the patch 
into Gnulib, and this resulted in the attached patches which I installed 
into Gnulib master. I'll CC: this to bug-gnulib accordingly. Please 
consider merging the glibc-relevant parts of these patches back into 
glibc, for your next iteration of this glibc proposal. As always, the 
goal is for glob.c and related files to be identical in glibc and Gnulib.
From 1e19d5ce886d7db7c7ad074d8a7f64f75ec6f42e Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>

Date: Thu, 31 Aug 2017 14:34:24 -0700
Subject: [PATCH 1/3] glob: simplify symlink detection

* lib/glob.c (dirent_type): New type.  Use uint_fast8_t not
uint8_t, as C99 does not require uint8_t.
(DT_UNKNOWN, DT_DIR, DT_LNK) [!HAVE_STRUCT_DIRENT_D_TYPE]:
New macros.
(struct readdir_result): Use dirent_type.  Do not define skip_entry
unless it is needed; this saves a byte on platforms lacking d_ino.
(readdir_result_type, readdir_result_skip_entry):
New functions, replacing ...
(readdir_result_might_be_symlink, readdir_result_might_be_dir):
... these functions, which were removed.  This makes the callers
easier to read.  All callers changed.
(D_INO_TO_RESULT): Now empty if there is no d_ino.
---
 ChangeLog  | 14 ++++++++++++
 lib/glob.c | 75 ++++++++++++++++++++++++++++++++------------------------------
 2 files changed, 53 insertions(+), 36 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 58f42dbf6..f372dbd38 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2017-08-31  Paul Eggert  <eggert@cs.ucla.edu>
+
+	glob: simplify symlink detection
+	* lib/glob.c (dirent_type): New type.  Use uint_fast8_t not
+	uint8_t, as C99 does not require uint8_t.
+	(struct readdir_result): Use it.  Do not define skip_entry unless
+	it is needed; this saves a byte on platforms lacking d_ino.
+	(readdir_result_type, readdir_result_skip_entry):
+	New functions, replacing ...
+	(readdir_result_might_be_symlink, readdir_result_might_be_dir):
+	... these functions, which were removed.  This makes the callers
+	easier to read.  All callers changed.
+	(D_INO_TO_RESULT): Now empty if there is no d_ino.
+
 2017-08-30  Pádraig Brady  <P@draigBrady.com>
 
 	fts-tests: tag as a longrunning-test so not included by default
diff --git a/lib/glob.c b/lib/glob.c
index a1e50d709..6113af6fd 100644
--- a/lib/glob.c
+++ b/lib/glob.c
@@ -92,61 +92,59 @@
 
 static const char *next_brace_sub (const char *begin, int flags) __THROWNL;
 
+typedef uint_fast8_t dirent_type;
+
+#ifndef HAVE_STRUCT_DIRENT_D_TYPE
+/* Any distinct values will do here.
+   Undef any existing macros out of the way.  */
+# undef DT_UNKNOWN
+# undef DT_DIR
+# undef DT_LNK
+# define DT_UNKNOWN 0
+# define DT_DIR 1
+# define DT_LNK 2
+#endif
+
 /* A representation of a directory entry which does not depend on the
    layout of struct dirent, or the size of ino_t.  */
 struct readdir_result
 {
   const char *name;
 #if defined _DIRENT_HAVE_D_TYPE || defined HAVE_STRUCT_DIRENT_D_TYPE
-  uint8_t type;
+  dirent_type type;
 #endif
+#if defined _LIBC || defined D_INO_IN_DIRENT
   bool skip_entry;
+#endif
 };
 
+/* Initialize and return type member of struct readdir_result.  */
+static dirent_type
+readdir_result_type (struct readdir_result d)
+{
 #if defined _DIRENT_HAVE_D_TYPE || defined HAVE_STRUCT_DIRENT_D_TYPE
-/* Initializer based on the d_type member of struct dirent.  */
 # define D_TYPE_TO_RESULT(source) (source)->d_type,
-
-/* True if the directory entry D might be a symbolic link.  */
-static bool
-readdir_result_might_be_symlink (struct readdir_result d)
-{
-  return d.type == DT_UNKNOWN || d.type == DT_LNK;
-}
-
-/* True if the directory entry D might be a directory.  */
-static bool
-readdir_result_might_be_dir (struct readdir_result d)
-{
-  return d.type == DT_DIR || readdir_result_might_be_symlink (d);
-}
-#else /* defined _DIRENT_HAVE_D_TYPE || defined HAVE_STRUCT_DIRENT_D_TYPE */
+  return d.type;
+#else
 # define D_TYPE_TO_RESULT(source)
-
-/* If we do not have type information, symbolic links and directories
-   are always a possibility.  */
-
-static bool
-readdir_result_might_be_symlink (struct readdir_result d)
-{
-  return true;
+  return DT_UNKNOWN;
+#endif
 }
 
+/* Initialize and return skip_entry member of struct readdir_result.  */
 static bool
-readdir_result_might_be_dir (struct readdir_result d)
+readdir_result_skip_entry (struct readdir_result d)
 {
-  return true;
-}
-
-#endif /* defined _DIRENT_HAVE_D_TYPE || defined HAVE_STRUCT_DIRENT_D_TYPE */
-
 /* Initializer for skip_entry.  POSIX does not require that the d_ino
    field be present, and some systems do not provide it. */
 #if defined _LIBC || defined D_INO_IN_DIRENT
 # define D_INO_TO_RESULT(source) (source)->d_ino == 0,
+  return d.skip_entry;
 #else
-# define D_INO_TO_RESULT(source) false,
+# define D_INO_TO_RESULT(source)
+  return false;
 #endif
+}
 
 /* Construct an initializer for a struct readdir_result object from a
    struct dirent *.  No copy of the name is made.  */
@@ -1545,19 +1543,24 @@ glob_in_dir (const char *pattern, const char *directory, int flags,
               }
               if (d.name == NULL)
                 break;
-              if (d.skip_entry)
+              if (readdir_result_skip_entry (d))
                 continue;
 
               /* If we shall match only directories use the information
                  provided by the dirent call if possible.  */
-              if ((flags & GLOB_ONLYDIR) && !readdir_result_might_be_dir (d))
-                continue;
+              if (flags & GLOB_ONLYDIR)
+                switch (readdir_result_type (d))
+                  {
+                  case DT_DIR: case DT_LNK: case DT_UNKNOWN: break;
+                  default: continue;
+                  }
 
               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.  */
-                  if (!readdir_result_might_be_symlink (d)
+                  dirent_type type = readdir_result_type (d);
+                  if (! (type == DT_LNK || type == DT_UNKNOWN)
                       || link_exists_p (dfd, directory, dirlen, d.name,
                                         pglob, flags))
                     {
-- 
2.13.5
From 972a46f6061a5147c0c8f5e24c48bb83dd9f8d1b Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>

Date: Thu, 31 Aug 2017 14:34:24 -0700
Subject: [PATCH 2/3] glob, backupfile: inode 0 is a valid inode number
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* doc/posix-functions/readdir.texi (readdir):
* doc/posix-headers/dirent.texi (dirent.h):
Document more readdir portability issues.
* lib/backupfile.c (REAL_DIR_ENTRY): Remove.
(numbered_backup): Don’t treat inode 0 any differently from
other inode values.
* lib/glob.c (struct readdir_result): Remove skip_entry member.
(readdir_result_skip_entry, D_INO_TO_RESULT): Remove.
All uses removed.
* modules/glob (Depends-on): Remove d-ino.
---
 ChangeLog                        | 12 ++++++++++++
 doc/posix-functions/readdir.texi |  8 ++++++++
 doc/posix-headers/dirent.texi    | 25 +++++++++++++++++++++++++
 lib/backupfile.c                 |  7 +------
 lib/glob.c                       | 21 ---------------------
 modules/glob                     |  1 -
 6 files changed, 46 insertions(+), 28 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index f372dbd38..53945d434 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2017-08-31  Paul Eggert  <eggert@cs.ucla.edu>
 
+	glob, backupfile: inode 0 is a valid inode number
+	* doc/posix-functions/readdir.texi (readdir):
+	* doc/posix-headers/dirent.texi (dirent.h):
+	Document more readdir portability issues.
+	* lib/backupfile.c (REAL_DIR_ENTRY): Remove.
+	(numbered_backup): Don’t treat inode 0 any differently from
+	other inode values.
+	* lib/glob.c (struct readdir_result): Remove skip_entry member.
+	(readdir_result_skip_entry, D_INO_TO_RESULT): Remove.
+	All uses removed.
+	* modules/glob (Depends-on): Remove d-ino.
+
 	glob: simplify symlink detection
 	* lib/glob.c (dirent_type): New type.  Use uint_fast8_t not
 	uint8_t, as C99 does not require uint8_t.
diff --git a/doc/posix-functions/readdir.texi b/doc/posix-functions/readdir.texi
index a9da77a14..701a10faf 100644
--- a/doc/posix-functions/readdir.texi
+++ b/doc/posix-functions/readdir.texi
@@ -20,4 +20,12 @@ incorrectly.  (Cf. @code{AC_SYS_LARGEFILE}.)
 
 Portability problems not fixed by Gnulib:
 @itemize
+@item
+Although POSIX places no restrictions on @code{d_ino} values, some
+older systems are rumored to return @code{d_ino} values equal to zero
+for directory entries that do not really exist.  Although Gnulib
+formerly attempted to cater to these older systems, this caused
+misbehavior on standard systems and so Gnulib does not attempt to
+cater to them any more.  If you know of any problems caused by this,
+please send a bug report.
 @end itemize
diff --git a/doc/posix-headers/dirent.texi b/doc/posix-headers/dirent.texi
index b61f82687..0a19fd3c2 100644
--- a/doc/posix-headers/dirent.texi
+++ b/doc/posix-headers/dirent.texi
@@ -17,4 +17,29 @@ Portability problems not fixed by Gnulib:
 @item
 This header file is missing on some platforms:
 MSVC 14.
+
+@item
+Although many systems define a @code{struct dirent} member named
+@code{d_type} and directory entry type macros like @code{DT_DIR} and
+@code{DT_LINK}, some do not:
+AIX 7.2, HP-UX 11, Solaris 11, probably others.
+
+@item
+On systems with @code{d_type}, not every filesystem supports
+@code{d_type}, and those lacking support will set it to @code{DT_UNKNOWN}.
+
+@item
+Some systems define a @code{struct dirent} member named @code{d_namlen}
+containing the string length of @code{d_name}, but others do not.
+
+@item
+Some systems define a @code{struct dirent} member named @code{d_off}
+containing a magic cookie suitable as an argument to @code{seekdir},
+but others do not.
+
+@item
+Some systems define a @code{struct dirent} member named
+@code{d_reclen} containing the number of bytes in the directory entry
+record, but others do not.  This member has limited utility, as it is
+an implementation detail.
 @end itemize
diff --git a/lib/backupfile.c b/lib/backupfile.c
index d7115bd61..ae171ecee 100644
--- a/lib/backupfile.c
+++ b/lib/backupfile.c
@@ -40,11 +40,6 @@
 #ifndef _D_EXACT_NAMLEN
 # define _D_EXACT_NAMLEN(dp) strlen ((dp)->d_name)
 #endif
-#if D_INO_IN_DIRENT
-# define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0)
-#else
-# define REAL_DIR_ENTRY(dp) 1
-#endif
 
 #if ! (HAVE_PATHCONF && defined _PC_NAME_MAX)
 # define pathconf(file, option) (errno = -1)
@@ -225,7 +220,7 @@ numbered_backup (char **buffer, size_t buffer_size, size_t filelen,
       bool all_9s;
       size_t versionlen;
 
-      if (! REAL_DIR_ENTRY (dp) || _D_EXACT_NAMLEN (dp) < baselen + 4)
+      if (_D_EXACT_NAMLEN (dp) < baselen + 4)
         continue;
 
       if (memcmp (buf + base_offset, dp->d_name, baselen + 2) != 0)
diff --git a/lib/glob.c b/lib/glob.c
index 6113af6fd..106f4cb26 100644
--- a/lib/glob.c
+++ b/lib/glob.c
@@ -113,9 +113,6 @@ struct readdir_result
 #if defined _DIRENT_HAVE_D_TYPE || defined HAVE_STRUCT_DIRENT_D_TYPE
   dirent_type type;
 #endif
-#if defined _LIBC || defined D_INO_IN_DIRENT
-  bool skip_entry;
-#endif
 };
 
 /* Initialize and return type member of struct readdir_result.  */
@@ -131,28 +128,12 @@ readdir_result_type (struct readdir_result d)
 #endif
 }
 
-/* Initialize and return skip_entry member of struct readdir_result.  */
-static bool
-readdir_result_skip_entry (struct readdir_result d)
-{
-/* Initializer for skip_entry.  POSIX does not require that the d_ino
-   field be present, and some systems do not provide it. */
-#if defined _LIBC || defined D_INO_IN_DIRENT
-# define D_INO_TO_RESULT(source) (source)->d_ino == 0,
-  return d.skip_entry;
-#else
-# define D_INO_TO_RESULT(source)
-  return false;
-#endif
-}
-
 /* Construct an initializer for a struct readdir_result object from a
    struct dirent *.  No copy of the name is made.  */
 #define READDIR_RESULT_INITIALIZER(source) \
   {                                        \
     source->d_name,                        \
     D_TYPE_TO_RESULT (source)              \
-    D_INO_TO_RESULT (source)               \
   }
 
 /* Call gl_readdir on STREAM.  This macro can be overridden to reduce
@@ -1543,8 +1524,6 @@ glob_in_dir (const char *pattern, const char *directory, int flags,
               }
               if (d.name == NULL)
                 break;
-              if (readdir_result_skip_entry (d))
-                continue;
 
               /* If we shall match only directories use the information
                  provided by the dirent call if possible.  */
diff --git a/modules/glob b/modules/glob
index 48cf387bd..0d6209de6 100644
--- a/modules/glob
+++ b/modules/glob
@@ -20,7 +20,6 @@ snippet/warn-on-use
 alloca          [test -n "$GLOB_H"]
 builtin-expect  [test -n "$GLOB_H"]
 closedir        [test -n "$GLOB_H"]
-d-ino           [test -n "$GLOB_H"]
 d-type          [test -n "$GLOB_H"]
 dirfd           [test -n "$GLOB_H"]
 flexmember      [test -n "$GLOB_H"]
-- 
2.13.5
From f43e4827d2ae03d01979d89c9bdf294bf10af7e1 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>

Date: Thu, 31 Aug 2017 14:34:25 -0700
Subject: [PATCH 3/3] 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.
---
 ChangeLog                     |  17 ++++
 doc/posix-functions/glob.texi |   2 +-
 lib/glob.c                    | 227 +++++++++++++++---------------------------
 lib/glob.in.h                 |   8 --
 m4/glob.m4                    |   4 +-
 modules/glob                  |   1 -
 modules/glob-tests            |   1 +
 tests/test-glob.c             |  20 ++++
 8 files changed, 121 insertions(+), 159 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 53945d434..960c56029 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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):
diff --git a/doc/posix-functions/glob.texi b/doc/posix-functions/glob.texi
index 382fe5234..5ea89a87f 100644
--- a/doc/posix-functions/glob.texi
+++ b/doc/posix-functions/glob.texi
@@ -14,7 +14,7 @@ IRIX 5.3, mingw, MSVC 14, BeOS.
 @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.
diff --git a/lib/glob.c b/lib/glob.c
index 106f4cb26..610d50a7f 100644
--- a/lib/glob.c
+++ b/lib/glob.c
@@ -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 prefix_array (const char *prefix, char **array, size_t n) __THROWNL;
 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 @@ glob (const char *pattern, int flags, int (*errfunc) (const char *, int),
      can give the answer now.  */
   if (filename == NULL)
     {
-      struct stat st;
-      struct_stat64 st64;
-
-      /* 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)))))
+      size_t newcount = pglob->gl_pathc + pglob->gl_offs;
+      char **new_gl_pathv;
+
+      if (newcount > SIZE_MAX / sizeof (char *) - 2)
         {
-          size_t newcount = pglob->gl_pathc + pglob->gl_offs;
-          char **new_gl_pathv;
+        nospace:
+          free (pglob->gl_pathv);
+          pglob->gl_pathv = NULL;
+          pglob->gl_pathc = 0;
+          retval = GLOB_NOSPACE;
+          goto out;
+        }
 
-          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;
-            }
+      new_gl_pathv = realloc (pglob->gl_pathv,
+                              (newcount + 2) * sizeof (char *));
+      if (new_gl_pathv == NULL)
+        goto nospace;
+      pglob->gl_pathv = new_gl_pathv;
 
-          new_gl_pathv = realloc (pglob->gl_pathv,
-                                  (newcount + 2) * sizeof (char *));
-          if (new_gl_pathv == NULL)
+      if (flags & GLOB_MARK && is_dir (dirname, flags, pglob))
+        {
+          char *p;
+          pglob->gl_pathv[newcount] = malloc (dirlen + 2);
+          if (pglob->gl_pathv[newcount] == NULL)
             goto nospace;
-          pglob->gl_pathv = new_gl_pathv;
-
-          if (flags & GLOB_MARK)
+          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
             {
-              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;
-
-          return 0;
         }
+      pglob->gl_pathv[++newcount] = NULL;
+      ++pglob->gl_pathc;
+      pglob->gl_flags = flags;
 
-      /* Not found.  */
-      retval = GLOB_NOMATCH;
-      goto out;
+      return 0;
     }
 
   meta = __glob_pattern_type (dirname, !(flags & GLOB_NOESCAPE));
@@ -1245,15 +1246,9 @@ glob (const char *pattern, int flags, int (*errfunc) (const char *, int),
     {
       /* 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 @@ prefix_array (const char *dirname, char **array, size_t n)
   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 @@ glob_in_dir (const char *pattern, const char *directory, int flags,
     }
   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 @@ glob_in_dir (const char *pattern, const char *directory, int flags,
                         "/", 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 @@ glob_in_dir (const char *pattern, const char *directory, int flags,
         }
       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 @@ glob_in_dir (const char *pattern, const char *directory, int flags,
 
               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;
                 }
             }
         }
diff --git a/lib/glob.in.h b/lib/glob.in.h
index cfb7e4996..b0d27cff6 100644
--- a/lib/glob.in.h
+++ b/lib/glob.in.h
@@ -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
diff --git a/m4/glob.m4 b/m4/glob.m4
index 23fc80219..189cd3a18 100644
--- a/m4/glob.m4
+++ b/m4/glob.m4
@@ -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 @@ AC_DEFUN([gl_PREREQ_GLOB],
     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
 ])
diff --git a/modules/glob b/modules/glob
index 0d6209de6..98d49e34f 100644
--- a/modules/glob
+++ b/modules/glob
@@ -21,7 +21,6 @@ alloca          [test -n "$GLOB_H"]
 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"]
diff --git a/modules/glob-tests b/modules/glob-tests
index 151bc06b3..abc3666ed 100644
--- a/modules/glob-tests
+++ b/modules/glob-tests
@@ -5,6 +5,7 @@ tests/macros.h
 
 Depends-on:
 glob-c++-tests
+symlink
 
 configure.ac:
 
diff --git a/tests/test-glob.c b/tests/test-glob.c
index 7e755d9c4..eb3472c6e 100644
--- a/tests/test-glob.c
+++ b/tests/test-glob.c
@@ -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 @@ SIGNATURE_CHECK (globfree, void, (glob_t *));
 
 #include "macros.h"
 
+#define BASE "test-glob.t"
 #define GL_NO_SUCH_FILE "/gnulib-magic-does-not-exist"
 
 int
@@ -73,5 +77,21 @@ main ()
   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;
 }
-- 
2.13.5

Patch

diff --git a/posix/Makefile b/posix/Makefile
index 8340549..91f78c9 100644
--- a/posix/Makefile
+++ b/posix/Makefile
@@ -69,7 +69,7 @@  tests		:= test-errno tstgetopt testfnm runtests runptests \
 		   tst-mmap tst-mmap-offset tst-getaddrinfo tst-truncate \
 		   tst-truncate64 tst-fork tst-fnmatch tst-regexloc tst-dir \
 		   tst-chmod bug-regex1 bug-regex2 bug-regex3 bug-regex4 \
-		   tst-gnuglob tst-regex bug-regex6 bug-regex7 \
+		   tst-gnuglob tst-glob_symlinks tst-regex bug-regex6 bug-regex7 \
 		   bug-regex8 bug-regex9 bug-regex10 bug-regex11 bug-regex12 \
 		   bug-regex13 bug-regex14 bug-regex15 bug-regex16 \
 		   bug-regex17 bug-regex18 bug-regex19 \
@@ -250,7 +250,6 @@  tst-rxspencer-ARGS = --utf8 rxspencer/tests
 tst-rxspencer-no-utf8-ARGS = rxspencer/tests
 tst-pcre-ARGS = PCRE.tests
 tst-boost-ARGS = BOOST.tests
-bug-glob1-ARGS = "$(objpfx)"
 tst-execvp3-ARGS = --test-dir=$(objpfx)
 
 testcases.h: TESTS TESTS2C.sed
diff --git a/posix/bug-glob1.c b/posix/bug-glob1.c
deleted file mode 100644
index 05c2da7..0000000
--- a/posix/bug-glob1.c
+++ /dev/null
@@ -1,88 +0,0 @@ 
-/* Test case for globbing dangling symlink.  By Ulrich Drepper.  */
-#include <errno.h>
-#include <error.h>
-#include <glob.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-
-static void prepare (int argc, char *argv[]);
-#define PREPARE prepare
-static int do_test (void);
-#define TEST_FUNCTION do_test ()
-
-#include "../test-skeleton.c"
-
-
-static char *fname;
-
-static void
-prepare (int argc, char *argv[])
-{
-  if (argc < 2)
-    error (EXIT_FAILURE, 0, "missing argument");
-
-  size_t len = strlen (argv[1]);
-  static const char ext[] = "globXXXXXX";
-  fname = malloc (len + sizeof (ext));
-  if (fname == NULL)
-    error (EXIT_FAILURE, errno, "cannot create temp file");
- again:
-  strcpy (stpcpy (fname, argv[1]), ext);
-  fname = mktemp (fname);
-  if (fname == NULL || *fname == '\0')
-    error (EXIT_FAILURE, errno, "cannot create temp file name");
-  if (symlink ("bug-glob1-does-not-exist", fname) != 0)
-    {
-      if (errno == EEXIST)
-	goto again;
-
-      error (EXIT_FAILURE, errno, "cannot create symlink");
-    }
-  add_temp_file (fname);
-}
-
-
-static int
-do_test (void)
-{
-  glob_t gl;
-  int retval = 0;
-  int e;
-
-  e = glob (fname, 0, NULL, &gl);
-  if (e == 0)
-    {
-      printf ("glob(\"%s\") succeeded\n", fname);
-      retval = 1;
-    }
-  globfree (&gl);
-
-  size_t fnamelen = strlen (fname);
-  char buf[fnamelen + 2];
-
-  strcpy (buf, fname);
-  buf[fnamelen - 1] = '?';
-  e = glob (buf, 0, NULL, &gl);
-  if (e == 0)
-    {
-      printf ("glob(\"%s\") succeeded\n", buf);
-      retval = 1;
-    }
-  globfree (&gl);
-
-  strcpy (buf, fname);
-  buf[fnamelen] = '*';
-  buf[fnamelen + 1] = '\0';
-  e = glob (buf, 0, NULL, &gl);
-  if (e == 0)
-    {
-      printf ("glob(\"%s\") succeeded\n", buf);
-      retval = 1;
-    }
-  globfree (&gl);
-
-  return retval;
-}
diff --git a/posix/glob.c b/posix/glob.c
index dcfbc78..7b6b426 100644
--- a/posix/glob.c
+++ b/posix/glob.c
@@ -69,8 +69,8 @@ 
 # define readdir(str) __readdir64 (str)
 # define getpwnam_r(name, bufp, buf, len, res) \
    __getpwnam_r (name, bufp, buf, len, res)
-# ifndef __stat64
-#  define __stat64(fname, buf) __xstat64 (_STAT_VER, fname, buf)
+# ifndef __lstat64
+#  define __lstat64(fname, buf) __lxstat64 (_STAT_VER, fname, buf)
 # endif
 # define struct_stat64		struct stat64
 #else /* !_LIBC */
@@ -1046,9 +1046,9 @@  glob (const char *pattern, int flags, int (*errfunc) (const char *, int),
       /* 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
+	       ? ((*pglob->gl_lstat) (dirname, &st) == 0
 		  && S_ISDIR (st.st_mode))
-	       : (__stat64 (dirname, &st64) == 0 && S_ISDIR (st64.st_mode)))))
+	       : (__lstat64 (dirname, &st64) == 0 && S_ISDIR (st64.st_mode)))))
 	{
 	  size_t newcount = pglob->gl_pathc + pglob->gl_offs;
 	  char **new_gl_pathv;
@@ -1328,10 +1328,10 @@  glob (const char *pattern, int flags, int (*errfunc) (const char *, int),
 
       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))))
+	     ? ((*pglob->gl_lstat) (pglob->gl_pathv[i], &st) == 0
+		&& (S_ISDIR (st.st_mode) || S_ISLNK (st.st_mode)))
+	     : (__lstat64 (pglob->gl_pathv[i], &st64) == 0
+		&& (S_ISDIR (st64.st_mode) || S_ISLNK (st64.st_mode)))))
 	  {
 	    size_t len = strlen (pglob->gl_pathv[i]) + 2;
 	    char *new = realloc (pglob->gl_pathv[i], len);
@@ -1441,58 +1441,6 @@  prefix_array (const char *dirname, char **array, size_t n)
   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
-# ifndef _LIBC
-		, int flags
-# endif
-		)
-{
-  size_t fnamelen = strlen (fname);
-  char *fullname = (char *) __alloca (dirlen + 1 + fnamelen + 1);
-  struct stat st;
-# ifndef _LIBC
-  struct_stat64 st64;
-# endif
-
-  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.
@@ -1563,7 +1511,7 @@  glob_in_dir (const char *pattern, const char *directory, int flags,
 	       pattern, patlen + 1);
       if (((__builtin_expect (flags & GLOB_ALTDIRFUNC, 0)
 	   ? (*pglob->gl_stat) (fullname, &ust.st)
-	   : __stat64 (fullname, &ust.st64))
+	   : __lstat64 (fullname, &ust.st64))
 	  == 0)
 	  || errno == EOVERFLOW)
 	/* We found this file to be existing.  Now tell the rest
@@ -1587,8 +1535,6 @@  glob_in_dir (const char *pattern, const char *directory, int flags,
 	}
       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)
 #if defined _AMIGA || defined VMS
@@ -1624,38 +1570,30 @@  glob_in_dir (const char *pattern, const char *directory, int flags,
 
 	      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.  */
-		  if (!readdir_result_might_be_symlink (d)
-		      || 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 size = (sizeof (struct globnames)
-					 + ((count - INITIAL_COUNT)
-					    * sizeof (char *)));
-			  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)
+		      struct globnames *newnames;
+		      size_t count = names->count * 2;
+		      size_t size = (sizeof (struct globnames)
+				     + ((count - INITIAL_COUNT)
+				     * sizeof (char *)));
+		      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;
 		}
 	    }
 	}
diff --git a/posix/tst-glob_symlinks.c b/posix/tst-glob_symlinks.c
new file mode 100644
index 0000000..4af8287
--- /dev/null
+++ b/posix/tst-glob_symlinks.c
@@ -0,0 +1,133 @@ 
+/* Test glob danglin symlink match (BZ #866).
+   for the filesystem access functions.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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.
+
+   The GNU C Library 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.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stddef.h>
+#include <glob.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+
+static void do_prepare (int argc, char *argv[]);
+#define PREPARE do_prepare
+static int do_test (void);
+#include <support/test-driver.c>
+
+static void
+create_link (const char *base, const char *fname, char *linkname,
+	     size_t linknamesize)
+{
+  int ntries = 0;
+  while (1)
+    {
+      snprintf (linkname, linknamesize, "%s/%s%02d", test_dir, base,
+		ntries);
+      if (symlink (fname, linkname) == 0)
+	break;
+      if (errno != EEXIST)
+	FAIL_EXIT1 ("symlink failed: %m");
+      if (ntries++ == 10)
+	FAIL_EXIT1 ("symlink failed with EEXIST too many times");
+    }
+  add_temp_file (linkname);
+}
+
+static char valid_link[PATH_MAX];
+static char dangling_link[PATH_MAX];
+static char dangling_dir[PATH_MAX];
+
+static void
+do_prepare (int argc, char *argv[])
+{
+  char *fname;
+
+  create_temp_file ("tst-glob_symlinks.", &fname);
+
+  /* Create a existing symlink.  */
+  create_link ("valid-symlink-tst-glob_symlinks", fname, valid_link,
+	       sizeof valid_link);
+
+  /* Create a dangling symlink to a file.  */
+  int fd = create_temp_file ("dangling-tst-glob_file", &fname);
+  TEST_VERIFY_EXIT (close (fd) == 0);
+  /* It throws an warning at process end due 'add_temp_file' trying to
+     unlink it again.  */
+  TEST_VERIFY_EXIT (unlink (fname) == 0);
+  create_link ("dangling-symlink-file-tst-glob", fname, dangling_link,
+	       sizeof dangling_link);
+
+  /* Create a dangling symlink to a directory.  */
+  char tmpdir[PATH_MAX];
+  snprintf (tmpdir, sizeof tmpdir, "%s/dangling-tst-glob_folder.XXXXXX",
+	    test_dir);
+  TEST_VERIFY_EXIT (mkdtemp (tmpdir) != NULL);
+  create_link ("dangling-symlink-dir-tst-glob", tmpdir, dangling_dir,
+	       sizeof dangling_dir);
+  TEST_VERIFY_EXIT (rmdir (tmpdir) == 0);
+}
+
+static int
+do_test (void)
+{
+  char buf[PATH_MAX];
+  glob_t gl;
+
+  TEST_VERIFY_EXIT (glob (valid_link, 0, NULL, &gl) == 0);
+  TEST_VERIFY_EXIT (gl.gl_pathc == 1);
+  TEST_VERIFY_EXIT (strcmp (gl.gl_pathv[0], valid_link) == 0);
+  globfree (&gl);
+
+  TEST_VERIFY_EXIT (glob (dangling_link, 0, NULL, &gl) == 0);
+  TEST_VERIFY_EXIT (gl.gl_pathc == 1);
+  TEST_VERIFY_EXIT (strcmp (gl.gl_pathv[0], dangling_link) == 0);
+  globfree (&gl);
+
+  TEST_VERIFY_EXIT (glob (dangling_dir, 0, NULL, &gl) == 0);
+  TEST_VERIFY_EXIT (gl.gl_pathc == 1);
+  TEST_VERIFY_EXIT (strcmp (gl.gl_pathv[0], dangling_dir) == 0);
+  globfree (&gl);
+
+  snprintf (buf, sizeof buf, "%s", dangling_link);
+  buf[strlen(buf) - 1] = '?';
+  TEST_VERIFY_EXIT (glob (buf, 0, NULL, &gl) == 0);
+  TEST_VERIFY_EXIT (gl.gl_pathc == 1);
+  TEST_VERIFY_EXIT (strcmp (gl.gl_pathv[0], dangling_link) == 0);
+  globfree (&gl);
+
+  /* glob should handle dangling symbol as normal file, so <file>? should
+     return an empty string.  */
+  snprintf (buf, sizeof buf, "%s?", dangling_link);
+  TEST_VERIFY_EXIT (glob (buf, 0, NULL, &gl) != 0);
+  globfree (&gl);
+
+  snprintf (buf, sizeof buf, "%s*", dangling_link);
+  TEST_VERIFY_EXIT (glob (buf, 0, NULL, &gl) == 0);
+  TEST_VERIFY_EXIT (gl.gl_pathc == 1);
+  TEST_VERIFY_EXIT (strcmp (gl.gl_pathv[0], dangling_link) == 0);
+  globfree (&gl);
+
+  return 0;
+}