mirror of
git://git.code.sf.net/p/zsh/code
synced 2024-11-19 13:33:52 +01:00
2801 lines
66 KiB
C
2801 lines
66 KiB
C
/*
|
|
* glob.c - filename generation
|
|
*
|
|
* This file is part of zsh, the Z shell.
|
|
*
|
|
* Copyright (c) 1992-1997 Paul Falstad
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, without written agreement and without
|
|
* license or royalty fees, to use, copy, modify, and distribute this
|
|
* software and to distribute modified versions of this software for any
|
|
* purpose, provided that the above copyright notice and the following
|
|
* two paragraphs appear in all copies of this software.
|
|
*
|
|
* In no event shall Paul Falstad or the Zsh Development Group be liable
|
|
* to any party for direct, indirect, special, incidental, or consequential
|
|
* damages arising out of the use of this software and its documentation,
|
|
* even if Paul Falstad and the Zsh Development Group have been advised of
|
|
* the possibility of such damage.
|
|
*
|
|
* Paul Falstad and the Zsh Development Group specifically disclaim any
|
|
* warranties, including, but not limited to, the implied warranties of
|
|
* merchantability and fitness for a particular purpose. The software
|
|
* provided hereunder is on an "as is" basis, and Paul Falstad and the
|
|
* Zsh Development Group have no obligation to provide maintenance,
|
|
* support, updates, enhancements, or modifications.
|
|
*
|
|
*/
|
|
|
|
#include "zsh.mdh"
|
|
#include "glob.pro"
|
|
|
|
/* flag for CSHNULLGLOB */
|
|
|
|
/**/
|
|
int badcshglob;
|
|
|
|
static int mode; /* != 0 if we are parsing glob patterns */
|
|
static int pathpos; /* position in pathbuf */
|
|
static int matchsz; /* size of matchbuf */
|
|
static int matchct; /* number of matches found */
|
|
static char *pathbuf; /* pathname buffer */
|
|
static int pathbufsz; /* size of pathbuf */
|
|
static int pathbufcwd; /* where did we chdir()'ed */
|
|
static char **matchbuf; /* array of matches */
|
|
static char **matchptr; /* &matchbuf[matchct] */
|
|
static char *colonmod; /* colon modifiers in qualifier list */
|
|
|
|
typedef struct stat *Statptr; /* This makes the Ultrix compiler happy. Go figure. */
|
|
|
|
/* modifier for unit conversions */
|
|
|
|
#define TT_DAYS 0
|
|
#define TT_HOURS 1
|
|
#define TT_MINS 2
|
|
#define TT_WEEKS 3
|
|
#define TT_MONTHS 4
|
|
|
|
#define TT_BYTES 0
|
|
#define TT_POSIX_BLOCKS 1
|
|
#define TT_KILOBYTES 2
|
|
#define TT_MEGABYTES 3
|
|
|
|
typedef int (*TestMatchFunc) _((struct stat *, long));
|
|
|
|
struct qual {
|
|
struct qual *next; /* Next qualifier, must match */
|
|
struct qual *or; /* Alternative set of qualifiers to match */
|
|
TestMatchFunc func; /* Function to call to test match */
|
|
long data; /* Argument passed to function */
|
|
int sense; /* Whether asserting or negating */
|
|
int amc; /* Flag for which time to test (a, m, c) */
|
|
int range; /* Whether to test <, > or = (as per signum) */
|
|
int units; /* Multiplier for time or size, respectively */
|
|
};
|
|
|
|
/* Qualifiers pertaining to current pattern */
|
|
static struct qual *quals;
|
|
|
|
/* Other state values for current pattern */
|
|
static int qualct, qualorct;
|
|
static int range, amc, units;
|
|
static int gf_nullglob, gf_markdirs, gf_noglobdots, gf_listtypes, gf_follow;
|
|
|
|
/* Prefix, suffix for doing zle trickery */
|
|
|
|
/**/
|
|
char *glob_pre, *glob_suf;
|
|
|
|
/* pathname component in filename patterns */
|
|
|
|
struct complist {
|
|
Complist next;
|
|
Comp comp;
|
|
int closure; /* 1 if this is a (foo/)# */
|
|
int follow; /* 1 to go thru symlinks */
|
|
};
|
|
struct comp {
|
|
Comp left, right, next, exclude;
|
|
char *str;
|
|
int stat;
|
|
};
|
|
|
|
/* Type of Comp: a closure with one or two #'s, the end of a *
|
|
* pattern or path component, a piece of path to be added. */
|
|
#define C_ONEHASH 1
|
|
#define C_TWOHASH 2
|
|
#define C_OPTIONAL 4
|
|
#define C_STAR 8
|
|
#define C_CLOSURE (C_ONEHASH|C_TWOHASH|C_OPTIONAL|C_STAR)
|
|
#define C_LAST 16
|
|
#define C_PATHADD 32
|
|
|
|
/* Test macros for the above */
|
|
#define CLOSUREP(c) (c->stat & C_CLOSURE)
|
|
#define ONEHASHP(c) (c->stat & (C_ONEHASH|C_STAR))
|
|
#define TWOHASHP(c) (c->stat & C_TWOHASH)
|
|
#define OPTIONALP(c) (c->stat & C_OPTIONAL)
|
|
#define STARP(c) (c->stat & C_STAR)
|
|
#define LASTP(c) (c->stat & C_LAST)
|
|
#define PATHADDP(c) (c->stat & C_PATHADD)
|
|
|
|
/* Flags passed down to guts when compiling */
|
|
#define GF_PATHADD 1 /* file glob, adding path components */
|
|
#define GF_TOPLEV 2 /* outside (), so ~ ends main match */
|
|
|
|
static char *pptr; /* current place in string being matched */
|
|
static Comp tail;
|
|
static int first; /* are leading dots special? */
|
|
|
|
/* Add a component to pathbuf: This keeps track of how *
|
|
* far we are into a file name, since each path component *
|
|
* must be matched separately. */
|
|
|
|
/**/
|
|
static void
|
|
addpath(char *s)
|
|
{
|
|
DPUTS(!pathbuf, "BUG: pathbuf not initialised");
|
|
while (pathpos + (int) strlen(s) + 1 >= pathbufsz)
|
|
pathbuf = realloc(pathbuf, pathbufsz *= 2);
|
|
while ((pathbuf[pathpos++] = *s++));
|
|
pathbuf[pathpos - 1] = '/';
|
|
pathbuf[pathpos] = '\0';
|
|
}
|
|
|
|
/* stat the filename s appended to pathbuf. l should be true for lstat, *
|
|
* false for stat. If st is NULL, the file is only chechked for existance. *
|
|
* s == "" is treated as s == ".". This is necessary since on most systems *
|
|
* foo/ can be used to reference a non-directory foo. Returns nonzero if *
|
|
* the file does not exists. */
|
|
|
|
/**/
|
|
static int
|
|
statfullpath(const char *s, struct stat *st, int l)
|
|
{
|
|
char buf[PATH_MAX];
|
|
|
|
DPUTS(strlen(s) + !*s + pathpos - pathbufcwd >= PATH_MAX,
|
|
"BUG: statfullpath(): pathname too long");
|
|
strcpy(buf, pathbuf + pathbufcwd);
|
|
strcpy(buf + pathpos - pathbufcwd, s);
|
|
if (!*s) {
|
|
buf[pathpos - pathbufcwd] = '.';
|
|
buf[pathpos - pathbufcwd + 1] = '\0';
|
|
l = 0;
|
|
}
|
|
unmetafy(buf, NULL);
|
|
if (!st)
|
|
return access(buf, F_OK) && (!l || readlink(buf, NULL, 0));
|
|
return l ? lstat(buf, st) : stat(buf, st);
|
|
}
|
|
|
|
/* add a match to the list */
|
|
|
|
/**/
|
|
static void
|
|
insert(char *s, int checked)
|
|
{
|
|
struct stat buf, buf2, *bp;
|
|
char *news = s;
|
|
int statted = 0;
|
|
|
|
if (gf_listtypes || gf_markdirs) {
|
|
/* Add the type marker to the end of the filename */
|
|
mode_t mode;
|
|
checked = statted = 1;
|
|
if (statfullpath(s, &buf, 1))
|
|
return;
|
|
mode = buf.st_mode;
|
|
if (gf_follow) {
|
|
if (!S_ISLNK(mode) || statfullpath(s, &buf2, 0))
|
|
memcpy(&buf2, &buf, sizeof(buf));
|
|
statted = 2;
|
|
mode = buf2.st_mode;
|
|
}
|
|
if (gf_listtypes || S_ISDIR(mode)) {
|
|
int ll = strlen(s);
|
|
|
|
news = (char *)ncalloc(ll + 2);
|
|
strcpy(news, s);
|
|
news[ll] = file_type(mode);
|
|
news[ll + 1] = '\0';
|
|
}
|
|
}
|
|
if (qualct || qualorct) {
|
|
/* Go through the qualifiers, rejecting the file if appropriate */
|
|
struct qual *qo, *qn;
|
|
|
|
if (!statted && statfullpath(s, &buf, 1))
|
|
return;
|
|
qo = quals;
|
|
for (qn = qo; qn && qn->func;) {
|
|
range = qn->range;
|
|
amc = qn->amc;
|
|
units = qn->units;
|
|
if ((qn->sense & 2) && statted != 2) {
|
|
/* If (sense & 2), we're following links */
|
|
if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0))
|
|
memcpy(&buf2, &buf, sizeof(buf));
|
|
statted = 2;
|
|
}
|
|
bp = (qn->sense & 2) ? &buf2 : &buf;
|
|
/* Reject the file if the function returned zero *
|
|
* and the sense was positive (sense&1 == 0), or *
|
|
* vice versa. */
|
|
if ((!((qn->func) (bp, qn->data)) ^ qn->sense) & 1) {
|
|
/* Try next alternative, or return if there are no more */
|
|
if (!(qo = qo->or))
|
|
return;
|
|
qn = qo;
|
|
continue;
|
|
}
|
|
qn = qn->next;
|
|
}
|
|
} else if (!checked && statfullpath(s, NULL, 1))
|
|
return;
|
|
|
|
news = dyncat(pathbuf, news);
|
|
if (colonmod) {
|
|
/* Handle the remainder of the qualifer: e.g. (:r:s/foo/bar/). */
|
|
s = colonmod;
|
|
modify(&news, &s);
|
|
}
|
|
*matchptr++ = news;
|
|
if (++matchct == matchsz) {
|
|
matchbuf = (char **)realloc((char *)matchbuf,
|
|
sizeof(char **) * (matchsz *= 2));
|
|
|
|
matchptr = matchbuf + matchct;
|
|
}
|
|
}
|
|
|
|
/* Check to see if str is eligible for filename generation. */
|
|
|
|
/**/
|
|
int
|
|
haswilds(char *str)
|
|
{
|
|
/* `[' and `]' are legal even if bad patterns are usually not. */
|
|
if ((*str == Inbrack || *str == Outbrack) && !str[1])
|
|
return 0;
|
|
|
|
/* If % is immediately followed by ?, then that ? is *
|
|
* not treated as a wildcard. This is so you don't have *
|
|
* to escape job references such as %?foo. */
|
|
if (str[0] == '%' && str[1] == Quest)
|
|
str[1] = '?';
|
|
|
|
for (; *str; str++) {
|
|
switch (*str) {
|
|
case Inpar:
|
|
case Bar:
|
|
case Star:
|
|
case Inbrack:
|
|
case Inang:
|
|
case Quest:
|
|
return 1;
|
|
case Pound:
|
|
case Hat:
|
|
if (isset(EXTENDEDGLOB))
|
|
return 1;
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Do the globbing: scanner is called recursively *
|
|
* with successive bits of the path until we've *
|
|
* tried all of it. */
|
|
|
|
/**/
|
|
static void
|
|
scanner(Complist q)
|
|
{
|
|
Comp c;
|
|
int closure;
|
|
int pbcwdsav = pathbufcwd;
|
|
struct dirsav ds;
|
|
|
|
ds.ino = ds.dev = 0;
|
|
ds.dirname = NULL;
|
|
ds.dirfd = ds.level = -1;
|
|
if (!q)
|
|
return;
|
|
|
|
if ((closure = q->closure)) /* (foo/)# - match zero or more dirs */
|
|
if (q->closure == 2) /* (foo/)## - match one or more dirs */
|
|
q->closure = 1;
|
|
else
|
|
scanner(q->next);
|
|
c = q->comp;
|
|
/* Now the actual matching for the current path section. */
|
|
if (!(c->next || c->left) && !haswilds(c->str)) {
|
|
/* It's a straight string to the end of the path section. */
|
|
int l = strlen(c->str);
|
|
|
|
if (l + !l + pathpos - pathbufcwd >= PATH_MAX) {
|
|
int err;
|
|
|
|
if (l >= PATH_MAX)
|
|
return;
|
|
err = lchdir(pathbuf + pathbufcwd, &ds, 0);
|
|
if (err == -1)
|
|
return;
|
|
if (err) {
|
|
zerr("current directory lost during glob", NULL, 0);
|
|
return;
|
|
}
|
|
pathbufcwd = pathpos;
|
|
}
|
|
if (q->next) {
|
|
/* Not the last path section. Just add it to the path. */
|
|
int oppos = pathpos;
|
|
|
|
if (!errflag && !(q->closure && !strcmp(c->str, "."))) {
|
|
addpath(c->str);
|
|
if (!closure || statfullpath("", NULL, 1))
|
|
scanner((q->closure) ? q : q->next);
|
|
pathbuf[pathpos = oppos] = '\0';
|
|
}
|
|
} else
|
|
insert(c->str, 0);
|
|
} else {
|
|
/* Do pattern matching on current path section. */
|
|
char *fn;
|
|
int dirs = !!q->next;
|
|
DIR *lock;
|
|
char *subdirs = NULL;
|
|
int subdirlen = 0;
|
|
|
|
fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : ".";
|
|
if (dirs) {
|
|
struct stat st;
|
|
stat(fn, &st);
|
|
/* a directory with subdirectories has link count greater than 2 */
|
|
if (!S_ISDIR(st.st_mode) || st.st_nlink == 2)
|
|
return;
|
|
}
|
|
lock = opendir(fn);
|
|
if (lock == NULL)
|
|
return;
|
|
while ((fn = zreaddir(lock, 1)) && !errflag) {
|
|
/* prefix and suffix are zle trickery */
|
|
if (!dirs && !colonmod &&
|
|
((glob_pre && !strpfx(glob_pre, fn))
|
|
|| (glob_suf && !strsfx(glob_suf, fn))))
|
|
continue;
|
|
if (domatch(fn, c, gf_noglobdots)) {
|
|
/* if this name matchs the pattern... */
|
|
if (pbcwdsav == pathbufcwd &&
|
|
strlen(fn) + pathpos - pathbufcwd >= PATH_MAX) {
|
|
int err;
|
|
|
|
DPUTS(pathpos == pathbufcwd,
|
|
"BUG: filename longer than PATH_MAX");
|
|
err = lchdir(pathbuf + pathbufcwd, &ds, 0);
|
|
if (err == -1)
|
|
break;
|
|
if (err) {
|
|
zerr("current directory lost during glob", NULL, 0);
|
|
break;
|
|
}
|
|
pathbufcwd = pathpos;
|
|
}
|
|
if (dirs) {
|
|
int l;
|
|
|
|
/* if not the last component in the path */
|
|
if (closure) {
|
|
/* if matching multiple directories */
|
|
struct stat buf;
|
|
|
|
if (statfullpath(fn, &buf, !q->follow)) {
|
|
if (errno != ENOENT && errno != EINTR &&
|
|
errno != ENOTDIR && !errflag) {
|
|
zerr("%e: %s", fn, errno);
|
|
errflag = 0;
|
|
}
|
|
continue;
|
|
}
|
|
if (!S_ISDIR(buf.st_mode))
|
|
continue;
|
|
}
|
|
l = strlen(fn) + 1;
|
|
subdirs = hrealloc(subdirs, subdirlen, subdirlen + l);
|
|
strcpy(subdirs + subdirlen, fn);
|
|
subdirlen += l;
|
|
} else
|
|
/* if the last filename component, just add it */
|
|
insert(fn, 1);
|
|
}
|
|
}
|
|
closedir(lock);
|
|
if (subdirs) {
|
|
int oppos = pathpos;
|
|
|
|
for (fn = subdirs; fn < subdirs+subdirlen; fn += strlen(fn) + 1) {
|
|
addpath(fn);
|
|
scanner((q->closure) ? q : q->next); /* scan next level */
|
|
pathbuf[pathpos = oppos] = '\0';
|
|
}
|
|
hrealloc(subdirs, subdirlen, 0);
|
|
}
|
|
}
|
|
if (pbcwdsav < pathbufcwd) {
|
|
if (restoredir(&ds))
|
|
zerr("current directory lost during glob", NULL, 0);
|
|
zsfree(ds.dirname);
|
|
if (ds.dirfd >= 0)
|
|
close(ds.dirfd);
|
|
pathbufcwd = pbcwdsav;
|
|
}
|
|
}
|
|
|
|
/* Parse a series of path components pointed to by pptr */
|
|
|
|
/* enum used with ksh-like patterns, @(...) etc. */
|
|
|
|
enum { KF_NONE, KF_AT, KF_QUEST, KF_STAR, KF_PLUS, KF_NOT };
|
|
|
|
/* parse lowest level pattern */
|
|
|
|
/**/
|
|
static Comp
|
|
parsecomp(int gflag)
|
|
{
|
|
int kshfunc;
|
|
Comp c = (Comp) alloc(sizeof *c), c1, c2;
|
|
char *cstr, *ls = NULL;
|
|
|
|
/* In case of alternatives, code coming up is stored in tail. */
|
|
c->next = tail;
|
|
cstr = pptr;
|
|
|
|
while (*pptr && (mode || *pptr != '/') && *pptr != Bar &&
|
|
(unset(EXTENDEDGLOB) || *pptr != Tilde ||
|
|
!pptr[1] || pptr[1] == Outpar || pptr[1] == Bar) &&
|
|
*pptr != Outpar) {
|
|
/* Go through code until we find something separating alternatives,
|
|
* or path components if relevant.
|
|
*/
|
|
if (*pptr == Hat && isset(EXTENDEDGLOB)) {
|
|
/* negate remaining pattern */
|
|
Comp stail = tail;
|
|
tail = NULL;
|
|
c->str = dupstrpfx(cstr, pptr - cstr);
|
|
pptr++;
|
|
|
|
c1 = (Comp) alloc(sizeof *c1);
|
|
c1->stat |= C_STAR;
|
|
|
|
c2 = (Comp) alloc(sizeof *c2);
|
|
if (!(c2->exclude = parsecomp(gflag)))
|
|
return NULL;
|
|
if (!*pptr || *pptr == '/')
|
|
c2->stat |= C_LAST;
|
|
c2->left = c1;
|
|
c2->next = stail;
|
|
c->next = c2;
|
|
tail = stail;
|
|
return c;
|
|
}
|
|
|
|
/* Ksh-type globs */
|
|
kshfunc = KF_NONE;
|
|
if (isset(KSHGLOB) && *pptr && pptr[1] == Inpar) {
|
|
switch (*pptr) {
|
|
case '@': /* just do paren as usual */
|
|
kshfunc = KF_AT;
|
|
break;
|
|
|
|
case Quest:
|
|
case '?': /* matched optionally, treat as (...|) */
|
|
kshfunc = KF_QUEST;
|
|
break;
|
|
|
|
case Star:
|
|
case '*': /* treat as (...)# */
|
|
kshfunc = KF_STAR;
|
|
break;
|
|
|
|
case '+': /* treat as (...)## */
|
|
kshfunc = KF_PLUS;
|
|
break;
|
|
|
|
case '!': /* treat as (*~...) */
|
|
kshfunc = KF_NOT;
|
|
break;
|
|
}
|
|
if (kshfunc != KF_NONE)
|
|
pptr++;
|
|
}
|
|
|
|
if (*pptr == Inpar) {
|
|
/* Found a group (...) */
|
|
char *startp = pptr, *endp;
|
|
Comp stail = tail;
|
|
int dpnd = 0;
|
|
|
|
/* Need matching close parenthesis */
|
|
if (skipparens(Inpar, Outpar, &pptr)) {
|
|
errflag = 1;
|
|
return NULL;
|
|
}
|
|
if (kshfunc == KF_STAR)
|
|
dpnd = 1;
|
|
else if (kshfunc == KF_PLUS)
|
|
dpnd = 2;
|
|
else if (kshfunc == KF_QUEST)
|
|
dpnd = 3;
|
|
if (*pptr == Pound && isset(EXTENDEDGLOB)) {
|
|
/* Zero (or one) or more repetitions of group */
|
|
pptr++;
|
|
if(*pptr == Pound) {
|
|
pptr++;
|
|
if(dpnd == 0)
|
|
dpnd = 2;
|
|
else if(dpnd == 3)
|
|
dpnd = 1;
|
|
} else
|
|
dpnd = 1;
|
|
}
|
|
/* Parse the remaining pattern following the group... */
|
|
if (!(c1 = parsecomp(gflag)))
|
|
return NULL;
|
|
/* ...remembering what comes after it... */
|
|
tail = (dpnd || kshfunc == KF_NOT) ? NULL : c1;
|
|
/* ...before going back and parsing inside the group. */
|
|
endp = pptr;
|
|
pptr = startp;
|
|
c->str = dupstrpfx(cstr, (pptr - cstr) - (kshfunc != KF_NONE));
|
|
pptr++;
|
|
c2 = (Comp) alloc(sizeof *c);
|
|
c->next = c2;
|
|
c2->next = (dpnd || kshfunc == KF_NOT) ?
|
|
c1 : (Comp) alloc(sizeof *c);
|
|
if (!(c2->left = parsecompsw(0)))
|
|
return NULL;
|
|
if (kshfunc == KF_NOT) {
|
|
/* we'd actually rather it didn't match. Instead, match *
|
|
* a star and put the parsed pattern into exclude. */
|
|
Comp c3 = (Comp) alloc(sizeof *c3);
|
|
c3->stat |= C_STAR;
|
|
|
|
c2->exclude = c2->left;
|
|
c2->left = c3;
|
|
}
|
|
/* Remember closures for group. */
|
|
if (dpnd)
|
|
c2->stat |= (dpnd == 3) ? C_OPTIONAL
|
|
: (dpnd == 2) ? C_TWOHASH : C_ONEHASH;
|
|
pptr = endp;
|
|
tail = stail;
|
|
return c;
|
|
}
|
|
if (*pptr == Star && pptr[1] &&
|
|
(unset(EXTENDEDGLOB) || !(gflag & GF_TOPLEV) ||
|
|
pptr[1] != Tilde || !pptr[2] || pptr[2] == Bar ||
|
|
pptr[2] == Outpar) && (mode || pptr[1] != '/')) {
|
|
/* Star followed by other patterns is now treated as a special
|
|
* type of closure in doesmatch().
|
|
*/
|
|
c->str = dupstrpfx(cstr, pptr - cstr);
|
|
pptr++;
|
|
c1 = (Comp) alloc(sizeof *c1);
|
|
c1->stat |= C_STAR;
|
|
if (!(c2 = parsecomp(gflag)))
|
|
return NULL;
|
|
c1->next = c2;
|
|
c->next = c1;
|
|
return c;
|
|
}
|
|
if (*pptr == Pound && isset(EXTENDEDGLOB)) {
|
|
/* repeat whatever we've just had (ls) zero or more times */
|
|
if (!ls)
|
|
return NULL;
|
|
c2 = (Comp) alloc(sizeof *c);
|
|
c2->str = dupstrpfx(ls, pptr - ls);
|
|
pptr++;
|
|
if (*pptr == Pound) {
|
|
/* need one or more matches: cheat by copying previous char */
|
|
pptr++;
|
|
c->next = c1 = (Comp) alloc(sizeof *c);
|
|
c1->str = c2->str;
|
|
} else
|
|
c1 = c;
|
|
c1->next = c2;
|
|
c2->stat |= C_ONEHASH;
|
|
/* parse the rest of the pattern and return. */
|
|
c2->next = parsecomp(gflag);
|
|
if (!c2->next)
|
|
return NULL;
|
|
c->str = dupstrpfx(cstr, ls - cstr);
|
|
return c;
|
|
}
|
|
ls = pptr; /* whatever we just parsed */
|
|
if (*pptr == Inang) {
|
|
/* Numeric glob */
|
|
int dshct;
|
|
|
|
dshct = (pptr[1] == Outang);
|
|
while (*++pptr && *pptr != Outang)
|
|
if (*pptr == '-' && !dshct)
|
|
dshct = 1;
|
|
else if (!idigit(*pptr))
|
|
break;
|
|
if (*pptr != Outang)
|
|
return NULL;
|
|
} else if (*pptr == Inbrack) {
|
|
/* Character set: brackets had better match */
|
|
if (pptr[1] == Outbrack)
|
|
*++pptr = ']';
|
|
else if ((pptr[1] == Hat || pptr[1] == '^' || pptr[1] == '!') &&
|
|
pptr[2] == Outbrack)
|
|
*(pptr += 2) = ']';
|
|
while (*++pptr && *pptr != Outbrack) {
|
|
if (itok(*pptr)) {
|
|
/* POSIX classes: make sure it's a real one, *
|
|
* leave the Inbrack tokenised if so. */
|
|
char *nptr;
|
|
if (*pptr == Inbrack && pptr[1] == ':'
|
|
&& (nptr = strchr(pptr+2, ':')) &&
|
|
*++nptr == Outbrack)
|
|
pptr = nptr;
|
|
*pptr = ztokens[*pptr - Pound];
|
|
}
|
|
}
|
|
if (*pptr != Outbrack)
|
|
return NULL;
|
|
} else if (itok(*pptr) && *pptr != Star && *pptr != Quest)
|
|
/* something that can be tokenised which isn't otherwise special */
|
|
*pptr = ztokens[*pptr - Pound];
|
|
pptr++;
|
|
}
|
|
/* mark if last pattern component in path component or pattern */
|
|
if (*pptr == '/' || !*pptr ||
|
|
(isset(EXTENDEDGLOB) && *pptr == Tilde && (gflag & GF_TOPLEV)))
|
|
c->stat |= C_LAST;
|
|
c->str = dupstrpfx(cstr, pptr - cstr);
|
|
return c;
|
|
}
|
|
|
|
/* Parse pattern possibly with different alternatives (|) */
|
|
|
|
/**/
|
|
static Comp
|
|
parsecompsw(int gflag)
|
|
{
|
|
Comp c1, c2, c3, excl = NULL, stail = tail;
|
|
char *sptr;
|
|
|
|
/*
|
|
* Check for a tilde in the expression. We need to know this in
|
|
* advance so as to be able to treat the whole a~b expression by
|
|
* backtracking: see exclusion code in doesmatch().
|
|
*/
|
|
if (isset(EXTENDEDGLOB)) {
|
|
int pct = 0;
|
|
for (sptr = pptr; *sptr; sptr++) {
|
|
if (*sptr == Inpar)
|
|
pct++;
|
|
else if (*sptr == Outpar && --pct < 0)
|
|
break;
|
|
else if (*sptr == Bar && !pct)
|
|
break;
|
|
else if (*sptr == Tilde && !pct) {
|
|
tail = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
c1 = parsecomp(gflag);
|
|
if (!c1)
|
|
return NULL;
|
|
if (isset(EXTENDEDGLOB) && *pptr == Tilde) {
|
|
/* Matching remainder of pattern excludes the pattern from matching */
|
|
int oldmode = mode;
|
|
|
|
mode = 1;
|
|
pptr++;
|
|
excl = parsecomp(gflag);
|
|
mode = oldmode;
|
|
if (!excl)
|
|
return NULL;
|
|
}
|
|
tail = stail;
|
|
if (*pptr == Bar || excl) {
|
|
/* found an alternative or something to exclude */
|
|
c2 = (Comp) alloc(sizeof *c2);
|
|
if (*pptr == Bar) {
|
|
/* get the next alternative after the | */
|
|
pptr++;
|
|
c3 = parsecompsw(gflag);
|
|
if (!c3)
|
|
return NULL;
|
|
} else
|
|
c3 = NULL;
|
|
/* mark if end of pattern or path component */
|
|
if (!*pptr || *pptr == '/')
|
|
c1->stat |= c2->stat = C_LAST;
|
|
c2->str = dupstring("");
|
|
c2->left = c1;
|
|
c2->right = c3;
|
|
if ((c2->exclude = excl))
|
|
c2->next = stail;
|
|
if (gflag & GF_PATHADD)
|
|
c2->stat |= C_PATHADD;
|
|
return c2;
|
|
}
|
|
return c1;
|
|
}
|
|
|
|
/* This function tokenizes a zsh glob pattern */
|
|
|
|
/**/
|
|
static Complist
|
|
parsecomplist(void)
|
|
{
|
|
Comp c1;
|
|
Complist p1;
|
|
char *str;
|
|
|
|
if (pptr[0] == Star && pptr[1] == Star &&
|
|
(pptr[2] == '/' || (pptr[2] == Star && pptr[3] == '/'))) {
|
|
/* Match any number of directories. */
|
|
int follow;
|
|
|
|
/* with three stars, follow symbolic links */
|
|
follow = (pptr[2] == Star);
|
|
pptr += (3 + follow);
|
|
|
|
/* Now get the next path component if there is one. */
|
|
p1 = (Complist) alloc(sizeof *p1);
|
|
if ((p1->next = parsecomplist()) == NULL) {
|
|
errflag = 1;
|
|
return NULL;
|
|
}
|
|
p1->comp = (Comp) alloc(sizeof *p1->comp);
|
|
p1->comp->stat |= C_LAST; /* end of path component */
|
|
p1->comp->str = dupstring("*");
|
|
*p1->comp->str = Star; /* match anything... */
|
|
p1->closure = 1; /* ...zero or more times. */
|
|
p1->follow = follow;
|
|
return p1;
|
|
}
|
|
|
|
/* Parse repeated directories such as (dir/)# and (dir/)## */
|
|
if (*(str = pptr) == Inpar && !skipparens(Inpar, Outpar, &str) &&
|
|
*str == Pound && isset(EXTENDEDGLOB) && str[-2] == '/') {
|
|
pptr++;
|
|
if (!(c1 = parsecompsw(0)))
|
|
return NULL;
|
|
if (pptr[0] == '/' && pptr[1] == Outpar && pptr[2] == Pound) {
|
|
int pdflag = 0;
|
|
|
|
pptr += 3;
|
|
if (*pptr == Pound) {
|
|
pdflag = 1;
|
|
pptr++;
|
|
}
|
|
p1 = (Complist) alloc(sizeof *p1);
|
|
p1->comp = c1;
|
|
p1->closure = 1 + pdflag;
|
|
p1->follow = 0;
|
|
p1->next = parsecomplist();
|
|
return (p1->comp) ? p1 : NULL;
|
|
}
|
|
} else {
|
|
/* parse single path component */
|
|
if (!(c1 = parsecompsw(GF_PATHADD|GF_TOPLEV)))
|
|
return NULL;
|
|
/* then do the remaining path compoents */
|
|
if (*pptr == '/' || !*pptr) {
|
|
int ef = *pptr == '/';
|
|
|
|
p1 = (Complist) alloc(sizeof *p1);
|
|
p1->comp = c1;
|
|
p1->closure = 0;
|
|
p1->next = ef ? (pptr++, parsecomplist()) : NULL;
|
|
return (ef && !p1->next) ? NULL : p1;
|
|
}
|
|
}
|
|
errflag = 1;
|
|
return NULL;
|
|
}
|
|
|
|
/* turn a string into a Complist struct: this has path components */
|
|
|
|
/**/
|
|
static Complist
|
|
parsepat(char *str)
|
|
{
|
|
mode = 0; /* path components present */
|
|
pptr = str;
|
|
tail = NULL;
|
|
return parsecomplist();
|
|
}
|
|
|
|
/* get number after qualifier */
|
|
|
|
/**/
|
|
static long
|
|
qgetnum(char **s)
|
|
{
|
|
long v = 0;
|
|
|
|
if (!idigit(**s)) {
|
|
zerr("number expected", NULL, 0);
|
|
return 0;
|
|
}
|
|
while (idigit(**s))
|
|
v = v * 10 + *(*s)++ - '0';
|
|
return v;
|
|
}
|
|
|
|
/* get octal number after qualifier */
|
|
|
|
/**/
|
|
static long
|
|
qgetoctnum(char **s)
|
|
{
|
|
long v = 0;
|
|
|
|
if (!idigit(**s)) {
|
|
zerr("octal number expected", NULL, 0);
|
|
return 0;
|
|
}
|
|
while (**s >= '0' && **s <= '7')
|
|
v = v * 010 + *(*s)++ - '0';
|
|
return v;
|
|
}
|
|
|
|
/* Main entry point to the globbing code for filename globbing. *
|
|
* np points to a node in the list list which will be expanded *
|
|
* into a series of nodes. */
|
|
|
|
/**/
|
|
void
|
|
glob(LinkList list, LinkNode np)
|
|
{
|
|
struct qual *qo, *qn, *ql;
|
|
LinkNode node = prevnode(np);
|
|
char *str; /* the pattern */
|
|
int sl; /* length of the pattern */
|
|
Complist q; /* pattern after parsing */
|
|
char *ostr = (char *)getdata(np); /* the pattern before the parser */
|
|
/* chops it up */
|
|
|
|
MUSTUSEHEAP("glob");
|
|
if (unset(GLOBOPT) || !haswilds(ostr)) {
|
|
untokenize(ostr);
|
|
return;
|
|
}
|
|
str = dupstring(ostr);
|
|
sl = strlen(str);
|
|
uremnode(list, np);
|
|
|
|
/* Initialise state variables for current file pattern */
|
|
qo = qn = quals = ql = NULL;
|
|
qualct = qualorct = 0;
|
|
colonmod = NULL;
|
|
gf_nullglob = isset(NULLGLOB);
|
|
gf_markdirs = isset(MARKDIRS);
|
|
gf_listtypes = gf_follow = 0;
|
|
gf_noglobdots = unset(GLOBDOTS);
|
|
|
|
/* Check for qualifiers */
|
|
if (isset(BAREGLOBQUAL) && str[sl - 1] == Outpar) {
|
|
char *s;
|
|
|
|
/* Check these are really qualifiers, not a set of *
|
|
* alternatives or exclusions */
|
|
for (s = str + sl - 2; *s != Inpar; s--)
|
|
if (*s == Bar || *s == Outpar ||
|
|
(isset(EXTENDEDGLOB) && *s == Tilde))
|
|
break;
|
|
if (*s == Inpar) {
|
|
/* Real qualifiers found. */
|
|
int sense = 0; /* bit 0 for match (0)/don't match (1) */
|
|
/* bit 1 for follow links (2), don't (0) */
|
|
long data = 0; /* Any numerical argument required */
|
|
int (*func) _((Statptr, long));
|
|
|
|
str[sl-1] = 0;
|
|
*s++ = 0;
|
|
while (*s && !colonmod) {
|
|
func = (int (*) _((Statptr, long)))0;
|
|
if (idigit(*s)) {
|
|
/* Store numeric argument for qualifier */
|
|
func = qualflags;
|
|
data = 0;
|
|
while (idigit(*s))
|
|
data = data * 010 + (*s++ - '0');
|
|
} else if (*s == ',') {
|
|
/* A comma separates alternative sets of qualifiers */
|
|
s++;
|
|
sense = 0;
|
|
if (qualct) {
|
|
qn = (struct qual *)hcalloc(sizeof *qn);
|
|
qo->or = qn;
|
|
qo = qn;
|
|
qualorct++;
|
|
qualct = 0;
|
|
ql = NULL;
|
|
}
|
|
} else
|
|
switch (*s++) {
|
|
case ':':
|
|
/* Remaining arguments are history-type *
|
|
* colon substitutions, handled separately. */
|
|
colonmod = s - 1;
|
|
untokenize(colonmod);
|
|
break;
|
|
case Hat:
|
|
case '^':
|
|
/* Toggle sense: go from positive to *
|
|
* negative match and vice versa. */
|
|
sense ^= 1;
|
|
break;
|
|
case '-':
|
|
/* Toggle matching of symbolic links */
|
|
sense ^= 2;
|
|
break;
|
|
case '@':
|
|
/* Match symbolic links */
|
|
func = qualislnk;
|
|
break;
|
|
case Equals:
|
|
case '=':
|
|
/* Match sockets */
|
|
func = qualissock;
|
|
break;
|
|
case 'p':
|
|
/* Match named pipes */
|
|
func = qualisfifo;
|
|
break;
|
|
case '/':
|
|
/* Match directories */
|
|
func = qualisdir;
|
|
break;
|
|
case '.':
|
|
/* Match regular files */
|
|
func = qualisreg;
|
|
break;
|
|
case '%':
|
|
/* Match special files: block, *
|
|
* character or any device */
|
|
if (*s == 'b')
|
|
s++, func = qualisblk;
|
|
else if (*s == 'c')
|
|
s++, func = qualischr;
|
|
else
|
|
func = qualisdev;
|
|
break;
|
|
case Star:
|
|
/* Match executable plain files */
|
|
func = qualiscom;
|
|
break;
|
|
case 'R':
|
|
/* Match world-readable files */
|
|
func = qualflags;
|
|
data = 0004;
|
|
break;
|
|
case 'W':
|
|
/* Match world-writeable files */
|
|
func = qualflags;
|
|
data = 0002;
|
|
break;
|
|
case 'X':
|
|
/* Match world-executable files */
|
|
func = qualflags;
|
|
data = 0001;
|
|
break;
|
|
case 'A':
|
|
func = qualflags;
|
|
data = 0040;
|
|
break;
|
|
case 'I':
|
|
func = qualflags;
|
|
data = 0020;
|
|
break;
|
|
case 'E':
|
|
func = qualflags;
|
|
data = 0010;
|
|
break;
|
|
case 'r':
|
|
/* Match files readable by current process */
|
|
func = qualflags;
|
|
data = 0400;
|
|
break;
|
|
case 'w':
|
|
/* Match files writeable by current process */
|
|
func = qualflags;
|
|
data = 0200;
|
|
break;
|
|
case 'x':
|
|
/* Match files executable by current process */
|
|
func = qualflags;
|
|
data = 0100;
|
|
break;
|
|
case 's':
|
|
/* Match setuid files */
|
|
func = qualflags;
|
|
data = 04000;
|
|
break;
|
|
case 'S':
|
|
/* Match setgid files */
|
|
func = qualflags;
|
|
data = 02000;
|
|
break;
|
|
case 't':
|
|
func = qualflags;
|
|
data = 01000;
|
|
break;
|
|
case 'd':
|
|
/* Match device files by device number *
|
|
* (as given by stat's st_dev element). */
|
|
func = qualdev;
|
|
data = qgetnum(&s);
|
|
break;
|
|
case 'l':
|
|
/* Match files with the given no. of hard links */
|
|
func = qualnlink;
|
|
amc = -1;
|
|
goto getrange;
|
|
case 'U':
|
|
/* Match files owned by effective user ID */
|
|
func = qualuid;
|
|
data = geteuid();
|
|
break;
|
|
case 'G':
|
|
/* Match files owned by effective group ID */
|
|
func = qualgid;
|
|
data = getegid();
|
|
break;
|
|
case 'u':
|
|
/* Match files owned by given user id */
|
|
func = qualuid;
|
|
/* either the actual uid... */
|
|
if (idigit(*s))
|
|
data = qgetnum(&s);
|
|
else {
|
|
/* ... or a user name */
|
|
char sav, *tt;
|
|
|
|
/* Find matching delimiters */
|
|
tt = get_strarg(s);
|
|
if (!*tt) {
|
|
zerr("missing end of name",
|
|
NULL, 0);
|
|
data = 0;
|
|
} else {
|
|
#ifdef HAVE_GETPWNAM
|
|
struct passwd *pw;
|
|
sav = *tt;
|
|
*tt = '\0';
|
|
|
|
if ((pw = getpwnam(s + 1)))
|
|
data = pw->pw_uid;
|
|
else {
|
|
zerr("unknown user", NULL, 0);
|
|
data = 0;
|
|
}
|
|
*tt = sav;
|
|
#else /* !HAVE_GETPWNAM */
|
|
sav = *tt;
|
|
zerr("unknown user", NULL, 0);
|
|
data = 0;
|
|
#endif /* !HAVE_GETPWNAM */
|
|
if (sav)
|
|
s = tt + 1;
|
|
else
|
|
s = tt;
|
|
}
|
|
}
|
|
break;
|
|
case 'g':
|
|
/* Given gid or group id... works like `u' */
|
|
func = qualgid;
|
|
/* either the actual gid... */
|
|
if (idigit(*s))
|
|
data = qgetnum(&s);
|
|
else {
|
|
/* ...or a delimited group name. */
|
|
char sav, *tt;
|
|
|
|
tt = get_strarg(s);
|
|
if (!*tt) {
|
|
zerr("missing end of name",
|
|
NULL, 0);
|
|
data = 0;
|
|
} else {
|
|
#ifdef HAVE_GETGRNAM
|
|
struct group *gr;
|
|
sav = *tt;
|
|
*tt = '\0';
|
|
|
|
if ((gr = getgrnam(s + 1)))
|
|
data = gr->gr_gid;
|
|
else {
|
|
zerr("unknown group", NULL, 0);
|
|
data = 0;
|
|
}
|
|
*tt = sav;
|
|
#else /* !HAVE_GETGRNAM */
|
|
sav = *tt;
|
|
zerr("unknown group", NULL, 0);
|
|
data = 0;
|
|
#endif /* !HAVE_GETGRNAM */
|
|
if (sav)
|
|
s = tt + 1;
|
|
else
|
|
s = tt;
|
|
}
|
|
}
|
|
break;
|
|
case 'o':
|
|
/* Match octal mode of file exactly. *
|
|
* Currently undocumented. */
|
|
func = qualeqflags;
|
|
data = qgetoctnum(&s);
|
|
break;
|
|
case 'M':
|
|
/* Mark directories with a / */
|
|
if ((gf_markdirs = !(sense & 1)))
|
|
gf_follow = sense & 2;
|
|
break;
|
|
case 'T':
|
|
/* Mark types in a `ls -F' type fashion */
|
|
if ((gf_listtypes = !(sense & 1)))
|
|
gf_follow = sense & 2;
|
|
break;
|
|
case 'N':
|
|
/* Nullglob: remove unmatched patterns. */
|
|
gf_nullglob = !(sense & 1);
|
|
break;
|
|
case 'D':
|
|
/* Glob dots: match leading dots implicitly */
|
|
gf_noglobdots = sense & 1;
|
|
break;
|
|
case 'a':
|
|
/* Access time in given range */
|
|
amc = 0;
|
|
func = qualtime;
|
|
goto getrange;
|
|
case 'm':
|
|
/* Modification time in given range */
|
|
amc = 1;
|
|
func = qualtime;
|
|
goto getrange;
|
|
case 'c':
|
|
/* Inode creation time in given range */
|
|
amc = 2;
|
|
func = qualtime;
|
|
goto getrange;
|
|
case 'L':
|
|
/* File size (Length) in given range */
|
|
func = qualsize;
|
|
amc = -1;
|
|
/* Get size multiplier */
|
|
units = TT_BYTES;
|
|
if (*s == 'p' || *s == 'P')
|
|
units = TT_POSIX_BLOCKS, ++s;
|
|
else if (*s == 'k' || *s == 'K')
|
|
units = TT_KILOBYTES, ++s;
|
|
else if (*s == 'm' || *s == 'M')
|
|
units = TT_MEGABYTES, ++s;
|
|
getrange:
|
|
/* Get time multiplier */
|
|
if (amc >= 0) {
|
|
units = TT_DAYS;
|
|
if (*s == 'h')
|
|
units = TT_HOURS, ++s;
|
|
else if (*s == 'm')
|
|
units = TT_MINS, ++s;
|
|
else if (*s == 'w')
|
|
units = TT_WEEKS, ++s;
|
|
else if (*s == 'M')
|
|
units = TT_MONTHS, ++s;
|
|
}
|
|
/* See if it's greater than, equal to, or less than */
|
|
if ((range = *s == '+' ? 1 : *s == '-' ? -1 : 0))
|
|
++s;
|
|
data = qgetnum(&s);
|
|
break;
|
|
|
|
default:
|
|
zerr("unknown file attribute", NULL, 0);
|
|
return;
|
|
}
|
|
if (func) {
|
|
/* Requested test is performed by function func */
|
|
if (!qn)
|
|
qn = (struct qual *)hcalloc(sizeof *qn);
|
|
if (ql)
|
|
ql->next = qn;
|
|
ql = qn;
|
|
if (!quals)
|
|
quals = qo = qn;
|
|
qn->func = func;
|
|
qn->sense = sense;
|
|
qn->data = data;
|
|
qn->range = range;
|
|
qn->units = units;
|
|
qn->amc = amc;
|
|
qn = NULL;
|
|
qualct++;
|
|
}
|
|
if (errflag)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (!pathbuf)
|
|
pathbuf = zalloc(pathbufsz = PATH_MAX);
|
|
DPUTS(pathbufcwd, "BUG: glob changed directory");
|
|
if (*str == '/') { /* pattern has absolute path */
|
|
str++;
|
|
pathbuf[0] = '/';
|
|
pathbuf[pathpos = 1] = '\0';
|
|
} else /* pattern is relative to pwd */
|
|
pathbuf[pathpos = 0] = '\0';
|
|
q = parsepat(str);
|
|
if (!q || errflag) { /* if parsing failed */
|
|
if (unset(BADPATTERN)) {
|
|
untokenize(ostr);
|
|
insertlinknode(list, node, ostr);
|
|
return;
|
|
}
|
|
errflag = 0;
|
|
zerr("bad pattern: %s", ostr, 0);
|
|
return;
|
|
}
|
|
|
|
/* Initialise receptacle for matched files, *
|
|
* expanded by insert() where necessary. */
|
|
matchptr = matchbuf = (char **)zalloc((matchsz = 16) * sizeof(char *));
|
|
matchct = 0;
|
|
|
|
/* The actual processing takes place here: matches go into *
|
|
* matchbuf. This is the only top-level call to scanner(). */
|
|
scanner(q);
|
|
|
|
/* Deal with failures to match depending on options */
|
|
if (matchct)
|
|
badcshglob |= 2; /* at least one cmd. line expansion O.K. */
|
|
else if (!gf_nullglob)
|
|
if (isset(CSHNULLGLOB)) {
|
|
badcshglob |= 1; /* at least one cmd. line expansion failed */
|
|
} else if (isset(NOMATCH)) {
|
|
zerr("no matches found: %s", ostr, 0);
|
|
free(matchbuf);
|
|
return;
|
|
} else {
|
|
/* treat as an ordinary string */
|
|
untokenize(*matchptr++ = dupstring(ostr));
|
|
matchct = 1;
|
|
}
|
|
/* Sort arguments in to lexical (and possibly numeric) order. *
|
|
* This is reversed to facilitate insertion into the list. */
|
|
qsort((void *) & matchbuf[0], matchct, sizeof(char *),
|
|
(int (*) _((const void *, const void *)))notstrcmp);
|
|
|
|
matchptr = matchbuf;
|
|
while (matchct--) /* insert matches in the arg list */
|
|
insertlinknode(list, node, *matchptr++);
|
|
free(matchbuf);
|
|
}
|
|
|
|
/* Return the order of two strings, taking into account *
|
|
* possible numeric order if NUMERICGLOBSORT is set. *
|
|
* The comparison here is reversed. */
|
|
|
|
/**/
|
|
static int
|
|
notstrcmp(char **a, char **b)
|
|
{
|
|
char *c = *b, *d = *a;
|
|
int cmp;
|
|
|
|
#ifdef HAVE_STRCOLL
|
|
cmp = strcoll(c, d);
|
|
#endif
|
|
for (; *c == *d && *c; c++, d++);
|
|
#ifndef HAVE_STRCOLL
|
|
cmp = (int)STOUC(*c) - (int)STOUC(*d);
|
|
#endif
|
|
if (isset(NUMERICGLOBSORT) && (idigit(*c) || idigit(*d))) {
|
|
for (; c > *b && idigit(c[-1]); c--, d--);
|
|
if (idigit(*c) && idigit(*d)) {
|
|
while (*c == '0')
|
|
c++;
|
|
while (*d == '0')
|
|
d++;
|
|
for (; idigit(*c) && *c == *d; c++, d++);
|
|
if (idigit(*c) || idigit(*d)) {
|
|
cmp = (int)STOUC(*c) - (int)STOUC(*d);
|
|
while (idigit(*c) && idigit(*d))
|
|
c++, d++;
|
|
if (idigit(*c) && !idigit(*d))
|
|
return 1;
|
|
if (idigit(*d) && !idigit(*c))
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return cmp;
|
|
}
|
|
|
|
/* Return the trailing character for marking file types */
|
|
|
|
/**/
|
|
char
|
|
file_type(mode_t filemode)
|
|
{
|
|
if(S_ISBLK(filemode))
|
|
return '#';
|
|
else if(S_ISCHR(filemode))
|
|
return '%';
|
|
else if(S_ISDIR(filemode))
|
|
return '/';
|
|
else if(S_ISFIFO(filemode))
|
|
return '|';
|
|
else if(S_ISLNK(filemode))
|
|
return '@';
|
|
else if(S_ISREG(filemode))
|
|
return (filemode & S_IXUGO) ? '*' : ' ';
|
|
else if(S_ISSOCK(filemode))
|
|
return '=';
|
|
else
|
|
return '?';
|
|
}
|
|
|
|
/* check to see if str is eligible for brace expansion */
|
|
|
|
/**/
|
|
int
|
|
hasbraces(char *str)
|
|
{
|
|
char *lbr, *mbr, *comma;
|
|
|
|
if (isset(BRACECCL)) {
|
|
/* In this case, any properly formed brace expression *
|
|
* will match and expand to the characters in between. */
|
|
int bc;
|
|
|
|
for (bc = 0; *str; ++str)
|
|
if (*str == Inbrace) {
|
|
if (!bc && str[1] == Outbrace)
|
|
*str++ = '{', *str = '}';
|
|
else
|
|
bc++;
|
|
} else if (*str == Outbrace)
|
|
if (!bc)
|
|
*str = '}';
|
|
else if (!--bc)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
/* Otherwise we need to look for... */
|
|
lbr = mbr = comma = NULL;
|
|
for (;;) {
|
|
switch (*str++) {
|
|
case Inbrace:
|
|
if (!lbr) {
|
|
lbr = str - 1;
|
|
while (idigit(*str))
|
|
str++;
|
|
if (*str == '.' && str[1] == '.') {
|
|
str++;
|
|
while (idigit(*++str));
|
|
if (*str == Outbrace &&
|
|
(idigit(lbr[1]) || idigit(str[-1])))
|
|
return 1;
|
|
}
|
|
} else {
|
|
char *s = --str;
|
|
|
|
if (skipparens(Inbrace, Outbrace, &str)) {
|
|
*lbr = *s = '{';
|
|
if (comma)
|
|
str = comma;
|
|
if (mbr && mbr < str)
|
|
str = mbr;
|
|
lbr = mbr = comma = NULL;
|
|
} else if (!mbr)
|
|
mbr = s;
|
|
}
|
|
break;
|
|
case Outbrace:
|
|
if (!lbr)
|
|
str[-1] = '}';
|
|
else if (comma)
|
|
return 1;
|
|
else {
|
|
*lbr = '{';
|
|
str[-1] = '}';
|
|
if (mbr)
|
|
str = mbr;
|
|
mbr = lbr = NULL;
|
|
}
|
|
break;
|
|
case Comma:
|
|
if (!lbr)
|
|
str[-1] = ',';
|
|
else if (!comma)
|
|
comma = str - 1;
|
|
break;
|
|
case '\0':
|
|
if (lbr)
|
|
*lbr = '{';
|
|
if (!mbr && !comma)
|
|
return 0;
|
|
if (comma)
|
|
str = comma;
|
|
if (mbr && mbr < str)
|
|
str = mbr;
|
|
lbr = mbr = comma = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* expand stuff like >>*.c */
|
|
|
|
/**/
|
|
int
|
|
xpandredir(struct redir *fn, LinkList tab)
|
|
{
|
|
LinkList fake;
|
|
char *nam;
|
|
struct redir *ff;
|
|
int ret = 0;
|
|
|
|
/* Stick the name in a list... */
|
|
fake = newlinklist();
|
|
addlinknode(fake, fn->name);
|
|
/* ...which undergoes all the usual shell expansions */
|
|
prefork(fake, isset(MULTIOS) ? 0 : 4);
|
|
/* Globbing is only done for multios. */
|
|
if (!errflag && isset(MULTIOS))
|
|
globlist(fake);
|
|
if (errflag)
|
|
return 0;
|
|
if (nonempty(fake) && !nextnode(firstnode(fake))) {
|
|
/* Just one match, the usual case. */
|
|
char *s = peekfirst(fake);
|
|
fn->name = s;
|
|
untokenize(s);
|
|
if (fn->type == MERGEIN || fn->type == MERGEOUT) {
|
|
if (s[0] == '-' && !s[1])
|
|
fn->type = CLOSE;
|
|
else if (s[0] == 'p' && !s[1])
|
|
fn->fd2 = (fn->type == MERGEOUT) ? coprocout : coprocin;
|
|
else {
|
|
while (idigit(*s))
|
|
s++;
|
|
if (!*s && s > fn->name)
|
|
fn->fd2 = zstrtol(fn->name, NULL, 10);
|
|
else if (fn->type == MERGEIN)
|
|
zerr("file number expected", NULL, 0);
|
|
else
|
|
fn->type = ERRWRITE;
|
|
}
|
|
}
|
|
} else if (fn->type == MERGEIN)
|
|
zerr("file number expected", NULL, 0);
|
|
else {
|
|
if (fn->type == MERGEOUT)
|
|
fn->type = ERRWRITE;
|
|
while ((nam = (char *)ugetnode(fake))) {
|
|
/* Loop over matches, duplicating the *
|
|
* redirection for each file found. */
|
|
ff = (struct redir *)alloc(sizeof *ff);
|
|
*ff = *fn;
|
|
ff->name = nam;
|
|
addlinknode(tab, ff);
|
|
ret = 1;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* concatenate s1 and s2 in dynamically allocated buffer */
|
|
|
|
/**/
|
|
char *
|
|
dyncat(char *s1, char *s2)
|
|
{
|
|
/* This version always uses space from the current heap. */
|
|
char *ptr;
|
|
int l1 = strlen(s1);
|
|
|
|
ptr = (char *)ncalloc(l1 + strlen(s2) + 1);
|
|
strcpy(ptr, s1);
|
|
strcpy(ptr + l1, s2);
|
|
return ptr;
|
|
}
|
|
|
|
/* concatenate s1, s2, and s3 in dynamically allocated buffer */
|
|
|
|
/**/
|
|
char *
|
|
tricat(char const *s1, char const *s2, char const *s3)
|
|
{
|
|
/* This version always uses permanently-allocated space. */
|
|
char *ptr;
|
|
|
|
ptr = (char *)zalloc(strlen(s1) + strlen(s2) + strlen(s3) + 1);
|
|
strcpy(ptr, s1);
|
|
strcat(ptr, s2);
|
|
strcat(ptr, s3);
|
|
return ptr;
|
|
}
|
|
|
|
/* brace expansion */
|
|
|
|
/**/
|
|
void
|
|
xpandbraces(LinkList list, LinkNode *np)
|
|
{
|
|
LinkNode node = (*np), last = prevnode(node);
|
|
char *str = (char *)getdata(node), *str3 = str, *str2;
|
|
int prev, bc, comma, dotdot;
|
|
|
|
for (; *str != Inbrace; str++);
|
|
/* First, match up braces and see what we have. */
|
|
for (str2 = str, bc = comma = dotdot = 0; *str2; ++str2)
|
|
if (*str2 == Inbrace)
|
|
++bc;
|
|
else if (*str2 == Outbrace) {
|
|
if (--bc == 0)
|
|
break;
|
|
} else if (bc == 1)
|
|
if (*str2 == Comma)
|
|
++comma; /* we have {foo,bar} */
|
|
else if (*str2 == '.' && str2[1] == '.')
|
|
dotdot++; /* we have {num1..num2} */
|
|
DPUTS(bc, "BUG: unmatched brace in xpandbraces()");
|
|
if (!comma && dotdot) {
|
|
/* Expand range like 0..10 numerically: comma or recursive
|
|
brace expansion take precedence. */
|
|
char *dots, *p;
|
|
LinkNode olast = last;
|
|
/* Get the first number of the range */
|
|
int rstart = zstrtol(str+1,&dots,10), rend = 0, err = 0, rev = 0;
|
|
int wid1 = (dots - str) - 1, wid2 = (str2 - dots) - 2;
|
|
int strp = str - str3;
|
|
|
|
if (dots == str + 1 || *dots != '.' || dots[1] != '.')
|
|
err++;
|
|
else {
|
|
/* Get the last number of the range */
|
|
rend = zstrtol(dots+2,&p,10);
|
|
if (p == dots+2 || p != str2)
|
|
err++;
|
|
}
|
|
if (!err) {
|
|
/* If either no. begins with a zero, pad the output with *
|
|
* zeroes. Otherwise, choose a min width to suppress them. */
|
|
int minw = (str[1] == '0') ? wid1 : (dots[2] == '0' ) ? wid2 :
|
|
(wid2 > wid1) ? wid1 : wid2;
|
|
if (rstart > rend) {
|
|
/* Handle decreasing ranges correctly. */
|
|
int rt = rend;
|
|
rend = rstart;
|
|
rstart = rt;
|
|
rev = 1;
|
|
}
|
|
uremnode(list, node);
|
|
for (; rend >= rstart; rend--) {
|
|
/* Node added in at end, so do highest first */
|
|
p = dupstring(str3);
|
|
sprintf(p + strp, "%0*d", minw, rend);
|
|
strcat(p + strp, str2 + 1);
|
|
insertlinknode(list, last, p);
|
|
if (rev) /* decreasing: add in reverse order. */
|
|
last = nextnode(last);
|
|
}
|
|
*np = nextnode(olast);
|
|
return;
|
|
}
|
|
}
|
|
if (!comma && isset(BRACECCL)) { /* {a-mnop} */
|
|
/* Here we expand each character to a separate node, *
|
|
* but also ranges of characters like a-m. ccl is a *
|
|
* set of flags saying whether each character is present; *
|
|
* the final list is in lexical order. */
|
|
char ccl[256], *p;
|
|
unsigned char c1, c2, lastch;
|
|
unsigned int len, pl;
|
|
|
|
uremnode(list, node);
|
|
memset(ccl, 0, sizeof(ccl) / sizeof(ccl[0]));
|
|
for (p = str + 1, lastch = 0; p < str2;) {
|
|
if (itok(c1 = *p++))
|
|
c1 = ztokens[c1 - STOUC(Pound)];
|
|
if ((char) c1 == Meta)
|
|
c1 = 32 ^ *p++;
|
|
if (itok(c2 = *p))
|
|
c2 = ztokens[c2 - STOUC(Pound)];
|
|
if ((char) c2 == Meta)
|
|
c2 = 32 ^ p[1];
|
|
if (c1 == '-' && lastch && p < str2 && (int)lastch <= (int)c2) {
|
|
while ((int)lastch < (int)c2)
|
|
ccl[lastch++] = 1;
|
|
lastch = 0;
|
|
} else
|
|
ccl[lastch = c1] = 1;
|
|
}
|
|
pl = str - str3;
|
|
len = pl + strlen(++str2) + 2;
|
|
for (p = ccl + 255; p-- > ccl;)
|
|
if (*p) {
|
|
c1 = p - ccl;
|
|
if (imeta(c1)) {
|
|
str = ncalloc(len + 1);
|
|
str[pl] = Meta;
|
|
str[pl+1] = c1 ^ 32;
|
|
strcpy(str + pl + 2, str2);
|
|
} else {
|
|
str = ncalloc(len);
|
|
str[pl] = c1;
|
|
strcpy(str + pl + 1, str2);
|
|
}
|
|
memcpy(str, str3, pl);
|
|
insertlinknode(list, last, str);
|
|
}
|
|
*np = nextnode(last);
|
|
return;
|
|
}
|
|
prev = str++ - str3;
|
|
str2++;
|
|
uremnode(list, node);
|
|
node = last;
|
|
/* Finally, normal comma expansion *
|
|
* str1{foo,bar}str2 -> str1foostr2 str1barstr2. *
|
|
* Any number of intervening commas is allowed. */
|
|
for (;;) {
|
|
char *zz, *str4;
|
|
int cnt;
|
|
|
|
for (str4 = str, cnt = 0; cnt || (*str != Comma && *str !=
|
|
Outbrace); str++) {
|
|
if (*str == Inbrace)
|
|
cnt++;
|
|
else if (*str == Outbrace)
|
|
cnt--;
|
|
DPUTS(!*str, "BUG: illegal brace expansion");
|
|
}
|
|
/* Concatenate the string before the braces (str3), the section *
|
|
* just found (str4) and the text after the braces (str2) */
|
|
zz = (char *)ncalloc(prev + (str - str4) + strlen(str2) + 1);
|
|
ztrncpy(zz, str3, prev);
|
|
strncat(zz, str4, str - str4);
|
|
strcat(zz, str2);
|
|
/* and add this text to the argument list. */
|
|
insertlinknode(list, node, zz);
|
|
incnode(node);
|
|
if (*str != Outbrace)
|
|
str++;
|
|
else
|
|
break;
|
|
}
|
|
*np = nextnode(last);
|
|
}
|
|
|
|
/* check to see if a matches b (b is not a filename pattern) */
|
|
|
|
/**/
|
|
int
|
|
matchpat(char *a, char *b)
|
|
{
|
|
Comp c = parsereg(b);
|
|
|
|
if (!c) {
|
|
zerr("bad pattern: %s", b, 0);
|
|
return 0;
|
|
}
|
|
return domatch(a, c, 0);
|
|
}
|
|
|
|
/* do the ${foo%%bar}, ${foo#bar} stuff */
|
|
/* please do not laugh at this code. */
|
|
|
|
/* Having found a match in getmatch, decide what part of string
|
|
* to return. The matched part starts b characters into string s
|
|
* and finishes e characters in: 0 <= b <= e <= strlen(s)
|
|
* (yes, empty matches should work).
|
|
* Bits 3 and higher in fl are used: the flags are
|
|
* 8: Result is matched portion.
|
|
* 16: Result is unmatched portion.
|
|
* (N.B. this should be set for standard ${foo#bar} etc. matches.)
|
|
* 32: Result is numeric position of start of matched portion.
|
|
* 64: Result is numeric position of end of matched portion.
|
|
* 128: Result is length of matched portion.
|
|
*/
|
|
|
|
/**/
|
|
static char *
|
|
get_match_ret(char *s, int b, int e, int fl)
|
|
{
|
|
char buf[80], *r, *p, *rr;
|
|
int ll = 0, l = strlen(s), bl = 0, t = 0, i;
|
|
|
|
if (fl & 8) /* matched portion */
|
|
ll += 1 + (e - b);
|
|
if (fl & 16) /* unmatched portion */
|
|
ll += 1 + (l - (e - b));
|
|
if (fl & 32) {
|
|
/* position of start of matched portion */
|
|
sprintf(buf, "%d ", b + 1);
|
|
ll += (bl = strlen(buf));
|
|
}
|
|
if (fl & 64) {
|
|
/* position of end of matched portion */
|
|
sprintf(buf + bl, "%d ", e + 1);
|
|
ll += (bl = strlen(buf));
|
|
}
|
|
if (fl & 128) {
|
|
/* length of matched portion */
|
|
sprintf(buf + bl, "%d ", e - b);
|
|
ll += (bl = strlen(buf));
|
|
}
|
|
if (bl)
|
|
buf[bl - 1] = '\0';
|
|
|
|
rr = r = (char *)ncalloc(ll);
|
|
|
|
if (fl & 8) {
|
|
/* copy matched portion to new buffer */
|
|
for (i = b, p = s + b; i < e; i++)
|
|
*rr++ = *p++;
|
|
t = 1;
|
|
}
|
|
if (fl & 16) {
|
|
/* Copy unmatched portion to buffer. If both portions *
|
|
* requested, put a space in between (why?) */
|
|
if (t)
|
|
*rr++ = ' ';
|
|
/* there may be unmatched bits at both beginning and end of string */
|
|
for (i = 0, p = s; i < b; i++)
|
|
*rr++ = *p++;
|
|
for (i = e, p = s + e; i < l; i++)
|
|
*rr++ = *p++;
|
|
t = 1;
|
|
}
|
|
*rr = '\0';
|
|
if (bl) {
|
|
/* if there was a buffer (with a numeric result), add it; *
|
|
* if there was other stuff too, stick in a space first. */
|
|
if (t)
|
|
*rr++ = ' ';
|
|
strcpy(rr, buf);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/* It is called from paramsubst to get the match for ${foo#bar} etc.
|
|
* Bits of fl determines the required action:
|
|
* bit 0: match the end instead of the beginning (% or %%)
|
|
* bit 1: % or # was doubled so get the longest match
|
|
* bit 2: substring match
|
|
* bit 3: include the matched portion
|
|
* bit 4: include the unmatched portion
|
|
* bit 5: the index of the beginning
|
|
* bit 6: the index of the end
|
|
* bit 7: the length of the match
|
|
* bit 8: match the complete string
|
|
* *sp points to the string we have to modify. The n'th match will be
|
|
* returned in *sp. ncalloc is used to get memory for the result string.
|
|
*/
|
|
|
|
/**/
|
|
int
|
|
getmatch(char **sp, char *pat, int fl, int n)
|
|
{
|
|
Comp c;
|
|
char *s = *sp, *t, sav;
|
|
int i, j, l = strlen(*sp);
|
|
|
|
c = parsereg(pat);
|
|
if (!c) {
|
|
zerr("bad pattern: %s", pat, 0);
|
|
return 1;
|
|
}
|
|
if (fl & 256) {
|
|
i = domatch(s, c, 0);
|
|
*sp = get_match_ret(*sp, 0, domatch(s, c, 0) ? l : 0, fl);
|
|
if (! **sp && (((fl & 8) && !i) || ((fl & 16) && i)))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
switch (fl & 7) {
|
|
case 0:
|
|
/* Smallest possible match at head of string: *
|
|
* start adding characters until we get a match. */
|
|
for (i = 0, t = s; i <= l; i++, t++) {
|
|
sav = *t;
|
|
*t = '\0';
|
|
if (domatch(s, c, 0) && !--n) {
|
|
*t = sav;
|
|
*sp = get_match_ret(*sp, 0, i, fl);
|
|
return 1;
|
|
}
|
|
if ((*t = sav) == Meta)
|
|
i++, t++;
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
/* Smallest possible match at tail of string: *
|
|
* move back down string until we get a match. */
|
|
for (t = s + l; t >= s; t--) {
|
|
if (domatch(t, c, 0) && !--n) {
|
|
*sp = get_match_ret(*sp, t - s, l, fl);
|
|
return 1;
|
|
}
|
|
if (t > s+1 && t[-2] == Meta)
|
|
t--;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
/* Largest possible match at head of string: *
|
|
* delete characters from end until we get a match. */
|
|
for (t = s + l; t > s; t--) {
|
|
sav = *t;
|
|
*t = '\0';
|
|
if (domatch(s, c, 0) && !--n) {
|
|
*t = sav;
|
|
*sp = get_match_ret(*sp, 0, t - s, fl);
|
|
return 1;
|
|
}
|
|
*t = sav;
|
|
if (t >= s+2 && t[-2] == Meta)
|
|
t--;
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
/* Largest possible match at tail of string: *
|
|
* move forward along string until we get a match. */
|
|
for (i = 0, t = s; i < l; i++, t++) {
|
|
if (domatch(t, c, 0) && !--n) {
|
|
*sp = get_match_ret(*sp, i, l, fl);
|
|
return 1;
|
|
}
|
|
if (*t == Meta)
|
|
i++, t++;
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
/* Smallest at start, but matching substrings. */
|
|
if (domatch(s + l, c, 0) && !--n) {
|
|
*sp = get_match_ret(*sp, 0, 0, fl);
|
|
return 1;
|
|
}
|
|
for (i = 1; i <= l; i++) {
|
|
for (t = s, j = i; j <= l; j++, t++) {
|
|
sav = s[j];
|
|
s[j] = '\0';
|
|
if (domatch(t, c, 0) && !--n) {
|
|
s[j] = sav;
|
|
*sp = get_match_ret(*sp, t - s, j, fl);
|
|
return 1;
|
|
}
|
|
if ((s[j] = sav) == Meta)
|
|
j++;
|
|
if (*t == Meta)
|
|
t++;
|
|
}
|
|
if (s[i] == Meta)
|
|
i++;
|
|
}
|
|
break;
|
|
|
|
case 5:
|
|
/* Smallest at end, matching substrings */
|
|
if (domatch(s + l, c, 0) && !--n) {
|
|
*sp = get_match_ret(*sp, l, l, fl);
|
|
return 1;
|
|
}
|
|
for (i = l; i--;) {
|
|
if (i && s[i-1] == Meta)
|
|
i--;
|
|
for (t = s + l, j = i; j >= 0; j--, t--) {
|
|
sav = *t;
|
|
*t = '\0';
|
|
if (domatch(s + j, c, 0) && !--n) {
|
|
*t = sav;
|
|
*sp = get_match_ret(*sp, j, t - s, fl);
|
|
return 1;
|
|
}
|
|
*t = sav;
|
|
if (t >= s+2 && t[-2] == Meta)
|
|
t--;
|
|
if (j >= 2 && s[j-2] == Meta)
|
|
j--;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 6:
|
|
/* Largest at start, matching substrings. */
|
|
for (i = l; i; i--) {
|
|
for (t = s, j = i; j <= l; j++, t++) {
|
|
sav = s[j];
|
|
s[j] = '\0';
|
|
if (domatch(t, c, 0) && !--n) {
|
|
s[j] = sav;
|
|
*sp = get_match_ret(*sp, t - s, j, fl);
|
|
return 1;
|
|
}
|
|
if ((s[j] = sav) == Meta)
|
|
j++;
|
|
if (*t == Meta)
|
|
t++;
|
|
}
|
|
if (i >= 2 && s[i-2] == Meta)
|
|
i--;
|
|
}
|
|
if (domatch(s + l, c, 0) && !--n) {
|
|
*sp = get_match_ret(*sp, 0, 0, fl);
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case 7:
|
|
/* Largest at end, matching substrings. */
|
|
for (i = 0; i < l; i++) {
|
|
for (t = s + l, j = i; j >= 0; j--, t--) {
|
|
sav = *t;
|
|
*t = '\0';
|
|
if (domatch(s + j, c, 0) && !--n) {
|
|
*t = sav;
|
|
*sp = get_match_ret(*sp, j, t - s, fl);
|
|
return 1;
|
|
}
|
|
*t = sav;
|
|
if (t >= s+2 && t[-2] == Meta)
|
|
t--;
|
|
if (j >= 2 && s[j-2] == Meta)
|
|
j--;
|
|
}
|
|
if (s[i] == Meta)
|
|
i++;
|
|
}
|
|
if (domatch(s + l, c, 0) && !--n) {
|
|
*sp = get_match_ret(*sp, l, l, fl);
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
/* munge the whole string */
|
|
*sp = get_match_ret(*sp, 0, 0, fl);
|
|
return 1;
|
|
}
|
|
|
|
/* The main entry point for matching a string str against *
|
|
* a compiled pattern c. `fist' indicates whether leading *
|
|
* dots are special. */
|
|
|
|
/**/
|
|
int
|
|
domatch(char *str, Comp c, int fist)
|
|
{
|
|
int ret;
|
|
pptr = str;
|
|
first = fist;
|
|
if (*pptr == Nularg)
|
|
pptr++;
|
|
PERMALLOC {
|
|
ret = doesmatch(c);
|
|
} LASTALLOC;
|
|
return ret;
|
|
}
|
|
|
|
#define untok(C) (itok(C) ? ztokens[(C) - Pound] : (C))
|
|
|
|
/* See if pattern has a matching exclusion (~...) part */
|
|
|
|
/**/
|
|
static int
|
|
excluded(Comp c, char *eptr)
|
|
{
|
|
char *saves = pptr;
|
|
int savei = first, ret;
|
|
|
|
first = 0;
|
|
if (PATHADDP(c) && pathpos) {
|
|
VARARR(char, buf, pathpos + strlen(eptr) + 1);
|
|
|
|
strcpy(buf, pathbuf);
|
|
strcpy(buf + pathpos, eptr);
|
|
pptr = buf;
|
|
ret = doesmatch(c->exclude);
|
|
} else {
|
|
pptr = eptr;
|
|
ret = doesmatch(c->exclude);
|
|
}
|
|
if (*pptr)
|
|
ret = 0;
|
|
|
|
pptr = saves;
|
|
first = savei;
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct gclose {
|
|
char *start;
|
|
char *end;
|
|
};
|
|
typedef struct gclose *Gclose;
|
|
|
|
static int inclosure; /* see comment in doesmatch() */
|
|
|
|
/* Add a list of matches that fit the closure. trystring is a string of
|
|
* the same length as the target string; a non-zero in that string
|
|
* indicates that we have already tried to match the patterns following
|
|
* the closure (c->next) at that point and failed. This means that not
|
|
* only should we not bother using the corresponding match, we should
|
|
* also not bother going any further, since the first time we got to
|
|
* that position (when it was marked), we must already have failed on
|
|
* and backtracked over any further closure matches beyond that point.
|
|
*/
|
|
|
|
/**/
|
|
static void
|
|
addclosures(Comp c, LinkList closlist, int *pdone, char *trystring)
|
|
{
|
|
Gclose gcnode;
|
|
char *opptr = pptr;
|
|
|
|
while (*pptr) {
|
|
if (STARP(c)) {
|
|
if (trystring[(pptr+1)-opptr])
|
|
break;
|
|
gcnode = (Gclose)zalloc(sizeof(struct gclose));
|
|
gcnode->start = pptr;
|
|
gcnode->end = ++pptr;
|
|
} else {
|
|
char *saves = pptr;
|
|
if (OPTIONALP(c) && *pdone >= 1)
|
|
return;
|
|
if (!matchonce(c) || saves == pptr ||
|
|
trystring[pptr-opptr]) {
|
|
pptr = saves;
|
|
break;
|
|
}
|
|
gcnode = (Gclose)zalloc(sizeof(struct gclose));
|
|
gcnode->start = saves;
|
|
gcnode->end = pptr;
|
|
}
|
|
pushnode(closlist, gcnode);
|
|
(*pdone)++;
|
|
}
|
|
}
|
|
|
|
/* see if current string in pptr matches c */
|
|
|
|
/**/
|
|
static int
|
|
doesmatch(Comp c)
|
|
{
|
|
if (CLOSUREP(c)) {
|
|
int done, retflag = 0;
|
|
char *saves, *trystring, *opptr;
|
|
LinkList closlist;
|
|
Gclose gcnode;
|
|
|
|
if (first && *pptr == '.')
|
|
return 0;
|
|
|
|
if (!inclosure && !c->left) {
|
|
/* We are not inside another closure, and the current
|
|
* pattern is a simple string. We handle this very common
|
|
* case specially: otherwise, matches like *foo* are
|
|
* extremely slow. Here, instead of backtracking, we track
|
|
* forward until we get a match. At top level, we are bound
|
|
* to get there eventually, so this is OK.
|
|
*/
|
|
char looka;
|
|
|
|
if (STARP(c) && c->next &&
|
|
!c->next->left && (looka = *c->next->str) &&
|
|
!itok(looka)) {
|
|
/* Another simple optimisation for a very common case:
|
|
* we are processing a * and there is
|
|
* an ordinary character match next. We look ahead for
|
|
* that character, taking care of Meta bytes.
|
|
*/
|
|
while (*pptr) {
|
|
for (; *pptr; pptr++) {
|
|
if (*pptr == Meta)
|
|
pptr++;
|
|
else if (*pptr == looka)
|
|
break;
|
|
}
|
|
if (!*(saves = pptr))
|
|
break;
|
|
if (doesmatch(c->next))
|
|
return 1;
|
|
pptr = saves+1;
|
|
}
|
|
} else {
|
|
/* Standard track-forward code */
|
|
for (done = 0; ; done++) {
|
|
saves = pptr;
|
|
if ((done || ONEHASHP(c) || OPTIONALP(c)) &&
|
|
((!c->next && (!LASTP(c) || !*pptr)) ||
|
|
(c->next && doesmatch(c->next))))
|
|
return 1;
|
|
if (done && OPTIONALP(c))
|
|
return 0;
|
|
pptr = saves;
|
|
first = 0;
|
|
if (STARP(c)) {
|
|
if (!*pptr)
|
|
return 0;
|
|
pptr++;
|
|
} else if (!matchonce(c) || pptr == saves)
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
/* The full, gory backtracking code is now necessary. */
|
|
inclosure++;
|
|
closlist = newlinklist();
|
|
trystring = zcalloc(strlen(pptr)+1);
|
|
opptr = pptr;
|
|
|
|
/* Start by making a list where each match is as long
|
|
* as possible. We later have to take account of the
|
|
* fact that earlier matches may be too long.
|
|
*/
|
|
done = 0;
|
|
addclosures(c, closlist, &done, trystring);
|
|
for (;;) {
|
|
if (TWOHASHP(c) && !done)
|
|
break;
|
|
saves = pptr;
|
|
/* do we really want this LASTP here?? */
|
|
if ((!c->next && (!LASTP(c) || !*pptr)) ||
|
|
(c->next && doesmatch(c->next))) {
|
|
retflag = 1;
|
|
break;
|
|
}
|
|
trystring[saves-opptr] = 1;
|
|
/*
|
|
* If we failed, the first thing to try is whether we can
|
|
* shorten the match using the last pattern in the closure.
|
|
*/
|
|
gcnode = firstnode(closlist) ? peekfirst(closlist) : NULL;
|
|
if (gcnode && --gcnode->end > gcnode->start
|
|
&& (gcnode->end[-1] != Meta ||
|
|
--gcnode->end > gcnode->start)) {
|
|
char savec = *gcnode->end;
|
|
*gcnode->end = '\0';
|
|
pptr = gcnode->start;
|
|
if (matchonce(c) && pptr != gcnode->start
|
|
&& !trystring[pptr-opptr]) {
|
|
*gcnode->end = savec;
|
|
gcnode->end = pptr;
|
|
/* Try again to construct a list based on
|
|
* this new position
|
|
*/
|
|
addclosures(c, closlist, &done, trystring+(pptr-opptr));
|
|
continue;
|
|
}
|
|
*gcnode->end = savec;
|
|
}
|
|
/* We've now exhausted the possibilities with that match,
|
|
* backtrack to the previous.
|
|
*/
|
|
if ((gcnode = (Gclose)getlinknode(closlist))) {
|
|
pptr = gcnode->start;
|
|
zfree(gcnode, sizeof(struct gclose));
|
|
done--;
|
|
} else
|
|
break;
|
|
}
|
|
freelinklist(closlist, free);
|
|
zfree(trystring, strlen(opptr)+1);
|
|
inclosure--;
|
|
|
|
return retflag;
|
|
} else
|
|
return matchonce(c);
|
|
}
|
|
|
|
/**/
|
|
static int
|
|
posix_range(char **patptr, int ch)
|
|
{
|
|
/* Match POSIX ranges, which correspond to ctype macros, *
|
|
* e.g. [:alpha:] -> isalpha. It just doesn't seem worth *
|
|
* the palaver of creating a hash table for this. */
|
|
char *start = *patptr;
|
|
int len;
|
|
|
|
/* we made sure in parsecomp() there was a ':' to search for */
|
|
*patptr = strchr(start, ':');
|
|
len = (*patptr)++ - start;
|
|
|
|
if (!strncmp(start, "alpha", len))
|
|
return isalpha(ch);
|
|
if (!strncmp(start, "alnum", len))
|
|
return isalnum(ch);
|
|
if (!strncmp(start, "blank", len))
|
|
return ch == ' ' || ch == '\t';
|
|
if (!strncmp(start, "cntrl", len))
|
|
return iscntrl(ch);
|
|
if (!strncmp(start, "digit", len))
|
|
return isdigit(ch);
|
|
if (!strncmp(start, "graph", len))
|
|
return isgraph(ch);
|
|
if (!strncmp(start, "lower", len))
|
|
return islower(ch);
|
|
if (!strncmp(start, "print", len))
|
|
return isprint(ch);
|
|
if (!strncmp(start, "punct", len))
|
|
return ispunct(ch);
|
|
if (!strncmp(start, "space", len))
|
|
return isspace(ch);
|
|
if (!strncmp(start, "upper", len))
|
|
return isupper(ch);
|
|
if (!strncmp(start, "xdigit", len))
|
|
return isxdigit(ch);
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
rangematch(char **patptr, int ch, int rchar)
|
|
{
|
|
/* Check for a character in a [...] or [^...]. The [ *
|
|
* and optional ^ have already been skipped. */
|
|
|
|
char *pat = *patptr;
|
|
#ifdef HAVE_STRCOLL
|
|
char l_buf[2], r_buf[2], ch_buf[2];
|
|
|
|
ch_buf[0] = ch;
|
|
l_buf[1] = r_buf[1] = ch_buf[1] = '\0';
|
|
#endif
|
|
|
|
#define PAT(X) (pat[X] == Meta ? pat[(X)+1] ^ 32 : untok(pat[X]))
|
|
#define PPAT(X) (pat[(X)-1] == Meta ? pat[X] ^ 32 : untok(pat[X]))
|
|
|
|
for (pat++; *pat != Outbrack && *pat;
|
|
*pat == Meta ? pat += 2 : pat++) {
|
|
if (*pat == Inbrack) {
|
|
/* Inbrack can only occur inside a range if we found [:...:]. */
|
|
pat += 2;
|
|
if (posix_range(&pat, ch))
|
|
break;
|
|
} else if (*pat == '-' && pat[-1] != rchar &&
|
|
pat[1] != Outbrack) {
|
|
#ifdef HAVE_STRCOLL
|
|
l_buf[0] = PPAT(-1);
|
|
r_buf[0] = PAT(1);
|
|
if (strcoll(l_buf, ch_buf) <= 0 &&
|
|
strcoll(ch_buf, r_buf) <= 0)
|
|
#else
|
|
if (PPAT(-1) <= ch && PAT(1) >= ch)
|
|
#endif
|
|
break;
|
|
} else if (ch == PAT(0))
|
|
break;
|
|
}
|
|
|
|
*patptr = pat;
|
|
}
|
|
|
|
/**/
|
|
static int
|
|
matchonce(Comp c)
|
|
{
|
|
char *pat = c->str;
|
|
for (;;) {
|
|
/* loop until success or failure of pattern */
|
|
if (!pat || !*pat) {
|
|
/* No current pattern (c->str). */
|
|
char *saves;
|
|
int savei;
|
|
|
|
if (errflag)
|
|
return 0;
|
|
/* Remember state in case we need to go back and *
|
|
* check for exclusion of pattern or alternatives. */
|
|
saves = pptr;
|
|
savei = first;
|
|
/* Loop over alternatives with exclusions: (foo~bar|...). *
|
|
* Exclusions apply to the pattern in c->left. */
|
|
if (c->left || c->right) {
|
|
int ret = 0, ret2 = 0;
|
|
if (c->exclude) {
|
|
char *exclend = 0;
|
|
|
|
/* We may need to back up on things like `(*~foo)'
|
|
* if the `*' matched `foo' but needs to match `fo'.
|
|
* exclend marks the end of the shortened text. We
|
|
* need to restore it to match the tail.
|
|
* We set `inclosure' because we need the more
|
|
* sophisticated code in doesmatch() for any nested
|
|
* closures.
|
|
*/
|
|
inclosure++;
|
|
|
|
while (!exclend || exclend >= pptr) {
|
|
char exclsav = 0;
|
|
if (exclend) {
|
|
exclsav = *exclend;
|
|
*exclend = '\0';
|
|
}
|
|
if ((ret = doesmatch(c->left))) {
|
|
if (exclend)
|
|
*exclend = exclsav;
|
|
exclsav = *(exclend = pptr);
|
|
*exclend = '\0';
|
|
ret2 = !excluded(c, saves);
|
|
}
|
|
if (exclend)
|
|
*exclend = exclsav;
|
|
|
|
if (!ret)
|
|
break;
|
|
if ((ret = ret2 &&
|
|
((!c->next && (!LASTP(c) || !*pptr))
|
|
|| (c->next && doesmatch(c->next)))) ||
|
|
(!c->next && LASTP(c)))
|
|
break;
|
|
/* Back up if necessary: exclend gives the position
|
|
* of the end of the match we are excluding,
|
|
* so only try to match to there.
|
|
*/
|
|
exclend--;
|
|
pptr = saves;
|
|
}
|
|
inclosure--;
|
|
if (ret)
|
|
return 1;
|
|
} else
|
|
ret = doesmatch(c->left);
|
|
ret2 = 0;
|
|
if (c->right && (!ret || inclosure)) {
|
|
/* If in a closure, we always want the longest match. */
|
|
char *newpptr = pptr;
|
|
pptr = saves;
|
|
first = savei;
|
|
ret2 = doesmatch(c->right);
|
|
if (ret && (!ret2 || pptr < newpptr))
|
|
pptr = newpptr;
|
|
}
|
|
if (!ret && !ret2)
|
|
return 0;
|
|
}
|
|
if (CLOSUREP(c))
|
|
return 1;
|
|
if (!c->next) /* no more patterns left */
|
|
return (!LASTP(c) || !*pptr);
|
|
/* optimisation when next pattern is not a closure */
|
|
if (!CLOSUREP(c->next)) {
|
|
c = c->next;
|
|
pat = c->str;
|
|
continue;
|
|
}
|
|
return doesmatch(c->next);
|
|
}
|
|
/* Don't match leading dot if first is set */
|
|
if (first && *pptr == '.' && *pat != '.')
|
|
return 0;
|
|
if (*pat == Star) { /* final * is not expanded to ?#; returns success */
|
|
while (*pptr)
|
|
pptr++;
|
|
return 1;
|
|
}
|
|
first = 0; /* finished checking start of pattern */
|
|
if (*pat == Quest && *pptr) {
|
|
/* match exactly one character */
|
|
if (*pptr == Meta)
|
|
pptr++;
|
|
pptr++;
|
|
pat++;
|
|
continue;
|
|
}
|
|
if (*pat == Inbrack) {
|
|
/* Match groups of characters */
|
|
char ch;
|
|
|
|
if (!*pptr)
|
|
break;
|
|
ch = *pptr == Meta ? pptr[1] ^ 32 : *pptr;
|
|
if (pat[1] == Hat || pat[1] == '^' || pat[1] == '!') {
|
|
/* group is negated */
|
|
*++pat = Hat;
|
|
rangematch(&pat, ch, Hat);
|
|
DPUTS(!*pat, "BUG: something is very wrong in doesmatch()");
|
|
if (*pat != Outbrack)
|
|
break;
|
|
pat++;
|
|
*pptr == Meta ? pptr += 2 : pptr++;
|
|
continue;
|
|
} else {
|
|
/* pattern is not negated (affirmed? asserted?) */
|
|
rangematch(&pat, ch, Inbrack);
|
|
DPUTS(!pat || !*pat, "BUG: something is very wrong in doesmatch()");
|
|
if (*pat == Outbrack)
|
|
break;
|
|
for (*pptr == Meta ? pptr += 2 : pptr++;
|
|
*pat != Outbrack; pat++);
|
|
pat++;
|
|
continue;
|
|
}
|
|
}
|
|
if (*pat == Inang) {
|
|
/* Numeric globbing. */
|
|
unsigned long t1, t2, t3;
|
|
char *ptr;
|
|
|
|
if (!idigit(*pptr))
|
|
break;
|
|
if (*++pat == Outang ||
|
|
(*pat == '-' && pat[1] == Outang && ++pat)) {
|
|
/* <> or <->: any number matches */
|
|
while (idigit(*++pptr));
|
|
pat++;
|
|
} else {
|
|
/* Flag that there is no upper limit */
|
|
int not3 = 0;
|
|
char *opptr = pptr;
|
|
/*
|
|
* Form is <a-b>, where a or b are numbers or blank.
|
|
* t1 = number supplied: must be positive, so use
|
|
* unsigned arithmetic.
|
|
*/
|
|
t1 = (unsigned long)zstrtol(pptr, &ptr, 10);
|
|
pptr = ptr;
|
|
/* t2 = lower limit */
|
|
if (idigit(*pat))
|
|
t2 = (unsigned long)zstrtol(pat, &ptr, 10);
|
|
else
|
|
t2 = 0, ptr = pat;
|
|
if (*ptr != '-' || (not3 = (ptr[1] == Outang)))
|
|
/* exact match or no upper limit */
|
|
t3 = t2, pat = ptr + not3;
|
|
else /* t3 = upper limit */
|
|
t3 = (unsigned long)zstrtol(ptr + 1, &pat, 10);
|
|
DPUTS(*pat != Outang, "BUG: wrong internal range pattern");
|
|
pat++;
|
|
/*
|
|
* If the number found is too large for the pattern,
|
|
* try matching just the first part. This way
|
|
* we always get the longest possible match.
|
|
*/
|
|
while (!not3 && t1 > t3 && pptr > opptr+1) {
|
|
pptr--;
|
|
t1 /= 10;
|
|
}
|
|
if (t1 < t2 || (!not3 && t1 > t3))
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if (*pptr == *pat) {
|
|
/* just plain old characters */
|
|
pptr++;
|
|
pat++;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* turn a string into a Comp struct: this doesn't treat / specially */
|
|
|
|
/**/
|
|
Comp
|
|
parsereg(char *str)
|
|
{
|
|
remnulargs(str);
|
|
mode = 1; /* no path components */
|
|
pptr = str;
|
|
tail = NULL;
|
|
return parsecompsw(GF_TOPLEV);
|
|
}
|
|
|
|
/* blindly turn a string into a tokenised expression without lexing */
|
|
|
|
/**/
|
|
void
|
|
tokenize(char *s)
|
|
{
|
|
char *t;
|
|
int bslash = 0;
|
|
|
|
for (; *s; s++) {
|
|
cont:
|
|
switch (*s) {
|
|
case Bnull:
|
|
case '\\':
|
|
if (bslash) {
|
|
s[-1] = Bnull;
|
|
break;
|
|
}
|
|
bslash = 1;
|
|
continue;
|
|
case '<':
|
|
if (isset(SHGLOB))
|
|
break;
|
|
if (bslash) {
|
|
s[-1] = Bnull;
|
|
break;
|
|
}
|
|
t = s;
|
|
while (idigit(*++s));
|
|
if (*s != '-')
|
|
goto cont;
|
|
while (idigit(*++s));
|
|
if (*s != '>')
|
|
goto cont;
|
|
*t = Inang;
|
|
*s = Outang;
|
|
break;
|
|
case '^':
|
|
case '#':
|
|
case '~':
|
|
if (unset(EXTENDEDGLOB))
|
|
break;
|
|
case '(':
|
|
case '|':
|
|
case ')':
|
|
if (isset(SHGLOB))
|
|
break;
|
|
case '[':
|
|
case ']':
|
|
case '*':
|
|
case '?':
|
|
for (t = ztokens; *t; t++)
|
|
if (*t == *s) {
|
|
if (bslash)
|
|
s[-1] = Bnull;
|
|
else
|
|
*s = (t - ztokens) + Pound;
|
|
break;
|
|
}
|
|
}
|
|
bslash = 0;
|
|
}
|
|
}
|
|
|
|
/* remove unnecessary Nulargs */
|
|
|
|
/**/
|
|
void
|
|
remnulargs(char *s)
|
|
{
|
|
int nl = *s;
|
|
char *t = s;
|
|
|
|
while (*s)
|
|
if (INULL(*s))
|
|
chuck(s);
|
|
else
|
|
s++;
|
|
if (!*t && nl) {
|
|
t[0] = Nularg;
|
|
t[1] = '\0';
|
|
}
|
|
}
|
|
|
|
/* qualifier functions: mostly self-explanatory, see glob(). */
|
|
|
|
/* device number */
|
|
|
|
/**/
|
|
static int
|
|
qualdev(struct stat *buf, long dv)
|
|
{
|
|
return buf->st_dev == dv;
|
|
}
|
|
|
|
/* number of hard links to file */
|
|
|
|
/**/
|
|
static int
|
|
qualnlink(struct stat *buf, long ct)
|
|
{
|
|
return (range < 0 ? buf->st_nlink < ct :
|
|
range > 0 ? buf->st_nlink > ct :
|
|
buf->st_nlink == ct);
|
|
}
|
|
|
|
/* user ID */
|
|
|
|
/**/
|
|
static int
|
|
qualuid(struct stat *buf, long uid)
|
|
{
|
|
return buf->st_uid == uid;
|
|
}
|
|
|
|
/* group ID */
|
|
|
|
/**/
|
|
static int
|
|
qualgid(struct stat *buf, long gid)
|
|
{
|
|
return buf->st_gid == gid;
|
|
}
|
|
|
|
/* device special file? */
|
|
|
|
/**/
|
|
static int
|
|
qualisdev(struct stat *buf, long junk)
|
|
{
|
|
return S_ISBLK(buf->st_mode) || S_ISCHR(buf->st_mode);
|
|
}
|
|
|
|
/* block special file? */
|
|
|
|
/**/
|
|
static int
|
|
qualisblk(struct stat *buf, long junk)
|
|
{
|
|
return S_ISBLK(buf->st_mode);
|
|
}
|
|
|
|
/* character special file? */
|
|
|
|
/**/
|
|
static int
|
|
qualischr(struct stat *buf, long junk)
|
|
{
|
|
return S_ISCHR(buf->st_mode);
|
|
}
|
|
|
|
/* directory? */
|
|
|
|
/**/
|
|
static int
|
|
qualisdir(struct stat *buf, long junk)
|
|
{
|
|
return S_ISDIR(buf->st_mode);
|
|
}
|
|
|
|
/* FIFO? */
|
|
|
|
/**/
|
|
static int
|
|
qualisfifo(struct stat *buf, long junk)
|
|
{
|
|
return S_ISFIFO(buf->st_mode);
|
|
}
|
|
|
|
/* symbolic link? */
|
|
|
|
/**/
|
|
static int
|
|
qualislnk(struct stat *buf, long junk)
|
|
{
|
|
return S_ISLNK(buf->st_mode);
|
|
}
|
|
|
|
/* regular file? */
|
|
|
|
/**/
|
|
static int
|
|
qualisreg(struct stat *buf, long junk)
|
|
{
|
|
return S_ISREG(buf->st_mode);
|
|
}
|
|
|
|
/* socket? */
|
|
|
|
/**/
|
|
static int
|
|
qualissock(struct stat *buf, long junk)
|
|
{
|
|
return S_ISSOCK(buf->st_mode);
|
|
}
|
|
|
|
/* given flag is set in mode */
|
|
|
|
/**/
|
|
static int
|
|
qualflags(struct stat *buf, long mod)
|
|
{
|
|
return mode_to_octal(buf->st_mode) & mod;
|
|
}
|
|
|
|
/* mode matches number supplied exactly */
|
|
|
|
/**/
|
|
static int
|
|
qualeqflags(struct stat *buf, long mod)
|
|
{
|
|
return mode_to_octal(buf->st_mode) == mod;
|
|
}
|
|
|
|
/* regular executable file? */
|
|
|
|
/**/
|
|
static int
|
|
qualiscom(struct stat *buf, long mod)
|
|
{
|
|
return S_ISREG(buf->st_mode) && (buf->st_mode & S_IXUGO);
|
|
}
|
|
|
|
/* size in required range? */
|
|
|
|
/**/
|
|
static int
|
|
qualsize(struct stat *buf, long size)
|
|
{
|
|
unsigned long scaled = buf->st_size;
|
|
|
|
switch (units) {
|
|
case TT_POSIX_BLOCKS:
|
|
scaled += 511l;
|
|
scaled /= 512l;
|
|
break;
|
|
case TT_KILOBYTES:
|
|
scaled += 1023l;
|
|
scaled /= 1024l;
|
|
break;
|
|
case TT_MEGABYTES:
|
|
scaled += 1048575l;
|
|
scaled /= 1048576l;
|
|
break;
|
|
}
|
|
|
|
return (range < 0 ? scaled < (unsigned long) size :
|
|
range > 0 ? scaled > (unsigned long) size :
|
|
scaled == (unsigned long) size);
|
|
}
|
|
|
|
/* time in required range? */
|
|
|
|
/**/
|
|
static int
|
|
qualtime(struct stat *buf, long days)
|
|
{
|
|
time_t now, diff;
|
|
|
|
time(&now);
|
|
diff = now - (amc == 0 ? buf->st_atime : amc == 1 ? buf->st_mtime :
|
|
buf->st_ctime);
|
|
/* handle multipliers indicating units */
|
|
switch (units) {
|
|
case TT_DAYS:
|
|
diff /= 86400l;
|
|
break;
|
|
case TT_HOURS:
|
|
diff /= 3600l;
|
|
break;
|
|
case TT_MINS:
|
|
diff /= 60l;
|
|
break;
|
|
case TT_WEEKS:
|
|
diff /= 604800l;
|
|
break;
|
|
case TT_MONTHS:
|
|
diff /= 2592000l;
|
|
break;
|
|
}
|
|
|
|
return (range < 0 ? diff < days :
|
|
range > 0 ? diff > days :
|
|
diff == days);
|
|
}
|