diff options
| author | Craig Jennings <c@cjennings.net> | 2025-05-08 18:49:34 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-05-08 18:51:59 -0500 |
| commit | 000e00871830cd15de032c80e2b62946cf19445c (patch) | |
| tree | 794a7922750472bbe0e024042d6ba84f411fc3e0 /dotfiles/system/.zsh/modules/Src/glob.c | |
| parent | fe302606931e4bad91c4ed6df81a4403523ba780 (diff) | |
adding missing dotfiles and folders
- profile.d/
- bashrc
- authinfo.gpg
- .zsh/
Diffstat (limited to 'dotfiles/system/.zsh/modules/Src/glob.c')
| -rw-r--r-- | dotfiles/system/.zsh/modules/Src/glob.c | 3913 |
1 files changed, 3913 insertions, 0 deletions
diff --git a/dotfiles/system/.zsh/modules/Src/glob.c b/dotfiles/system/.zsh/modules/Src/glob.c new file mode 100644 index 0000000..ed2c90b --- /dev/null +++ b/dotfiles/system/.zsh/modules/Src/glob.c @@ -0,0 +1,3913 @@ +/* + * 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" + +#if defined(OFF_T_IS_64_BIT) && defined(__GNUC__) +# define ALIGN64 __attribute__((aligned(8))) +#else +# define ALIGN64 +#endif + +/* flag for CSHNULLGLOB */ + +typedef struct gmatch *Gmatch; + +struct gmatch { + /* Metafied file name */ + char *name; + /* Unmetafied file name; embedded nulls can't occur in file names */ + char *uname; + /* + * Array of sort strings: one for each GS_EXEC sort type in + * the glob qualifiers. + */ + char **sortstrs; + off_t size ALIGN64; + long atime; + long mtime; + long ctime; + long links; + off_t _size ALIGN64; + long _atime; + long _mtime; + long _ctime; + long _links; +#ifdef GET_ST_ATIME_NSEC + long ansec; + long _ansec; +#endif +#ifdef GET_ST_MTIME_NSEC + long mnsec; + long _mnsec; +#endif +#ifdef GET_ST_CTIME_NSEC + long cnsec; + long _cnsec; +#endif +}; + +#define GS_NAME 1 +#define GS_DEPTH 2 +#define GS_EXEC 4 + +#define GS_SHIFT_BASE 8 + +#define GS_SIZE (GS_SHIFT_BASE) +#define GS_ATIME (GS_SHIFT_BASE << 1) +#define GS_MTIME (GS_SHIFT_BASE << 2) +#define GS_CTIME (GS_SHIFT_BASE << 3) +#define GS_LINKS (GS_SHIFT_BASE << 4) + +#define GS_SHIFT 5 +#define GS__SIZE (GS_SIZE << GS_SHIFT) +#define GS__ATIME (GS_ATIME << GS_SHIFT) +#define GS__MTIME (GS_MTIME << GS_SHIFT) +#define GS__CTIME (GS_CTIME << GS_SHIFT) +#define GS__LINKS (GS_LINKS << GS_SHIFT) + +#define GS_DESC (GS_SHIFT_BASE << (2*GS_SHIFT)) +#define GS_NONE (GS_SHIFT_BASE << (2*GS_SHIFT+1)) + +#define GS_NORMAL (GS_SIZE | GS_ATIME | GS_MTIME | GS_CTIME | GS_LINKS) +#define GS_LINKED (GS_NORMAL << GS_SHIFT) + +/**/ +int badcshglob; + +/**/ +int pathpos; /* position in pathbuf (needed by pattern code) */ + +/* + * pathname buffer (needed by pattern code). + * It is currently believed the string in here is stored metafied and is + * unmetafied temporarily as needed by system calls. + */ + +/**/ +char *pathbuf; + +typedef struct stat *Statptr; /* This makes the Ultrix compiler happy. Go figure. */ + +/* modifier for unit conversions */ + +#define TT_DAYS 0 +#define TT_HOURS 1 +#define TT_MINS 2 +#define TT_WEEKS 3 +#define TT_MONTHS 4 +#define TT_SECONDS 5 + +#define TT_BYTES 0 +#define TT_POSIX_BLOCKS 1 +#define TT_KILOBYTES 2 +#define TT_MEGABYTES 3 +#define TT_GIGABYTES 4 +#define TT_TERABYTES 5 + + +typedef int (*TestMatchFunc) _((char *, struct stat *, off_t, char *)); + +struct qual { + struct qual *next; /* Next qualifier, must match */ + struct qual *or; /* Alternative set of qualifiers to match */ + TestMatchFunc func; /* Function to call to test match */ + off_t data ALIGN64; /* Argument passed to function */ + int sense; /* Whether asserting or negating */ + int amc; /* Flag for which time to test (a, m, c) */ + int range; /* Whether to test <, > or = (as per signum) */ + int units; /* Multiplier for time or size, respectively */ + char *sdata; /* currently only: expression to eval */ +}; + +/* Prefix, suffix for doing zle trickery */ + +/**/ +mod_export char *glob_pre, *glob_suf; + +/* Element of a glob sort */ +struct globsort { + /* Sort type */ + int tp; + /* Sort code to eval, if type is GS_EXEC */ + char *exec; +}; + +/* Maximum entries in sort array */ +#define MAX_SORTS (12) + +/* struct to easily save/restore current state */ + +struct globdata { + int gd_pathpos; + char *gd_pathbuf; + + int gd_matchsz; /* size of matchbuf */ + int gd_matchct; /* number of matches found */ + int gd_pathbufsz; /* size of pathbuf */ + int gd_pathbufcwd; /* where did we chdir()'ed */ + Gmatch gd_matchbuf; /* array of matches */ + Gmatch gd_matchptr; /* &matchbuf[matchct] */ + char *gd_colonmod; /* colon modifiers in qualifier list */ + + /* Qualifiers pertaining to current pattern */ + struct qual *gd_quals; + + /* Other state values for current pattern */ + int gd_qualct, gd_qualorct; + int gd_range, gd_amc, gd_units; + int gd_gf_nullglob, gd_gf_markdirs, gd_gf_noglobdots, gd_gf_listtypes; + int gd_gf_numsort; + int gd_gf_follow, gd_gf_sorts, gd_gf_nsorts; + struct globsort gd_gf_sortlist[MAX_SORTS]; + LinkList gd_gf_pre_words, gd_gf_post_words; + + char *gd_glob_pre, *gd_glob_suf; +}; + +/* The variable with the current globbing state and convenience macros */ + +static struct globdata curglobdata; + +#define matchsz (curglobdata.gd_matchsz) +#define matchct (curglobdata.gd_matchct) +#define pathbufsz (curglobdata.gd_pathbufsz) +#define pathbufcwd (curglobdata.gd_pathbufcwd) +#define matchbuf (curglobdata.gd_matchbuf) +#define matchptr (curglobdata.gd_matchptr) +#define colonmod (curglobdata.gd_colonmod) +#define quals (curglobdata.gd_quals) +#define qualct (curglobdata.gd_qualct) +#define qualorct (curglobdata.gd_qualorct) +#define g_range (curglobdata.gd_range) +#define g_amc (curglobdata.gd_amc) +#define g_units (curglobdata.gd_units) +#define gf_nullglob (curglobdata.gd_gf_nullglob) +#define gf_markdirs (curglobdata.gd_gf_markdirs) +#define gf_noglobdots (curglobdata.gd_gf_noglobdots) +#define gf_listtypes (curglobdata.gd_gf_listtypes) +#define gf_numsort (curglobdata.gd_gf_numsort) +#define gf_follow (curglobdata.gd_gf_follow) +#define gf_sorts (curglobdata.gd_gf_sorts) +#define gf_nsorts (curglobdata.gd_gf_nsorts) +#define gf_sortlist (curglobdata.gd_gf_sortlist) +#define gf_pre_words (curglobdata.gd_gf_pre_words) +#define gf_post_words (curglobdata.gd_gf_post_words) + +/* and macros for save/restore */ + +#define save_globstate(N) \ + do { \ + queue_signals(); \ + memcpy(&(N), &curglobdata, sizeof(struct globdata)); \ + (N).gd_pathpos = pathpos; \ + (N).gd_pathbuf = pathbuf; \ + (N).gd_glob_pre = glob_pre; \ + (N).gd_glob_suf = glob_suf; \ + pathbuf = NULL; \ + unqueue_signals(); \ + } while (0) + +#define restore_globstate(N) \ + do { \ + queue_signals(); \ + zfree(pathbuf, pathbufsz); \ + memcpy(&curglobdata, &(N), sizeof(struct globdata)); \ + pathpos = (N).gd_pathpos; \ + pathbuf = (N).gd_pathbuf; \ + glob_pre = (N).gd_glob_pre; \ + glob_suf = (N).gd_glob_suf; \ + unqueue_signals(); \ + } while (0) + +/* pathname component in filename patterns */ + +struct complist { + Complist next; + Patprog pat; + int closure; /* 1 if this is a (foo/)# */ + int follow; /* 1 to go thru symlinks */ +}; + +/* Add a component to pathbuf: This keeps track of how * + * far we are into a file name, since each path component * + * must be matched separately. */ + +/**/ +static void +addpath(char *s, int l) +{ + DPUTS(!pathbuf, "BUG: pathbuf not initialised"); + while (pathpos + l + 1 >= pathbufsz) + pathbuf = zrealloc(pathbuf, pathbufsz *= 2); + while (l--) + pathbuf[pathpos++] = *s++; + pathbuf[pathpos++] = '/'; + pathbuf[pathpos] = '\0'; +} + +/* stat the filename s appended to pathbuf. l should be true for lstat, * + * false for stat. If st is NULL, the file is only checked for existance. * + * s == "" is treated as s == ".". This is necessary since on most systems * + * foo/ can be used to reference a non-directory foo. Returns nonzero if * + * the file does not exists. */ + +/**/ +static int +statfullpath(const char *s, struct stat *st, int l) +{ + char buf[PATH_MAX+1]; + + DPUTS(strlen(s) + !*s + pathpos - pathbufcwd >= PATH_MAX, + "BUG: statfullpath(): pathname too long"); + strcpy(buf, pathbuf + pathbufcwd); + strcpy(buf + pathpos - pathbufcwd, s); + if (!*s && *buf) { + /* + * Don't add the '.' if the path so far is empty, since + * then we get bogus empty strings inserted as files. + */ + buf[pathpos - pathbufcwd] = '.'; + buf[pathpos - pathbufcwd + 1] = '\0'; + l = 0; + } + unmetafy(buf, NULL); + if (!st) { + char lbuf[1]; + return access(buf, F_OK) && (!l || readlink(buf, lbuf, 1) < 0); + } + return l ? lstat(buf, st) : stat(buf, st); +} + +/* This may be set by qualifier functions to an array of strings to insert + * into the list instead of the original string. */ + +static char **inserts; + +/* add a match to the list */ + +/**/ +static void +insert(char *s, int checked) +{ + struct stat buf, buf2, *bp; + char *news = s; + int statted = 0; + + queue_signals(); + inserts = NULL; + + if (gf_listtypes || gf_markdirs) { + /* Add the type marker to the end of the filename */ + mode_t mode; + checked = statted = 1; + if (statfullpath(s, &buf, 1)) { + unqueue_signals(); + return; + } + mode = buf.st_mode; + if (gf_follow) { + if (!S_ISLNK(mode) || statfullpath(s, &buf2, 0)) + memcpy(&buf2, &buf, sizeof(buf)); + statted |= 2; + mode = buf2.st_mode; + } + if (gf_listtypes || S_ISDIR(mode)) { + int ll = strlen(s); + + news = (char *) hcalloc(ll + 2); + strcpy(news, s); + news[ll] = file_type(mode); + news[ll + 1] = '\0'; + } + } + if (qualct || qualorct) { + /* Go through the qualifiers, rejecting the file if appropriate */ + struct qual *qo, *qn; + + if (!statted && statfullpath(s, &buf, 1)) { + unqueue_signals(); + return; + } + news = dyncat(pathbuf, news); + + statted = 1; + qo = quals; + for (qn = qo; qn && qn->func;) { + g_range = qn->range; + g_amc = qn->amc; + g_units = qn->units; + if ((qn->sense & 2) && !(statted & 2)) { + /* If (sense & 2), we're following links */ + if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0)) + memcpy(&buf2, &buf, sizeof(buf)); + statted |= 2; + } + bp = (qn->sense & 2) ? &buf2 : &buf; + /* Reject the file if the function returned zero * + * and the sense was positive (sense&1 == 0), or * + * vice versa. */ + if ((!((qn->func) (news, bp, qn->data, qn->sdata)) + ^ qn->sense) & 1) { + /* Try next alternative, or return if there are no more */ + if (!(qo = qo->or)) { + unqueue_signals(); + return; + } + qn = qo; + continue; + } + qn = qn->next; + } + } else if (!checked) { + if (statfullpath(s, &buf, 1)) { + unqueue_signals(); + return; + } + statted = 1; + news = dyncat(pathbuf, news); + } else + news = dyncat(pathbuf, news); + + while (!inserts || (news = dupstring(*inserts++))) { + if (colonmod) { + /* Handle the remainder of the qualifier: e.g. (:r:s/foo/bar/). */ + char *mod = colonmod; + modify(&news, &mod); + } + if (!statted && (gf_sorts & GS_NORMAL)) { + statfullpath(s, &buf, 1); + statted = 1; + } + if (!(statted & 2) && (gf_sorts & GS_LINKED)) { + if (statted) { + if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0)) + memcpy(&buf2, &buf, sizeof(buf)); + } else if (statfullpath(s, &buf2, 0)) + statfullpath(s, &buf2, 1); + statted |= 2; + } + matchptr->name = news; + if (statted & 1) { + matchptr->size = buf.st_size; + matchptr->atime = buf.st_atime; + matchptr->mtime = buf.st_mtime; + matchptr->ctime = buf.st_ctime; + matchptr->links = buf.st_nlink; +#ifdef GET_ST_ATIME_NSEC + matchptr->ansec = GET_ST_ATIME_NSEC(buf); +#endif +#ifdef GET_ST_MTIME_NSEC + matchptr->mnsec = GET_ST_MTIME_NSEC(buf); +#endif +#ifdef GET_ST_CTIME_NSEC + matchptr->cnsec = GET_ST_CTIME_NSEC(buf); +#endif + } + if (statted & 2) { + matchptr->_size = buf2.st_size; + matchptr->_atime = buf2.st_atime; + matchptr->_mtime = buf2.st_mtime; + matchptr->_ctime = buf2.st_ctime; + matchptr->_links = buf2.st_nlink; +#ifdef GET_ST_ATIME_NSEC + matchptr->_ansec = GET_ST_ATIME_NSEC(buf2); +#endif +#ifdef GET_ST_MTIME_NSEC + matchptr->_mnsec = GET_ST_MTIME_NSEC(buf2); +#endif +#ifdef GET_ST_CTIME_NSEC + matchptr->_cnsec = GET_ST_CTIME_NSEC(buf2); +#endif + } + matchptr++; + + if (++matchct == matchsz) { + matchbuf = (Gmatch)zrealloc((char *)matchbuf, + sizeof(struct gmatch) * (matchsz *= 2)); + + matchptr = matchbuf + matchct; + } + if (!inserts) + break; + } + unqueue_signals(); + return; +} + +/* 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, int shortcircuit) +{ + Patprog p; + int closure; + int pbcwdsav = pathbufcwd; + int errssofar = errsfound; + struct dirsav ds; + + if (!q || errflag) + return; + init_dirsav(&ds); + + 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, shortcircuit); + if (shortcircuit && shortcircuit == matchct) + return; + } + } + p = q->pat; + /* Now the actual matching for the current path section. */ + if (p->flags & PAT_PURES) { + /* + * It's a straight string to the end of the path section. + */ + char *str = (char *)p + p->startoff; + int l = p->patmlen; + + if (l + !l + pathpos - pathbufcwd >= PATH_MAX) { + int err; + + if (l >= PATH_MAX) + return; + err = lchdir(unmeta(pathbuf + pathbufcwd), &ds, 0); + if (err == -1) + return; + if (err) { + zerr("current directory lost during glob"); + return; + } + pathbufcwd = pathpos; + } + if (q->next) { + /* Not the last path section. Just add it to the path. */ + int oppos = pathpos; + + if (!errflag) { + int add = 1; + + if (q->closure && *pathbuf) { + if (!strcmp(str, ".")) + add = 0; + else if (!strcmp(str, "..")) { + struct stat sc, sr; + + add = (stat("/", &sr) || stat(unmeta(pathbuf), &sc) || + sr.st_ino != sc.st_ino || + sr.st_dev != sc.st_dev); + } + } + if (add) { + addpath(str, l); + if (!closure || !statfullpath("", NULL, 1)) { + scanner((q->closure) ? q : q->next, shortcircuit); + if (shortcircuit && shortcircuit == matchct) + return; + } + pathbuf[pathpos = oppos] = '\0'; + } + } + } else { + if (str[l]) + str = dupstrpfx(str, l); + insert(str, 0); + if (shortcircuit && shortcircuit == matchct) + return; + } + } else { + /* Do pattern matching on current path section. */ + char *fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : "."; + int dirs = !!q->next; + DIR *lock = opendir(fn); + char *subdirs = NULL; + int subdirlen = 0; + + if (lock == NULL) + return; + while ((fn = zreaddir(lock, 1)) && !errflag) { + /* prefix and suffix are zle trickery */ + if (!dirs && !colonmod && + ((glob_pre && !strpfx(glob_pre, fn)) + || (glob_suf && !strsfx(glob_suf, fn)))) + continue; + errsfound = errssofar; + if (pattry(p, fn)) { + /* if this name matchs the pattern... */ + if (pbcwdsav == pathbufcwd && + strlen(fn) + pathpos - pathbufcwd >= PATH_MAX) { + int err; + + DPUTS(pathpos == pathbufcwd, + "BUG: filename longer than PATH_MAX"); + err = lchdir(unmeta(pathbuf + pathbufcwd), &ds, 0); + if (err == -1) + break; + if (err) { + zerr("current directory lost during glob"); + break; + } + pathbufcwd = pathpos; + } + if (dirs) { + int l; + + /* + * If not the last component in the path: + * + * If we made an approximation in the new path segment, + * then it is possible we made too many errors. For + * example, (ab)#(cb)# will match the directory abcb + * with one error if allowed to, even though it can + * match with none. This will stop later parts of the + * path matching, so we need to check by reducing the + * maximum number of errors and seeing if the directory + * still matches. Luckily, this is not a terribly + * common case, since complex patterns typically occur + * in the last part of the path which is not affected + * by this problem. + */ + if (errsfound > errssofar) { + forceerrs = errsfound - 1; + while (forceerrs >= errssofar) { + errsfound = errssofar; + if (!pattry(p, fn)) + break; + forceerrs = errsfound - 1; + } + errsfound = forceerrs + 1; + forceerrs = -1; + } + if (closure) { + /* if matching multiple directories */ + struct stat buf; + + if (statfullpath(fn, &buf, !q->follow)) { + if (errno != ENOENT && errno != EINTR && + errno != ENOTDIR && !errflag) { + zwarn("%e: %s", errno, fn); + } + continue; + } + if (!S_ISDIR(buf.st_mode)) + continue; + } + l = strlen(fn) + 1; + subdirs = hrealloc(subdirs, subdirlen, subdirlen + l + + sizeof(int)); + strcpy(subdirs + subdirlen, fn); + subdirlen += l; + /* store the count of errors made so far, too */ + memcpy(subdirs + subdirlen, (char *)&errsfound, + sizeof(int)); + subdirlen += sizeof(int); + } else { + /* if the last filename component, just add it */ + insert(fn, 1); + if (shortcircuit && shortcircuit == matchct) { + closedir(lock); + return; + } + } + } + } + closedir(lock); + if (subdirs) { + int oppos = pathpos; + + for (fn = subdirs; fn < subdirs+subdirlen; ) { + int l = strlen(fn); + addpath(fn, l); + fn += l + 1; + memcpy((char *)&errsfound, fn, sizeof(int)); + fn += sizeof(int); + /* scan next level */ + scanner((q->closure) ? q : q->next, shortcircuit); + if (shortcircuit && shortcircuit == matchct) + return; + pathbuf[pathpos = oppos] = '\0'; + } + hrealloc(subdirs, subdirlen, 0); + } + } + if (pbcwdsav < pathbufcwd) { + if (restoredir(&ds)) + zerr("current directory lost during glob"); + zsfree(ds.dirname); + if (ds.dirfd >= 0) + close(ds.dirfd); + pathbufcwd = pbcwdsav; + } + return; +} + +/* This function tokenizes a zsh glob pattern */ + +/**/ +static Complist +parsecomplist(char *instr) +{ + Patprog p1; + Complist l1; + char *str; + int compflags = gf_noglobdots ? (PAT_FILE|PAT_NOGLD) : PAT_FILE; + + if (instr[0] == Star && instr[1] == Star) { + int shortglob = 0; + if (instr[2] == '/' || (instr[2] == Star && instr[3] == '/') + || (shortglob = isset(GLOBSTARSHORT))) { + /* Match any number of directories. */ + int follow; + + /* with three stars, follow symbolic links */ + follow = (instr[2] == Star); + /* + * With GLOBSTARSHORT, leave a star in place for the + * pattern inside the directory. + */ + instr += ((shortglob ? 1 : 3) + follow); + + /* Now get the next path component if there is one. */ + l1 = (Complist) zhalloc(sizeof *l1); + if ((l1->next = parsecomplist(instr)) == NULL) { + errflag |= ERRFLAG_ERROR; + return NULL; + } + l1->pat = patcompile(NULL, compflags | PAT_ANY, NULL); + l1->closure = 1; /* ...zero or more times. */ + l1->follow = follow; + return l1; + } + } + + /* Parse repeated directories such as (dir/)# and (dir/)## */ + if (*(str = instr) == zpc_special[ZPC_INPAR] && + !skipparens(Inpar, Outpar, (char **)&str) && + *str == zpc_special[ZPC_HASH] && str[-2] == '/') { + instr++; + if (!(p1 = patcompile(instr, compflags, &instr))) + return NULL; + if (instr[0] == '/' && instr[1] == Outpar && instr[2] == Pound) { + int pdflag = 0; + + instr += 3; + if (*instr == Pound) { + pdflag = 1; + instr++; + } + l1 = (Complist) zhalloc(sizeof *l1); + l1->pat = p1; + /* special case (/)# to avoid infinite recursion */ + l1->closure = (*((char *)p1 + p1->startoff)) ? 1 + pdflag : 0; + l1->follow = 0; + l1->next = parsecomplist(instr); + return (l1->pat) ? l1 : NULL; + } + } else { + /* parse single path component */ + if (!(p1 = patcompile(instr, compflags|PAT_FILET, &instr))) + return NULL; + /* then do the remaining path components */ + if (*instr == '/' || !*instr) { + int ef = *instr == '/'; + + l1 = (Complist) zhalloc(sizeof *l1); + l1->pat = p1; + l1->closure = 0; + l1->next = ef ? parsecomplist(instr+1) : NULL; + return (ef && !l1->next) ? NULL : l1; + } + } + errflag |= ERRFLAG_ERROR; + return NULL; +} + +/* turn a string into a Complist struct: this has path components */ + +/**/ +static Complist +parsepat(char *str) +{ + long assert; + int ignore; + + patcompstart(); + /* + * Check for initial globbing flags, so that they don't form + * a bogus path component. + */ + if ((*str == zpc_special[ZPC_INPAR] && str[1] == zpc_special[ZPC_HASH]) || + (*str == zpc_special[ZPC_KSH_AT] && str[1] == Inpar && + str[2] == zpc_special[ZPC_HASH])) { + str += (*str == Inpar) ? 2 : 3; + if (!patgetglobflags(&str, &assert, &ignore)) + return NULL; + } + + /* Now there is no (#X) in front, we can check the path. */ + if (!pathbuf) + pathbuf = zalloc(pathbufsz = PATH_MAX+1); + DPUTS(pathbufcwd, "BUG: glob changed directory"); + if (*str == '/') { /* pattern has absolute path */ + str++; + pathbuf[0] = '/'; + pathbuf[pathpos = 1] = '\0'; + } else /* pattern is relative to pwd */ + pathbuf[pathpos = 0] = '\0'; + + return parsecomplist(str); +} + +/* get number after qualifier */ + +/**/ +static off_t +qgetnum(char **s) +{ + off_t v = 0; + + if (!idigit(**s)) { + zerr("number expected"); + return 0; + } + while (idigit(**s)) + v = v * 10 + *(*s)++ - '0'; + return v; +} + +/* get mode spec after qualifier */ + +/**/ +static zlong +qgetmodespec(char **s) +{ + zlong yes = 0, no = 0, val, mask, t; + char *p = *s, c, how, end; + + if ((c = *p) == '=' || c == Equals || c == '+' || c == '-' || + c == '?' || c == Quest || (c >= '0' && c <= '7')) { + end = 0; + c = 0; + } else { + end = (c == '<' ? '>' : + (c == '[' ? ']' : + (c == '{' ? '}' : + (c == Inang ? Outang : + (c == Inbrack ? Outbrack : + (c == Inbrace ? Outbrace : c)))))); + p++; + } + do { + mask = 0; + while (((c = *p) == 'u' || c == 'g' || c == 'o' || c == 'a') && end) { + switch (c) { + case 'o': mask |= 01007; break; + case 'g': mask |= 02070; break; + case 'u': mask |= 04700; break; + case 'a': mask |= 07777; break; + } + p++; + } + how = ((c == '+' || c == '-') ? c : '='); + if (c == '+' || c == '-' || c == '=' || c == Equals) + p++; + val = 0; + if (mask) { + while ((c = *p++) != ',' && c != end) { + switch (c) { + case 'x': val |= 00111; break; + case 'w': val |= 00222; break; + case 'r': val |= 00444; break; + case 's': val |= 06000; break; + case 't': val |= 01000; break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + t = ((zlong) c - '0'); + val |= t | (t << 3) | (t << 6); + break; + default: + zerr("invalid mode specification"); + return 0; + } + } + if (how == '=' || how == '+') { + yes |= val & mask; + val = ~val; + } + if (how == '=' || how == '-') + no |= val & mask; + } else if (!(end && c == end) && c != ',' && c) { + t = 07777; + while ((c = *p) == '?' || c == Quest || + (c >= '0' && c <= '7')) { + if (c == '?' || c == Quest) { + t = (t << 3) | 7; + val <<= 3; + } else { + t <<= 3; + val = (val << 3) | ((zlong) c - '0'); + } + p++; + } + if (end && c != end && c != ',') { + zerr("invalid mode specification"); + return 0; + } + if (how == '=') { + yes = (yes & ~t) | val; + no = (no & ~t) | (~val & ~t); + } else if (how == '+') + yes |= val; + else + no |= val; + } else { + zerr("invalid mode specification"); + return 0; + } + } while (end && c != end); + + *s = p; + return ((yes & 07777) | ((no & 07777) << 12)); +} + +static int +gmatchcmp(Gmatch a, Gmatch b) +{ + int i; + off_t r = 0L; + struct globsort *s; + char **asortstrp = NULL, **bsortstrp = NULL; + + for (i = gf_nsorts, s = gf_sortlist; i; i--, s++) { + switch (s->tp & ~GS_DESC) { + case GS_NAME: + r = zstrcmp(b->uname, a->uname, + gf_numsort ? SORTIT_NUMERICALLY : 0); + break; + case GS_DEPTH: + { + char *aptr = a->name, *bptr = b->name; + int slasha = 0, slashb = 0; + /* Count slashes. Trailing slashes don't count. */ + while (*aptr && *aptr == *bptr) + aptr++, bptr++; + /* Like I just said... */ + if ((!*aptr || !*bptr) && aptr > a->name && aptr[-1] == '/') + aptr--, bptr--; + if (*aptr) + for (; aptr[1]; aptr++) + if (*aptr == '/') { + slasha = 1; + break; + } + if (*bptr) + for (; bptr[1]; bptr++) + if (*bptr == '/') { + slashb = 1; + break; + } + r = slasha - slashb; + } + break; + case GS_EXEC: + if (!asortstrp) { + asortstrp = a->sortstrs; + bsortstrp = b->sortstrs; + } else { + asortstrp++; + bsortstrp++; + } + r = zstrcmp(*bsortstrp, *asortstrp, + gf_numsort ? SORTIT_NUMERICALLY : 0); + break; + case GS_SIZE: + r = b->size - a->size; + break; + case GS_ATIME: + r = a->atime - b->atime; +#ifdef GET_ST_ATIME_NSEC + if (!r) + r = a->ansec - b->ansec; +#endif + break; + case GS_MTIME: + r = a->mtime - b->mtime; +#ifdef GET_ST_MTIME_NSEC + if (!r) + r = a->mnsec - b->mnsec; +#endif + break; + case GS_CTIME: + r = a->ctime - b->ctime; +#ifdef GET_ST_CTIME_NSEC + if (!r) + r = a->cnsec - b->cnsec; +#endif + break; + case GS_LINKS: + r = b->links - a->links; + break; + case GS__SIZE: + r = b->_size - a->_size; + break; + case GS__ATIME: + r = a->_atime - b->_atime; +#ifdef GET_ST_ATIME_NSEC + if (!r) + r = a->_ansec - b->_ansec; +#endif + break; + case GS__MTIME: + r = a->_mtime - b->_mtime; +#ifdef GET_ST_MTIME_NSEC + if (!r) + r = a->_mnsec - b->_mnsec; +#endif + break; + case GS__CTIME: + r = a->_ctime - b->_ctime; +#ifdef GET_ST_CTIME_NSEC + if (!r) + r = a->_cnsec - b->_cnsec; +#endif + break; + case GS__LINKS: + r = b->_links - a->_links; + break; + } + if (r) + return (s->tp & GS_DESC) ? + (r < 0L ? 1 : -1) : + (r > 0L ? 1 : -1); + } + return 0; +} + +/* + * Duplicate a list of qualifiers using the `next' linkage (not the + * `or' linkage). Return the head element and set *last (if last non-NULL) + * to point to the last element of the new list. All allocation is on the + * heap (or off the heap?) + */ +static struct qual *dup_qual_list(struct qual *orig, struct qual **lastp) +{ + struct qual *qfirst = NULL, *qlast = NULL; + + while (orig) { + struct qual *qnew = (struct qual *)zhalloc(sizeof(struct qual)); + *qnew = *orig; + qnew->next = qnew->or = NULL; + + if (!qfirst) + qfirst = qnew; + if (qlast) + qlast->next = qnew; + qlast = qnew; + + orig = orig->next; + } + + if (lastp) + *lastp = qlast; + return qfirst; +} + + +/* + * Get a glob string for execution, following e, P or + qualifiers. + * Pointer is character after the e, P or +. + */ + +/**/ +static char * +glob_exec_string(char **sp) +{ + char sav, *tt, *sdata, *s = *sp; + int plus; + + if (s[-1] == '+') { + plus = 0; + tt = itype_end(s, IIDENT, 0); + if (tt == s) + { + zerr("missing identifier after `+'"); + return NULL; + } + } else { + tt = get_strarg(s, &plus); + if (!*tt) + { + zerr("missing end of string"); + return NULL; + } + } + + sav = *tt; + *tt = '\0'; + sdata = dupstring(s + plus); + untokenize(sdata); + *tt = sav; + if (sav) + *sp = tt + plus; + else + *sp = tt; + + return sdata; +} + +/* + * Insert a glob match. + * If there were words to prepend given by the P glob qualifier, do so. + */ +static void +insert_glob_match(LinkList list, LinkNode next, char *data) +{ + if (gf_pre_words) { + LinkNode added; + for (added = firstnode(gf_pre_words); added; incnode(added)) { + next = insertlinknode(list, next, dupstring(getdata(added))); + } + } + + next = insertlinknode(list, next, data); + + if (gf_post_words) { + LinkNode added; + for (added = firstnode(gf_post_words); added; incnode(added)) { + next = insertlinknode(list, next, dupstring(getdata(added))); + } + } +} + +/* + * Return + * 1 if str ends in bare glob qualifiers + * 2 if str ends in non-bare glob qualifiers (#q) + * 0 otherwise. + * + * str is the string to check. + * sl is its length (to avoid recalculation). + * nobareglob is 1 if bare glob qualifiers are not allowed. + * *sp, if sp is not null, will be a pointer to the opening parenthesis. + */ + +/**/ +int +checkglobqual(char *str, int sl, int nobareglob, char **sp) +{ + char *s; + int paren, ret = 1; + + if (str[sl - 1] != Outpar) + return 0; + + /* Check these are really qualifiers, not a set of * + * alternatives or exclusions. We can be more * + * lenient with an explicit (#q) than with a bare * + * set of qualifiers. */ + paren = 0; + for (s = str + sl - 2; *s && (*s != Inpar || paren); s--) { + switch (*s) { + case Outpar: + paren++; /*FALLTHROUGH*/ + case Bar: + if (!zpc_disables[ZPC_BAR]) + nobareglob = 1; + break; + case Tilde: + if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_TILDE]) + nobareglob = 1; + break; + case Inpar: + paren--; + break; + } + if (s == str) + break; + } + if (*s != Inpar) + return 0; + if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_HASH] && s[1] == Pound) { + if (s[2] != 'q') + return 0; + ret = 2; + } else if (nobareglob) + return 0; + + if (sp) + *sp = s; + + return ret; +} + +/* Main entry point to the globbing code for filename globbing. * + * np points to a node in the list which will be expanded * + * into a series of nodes. */ + +/**/ +void +zglob(LinkList list, LinkNode np, int nountok) +{ + struct qual *qo, *qn, *ql; + LinkNode node = prevnode(np); + char *str; /* the pattern */ + int sl; /* length of the pattern */ + Complist q; /* pattern after parsing */ + char *ostr = (char *)getdata(np); /* the pattern before the parser */ + /* chops it up */ + int first = 0, end = -1; /* index of first match to return */ + /* and index+1 of the last match */ + struct globdata saved; /* saved glob state */ + int nobareglob = !isset(BAREGLOBQUAL); + int shortcircuit = 0; /* How many files to match; */ + /* 0 means no limit */ + + if (unset(GLOBOPT) || !haswilds(ostr) || unset(EXECOPT)) { + if (!nountok) + untokenize(ostr); + return; + } + save_globstate(saved); + + str = dupstring(ostr); + uremnode(list, np); + + /* quals will hold the complete list of qualifiers (file static). */ + quals = NULL; + /* + * qualct and qualorct indicate we have qualifiers in the last + * alternative, or a set of alternatives, respectively. They + * are not necessarily an accurate count, however. + */ + qualct = qualorct = 0; + /* + * colonmod is a concatenated list of all colon modifiers found in + * all sets of qualifiers. + */ + colonmod = NULL; + /* The gf_* flags are qualifiers which are applied globally. */ + gf_nullglob = isset(NULLGLOB); + gf_markdirs = isset(MARKDIRS); + gf_listtypes = gf_follow = 0; + gf_noglobdots = unset(GLOBDOTS); + gf_numsort = isset(NUMERICGLOBSORT); + gf_sorts = gf_nsorts = 0; + gf_pre_words = gf_post_words = NULL; + + /* Check for qualifiers */ + while (!nobareglob || + (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_HASH])) { + struct qual *newquals; + char *s; + int sense, qualsfound; + off_t data; + char *sdata, *newcolonmod, *ptr; + int (*func) _((char *, Statptr, off_t, char *)); + + /* + * Initialise state variables for current file pattern. + * newquals is the root for the linked list of all qualifiers. + * qo is the root of the current list of alternatives. + * ql is the end of the current alternative where the `next' will go. + * qn is the current qualifier node to be added. + * + * Here is an attempt at a diagram. An `or' is added horizontally + * to the top line, a `next' at the bottom of the right hand line. + * `qn' is usually NULL unless a new `or' has just been added. + * + * quals -> x -> x -> qo + * | | | + * x x x + * | | + * x ql + * + * In fact, after each loop the complete set is in the file static + * `quals'. Then, if we have a second set of qualifiers, we merge + * the lists together. This is only tricky if one or both have an + * `or' in them; then we need to distribute over all alternatives. + */ + newquals = qo = qn = ql = NULL; + + sl = strlen(str); + if (!(qualsfound = checkglobqual(str, sl, nobareglob, &s))) + break; + + /* Real qualifiers found. */ + nobareglob = 1; + sense = 0; /* bit 0 for match (0)/don't match (1) */ + /* bit 1 for follow links (2), don't (0) */ + data = 0; /* Any numerical argument required */ + sdata = NULL; /* Any list argument required */ + newcolonmod = NULL; /* Contains trailing colon modifiers */ + + str[sl-1] = 0; + *s++ = 0; + if (qualsfound == 2) + s += 2; + for (ptr = s; *ptr; ptr++) + if (*ptr == Dash) + *ptr = '-'; + while (*s && !newcolonmod) { + func = (int (*) _((char *, Statptr, off_t, char *)))0; + if (*s == ',') { + /* A comma separates alternative sets of qualifiers */ + s++; + sense = 0; + if (qualct) { + qn = (struct qual *)hcalloc(sizeof *qn); + qo->or = qn; + qo = qn; + qualorct++; + qualct = 0; + ql = NULL; + } + } else { + switch (*s++) { + case ':': + /* Remaining arguments are history-type * + * colon substitutions, handled separately. */ + newcolonmod = s - 1; + untokenize(newcolonmod); + if (colonmod) { + /* remember we're searching backwards */ + colonmod = dyncat(newcolonmod, colonmod); + } else + colonmod = newcolonmod; + break; + case Hat: + case '^': + /* Toggle sense: go from positive to * + * negative match and vice versa. */ + sense ^= 1; + break; + case '-': + case Dash: + /* Toggle matching of symbolic links */ + sense ^= 2; + break; + case '@': + /* Match symbolic links */ + func = qualislnk; + break; + case Equals: + case '=': + /* Match sockets */ + func = qualissock; + break; + case 'p': + /* Match named pipes */ + func = qualisfifo; + break; + case '/': + /* Match directories */ + func = qualisdir; + break; + case '.': + /* Match regular files */ + func = qualisreg; + break; + case '%': + /* Match special files: block, * + * character or any device */ + if (*s == 'b') + s++, func = qualisblk; + else if (*s == 'c') + s++, func = qualischr; + else + func = qualisdev; + break; + case Star: + /* Match executable plain files */ + func = qualiscom; + break; + case 'R': + /* Match world-readable files */ + func = qualflags; + data = 0004; + break; + case 'W': + /* Match world-writeable files */ + func = qualflags; + data = 0002; + break; + case 'X': + /* Match world-executable files */ + func = qualflags; + data = 0001; + break; + case 'A': + func = qualflags; + data = 0040; + break; + case 'I': + func = qualflags; + data = 0020; + break; + case 'E': + func = qualflags; + data = 0010; + break; + case 'r': + /* Match files readable by current process */ + func = qualflags; + data = 0400; + break; + case 'w': + /* Match files writeable by current process */ + func = qualflags; + data = 0200; + break; + case 'x': + /* Match files executable by current process */ + func = qualflags; + data = 0100; + break; + case 's': + /* Match setuid files */ + func = qualflags; + data = 04000; + break; + case 'S': + /* Match setgid files */ + func = qualflags; + data = 02000; + break; + case 't': + func = qualflags; + data = 01000; + break; + case 'd': + /* Match device files by device number * + * (as given by stat's st_dev element). */ + func = qualdev; + data = qgetnum(&s); + break; + case 'l': + /* Match files with the given no. of hard links */ + func = qualnlink; + g_amc = -1; + goto getrange; + case 'U': + /* Match files owned by effective user ID */ + func = qualuid; + data = geteuid(); + break; + case 'G': + /* Match files owned by effective group ID */ + func = qualgid; + data = getegid(); + break; + case 'u': + /* Match files owned by given user id */ + func = qualuid; + /* either the actual uid... */ + if (idigit(*s)) + data = qgetnum(&s); + else { + /* ... or a user name */ + char sav, *tt; + int arglen; + + /* Find matching delimiters */ + tt = get_strarg(s, &arglen); + if (!*tt) { + zerr("missing delimiter for 'u' glob qualifier"); + data = 0; + } else { +#ifdef USE_GETPWNAM + struct passwd *pw; + sav = *tt; + *tt = '\0'; + + if ((pw = getpwnam(s + arglen))) + data = pw->pw_uid; + else { + zerr("unknown username '%s'", s + arglen); + data = 0; + } + *tt = sav; +#else /* !USE_GETPWNAM */ + sav = *tt; + *tt = '\0'; + zerr("unable to resolve non-numeric username '%s'", s + arglen); + *tt = sav; + data = 0; +#endif /* !USE_GETPWNAM */ + if (sav) + s = tt + arglen; + 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; + int arglen; + + tt = get_strarg(s, &arglen); + if (!*tt) { + zerr("missing delimiter for 'g' glob qualifier"); + data = 0; + } else { +#ifdef USE_GETGRNAM + struct group *gr; + sav = *tt; + *tt = '\0'; + + if ((gr = getgrnam(s + arglen))) + data = gr->gr_gid; + else { + zerr("unknown group"); + data = 0; + } + *tt = sav; +#else /* !USE_GETGRNAM */ + sav = *tt; + zerr("unknown group"); + data = 0; +#endif /* !USE_GETGRNAM */ + if (sav) + s = tt + arglen; + else + s = tt; + } + } + break; + case 'f': + /* Match modes with chmod-spec. */ + func = qualmodeflags; + data = qgetmodespec(&s); + break; + case 'F': + func = qualnonemptydir; + break; + case 'M': + /* Mark directories with a / */ + if ((gf_markdirs = !(sense & 1))) + gf_follow = sense & 2; + break; + case 'T': + /* Mark types in a `ls -F' type fashion */ + if ((gf_listtypes = !(sense & 1))) + gf_follow = sense & 2; + break; + case 'N': + /* Nullglob: remove unmatched patterns. */ + gf_nullglob = !(sense & 1); + break; + case 'D': + /* Glob dots: match leading dots implicitly */ + gf_noglobdots = sense & 1; + break; + case 'n': + /* Numeric glob sort */ + gf_numsort = !(sense & 1); + break; + case 'Y': + { + /* Short circuit: limit number of matches */ + const char *s_saved = s; + shortcircuit = !(sense & 1); + if (shortcircuit) { + /* Parse the argument. */ + data = qgetnum(&s); + if ((shortcircuit = data) != data) { + /* Integer overflow */ + zerr("value too big: Y%s", s_saved); + restore_globstate(saved); + return; + } + } + break; + } + case 'a': + /* Access time in given range */ + g_amc = 0; + func = qualtime; + goto getrange; + case 'm': + /* Modification time in given range */ + g_amc = 1; + func = qualtime; + goto getrange; + case 'c': + /* Inode creation time in given range */ + g_amc = 2; + func = qualtime; + goto getrange; + case 'L': + /* File size (Length) in given range */ + func = qualsize; + g_amc = -1; + /* Get size multiplier */ + g_units = TT_BYTES; + if (*s == 'p' || *s == 'P') + g_units = TT_POSIX_BLOCKS, ++s; + else if (*s == 'k' || *s == 'K') + g_units = TT_KILOBYTES, ++s; + else if (*s == 'm' || *s == 'M') + g_units = TT_MEGABYTES, ++s; +#if defined(ZSH_64_BIT_TYPE) || defined(LONG_IS_64_BIT) + else if (*s == 'g' || *s == 'G') + g_units = TT_GIGABYTES, ++s; + else if (*s == 't' || *s == 'T') + g_units = TT_TERABYTES, ++s; +#endif + getrange: + /* Get time multiplier */ + if (g_amc >= 0) { + g_units = TT_DAYS; + if (*s == 'h') + g_units = TT_HOURS, ++s; + else if (*s == 'm') + g_units = TT_MINS, ++s; + else if (*s == 'w') + g_units = TT_WEEKS, ++s; + else if (*s == 'M') + g_units = TT_MONTHS, ++s; + else if (*s == 's') + g_units = TT_SECONDS, ++s; + else if (*s == 'd') + ++s; + } + /* See if it's greater than, equal to, or less than */ + if ((g_range = *s == '+' ? 1 : IS_DASH(*s) ? -1 : 0)) + ++s; + data = qgetnum(&s); + break; + + case 'o': + case 'O': + { + int t; + char *send; + + if (gf_nsorts == MAX_SORTS) { + zerr("too many glob sort specifiers"); + restore_globstate(saved); + return; + } + + /* usually just one character */ + send = s+1; + switch (*s) { + case 'n': t = GS_NAME; break; + case 'L': t = GS_SIZE; break; + case 'l': t = GS_LINKS; break; + case 'a': t = GS_ATIME; break; + case 'm': t = GS_MTIME; break; + case 'c': t = GS_CTIME; break; + case 'd': t = GS_DEPTH; break; + case 'N': t = GS_NONE; break; + case 'e': + case '+': + { + t = GS_EXEC; + if ((gf_sortlist[gf_nsorts].exec = + glob_exec_string(&send)) == NULL) + { + restore_globstate(saved); + return; + } + break; + } + default: + zerr("unknown sort specifier"); + restore_globstate(saved); + return; + } + if ((sense & 2) && + (t & (GS_SIZE|GS_ATIME|GS_MTIME|GS_CTIME|GS_LINKS))) + t <<= GS_SHIFT; /* HERE: GS_EXEC? */ + if (t != GS_EXEC) { + if (gf_sorts & t) { + zerr("doubled sort specifier"); + restore_globstate(saved); + return; + } + } + gf_sorts |= t; + gf_sortlist[gf_nsorts++].tp = t | + (((sense & 1) ^ (s[-1] == 'O')) ? GS_DESC : 0); + s = send; + break; + } + case '+': + case 'e': + { + char *tt; + + tt = glob_exec_string(&s); + + if (tt == NULL) { + data = 0; + } else { + func = qualsheval; + sdata = tt; + } + break; + } + case '[': + case Inbrack: + { + char *os = --s; + struct value v; + + v.isarr = SCANPM_WANTVALS; + v.pm = NULL; + v.end = -1; + v.flags = 0; + if (getindex(&s, &v, 0) || s == os) { + zerr("invalid subscript"); + restore_globstate(saved); + return; + } + first = v.start; + end = v.end; + break; + } + case 'P': + { + char *tt; + tt = glob_exec_string(&s); + + if (tt != NULL) + { + LinkList *words = sense & 1 ? &gf_post_words : &gf_pre_words; + if (!*words) + *words = newlinklist(); + addlinknode(*words, tt); + } + break; + } + default: + untokenize(--s); + zerr("unknown file attribute: %c", *s); + restore_globstate(saved); + return; + } + } + if (func) { + /* Requested test is performed by function func */ + if (!qn) + qn = (struct qual *)hcalloc(sizeof *qn); + if (ql) + ql->next = qn; + ql = qn; + if (!newquals) + newquals = qo = qn; + qn->func = func; + qn->sense = sense; + qn->data = data; + qn->sdata = sdata; + qn->range = g_range; + qn->units = g_units; + qn->amc = g_amc; + + qn = NULL; + qualct++; + } + if (errflag) { + restore_globstate(saved); + return; + } + } + + if (quals && newquals) { + /* Merge previous group of qualifiers with new set. */ + if (quals->or || newquals->or) { + /* The hard case. */ + struct qual *qorhead = NULL, *qortail = NULL; + /* + * Distribute in the most trivial way, by creating + * all possible combinations of the two sets and chaining + * these into one long set of alternatives given + * by qorhead and qortail. + */ + for (qn = newquals; qn; qn = qn->or) { + for (qo = quals; qo; qo = qo->or) { + struct qual *qfirst, *qlast; + int islast = !qn->or && !qo->or; + /* Generate first set of qualifiers... */ + if (islast) { + /* Last time round: don't bother copying. */ + qfirst = qn; + for (qlast = qfirst; qlast->next; + qlast = qlast->next) + ; + } else + qfirst = dup_qual_list(qn, &qlast); + /* ... link into new `or' chain ... */ + if (!qorhead) + qorhead = qfirst; + if (qortail) + qortail->or = qfirst; + qortail = qfirst; + /* ... and concatenate second set. */ + qlast->next = islast ? qo : dup_qual_list(qo, NULL); + } + } + quals = qorhead; + } else { + /* + * Easy: we can just chain the qualifiers together. + * This is an optimisation; the code above will work, too. + * We retain the original left to right ordering --- remember + * we are searching for sets of qualifiers from the right. + */ + qn = newquals; + for ( ; newquals->next; newquals = newquals->next) + ; + newquals->next = quals; + quals = qn; + } + } else if (newquals) + quals = newquals; + } + q = parsepat(str); + if (!q || errflag) { /* if parsing failed */ + restore_globstate(saved); + if (unset(BADPATTERN)) { + if (!nountok) + untokenize(ostr); + insertlinknode(list, node, ostr); + return; + } + errflag &= ~ERRFLAG_ERROR; + zerr("bad pattern: %s", ostr); + return; + } + if (!gf_nsorts) { + gf_sortlist[0].tp = gf_sorts = (shortcircuit ? GS_NONE : GS_NAME); + gf_nsorts = 1; + } + /* Initialise receptacle for matched files, * + * expanded by insert() where necessary. */ + matchptr = matchbuf = (Gmatch)zalloc((matchsz = 16) * + sizeof(struct gmatch)); + matchct = 0; + pattrystart(); + + /* The actual processing takes place here: matches go into * + * matchbuf. This is the only top-level call to scanner(). */ + scanner(q, shortcircuit); + + /* 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); + zfree(matchbuf, 0); + restore_globstate(saved); + return; + } else { + /* treat as an ordinary string */ + untokenize(matchptr->name = dupstring(ostr)); + matchptr++; + matchct = 1; + } + } + + if (!(gf_sortlist[0].tp & GS_NONE)) { + /* + * Get the strings to use for sorting by executing + * the code chunk. We allow more than one of these. + */ + int nexecs = 0; + struct globsort *sortp; + struct globsort *lastsortp = gf_sortlist + gf_nsorts; + Gmatch gmptr; + + /* First find out if there are any GS_EXECs, counting them. */ + for (sortp = gf_sortlist; sortp < lastsortp; sortp++) + { + if (sortp->tp & GS_EXEC) + nexecs++; + } + + if (nexecs) { + Gmatch tmpptr; + int iexec = 0; + + /* Yes; allocate enough space for strings for each */ + for (tmpptr = matchbuf; tmpptr < matchptr; tmpptr++) + tmpptr->sortstrs = (char **)zhalloc(nexecs*sizeof(char*)); + + /* Loop over each one, incrementing iexec */ + for (sortp = gf_sortlist; sortp < lastsortp; sortp++) + { + /* Ignore unless this is a GS_EXEC */ + if (sortp->tp & GS_EXEC) { + Eprog prog; + + if ((prog = parse_string(sortp->exec, 0))) { + int ef = errflag, lv = lastval; + + /* Parsed OK, execute for each name */ + for (tmpptr = matchbuf; tmpptr < matchptr; tmpptr++) { + setsparam("REPLY", ztrdup(tmpptr->name)); + execode(prog, 1, 0, "globsort"); + if (!errflag) + tmpptr->sortstrs[iexec] = + dupstring(getsparam("REPLY")); + else + tmpptr->sortstrs[iexec] = tmpptr->name; + } + + /* Retain any user interrupt error status */ + errflag = ef | (errflag & ERRFLAG_INT); + lastval = lv; + } else { + /* Failed, let's be safe */ + for (tmpptr = matchbuf; tmpptr < matchptr; tmpptr++) + tmpptr->sortstrs[iexec] = tmpptr->name; + } + + iexec++; + } + } + } + + /* + * Where necessary, create unmetafied version of names + * for comparison. If no Meta characters just point + * to original string. All on heap. + */ + for (gmptr = matchbuf; gmptr < matchptr; gmptr++) + { + if (strchr(gmptr->name, Meta)) + { + int dummy; + gmptr->uname = dupstring(gmptr->name); + unmetafy(gmptr->uname, &dummy); + } else { + gmptr->uname = gmptr->name; + } + } + + /* Sort arguments in to lexical (and possibly numeric) order. * + * This is reversed to facilitate insertion into the list. */ + qsort((void *) & matchbuf[0], matchct, sizeof(struct gmatch), + (int (*) _((const void *, const void *)))gmatchcmp); + } + + if (first < 0) { + first += matchct; + if (first < 0) + first = 0; + } + if (end < 0) + end += matchct + 1; + else if (end > matchct) + end = matchct; + if ((end -= first) > 0) { + if (gf_sortlist[0].tp & GS_NONE) { + /* Match list was never reversed, so insert back to front. */ + matchptr = matchbuf + matchct - first - 1; + while (end-- > 0) { + /* insert matches in the arg list */ + insert_glob_match(list, node, matchptr->name); + matchptr--; + } + } else { + matchptr = matchbuf + matchct - first - end; + while (end-- > 0) { + /* insert matches in the arg list */ + insert_glob_match(list, node, matchptr->name); + matchptr++; + } + } + } else if (!badcshglob && !isset(NOMATCH) && matchct == 1) { + insert_glob_match(list, node, (--matchptr)->name); + } + zfree(matchbuf, 0); + + restore_globstate(saved); +} + +/* Return the trailing character for marking file types */ + +/**/ +mod_export char +file_type(mode_t filemode) +{ + if(S_ISBLK(filemode)) + return '#'; + else if(S_ISCHR(filemode)) + return '%'; + else if(S_ISDIR(filemode)) + return '/'; + else if(S_ISFIFO(filemode)) + return '|'; + else if(S_ISLNK(filemode)) + return '@'; + else if(S_ISREG(filemode)) + return (filemode & S_IXUGO) ? '*' : ' '; + else if(S_ISSOCK(filemode)) + return '='; + else + return '?'; +} + +/* check to see if str is eligible for brace expansion */ + +/**/ +mod_export int +hasbraces(char *str) +{ + char *lbr, *mbr, *comma; + + if (isset(BRACECCL)) { + /* In this case, any properly formed brace expression * + * will match and expand to the characters in between. */ + int bc, c; + + for (bc = 0; (c = *str); ++str) + if (c == Inbrace) { + if (!bc && str[1] == Outbrace) + *str++ = '{', *str = '}'; + else + bc++; + } else if (c == Outbrace) { + if (!bc) + *str = '}'; + else if (!--bc) + return 1; + } + return 0; + } + /* Otherwise we need to look for... */ + lbr = mbr = comma = NULL; + for (;;) { + switch (*str++) { + case Inbrace: + if (!lbr) { + if (bracechardots(str-1, NULL, NULL)) + return 1; + lbr = str - 1; + if (IS_DASH(*str)) + str++; + while (idigit(*str)) + str++; + if (*str == '.' && str[1] == '.') { + str++; str++; + if (IS_DASH(*str)) + str++; + while (idigit(*str)) + str++; + if (*str == Outbrace && + (idigit(lbr[1]) || idigit(str[-1]))) + return 1; + else if (*str == '.' && str[1] == '.') { + str++; str++; + if (IS_DASH(*str)) + str++; + while (idigit(*str)) + 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 redirtab) +{ + char *nam; + struct redir *ff; + int ret = 0; + local_list1(fake); + + /* Stick the name in a list... */ + init_list1(fake, fn->name); + /* ...which undergoes all the usual shell expansions */ + prefork(&fake, isset(MULTIOS) ? 0 : PREFORK_SINGLE, NULL); + /* Globbing is only done for multios. */ + if (!errflag && isset(MULTIOS)) + globlist(&fake, 0); + if (errflag) + return 0; + if (nonempty(&fake) && !nextnode(firstnode(&fake))) { + /* Just one match, the usual case. */ + char *s = peekfirst(&fake); + fn->name = s; + untokenize(s); + if (fn->type == REDIR_MERGEIN || fn->type == REDIR_MERGEOUT) { + if (IS_DASH(s[0]) && !s[1]) + fn->type = REDIR_CLOSE; + else if (s[0] == 'p' && !s[1]) + fn->fd2 = -2; + else { + while (idigit(*s)) + s++; + if (!*s && s > fn->name) + fn->fd2 = zstrtol(fn->name, NULL, 10); + else if (fn->type == REDIR_MERGEIN) + zerr("file number expected"); + else + fn->type = REDIR_ERRWRITE; + } + } + } else if (fn->type == REDIR_MERGEIN) + zerr("file number expected"); + else { + if (fn->type == REDIR_MERGEOUT) + fn->type = REDIR_ERRWRITE; + while ((nam = (char *)ugetnode(&fake))) { + /* Loop over matches, duplicating the * + * redirection for each file found. */ + ff = (struct redir *) zhalloc(sizeof *ff); + *ff = *fn; + ff->name = nam; + addlinknode(redirtab, ff); + ret = 1; + } + } + return ret; +} + +/* + * Check for a brace expansion of the form {<char>..<char>}. + * On input str must be positioned at an Inbrace, but the sequence + * of characters beyond that has not necessarily been checked. + * Return 1 if found else 0. + * + * The other parameters are optionaland if the function returns 1 are + * used to return: + * - *c1p: the first character in the expansion. + * - *c2p: the final character in the expansion. + */ + +/**/ +static int +bracechardots(char *str, convchar_t *c1p, convchar_t *c2p) +{ + convchar_t cstart, cend; + char *pnext = str + 1, *pconv, convstr[2]; + if (itok(*pnext)) { + if (*pnext == Inbrace) + return 0; + convstr[0] = ztokens[*pnext - Pound]; + convstr[1] = '\0'; + pconv = convstr; + } else + pconv = pnext; + MB_METACHARINIT(); + pnext += MB_METACHARLENCONV(pconv, &cstart); + if ( +#ifdef MULTIBYTE_SUPPORT + cstart == WEOF || +#else + !cstart || +#endif + pnext[0] != '.' || pnext[1] != '.') + return 0; + pnext += 2; + if (!*pnext) + return 0; + if (itok(*pnext)) { + if (*pnext == Inbrace) + return 0; + convstr[0] = ztokens[*pnext - Pound]; + convstr[1] = '\0'; + pconv = convstr; + } else + pconv = pnext; + MB_METACHARINIT(); + pnext += MB_METACHARLENCONV(pconv, &cend); + if ( +#ifdef MULTIBYTE_SUPPORT + cend == WEOF || +#else + !cend || +#endif + *pnext != Outbrace) + return 0; + if (c1p) + *c1p = cstart; + if (c2p) + *c2p = cend; + return 1; +} + +/* brace expansion */ + +/**/ +mod_export void +xpandbraces(LinkList list, LinkNode *np) +{ + LinkNode node = (*np), last = prevnode(node); + char *str = (char *)getdata(node), *str3 = str, *str2; + int prev, bc, comma, dotdot; + + for (; *str != Inbrace; str++); + /* First, match up braces and see what we have. */ + for (str2 = str, bc = comma = dotdot = 0; *str2; ++str2) + if (*str2 == Inbrace) + ++bc; + else if (*str2 == Outbrace) { + if (--bc == 0) + break; + } else if (bc == 1) { + if (*str2 == Comma) + ++comma; /* we have {foo,bar} */ + else if (*str2 == '.' && str2[1] == '.') { + dotdot++; /* we have {num1..num2} */ + ++str2; + } + } + 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, *dots2 = NULL; + LinkNode olast = last; + /* Get the first number of the range */ + zlong rstart, rend; + int err = 0, rev = 0, rincr = 1; + int wid1, wid2, wid3, strp; + convchar_t cstart, cend; + + if (bracechardots(str, &cstart, &cend)) { + int lenalloc; + /* + * This is a character range. + */ + if (cend < cstart) { + convchar_t ctmp = cend; + cend = cstart; + cstart = ctmp; + rev = 1; + } + uremnode(list, node); + strp = str - str3; + lenalloc = strp + strlen(str2+1) + 1; + do { +#ifdef MULTIBYTE_SUPPORT + char *ncptr; + int nclen; + mb_charinit(); + ncptr = wcs_nicechar(cend, NULL, NULL); + nclen = strlen(ncptr); + p = zhalloc(lenalloc + nclen); + memcpy(p, str3, strp); + memcpy(p + strp, ncptr, nclen); + strcpy(p + strp + nclen, str2 + 1); +#else + p = zhalloc(lenalloc + 1); + memcpy(p, str3, strp); + sprintf(p + strp, "%c", cend); + strcat(p + strp, str2 + 1); +#endif + insertlinknode(list, last, p); + if (rev) /* decreasing: add in reverse order. */ + last = nextnode(last); + } while (cend-- > cstart); + *np = nextnode(olast); + return; + } + + /* Get the first number of the range */ + rstart = zstrtol(str+1,&dots,10); + rend = 0; + wid1 = (dots - str) - 1; + wid2 = (str2 - dots) - 2; + wid3 = 0; + 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) + err++; + /* check for {num1..num2..incr} */ + if (p != str2) { + wid2 = (p - dots) - 2; + dots2 = p; + if (dotdot == 2 && *p == '.' && p[1] == '.') { + rincr = zstrtol(p+2, &p, 10); + wid3 = p - dots2 - 2; + if (p != str2 || !rincr) + err++; + } else + err++; + } + } + if (!err) { + /* If either no. begins with a zero, pad the output with * + * zeroes. Otherwise, set min width to 0 to suppress them. + * str+1 is the first number in the range, dots+2 the last, + * and dots2+2 is the increment if that's given. */ + /* TODO: sorry about this */ + int minw = (str[1] == '0' || + (IS_DASH(str[1]) && str[2] == '0')) + ? wid1 + : (dots[2] == '0' || + (IS_DASH(dots[2]) && dots[3] == '0')) + ? wid2 + : (dots2 && (dots2[2] == '0' || + (IS_DASH(dots2[2]) && dots2[3] == '0'))) + ? wid3 + : 0; + if (rincr < 0) { + /* Handle negative increment */ + rincr = -rincr; + rev = !rev; + } + if (rstart > rend) { + /* Handle decreasing ranges correctly. */ + zlong rt = rend; + rend = rstart; + rstart = rt; + rev = !rev; + } else if (rincr > 1) { + /* when incr > 1, range is aligned to the highest number of str1, + * compensate for this so that it is aligned to the first number */ + rend -= (rend - rstart) % rincr; + } + uremnode(list, node); + for (; rend >= rstart; rend -= rincr) { + /* Node added in at end, so do highest first */ + p = dupstring(str3); +#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD) + sprintf(p + strp, "%0*lld", minw, rend); +#else + sprintf(p + strp, "%0*ld", minw, (long)rend); +#endif + strcat(p + strp, str2 + 1); + insertlinknode(list, last, p); + if (rev) /* decreasing: add in reverse order. */ + last = nextnode(last); + } + *np = nextnode(olast); + return; + } + } + if (!comma && isset(BRACECCL)) { /* {a-mnop} */ + /* Here we expand each character to a separate node, * + * but also ranges of characters like a-m. ccl is a * + * set of flags saying whether each character is present; * + * the final list is in lexical order. */ + char ccl[256], *p; + unsigned char c1, c2; + unsigned int len, pl; + int lastch = -1; + + uremnode(list, node); + memset(ccl, 0, sizeof(ccl) / sizeof(ccl[0])); + for (p = str + 1; p < str2;) { + if (itok(c1 = *p++)) + c1 = ztokens[c1 - STOUC(Pound)]; + if ((char) c1 == Meta) + c1 = 32 ^ *p++; + if (itok(c2 = *p)) + c2 = ztokens[c2 - STOUC(Pound)]; + if ((char) c2 == Meta) + c2 = 32 ^ p[1]; + if (IS_DASH((char)c1) && lastch >= 0 && + p < str2 && lastch <= (int)c2) { + while (lastch < (int)c2) + ccl[lastch++] = 1; + lastch = -1; + } else + ccl[lastch = c1] = 1; + } + pl = str - str3; + len = pl + strlen(++str2) + 2; + for (p = ccl + 256; p-- > ccl;) + if (*p) { + c1 = p - ccl; + if (imeta(c1)) { + str = hcalloc(len + 1); + str[pl] = Meta; + str[pl+1] = c1 ^ 32; + strcpy(str + pl + 2, str2); + } else { + str = hcalloc(len); + str[pl] = c1; + strcpy(str + pl + 1, str2); + } + memcpy(str, str3, pl); + insertlinknode(list, last, str); + } + *np = nextnode(last); + return; + } + prev = str++ - str3; + str2++; + uremnode(list, node); + node = last; + /* Finally, normal comma expansion * + * str1{foo,bar}str2 -> str1foostr2 str1barstr2. * + * Any number of intervening commas is allowed. */ + for (;;) { + char *zz, *str4; + int cnt; + + for (str4 = str, cnt = 0; cnt || (*str != Comma && *str != + Outbrace); str++) { + if (*str == Inbrace) + cnt++; + else if (*str == Outbrace) + cnt--; + DPUTS(!*str, "BUG: illegal brace expansion"); + } + /* Concatenate the string before the braces (str3), the section * + * just found (str4) and the text after the braces (str2) */ + zz = (char *) hcalloc(prev + (str - str4) + strlen(str2) + 1); + ztrncpy(zz, str3, prev); + strncat(zz, str4, str - str4); + strcat(zz, str2); + /* and add this text to the argument list. */ + insertlinknode(list, node, zz); + incnode(node); + if (*str != Outbrace) + str++; + else + break; + } + *np = nextnode(last); +} + +/* check to see if a matches b (b is not a filename pattern) */ + +/**/ +int +matchpat(char *a, char *b) +{ + Patprog p; + int ret; + + queue_signals(); /* Protect PAT_STATIC */ + + if (!(p = patcompile(b, PAT_STATIC, NULL))) { + zerr("bad pattern: %s", b); + ret = 0; + } else + ret = pattry(p, a); + + unqueue_signals(); + + return ret; +} + +/* 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 imd->ustr + * and finishes e characters in: 0 <= b <= e <= imd->ulen on input + * (yes, empty matches should work). + * + * imd->flags is a set of the SUB_* matches defined in zsh.h from + * SUB_MATCH onwards; the lower parts are ignored. + * + * imd->replstr is the replacement string for a substitution + * + * imd->replstr is metafied and the values put in imd->repllist are metafied. + */ + +/**/ +static char * +get_match_ret(Imatchdata imd, int b, int e) +{ + char buf[80], *r, *p, *rr, *replstr = imd->replstr; + int ll = 0, bl = 0, t = 0, add = 0, fl = imd->flags, i; + + /* Account for b and e referring to unmetafied string */ + for (p = imd->ustr; p < imd->ustr + b; p++) + if (imeta(*p)) + add++; + b += add; + for (; p < imd->ustr + e; p++) + if (imeta(*p)) + add++; + e += add; + + /* Everything now refers to metafied lengths. */ + if (replstr || (fl & SUB_LIST)) { + if (fl & SUB_DOSUBST) { + replstr = dupstring(replstr); + singsub(&replstr); + untokenize(replstr); + } + if ((fl & (SUB_GLOBAL|SUB_LIST)) && imd->repllist) { + /* We are replacing the chunk, just add this to the list */ + Repldata rd = (Repldata) + ((fl & SUB_LIST) ? zalloc(sizeof(*rd)) : zhalloc(sizeof(*rd))); + rd->b = b; + rd->e = e; + rd->replstr = replstr; + if (fl & SUB_LIST) + zaddlinknode(imd->repllist, rd); + else + addlinknode(imd->repllist, rd); + return imd->mstr; + } + ll += strlen(replstr); + } + if (fl & SUB_MATCH) /* matched portion */ + ll += 1 + (e - b); + if (fl & SUB_REST) /* unmatched portion */ + ll += 1 + (imd->mlen - (e - b)); + if (fl & SUB_BIND) { + /* position of start of matched portion */ + sprintf(buf, "%d ", MB_METASTRLEN2END(imd->mstr, 0, imd->mstr+b) + 1); + ll += (bl = strlen(buf)); + } + if (fl & SUB_EIND) { + /* position of end of matched portion */ + sprintf(buf + bl, "%d ", + MB_METASTRLEN2END(imd->mstr, 0, imd->mstr+e) + 1); + ll += (bl = strlen(buf)); + } + if (fl & SUB_LEN) { + /* length of matched portion */ + sprintf(buf + bl, "%d ", MB_METASTRLEN2END(imd->mstr+b, 0, + imd->mstr+e)); + ll += (bl = strlen(buf)); + } + if (bl) + buf[bl - 1] = '\0'; + + rr = r = (char *) hcalloc(ll); + + if (fl & SUB_MATCH) { + /* copy matched portion to new buffer */ + for (i = b, p = imd->mstr + b; i < e; i++) + *rr++ = *p++; + t = 1; + } + if (fl & SUB_REST) { + /* Copy unmatched portion to buffer. If both portions * + * requested, put a space in between (why?) */ + if (t) + *rr++ = ' '; + /* there may be unmatched bits at both beginning and end of string */ + for (i = 0, p = imd->mstr; i < b; i++) + *rr++ = *p++; + if (replstr) + for (p = replstr; *p; ) + *rr++ = *p++; + for (i = e, p = imd->mstr + e; i < imd->mlen; i++) + *rr++ = *p++; + t = 1; + } + *rr = '\0'; + if (bl) { + /* if there was a buffer (with a numeric result), add it; * + * if there was other stuff too, stick in a space first. */ + if (t) + *rr++ = ' '; + strcpy(rr, buf); + } + return r; +} + +static Patprog +compgetmatch(char *pat, int *flp, char **replstrp) +{ + Patprog p; + /* + * Flags to pattern compiler: use static buffer since we only + * have one pattern at a time; we will try the must-match test ourselves, + * so tell the pattern compiler we are scanning. + */ + + /* int patflags = PAT_STATIC|PAT_SCAN|PAT_NOANCH;*/ + + /* Unfortunately, PAT_STATIC doesn't work if we have a replstr with + * something like ${x#...} in it which will be singsub()ed below because + * that would overwrite the pattern buffer. */ + + int patflags = PAT_SCAN|PAT_NOANCH | (*replstrp ? 0 : PAT_STATIC); + + /* + * Search is anchored to the end of the string if we want to match + * it all, or if we are matching at the end of the string and not + * using substrings. + */ + if ((*flp & SUB_ALL) || ((*flp & SUB_END) && !(*flp & SUB_SUBSTR))) + patflags &= ~PAT_NOANCH; + p = patcompile(pat, patflags, NULL); + if (!p) { + zerr("bad pattern: %s", pat); + return NULL; + } + if (*replstrp) { + if (p->patnpar || (p->globend & GF_MATCHREF)) { + /* + * Either backreferences or match references, so we + * need to re-substitute replstr each time round. + */ + *flp |= SUB_DOSUBST; + } else { + singsub(replstrp); + untokenize(*replstrp); + } + } + + return p; +} + +/* + * This is called from paramsubst to get the match for ${foo#bar} etc. + * fl is a set of the SUB_* flags defined in zsh.h + * *sp points to the string we have to modify. The n'th match will be + * returned in *sp. The heap is used to get memory for the result string. + * replstr is the replacement string from a ${.../orig/repl}, in + * which case pat is the original. + * + * n is now ignored unless we are looking for a substring, in + * which case the n'th match from the start is counted such that + * there is no more than one match from each position. + */ + +/**/ +int +getmatch(char **sp, char *pat, int fl, int n, char *replstr) +{ + Patprog p; + + if (!(p = compgetmatch(pat, &fl, &replstr))) + return 1; + + return igetmatch(sp, p, fl, n, replstr, NULL); +} + +/* + * This is the corresponding function for array variables. + * Matching is done with the same pattern on each element. + */ + +/**/ +void +getmatcharr(char ***ap, char *pat, int fl, int n, char *replstr) +{ + char **arr = *ap, **pp; + Patprog p; + + if (!(p = compgetmatch(pat, &fl, &replstr))) + return; + + *ap = pp = hcalloc(sizeof(char *) * (arrlen(arr) + 1)); + while ((*pp = *arr++)) + if (igetmatch(pp, p, fl, n, replstr, NULL)) + pp++; +} + +/* + * Match against str using pattern pp; return a list of + * Repldata matches in the linked list *repllistp; this is + * in permanent storage and to be freed by freematchlist() + */ + +/**/ +mod_export int +getmatchlist(char *str, Patprog p, LinkList *repllistp) +{ + char **sp = &str; + + /* + * We don't care if we have longest or shortest match, but SUB_LONG + * is cheaper since the pattern code does that by default. + * We need SUB_GLOBAL to get all matches. + * We need SUB_SUBSTR to scan through for substrings. + * We need SUB_LIST to activate the special handling of the list + * passed in. + */ + return igetmatch(sp, p, SUB_LONG|SUB_GLOBAL|SUB_SUBSTR|SUB_LIST, + 0, NULL, repllistp); +} + +static void +freerepldata(void *ptr) +{ + zfree(ptr, sizeof(struct repldata)); +} + +/**/ +mod_export void +freematchlist(LinkList repllist) +{ + freelinklist(repllist, freerepldata); +} + +/**/ +static void +set_pat_start(Patprog p, int offs) +{ + /* + * If we are messing around with the test string by advancing up + * it from the start, we need to tell the pattern matcher that + * a start-of-string assertion, i.e. (#s), should fail. Hence + * we test whether the offset of the real start of string from + * the actual start, passed as offs, is zero. + */ + if (offs) + p->flags |= PAT_NOTSTART; + else + p->flags &= ~PAT_NOTSTART; +} + +/**/ +static void +set_pat_end(Patprog p, char null_me) +{ + /* + * If we are messing around with the string by shortening it at the + * tail, we need to tell the pattern matcher that an end-of-string + * assertion, i.e. (#e), should fail. Hence we test whether + * the character null_me about to be zapped is or is not already a null. + */ + if (null_me) + p->flags |= PAT_NOTEND; + else + p->flags &= ~PAT_NOTEND; +} + +/**/ +#ifdef MULTIBYTE_SUPPORT + +/* + * Increment *tp over character which may be multibyte. + * Return number of bytes. + * All unmetafied here. + */ + +/**/ +static int iincchar(char **tp, int left) +{ + char *t = *tp; + int mbclen = mb_charlenconv(t, left, NULL); + *tp = t + mbclen; + + return mbclen; +} + +/**/ +static int +igetmatch(char **sp, Patprog p, int fl, int n, char *replstr, + LinkList *repllistp) +{ + char *s = *sp, *t, *tmatch, *send; + /* + * Note that ioff counts (possibly multibyte) characters in the + * character set (Meta's are not included), while l counts characters in + * the metafied string. + * + * umlen is a counter for (unmetafied) byte lengths---neither characters + * nor raw byte indices; this is simply an optimisation for allocation. + * umltot is the full length of the string in this scheme. + * + * l is the raw string length, used together with any pointers into + * the string (typically t). + */ + int ioff, l = strlen(*sp), matched = 1, umltot = ztrlen(*sp); + int umlen, nmatches; + struct patstralloc patstralloc; + struct imatchdata imd; + + (void)patallocstr(p, s, l, umltot, 1, &patstralloc); + s = patstralloc.alloced; + DPUTS(!s, "forced patallocstr failed"); + send = s + umltot; + + imd.mstr = *sp; + imd.mlen = l; + imd.ustr = s; + imd.ulen = umltot; + imd.flags = fl; + imd.replstr = replstr; + imd.repllist = NULL; + + /* perform must-match test for complex closures */ + if (p->mustoff) + { + char *muststr = (char *)p + p->mustoff; + + matched = 0; + if (p->patmlen <= umltot) + { + for (t = s; t <= send - p->patmlen; t++) + { + if (!memcmp(muststr, t, p->patmlen)) { + matched = 1; + break; + } + } + } + } + + /* in case we used the prog before... */ + p->flags &= ~(PAT_NOTSTART|PAT_NOTEND); + + if (fl & SUB_ALL) { + int i = matched && pattrylen(p, s, umltot, 0, &patstralloc, 0); + if (!i) { + /* Perform under no-match conditions */ + umltot = 0; + imd.replstr = NULL; + } + *sp = get_match_ret(&imd, 0, umltot); + if (! **sp && (((fl & SUB_MATCH) && !i) || ((fl & SUB_REST) && i))) + return 0; + return 1; + } + if (matched) { + /* + * The default behaviour is to match at the start; this + * is modified by SUB_END and SUB_SUBSTR. SUB_END matches + * at the end of the string instead of the start. SUB_SUBSTR + * without SUB_END matches substrings searching from the start; + * with SUB_END it matches substrings searching from the end. + * + * The possibilities are further modified by whether we want the + * longest (SUB_LONG) or shortest possible match. + * + * SUB_START is only used in the case where we are also + * forcing a match at the end (SUB_END with no SUB_SUBSTR, + * with or without SUB_LONG), to indicate we should match + * the entire string. + */ + switch (fl & (SUB_END|SUB_LONG|SUB_SUBSTR)) { + case 0: + case SUB_LONG: + /* + * Largest/smallest possible match at head of string. + * First get the longest match... + */ + if (pattrylen(p, s, umltot, 0, &patstralloc, 0)) { + /* patmatchlen returns unmetafied length in this case */ + int mlen = patmatchlen(); + if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) { + send = s + mlen; + /* + * ... now we know whether it's worth looking for the + * shortest, which we do by brute force. + */ + mb_charinit(); + for (t = s, umlen = 0; t < send; ) { + set_pat_end(p, *t); + if (pattrylen(p, s, umlen, 0, &patstralloc, 0)) { + mlen = patmatchlen(); + break; + } + umlen += iincchar(&t, send - t); + } + } + *sp = get_match_ret(&imd, 0, mlen); + return 1; + } + break; + + case SUB_END: + /* + * Smallest possible match at tail of string. + * As we can only be sure we've got wide characters right + * when going forwards, we need to match at every point + * until we fail and record the last successful match. + * + * It's important that we return the last successful match + * so that match, mbegin, mend and MATCH, MBEGIN, MEND are + * correct. + */ + mb_charinit(); + tmatch = NULL; + for (ioff = 0, t = s, umlen = umltot; t < send; ioff++) { + set_pat_start(p, t-s); + if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) + tmatch = t; + if (fl & SUB_START) + break; + umlen -= iincchar(&t, send - t); + } + if (tmatch) { + *sp = get_match_ret(&imd, tmatch - s, umltot); + return 1; + } + if (!(fl & SUB_START) && pattrylen(p, s + umltot, 0, 0, + &patstralloc, ioff)) { + *sp = get_match_ret(&imd, umltot, umltot); + return 1; + } + break; + + case (SUB_END|SUB_LONG): + /* Largest possible match at tail of string: * + * move forward along string until we get a match. * + * Again there's no optimisation. */ + mb_charinit(); + for (ioff = 0, t = s, umlen = umltot; t <= send ; ioff++) { + set_pat_start(p, t-s); + if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) { + *sp = get_match_ret(&imd, t-s, umltot); + return 1; + } + if (fl & SUB_START) + break; + if (t == send) + break; + umlen -= iincchar(&t, send - t); + } + if (!(fl & SUB_START) && pattrylen(p, send, 0, 0, + &patstralloc, ioff)) { + *sp = get_match_ret(&imd, umltot, umltot); + return 1; + } + break; + + case SUB_SUBSTR: + /* Smallest at start, but matching substrings. */ + set_pat_start(p, l); + if (!(fl & SUB_GLOBAL) && + pattrylen(p, send, 0, 0, &patstralloc, 0) && + !--n) { + *sp = get_match_ret(&imd, 0, 0); + return 1; + } /* fall through */ + case (SUB_SUBSTR|SUB_LONG): + /* longest or smallest at start with substrings */ + t = s; + if (fl & SUB_GLOBAL) { + imd.repllist = (fl & SUB_LIST) ? znewlinklist() : newlinklist(); + if (repllistp) + *repllistp = imd.repllist; + } + ioff = 0; /* offset into string */ + umlen = umltot; + mb_charinit(); + do { + /* loop over all matches for global substitution */ + matched = 0; + for (; t <= send; ioff++) { + /* Find the longest match from this position. */ + set_pat_start(p, t-s); + if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) { + char *mpos = t + patmatchlen(); + if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) { + char *ptr; + int umlen2; + /* + * If searching for the shortest match, + * start with a zero length and increase + * it until we reach the longest possible + * match, accepting the first successful + * match. + */ + for (ptr = t, umlen2 = 0; ptr < mpos;) { + set_pat_end(p, *ptr); + if (pattrylen(p, t, umlen2, 0, + &patstralloc, ioff)) { + mpos = t + patmatchlen(); + break; + } + umlen2 += iincchar(&ptr, mpos - ptr); + } + } + if (!--n || (n <= 0 && (fl & SUB_GLOBAL))) { + *sp = get_match_ret(&imd, t-s, mpos-s); + if (mpos == t) + mpos += mb_charlenconv(mpos, send - mpos, NULL); + } + if (!(fl & SUB_GLOBAL)) { + if (n) { + /* + * Looking for a later match: in this case, + * we can continue looking for matches from + * the next character, even if it overlaps + * with what we just found. + */ + umlen -= iincchar(&t, send - t); + continue; + } else { + return 1; + } + } + /* + * For a global match, we need to skip the stuff + * which is already marked for replacement. + */ + matched = 1; + if (t == send) + break; + while (t < mpos) { + ioff++; + umlen -= iincchar(&t, send - t); + } + break; + } + if (t == send) + break; + umlen -= iincchar(&t, send - t); + } + } while (matched && t < send); + /* + * check if we can match a blank string, if so do it + * at the start. Goodness knows if this is a good idea + * with global substitution, so it doesn't happen. + */ + set_pat_start(p, l); + if ((fl & (SUB_LONG|SUB_GLOBAL)) == SUB_LONG && + pattrylen(p, send, 0, 0, &patstralloc, 0) && !--n) { + *sp = get_match_ret(&imd, 0, 0); + return 1; + } + break; + + case (SUB_END|SUB_SUBSTR): + case (SUB_END|SUB_LONG|SUB_SUBSTR): + /* Longest/shortest at end, matching substrings. */ + if (!(fl & SUB_LONG)) { + set_pat_start(p, l); + if (pattrylen(p, send, 0, 0, &patstralloc, umltot) && + !--n) { + *sp = get_match_ret(&imd, umltot, umltot); + return 1; + } + } + /* + * If multibyte characters are present we need to start from the + * beginning. This is a bit unpleasant because we can't tell in + * advance how many times it will match and from where, so if n is + * greater then 1 we will need to count the number of times it + * matched and then go through again until we reach the right + * point. (Either that or record every single match in a list, + * which isn't stupid; it involves more memory management at this + * level but less use of the pattern matcher.) + */ + nmatches = 0; + tmatch = NULL; + mb_charinit(); + for (ioff = 0, t = s, umlen = umltot; t < send; ioff++) { + set_pat_start(p, t-s); + if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) { + nmatches++; + tmatch = t; + } + umlen -= iincchar(&t, send - t); + } + if (nmatches) { + char *mpos; + if (n > 1) { + /* + * We need to find the n'th last match. + */ + n = nmatches - n; + mb_charinit(); + for (ioff = 0, t = s, umlen = umltot; t < send; ioff++) { + set_pat_start(p, t-s); + if (pattrylen(p, t, umlen, 0, &patstralloc, ioff) && + !n--) { + tmatch = t; + break; + } + umlen -= iincchar(&t, send - t); + } + } + mpos = tmatch + patmatchlen(); + /* Look for the shortest match if necessary */ + if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) { + for (t = tmatch, umlen = 0; t < mpos; ) { + set_pat_end(p, *t); + if (pattrylen(p, tmatch, umlen, 0, + &patstralloc, ioff)) { + mpos = tmatch + patmatchlen(); + break; + } + umlen += iincchar(&t, mpos - t); + } + } + *sp = get_match_ret(&imd, tmatch-s, mpos-s); + return 1; + } + set_pat_start(p, l); + if ((fl & SUB_LONG) && pattrylen(p, send, 0, 0, + &patstralloc, umltot) && + !--n) { + *sp = get_match_ret(&imd, umltot, umltot); + return 1; + } + break; + } + } + + if (imd.repllist && nonempty(imd.repllist)) { + /* Put all the bits of a global search and replace together. */ + LinkNode nd; + Repldata rd; + int lleft; + char *ptr, *start; + int i; + + /* + * Use metafied string again. + * Results from get_match_ret in repllist are all metafied. + */ + s = *sp; + if (!(fl & SUB_LIST)) { + lleft = 0; /* size of returned string */ + i = 0; /* start of last chunk we got from *sp */ + for (nd = firstnode(imd.repllist); nd; incnode(nd)) { + rd = (Repldata) getdata(nd); + lleft += rd->b - i; /* previous chunk of *sp */ + lleft += strlen(rd->replstr); /* the replaced bit */ + i = rd->e; /* start of next chunk of *sp */ + } + lleft += l - i; /* final chunk from *sp */ + start = t = zhalloc(lleft+1); + i = 0; + for (nd = firstnode(imd.repllist); nd; incnode(nd)) { + rd = (Repldata) getdata(nd); + memcpy(t, s + i, rd->b - i); + t += rd->b - i; + ptr = rd->replstr; + while (*ptr) + *t++ = *ptr++; + i = rd->e; + } + memcpy(t, s + i, l - i); + start[lleft] = '\0'; + *sp = (char *)start; + } + return 1; + } + if (fl & SUB_LIST) { /* safety: don't think this can happen */ + return 0; + } + + /* munge the whole string: no match, so no replstr */ + imd.replstr = NULL; + imd.repllist = NULL; + *sp = get_match_ret(&imd, 0, 0); + return (fl & SUB_RETFAIL) ? 0 : 1; +} + +/**/ +#else + +/* + * Increment pointer which may be on a Meta (x is a pointer variable), + * returning the incremented value (i.e. like pre-increment). + */ +#define METAINC(x) ((x) += (*(x) == Meta) ? 2 : 1) + +/**/ +static int +igetmatch(char **sp, Patprog p, int fl, int n, char *replstr, + LinkList *repllistp) +{ + char *s = *sp, *t, *send; + /* + * Note that ioff and uml count characters in the character + * set (Meta's are not included), while l counts characters in the + * metafied string. umlen is a counter for (unmetafied) character + * lengths. + */ + int ioff, l = strlen(*sp), uml = ztrlen(*sp), matched = 1, umlen; + struct patstralloc patstralloc; + struct imatchdata imd; + + (void)patallocstr(p, s, l, uml, 1, &patstralloc); + s = patstralloc.alloced; + DPUTS(!s, "forced patallocstr failed"); + send = s + uml; + + imd.mstr = *sp; + imd.mlen = l; + imd.ustr = s; + imd.ulen = uml; + imd.flags = fl; + imd.replstr = replstr; + imd.repllist = NULL; + + /* perform must-match test for complex closures */ + if (p->mustoff) + { + char *muststr = (char *)p + p->mustoff; + + matched = 0; + if (p->patmlen <= uml) + { + for (t = s; t <= send - p->patmlen; t++) + { + if (!memcmp(muststr, t, p->patmlen)) { + matched = 1; + break; + } + } + } + } + + /* in case we used the prog before... */ + p->flags &= ~(PAT_NOTSTART|PAT_NOTEND); + + if (fl & SUB_ALL) { + int i = matched && pattrylen(p, s, uml, 0, &patstralloc, 0); + if (!i) + imd.replstr = NULL; + *sp = get_match_ret(&imd, 0, i ? l : 0); + if (! **sp && (((fl & SUB_MATCH) && !i) || ((fl & SUB_REST) && i))) + return 0; + return 1; + } + if (matched) { + switch (fl & (SUB_END|SUB_LONG|SUB_SUBSTR)) { + case 0: + case SUB_LONG: + /* + * Largest/smallest possible match at head of string. + * First get the longest match... + */ + if (pattrylen(p, s, uml, 0, &patstralloc, 0)) { + /* patmatchlen returns metafied length, as we need */ + int mlen = patmatchlen(); + if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) { + send = s + mlen; + /* + * ... now we know whether it's worth looking for the + * shortest, which we do by brute force. + */ + for (t = s, umlen = 0; t < s + mlen; METAINC(t), umlen++) { + set_pat_end(p, *t); + if (pattrylen(p, s, umlen, 0, &patstralloc, 0)) { + mlen = patmatchlen(); + break; + } + } + } + *sp = get_match_ret(&imd, 0, mlen); + return 1; + } + break; + + case SUB_END: + /* Smallest possible match at tail of string: * + * move back down string until we get a match. * + * There's no optimization here. */ + for (ioff = uml, t = send, umlen = 0; t >= s; + t--, ioff--, umlen++) { + set_pat_start(p, t-s); + if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) { + *sp = get_match_ret(&imd, t - s, uml); + return 1; + } + } + break; + + case (SUB_END|SUB_LONG): + /* Largest possible match at tail of string: * + * move forward along string until we get a match. * + * Again there's no optimisation. */ + for (ioff = 0, t = s, umlen = uml; t < send; + ioff++, t++, umlen--) { + set_pat_start(p, t-s); + if (pattrylen(p, t, send - t, umlen, &patstralloc, ioff)) { + *sp = get_match_ret(&imd, t-s, uml); + return 1; + } + } + break; + + case SUB_SUBSTR: + /* Smallest at start, but matching substrings. */ + set_pat_start(p, l); + if (!(fl & SUB_GLOBAL) && + pattrylen(p, send, 0, 0, &patstralloc, 0) && !--n) { + *sp = get_match_ret(&imd, 0, 0); + return 1; + } /* fall through */ + case (SUB_SUBSTR|SUB_LONG): + /* longest or smallest at start with substrings */ + t = s; + if (fl & SUB_GLOBAL) { + imd.repllist = newlinklist(); + if (repllistp) + *repllistp = imd.repllist; + } + ioff = 0; /* offset into string */ + umlen = uml; + do { + /* loop over all matches for global substitution */ + matched = 0; + for (; t < send; t++, ioff++, umlen--) { + /* Find the longest match from this position. */ + set_pat_start(p, t-s); + if (pattrylen(p, t, send - t, umlen, &patstralloc, ioff)) { + char *mpos = t + patmatchlen(); + if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) { + char *ptr; + int umlen2; + for (ptr = t, umlen2 = 0; ptr < mpos; + ptr++, umlen2++) { + set_pat_end(p, *ptr); + if (pattrylen(p, t, ptr - t, umlen2, + &patstralloc, ioff)) { + mpos = t + patmatchlen(); + break; + } + } + } + if (!--n || (n <= 0 && (fl & SUB_GLOBAL))) { + *sp = get_match_ret(&imd, t-s, mpos-s); + if (mpos == t) + mpos++; + } + if (!(fl & SUB_GLOBAL)) { + if (n) { + /* + * Looking for a later match: in this case, + * we can continue looking for matches from + * the next character, even if it overlaps + * with what we just found. + */ + continue; + } else { + return 1; + } + } + /* + * For a global match, we need to skip the stuff + * which is already marked for replacement. + */ + matched = 1; + while (t < mpos) { + ioff++; + umlen--; + t++; + } + break; + } + } + } while (matched); + /* + * check if we can match a blank string, if so do it + * at the start. Goodness knows if this is a good idea + * with global substitution, so it doesn't happen. + */ + set_pat_start(p, l); + if ((fl & (SUB_LONG|SUB_GLOBAL)) == SUB_LONG && + pattrylen(p, send, 0, 0, &patstralloc, 0) && !--n) { + *sp = get_match_ret(&imd, 0, 0); + return 1; + } + break; + + case (SUB_END|SUB_SUBSTR): + case (SUB_END|SUB_LONG|SUB_SUBSTR): + /* Longest/shortest at end, matching substrings. */ + if (!(fl & SUB_LONG)) { + set_pat_start(p, l); + if (pattrylen(p, send, 0, 0, &patstralloc, uml) && !--n) { + *sp = get_match_ret(&imd, uml, uml); + return 1; + } + } + for (ioff = uml - 1, t = send - 1, umlen = 1; t >= s; + t--, ioff--, umlen++) { + set_pat_start(p, t-s); + if (pattrylen(p, t, send - t, umlen, &patstralloc, ioff) && + !--n) { + /* Found the longest match */ + char *mpos = t + patmatchlen(); + if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) { + char *ptr; + int umlen2; + for (ptr = t, umlen2 = 0; ptr < mpos; + ptr++, umlen2++) { + set_pat_end(p, *ptr); + if (pattrylen(p, t, umlen2, 0, &patstralloc, + ioff)) { + mpos = t + patmatchlen(); + break; + } + } + } + *sp = get_match_ret(&imd, t-s, mpos-s); + return 1; + } + } + set_pat_start(p, l); + if ((fl & SUB_LONG) && pattrylen(p, send, 0, 0, + &patstralloc, uml) && + !--n) { + *sp = get_match_ret(&imd, uml, uml); + return 1; + } + break; + } + } + + if (imd.repllist && nonempty(imd.repllist)) { + /* Put all the bits of a global search and replace together. */ + LinkNode nd; + Repldata rd; + int lleft = 0; /* size of returned string */ + char *ptr, *start; + int i; + + /* + * Use metafied string again. + * Results from get_match_ret in repllist are all metafied. + */ + s = *sp; + i = 0; /* start of last chunk we got from *sp */ + for (nd = firstnode(imd.repllist); nd; incnode(nd)) { + rd = (Repldata) getdata(nd); + lleft += rd->b - i; /* previous chunk of *sp */ + lleft += strlen(rd->replstr); /* the replaced bit */ + i = rd->e; /* start of next chunk of *sp */ + } + lleft += l - i; /* final chunk from *sp */ + start = t = zhalloc(lleft+1); + i = 0; + for (nd = firstnode(imd.repllist); nd; incnode(nd)) { + rd = (Repldata) getdata(nd); + memcpy(t, s + i, rd->b - i); + t += rd->b - i; + ptr = rd->replstr; + while (*ptr) + *t++ = *ptr++; + i = rd->e; + } + memcpy(t, s + i, l - i); + start[lleft] = '\0'; + *sp = (char *)start; + return 1; + } + + /* munge the whole string: no match, so no replstr */ + imd.replstr = NULL; + imd.repllist = NULL; + *sp = get_match_ret(&imd, 0, 0); + return 1; +} + +/**/ +#endif /* MULTIBYTE_SUPPORT */ + +/* blindly turn a string into a tokenised expression without lexing */ + +/**/ +mod_export void +tokenize(char *s) +{ + zshtokenize(s, 0); +} + +/* + * shtokenize is used when we tokenize a string with GLOB_SUBST set. + * In that case we need to retain backslashes when we turn the + * pattern back into a string, so that the string is not + * modified if it failed to match a pattern. + * + * It may be modified by the effect of SH_GLOB which turns off + * various zsh-specific options. + */ + +/**/ +mod_export void +shtokenize(char *s) +{ + int flags = ZSHTOK_SUBST; + if (isset(SHGLOB)) + flags |= ZSHTOK_SHGLOB; + zshtokenize(s, flags); +} + +/**/ +static void +zshtokenize(char *s, int flags) +{ + char *t; + int bslash = 0; + + for (; *s; s++) { + cont: + switch (*s) { + case Meta: + /* skip both Meta and following character */ + s++; + break; + case Bnull: + case Bnullkeep: + case '\\': + if (bslash) { + s[-1] = (flags & ZSHTOK_SUBST) ? Bnullkeep : Bnull; + break; + } + bslash = 1; + continue; + case '<': + if (flags & ZSHTOK_SHGLOB) + break; + if (bslash) { + s[-1] = (flags & ZSHTOK_SUBST) ? Bnullkeep : Bnull; + break; + } + t = s; + while (idigit(*++s)); + if (!IS_DASH(*s)) + goto cont; + while (idigit(*++s)); + if (*s != '>') + goto cont; + *t = Inang; + *s = Outang; + break; + case '(': + case '|': + case ')': + if (flags & ZSHTOK_SHGLOB) + break; + /*FALLTHROUGH*/ + case '>': + case '^': + case '#': + case '~': + case '[': + case ']': + case '*': + case '?': + case '=': + case '-': + case '!': + for (t = ztokens; *t; t++) { + if (*t == *s) { + if (bslash) + s[-1] = (flags & ZSHTOK_SUBST) ? Bnullkeep : Bnull; + else + *s = (t - ztokens) + Pound; + break; + } + } + break; + } + bslash = 0; + } +} + +/* remove unnecessary Nulargs */ + +/**/ +mod_export void +remnulargs(char *s) +{ + if (*s) { + char *o = s, c; + + while ((c = *s++)) + if (c == Bnullkeep) { + /* + * An active backslash that needs to be turned back into + * a real backslash for output. However, we don't + * do that yet since we need to ignore it during + * pattern matching. + */ + continue; + } else if (inull(c)) { + char *t = s - 1; + + while ((c = *s++)) { + if (c == Bnullkeep) + *t++ = '\\'; + else if (!inull(c)) + *t++ = c; + } + *t = '\0'; + if (!*o) { + o[0] = Nularg; + o[1] = '\0'; + } + break; + } + } +} + +/* qualifier functions: mostly self-explanatory, see glob(). */ + +/* device number */ + +/**/ +static int +qualdev(UNUSED(char *name), struct stat *buf, off_t dv, UNUSED(char *dummy)) +{ + return (off_t)buf->st_dev == dv; +} + +/* number of hard links to file */ + +/**/ +static int +qualnlink(UNUSED(char *name), struct stat *buf, off_t ct, UNUSED(char *dummy)) +{ + return (g_range < 0 ? buf->st_nlink < ct : + g_range > 0 ? buf->st_nlink > ct : + buf->st_nlink == ct); +} + +/* user ID */ + +/**/ +static int +qualuid(UNUSED(char *name), struct stat *buf, off_t uid, UNUSED(char *dummy)) +{ + return buf->st_uid == uid; +} + +/* group ID */ + +/**/ +static int +qualgid(UNUSED(char *name), struct stat *buf, off_t gid, UNUSED(char *dummy)) +{ + return buf->st_gid == gid; +} + +/* device special file? */ + +/**/ +static int +qualisdev(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy)) +{ + return S_ISBLK(buf->st_mode) || S_ISCHR(buf->st_mode); +} + +/* block special file? */ + +/**/ +static int +qualisblk(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy)) +{ + return S_ISBLK(buf->st_mode); +} + +/* character special file? */ + +/**/ +static int +qualischr(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy)) +{ + return S_ISCHR(buf->st_mode); +} + +/* directory? */ + +/**/ +static int +qualisdir(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy)) +{ + return S_ISDIR(buf->st_mode); +} + +/* FIFO? */ + +/**/ +static int +qualisfifo(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy)) +{ + return S_ISFIFO(buf->st_mode); +} + +/* symbolic link? */ + +/**/ +static int +qualislnk(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy)) +{ + return S_ISLNK(buf->st_mode); +} + +/* regular file? */ + +/**/ +static int +qualisreg(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy)) +{ + return S_ISREG(buf->st_mode); +} + +/* socket? */ + +/**/ +static int +qualissock(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy)) +{ + return S_ISSOCK(buf->st_mode); +} + +/* given flag is set in mode */ + +/**/ +static int +qualflags(UNUSED(char *name), struct stat *buf, off_t mod, UNUSED(char *dummy)) +{ + return mode_to_octal(buf->st_mode) & mod; +} + +/* mode matches specification */ + +/**/ +static int +qualmodeflags(UNUSED(char *name), struct stat *buf, off_t mod, UNUSED(char *dummy)) +{ + long v = mode_to_octal(buf->st_mode), y = mod & 07777, n = mod >> 12; + + return ((v & y) == y && !(v & n)); +} + +/* regular executable file? */ + +/**/ +static int +qualiscom(UNUSED(char *name), struct stat *buf, UNUSED(off_t mod), UNUSED(char *dummy)) +{ + return S_ISREG(buf->st_mode) && (buf->st_mode & S_IXUGO); +} + +/* size in required range? */ + +/**/ +static int +qualsize(UNUSED(char *name), struct stat *buf, off_t size, UNUSED(char *dummy)) +{ +#if defined(ZSH_64_BIT_TYPE) || defined(LONG_IS_64_BIT) +# define QS_CAST_SIZE() + zlong scaled = buf->st_size; +#else +# define QS_CAST_SIZE() (unsigned long) + unsigned long scaled = (unsigned long)buf->st_size; +#endif + + switch (g_units) { + case TT_POSIX_BLOCKS: + scaled += 511l; + scaled /= 512l; + break; + case TT_KILOBYTES: + scaled += 1023l; + scaled /= 1024l; + break; + case TT_MEGABYTES: + scaled += 1048575l; + scaled /= 1048576l; + break; +#if defined(ZSH_64_BIT_TYPE) || defined(LONG_IS_64_BIT) + case TT_GIGABYTES: + scaled += ZLONG_CONST(1073741823); + scaled /= ZLONG_CONST(1073741824); + break; + case TT_TERABYTES: + scaled += ZLONG_CONST(1099511627775); + scaled /= ZLONG_CONST(1099511627776); + break; +#endif + } + + return (g_range < 0 ? scaled < QS_CAST_SIZE() size : + g_range > 0 ? scaled > QS_CAST_SIZE() size : + scaled == QS_CAST_SIZE() size); +#undef QS_CAST_SIZE +} + +/* time in required range? */ + +/**/ +static int +qualtime(UNUSED(char *name), struct stat *buf, off_t days, UNUSED(char *dummy)) +{ + time_t now, diff; + + time(&now); + diff = now - (g_amc == 0 ? buf->st_atime : g_amc == 1 ? buf->st_mtime : + buf->st_ctime); + /* handle multipliers indicating units */ + switch (g_units) { + case TT_DAYS: + diff /= 86400l; + break; + case TT_HOURS: + diff /= 3600l; + break; + case TT_MINS: + diff /= 60l; + break; + case TT_WEEKS: + diff /= 604800l; + break; + case TT_MONTHS: + diff /= 2592000l; + break; + } + + return (g_range < 0 ? diff < days : + g_range > 0 ? diff > days : + diff == days); +} + +/* evaluate a string */ + +/**/ +static int +qualsheval(char *name, UNUSED(struct stat *buf), UNUSED(off_t days), char *str) +{ + Eprog prog; + + if ((prog = parse_string(str, 0))) { + int ef = errflag, lv = lastval, ret; + int cshglob = badcshglob; + + unsetparam("reply"); + setsparam("REPLY", ztrdup(name)); + badcshglob = 0; + + execode(prog, 1, 0, "globqual"); + + if ((ret = lastval)) + badcshglob |= cshglob; + /* Retain any user interrupt error status */ + errflag = ef | (errflag & ERRFLAG_INT); + lastval = lv; + + if (!(inserts = getaparam("reply")) && + !(inserts = gethparam("reply"))) { + char *tmp; + + if ((tmp = getsparam("reply")) || (tmp = getsparam("REPLY"))) { + static char *tmparr[2]; + + tmparr[0] = tmp; + tmparr[1] = NULL; + + inserts = tmparr; + } + } + + return !ret; + } + return 0; +} + +/**/ +static int +qualnonemptydir(char *name, struct stat *buf, UNUSED(off_t days), UNUSED(char *str)) +{ + DIR *dirh; + struct dirent *de; + int unamelen; + char *uname = unmetafy(dupstring(name), &unamelen); + + if (!S_ISDIR(buf->st_mode)) + return 0; + + if (buf->st_nlink > 2) + return 1; + + if (!(dirh = opendir(uname))) + return 0; + + while ((de = readdir(dirh))) { + if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) { + closedir(dirh); + return 1; + } + } + + closedir(dirh); + return 0; +} |
