find.c 24 KB

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