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