master xplshn/aruu / cmd / posix / find.c
   1/* See LICENSE file for copyright and license details. */
   2
   3
   4#include "config.h"
   5#include "util.h"
   6
   7#include <dirent.h>
   8#include <errno.h>
   9#include <fnmatch.h>
  10#include <grp.h>
  11#include <libgen.h>
  12#include <pwd.h>
  13#include <regex.h>
  14#include <stdint.h>
  15#include <stdio.h>
  16#include <stdlib.h>
  17#include <string.h>
  18#include <sys/stat.h>
  19#include <sys/wait.h>
  20#include <time.h>
  21#include <unistd.h>
  22
  23/* because putting integers in pointers is undefined by the standard */
  24union extra {
  25	void    *p;
  26	intmax_t i;
  27};
  28
  29/* Argument passed into a primary's function */
  30struct arg {
  31	char        *path;
  32	struct stat *st;
  33	union extra  extra;
  34};
  35
  36/* Information about each primary, for lookup table */
  37struct pri_info {
  38	char  *name;
  39	int    (*func)(struct arg *arg);
  40	char **(*getarg)(char **argv, union extra *extra);
  41	void   (*freearg)(union extra extra);
  42	char   narg; /* -xdev, -depth, -print don't take args but have getarg() */
  43};
  44
  45/* Information about operators, for lookup table */
  46struct op_info {
  47	char *name;   /* string representation of op           */
  48	char  type;   /* from tok.type                         */
  49	char  prec;   /* precedence                            */
  50	char  nargs;  /* number of arguments (unary or binary) */
  51	char  lassoc; /* left associative                      */
  52};
  53
  54/* Token when lexing/parsing
  55 * (although also used for the expression tree) */
  56struct tok {
  57	struct tok *left, *right; /* if (type == NOT) left = NULL */
  58	union extra extra;
  59	union {
  60		struct pri_info *pinfo; /* if (type == PRIM) */
  61		struct op_info  *oinfo;
  62	} u;
  63	enum {
  64		PRIM = 0, LPAR, RPAR, NOT, AND, OR, END
  65	} type;
  66};
  67
  68/* structures used for arg.extra.p and tok.extra.p */
  69struct permarg {
  70	mode_t mode;
  71	char   exact;
  72};
  73
  74struct okarg {
  75	char ***braces;
  76	char **argv;
  77};
  78
  79/* for all arguments that take a number
  80 * +n, n, -n mean > n, == n, < n respectively */
  81struct narg {
  82	int (*cmp)(int a, int b);
  83	int n;
  84};
  85
  86struct sizearg {
  87	struct narg n;
  88	char bytes; /* size is in bytes, not 512 byte sectors */
  89};
  90
  91struct execarg {
  92	union {
  93		struct {
  94			char ***braces; /* NULL terminated list of pointers into argv where {} were */
  95		} s; /* semicolon */
  96		struct {
  97			size_t arglen;  /* number of bytes in argv before files are added */
  98			size_t filelen; /* numer of bytes in file names added to argv     */
  99			size_t first;   /* index one past last arg, where first file goes */
 100			size_t next;    /* index where next file goes                     */
 101			size_t cap;     /* capacity of argv                               */
 102		} p; /* plus */
 103	} u;
 104	char **argv; /* NULL terminated list of arguments (allocated if isplus) */
 105	char   isplus; /* -exec + instead of -exec ; */
 106};
 107
 108/* used to find loops while recursing through directory structure */
 109struct findhist {
 110	struct findhist *next;
 111	char *path;
 112	dev_t dev;
 113	ino_t ino;
 114};
 115
 116/* Utility */
 117static int spawn(char *argv[]);
 118static int do_stat(char *path, struct stat *sb, struct findhist *hist);
 119
 120/* Primaries */
 121static int pri_name   (struct arg *arg);
 122#if FEATURE_FIND_INAME
 123static int pri_iname(struct arg *arg);
 124#endif
 125#if FEATURE_FIND_IPATH
 126static int pri_ipath(struct arg *arg);
 127#endif
 128#if FEATURE_FIND_REGEX
 129static int pri_regex(struct arg *arg);
 130static char **get_regex_arg(char *argv[], union extra *extra);
 131static void free_regex_arg(union extra extra);
 132#endif
 133#if FEATURE_FIND_INUM
 134static int pri_inum(struct arg *arg);
 135static char **get_inum_arg(char *argv[], union extra *extra);
 136#endif
 137#if FEATURE_FIND_SAMEFILE
 138static int pri_samefile(struct arg *arg);
 139static char **get_samefile_arg(char *argv[], union extra *extra);
 140#endif
 141#if FEATURE_FIND_MAXDEPTH
 142static int pri_maxdepth(struct arg *arg);
 143static char **get_maxdepth_arg(char *argv[], union extra *extra);
 144#endif
 145#if FEATURE_FIND_MINDEPTH
 146static int pri_mindepth(struct arg *arg);
 147static char **get_mindepth_arg(char *argv[], union extra *extra);
 148#endif
 149#if FEATURE_FIND_DELETE
 150static int pri_delete(struct arg *arg);
 151static char **get_delete_arg(char *argv[], union extra *extra);
 152#endif
 153#if FEATURE_FIND_QUIT
 154static int pri_quit(struct arg *arg);
 155static char **get_quit_arg(char *argv[], union extra *extra);
 156#endif
 157#if FEATURE_FIND_EMPTY
 158static int pri_empty(struct arg *arg);
 159#endif
 160#if FEATURE_FIND_MMIN
 161static int pri_mmin(struct arg *arg);
 162#endif
 163#if FEATURE_FIND_AMIN
 164static int pri_amin(struct arg *arg);
 165#endif
 166#if FEATURE_FIND_CMIN
 167static int pri_cmin(struct arg *arg);
 168#endif
 169static int pri_path   (struct arg *arg);
 170static int pri_nouser (struct arg *arg);
 171static int pri_nogroup(struct arg *arg);
 172static int pri_xdev   (struct arg *arg);
 173static int pri_prune  (struct arg *arg);
 174static int pri_perm   (struct arg *arg);
 175static int pri_type   (struct arg *arg);
 176static int pri_links  (struct arg *arg);
 177static int pri_user   (struct arg *arg);
 178static int pri_group  (struct arg *arg);
 179static int pri_size   (struct arg *arg);
 180static int pri_atime  (struct arg *arg);
 181static int pri_ctime  (struct arg *arg);
 182static int pri_mtime  (struct arg *arg);
 183static int pri_exec   (struct arg *arg);
 184static int pri_ok     (struct arg *arg);
 185static int pri_print  (struct arg *arg);
 186#if FEATURE_FIND_PRINT0
 187static int pri_print0 (struct arg *arg);
 188#endif
 189static int pri_newer  (struct arg *arg);
 190static int pri_depth  (struct arg *arg);
 191
 192/* Getargs */
 193static char **get_name_arg (char *argv[], union extra *extra);
 194static char **get_path_arg (char *argv[], union extra *extra);
 195static char **get_xdev_arg (char *argv[], union extra *extra);
 196static char **get_perm_arg (char *argv[], union extra *extra);
 197static char **get_type_arg (char *argv[], union extra *extra);
 198static char **get_n_arg    (char *argv[], union extra *extra);
 199static char **get_user_arg (char *argv[], union extra *extra);
 200static char **get_group_arg(char *argv[], union extra *extra);
 201static char **get_size_arg (char *argv[], union extra *extra);
 202static char **get_exec_arg (char *argv[], union extra *extra);
 203static char **get_ok_arg   (char *argv[], union extra *extra);
 204static char **get_print_arg(char *argv[], union extra *extra);
 205static char **get_newer_arg(char *argv[], union extra *extra);
 206static char **get_depth_arg(char *argv[], union extra *extra);
 207
 208/* Freeargs */
 209static void free_extra   (union extra extra);
 210static void free_exec_arg(union extra extra);
 211static void free_ok_arg  (union extra extra);
 212
 213/* Parsing/Building/Running */
 214static void fill_narg(char *s, struct narg *n);
 215static struct pri_info *find_primary(char *name);
 216static struct op_info *find_op(char *name);
 217static void parse(int argc, char **argv);
 218static int eval(struct tok *tok, struct arg *arg);
 219static void find(char *path, struct findhist *hist);
 220static void usage(void);
 221
 222/* for comparisons with narg */
 223static int cmp_gt(int a, int b) { return a >  b; }
 224static int cmp_eq(int a, int b) { return a == b; }
 225static int cmp_lt(int a, int b) { return a <  b; }
 226
 227/* order from find(1p), may want to alphabetize */
 228static struct pri_info primaries[] = {
 229#if FEATURE_FIND_INAME
 230	{ "-iname"  , pri_iname  , get_name_arg , NULL         , 1 },
 231#endif
 232#if FEATURE_FIND_IPATH
 233	{ "-ipath"  , pri_ipath  , get_path_arg , NULL         , 1 },
 234#endif
 235#if FEATURE_FIND_REGEX
 236	{ "-regex"  , pri_regex  , get_regex_arg, free_regex_arg, 1 },
 237#endif
 238#if FEATURE_FIND_INUM
 239	{ "-inum"   , pri_inum   , get_inum_arg , NULL         , 1 },
 240#endif
 241#if FEATURE_FIND_SAMEFILE
 242	{ "-samefile", pri_samefile, get_samefile_arg, free_extra, 1 },
 243#endif
 244#if FEATURE_FIND_MAXDEPTH
 245	{ "-maxdepth", pri_maxdepth, get_maxdepth_arg, NULL    , 1 },
 246#endif
 247#if FEATURE_FIND_MINDEPTH
 248	{ "-mindepth", pri_mindepth, get_mindepth_arg, NULL    , 1 },
 249#endif
 250#if FEATURE_FIND_DELETE
 251	{ "-delete" , pri_delete , get_delete_arg, NULL        , 0 },
 252#endif
 253#if FEATURE_FIND_QUIT
 254	{ "-quit"   , pri_quit   , get_quit_arg , NULL         , 0 },
 255#endif
 256#if FEATURE_FIND_MMIN
 257	{ "-mmin"   , pri_mmin   , get_n_arg    , free_extra   , 1 },
 258#endif
 259#if FEATURE_FIND_AMIN
 260	{ "-amin"   , pri_amin   , get_n_arg    , free_extra   , 1 },
 261#endif
 262#if FEATURE_FIND_CMIN
 263	{ "-cmin"   , pri_cmin   , get_n_arg    , free_extra   , 1 },
 264#endif
 265#if FEATURE_FIND_EMPTY
 266	{ "-empty"  , pri_empty  , NULL         , NULL         , 1 },
 267#endif
 268	{ "-name"   , pri_name   , get_name_arg , NULL         , 1 },
 269	{ "-path"   , pri_path   , get_path_arg , NULL         , 1 },
 270	{ "-nouser" , pri_nouser , NULL         , NULL         , 1 },
 271	{ "-nogroup", pri_nogroup, NULL         , NULL         , 1 },
 272	{ "-xdev"   , pri_xdev   , get_xdev_arg , NULL         , 0 },
 273	{ "-prune"  , pri_prune  , NULL         , NULL         , 1 },
 274	{ "-perm"   , pri_perm   , get_perm_arg , free_extra   , 1 },
 275	{ "-type"   , pri_type   , get_type_arg , NULL         , 1 },
 276	{ "-links"  , pri_links  , get_n_arg    , free_extra   , 1 },
 277	{ "-user"   , pri_user   , get_user_arg , NULL         , 1 },
 278	{ "-group"  , pri_group  , get_group_arg, NULL         , 1 },
 279	{ "-size"   , pri_size   , get_size_arg , free_extra   , 1 },
 280	{ "-atime"  , pri_atime  , get_n_arg    , free_extra   , 1 },
 281	{ "-ctime"  , pri_ctime  , get_n_arg    , free_extra   , 1 },
 282	{ "-mtime"  , pri_mtime  , get_n_arg    , free_extra   , 1 },
 283	{ "-exec"   , pri_exec   , get_exec_arg , free_exec_arg, 1 },
 284	{ "-ok"     , pri_ok     , get_ok_arg   , free_ok_arg  , 1 },
 285	{ "-print"  , pri_print  , get_print_arg, NULL         , 0 },
 286#if FEATURE_FIND_PRINT0
 287	{ "-print0" , pri_print0 , get_print_arg, NULL         , 0 },
 288#endif
 289	{ "-newer"  , pri_newer  , get_newer_arg, NULL         , 1 },
 290	{ "-depth"  , pri_depth  , get_depth_arg, NULL         , 0 },
 291
 292	{ NULL, NULL, NULL, NULL, 0 }
 293};
 294
 295static struct op_info ops[] = {
 296	{ "(" , LPAR, 0, 0, 0 }, /* parens are handled specially */
 297	{ ")" , RPAR, 0, 0, 0 },
 298	{ "!" , NOT , 3, 1, 0 },
 299	{ "-a", AND , 2, 2, 1 },
 300	{ "-o", OR  , 1, 2, 1 },
 301
 302	{ NULL, 0, 0, 0, 0 }
 303};
 304
 305extern char **environ;
 306
 307static struct tok *toks; /* holds allocated array of all toks created while parsing */
 308static struct tok *root; /* points to root of expression tree, inside toks array */
 309
 310static struct timespec start; /* time find was started, used for -[acm]time */
 311
 312static size_t envlen; /* number of bytes in environ, used to calculate against ARG_MAX */
 313static size_t argmax; /* value of ARG_MAX retrieved using sysconf(3p) */
 314
 315static struct {
 316	char ret  ; /* return value from main                             */
 317	char depth; /* -depth, directory contents before directory itself */
 318	char h    ; /* -H, follow symlinks on command line                */
 319	char l    ; /* -L, follow all symlinks (command line and search)  */
 320	char prune; /* hit -prune                                         */
 321	char xdev ; /* -xdev, prune directories on different devices      */
 322	char print; /* whether we will need -print when parsing           */
 323	char quit ; /* quit execution immediately                         */
 324	long maxdepth; /* max depth of recursion                          */
 325	long mindepth; /* min depth of recursion                          */
 326} gflags;
 327
 328/*
 329 * Utility
 330 */
 331static int
 332spawn(char *argv[])
 333{
 334	pid_t pid;
 335	int status;
 336
 337	/* flush stdout so that -print output always appears before
 338	 * any output from the command and does not get cut-off in
 339	 * the middle of a line. */
 340	fflush(stdout);
 341
 342	switch((pid = fork())) {
 343	case -1:
 344		eprintf("fork:");
 345		break;
 346	case 0:
 347		execvp(*argv, argv);
 348		weprintf("exec %s failed:", *argv);
 349		_exit(1);
 350	}
 351
 352	while (waitpid(pid, &status, 0) < 0) {
 353		if (errno != EINTR) {
 354			status = -1;
 355			break;
 356		}
 357	}
 358	return status;
 359}
 360
 361static int
 362do_stat(char *path, struct stat *sb, struct findhist *hist)
 363{
 364	if (gflags.l || (gflags.h && !hist)) {
 365		if (stat(path, sb) == 0) {
 366			return 0;
 367		} else if (errno != ENOENT && errno != ENOTDIR) {
 368			return -1;
 369		}
 370	}
 371
 372	return lstat(path, sb);
 373}
 374
 375/*
 376 * Primaries
 377 */
 378static int
 379pri_name(struct arg *arg)
 380{
 381	int ret;
 382	char *path;
 383
 384	path = estrdup(arg->path);
 385	ret = !fnmatch((char *)arg->extra.p, basename(path), 0);
 386	free(path);
 387
 388	return ret;
 389}
 390
 391static int
 392pri_path(struct arg *arg)
 393{
 394	return !fnmatch((char *)arg->extra.p, arg->path, 0);
 395}
 396
 397/* FIXME: what about errors? find(1p) literally just says
 398 * "for which the getpwuid() function ... returns NULL" */
 399static int
 400pri_nouser(struct arg *arg)
 401{
 402	return !getpwuid(arg->st->st_uid);
 403}
 404
 405static int
 406pri_nogroup(struct arg *arg)
 407{
 408	return !getgrgid(arg->st->st_gid);
 409}
 410
 411static int
 412pri_xdev(struct arg *arg)
 413{
 414	(void)arg;
 415	return 1;
 416}
 417
 418static int
 419pri_prune(struct arg *arg)
 420{
 421	(void)arg;
 422	return gflags.prune = 1;
 423}
 424
 425static int
 426pri_perm(struct arg *arg)
 427{
 428	struct permarg *p = (struct permarg *)arg->extra.p;
 429
 430	return (arg->st->st_mode & 07777 & (p->exact ? -1U : p->mode)) == p->mode;
 431}
 432
 433static int
 434pri_type(struct arg *arg)
 435{
 436	switch ((char)arg->extra.i) {
 437	default : return 0; /* impossible, but placate warnings */
 438	case 'b': return S_ISBLK (arg->st->st_mode);
 439	case 'c': return S_ISCHR (arg->st->st_mode);
 440	case 'd': return S_ISDIR (arg->st->st_mode);
 441	case 'l': return S_ISLNK (arg->st->st_mode);
 442	case 'p': return S_ISFIFO(arg->st->st_mode);
 443	case 'f': return S_ISREG (arg->st->st_mode);
 444	case 's': return S_ISSOCK(arg->st->st_mode);
 445	}
 446}
 447
 448static int
 449pri_links(struct arg *arg)
 450{
 451	struct narg *n = arg->extra.p;
 452	return n->cmp(arg->st->st_nlink, n->n);
 453}
 454
 455static int
 456pri_user(struct arg *arg)
 457{
 458	return arg->st->st_uid == (uid_t)arg->extra.i;
 459}
 460
 461static int
 462pri_group(struct arg *arg)
 463{
 464	return arg->st->st_gid == (gid_t)arg->extra.i;
 465}
 466
 467static int
 468pri_size(struct arg *arg)
 469{
 470	struct sizearg *s = arg->extra.p;
 471	off_t size = arg->st->st_size;
 472
 473	if (!s->bytes)
 474		size = size / 512 + !!(size % 512);
 475
 476	return s->n.cmp(size, s->n.n);
 477}
 478
 479/* FIXME: ignoring nanoseconds in atime, ctime, mtime */
 480static int
 481pri_atime(struct arg *arg)
 482{
 483	struct narg *n = arg->extra.p;
 484	return n->cmp((start.tv_sec - arg->st->st_atime) / 86400, n->n);
 485}
 486
 487static int
 488pri_ctime(struct arg *arg)
 489{
 490	struct narg *n = arg->extra.p;
 491	return n->cmp((start.tv_sec - arg->st->st_ctime) / 86400, n->n);
 492}
 493
 494static int
 495pri_mtime(struct arg *arg)
 496{
 497	struct narg *n = arg->extra.p;
 498	return n->cmp((start.tv_sec - arg->st->st_mtime) / 86400, n->n);
 499}
 500
 501static int
 502pri_exec(struct arg *arg)
 503{
 504	int status;
 505	size_t len;
 506	char **sp, ***brace;
 507	struct execarg *e = arg->extra.p;
 508
 509	if (e->isplus) {
 510		len = strlen(arg->path) + 1;
 511
 512		/* if we reached ARG_MAX, fork, exec, wait, free file names, reset list */
 513		if (len + e->u.p.arglen + e->u.p.filelen + envlen > argmax) {
 514			e->argv[e->u.p.next] = NULL;
 515
 516			status = spawn(e->argv);
 517			gflags.ret = gflags.ret || status;
 518
 519			for (sp = e->argv + e->u.p.first; *sp; sp++)
 520				free(*sp);
 521
 522			e->u.p.next = e->u.p.first;
 523			e->u.p.filelen = 0;
 524		}
 525
 526		/* if we have too many files, realloc (with space for NULL termination) */
 527		if (e->u.p.next + 1 == e->u.p.cap)
 528			e->argv = ereallocarray(e->argv, e->u.p.cap *= 2, sizeof(*e->argv));
 529
 530		e->argv[e->u.p.next++] = estrdup(arg->path);
 531		e->u.p.filelen += len + sizeof(arg->path);
 532
 533		return 1;
 534	} else {
 535		/* insert path everywhere user gave us {} */
 536		for (brace = e->u.s.braces; *brace; brace++)
 537			**brace = arg->path;
 538
 539		status = spawn(e->argv);
 540		return !status;
 541	}
 542}
 543
 544static int
 545pri_ok(struct arg *arg)
 546{
 547	int status, reply;
 548	char ***brace, c;
 549	struct okarg *o = arg->extra.p;
 550
 551	fprintf(stderr, "%s: %s ? ", *o->argv, arg->path);
 552	reply = fgetc(stdin);
 553
 554	/* throw away rest of line */
 555	while ((c = fgetc(stdin)) != '\n' && c != EOF)
 556		/* FIXME: what if the first character of the rest of the line is a null
 557		 * byte? */
 558		;
 559
 560	if (feof(stdin) || ferror(stdin))
 561		clearerr(stdin);
 562
 563	if (reply != 'y' && reply != 'Y')
 564		return 0;
 565
 566	/* insert filename everywhere user gave us {} */
 567	for (brace = o->braces; *brace; brace++)
 568		**brace = arg->path;
 569
 570	status = spawn(o->argv);
 571	return !!status;
 572}
 573
 574static int
 575pri_print(struct arg *arg)
 576{
 577	if (puts(arg->path) == EOF)
 578		eprintf("puts failed:");
 579	return 1;
 580}
 581
 582#if FEATURE_FIND_PRINT0
 583static int
 584pri_print0(struct arg *arg)
 585{
 586	if (fwrite(arg->path, strlen(arg->path) + 1, 1, stdout) != 1)
 587		eprintf("fwrite failed:");
 588	return 1;
 589}
 590#endif
 591
 592/* FIXME: ignoring nanoseconds */
 593static int
 594pri_newer(struct arg *arg)
 595{
 596	return arg->st->st_mtime > (time_t)arg->extra.i;
 597}
 598
 599static int
 600pri_depth(struct arg *arg)
 601{
 602	(void)arg;
 603	return 1;
 604}
 605
 606/*
 607 * Getargs
 608 * consume any arguments for given primary and fill extra
 609 * return pointer to last argument, the pointer will be incremented in parse()
 610 */
 611static char **
 612get_name_arg(char *argv[], union extra *extra)
 613{
 614	extra->p = *argv;
 615	return argv;
 616}
 617
 618static char **
 619get_path_arg(char *argv[], union extra *extra)
 620{
 621	extra->p = *argv;
 622	return argv;
 623}
 624
 625static char **
 626get_xdev_arg(char *argv[], union extra *extra)
 627{
 628	(void)extra;
 629	gflags.xdev = 1;
 630	return argv;
 631}
 632
 633static char **
 634get_perm_arg(char *argv[], union extra *extra)
 635{
 636	mode_t mask;
 637	struct permarg *p = extra->p = emalloc(sizeof(*p));
 638
 639	if (**argv == '-')
 640		(*argv)++;
 641	else
 642		p->exact = 1;
 643
 644	mask = umask(0);
 645	umask(mask);
 646
 647	p->mode = parsemode(*argv, 0, mask);
 648
 649	return argv;
 650}
 651
 652static char **
 653get_type_arg(char *argv[], union extra *extra)
 654{
 655	if (!strchr("bcdlpfs", **argv))
 656		eprintf("invalid type %c for -type primary\n", **argv);
 657
 658	extra->i = **argv;
 659	return argv;
 660}
 661
 662static char **
 663get_n_arg(char *argv[], union extra *extra)
 664{
 665	struct narg *n = extra->p = emalloc(sizeof(*n));
 666	fill_narg(*argv, n);
 667	return argv;
 668}
 669
 670static char **
 671get_user_arg(char *argv[], union extra *extra)
 672{
 673	char *end;
 674	struct passwd *p = getpwnam(*argv);
 675
 676	if (p) {
 677		extra->i = p->pw_uid;
 678	} else {
 679		extra->i = strtol(*argv, &end, 10);
 680		if (end == *argv || *end)
 681			eprintf("unknown user '%s'\n", *argv);
 682	}
 683	return argv;
 684}
 685
 686static char **
 687get_group_arg(char *argv[], union extra *extra)
 688{
 689	char *end;
 690	struct group *g = getgrnam(*argv);
 691
 692	if (g) {
 693		extra->i = g->gr_gid;
 694	} else {
 695		extra->i = strtol(*argv, &end, 10);
 696		if (end == *argv || *end)
 697			eprintf("unknown group '%s'\n", *argv);
 698	}
 699	return argv;
 700}
 701
 702static char **
 703get_size_arg(char *argv[], union extra *extra)
 704{
 705	char *p = *argv + strlen(*argv);
 706	struct sizearg *s = extra->p = emalloc(sizeof(*s));
 707	/* if the number is followed by 'c', the size will by in bytes */
 708	if ((s->bytes = (p > *argv && *--p == 'c')))
 709		*p = '\0';
 710
 711	fill_narg(*argv, &s->n);
 712	return argv;
 713}
 714
 715static char **
 716get_exec_arg(char *argv[], union extra *extra)
 717{
 718	char **arg, **new, ***braces;
 719	int nbraces = 0;
 720	struct execarg *e = extra->p = emalloc(sizeof(*e));
 721
 722	for (arg = argv; *arg; arg++)
 723		if (!strcmp(*arg, ";"))
 724			break;
 725		else if (arg > argv && !strcmp(*(arg - 1), "{}") && !strcmp(*arg, "+"))
 726			break;
 727		else if (!strcmp(*arg, "{}"))
 728			nbraces++;
 729
 730	if (!*arg)
 731		eprintf("no terminating ; or {} + for -exec primary\n");
 732
 733	e->isplus = **arg == '+';
 734	*arg = NULL;
 735
 736	if (e->isplus) {
 737		*(arg - 1) = NULL; /* don't need the {} in there now */
 738		e->u.p.arglen = e->u.p.filelen = 0;
 739		e->u.p.first = e->u.p.next = arg - argv - 1;
 740		e->u.p.cap = (arg - argv) * 2;
 741		e->argv = ereallocarray(NULL, e->u.p.cap, sizeof(*e->argv));
 742
 743		for (arg = argv, new = e->argv; *arg; arg++, new++) {
 744			*new = *arg;
 745			e->u.p.arglen += strlen(*arg) + 1 + sizeof(*arg);
 746		}
 747		arg++; /* due to our extra NULL */
 748	} else {
 749		e->argv = argv;
 750		e->u.s.braces = ereallocarray(NULL, ++nbraces, sizeof(*e->u.s.braces)); /* ++ for NULL */
 751
 752		for (arg = argv, braces = e->u.s.braces; *arg; arg++)
 753			if (!strcmp(*arg, "{}"))
 754				*braces++ = arg;
 755		*braces = NULL;
 756	}
 757	gflags.print = 0;
 758	return arg;
 759}
 760
 761static char **
 762get_ok_arg(char *argv[], union extra *extra)
 763{
 764	char **arg, ***braces;
 765	int nbraces = 0;
 766	struct okarg *o = extra->p = emalloc(sizeof(*o));
 767
 768	for (arg = argv; *arg; arg++)
 769		if (!strcmp(*arg, ";"))
 770			break;
 771		else if (!strcmp(*arg, "{}"))
 772			nbraces++;
 773
 774	if (!*arg)
 775		eprintf("no terminating ; for -ok primary\n");
 776	*arg = NULL;
 777
 778	o->argv = argv;
 779	o->braces = ereallocarray(NULL, ++nbraces, sizeof(*o->braces));
 780
 781	for (arg = argv, braces = o->braces; *arg; arg++)
 782		if (!strcmp(*arg, "{}"))
 783			*braces++ = arg;
 784	*braces = NULL;
 785
 786	gflags.print = 0;
 787	return arg;
 788}
 789
 790static char **
 791get_print_arg(char *argv[], union extra *extra)
 792{
 793	(void)extra;
 794	gflags.print = 0;
 795	return argv;
 796}
 797
 798/* FIXME: ignoring nanoseconds */
 799static char **
 800get_newer_arg(char *argv[], union extra *extra)
 801{
 802	struct stat st;
 803
 804	if (do_stat(*argv, &st, NULL))
 805		eprintf("failed to stat '%s':", *argv);
 806
 807	extra->i = st.st_mtime;
 808	return argv;
 809}
 810
 811static char **
 812get_depth_arg(char *argv[], union extra *extra)
 813{
 814	(void)extra;
 815	gflags.depth = 1;
 816	return argv;
 817}
 818
 819/*
 820 * Freeargs
 821 */
 822static void
 823free_extra(union extra extra)
 824{
 825	free(extra.p);
 826}
 827
 828static void
 829free_exec_arg(union extra extra)
 830{
 831	int status;
 832	char **arg;
 833	struct execarg *e = extra.p;
 834
 835	if (!e->isplus) {
 836		free(e->u.s.braces);
 837	} else {
 838		e->argv[e->u.p.next] = NULL;
 839
 840		/* if we have files, do the last exec */
 841		if (e->u.p.first != e->u.p.next) {
 842			status = spawn(e->argv);
 843			gflags.ret = gflags.ret || status;
 844		}
 845		for (arg = e->argv + e->u.p.first; *arg; arg++)
 846			free(*arg);
 847		free(e->argv);
 848	}
 849	free(e);
 850}
 851
 852static void
 853free_ok_arg(union extra extra)
 854{
 855	struct okarg *o = extra.p;
 856
 857	free(o->braces);
 858	free(o);
 859}
 860
 861/*
 862 * Parsing/Building/Running
 863 */
 864static void
 865fill_narg(char *s, struct narg *n)
 866{
 867	char *end;
 868
 869	switch (*s) {
 870	case '+': n->cmp = cmp_gt; s++; break;
 871	case '-': n->cmp = cmp_lt; s++; break;
 872	default : n->cmp = cmp_eq;      break;
 873	}
 874	n->n = strtol(s, &end, 10);
 875	if (end == s || *end)
 876		eprintf("bad number '%s'\n", s);
 877}
 878
 879static struct pri_info *
 880find_primary(char *name)
 881{
 882	struct pri_info *p;
 883
 884	for (p = primaries; p->name; p++)
 885		if (!strcmp(name, p->name))
 886			return p;
 887	return NULL;
 888}
 889
 890static struct op_info *
 891find_op(char *name)
 892{
 893	struct op_info *o;
 894
 895	for (o = ops; o->name; o++)
 896		if (!strcmp(name, o->name))
 897			return o;
 898	return NULL;
 899}
 900
 901/* given the expression from the command line
 902 * 1) convert arguments from strings to tok and place in an array duplicating
 903 *    the infix expression given, inserting "-a" where it was omitted
 904 * 2) allocate an array to hold the correct number of tok, and convert from
 905 *    infix to rpn (using shunting-yard), add -a and -print if necessary
 906 * 3) evaluate the rpn filling in left and right pointers to create an
 907 *    expression tree (tok are still all contained in the rpn array, just
 908 *    pointing at eachother)
 909 */
 910static void
 911parse(int argc, char **argv)
 912{
 913	struct tok *tok, *rpn, *out, **top, *infix, **stack;
 914	struct op_info *op;
 915	struct pri_info *pri;
 916	char **arg;
 917	int lasttype = -1;
 918	size_t ntok = 0;
 919	struct tok and = { .u.oinfo = find_op("-a"), .type = AND };
 920
 921	gflags.print = 1;
 922
 923	/* convert argv to infix expression of tok, inserting in *tok */
 924	infix = ereallocarray(NULL, 2 * argc + 1, sizeof(*infix));
 925	for (arg = argv, tok = infix; *arg; arg++, tok++) {
 926		pri = find_primary(*arg);
 927
 928		if (pri) { /* token is a primary, fill out tok and get arguments */
 929			if (lasttype == PRIM || lasttype == RPAR) {
 930				*tok++ = and;
 931				ntok++;
 932			}
 933			if (pri->getarg) {
 934				if (pri->narg && !*++arg)
 935					eprintf("no argument for primary %s\n", pri->name);
 936				arg = pri->getarg(arg, &tok->extra);
 937			}
 938			tok->u.pinfo = pri;
 939			tok->type = PRIM;
 940		} else if ((op = find_op(*arg))) { /* token is an operator */
 941			if (lasttype == LPAR && op->type == RPAR)
 942				eprintf("empty parens\n");
 943			if ((lasttype == PRIM || lasttype == RPAR) &&
 944			    (op->type == NOT || op->type == LPAR)) { /* need another implicit -a */
 945				*tok++ = and;
 946				ntok++;
 947			}
 948			tok->type = op->type;
 949			tok->u.oinfo = op;
 950		} else {
 951			/* token is neither primary nor operator, must be */
 952			if ((*arg)[0] == '-') /* an unsupported option */
 953				eprintf("unknown operand: %s\n", *arg);
 954			else /* or a path in the wrong place */
 955				eprintf("paths must precede expression: %s\n", *arg);
 956		}
 957		if (tok->type != LPAR && tok->type != RPAR)
 958			ntok++; /* won't have parens in rpn */
 959		lasttype = tok->type;
 960	}
 961	tok->type = END;
 962	ntok++;
 963
 964	if (gflags.print && (arg != argv)) /* need to add -a -print (not just -print) */
 965		gflags.print++;
 966
 967	/* use shunting-yard to convert from infix to rpn
 968	 * https://en.wikipedia.org/wiki/Shunting-yard_algorithm
 969	 * read from infix, resulting rpn ends up in rpn, next position in rpn is out
 970	 * push operators onto stack, next position in stack is top */
 971	rpn = ereallocarray(NULL, ntok + gflags.print, sizeof(*rpn));
 972	stack = ereallocarray(NULL, argc + gflags.print, sizeof(*stack));
 973	for (tok = infix, out = rpn, top = stack; tok->type != END; tok++) {
 974		switch (tok->type) {
 975		case PRIM: *out++ = *tok; break;
 976		case LPAR: *top++ =  tok; break;
 977		case RPAR:
 978			while (top-- > stack && (*top)->type != LPAR)
 979				*out++ = **top;
 980			if (top < stack)
 981				eprintf("extra )\n");
 982			break;
 983		default:
 984			/* this expression can be simplified, but I decided copy the
 985			 * verbage from the wikipedia page in order to more clearly explain
 986			 * what's going on */
 987			while (top-- > stack &&
 988			       (( tok->u.oinfo->lassoc && tok->u.oinfo->prec <= (*top)->u.oinfo->prec) ||
 989			        (!tok->u.oinfo->lassoc && tok->u.oinfo->prec <  (*top)->u.oinfo->prec)))
 990				*out++ = **top;
 991
 992			/* top now points to either an operator we didn't pop, or stack[-1]
 993			 * either way we need to increment it before using it, then
 994			 * increment again so the stack works */
 995			top++;
 996			*top++ = tok;
 997			break;
 998		}
 999	}
1000	while (top-- > stack) {
1001		if ((*top)->type == LPAR)
1002			eprintf("extra (\n");
1003		*out++ = **top;
1004	}
1005
1006	/* if there was no expression, use -print
1007	 * if there was an expression but no -print, -exec, or -ok, add -a -print
1008	 * in rpn, not infix */
1009	if (gflags.print)
1010		*out++ = (struct tok){ .u.pinfo = find_primary("-print"), .type = PRIM };
1011	if (gflags.print == 2)
1012		*out++ = and;
1013
1014	out->type = END;
1015
1016	/* rpn now holds all operators and arguments in reverse polish notation
1017	 * values are pushed onto stack, operators pop values off stack into left
1018	 * and right pointers, pushing operator node back onto stack
1019	 * could probably just do this during shunting-yard, but this is simpler
1020	 * code IMO */
1021	for (tok = rpn, top = stack; tok->type != END; tok++) {
1022		if (tok->type == PRIM) {
1023			*top++ = tok;
1024		} else {
1025			if (top - stack < tok->u.oinfo->nargs)
1026				eprintf("insufficient arguments for operator %s\n", tok->u.oinfo->name);
1027			tok->right = *--top;
1028			tok->left  = tok->u.oinfo->nargs == 2 ? *--top : NULL;
1029			*top++ = tok;
1030		}
1031	}
1032	if (--top != stack)
1033		eprintf("extra arguments\n");
1034
1035	toks = rpn;
1036	root = *top;
1037
1038	free(infix);
1039	free(stack);
1040}
1041
1042/* for a primary, run and return result
1043 * for an operator evaluate the left side of the tree, decide whether or not to
1044 * evaluate the right based on the short-circuit boolean logic, return result
1045 * NOTE: operator NOT has NULL left side, expression on right side
1046 */
1047static int
1048eval(struct tok *tok, struct arg *arg)
1049{
1050	int ret;
1051
1052	if (!tok)
1053		return 0;
1054
1055	if (tok->type == PRIM) {
1056		arg->extra = tok->extra;
1057		return tok->u.pinfo->func(arg);
1058	}
1059
1060	ret = eval(tok->left, arg);
1061
1062	if ((tok->type == AND && ret) || (tok->type == OR && !ret) || tok->type == NOT)
1063		ret = eval(tok->right, arg);
1064
1065	return ret ^ (tok->type == NOT);
1066}
1067
1068/* evaluate path, if it's a directory iterate through directory entries and
1069 * recurse
1070 */
1071#if FEATURE_FIND_INAME
1072static int
1073pri_iname(struct arg *arg)
1074{
1075	int ret;
1076	char *path;
1077
1078	path = estrdup(arg->path);
1079	ret = !fnmatch((char *)arg->extra.p, basename(path), FNM_CASEFOLD);
1080	free(path);
1081
1082	return ret;
1083}
1084#endif
1085
1086#if FEATURE_FIND_IPATH
1087static int
1088pri_ipath(struct arg *arg)
1089{
1090	return !fnmatch((char *)arg->extra.p, arg->path, FNM_CASEFOLD);
1091}
1092#endif
1093
1094#if FEATURE_FIND_REGEX
1095static int
1096pri_regex(struct arg *arg)
1097{
1098	regex_t *re = arg->extra.p;
1099	regmatch_t match;
1100	if (regexec(re, arg->path, 1, &match, 0) == 0) {
1101		return match.rm_so == 0 && (size_t)match.rm_eo == strlen(arg->path);
1102	}
1103	return 0;
1104}
1105
1106static char **
1107get_regex_arg(char *argv[], union extra *extra)
1108{
1109	regex_t *re = emalloc(sizeof(*re));
1110	eregcomp(re, *argv, 0);
1111	extra->p = re;
1112	return argv;
1113}
1114
1115static void
1116free_regex_arg(union extra extra)
1117{
1118	regex_t *re = extra.p;
1119	regfree(re);
1120	free(re);
1121}
1122#endif
1123
1124#if FEATURE_FIND_INUM
1125static int
1126pri_inum(struct arg *arg)
1127{
1128	ino_t ino = (ino_t)arg->extra.i;
1129	return arg->st->st_ino == ino;
1130}
1131
1132static char **
1133get_inum_arg(char *argv[], union extra *extra)
1134{
1135	char *end;
1136	extra->i = strtol(*argv, &end, 10);
1137	if (end == *argv || *end)
1138		eprintf("bad number '%s'\n", *argv);
1139	return argv;
1140}
1141#endif
1142
1143#if FEATURE_FIND_SAMEFILE
1144struct SameFileArg {
1145	ino_t ino;
1146	dev_t dev;
1147};
1148
1149static int
1150pri_samefile(struct arg *arg)
1151{
1152	struct SameFileArg *s = arg->extra.p;
1153	return arg->st->st_ino == s->ino && arg->st->st_dev == s->dev;
1154}
1155
1156static char **
1157get_samefile_arg(char *argv[], union extra *extra)
1158{
1159	struct stat st;
1160	struct SameFileArg *s = emalloc(sizeof(*s));
1161	if (do_stat(*argv, &st, NULL))
1162		eprintf("failed to stat '%s':", *argv);
1163	s->ino = st.st_ino;
1164	s->dev = st.st_dev;
1165	extra->p = s;
1166	return argv;
1167}
1168#endif
1169
1170#if FEATURE_FIND_MAXDEPTH
1171static int
1172pri_maxdepth(struct arg *arg)
1173{
1174	(void)arg;
1175	return 1;
1176}
1177
1178static char **
1179get_maxdepth_arg(char *argv[], union extra *extra)
1180{
1181	(void)extra;
1182	char *end;
1183	gflags.maxdepth = strtol(*argv, &end, 10);
1184	if (end == *argv || *end || gflags.maxdepth < 0)
1185		eprintf("bad number '%s'\n", *argv);
1186	return argv;
1187}
1188#endif
1189
1190#if FEATURE_FIND_MINDEPTH
1191static int
1192pri_mindepth(struct arg *arg)
1193{
1194	(void)arg;
1195	return 1;
1196}
1197
1198static char **
1199get_mindepth_arg(char *argv[], union extra *extra)
1200{
1201	(void)extra;
1202	char *end;
1203	gflags.mindepth = strtol(*argv, &end, 10);
1204	if (end == *argv || *end || gflags.mindepth < 0)
1205		eprintf("bad number '%s'\n", *argv);
1206	return argv;
1207}
1208#endif
1209
1210#if FEATURE_FIND_DELETE
1211static int
1212pri_delete(struct arg *arg)
1213{
1214	if (remove(arg->path) < 0) {
1215		weprintf("remove %s failed:", arg->path);
1216		gflags.ret = 1;
1217		return 0;
1218	}
1219	return 1;
1220}
1221
1222static char **
1223get_delete_arg(char *argv[], union extra *extra)
1224{
1225	(void)extra;
1226	gflags.depth = 1;
1227	gflags.print = 0;
1228	return argv;
1229}
1230#endif
1231
1232#if FEATURE_FIND_QUIT
1233static int
1234pri_quit(struct arg *arg)
1235{
1236	(void)arg;
1237	gflags.quit = 1;
1238	return 1;
1239}
1240
1241static char **
1242get_quit_arg(char *argv[], union extra *extra)
1243{
1244	(void)extra;
1245	gflags.print = 0;
1246	return argv;
1247}
1248#endif
1249
1250#if FEATURE_FIND_EMPTY
1251static int
1252pri_empty(struct arg *arg)
1253{
1254	DIR *dir;
1255	struct dirent *de;
1256	int empty = 1;
1257
1258	if (S_ISREG(arg->st->st_mode)) {
1259		return arg->st->st_size == 0;
1260	} else if (S_ISDIR(arg->st->st_mode)) {
1261		dir = opendir(arg->path);
1262		if (!dir)
1263			return 0;
1264		while ((de = readdir(dir))) {
1265			if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
1266				empty = 0;
1267				break;
1268			}
1269		}
1270		closedir(dir);
1271		return empty;
1272	}
1273	return 0;
1274}
1275#endif
1276
1277#if FEATURE_FIND_MMIN
1278static int
1279pri_mmin(struct arg *arg)
1280{
1281	struct narg *n = arg->extra.p;
1282	return n->cmp((start.tv_sec - arg->st->st_mtime) / 60, n->n);
1283}
1284#endif
1285
1286#if FEATURE_FIND_AMIN
1287static int
1288pri_amin(struct arg *arg)
1289{
1290	struct narg *n = arg->extra.p;
1291	return n->cmp((start.tv_sec - arg->st->st_atime) / 60, n->n);
1292}
1293#endif
1294
1295#if FEATURE_FIND_CMIN
1296static int
1297pri_cmin(struct arg *arg)
1298{
1299	struct narg *n = arg->extra.p;
1300	return n->cmp((start.tv_sec - arg->st->st_ctime) / 60, n->n);
1301}
1302#endif
1303
1304static int
1305get_depth(struct findhist *hist)
1306{
1307	int d = 0;
1308	while (hist) {
1309		d++;
1310		hist = hist->next;
1311	}
1312	return d;
1313}
1314
1315static void
1316find(char *path, struct findhist *hist)
1317{
1318	struct stat st;
1319	DIR *dir;
1320	struct dirent *de;
1321	struct findhist *f, cur;
1322	size_t namelen, pathcap = 0, len;
1323	struct arg arg = { path, &st, { NULL } };
1324	char *p, *pathbuf = NULL;
1325	int depth = get_depth(hist);
1326
1327	if (gflags.quit)
1328		return;
1329
1330	len = strlen(path) + 2; /* \0 and '/' */
1331
1332	if (do_stat(path, &st, hist) < 0) {
1333		weprintf("failed to stat %s:", path);
1334		gflags.ret = 1;
1335		return;
1336	}
1337
1338	gflags.prune = 0;
1339
1340	if (gflags.maxdepth >= 0 && depth > gflags.maxdepth)
1341		return;
1342
1343	if (gflags.mindepth < 0 || depth >= gflags.mindepth) {
1344		/* don't eval now iff we will hit the eval at the bottom which means
1345		 * 1. we are a directory 2. we have -depth 3. we don't have -xdev or we are
1346		 * on same device (so most of the time we eval here) */
1347		if (!S_ISDIR(st.st_mode) ||
1348		    !gflags.depth        ||
1349		    (gflags.xdev && hist && st.st_dev != hist->dev))
1350			eval(root, &arg);
1351	}
1352
1353	if (gflags.maxdepth >= 0 && depth >= gflags.maxdepth) {
1354		if (gflags.depth && (gflags.mindepth < 0 || depth >= gflags.mindepth)) {
1355			if (S_ISDIR(st.st_mode) &&
1356			    (!gflags.xdev || !hist || st.st_dev == hist->dev))
1357				eval(root, &arg);
1358		}
1359		return;
1360	}
1361
1362	if (!S_ISDIR(st.st_mode)                          ||
1363	    gflags.prune                                  ||
1364	    (gflags.xdev && hist && st.st_dev != hist->dev))
1365		return;
1366
1367	for (f = hist; f; f = f->next) {
1368		if (f->dev == st.st_dev && f->ino == st.st_ino) {
1369			weprintf("loop detected '%s' is '%s'\n", path, f->path);
1370			gflags.ret = 1;
1371			return;
1372		}
1373	}
1374	cur.next = hist;
1375	cur.path = path;
1376	cur.dev  = st.st_dev;
1377	cur.ino  = st.st_ino;
1378
1379	if (!(dir = opendir(path))) {
1380		weprintf("failed to opendir %s:", path);
1381		gflags.ret = 1;
1382		/* should we just ignore this since we hit an error? */
1383		if (gflags.depth && (gflags.mindepth < 0 || depth >= gflags.mindepth))
1384			eval(root, &arg);
1385		return;
1386	}
1387
1388	while (errno = 0, (de = readdir(dir))) {
1389		if (gflags.quit)
1390			break;
1391		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
1392			continue;
1393		namelen = strlen(de->d_name);
1394		if (len + namelen > pathcap) {
1395			pathcap = len + namelen;
1396			pathbuf = erealloc(pathbuf, pathcap);
1397		}
1398		p = pathbuf + estrlcpy(pathbuf, path, pathcap);
1399		if (*--p != '/')
1400			estrlcat(pathbuf, "/", pathcap);
1401		estrlcat(pathbuf, de->d_name, pathcap);
1402		find(pathbuf, &cur);
1403	}
1404	free(pathbuf);
1405	if (errno) {
1406		weprintf("readdir %s:", path);
1407		gflags.ret = 1;
1408		closedir(dir);
1409		return;
1410	}
1411	closedir(dir);
1412
1413	if (gflags.depth && (gflags.mindepth < 0 || depth >= gflags.mindepth))
1414		eval(root, &arg);
1415}
1416
1417static void
1418usage(void)
1419{
1420	eprintf("usage: %s [-H | -L] path ... [expression ...]\n", argv0);
1421}
1422
1423// ?man find: search for files
1424// ?man arguments: path ... [expression ...
1425// ?man search for files in a directory hierarchy
1426int
1427main(int argc, char **argv)
1428{
1429	char **paths;
1430	int npaths;
1431	struct tok *t;
1432
1433	gflags.maxdepth = -1;
1434	gflags.mindepth = -1;
1435
1436	ARGBEGIN {
1437	// ?man -H: specify option flag
1438	case 'H':
1439		gflags.h = 1;
1440		gflags.l = 0;
1441		break;
1442	// ?man -L: specify option flag
1443	case 'L':
1444		gflags.l = 1;
1445		gflags.h = 0;
1446		break;
1447	default:
1448		usage();
1449	} ARGEND
1450
1451	paths = argv;
1452
1453	for (; *argv && **argv != '-' && strcmp(*argv, "!") && strcmp(*argv, "("); argv++)
1454		;
1455
1456	if (!(npaths = argv - paths))
1457		eprintf("must specify a path\n");
1458
1459	parse(argc - npaths, argv);
1460
1461	/* calculate number of bytes in environ for -exec {} + ARG_MAX avoidance
1462	 * libc implementation defined whether null bytes, pointers, and alignment
1463	 * are counted, so count them */
1464	for (argv = environ; *argv; argv++)
1465		envlen += strlen(*argv) + 1 + sizeof(*argv);
1466
1467	if ((argmax = sysconf(_SC_ARG_MAX)) == (size_t)-1)
1468		argmax = _POSIX_ARG_MAX;
1469
1470	if (clock_gettime(CLOCK_REALTIME, &start) < 0)
1471		weprintf("clock_gettime() failed:");
1472
1473	while (npaths--)
1474		find(*paths++, NULL);
1475
1476	for (t = toks; t->type != END; t++)
1477		if (t->type == PRIM && t->u.pinfo->freearg)
1478			t->u.pinfo->freearg(t->extra);
1479	free(toks);
1480
1481	gflags.ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
1482
1483	return gflags.ret;
1484}