toybox/toys/posix/find.c
<<
>>
Prefs
   1/* find.c - Search directories for matching files.
   2 *
   3 * Copyright 2014 Rob Landley <rob@landley.net>
   4 *
   5 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.c
   6 *
   7 * Our "unspecified" behavior for no paths is to use "."
   8 * Parentheses can only stack 4096 deep
   9 * Not treating two {} as an error, but only using last
  10 * TODO: -context
  11
  12USE_FIND(NEWTOY(find, "?^HL[-HL]", TOYFLAG_USR|TOYFLAG_BIN))
  13
  14config FIND
  15  bool "find"
  16  default y
  17  help
  18    usage: find [-HL] [DIR...] [<options>]
  19
  20    Search directories for matching files.
  21    Default: search ".", match all, -print matches.
  22
  23    -H  Follow command line symlinks         -L  Follow all symlinks
  24
  25    Match filters:
  26    -name  PATTERN   filename with wildcards   -iname      ignore case -name
  27    -path  PATTERN   path name with wildcards  -ipath      ignore case -path
  28    -user  UNAME     belongs to user UNAME     -nouser     user ID not known
  29    -group GROUP     belongs to group GROUP    -nogroup    group ID not known
  30    -perm  [-/]MODE  permissions (-=min /=any) -prune      ignore dir contents
  31    -size  N[c]      512 byte blocks (c=bytes) -xdev       only this filesystem
  32    -links N         hardlink count            -atime N[u] accessed N units ago
  33    -ctime N[u]      created N units ago       -mtime N[u] modified N units ago
  34    -inum N          inode number N            -empty      empty files and dirs
  35    -true            always true               -false      always false
  36    -context PATTERN security context          -executable access(X_OK) perm+ACL
  37    -samefile FILE   hardlink to FILE          -quit       exit immediately
  38    -depth           ignore contents of dir    -maxdepth N at most N dirs down
  39    -newer FILE      newer mtime than FILE     -mindepth N at least N dirs down
  40    -newerXY FILE    X=acm time > FILE's Y=acm time (Y=t: FILE is literal time)
  41    -type [bcdflps]  type is (block, char, dir, file, symlink, pipe, socket)
  42
  43    Numbers N may be prefixed by a - (less than) or + (greater than). Units for
  44    -Xtime are d (days, default), h (hours), m (minutes), or s (seconds).
  45
  46    Combine matches with:
  47    !, -a, -o, ( )    not, and, or, group expressions
  48
  49    Actions:
  50    -print  Print match with newline  -print0        Print match with null
  51    -exec   Run command with path     -execdir       Run command in file's dir
  52    -ok     Ask before exec           -okdir         Ask before execdir
  53    -delete Remove matching file/dir  -printf FORMAT Print using format string
  54
  55    Commands substitute "{}" with matched file. End with ";" to run each file,
  56    or "+" (next argument after "{}") to collect and run with multiple files.
  57
  58    -printf FORMAT characters are \ escapes and:
  59    %b  512 byte blocks used
  60    %f  basename            %g  textual gid          %G  numeric gid
  61    %i  decimal inode       %l  target of symlink    %m  octal mode
  62    %M  ls format type/mode %p  path to file         %P  path to file minus DIR
  63    %s  size in bytes       %T@ mod time as unixtime
  64    %u  username            %U  numeric uid          %Z  security context
  65*/
  66
  67#define FOR_find
  68#include "toys.h"
  69
  70GLOBALS(
  71  char **filter;
  72  struct double_list *argdata;
  73  int topdir, xdev, depth;
  74  time_t now;
  75  long max_bytes;
  76  char *start;
  77)
  78
  79struct execdir_data {
  80  struct execdir_data *next;
  81
  82  int namecount;
  83  struct double_list *names;
  84};
  85
  86// None of this can go in TT because you can have more than one -exec
  87struct exec_range {
  88  char *next, *prev;  // layout compatible with struct double_list
  89
  90  int dir, plus, arglen, argsize, curly;
  91  char **argstart;
  92  struct execdir_data exec, *execdir;
  93};
  94
  95// Perform pending -exec (if any)
  96static int flush_exec(struct dirtree *new, struct exec_range *aa)
  97{
  98  struct execdir_data *bb = aa->execdir ? aa->execdir : &aa->exec;
  99  char **newargs;
 100  int rc, revert = 0;
 101
 102  if (!bb->namecount) return 0;
 103
 104  dlist_terminate(bb->names);
 105
 106  // switch to directory for -execdir, or back to top if we have an -execdir
 107  // _and_ a normal -exec, or are at top of tree in -execdir
 108  if (TT.topdir != -1) {
 109    if (aa->dir && new && new->parent) {
 110      revert++;
 111      rc = fchdir(new->parent->dirfd);
 112    } else rc = fchdir(TT.topdir);
 113    if (rc) {
 114      perror_msg_raw(revert ? new->name : ".");
 115
 116      return rc;
 117    }
 118  }
 119
 120  // execdir: accumulated execs in this directory's children.
 121  newargs = xmalloc(sizeof(char *)*(aa->arglen+bb->namecount+1));
 122  if (aa->curly < 0) {
 123    memcpy(newargs, aa->argstart, sizeof(char *)*aa->arglen);
 124    newargs[aa->arglen] = 0;
 125  } else {
 126    int pos = aa->curly, rest = aa->arglen - aa->curly;
 127    struct double_list *dl;
 128
 129    // Collate argument list
 130    memcpy(newargs, aa->argstart, sizeof(char *)*pos);
 131    for (dl = bb->names; dl; dl = dl->next) newargs[pos++] = dl->data;
 132    rest = aa->arglen - aa->curly - 1;
 133    memcpy(newargs+pos, aa->argstart+aa->curly+1, sizeof(char *)*rest);
 134    newargs[pos+rest] = 0;
 135  }
 136
 137  rc = xrun(newargs);
 138
 139  llist_traverse(bb->names, llist_free_double);
 140  bb->names = 0;
 141  bb->namecount = 0;
 142
 143  if (revert) revert = fchdir(TT.topdir);
 144
 145  return rc;
 146}
 147
 148// Return numeric value with explicit sign
 149static int compare_numsign(long val, long units, char *str)
 150{
 151  char sign = 0;
 152  long myval;
 153
 154  if (*str == '+' || *str == '-') sign = *(str++);
 155  else if (!isdigit(*str)) error_exit("%s not [+-]N", str);
 156  myval = atolx(str);
 157  if (units && isdigit(str[strlen(str)-1])) myval *= units;
 158
 159  if (sign == '+') return val > myval;
 160  if (sign == '-') return val < myval;
 161  return val == myval;
 162}
 163
 164static void do_print(struct dirtree *new, char c)
 165{
 166  char *s=dirtree_path(new, 0);
 167
 168  xprintf("%s%c", s, c);
 169  free(s);
 170}
 171
 172// Descend or ascend -execdir + directory level
 173static void execdir(struct dirtree *new, int flush)
 174{
 175  struct double_list *dl;
 176  struct exec_range *aa;
 177  struct execdir_data *bb;
 178
 179  if (new && TT.topdir == -1) return;
 180
 181  for (dl = TT.argdata; dl; dl = dl->next) {
 182    if (dl->prev != (void *)1) continue;
 183    aa = (void *)dl;
 184    if (!aa->plus || (new && !aa->dir)) continue;
 185
 186    if (flush) {
 187
 188      // Flush pending "-execdir +" instances for this dir
 189      // or flush everything for -exec at top
 190      toys.exitval |= flush_exec(new, aa);
 191
 192      // pop per-directory struct
 193      if ((bb = aa->execdir)) {
 194        aa->execdir = bb->next;
 195        free(bb);
 196      }
 197    } else if (aa->dir) {
 198
 199      // Push new per-directory struct for -execdir/okdir + codepath. (Can't
 200      // use new->extra because command line may have multiple -execdir)
 201      bb = xzalloc(sizeof(struct execdir_data));
 202      bb->next = aa->execdir;
 203      aa->execdir = bb;
 204    }
 205  }
 206}
 207
 208// Call this with 0 for first pass argument parsing and syntax checking (which
 209// populates argdata). Later commands traverse argdata (in order) when they
 210// need "do once" results.
 211static int do_find(struct dirtree *new)
 212{
 213  int pcount = 0, print = 0, not = 0, active = !!new, test = active, recurse;
 214  struct double_list *argdata = TT.argdata;
 215  char *s, **ss, *arg;
 216
 217  recurse = DIRTREE_STATLESS|DIRTREE_COMEAGAIN|
 218    (DIRTREE_SYMFOLLOW*!!(toys.optflags&FLAG_L));
 219
 220  // skip . and .. below topdir, handle -xdev and -depth
 221  if (new) {
 222    // Handle stat failures first.
 223    if (new->again&2) {
 224      if (!new->parent || errno != ENOENT) {
 225        perror_msg("'%s'", s = dirtree_path(new, 0));
 226        free(s);
 227      }
 228      return 0;
 229    }
 230    if (new->parent) {
 231      if (!dirtree_notdotdot(new)) return 0;
 232      if (TT.xdev && new->st.st_dev != new->parent->st.st_dev) recurse = 0;
 233    } else TT.start = new->name;
 234
 235    if (S_ISDIR(new->st.st_mode)) {
 236      // Descending into new directory
 237      if (!new->again) {
 238        struct dirtree *n;
 239
 240        for (n = new->parent; n; n = n->parent) {
 241          if (same_file(&n->st, &new->st)) {
 242            error_msg("'%s': loop detected", s = dirtree_path(new, 0));
 243            free(s);
 244
 245            return 0;
 246          }
 247        }
 248
 249        if (TT.depth) {
 250          execdir(new, 0);
 251
 252          return recurse;
 253        }
 254      // Done with directory (COMEAGAIN call)
 255      } else {
 256        execdir(new, 1);
 257        recurse = 0;
 258        if (!TT.depth) return 0;
 259      }
 260    }
 261  }
 262
 263  // pcount: parentheses stack depth (using toybuf bytes, 4096 max depth)
 264  // test: result of most recent test
 265  // active: if 0 don't perform tests
 266  // not: a pending ! applies to this test (only set if performing tests)
 267  // print: saw one of print/ok/exec, no need for default -print
 268
 269  if (TT.filter) for (ss = TT.filter; (s = *ss); ss++) {
 270    int check = active && test;
 271
 272    // if (!new) perform one-time setup, if (check) perform test
 273
 274    // handle ! ( ) using toybuf as a stack
 275    if (*s != '-') {
 276      if (s[1]) goto error;
 277
 278      if (*s == '!') {
 279        // Don't invert if we're not making a decision
 280        if (check) not = !not;
 281
 282      // Save old "not" and "active" on toybuf stack.
 283      // Deactivate this parenthetical if !test
 284      // Note: test value should never change while !active
 285      } else if (*s == '(') {
 286        if (pcount == sizeof(toybuf)) goto error;
 287        toybuf[pcount++] = not+(active<<1);
 288        if (!check) active = 0;
 289        not = 0;
 290
 291      // Pop status, apply deferred not to test
 292      } else if (*s == ')') {
 293        if (--pcount < 0) goto error;
 294        // Pop active state, apply deferred not (which was only set if checking)
 295        active = (toybuf[pcount]>>1)&1;
 296        if (active && (toybuf[pcount]&1)) test = !test;
 297        not = 0;
 298      } else goto error;
 299
 300      continue;
 301    } else s++;
 302
 303    if (!strcmp(s, "xdev")) TT.xdev = 1;
 304    else if (!strcmp(s, "delete")) {
 305      // Delete forces depth first
 306      TT.depth = 1;
 307      if (new && check)
 308        test = !unlinkat(dirtree_parentfd(new), new->name,
 309          S_ISDIR(new->st.st_mode) ? AT_REMOVEDIR : 0);
 310    } else if (!strcmp(s, "depth") || !strcmp(s, "d")) TT.depth = 1;
 311    else if (!strcmp(s, "o") || !strcmp(s, "or")) {
 312      if (not) goto error;
 313      if (active) {
 314        if (!test) test = 1;
 315        else active = 0;     // decision has been made until next ")"
 316      }
 317    } else if (!strcmp(s, "not")) {
 318      if (check) not = !not;
 319      continue;
 320    } else if (!strcmp(s, "true")) {
 321      if (check) test = 1;
 322    } else if (!strcmp(s, "false")) {
 323      if (check) test = 0;
 324
 325    // Mostly ignore NOP argument
 326    } else if (!strcmp(s, "a") || !strcmp(s, "and") || !strcmp(s, "noleaf")) {
 327      if (not) goto error;
 328
 329    } else if (!strcmp(s, "print") || !strcmp("print0", s)) {
 330      print++;
 331      if (check) do_print(new, s[5] ? 0 : '\n');
 332
 333    } else if (!strcmp(s, "empty")) {
 334      if (check) {
 335        // Alas neither st_size nor st_blocks reliably show an empty directory
 336        if (S_ISDIR(new->st.st_mode)) {
 337          int fd = openat(dirtree_parentfd(new), new->name, O_RDONLY);
 338          DIR *dfd = fdopendir(fd);
 339          struct dirent *de = (void *)1;
 340          if (dfd) {
 341            while ((de = readdir(dfd)) && isdotdot(de->d_name));
 342            closedir(dfd);
 343          }
 344          if (de) test = 0;
 345        } else if (S_ISREG(new->st.st_mode)) {
 346          if (new->st.st_size) test = 0;
 347        } else test = 0;
 348      }
 349    } else if (!strcmp(s, "nouser")) {
 350      if (check && bufgetpwuid(new->st.st_uid)) test = 0;
 351    } else if (!strcmp(s, "nogroup")) {
 352      if (check && bufgetgrgid(new->st.st_gid)) test = 0;
 353    } else if (!strcmp(s, "prune")) {
 354      if (check && S_ISDIR(new->st.st_mode) && !TT.depth) recurse = 0;
 355    } else if (!strcmp(s, "executable")) {
 356      if (check && faccessat(dirtree_parentfd(new), new->name,X_OK,0)) test = 0;
 357    } else if (!strcmp(s, "quit")) {
 358      if (check) {
 359        execdir(0, 1);
 360        xexit();
 361      }
 362
 363    // Remaining filters take an argument
 364    } else {
 365      arg = *++ss;
 366      if (!strcmp(s, "name") || !strcmp(s, "iname")
 367        || !strcmp(s, "wholename") || !strcmp(s, "iwholename")
 368        || !strcmp(s, "path") || !strcmp(s, "ipath")
 369        || !strcmp(s, "lname") || !strcmp(s, "ilname"))
 370      {
 371        int i = (*s == 'i'), is_path = (s[i] != 'n');
 372        char *path = 0, *name = new ? new->name : arg;
 373
 374        // Handle path expansion and case flattening
 375        if (new && s[i] == 'l')
 376          name = path = xreadlinkat(dirtree_parentfd(new), new->name);
 377        else if (new && is_path) name = path = dirtree_path(new, 0);
 378        if (i) {
 379          if ((check || !new) && name) name = strlower(name);
 380          if (!new) dlist_add(&TT.argdata, name);
 381          else arg = ((struct double_list *)llist_pop(&argdata))->data;
 382        }
 383
 384        if (check) {
 385          test = !fnmatch(arg, path ? name : basename(name),
 386            FNM_PATHNAME*(!is_path));
 387          if (i) free(name);
 388        }
 389        free(path);
 390      } else if (!CFG_TOYBOX_LSM_NONE && !strcmp(s, "context")) {
 391        if (check) {
 392          char *path = dirtree_path(new, 0), *context;
 393
 394          if (lsm_get_context(path, &context) != -1) {
 395            test = !fnmatch(arg, context, 0);
 396            free(context);
 397          } else test = 0;
 398          free(path);
 399        }
 400      } else if (!strcmp(s, "perm")) {
 401        if (check) {
 402          int match_min = *arg == '-', match_any = *arg == '/';
 403          mode_t m1 = string_to_mode(arg+(match_min || match_any), 0),
 404                 m2 = new->st.st_mode & 07777;
 405
 406          if (match_min || match_any) m2 &= m1;
 407          test = match_any ? !m1 || m2 : m1 == m2;
 408        }
 409      } else if (!strcmp(s, "type")) {
 410        if (check) {
 411          int types[] = {S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFIFO,
 412                         S_IFREG, S_IFSOCK}, i;
 413
 414          for (; *arg; arg++) {
 415            if (*arg == ',') continue;
 416            i = stridx("bcdlpfs", *arg);
 417            if (i<0) error_exit("bad -type '%c'", *arg);
 418            if ((new->st.st_mode & S_IFMT) == types[i]) break;
 419          }
 420          test = *arg;
 421        }
 422
 423      } else if (strchr("acm", *s)
 424        && (!strcmp(s+1, "time") || !strcmp(s+1, "min")))
 425      {
 426        if (check) {
 427          time_t thyme = (int []){new->st.st_atime, new->st.st_ctime,
 428                                  new->st.st_mtime}[stridx("acm", *s)];
 429          int len = strlen(arg), uu, units = (s[1]=='m') ? 60 : 86400;
 430
 431          if (len && -1!=(uu = stridx("dhms",tolower(arg[len-1])))) {
 432            arg = xstrdup(arg);
 433            arg[--len] = 0;
 434            units = (int []){86400, 3600, 60, 1}[uu];
 435          }
 436          test = compare_numsign(TT.now - thyme, units, arg);
 437          if (*ss != arg) free(arg);
 438        }
 439      } else if (!strcmp(s, "size")) {
 440        if (check) test = compare_numsign(new->st.st_size, 512, arg);
 441      } else if (!strcmp(s, "links")) {
 442        if (check) test = compare_numsign(new->st.st_nlink, 0, arg);
 443      } else if (!strcmp(s, "inum")) {
 444        if (check) test = compare_numsign(new->st.st_ino, 0, arg);
 445      } else if (!strcmp(s, "mindepth") || !strcmp(s, "maxdepth")) {
 446        if (check) {
 447          struct dirtree *dt = new;
 448          int i = 0, d = atolx(arg);
 449
 450          while ((dt = dt->parent)) i++;
 451          if (s[1] == 'i') {
 452            test = i >= d;
 453            if (i == d && not) recurse = 0;
 454          } else {
 455            test = i <= d;
 456            if (i == d && !not) recurse = 0;
 457          }
 458        }
 459      } else if (!strcmp(s, "user") || !strcmp(s, "group")
 460              || !strncmp(s, "newer", 5) || !strcmp(s, "samefile"))
 461      {
 462        int macoff[] = {offsetof(struct stat, st_mtim),
 463          offsetof(struct stat, st_atim), offsetof(struct stat, st_ctim)};
 464        struct {
 465          void *next, *prev;
 466          union {
 467            uid_t uid;
 468            gid_t gid;
 469            struct timespec tm;
 470            struct dev_ino di;
 471          };
 472        } *udl;
 473        struct stat st;
 474
 475        if (!new) {
 476          if (arg) {
 477            udl = xmalloc(sizeof(*udl));
 478            dlist_add_nomalloc(&TT.argdata, (void *)udl);
 479
 480            if (strchr("sn", *s)) {
 481              if (*s=='n' && s[5] && (s[7] || !strchr("Bmac", s[5]) || !strchr("tBmac", s[6])))
 482                goto error;
 483              if (*s=='s' || !s[5] || s[6]!='t') {
 484                xstat(arg, &st);
 485                if (*s=='s') udl->di.dev = st.st_dev, udl->di.ino = st.st_ino;
 486                else udl->tm = *(struct timespec *)(((char *)&st)
 487                               + macoff[!s[5] ? 0 : stridx("ac", s[6])+1]);
 488              } else if (s[6] == 't') {
 489                unsigned nano;
 490
 491                xparsedate(arg, &(udl->tm.tv_sec), &nano, 1);
 492                udl->tm.tv_nsec = nano;
 493              }
 494            } else if (*s == 'u') udl->uid = xgetuid(arg);
 495            else udl->gid = xgetgid(arg);
 496          }
 497        } else {
 498          udl = (void *)llist_pop(&argdata);
 499          if (check) {
 500            if (*s == 'u') test = new->st.st_uid == udl->uid;
 501            else if (*s == 'g') test = new->st.st_gid == udl->gid;
 502            else if (*s == 's') test = same_dev_ino(&new->st, &udl->di);
 503            else {
 504              struct timespec *tm = (void *)(((char *)&new->st)
 505                + macoff[!s[5] ? 0 : stridx("ac", s[5])+1]);
 506
 507              if (s[5] == 'B') test = 0;
 508              else test = (tm->tv_sec == udl->tm.tv_sec)
 509                ? tm->tv_nsec > udl->tm.tv_nsec
 510                : tm->tv_sec > udl->tm.tv_sec;
 511            }
 512          }
 513        }
 514      } else if (!strcmp(s, "exec") || !strcmp("ok", s)
 515              || !strcmp(s, "execdir") || !strcmp(s, "okdir"))
 516      {
 517        struct exec_range *aa;
 518
 519        print++;
 520
 521        // Initial argument parsing pass
 522        if (!new) {
 523          int len;
 524
 525          // catch "-exec" with no args and "-exec \;"
 526          if (!arg || !strcmp(arg, ";")) error_exit("'%s' needs 1 arg", s);
 527
 528          dlist_add_nomalloc(&TT.argdata, (void *)(aa = xzalloc(sizeof(*aa))));
 529          aa->argstart = ss;
 530          aa->curly = -1;
 531
 532          // Record command line arguments to -exec
 533          for (len = 0; ss[len]; len++) {
 534            if (!strcmp(ss[len], ";")) break;
 535            else if (!strcmp(ss[len], "{}")) {
 536              aa->curly = len;
 537              if (ss[len+1] && !strcmp(ss[len+1], "+")) {
 538                aa->plus++;
 539                len++;
 540                break;
 541              }
 542            } else aa->argsize += sizeof(char *) + strlen(ss[len]) + 1;
 543          }
 544          if (!ss[len]) error_exit("-exec without %s",
 545            aa->curly!=-1 ? "\\;" : "{}");
 546          ss += len;
 547          aa->arglen = len;
 548          aa->dir = !!strchr(s, 'd');
 549          if (TT.topdir == -1) TT.topdir = xopenro(".");
 550
 551        // collect names and execute commands
 552        } else {
 553          char *name;
 554          struct execdir_data *bb;
 555
 556          // Grab command line exec argument list
 557          aa = (void *)llist_pop(&argdata);
 558          ss += aa->arglen;
 559
 560          if (!check) goto cont;
 561          // name is always a new malloc, so we can always free it.
 562          name = aa->dir ? xstrdup(new->name) : dirtree_path(new, 0);
 563
 564          if (*s == 'o') {
 565            fprintf(stderr, "[%s] %s", arg, name);
 566            if (!(test = yesno(0))) {
 567              free(name);
 568              goto cont;
 569            }
 570          }
 571
 572          // Add next name to list (global list without -dir, local with)
 573          bb = aa->execdir ? aa->execdir : &aa->exec;
 574          dlist_add(&bb->names, name);
 575          bb->namecount++;
 576
 577          // -exec + collates and saves result in exitval
 578          if (aa->plus) {
 579            // Mark entry so COMEAGAIN can call flush_exec() in parent.
 580            // This is never a valid pointer value for prev to have otherwise
 581            // Done here vs argument parsing pass so it's after dlist_terminate
 582            aa->prev = (void *)1;
 583
 584            // Flush if the child's environment space gets too large.
 585            // Linux caps individual arguments/variables at 131072 bytes,
 586            // so this counter can't wrap.
 587            if ((aa->plus += sizeof(char *)+strlen(name)+1) > TT.max_bytes) {
 588              aa->plus = 1;
 589              toys.exitval |= flush_exec(new, aa);
 590            }
 591          } else test = !flush_exec(new, aa);
 592        }
 593
 594        // Argument consumed, skip the check.
 595        goto cont;
 596      } else if (!strcmp(s, "printf")) {
 597        char *fmt, *ff, next[32], buf[64], ch;
 598        long ll;
 599        int len;
 600
 601        print++;
 602        if (check) for (fmt = arg; *fmt; fmt++) {
 603          // Print the parts that aren't escapes
 604          if (*fmt == '\\') {
 605            unsigned u;
 606
 607            if (fmt[1] == 'c') break;
 608            if ((u = unescape2(&fmt, 0))<128) putchar(u);
 609            else printf("%.*s", (int)wcrtomb(buf, u, 0), buf);
 610            fmt--;
 611          } else if (*fmt != '%') putchar(*fmt);
 612          else if (*++fmt == '%') putchar('%');
 613          else {
 614            fmt = next_printf(ff = fmt-1, 0);
 615            if ((len = fmt-ff)>28) error_exit("bad %.*s", len+1, ff);
 616            memcpy(next, ff, len);
 617            ff = 0;
 618            ch = *fmt;
 619
 620            // long long is its own stack size on LP64, so handle seperately
 621            if (ch == 'i' || ch == 's') {
 622              strcpy(next+len, "lld");
 623              printf(next, (ch == 'i') ? (long long)new->st.st_ino
 624                : (long long)new->st.st_size);
 625            } else {
 626
 627              // LP64 says these are all a single "long" argument to printf
 628              strcpy(next+len, "s");
 629              if (ch == 'G') next[len] = 'd', ll = new->st.st_gid;
 630              else if (ch == 'm') next[len] = 'o', ll = new->st.st_mode&~S_IFMT;
 631              else if (ch == 'U') next[len] = 'd', ll = new->st.st_uid;
 632              else if (ch == 'f') ll = (long)new->name;
 633              else if (ch == 'g') ll = (long)getgroupname(new->st.st_gid);
 634              else if (ch == 'u') ll = (long)getusername(new->st.st_uid);
 635              else if (ch == 'l') {
 636                ll = (long)(ff = xreadlinkat(dirtree_parentfd(new), new->name));
 637                if (!ll) ll = (long)"";
 638              } else if (ch == 'M') {
 639                mode_to_string(new->st.st_mode, buf);
 640                ll = (long)buf;
 641              } else if (ch == 'P') {
 642                ch = *TT.start;
 643                *TT.start = 0;
 644                ll = (long)(ff = dirtree_path(new, 0));
 645                *TT.start = ch;
 646              } else if (ch == 'p') ll = (long)(ff = dirtree_path(new, 0));
 647              else if (ch == 'T') {
 648                if (*++fmt!='@') error_exit("bad -printf %%T: %%T%c", *fmt);
 649                sprintf(buf, "%lld.%ld", (long long)new->st.st_mtim.tv_sec,
 650                             new->st.st_mtim.tv_nsec);
 651                ll = (long)buf;
 652              } else if (ch == 'Z') {
 653                char *path = dirtree_path(new, 0);
 654
 655                ll = (lsm_get_context(path, &ff) != -1) ? (long)ff : (long)"?";
 656                free(path);
 657              } else error_exit("bad -printf %%%c", ch);
 658
 659              printf(next, ll);
 660              free(ff);
 661            }
 662          }
 663        }
 664      } else goto error;
 665
 666      // This test can go at the end because we do a syntax checking
 667      // pass first. Putting it here gets the error message (-unknown
 668      // vs -known noarg) right.
 669      if (!check && !arg) error_exit("'%s' needs 1 arg", s-1);
 670    }
 671cont:
 672    // Apply pending "!" to result
 673    if (active && not) test = !test;
 674    not = 0;
 675  }
 676
 677  if (new) {
 678    // If there was no action, print
 679    if (!print && test) do_print(new, '\n');
 680
 681    if (S_ISDIR(new->st.st_mode)) execdir(new, 0);
 682
 683  } else dlist_terminate(TT.argdata);
 684
 685  return recurse;
 686
 687error:
 688  error_exit("bad arg '%s'", *ss);
 689}
 690
 691void find_main(void)
 692{
 693  int i, len;
 694  char **ss = (char *[]){"."};
 695
 696  TT.topdir = -1;
 697  TT.max_bytes = sysconf(_SC_ARG_MAX) - environ_bytes();
 698
 699  // Distinguish paths from filters
 700  for (len = 0; toys.optargs[len]; len++)
 701    if (*toys.optargs[len] && strchr("-!(", *toys.optargs[len])) break;
 702  TT.filter = toys.optargs+len;
 703
 704  // use "." if no paths
 705  if (len) ss = toys.optargs;
 706  else len = 1;
 707
 708  // first pass argument parsing, verify args match up, handle "evaluate once"
 709  TT.now = time(0);
 710  do_find(0);
 711
 712  // Loop through paths
 713  for (i = 0; i < len; i++)
 714    dirtree_flagread(ss[i],
 715      DIRTREE_STATLESS|(DIRTREE_SYMFOLLOW*!!(toys.optflags&(FLAG_H|FLAG_L))),
 716      do_find);
 717
 718  execdir(0, 1);
 719
 720  if (CFG_TOYBOX_FREE) {
 721    close(TT.topdir);
 722    llist_traverse(TT.argdata, free);
 723  }
 724}
 725