BASH PATCH REPORT ================= Bash-Release: 5.0 Patch-ID: bash50-003 Bug-Reported-by: Andrew Church Bug-Reference-ID: <5c534aa2.04371@msgid.achurch.org> Bug-Reference-URL: http://lists.gnu.org/archive/html/bug-bash/2019-01/msg00276.html Bug-Description: There are several incompatibilities in how bash-5.0 processes pathname expansion (globbing) of filename arguments that have backslashes in the directory portion. --- a/bashline.c +++ b/bashline.c @@ -3752,7 +3752,7 @@ completion_glob_pattern (string) continue; case '\\': - if (*string == 0) + if (*string++ == 0) return (0); } --- a/lib/glob/glob.c +++ b/lib/glob/glob.c @@ -1061,7 +1061,7 @@ glob_filename (pathname, flags) char *directory_name, *filename, *dname, *fn; unsigned int directory_len; int free_dirname; /* flag */ - int dflags; + int dflags, hasglob; result = (char **) malloc (sizeof (char *)); result_size = 1; @@ -1110,9 +1110,12 @@ glob_filename (pathname, flags) free_dirname = 1; } + hasglob = 0; /* If directory_name contains globbing characters, then we - have to expand the previous levels. Just recurse. */ - if (directory_len > 0 && glob_pattern_p (directory_name)) + have to expand the previous levels. Just recurse. + If glob_pattern_p returns != [0,1] we have a pattern that has backslash + quotes but no unquoted glob pattern characters. We dequote it below. */ + if (directory_len > 0 && (hasglob = glob_pattern_p (directory_name)) == 1) { char **directories, *d, *p; register unsigned int i; @@ -1175,7 +1178,7 @@ glob_filename (pathname, flags) if (d[directory_len - 1] == '/') d[directory_len - 1] = '\0'; - directories = glob_filename (d, dflags); + directories = glob_filename (d, dflags|GX_RECURSE); if (free_dirname) { @@ -1332,6 +1335,20 @@ only_filename: free (directory_name); return (NULL); } + /* If we have a directory name with quoted characters, and we are + being called recursively to glob the directory portion of a pathname, + we need to dequote the directory name before returning it so the + caller can read the directory */ + if (directory_len > 0 && hasglob == 2 && (flags & GX_RECURSE) != 0) + { + dequote_pathname (directory_name); + directory_len = strlen (directory_name); + } + + /* We could check whether or not the dequoted directory_name is a + directory and return it here, returning the original directory_name + if not, but we don't do that yet. I'm not sure it matters. */ + /* Handle GX_MARKDIRS here. */ result[0] = (char *) malloc (directory_len + 1); if (result[0] == NULL) --- a/lib/glob/glob.h +++ b/lib/glob/glob.h @@ -30,6 +30,7 @@ #define GX_NULLDIR 0x100 /* internal -- no directory preceding pattern */ #define GX_ADDCURDIR 0x200 /* internal -- add passed directory name */ #define GX_GLOBSTAR 0x400 /* turn on special handling of ** */ +#define GX_RECURSE 0x800 /* internal -- glob_filename called recursively */ extern int glob_pattern_p __P((const char *)); extern char **glob_vector __P((char *, char *, int)); --- a/lib/glob/glob_loop.c +++ b/lib/glob/glob_loop.c @@ -26,10 +26,10 @@ INTERNAL_GLOB_PATTERN_P (pattern) { register const GCHAR *p; register GCHAR c; - int bopen; + int bopen, bsquote; p = pattern; - bopen = 0; + bopen = bsquote = 0; while ((c = *p++) != L('\0')) switch (c) @@ -55,13 +55,22 @@ INTERNAL_GLOB_PATTERN_P (pattern) case L('\\'): /* Don't let the pattern end in a backslash (GMATCH returns no match - if the pattern ends in a backslash anyway), but otherwise return 1, - since the matching engine uses backslash as an escape character - and it can be removed. */ - return (*p != L('\0')); + if the pattern ends in a backslash anyway), but otherwise note that + we have seen this, since the matching engine uses backslash as an + escape character and it can be removed. We return 2 later if we + have seen only backslash-escaped characters, so interested callers + know they can shortcut and just dequote the pathname. */ + if (*p != L('\0')) + { + p++; + bsquote = 1; + continue; + } + else /* (*p == L('\0')) */ + return 0; } - return 0; + return bsquote ? 2 : 0; } #undef INTERNAL_GLOB_PATTERN_P --- a/patchlevel.h +++ b/patchlevel.h @@ -25,6 +25,6 @@ regexp `^#define[ ]*PATCHLEVEL', since that's what support/mkversion.sh looks for to find the patch level (for the sccs version string). */ -#define PATCHLEVEL 2 +#define PATCHLEVEL 3 #endif /* _PATCHLEVEL_H_ */ --- a/pathexp.c +++ b/pathexp.c @@ -65,11 +65,11 @@ unquoted_glob_pattern_p (string) { register int c; char *send; - int open; + int open, bsquote; DECLARE_MBSTATE; - open = 0; + open = bsquote = 0; send = string + strlen (string); while (c = *string++) @@ -100,7 +100,14 @@ unquoted_glob_pattern_p (string) can be removed by the matching engine, so we have to run it through globbing. */ case '\\': - return (*string != 0); + if (*string != '\0' && *string != '/') + { + bsquote = 1; + string++; + continue; + } + else if (*string == 0) + return (0); case CTLESC: if (*string++ == '\0') @@ -117,7 +124,8 @@ unquoted_glob_pattern_p (string) ADVANCE_CHAR_P (string, send - string); #endif } - return (0); + + return (bsquote ? 2 : 0); } /* Return 1 if C is a character that is `special' in a POSIX ERE and needs to