sbase/find.c

1041 lines
26 KiB
C
Raw Normal View History

/* See LICENSE file for copyright and license details. */
#include <dirent.h>
#include <fnmatch.h>
#include <grp.h>
#include <libgen.h>
#include <pwd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "util.h"
/* because putting integers in pointers is undefined by the standard */
union extra {
void *p;
intptr_t i;
};
/* Argument passed into a primary's function */
struct arg {
char *path;
struct stat *st;
union extra extra;
};
/* Information about each primary, for lookup table */
struct pri_info {
char *name;
int (*func)(struct arg *arg);
char **(*getarg)(char **argv, union extra *extra);
void (*freearg)(union extra extra);
2015-02-24 22:34:36 -05:00
char narg; /* -xdev, -depth, -print don't take args but have getarg() */
};
/* Information about operators, for lookup table */
struct op_info {
char *name; /* string representation of op */
char type; /* from tok.type */
char prec; /* precedence */
char nargs; /* number of arguments (unary or binary) */
char lassoc; /* left associative */
};
/* Token when lexing/parsing
* (although also used for the expression tree) */
struct tok {
struct tok *left, *right; /* if (type == NOT) left = NULL */
union extra extra;
union {
struct pri_info *pinfo; /* if (type == PRIM) */
struct op_info *oinfo;
} u;
enum {
PRIM = 0, LPAR, RPAR, NOT, AND, OR, END
} type;
};
/* structures used for arg.extra.p and tok.extra.p */
struct permarg {
mode_t mode;
char exact;
};
struct okarg {
char ***braces;
char **argv;
};
/* for all arguments that take a number
* +n, n, -n mean > n, == n, < n respectively */
struct narg {
int (*cmp)(int a, int b);
int n;
};
struct sizearg {
struct narg n;
char bytes; /* size is in bytes, not 512 byte sectors */
};
struct execarg {
union {
struct {
char ***braces; /* NULL terminated list of pointers into argv where {} were */
} s; /* semicolon */
struct {
size_t arglen; /* number of bytes in argv before files are added */
size_t filelen; /* numer of bytes in file names added to argv */
size_t first; /* index one past last arg, where first file goes */
size_t next; /* index where next file goes */
size_t cap; /* capacity of argv */
} p; /* plus */
} u;
char **argv; /* NULL terminated list of arguments (allocated if isplus) */
char isplus; /* -exec + instead of -exec ; */
};
/* used to find loops while recursing through directory structure */
struct findhist {
struct findhist *next;
char *path;
dev_t dev;
ino_t ino;
};
/* Primaries */
static int pri_name (struct arg *arg);
static int pri_path (struct arg *arg);
static int pri_nouser (struct arg *arg);
static int pri_nogroup(struct arg *arg);
static int pri_xdev (struct arg *arg);
static int pri_prune (struct arg *arg);
static int pri_perm (struct arg *arg);
static int pri_type (struct arg *arg);
static int pri_links (struct arg *arg);
static int pri_user (struct arg *arg);
static int pri_group (struct arg *arg);
static int pri_size (struct arg *arg);
static int pri_atime (struct arg *arg);
static int pri_ctime (struct arg *arg);
static int pri_mtime (struct arg *arg);
static int pri_exec (struct arg *arg);
static int pri_ok (struct arg *arg);
static int pri_print (struct arg *arg);
static int pri_newer (struct arg *arg);
static int pri_depth (struct arg *arg);
/* Getargs */
static char **get_name_arg (char *argv[], union extra *extra);
static char **get_path_arg (char *argv[], union extra *extra);
static char **get_xdev_arg (char *argv[], union extra *extra);
static char **get_perm_arg (char *argv[], union extra *extra);
static char **get_type_arg (char *argv[], union extra *extra);
static char **get_n_arg (char *argv[], union extra *extra);
static char **get_user_arg (char *argv[], union extra *extra);
static char **get_group_arg(char *argv[], union extra *extra);
static char **get_size_arg (char *argv[], union extra *extra);
static char **get_exec_arg (char *argv[], union extra *extra);
static char **get_ok_arg (char *argv[], union extra *extra);
static char **get_print_arg(char *argv[], union extra *extra);
static char **get_newer_arg(char *argv[], union extra *extra);
static char **get_depth_arg(char *argv[], union extra *extra);
/* Freeargs */
static void free_extra (union extra extra);
static void free_exec_arg(union extra extra);
static void free_ok_arg (union extra extra);
/* Parsing/Building/Running */
static void fill_narg(char *s, struct narg *n);
static struct pri_info *find_primary(char *name);
static struct op_info *find_op(char *name);
static void parse(int argc, char **argv);
static int eval(struct tok *tok, struct arg *arg);
static void find(char *path, struct findhist *hist);
static void usage(void);
/* for comparisons with narg */
static int cmp_gt(int a, int b) { return a > b; }
static int cmp_eq(int a, int b) { return a == b; }
static int cmp_lt(int a, int b) { return a < b; }
/* order from find(1p), may want to alphabetize */
static struct pri_info primaries[] = {
2015-02-24 22:34:36 -05:00
{ "-name" , pri_name , get_name_arg , NULL , 1 },
{ "-path" , pri_path , get_path_arg , NULL , 1 },
{ "-nouser" , pri_nouser , NULL , NULL , 1 },
{ "-nogroup", pri_nogroup, NULL , NULL , 1 },
{ "-xdev" , pri_xdev , get_xdev_arg , NULL , 0 },
{ "-prune" , pri_prune , NULL , NULL , 1 },
{ "-perm" , pri_perm , get_perm_arg , free_extra , 1 },
{ "-type" , pri_type , get_type_arg , NULL , 1 },
{ "-links" , pri_links , get_n_arg , free_extra , 1 },
{ "-user" , pri_user , get_user_arg , NULL , 1 },
{ "-group" , pri_group , get_group_arg, NULL , 1 },
{ "-size" , pri_size , get_size_arg , free_extra , 1 },
{ "-atime" , pri_atime , get_n_arg , free_extra , 1 },
{ "-ctime" , pri_ctime , get_n_arg , free_extra , 1 },
{ "-mtime" , pri_mtime , get_n_arg , free_extra , 1 },
{ "-exec" , pri_exec , get_exec_arg , free_exec_arg, 1 },
{ "-ok" , pri_ok , get_ok_arg , free_ok_arg , 1 },
{ "-print" , pri_print , get_print_arg, NULL , 0 },
{ "-newer" , pri_newer , get_newer_arg, NULL , 1 },
{ "-depth" , pri_depth , get_depth_arg, NULL , 0 },
{ NULL, NULL, NULL, NULL, 0 }
};
static struct op_info ops[] = {
{ "(" , LPAR, 0, 0, 0 }, /* parens are handled specially */
{ ")" , RPAR, 0, 0, 0 },
{ "!" , NOT , 3, 1, 0 },
{ "-a", AND , 2, 2, 1 },
{ "-o", OR , 1, 2, 1 },
{ NULL, 0, 0, 0, 0 }
};
extern char **environ;
static struct tok *toks; /* holds allocated array of all toks created while parsing */
static struct tok *root; /* points to root of expression tree, inside toks array */
static struct timespec start; /* time find was started, used for -[acm]time */
static size_t envlen; /* number of bytes in environ, used to calculate against ARG_MAX */
static size_t argmax; /* value of ARG_MAX retrieved using sysconf(3p) */
static struct {
char ret ; /* return value from main */
char depth; /* -depth, directory contents before directory itself */
char h ; /* -H, follow symlinks on command line */
char l ; /* -L, follow all symlinks (command line and search) */
char prune; /* hit -prune */
char xdev ; /* -xdev, prune directories on different devices */
2015-02-24 22:34:36 -05:00
char print; /* whether we will need -print when parsing */
} gflags;
/*
* Primaries
*/
static int
pri_name(struct arg *arg)
{
return !fnmatch((char *)arg->extra.p, basename(arg->path), 0);
}
static int
pri_path(struct arg *arg)
{
return !fnmatch((char *)arg->extra.p, arg->path, 0);
}
/* FIXME: what about errors? find(1p) literally just says
* "for which the getpwuid() function ... returns NULL" */
static int
pri_nouser(struct arg *arg)
{
return !getpwuid(arg->st->st_uid);
}
static int
pri_nogroup(struct arg *arg)
{
return !getgrgid(arg->st->st_gid);
}
static int
pri_xdev(struct arg *arg)
{
return 1;
}
static int
pri_prune(struct arg *arg)
{
return gflags.prune = 1;
}
static int
pri_perm(struct arg *arg)
{
struct permarg *p = (struct permarg *)arg->extra.p;
return (arg->st->st_mode & 07777 & (p->exact ? -1U : p->mode)) == p->mode;
}
static int
pri_type(struct arg *arg)
{
switch ((char)arg->extra.i) {
default : return 0; /* impossible, but placate warnings */
case 'b': return S_ISBLK (arg->st->st_mode);
case 'c': return S_ISCHR (arg->st->st_mode);
case 'd': return S_ISDIR (arg->st->st_mode);
case 'l': return S_ISLNK (arg->st->st_mode);
case 'p': return S_ISFIFO(arg->st->st_mode);
case 'f': return S_ISREG (arg->st->st_mode);
case 's': return S_ISSOCK(arg->st->st_mode);
}
}
static int
pri_links(struct arg *arg)
{
struct narg *n = arg->extra.p;
return n->cmp(arg->st->st_nlink, n->n);
}
static int
pri_user(struct arg *arg)
{
return arg->st->st_uid == (uid_t)arg->extra.i;
}
static int
pri_group(struct arg *arg)
{
return arg->st->st_gid == (gid_t)arg->extra.i;
}
static int
pri_size(struct arg *arg)
{
struct sizearg *s = arg->extra.p;
off_t size = arg->st->st_size;
if (!s->bytes)
size = size / 512 + !!(size % 512);
return s->n.cmp(size, s->n.n);
}
/* FIXME: ignoring nanoseconds in atime, ctime, mtime */
static int
pri_atime(struct arg *arg)
{
struct narg *n = arg->extra.p;
time_t time = (n->n - start.tv_sec) / 86400;
return n->cmp(time, n->n);
}
static int
pri_ctime(struct arg *arg)
{
struct narg *n = arg->extra.p;
time_t time = (n->n - start.tv_sec) / 86400;
return n->cmp(time, n->n);
}
static int
pri_mtime(struct arg *arg)
{
struct narg *n = arg->extra.p;
time_t time = (n->n - start.tv_sec) / 86400;
return n->cmp(time, n->n);
}
static int
pri_exec(struct arg *arg)
{
2015-02-24 22:34:36 -05:00
int status;
size_t len;
2015-02-24 22:34:36 -05:00
pid_t pid;
char **sp, ***brace;
struct execarg *e = arg->extra.p;
if (e->isplus) {
len = strlen(arg->path) + 1;
2015-02-24 22:34:36 -05:00
/* if we reached ARG_MAX, fork, exec, wait, free file names, reset list */
if (len + e->u.p.arglen + e->u.p.filelen + envlen > argmax) {
e->argv[e->u.p.next] = NULL;
switch((pid = fork())) {
case -1:
eprintf("fork:");
case 0:
execvp(*e->argv, e->argv);
weprintf("exec %s failed:", *e->argv);
_exit(1);
}
waitpid(pid, &status, 0);
gflags.ret = gflags.ret || status;
for (sp = e->argv + e->u.p.first; *sp; sp++)
free(*sp);
e->u.p.next = e->u.p.first;
e->u.p.filelen = 0;
}
2015-02-24 22:34:36 -05:00
/* if we have too many files, realloc (with space for NULL termination) */
if (e->u.p.next + 1 == e->u.p.cap)
e->argv = ereallocarray(e->argv, e->u.p.cap *= 2, sizeof(*e->argv));
e->argv[e->u.p.next++] = estrdup(arg->path);
e->u.p.filelen += len + sizeof(arg->path);
return 1;
} else {
/* insert path everywhere user gave us {} */
for (brace = e->u.s.braces; *brace; brace++)
**brace = arg->path;
switch((pid = fork())) {
case -1:
eprintf("fork:");
case 0:
execvp(*e->argv, e->argv);
weprintf("exec %s failed:", *e->argv);
_exit(1);
}
/* FIXME: propper course of action for all waitpid() on EINTR? */
waitpid(pid, &status, 0);
return !!status;
}
}
static int
pri_ok(struct arg *arg)
{
int status;
pid_t pid;
char ***brace, reply, buf[256];
struct okarg *o = arg->extra.p;
fprintf(stderr, "%s: %s ?", *o->argv, arg->path);
reply = fgetc(stdin);
/* throw away rest of line */
while (fgets(buf, sizeof(buf), stdin) && *buf && buf[strlen(buf) - 1] == '\n')
/* FIXME: what if the first character of the rest of the line is a null
* byte? probably shouldn't juse fgets() */
;
2015-02-24 22:34:36 -05:00
if (feof(stdin)) /* FIXME: ferror()? */
clearerr(stdin);
if (reply != 'y' && reply != 'Y')
return 0;
2015-02-24 22:34:36 -05:00
/* insert filename everywhere user gave us {} */
for (brace = o->braces; *brace; brace++)
**brace = arg->path;
switch((pid = fork())) {
case -1:
eprintf("fork:");
case 0:
execvp(*o->argv, o->argv);
weprintf("exec %s failed:", *o->argv);
_exit(1);
}
waitpid(pid, &status, 0);
return !!status;
}
static int
pri_print(struct arg *arg)
{
2015-02-24 22:34:36 -05:00
if (puts(arg->path) == EOF)
eprintf("puts failed:");
return 1;
}
/* FIXME: ignoring nanoseconds */
static int
pri_newer(struct arg *arg)
{
return arg->st->st_mtime > (time_t)arg->extra.i;
}
static int
pri_depth(struct arg *arg)
{
return 1;
}
/*
* Getargs
* consume any arguments for given primary and fill extra
* return pointer to last argument, the pointer will be incremented in parse()
*/
static char **
get_name_arg(char *argv[], union extra *extra)
{
extra->p = *argv;
return argv;
}
static char **
get_path_arg(char *argv[], union extra *extra)
{
extra->p = *argv;
return argv;
}
2015-02-24 22:34:36 -05:00
static char **
get_xdev_arg(char *argv[], union extra *extra)
2015-02-24 22:34:36 -05:00
{
gflags.xdev = 1;
return argv;
}
static char **
get_perm_arg(char *argv[], union extra *extra)
{
struct permarg *p = extra->p = emalloc(sizeof(*p));
if (**argv == '-')
(*argv)++;
else
p->exact = 1;
p->mode = parsemode(*argv, 0, 0);
return argv;
}
static char **
get_type_arg(char *argv[], union extra *extra)
{
if (!strchr("bcdlpfs", **argv))
eprintf("invalid type %c for -type primary\n", **argv);
extra->i = **argv;
return argv;
}
static char **
get_n_arg(char *argv[], union extra *extra)
{
struct narg *n = extra->p = emalloc(sizeof(*n));
2015-02-24 22:34:36 -05:00
fill_narg(*argv, n);
return argv;
}
static char **
get_user_arg(char *argv[], union extra *extra)
{
char *end;
struct passwd *p = getpwnam(*argv);
if (p) {
extra->i = p->pw_uid;
} else {
extra->i = strtol(*argv, &end, 10);
if (end == *argv || *end)
eprintf("unknown user '%s'\n", *argv);
}
return argv;
}
static char **
get_group_arg(char *argv[], union extra *extra)
{
char *end;
struct group *g = getgrnam(*argv);
if (g) {
extra->i = g->gr_gid;
} else {
extra->i = strtol(*argv, &end, 10);
if (end == *argv || *end)
eprintf("unknown group '%s'\n", *argv);
}
return argv;
}
static char **
get_size_arg(char *argv[], union extra *extra)
{
char *p = *argv + strlen(*argv);
struct sizearg *s = extra->p = emalloc(sizeof(*s));
/* if the number is followed by 'c', the size will by in bytes */
2015-02-24 22:34:36 -05:00
if ((s->bytes = (p > *argv && *--p == 'c')))
*p = '\0';
2015-02-24 22:34:36 -05:00
fill_narg(*argv, &s->n);
return argv;
}
static char **
get_exec_arg(char *argv[], union extra *extra)
{
char **arg, **new, ***braces;
int nbraces = 0;
struct execarg *e = extra->p = emalloc(sizeof(*e));
for (arg = argv; *arg; arg++)
if (!strcmp(*arg, ";"))
break;
else if (arg > argv && !strcmp(*(arg - 1), "{}") && !strcmp(*arg, "+"))
break;
else if (!strcmp(*arg, "{}"))
nbraces++;
if (!*arg)
eprintf("no terminating ; or {} + for -exec primary\n");
e->isplus = **arg == '+';
*arg = NULL;
if (e->isplus) {
*(arg - 1) = NULL; /* don't need the {} in there now */
e->u.p.arglen = e->u.p.filelen = 0;
e->u.p.first = e->u.p.next = arg - argv - 1;
e->u.p.cap = (arg - argv) * 2;
e->argv = ereallocarray(NULL, e->u.p.cap, sizeof(*e->argv));
for (arg = argv, new = e->argv; *arg; arg++, new++) {
*new = *arg;
e->u.p.arglen += strlen(*arg) + 1 + sizeof(*arg);
}
arg++; /* due to our extra NULL */
} else {
e->argv = argv;
e->u.s.braces = ereallocarray(NULL, ++nbraces, sizeof(*e->u.s.braces)); /* ++ for NULL */
for (arg = argv, braces = e->u.s.braces; *arg; arg++)
if (!strcmp(*arg, "{}"))
*braces++ = arg;
}
2015-02-24 22:34:36 -05:00
gflags.print = 0;
return arg;
}
static char **
get_ok_arg(char *argv[], union extra *extra)
{
char **arg, ***braces;
int nbraces = 0;
struct okarg *o = extra->p = emalloc(sizeof(*o));
for (arg = argv; *arg; arg++)
if (!strcmp(*arg, ";"))
break;
else if (!strcmp(*arg, "{}"))
nbraces++;
if (!*arg)
eprintf("no terminating ; for -ok primary\n");
*arg = NULL;
o->argv = argv;
o->braces = ereallocarray(NULL, ++nbraces, sizeof(*o->braces));
for (arg = argv, braces = o->braces; *arg; arg++)
if (!strcmp(*arg, "{}"))
*braces++ = arg;
2015-02-24 22:34:36 -05:00
gflags.print = 0;
return arg;
}
2015-02-24 22:34:36 -05:00
static char **
get_print_arg(char *argv[], union extra *extra)
2015-02-24 22:34:36 -05:00
{
gflags.print = 0;
return argv;
}
/* FIXME: ignoring nanoseconds */
static char **
get_newer_arg(char *argv[], union extra *extra)
{
struct stat st;
if (stat(*argv, &st))
eprintf("failed to stat '%s':", *argv);
extra->i = st.st_mtime;
return argv;
}
2015-02-24 22:34:36 -05:00
static char **
get_depth_arg(char *argv[], union extra *extra)
2015-02-24 22:34:36 -05:00
{
gflags.depth = 1;
return argv;
}
/*
* Freeargs
*/
static void
free_extra(union extra extra)
{
free(extra.p);
}
static void
free_exec_arg(union extra extra)
{
int status;
pid_t pid;
char **arg;
struct execarg *e = extra.p;
2015-02-24 22:34:36 -05:00
if (!e->isplus) {
free(e->u.s.braces);
} else {
e->argv[e->u.p.next] = NULL;
/* if we have files, do the last exec */
if (e->u.p.first != e->u.p.next) {
switch((pid = fork())) {
case -1:
eprintf("fork:");
case 0:
execvp(*e->argv, e->argv);
weprintf("exec %s failed:", *e->argv);
_exit(1);
}
waitpid(pid, &status, 0);
gflags.ret = gflags.ret || status;
}
for (arg = e->argv + e->u.p.first; *arg; arg++)
free(*arg);
free(e->argv);
}
free(e);
}
static void
free_ok_arg(union extra extra)
{
struct okarg *o = extra.p;
2015-02-24 22:34:36 -05:00
free(o->braces);
free(o);
}
/*
* Parsing/Building/Running
*/
2015-02-24 22:34:36 -05:00
static void
fill_narg(char *s, struct narg *n)
2015-02-24 22:34:36 -05:00
{
char *end;
switch (*s) {
case '+': n->cmp = cmp_gt; s++; break;
case '-': n->cmp = cmp_lt; s++; break;
default : n->cmp = cmp_eq; break;
}
n->n = strtol(s, &end, 10);
if (end == s || *end)
eprintf("bad number '%s'\n", s);
}
static struct pri_info *
find_primary(char *name)
{
struct pri_info *p;
for (p = primaries; p->name; p++)
if (!strcmp(name, p->name))
return p;
return NULL;
}
static struct op_info *
find_op(char *name)
{
struct op_info *o;
for (o = ops; o->name; o++)
if (!strcmp(name, o->name))
return o;
return NULL;
}
/* given the expression from the command line
* 1) convert arguments from strings to tok and place in an array duplicating
* the infix expression given, inserting "-a" where it was omitted
* 2) allocate an array to hold the correct number of tok, and convert from
* infix to rpn (using shunting-yard), add -a and -print if necessary
* 3) evaluate the rpn filling in left and right pointers to create an
* expression tree (tok are still all contained in the rpn array, just
* pointing at eachother)
*/
static void
parse(int argc, char **argv)
{
struct tok infix[2 * argc + 1], *stack[argc], *tok, *rpn, *out, **top;
struct op_info *op;
struct pri_info *pri;
char **arg;
int lasttype = -1;
size_t ntok = 0;
struct tok and = { .u.oinfo = find_op("-a"), .type = AND };
2015-02-24 22:34:36 -05:00
gflags.print = 1;
/* convert argv to infix expression of tok, inserting in *tok */
for (arg = argv, tok = infix; *arg; arg++, tok++) {
pri = find_primary(*arg);
if (pri) { /* token is a primary, fill out tok and get arguments */
if (lasttype == PRIM || lasttype == RPAR) {
*tok++ = and;
ntok++;
}
if (pri->getarg) {
2015-02-24 22:34:36 -05:00
if (pri->narg && !*++arg)
eprintf("no argument for primary %s\n", pri->name);
arg = pri->getarg(arg, &tok->extra);
}
tok->u.pinfo = pri;
tok->type = PRIM;
} else if ((op = find_op(*arg))) { /* token is an operator */
if (lasttype == LPAR && op->type == RPAR)
eprintf("empty parens\n");
if (lasttype == PRIM && op->type == NOT) { /* need another implicit -a */
</