Imported from ../bash-4.0-rc1.tar.gz.
[platform/upstream/bash.git] / lib / glob / glob.c
index 08a7da8..dceb2e5 100644 (file)
@@ -1,20 +1,22 @@
 /* glob.c -- file-name wildcard pattern matching for Bash.
 
-   Copyright (C) 1985-2005 Free Software Foundation, Inc.
+   Copyright (C) 1985-2009 Free Software Foundation, Inc.
 
-   This program is free software; you can redistribute it and/or modify
+   This file is part of GNU Bash, the Bourne-Again SHell.
+   
+   Bash is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   This program is distributed in the hope that it will be useful,
+   Bash is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.  */
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 /* To whomever it may concern: I have never seen the code which most
    Unix programs use to perform this function.  I wrote this from scratch
@@ -45,7 +47,8 @@
 
 #include "stdc.h"
 #include "memalloc.h"
-#include "quit.h"
+
+#include "shell.h"
 
 #include "glob.h"
 #include "strmatch.h"
 #  define ALLOCA_MAX   100000
 #endif
 
+struct globval
+  {
+    struct globval *next;
+    char *name;
+  };
+
 extern void throw_to_top_level __P((void));
 extern int sh_eaccess __P((char *, int));
+extern char *sh_makepath __P((const char *, const char *, int));
 
 extern int extended_glob;
 
@@ -88,10 +98,12 @@ int glob_ignore_case = 0;
 /* Global variable to return to signify an error in globbing. */
 char *glob_error_return;
 
+static struct globval finddirs_error_return;
+
 /* Some forward declarations. */
-static int skipname __P((char *, char *));
+static int skipname __P((char *, char *, int));
 #if HANDLE_MULTIBYTE
-static int mbskipname __P((char *, char *));
+static int mbskipname __P((char *, char *, int));
 #endif
 #if HANDLE_MULTIBYTE
 static void udequote_pathname __P((char *));
@@ -154,9 +166,10 @@ glob_pattern_p (pattern)
    with matching leading `.'. */
 
 static int
-skipname (pat, dname)
+skipname (pat, dname, flags)
      char *pat;
      char *dname;
+     int flags;
 {
   /* If a leading dot need not be explicitly matched, and the pattern
      doesn't start with a `.', don't match `.' or `..' */
@@ -179,8 +192,9 @@ skipname (pat, dname)
    characters in PAT and DNAME.  Mostly concerned with matching leading `.'. */
 
 static int
-mbskipname (pat, dname)
+mbskipname (pat, dname, flags)
      char *pat, *dname;
+     int flags;
 {
   int ret;
   wchar_t *pat_wc, *dn_wc;
@@ -315,6 +329,75 @@ glob_testdir (dir)
   return (0);
 }
 
+/* Recursively scan SDIR for directories matching PAT (PAT is always `**').
+   FLAGS is simply passed down to the recursive call to glob_vector.  Returns
+   a list of matching directory names.  EP, if non-null, is set to the last
+   element of the returned list.  NP, if non-null, is set to the number of
+   directories in the returned list.  These two variables exist for the
+   convenience of the caller (always glob_vector). */
+static struct globval *
+finddirs (pat, sdir, flags, ep, np)
+     char *pat;
+     char *sdir;
+     int flags;
+     struct globval **ep;
+     int *np;
+{
+  char **r, *n;
+  int ndirs;
+  struct globval *ret, *e, *g;
+
+/*itrace("finddirs: pat = `%s' sdir = `%s' flags = 0x%x", pat, sdir, flags);*/
+  e = ret = 0;
+  r = glob_vector (pat, sdir, flags);
+  if (r == 0 || r[0] == 0)
+    {
+      if (np)
+       *np = 0;
+      if (ep)
+        *ep = 0;
+      if (r)
+       free (r);
+      return (struct globval *)0;
+    }
+  for (ndirs = 0; r[ndirs] != 0; ndirs++)
+    {
+      g = (struct globval *) malloc (sizeof (struct globval));
+      if (g == 0)
+       {
+         while (ret)           /* free list built so far */
+           {
+             g = ret->next;
+             free (ret);
+             ret = g;
+           }
+
+         free (r);
+         if (np)
+           *np = 0;
+         if (ep)
+           *ep = 0;
+         return (&finddirs_error_return);
+       }
+      if (e == 0)
+       e = g;
+
+      g->next = ret;
+      ret = g;
+
+      g->name = r[ndirs];
+    }
+
+  free (r);
+  if (ep)
+    *ep = e;
+  if (np)
+    *np = ndirs;
+
+  return ret;
+}
+
+       
 /* Return a vector of names of files in directory DIR
    whose names match glob pattern PAT.
    The names are not in any particular order.
@@ -337,31 +420,27 @@ glob_vector (pat, dir, flags)
      char *dir;
      int flags;
 {
-  struct globval
-    {
-      struct globval *next;
-      char *name;
-    };
-
   DIR *d;
   register struct dirent *dp;
-  struct globval *lastlink;
+  struct globval *lastlink, *e, *dirlist;
   register struct globval *nextlink;
-  register char *nextname, *npat;
+  register char *nextname, *npat, *subdir;
   unsigned int count;
-  int lose, skip;
+  int lose, skip, ndirs, isdir, sdlen, add_current;
   register char **name_vector;
   register unsigned int i;
   int mflags;          /* Flags passed to strmatch (). */
+  int pflags;          /* flags passed to sh_makepath () */
   int nalloca;
   struct globval *firstmalloc, *tmplink;
 
   lastlink = 0;
-  count = lose = skip = 0;
+  count = lose = skip = add_current = 0;
 
   firstmalloc = 0;
   nalloca = 0;
 
+/*itrace("glob_vector: pat = `%s' dir = `%s' flags = 0x%x", pat, dir, flags);*/
   /* If PAT is empty, skip the loop, but return one (empty) filename. */
   if (pat == 0 || *pat == '\0')
     {
@@ -463,6 +542,8 @@ glob_vector (pat, dir, flags)
       if (extended_glob)
        mflags |= FNM_EXTMATCH;
 
+      add_current = ((flags & (GX_ALLDIRS|GX_ADDCURDIR)) == (GX_ALLDIRS|GX_ADDCURDIR));
+
       /* Scan the directory, finding all names that match.
         For each name that matches, allocate a struct globval
         on the stack and store the name in it.
@@ -490,13 +571,69 @@ glob_vector (pat, dir, flags)
 #endif
 
 #if HANDLE_MULTIBYTE
-         if (MB_CUR_MAX > 1 && mbskipname (pat, dp->d_name))
+         if (MB_CUR_MAX > 1 && mbskipname (pat, dp->d_name, flags))
            continue;
          else
 #endif
-         if (skipname (pat, dp->d_name))
+         if (skipname (pat, dp->d_name, flags))
            continue;
 
+         /* If we're only interested in directories, don't bother with files */
+         if (flags & (GX_MATCHDIRS|GX_ALLDIRS))
+           {
+             pflags = (flags & GX_ALLDIRS) ? MP_RMDOT : 0;
+             if (flags & GX_NULLDIR)
+               pflags |= MP_IGNDOT;
+             subdir = sh_makepath (dir, dp->d_name, pflags);
+             isdir = glob_testdir (subdir);
+             if (isdir < 0 && (flags & GX_MATCHDIRS))
+               {
+                 free (subdir);
+                 continue;
+               }
+           }
+
+         if (flags & GX_ALLDIRS)
+           {
+             if (isdir == 0)
+               {
+                 dirlist = finddirs (pat, subdir, (flags & ~GX_ADDCURDIR), &e, &ndirs);
+                 if (dirlist == &finddirs_error_return)
+                   {
+                     free (subdir);
+                     lose = 1;
+                     break;
+                   }
+                 if (ndirs)            /* add recursive directories to list */
+                   {
+                     if (firstmalloc == 0)
+                       firstmalloc = e;
+                     e->next = lastlink;
+                     lastlink = dirlist;
+                     count += ndirs;
+                   }
+               }
+
+             nextlink = (struct globval *) malloc (sizeof (struct globval));
+             if (firstmalloc == 0)
+               firstmalloc = nextlink;
+             sdlen = strlen (subdir);
+             nextname = (char *) malloc (sdlen + 1);
+             if (nextlink == 0 || nextname == 0)
+               {
+                 free (subdir);
+                 lose = 1;
+                 break;
+               }
+             nextlink->next = lastlink;
+             lastlink = nextlink;
+             nextlink->name = nextname;
+             bcopy (subdir, nextname, sdlen + 1);
+             free (subdir);
+             ++count;
+             continue;
+           }
+             
          if (strmatch (pat, dp->d_name, mflags) != FNM_NOMATCH)
            {
              if (nalloca < ALLOCA_MAX)
@@ -510,6 +647,7 @@ glob_vector (pat, dir, flags)
                  if (firstmalloc == 0)
                    firstmalloc = nextlink;
                }
+
              nextname = (char *) malloc (D_NAMLEN (dp) + 1);
              if (nextlink == 0 || nextname == 0)
                {
@@ -527,6 +665,27 @@ glob_vector (pat, dir, flags)
       (void) closedir (d);
     }
 
+  /* compat: if GX_ALLDIRS, add the passed directory also */
+  if (add_current)
+    {
+      sdlen = strlen (dir);
+      nextname = (char *)malloc (sdlen + 1);
+      nextlink = (struct globval *) malloc (sizeof (struct globval));
+      if (nextlink == 0 || nextname == 0)
+       lose = 1;
+      else
+       {
+         nextlink->name = nextname;
+         nextlink->next = lastlink;
+         lastlink = nextlink;
+         if (flags & GX_NULLDIR)
+           nextname[0] = '\0';
+         else
+           bcopy (dir, nextname, sdlen + 1);
+         ++count;
+       }
+    }
+
   if (lose == 0)
     {
       name_vector = (char **) malloc ((count + 1) * sizeof (char *));
@@ -585,7 +744,7 @@ glob_vector (pat, dir, flags)
          free (tmplink);
        }
     }
-       
+
   return (name_vector);
 }
 
@@ -678,9 +837,10 @@ glob_filename (pathname, flags)
 {
   char **result;
   unsigned int result_size;
-  char *directory_name, *filename;
+  char *directory_name, *filename, *dname;
   unsigned int directory_len;
   int free_dirname;                    /* flag */
+  int dflags;
 
   result = (char **) malloc (sizeof (char *));
   result_size = 1;
@@ -721,10 +881,14 @@ glob_filename (pathname, flags)
       char **directories;
       register unsigned int i;
 
+      dflags = flags & ~GX_MARKDIRS;
+      if ((flags & GX_GLOBSTAR) && directory_name[0] == '*' && directory_name[1] == '*' && (directory_name[2] == '/' || directory_name[2] == '\0'))
+       dflags |= GX_ALLDIRS|GX_ADDCURDIR;
+
       if (directory_name[directory_len - 1] == '/')
        directory_name[directory_len - 1] = '\0';
 
-      directories = glob_filename (directory_name, flags & ~GX_MARKDIRS);
+      directories = glob_filename (directory_name, dflags);
 
       if (free_dirname)
        {
@@ -753,10 +917,19 @@ glob_filename (pathname, flags)
        {
          char **temp_results;
 
-         /* Scan directory even on a NULL pathname.  That way, `*h/'
+         /* Scan directory even on a NULL filename.  That way, `*h/'
             returns only directories ending in `h', instead of all
             files ending in `h' with a `/' appended. */
-         temp_results = glob_vector (filename, directories[i], flags & ~GX_MARKDIRS);
+         dname = directories[i];
+         dflags = flags & ~GX_MARKDIRS;
+         if ((flags & GX_GLOBSTAR) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0')
+           dflags |= GX_ALLDIRS|GX_ADDCURDIR;
+         if (dname[0] == '\0' && filename[0])
+           {
+             dflags |= GX_NULLDIR;
+             dname = ".";      /* treat null directory name and non-null filename as current directory */
+           }
+         temp_results = glob_vector (filename, dname, dflags);
 
          /* Handle error cases. */
          if (temp_results == NULL)
@@ -830,9 +1003,14 @@ glob_filename (pathname, flags)
 
       /* Just return what glob_vector () returns appended to the
         directory name. */
+      dflags = flags & ~GX_MARKDIRS;
+      if (directory_len == 0)
+       dflags |= GX_NULLDIR;
+      if ((flags & GX_GLOBSTAR) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0')
+       dflags |= GX_ALLDIRS|GX_ADDCURDIR;
       temp_results = glob_vector (filename,
                                  (directory_len == 0 ? "." : directory_name),
-                                 flags & ~GX_MARKDIRS);
+                                 dflags);
 
       if (temp_results == NULL || temp_results == (char **)&glob_error_return)
        {
@@ -841,7 +1019,7 @@ glob_filename (pathname, flags)
          return (temp_results);
        }
 
-      result = glob_dir_to_array (directory_name, temp_results, flags);
+      result = glob_dir_to_array ((dflags & GX_ALLDIRS) ? "" : directory_name, temp_results, flags);
       if (free_dirname)
        free (directory_name);
       return (result);