find.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * Mini find implementation for busybox
  4. *
  5. * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
  6. *
  7. * Reworked by David Douthitt <n9ubh@callsign.net> and
  8. * Matt Kraai <kraai@alumni.carnegiemellon.edu>.
  9. *
  10. * Licensed under the GPL version 2, see the file LICENSE in this tarball.
  11. */
  12. /* findutils-4.1.20:
  13. *
  14. * # find file.txt -exec 'echo {}' '{} {}' ';'
  15. * find: echo file.txt: No such file or directory
  16. * # find file.txt -exec 'echo' '{} {}' '; '
  17. * find: missing argument to `-exec'
  18. * # find file.txt -exec 'echo {}' '{} {}' ';' junk
  19. * find: paths must precede expression
  20. * # find file.txt -exec 'echo {}' '{} {}' ';' junk ';'
  21. * find: paths must precede expression
  22. * # find file.txt -exec 'echo' '{} {}' ';'
  23. * file.txt file.txt
  24. * (strace: execve("/bin/echo", ["echo", "file.txt file.txt"], [ 30 vars ]))
  25. * # find file.txt -exec 'echo' '{} {}' ';' -print -exec pwd ';'
  26. * file.txt file.txt
  27. * file.txt
  28. * /tmp
  29. * # find -name '*.c' -o -name '*.h'
  30. * [shows files, *.c and *.h intermixed]
  31. * # find file.txt -name '*f*' -o -name '*t*'
  32. * file.txt
  33. * # find file.txt -name '*z*' -o -name '*t*'
  34. * file.txt
  35. * # find file.txt -name '*f*' -o -name '*z*'
  36. * file.txt
  37. *
  38. * # find t z -name '*t*' -print -o -name '*z*'
  39. * t
  40. * # find t z t z -name '*t*' -o -name '*z*' -print
  41. * z
  42. * z
  43. * # find t z t z '(' -name '*t*' -o -name '*z*' ')' -o -print
  44. * (no output)
  45. */
  46. #include "busybox.h"
  47. #include <fnmatch.h>
  48. USE_FEATURE_FIND_XDEV(static dev_t *xdev_dev;)
  49. USE_FEATURE_FIND_XDEV(static int xdev_count;)
  50. typedef int (*action_fp)(const char *fileName, struct stat *statbuf, void *);
  51. typedef struct {
  52. action_fp f;
  53. } action;
  54. #define ACTS(name, arg...) typedef struct { action a; arg; } action_##name;
  55. #define ACTF(name) static int func_##name(const char *fileName, struct stat *statbuf, action_##name* ap)
  56. ACTS(print)
  57. ACTS(name, char *pattern;)
  58. USE_FEATURE_FIND_PRINT0(ACTS(print0))
  59. USE_FEATURE_FIND_TYPE( ACTS(type, int type_mask;))
  60. USE_FEATURE_FIND_PERM( ACTS(perm, char perm_char; int perm_mask;))
  61. USE_FEATURE_FIND_MTIME( ACTS(mtime, char mtime_char; int mtime_days;))
  62. USE_FEATURE_FIND_MMIN( ACTS(mmin, char mmin_char; int mmin_mins;))
  63. USE_FEATURE_FIND_NEWER( ACTS(newer, time_t newer_mtime;))
  64. USE_FEATURE_FIND_INUM( ACTS(inum, ino_t inode_num;))
  65. USE_FEATURE_FIND_EXEC( ACTS(exec, char **exec_argv; int *subst_count; int exec_argc;))
  66. USE_DESKTOP( ACTS(paren, action ***subexpr;))
  67. USE_DESKTOP( ACTS(size, off_t size;))
  68. USE_DESKTOP( ACTS(prune))
  69. static action ***actions;
  70. static int need_print = 1;
  71. static inline int one_char(const char* str, char c)
  72. {
  73. return (str[0] == c && str[1] == '\0');
  74. }
  75. #if ENABLE_FEATURE_FIND_EXEC
  76. static int count_subst(const char *str)
  77. {
  78. int count = 0;
  79. while ((str = strstr(str, "{}"))) {
  80. count++;
  81. str++;
  82. }
  83. return count;
  84. }
  85. static char* subst(const char *src, int count, const char* filename)
  86. {
  87. char *buf, *dst, *end;
  88. int flen = strlen(filename);
  89. /* we replace each '{}' with filename: growth by strlen-2 */
  90. buf = dst = xmalloc(strlen(src) + count*(flen-2) + 1);
  91. while ((end = strstr(src, "{}"))) {
  92. memcpy(dst, src, end - src);
  93. dst += end - src;
  94. src = end + 2;
  95. memcpy(dst, filename, flen);
  96. dst += flen;
  97. }
  98. strcpy(dst, src);
  99. return buf;
  100. }
  101. #endif
  102. static int exec_actions(action ***appp, const char *fileName, struct stat *statbuf)
  103. {
  104. int cur_group;
  105. int cur_action;
  106. int rc = TRUE;
  107. action **app, *ap;
  108. cur_group = -1;
  109. while ((app = appp[++cur_group])) {
  110. cur_action = -1;
  111. do {
  112. ap = app[++cur_action];
  113. } while (ap && (rc = ap->f(fileName, statbuf, ap)));
  114. if (!ap) {
  115. /* all actions in group were successful */
  116. break;
  117. }
  118. }
  119. return rc;
  120. }
  121. ACTF(name)
  122. {
  123. const char *tmp = strrchr(fileName, '/');
  124. if (tmp == NULL)
  125. tmp = fileName;
  126. else
  127. tmp++;
  128. return fnmatch(ap->pattern, tmp, FNM_PERIOD) == 0;
  129. }
  130. #if ENABLE_FEATURE_FIND_TYPE
  131. ACTF(type)
  132. {
  133. return ((statbuf->st_mode & S_IFMT) == ap->type_mask);
  134. }
  135. #endif
  136. #if ENABLE_FEATURE_FIND_PERM
  137. ACTF(perm)
  138. {
  139. return !((isdigit(ap->perm_char) && (statbuf->st_mode & 07777) == ap->perm_mask)
  140. || (ap->perm_char == '-' && (statbuf->st_mode & ap->perm_mask) == ap->perm_mask)
  141. || (ap->perm_char == '+' && (statbuf->st_mode & ap->perm_mask) != 0));
  142. }
  143. #endif
  144. #if ENABLE_FEATURE_FIND_MTIME
  145. ACTF(mtime)
  146. {
  147. time_t file_age = time(NULL) - statbuf->st_mtime;
  148. time_t mtime_secs = ap->mtime_days * 24 * 60 * 60;
  149. return !((isdigit(ap->mtime_char) && file_age >= mtime_secs
  150. && file_age < mtime_secs + 24 * 60 * 60)
  151. || (ap->mtime_char == '+' && file_age >= mtime_secs + 24 * 60 * 60)
  152. || (ap->mtime_char == '-' && file_age < mtime_secs));
  153. }
  154. #endif
  155. #if ENABLE_FEATURE_FIND_MMIN
  156. ACTF(mmin)
  157. {
  158. time_t file_age = time(NULL) - statbuf->st_mtime;
  159. time_t mmin_secs = ap->mmin_mins * 60;
  160. return !((isdigit(ap->mmin_char) && file_age >= mmin_secs
  161. && file_age < mmin_secs + 60)
  162. || (ap->mmin_char == '+' && file_age >= mmin_secs + 60)
  163. || (ap->mmin_char == '-' && file_age < mmin_secs));
  164. }
  165. #endif
  166. #if ENABLE_FEATURE_FIND_NEWER
  167. ACTF(newer)
  168. {
  169. return (ap->newer_mtime >= statbuf->st_mtime);
  170. }
  171. #endif
  172. #if ENABLE_FEATURE_FIND_INUM
  173. ACTF(inum)
  174. {
  175. return (statbuf->st_ino != ap->inode_num);
  176. }
  177. #endif
  178. #if ENABLE_FEATURE_FIND_EXEC
  179. ACTF(exec)
  180. {
  181. int i, rc;
  182. char *argv[ap->exec_argc+1];
  183. for (i = 0; i < ap->exec_argc; i++)
  184. argv[i] = subst(ap->exec_argv[i], ap->subst_count[i], fileName);
  185. argv[i] = NULL; /* terminate the list */
  186. errno = 0;
  187. rc = wait4pid(spawn(argv));
  188. if (errno)
  189. bb_perror_msg("%s", argv[0]);
  190. for (i = 0; i < ap->exec_argc; i++)
  191. free(argv[i]);
  192. return rc == 0; /* return 1 if success */
  193. }
  194. #endif
  195. #if ENABLE_FEATURE_FIND_PRINT0
  196. ACTF(print0)
  197. {
  198. printf("%s%c", fileName, '\0');
  199. return TRUE;
  200. }
  201. #endif
  202. ACTF(print)
  203. {
  204. puts(fileName);
  205. return TRUE;
  206. }
  207. #if ENABLE_DESKTOP
  208. ACTF(paren)
  209. {
  210. return exec_actions(ap->subexpr, fileName, statbuf);
  211. }
  212. /*
  213. * -prune: if -depth is not given, return true and do not descend
  214. * current dir; if -depth is given, return false with no effect.
  215. * Example:
  216. * find dir -name 'asm-*' -prune -o -name '*.[chS]' -print
  217. */
  218. ACTF(prune)
  219. {
  220. return SKIP;
  221. }
  222. ACTF(size)
  223. {
  224. return statbuf->st_size == ap->size;
  225. }
  226. #endif
  227. static int fileAction(const char *fileName, struct stat *statbuf, void* junk, int depth)
  228. {
  229. int rc;
  230. #ifdef CONFIG_FEATURE_FIND_XDEV
  231. if (S_ISDIR(statbuf->st_mode) && xdev_count) {
  232. int i;
  233. for (i = 0; i < xdev_count; i++) {
  234. if (xdev_dev[i] != statbuf->st_dev)
  235. return SKIP;
  236. }
  237. }
  238. #endif
  239. rc = exec_actions(actions, fileName, statbuf);
  240. /* Had no explicit -print[0] or -exec? then print */
  241. if (rc && need_print)
  242. puts(fileName);
  243. /* Cannot return 0: our caller, recursive_action(),
  244. * will perror() and skip dirs (if called on dir) */
  245. return rc == 0 ? TRUE : rc;
  246. }
  247. #if ENABLE_FEATURE_FIND_TYPE
  248. static int find_type(char *type)
  249. {
  250. int mask = 0;
  251. switch (type[0]) {
  252. case 'b':
  253. mask = S_IFBLK;
  254. break;
  255. case 'c':
  256. mask = S_IFCHR;
  257. break;
  258. case 'd':
  259. mask = S_IFDIR;
  260. break;
  261. case 'p':
  262. mask = S_IFIFO;
  263. break;
  264. case 'f':
  265. mask = S_IFREG;
  266. break;
  267. case 'l':
  268. mask = S_IFLNK;
  269. break;
  270. case 's':
  271. mask = S_IFSOCK;
  272. break;
  273. }
  274. if (mask == 0 || type[1] != '\0')
  275. bb_error_msg_and_die(bb_msg_invalid_arg, type, "-type");
  276. return mask;
  277. }
  278. #endif
  279. action*** parse_params(char **argv)
  280. {
  281. action*** appp;
  282. int cur_group = 0;
  283. int cur_action = 0;
  284. action* alloc_action(int sizeof_struct, action_fp f)
  285. {
  286. action *ap;
  287. appp[cur_group] = xrealloc(appp[cur_group], (cur_action+2) * sizeof(*appp));
  288. appp[cur_group][cur_action++] = ap = xmalloc(sizeof_struct);
  289. appp[cur_group][cur_action] = NULL;
  290. ap->f = f;
  291. return ap;
  292. }
  293. #define ALLOC_ACTION(name) (action_##name*)alloc_action(sizeof(action_##name), (action_fp) func_##name)
  294. appp = xzalloc(2 * sizeof(*appp)); /* appp[0],[1] == NULL */
  295. // Actions have side effects and return a true or false value
  296. // We implement: -print, -print0, -exec
  297. // The rest are tests.
  298. // Tests and actions are grouped by operators
  299. // ( expr ) Force precedence
  300. // ! expr True if expr is false
  301. // -not expr Same as ! expr
  302. // expr1 [-a[nd]] expr2 And; expr2 is not evaluated if expr1 is false
  303. // expr1 -o[r] expr2 Or; expr2 is not evaluated if expr1 is true
  304. // expr1 , expr2 List; both expr1 and expr2 are always evaluated
  305. // We implement: (), -a, -o
  306. while (*argv) {
  307. char *arg = argv[0];
  308. char *arg1 = argv[1];
  309. /* --- Operators --- */
  310. if (strcmp(arg, "-a") == 0
  311. USE_DESKTOP(|| strcmp(arg, "-and") == 0)
  312. ) {
  313. /* no special handling required */
  314. }
  315. else if (strcmp(arg, "-o") == 0
  316. USE_DESKTOP(|| strcmp(arg, "-or") == 0)
  317. ) {
  318. /* start new OR group */
  319. cur_group++;
  320. appp = xrealloc(appp, (cur_group+2) * sizeof(*appp));
  321. appp[cur_group] = NULL;
  322. appp[cur_group+1] = NULL;
  323. cur_action = 0;
  324. }
  325. /* --- Tests and actions --- */
  326. else if (strcmp(arg, "-print") == 0) {
  327. need_print = 0;
  328. (void) ALLOC_ACTION(print);
  329. }
  330. #if ENABLE_FEATURE_FIND_PRINT0
  331. else if (strcmp(arg, "-print0") == 0) {
  332. need_print = 0;
  333. (void) ALLOC_ACTION(print0);
  334. }
  335. #endif
  336. else if (strcmp(arg, "-name") == 0) {
  337. action_name *ap;
  338. if (!*++argv)
  339. bb_error_msg_and_die(bb_msg_requires_arg, arg);
  340. ap = ALLOC_ACTION(name);
  341. ap->pattern = arg1;
  342. }
  343. #if ENABLE_FEATURE_FIND_TYPE
  344. else if (strcmp(arg, "-type") == 0) {
  345. action_type *ap;
  346. if (!*++argv)
  347. bb_error_msg_and_die(bb_msg_requires_arg, arg);
  348. ap = ALLOC_ACTION(type);
  349. ap->type_mask = find_type(arg1);
  350. }
  351. #endif
  352. #if ENABLE_FEATURE_FIND_PERM
  353. /* TODO:
  354. * -perm mode File's permission bits are exactly mode (octal or symbolic).
  355. * Symbolic modes use mode 0 as a point of departure.
  356. * -perm -mode All of the permission bits mode are set for the file.
  357. * -perm +mode Any of the permission bits mode are set for the file.
  358. */
  359. else if (strcmp(arg, "-perm") == 0) {
  360. action_perm *ap;
  361. if (!*++argv)
  362. bb_error_msg_and_die(bb_msg_requires_arg, arg);
  363. ap = ALLOC_ACTION(perm);
  364. ap->perm_mask = xstrtol_range(arg1, 8, 0, 07777);
  365. ap->perm_char = arg1[0];
  366. if (ap->perm_char == '-')
  367. ap->perm_mask = -ap->perm_mask;
  368. }
  369. #endif
  370. #if ENABLE_FEATURE_FIND_MTIME
  371. else if (strcmp(arg, "-mtime") == 0) {
  372. action_mtime *ap;
  373. if (!*++argv)
  374. bb_error_msg_and_die(bb_msg_requires_arg, arg);
  375. ap = ALLOC_ACTION(mtime);
  376. ap->mtime_days = xatol(arg1);
  377. ap->mtime_char = arg1[0];
  378. if (ap->mtime_char == '-')
  379. ap->mtime_days = -ap->mtime_days;
  380. }
  381. #endif
  382. #if ENABLE_FEATURE_FIND_MMIN
  383. else if (strcmp(arg, "-mmin") == 0) {
  384. action_mmin *ap;
  385. if (!*++argv)
  386. bb_error_msg_and_die(bb_msg_requires_arg, arg);
  387. ap = ALLOC_ACTION(mmin);
  388. ap->mmin_mins = xatol(arg1);
  389. ap->mmin_char = arg1[0];
  390. if (ap->mmin_char == '-')
  391. ap->mmin_mins = -ap->mmin_mins;
  392. }
  393. #endif
  394. #if ENABLE_FEATURE_FIND_NEWER
  395. else if (strcmp(arg, "-newer") == 0) {
  396. action_newer *ap;
  397. struct stat stat_newer;
  398. if (!*++argv)
  399. bb_error_msg_and_die(bb_msg_requires_arg, arg);
  400. xstat(arg1, &stat_newer);
  401. ap = ALLOC_ACTION(newer);
  402. ap->newer_mtime = stat_newer.st_mtime;
  403. }
  404. #endif
  405. #if ENABLE_FEATURE_FIND_INUM
  406. else if (strcmp(arg, "-inum") == 0) {
  407. action_inum *ap;
  408. if (!*++argv)
  409. bb_error_msg_and_die(bb_msg_requires_arg, arg);
  410. ap = ALLOC_ACTION(inum);
  411. ap->inode_num = xatoul(arg1);
  412. }
  413. #endif
  414. #if ENABLE_FEATURE_FIND_EXEC
  415. else if (strcmp(arg, "-exec") == 0) {
  416. int i;
  417. action_exec *ap;
  418. need_print = 0;
  419. ap = ALLOC_ACTION(exec);
  420. ap->exec_argv = ++argv; /* first arg after -exec */
  421. ap->exec_argc = 0;
  422. while (1) {
  423. if (!*argv) /* did not see ';' till end */
  424. bb_error_msg_and_die(bb_msg_requires_arg, arg);
  425. if (one_char(argv[0], ';'))
  426. break;
  427. argv++;
  428. ap->exec_argc++;
  429. }
  430. if (ap->exec_argc == 0)
  431. bb_error_msg_and_die(bb_msg_requires_arg, arg);
  432. ap->subst_count = xmalloc(ap->exec_argc * sizeof(int));
  433. i = ap->exec_argc;
  434. while (i--)
  435. ap->subst_count[i] = count_subst(ap->exec_argv[i]);
  436. }
  437. #endif
  438. #if ENABLE_DESKTOP
  439. else if (one_char(arg, '(')) {
  440. action_paren *ap;
  441. char **endarg;
  442. int nested = 1;
  443. endarg = argv;
  444. while (1) {
  445. if (!*++endarg)
  446. bb_error_msg_and_die("unpaired '('");
  447. if (one_char(*endarg, '('))
  448. nested++;
  449. else if (one_char(*endarg, ')') && !--nested) {
  450. *endarg = NULL;
  451. break;
  452. }
  453. }
  454. ap = ALLOC_ACTION(paren);
  455. ap->subexpr = parse_params(argv + 1);
  456. *endarg = ")"; /* restore NULLed parameter */
  457. argv = endarg;
  458. }
  459. else if (strcmp(arg, "-prune") == 0) {
  460. (void) ALLOC_ACTION(prune);
  461. }
  462. else if (strcmp(arg, "-size") == 0) {
  463. action_size *ap;
  464. if (!*++argv)
  465. bb_error_msg_and_die(bb_msg_requires_arg, arg);
  466. ap = ALLOC_ACTION(size);
  467. ap->size = XATOOFF(arg1);
  468. }
  469. #endif
  470. else
  471. bb_show_usage();
  472. argv++;
  473. }
  474. return appp;
  475. #undef ALLOC_ACTION
  476. }
  477. int find_main(int argc, char **argv)
  478. {
  479. int dereference = FALSE;
  480. char *arg;
  481. char **argp;
  482. int i, firstopt, status = EXIT_SUCCESS;
  483. for (firstopt = 1; firstopt < argc; firstopt++) {
  484. if (argv[firstopt][0] == '-')
  485. break;
  486. #if ENABLE_DESKTOP
  487. if (one_char(argv[firstopt], '('))
  488. break;
  489. #endif
  490. }
  491. if (firstopt == 1) {
  492. argv[0] = ".";
  493. argv--;
  494. firstopt++;
  495. }
  496. // All options always return true. They always take effect,
  497. // rather than being processed only when their place in the
  498. // expression is reached
  499. // We implement: -follow, -xdev
  500. /* Process options, and replace then with -a */
  501. /* (-a will be ignored by recursive parser later) */
  502. argp = &argv[firstopt];
  503. while ((arg = argp[0])) {
  504. if (strcmp(arg, "-follow") == 0) {
  505. dereference = TRUE;
  506. argp[0] = "-a";
  507. }
  508. #if ENABLE_FEATURE_FIND_XDEV
  509. else if (strcmp(arg, "-xdev") == 0) {
  510. struct stat stbuf;
  511. if (!xdev_count) {
  512. xdev_count = firstopt - 1;
  513. xdev_dev = xmalloc(xdev_count * sizeof(dev_t));
  514. for (i = 1; i < firstopt; i++) {
  515. /* not xstat(): shouldn't bomb out on
  516. * "find not_exist exist -xdev" */
  517. if (stat(argv[i], &stbuf)) stbuf.st_dev = -1L;
  518. xdev_dev[i-1] = stbuf.st_dev;
  519. }
  520. }
  521. argp[0] = "-a";
  522. }
  523. argp++;
  524. #endif
  525. }
  526. actions = parse_params(&argv[firstopt]);
  527. for (i = 1; i < firstopt; i++) {
  528. if (!recursive_action(argv[i],
  529. TRUE, // recurse
  530. dereference, // follow links
  531. FALSE, // depth first
  532. fileAction, // file action
  533. fileAction, // dir action
  534. NULL, // user data
  535. 0)) // depth
  536. status = EXIT_FAILURE;
  537. }
  538. return status;
  539. }