zsh/Src/glob.c

2686 lines
64 KiB
C
Raw Normal View History

1999-04-15 20:05:38 +02:00
/*
* 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
1999-04-15 20:05:38 +02:00
/* flag for CSHNULLGLOB */
typedef struct gmatch *Gmatch;
struct gmatch {
char *name;
off_t size ALIGN64;
long atime;
long mtime;
long ctime;
long links;
off_t _size ALIGN64;
long _atime;
long _mtime;
long _ctime;
long _links;
};
#define GS_NAME 1
#define GS_DEPTH 2
#define GS_SIZE 4
#define GS_ATIME 8
#define GS_MTIME 16
#define GS_CTIME 32
#define GS_LINKS 64
#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 4096
#define GS_NORMAL (GS_SIZE | GS_ATIME | GS_MTIME | GS_CTIME | GS_LINKS)
#define GS_LINKED (GS_NORMAL << GS_SHIFT)
1999-04-15 20:05:38 +02:00
/**/
int badcshglob;
/**/
int pathpos; /* position in pathbuf (needed by pattern code) */
/**/
char *pathbuf; /* pathname buffer (needed by pattern code) */
1999-04-15 20:05:38 +02:00
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
1999-04-15 20:05:38 +02:00
#define TT_BYTES 0
#define TT_POSIX_BLOCKS 1
#define TT_KILOBYTES 2
#define TT_MEGABYTES 3
typedef int (*TestMatchFunc) _((char *, struct stat *, off_t, char *));
1999-04-15 20:05:38 +02:00
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 */
1999-04-15 20:05:38 +02:00
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 */
1999-04-15 20:05:38 +02:00
};
/* Prefix, suffix for doing zle trickery */
/**/
mod_export char *glob_pre, *glob_suf;
/* 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, gd_gf_sortlist[11];
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)
/* and macros for save/restore */
#define save_globstate(N) \
do { \
memcpy(&(N), &curglobdata, sizeof(struct globdata)); \
(N).gd_pathpos = pathpos; \
(N).gd_pathbuf = pathbuf; \
(N).gd_pathbufsz = 0; \
(N).gd_pathbuf = NULL; \
(N).gd_glob_pre = glob_pre; \
(N).gd_glob_suf = glob_suf; \
} while (0)
#define restore_globstate(N) \
do { \
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; \
} while (0)
1999-04-15 20:05:38 +02:00
/* pathname component in filename patterns */
struct complist {
Complist next;
Patprog pat;
1999-04-15 20:05:38 +02:00
int closure; /* 1 if this is a (foo/)# */
int follow; /* 1 to go thru symlinks */
};
/* Next character after one which may be a Meta (x is any char *) */
#define METANEXT(x) (*(x) == Meta ? (x)+2 : (x)+1)
/*
* 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)
/*
* Return unmetafied char from string (x is any char *)
*/
#define UNMETA(x) (*(x) == Meta ? (x)[1] ^ 32 : *(x))
1999-04-15 20:05:38 +02:00
/* 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) {
/*
* Don't add the '.' if the path so far is empty, since
* then we get bogus empty strings inserted as files.
*/
1999-04-15 20:05:38 +02:00
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);
}
/* This may be set by qualifier functions to an array of strings to insert
* into the list instead of the original string. */
char **inserts;
1999-04-15 20:05:38 +02:00
/* add a match to the list */
/**/
static void
insert(char *s, int checked)
{
struct stat buf, buf2, *bp;
char *news = s;
int statted = 0;
inserts = NULL;
1999-04-15 20:05:38 +02:00
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;
1999-04-15 20:05:38 +02:00
mode = buf2.st_mode;
}
if (gf_listtypes || S_ISDIR(mode)) {
int ll = strlen(s);
news = (char *) hcalloc(ll + 2);
1999-04-15 20:05:38 +02:00
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;
news = dyncat(pathbuf, news);
statted = 1;
1999-04-15 20:05:38 +02:00
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)) {
1999-04-15 20:05:38 +02:00
/* If (sense & 2), we're following links */
if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0))
memcpy(&buf2, &buf, sizeof(buf));
statted |= 2;
1999-04-15 20:05:38 +02:00
}
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) {
1999-04-15 20:05:38 +02:00
/* Try next alternative, or return if there are no more */
if (!(qo = qo->or))
return;
qn = qo;
continue;
}
qn = qn->next;
}
} else if (!checked) {
if (statfullpath(s, NULL, 1))
return;
statted = 1;
news = dyncat(pathbuf, news);
} else
news = dyncat(pathbuf, news);
1999-04-15 20:05:38 +02:00
while (!inserts || (news = dupstring(*inserts++))) {
if (colonmod) {
/* Handle the remainder of the qualifer: e.g. (:r:s/foo/bar/). */
s = colonmod;
modify(&news, &s);
}
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;
}
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;
}
matchptr++;
1999-04-15 20:05:38 +02:00
if (++matchct == matchsz) {
matchbuf = (Gmatch )realloc((char *)matchbuf,
sizeof(struct gmatch) * (matchsz *= 2));
matchptr = matchbuf + matchct;
}
if (!inserts)
break;
1999-04-15 20:05:38 +02:00
}
}
/* Check to see if str is eligible for filename generation. */
/**/
mod_export int
1999-04-15 20:05:38 +02:00
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)
{
Patprog p;
1999-04-15 20:05:38 +02:00
int closure;
int pbcwdsav = pathbufcwd;
int errssofar = errsfound;
1999-04-15 20:05:38 +02:00
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 */
1999-04-15 20:05:38 +02:00
if (q->closure == 2) /* (foo/)## - match one or more dirs */
q->closure = 1;
else
scanner(q->next);
}
p = q->pat;
1999-04-15 20:05:38 +02:00
/* 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;
1999-04-15 20:05:38 +02:00
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) {
int add = 1;
if (q->closure && *pathbuf) {
if (!strcmp(str, "."))
add = 0;
else if (!strcmp(str, "..")) {
struct stat sc, sr;
add = (stat("/", &sr) || stat(pathbuf, &sc) ||
sr.st_ino != sc.st_ino ||
sr.st_dev != sc.st_dev);
}
}
if (add) {
addpath(str);
if (!closure || !statfullpath("", NULL, 1))
scanner((q->closure) ? q : q->next);
pathbuf[pathpos = oppos] = '\0';
}
1999-04-15 20:05:38 +02:00
}
} else
insert(str, 0);
1999-04-15 20:05:38 +02:00
} else {
/* Do pattern matching on current path section. */
char *fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : ".";
1999-04-15 20:05:38 +02:00
int dirs = !!q->next;
DIR *lock = opendir(fn);
1999-04-15 20:05:38 +02:00
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)) {
1999-04-15 20:05:38 +02:00
/* 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 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;
}
1999-04-15 20:05:38 +02:00
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
+ sizeof(int));
1999-04-15 20:05:38 +02:00
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);
1999-04-15 20:05:38 +02:00
} 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; ) {
1999-04-15 20:05:38 +02:00
addpath(fn);
fn += strlen(fn) + 1;
memcpy((char *)&errsfound, fn, sizeof(int));
fn += sizeof(int);
1999-04-15 20:05:38 +02:00
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;
}
}
/* This function tokenizes a zsh glob pattern */
/**/
static Complist
parsecomplist(char *instr)
1999-04-15 20:05:38 +02:00
{
Patprog p1;
Complist l1;
1999-04-15 20:05:38 +02:00
char *str;
int compflags = gf_noglobdots ? (PAT_FILE|PAT_NOGLD) : PAT_FILE;
1999-04-15 20:05:38 +02:00
if (instr[0] == Star && instr[1] == Star &&
(instr[2] == '/' || (instr[2] == Star && instr[3] == '/'))) {
1999-04-15 20:05:38 +02:00
/* Match any number of directories. */
int follow;
/* with three stars, follow symbolic links */
follow = (instr[2] == Star);
instr += (3 + follow);
1999-04-15 20:05:38 +02:00
/* Now get the next path component if there is one. */
l1 = (Complist) zhalloc(sizeof *l1);
if ((l1->next = parsecomplist(instr)) == NULL) {
1999-04-15 20:05:38 +02:00
errflag = 1;
return NULL;
}
l1->pat = patcompile(NULL, compflags | PAT_ANY, NULL);
l1->closure = 1; /* ...zero or more times. */
l1->follow = follow;
return l1;
1999-04-15 20:05:38 +02:00
}
/* Parse repeated directories such as (dir/)# and (dir/)## */
if (*(str = instr) == Inpar && !skipparens(Inpar, Outpar, (char **)&str) &&
1999-04-15 20:05:38 +02:00
*str == Pound && isset(EXTENDEDGLOB) && str[-2] == '/') {
instr++;
if (!(p1 = patcompile(instr, compflags, &instr)))
1999-04-15 20:05:38 +02:00
return NULL;
if (instr[0] == '/' && instr[1] == Outpar && instr[2] == Pound) {
1999-04-15 20:05:38 +02:00
int pdflag = 0;
instr += 3;
if (*instr == Pound) {
1999-04-15 20:05:38 +02:00
pdflag = 1;
instr++;
1999-04-15 20:05:38 +02:00
}
l1 = (Complist) zhalloc(sizeof *l1);
l1->pat = p1;
l1->closure = 1 + pdflag;
l1->follow = 0;
l1->next = parsecomplist(instr);
return (l1->pat) ? l1 : NULL;
1999-04-15 20:05:38 +02:00
}
} else {
/* parse single path component */
if (!(p1 = patcompile(instr, compflags|PAT_FILET, &instr)))
1999-04-15 20:05:38 +02:00
return NULL;
/* then do the remaining path compoents */
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;
1999-04-15 20:05:38 +02:00
}
}
errflag = 1;
return NULL;
}
/* turn a string into a Complist struct: this has path components */
/**/
static Complist
parsepat(char *str)
{
long assert;
patcompstart();
/*
* Check for initial globbing flags, so that they don't form
* a bogus path component.
*/
if ((*str == Inpar && str[1] == Pound && isset(EXTENDEDGLOB)) ||
(isset(KSHGLOB) && *str == '@' && str[1] == Inpar &&
str[2] == Pound)) {
str += (*str == Inpar) ? 2 : 3;
if (!patgetglobflags(&str, &assert))
return NULL;
}
/* Now there is no (#X) in front, we can check the path. */
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';
return parsecomplist(str);
1999-04-15 20:05:38 +02:00
}
/* get number after qualifier */
/**/
static off_t
1999-04-15 20:05:38 +02:00
qgetnum(char **s)
{
off_t v = 0;
1999-04-15 20:05:38 +02:00
if (!idigit(**s)) {
zerr("number expected", NULL, 0);
return 0;
}
while (idigit(**s))
v = v * 10 + *(*s)++ - '0';
return v;
}
/* get mode spec after qualifier */
1999-04-15 20:05:38 +02:00
/**/
static zlong
qgetmodespec(char **s)
1999-04-15 20:05:38 +02:00
{
zlong yes = 0, no = 0, val, mask, t;
char *p = *s, c, how, end;
1999-04-15 20:05:38 +02:00
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++;
1999-04-15 20:05:38 +02:00
}
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", NULL, 0);
return 0;
}
}
if (how == '=' || how == '+') {
yes |= val & mask;
val = ~val;
}
if (how == '=' || how == '-')
no |= val & mask;
} else {
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", NULL, 0);
return 0;
}
if (how == '=') {
yes = (yes & ~t) | val;
no = (no & ~t) | (~val & ~t);
} else if (how == '+')
yes |= val;
else
no |= val;
}
} while (end && c != end);
*s = p;
return ((yes & 07777) | ((no & 07777) << 12));
}
static int
gmatchcmp(Gmatch a, Gmatch b)
{
int i, *s;
off_t r = 0L;
for (i = gf_nsorts, s = gf_sortlist; i; i--, s++) {
switch (*s & ~GS_DESC) {
case GS_NAME:
r = notstrcmp(&a->name, &b->name);
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++;
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_SIZE:
r = b->size - a->size;
break;
case GS_ATIME:
r = a->atime - b->atime;
break;
case GS_MTIME:
r = a->mtime - b->mtime;
break;
case GS_CTIME:
r = a->ctime - b->ctime;
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;
break;
case GS__MTIME:
r = a->_mtime - b->_mtime;
break;
case GS__CTIME:
r = a->_ctime - b->_ctime;
break;
case GS__LINKS:
r = b->_links - a->_links;
break;
}
if (r)
return (int) ((*s & GS_DESC) ? -r : r);
}
return 0;
1999-04-15 20:05:38 +02:00
}
/* 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, int nountok)
1999-04-15 20:05:38 +02:00
{
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 */
1999-04-15 20:05:38 +02:00
if (unset(GLOBOPT) || !haswilds(ostr)) {
if (!nountok)
untokenize(ostr);
1999-04-15 20:05:38 +02:00
return;
}
save_globstate(saved);
1999-04-15 20:05:38 +02:00
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);
gf_numsort = isset(NUMERICGLOBSORT);
gf_sorts = gf_nsorts = 0;
1999-04-15 20:05:38 +02:00
/* 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 && (!isset(EXTENDEDGLOB) || s[1] != Pound)) {
1999-04-15 20:05:38 +02:00
/* Real qualifiers found. */
int sense = 0; /* bit 0 for match (0)/don't match (1) */
/* bit 1 for follow links (2), don't (0) */
off_t data = 0; /* Any numerical argument required */
char *sdata = NULL; /* Any list argument required */
int (*func) _((char *, Statptr, off_t, char *));
1999-04-15 20:05:38 +02:00
str[sl-1] = 0;
*s++ = 0;
while (*s && !colonmod) {
func = (int (*) _((char *, Statptr, off_t, char *)))0;
1999-04-15 20:05:38 +02:00
if (idigit(*s)) {
/* Store numeric argument for qualifier */
func = qualflags;
data = 0;
sdata = NULL;
1999-04-15 20:05:38 +02:00
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;
g_amc = -1;
1999-04-15 20:05:38 +02:00
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 'f':
/* Match modes with chmod-spec. */
func = qualmodeflags;
data = qgetmodespec(&s);
1999-04-15 20:05:38 +02:00
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;
1999-04-15 20:05:38 +02:00
case 'a':
/* Access time in given range */
g_amc = 0;
1999-04-15 20:05:38 +02:00
func = qualtime;
goto getrange;
case 'm':
/* Modification time in given range */
g_amc = 1;
1999-04-15 20:05:38 +02:00
func = qualtime;
goto getrange;
case 'c':
/* Inode creation time in given range */
g_amc = 2;
1999-04-15 20:05:38 +02:00
func = qualtime;
goto getrange;
case 'L':
/* File size (Length) in given range */
func = qualsize;
g_amc = -1;
1999-04-15 20:05:38 +02:00
/* Get size multiplier */
g_units = TT_BYTES;
1999-04-15 20:05:38 +02:00
if (*s == 'p' || *s == 'P')
g_units = TT_POSIX_BLOCKS, ++s;
1999-04-15 20:05:38 +02:00
else if (*s == 'k' || *s == 'K')
g_units = TT_KILOBYTES, ++s;
1999-04-15 20:05:38 +02:00
else if (*s == 'm' || *s == 'M')
g_units = TT_MEGABYTES, ++s;
1999-04-15 20:05:38 +02:00
getrange:
/* Get time multiplier */
if (g_amc >= 0) {
g_units = TT_DAYS;
1999-04-15 20:05:38 +02:00
if (*s == 'h')
g_units = TT_HOURS, ++s;
1999-04-15 20:05:38 +02:00
else if (*s == 'm')
g_units = TT_MINS, ++s;
1999-04-15 20:05:38 +02:00
else if (*s == 'w')
g_units = TT_WEEKS, ++s;
1999-04-15 20:05:38 +02:00
else if (*s == 'M')
g_units = TT_MONTHS, ++s;
else if (*s == 's')
g_units = TT_SECONDS, ++s;
1999-04-15 20:05:38 +02:00
}
/* See if it's greater than, equal to, or less than */
if ((g_range = *s == '+' ? 1 : *s == '-' ? -1 : 0))
1999-04-15 20:05:38 +02:00
++s;
data = qgetnum(&s);
break;
case 'o':
case 'O':
{
int t;
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;
default:
zerr("unknown sort specifier", NULL, 0);
restore_globstate(saved);
return;
}
if ((sense & 2) && !(t & (GS_NAME|GS_DEPTH)))
t <<= GS_SHIFT;
if (gf_sorts & t) {
zerr("doubled sort specifier", NULL, 0);
restore_globstate(saved);
return;
}
gf_sorts |= t;
gf_sortlist[gf_nsorts++] = t |
(((sense & 1) ^ (s[-1] == 'O')) ? GS_DESC : 0);
s++;
break;
}
case 'e':
{
char sav, *tt = get_strarg(s);
if (!*tt) {
zerr("missing end of string", NULL, 0);
data = 0;
} else {
sav = *tt;
*tt = '\0';
func = qualsheval;
sdata = dupstring(s + 1);
untokenize(sdata);
*tt = sav;
if (sav)
s = tt + 1;
else
s = tt;
}
break;
}
case '[':
case Inbrack:
{
char *os = --s;
struct value v;
v.isarr = SCANPM_WANTVALS;
v.pm = NULL;
v.end = -1;
v.inv = 0;
if (getindex(&s, &v) || s == os) {
zerr("invalid subscript", NULL, 0);
restore_globstate(saved);
return;
}
first = v.start;
end = v.end;
break;
}
1999-04-15 20:05:38 +02:00
default:
zerr("unknown file attribute", NULL, 0);
restore_globstate(saved);
1999-04-15 20:05:38 +02:00
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->sdata = sdata;
qn->range = g_range;
qn->units = g_units;
qn->amc = g_amc;
1999-04-15 20:05:38 +02:00
qn = NULL;
qualct++;
}
if (errflag) {
restore_globstate(saved);
1999-04-15 20:05:38 +02:00
return;
}
1999-04-15 20:05:38 +02:00
}
}
}
q = parsepat(str);
if (!q || errflag) { /* if parsing failed */
restore_globstate(saved);
1999-04-15 20:05:38 +02:00
if (unset(BADPATTERN)) {
if (!nountok)
untokenize(ostr);
1999-04-15 20:05:38 +02:00
insertlinknode(list, node, ostr);
return;
}
errflag = 0;
zerr("bad pattern: %s", ostr, 0);
return;
}
if (!gf_nsorts) {
gf_sortlist[0] = gf_sorts = GS_NAME;
gf_nsorts = 1;
}
1999-04-15 20:05:38 +02:00
/* Initialise receptacle for matched files, *
* expanded by insert() where necessary. */
matchptr = matchbuf = (Gmatch)zalloc((matchsz = 16) *
sizeof(struct gmatch));
1999-04-15 20:05:38 +02:00
matchct = 0;
pattrystart();
1999-04-15 20:05:38 +02:00
/* 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) {
1999-04-15 20:05:38 +02:00
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->name = dupstring(ostr));
matchptr++;
1999-04-15 20:05:38 +02:00
matchct = 1;
}
}
1999-04-15 20:05:38 +02:00
/* 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;
2000-06-02 12:09:13 +02:00
if ((end -= first) > 0) {
matchptr = matchbuf + matchct - first - end;
while (end-- > 0) { /* insert matches in the arg list */
insertlinknode(list, node, matchptr->name);
matchptr++;
}
}
1999-04-15 20:05:38 +02:00
free(matchbuf);
restore_globstate(saved);
1999-04-15 20:05:38 +02:00
}
/* 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 (gf_numsort && (idigit(*c) || idigit(*d))) {
1999-04-15 20:05:38 +02:00
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 */
/**/
mod_export char
1999-04-15 20:05:38 +02:00
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 */
/**/
2000-04-30 19:58:35 +02:00
mod_export int
1999-04-15 20:05:38 +02:00
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;
1999-04-15 20:05:38 +02:00
for (bc = 0; (c = *str); ++str)
if (c == Inbrace) {
1999-04-15 20:05:38 +02:00
if (!bc && str[1] == Outbrace)
*str++ = '{', *str = '}';
else
bc++;
} else if (c == Outbrace) {
1999-04-15 20:05:38 +02:00
if (!bc)
*str = '}';
else if (!--bc)
return 1;
}
1999-04-15 20:05:38 +02:00
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)
{
char *nam;
struct redir *ff;
int ret = 0;
local_list1(fake);
1999-04-15 20:05:38 +02:00
/* Stick the name in a list... */
init_list1(fake, fn->name);
1999-04-15 20:05:38 +02:00
/* ...which undergoes all the usual shell expansions */
prefork(&fake, isset(MULTIOS) ? 0 : PF_SINGLE);
1999-04-15 20:05:38 +02:00
/* Globbing is only done for multios. */
if (!errflag && isset(MULTIOS))
globlist(&fake, 0);
1999-04-15 20:05:38 +02:00
if (errflag)
return 0;
if (nonempty(&fake) && !nextnode(firstnode(&fake))) {
1999-04-15 20:05:38 +02:00
/* Just one match, the usual case. */
char *s = peekfirst(&fake);
1999-04-15 20:05:38 +02:00
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 = -2;
1999-04-15 20:05:38 +02:00
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))) {
1999-04-15 20:05:38 +02:00
/* Loop over matches, duplicating the *
* redirection for each file found. */
ff = (struct redir *) zhalloc(sizeof *ff);
1999-04-15 20:05:38 +02:00
*ff = *fn;
ff->name = nam;
addlinknode(tab, ff);
ret = 1;
}
}
return ret;
}
/* concatenate s1 and s2 in dynamically allocated buffer */
/**/
mod_export char *
1999-04-15 20:05:38 +02:00
dyncat(char *s1, char *s2)
{
/* This version always uses space from the current heap. */
char *ptr;
int l1 = strlen(s1);
ptr = (char *)zhalloc(l1 + strlen(s2) + 1);
1999-04-15 20:05:38 +02:00
strcpy(ptr, s1);
strcpy(ptr + l1, s2);
return ptr;
}
/* concatenate s1, s2, and s3 in dynamically allocated buffer */
/**/
mod_export char *
1999-04-15 20:05:38 +02:00
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 */
/**/
2000-04-30 19:58:35 +02:00
mod_export void
1999-04-15 20:05:38 +02:00
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) {
1999-04-15 20:05:38 +02:00
if (*str2 == Comma)
++comma; /* we have {foo,bar} */
else if (*str2 == '.' && str2[1] == '.')
dotdot++; /* we have {num1..num2} */
}
1999-04-15 20:05:38 +02:00
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 = hcalloc(len + 1);
1999-04-15 20:05:38 +02:00
str[pl] = Meta;
str[pl+1] = c1 ^ 32;
strcpy(str + pl + 2, str2);
} else {
str = hcalloc(len);
1999-04-15 20:05:38 +02:00
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);
1999-04-15 20:05:38 +02:00
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 = patcompile(b, PAT_STATIC, NULL);
1999-04-15 20:05:38 +02:00
if (!p) {
1999-04-15 20:05:38 +02:00
zerr("bad pattern: %s", b, 0);
return 0;
}
return pattry(p, a);
1999-04-15 20:05:38 +02:00
}
/* do the ${foo%%bar}, ${foo#bar} stuff */
/* please do not laugh at this code. */
struct repldata {
int b, e; /* beginning and end of chunk to replace */
char *replstr; /* replacement string to use */
};
typedef struct repldata *Repldata;
/*
* List of bits of matches to concatenate with replacement string.
* The data is a struct repldata. It is not used in cases like
* ${...//#foo/bar} even though SUB_GLOBAL is set, since the match
* is anchored. It goes on the heap.
*/
static LinkList repllist;
1999-04-15 20:05:38 +02:00
/* 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).
* fl is a set of the SUB_* matches defined in zsh.h from SUB_MATCH onwards;
* the lower parts are ignored.
* replstr is the replacement string for a substitution
1999-04-15 20:05:38 +02:00
*/
/**/
static char *
get_match_ret(char *s, int b, int e, int fl, char *replstr)
1999-04-15 20:05:38 +02:00
{
char buf[80], *r, *p, *rr;
int ll = 0, l = strlen(s), bl = 0, t = 0, i;
if (replstr) {
if (fl & SUB_DOSUBST) {
replstr = dupstring(replstr);
singsub(&replstr);
untokenize(replstr);
}
if ((fl & SUB_GLOBAL) && repllist) {
/* We are replacing the chunk, just add this to the list */
Repldata rd = (Repldata) zhalloc(sizeof(*rd));
rd->b = b;
rd->e = e;
rd->replstr = replstr;
addlinknode(repllist, rd);
return s;
}
ll += strlen(replstr);
}
if (fl & SUB_MATCH) /* matched portion */
1999-04-15 20:05:38 +02:00
ll += 1 + (e - b);
if (fl & SUB_REST) /* unmatched portion */
1999-04-15 20:05:38 +02:00
ll += 1 + (l - (e - b));
if (fl & SUB_BIND) {
1999-04-15 20:05:38 +02:00
/* position of start of matched portion */
sprintf(buf, "%d ", b + 1);
ll += (bl = strlen(buf));
}
if (fl & SUB_EIND) {
1999-04-15 20:05:38 +02:00
/* position of end of matched portion */
sprintf(buf + bl, "%d ", e + 1);
ll += (bl = strlen(buf));
}
if (fl & SUB_LEN) {
1999-04-15 20:05:38 +02:00
/* length of matched portion */
sprintf(buf + bl, "%d ", e - b);
ll += (bl = strlen(buf));
}
if (bl)
buf[bl - 1] = '\0';
rr = r = (char *) hcalloc(ll);
1999-04-15 20:05:38 +02:00
if (fl & SUB_MATCH) {
1999-04-15 20:05:38 +02:00
/* copy matched portion to new buffer */
for (i = b, p = s + b; i < e; i++)
*rr++ = *p++;
t = 1;
}
if (fl & SUB_REST) {
1999-04-15 20:05:38 +02:00
/* 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++;
if (replstr)
for (p = replstr; *p; )
*rr++ = *p++;
1999-04-15 20:05:38 +02:00
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;
}
static Patprog
compgetmatch(char *pat, int *flp, char **replstrp)
1999-04-15 20:05:38 +02:00
{
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.
*/
1999-04-15 20:05:38 +02:00
/* int patflags = PAT_STATIC|PAT_SCAN|PAT_NOANCH;*/
1999-04-15 20:05:38 +02:00
/* 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. */
1999-04-15 20:05:38 +02:00
int patflags = PAT_SCAN|PAT_NOANCH | (*replstrp ? 0 : PAT_STATIC);
1999-04-15 20:05:38 +02:00
/*
* 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, 0);
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);
1999-04-15 20:05:38 +02:00
}
}
return p;
1999-04-15 20:05:38 +02:00
}
/*
* 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.
*/
1999-04-15 20:05:38 +02:00
/**/
int
getmatch(char **sp, char *pat, int fl, int n, char *replstr)
1999-04-15 20:05:38 +02:00
{
Patprog p;
1999-04-15 20:05:38 +02:00
if (!(p = compgetmatch(pat, &fl, &replstr)))
return 1;
1999-04-15 20:05:38 +02:00
return igetmatch(sp, p, fl, n, replstr);
}
1999-04-15 20:05:38 +02:00
/**/
void
getmatcharr(char ***ap, char *pat, int fl, int n, char *replstr)
1999-04-15 20:05:38 +02:00
{
char **arr = *ap, **pp;
Patprog p;
1999-04-15 20:05:38 +02:00
if (!(p = compgetmatch(pat, &fl, &replstr)))
return;
1999-04-15 20:05:38 +02:00
*ap = pp = hcalloc(sizeof(char *) * (arrlen(arr) + 1));
while ((*pp = *arr++))
if (igetmatch(pp, p, fl, n, replstr))
pp++;
1999-04-15 20:05:38 +02:00
}
/**/
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;
}
1999-04-15 20:05:38 +02:00
/**/
static int
igetmatch(char **sp, Patprog p, int fl, int n, char *replstr)
1999-04-15 20:05:38 +02:00
{
char *s = *sp, *t, sav;
int i, l = strlen(*sp), ml = ztrlen(*sp), matched = 1;
1999-04-15 20:05:38 +02:00
repllist = NULL;
1999-04-15 20:05:38 +02:00
/* perform must-match test for complex closures */
if (p->mustoff && !strstr((char *)s, (char *)p + p->mustoff))
matched = 0;
1999-04-15 20:05:38 +02:00
/* in case we used the prog before... */
p->flags &= ~(PAT_NOTSTART|PAT_NOTEND);
if (fl & SUB_ALL) {
i = matched && pattry(p, s);
*sp = get_match_ret(*sp, 0, i ? l : 0, fl, i ? replstr : 0);
if (! **sp && (((fl & SUB_MATCH) && !i) || ((fl & SUB_REST) && i)))
1999-04-15 20:05:38 +02:00
return 0;
return 1;
}
if (matched) {
switch (fl & (SUB_END|SUB_LONG|SUB_SUBSTR)) {
case 0:
case SUB_LONG:
/*
* Largest/smallest possible match at head of string.
* First get the longest match...
1999-04-15 20:05:38 +02:00
*/
if (pattry(p, s)) {
char *mpos = patinput;
if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
/*
* ... now we know whether it's worth looking for the
* shortest, which we do by brute force.
*/
for (t = s; t < mpos; METAINC(t)) {
sav = *t;
set_pat_end(p, sav);
*t = '\0';
if (pattry(p, s)) {
mpos = patinput;
*t = sav;
1999-04-15 20:05:38 +02:00
break;
}
*t = sav;
1999-04-15 20:05:38 +02:00
}
}
*sp = get_match_ret(*sp, 0, mpos-s, fl, replstr);
return 1;
1999-04-15 20:05:38 +02:00
}
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. */
patoffset = ml;
for (t = s + l; t >= s; t--, patoffset--) {
set_pat_start(p, t-s);
if (pattry(p, t)) {
*sp = get_match_ret(*sp, t - s, l, fl, replstr);
patoffset = 0;
return 1;
1999-04-15 20:05:38 +02:00
}
if (t > s+1 && t[-2] == Meta)
t--;
1999-04-15 20:05:38 +02:00
}
patoffset = 0;
1999-04-15 20:05:38 +02:00
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 (i = 0, t = s; i < l; i++, t++, patoffset++) {
set_pat_start(p, t-s);
if (pattry(p, t)) {
*sp = get_match_ret(*sp, i, l, fl, replstr);
patoffset = 0;
return 1;
}
if (*t == Meta)
i++, t++;
}
patoffset = 0;
break;
1999-04-15 20:05:38 +02:00
case SUB_SUBSTR:
/* Smallest at start, but matching substrings. */
set_pat_start(p, l);
if (!(fl & SUB_GLOBAL) && pattry(p, s + l) && !--n) {
*sp = get_match_ret(*sp, 0, 0, fl, replstr);
return 1;
} /* fall through */
case (SUB_SUBSTR|SUB_LONG):
/* longest or smallest at start with substrings */
t = s;
if (fl & SUB_GLOBAL)
repllist = newlinklist();
do {
/* loop over all matches for global substitution */
matched = 0;
for (; t < s + l; t++, patoffset++) {
/* Find the longest match from this position. */
set_pat_start(p, t-s);
if (pattry(p, t) && patinput > t) {
char *mpos = patinput;
if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
char *ptr;
for (ptr = t; ptr < mpos; METAINC(ptr)) {
sav = *ptr;
set_pat_end(p, sav);
*ptr = '\0';
if (pattry(p, t)) {
mpos = patinput;
*ptr = sav;
break;
}
*ptr = sav;
}
1999-04-15 20:05:38 +02:00
}
if (!--n || (n <= 0 && (fl & SUB_GLOBAL))) {
*sp = get_match_ret(*sp, t-s, mpos-s, fl, replstr);
if (mpos == t)
METAINC(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 {
patoffset = 0;
return 1;
}
}
/*
* For a global match, we need to skip the stuff
* which is already marked for replacement.
1999-04-15 20:05:38 +02:00
*/
matched = 1;
for ( ; t < mpos; t++, patoffset++)
if (*t == Meta)
t++;
break;
1999-04-15 20:05:38 +02:00
}
if (*t == Meta)
t++;
1999-04-15 20:05:38 +02:00
}
} while (matched);
patoffset = 0;
/*
* 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 &&
pattry(p, s + l) && !--n) {
*sp = get_match_ret(*sp, 0, 0, fl, replstr);
1999-04-15 20:05:38 +02:00
return 1;
}
break;
1999-04-15 20:05:38 +02:00
case (SUB_END|SUB_SUBSTR):
/* Shortest at end with substrings */
patoffset = ml;
set_pat_start(p, l);
if (pattry(p, s + l) && !--n) {
*sp = get_match_ret(*sp, l, l, fl, replstr);
patoffset = 0;
return 1;
} /* fall through */
case (SUB_END|SUB_LONG|SUB_SUBSTR):
/* Longest/shortest at end, matching substrings. */
patoffset--;
for (t = s + l - 1; t >= s; t--, patoffset--) {
if (t > s && t[-1] == Meta)
t--;
set_pat_start(p, t-s);
if (pattry(p, t) && patinput > t && !--n) {
/* Found the longest match */
char *mpos = patinput;
if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
char *ptr;
for (ptr = t; ptr < mpos; METAINC(ptr)) {
sav = *ptr;
set_pat_end(p, sav);
*ptr = '\0';
if (pattry(p, t)) {
mpos = patinput;
*ptr = sav;
break;
}
*ptr = sav;
}
}
*sp = get_match_ret(*sp, t-s, mpos-s, fl, replstr);
patoffset = 0;
return 1;
}
}
patoffset = ml;
set_pat_start(p, l);
if ((fl & SUB_LONG) && pattry(p, s + l) && !--n) {
*sp = get_match_ret(*sp, l, l, fl, replstr);
patoffset = 0;
return 1;
1999-04-15 20:05:38 +02:00
}
patoffset = 0;
break;
1999-04-15 20:05:38 +02:00
}
}
1999-04-15 20:05:38 +02:00
if (repllist && nonempty(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;
i = 0; /* start of last chunk we got from *sp */
for (nd = firstnode(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 */
1999-04-15 20:05:38 +02:00
}
lleft += l - i; /* final chunk from *sp */
start = t = zhalloc(lleft+1);
i = 0;
for (nd = firstnode(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;
1999-04-15 20:05:38 +02:00
}
memcpy(t, s + i, l - i);
start[lleft] = '\0';
*sp = (char *)start;
return 1;
1999-04-15 20:05:38 +02:00
}
/* munge the whole string: no match, so no replstr */
*sp = get_match_ret(*sp, 0, 0, fl, 0);
return 1;
1999-04-15 20:05:38 +02:00
}
/* blindly turn a string into a tokenised expression without lexing */
/**/
mod_export void
1999-04-15 20:05:38 +02:00
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 (isset(SHGLOB))
break;
case '^':
case '#':
case '~':
1999-04-15 20:05:38 +02:00
case '[':
case ']':
case '*':
case '?':
case '=':
1999-04-15 20:05:38 +02:00
for (t = ztokens; *t; t++)
if (*t == *s) {
if (bslash)
s[-1] = Bnull;
else
*s = (t - ztokens) + Pound;
break;
}
}
bslash = 0;
}
}
/* remove unnecessary Nulargs */
/**/
mod_export void
1999-04-15 20:05:38 +02:00
remnulargs(char *s)
{
if (*s) {
char *o = s, c;
1999-04-15 20:05:38 +02:00
while ((c = *s++))
if (INULL(c)) {
char *t = s - 1;
while ((c = *s++))
if (!INULL(c))
*t++ = c;
*t = '\0';
if (!*o) {
o[0] = Nularg;
o[1] = '\0';
}
break;
}
1999-04-15 20:05:38 +02:00
}
}
/* qualifier functions: mostly self-explanatory, see glob(). */
/* device number */
/**/
static int
qualdev(char *name, struct stat *buf, off_t dv, char *dummy)
1999-04-15 20:05:38 +02:00
{
return buf->st_dev == dv;
}
/* number of hard links to file */
/**/
static int
qualnlink(char *name, struct stat *buf, off_t ct, char *dummy)
1999-04-15 20:05:38 +02:00
{
return (g_range < 0 ? buf->st_nlink < ct :
g_range > 0 ? buf->st_nlink > ct :
1999-04-15 20:05:38 +02:00
buf->st_nlink == ct);
}
/* user ID */
/**/
static int
qualuid(char *name, struct stat *buf, off_t uid, char *dummy)
1999-04-15 20:05:38 +02:00
{
return buf->st_uid == uid;
}
/* group ID */
/**/
static int
qualgid(char *name, struct stat *buf, off_t gid, char *dummy)
1999-04-15 20:05:38 +02:00
{
return buf->st_gid == gid;
}
/* device special file? */
/**/
static int
qualisdev(char *name, struct stat *buf, off_t junk, char *dummy)
1999-04-15 20:05:38 +02:00
{
return S_ISBLK(buf->st_mode) || S_ISCHR(buf->st_mode);
}
/* block special file? */
/**/
static int
qualisblk(char *name, struct stat *buf, off_t junk, char *dummy)
1999-04-15 20:05:38 +02:00
{
return S_ISBLK(buf->st_mode);
}
/* character special file? */
/**/
static int
qualischr(char *name, struct stat *buf, off_t junk, char *dummy)
1999-04-15 20:05:38 +02:00
{
return S_ISCHR(buf->st_mode);
}
/* directory? */
/**/
static int
qualisdir(char *name, struct stat *buf, off_t junk, char *dummy)
1999-04-15 20:05:38 +02:00
{
return S_ISDIR(buf->st_mode);
}
/* FIFO? */
/**/
static int
qualisfifo(char *name, struct stat *buf, off_t junk, char *dummy)
1999-04-15 20:05:38 +02:00
{
return S_ISFIFO(buf->st_mode);
}
/* symbolic link? */
/**/
static int
qualislnk(char *name, struct stat *buf, off_t junk, char *dummy)
1999-04-15 20:05:38 +02:00
{
return S_ISLNK(buf->st_mode);
}
/* regular file? */
/**/
static int
qualisreg(char *name, struct stat *buf, off_t junk, char *dummy)
1999-04-15 20:05:38 +02:00
{
return S_ISREG(buf->st_mode);
}
/* socket? */
/**/
static int
qualissock(char *name, struct stat *buf, off_t junk, char *dummy)
1999-04-15 20:05:38 +02:00
{
return S_ISSOCK(buf->st_mode);
}
/* given flag is set in mode */
/**/
static int
qualflags(char *name, struct stat *buf, off_t mod, char *dummy)
1999-04-15 20:05:38 +02:00
{
return mode_to_octal(buf->st_mode) & mod;
}
/* mode matches specification */
1999-04-15 20:05:38 +02:00
/**/
static int
qualmodeflags(char *name, struct stat *buf, off_t mod, char *dummy)
1999-04-15 20:05:38 +02:00
{
long v = mode_to_octal(buf->st_mode), y = mod & 07777, n = mod >> 12;
return ((v & y) == y && !(v & n));
1999-04-15 20:05:38 +02:00
}
/* regular executable file? */
/**/
static int
qualiscom(char *name, struct stat *buf, off_t mod, char *dummy)
1999-04-15 20:05:38 +02:00
{
return S_ISREG(buf->st_mode) && (buf->st_mode & S_IXUGO);
}
/* size in required range? */
/**/
static int
qualsize(char *name, struct stat *buf, off_t size, char *dummy)
1999-04-15 20:05:38 +02:00
{
#if defined(LONG_IS_64_BIT) || defined(OFF_T_IS_64_BIT)
# define QS_CAST_SIZE()
off_t scaled = buf->st_size;
#else
# define QS_CAST_SIZE() (unsigned long)
unsigned long scaled = (unsigned long)buf->st_size;
#endif
1999-04-15 20:05:38 +02:00
switch (g_units) {
1999-04-15 20:05:38 +02:00
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 (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
1999-04-15 20:05:38 +02:00
}
/* time in required range? */
/**/
static int
qualtime(char *name, struct stat *buf, off_t days, char *dummy)
1999-04-15 20:05:38 +02:00
{
time_t now, diff;
time(&now);
diff = now - (g_amc == 0 ? buf->st_atime : g_amc == 1 ? buf->st_mtime :
1999-04-15 20:05:38 +02:00
buf->st_ctime);
/* handle multipliers indicating units */
switch (g_units) {
1999-04-15 20:05:38 +02:00
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 :
1999-04-15 20:05:38 +02:00
diff == days);
}
/* evaluate a string */
/**/
static int
qualsheval(char *name, struct stat *buf, off_t days, char *str)
{
Eprog prog;
if ((prog = parse_string(str, 0))) {
int ef = errflag, lv = lastval, ret;
unsetparam("reply");
setsparam("REPLY", ztrdup(name));
execode(prog, 1, 0);
ret = lastval;
errflag = ef;
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;
}