/* * 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 existence. * * 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]; int check_for_being_a_directory = 0; 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. */ if (st) { buf[pathpos - pathbufcwd] = '.'; buf[pathpos - pathbufcwd + 1] = '\0'; l = 0; } else { check_for_being_a_directory = 1; } } unmetafy(buf, NULL); if (st) { return l ? lstat(buf, st) : stat(buf, st); } else if (check_for_being_a_directory) { struct stat tmp; if (stat(buf, &tmp)) return -1; return S_ISDIR(tmp.st_mode) ? 0 : -1; } else { char lbuf[1]; /* If it exists, signal success. */ if (access(buf, F_OK) == 0) return 0; /* Would a dangling symlink be good enough? */ if (l == 0) return -1; /* Is it a dangling symlink? */ if (readlink(buf, lbuf, 1) >= 0) return 0; /* Guess it doesn't exist, then. */ return -1; } } /* 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; if (statfullpath(s, &buf, 1)) { unqueue_signals(); return; } else { checked = statted = 1; } mode = buf.st_mode; if (gf_follow) { if (!S_ISLNK(mode) || statfullpath(s, &buf2, 0)) memcpy(&buf2, &buf, sizeof(buf)); statted |= 2; mode = buf2.st_mode; } if (gf_listtypes || S_ISDIR(mode)) { int ll = strlen(s); news = (char *) hcalloc(ll + 2); strcpy(news, s); news[ll] = file_type(mode); news[ll + 1] = '\0'; } } if (qualct || qualorct) { /* Go through the qualifiers, rejecting the file if appropriate */ struct qual *qo, *qn; if (!statted && statfullpath(s, &buf, 1)) { unqueue_signals(); return; } news = dyncat(pathbuf, news); statted = 1; qo = quals; for (qn = qo; qn && qn->func;) { g_range = qn->range; g_amc = qn->amc; g_units = qn->units; if ((qn->sense & 2) && !(statted & 2)) { /* If (sense & 2), we're following links */ if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0)) memcpy(&buf2, &buf, sizeof(buf)); statted |= 2; } bp = (qn->sense & 2) ? &buf2 : &buf; /* Reject the file if the function returned zero * * and the sense was positive (sense&1 == 0), or * * vice versa. */ if ((!((qn->func) (news, bp, qn->data, qn->sdata)) ^ qn->sense) & 1) { /* Try next alternative, or return if there are no more */ if (!(qo = qo->or)) { unqueue_signals(); return; } qn = qo; continue; } qn = qn->next; } } else if (!checked) { if (statfullpath(s, NULL, 1)) { unqueue_signals(); return; } 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, 1); } 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 matches 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 {..}. * 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; } if (replstr) 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'; if (ll == 0) return NULL; 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; set_pat_start(p, l); if (pattrylen(p, send, 0, 0, &patstralloc, umltot) && !--n) { *sp = get_match_ret(&imd, umltot, umltot); return 1; } 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. */ { 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) { /* Default is to match at the start; see comment in MULTIBYTE above */ 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 = (fl & SUB_LIST) ? znewlinklist() : 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; if (t == send) break; while (t < mpos) { ioff++; umlen--; t++; } break; } if (t == send) break; } } 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. */ { 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; if (fl & SUB_LIST) return 1; 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 (fl & SUB_RETFAIL) ? 0 : 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; }