3c33abc520
A function used only in the OpenBSD-Kernel as of now, but it surely provides a helpful interface when you just don't want to make sure the incoming pointer to erealloc() is really NULL so it behaves like malloc, making it a bit more safer. Talking about *allocarray(): It's definitely a major step in code- hardening. Especially as a system administrator, you should be able to trust your core tools without having to worry about segfaults like this, which can easily lead to privilege escalation. How do the GNU coreutils handle this? $ strings -n 4611686018427387903 strings: invalid minimum string length -1 $ strings -n 4611686018427387904 strings: invalid minimum string length 0 They silently overflow... In comparison, sbase: $ strings -n 4611686018427387903 mallocarray: out of memory $ strings -n 4611686018427387904 mallocarray: out of memory The first out of memory is actually a true OOM returned by malloc, whereas the second one is a detected overflow, which is not marked in a special way. Now tell me which diagnostic error-messages are easier to understand.
1043 lines
25 KiB
C
1043 lines
25 KiB
C
/* 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 */
|
|
typedef union {
|
|
void *p;
|
|
intptr_t i;
|
|
} Extra;
|
|
|
|
/* Argument passed into a primary's function */
|
|
typedef struct {
|
|
char *path;
|
|
struct stat *st;
|
|
Extra extra;
|
|
} Arg;
|
|
|
|
/* Information about each primary, for lookup table */
|
|
typedef struct {
|
|
char *name;
|
|
int (*func)(Arg *arg);
|
|
char **(*getarg)(char **argv, Extra *extra);
|
|
void (*freearg)(Extra extra);
|
|
char narg; /* -xdev, -depth, -print don't take args but have getarg() */
|
|
} Pri_info;
|
|
|
|
/* Information about operators, for lookup table */
|
|
typedef struct {
|
|
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 */
|
|
} Op_info;
|
|
|
|
/* Token when lexing/parsing
|
|
* (although also used for the expression tree) */
|
|
typedef struct Tok Tok;
|
|
struct Tok {
|
|
Tok *left, *right; /* if (type == NOT) left = NULL */
|
|
Extra extra;
|
|
union {
|
|
Pri_info *pinfo; /* if (type == PRIM) */
|
|
Op_info *oinfo;
|
|
} u;
|
|
enum {
|
|
PRIM = 0, LPAR, RPAR, NOT, AND, OR, END
|
|
} type;
|
|
};
|
|
|
|
/* structures used for Arg.extra.p and Tok.extra.p */
|
|
typedef struct {
|
|
mode_t mode;
|
|
char exact;
|
|
} Permarg;
|
|
|
|
typedef struct {
|
|
char ***braces;
|
|
char **argv;
|
|
} Okarg;
|
|
|
|
/* for all arguments that take a number
|
|
* +n, n, -n mean > n, == n, < n respectively */
|
|
typedef struct {
|
|
int (*cmp)(int a, int b);
|
|
int n;
|
|
} Narg;
|
|
|
|
typedef struct {
|
|
Narg n;
|
|
char bytes; /* size is in bytes, not 512 byte sectors */
|
|
} Sizearg;
|
|
|
|
typedef struct {
|
|
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 ; */
|
|
} Execarg;
|
|
|
|
/* used to find loops while recursing through directory structure */
|
|
typedef struct Findhist Findhist;
|
|
struct Findhist {
|
|
Findhist *next;
|
|
char *path;
|
|
dev_t dev;
|
|
ino_t ino;
|
|
};
|
|
|
|
/* Primaries */
|
|
static int pri_name (Arg *arg);
|
|
static int pri_path (Arg *arg);
|
|
static int pri_nouser (Arg *arg);
|
|
static int pri_nogroup(Arg *arg);
|
|
static int pri_xdev (Arg *arg);
|
|
static int pri_prune (Arg *arg);
|
|
static int pri_perm (Arg *arg);
|
|
static int pri_type (Arg *arg);
|
|
static int pri_links (Arg *arg);
|
|
static int pri_user (Arg *arg);
|
|
static int pri_group (Arg *arg);
|
|
static int pri_size (Arg *arg);
|
|
static int pri_atime (Arg *arg);
|
|
static int pri_ctime (Arg *arg);
|
|
static int pri_mtime (Arg *arg);
|
|
static int pri_exec (Arg *arg);
|
|
static int pri_ok (Arg *arg);
|
|
static int pri_print (Arg *arg);
|
|
static int pri_newer (Arg *arg);
|
|
static int pri_depth (Arg *arg);
|
|
|
|
/* Getargs */
|
|
static char **get_name_arg (char *argv[], Extra *extra);
|
|
static char **get_path_arg (char *argv[], Extra *extra);
|
|
static char **get_xdev_arg (char *argv[], Extra *extra);
|
|
static char **get_perm_arg (char *argv[], Extra *extra);
|
|
static char **get_type_arg (char *argv[], Extra *extra);
|
|
static char **get_n_arg (char *argv[], Extra *extra);
|
|
static char **get_user_arg (char *argv[], Extra *extra);
|
|
static char **get_group_arg(char *argv[], Extra *extra);
|
|
static char **get_size_arg (char *argv[], Extra *extra);
|
|
static char **get_exec_arg (char *argv[], Extra *extra);
|
|
static char **get_ok_arg (char *argv[], Extra *extra);
|
|
static char **get_print_arg(char *argv[], Extra *extra);
|
|
static char **get_newer_arg(char *argv[], Extra *extra);
|
|
static char **get_depth_arg(char *argv[], Extra *extra);
|
|
|
|
/* Freeargs */
|
|
static void free_extra (Extra extra);
|
|
static void free_exec_arg(Extra extra);
|
|
static void free_ok_arg (Extra extra);
|
|
|
|
/* Parsing/Building/Running */
|
|
static void fill_narg(char *s, Narg *n);
|
|
static Pri_info *find_primary(char *name);
|
|
static Op_info *find_op(char *name);
|
|
static void parse(int argc, char **argv);
|
|
static int eval(Tok *tok, Arg *arg);
|
|
static void find(char *path, 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 Pri_info primaries[] = {
|
|
{ "-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 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 Tok *toks; /* holds allocated array of all Toks created while parsing */
|
|
static 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 */
|
|
char print; /* whether we will need -print when parsing */
|
|
} gflags;
|
|
|
|
/*
|
|
* Primaries
|
|
*/
|
|
static int
|
|
pri_name(Arg *arg)
|
|
{
|
|
return !fnmatch((char *)arg->extra.p, basename(arg->path), 0);
|
|
}
|
|
|
|
static int
|
|
pri_path(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(Arg *arg)
|
|
{
|
|
return !getpwuid(arg->st->st_uid);
|
|
}
|
|
|
|
static int
|
|
pri_nogroup(Arg *arg)
|
|
{
|
|
return !getgrgid(arg->st->st_gid);
|
|
}
|
|
|
|
static int
|
|
pri_xdev(Arg *arg)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
pri_prune(Arg *arg)
|
|
{
|
|
return gflags.prune = 1;
|
|
}
|
|
|
|
static int
|
|
pri_perm(Arg *arg)
|
|
{
|
|
Permarg *p = (Permarg *)arg->extra.p;
|
|
|
|
return (arg->st->st_mode & 07777 & (p->exact ? -1U : p->mode)) == p->mode;
|
|
}
|
|
|
|
static int
|
|
pri_type(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(Arg *arg)
|
|
{
|
|
Narg *n = arg->extra.p;
|
|
return n->cmp(arg->st->st_nlink, n->n);
|
|
}
|
|
|
|
static int
|
|
pri_user(Arg *arg)
|
|
{
|
|
return arg->st->st_uid == (uid_t)arg->extra.i;
|
|
}
|
|
|
|
static int
|
|
pri_group(Arg *arg)
|
|
{
|
|
return arg->st->st_gid == (gid_t)arg->extra.i;
|
|
}
|
|
|
|
static int
|
|
pri_size(Arg *arg)
|
|
{
|
|
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(Arg *arg)
|
|
{
|
|
Narg *n = arg->extra.p;
|
|
time_t time = (n->n - start.tv_sec) / 86400;
|
|
return n->cmp(time, n->n);
|
|
}
|
|
|
|
static int
|
|
pri_ctime(Arg *arg)
|
|
{
|
|
Narg *n = arg->extra.p;
|
|
time_t time = (n->n - start.tv_sec) / 86400;
|
|
return n->cmp(time, n->n);
|
|
}
|
|
|
|
static int
|
|
pri_mtime(Arg *arg)
|
|
{
|
|
Narg *n = arg->extra.p;
|
|
time_t time = (n->n - start.tv_sec) / 86400;
|
|
return n->cmp(time, n->n);
|
|
}
|
|
|
|
static int
|
|
pri_exec(Arg *arg)
|
|
{
|
|
int status;
|
|
size_t len;
|
|
pid_t pid;
|
|
char **sp, ***brace;
|
|
Execarg *e = arg->extra.p;
|
|
|
|
if (e->isplus) {
|
|
len = strlen(arg->path) + 1;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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(Arg *arg)
|
|
{
|
|
int status;
|
|
pid_t pid;
|
|
char ***brace, reply, buf[256];
|
|
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() */
|
|
;
|
|
|
|
if (feof(stdin)) /* FIXME: ferror()? */
|
|
clearerr(stdin);
|
|
|
|
if (reply != 'y' && reply != 'Y')
|
|
return 0;
|
|
|
|
/* 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(Arg *arg)
|
|
{
|
|
if (puts(arg->path) == EOF)
|
|
eprintf("puts failed:");
|
|
return 1;
|
|
}
|
|
|
|
/* FIXME: ignoring nanoseconds */
|
|
static int
|
|
pri_newer(Arg *arg)
|
|
{
|
|
return arg->st->st_mtime > (time_t)arg->extra.i;
|
|
}
|
|
|
|
static int
|
|
pri_depth(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[], Extra *extra)
|
|
{
|
|
extra->p = *argv;
|
|
return argv;
|
|
}
|
|
|
|
static char **
|
|
get_path_arg(char *argv[], Extra *extra)
|
|
{
|
|
extra->p = *argv;
|
|
return argv;
|
|
}
|
|
|
|
static char **
|
|
get_xdev_arg(char *argv[], Extra *extra)
|
|
{
|
|
gflags.xdev = 1;
|
|
return argv;
|
|
}
|
|
|
|
static char **
|
|
get_perm_arg(char *argv[], Extra *extra)
|
|
{
|
|
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[], 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[], Extra *extra)
|
|
{
|
|
Narg *n = extra->p = emalloc(sizeof(*n));
|
|
fill_narg(*argv, n);
|
|
return argv;
|
|
}
|
|
|
|
static char **
|
|
get_user_arg(char *argv[], 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[], 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[], Extra *extra)
|
|
{
|
|
char *p = *argv + strlen(*argv);
|
|
Sizearg *s = extra->p = emalloc(sizeof(*s));
|
|
/* if the number is followed by 'c', the size will by in bytes */
|
|
if ((s->bytes = (p > *argv && *--p == 'c')))
|
|
*p = '\0';
|
|
|
|
fill_narg(*argv, &s->n);
|
|
return argv;
|
|
}
|
|
|
|
static char **
|
|
get_exec_arg(char *argv[], Extra *extra)
|
|
{
|
|
char **arg, **new, ***braces;
|
|
int nbraces = 0;
|
|
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 = emallocarray(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 = emallocarray(++nbraces, sizeof(*e->u.s.braces)); /* ++ for NULL */
|
|
|
|
for (arg = argv, braces = e->u.s.braces; *arg; arg++)
|
|
if (!strcmp(*arg, "{}"))
|
|
*braces++ = arg;
|
|
}
|
|
gflags.print = 0;
|
|
return arg;
|
|
}
|
|
|
|
static char **
|
|
get_ok_arg(char *argv[], Extra *extra)
|
|
{
|
|
char **arg, ***braces;
|
|
int nbraces = 0;
|
|
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 = emallocarray(++nbraces, sizeof(*o->braces));
|
|
|
|
for (arg = argv, braces = o->braces; *arg; arg++)
|
|
if (!strcmp(*arg, "{}"))
|
|
*braces++ = arg;
|
|
|
|
gflags.print = 0;
|
|
return arg;
|
|
}
|
|
|
|
static char **
|
|
get_print_arg(char *argv[], Extra *extra)
|
|
{
|
|
gflags.print = 0;
|
|
return argv;
|
|
}
|
|
|
|
/* FIXME: ignoring nanoseconds */
|
|
static char **
|
|
get_newer_arg(char *argv[], Extra *extra)
|
|
{
|
|
struct stat st;
|
|
|
|
if (stat(*argv, &st))
|
|
eprintf("failed to stat '%s':", *argv);
|
|
|
|
extra->i = st.st_mtime;
|
|
return argv;
|
|
}
|
|
|
|
static char **
|
|
get_depth_arg(char *argv[], Extra *extra)
|
|
{
|
|
gflags.depth = 1;
|
|
return argv;
|
|
}
|
|
|
|
/*
|
|
* Freeargs
|
|
*/
|
|
static void
|
|
free_extra(Extra extra)
|
|
{
|
|
free(extra.p);
|
|
}
|
|
|
|
static void
|
|
free_exec_arg(Extra extra)
|
|
{
|
|
int status;
|
|
pid_t pid;
|
|
char **arg;
|
|
Execarg *e = extra.p;
|
|
|
|
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(Extra extra)
|
|
{
|
|
Okarg *o = extra.p;
|
|
|
|
free(o->braces);
|
|
free(o);
|
|
}
|
|
|
|
/*
|
|
* Parsing/Building/Running
|
|
*/
|
|
static void
|
|
fill_narg(char *s, Narg *n)
|
|
{
|
|
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 Pri_info *
|
|
find_primary(char *name)
|
|
{
|
|
Pri_info *p;
|
|
|
|
for (p = primaries; p->name; p++)
|
|
if (!strcmp(name, p->name))
|
|
return p;
|
|
return NULL;
|
|
}
|
|
|
|
static Op_info *
|
|
find_op(char *name)
|
|
{
|
|
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)
|
|
{
|
|
Tok infix[2 * argc + 1], *stack[argc], *tok, *rpn, *out, **top;
|
|
Op_info *op;
|
|
Pri_info *pri;
|
|
char **arg;
|
|
int lasttype = -1;
|
|
size_t ntok = 0;
|
|
Tok and = { .u.oinfo = find_op("-a"), .type = AND };
|
|
|
|
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) {
|
|
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 */
|
|
*tok++ = and;
|
|
ntok++;
|
|
}
|
|
tok->type = op->type;
|
|
tok->u.oinfo = op;
|
|
} else { /* token is neither primary nor operator, must be path in the wrong place */
|
|
eprintf("paths must precede expression: %s\n", *arg);
|
|
}
|
|
if (tok->type != LPAR && tok->type != RPAR)
|
|
ntok++; /* won't have parens in rpn */
|
|
lasttype = tok->type;
|
|
}
|
|
tok->type = END;
|
|
ntok++;
|
|
|
|
if (gflags.print && (arg != argv)) /* need to add -a -print (not just -print) */
|
|
gflags.print++;
|
|
|
|
/* use shunting-yard to convert from infix to rpn
|
|
* https://en.wikipedia.org/wiki/Shunting-yard_algorithm
|
|
* read from infix, resulting rpn ends up in rpn, next position in rpn is out
|
|
* push operators onto stack, next position in stack is top */
|
|
rpn = emallocarray(ntok + gflags.print, sizeof(*rpn));
|
|
for (tok = infix, out = rpn, top = stack; tok->type != END; tok++) {
|
|
switch (tok->type) {
|
|
case PRIM: *out++ = *tok; break;
|
|
case LPAR: *top++ = tok; break;
|
|
case RPAR:
|
|
while (top-- > stack && (*top)->type != LPAR)
|
|
*out++ = **top;
|
|
if (top < stack)
|
|
eprintf("extra )\n");
|
|
break;
|
|
default:
|
|
/* this expression can be simplified, but I decided copy the
|
|
* verbage from the wikipedia page in order to more clearly explain
|
|
* what's going on */
|
|
while (top-- > stack &&
|
|
(( tok->u.oinfo->lassoc && tok->u.oinfo->prec <= (*top)->u.oinfo->prec) ||
|
|
(!tok->u.oinfo->lassoc && tok->u.oinfo->prec < (*top)->u.oinfo->prec)))
|
|
*out++ = **top;
|
|
|
|
/* top now points to either an operator we didn't pop, or stack[-1]
|
|
* either way we need to increment it before using it, then
|
|
* increment again so the stack works */
|
|
top++;
|
|
*top++ = tok;
|
|
break;
|
|
}
|
|
}
|
|
while (top-- > stack) {
|
|
if ((*top)->type == LPAR)
|
|
eprintf("extra (\n");
|
|
*out++ = **top;
|
|
}
|
|
|
|
/* if there was no expression, use -print
|
|
* if there was an expression but no -print, -exec, or -ok, add -a -print
|
|
* in rpn, not infix */
|
|
if (gflags.print)
|
|
*out++ = (Tok){ .u.pinfo = find_primary("-print"), .type = PRIM };
|
|
if (gflags.print == 2)
|
|
*out++ = and;
|
|
|
|
out->type = END;
|
|
|
|
/* rpn now holds all operators and arguments in reverse polish notation
|
|
* values are pushed onto stack, operators pop values off stack into left
|
|
* and right pointers, pushing operator node back onto stack
|
|
* could probably just do this during shunting-yard, but this is simpler
|
|
* code IMO */
|
|
for (tok = rpn, top = stack; tok->type != END; tok++) {
|
|
if (tok->type == PRIM) {
|
|
*top++ = tok;
|
|
} else {
|
|
if (top - stack < tok->u.oinfo->nargs)
|
|
eprintf("insufficient arguments for operator %s\n", tok->u.oinfo->name);
|
|
tok->right = *--top;
|
|
tok->left = tok->u.oinfo->nargs == 2 ? *--top : NULL;
|
|
*top++ = tok;
|
|
}
|
|
}
|
|
if (--top != stack)
|
|
eprintf("extra arguments\n");
|
|
|
|
toks = rpn;
|
|
root = *top;
|
|
}
|
|
|
|
/* for a primary, run and return result
|
|
* for an operator evaluate the left side of the tree, decide whether or not to
|
|
* evaluate the right based on the short-circuit boolean logic, return result
|
|
* NOTE: operator NOT has NULL left side, expression on right side
|
|
*/
|
|
static int
|
|
eval(Tok *tok, Arg *arg)
|
|
{
|
|
int ret;
|
|
|
|
if (!tok)
|
|
return 0;
|
|
|
|
if (tok->type == PRIM) {
|
|
arg->extra = tok->extra;
|
|
return tok->u.pinfo->func(arg);
|
|
}
|
|
|
|
ret = eval(tok->left, arg);
|
|
|
|
if ((tok->type == AND && ret) || (tok->type == OR && !ret) || tok->type == NOT)
|
|
ret = eval(tok->right, arg);
|
|
|
|
return ret ^ (tok->type == NOT);
|
|
}
|
|
|
|
/* evaluate path, if it's a directory iterate through directory entries and
|
|
* recurse
|
|
*/
|
|
static void
|
|
find(char *path, Findhist *hist)
|
|
{
|
|
struct stat st;
|
|
DIR *dir;
|
|
struct dirent *de;
|
|
Findhist *f, cur;
|
|
size_t len = strlen(path) + 2; /* null and '/' */
|
|
Arg arg = { path, &st, { NULL } };
|
|
|
|
if ((gflags.l || (gflags.h && !hist) ? stat(path, &st) : lstat(path, &st)) < 0) {
|
|
weprintf("failed to stat %s:", path);
|
|
return;
|
|
}
|
|
|
|
gflags.prune = 0;
|
|
|
|
/* don't eval now iff we will hit the eval at the bottom which means
|
|
* 1. we are a directory 2. we have -depth 3. we don't have -xdev or we are
|
|
* on same device (so most of the time we eval here) */
|
|
if (!S_ISDIR(st.st_mode) ||
|
|
!gflags.depth ||
|
|
(gflags.xdev && hist && st.st_dev != hist->dev))
|
|
eval(root, &arg);
|
|
|
|
if (!S_ISDIR(st.st_mode) ||
|
|
gflags.prune ||
|
|
(gflags.xdev && hist && st.st_dev != hist->dev))
|
|
return;
|
|
|
|
for (f = hist; f; f = f->next) {
|
|
if (f->dev == st.st_dev && f->ino == st.st_ino) {
|
|
weprintf("loop detected '%s' is '%s'\n", path, f->path);
|
|
return;
|
|
}
|
|
}
|
|
cur.next = hist;
|
|
cur.path = path;
|
|
cur.dev = st.st_dev;
|
|
cur.ino = st.st_ino;
|
|
|
|
if (!(dir = opendir(path))) {
|
|
weprintf("failed to opendir %s:", path);
|
|
/* should we just ignore this since we hit an error? */
|
|
if (gflags.depth)
|
|
eval(root, &arg);
|
|
return;
|
|
}
|
|
|
|
/* FIXME: check errno to see if we are done or encountered an error? */
|
|
while ((de = readdir(dir))) {
|
|
size_t pathcap = len + strlen(de->d_name);
|
|
char pathbuf[pathcap], *p;
|
|
|
|
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
|
|
continue;
|
|
|
|
p = pathbuf + strlcpy(pathbuf, path, pathcap);
|
|
if (*--p != '/')
|
|
strlcat(pathbuf, "/", pathcap);
|
|
strlcat(pathbuf, de->d_name, pathcap);
|
|
find(pathbuf, &cur);
|
|
}
|
|
closedir(dir); /* check return value? */
|
|
|
|
if (gflags.depth)
|
|
eval(root, &arg);
|
|
}
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
eprintf("usage: %s [-H|-L] path... [expression...]\n", argv0);
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
char **paths;
|
|
int npaths;
|
|
Tok *t;
|
|
|
|
ARGBEGIN {
|
|
case 'H': gflags.l = !(gflags.h = 1); break;
|
|
case 'L': gflags.h = !(gflags.l = 1); break;
|
|
default : usage();
|
|
} ARGEND;
|
|
|
|
paths = argv;
|
|
|
|
for (; *argv && **argv != '-' && strcmp(*argv, "!") && strcmp(*argv, "("); argv++)
|
|
;
|
|
|
|
if (!(npaths = argv - paths))
|
|
eprintf("must specify a path\n");
|
|
|
|
parse(argc - npaths, argv);
|
|
|
|
/* calculate number of bytes in environ for -exec {} + ARG_MAX avoidance
|
|
* libc implementation defined whether null bytes, pointers, and alignment
|
|
* are counted, so count them */
|
|
for (argv = environ; *argv; argv++)
|
|
envlen += strlen(*argv) + 1 + sizeof(*argv);
|
|
|
|
if ((argmax = sysconf(_SC_ARG_MAX)) == (size_t)-1)
|
|
argmax = _POSIX_ARG_MAX;
|
|
|
|
if (clock_gettime(CLOCK_REALTIME, &start) < 0)
|
|
weprintf("clock_gettime() failed:");
|
|
|
|
while (npaths--)
|
|
find(*paths, NULL);
|
|
|
|
for (t = toks; t->type != END; t++)
|
|
if (t->type == PRIM && t->u.pinfo->freearg)
|
|
t->u.pinfo->freearg(t->extra);
|
|
free(toks);
|
|
|
|
return gflags.ret;
|
|
}
|