summaryrefslogtreecommitdiff
path: root/Src/glob.c
diff options
context:
space:
mode:
Diffstat (limited to 'Src/glob.c')
-rw-r--r--Src/glob.c2800
1 files changed, 2800 insertions, 0 deletions
diff --git a/Src/glob.c b/Src/glob.c
new file mode 100644
index 000000000..be7a04515
--- /dev/null
+++ b/Src/glob.c
@@ -0,0 +1,2800 @@
+/*
+ * glob.c - filename generation
+ *
+ * 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"
+
+/* flag for CSHNULLGLOB */
+
+/**/
+int badcshglob;
+
+static int mode; /* != 0 if we are parsing glob patterns */
+static int pathpos; /* position in pathbuf */
+static int matchsz; /* size of matchbuf */
+static int matchct; /* number of matches found */
+static char *pathbuf; /* pathname buffer */
+static int pathbufsz; /* size of pathbuf */
+static int pathbufcwd; /* where did we chdir()'ed */
+static char **matchbuf; /* array of matches */
+static char **matchptr; /* &matchbuf[matchct] */
+static char *colonmod; /* colon modifiers in qualifier list */
+
+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_BYTES 0
+#define TT_POSIX_BLOCKS 1
+#define TT_KILOBYTES 2
+#define TT_MEGABYTES 3
+
+typedef int (*TestMatchFunc) _((struct stat *, long));
+
+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 */
+ long data; /* 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 */
+};
+
+/* Qualifiers pertaining to current pattern */
+static struct qual *quals;
+
+/* Other state values for current pattern */
+static int qualct, qualorct;
+static int range, amc, units;
+static int gf_nullglob, gf_markdirs, gf_noglobdots, gf_listtypes, gf_follow;
+
+/* Prefix, suffix for doing zle trickery */
+
+/**/
+char *glob_pre, *glob_suf;
+
+/* pathname component in filename patterns */
+
+struct complist {
+ Complist next;
+ Comp comp;
+ int closure; /* 1 if this is a (foo/)# */
+ int follow; /* 1 to go thru symlinks */
+};
+struct comp {
+ Comp left, right, next, exclude;
+ char *str;
+ int stat;
+};
+
+/* Type of Comp: a closure with one or two #'s, the end of a *
+ * pattern or path component, a piece of path to be added. */
+#define C_ONEHASH 1
+#define C_TWOHASH 2
+#define C_OPTIONAL 4
+#define C_STAR 8
+#define C_CLOSURE (C_ONEHASH|C_TWOHASH|C_OPTIONAL|C_STAR)
+#define C_LAST 16
+#define C_PATHADD 32
+
+/* Test macros for the above */
+#define CLOSUREP(c) (c->stat & C_CLOSURE)
+#define ONEHASHP(c) (c->stat & (C_ONEHASH|C_STAR))
+#define TWOHASHP(c) (c->stat & C_TWOHASH)
+#define OPTIONALP(c) (c->stat & C_OPTIONAL)
+#define STARP(c) (c->stat & C_STAR)
+#define LASTP(c) (c->stat & C_LAST)
+#define PATHADDP(c) (c->stat & C_PATHADD)
+
+/* Flags passed down to guts when compiling */
+#define GF_PATHADD 1 /* file glob, adding path components */
+#define GF_TOPLEV 2 /* outside (), so ~ ends main match */
+
+static char *pptr; /* current place in string being matched */
+static Comp tail;
+static int first; /* are leading dots special? */
+
+/* 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)
+{
+ DPUTS(!pathbuf, "BUG: pathbuf not initialised");
+ while (pathpos + (int) strlen(s) + 1 >= pathbufsz)
+ pathbuf = realloc(pathbuf, pathbufsz *= 2);
+ while ((pathbuf[pathpos++] = *s++));
+ pathbuf[pathpos - 1] = '/';
+ 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 chechked 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[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);
+}
+
+/* add a match to the list */
+
+/**/
+static void
+insert(char *s, int checked)
+{
+ struct stat buf, buf2, *bp;
+ char *news = s;
+ int statted = 0;
+
+ 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))
+ 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 *)ncalloc(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))
+ return;
+ qo = quals;
+ for (qn = qo; qn && qn->func;) {
+ range = qn->range;
+ amc = qn->amc;
+ 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) (bp, qn->data)) ^ qn->sense) & 1) {
+ /* Try next alternative, or return if there are no more */
+ if (!(qo = qo->or))
+ return;
+ qn = qo;
+ continue;
+ }
+ qn = qn->next;
+ }
+ } else if (!checked && statfullpath(s, NULL, 1))
+ return;
+
+ news = dyncat(pathbuf, news);
+ if (colonmod) {
+ /* Handle the remainder of the qualifer: e.g. (:r:s/foo/bar/). */
+ s = colonmod;
+ modify(&news, &s);
+ }
+ *matchptr++ = news;
+ if (++matchct == matchsz) {
+ matchbuf = (char **)realloc((char *)matchbuf,
+ sizeof(char **) * (matchsz *= 2));
+
+ matchptr = matchbuf + matchct;
+ }
+}
+
+/* Check to see if str is eligible for filename generation. */
+
+/**/
+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)
+{
+ Comp c;
+ int closure;
+ int pbcwdsav = pathbufcwd;
+ 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);
+ c = q->comp;
+ /* Now the actual matching for the current path section. */
+ if (!(c->next || c->left) && !haswilds(c->str)) {
+ /* It's a straight string to the end of the path section. */
+ int l = strlen(c->str);
+
+ 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 && !(q->closure && !strcmp(c->str, "."))) {
+ addpath(c->str);
+ if (!closure || statfullpath("", NULL, 1))
+ scanner((q->closure) ? q : q->next);
+ pathbuf[pathpos = oppos] = '\0';
+ }
+ } else
+ insert(c->str, 0);
+ } else {
+ /* Do pattern matching on current path section. */
+ char *fn;
+ int dirs = !!q->next;
+ DIR *lock;
+ char *subdirs = NULL;
+ int subdirlen = 0;
+
+ fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : ".";
+ if (dirs) {
+ struct stat st;
+ stat(fn, &st);
+ /* a directory with subdirectories has link count greater than 2 */
+ if (!S_ISDIR(st.st_mode) || st.st_nlink == 2)
+ return;
+ }
+ lock = opendir(fn);
+ 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;
+ if (domatch(fn, c, gf_noglobdots)) {
+ /* 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 (closure) {
+ /* if matching multiple directories */
+ struct stat buf;
+
+ if (statfullpath(fn, &buf, !q->follow)) {
+ if (errno != ENOENT && errno != EINTR &&
+ errno != ENOTDIR && !errflag) {
+ zerr("%e: %s", fn, errno);
+ errflag = 0;
+ }
+ continue;
+ }
+ if (!S_ISDIR(buf.st_mode))
+ continue;
+ }
+ l = strlen(fn) + 1;
+ subdirs = hrealloc(subdirs, subdirlen, subdirlen + l);
+ strcpy(subdirs + subdirlen, fn);
+ subdirlen += l;
+ } 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; fn += strlen(fn) + 1) {
+ addpath(fn);
+ 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;
+ }
+}
+
+/* Parse a series of path components pointed to by pptr */
+
+/* enum used with ksh-like patterns, @(...) etc. */
+
+enum { KF_NONE, KF_AT, KF_QUEST, KF_STAR, KF_PLUS, KF_NOT };
+
+/* parse lowest level pattern */
+
+/**/
+static Comp
+parsecomp(int gflag)
+{
+ int kshfunc;
+ Comp c = (Comp) alloc(sizeof *c), c1, c2;
+ char *cstr, *ls = NULL;
+
+ /* In case of alternatives, code coming up is stored in tail. */
+ c->next = tail;
+ cstr = pptr;
+
+ while (*pptr && (mode || *pptr != '/') && *pptr != Bar &&
+ (unset(EXTENDEDGLOB) || *pptr != Tilde ||
+ !pptr[1] || pptr[1] == Outpar || pptr[1] == Bar) &&
+ *pptr != Outpar) {
+ /* Go through code until we find something separating alternatives,
+ * or path components if relevant.
+ */
+ if (*pptr == Hat && isset(EXTENDEDGLOB)) {
+ /* negate remaining pattern */
+ Comp stail = tail;
+ tail = NULL;
+ c->str = dupstrpfx(cstr, pptr - cstr);
+ pptr++;
+
+ c1 = (Comp) alloc(sizeof *c1);
+ c1->stat |= C_STAR;
+
+ c2 = (Comp) alloc(sizeof *c2);
+ if (!(c2->exclude = parsecomp(gflag)))
+ return NULL;
+ if (!*pptr || *pptr == '/')
+ c2->stat |= C_LAST;
+ c2->left = c1;
+ c2->next = stail;
+ c->next = c2;
+ tail = stail;
+ return c;
+ }
+
+ /* Ksh-type globs */
+ kshfunc = KF_NONE;
+ if (isset(KSHGLOB) && *pptr && pptr[1] == Inpar) {
+ switch (*pptr) {
+ case '@': /* just do paren as usual */
+ kshfunc = KF_AT;
+ break;
+
+ case Quest:
+ case '?': /* matched optionally, treat as (...|) */
+ kshfunc = KF_QUEST;
+ break;
+
+ case Star:
+ case '*': /* treat as (...)# */
+ kshfunc = KF_STAR;
+ break;
+
+ case '+': /* treat as (...)## */
+ kshfunc = KF_PLUS;
+ break;
+
+ case '!': /* treat as (*~...) */
+ kshfunc = KF_NOT;
+ break;
+ }
+ if (kshfunc != KF_NONE)
+ pptr++;
+ }
+
+ if (*pptr == Inpar) {
+ /* Found a group (...) */
+ char *startp = pptr, *endp;
+ Comp stail = tail;
+ int dpnd = 0;
+
+ /* Need matching close parenthesis */
+ if (skipparens(Inpar, Outpar, &pptr)) {
+ errflag = 1;
+ return NULL;
+ }
+ if (kshfunc == KF_STAR)
+ dpnd = 1;
+ else if (kshfunc == KF_PLUS)
+ dpnd = 2;
+ else if (kshfunc == KF_QUEST)
+ dpnd = 3;
+ if (*pptr == Pound && isset(EXTENDEDGLOB)) {
+ /* Zero (or one) or more repetitions of group */
+ pptr++;
+ if(*pptr == Pound) {
+ pptr++;
+ if(dpnd == 0)
+ dpnd = 2;
+ else if(dpnd == 3)
+ dpnd = 1;
+ } else
+ dpnd = 1;
+ }
+ /* Parse the remaining pattern following the group... */
+ if (!(c1 = parsecomp(gflag)))
+ return NULL;
+ /* ...remembering what comes after it... */
+ tail = (dpnd || kshfunc == KF_NOT) ? NULL : c1;
+ /* ...before going back and parsing inside the group. */
+ endp = pptr;
+ pptr = startp;
+ c->str = dupstrpfx(cstr, (pptr - cstr) - (kshfunc != KF_NONE));
+ pptr++;
+ c2 = (Comp) alloc(sizeof *c);
+ c->next = c2;
+ c2->next = (dpnd || kshfunc == KF_NOT) ?
+ c1 : (Comp) alloc(sizeof *c);
+ if (!(c2->left = parsecompsw(0)))
+ return NULL;
+ if (kshfunc == KF_NOT) {
+ /* we'd actually rather it didn't match. Instead, match *
+ * a star and put the parsed pattern into exclude. */
+ Comp c3 = (Comp) alloc(sizeof *c3);
+ c3->stat |= C_STAR;
+
+ c2->exclude = c2->left;
+ c2->left = c3;
+ }
+ /* Remember closures for group. */
+ if (dpnd)
+ c2->stat |= (dpnd == 3) ? C_OPTIONAL
+ : (dpnd == 2) ? C_TWOHASH : C_ONEHASH;
+ pptr = endp;
+ tail = stail;
+ return c;
+ }
+ if (*pptr == Star && pptr[1] &&
+ (unset(EXTENDEDGLOB) || !(gflag & GF_TOPLEV) ||
+ pptr[1] != Tilde || !pptr[2] || pptr[2] == Bar ||
+ pptr[2] == Outpar) && (mode || pptr[1] != '/')) {
+ /* Star followed by other patterns is now treated as a special
+ * type of closure in doesmatch().
+ */
+ c->str = dupstrpfx(cstr, pptr - cstr);
+ pptr++;
+ c1 = (Comp) alloc(sizeof *c1);
+ c1->stat |= C_STAR;
+ if (!(c2 = parsecomp(gflag)))
+ return NULL;
+ c1->next = c2;
+ c->next = c1;
+ return c;
+ }
+ if (*pptr == Pound && isset(EXTENDEDGLOB)) {
+ /* repeat whatever we've just had (ls) zero or more times */
+ if (!ls)
+ return NULL;
+ c2 = (Comp) alloc(sizeof *c);
+ c2->str = dupstrpfx(ls, pptr - ls);
+ pptr++;
+ if (*pptr == Pound) {
+ /* need one or more matches: cheat by copying previous char */
+ pptr++;
+ c->next = c1 = (Comp) alloc(sizeof *c);
+ c1->str = c2->str;
+ } else
+ c1 = c;
+ c1->next = c2;
+ c2->stat |= C_ONEHASH;
+ /* parse the rest of the pattern and return. */
+ c2->next = parsecomp(gflag);
+ if (!c2->next)
+ return NULL;
+ c->str = dupstrpfx(cstr, ls - cstr);
+ return c;
+ }
+ ls = pptr; /* whatever we just parsed */
+ if (*pptr == Inang) {
+ /* Numeric glob */
+ int dshct;
+
+ dshct = (pptr[1] == Outang);
+ while (*++pptr && *pptr != Outang)
+ if (*pptr == '-' && !dshct)
+ dshct = 1;
+ else if (!idigit(*pptr))
+ break;
+ if (*pptr != Outang)
+ return NULL;
+ } else if (*pptr == Inbrack) {
+ /* Character set: brackets had better match */
+ if (pptr[1] == Outbrack)
+ *++pptr = ']';
+ else if ((pptr[1] == Hat || pptr[1] == '^' || pptr[1] == '!') &&
+ pptr[2] == Outbrack)
+ *(pptr += 2) = ']';
+ while (*++pptr && *pptr != Outbrack) {
+ if (itok(*pptr)) {
+ /* POSIX classes: make sure it's a real one, *
+ * leave the Inbrack tokenised if so. */
+ char *nptr;
+ if (*pptr == Inbrack && pptr[1] == ':'
+ && (nptr = strchr(pptr+2, ':')) &&
+ *++nptr == Outbrack)
+ pptr = nptr;
+ *pptr = ztokens[*pptr - Pound];
+ }
+ }
+ if (*pptr != Outbrack)
+ return NULL;
+ } else if (itok(*pptr) && *pptr != Star && *pptr != Quest)
+ /* something that can be tokenised which isn't otherwise special */
+ *pptr = ztokens[*pptr - Pound];
+ pptr++;
+ }
+ /* mark if last pattern component in path component or pattern */
+ if (*pptr == '/' || !*pptr ||
+ (isset(EXTENDEDGLOB) && *pptr == Tilde && (gflag & GF_TOPLEV)))
+ c->stat |= C_LAST;
+ c->str = dupstrpfx(cstr, pptr - cstr);
+ return c;
+}
+
+/* Parse pattern possibly with different alternatives (|) */
+
+/**/
+static Comp
+parsecompsw(int gflag)
+{
+ Comp c1, c2, c3, excl = NULL, stail = tail;
+ char *sptr;
+
+ /*
+ * Check for a tilde in the expression. We need to know this in
+ * advance so as to be able to treat the whole a~b expression by
+ * backtracking: see exclusion code in doesmatch().
+ */
+ if (isset(EXTENDEDGLOB)) {
+ int pct = 0;
+ for (sptr = pptr; *sptr; sptr++) {
+ if (*sptr == Inpar)
+ pct++;
+ else if (*sptr == Outpar && --pct < 0)
+ break;
+ else if (*sptr == Bar && !pct)
+ break;
+ else if (*sptr == Tilde && !pct) {
+ tail = NULL;
+ break;
+ }
+ }
+ }
+
+ c1 = parsecomp(gflag);
+ if (!c1)
+ return NULL;
+ if (isset(EXTENDEDGLOB) && *pptr == Tilde) {
+ /* Matching remainder of pattern excludes the pattern from matching */
+ int oldmode = mode;
+
+ mode = 1;
+ pptr++;
+ excl = parsecomp(gflag);
+ mode = oldmode;
+ if (!excl)
+ return NULL;
+ }
+ tail = stail;
+ if (*pptr == Bar || excl) {
+ /* found an alternative or something to exclude */
+ c2 = (Comp) alloc(sizeof *c2);
+ if (*pptr == Bar) {
+ /* get the next alternative after the | */
+ pptr++;
+ c3 = parsecompsw(gflag);
+ if (!c3)
+ return NULL;
+ } else
+ c3 = NULL;
+ /* mark if end of pattern or path component */
+ if (!*pptr || *pptr == '/')
+ c1->stat |= c2->stat = C_LAST;
+ c2->str = dupstring("");
+ c2->left = c1;
+ c2->right = c3;
+ if ((c2->exclude = excl))
+ c2->next = stail;
+ if (gflag & GF_PATHADD)
+ c2->stat |= C_PATHADD;
+ return c2;
+ }
+ return c1;
+}
+
+/* This function tokenizes a zsh glob pattern */
+
+/**/
+static Complist
+parsecomplist(void)
+{
+ Comp c1;
+ Complist p1;
+ char *str;
+
+ if (pptr[0] == Star && pptr[1] == Star &&
+ (pptr[2] == '/' || (pptr[2] == Star && pptr[3] == '/'))) {
+ /* Match any number of directories. */
+ int follow;
+
+ /* with three stars, follow symbolic links */
+ follow = (pptr[2] == Star);
+ pptr += (3 + follow);
+
+ /* Now get the next path component if there is one. */
+ p1 = (Complist) alloc(sizeof *p1);
+ if ((p1->next = parsecomplist()) == NULL) {
+ errflag = 1;
+ return NULL;
+ }
+ p1->comp = (Comp) alloc(sizeof *p1->comp);
+ p1->comp->stat |= C_LAST; /* end of path component */
+ p1->comp->str = dupstring("*");
+ *p1->comp->str = Star; /* match anything... */
+ p1->closure = 1; /* ...zero or more times. */
+ p1->follow = follow;
+ return p1;
+ }
+
+ /* Parse repeated directories such as (dir/)# and (dir/)## */
+ if (*(str = pptr) == Inpar && !skipparens(Inpar, Outpar, &str) &&
+ *str == Pound && isset(EXTENDEDGLOB) && str[-2] == '/') {
+ pptr++;
+ if (!(c1 = parsecompsw(0)))
+ return NULL;
+ if (pptr[0] == '/' && pptr[1] == Outpar && pptr[2] == Pound) {
+ int pdflag = 0;
+
+ pptr += 3;
+ if (*pptr == Pound) {
+ pdflag = 1;
+ pptr++;
+ }
+ p1 = (Complist) alloc(sizeof *p1);
+ p1->comp = c1;
+ p1->closure = 1 + pdflag;
+ p1->follow = 0;
+ p1->next = parsecomplist();
+ return (p1->comp) ? p1 : NULL;
+ }
+ } else {
+ /* parse single path component */
+ if (!(c1 = parsecompsw(GF_PATHADD|GF_TOPLEV)))
+ return NULL;
+ /* then do the remaining path compoents */
+ if (*pptr == '/' || !*pptr) {
+ int ef = *pptr == '/';
+
+ p1 = (Complist) alloc(sizeof *p1);
+ p1->comp = c1;
+ p1->closure = 0;
+ p1->next = ef ? (pptr++, parsecomplist()) : NULL;
+ return (ef && !p1->next) ? NULL : p1;
+ }
+ }
+ errflag = 1;
+ return NULL;
+}
+
+/* turn a string into a Complist struct: this has path components */
+
+/**/
+static Complist
+parsepat(char *str)
+{
+ mode = 0; /* path components present */
+ pptr = str;
+ tail = NULL;
+ return parsecomplist();
+}
+
+/* get number after qualifier */
+
+/**/
+static long
+qgetnum(char **s)
+{
+ long v = 0;
+
+ if (!idigit(**s)) {
+ zerr("number expected", NULL, 0);
+ return 0;
+ }
+ while (idigit(**s))
+ v = v * 10 + *(*s)++ - '0';
+ return v;
+}
+
+/* get octal number after qualifier */
+
+/**/
+static long
+qgetoctnum(char **s)
+{
+ long v = 0;
+
+ if (!idigit(**s)) {
+ zerr("octal number expected", NULL, 0);
+ return 0;
+ }
+ while (**s >= '0' && **s <= '7')
+ v = v * 010 + *(*s)++ - '0';
+ return v;
+}
+
+/* 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
+glob(LinkList list, LinkNode np)
+{
+ 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 */
+
+ MUSTUSEHEAP("glob");
+ if (unset(GLOBOPT) || !haswilds(ostr)) {
+ untokenize(ostr);
+ return;
+ }
+ str = dupstring(ostr);
+ sl = strlen(str);
+ uremnode(list, np);
+
+ /* Initialise state variables for current file pattern */
+ qo = qn = quals = ql = NULL;
+ qualct = qualorct = 0;
+ colonmod = NULL;
+ gf_nullglob = isset(NULLGLOB);
+ gf_markdirs = isset(MARKDIRS);
+ gf_listtypes = gf_follow = 0;
+ gf_noglobdots = unset(GLOBDOTS);
+
+ /* Check for qualifiers */
+ if (isset(BAREGLOBQUAL) && str[sl - 1] == Outpar) {
+ char *s;
+
+ /* Check these are really qualifiers, not a set of *
+ * alternatives or exclusions */
+ for (s = str + sl - 2; *s != Inpar; s--)
+ if (*s == Bar || *s == Outpar ||
+ (isset(EXTENDEDGLOB) && *s == Tilde))
+ break;
+ if (*s == Inpar) {
+ /* Real qualifiers found. */
+ int sense = 0; /* bit 0 for match (0)/don't match (1) */
+ /* bit 1 for follow links (2), don't (0) */
+ long data = 0; /* Any numerical argument required */
+ int (*func) _((Statptr, long));
+
+ str[sl-1] = 0;
+ *s++ = 0;
+ while (*s && !colonmod) {
+ func = (int (*) _((Statptr, long)))0;
+ if (idigit(*s)) {
+ /* Store numeric argument for qualifier */
+ func = qualflags;
+ data = 0;
+ 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. */
+ colonmod = s - 1;
+ untokenize(colonmod);
+ 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;
+ 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 'o':
+ /* Match octal mode of file exactly. *
+ * Currently undocumented. */
+ func = qualeqflags;
+ data = qgetoctnum(&s);
+ 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 'a':
+ /* Access time in given range */
+ amc = 0;
+ func = qualtime;
+ goto getrange;
+ case 'm':
+ /* Modification time in given range */
+ amc = 1;
+ func = qualtime;
+ goto getrange;
+ case 'c':
+ /* Inode creation time in given range */
+ amc = 2;
+ func = qualtime;
+ goto getrange;
+ case 'L':
+ /* File size (Length) in given range */
+ func = qualsize;
+ amc = -1;
+ /* Get size multiplier */
+ units = TT_BYTES;
+ if (*s == 'p' || *s == 'P')
+ units = TT_POSIX_BLOCKS, ++s;
+ else if (*s == 'k' || *s == 'K')
+ units = TT_KILOBYTES, ++s;
+ else if (*s == 'm' || *s == 'M')
+ units = TT_MEGABYTES, ++s;
+ getrange:
+ /* Get time multiplier */
+ if (amc >= 0) {
+ units = TT_DAYS;
+ if (*s == 'h')
+ units = TT_HOURS, ++s;
+ else if (*s == 'm')
+ units = TT_MINS, ++s;
+ else if (*s == 'w')
+ units = TT_WEEKS, ++s;
+ else if (*s == 'M')
+ units = TT_MONTHS, ++s;
+ }
+ /* See if it's greater than, equal to, or less than */
+ if ((range = *s == '+' ? 1 : *s == '-' ? -1 : 0))
+ ++s;
+ data = qgetnum(&s);
+ break;
+
+ default:
+ zerr("unknown file attribute", NULL, 0);
+ 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 (!quals)
+ quals = qo = qn;
+ qn->func = func;
+ qn->sense = sense;
+ qn->data = data;
+ qn->range = range;
+ qn->units = units;
+ qn->amc = amc;
+ qn = NULL;
+ qualct++;
+ }
+ if (errflag)
+ return;
+ }
+ }
+ }
+ 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';
+ q = parsepat(str);
+ if (!q || errflag) { /* if parsing failed */
+ if (unset(BADPATTERN)) {
+ untokenize(ostr);
+ insertlinknode(list, node, ostr);
+ return;
+ }
+ errflag = 0;
+ zerr("bad pattern: %s", ostr, 0);
+ return;
+ }
+
+ /* Initialise receptacle for matched files, *
+ * expanded by insert() where necessary. */
+ matchptr = matchbuf = (char **)zalloc((matchsz = 16) * sizeof(char *));
+ matchct = 0;
+
+ /* 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);
+ return;
+ } else {
+ /* treat as an ordinary string */
+ untokenize(*matchptr++ = dupstring(ostr));
+ 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(char *),
+ (int (*) _((const void *, const void *)))notstrcmp);
+
+ matchptr = matchbuf;
+ while (matchct--) /* insert matches in the arg list */
+ insertlinknode(list, node, *matchptr++);
+ free(matchbuf);
+}
+
+/* Return the order of two strings, taking into account *
+ * possible numeric order if NUMERICGLOBSORT is set. *
+ * The comparison here is reversed. */
+
+/**/
+static int
+notstrcmp(char **a, char **b)
+{
+ char *c = *b, *d = *a;
+ int cmp;
+
+#ifdef HAVE_STRCOLL
+ cmp = strcoll(c, d);
+#endif
+ for (; *c == *d && *c; c++, d++);
+#ifndef HAVE_STRCOLL
+ cmp = (int)STOUC(*c) - (int)STOUC(*d);
+#endif
+ if (isset(NUMERICGLOBSORT) && (idigit(*c) || idigit(*d))) {
+ for (; c > *b && idigit(c[-1]); c--, d--);
+ if (idigit(*c) && idigit(*d)) {
+ while (*c == '0')
+ c++;
+ while (*d == '0')
+ d++;
+ for (; idigit(*c) && *c == *d; c++, d++);
+ if (idigit(*c) || idigit(*d)) {
+ cmp = (int)STOUC(*c) - (int)STOUC(*d);
+ while (idigit(*c) && idigit(*d))
+ c++, d++;
+ if (idigit(*c) && !idigit(*d))
+ return 1;
+ if (idigit(*d) && !idigit(*c))
+ return -1;
+ }
+ }
+ }
+ return cmp;
+}
+
+/* Return the trailing character for marking file types */
+
+/**/
+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 */
+
+/**/
+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;
+
+ for (bc = 0; *str; ++str)
+ if (*str == Inbrace) {
+ if (!bc && str[1] == Outbrace)
+ *str++ = '{', *str = '}';
+ else
+ bc++;
+ } else if (*str == 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)
+{
+ LinkList fake;
+ char *nam;
+ struct redir *ff;
+ int ret = 0;
+
+ /* Stick the name in a list... */
+ fake = newlinklist();
+ addlinknode(fake, fn->name);
+ /* ...which undergoes all the usual shell expansions */
+ prefork(fake, isset(MULTIOS) ? 0 : 4);
+ /* Globbing is only done for multios. */
+ if (!errflag && isset(MULTIOS))
+ globlist(fake);
+ 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 == MERGEIN || fn->type == MERGEOUT) {
+ if (s[0] == '-' && !s[1])
+ fn->type = CLOSE;
+ else if (s[0] == 'p' && !s[1])
+ fn->fd2 = (fn->type == MERGEOUT) ? coprocout : coprocin;
+ else {
+ while (idigit(*s))
+ s++;
+ if (!*s && s > fn->name)
+ fn->fd2 = zstrtol(fn->name, NULL, 10);
+ else if (fn->type == MERGEIN)
+ zerr("file number expected", NULL, 0);
+ else
+ fn->type = ERRWRITE;
+ }
+ }
+ } else if (fn->type == MERGEIN)
+ zerr("file number expected", NULL, 0);
+ else {
+ if (fn->type == MERGEOUT)
+ fn->type = ERRWRITE;
+ while ((nam = (char *)ugetnode(fake))) {
+ /* Loop over matches, duplicating the *
+ * redirection for each file found. */
+ ff = (struct redir *)alloc(sizeof *ff);
+ *ff = *fn;
+ ff->name = nam;
+ addlinknode(tab, ff);
+ ret = 1;
+ }
+ }
+ return ret;
+}
+
+/* concatenate s1 and s2 in dynamically allocated buffer */
+
+/**/
+char *
+dyncat(char *s1, char *s2)
+{
+ /* This version always uses space from the current heap. */
+ char *ptr;
+ int l1 = strlen(s1);
+
+ ptr = (char *)ncalloc(l1 + strlen(s2) + 1);
+ strcpy(ptr, s1);
+ strcpy(ptr + l1, s2);
+ return ptr;
+}
+
+/* concatenate s1, s2, and s3 in dynamically allocated buffer */
+
+/**/
+char *
+tricat(char const *s1, char const *s2, char const *s3)
+{
+ /* This version always uses permanently-allocated space. */
+ char *ptr;
+
+ ptr = (char *)zalloc(strlen(s1) + strlen(s2) + strlen(s3) + 1);
+ strcpy(ptr, s1);
+ strcat(ptr, s2);
+ strcat(ptr, s3);
+ return ptr;
+}
+
+/* brace expansion */
+
+/**/
+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, lastch;
+ unsigned int len, pl;
+
+ uremnode(list, node);
+ memset(ccl, 0, sizeof(ccl) / sizeof(ccl[0]));
+ for (p = str + 1, lastch = 0; 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 && p < str2 && (int)lastch <= (int)c2) {
+ while ((int)lastch < (int)c2)
+ ccl[lastch++] = 1;
+ lastch = 0;
+ } 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 = ncalloc(len + 1);
+ str[pl] = Meta;
+ str[pl+1] = c1 ^ 32;
+ strcpy(str + pl + 2, str2);
+ } else {
+ str = ncalloc(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 *)ncalloc(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)
+{
+ Comp c = parsereg(b);
+
+ if (!c) {
+ zerr("bad pattern: %s", b, 0);
+ return 0;
+ }
+ return domatch(a, c, 0);
+}
+
+/* do the ${foo%%bar}, ${foo#bar} stuff */
+/* please do not laugh at this code. */
+
+/* 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).
+ * Bits 3 and higher in fl are used: the flags are
+ * 8: Result is matched portion.
+ * 16: Result is unmatched portion.
+ * (N.B. this should be set for standard ${foo#bar} etc. matches.)
+ * 32: Result is numeric position of start of matched portion.
+ * 64: Result is numeric position of end of matched portion.
+ * 128: Result is length of matched portion.
+ */
+
+/**/
+static char *
+get_match_ret(char *s, int b, int e, int fl)
+{
+ char buf[80], *r, *p, *rr;
+ int ll = 0, l = strlen(s), bl = 0, t = 0, i;
+
+ if (fl & 8) /* matched portion */
+ ll += 1 + (e - b);
+ if (fl & 16) /* unmatched portion */
+ ll += 1 + (l - (e - b));
+ if (fl & 32) {
+ /* position of start of matched portion */
+ sprintf(buf, "%d ", b + 1);
+ ll += (bl = strlen(buf));
+ }
+ if (fl & 64) {
+ /* position of end of matched portion */
+ sprintf(buf + bl, "%d ", e + 1);
+ ll += (bl = strlen(buf));
+ }
+ if (fl & 128) {
+ /* length of matched portion */
+ sprintf(buf + bl, "%d ", e - b);
+ ll += (bl = strlen(buf));
+ }
+ if (bl)
+ buf[bl - 1] = '\0';
+
+ rr = r = (char *)ncalloc(ll);
+
+ if (fl & 8) {
+ /* copy matched portion to new buffer */
+ for (i = b, p = s + b; i < e; i++)
+ *rr++ = *p++;
+ t = 1;
+ }
+ if (fl & 16) {
+ /* 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++;
+ 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;
+}
+
+/* It is called from paramsubst to get the match for ${foo#bar} etc.
+ * Bits of fl determines the required action:
+ * bit 0: match the end instead of the beginning (% or %%)
+ * bit 1: % or # was doubled so get the longest match
+ * bit 2: substring match
+ * bit 3: include the matched portion
+ * bit 4: include the unmatched portion
+ * bit 5: the index of the beginning
+ * bit 6: the index of the end
+ * bit 7: the length of the match
+ * bit 8: match the complete string
+ * *sp points to the string we have to modify. The n'th match will be
+ * returned in *sp. ncalloc is used to get memory for the result string.
+ */
+
+/**/
+int
+getmatch(char **sp, char *pat, int fl, int n)
+{
+ Comp c;
+ char *s = *sp, *t, sav;
+ int i, j, l = strlen(*sp);
+
+ c = parsereg(pat);
+ if (!c) {
+ zerr("bad pattern: %s", pat, 0);
+ return 1;
+ }
+ if (fl & 256) {
+ i = domatch(s, c, 0);
+ *sp = get_match_ret(*sp, 0, domatch(s, c, 0) ? l : 0, fl);
+ if (! **sp && (((fl & 8) && !i) || ((fl & 16) && i)))
+ return 0;
+ return 1;
+ }
+ switch (fl & 7) {
+ case 0:
+ /* Smallest possible match at head of string: *
+ * start adding characters until we get a match. */
+ for (i = 0, t = s; i <= l; i++, t++) {
+ sav = *t;
+ *t = '\0';
+ if (domatch(s, c, 0) && !--n) {
+ *t = sav;
+ *sp = get_match_ret(*sp, 0, i, fl);
+ return 1;
+ }
+ if ((*t = sav) == Meta)
+ i++, t++;
+ }
+ break;
+
+ case 1:
+ /* Smallest possible match at tail of string: *
+ * move back down string until we get a match. */
+ for (t = s + l; t >= s; t--) {
+ if (domatch(t, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, t - s, l, fl);
+ return 1;
+ }
+ if (t > s+1 && t[-2] == Meta)
+ t--;
+ }
+ break;
+
+ case 2:
+ /* Largest possible match at head of string: *
+ * delete characters from end until we get a match. */
+ for (t = s + l; t > s; t--) {
+ sav = *t;
+ *t = '\0';
+ if (domatch(s, c, 0) && !--n) {
+ *t = sav;
+ *sp = get_match_ret(*sp, 0, t - s, fl);
+ return 1;
+ }
+ *t = sav;
+ if (t >= s+2 && t[-2] == Meta)
+ t--;
+ }
+ break;
+
+ case 3:
+ /* Largest possible match at tail of string: *
+ * move forward along string until we get a match. */
+ for (i = 0, t = s; i < l; i++, t++) {
+ if (domatch(t, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, i, l, fl);
+ return 1;
+ }
+ if (*t == Meta)
+ i++, t++;
+ }
+ break;
+
+ case 4:
+ /* Smallest at start, but matching substrings. */
+ if (domatch(s + l, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, 0, 0, fl);
+ return 1;
+ }
+ for (i = 1; i <= l; i++) {
+ for (t = s, j = i; j <= l; j++, t++) {
+ sav = s[j];
+ s[j] = '\0';
+ if (domatch(t, c, 0) && !--n) {
+ s[j] = sav;
+ *sp = get_match_ret(*sp, t - s, j, fl);
+ return 1;
+ }
+ if ((s[j] = sav) == Meta)
+ j++;
+ if (*t == Meta)
+ t++;
+ }
+ if (s[i] == Meta)
+ i++;
+ }
+ break;
+
+ case 5:
+ /* Smallest at end, matching substrings */
+ if (domatch(s + l, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, l, l, fl);
+ return 1;
+ }
+ for (i = l; i--;) {
+ if (i && s[i-1] == Meta)
+ i--;
+ for (t = s + l, j = i; j >= 0; j--, t--) {
+ sav = *t;
+ *t = '\0';
+ if (domatch(s + j, c, 0) && !--n) {
+ *t = sav;
+ *sp = get_match_ret(*sp, j, t - s, fl);
+ return 1;
+ }
+ *t = sav;
+ if (t >= s+2 && t[-2] == Meta)
+ t--;
+ if (j >= 2 && s[j-2] == Meta)
+ j--;
+ }
+ }
+ break;
+
+ case 6:
+ /* Largest at start, matching substrings. */
+ for (i = l; i; i--) {
+ for (t = s, j = i; j <= l; j++, t++) {
+ sav = s[j];
+ s[j] = '\0';
+ if (domatch(t, c, 0) && !--n) {
+ s[j] = sav;
+ *sp = get_match_ret(*sp, t - s, j, fl);
+ return 1;
+ }
+ if ((s[j] = sav) == Meta)
+ j++;
+ if (*t == Meta)
+ t++;
+ }
+ if (i >= 2 && s[i-2] == Meta)
+ i--;
+ }
+ if (domatch(s + l, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, 0, 0, fl);
+ return 1;
+ }
+ break;
+
+ case 7:
+ /* Largest at end, matching substrings. */
+ for (i = 0; i < l; i++) {
+ for (t = s + l, j = i; j >= 0; j--, t--) {
+ sav = *t;
+ *t = '\0';
+ if (domatch(s + j, c, 0) && !--n) {
+ *t = sav;
+ *sp = get_match_ret(*sp, j, t - s, fl);
+ return 1;
+ }
+ *t = sav;
+ if (t >= s+2 && t[-2] == Meta)
+ t--;
+ if (j >= 2 && s[j-2] == Meta)
+ j--;
+ }
+ if (s[i] == Meta)
+ i++;
+ }
+ if (domatch(s + l, c, 0) && !--n) {
+ *sp = get_match_ret(*sp, l, l, fl);
+ return 1;
+ }
+ break;
+ }
+ /* munge the whole string */
+ *sp = get_match_ret(*sp, 0, 0, fl);
+ return 1;
+}
+
+/* The main entry point for matching a string str against *
+ * a compiled pattern c. `fist' indicates whether leading *
+ * dots are special. */
+
+/**/
+int
+domatch(char *str, Comp c, int fist)
+{
+ int ret;
+ pptr = str;
+ first = fist;
+ if (*pptr == Nularg)
+ pptr++;
+ PERMALLOC {
+ ret = doesmatch(c);
+ } LASTALLOC;
+ return ret;
+}
+
+#define untok(C) (itok(C) ? ztokens[(C) - Pound] : (C))
+
+/* See if pattern has a matching exclusion (~...) part */
+
+/**/
+static int
+excluded(Comp c, char *eptr)
+{
+ char *saves = pptr;
+ int savei = first, ret;
+
+ first = 0;
+ if (PATHADDP(c) && pathpos) {
+ VARARR(char, buf, pathpos + strlen(eptr) + 1);
+
+ strcpy(buf, pathbuf);
+ strcpy(buf + pathpos, eptr);
+ pptr = buf;
+ ret = doesmatch(c->exclude);
+ } else {
+ pptr = eptr;
+ ret = doesmatch(c->exclude);
+ }
+ if (*pptr)
+ ret = 0;
+
+ pptr = saves;
+ first = savei;
+
+ return ret;
+}
+
+struct gclose {
+ char *start;
+ char *end;
+};
+typedef struct gclose *Gclose;
+
+static int inclosure; /* see comment in doesmatch() */
+
+/* Add a list of matches that fit the closure. trystring is a string of
+ * the same length as the target string; a non-zero in that string
+ * indicates that we have already tried to match the patterns following
+ * the closure (c->next) at that point and failed. This means that not
+ * only should we not bother using the corresponding match, we should
+ * also not bother going any further, since the first time we got to
+ * that position (when it was marked), we must already have failed on
+ * and backtracked over any further closure matches beyond that point.
+ */
+
+/**/
+static void
+addclosures(Comp c, LinkList closlist, int *pdone, char *trystring)
+{
+ Gclose gcnode;
+ char *opptr = pptr;
+
+ while (*pptr) {
+ if (STARP(c)) {
+ if (trystring[(pptr+1)-opptr])
+ break;
+ gcnode = (Gclose)zalloc(sizeof(struct gclose));
+ gcnode->start = pptr;
+ gcnode->end = ++pptr;
+ } else {
+ char *saves = pptr;
+ if (OPTIONALP(c) && *pdone >= 1)
+ return;
+ if (!matchonce(c) || saves == pptr ||
+ trystring[pptr-opptr]) {
+ pptr = saves;
+ break;
+ }
+ gcnode = (Gclose)zalloc(sizeof(struct gclose));
+ gcnode->start = saves;
+ gcnode->end = pptr;
+ }
+ pushnode(closlist, gcnode);
+ (*pdone)++;
+ }
+}
+
+/* see if current string in pptr matches c */
+
+/**/
+static int
+doesmatch(Comp c)
+{
+ if (CLOSUREP(c)) {
+ int done, retflag = 0;
+ char *saves, *trystring, *opptr;
+ LinkList closlist;
+ Gclose gcnode;
+
+ if (first && *pptr == '.')
+ return 0;
+
+ if (!inclosure && !c->left) {
+ /* We are not inside another closure, and the current
+ * pattern is a simple string. We handle this very common
+ * case specially: otherwise, matches like *foo* are
+ * extremely slow. Here, instead of backtracking, we track
+ * forward until we get a match. At top level, we are bound
+ * to get there eventually, so this is OK.
+ */
+ char looka;
+
+ if (STARP(c) && c->next &&
+ !c->next->left && (looka = *c->next->str) &&
+ !itok(looka)) {
+ /* Another simple optimisation for a very common case:
+ * we are processing a * and there is
+ * an ordinary character match next. We look ahead for
+ * that character, taking care of Meta bytes.
+ */
+ while (*pptr) {
+ for (; *pptr; pptr++) {
+ if (*pptr == Meta)
+ pptr++;
+ else if (*pptr == looka)
+ break;
+ }
+ if (!*(saves = pptr))
+ break;
+ if (doesmatch(c->next))
+ return 1;
+ pptr = saves+1;
+ }
+ } else {
+ /* Standard track-forward code */
+ for (done = 0; ; done++) {
+ saves = pptr;
+ if ((done || ONEHASHP(c) || OPTIONALP(c)) &&
+ ((!c->next && (!LASTP(c) || !*pptr)) ||
+ (c->next && doesmatch(c->next))))
+ return 1;
+ if (done && OPTIONALP(c))
+ return 0;
+ pptr = saves;
+ first = 0;
+ if (STARP(c)) {
+ if (!*pptr)
+ return 0;
+ pptr++;
+ } else if (!matchonce(c) || pptr == saves)
+ return 0;
+ }
+ }
+ return 0;
+ }
+ /* The full, gory backtracking code is now necessary. */
+ inclosure++;
+ closlist = newlinklist();
+ trystring = zcalloc(strlen(pptr)+1);
+ opptr = pptr;
+
+ /* Start by making a list where each match is as long
+ * as possible. We later have to take account of the
+ * fact that earlier matches may be too long.
+ */
+ done = 0;
+ addclosures(c, closlist, &done, trystring);
+ for (;;) {
+ if (TWOHASHP(c) && !done)
+ break;
+ saves = pptr;
+ /* do we really want this LASTP here?? */
+ if ((!c->next && (!LASTP(c) || !*pptr)) ||
+ (c->next && doesmatch(c->next))) {
+ retflag = 1;
+ break;
+ }
+ trystring[saves-opptr] = 1;
+ /*
+ * If we failed, the first thing to try is whether we can
+ * shorten the match using the last pattern in the closure.
+ */
+ gcnode = firstnode(closlist) ? peekfirst(closlist) : NULL;
+ if (gcnode && --gcnode->end > gcnode->start
+ && (gcnode->end[-1] != Meta ||
+ --gcnode->end > gcnode->start)) {
+ char savec = *gcnode->end;
+ *gcnode->end = '\0';
+ pptr = gcnode->start;
+ if (matchonce(c) && pptr != gcnode->start
+ && !trystring[pptr-opptr]) {
+ *gcnode->end = savec;
+ gcnode->end = pptr;
+ /* Try again to construct a list based on
+ * this new position
+ */
+ addclosures(c, closlist, &done, trystring+(pptr-opptr));
+ continue;
+ }
+ *gcnode->end = savec;
+ }
+ /* We've now exhausted the possibilities with that match,
+ * backtrack to the previous.
+ */
+ if ((gcnode = (Gclose)getlinknode(closlist))) {
+ pptr = gcnode->start;
+ zfree(gcnode, sizeof(struct gclose));
+ done--;
+ } else
+ break;
+ }
+ freelinklist(closlist, free);
+ zfree(trystring, strlen(opptr)+1);
+ inclosure--;
+
+ return retflag;
+ } else
+ return matchonce(c);
+}
+
+/**/
+static int
+posix_range(char **patptr, int ch)
+{
+ /* Match POSIX ranges, which correspond to ctype macros, *
+ * e.g. [:alpha:] -> isalpha. It just doesn't seem worth *
+ * the palaver of creating a hash table for this. */
+ char *start = *patptr;
+ int len;
+
+ /* we made sure in parsecomp() there was a ':' to search for */
+ *patptr = strchr(start, ':');
+ len = (*patptr)++ - start;
+
+ if (!strncmp(start, "alpha", len))
+ return isalpha(ch);
+ if (!strncmp(start, "alnum", len))
+ return isalnum(ch);
+ if (!strncmp(start, "blank", len))
+ return ch == ' ' || ch == '\t';
+ if (!strncmp(start, "cntrl", len))
+ return iscntrl(ch);
+ if (!strncmp(start, "digit", len))
+ return isdigit(ch);
+ if (!strncmp(start, "graph", len))
+ return isgraph(ch);
+ if (!strncmp(start, "lower", len))
+ return islower(ch);
+ if (!strncmp(start, "print", len))
+ return isprint(ch);
+ if (!strncmp(start, "punct", len))
+ return ispunct(ch);
+ if (!strncmp(start, "space", len))
+ return isspace(ch);
+ if (!strncmp(start, "upper", len))
+ return isupper(ch);
+ if (!strncmp(start, "xdigit", len))
+ return isxdigit(ch);
+ return 0;
+}
+
+/**/
+static void
+rangematch(char **patptr, int ch, int rchar)
+{
+ /* Check for a character in a [...] or [^...]. The [ *
+ * and optional ^ have already been skipped. */
+
+ char *pat = *patptr;
+#ifdef HAVE_STRCOLL
+ char l_buf[2], r_buf[2], ch_buf[2];
+
+ ch_buf[0] = ch;
+ l_buf[1] = r_buf[1] = ch_buf[1] = '\0';
+#endif
+
+#define PAT(X) (pat[X] == Meta ? pat[(X)+1] ^ 32 : untok(pat[X]))
+#define PPAT(X) (pat[(X)-1] == Meta ? pat[X] ^ 32 : untok(pat[X]))
+
+ for (pat++; *pat != Outbrack && *pat;
+ *pat == Meta ? pat += 2 : pat++) {
+ if (*pat == Inbrack) {
+ /* Inbrack can only occur inside a range if we found [:...:]. */
+ pat += 2;
+ if (posix_range(&pat, ch))
+ break;
+ } else if (*pat == '-' && pat[-1] != rchar &&
+ pat[1] != Outbrack) {
+#ifdef HAVE_STRCOLL
+ l_buf[0] = PPAT(-1);
+ r_buf[0] = PAT(1);
+ if (strcoll(l_buf, ch_buf) <= 0 &&
+ strcoll(ch_buf, r_buf) <= 0)
+#else
+ if (PPAT(-1) <= ch && PAT(1) >= ch)
+#endif
+ break;
+ } else if (ch == PAT(0))
+ break;
+ }
+
+ *patptr = pat;
+}
+
+/**/
+static int
+matchonce(Comp c)
+{
+ char *pat = c->str;
+ for (;;) {
+ /* loop until success or failure of pattern */
+ if (!pat || !*pat) {
+ /* No current pattern (c->str). */
+ char *saves;
+ int savei;
+
+ if (errflag)
+ return 0;
+ /* Remember state in case we need to go back and *
+ * check for exclusion of pattern or alternatives. */
+ saves = pptr;
+ savei = first;
+ /* Loop over alternatives with exclusions: (foo~bar|...). *
+ * Exclusions apply to the pattern in c->left. */
+ if (c->left || c->right) {
+ int ret = 0, ret2 = 0;
+ if (c->exclude) {
+ char *exclend = 0;
+
+ /* We may need to back up on things like `(*~foo)'
+ * if the `*' matched `foo' but needs to match `fo'.
+ * exclend marks the end of the shortened text. We
+ * need to restore it to match the tail.
+ * We set `inclosure' because we need the more
+ * sophisticated code in doesmatch() for any nested
+ * closures.
+ */
+ inclosure++;
+
+ while (!exclend || exclend >= pptr) {
+ char exclsav = 0;
+ if (exclend) {
+ exclsav = *exclend;
+ *exclend = '\0';
+ }
+ if ((ret = doesmatch(c->left))) {
+ if (exclend)
+ *exclend = exclsav;
+ exclsav = *(exclend = pptr);
+ *exclend = '\0';
+ ret2 = !excluded(c, saves);
+ }
+ if (exclend)
+ *exclend = exclsav;
+
+ if (!ret)
+ break;
+ if ((ret = ret2 &&
+ ((!c->next && (!LASTP(c) || !*pptr))
+ || (c->next && doesmatch(c->next)))) ||
+ (!c->next && LASTP(c)))
+ break;
+ /* Back up if necessary: exclend gives the position
+ * of the end of the match we are excluding,
+ * so only try to match to there.
+ */
+ exclend--;
+ pptr = saves;
+ }
+ inclosure--;
+ if (ret)
+ return 1;
+ } else
+ ret = doesmatch(c->left);
+ ret2 = 0;
+ if (c->right && (!ret || inclosure)) {
+ /* If in a closure, we always want the longest match. */
+ char *newpptr = pptr;
+ pptr = saves;
+ first = savei;
+ ret2 = doesmatch(c->right);
+ if (ret && (!ret2 || pptr < newpptr))
+ pptr = newpptr;
+ }
+ if (!ret && !ret2)
+ return 0;
+ }
+ if (CLOSUREP(c))
+ return 1;
+ if (!c->next) /* no more patterns left */
+ return (!LASTP(c) || !*pptr);
+ /* optimisation when next pattern is not a closure */
+ if (!CLOSUREP(c->next)) {
+ c = c->next;
+ pat = c->str;
+ continue;
+ }
+ return doesmatch(c->next);
+ }
+ /* Don't match leading dot if first is set */
+ if (first && *pptr == '.' && *pat != '.')
+ return 0;
+ if (*pat == Star) { /* final * is not expanded to ?#; returns success */
+ while (*pptr)
+ pptr++;
+ return 1;
+ }
+ first = 0; /* finished checking start of pattern */
+ if (*pat == Quest && *pptr) {
+ /* match exactly one character */
+ if (*pptr == Meta)
+ pptr++;
+ pptr++;
+ pat++;
+ continue;
+ }
+ if (*pat == Inbrack) {
+ /* Match groups of characters */
+ char ch;
+
+ if (!*pptr)
+ break;
+ ch = *pptr == Meta ? pptr[1] ^ 32 : *pptr;
+ if (pat[1] == Hat || pat[1] == '^' || pat[1] == '!') {
+ /* group is negated */
+ *++pat = Hat;
+ rangematch(&pat, ch, Hat);
+ DPUTS(!*pat, "BUG: something is very wrong in doesmatch()");
+ if (*pat != Outbrack)
+ break;
+ pat++;
+ *pptr == Meta ? pptr += 2 : pptr++;
+ continue;
+ } else {
+ /* pattern is not negated (affirmed? asserted?) */
+ rangematch(&pat, ch, Inbrack);
+ DPUTS(!pat || !*pat, "BUG: something is very wrong in doesmatch()");
+ if (*pat == Outbrack)
+ break;
+ for (*pptr == Meta ? pptr += 2 : pptr++;
+ *pat != Outbrack; pat++);
+ pat++;
+ continue;
+ }
+ }
+ if (*pat == Inang) {
+ /* Numeric globbing. */
+ unsigned long t1, t2, t3;
+ char *ptr;
+
+ if (!idigit(*pptr))
+ break;
+ if (*++pat == Outang ||
+ (*pat == '-' && pat[1] == Outang && ++pat)) {
+ /* <> or <->: any number matches */
+ while (idigit(*++pptr));
+ pat++;
+ } else {
+ /* Flag that there is no upper limit */
+ int not3 = 0;
+ char *opptr = pptr;
+ /*
+ * Form is <a-b>, where a or b are numbers or blank.
+ * t1 = number supplied: must be positive, so use
+ * unsigned arithmetic.
+ */
+ t1 = (unsigned long)zstrtol(pptr, &ptr, 10);
+ pptr = ptr;
+ /* t2 = lower limit */
+ if (idigit(*pat))
+ t2 = (unsigned long)zstrtol(pat, &ptr, 10);
+ else
+ t2 = 0, ptr = pat;
+ if (*ptr != '-' || (not3 = (ptr[1] == Outang)))
+ /* exact match or no upper limit */
+ t3 = t2, pat = ptr + not3;
+ else /* t3 = upper limit */
+ t3 = (unsigned long)zstrtol(ptr + 1, &pat, 10);
+ DPUTS(*pat != Outang, "BUG: wrong internal range pattern");
+ pat++;
+ /*
+ * If the number found is too large for the pattern,
+ * try matching just the first part. This way
+ * we always get the longest possible match.
+ */
+ while (!not3 && t1 > t3 && pptr > opptr+1) {
+ pptr--;
+ t1 /= 10;
+ }
+ if (t1 < t2 || (!not3 && t1 > t3))
+ break;
+ }
+ continue;
+ }
+ if (*pptr == *pat) {
+ /* just plain old characters */
+ pptr++;
+ pat++;
+ continue;
+ }
+ break;
+ }
+ return 0;
+}
+
+/* turn a string into a Comp struct: this doesn't treat / specially */
+
+/**/
+Comp
+parsereg(char *str)
+{
+ remnulargs(str);
+ mode = 1; /* no path components */
+ pptr = str;
+ tail = NULL;
+ return parsecompsw(GF_TOPLEV);
+}
+
+/* blindly turn a string into a tokenised expression without lexing */
+
+/**/
+void
+tokenize(char *s)
+{
+ 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 (isset(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 (unset(EXTENDEDGLOB))
+ break;
+ case '(':
+ case '|':
+ case ')':
+ if (isset(SHGLOB))
+ break;
+ 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 */
+
+/**/
+void
+remnulargs(char *s)
+{
+ int nl = *s;
+ char *t = s;
+
+ while (*s)
+ if (INULL(*s))
+ chuck(s);
+ else
+ s++;
+ if (!*t && nl) {
+ t[0] = Nularg;
+ t[1] = '\0';
+ }
+}
+
+/* qualifier functions: mostly self-explanatory, see glob(). */
+
+/* device number */
+
+/**/
+static int
+qualdev(struct stat *buf, long dv)
+{
+ return buf->st_dev == dv;
+}
+
+/* number of hard links to file */
+
+/**/
+static int
+qualnlink(struct stat *buf, long ct)
+{
+ return (range < 0 ? buf->st_nlink < ct :
+ range > 0 ? buf->st_nlink > ct :
+ buf->st_nlink == ct);
+}
+
+/* user ID */
+
+/**/
+static int
+qualuid(struct stat *buf, long uid)
+{
+ return buf->st_uid == uid;
+}
+
+/* group ID */
+
+/**/
+static int
+qualgid(struct stat *buf, long gid)
+{
+ return buf->st_gid == gid;
+}
+
+/* device special file? */
+
+/**/
+static int
+qualisdev(struct stat *buf, long junk)
+{
+ return S_ISBLK(buf->st_mode) || S_ISCHR(buf->st_mode);
+}
+
+/* block special file? */
+
+/**/
+static int
+qualisblk(struct stat *buf, long junk)
+{
+ return S_ISBLK(buf->st_mode);
+}
+
+/* character special file? */
+
+/**/
+static int
+qualischr(struct stat *buf, long junk)
+{
+ return S_ISCHR(buf->st_mode);
+}
+
+/* directory? */
+
+/**/
+static int
+qualisdir(struct stat *buf, long junk)
+{
+ return S_ISDIR(buf->st_mode);
+}
+
+/* FIFO? */
+
+/**/
+static int
+qualisfifo(struct stat *buf, long junk)
+{
+ return S_ISFIFO(buf->st_mode);
+}
+
+/* symbolic link? */
+
+/**/
+static int
+qualislnk(struct stat *buf, long junk)
+{
+ return S_ISLNK(buf->st_mode);
+}
+
+/* regular file? */
+
+/**/
+static int
+qualisreg(struct stat *buf, long junk)
+{
+ return S_ISREG(buf->st_mode);
+}
+
+/* socket? */
+
+/**/
+static int
+qualissock(struct stat *buf, long junk)
+{
+ return S_ISSOCK(buf->st_mode);
+}
+
+/* given flag is set in mode */
+
+/**/
+static int
+qualflags(struct stat *buf, long mod)
+{
+ return mode_to_octal(buf->st_mode) & mod;
+}
+
+/* mode matches number supplied exactly */
+
+/**/
+static int
+qualeqflags(struct stat *buf, long mod)
+{
+ return mode_to_octal(buf->st_mode) == mod;
+}
+
+/* regular executable file? */
+
+/**/
+static int
+qualiscom(struct stat *buf, long mod)
+{
+ return S_ISREG(buf->st_mode) && (buf->st_mode & S_IXUGO);
+}
+
+/* size in required range? */
+
+/**/
+static int
+qualsize(struct stat *buf, long size)
+{
+ unsigned long scaled = buf->st_size;
+
+ switch (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 (range < 0 ? scaled < (unsigned long) size :
+ range > 0 ? scaled > (unsigned long) size :
+ scaled == (unsigned long) size);
+}
+
+/* time in required range? */
+
+/**/
+static int
+qualtime(struct stat *buf, long days)
+{
+ time_t now, diff;
+
+ time(&now);
+ diff = now - (amc == 0 ? buf->st_atime : amc == 1 ? buf->st_mtime :
+ buf->st_ctime);
+ /* handle multipliers indicating units */
+ switch (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 (range < 0 ? diff < days :
+ range > 0 ? diff > days :
+ diff == days);
+}