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