cdrtools/libfind/ifpdi
2025-06-15 04:19:58 +08:00

2567 lines
57 KiB
Plaintext
Raw Permalink Blame History

/*#define PLUS_DEBUG*/
/* @(#)find.c 1.101 15/09/12 Copyright 2004-2015 J. Schilling */
#include <schily/mconfig.h>
#ifndef lint
static UConst char sccsid[] =
"@(#)find.c 1.101 15/09/12 Copyright 2004-2015 J. Schilling";
#endif
/*
* Another find implementation...
*
* Copyright (c) 2004-2015 J. Schilling
*/
/*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* See the file CDDL.Schily.txt in this distribution for details.
* A copy of the CDDL is also available via the Internet at
* http://www.opensource.org/licenses/cddl1.txt
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file CDDL.Schily.txt from this distribution.
*/
#ifdef __FIND__
#define FIND_MAIN
#endif
#include <schily/stdio.h>
#include <schily/unistd.h>
#include <schily/stdlib.h>
#include <schily/fcntl.h>
#include <schily/stat.h>
#include <schily/dirent.h>
#include <schily/time.h>
#include <schily/wait.h>
#include <schily/string.h>
#include <schily/utypes.h> /* incl. limits.h (_POSIX_ARG_MAX/ARG_MAX) */
#include <schily/param.h> /* #defines NCARGS on old systems */
#include <schily/btorder.h>
#include <schily/patmatch.h>
#include <schily/fnmatch.h>
#include <schily/standard.h>
#include <schily/jmpdefs.h>
#include <schily/schily.h>
#include <schily/pwd.h>
#include <schily/grp.h>
#define VMS_VFORK_OK
#include <schily/vfork.h>
#include <schily/nlsdefs.h>
#if defined(_ARG_MAX32) && defined(_ARG_MAX64)
#define MULTI_ARG_MAX /* SunOS only ? */
#endif
#ifdef __FIND__
char strvers[] = "1.4"; /* The pure version string */
#endif
typedef struct {
char *left;
char *right;
char *this;
int op;
union {
int i;
long l;
dev_t dev;
ino_t ino;
mode_t mode;
nlink_t nlink;
uid_t uid;
gid_t gid;
size_t size;
time_t time;
FILE *fp;
} val, val2;
} findn_t;
#include <schily/walk.h>
#define FIND_NODE
#include <schily/find.h>
#include "find_list.h"
#include "find_misc.h"
#define TOKEN_NAMES
#include "find_tok.h"
/*
* The struct plusargs and the adjacent space that holds the
* arg vector and the string table. The struct plusargs member "av"
* is also part of the ARG_MAX sized space that follows.
*
* ---------------------------------
* | Other struct plusargs fields | Don't count against ARG_MAX
* ---------------------------------
* ---------------------------------
* | New Arg vector[0] | Space for ARG_MAX starts here
* ---------------------------------
* | . |
* | . | Arg space grows upwards
* | V |
* ---------------------------------
* | Arg vector end | "nextargp" points here
* ---------------------------------
* ---------------------------------
* | Space for first arg string |
* --------------------------------- "laststr" points here
* | ^ |
* | . | String space "grows" downwards
* | . |
* ---------------------------------
* | Space for first arg string | Space for ARG_MAX ends here
* --------------------------------- "endp" points here
*
* The Arg vector in struct plusargs uses the native pointer size
* for libfind. ARG_MAX however is based on the pointer size in the
* called program.
*
* If a 32 bit libfind calls a 64 bit program, the arg vector and the
* environment array in the called program needs more space than in the
* calling libfind code.
*
* If a 64 bit libfind calls a 32 bit program, the arg vector and the
* environment array in the called program needs less space than in the
* calling libfind code.
*/
struct plusargs {
struct plusargs *next; /* Next in list for flushing */
char *endp; /* Points to end of data block */
char **nextargp; /* Points to next av[] entry */
char *laststr; /* points to last used string */
int nenv; /* Number of entries in env */
int ac; /* The argc for our command */
char *av[1]; /* The argv for our command */
};
#ifdef PLUS_DEBUG /* We are no longer reentrant */
LOCAL struct plusargs *plusp; /* Avoid PLUS_DEBUG if possible */
#endif
#define MINSECS (60)
#define HOURSECS (60 * MINSECS)
#define DAYSECS (24 * HOURSECS)
#define YEARSECS (365 * DAYSECS)
extern time_t find_sixmonth; /* 6 months before limit (ls) */
extern time_t find_now; /* now limit (ls) */
LOCAL findn_t Printnode = { 0, 0, 0, PRINT };
EXPORT void find_argsinit __PR((finda_t *fap));
EXPORT void find_timeinit __PR((time_t now));
EXPORT findn_t *find_printnode __PR((void));
EXPORT findn_t *find_addprint __PR((findn_t *np, finda_t *fap));
LOCAL findn_t *allocnode __PR((finda_t *fap));
EXPORT void find_free __PR((findn_t *t, finda_t *fap));
LOCAL void find_freenode __PR((findn_t *t));
LOCAL void nexttoken __PR((finda_t *fap));
LOCAL BOOL _nexttoken __PR((finda_t *fap));
LOCAL void errjmp __PR((finda_t *fap, int err));
EXPORT int find_token __PR((char *word));
EXPORT char *find_tname __PR((int op));
LOCAL char *nextarg __PR((finda_t *fap, findn_t *t));
EXPORT findn_t *find_parse __PR((finda_t *fap));
LOCAL findn_t *parse __PR((finda_t *fap));
LOCAL findn_t *parseand __PR((finda_t *fap));
LOCAL findn_t *parseprim __PR((finda_t *fap));
EXPORT void find_firstprim __PR((int *pac, char *const **pav));
EXPORT BOOL find_primary __PR((findn_t *t, int op));
EXPORT BOOL find_pname __PR((findn_t *t, char *word));
EXPORT BOOL find_hasprint __PR((findn_t *t));
EXPORT BOOL find_hasexec __PR((findn_t *t));
#ifdef FIND_MAIN
LOCAL int walkfunc __PR((char *nm, struct stat *fs, int type, struct WALK *state));
#endif
#ifdef __FIND__
LOCAL inline BOOL find_expr __PR((char *f, char *ff, struct stat *fs, struct WALK *state, findn_t *t));
#else
EXPORT BOOL find_expr __PR((char *f, char *ff, struct stat *fs, struct WALK *state, findn_t *t));
#endif
#ifdef HAVE_FORK
LOCAL BOOL doexec __PR((char *f, findn_t *t, int ac, char **av, struct WALK *state));
#endif
LOCAL int countenv __PR((void));
LOCAL int argsize __PR((int xtype));
LOCAL int extype __PR((char *name));
#ifdef MULTI_ARG_MAX
LOCAL int xargsize __PR((int xtype, int maxarg));
#endif
LOCAL BOOL pluscreate __PR((FILE *f, int ac, char **av, finda_t *fap));
#ifdef HAVE_FORK
LOCAL BOOL plusexec __PR((char *f, findn_t *t, struct WALK *state));
#endif
EXPORT int find_plusflush __PR((void *p, struct WALK *state));
EXPORT void find_usage __PR((FILE *f));
#ifdef FIND_MAIN
LOCAL int getflg __PR((char *optstr, long *argp));
EXPORT int main __PR((int ac, char **av));
#endif
EXPORT void
find_argsinit(fap)
finda_t *fap;
{
fap->Argc = 0;
fap->Argv = (char **)NULL;
fap->std[0] = stdin;
fap->std[1] = stdout;
fap->std[2] = stderr;
fap->primtype = 0;
fap->found_action = FALSE;
fap->patlen = 0;
fap->walkflags = 0;
fap->maxdepth = -1;
fap->mindepth = -1;
fap->plusp = (struct plusargs *)NULL;
fap->jmp = NULL;
fap->error = 0;
}
EXPORT void
find_timeinit(now)
time_t now;
{
find_now = now + 60;
find_sixmonth = now - 6L*30L*24L*60L*60L;
}
EXPORT findn_t *
find_printnode()
{
return (&Printnode);
}
/*
* Add a -print node to the parsed tree if there is no action already.
*/
EXPORT findn_t *
find_addprint(np, fap)
findn_t *np;
finda_t *fap;
{
findn_t *n;
n = allocnode(fap);
if (n == NULL) {
find_freenode(np);
return ((void *)NULL);
}
n->op = AND;
n->left = (char *)np;
n->right = (char *)&Printnode;
return (n);
}
/*
* allocnode is currently called by:
* find_addprint(), parse(), parseand(), parseprim()
*/
LOCAL findn_t *
allocnode(fap)
finda_t *fap;
{
findn_t *n;
n = __fjmalloc(fap->std[2], sizeof (findn_t), "allocnode", JM_RETURN);
if (n == NULL)
return (n);
n->left = 0;
n->right = 0;
n->this = 0;
n->op = 0;
n->val.l = 0;
n->val2.l = 0;
return (n);
}
EXPORT void
find_free(t, fap)
findn_t *t;
finda_t *fap;
{
if (fap != NULL) {
struct plusargs *p;
struct plusargs *np = NULL;
for (p = fap->plusp; p != NULL; p = np) {
np = p->next;
free(p);
}
}
find_freenode(t);
}
LOCAL void
find_freenode(t)
findn_t *t;
{
if (t == (findn_t *)NULL || t == &Printnode)
return;
switch (t->op) {
case OPEN:
case LNOT:
find_freenode((findn_t *)t->this);
break;
case AND:
case LOR:
find_freenode((findn_t *)t->left);
find_freenode((findn_t *)t->right);
break;
case PAT:
case PPAT:
case LPAT:
if (t->right != NULL)
free(t->right); /* aux array for patcompile() */
break;
case FLS:
case FPRINT:
case FPRINT0:
case FPRINTNNL:
fclose(t->val.fp);
default:
;
}
free(t);
}
LOCAL void
nexttoken(fap)
register finda_t *fap;
{
if (!_nexttoken(fap)) {
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
}
/*
* No errjmp() variant of nexttoken(), returns FALSE on error.
*/
LOCAL BOOL
_nexttoken(fap)
register finda_t *fap;
{
register char *word;
register char *tail;
if (fap->Argc <= 0) {
fap->primtype = FIND_ENDARGS;
return (TRUE);
}
word = *fap->Argv;
if ((tail = strchr(word, '=')) != NULL) {
#ifdef XXX
if (*tail == '\0') {
fap->Argv++; fap->Argc--;
} else
#endif
*fap->Argv = ++tail;
} else {
fap->Argv++; fap->Argc--;
}
if ((fap->primtype = find_token(word)) >= 0)
return (TRUE);
ferrmsgno(fap->std[2], EX_BAD, _("Bad Option: '%s'.\n"), word);
find_usage(fap->std[2]);
fap->primtype = FIND_ERRARG; /* Mark as "parse aborted" */
return (FALSE);
}
LOCAL void
errjmp(fap, err)
register finda_t *fap;
int err;
{
fap->primtype = FIND_ERRARG; /* Mark as "parse aborted" */
fap->error = err; /* Set error return */
siglongjmp(((sigjmps_t *)fap->jmp)->jb, 1);
/* NOTREACHED */
}
EXPORT int
find_token(word)
register char *word;
{
char **tp;
char *tn;
char *equalp;
int type;
if ((equalp = strchr(word, '=')) != NULL)
*equalp = '\0';
if (*word == '-') {
/*
* Do not allow -(, -), -!
*/
if (word[1] == '\0' || !strchr("()!", word[1]))
word++;
} else if (!strchr("()!", word[0]) && (!equalp || equalp[1] == '\0')) {
goto bad;
}
for (type = 0, tp = tokennames; *tp; tp++, type++) {
tn = *tp;
if (*tn != *word)
continue;
if (streql(tn, word)) {
if (equalp)
*equalp = '=';
return (type);
}
}
bad:
if (equalp)
*equalp = '=';
return (-1);
}
EXPORT char *
find_tname(op)
int op;
{
if (op >= 0 && op < NTOK)
return (tokennames[op]);
return ("unknown");
}
LOCAL char *
nextarg(fap, t)
finda_t *fap;
findn_t *t;
{
if (fap->Argc-- <= 0) {
char *prim = NULL;
int pt = t->op;
if (pt >= 0 && pt < NTOK)
prim = tokennames[pt];
if (prim) {
ferrmsgno(fap->std[2], EX_BAD,
_("Missing arg for '%s%s'.\n"),
pt > LNOT ? "-":"", prim);
} else {
ferrmsgno(fap->std[2], EX_BAD,
_("Missing arg.\n"));
}
errjmp(fap, EX_BAD);
/* NOTREACHED */
return ((char *)0);
} else {
return (*fap->Argv++);
}
}
EXPORT findn_t *
find_parse(fap)
finda_t *fap;
{
findn_t *ret;
if (!_nexttoken(fap))
return ((findn_t *)NULL); /* Immediate parse error */
if (fap->primtype == FIND_ENDARGS)
return ((findn_t *)NULL); /* Empty command */
ret = parse(fap);
if (ret)
return (ret);
if (fap->primtype == HELP) {
fap->primtype = FIND_ERRARG;
} else if (fap->error == 0) {
fap->primtype = FIND_ERRARG;
fap->error = geterrno();
if (fap->error == 0)
fap->error = EX_BAD;
}
return (ret);
}
LOCAL findn_t *
parse(fap)
finda_t *fap;
{
findn_t *n;
n = parseand(fap);
if (n == NULL)
return (n);
if (fap->primtype == LOR) {
findn_t *l = allocnode(fap);
if (l == NULL)
goto err;
l->left = (char *)n;
l->op = fap->primtype;
if (_nexttoken(fap))
l->right = (char *)parse(fap);
if (l->right == NULL) {
find_freenode(l);
n = NULL; /* Do not free twice */
goto err;
}
return (l);
}
return (n);
err:
find_freenode(n);
fap->primtype = FIND_ERRARG; /* Mark as "parse aborted" */
return ((findn_t *)NULL);
}
LOCAL findn_t *
parseand(fap)
finda_t *fap;
{
findn_t *n;
n = parseprim(fap);
if (n == NULL)
return (n);
if ((fap->primtype == AND) ||
(fap->primtype != LOR && fap->primtype != CLOSE &&
fap->primtype != FIND_ENDARGS)) {
findn_t *l = allocnode(fap);
BOOL ok = TRUE;
if (l == NULL)
goto err;
l->left = (char *)n;
l->op = AND; /* If no Operator, default to AND -a */
if (fap->primtype == AND) /* Fetch Operator for next node */
ok = _nexttoken(fap);
if (ok)
l->right = (char *)parseand(fap);
if (l->right == NULL) {
find_freenode(l);
n = NULL; /* Do not free twice */
goto err;
}
return (l);
}
return (n);
err:
find_freenode(n);
fap->primtype = FIND_ERRARG; /* Mark as "parse aborted" */
return ((findn_t *)NULL);
}
LOCAL findn_t *
parseprim(fap)
finda_t *fap;
{
sigjmps_t jmp;
sigjmps_t *ojmp = fap->jmp;
register findn_t *n;
register char *p;
Llong ll;
n = allocnode(fap);
if (n == (findn_t *)NULL) {
fap->primtype = FIND_ERRARG; /* Mark as "parse aborted" */
return ((findn_t *)NULL);
}
fap->jmp = &jmp;
if (sigsetjmp(jmp.jb, 1) != 0) {
/*
* We come here from siglongjmp()
*/
find_freenode(n);
fap->jmp = ojmp; /* Restore old jump target */
return ((findn_t *)NULL);
}
switch (n->op = fap->primtype) {
/*
* Use simple to old (historic) shell globbing.
*/
case INAME:
case ILNAME:
case IPATH:
#ifndef FNM_IGNORECASE
ferrmsgno(fap->std[2],
EX_BAD, _("The primary '-%s' is unsupported on this OS.\n"),
tokennames[n->op]);
errjmp(fap, EX_BAD);
/* NOTREACHED */
#endif
case NAME:
case PATH:
case LNAME:
#ifndef HAVE_FNMATCH
#define HAVE_FNMATCH /* We have fnmatch() in libschily */
#endif
#if defined(HAVE_FNMATCH)
n->this = nextarg(fap, n);
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
#endif
/* FALLTHRU */
/* Implement "fallback" to patmatch() if we have no fnmatch() */
/*
* Use patmatch() which is a regular expression matcher that implements
* extensions that are compatible to old (historic) shell globbing.
*/
case IPAT:
case IPPAT:
case ILPAT:
ferrmsgno(fap->std[2],
EX_BAD, _("The primary '-%s' is currently unsupported.\n"),
tokennames[n->op]);
errjmp(fap, EX_BAD);
/* NOTREACHED */
case PAT:
case PPAT:
case LPAT: {
int plen;
plen = strlen(n->this = nextarg(fap, n));
if (plen > fap->patlen)
fap->patlen = plen;
n->right = __fjmalloc(fap->std[2], sizeof (int)*plen,
"space for pattern", fap->jmp);
if ((n->val.i = patcompile((Uchar *)n->this, plen, (int *)n->right)) == 0) {
ferrmsgno(fap->std[2],
EX_BAD, _("Bad pattern in '-%s %s'.\n"),
tokennames[n->op], n->this);
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
}
case SIZE: {
char *numarg;
fap->walkflags &= ~WALK_NOSTAT;
p = n->left = nextarg(fap, n);
numarg = p;
if (p[0] == '-' || p[0] == '+')
numarg = ++p;
p = astoll(p, &ll);
if (p[0] == '\0') {
/* EMPTY */
;
} else if (p[0] == 'c' && p[1] == '\0') {
n->this = p;
} else if (getllnum(numarg, &ll) == 1) {
n->this = p;
} else if (*p) {
ferrmsgno(fap->std[2], EX_BAD,
_("Non numeric character '%c' in '-size %s'.\n"),
*p, n->left);
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
n->val.size = ll;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
}
case EMPTY:
fap->walkflags &= ~WALK_NOSTAT;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case LINKS:
fap->walkflags &= ~WALK_NOSTAT;
p = n->left = nextarg(fap, n);
if (p[0] == '-' || p[0] == '+')
p++;
p = astoll(p, &ll);
if (*p) {
ferrmsgno(fap->std[2], EX_BAD,
_("Non numeric character '%c' in '-links %s'.\n"),
*p, n->left);
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
n->val.nlink = ll;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case INUM:
fap->walkflags &= ~WALK_NOSTAT;
p = n->left = nextarg(fap, n);
if (p[0] == '-' || p[0] == '+')
p++;
p = astoll(p, &ll);
if (*p) {
ferrmsgno(fap->std[2], EX_BAD,
_("Non numeric character '%c' in '-inum %s'.\n"),
*p, n->left);
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
n->val.ino = ll;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case LINKEDTO: {
struct stat ns;
fap->walkflags &= ~WALK_NOSTAT;
if (stat(n->left = nextarg(fap, n), &ns) < 0) {
ferrmsg(fap->std[2],
_("Cannot stat '%s'.\n"), n->left);
errjmp(fap, geterrno());
/* NOTREACHED */
}
n->val.ino = ns.st_ino;
n->val2.dev = ns.st_dev;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
}
case AMIN:
case CMIN:
case MMIN:
n->val2.i = 1;
case TIME:
case ATIME:
case CTIME:
case MTIME: {
int len;
fap->walkflags &= ~WALK_NOSTAT;
p = n->left = nextarg(fap, n);
if (p[0] == '-' || p[0] == '+')
p++;
if (gettnum(p, &n->val.time) != 1) {
ferrmsgno(fap->std[2], EX_BAD,
_("Bad timespec in '-%s %s'.\n"),
tokennames[n->op], n->left);
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
if (n->val2.i)
n->val.time *= 60;
len = strlen(p);
if (len > 0) {
len = (Uchar)p[len-1];
if (!(len >= '0' && len <= '9')) {
if (n->val2.i) { /* -mmin, No ext. time spec */
ferrmsgno(fap->std[2], EX_BAD,
_("Unsupported timespec in '-%s %s'.\n"),
tokennames[n->op], n->left);
errjmp(fap, geterrno());
/* NOTREACHED */
}
n->val2.i = 1; /* Ext. time spec permitted */
}
}
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
}
case NEWERAA:
case NEWERCA:
case NEWERMA: {
struct stat ns;
fap->walkflags &= ~WALK_NOSTAT;
if (stat(n->left = nextarg(fap, n), &ns) < 0) {
ferrmsg(fap->std[2],
_("Cannot stat '%s'.\n"), n->left);
errjmp(fap, geterrno());
/* NOTREACHED */
}
n->val.time = ns.st_atime;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
}
case NEWERAC:
case NEWERCC:
case NEWERMC: {
struct stat ns;
fap->walkflags &= ~WALK_NOSTAT;
if (stat(n->left = nextarg(fap, n), &ns) < 0) {
ferrmsg(fap->std[2],
_("Cannot stat '%s'.\n"), n->left);
errjmp(fap, geterrno());
/* NOTREACHED */
}
n->val.time = ns.st_ctime;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
}
case NEWERAM:
case NEWERCM:
case NEWERMM:
case NEWER: {
struct stat ns;
fap->walkflags &= ~WALK_NOSTAT;
if (stat(n->left = nextarg(fap, n), &ns) < 0) {
ferrmsg(fap->std[2],
_("Cannot stat '%s'.\n"), n->left);
errjmp(fap, geterrno());
/* NOTREACHED */
}
n->val.time = ns.st_mtime;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
}
case TYPE:
fap->walkflags &= ~WALK_NOSTAT;
n->this = (char *)nextarg(fap, n);
switch (*(n->this)) {
/*
* 'b'lock, 'c'har, 'd'ir, 'D'oor,
* 'e'ventcount, 'f'ile, 'l'ink, 'p'ipe,
* 'P'ort event, 's'ocket
*/
case 'b': case 'c': case 'd': case 'D':
case 'e': case 'f': case 'l': case 'p':
case 'P': case 's':
if ((n->this)[1] == '\0') {
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
}
}
ferrmsgno(fap->std[2], EX_BAD,
_("Bad type '%c' in '-type %s'.\n"),
*n->this, n->this);
errjmp(fap, EX_BAD);
/* NOTREACHED */
break;
case FSTYPE:
fap->walkflags &= ~WALK_NOSTAT;
#ifdef HAVE_ST_FSTYPE
n->this = (char *)nextarg(fap, n);
#else
ferrmsgno(fap->std[2], EX_BAD,
_("-fstype not supported by this OS.\n"));
errjmp(fap, EX_BAD);
/* NOTREACHED */
#endif
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case LOCL:
fap->walkflags &= ~WALK_NOSTAT;
#ifndef HAVE_ST_FSTYPE
ferrmsgno(fap->std[2], EX_BAD,
_("-local not supported by this OS.\n"));
errjmp(fap, EX_BAD);
/* NOTREACHED */
#endif
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
#ifdef CHOWN
case CHOWN:
#endif
case USER: {
struct passwd *pw;
char *u;
fap->walkflags &= ~WALK_NOSTAT;
u = n->left = nextarg(fap, n);
if (u[0] == '-' || u[0] == '+')
u++;
if ((pw = getpwnam(u)) != NULL) {
n->val.uid = pw->pw_uid;
} else {
if (*astoll(n->left, &ll)) {
ferrmsgno(fap->std[2], EX_BAD,
_("User '%s' not in passwd database.\n"),
n->left);
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
n->val.uid = ll;
}
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
}
#ifdef CHGRP
case CHGRP:
#endif
case GROUP: {
struct group *gr;
char *g;
fap->walkflags &= ~WALK_NOSTAT;
g = n->left = nextarg(fap, n);
if (g[0] == '-' || g[0] == '+')
g++;
if ((gr = getgrnam(g)) != NULL) {
n->val.gid = gr->gr_gid;
} else {
if (*astoll(n->left, &ll)) {
ferrmsgno(fap->std[2], EX_BAD,
_("Group '%s' not in group database.\n"),
n->left);
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
n->val.gid = ll;
}
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
}
#ifdef CHMOD
case CHMOD:
#endif
case PERM:
fap->walkflags &= ~WALK_NOSTAT;
p = n->left = nextarg(fap, n);
if (p[0] == '+') {
if (p[1] == '0' ||
p[1] == 'u' || p[1] == 'g' || p[1] == 'o' || p[1] == 'a')
p++;
else
n->left = "";
}
if (getperm(fap->std[2], p, tokennames[n->op],
&n->val.mode, (mode_t)0,
n->op == PERM ? GP_FPERM|GP_XERR:GP_NOX) < 0) {
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case MODE:
fap->walkflags &= ~WALK_NOSTAT;
ferrmsgno(fap->std[2], EX_BAD,
_("-mode not yet implemented.\n"));
errjmp(fap, EX_BAD);
/* NOTREACHED */
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case XDEV:
case MOUNT:
fap->walkflags &= ~WALK_NOSTAT;
fap->walkflags |= WALK_MOUNT;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case DEPTH:
fap->walkflags |= WALK_DEPTH;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case FOLLOW:
fap->walkflags &= ~WALK_PHYS;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case MAXDEPTH:
case MINDEPTH:
p = n->left = nextarg(fap, n);
p = astoll(p, &ll);
if (*p) {
ferrmsgno(fap->std[2], EX_BAD,
_("Non numeric character '%c' in '-%s %s'.\n"),
*p, tokennames[n->op], n->left);
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
n->val.l = ll;
if (n->op == MAXDEPTH)
fap->maxdepth = ll;
else
fap->mindepth = ll;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case NOUSER:
case NOGRP:
case PACL:
case XATTR:
case SPARSE:
case DOSTAT:
fap->walkflags &= ~WALK_NOSTAT;
/* FALLTHRU */
case PRUNE:
case LTRUE:
case LFALSE:
case READABLE:
case WRITABLE:
case EXECUTABLE:
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case OK_EXEC:
case OK_EXECDIR:
case EXEC:
case EXECDIR: {
int i = 1;
n->this = (char *)fap->Argv; /* Cheat: Pointer is pointer */
nextarg(fap, n); /* Eat up cmd name */
while ((p = nextarg(fap, n)) != NULL) {
if (streql(p, ";"))
break;
else if (streql(p, "+") && streql(fap->Argv[-2], "{}")) {
if (n->op == OK_EXECDIR || n->op == EXECDIR) {
ferrmsgno(fap->std[2], EX_BAD,
_("'-%s' does not yet work with '+'.\n"),
tokennames[n->op]);
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
n->op = fap->primtype = EXECPLUS;
if (!pluscreate(fap->std[2], --i, (char **)n->this, fap)) {
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
n->this = (char *)fap->plusp;
break;
}
i++;
}
n->val.i = i;
#ifdef PLUS_DEBUG
if (0) {
char **pp = (char **)n->this;
for (i = 0; i < n->val.i; i++, pp++)
printf("ARG %d '%s'\n", i, *pp);
}
#endif
}
/* FALLTHRU */
case LS:
fap->walkflags &= ~WALK_NOSTAT;
goto found_action;
case FLS:
fap->walkflags &= ~WALK_NOSTAT;
case FPRINT:
case FPRINT0:
case FPRINTNNL:
p = nextarg(fap, n);
n->val.fp = fileopen(p, "wcta");
if (n->val.fp == NULL) {
ferrmsg(fap->std[2],
_("Cannot open '%s' for '-%s'.\n"),
p, tokennames[n->op]);
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
case PRINT:
case PRINT0:
case PRINTNNL:
found_action:
fap->found_action = TRUE;
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case FIND_ENDARGS:
#ifdef DEBUG
ferrmsgno(fap->std[2], EX_BAD,
_("ENDARGS in parseprim()\n"));
#endif
ferrmsgno(fap->std[2], EX_BAD,
_("Incomplete expression.\n"));
find_freenode(n);
fap->jmp = ojmp; /* Restore old jump target */
return ((findn_t *)NULL);
case OPEN:
nexttoken(fap);
n->this = (char *)parse(fap);
if (n->this == NULL) {
find_freenode(n);
fap->jmp = ojmp; /* Restore old jump target */
return ((findn_t *)NULL);
}
if (fap->primtype != CLOSE) {
ferrmsgno(fap->std[2], EX_BAD,
_("Found '%s', but ')' expected.\n"),
fap->Argv[-1]);
errjmp(fap, EX_BAD);
/* NOTREACHED */
} else {
nexttoken(fap);
fap->jmp = ojmp; /* Restore old jump target */
return (n);
}
break;
case CLOSE:
/*
* The triggering arg is at fap->Argv[-2].
*/
ferrmsgno(fap->std[2], EX_BAD, _("Missing '('.\n"));
errjmp(fap, EX_BAD);
/* NOTREACHED */
case LNOT:
nexttoken(fap);
n->this = (char *)parseprim(fap);
if (n->this == NULL) {
find_freenode(n);
n = (findn_t *)NULL;
}
fap->jmp = ojmp; /* Restore old jump target */
return (n);
case AND:
case LOR:
ferrmsgno(fap->std[2], EX_BAD,
_("Invalid expression with '-%s'.\n"), tokennames[n->op]);
errjmp(fap, EX_BAD);
/* NOTREACHED */
case HELP:
find_usage(fap->std[2]);
find_freenode(n);
fap->jmp = ojmp; /* Restore old jump target */
return ((findn_t *)NULL);
default:
ferrmsgno(fap->std[2], EX_BAD,
_("Internal malfunction, found unknown primary '-%s' (%d).\n"),
find_tname(n->op), n->op);
errjmp(fap, EX_BAD);
/* NOTREACHED */
}
fap->jmp = ojmp; /* Restore old jump target */
fap->primtype = FIND_ERRARG; /* Mark as "parse aborted" */
return (0);
}
#define S_ALLPERM (S_IRWXU|S_IRWXG|S_IRWXO)
#define S_ALLFLAGS (S_ISUID|S_ISGID|S_ISVTX)
#define S_ALLMODES (S_ALLFLAGS | S_ALLPERM)
EXPORT void
find_firstprim(pac, pav)
int *pac;
char *const *pav[];
{
register int cac = *pac;
register char *const *cav = *pav;
register char c;
while (cac > 0 &&
(c = **cav) != '-' && c != '(' && c != ')' && c != '!') {
cav++;
cac--;
}
*pac = cac;
*pav = cav;
}
EXPORT BOOL
find_primary(t, op)
findn_t *t;
int op;
{
BOOL ret = FALSE;
if (t->op == op) {
return (TRUE);
}
switch (t->op) {
case OPEN:
ret = find_primary((findn_t *)t->this, op);
break;
case LNOT:
ret = find_primary((findn_t *)t->this, op);
break;
case AND:
ret = find_primary((findn_t *)t->left, op);
if (ret)
return (ret);
ret = find_primary((findn_t *)t->right, op);
break;
case LOR:
ret = find_primary((findn_t *)t->left, op);
if (ret)
return (ret);
ret = find_primary((findn_t *)t->right, op);
break;
default:
;
}
return (ret);
}
EXPORT BOOL
find_pname(t, word)
findn_t *t;
char *word;
{
if (streql(word, "-exec+"))
return (find_primary(t, EXECPLUS));
if (streql(word, "-execdir+"))
return (find_primary(t, EXECDIRPLUS));
return (find_primary(t, find_token(word)));
}
EXPORT BOOL
find_hasprint(t)
findn_t *t;
{
if (t == NULL)
return (FALSE);
if (find_primary(t, PRINT) || find_primary(t, PRINTNNL) ||
find_primary(t, PRINT0))
return (TRUE);
if (find_primary(t, LS))
return (TRUE);
return (FALSE);
}
EXPORT BOOL
find_hasexec(t)
findn_t *t;
{
if (t == NULL)
return (FALSE);
if (find_primary(t, EXEC) || find_primary(t, EXECPLUS))
return (TRUE);
if (find_primary(t, EXECDIR) || find_primary(t, EXECDIRPLUS))
return (TRUE);
if (find_primary(t, OK_EXEC) || find_primary(t, OK_EXECDIR))
return (TRUE);
return (FALSE);
}
#ifdef FIND_MAIN
LOCAL int
walkfunc(nm, fs, type, state)
char *nm;
struct stat *fs;
int type;
struct WALK *state;
{
if (type == WALK_NS) {
ferrmsg(state->std[2], _("Cannot stat '%s'.\n"), nm);
state->err = 1;
return (0);
} else if (type == WALK_SLN && (state->walkflags & WALK_PHYS) == 0) {
ferrmsg(state->std[2],
_("Cannot follow symlink '%s'.\n"), nm);
state->err = 1;
return (0);
} else if (type == WALK_DNR) {
if (state->flags & WALK_WF_NOCHDIR) {
ferrmsg(state->std[2],
_("Cannot chdir to '%s'.\n"), nm);
} else {
ferrmsg(state->std[2],
_("Cannot read '%s'.\n"), nm);
}
state->err = 1;
return (0);
}
if (state->maxdepth >= 0 && state->level >= state->maxdepth)
state->flags |= WALK_WF_PRUNE;
if (state->mindepth >= 0 && state->level < state->mindepth)
return (0);
find_expr(nm, nm + state->base, fs, state, state->tree);
return (0);
}
#endif
#ifdef __FIND__
LOCAL inline BOOL
#else
EXPORT BOOL
#endif
find_expr(f, ff, fs, state, t)
char *f; /* path name */
char *ff; /* file name */
struct stat *fs;
struct WALK *state;
findn_t *t;
{
time_t xtime;
FILE *fp = state->std[1];
char *p;
char lname[8192];
int fnflags = 0;
switch (t->op) {
case ILNAME:
#ifdef FNM_IGNORECASE
fnflags = FNM_IGNORECASE;
#endif
case LNAME: {
int lsize;
if (!S_ISLNK(fs->st_mode))
return (FALSE);
if (state->lname != NULL) {
p = state->lname;
goto nmatch;
}
lname[0] = '\0';
/*
* For file names from the command line, we did not perform
* a chdir() before, so we need to use the full path name.
*/
lsize = readlink(state->level ? ff : f, lname, sizeof (lname));
if (lsize < 0) {
ferrmsg(state->std[2],
_("Cannot read link '%s'.\n"), ff);
return (FALSE);
}
lname[sizeof (lname)-1] = '\0';
if (lsize >= 0)
lname[lsize] = '\0';
p = lname;
goto nmatch;
}
case IPATH:
#ifdef FNM_IGNORECASE
fnflags = FNM_IGNORECASE;
#endif
case PATH:
p = f;
goto nmatch;
case INAME:
#ifdef FNM_IGNORECASE
fnflags = FNM_IGNORECASE;
#endif
case NAME:
p = ff;
nmatch:
#if defined(HAVE_FNMATCH)
return (!fnmatch(t->this, p, fnflags));
#else
goto pattern; /* Use patmatch() as "fallback" */
#endif
case LPAT: {
int lsize;
if (!S_ISLNK(fs->st_mode))
return (FALSE);
if (state->lname != NULL) {
p = state->lname;
goto pattern;
}
lname[0] = '\0';
/*
* For file names from the command line, we did not perform
* a chdir() before, so we need to use the full path name.
*/
lsize = readlink(state->level ? ff : f, lname, sizeof (lname));
if (lsize < 0) {
ferrmsg(state->std[2],
_("Cannot read link '%s'.\n"), ff);
return (FALSE);
}
lname[sizeof (lname)-1] = '\0';
if (lsize >= 0)
lname[lsize] = '\0';
p = lname;
goto pattern;
}
case PPAT:
p = f;
goto pattern;
case PAT:
p = ff;
pattern: {
Uchar *pr; /* patmatch() return */
pr = patmatch((Uchar *)t->this, (int *)t->right,
(Uchar *)p, 0, strlen(p), t->val.i, state->patstate);
return (*p && pr && (*pr == '\0'));
}
case SIZE:
switch (*(t->left)) {
case '+':
if (t->this)
return (fs->st_size > t->val.size);
return ((fs->st_size+511)/512 > t->val.size);
case '-':
if (t->this)
return (fs->st_size < t->val.size);
return ((fs->st_size+511)/512 < t->val.size);
default:
if (t->this)
return (fs->st_size == t->val.size);
return ((fs->st_size+511)/512 == t->val.size);
}
case EMPTY:
if (S_ISREG(fs->st_mode) && fs->st_size == 0)
return (TRUE);
/*
* For file names from the command line, we did not perform
* a chdir() before, so we need to use the full path name.
*/
if (S_ISDIR(fs->st_mode)) {
struct dirent *dp;
DIR *d = opendir(state->level ? ff : f);
if (d == NULL) {
ferrmsg(state->std[2],
_("Cannot open directory '%s'.\n"),
ff);
return (FALSE);
}
while ((dp = readdir(d)) != NULL) {
register char *name = dp->d_name;
/*
* Skip the following names: "", ".", "..".
*/
if (name[name[0] != '.' ? 0 :
name[1] != '.' ? 1 : 2] == '\0')
continue;
closedir(d);
return (FALSE);
}
closedir(d);
return (TRUE);
}
return (FALSE);
case LINKS:
switch (*(t->left)) {
case '+':
return (fs->st_nlink > t->val.nlink);
case '-':
return (fs->st_nlink < t->val.nlink);
default:
return (fs->st_nlink == t->val.nlink);
}
case INUM:
switch (*(t->left)) {
case '+':
return (fs->st_ino > t->val.ino);
case '-':
return (fs->st_ino < t->val.ino);
default:
return (fs->st_ino == t->val.ino);
}
case LINKEDTO:
return ((fs->st_ino == t->val.ino) &&
(fs->st_dev == t->val2.dev));
case READABLE:
t->val.i = R_OK;
goto check_access;
case WRITABLE:
t->val.i = W_OK;
goto check_access;
case EXECUTABLE:
t->val.i = X_OK;
check_access:
/*
* For file names from the command line, we did not perform
* a chdir() before, so we need to use the full path name.
*/
if (access(state->level ? ff : f, t->val.i) < 0)
return (FALSE);
return (TRUE);
case AMIN:
case ATIME:
xtime = fs->st_atime;
goto times;
case CMIN:
case CTIME:
xtime = fs->st_ctime;
goto times;
case MMIN:
case MTIME:
case TIME:
xtime = fs->st_mtime;
times:
if (t->val2.i != 0)
goto timex;
switch (*(t->left)) {
case '+':
return ((find_now-xtime)/DAYSECS > t->val.time);
case '-':
return ((find_now-xtime)/DAYSECS < t->val.time);
default:
return ((find_now-xtime)/DAYSECS == t->val.time);
}
timex:
switch (*(t->left)) {
case '+':
return ((find_now-xtime) > t->val.time);
case '-':
return ((find_now-xtime) < t->val.time);
default:
return ((find_now-xtime) == t->val.time);
}
case NEWERAA:
case NEWERAC:
case NEWERAM:
return (t->val.time < fs->st_atime);
case NEWERCA:
case NEWERCC:
case NEWERCM:
return (t->val.time < fs->st_ctime);
case NEWER:
case NEWERMA:
case NEWERMC:
case NEWERMM:
return (t->val.time < fs->st_mtime);
case TYPE:
switch (*(t->this)) {
case 'b':
return (S_ISBLK(fs->st_mode));
case 'c':
return (S_ISCHR(fs->st_mode));
case 'd':
return (S_ISDIR(fs->st_mode));
case 'D':
return (S_ISDOOR(fs->st_mode));
case 'e':
return (S_ISEVC(fs->st_mode));
case 'f':
return (S_ISREG(fs->st_mode));
case 'l':
return (S_ISLNK(fs->st_mode));
case 'p':
return (S_ISFIFO(fs->st_mode));
case 'P':
return (S_ISPORT(fs->st_mode));
case 's':
return (S_ISSOCK(fs->st_mode));
default:
return (FALSE);
}
case FSTYPE:
#ifdef HAVE_ST_FSTYPE
return (streql(t->this, fs->st_fstype));
#else
return (TRUE);
#endif
case LOCL:
#ifdef HAVE_ST_FSTYPE
if (streql("nfs", fs->st_fstype) ||
streql("autofs", fs->st_fstype) ||
streql("cachefs", fs->st_fstype))
return (FALSE);
#endif
return (TRUE);
#ifdef CHOWN
case CHOWN:
fs->st_uid = t->val.uid;
return (TRUE);
#endif
case USER:
switch (*(t->left)) {
case '+':
return (fs->st_uid > t->val.uid);
case '-':
return (fs->st_uid < t->val.uid);
default:
return (fs->st_uid == t->val.uid);
}
#ifdef CHGRP
case CHGRP:
fs->st_gid = t->val.gid;
return (TRUE);
#endif
case GROUP:
switch (*(t->left)) {
case '+':
return (fs->st_gid > t->val.gid);
case '-':
return (fs->st_gid < t->val.gid);
default:
return (fs->st_gid == t->val.gid);
}
#ifdef CHMOD
case CHMOD:
getperm(state->std[2], t->left, tokennames[t->op],
&t->val.mode, fs->st_mode & S_ALLMODES,
(S_ISDIR(fs->st_mode) ||
(fs->st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) != 0) ?
GP_DOX:GP_NOX);
fs->st_mode &= ~S_ALLMODES;
fs->st_mode |= t->val.mode;
return (TRUE);
#endif
case PERM:
if (t->left[0] == '+')
return ((fs->st_mode & t->val.mode) != 0);
else if (t->left[0] == '-')
return ((fs->st_mode & t->val.mode) == t->val.mode);
else
return ((fs->st_mode & S_ALLMODES) == t->val.mode);
case MODE:
return (TRUE);
case XDEV:
case MOUNT:
case DEPTH:
case FOLLOW:
case DOSTAT:
return (TRUE);
case NOUSER:
return (getpwuid(fs->st_uid) == NULL);
case NOGRP:
return (getgrgid(fs->st_gid) == NULL);
case PRUNE:
state->flags |= WALK_WF_PRUNE;
return (TRUE);
case MAXDEPTH:
case MINDEPTH:
return (TRUE);
case PACL:
if (state->pflags & PF_ACL) {
return ((state->pflags & PF_HAS_ACL) != 0);
}
return (has_acl(state->std[2], f, ff, fs));
case XATTR:
if (state->pflags & PF_XATTR) {
return ((state->pflags & PF_HAS_XATTR) != 0);
}
return (has_xattr(state->std[2], ff));
case SPARSE:
if (!S_ISREG(fs->st_mode))
return (FALSE);
#ifdef HAVE_ST_BLOCKS
return (fs->st_size > (fs->st_blocks * DEV_BSIZE + DEV_BSIZE));
#else
return (FALSE);
#endif
case OK_EXEC:
case OK_EXECDIR: {
char qbuf[32];
fflush(state->std[1]);
fprintf(state->std[2], "< %s ... %s > ? ", ((char **)t->this)[0], f);
fflush(state->std[2]);
fgetline(state->std[0], qbuf, sizeof (qbuf) - 1);
switch (qbuf[0]) {
case 'y':
if (qbuf[1] == '\0' || streql(qbuf, "yes")) break;
default:
return (FALSE);
}
}
/* FALLTHRU */
case EXEC:
case EXECDIR:
#ifdef HAVE_FORK
return (doexec(
state->level && (t->op == OK_EXECDIR || t->op == EXECDIR)?
ff:f,
t, t->val.i, (char **)t->this, state));
#else
ferrmsgno(state->std[2], EX_BAD,
_("'-%s' is unsupported on this platform, returning FALSE.\n"),
find_tname(t->op));
return (FALSE);
#endif
case EXECPLUS:
#ifdef HAVE_FORK
return (plusexec(f, t, state));
#else
ferrmsgno(state->std[2], EX_BAD,
_("'-%s' is unsupported on this platform, returning FALSE.\n"),
find_tname(t->op));
return (FALSE);
#endif
case FPRINT:
fp = t->val.fp;
/* FALLTHRU */
case PRINT:
filewrite(fp, f, strlen(f));
putc('\n', fp);
return (TRUE);
case FPRINT0:
fp = t->val.fp;
/* FALLTHRU */
case PRINT0:
filewrite(fp, f, strlen(f));
putc('\0', fp);
return (TRUE);
case FPRINTNNL:
fp = t->val.fp;
/* FALLTHRU */
case PRINTNNL:
filewrite(fp, f, strlen(f));
putc(' ', fp);
return (TRUE);
case FLS:
fp = t->val.fp;
/* FALLTHRU */
case LS: {
FILE *std[3];
/*
* The third parameter is the file name used for readlink()
* (inside find_list()) relatively to the current working
* directory. For file names from the command line, we did not
* perform a chdir() before, so we need to use the full path
* name.
*/
std[0] = state->std[0];
std[1] = fp;
std[2] = state->std[2];
find_list(std, fs, f, state->level ? ff : f, state);
return (TRUE);
}
case LTRUE:
return (TRUE);
case LFALSE:
return (FALSE);
case OPEN:
return (find_expr(f, ff, fs, state, (findn_t *)t->this));
case LNOT:
return (!find_expr(f, ff, fs, state, (findn_t *)t->this));
case AND:
return (find_expr(f, ff, fs, state, (findn_t *)t->left) ?
find_expr(f, ff, fs, state, (findn_t *)t->right) : 0);
case LOR:
return (find_expr(f, ff, fs, state, (findn_t *)t->left) ?
1 : find_expr(f, ff, fs, state, (findn_t *)t->right));
}
if (!(state->pflags & 0x80000000)) {
ferrmsgno(state->std[2], EX_BAD,
_("Internal malfunction, found unknown primary '-%s' (%d).\n"),
find_tname(t->op), t->op);
state->pflags |= 0x80000000;
}
return (FALSE); /* Unknown operator ??? */
}
#ifdef HAVE_FORK
LOCAL BOOL
doexec(f, t, ac, av, state)
char *f;
findn_t *t;
int ac;
char **av;
struct WALK *state;
{
#ifdef HAVE_VFORK
char ** volatile aav = NULL;
#endif
pid_t pid;
int retval;
#ifdef HAVE_VFORK
if (f && ac >= 32) {
aav = malloc((ac+1) * sizeof (char **));
if (aav == NULL) {
ferrmsg(state->std[2], _("Cannot malloc arg vector for -exec.\n"));
return (FALSE);
}
}
#endif
if ((pid = vfork()) < 0) {
#ifdef HAVE_VFORK
ferrmsg(state->std[2], _("Cannot vfork child.\n"));
#else
ferrmsg(state->std[2], _("Cannot fork child.\n"));
#endif
#ifdef HAVE_VFORK
/*
* Ugly code as a workaround for broken Linux include files
* that do not specify vfork() as a problem. This may
* cause aav to be != NULL even when malloc() above was never
* called. Freeing a random address on some platforms causes a
* coredump.
* As similar problems may exist on other platforms, where the
* correct fix to mark aav volatile does not work, we keep the
* workaround to check f and ac as well.
*/
if (aav && f && ac >= 32)
free(aav);
#endif
return (FALSE);
}
if (pid) {
while (wait(&retval) != pid)
/* LINTED */
;
#ifdef HAVE_VFORK
/*
* Ugly code as a workaround for broken Linux include files
* that do not specify vfork() as a problem. This may
* cause aav to be != NULL even when malloc() above was never
* called. Freeing a random address on some platforms causes a
* coredump.
* As similar problems may exist on other platforms, where the
* correct fix to mark aav volatile does not work, we keep the
* workaround to check f and ac as well.
*/
if (aav && f && ac >= 32)
free(aav);
#endif
return (retval == 0);
} else {
#ifdef HAVE_VFORK
char *xav[32];
register char **pp2 = xav;
#endif
register int i;
register char **pp = av;
int err;
/*
* This is the forked process and for this reason, we may
* call fcomerr() here without problems.
*/
if (t != NULL && /* Not called from find_plusflush() */
t->op != OK_EXECDIR && t->op != EXECDIR &&
walkhome(state) < 0) {
fcomerr(state->std[2],
_("Cannot chdir to '.'.\n"));
}
#ifndef F_SETFD
walkclose(state);
#endif
#define iscurlypair(p) ((p)[0] == '{' && (p)[1] == '}' && (p)[2] == '\0')
#ifdef HAVE_VFORK
if (aav)
pp2 = aav;
#endif
if (f) { /* NULL for -exec+ */
for (i = 0; i < ac; i++, pp++) {
register char *p = *pp;
#ifdef HAVE_VFORK
if (iscurlypair(p)) /* streql(p, "{}") */
*pp2++ = f;
else
*pp2++ = p;
#else
if (iscurlypair(p)) /* streql(p, "{}") */
*pp = f;
#endif
}
#ifdef HAVE_VFORK
if (aav)
pp = aav;
else
pp = xav;
#endif
} else {
pp = av;
}
#ifndef HAVE_VFORK
pp = av;
#endif
#ifdef PLUS_DEBUG
error("argsize %d\n",
(plusp->endp - (char *)&plusp->nextargp[0]) -
(plusp->laststr - (char *)&plusp->nextargp[1]));
#endif
pp[ac] = NULL; /* -exec {} \; is not NULL terminated */
fexecve(av[0], state->std[0], state->std[1], state->std[2],
pp, state->env);
err = geterrno();
#ifdef PLUS_DEBUG
error("argsize %d\n",
(plusp->endp - (char *)&plusp->nextargp[0]) -
(plusp->laststr - (char *)&plusp->nextargp[1]));
#endif
/*
* This is the forked process and for this reason, we may
* call _exit() here without problems.
*/
ferrmsgno(state->std[2], err,
_("Cannot execute '%s'.\n"), av[0]);
_exit(err);
/* NOTREACHED */
return (-1);
}
}
#endif /* HAVE FORK */
#ifndef LINE_MAX
#define LINE_MAX 1024
#endif
#if defined(IS_MACOS_X) && defined(HAVE_CRT_EXTERNS_H)
/*
* The MAC OS X linker does not grok "common" varaibles.
* We need to fetch the address of "environ" using a hack.
*/
#include <crt_externs.h>
#define environ *_NSGetEnviron()
#else
extern char **environ;
#endif
/*
* Return the number of environment entries including the final NULL pointer.
*/
LOCAL int
countenv()
{
register int evs = 0;
register char **ep;
for (ep = environ; *ep; ep++) {
evs++;
}
evs++; /* The environ NULL ptr at the end */
return (evs);
}
/*
* Return ARG_MAX - LINE_MAX - size of current environment.
*
* The return value is reduced by LINE_MAX to allow the called
* program to do own exec(2) calls with slightly increased arg size.
*/
LOCAL int
argsize(xtype)
int xtype;
{
static int ret = 0;
if (ret == 0) {
register int evs = 0;
register char **ep;
for (ep = environ; *ep; ep++) {
evs += strlen(*ep) + 1 + sizeof (ep);
}
evs += sizeof (char **); /* The environ NULL ptr at the end */
#ifdef _SC_ARG_MAX
ret = sysconf(_SC_ARG_MAX);
if (ret < 0)
#ifdef _POSIX_ARG_MAX
ret = _POSIX_ARG_MAX;
#else
ret = ARG_MAX;
#endif
#else /* VV NO _SC_ARG_MAX VV */
#ifdef ARG_MAX
ret = ARG_MAX;
#else
#ifdef NCARGS
/*
* On Solaris: ARG_MAX = NCARGS - space for other stuff on
* initial stack. This size is 256 for 32 bit and 512 for
* 64 bit programs.
*/
ret = NCARGS - 256; /* Let us do the same */
#endif
#endif
#endif
#ifdef MULTI_ARG_MAX
ret = xargsize(xtype, ret);
#endif
#ifdef PLUS_DEBUG
ret = 3000;
#define LINE_MAX 100
error("evs %d\n", evs);
#endif
if (ret <= 0)
ret = 10000; /* Just a guess */
ret -= evs; /* Subtract current env size */
if ((ret - LINE_MAX) > 0)
ret -= LINE_MAX;
else
ret -= 100;
}
return (ret);
}
/*
* Return the executable type:
*
* 0 unknown type -> default
* 32 a 32 bit binary
* 64 a 64 bit binary
*/
LOCAL int
extype(name)
char *name;
{
int f;
char *xname;
char elfbuf[8];
xname = findinpath(name, X_OK, TRUE, NULL);
if (name == NULL)
xname = name;
if ((f = open(xname, O_RDONLY|O_BINARY)) < 0) {
if (xname != name)
free(xname);
return (0);
}
if (xname != name)
free(xname);
if (read(f, elfbuf, sizeof (elfbuf)) < sizeof (elfbuf)) {
close(f);
return (0);
}
close(f);
/*
* We only support ELF binaries
*/
if (elfbuf[0] != 0x7F ||
elfbuf[1] != 'E' || elfbuf[2] != 'L' || elfbuf[3] != 'F')
return (0);
switch (elfbuf[4] & 0xFF) {
case 1: /* ELFCLASS32 */
return (32);
case 2: /* ELFCLASS64 */
return (64);
}
return (0);
}
#ifdef MULTI_ARG_MAX
/*
* If we have both _ARG_MAX32 and _ARG_MAX64 (currently on SunOS)
* correct maxarg based on the target binary type.
*/
LOCAL int
xargsize(xtype, maxarg)
int xtype;
int maxarg;
{
/*
* Set up a safe fallback in case we are not able to determine the
* binary type.
*/
if (maxarg > _ARG_MAX32)
maxarg = _ARG_MAX32;
switch (xtype) {
case 32:
maxarg = _ARG_MAX32;
break;
case 64:
maxarg = _ARG_MAX64;
break;
}
return (maxarg);
}
#endif
LOCAL BOOL
pluscreate(f, ac, av, fap)
FILE *f;
int ac;
char **av;
finda_t *fap;
{
struct plusargs *pp;
register char **ap = av;
register int i;
int mxtype;
int xtype;
int maxarg;
int nenv;
xtype = extype(av[0]); /* Get -exec executable type */
maxarg = argsize(xtype); /* Get ARG_MAX for executable */
nenv = countenv(); /* # of ents in current env */
mxtype = sizeof (char *) * CHAR_BIT;
if (xtype == 0)
mxtype = 0;
if (xtype == mxtype)
nenv = 0; /* Easy case */
else if (xtype > mxtype)
nenv = -nenv; /* Need to reduce arg size */
maxarg += nenv * (32 / CHAR_BIT); /* Correct maxarg by ptr size */
#ifdef PLUS_DEBUG
printf("Argc %d\n", ac);
ap = av;
for (i = 0; i < ac; i++, ap++)
printf("ARG %d '%s'\n", i, *ap);
#endif
pp = __fjmalloc(fap->std[2], maxarg + sizeof (struct plusargs),
"-exec args", fap->jmp);
pp->laststr = pp->endp = (char *)(&pp->av[0]) + maxarg;
pp->nenv = nenv;
pp->ac = 0;
pp->nextargp = &pp->av[0];
#ifdef PLUS_DEBUG
printf("pp %d\n", pp);
printf("pp->laststr %d\n", pp->laststr);
printf("argsize() %d\n", maxarg);
#endif
/*
* Copy args from command line.
*/
ap = av;
for (i = 0; i < ac; i++, ap++) {
#ifdef PLUS_DEBUG
printf("ARG %d '%s'\n", i, *ap);
#endif
*(pp->nextargp++) = *ap;
pp->laststr -= strlen(*ap) + 1;
pp->ac++;
if (pp->laststr <= (char *)pp->nextargp) {
ferrmsgno(f, EX_BAD,
_("No space to copy -exec args.\n"));
free(pp); /* The exec plusargs struct */
return (FALSE);
}
}
#ifdef PLUS_DEBUG
error("lastr %d endp %d diff %d\n",
pp->laststr, pp->endp, pp->endp - pp->laststr);
#endif
pp->endp = pp->laststr; /* Reduce endp by the size of cmdline args */
#ifdef PLUS_DEBUG
ap = &pp->av[0];
for (i = 0; i < pp->ac; i++, ap++) {
printf("ARG %d '%s'\n", i, *ap);
}
#endif
#ifdef PLUS_DEBUG
printf("pp %d\n", pp);
printf("pp->laststr %d\n", pp->laststr);
#endif
pp->next = fap->plusp;
fap->plusp = pp;
#ifdef PLUS_DEBUG
plusp = fap->plusp; /* Makes libfind not MT safe */
#endif
return (TRUE);
}
#ifdef HAVE_FORK
LOCAL BOOL
plusexec(f, t, state)
char *f;
findn_t *t;
struct WALK *state;
{
register struct plusargs *pp = (struct plusargs *)t->this;
#ifdef PLUS_DEBUG
register char **ap;
register int i;
#endif
size_t size;
size_t slen = strlen(f) + 1;
char *nargp = (char *)&pp->nextargp[2];
char *cp;
int ret = TRUE;
size = pp->laststr - (char *)&pp->nextargp[2]; /* Remaining strlen */
if (pp->nenv < 0) /* Called cmd has */
nargp += pp->ac * (32 / CHAR_BIT); /* larger ptr size */
if (pp->laststr < nargp || /* Already full */
slen > size) { /* str does not fit */
pp->nextargp[0] = NULL;
ret = doexec(NULL, t, pp->ac, pp->av, state);
pp->laststr = pp->endp;
pp->ac = t->val.i;
pp->nextargp = &pp->av[t->val.i];
size = pp->laststr - (char *)&pp->nextargp[2];
}
if (pp->laststr < nargp ||
slen > size) {
ferrmsgno(state->std[2], EX_BAD,
_("No space for arg '%s'.\n"), f);
return (FALSE);
}
cp = pp->laststr - slen;
strcpy(cp, f);
pp->nextargp[0] = cp;
pp->ac++;
pp->nextargp++;
pp->laststr -= slen;
#ifdef PLUS_DEBUG
ap = &plusp->av[0];
for (i = 0; i < plusp->ac; i++, ap++) {
printf("ARG %d '%s'\n", i, *ap);
}
error("EXECPLUS '%s'\n", f);
#endif
return (ret);
}
#endif /* HAVE_FORK */
EXPORT int
find_plusflush(p, state)
void *p;
struct WALK *state;
{
struct plusargs *plusp = p;
BOOL ret = TRUE;
/*
* Execute all unflushed '-exec .... {} +' expressions.
*/
while (plusp) {
#ifdef PLUS_DEBUG
error("lastr %p endp %p\n", plusp->laststr, plusp->endp);
#endif
if (plusp->laststr != plusp->endp) {
plusp->nextargp[0] = NULL;
#ifdef HAVE_FORK
if (!doexec(NULL, NULL, plusp->ac, plusp->av, state))
ret = FALSE;
#endif
}
plusp = plusp->next;
}
return (ret);
}
EXPORT void
find_usage(f)
FILE *f;
{
fprintf(f, _("Usage: %s [options] [path_1 ... path_n] [expression]\n"), get_progname());
fprintf(f, _("Options:\n"));
fprintf(f, _(" -H follow symbolic links encountered on command line\n"));
fprintf(f, _(" -L follow all symbolic links\n"));
fprintf(f, _("* -P do not follow symbolic links (default)\n"));
fprintf(f, _("* -help Print this help.\n"));
fprintf(f, _("* -version Print version number.\n"));
fprintf(f, _("Operators in decreasing precedence:\n"));
fprintf(f, _(" ( ) group an expression\n"));
fprintf(f, _(" !, -a, -o negate a primary (unary NOT), logical AND, logical OR\n"));
fprintf(f, _("Primaries:\n"));
fprintf(f, _("* -acl TRUE if the file has additional ACLs defined\n"));
fprintf(f, _(" -atime # TRUE if st_atime is in specified range\n"));
#ifdef CHGRP
fprintf(f, _("* -chgrp gname/gid always TRUE, sets st_gid to gname/gid\n"));
#endif
#ifdef CHMOD
fprintf(f, _("* -chmod mode/onum always TRUE, sets permissions to mode/onum\n"));
#endif
#ifdef CHOWN
fprintf(f, _("* -chown uname/uid always TRUE, sets st_uid to uname/uid\n"));
#endif
fprintf(f, _(" -ctime # TRUE if st_ctime is in specified range\n"));
fprintf(f, _(" -depth evaluate directory content before directory (always TRUE)\n"));
fprintf(f, _("* -dostat Do not do stat optimization (always TRUE)\n"));
fprintf(f, _("* -empty TRUE zero sized plain file or empty directory\n"));
fprintf(f, _(" -exec program [argument ...] \\;\n"));
fprintf(f, _(" -exec program [argument ...] {} +\n"));
fprintf(f, _("* -execdir program [argument ...] \\;\n"));
fprintf(f, _("* -executable TRUE if file is executable by real user id\n"));
fprintf(f, _("* -false always FALSE\n"));
fprintf(f, _("* -fls file list files similar to 'ls -ilds' into 'file' (always TRUE)\n"));
fprintf(f, _("* -follow outdated: follow all symbolic links (always TRUE)\n"));
fprintf(f, _("* -fprint file print file names line separated into 'file' (always TRUE)\n"));
fprintf(f, _("* -fprint0 file print file names nul separated into 'file' (always TRUE)\n"));
fprintf(f, _("* -fprintnnl file print file names space separated into 'file' (always TRUE)\n"));
fprintf(f, _("* -fstype type TRUE if st_fstype matches type\n"));
fprintf(f, _(" -group gname/gid TRUE if st_gid matches gname/gid\n"));
fprintf(f, _("* -ilname glob TRUE if symlink name matches shell glob\n"));
fprintf(f, _("* -ilpat pattern TRUE if symlink name matches pattern\n"));
fprintf(f, _("* -iname glob TRUE if path component matches shell glob\n"));
fprintf(f, _("* -inum # TRUE if st_ino is in specified range\n"));
fprintf(f, _("* -ipat pattern TRUE if path component matches pattern\n"));
fprintf(f, _("* -ipath glob TRUE if full path matches shell glob\n"));
fprintf(f, _("* -ippat pattern TRUE if full path matches pattern\n"));
fprintf(f, _("* -linkedto path TRUE if the file is linked to path\n"));
fprintf(f, _(" -links # TRUE if st_nlink is in specified range\n"));
fprintf(f, _("* -lname glob TRUE if symlink name matches shell glob\n"));
fprintf(f, _("* -local TRUE if st_fstype does not match remote fs types\n"));
fprintf(f, _("* -lpat pattern TRUE if symlink name matches pattern\n"));
fprintf(f, _("* -ls list files similar to 'ls -ilds' (always TRUE)\n"));
fprintf(f, _("* -maxdepth # descend at most # directory levels (always TRUE)\n"));
fprintf(f, _("* -mindepth # start tests at directory level # (always TRUE)\n"));
fprintf(f, _(" -mtime # TRUE if st_mtime is in specified range\n"));
fprintf(f, _(" -name glob TRUE if path component matches shell glob\n"));
fprintf(f, _(" -newer file TRUE if st_mtime newer then mtime of file\n"));
fprintf(f, _("* -newerXY file TRUE if [acm]time (X) newer then [acm]time (Y) of file\n"));
fprintf(f, _(" -nogroup TRUE if not in group database\n"));
fprintf(f, _(" -nouser TRUE if not in user database\n"));
fprintf(f, _(" -ok program [argument ...] \\;\n"));
fprintf(f, _("* -okdir program [argument ...] \\;\n"));
fprintf(f, _("* -pat pattern TRUE if path component matches pattern\n"));
fprintf(f, _("* -path glob TRUE if full path matches shell glob\n"));
fprintf(f, _(" -perm mode/onum TRUE if symbolic/octal permission matches\n"));
fprintf(f, _("* -ppat pattern TRUE if full path matches pattern\n"));
fprintf(f, _(" -print print file names line separated to stdout (always TRUE)\n"));
fprintf(f, _("* -print0 print file names nul separated to stdout (always TRUE)\n"));
fprintf(f, _("* -printnnl print file names space separated to stdout (always TRUE)\n"));
fprintf(f, _(" -prune do not descent current directory (always TRUE)\n"));
fprintf(f, _("* -readable TRUE if file is readable by real user id\n"));
fprintf(f, _(" -size # TRUE if st_size is in specified range\n"));
fprintf(f, _("* -sparse TRUE if file appears to be sparse\n"));
fprintf(f, _("* -true always TRUE\n"));
fprintf(f, _(" -type c TRUE if file type matches, c is from (b c d D e f l p P s)\n"));
fprintf(f, _(" -user uname/uid TRUE if st_uid matches uname/uid\n"));
fprintf(f, _("* -writable TRUE if file is writable by real user id\n"));
fprintf(f, _("* -xattr TRUE if the file has extended attributes\n"));
fprintf(f, _(" -xdev, -mount restrict search to current filesystem (always TRUE)\n"));
fprintf(f, _("Primaries marked with '*' are POSIX extensions, avoid them in portable scripts.\n"));
fprintf(f, _("If path is omitted, '.' is used. If expression is omitted, -print is used.\n"));
}
#ifdef FIND_MAIN
/* ARGSUSED */
LOCAL int
getflg(optstr, argp)
char *optstr;
long *argp;
{
if (optstr[1] != '\0')
return (-1);
switch (*optstr) {
case 'H':
*(int *)argp |= WALK_ARGFOLLOW;
return (TRUE);
case 'L':
*(int *)argp |= WALK_ALLFOLLOW;
return (TRUE);
case 'P':
*(int *)argp &= ~(WALK_ARGFOLLOW | WALK_ALLFOLLOW);
return (TRUE);
default:
return (-1);
}
}
EXPORT int
main(ac, av)
int ac;
char **av;
{
int cac = ac;
char **cav = av;
char **firstpath;
char **firstprim;
BOOL help = FALSE;
BOOL prversion = FALSE;
finda_t fa;
findn_t *Tree;
struct WALK walkstate;
save_args(ac, av);
#ifdef USE_NLS
setlocale(LC_ALL, "");
bindtextdomain("SCHILY_FIND", "/opt/schily/lib/locale");
textdomain("SCHILY_FIND");
#endif
find_argsinit(&fa);
fa.walkflags = WALK_CHDIR | WALK_PHYS;
fa.walkflags |= WALK_NOSTAT;
fa.walkflags |= WALK_NOEXIT;
/*
* Do not check the return code for getargs() as we may get an error
* code from e.g. "find -print" and we do not like to handle this here.
*/
cac--, cav++;
getargs(&cac, (char * const **)&cav, "help,version,&",
&help, &prversion,
getflg, (long *)&fa.walkflags);
if (help) {
find_usage(stderr);
return (0);
}
if (prversion) {
printf("sfind release %s (%s-%s-%s) Copyright (C) 2004-2008 J<>rg Schilling\n",
strvers,
HOST_CPU, HOST_VENDOR, HOST_OS);
return (0);
}
firstpath = cav; /* Remember first file type arg */
find_firstprim(&cac, (char *const **)&cav);
firstprim = cav; /* Remember first Primary type arg */
fa.Argv = cav;
fa.Argc = cac;
if (cac) {
Tree = find_parse(&fa);
if (fa.primtype == FIND_ERRARG) {
find_free(Tree, &fa);
return (fa.error);
}
if (fa.primtype != FIND_ENDARGS) {
ferrmsgno(stderr, EX_BAD,
_("Incomplete expression.\n"));
find_free(Tree, &fa);
return (EX_BAD);
}
if (find_pname(Tree, "-chown") || find_pname(Tree, "-chgrp") ||
find_pname(Tree, "-chmod")) {
ferrmsgno(stderr, EX_BAD,
_("Unsupported primary -chown/-chgrp/-chmod.\n"));
find_free(Tree, &fa);
return (EX_BAD);
}
} else {
Tree = 0;
}
if (Tree == 0) {
Tree = find_printnode();
} else if (!fa.found_action) {
Tree = find_addprint(Tree, &fa);
if (Tree == (findn_t *)NULL)
return (geterrno());
}
walkinitstate(&walkstate);
if (fa.patlen > 0) {
walkstate.patstate = __jmalloc(sizeof (int) * fa.patlen,
"space for pattern state", JM_RETURN);
if (walkstate.patstate == NULL)
return (geterrno());
}
find_timeinit(time(0));
walkstate.walkflags = fa.walkflags;
walkstate.maxdepth = fa.maxdepth;
walkstate.mindepth = fa.mindepth;
walkstate.lname = NULL;
walkstate.tree = Tree;
walkstate.err = 0;
walkstate.pflags = 0;
if (firstpath == firstprim) {
treewalk(".", walkfunc, &walkstate);
} else {
for (cav = firstpath; cav != firstprim; cav++) {
treewalk(*cav, walkfunc, &walkstate);
/*
* XXX hier break wenn treewalk() Fehler gemeldet
*/
}
}
/*
* Execute all unflushed '-exec .... {} +' expressions.
*/
find_plusflush(fa.plusp, &walkstate);
find_free(Tree, &fa);
if (walkstate.patstate != NULL)
free(walkstate.patstate);
return (walkstate.err);
}
#endif /* FIND_MAIN */