In cygwin, we have "the .exe hack" where both foo and foo.exe are treated as hardlinks to the same thing if foo is an executable or a symlink to an executable. This doesn't present a problem when globbing for something that matches "foo.exe" since readdir() always seems to tack the .exe on to the end. However, when globbing for "foo", we completely fail to find the file, for the same reason. The only thing for it is to explicitly check for this circumstance and inject a match into our results iff the glob matches "foo" but not "foo.exe". -gmt diff -urN bash-4.2.orig/lib/glob/glob.c bash-4.2/lib/glob/glob.c --- bash-4.2.orig/lib/glob/glob.c 2012-08-29 14:04:40.707465500 -0700 +++ bash-4.2/lib/glob/glob.c 2012-08-29 14:51:45.275465500 -0700 @@ -675,6 +675,102 @@ bcopy (dp->d_name, nextname, D_NAMLEN (dp) + 1); ++count; } +#if __CYGWIN__ + /* if file i.e. foo.exe is executable, see if "foo" matches the glob */ + else if (strmatch ("*.exe", convfn, mflags) != FNM_NOMATCH) + { + /* if sanity checks pass, assume this is the cygwin .exe hack at work. */ + struct stat finfo; + + /* ".exe" matches *.exe so lets rule that out rather than match '\0' */ + if (!(convfn[0] == '.' && convfn[1] == 'e' && convfn[2] == 'x' && + convfn[3] == 'e' && convfn[4] == '\0')) + { + subdir = sh_makepath (dir, dp->d_name, 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)))) + { + size_t dnamlen = D_NAMLEN (dp) - 4; /* length of ".exe" */ + /* We can kind-of abuse nextname here -- that's where it's + going, anyhow, if everything checks out OK */ + nextname = (char *) malloc (dnamlen + 1); + if (!nextname) + { + lose = 1; + break; + } + bcopy (dp->d_name, nextname, dnamlen); + *(nextname + dnamlen) = '\0'; + convfn = fnx_fromfs (nextname, dnamlen); + /* So, it's executable and ends in .exe -- does it match the + pattern, now that we have stripped the ".exe" off the end? */ + 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 */ + free(subdir); + subdir = sh_makepath (dir, convfn, 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 as we would get + stat()ing the link-target. The .exe hack does apply to such links! + I haven't tested various wierd corner cases. Guessing incorrectly + whether the .exe hack is operating will result in missed globs, or + (if we inject but then encounter the real file) duplicate matches */ + 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. Add it, it's 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);