diff options
Diffstat (limited to 'Src/glob.c')
-rw-r--r-- | Src/glob.c | 2800 |
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); +} |