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. Patch (apply with `patch -p0'): *** a/lib/glob/glob_loop.c 2019-01-16 16:13:21.000000000 -0500 --- b/lib/glob/glob_loop.c 2019-02-01 09:45:11.000000000 -0500 *************** *** 27,34 **** register const GCHAR *p; register GCHAR c; ! int bopen; p = pattern; ! bopen = 0; while ((c = *p++) != L('\0')) --- 27,34 ---- register const GCHAR *p; register GCHAR c; ! int bopen, bsquote; p = pattern; ! bopen = bsquote = 0; while ((c = *p++) != L('\0')) *************** *** 56,66 **** 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')); } ! return 0; } --- 56,75 ---- 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 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 bsquote ? 2 : 0; } *** a/lib/glob/glob.h 2013-10-28 14:46:12.000000000 -0400 --- b/lib/glob/glob.h 2019-03-07 11:06:47.000000000 -0500 *************** *** 31,34 **** --- 31,35 ---- #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 *)); *** a/lib/glob/glob.c 2018-09-20 10:53:23.000000000 -0400 --- b/lib/glob/glob.c 2019-03-07 14:23:43.000000000 -0500 *************** *** 1062,1066 **** unsigned int directory_len; int free_dirname; /* flag */ ! int dflags; result = (char **) malloc (sizeof (char *)); --- 1078,1082 ---- unsigned int directory_len; int free_dirname; /* flag */ ! int dflags, hasglob; result = (char **) malloc (sizeof (char *)); *************** *** 1111,1117 **** } /* 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)) { char **directories, *d, *p; --- 1127,1136 ---- } + hasglob = 0; /* If directory_name contains globbing characters, then we ! 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; *************** *** 1176,1180 **** d[directory_len - 1] = '\0'; ! directories = glob_filename (d, dflags); if (free_dirname) --- 1195,1199 ---- d[directory_len - 1] = '\0'; ! directories = glob_filename (d, dflags|GX_RECURSE); if (free_dirname) *************** *** 1333,1336 **** --- 1352,1369 ---- 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); *** a/pathexp.c 2018-04-29 17:44:48.000000000 -0400 --- b/pathexp.c 2019-01-31 20:19:41.000000000 -0500 *************** *** 66,74 **** register int c; char *send; ! int open; DECLARE_MBSTATE; ! open = 0; send = string + strlen (string); --- 66,74 ---- register int c; char *send; ! int open, bsquote; DECLARE_MBSTATE; ! open = bsquote = 0; send = string + strlen (string); *************** *** 101,105 **** globbing. */ case '\\': ! return (*string != 0); case CTLESC: --- 101,112 ---- globbing. */ case '\\': ! if (*string != '\0' && *string != '/') ! { ! bsquote = 1; ! string++; ! continue; ! } ! else if (*string == 0) ! return (0); case CTLESC: *************** *** 118,122 **** #endif } ! return (0); } --- 125,130 ---- #endif } ! ! return (bsquote ? 2 : 0); } *** a/bashline.c 2019-01-16 16:13:21.000000000 -0500 --- b/bashline.c 2019-02-22 09:29:08.000000000 -0500 *************** *** 3753,3757 **** case '\\': ! if (*string == 0) return (0); } --- 3766,3770 ---- case '\\': ! if (*string++ == 0) return (0); } *** a/patchlevel.h 2016-06-22 14:51:03.000000000 -0400 --- b/patchlevel.h 2016-10-01 11:01:28.000000000 -0400 *************** *** 26,30 **** looks for to find the patch level (for the sccs version string). */ ! #define PATCHLEVEL 2 #endif /* _PATCHLEVEL_H_ */ --- 26,30 ---- looks for to find the patch level (for the sccs version string). */ ! #define PATCHLEVEL 3 #endif /* _PATCHLEVEL_H_ */