openenvutils/commandshell/shell/src/glob.c
changeset 0 2e3d3ce01487
child 4 0fdb7f6b0309
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/openenvutils/commandshell/shell/src/glob.c	Tue Feb 02 10:12:00 2010 +0200
@@ -0,0 +1,2872 @@
+//glob.c - filename generation
+//
+// © Portions Copyright (c) Symbian Software Ltd 2007. All rights reserved.
+//
+/*
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "glob.pro"
+
+#ifdef __SYMBIAN32__
+#ifdef __WINSCW__
+#pragma warn_unusedarg off
+#pragma warn_possunwant off
+#endif//__WINSCW__
+#endif//__SYMBIAN32__
+
+
+#if defined(OFF_T_IS_64_BIT) && defined(__GNUC__)
+# define ALIGN64 __attribute__((aligned(8)))
+#else
+# define ALIGN64
+#endif
+
+/* flag for CSHNULLGLOB */
+
+typedef struct gmatch *Gmatch;
+
+struct gmatch {
+    char *name;
+    off_t size ALIGN64;
+    long atime;
+    long mtime;
+    long ctime;
+    long links;
+    off_t _size ALIGN64;
+    long _atime;
+    long _mtime;
+    long _ctime;
+    long _links;
+};
+
+#define GS_NAME   1
+#define GS_DEPTH  2
+#define GS_SIZE   4
+#define GS_ATIME  8
+#define GS_MTIME 16
+#define GS_CTIME 32
+#define GS_LINKS 64
+
+#define GS_SHIFT  5
+#define GS__SIZE  (GS_SIZE << GS_SHIFT)
+#define GS__ATIME (GS_ATIME << GS_SHIFT)
+#define GS__MTIME (GS_MTIME << GS_SHIFT)
+#define GS__CTIME (GS_CTIME << GS_SHIFT)
+#define GS__LINKS (GS_LINKS << GS_SHIFT)
+
+#define GS_DESC  4096
+
+#define GS_NORMAL (GS_SIZE | GS_ATIME | GS_MTIME | GS_CTIME | GS_LINKS)
+#define GS_LINKED (GS_NORMAL << GS_SHIFT)
+
+/**/
+int badcshglob;
+
+/**/
+int pathpos;		/* position in pathbuf (needed by pattern code) */
+
+/**/
+char *pathbuf;		/* pathname buffer (needed by pattern code) */
+
+typedef struct stat *Statptr;	 /* This makes the Ultrix compiler happy.  Go figure. */
+
+/* modifier for unit conversions */
+
+#define TT_DAYS 0
+#define TT_HOURS 1
+#define TT_MINS 2
+#define TT_WEEKS 3
+#define TT_MONTHS 4
+#define TT_SECONDS 5
+
+#define TT_BYTES 0
+#define TT_POSIX_BLOCKS 1
+#define TT_KILOBYTES 2
+#define TT_MEGABYTES 3
+
+
+typedef int (*TestMatchFunc) _((char *, struct stat *, off_t, char *));
+
+struct qual {
+    struct qual *next;		/* Next qualifier, must match                */
+    struct qual *or;		/* Alternative set of qualifiers to match    */
+    TestMatchFunc func;		/* Function to call to test match            */
+    off_t data ALIGN64;		/* Argument passed to function               */
+    int sense;			/* Whether asserting or negating             */
+    int amc;			/* Flag for which time to test (a, m, c)     */
+    int range;			/* Whether to test <, > or = (as per signum) */
+    int units;			/* Multiplier for time or size, respectively */
+    char *sdata;		/* currently only: expression to eval        */
+};
+
+/* Prefix, suffix for doing zle trickery */
+
+/**/
+mod_export char *glob_pre, *glob_suf;
+
+/* struct to easily save/restore current state */
+
+struct globdata {
+    int gd_pathpos;
+    char *gd_pathbuf;
+
+    int gd_matchsz;		/* size of matchbuf                     */
+    int gd_matchct;		/* number of matches found              */
+    int gd_pathbufsz;		/* size of pathbuf			*/
+    int gd_pathbufcwd;		/* where did we chdir()'ed		*/
+    Gmatch gd_matchbuf;		/* array of matches                     */
+    Gmatch gd_matchptr;		/* &matchbuf[matchct]                   */
+    char *gd_colonmod;		/* colon modifiers in qualifier list    */
+
+    /* Qualifiers pertaining to current pattern */
+    struct qual *gd_quals;
+
+    /* Other state values for current pattern */
+    int gd_qualct, gd_qualorct;
+    int gd_range, gd_amc, gd_units;
+    int gd_gf_nullglob, gd_gf_markdirs, gd_gf_noglobdots, gd_gf_listtypes;
+    int gd_gf_numsort;
+    int gd_gf_follow, gd_gf_sorts, gd_gf_nsorts, gd_gf_sortlist[11];
+
+    char *gd_glob_pre, *gd_glob_suf;
+};
+
+/* The variable with the current globbing state and convenience macros */
+
+static struct globdata curglobdata;
+
+#define matchsz       (curglobdata.gd_matchsz)
+#define matchct       (curglobdata.gd_matchct)
+#define pathbufsz     (curglobdata.gd_pathbufsz)
+#define pathbufcwd    (curglobdata.gd_pathbufcwd)
+#define matchbuf      (curglobdata.gd_matchbuf)
+#define matchptr      (curglobdata.gd_matchptr)
+#define colonmod      (curglobdata.gd_colonmod)
+#define quals         (curglobdata.gd_quals)
+#define qualct        (curglobdata.gd_qualct)
+#define qualorct      (curglobdata.gd_qualorct)
+#define g_range       (curglobdata.gd_range)
+#define g_amc         (curglobdata.gd_amc)
+#define g_units       (curglobdata.gd_units)
+#define gf_nullglob   (curglobdata.gd_gf_nullglob)
+#define gf_markdirs   (curglobdata.gd_gf_markdirs)
+#define gf_noglobdots (curglobdata.gd_gf_noglobdots)
+#define gf_listtypes  (curglobdata.gd_gf_listtypes)
+#define gf_numsort    (curglobdata.gd_gf_numsort)
+#define gf_follow     (curglobdata.gd_gf_follow)
+#define gf_sorts      (curglobdata.gd_gf_sorts)
+#define gf_nsorts     (curglobdata.gd_gf_nsorts)
+#define gf_sortlist   (curglobdata.gd_gf_sortlist)
+
+/* and macros for save/restore */
+
+#define save_globstate(N) \
+  do { \
+    memcpy(&(N), &curglobdata, sizeof(struct globdata)); \
+    (N).gd_pathpos = pathpos; \
+    (N).gd_pathbuf = pathbuf; \
+    (N).gd_pathbufsz = 0; \
+    (N).gd_glob_pre = glob_pre; \
+    (N).gd_glob_suf = glob_suf; \
+    pathbuf = NULL; \
+  } while (0)
+
+#define restore_globstate(N) \
+  do { \
+    zfree(pathbuf, pathbufsz); \
+    memcpy(&curglobdata, &(N), sizeof(struct globdata)); \
+    pathpos = (N).gd_pathpos; \
+    pathbuf = (N).gd_pathbuf; \
+    glob_pre = (N).gd_glob_pre; \
+    glob_suf = (N).gd_glob_suf; \
+  } while (0)
+
+/* pathname component in filename patterns */
+
+struct complist {
+    Complist next;
+    Patprog pat;
+    int closure;		/* 1 if this is a (foo/)# */
+    int follow; 		/* 1 to go thru symlinks */
+};
+
+/* Next character after one which may be a Meta (x is any char *) */
+#define METANEXT(x)	(*(x) == Meta ? (x)+2 : (x)+1)
+/*
+ * Increment pointer which may be on a Meta (x is a pointer variable),
+ * returning the incremented value (i.e. like pre-increment).
+ */
+#define METAINC(x)	((x) += (*(x) == Meta) ? 2 : 1)
+/*
+ * Return unmetafied char from string (x is any char *)
+ */
+#define UNMETA(x)	(*(x) == Meta ? (x)[1] ^ 32 : *(x))
+
+/* Add a component to pathbuf: This keeps track of how    *
+ * far we are into a file name, since each path component *
+ * must be matched separately.                            */
+
+/**/
+static void
+addpath(char *s, int l)
+{
+    DPUTS(!pathbuf, "BUG: pathbuf not initialised");
+    while (pathpos + l + 1 >= pathbufsz)
+	pathbuf = realloc(pathbuf, pathbufsz *= 2);
+    while (l--)
+	pathbuf[pathpos++] = *s++;
+    pathbuf[pathpos++] = '/';
+    pathbuf[pathpos] = '\0';
+}
+
+/* stat the filename s appended to pathbuf.  l should be true for lstat,    *
+ * false for stat.  If st is NULL, the file is only checked for existance.  *
+ * s == "" is treated as s == ".".  This is necessary since on most systems *
+ * foo/ can be used to reference a non-directory foo.  Returns nonzero if   *
+ * the file does not exists.                                                */
+
+/**/
+static int
+statfullpath(const char *s, struct stat *st, int l)
+{
+    char buf[PATH_MAX];
+
+    DPUTS(strlen(s) + !*s + pathpos - pathbufcwd >= PATH_MAX,
+	  "BUG: statfullpath(): pathname too long");
+    strcpy(buf, pathbuf + pathbufcwd);
+    strcpy(buf + pathpos - pathbufcwd, s);
+    if (!*s && *buf) {
+	/*
+	 * Don't add the '.' if the path so far is empty, since
+	 * then we get bogus empty strings inserted as files.
+	 */
+	buf[pathpos - pathbufcwd] = '.';
+	buf[pathpos - pathbufcwd + 1] = '\0';
+	l = 0;
+    }
+    unmetafy(buf, NULL);
+    if (!st)
+	return access(buf, F_OK) && (!l || readlink(buf, NULL, 0));
+    return l ? lstat(buf, st) : stat(buf, st);
+}
+
+/* This may be set by qualifier functions to an array of strings to insert
+ * into the list instead of the original string. */
+
+char **inserts;
+
+/* add a match to the list */
+
+/**/
+static void
+insert(char *s, int checked)
+{
+    struct stat buf, buf2, *bp;
+    char *news = s;
+    int statted = 0;
+
+    queue_signals();
+    inserts = NULL;
+
+    if (gf_listtypes || gf_markdirs) {
+	/* Add the type marker to the end of the filename */
+	mode_t mode;
+	checked = statted = 1;
+	if (statfullpath(s, &buf, 1)) {
+	    unqueue_signals();
+	    return;
+	}
+	mode = buf.st_mode;
+	if (gf_follow) {
+	    if (!S_ISLNK(mode) || statfullpath(s, &buf2, 0))
+		memcpy(&buf2, &buf, sizeof(buf));
+	    statted |= 2;
+	    mode = buf2.st_mode;
+	}
+	if (gf_listtypes || S_ISDIR(mode)) {
+	    int ll = strlen(s);
+
+	    news = (char *) hcalloc(ll + 2);
+	    strcpy(news, s);
+	    news[ll] = file_type(mode);
+	    news[ll + 1] = '\0';
+	}
+    }
+    if (qualct || qualorct) {
+	/* Go through the qualifiers, rejecting the file if appropriate */
+	struct qual *qo, *qn;
+
+	if (!statted && statfullpath(s, &buf, 1)) {
+	    unqueue_signals();
+	    return;
+	}
+	news = dyncat(pathbuf, news);
+
+	statted = 1;
+	qo = quals;
+	for (qn = qo; qn && qn->func;) {
+	    g_range = qn->range;
+	    g_amc = qn->amc;
+	    g_units = qn->units;
+	    if ((qn->sense & 2) && !(statted & 2)) {
+		/* If (sense & 2), we're following links */
+		if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0))
+		    memcpy(&buf2, &buf, sizeof(buf));
+		statted |= 2;
+	    }
+	    bp = (qn->sense & 2) ? &buf2 : &buf;
+	    /* Reject the file if the function returned zero *
+	     * and the sense was positive (sense&1 == 0), or *
+	     * vice versa.                                   */
+	    if ((!((qn->func) (news, bp, qn->data, qn->sdata)) ^ qn->sense) & 1) {
+		/* Try next alternative, or return if there are no more */
+		if (!(qo = qo->or)) {
+		    unqueue_signals();
+		    return;
+		}
+		qn = qo;
+		continue;
+	    }
+	    qn = qn->next;
+	}
+    } else if (!checked) {
+	if (statfullpath(s, NULL, 1)) {
+	    unqueue_signals();
+	    return;
+	}
+	statted = 1;
+	news = dyncat(pathbuf, news);
+    } else
+	news = dyncat(pathbuf, news);
+
+    while (!inserts || (news = dupstring(*inserts++))) {
+	if (colonmod) {
+	    /* Handle the remainder of the qualifier:  e.g. (:r:s/foo/bar/). */
+	    s = colonmod;
+	    modify(&news, &s);
+	}
+	if (!statted && (gf_sorts & GS_NORMAL)) {
+	    statfullpath(s, &buf, 1);
+	    statted = 1;
+	}
+	if (!(statted & 2) && (gf_sorts & GS_LINKED)) {
+	    if (statted) {
+		if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0))
+		    memcpy(&buf2, &buf, sizeof(buf));
+	    } else if (statfullpath(s, &buf2, 0))
+		statfullpath(s, &buf2, 1);
+	    statted |= 2;
+	}
+	matchptr->name = news;
+	if (statted & 1) {
+	    matchptr->size = buf.st_size;
+	    matchptr->atime = buf.st_atime;
+	    matchptr->mtime = buf.st_mtime;
+	    matchptr->ctime = buf.st_ctime;
+	    matchptr->links = buf.st_nlink;
+	}
+	if (statted & 2) {
+	    matchptr->_size = buf2.st_size;
+	    matchptr->_atime = buf2.st_atime;
+	    matchptr->_mtime = buf2.st_mtime;
+	    matchptr->_ctime = buf2.st_ctime;
+	    matchptr->_links = buf2.st_nlink;
+	}
+	matchptr++;
+
+	if (++matchct == matchsz) {
+	    matchbuf = (Gmatch )realloc((char *)matchbuf,
+					sizeof(struct gmatch) * (matchsz *= 2));
+
+	    matchptr = matchbuf + matchct;
+	}
+	if (!inserts)
+	    break;
+    }
+    unqueue_signals();
+}
+
+/* Check to see if str is eligible for filename generation. */
+
+/**/
+mod_export int
+haswilds(char *str)
+{
+    /* `[' and `]' are legal even if bad patterns are usually not. */
+    if ((*str == Inbrack || *str == Outbrack) && !str[1])
+	return 0;
+
+    /* If % is immediately followed by ?, then that ? is     *
+     * not treated as a wildcard.  This is so you don't have *
+     * to escape job references such as %?foo.               */
+    if (str[0] == '%' && str[1] == Quest)
+	str[1] = '?';
+
+    for (; *str; str++) {
+	switch (*str) {
+	    case Inpar:
+	    case Bar:
+	    case Star:
+	    case Inbrack:
+	    case Inang:
+	    case Quest:
+		return 1;
+	    case Pound:
+	    case Hat:
+		if (isset(EXTENDEDGLOB))
+		    return 1;
+		break;
+	}
+    }
+    return 0;
+}
+
+/* Do the globbing:  scanner is called recursively *
+ * with successive bits of the path until we've    *
+ * tried all of it.                                */
+
+/**/
+static void
+scanner(Complist q)
+{
+    Patprog p;
+    int closure;
+    int pbcwdsav = pathbufcwd;
+    int errssofar = errsfound;
+    struct dirsav ds;
+
+    ds.ino = ds.dev = 0;
+    ds.dirname = NULL;
+    ds.dirfd = ds.level = -1;
+    if (!q)
+	return;
+
+    if ((closure = q->closure)) {
+	/* (foo/)# - match zero or more dirs */
+	if (q->closure == 2)	/* (foo/)## - match one or more dirs */
+	    q->closure = 1;
+	else
+	    scanner(q->next);
+    }
+    p = q->pat;
+    /* Now the actual matching for the current path section. */
+    if (p->flags & PAT_PURES) {
+	/*
+	 * It's a straight string to the end of the path section.
+	 */
+	char *str = (char *)p + p->startoff;
+	int l = p->patmlen;
+
+	if (l + !l + pathpos - pathbufcwd >= PATH_MAX) {
+	    int err;
+
+	    if (l >= PATH_MAX)
+		return;
+	    err = lchdir(pathbuf + pathbufcwd, &ds, 0);
+	    if (err == -1)
+		return;
+	    if (err) {
+		zerr("current directory lost during glob", NULL, 0);
+		return;
+	    }
+	    pathbufcwd = pathpos;
+	}
+	if (q->next) {
+	    /* Not the last path section. Just add it to the path. */
+	    int oppos = pathpos;
+
+	    if (!errflag) {
+		int add = 1;
+
+		if (q->closure && *pathbuf) {
+		    if (!strcmp(str, "."))
+			add = 0;
+		    else if (!strcmp(str, "..")) {
+			struct stat sc, sr;
+
+			add = (stat("/", &sr) || stat(pathbuf, &sc) ||
+			       sr.st_ino != sc.st_ino ||
+			       sr.st_dev != sc.st_dev);
+		    }
+		}
+		if (add) {
+		    addpath(str, l);
+		    if (!closure || !statfullpath("", NULL, 1))
+			scanner((q->closure) ? q : q->next);
+		    pathbuf[pathpos = oppos] = '\0';
+		}
+	    }
+	} else {
+	    if (str[l])
+		str = dupstrpfx(str, l);
+	    insert(str, 0);
+	}
+    } else {
+	/* Do pattern matching on current path section. */
+	char *fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : ".";
+	int dirs = !!q->next;
+	DIR *lock = opendir(fn);
+	char *subdirs = NULL;
+	int subdirlen = 0;
+
+	if (lock == NULL)
+	    return;
+	while ((fn = zreaddir(lock, 1)) && !errflag) {
+	    /* prefix and suffix are zle trickery */
+	    if (!dirs && !colonmod &&
+		((glob_pre && !strpfx(glob_pre, fn))
+		 || (glob_suf && !strsfx(glob_suf, fn))))
+		continue;
+	    errsfound = errssofar;
+	    if (pattry(p, fn)) {
+		/* if this name matchs the pattern... */
+		if (pbcwdsav == pathbufcwd &&
+		    strlen(fn) + pathpos - pathbufcwd >= PATH_MAX) {
+		    int err;
+
+		    DPUTS(pathpos == pathbufcwd,
+			  "BUG: filename longer than PATH_MAX");
+		    err = lchdir(pathbuf + pathbufcwd, &ds, 0);
+		    if (err == -1)
+			break;
+		    if (err) {
+			zerr("current directory lost during glob", NULL, 0);
+			break;
+		    }
+		    pathbufcwd = pathpos;
+		}
+		if (dirs) {
+		    int l;
+
+		    /*
+		     * If not the last component in the path:
+		     *
+		     * If we made an approximation in the new path segment,
+		     * then it is possible we made too many errors.  For
+		     * example, (ab)#(cb)# will match the directory abcb
+		     * with one error if allowed to, even though it can
+		     * match with none.  This will stop later parts of the
+		     * path matching, so we need to check by reducing the
+		     * maximum number of errors and seeing if the directory
+		     * still matches.  Luckily, this is not a terribly
+		     * common case, since complex patterns typically occur
+		     * in the last part of the path which is not affected
+		     * by this problem.
+		     */
+		    if (errsfound > errssofar) {
+			forceerrs = errsfound - 1;
+			while (forceerrs >= errssofar) {
+			    errsfound = errssofar;
+			    if (!pattry(p, fn))
+				break;
+			    forceerrs = errsfound - 1;
+			}
+			errsfound = forceerrs + 1;
+			forceerrs = -1;
+		    }
+		    if (closure) {
+			/* if matching multiple directories */
+			struct stat buf;
+
+			if (statfullpath(fn, &buf, !q->follow)) {
+			    if (errno != ENOENT && errno != EINTR &&
+				errno != ENOTDIR && !errflag) {
+				zwarn("%e: %s", fn, errno);
+			    }
+			    continue;
+			}
+			if (!S_ISDIR(buf.st_mode))
+			    continue;
+		    }
+		    l = strlen(fn) + 1;
+		    subdirs = hrealloc(subdirs, subdirlen, subdirlen + l
+				       + sizeof(int));
+		    strcpy(subdirs + subdirlen, fn);
+		    subdirlen += l;
+		    /* store the count of errors made so far, too */
+		    memcpy(subdirs + subdirlen, (char *)&errsfound,
+			   sizeof(int));
+		    subdirlen += sizeof(int);
+		} else
+		    /* if the last filename component, just add it */
+		    insert(fn, 1);
+	    }
+	}
+	closedir(lock);
+	if (subdirs) {
+	    int oppos = pathpos;
+
+	    for (fn = subdirs; fn < subdirs+subdirlen; ) {
+		int l = strlen(fn);
+		addpath(fn, l);
+		fn += l + 1;
+		memcpy((char *)&errsfound, fn, sizeof(int));
+		fn += sizeof(int);
+		scanner((q->closure) ? q : q->next);  /* scan next level */
+		pathbuf[pathpos = oppos] = '\0';
+	    }
+	    hrealloc(subdirs, subdirlen, 0);
+	}
+    }
+    if (pbcwdsav < pathbufcwd) {
+	if (restoredir(&ds))
+	    zerr("current directory lost during glob", NULL, 0);
+	zsfree(ds.dirname);
+	if (ds.dirfd >= 0)
+	    close(ds.dirfd);
+	pathbufcwd = pbcwdsav;
+    }
+}
+
+/* This function tokenizes a zsh glob pattern */
+
+/**/
+static Complist
+parsecomplist(char *instr)
+{
+    Patprog p1;
+    Complist l1;
+    char *str;
+    int compflags = gf_noglobdots ? (PAT_FILE|PAT_NOGLD) : PAT_FILE;
+
+    if (instr[0] == Star && instr[1] == Star &&
+        (instr[2] == '/' || (instr[2] == Star && instr[3] == '/'))) {
+	/* Match any number of directories. */
+	int follow;
+
+	/* with three stars, follow symbolic links */
+	follow = (instr[2] == Star);
+	instr += (3 + follow);
+
+	/* Now get the next path component if there is one. */
+	l1 = (Complist) zhalloc(sizeof *l1);
+	if ((l1->next = parsecomplist(instr)) == NULL) {
+	    errflag = 1;
+	    return NULL;
+	}
+	l1->pat = patcompile(NULL, compflags | PAT_ANY, NULL);
+	l1->closure = 1;		/* ...zero or more times. */
+	l1->follow = follow;
+	return l1;
+    }
+
+    /* Parse repeated directories such as (dir/)# and (dir/)## */
+    if (*(str = instr) == Inpar && !skipparens(Inpar, Outpar, (char **)&str) &&
+        *str == Pound && isset(EXTENDEDGLOB) && str[-2] == '/') {
+	instr++;
+	if (!(p1 = patcompile(instr, compflags, &instr)))
+	    return NULL;
+	if (instr[0] == '/' && instr[1] == Outpar && instr[2] == Pound) {
+	    int pdflag = 0;
+
+	    instr += 3;
+	    if (*instr == Pound) {
+		pdflag = 1;
+		instr++;
+	    }
+	    l1 = (Complist) zhalloc(sizeof *l1);
+	    l1->pat = p1;
+	    l1->closure = 1 + pdflag;
+	    l1->follow = 0;
+	    l1->next = parsecomplist(instr);
+	    return (l1->pat) ? l1 : NULL;
+	}
+    } else {
+	/* parse single path component */
+	if (!(p1 = patcompile(instr, compflags|PAT_FILET, &instr)))
+	    return NULL;
+	/* then do the remaining path components */
+	if (*instr == '/' || !*instr) {
+	    int ef = *instr == '/';
+
+	    l1 = (Complist) zhalloc(sizeof *l1);
+	    l1->pat = p1;
+	    l1->closure = 0;
+	    l1->next = ef ? parsecomplist(instr+1) : NULL;
+	    return (ef && !l1->next) ? NULL : l1;
+	}
+    }
+    errflag = 1;
+    return NULL;
+}
+
+/* turn a string into a Complist struct:  this has path components */
+
+/**/
+static Complist
+parsepat(char *str)
+{
+    long assert;
+    int ignore;
+
+    patcompstart();
+    /*
+     * Check for initial globbing flags, so that they don't form
+     * a bogus path component.
+     */
+    if ((*str == Inpar && str[1] == Pound && isset(EXTENDEDGLOB)) ||
+	(isset(KSHGLOB) && *str == '@' && str[1] == Inpar &&
+	 str[2] == Pound)) {
+	str += (*str == Inpar) ? 2 : 3;
+	if (!patgetglobflags(&str, &assert, &ignore))
+	    return NULL;
+    }
+
+    /* Now there is no (#X) in front, we can check the path. */
+    if (!pathbuf)
+	pathbuf = zalloc(pathbufsz = PATH_MAX);
+    DPUTS(pathbufcwd, "BUG: glob changed directory");
+    if (*str == '/') {		/* pattern has absolute path */
+	str++;
+	pathbuf[0] = '/';
+	pathbuf[pathpos = 1] = '\0';
+    } else			/* pattern is relative to pwd */
+	pathbuf[pathpos = 0] = '\0';
+
+    return parsecomplist(str);
+}
+
+/* get number after qualifier */
+
+/**/
+static off_t
+qgetnum(char **s)
+{
+    off_t v = 0;
+
+    if (!idigit(**s)) {
+	zerr("number expected", NULL, 0);
+	return 0;
+    }
+    while (idigit(**s))
+	v = v * 10 + *(*s)++ - '0';
+    return v;
+}
+
+/* get mode spec after qualifier */
+
+/**/
+static zlong
+qgetmodespec(char **s)
+{
+    zlong yes = 0, no = 0, val, mask, t;
+    char *p = *s, c, how, end;
+
+    if ((c = *p) == '=' || c == Equals || c == '+' || c == '-' ||
+	c == '?' || c == Quest || (c >= '0' && c <= '7')) {
+	end = 0;
+	c = 0;
+    } else {
+	end = (c == '<' ? '>' :
+	       (c == '[' ? ']' :
+		(c == '{' ? '}' :
+		 (c == Inang ? Outang :
+		  (c == Inbrack ? Outbrack :
+		   (c == Inbrace ? Outbrace : c))))));
+	p++;
+    }
+    do {
+	mask = 0;
+	while (((c = *p) == 'u' || c == 'g' || c == 'o' || c == 'a') && end) {
+	    switch (c) {
+	    case 'o': mask |= 01007; break;
+	    case 'g': mask |= 02070; break;
+	    case 'u': mask |= 04700; break;
+	    case 'a': mask |= 07777; break;
+	    }
+	    p++;
+	}
+	how = ((c == '+' || c == '-') ? c : '=');
+	if (c == '+' || c == '-' || c == '=' || c == Equals)
+	    p++;
+	val = 0;
+	if (mask) {
+	    while ((c = *p++) != ',' && c != end) {
+		switch (c) {
+		case 'x': val |= 00111; break;
+		case 'w': val |= 00222; break;
+		case 'r': val |= 00444; break;
+		case 's': val |= 06000; break;
+		case 't': val |= 01000; break;
+		case '0': case '1': case '2': case '3':
+		case '4': case '5': case '6': case '7':
+		    t = ((zlong) c - '0');
+		    val |= t | (t << 3) | (t << 6);
+		    break;
+		default:
+		    zerr("invalid mode specification", NULL, 0);
+		    return 0;
+		}
+	    }
+	    if (how == '=' || how == '+') {
+		yes |= val & mask;
+		val = ~val;
+	    }
+	    if (how == '=' || how == '-')
+		no |= val & mask;
+	} else if (!(end && c == end) && c != ',' && c) {
+	    t = 07777;
+	    while ((c = *p) == '?' || c == Quest ||
+		   (c >= '0' && c <= '7')) {
+		if (c == '?' || c == Quest) {
+		    t = (t << 3) | 7;
+		    val <<= 3;
+		} else {
+		    t <<= 3;
+		    val = (val << 3) | ((zlong) c - '0');
+		}
+		p++;
+	    }
+	    if (end && c != end && c != ',') {
+		zerr("invalid mode specification", NULL, 0);
+		return 0;
+	    }
+	    if (how == '=') {
+		yes = (yes & ~t) | val;
+		no = (no & ~t) | (~val & ~t);
+	    } else if (how == '+')
+		yes |= val;
+	    else
+		no |= val;
+	} else {
+	    zerr("invalid mode specification", NULL, 0);
+	    return 0;
+        }
+    } while (end && c != end);
+
+    *s = p;
+    return ((yes & 07777) | ((no & 07777) << 12));
+}
+
+static int
+gmatchcmp(Gmatch a, Gmatch b)
+{
+    int i, *s;
+    off_t r = 0L;
+
+    for (i = gf_nsorts, s = gf_sortlist; i; i--, s++) {
+	switch (*s & ~GS_DESC) {
+	case GS_NAME:
+	    if (gf_numsort)
+	    	r = nstrpcmp(&b->name, &a->name);
+	    else
+	    	r = strpcmp(&b->name, &a->name);
+	    break;
+	case GS_DEPTH:
+	    {
+		char *aptr = a->name, *bptr = b->name;
+		int slasha = 0, slashb = 0;
+		/* Count slashes.  Trailing slashes don't count. */
+		while (*aptr && *aptr == *bptr)
+		    aptr++, bptr++;
+		if (*aptr)
+		    for (; aptr[1]; aptr++)
+			if (*aptr == '/') {
+			    slasha = 1;
+			    break;
+			}
+		if (*bptr)
+		    for (; bptr[1]; bptr++)
+			if (*bptr == '/') {
+			    slashb = 1;
+			    break;
+			}
+		r = slasha - slashb;
+	    }
+	    break;
+	case GS_SIZE:
+	    r = b->size - a->size;
+	    break;
+	case GS_ATIME:
+	    r = a->atime - b->atime;
+	    break;
+	case GS_MTIME:
+	    r = a->mtime - b->mtime;
+	    break;
+	case GS_CTIME:
+	    r = a->ctime - b->ctime;
+	    break;
+	case GS_LINKS:
+	    r = b->links - a->links;
+	    break;
+	case GS__SIZE:
+	    r = b->_size - a->_size;
+	    break;
+	case GS__ATIME:
+	    r = a->_atime - b->_atime;
+	    break;
+	case GS__MTIME:
+	    r = a->_mtime - b->_mtime;
+	    break;
+	case GS__CTIME:
+	    r = a->_ctime - b->_ctime;
+	    break;
+	case GS__LINKS:
+	    r = b->_links - a->_links;
+	    break;
+	}
+	if (r)
+	    return (int) ((*s & GS_DESC) ? -r : r);
+    }
+    return 0;
+}
+
+/*
+ * Duplicate a list of qualifiers using the `next' linkage (not the
+ * `or' linkage).  Return the head element and set *last (if last non-NULL)
+ * to point to the last element of the new list.  All allocation is on the
+ * heap (or off the heap?)
+ */
+static struct qual *dup_qual_list(struct qual *orig, struct qual **lastp)
+{
+    struct qual *qfirst = NULL, *qlast = NULL;
+
+    while (orig) {
+	struct qual *qnew = (struct qual *)zhalloc(sizeof(struct qual));
+	*qnew = *orig;
+	qnew->next = qnew->or = NULL;
+
+	if (!qfirst)
+	    qfirst = qnew;
+	if (qlast)
+	    qlast->next = qnew;
+	qlast = qnew;
+
+	orig = orig->next;
+    }
+
+    if (lastp)
+	*lastp = qlast;
+    return qfirst;
+}
+
+/* Main entry point to the globbing code for filename globbing. *
+ * np points to a node in the list list which will be expanded  *
+ * into a series of nodes.                                      */
+
+/**/
+void
+zglob(LinkList list, LinkNode np, int nountok)
+{
+    struct qual *qo, *qn, *ql;
+    LinkNode node = prevnode(np);
+    char *str;				/* the pattern                   */
+    int sl;				/* length of the pattern         */
+    Complist q;				/* pattern after parsing         */
+    char *ostr = (char *)getdata(np);	/* the pattern before the parser */
+					/* chops it up                   */
+    int first = 0, end = -1;		/* index of first match to return */
+					/* and index+1 of the last match */
+    struct globdata saved;		/* saved glob state              */
+    int nobareglob = !isset(BAREGLOBQUAL);
+
+    if (unset(GLOBOPT) || !haswilds(ostr)) {
+	if (!nountok)
+	    untokenize(ostr);
+	return;
+    }
+    save_globstate(saved);
+
+    str = dupstring(ostr);
+    uremnode(list, np);
+
+    /* quals will hold the complete list of qualifiers (file static). */
+    quals = NULL;
+    /*
+     * qualct and qualorct indicate we have qualifiers in the last
+     * alternative, or a set of alternatives, respectively.  They
+     * are not necessarily an accurate count, however.
+     */
+    qualct = qualorct = 0;
+    /*
+     * colonmod is a concatenated list of all colon modifiers found in
+     * all sets of qualifiers.
+     */
+    colonmod = NULL;
+    /* The gf_* flags are qualifiers which are applied globally. */
+    gf_nullglob = isset(NULLGLOB);
+    gf_markdirs = isset(MARKDIRS);
+    gf_listtypes = gf_follow = 0;
+    gf_noglobdots = unset(GLOBDOTS);
+    gf_numsort = isset(NUMERICGLOBSORT);
+    gf_sorts = gf_nsorts = 0;
+
+    /* Check for qualifiers */
+    while (!nobareglob || isset(EXTENDEDGLOB)) {
+	struct qual *newquals;
+	char *s;
+	int sense, paren;
+	off_t data;
+	char *sdata, *newcolonmod;
+	int (*func) _((char *, Statptr, off_t, char *));
+
+	/*
+	 * Initialise state variables for current file pattern.
+	 * newquals is the root for the linked list of all qualifiers.
+	 * qo is the root of the current list of alternatives.
+	 * ql is the end of the current alternative where the `next' will go.
+	 * qn is the current qualifier node to be added.
+	 *
+	 * Here is an attempt at a diagram.  An `or' is added horizontally
+	 * to the top line, a `next' at the bottom of the right hand line.
+	 * `qn' is usually NULL unless a new `or' has just been added.
+	 *
+	 * quals -> x  -> x -> qo
+	 *          |     |    |
+	 *          x     x    x
+	 *          |          |
+	 *          x          ql
+	 *
+	 * In fact, after each loop the complete set is in the file static
+	 * `quals'.  Then, if we have a second set of qualifiers, we merge
+	 * the lists together.  This is only tricky if one or both have an
+	 * `or' in them; then we need to distribute over all alternatives.
+	 */
+	newquals = qo = qn = ql = NULL;
+
+	sl = strlen(str);
+	if (str[sl - 1] != Outpar)
+	    break;
+
+	/* Check these are really qualifiers, not a set of *
+	 * alternatives or exclusions.  We can be more     *
+	 * lenient with an explicit (#q) than with a bare  *
+	 * set of qualifiers.                              */
+	paren = 0;
+	for (s = str + sl - 2; *s && (*s != Inpar || paren); s--) {
+	    switch (*s) {
+	    case Outpar:
+		paren++; /*FALLTHROUGH*/
+	    case Bar:
+		nobareglob = 1;
+		break;
+	    case Tilde:
+		if (isset(EXTENDEDGLOB))
+		    nobareglob = 1;
+		break;
+	    case Inpar:
+		paren--;
+		break;
+	    }
+	}
+	if (*s != Inpar)
+	    break;
+	if (isset(EXTENDEDGLOB) && s[1] == Pound) {
+	    if (s[2] == 'q') {
+		*s = 0;
+		s += 2;
+	    } else
+		break;
+	} else if (nobareglob)
+	    break;
+
+	/* Real qualifiers found. */
+	nobareglob = 1;
+	sense = 0;	   /* bit 0 for match (0)/don't match (1)   */
+			   /* bit 1 for follow links (2), don't (0) */
+	data = 0;	   /* Any numerical argument required       */
+	sdata = NULL;	   /* Any list argument required            */
+	newcolonmod = NULL; /* Contains trailing colon modifiers    */
+
+	str[sl-1] = 0;
+	*s++ = 0;
+	while (*s && !newcolonmod) {
+	    func = (int (*) _((char *, Statptr, off_t, char *)))0;
+	    if (idigit(*s)) {
+		/* Store numeric argument for qualifier */
+		func = qualflags;
+		data = 0;
+		sdata = NULL;
+		while (idigit(*s))
+		    data = data * 010 + (*s++ - '0');
+	    } else if (*s == ',') {
+		/* A comma separates alternative sets of qualifiers */
+		s++;
+		sense = 0;
+		if (qualct) {
+		    qn = (struct qual *)hcalloc(sizeof *qn);
+		    qo->or = qn;
+		    qo = qn;
+		    qualorct++;
+		    qualct = 0;
+		    ql = NULL;
+		}
+	    } else {
+		switch (*s++) {
+		case ':':
+		    /* Remaining arguments are history-type     *
+		     * colon substitutions, handled separately. */
+		    newcolonmod = s - 1;
+		    untokenize(newcolonmod);
+		    if (colonmod) {
+			/* remember we're searching backwards */
+			colonmod = dyncat(newcolonmod, colonmod);
+		    } else
+			colonmod = newcolonmod;
+		    break;
+		case Hat:
+		case '^':
+		    /* Toggle sense:  go from positive to *
+		     * negative match and vice versa.     */
+		    sense ^= 1;
+		    break;
+		case '-':
+		    /* Toggle matching of symbolic links */
+		    sense ^= 2;
+		    break;
+		case '@':
+		    /* Match symbolic links */
+		    func = qualislnk;
+		    break;
+		case Equals:
+		case '=':
+		    /* Match sockets */
+		    func = qualissock;
+		    break;
+		case 'p':
+		    /* Match named pipes */
+		    func = qualisfifo;
+		    break;
+		case '/':
+		    /* Match directories */
+		    func = qualisdir;
+		    break;
+		case '.':
+		    /* Match regular files */
+		    func = qualisreg;
+		    break;
+		case '%':
+		    /* Match special files: block, *
+		     * character or any device     */
+		    if (*s == 'b')
+			s++, func = qualisblk;
+		    else if (*s == 'c')
+			s++, func = qualischr;
+		    else
+			func = qualisdev;
+		    break;
+		case Star:
+		    /* Match executable plain files */
+		    func = qualiscom;
+		    break;
+		case 'R':
+		    /* Match world-readable files */
+		    func = qualflags;
+		    data = 0004;
+		    break;
+		case 'W':
+		    /* Match world-writeable files */
+		    func = qualflags;
+		    data = 0002;
+		    break;
+		case 'X':
+		    /* Match world-executable files */
+		    func = qualflags;
+		    data = 0001;
+		    break;
+		case 'A':
+		    func = qualflags;
+		    data = 0040;
+		    break;
+		case 'I':
+		    func = qualflags;
+		    data = 0020;
+		    break;
+		case 'E':
+		    func = qualflags;
+		    data = 0010;
+		    break;
+		case 'r':
+		    /* Match files readable by current process */
+		    func = qualflags;
+		    data = 0400;
+		    break;
+		case 'w':
+		    /* Match files writeable by current process */
+		    func = qualflags;
+		    data = 0200;
+		    break;
+		case 'x':
+		    /* Match files executable by current process */
+		    func = qualflags;
+		    data = 0100;
+		    break;
+		case 's':
+		    /* Match setuid files */
+		    func = qualflags;
+		    data = 04000;
+		    break;
+		case 'S':
+		    /* Match setgid files */
+		    func = qualflags;
+		    data = 02000;
+		    break;
+		case 't':
+		    func = qualflags;
+		    data = 01000;
+		    break;
+		case 'd':
+		    /* Match device files by device number  *
+		     * (as given by stat's st_dev element). */
+		    func = qualdev;
+		    data = qgetnum(&s);
+		    break;
+		case 'l':
+		    /* Match files with the given no. of hard links */
+		    func = qualnlink;
+		    g_amc = -1;
+		    goto getrange;
+		case 'U':
+		    /* Match files owned by effective user ID */
+		    func = qualuid;
+		    data = geteuid();
+		    break;
+		case 'G':
+		    /* Match files owned by effective group ID */
+		    func = qualgid;
+		    data = getegid();
+		    break;
+		case 'u':
+		    /* Match files owned by given user id */
+		    func = qualuid;
+		    /* either the actual uid... */
+		    if (idigit(*s))
+			data = qgetnum(&s);
+		    else {
+			/* ... or a user name */
+			char sav, *tt;
+
+			/* Find matching delimiters */
+			tt = get_strarg(s);
+			if (!*tt) {
+			    zerr("missing end of name",
+				 NULL, 0);
+			    data = 0;
+			} else {
+#ifdef HAVE_GETPWNAM
+			    struct passwd *pw;
+			    sav = *tt;
+			    *tt = '\0';
+
+			    if ((pw = getpwnam(s + 1)))
+				data = pw->pw_uid;
+			    else {
+				zerr("unknown user", NULL, 0);
+				data = 0;
+			    }
+			    *tt = sav;
+#else /* !HAVE_GETPWNAM */
+			    sav = *tt;
+			    zerr("unknown user", NULL, 0);
+			    data = 0;
+#endif /* !HAVE_GETPWNAM */
+			    if (sav)
+				s = tt + 1;
+			    else
+				s = tt;
+			}
+		    }
+		    break;
+		case 'g':
+		    /* Given gid or group id... works like `u' */
+		    func = qualgid;
+		    /* either the actual gid... */
+		    if (idigit(*s))
+			data = qgetnum(&s);
+		    else {
+			/* ...or a delimited group name. */
+			char sav, *tt;
+
+			tt = get_strarg(s);
+			if (!*tt) {
+			    zerr("missing end of name",
+				 NULL, 0);
+			    data = 0;
+			} else {
+#ifdef HAVE_GETGRNAM
+			    struct group *gr;
+			    sav = *tt;
+			    *tt = '\0';
+
+			    if ((gr = getgrnam(s + 1)))
+				data = gr->gr_gid;
+			    else {
+				zerr("unknown group", NULL, 0);
+				data = 0;
+			    }
+			    *tt = sav;
+#else /* !HAVE_GETGRNAM */
+			    sav = *tt;
+			    zerr("unknown group", NULL, 0);
+			    data = 0;
+#endif /* !HAVE_GETGRNAM */
+			    if (sav)
+				s = tt + 1;
+			    else
+				s = tt;
+			}
+		    }
+		    break;
+		case 'f':
+		    /* Match modes with chmod-spec. */
+		    func = qualmodeflags;
+		    data = qgetmodespec(&s);
+		    break;
+		case 'F':
+		    func = qualnonemptydir;
+		    break;
+		case 'M':
+		    /* Mark directories with a / */
+		    if ((gf_markdirs = !(sense & 1)))
+			gf_follow = sense & 2;
+		    break;
+		case 'T':
+		    /* Mark types in a `ls -F' type fashion */
+		    if ((gf_listtypes = !(sense & 1)))
+			gf_follow = sense & 2;
+		    break;
+		case 'N':
+		    /* Nullglob:  remove unmatched patterns. */
+		    gf_nullglob = !(sense & 1);
+		    break;
+		case 'D':
+		    /* Glob dots: match leading dots implicitly */
+		    gf_noglobdots = sense & 1;
+		    break;
+		case 'n':
+		    /* Numeric glob sort */
+		    gf_numsort = !(sense & 1);
+		    break;
+		case 'a':
+		    /* Access time in given range */
+		    g_amc = 0;
+		    func = qualtime;
+		    goto getrange;
+		case 'm':
+		    /* Modification time in given range */
+		    g_amc = 1;
+		    func = qualtime;
+		    goto getrange;
+		case 'c':
+		    /* Inode creation time in given range */
+		    g_amc = 2;
+		    func = qualtime;
+		    goto getrange;
+		case 'L':
+		    /* File size (Length) in given range */
+		    func = qualsize;
+		    g_amc = -1;
+		    /* Get size multiplier */
+		    g_units = TT_BYTES;
+		    if (*s == 'p' || *s == 'P')
+			g_units = TT_POSIX_BLOCKS, ++s;
+		    else if (*s == 'k' || *s == 'K')
+			g_units = TT_KILOBYTES, ++s;
+		    else if (*s == 'm' || *s == 'M')
+			g_units = TT_MEGABYTES, ++s;
+		  getrange:
+		    /* Get time multiplier */
+		    if (g_amc >= 0) {
+			g_units = TT_DAYS;
+			if (*s == 'h')
+			    g_units = TT_HOURS, ++s;
+			else if (*s == 'm')
+			    g_units = TT_MINS, ++s;
+			else if (*s == 'w')
+			    g_units = TT_WEEKS, ++s;
+			else if (*s == 'M')
+			    g_units = TT_MONTHS, ++s;
+			else if (*s == 's')
+			    g_units = TT_SECONDS, ++s;
+		    }
+		    /* See if it's greater than, equal to, or less than */
+		    if ((g_range = *s == '+' ? 1 : *s == '-' ? -1 : 0))
+			++s;
+		    data = qgetnum(&s);
+		    break;
+
+		case 'o':
+		case 'O':
+		{
+		    int t;
+
+		    switch (*s) {
+		    case 'n': t = GS_NAME; break;
+		    case 'L': t = GS_SIZE; break;
+		    case 'l': t = GS_LINKS; break;
+		    case 'a': t = GS_ATIME; break;
+		    case 'm': t = GS_MTIME; break;
+		    case 'c': t = GS_CTIME; break;
+		    case 'd': t = GS_DEPTH; break;
+		    default:
+			zerr("unknown sort specifier", NULL, 0);
+			restore_globstate(saved);
+			return;
+		    }
+		    if ((sense & 2) && !(t & (GS_NAME|GS_DEPTH)))
+			t <<= GS_SHIFT;
+		    if (gf_sorts & t) {
+			zerr("doubled sort specifier", NULL, 0);
+			restore_globstate(saved);
+			return;
+		    }
+		    gf_sorts |= t;
+		    gf_sortlist[gf_nsorts++] = t |
+			(((sense & 1) ^ (s[-1] == 'O')) ? GS_DESC : 0);
+		    s++;
+		    break;
+		}
+		case '+':
+		case 'e':
+		{
+		    char sav, *tt;
+		    int plus;
+
+		    if (s[-1] == '+') {
+			plus = 0;
+			tt = s;
+			while (iident(*tt))
+			    tt++;
+			if (tt == s)
+			{
+			    zerr("missing identifier after `+'", NULL, 0);
+			    tt = NULL;
+			}
+		    } else {
+			plus = 1;
+			tt = get_strarg(s);
+			if (!*tt)
+			{
+			    zerr("missing end of string", NULL, 0);
+			    tt = NULL;
+			}
+		    }
+
+		    if (tt == NULL) {
+			data = 0;
+		    } else {
+			sav = *tt;
+			*tt = '\0';
+			func = qualsheval;
+			sdata = dupstring(s + plus);
+			untokenize(sdata);
+			*tt = sav;
+			if (sav)
+			    s = tt + plus;
+			else
+			    s = tt;
+		    }
+		    break;
+		}
+		case '[':
+		case Inbrack:
+		{
+		    char *os = --s;
+		    struct value v;
+
+		    v.isarr = SCANPM_WANTVALS;
+		    v.pm = NULL;
+		    v.end = -1;
+		    v.inv = 0;
+		    if (getindex(&s, &v, 0) || s == os) {
+			zerr("invalid subscript", NULL, 0);
+			restore_globstate(saved);
+			return;
+		    }
+		    first = v.start;
+		    end = v.end;
+		    break;
+		}
+		default:
+		    zerr("unknown file attribute", NULL, 0);
+		    restore_globstate(saved);
+		    return;
+		}
+	    }
+	    if (func) {
+		/* Requested test is performed by function func */
+		if (!qn)
+		    qn = (struct qual *)hcalloc(sizeof *qn);
+		if (ql)
+		    ql->next = qn;
+		ql = qn;
+		if (!newquals)
+		    newquals = qo = qn;
+		qn->func = func;
+		qn->sense = sense;
+		qn->data = data;
+		qn->sdata = sdata;
+		qn->range = g_range;
+		qn->units = g_units;
+		qn->amc = g_amc;
+
+		qn = NULL;
+		qualct++;
+	    }
+	    if (errflag) {
+		restore_globstate(saved);
+		return;
+	    }
+	}
+
+	if (quals && newquals) {
+	    /* Merge previous group of qualifiers with new set. */
+	    if (quals->or || newquals->or) {
+		/* The hard case. */
+		struct qual *qorhead = NULL, *qortail = NULL;
+		/*
+		 * Distribute in the most trivial way, by creating
+		 * all possible combinations of the two sets and chaining
+		 * these into one long set of alternatives given
+		 * by qorhead and qortail.
+		 */
+		for (qn = newquals; qn; qn = qn->or) {
+		    for (qo = quals; qo; qo = qo->or) {
+			struct qual *qfirst, *qlast;
+			int islast = !qn->or && !qo->or;
+			/* Generate first set of qualifiers... */
+			if (islast) {
+			    /* Last time round:  don't bother copying. */
+			    qfirst = qn;
+			    for (qlast = qfirst; qlast->next;
+				 qlast = qlast->next)
+				;			    
+			} else
+			    qfirst = dup_qual_list(qn, &qlast);
+			/* ... link into new `or' chain ... */
+			if (!qorhead)
+			    qorhead = qfirst;
+			if (qortail)
+			    qortail->or = qfirst;
+			qortail = qfirst;
+			/* ... and concatenate second set. */
+			qlast->next = islast ? qo : dup_qual_list(qo, NULL);
+		    }
+		}
+		quals = qorhead;
+	    } else {
+		/*
+		 * Easy: we can just chain the qualifiers together.
+		 * This is an optimisation; the code above will work, too.
+		 * We retain the original left to right ordering --- remember
+		 * we are searching for sets of qualifiers from the right.
+		 */
+		qn = newquals;
+		for ( ; newquals->next; newquals = newquals->next)
+		    ;
+		newquals->next = quals;
+		quals = qn;
+	    }
+	} else if (newquals)
+	    quals = newquals;
+    }
+    q = parsepat(str);
+    if (!q || errflag) {	/* if parsing failed */
+	restore_globstate(saved);
+	if (unset(BADPATTERN)) {
+	    if (!nountok)
+		untokenize(ostr);
+	    insertlinknode(list, node, ostr);
+	    return;
+	}
+	errflag = 0;
+	zerr("bad pattern: %s", ostr, 0);
+	return;
+    }
+    if (!gf_nsorts) {
+	gf_sortlist[0] = gf_sorts = GS_NAME;
+	gf_nsorts = 1;
+    }
+    /* Initialise receptacle for matched files, *
+     * expanded by insert() where necessary.    */
+    matchptr = matchbuf = (Gmatch)zalloc((matchsz = 16) *
+					 sizeof(struct gmatch));
+    matchct = 0;
+    pattrystart();
+
+    /* The actual processing takes place here: matches go into  *
+     * matchbuf.  This is the only top-level call to scanner(). */
+    scanner(q);
+
+    /* Deal with failures to match depending on options */
+    if (matchct)
+	badcshglob |= 2;	/* at least one cmd. line expansion O.K. */
+    else if (!gf_nullglob) {
+	if (isset(CSHNULLGLOB)) {
+	    badcshglob |= 1;	/* at least one cmd. line expansion failed */
+	} else if (isset(NOMATCH)) {
+	    zerr("no matches found: %s", ostr, 0);
+	    free(matchbuf);
+	    restore_globstate(saved);
+	    return;
+	} else {
+	    /* treat as an ordinary string */
+	    untokenize(matchptr->name = dupstring(ostr));
+	    matchptr++;
+	    matchct = 1;
+	}
+    }
+    /* Sort arguments in to lexical (and possibly numeric) order. *
+     * This is reversed to facilitate insertion into the list.    */
+    qsort((void *) & matchbuf[0], matchct, sizeof(struct gmatch),
+	       (int (*) _((const void *, const void *)))gmatchcmp);
+
+    if (first < 0) {
+	first += matchct;
+	if (first < 0)
+	    first = 0;
+    }
+    if (end < 0)
+	end += matchct + 1;
+    else if (end > matchct)
+	end = matchct;
+    if ((end -= first) > 0) {
+	matchptr = matchbuf + matchct - first - end;
+	while (end-- > 0) {		/* insert matches in the arg list */
+	    insertlinknode(list, node, matchptr->name);
+	    matchptr++;
+	}
+    }
+    free(matchbuf);
+
+    restore_globstate(saved);
+}
+
+/* Return the trailing character for marking file types */
+
+/**/
+mod_export char
+file_type(mode_t filemode)
+{
+    if(S_ISBLK(filemode))
+	return '#';
+    else if(S_ISCHR(filemode))
+	return '%';
+    else if(S_ISDIR(filemode))
+	return '/';
+    else if(S_ISFIFO(filemode))
+	return '|';
+    else if(S_ISLNK(filemode))
+	return '@';
+    else if(S_ISREG(filemode))
+	return (filemode & S_IXUGO) ? '*' : ' ';
+    else if(S_ISSOCK(filemode))
+	return '=';
+    else
+	return '?';
+}
+
+/* check to see if str is eligible for brace expansion */
+
+/**/
+mod_export int
+hasbraces(char *str)
+{
+    char *lbr, *mbr, *comma;
+
+    if (isset(BRACECCL)) {
+	/* In this case, any properly formed brace expression  *
+	 * will match and expand to the characters in between. */
+	int bc, c;
+
+	for (bc = 0; (c = *str); ++str)
+	    if (c == Inbrace) {
+		if (!bc && str[1] == Outbrace)
+		    *str++ = '{', *str = '}';
+		else
+		    bc++;
+	    } else if (c == Outbrace) {
+		if (!bc)
+		    *str = '}';
+		else if (!--bc)
+		    return 1;
+	    }
+	return 0;
+    }
+    /* Otherwise we need to look for... */
+    lbr = mbr = comma = NULL;
+    for (;;) {
+	switch (*str++) {
+	case Inbrace:
+	    if (!lbr) {
+		lbr = str - 1;
+		while (idigit(*str))
+		    str++;
+		if (*str == '.' && str[1] == '.') {
+		    str++;
+		    while (idigit(*++str));
+		    if (*str == Outbrace &&
+			(idigit(lbr[1]) || idigit(str[-1])))
+			return 1;
+		}
+	    } else {
+		char *s = --str;
+
+		if (skipparens(Inbrace, Outbrace, &str)) {
+		    *lbr = *s = '{';
+		    if (comma)
+			str = comma;
+		    if (mbr && mbr < str)
+			str = mbr;
+		    lbr = mbr = comma = NULL;
+		} else if (!mbr)
+		    mbr = s;
+	    }
+	    break;
+	case Outbrace:
+	    if (!lbr)
+		str[-1] = '}';
+	    else if (comma)
+		return 1;
+	    else {
+		*lbr = '{';
+		str[-1] = '}';
+		if (mbr)
+		    str = mbr;
+		mbr = lbr = NULL;
+	    }
+	    break;
+	case Comma:
+	    if (!lbr)
+		str[-1] = ',';
+	    else if (!comma)
+		comma = str - 1;
+	    break;
+	case '\0':
+	    if (lbr)
+		*lbr = '{';
+	    if (!mbr && !comma)
+		return 0;
+	    if (comma)
+		str = comma;
+	    if (mbr && mbr < str)
+		str = mbr;
+	    lbr = mbr = comma = NULL;
+	    break;
+	}
+    }
+}
+
+/* expand stuff like >>*.c */
+
+/**/
+int
+xpandredir(struct redir *fn, LinkList tab)
+{
+    char *nam;
+    struct redir *ff;
+    int ret = 0;
+    local_list1(fake);
+
+    /* Stick the name in a list... */
+    init_list1(fake, fn->name);
+    /* ...which undergoes all the usual shell expansions */
+    prefork(&fake, isset(MULTIOS) ? 0 : PF_SINGLE);
+    /* Globbing is only done for multios. */
+    if (!errflag && isset(MULTIOS))
+	globlist(&fake, 0);
+    if (errflag)
+	return 0;
+    if (nonempty(&fake) && !nextnode(firstnode(&fake))) {
+	/* Just one match, the usual case. */
+	char *s = peekfirst(&fake);
+	fn->name = s;
+	untokenize(s);
+	if (fn->type == REDIR_MERGEIN || fn->type == REDIR_MERGEOUT) {
+	    if (s[0] == '-' && !s[1])
+		fn->type = REDIR_CLOSE;
+	    else if (s[0] == 'p' && !s[1])
+		fn->fd2 = -2;
+	    else {
+		while (idigit(*s))
+		    s++;
+		if (!*s && s > fn->name)
+		    fn->fd2 = zstrtol(fn->name, NULL, 10);
+		else if (fn->type == REDIR_MERGEIN)
+		    zerr("file number expected", NULL, 0);
+		else
+		    fn->type = REDIR_ERRWRITE;
+	    }
+	}
+    } else if (fn->type == REDIR_MERGEIN)
+	zerr("file number expected", NULL, 0);
+    else {
+	if (fn->type == REDIR_MERGEOUT)
+	    fn->type = REDIR_ERRWRITE;
+	while ((nam = (char *)ugetnode(&fake))) {
+	    /* Loop over matches, duplicating the *
+	     * redirection for each file found.   */
+	    ff = (struct redir *) zhalloc(sizeof *ff);
+	    *ff = *fn;
+	    ff->name = nam;
+	    addlinknode(tab, ff);
+	    ret = 1;
+	}
+    }
+    return ret;
+}
+
+/* brace expansion */
+
+/**/
+mod_export void
+xpandbraces(LinkList list, LinkNode *np)
+{
+    LinkNode node = (*np), last = prevnode(node);
+    char *str = (char *)getdata(node), *str3 = str, *str2;
+    int prev, bc, comma, dotdot;
+
+    for (; *str != Inbrace; str++);
+    /* First, match up braces and see what we have. */
+    for (str2 = str, bc = comma = dotdot = 0; *str2; ++str2)
+	if (*str2 == Inbrace)
+	    ++bc;
+	else if (*str2 == Outbrace) {
+	    if (--bc == 0)
+		break;
+	} else if (bc == 1) {
+	    if (*str2 == Comma)
+		++comma;	/* we have {foo,bar} */
+	    else if (*str2 == '.' && str2[1] == '.')
+		dotdot++;	/* we have {num1..num2} */
+	}
+    DPUTS(bc, "BUG: unmatched brace in xpandbraces()");
+    if (!comma && dotdot) {
+	/* Expand range like 0..10 numerically: comma or recursive
+	   brace expansion take precedence. */
+	char *dots, *p;
+	LinkNode olast = last;
+	/* Get the first number of the range */
+	int rstart = zstrtol(str+1,&dots,10), rend = 0, err = 0, rev = 0;
+	int wid1 = (dots - str) - 1, wid2 = (str2 - dots) - 2;
+	int strp = str - str3;
+
+	if (dots == str + 1 || *dots != '.' || dots[1] != '.')
+	    err++;
+	else {
+	    /* Get the last number of the range */
+	    rend = zstrtol(dots+2,&p,10);
+	    if (p == dots+2 || p != str2)
+		err++;
+	}
+	if (!err) {
+	    /* If either no. begins with a zero, pad the output with   *
+	     * zeroes. Otherwise, choose a min width to suppress them. */
+	    int minw = (str[1] == '0') ? wid1 : (dots[2] == '0' ) ? wid2 :
+		(wid2 > wid1) ? wid1 : wid2;
+	    if (rstart > rend) {
+		/* Handle decreasing ranges correctly. */
+		int rt = rend;
+		rend = rstart;
+		rstart = rt;
+		rev = 1;
+	    }
+	    uremnode(list, node);
+	    for (; rend >= rstart; rend--) {
+		/* Node added in at end, so do highest first */
+		p = dupstring(str3);
+		sprintf(p + strp, "%0*d", minw, rend);
+		strcat(p + strp, str2 + 1);
+		insertlinknode(list, last, p);
+		if (rev)	/* decreasing:  add in reverse order. */
+		    last = nextnode(last);
+	    }
+	    *np = nextnode(olast);
+	    return;
+	}
+    }
+    if (!comma && isset(BRACECCL)) {	/* {a-mnop} */
+	/* Here we expand each character to a separate node,      *
+	 * but also ranges of characters like a-m.  ccl is a      *
+	 * set of flags saying whether each character is present; *
+	 * the final list is in lexical order.                    */
+	char ccl[256], *p;
+	unsigned char c1, c2;
+	unsigned int len, pl;
+	int lastch = -1;
+
+	uremnode(list, node);
+	memset(ccl, 0, sizeof(ccl) / sizeof(ccl[0]));
+	for (p = str + 1; p < str2;) {
+	    if (itok(c1 = *p++))
+		c1 = ztokens[c1 - STOUC(Pound)];
+	    if ((char) c1 == Meta)
+		c1 = 32 ^ *p++;
+	    if (itok(c2 = *p))
+		c2 = ztokens[c2 - STOUC(Pound)];
+	    if ((char) c2 == Meta)
+		c2 = 32 ^ p[1];
+	    if (c1 == '-' && lastch >= 0 && p < str2 && lastch <= (int)c2) {
+		while (lastch < (int)c2)
+		    ccl[lastch++] = 1;
+		lastch = -1;
+	    } else
+		ccl[lastch = c1] = 1;
+	}
+	pl = str - str3;
+	len = pl + strlen(++str2) + 2;
+	for (p = ccl + 255; p-- > ccl;)
+	    if (*p) {
+		c1 = p - ccl;
+		if (imeta(c1)) {
+		    str = hcalloc(len + 1);
+		    str[pl] = Meta;
+		    str[pl+1] = c1 ^ 32;
+		    strcpy(str + pl + 2, str2);
+		} else {
+		    str = hcalloc(len);
+		    str[pl] = c1;
+		    strcpy(str + pl + 1, str2);
+		}
+		memcpy(str, str3, pl);
+		insertlinknode(list, last, str);
+	    }
+	*np = nextnode(last);
+	return;
+    }
+    prev = str++ - str3;
+    str2++;
+    uremnode(list, node);
+    node = last;
+    /* Finally, normal comma expansion               *
+     * str1{foo,bar}str2 -> str1foostr2 str1barstr2. *
+     * Any number of intervening commas is allowed.  */
+    for (;;) {
+	char *zz, *str4;
+	int cnt;
+
+	for (str4 = str, cnt = 0; cnt || (*str != Comma && *str !=
+					  Outbrace); str++) {
+	    if (*str == Inbrace)
+		cnt++;
+	    else if (*str == Outbrace)
+		cnt--;
+	    DPUTS(!*str, "BUG: illegal brace expansion");
+	}
+	/* Concatenate the string before the braces (str3), the section *
+	 * just found (str4) and the text after the braces (str2)       */
+	zz = (char *) hcalloc(prev + (str - str4) + strlen(str2) + 1);
+	ztrncpy(zz, str3, prev);
+	strncat(zz, str4, str - str4);
+	strcat(zz, str2);
+	/* and add this text to the argument list. */
+	insertlinknode(list, node, zz);
+	incnode(node);
+	if (*str != Outbrace)
+	    str++;
+	else
+	    break;
+    }
+    *np = nextnode(last);
+}
+
+/* check to see if a matches b (b is not a filename pattern) */
+
+/**/
+int
+matchpat(char *a, char *b)
+{
+    Patprog p = patcompile(b, PAT_STATIC, NULL);
+
+    if (!p) {
+	zerr("bad pattern: %s", b, 0);
+	return 0;
+    }
+    return pattry(p, a);
+}
+
+/* do the ${foo%%bar}, ${foo#bar} stuff */
+/* please do not laugh at this code. */
+
+struct repldata {
+    int b, e;			/* beginning and end of chunk to replace */
+    char *replstr;		/* replacement string to use */
+};
+typedef struct repldata *Repldata;
+
+/*
+ * List of bits of matches to concatenate with replacement string.
+ * The data is a struct repldata.  It is not used in cases like
+ * ${...//#foo/bar} even though SUB_GLOBAL is set, since the match
+ * is anchored.  It goes on the heap.
+ */
+
+static LinkList repllist;
+
+/* Having found a match in getmatch, decide what part of string
+ * to return.  The matched part starts b characters into string s
+ * and finishes e characters in: 0 <= b <= e <= strlen(s)
+ * (yes, empty matches should work).
+ * fl is a set of the SUB_* matches defined in zsh.h from SUB_MATCH onwards;
+ * the lower parts are ignored.
+ * replstr is the replacement string for a substitution
+ */
+
+/**/
+static char *
+get_match_ret(char *s, int b, int e, int fl, char *replstr)
+{
+    char buf[80], *r, *p, *rr;
+    int ll = 0, l = strlen(s), bl = 0, t = 0, i;
+
+    if (replstr) {
+	if (fl & SUB_DOSUBST) {
+	    replstr = dupstring(replstr);
+	    singsub(&replstr);
+	    untokenize(replstr);
+	}
+	if ((fl & SUB_GLOBAL) && repllist) {
+	    /* We are replacing the chunk, just add this to the list */
+	    Repldata rd = (Repldata) zhalloc(sizeof(*rd));
+	    rd->b = b;
+	    rd->e = e;
+	    rd->replstr = replstr;
+	    addlinknode(repllist, rd);
+	    return s;
+	}
+	ll += strlen(replstr);
+    }
+    if (fl & SUB_MATCH)			/* matched portion */
+	ll += 1 + (e - b);
+    if (fl & SUB_REST)		/* unmatched portion */
+	ll += 1 + (l - (e - b));
+    if (fl & SUB_BIND) {
+	/* position of start of matched portion */
+	sprintf(buf, "%d ", b + 1);
+	ll += (bl = strlen(buf));
+    }
+    if (fl & SUB_EIND) {
+	/* position of end of matched portion */
+	sprintf(buf + bl, "%d ", e + 1);
+	ll += (bl = strlen(buf));
+    }
+    if (fl & SUB_LEN) {
+	/* length of matched portion */
+	sprintf(buf + bl, "%d ", e - b);
+	ll += (bl = strlen(buf));
+    }
+    if (bl)
+	buf[bl - 1] = '\0';
+
+    rr = r = (char *) hcalloc(ll);
+
+    if (fl & SUB_MATCH) {
+	/* copy matched portion to new buffer */
+	for (i = b, p = s + b; i < e; i++)
+	    *rr++ = *p++;
+	t = 1;
+    }
+    if (fl & SUB_REST) {
+	/* Copy unmatched portion to buffer.  If both portions *
+	 * requested, put a space in between (why?)            */
+	if (t)
+	    *rr++ = ' ';
+	/* there may be unmatched bits at both beginning and end of string */
+	for (i = 0, p = s; i < b; i++)
+	    *rr++ = *p++;
+	if (replstr)
+	    for (p = replstr; *p; )
+		*rr++ = *p++;
+	for (i = e, p = s + e; i < l; i++)
+	    *rr++ = *p++;
+	t = 1;
+    }
+    *rr = '\0';
+    if (bl) {
+	/* if there was a buffer (with a numeric result), add it; *
+	 * if there was other stuff too, stick in a space first.  */
+	if (t)
+	    *rr++ = ' ';
+	strcpy(rr, buf);
+    }
+    return r;
+}
+
+static Patprog
+compgetmatch(char *pat, int *flp, char **replstrp)
+{
+    Patprog p;
+    /*
+     * Flags to pattern compiler:  use static buffer since we only
+     * have one pattern at a time; we will try the must-match test ourselves,
+     * so tell the pattern compiler we are scanning.
+     */
+
+    /* int patflags = PAT_STATIC|PAT_SCAN|PAT_NOANCH;*/
+
+    /* Unfortunately, PAT_STATIC doesn't work if we have a replstr with
+     * something like ${x#...} in it which will be singsub()ed below because
+     * that would overwrite the pattern buffer. */
+
+    int patflags = PAT_SCAN|PAT_NOANCH | (*replstrp ? 0 : PAT_STATIC);
+
+    /*
+     * Search is anchored to the end of the string if we want to match
+     * it all, or if we are matching at the end of the string and not
+     * using substrings.
+     */
+    if ((*flp & SUB_ALL) || ((*flp & SUB_END) && !(*flp & SUB_SUBSTR)))
+	patflags &= ~PAT_NOANCH;
+    p = patcompile(pat, patflags, NULL);
+    if (!p) {
+	zerr("bad pattern: %s", pat, 0);
+	return NULL;
+    }
+    if (*replstrp) {
+	if (p->patnpar || (p->globend & GF_MATCHREF)) {
+	    /*
+	     * Either backreferences or match references, so we
+	     * need to re-substitute replstr each time round.
+	     */
+	    *flp |= SUB_DOSUBST;
+	} else {
+	    singsub(replstrp);
+	    untokenize(*replstrp);
+	}
+    }
+
+    return p;
+}
+
+/*
+ * This is called from paramsubst to get the match for ${foo#bar} etc.
+ * fl is a set of the SUB_* flags defined in zsh.h
+ * *sp points to the string we have to modify. The n'th match will be
+ * returned in *sp. The heap is used to get memory for the result string.
+ * replstr is the replacement string from a ${.../orig/repl}, in
+ * which case pat is the original.
+ *
+ * n is now ignored unless we are looking for a substring, in
+ * which case the n'th match from the start is counted such that
+ * there is no more than one match from each position.
+ */
+
+/**/
+int
+getmatch(char **sp, char *pat, int fl, int n, char *replstr)
+{
+    Patprog p;
+
+    if (!(p = compgetmatch(pat, &fl, &replstr)))
+	return 1;
+
+    return igetmatch(sp, p, fl, n, replstr);
+}
+
+/**/
+void
+getmatcharr(char ***ap, char *pat, int fl, int n, char *replstr)
+{
+    char **arr = *ap, **pp;
+    Patprog p;
+
+    if (!(p = compgetmatch(pat, &fl, &replstr)))
+	return;
+
+    *ap = pp = hcalloc(sizeof(char *) * (arrlen(arr) + 1));
+    while ((*pp = *arr++))
+	if (igetmatch(pp, p, fl, n, replstr))
+	    pp++;
+}
+
+/**/
+static void
+set_pat_start(Patprog p, int offs)
+{
+    /*
+     * If we are messing around with the test string by advancing up
+     * it from the start, we need to tell the pattern matcher that
+     * a start-of-string assertion, i.e. (#s), should fail.  Hence
+     * we test whether the offset of the real start of string from
+     * the actual start, passed as offs, is zero.
+     */
+    if (offs)
+	p->flags |= PAT_NOTSTART;
+    else
+	p->flags &= ~PAT_NOTSTART;
+}
+
+/**/
+static void
+set_pat_end(Patprog p, char null_me)
+{
+    /*
+     * If we are messing around with the string by shortening it at the
+     * tail, we need to tell the pattern matcher that an end-of-string
+     * assertion, i.e. (#e), should fail.  Hence we test whether
+     * the character null_me about to be zapped is or is not already a null.
+     */
+    if (null_me)
+	p->flags |= PAT_NOTEND;
+    else
+	p->flags &= ~PAT_NOTEND;
+}
+
+/**/
+static int
+igetmatch(char **sp, Patprog p, int fl, int n, char *replstr)
+{
+    char *s = *sp, *t;
+    /*
+     * Note that ioff and uml count characters in the character
+     * set (Meta's are not included), while l counts characters in the
+     * metafied string.  umlen is a counter for (unmetafied) character
+     * lengths.
+     */
+    int ioff, l = strlen(*sp), uml = ztrlen(*sp), matched = 1, umlen;
+
+    repllist = NULL;
+
+    /* perform must-match test for complex closures */
+    if (p->mustoff)
+    {
+	/*
+	 * Yuk.  Probably we should rewrite this whole function to
+	 * use an unmetafied test string.
+	 *
+	 * Use META_HEAPDUP because we need a terminating NULL.
+	 */
+	char *muststr = metafy((char *)p + p->mustoff,
+			       p->patmlen, META_HEAPDUP);
+
+	if (!strstr(s, muststr))
+	    matched = 0;
+    }
+
+    /* in case we used the prog before... */
+    p->flags &= ~(PAT_NOTSTART|PAT_NOTEND);
+
+    if (fl & SUB_ALL) {
+	int i = matched && pattry(p, s);
+	*sp = get_match_ret(*sp, 0, i ? l : 0, fl, i ? replstr : 0);
+	if (! **sp && (((fl & SUB_MATCH) && !i) || ((fl & SUB_REST) && i)))
+	    return 0;
+	return 1;
+    }
+    if (matched) {
+	switch (fl & (SUB_END|SUB_LONG|SUB_SUBSTR)) {
+	case 0:
+	case SUB_LONG:
+	    /*
+	     * Largest/smallest possible match at head of string.
+	     * First get the longest match...
+	     */
+	    if (pattry(p, s)) {
+		/* patmatchlen returns metafied length, as we need */
+	        int mlen = patmatchlen();
+		if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
+		    /*
+		     * ... now we know whether it's worth looking for the
+		     * shortest, which we do by brute force.
+		     */
+		    for (t = s, umlen = 0; t < s + mlen; METAINC(t), umlen++) {
+			set_pat_end(p, *t);
+			if (pattrylen(p, s, t - s, umlen, 0)) {
+			    mlen = patmatchlen();
+			    break;
+			}
+		    }
+		}
+		*sp = get_match_ret(*sp, 0, mlen, fl, replstr);
+		return 1;
+	    }
+	    break;
+
+	case SUB_END:
+	    /* Smallest possible match at tail of string:  *
+	     * move back down string until we get a match. *
+	     * There's no optimization here.               */
+	    for (ioff = uml, t = s + l, umlen = 0; t >= s;
+		 t--, ioff--, umlen++) {
+		if (t > s && t[-1] == Meta)
+		    t--;
+		set_pat_start(p, t-s);
+		if (pattrylen(p, t, s + l - t, umlen, ioff)) {
+		    *sp = get_match_ret(*sp, t - s, l, fl, replstr);
+		    return 1;
+		}
+		if (t > s+1 && t[-2] == Meta)
+		    t--;
+	    }
+	    break;
+
+	case (SUB_END|SUB_LONG):
+	    /* Largest possible match at tail of string:       *
+	     * move forward along string until we get a match. *
+	     * Again there's no optimisation.                  */
+	    for (ioff = 0, t = s, umlen = uml; t < s + l;
+		 ioff++, METAINC(t), umlen--) {
+		set_pat_start(p, t-s);
+		if (pattrylen(p, t, s + l - t, umlen, ioff)) {
+		    *sp = get_match_ret(*sp, t-s, l, fl, replstr);
+		    return 1;
+		}
+		if (*t == Meta)
+		    t++;
+	    }
+	    break;
+
+	case SUB_SUBSTR:
+	    /* Smallest at start, but matching substrings. */
+	    set_pat_start(p, l);
+	    if (!(fl & SUB_GLOBAL) && pattry(p, s + l) && !--n) {
+		*sp = get_match_ret(*sp, 0, 0, fl, replstr);
+		return 1;
+	    } /* fall through */
+	case (SUB_SUBSTR|SUB_LONG):
+	    /* longest or smallest at start with substrings */
+	    t = s;
+	    if (fl & SUB_GLOBAL)
+		repllist = newlinklist();
+	    ioff = 0;		/* offset into string */
+	    umlen = uml;
+	    do {
+		/* loop over all matches for global substitution */
+		matched = 0;
+		for (; t < s + l; METAINC(t), ioff++, umlen--) {
+		    /* Find the longest match from this position. */
+		    set_pat_start(p, t-s);
+		    if (pattrylen(p, t, s + l - t, umlen, ioff)) {
+			char *mpos = t + patmatchlen();
+			if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
+			    char *ptr;
+			    int umlen2;
+			    for (ptr = t, umlen2 = 0; ptr < mpos;
+				 METAINC(ptr), umlen2++) {
+				set_pat_end(p, *ptr);
+				if (pattrylen(p, t, ptr - t, umlen2, ioff)) {
+				    mpos = t + patmatchlen();
+				    break;
+				}
+			    }
+			}
+			if (!--n || (n <= 0 && (fl & SUB_GLOBAL))) {
+			    *sp = get_match_ret(*sp, t-s, mpos-s, fl, replstr);
+			    if (mpos == t)
+				METAINC(mpos);
+			}
+			if (!(fl & SUB_GLOBAL)) {
+			    if (n) {
+				/*
+				 * Looking for a later match: in this case,
+				 * we can continue looking for matches from
+				 * the next character, even if it overlaps
+				 * with what we just found.
+				 */
+				continue;
+			    } else {
+				return 1;
+			    }
+			}
+			/*
+			 * For a global match, we need to skip the stuff
+			 * which is already marked for replacement.
+			 */
+			matched = 1;
+			for ( ; t < mpos; t++, ioff++, umlen--)
+			    if (*t == Meta)
+				t++;
+			break;
+		    }
+		    if (*t == Meta)
+			t++;
+		}
+	    } while (matched);
+	    /*
+	     * check if we can match a blank string, if so do it
+	     * at the start.  Goodness knows if this is a good idea
+	     * with global substitution, so it doesn't happen.
+	     */
+	    set_pat_start(p, l);
+	    if ((fl & (SUB_LONG|SUB_GLOBAL)) == SUB_LONG &&
+		pattry(p, s + l) && !--n) {
+		*sp = get_match_ret(*sp, 0, 0, fl, replstr);
+		return 1;
+	    }
+	    break;
+
+	case (SUB_END|SUB_SUBSTR):
+	case (SUB_END|SUB_LONG|SUB_SUBSTR):
+	    /* Longest/shortest at end, matching substrings.       */
+	    if (!(fl & SUB_LONG)) {
+		set_pat_start(p, l);
+		if (pattrylen(p, s + l, 0, 0, uml) && !--n) {
+		    *sp = get_match_ret(*sp, l, l, fl, replstr);
+		    return 1;
+		}
+	    }
+	    for (ioff = uml - 1, t = s + l - 1, umlen = 1; t >= s;
+		 t--, ioff--, umlen++) {
+		if (t > s && t[-1] == Meta)
+		    t--;
+		set_pat_start(p, t-s);
+		if (pattrylen(p, t, s + l - t, umlen, ioff) && !--n) {
+		    /* Found the longest match */
+		    char *mpos = t + patmatchlen();
+		    if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
+			char *ptr;
+			int umlen2;
+			for (ptr = t, umlen2 = 0; ptr < mpos;
+			     METAINC(ptr), umlen2++) {
+			    set_pat_end(p, *ptr);
+			    if (pattrylen(p, t, ptr - t, umlen2, ioff)) {
+				mpos = t + patmatchlen();
+				break;
+			    }
+			}
+		    }
+		    *sp = get_match_ret(*sp, t-s, mpos-s, fl, replstr);
+		    return 1;
+		}
+	    }
+	    set_pat_start(p, l);
+	    if ((fl & SUB_LONG) && pattrylen(p, s + l, 0, 0, uml) && !--n) {
+		*sp = get_match_ret(*sp, l, l, fl, replstr);
+		return 1;
+	    }
+	    break;
+	}
+    }
+
+    if (repllist && nonempty(repllist)) {
+	/* Put all the bits of a global search and replace together. */
+	LinkNode nd;
+	Repldata rd;
+	int lleft = 0;		/* size of returned string */
+	char *ptr, *start;
+	int i;
+
+	i = 0;			/* start of last chunk we got from *sp */
+	for (nd = firstnode(repllist); nd; incnode(nd)) {
+	    rd = (Repldata) getdata(nd);
+	    lleft += rd->b - i; /* previous chunk of *sp */
+	    lleft += strlen(rd->replstr);	/* the replaced bit */
+	    i = rd->e;		/* start of next chunk of *sp */
+	}
+	lleft += l - i;	/* final chunk from *sp */
+	start = t = zhalloc(lleft+1);
+	i = 0;
+	for (nd = firstnode(repllist); nd; incnode(nd)) {
+	    rd = (Repldata) getdata(nd);
+	    memcpy(t, s + i, rd->b - i);
+	    t += rd->b - i;
+	    ptr = rd->replstr;
+	    while (*ptr)
+		*t++ = *ptr++;
+	    i = rd->e;
+	}
+	memcpy(t, s + i, l - i);
+	start[lleft] = '\0';
+	*sp = (char *)start;
+	return 1;
+    }
+
+    /* munge the whole string: no match, so no replstr */
+    *sp = get_match_ret(*sp, 0, 0, fl, 0);
+    return 1;
+}
+
+/* blindly turn a string into a tokenised expression without lexing */
+
+/**/
+mod_export void
+tokenize(char *s)
+{
+    zshtokenize(s, 0);
+}
+
+/**/
+mod_export void
+shtokenize(char *s)
+{
+    zshtokenize(s, isset(SHGLOB));
+}
+
+/**/
+static void
+zshtokenize(char *s, int shglob)
+{
+    char *t;
+    int bslash = 0;
+
+    for (; *s; s++) {
+      cont:
+	switch (*s) {
+	case Bnull:
+	case '\\':
+	    if (bslash) {
+		s[-1] = Bnull;
+		break;
+	    }
+	    bslash = 1;
+	    continue;
+	case '<':
+	    if (shglob)
+		break;
+	    if (bslash) {
+		s[-1] = Bnull;
+		break;
+	    }
+	    t = s;
+	    while (idigit(*++s));
+	    if (*s != '-')
+		goto cont;
+	    while (idigit(*++s));
+	    if (*s != '>')
+		goto cont;
+	    *t = Inang;
+	    *s = Outang;
+	    break;
+	case '(':
+	case '|':
+	case ')':
+	    if (shglob)
+		break;
+	case '>':
+	case '^':
+	case '#':
+	case '~':
+	case '[':
+	case ']':
+	case '*':
+	case '?':
+	case '=':
+	    for (t = ztokens; *t; t++)
+		if (*t == *s) {
+		    if (bslash)
+			s[-1] = Bnull;
+		    else
+			*s = (t - ztokens) + Pound;
+		    break;
+		}
+	}
+	bslash = 0;
+    }
+}
+
+/* remove unnecessary Nulargs */
+
+/**/
+mod_export void
+remnulargs(char *s)
+{
+    if (*s) {
+	char *o = s, c;
+
+	while ((c = *s++))
+	    if (INULL(c)) {
+		char *t = s - 1;
+
+		while ((c = *s++))
+		    if (!INULL(c))
+			*t++ = c;
+		*t = '\0';
+		if (!*o) {
+		    o[0] = Nularg;
+		    o[1] = '\0';
+		}
+		break;
+	    }
+    }
+}
+
+/* qualifier functions:  mostly self-explanatory, see glob(). */
+
+/* device number */
+
+/**/
+static int
+qualdev(UNUSED(char *name), struct stat *buf, off_t dv, UNUSED(char *dummy))
+{
+    return (off_t)buf->st_dev == dv;
+}
+
+/* number of hard links to file */
+
+/**/
+static int
+qualnlink(UNUSED(char *name), struct stat *buf, off_t ct, UNUSED(char *dummy))
+{
+    return (g_range < 0 ? buf->st_nlink < ct :
+	    g_range > 0 ? buf->st_nlink > ct :
+	    buf->st_nlink == ct);
+}
+
+/* user ID */
+
+/**/
+static int
+qualuid(UNUSED(char *name), struct stat *buf, off_t uid, UNUSED(char *dummy))
+{
+    return buf->st_uid == uid;
+}
+
+/* group ID */
+
+/**/
+static int
+qualgid(UNUSED(char *name), struct stat *buf, off_t gid, UNUSED(char *dummy))
+{
+    return buf->st_gid == gid;
+}
+
+/* device special file? */
+
+/**/
+static int
+qualisdev(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+    return S_ISBLK(buf->st_mode) || S_ISCHR(buf->st_mode);
+}
+
+/* block special file? */
+
+/**/
+static int
+qualisblk(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+    return S_ISBLK(buf->st_mode);
+}
+
+/* character special file? */
+
+/**/
+static int
+qualischr(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+    return S_ISCHR(buf->st_mode);
+}
+
+/* directory? */
+
+/**/
+static int
+qualisdir(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+    return S_ISDIR(buf->st_mode);
+}
+
+/* FIFO? */
+
+/**/
+static int
+qualisfifo(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+    return S_ISFIFO(buf->st_mode);
+}
+
+/* symbolic link? */
+
+/**/
+static int
+qualislnk(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+    return S_ISLNK(buf->st_mode);
+}
+
+/* regular file? */
+
+/**/
+static int
+qualisreg(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+    return S_ISREG(buf->st_mode);
+}
+
+/* socket? */
+
+/**/
+static int
+qualissock(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+    return S_ISSOCK(buf->st_mode);
+}
+
+/* given flag is set in mode */
+
+/**/
+static int
+qualflags(UNUSED(char *name), struct stat *buf, off_t mod, UNUSED(char *dummy))
+{
+    return mode_to_octal(buf->st_mode) & mod;
+}
+
+/* mode matches specification */
+
+/**/
+static int
+qualmodeflags(UNUSED(char *name), struct stat *buf, off_t mod, UNUSED(char *dummy))
+{
+    long v = mode_to_octal(buf->st_mode), y = mod & 07777, n = mod >> 12;
+
+    return ((v & y) == y && !(v & n));
+}
+
+/* regular executable file? */
+
+/**/
+static int
+qualiscom(UNUSED(char *name), struct stat *buf, UNUSED(off_t mod), UNUSED(char *dummy))
+{
+    return S_ISREG(buf->st_mode) && (buf->st_mode & S_IXUGO);
+}
+
+/* size in required range? */
+
+/**/
+static int
+qualsize(UNUSED(char *name), struct stat *buf, off_t size, UNUSED(char *dummy))
+{
+#if defined(LONG_IS_64_BIT) || defined(OFF_T_IS_64_BIT)
+# define QS_CAST_SIZE()
+    off_t scaled = buf->st_size;
+#else
+# define QS_CAST_SIZE() (unsigned long)
+    unsigned long scaled = (unsigned long)buf->st_size;
+#endif
+
+    switch (g_units) {
+    case TT_POSIX_BLOCKS:
+	scaled += 511l;
+	scaled /= 512l;
+	break;
+    case TT_KILOBYTES:
+	scaled += 1023l;
+	scaled /= 1024l;
+	break;
+    case TT_MEGABYTES:
+	scaled += 1048575l;
+	scaled /= 1048576l;
+	break;
+    }
+
+    return (g_range < 0 ? scaled < QS_CAST_SIZE() size :
+	    g_range > 0 ? scaled > QS_CAST_SIZE() size :
+	    scaled == QS_CAST_SIZE() size);
+#undef QS_CAST_SIZE
+}
+
+/* time in required range? */
+
+/**/
+static int
+qualtime(UNUSED(char *name), struct stat *buf, off_t days, UNUSED(char *dummy))
+{
+    time_t now, diff;
+
+    time(&now);
+    diff = now - (g_amc == 0 ? buf->st_atime : g_amc == 1 ? buf->st_mtime :
+		  buf->st_ctime);
+    /* handle multipliers indicating units */
+    switch (g_units) {
+    case TT_DAYS:
+	diff /= 86400l;
+	break;
+    case TT_HOURS:
+	diff /= 3600l;
+	break;
+    case TT_MINS:
+	diff /= 60l;
+	break;
+    case TT_WEEKS:
+	diff /= 604800l;
+	break;
+    case TT_MONTHS:
+	diff /= 2592000l;
+	break;
+    }
+
+    return (g_range < 0 ? diff < days :
+	    g_range > 0 ? diff > days :
+	    diff == days);
+}
+
+/* evaluate a string */
+
+/**/
+static int
+qualsheval(char *name, UNUSED(struct stat *buf), UNUSED(off_t days), char *str)
+{
+    Eprog prog;
+
+    if ((prog = parse_string(str))) {
+	int ef = errflag, lv = lastval, ret;
+
+	unsetparam("reply");
+	setsparam("REPLY", ztrdup(name));
+
+	execode(prog, 1, 0);
+
+	ret = lastval;
+	errflag = ef;
+	lastval = lv;
+
+	if (!(inserts = getaparam("reply")) &&
+	    !(inserts = gethparam("reply"))) {
+	    char *tmp;
+
+	    if ((tmp = getsparam("reply")) || (tmp = getsparam("REPLY"))) {
+		static char *tmparr[2];
+
+		tmparr[0] = tmp;
+		tmparr[1] = NULL;
+
+		inserts = tmparr;
+	    }
+	}
+	return !ret;
+    }
+    return 0;
+}
+
+/**/
+static int
+qualnonemptydir(char *name, struct stat *buf, UNUSED(off_t days), UNUSED(char *str))
+{
+    DIR *dirh;
+    struct dirent *de;
+
+    if (!S_ISDIR(buf->st_mode))
+	return 0;
+
+    if (buf->st_nlink > 2)
+	return 1;
+
+    if (!(dirh = opendir(name)))
+	return 0;
+
+    while ((de = readdir(dirh))) {
+	if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
+	    closedir(dirh);
+	    return 1;
+	}
+    }
+
+    closedir(dirh);
+    return 0;
+}