/* * 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 , 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); }