find: add conditional support for -maxdepth and -regex
[oweals/busybox.git] / findutils / find.c
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
13 /* findutils-4.1.20:
14  *
15  * # find file.txt -exec 'echo {}' '{}  {}' ';'
16  * find: echo file.txt: No such file or directory
17  * # find file.txt -exec 'echo' '{}  {}' '; '
18  * find: missing argument to `-exec'
19  * # find file.txt -exec 'echo {}' '{}  {}' ';' junk
20  * find: paths must precede expression
21  * # find file.txt -exec 'echo {}' '{}  {}' ';' junk ';'
22  * find: paths must precede expression
23  * # find file.txt -exec 'echo' '{}  {}' ';'
24  * file.txt  file.txt
25  * (strace: execve("/bin/echo", ["echo", "file.txt  file.txt"], [ 30 vars ]))
26  * # find file.txt -exec 'echo' '{}  {}' ';' -print -exec pwd ';'
27  * file.txt  file.txt
28  * file.txt
29  * /tmp
30  * # find -name '*.c' -o -name '*.h'
31  * [shows files, *.c and *.h intermixed]
32  * # find file.txt -name '*f*' -o -name '*t*'
33  * file.txt
34  * # find file.txt -name '*z*' -o -name '*t*'
35  * file.txt
36  * # find file.txt -name '*f*' -o -name '*z*'
37  * file.txt
38  *
39  * # find t z -name '*t*' -print -o -name '*z*'
40  * t
41  * # find t z t z -name '*t*' -o -name '*z*' -print
42  * z
43  * z
44  * # find t z t z '(' -name '*t*' -o -name '*z*' ')' -o -print
45  * (no output)
46  */
47
48 #include <fnmatch.h>
49 #include "libbb.h"
50 #if ENABLE_FEATURE_FIND_REGEX
51 #include "xregex.h"
52 #endif
53
54 /* This is a NOEXEC applet. Be very careful! */
55
56
57 USE_FEATURE_FIND_XDEV(static dev_t *xdev_dev;)
58 USE_FEATURE_FIND_XDEV(static int xdev_count;)
59
60 typedef int (*action_fp)(const char *fileName, struct stat *statbuf, void *);
61
62 typedef struct {
63         action_fp f;
64 #if ENABLE_FEATURE_FIND_NOT
65         bool invert;
66 #endif
67 } action;
68 #define ACTS(name, arg...) typedef struct { action a; arg; } action_##name;
69 #define ACTF(name)         static int func_##name(const char *fileName, struct stat *statbuf, action_##name* ap)
70                         ACTS(print)
71                         ACTS(name,  const char *pattern;)
72 USE_FEATURE_FIND_PATH(  ACTS(path,  const char *pattern;))
73 USE_FEATURE_FIND_REGEX( ACTS(regex, regex_t compiled_pattern;))
74 USE_FEATURE_FIND_PRINT0(ACTS(print0))
75 USE_FEATURE_FIND_TYPE(  ACTS(type,  int type_mask;))
76 USE_FEATURE_FIND_PERM(  ACTS(perm,  char perm_char; mode_t perm_mask;))
77 USE_FEATURE_FIND_MTIME( ACTS(mtime, char mtime_char; unsigned mtime_days;))
78 USE_FEATURE_FIND_MMIN(  ACTS(mmin,  char mmin_char; unsigned mmin_mins;))
79 USE_FEATURE_FIND_NEWER( ACTS(newer, time_t newer_mtime;))
80 USE_FEATURE_FIND_INUM(  ACTS(inum,  ino_t inode_num;))
81 USE_FEATURE_FIND_EXEC(  ACTS(exec,  char **exec_argv; unsigned *subst_count; int exec_argc;))
82 USE_FEATURE_FIND_USER(  ACTS(user,  uid_t uid;))
83 USE_FEATURE_FIND_GROUP( ACTS(group, gid_t gid;))
84 USE_FEATURE_FIND_PAREN( ACTS(paren, action ***subexpr;))
85 USE_FEATURE_FIND_SIZE(  ACTS(size,  off_t size;))
86 USE_FEATURE_FIND_PRUNE( ACTS(prune))
87 USE_FEATURE_FIND_DELETE(ACTS(delete))
88
89 static action ***actions;
90 static bool need_print = 1;
91 static int recurse_flags = ACTION_RECURSE;
92
93 #if ENABLE_FEATURE_FIND_EXEC
94 static unsigned count_subst(const char *str)
95 {
96         unsigned count = 0;
97         while ((str = strstr(str, "{}"))) {
98                 count++;
99                 str++;
100         }
101         return count;
102 }
103
104
105 static char* subst(const char *src, unsigned count, const char* filename)
106 {
107         char *buf, *dst, *end;
108         size_t flen = strlen(filename);
109         /* we replace each '{}' with filename: growth by strlen-2 */
110         buf = dst = xmalloc(strlen(src) + count*(flen-2) + 1);
111         while ((end = strstr(src, "{}"))) {
112                 memcpy(dst, src, end - src);
113                 dst += end - src;
114                 src = end + 2;
115                 memcpy(dst, filename, flen);
116                 dst += flen;
117         }
118         strcpy(dst, src);
119         return buf;
120 }
121 #endif
122
123 /* Return values of ACTFs ('action functions') are a bit mask:
124  * bit 1=1: prune (use SKIP constant for setting it)
125  * bit 0=1: matched successfully (TRUE)
126  */
127
128 static int exec_actions(action ***appp, const char *fileName, struct stat *statbuf)
129 {
130         int cur_group;
131         int cur_action;
132         int rc = 0;
133         action **app, *ap;
134
135         /* "action group" is a set of actions ANDed together.
136          * groups are ORed together.
137          * We simply evaluate each group until we find one in which all actions
138          * succeed. */
139
140         /* -prune is special: if it is encountered, then we won't
141          * descend into current directory. It doesn't matter whether
142          * action group (in which -prune sits) will succeed or not:
143          * find * -prune -name 'f*' -o -name 'm*' -- prunes every dir
144          * find * -name 'f*' -o -prune -name 'm*' -- prunes all dirs
145          *     not starting with 'f' */
146
147         /* We invert TRUE bit (bit 0). Now 1 there means 'failure'.
148          * and bitwise OR in "rc |= TRUE ^ ap->f()" will:
149          * (1) make SKIP (-prune) bit stick; and (2) detect 'failure'.
150          * On return, bit is restored.  */
151
152         cur_group = -1;
153         while ((app = appp[++cur_group])) {
154                 rc &= ~TRUE; /* 'success' so far, clear TRUE bit */
155                 cur_action = -1;
156                 while (1) {
157                         ap = app[++cur_action];
158                         if (!ap) /* all actions in group were successful */
159                                 return rc ^ TRUE; /* restore TRUE bit */
160                         rc |= TRUE ^ ap->f(fileName, statbuf, ap);
161 #if ENABLE_FEATURE_FIND_NOT
162                         if (ap->invert) rc ^= TRUE;
163 #endif
164                         if (rc & TRUE) /* current group failed, try next */
165                                 break;
166                 }
167         }
168         return rc ^ TRUE; /* restore TRUE bit */
169 }
170
171
172 ACTF(name)
173 {
174         const char *tmp = strrchr(fileName, '/');
175         if (tmp == NULL)
176                 tmp = fileName;
177         else {
178                 tmp++;
179                 if (!*tmp) { /* "foo/bar/". Oh no... go back to 'b' */
180                         tmp--;
181                         while (tmp != fileName && *--tmp != '/')
182                                 continue;
183                         if (*tmp == '/')
184                                 tmp++;
185                 }
186         }
187         return fnmatch(ap->pattern, tmp, FNM_PERIOD) == 0;
188 }
189 #if ENABLE_FEATURE_FIND_PATH
190 ACTF(path)
191 {
192         return fnmatch(ap->pattern, fileName, 0) == 0;
193 }
194 #endif
195 #if ENABLE_FEATURE_FIND_REGEX
196 ACTF(regex)
197 {
198         regmatch_t match;
199         if (regexec(&ap->compiled_pattern, fileName, 1, &match, 0 /*eflags*/))
200                 return 0; /* no match */
201         if (match.rm_so)
202                 return 0; /* match doesn't start at pos 0 */
203         if (fileName[match.rm_eo])
204                 return 0; /* match doesn't end exactly at end of pathname */
205         return 1;
206 }
207 #endif
208 #if ENABLE_FEATURE_FIND_TYPE
209 ACTF(type)
210 {
211         return ((statbuf->st_mode & S_IFMT) == ap->type_mask);
212 }
213 #endif
214 #if ENABLE_FEATURE_FIND_PERM
215 ACTF(perm)
216 {
217         /* -perm +mode: at least one of perm_mask bits are set */
218         if (ap->perm_char == '+')
219                 return (statbuf->st_mode & ap->perm_mask) != 0;
220         /* -perm -mode: all of perm_mask are set */
221         if (ap->perm_char == '-')
222                 return (statbuf->st_mode & ap->perm_mask) == ap->perm_mask;
223         /* -perm mode: file mode must match perm_mask */
224         return (statbuf->st_mode & 07777) == ap->perm_mask;
225 }
226 #endif
227 #if ENABLE_FEATURE_FIND_MTIME
228 ACTF(mtime)
229 {
230         time_t file_age = time(NULL) - statbuf->st_mtime;
231         time_t mtime_secs = ap->mtime_days * 24*60*60;
232         if (ap->mtime_char == '+')
233                 return file_age >= mtime_secs + 24*60*60;
234         if (ap->mtime_char == '-')
235                 return file_age < mtime_secs;
236         /* just numeric mtime */
237         return file_age >= mtime_secs && file_age < (mtime_secs + 24*60*60);
238 }
239 #endif
240 #if ENABLE_FEATURE_FIND_MMIN
241 ACTF(mmin)
242 {
243         time_t file_age = time(NULL) - statbuf->st_mtime;
244         time_t mmin_secs = ap->mmin_mins * 60;
245         if (ap->mmin_char == '+')
246                 return file_age >= mmin_secs + 60;
247         if (ap->mmin_char == '-')
248                 return file_age < mmin_secs;
249         /* just numeric mmin */
250         return file_age >= mmin_secs && file_age < (mmin_secs + 60);
251 }
252 #endif
253 #if ENABLE_FEATURE_FIND_NEWER
254 ACTF(newer)
255 {
256         return (ap->newer_mtime < statbuf->st_mtime);
257 }
258 #endif
259 #if ENABLE_FEATURE_FIND_INUM
260 ACTF(inum)
261 {
262         return (statbuf->st_ino == ap->inode_num);
263 }
264 #endif
265 #if ENABLE_FEATURE_FIND_EXEC
266 ACTF(exec)
267 {
268         int i, rc;
269         char *argv[ap->exec_argc + 1];
270         for (i = 0; i < ap->exec_argc; i++)
271                 argv[i] = subst(ap->exec_argv[i], ap->subst_count[i], fileName);
272         argv[i] = NULL; /* terminate the list */
273
274         rc = spawn_and_wait(argv);
275         if (rc < 0)
276                 bb_perror_msg("%s", argv[0]);
277
278         i = 0;
279         while (argv[i])
280                 free(argv[i++]);
281         return rc == 0; /* return 1 if exitcode 0 */
282 }
283 #endif
284 #if ENABLE_FEATURE_FIND_USER
285 ACTF(user)
286 {
287         return (statbuf->st_uid == ap->uid);
288 }
289 #endif
290 #if ENABLE_FEATURE_FIND_GROUP
291 ACTF(group)
292 {
293         return (statbuf->st_gid == ap->gid);
294 }
295 #endif
296 #if ENABLE_FEATURE_FIND_PRINT0
297 ACTF(print0)
298 {
299         printf("%s%c", fileName, '\0');
300         return TRUE;
301 }
302 #endif
303 ACTF(print)
304 {
305         puts(fileName);
306         return TRUE;
307 }
308 #if ENABLE_FEATURE_FIND_PAREN
309 ACTF(paren)
310 {
311         return exec_actions(ap->subexpr, fileName, statbuf);
312 }
313 #endif
314 #if ENABLE_FEATURE_FIND_SIZE
315 ACTF(size)
316 {
317         return statbuf->st_size == ap->size;
318 }
319 #endif
320 #if ENABLE_FEATURE_FIND_PRUNE
321 /*
322  * -prune: if -depth is not given, return true and do not descend
323  * current dir; if -depth is given, return false with no effect.
324  * Example:
325  * find dir -name 'asm-*' -prune -o -name '*.[chS]' -print
326  */
327 ACTF(prune)
328 {
329         return SKIP + TRUE;
330 }
331 #endif
332 #if ENABLE_FEATURE_FIND_DELETE
333 ACTF(delete)
334 {
335         int rc;
336         if (S_ISDIR(statbuf->st_mode)) {
337                 rc = rmdir(fileName);
338         } else {
339                 rc = unlink(fileName);
340         }
341         if (rc < 0)
342                 bb_perror_msg("%s", fileName);
343         return TRUE;
344 }
345 #endif
346
347
348 static int fileAction(const char *fileName, struct stat *statbuf, void *userData, int depth)
349 {
350         int i;
351 #if ENABLE_FEATURE_FIND_MAXDEPTH
352         int maxdepth = (int)(ptrdiff_t)userData;
353
354         if (depth > maxdepth) return SKIP;
355 #endif
356
357 #if ENABLE_FEATURE_FIND_XDEV
358         if (S_ISDIR(statbuf->st_mode) && xdev_count) {
359                 for (i = 0; i < xdev_count; i++) {
360                         if (xdev_dev[i] != statbuf->st_dev)
361                                 return SKIP;
362                 }
363         }
364 #endif
365         i = exec_actions(actions, fileName, statbuf);
366         /* Had no explicit -print[0] or -exec? then print */
367         if ((i & TRUE) && need_print)
368                 puts(fileName);
369         /* Cannot return 0: our caller, recursive_action(),
370          * will perror() and skip dirs (if called on dir) */
371         return (i & SKIP) ? SKIP : TRUE;
372 }
373
374
375 #if ENABLE_FEATURE_FIND_TYPE
376 static int find_type(const char *type)
377 {
378         int mask = 0;
379
380         if (*type == 'b')
381                 mask = S_IFBLK;
382         else if (*type == 'c')
383                 mask = S_IFCHR;
384         else if (*type == 'd')
385                 mask = S_IFDIR;
386         else if (*type == 'p')
387                 mask = S_IFIFO;
388         else if (*type == 'f')
389                 mask = S_IFREG;
390         else if (*type == 'l')
391                 mask = S_IFLNK;
392         else if (*type == 's')
393                 mask = S_IFSOCK;
394
395         if (mask == 0 || *(type + 1) != '\0')
396                 bb_error_msg_and_die(bb_msg_invalid_arg, type, "-type");
397
398         return mask;
399 }
400 #endif
401
402 #if ENABLE_FEATURE_FIND_PERM || ENABLE_FEATURE_FIND_MTIME \
403  || ENABLE_FEATURE_FIND_MMIN
404 static const char* plus_minus_num(const char* str)
405 {
406         if (*str == '-' || *str == '+')
407                 str++;
408         return str;
409 }
410 #endif
411
412 static action*** parse_params(char **argv)
413 {
414         enum {
415                                 PARM_a         ,
416                                 PARM_o         ,
417         USE_FEATURE_FIND_NOT(   PARM_char_not  ,)
418                                 PARM_print     ,
419         USE_FEATURE_FIND_PRINT0(PARM_print0    ,)
420                                 PARM_name      ,
421         USE_FEATURE_FIND_PATH(  PARM_path      ,)
422         USE_FEATURE_FIND_REGEX( PARM_regex     ,)
423         USE_FEATURE_FIND_TYPE(  PARM_type      ,)
424         USE_FEATURE_FIND_PERM(  PARM_perm      ,)
425         USE_FEATURE_FIND_MTIME( PARM_mtime     ,)
426         USE_FEATURE_FIND_MMIN(  PARM_mmin      ,)
427         USE_FEATURE_FIND_NEWER( PARM_newer     ,)
428         USE_FEATURE_FIND_INUM(  PARM_inum      ,)
429         USE_FEATURE_FIND_EXEC(  PARM_exec      ,)
430         USE_FEATURE_FIND_USER(  PARM_user      ,)
431         USE_FEATURE_FIND_GROUP( PARM_group     ,)
432         USE_FEATURE_FIND_DEPTH( PARM_depth     ,)
433         USE_FEATURE_FIND_PAREN( PARM_char_brace,)
434         USE_FEATURE_FIND_SIZE(  PARM_size      ,)
435         USE_FEATURE_FIND_PRUNE( PARM_prune     ,)
436         USE_FEATURE_FIND_DELETE(PARM_delete    ,)
437 #if ENABLE_DESKTOP
438                                 PARM_and       ,
439                                 PARM_or        ,
440         USE_FEATURE_FIND_NOT(   PARM_not       ,)
441 #endif
442         };
443
444         static const char *const params[] = {
445                                 "-a"     ,
446                                 "-o"     ,
447         USE_FEATURE_FIND_NOT(   "!"      ,)
448                                 "-print" ,
449         USE_FEATURE_FIND_PRINT0("-print0",)
450                                 "-name"  ,
451         USE_FEATURE_FIND_PATH(  "-path"  ,)
452         USE_FEATURE_FIND_REGEX( "-regex" ,)
453         USE_FEATURE_FIND_TYPE(  "-type"  ,)
454         USE_FEATURE_FIND_PERM(  "-perm"  ,)
455         USE_FEATURE_FIND_MTIME( "-mtime" ,)
456         USE_FEATURE_FIND_MMIN(  "-mmin"  ,)
457         USE_FEATURE_FIND_NEWER( "-newer" ,)
458         USE_FEATURE_FIND_INUM(  "-inum"  ,)
459         USE_FEATURE_FIND_EXEC(  "-exec"  ,)
460         USE_FEATURE_FIND_USER(  "-user"  ,)
461         USE_FEATURE_FIND_GROUP( "-group" ,)
462         USE_FEATURE_FIND_DEPTH( "-depth" ,)
463         USE_FEATURE_FIND_PAREN( "("      ,)
464         USE_FEATURE_FIND_SIZE(  "-size"  ,)
465         USE_FEATURE_FIND_PRUNE( "-prune" ,)
466         USE_FEATURE_FIND_DELETE("-delete",)
467 #if ENABLE_DESKTOP
468                                 "-and"   ,
469                                 "-or"    ,
470         USE_FEATURE_FIND_NOT(   "-not"   ,)
471 #endif
472                                 NULL
473         };
474
475         action*** appp;
476         unsigned cur_group = 0;
477         unsigned cur_action = 0;
478         USE_FEATURE_FIND_NOT( bool invert_flag = 0; )
479
480         /* 'static' doesn't work here! (gcc 4.1.2) */
481         action* alloc_action(int sizeof_struct, action_fp f)
482         {
483                 action *ap;
484                 appp[cur_group] = xrealloc(appp[cur_group], (cur_action+2) * sizeof(*appp));
485                 appp[cur_group][cur_action++] = ap = xmalloc(sizeof_struct);
486                 appp[cur_group][cur_action] = NULL;
487                 ap->f = f;
488                 USE_FEATURE_FIND_NOT( ap->invert = invert_flag; )
489                 USE_FEATURE_FIND_NOT( invert_flag = 0; )
490                 return ap;
491         }
492
493 #define ALLOC_ACTION(name) (action_##name*)alloc_action(sizeof(action_##name), (action_fp) func_##name)
494
495         appp = xzalloc(2 * sizeof(appp[0])); /* appp[0],[1] == NULL */
496
497 /* Actions have side effects and return a true or false value
498  * We implement: -print, -print0, -exec
499  *
500  * The rest are tests.
501  *
502  * Tests and actions are grouped by operators
503  * ( expr )              Force precedence
504  * ! expr                True if expr is false
505  * -not expr             Same as ! expr
506  * expr1 [-a[nd]] expr2  And; expr2 is not evaluated if expr1 is false
507  * expr1 -o[r] expr2     Or; expr2 is not evaluated if expr1 is true
508  * expr1 , expr2         List; both expr1 and expr2 are always evaluated
509  * We implement: (), -a, -o
510  */
511         while (*argv) {
512                 const char *arg = argv[0];
513                 const char *arg1 = argv[1];
514                 int parm = index_in_str_array(params, arg);
515         /* --- Operators --- */
516                 if (parm == PARM_a USE_DESKTOP(|| parm == PARM_and)) {
517                         /* no further special handling required */
518                 }
519                 else if (parm == PARM_o USE_DESKTOP(|| parm == PARM_or)) {
520                         /* start new OR group */
521                         cur_group++;
522                         appp = xrealloc(appp, (cur_group+2) * sizeof(*appp));
523                         /*appp[cur_group] = NULL; - already NULL */
524                         appp[cur_group+1] = NULL;
525                         cur_action = 0;
526                 }
527 #if ENABLE_FEATURE_FIND_NOT
528                 else if (parm == PARM_char_not USE_DESKTOP(|| parm == PARM_not)) {
529                         /* also handles "find ! ! -name 'foo*'" */
530                         invert_flag ^= 1;
531                 }
532 #endif
533
534         /* --- Tests and actions --- */
535                 else if (parm == PARM_print) {
536                         need_print = 0;
537                         /* GNU find ignores '!' here: "find ! -print" */
538                         USE_FEATURE_FIND_NOT( invert_flag = 0; )
539                         (void) ALLOC_ACTION(print);
540                 }
541 #if ENABLE_FEATURE_FIND_PRINT0
542                 else if (parm == PARM_print0) {
543                         need_print = 0;
544                         USE_FEATURE_FIND_NOT( invert_flag = 0; )
545                         (void) ALLOC_ACTION(print0);
546                 }
547 #endif
548                 else if (parm == PARM_name) {
549                         action_name *ap;
550                         if (!*++argv)
551                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
552                         ap = ALLOC_ACTION(name);
553                         ap->pattern = arg1;
554                 }
555 #if ENABLE_FEATURE_FIND_PATH
556                 else if (parm == PARM_path) {
557                         action_path *ap;
558                         if (!*++argv)
559                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
560                         ap = ALLOC_ACTION(path);
561                         ap->pattern = arg1;
562                 }
563 #endif
564 #if ENABLE_FEATURE_FIND_REGEX
565                 else if (parm == PARM_regex) {
566                         action_regex *ap;
567                         if (!*++argv)
568                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
569                         ap = ALLOC_ACTION(regex);
570                         xregcomp(&ap->compiled_pattern, arg1, 0 /*cflags*/);
571                 }
572 #endif
573 #if ENABLE_FEATURE_FIND_TYPE
574                 else if (parm == PARM_type) {
575                         action_type *ap;
576                         if (!*++argv)
577                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
578                         ap = ALLOC_ACTION(type);
579                         ap->type_mask = find_type(arg1);
580                 }
581 #endif
582 #if ENABLE_FEATURE_FIND_PERM
583 /* -perm mode   File's permission bits are exactly mode (octal or symbolic).
584  *              Symbolic modes use mode 0 as a point of departure.
585  * -perm -mode  All of the permission bits mode are set for the file.
586  * -perm +mode  Any of the permission bits mode are set for the file.
587  */
588                 else if (parm == PARM_perm) {
589                         action_perm *ap;
590                         if (!*++argv)
591                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
592                         ap = ALLOC_ACTION(perm);
593                         ap->perm_char = arg1[0];
594                         arg1 = plus_minus_num(arg1);
595                         ap->perm_mask = 0;
596                         if (!bb_parse_mode(arg1, &ap->perm_mask))
597                                 bb_error_msg_and_die("invalid mode: %s", arg1);
598                 }
599 #endif
600 #if ENABLE_FEATURE_FIND_MTIME
601                 else if (parm == PARM_mtime) {
602                         action_mtime *ap;
603                         if (!*++argv)
604                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
605                         ap = ALLOC_ACTION(mtime);
606                         ap->mtime_char = arg1[0];
607                         ap->mtime_days = xatoul(plus_minus_num(arg1));
608                 }
609 #endif
610 #if ENABLE_FEATURE_FIND_MMIN
611                 else if (parm == PARM_mmin) {
612                         action_mmin *ap;
613                         if (!*++argv)
614                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
615                         ap = ALLOC_ACTION(mmin);
616                         ap->mmin_char = arg1[0];
617                         ap->mmin_mins = xatoul(plus_minus_num(arg1));
618                 }
619 #endif
620 #if ENABLE_FEATURE_FIND_NEWER
621                 else if (parm == PARM_newer) {
622                         action_newer *ap;
623                         struct stat stat_newer;
624                         if (!*++argv)
625                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
626                         xstat(arg1, &stat_newer);
627                         ap = ALLOC_ACTION(newer);
628                         ap->newer_mtime = stat_newer.st_mtime;
629                 }
630 #endif
631 #if ENABLE_FEATURE_FIND_INUM
632                 else if (parm == PARM_inum) {
633                         action_inum *ap;
634                         if (!*++argv)
635                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
636                         ap = ALLOC_ACTION(inum);
637                         ap->inode_num = xatoul(arg1);
638                 }
639 #endif
640 #if ENABLE_FEATURE_FIND_EXEC
641                 else if (parm == PARM_exec) {
642                         int i;
643                         action_exec *ap;
644                         need_print = 0;
645                         USE_FEATURE_FIND_NOT( invert_flag = 0; )
646                         ap = ALLOC_ACTION(exec);
647                         ap->exec_argv = ++argv; /* first arg after -exec */
648                         ap->exec_argc = 0;
649                         while (1) {
650                                 if (!*argv) /* did not see ';' until end */
651                                         bb_error_msg_and_die(bb_msg_requires_arg, arg);
652                                 if (LONE_CHAR(argv[0], ';'))
653                                         break;
654                                 argv++;
655                                 ap->exec_argc++;
656                         }
657                         if (ap->exec_argc == 0)
658                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
659                         ap->subst_count = xmalloc(ap->exec_argc * sizeof(int));
660                         i = ap->exec_argc;
661                         while (i--)
662                                 ap->subst_count[i] = count_subst(ap->exec_argv[i]);
663                 }
664 #endif
665 #if ENABLE_FEATURE_FIND_USER
666                 else if (parm == PARM_user) {
667                         action_user *ap;
668                         if (!*++argv)
669                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
670                         ap = ALLOC_ACTION(user);
671                         ap->uid = bb_strtou(arg1, NULL, 10);
672                         if (errno)
673                                 ap->uid = xuname2uid(arg1);
674                 }
675 #endif
676 #if ENABLE_FEATURE_FIND_GROUP
677                 else if (parm == PARM_group) {
678                         action_group *ap;
679                         if (!*++argv)
680                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
681                         ap = ALLOC_ACTION(group);
682                         ap->gid = bb_strtou(arg1, NULL, 10);
683                         if (errno)
684                                 ap->gid = xgroup2gid(arg1);
685                 }
686 #endif
687 #if ENABLE_FEATURE_FIND_DEPTH
688                 else if (parm == PARM_depth) {
689                         recurse_flags |= ACTION_DEPTHFIRST;
690                 }
691 #endif
692 #if ENABLE_FEATURE_FIND_PAREN
693                 else if (parm == PARM_char_brace) {
694                         action_paren *ap;
695                         char **endarg;
696                         unsigned nested = 1;
697
698                         endarg = argv;
699                         while (1) {
700                                 if (!*++endarg)
701                                         bb_error_msg_and_die("unpaired '('");
702                                 if (LONE_CHAR(*endarg, '('))
703                                         nested++;
704                                 else if (LONE_CHAR(*endarg, ')') && !--nested) {
705                                         *endarg = NULL;
706                                         break;
707                                 }
708                         }
709                         ap = ALLOC_ACTION(paren);
710                         ap->subexpr = parse_params(argv + 1);
711                         *endarg = (char*) ")"; /* restore NULLed parameter */
712                         argv = endarg;
713                 }
714 #endif
715 #if ENABLE_FEATURE_FIND_SIZE
716                 else if (parm == PARM_size) {
717                         action_size *ap;
718                         if (!*++argv)
719                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
720                         ap = ALLOC_ACTION(size);
721                         ap->size = XATOOFF(arg1);
722                 }
723 #endif
724 #if ENABLE_FEATURE_FIND_PRUNE
725                 else if (parm == PARM_prune) {
726                         USE_FEATURE_FIND_NOT( invert_flag = 0; )
727                         (void) ALLOC_ACTION(prune);
728                 }
729 #endif
730 #if ENABLE_FEATURE_FIND_DELETE
731                 else if (parm == PARM_delete) {
732                         need_print = 0;
733                         recurse_flags |= ACTION_DEPTHFIRST;
734                         (void) ALLOC_ACTION(delete);
735                 }
736 #endif
737                 else {
738                         bb_error_msg("unrecognized: %s", arg);
739                         bb_show_usage();
740                 }
741                 argv++;
742         }
743         return appp;
744 #undef ALLOC_ACTION
745 }
746
747
748 int find_main(int argc, char **argv);
749 int find_main(int argc, char **argv)
750 {
751         static const char *const options[] = {
752                 "-follow",
753 USE_FEATURE_FIND_XDEV(    "-xdev"    ,)
754 USE_FEATURE_FIND_MAXDEPTH("-maxdepth",)
755                 NULL
756         };
757         enum {
758                 OPT_FOLLOW,
759 USE_FEATURE_FIND_XDEV(    OPT_XDEV    ,)
760 USE_FEATURE_FIND_MAXDEPTH(OPT_MAXDEPTH,)
761         };
762
763         char *arg;
764         char **argp;
765         int i, firstopt, status = EXIT_SUCCESS;
766 #if ENABLE_FEATURE_FIND_MAXDEPTH
767         int maxdepth = INT_MAX;
768 #endif
769
770         for (firstopt = 1; firstopt < argc; firstopt++) {
771                 if (argv[firstopt][0] == '-')
772                         break;
773                 if (ENABLE_FEATURE_FIND_NOT && LONE_CHAR(argv[firstopt], '!'))
774                         break;
775 #if ENABLE_FEATURE_FIND_PAREN
776                 if (LONE_CHAR(argv[firstopt], '('))
777                         break;
778 #endif
779         }
780         if (firstopt == 1) {
781                 argv[0] = (char*)".";
782                 argv--;
783                 firstopt++;
784         }
785
786 /* All options always return true. They always take effect
787  * rather than being processed only when their place in the
788  * expression is reached.
789  * We implement: -follow, -xdev, -maxdepth
790  */
791         /* Process options, and replace then with -a */
792         /* (-a will be ignored by recursive parser later) */
793         argp = &argv[firstopt];
794         while ((arg = argp[0])) {
795                 int opt = index_in_str_array(options, arg);
796                 if (opt == OPT_FOLLOW) {
797                         recurse_flags |= ACTION_FOLLOWLINKS;
798                         argp[0] = (char*)"-a";
799                 }
800 #if ENABLE_FEATURE_FIND_XDEV
801                 if (opt == OPT_XDEV) {
802                         struct stat stbuf;
803                         if (!xdev_count) {
804                                 xdev_count = firstopt - 1;
805                                 xdev_dev = xmalloc(xdev_count * sizeof(dev_t));
806                                 for (i = 1; i < firstopt; i++) {
807                                         /* not xstat(): shouldn't bomb out on
808                                          * "find not_exist exist -xdev" */
809                                         if (stat(argv[i], &stbuf))
810                                                 stbuf.st_dev = -1L;
811                                         xdev_dev[i-1] = stbuf.st_dev;
812                                 }
813                         }
814                         argp[0] = (char*)"-a";
815                 }
816 #endif
817 #if ENABLE_FEATURE_FIND_MAXDEPTH
818                 if (opt == OPT_MAXDEPTH) {
819                         if (!argp[1])
820                                 bb_show_usage();
821                         maxdepth = xatoi_u(argp[1]);
822                         argp[0] = (char*)"-a";
823                         argp[1] = (char*)"-a";
824                         argp++;
825                 }
826 #endif
827                 argp++;
828         }
829
830         actions = parse_params(&argv[firstopt]);
831
832         for (i = 1; i < firstopt; i++) {
833                 if (!recursive_action(argv[i],
834                                 recurse_flags,  /* flags */
835                                 fileAction,     /* file action */
836                                 fileAction,     /* dir action */
837 #if ENABLE_FEATURE_FIND_MAXDEPTH
838                                 (void*)maxdepth,/* user data */
839 #else
840                                 NULL,           /* user data */
841 #endif
842                                 0))             /* depth */
843                         status = EXIT_FAILURE;
844         }
845         return status;
846 }