ash: s/int/smallint/. -60 bytes.
[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                                 break;
388                 }
389                 if (i == xdev_count)
390                         return SKIP;
391         }
392 #endif
393         i = exec_actions(actions, fileName, statbuf);
394         /* Had no explicit -print[0] or -exec? then print */
395         if ((i & TRUE) && need_print)
396                 puts(fileName);
397         /* Cannot return 0: our caller, recursive_action(),
398          * will perror() and skip dirs (if called on dir) */
399         return (i & SKIP) ? SKIP : TRUE;
400 }
401
402
403 #if ENABLE_FEATURE_FIND_TYPE
404 static int find_type(const char *type)
405 {
406         int mask = 0;
407
408         if (*type == 'b')
409                 mask = S_IFBLK;
410         else if (*type == 'c')
411                 mask = S_IFCHR;
412         else if (*type == 'd')
413                 mask = S_IFDIR;
414         else if (*type == 'p')
415                 mask = S_IFIFO;
416         else if (*type == 'f')
417                 mask = S_IFREG;
418         else if (*type == 'l')
419                 mask = S_IFLNK;
420         else if (*type == 's')
421                 mask = S_IFSOCK;
422
423         if (mask == 0 || *(type + 1) != '\0')
424                 bb_error_msg_and_die(bb_msg_invalid_arg, type, "-type");
425
426         return mask;
427 }
428 #endif
429
430 #if ENABLE_FEATURE_FIND_PERM \
431  || ENABLE_FEATURE_FIND_MTIME || ENABLE_FEATURE_FIND_MMIN \
432  || ENABLE_FEATURE_FIND_SIZE
433 static const char* plus_minus_num(const char* str)
434 {
435         if (*str == '-' || *str == '+')
436                 str++;
437         return str;
438 }
439 #endif
440
441 static action*** parse_params(char **argv)
442 {
443         enum {
444                                  PARM_a         ,
445                                  PARM_o         ,
446         USE_FEATURE_FIND_NOT(    PARM_char_not  ,)
447 #if ENABLE_DESKTOP
448                                  PARM_and       ,
449                                  PARM_or        ,
450         USE_FEATURE_FIND_NOT(    PARM_not       ,)
451 #endif
452                                  PARM_print     ,
453         USE_FEATURE_FIND_PRINT0( PARM_print0    ,)
454         USE_FEATURE_FIND_DEPTH(  PARM_depth     ,)
455         USE_FEATURE_FIND_PRUNE(  PARM_prune     ,)
456         USE_FEATURE_FIND_DELETE( PARM_delete    ,)
457         USE_FEATURE_FIND_EXEC(   PARM_exec      ,)
458         USE_FEATURE_FIND_PAREN(  PARM_char_brace,)
459         /* All options starting from here require argument */
460                                  PARM_name      ,
461         USE_FEATURE_FIND_PATH(   PARM_path      ,)
462         USE_FEATURE_FIND_REGEX(  PARM_regex     ,)
463         USE_FEATURE_FIND_TYPE(   PARM_type      ,)
464         USE_FEATURE_FIND_PERM(   PARM_perm      ,)
465         USE_FEATURE_FIND_MTIME(  PARM_mtime     ,)
466         USE_FEATURE_FIND_MMIN(   PARM_mmin      ,)
467         USE_FEATURE_FIND_NEWER(  PARM_newer     ,)
468         USE_FEATURE_FIND_INUM(   PARM_inum      ,)
469         USE_FEATURE_FIND_USER(   PARM_user      ,)
470         USE_FEATURE_FIND_GROUP(  PARM_group     ,)
471         USE_FEATURE_FIND_SIZE(   PARM_size      ,)
472         USE_FEATURE_FIND_CONTEXT(PARM_context   ,)
473         };
474
475         static const char params[] ALIGN1 =
476                                  "-a\0"
477                                  "-o\0"
478         USE_FEATURE_FIND_NOT(    "!\0"       )
479 #if ENABLE_DESKTOP
480                                  "-and\0"
481                                  "-or\0"
482         USE_FEATURE_FIND_NOT(    "-not\0"    )
483 #endif
484                                  "-print\0"
485         USE_FEATURE_FIND_PRINT0( "-print0\0" )
486         USE_FEATURE_FIND_DEPTH(  "-depth\0"  )
487         USE_FEATURE_FIND_PRUNE(  "-prune\0"  )
488         USE_FEATURE_FIND_DELETE( "-delete\0" )
489         USE_FEATURE_FIND_EXEC(   "-exec\0"   )
490         USE_FEATURE_FIND_PAREN(  "(\0"       )
491         /* All options starting from here require argument */
492                                  "-name\0"
493         USE_FEATURE_FIND_PATH(   "-path\0"   )
494         USE_FEATURE_FIND_REGEX(  "-regex\0"  )
495         USE_FEATURE_FIND_TYPE(   "-type\0"   )
496         USE_FEATURE_FIND_PERM(   "-perm\0"   )
497         USE_FEATURE_FIND_MTIME(  "-mtime\0"  )
498         USE_FEATURE_FIND_MMIN(   "-mmin\0"   )
499         USE_FEATURE_FIND_NEWER(  "-newer\0"  )
500         USE_FEATURE_FIND_INUM(   "-inum\0"   )
501         USE_FEATURE_FIND_USER(   "-user\0"   )
502         USE_FEATURE_FIND_GROUP(  "-group\0"  )
503         USE_FEATURE_FIND_SIZE(   "-size\0"   )
504         USE_FEATURE_FIND_CONTEXT("-context\0")
505                                  ;
506
507         action*** appp;
508         unsigned cur_group = 0;
509         unsigned cur_action = 0;
510         USE_FEATURE_FIND_NOT( bool invert_flag = 0; )
511
512         /* 'static' doesn't work here! (gcc 4.1.2) */
513         action* alloc_action(int sizeof_struct, action_fp f)
514         {
515                 action *ap;
516                 appp[cur_group] = xrealloc(appp[cur_group], (cur_action+2) * sizeof(*appp));
517                 appp[cur_group][cur_action++] = ap = xmalloc(sizeof_struct);
518                 appp[cur_group][cur_action] = NULL;
519                 ap->f = f;
520                 USE_FEATURE_FIND_NOT( ap->invert = invert_flag; )
521                 USE_FEATURE_FIND_NOT( invert_flag = 0; )
522                 return ap;
523         }
524
525 #define ALLOC_ACTION(name) (action_##name*)alloc_action(sizeof(action_##name), (action_fp) func_##name)
526
527         appp = xzalloc(2 * sizeof(appp[0])); /* appp[0],[1] == NULL */
528
529 /* Actions have side effects and return a true or false value
530  * We implement: -print, -print0, -exec
531  *
532  * The rest are tests.
533  *
534  * Tests and actions are grouped by operators
535  * ( expr )              Force precedence
536  * ! expr                True if expr is false
537  * -not expr             Same as ! expr
538  * expr1 [-a[nd]] expr2  And; expr2 is not evaluated if expr1 is false
539  * expr1 -o[r] expr2     Or; expr2 is not evaluated if expr1 is true
540  * expr1 , expr2         List; both expr1 and expr2 are always evaluated
541  * We implement: (), -a, -o
542  */
543         while (*argv) {
544                 const char *arg = argv[0];
545                 int parm = index_in_strings(params, arg);
546                 const char *arg1 = argv[1];
547
548                 if (parm >= PARM_name) {
549                         /* All options starting from -name require argument */
550                         if (!arg1)
551                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
552                         argv++;
553                 }
554
555                 /* We can use big switch() here, but on i386
556                  * it doesn't give smaller code. Other arches? */
557
558         /* --- Operators --- */
559                 if (parm == PARM_a USE_DESKTOP(|| parm == PARM_and)) {
560                         /* no further special handling required */
561                 }
562                 else if (parm == PARM_o USE_DESKTOP(|| parm == PARM_or)) {
563                         /* start new OR group */
564                         cur_group++;
565                         appp = xrealloc(appp, (cur_group+2) * sizeof(*appp));
566                         /*appp[cur_group] = NULL; - already NULL */
567                         appp[cur_group+1] = NULL;
568                         cur_action = 0;
569                 }
570 #if ENABLE_FEATURE_FIND_NOT
571                 else if (parm == PARM_char_not USE_DESKTOP(|| parm == PARM_not)) {
572                         /* also handles "find ! ! -name 'foo*'" */
573                         invert_flag ^= 1;
574                 }
575 #endif
576
577         /* --- Tests and actions --- */
578                 else if (parm == PARM_print) {
579                         need_print = 0;
580                         /* GNU find ignores '!' here: "find ! -print" */
581                         USE_FEATURE_FIND_NOT( invert_flag = 0; )
582                         (void) ALLOC_ACTION(print);
583                 }
584 #if ENABLE_FEATURE_FIND_PRINT0
585                 else if (parm == PARM_print0) {
586                         need_print = 0;
587                         USE_FEATURE_FIND_NOT( invert_flag = 0; )
588                         (void) ALLOC_ACTION(print0);
589                 }
590 #endif
591 #if ENABLE_FEATURE_FIND_DEPTH
592                 else if (parm == PARM_depth) {
593                         recurse_flags |= ACTION_DEPTHFIRST;
594                 }
595 #endif
596 #if ENABLE_FEATURE_FIND_PRUNE
597                 else if (parm == PARM_prune) {
598                         USE_FEATURE_FIND_NOT( invert_flag = 0; )
599                         (void) ALLOC_ACTION(prune);
600                 }
601 #endif
602 #if ENABLE_FEATURE_FIND_DELETE
603                 else if (parm == PARM_delete) {
604                         need_print = 0;
605                         recurse_flags |= ACTION_DEPTHFIRST;
606                         (void) ALLOC_ACTION(delete);
607                 }
608 #endif
609 #if ENABLE_FEATURE_FIND_EXEC
610                 else if (parm == PARM_exec) {
611                         int i;
612                         action_exec *ap;
613                         need_print = 0;
614                         USE_FEATURE_FIND_NOT( invert_flag = 0; )
615                         ap = ALLOC_ACTION(exec);
616                         ap->exec_argv = ++argv; /* first arg after -exec */
617                         ap->exec_argc = 0;
618                         while (1) {
619                                 if (!*argv) /* did not see ';' until end */
620                                         bb_error_msg_and_die("-exec CMD must end by ';'");
621                                 if (LONE_CHAR(argv[0], ';'))
622                                         break;
623                                 argv++;
624                                 ap->exec_argc++;
625                         }
626                         if (ap->exec_argc == 0)
627                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
628                         ap->subst_count = xmalloc(ap->exec_argc * sizeof(int));
629                         i = ap->exec_argc;
630                         while (i--)
631                                 ap->subst_count[i] = count_subst(ap->exec_argv[i]);
632                 }
633 #endif
634 #if ENABLE_FEATURE_FIND_PAREN
635                 else if (parm == PARM_char_brace) {
636                         action_paren *ap;
637                         char **endarg;
638                         unsigned nested = 1;
639
640                         endarg = argv;
641                         while (1) {
642                                 if (!*++endarg)
643                                         bb_error_msg_and_die("unpaired '('");
644                                 if (LONE_CHAR(*endarg, '('))
645                                         nested++;
646                                 else if (LONE_CHAR(*endarg, ')') && !--nested) {
647                                         *endarg = NULL;
648                                         break;
649                                 }
650                         }
651                         ap = ALLOC_ACTION(paren);
652                         ap->subexpr = parse_params(argv + 1);
653                         *endarg = (char*) ")"; /* restore NULLed parameter */
654                         argv = endarg;
655                 }
656 #endif
657                 else if (parm == PARM_name) {
658                         action_name *ap;
659                         ap = ALLOC_ACTION(name);
660                         ap->pattern = arg1;
661                 }
662 #if ENABLE_FEATURE_FIND_PATH
663                 else if (parm == PARM_path) {
664                         action_path *ap;
665                         ap = ALLOC_ACTION(path);
666                         ap->pattern = arg1;
667                 }
668 #endif
669 #if ENABLE_FEATURE_FIND_REGEX
670                 else if (parm == PARM_regex) {
671                         action_regex *ap;
672                         ap = ALLOC_ACTION(regex);
673                         xregcomp(&ap->compiled_pattern, arg1, 0 /*cflags*/);
674                 }
675 #endif
676 #if ENABLE_FEATURE_FIND_TYPE
677                 else if (parm == PARM_type) {
678                         action_type *ap;
679                         ap = ALLOC_ACTION(type);
680                         ap->type_mask = find_type(arg1);
681                 }
682 #endif
683 #if ENABLE_FEATURE_FIND_PERM
684 /* -perm mode   File's permission bits are exactly mode (octal or symbolic).
685  *              Symbolic modes use mode 0 as a point of departure.
686  * -perm -mode  All of the permission bits mode are set for the file.
687  * -perm +mode  Any of the permission bits mode are set for the file.
688  */
689                 else if (parm == PARM_perm) {
690                         action_perm *ap;
691                         ap = ALLOC_ACTION(perm);
692                         ap->perm_char = arg1[0];
693                         arg1 = plus_minus_num(arg1);
694                         ap->perm_mask = 0;
695                         if (!bb_parse_mode(arg1, &ap->perm_mask))
696                                 bb_error_msg_and_die("invalid mode: %s", arg1);
697                 }
698 #endif
699 #if ENABLE_FEATURE_FIND_MTIME
700                 else if (parm == PARM_mtime) {
701                         action_mtime *ap;
702                         ap = ALLOC_ACTION(mtime);
703                         ap->mtime_char = arg1[0];
704                         ap->mtime_days = xatoul(plus_minus_num(arg1));
705                 }
706 #endif
707 #if ENABLE_FEATURE_FIND_MMIN
708                 else if (parm == PARM_mmin) {
709                         action_mmin *ap;
710                         ap = ALLOC_ACTION(mmin);
711                         ap->mmin_char = arg1[0];
712                         ap->mmin_mins = xatoul(plus_minus_num(arg1));
713                 }
714 #endif
715 #if ENABLE_FEATURE_FIND_NEWER
716                 else if (parm == PARM_newer) {
717                         struct stat stat_newer;
718                         action_newer *ap;
719                         ap = ALLOC_ACTION(newer);
720                         xstat(arg1, &stat_newer);
721                         ap->newer_mtime = stat_newer.st_mtime;
722                 }
723 #endif
724 #if ENABLE_FEATURE_FIND_INUM
725                 else if (parm == PARM_inum) {
726                         action_inum *ap;
727                         ap = ALLOC_ACTION(inum);
728                         ap->inode_num = xatoul(arg1);
729                 }
730 #endif
731 #if ENABLE_FEATURE_FIND_USER
732                 else if (parm == PARM_user) {
733                         action_user *ap;
734                         ap = ALLOC_ACTION(user);
735                         ap->uid = bb_strtou(arg1, NULL, 10);
736                         if (errno)
737                                 ap->uid = xuname2uid(arg1);
738                 }
739 #endif
740 #if ENABLE_FEATURE_FIND_GROUP
741                 else if (parm == PARM_group) {
742                         action_group *ap;
743                         ap = ALLOC_ACTION(group);
744                         ap->gid = bb_strtou(arg1, NULL, 10);
745                         if (errno)
746                                 ap->gid = xgroup2gid(arg1);
747                 }
748 #endif
749 #if ENABLE_FEATURE_FIND_SIZE
750                 else if (parm == PARM_size) {
751 /* -size n[bckw]: file uses n units of space
752  * b (default): units are 512-byte blocks
753  * c: 1 byte
754  * k: kilobytes
755  * w: 2-byte words
756  */
757 #if ENABLE_LFS
758 #define XATOU_SFX xatoull_sfx
759 #else
760 #define XATOU_SFX xatoul_sfx
761 #endif
762                         static const struct suffix_mult find_suffixes[] = {
763                                 { "c", 1 },
764                                 { "w", 2 },
765                                 { "", 512 },
766                                 { "b", 512 },
767                                 { "k", 1024 },
768                                 { }
769                         };
770                         action_size *ap;
771                         ap = ALLOC_ACTION(size);
772                         ap->size_char = arg1[0];
773                         ap->size = XATOU_SFX(plus_minus_num(arg1), find_suffixes);
774                 }
775 #endif
776 #if ENABLE_FEATURE_FIND_CONTEXT
777                 else if (parm == PARM_context) {
778                         action_context *ap;
779                         ap = ALLOC_ACTION(context);
780                         ap->context = NULL;
781                         /* SELinux headers erroneously declare non-const parameter */
782                         if (selinux_raw_to_trans_context((char*)arg1, &ap->context))
783                                 bb_perror_msg("%s", arg1);
784                 }
785 #endif
786                 else {
787                         bb_error_msg("unrecognized: %s", arg);
788                         bb_show_usage();
789                 }
790                 argv++;
791         }
792         return appp;
793 #undef ALLOC_ACTION
794 }
795
796
797 int find_main(int argc, char **argv);
798 int find_main(int argc, char **argv)
799 {
800         static const char options[] ALIGN1 =
801                           "-follow\0"
802 USE_FEATURE_FIND_XDEV(    "-xdev\0"    )
803 USE_FEATURE_FIND_MAXDEPTH("-maxdepth\0")
804                           ;
805         enum {
806                           OPT_FOLLOW,
807 USE_FEATURE_FIND_XDEV(    OPT_XDEV    ,)
808 USE_FEATURE_FIND_MAXDEPTH(OPT_MAXDEPTH,)
809         };
810
811         char *arg;
812         char **argp;
813         int i, firstopt, status = EXIT_SUCCESS;
814 #if ENABLE_FEATURE_FIND_MAXDEPTH
815         int maxdepth = INT_MAX;
816 #endif
817
818         for (firstopt = 1; firstopt < argc; firstopt++) {
819                 if (argv[firstopt][0] == '-')
820                         break;
821                 if (ENABLE_FEATURE_FIND_NOT && LONE_CHAR(argv[firstopt], '!'))
822                         break;
823 #if ENABLE_FEATURE_FIND_PAREN
824                 if (LONE_CHAR(argv[firstopt], '('))
825                         break;
826 #endif
827         }
828         if (firstopt == 1) {
829                 argv[0] = (char*)".";
830                 argv--;
831                 firstopt++;
832         }
833
834 /* All options always return true. They always take effect
835  * rather than being processed only when their place in the
836  * expression is reached.
837  * We implement: -follow, -xdev, -maxdepth
838  */
839         /* Process options, and replace then with -a */
840         /* (-a will be ignored by recursive parser later) */
841         argp = &argv[firstopt];
842         while ((arg = argp[0])) {
843                 int opt = index_in_strings(options, arg);
844                 if (opt == OPT_FOLLOW) {
845                         recurse_flags |= ACTION_FOLLOWLINKS;
846                         argp[0] = (char*)"-a";
847                 }
848 #if ENABLE_FEATURE_FIND_XDEV
849                 if (opt == OPT_XDEV) {
850                         struct stat stbuf;
851                         if (!xdev_count) {
852                                 xdev_count = firstopt - 1;
853                                 xdev_dev = xmalloc(xdev_count * sizeof(dev_t));
854                                 for (i = 1; i < firstopt; i++) {
855                                         /* not xstat(): shouldn't bomb out on
856                                          * "find not_exist exist -xdev" */
857                                         if (stat(argv[i], &stbuf))
858                                                 stbuf.st_dev = -1L;
859                                         xdev_dev[i-1] = stbuf.st_dev;
860                                 }
861                         }
862                         argp[0] = (char*)"-a";
863                 }
864 #endif
865 #if ENABLE_FEATURE_FIND_MAXDEPTH
866                 if (opt == OPT_MAXDEPTH) {
867                         if (!argp[1])
868                                 bb_show_usage();
869                         maxdepth = xatoi_u(argp[1]);
870                         argp[0] = (char*)"-a";
871                         argp[1] = (char*)"-a";
872                         argp++;
873                 }
874 #endif
875                 argp++;
876         }
877
878         actions = parse_params(&argv[firstopt]);
879
880         for (i = 1; i < firstopt; i++) {
881                 if (!recursive_action(argv[i],
882                                 recurse_flags,  /* flags */
883                                 fileAction,     /* file action */
884                                 fileAction,     /* dir action */
885 #if ENABLE_FEATURE_FIND_MAXDEPTH
886                                 /* double cast suppresses
887                                  * "cast to ptr from int of different size" */
888                                 (void*)(ptrdiff_t)maxdepth,/* user data */
889 #else
890                                 NULL,           /* user data */
891 #endif
892                                 0))             /* depth */
893                         status = EXIT_FAILURE;
894         }
895         return status;
896 }