[patch/rfc]: bash: fix globbing for executables without ".exe"

Gregory M. Turner gmt@malth.us
Fri Aug 31 09:13:00 GMT 2012


The attached attempts to mitigate a nasty interaction between globbing 
in bash and the cygwin .exe hack (i.e.: "/bin/b*sh" fails to match 
/bin/bash).

I originally posted this (incorrectly) to the -apps ml.

To be clear, this version is slightly revised from what I originally 
posted to -apps: specifically, I optimized the code selecting filenames 
matching "?*.exe" a bit, since this will often have to run nearly as 
many times as there are total files in all directories (nontrivially) 
searched by a given glob-pattern.

-gmt

-------------- next part --------------
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,103 @@
 	      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 (convfn[0] != '\0' && convfn[1] != '\0' && convfn[2] != '\0' &&
+	           convfn[3] != '\0' && convfn[4] != '\0')
+	    {
+	      char *x = &convfn[0];
+	      /* if sanity checks pass, assume this is the cygwin .exe hack at work. */
+	      struct stat finfo;
+	      
+	      while (*++x != '\0') ;
+	      if (*--x == 'e' && *--x == 'x' && *--x == 'e' && *--x == '.')
+	        {
+		  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 = x - convfn;
+		      /* 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);

-------------- next part --------------
--
Problem reports:       http://cygwin.com/problems.html
FAQ:                   http://cygwin.com/faq/
Documentation:         http://cygwin.com/docs.html
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple


More information about the Cygwin mailing list