The enclosed patch fixes (or tries to fix) the inability to glob for
executable files on cygwin without the ".exe" suffix. This is required
because cygwin has a hack to behave as though foo and foo.exe both exist
as hardlinks to the same file, but unlike a real hard-link, readdir()
only provides the ".exe"-suffixed filename.
I posted this to the cygwin mailing list some days ago and nobody's
ridiculed or flamed me yet, which comes about as close to a ringing
endorsement as I would dare to hope for :)
In all seriousness, I doubt it's 100% correct. There's a lot of goofy
corner cases I can imagine, i.e.: what happens if foo.exe is renamed to
foo outside of cygwin? So far, I haven't gone out of my way to try and
test all of these possibilities -- the patch only deals with the typical
case where readdir() returns foo.exe and we are globbing for foo.
Assuming there's no memory allocation bugs or similar lurking in the
patch, at worst, it makes bash globbing less broken on cygwin, even if
it leaves more work to be done to make it fully correct.
I'd be happy to iterate if folks have some good ideas as to how to
improve the implementation.
-gmt
commit e6206e337ac4577e4b83ceaac44241f48a2aec50
Author: Gregory M. Turner <gmturner...@ameritech.net>
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 <gmturner...@ameritech.net>
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);