commit e6206e337ac4577e4b83ceaac44241f48a2aec50 Author: Gregory M. Turner Date: Sat Sep 8 22:05:06 2012 -0700 lib/glob: cygwin: match executables without ".exe" suffix On cygwin we have the ".exe-hack" which is a feature that attempts to make the Windowsy ".exe" suffix on executables optional by acting (almost) as though "foo" and "foo.exe" were hard-links to the same file. The "almost" part is that when enumarating a directory's contents, only the ".exe"-suffixed version is returned. As a result, bash has historically failed to match globs which don't match the ".exe" suffix, even though, for all intents and purposes, the file is there. For example, echo /bin/ba?h would not find the /bin/bash executable. This patch fixes this by keeping an eye out for files that fail to match the glob but do look like they'd match "*?.exe". When such a file is encountered, some sanity checks are performed, and if the ".exe-hack" seems applicable, the ".exe" suffix is removed and the glob is checked against the suffix-free version of the filename, before rejection. Such matches are returned without the .exe suffix (as, it would be quite unexpected to get a result back from globbing that doesn't match the glob!). Signed-off-by: Gregory M. Turner diff --git a/lib/glob/glob.c b/lib/glob/glob.c index ad9b9d9..fd90e7d 100644 --- a/lib/glob/glob.c +++ b/lib/glob/glob.c @@ -675,6 +675,109 @@ glob_vector (pat, dir, flags) bcopy (dp->d_name, nextname, D_NAMLEN (dp) + 1); ++count; } +#if __CYGWIN__ + /* The master plan here is to check whether "foo" matches the glob when the + above has just ruled out a match for the executable or symlink-to-executable + "foo.exe". If it does match, we add "foo" as though we got it from readdir. + + Before we leap, though, we should look a bit. Incorrectly guessing whether + the .exe hack applies would result in missed globs, or (if we inject "mccoy" + but then encounter the real "mccoy") duplicate matches; hopefully this is + never wrong in practice -- if it becomes an issue, we should adjust our + sanity checking to better capture the conditions actually used by cygwin. */ + + /* We aren't interested in anything less than five characters long (what are + we going to match for ".exe," an empty string?) */ + else if (convfn[0] != '\0' && convfn[1] != '\0' && convfn[2] != '\0' && + convfn[3] != '\0' && convfn[4] != '\0') + { + register char *x = &convfn[0]; + struct stat finfo; + while (*++x != '\0') ; + if (*--x == 'e' && *--x == 'x' && *--x == 'e' && *--x == '.') + { + /* abuse the available register char* 'subdir' to build the path */ + subdir = sh_makepath (dir, dp->d_name, pflags); + if (!subdir) + { + lose = 1; + break; + } + /* My tests indicate that we can stat() the executable bit of a valid + symlink in cygwin, expecting to get the same result we would get + stat()ing the link-target. The .exe hack does apply to such links! */ + if (stat (subdir, &finfo) == 0 && + ((S_ISREG (finfo.st_mode) || S_ISLNK (finfo.st_mode)) && + (finfo.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) + { + /* So, it's an executable or symlink thereto and ends in .exe -- does + it match the pattern if we strip the ".exe" suffix? */ + size_t dnamlen = x - convfn; /* x points to the last char before '.exe' */ + nextname = (char *) malloc (dnamlen + 1); + if (!nextname) + { + lose = 1; + break; + } + bcopy (dp->d_name, nextname, dnamlen); /* always a noop on cygwin */ + *(nextname + dnamlen) = '\0'; + convfn = fnx_fromfs (nextname, dnamlen); + if (strmatch (pat, convfn, mflags) != FNM_NOMATCH) + { + /* Great, it matches, but does it actually exist, and is it executable? + Probably, yes. This protects against some future cygwin that made + the .exe hack optional or removed it, and against the possibility that + I've failed to capture the conditions that trigger the .exe hack in + general. A better test might be to check if they have the same inode, + but this might require some nuance due to inode-reliability quirks */ + free(subdir); + subdir = sh_makepath (dir, convfn, pflags); + if (!subdir) + { + lose = 1; + break; + } + if (stat (subdir, &finfo) == 0 && + ((S_ISREG (finfo.st_mode) || S_ISLNK (finfo.st_mode)) && + (finfo.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) + { + /* All signs point to cygwin .exe hack. Treat it as a match */ + if (nalloca < ALLOCA_MAX) + { + nextlink = (struct globval *) alloca (sizeof (struct globval)); + nalloca += sizeof (struct globval); + } + else + { + nextlink = (struct globval *) malloc (sizeof (struct globval)); + if (firstmalloc == 0) + firstmalloc = nextlink; + } + + if (!nextlink) + { + lose = 1; + break; + } + nextlink->next = lastlink; + lastlink = nextlink; + nextlink->name = nextname; + ++count; + } + else + { + free(nextname); + } + } + else + { + free (nextname); + } + } + free(subdir); + } + } +#endif } (void) closedir (d);