about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2019-07-11 16:55:17 -0400
committerRich Felker <dalias@aerifal.cx>2019-08-07 02:38:45 -0400
commite408deefeb1a60b6e9e1bb63393590926f96ee64 (patch)
tree96116a6faec49580bc3cd200ed08e15be7f66b65
parentd493206de7df4db07ad34f24701539ba0a6ed38c (diff)
downloadmusl-e408deefeb1a60b6e9e1bb63393590926f96ee64.tar.gz
musl-e408deefeb1a60b6e9e1bb63393590926f96ee64.tar.xz
musl-e408deefeb1a60b6e9e1bb63393590926f96ee64.zip
fix failure of glob to match broken symlinks under some conditions
when the pattern ended with one or more literal path components, or
when the GLOB_MARK flag was passed to request that glob flag directory
results and the type obtained by readdir was unknown or inconclusive
(symlink), the stat function was called to evaluate existence and/or
determine type. however, stat fails with ENOENT for broken symlinks,
and this caused the match to be omitted from the results.

instead, use stat only for the unknown/inconclusive cases with
GLOB_MARK, and otherwise, or if stat fails, use lstat existence still
needs to be determined. this minimizes the number of costly syscalls,
performing both only in the case where GLOB_MARK is in use and there
is a final literal path component which is a broken symlink.

based on/simplified from patch by James Y Knight.
-rw-r--r--src/regex/glob.c17
1 files changed, 12 insertions, 5 deletions
diff --git a/src/regex/glob.c b/src/regex/glob.c
index 58248675..9de080ed 100644
--- a/src/regex/glob.c
+++ b/src/regex/glob.c
@@ -92,16 +92,23 @@ static int do_glob(char *buf, size_t pos, int type, char *pat, int flags, int (*
 	if (!*pat) {
 		/* If we consumed any components above, or if GLOB_MARK is
 		 * requested and we don't yet know if the match is a dir,
-		 * we must call stat to confirm the file exists and/or
-		 * determine its type. */
+		 * we must confirm the file exists and/or determine its type.
+		 *
+		 * If marking dirs, symlink type is inconclusive; we need the
+		 * type for the symlink target, and therefore must try stat
+		 * first unless type is known not to be a symlink. Otherwise,
+		 * or if that fails, use lstat for determining existence to
+		 * avoid false negatives in the case of broken symlinks. */
 		struct stat st;
-		if ((flags & GLOB_MARK) && type==DT_LNK) type = 0;
-		if (!type && stat(buf, &st)) {
+		if ((flags & GLOB_MARK) && (!type||type==DT_LNK) && !stat(buf, &st)) {
+			if (S_ISDIR(st.st_mode)) type = DT_DIR;
+			else type = DT_REG;
+		}
+		if (!type && lstat(buf, &st)) {
 			if (errno!=ENOENT && (errfunc(buf, errno) || (flags & GLOB_ERR)))
 				return GLOB_ABORTED;
 			return 0;
 		}
-		if (!type && S_ISDIR(st.st_mode)) type = DT_DIR;
 		if (append(tail, buf, pos, (flags & GLOB_MARK) && type==DT_DIR))
 			return GLOB_NOSPACE;
 		return 0;