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