1 /* vi: set sw=4 ts=4: */
3 * Mini find implementation for busybox
5 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
7 * Reworked by David Douthitt <n9ubh@callsign.net> and
8 * Matt Kraai <kraai@alumni.carnegiemellon.edu>.
10 * Licensed under GPLv2, see file LICENSE in this source tree.
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' '{} {}' ';'
25 * (strace: execve("/bin/echo", ["echo", "file.txt file.txt"], [ 30 vars ]))
26 * # find file.txt -exec 'echo' '{} {}' ';' -print -exec pwd ';'
30 * # find -name '*.c' -o -name '*.h'
31 * [shows files, *.c and *.h intermixed]
32 * # find file.txt -name '*f*' -o -name '*t*'
34 * # find file.txt -name '*z*' -o -name '*t*'
36 * # find file.txt -name '*f*' -o -name '*z*'
39 * # find t z -name '*t*' -print -o -name '*z*'
41 * # find t z t z -name '*t*' -o -name '*z*' -print
44 * # find t z t z '(' -name '*t*' -o -name '*z*' ')' -o -print
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
60 //config: find is used to search your system to find specified files.
62 //config:config FEATURE_FIND_PRINT0
63 //config: bool "Enable -print0: NUL-terminated output"
65 //config: depends on FIND
67 //config: Causes output names to be separated by a NUL character
68 //config: rather than a newline. This allows names that contain
69 //config: newlines and other whitespace to be more easily
70 //config: interpreted by other programs.
72 //config:config FEATURE_FIND_MTIME
73 //config: bool "Enable -mtime: modified time matching"
75 //config: depends on FIND
77 //config: Allow searching based on the modification time of
78 //config: files, in days.
80 //config:config FEATURE_FIND_MMIN
81 //config: bool "Enable -mmin: modified time matching by minutes"
83 //config: depends on FIND
85 //config: Allow searching based on the modification time of
86 //config: files, in minutes.
88 //config:config FEATURE_FIND_PERM
89 //config: bool "Enable -perm: permissions matching"
91 //config: depends on FIND
93 //config: Enable searching based on file permissions.
95 //config:config FEATURE_FIND_TYPE
96 //config: bool "Enable -type: file type matching (file/dir/link/...)"
98 //config: depends on FIND
100 //config: Enable searching based on file type (file,
101 //config: directory, socket, device, etc.).
103 //config:config FEATURE_FIND_XDEV
104 //config: bool "Enable -xdev: 'stay in filesystem'"
106 //config: depends on FIND
108 //config: This option allows find to restrict searches to a single filesystem.
110 //config:config FEATURE_FIND_MAXDEPTH
111 //config: bool "Enable -mindepth N and -maxdepth N"
113 //config: depends on FIND
115 //config: This option enables -mindepth N and -maxdepth N option.
117 //config:config FEATURE_FIND_NEWER
118 //config: bool "Enable -newer: compare file modification times"
120 //config: depends on FIND
122 //config: Support the 'find -newer' option for finding any files which have
123 //config: modification time that is more recent than the specified FILE.
125 //config:config FEATURE_FIND_INUM
126 //config: bool "Enable -inum: inode number matching"
128 //config: depends on FIND
130 //config: Support the 'find -inum' option for searching by inode number.
132 //config:config FEATURE_FIND_EXEC
133 //config: bool "Enable -exec: execute commands"
135 //config: depends on FIND
137 //config: Support the 'find -exec' option for executing commands based upon
138 //config: the files matched.
140 //config:config FEATURE_FIND_USER
141 //config: bool "Enable -user: username/uid matching"
143 //config: depends on FIND
145 //config: Support the 'find -user' option for searching by username or uid.
147 //config:config FEATURE_FIND_GROUP
148 //config: bool "Enable -group: group/gid matching"
150 //config: depends on FIND
152 //config: Support the 'find -group' option for searching by group name or gid.
154 //config:config FEATURE_FIND_NOT
155 //config: bool "Enable the 'not' (!) operator"
157 //config: depends on FIND
159 //config: Support the '!' operator to invert the test results.
160 //config: If 'Enable full-blown desktop' is enabled, then will also support
161 //config: the non-POSIX notation '-not'.
163 //config:config FEATURE_FIND_DEPTH
164 //config: bool "Enable -depth"
166 //config: depends on FIND
168 //config: Process each directory's contents before the directory itself.
170 //config:config FEATURE_FIND_PAREN
171 //config: bool "Enable parens in options"
173 //config: depends on FIND
175 //config: Enable usage of parens '(' to specify logical order of arguments.
177 //config:config FEATURE_FIND_SIZE
178 //config: bool "Enable -size: file size matching"
180 //config: depends on FIND
182 //config: Support the 'find -size' option for searching by file size.
184 //config:config FEATURE_FIND_PRUNE
185 //config: bool "Enable -prune: exclude subdirectories"
187 //config: depends on FIND
189 //config: If the file is a directory, dont descend into it. Useful for
190 //config: exclusion .svn and CVS directories.
192 //config:config FEATURE_FIND_DELETE
193 //config: bool "Enable -delete: delete files/dirs"
195 //config: depends on FIND && FEATURE_FIND_DEPTH
197 //config: Support the 'find -delete' option for deleting files and directories.
198 //config: WARNING: This option can do much harm if used wrong. Busybox will not
199 //config: try to protect the user from doing stupid things. Use with care.
201 //config:config FEATURE_FIND_PATH
202 //config: bool "Enable -path: match pathname with shell pattern"
204 //config: depends on FIND
206 //config: The -path option matches whole pathname instead of just filename.
208 //config:config FEATURE_FIND_REGEX
209 //config: bool "Enable -regex: match pathname with regex"
211 //config: depends on FIND
213 //config: The -regex option matches whole pathname against regular expression.
215 //config:config FEATURE_FIND_CONTEXT
216 //config: bool "Enable -context: security context matching"
218 //config: depends on FIND && SELINUX
220 //config: Support the 'find -context' option for matching security context.
222 //config:config FEATURE_FIND_LINKS
223 //config: bool "Enable -links: link count matching"
225 //config: depends on FIND
227 //config: Support the 'find -links' option for matching number of links.
229 //applet:IF_FIND(APPLET_NOEXEC(find, find, BB_DIR_USR_BIN, BB_SUID_DROP, find))
231 //kbuild:lib-$(CONFIG_FIND) += find.o
233 //usage:#define find_trivial_usage
234 //usage: "[PATH]... [OPTIONS] [ACTIONS]"
235 //usage:#define find_full_usage "\n\n"
236 //usage: "Search for files and perform actions on them.\n"
237 //usage: "First failed action stops processing of current file.\n"
238 //usage: "Defaults: PATH is current directory, action is '-print'\n"
239 //usage: "\n -follow Follow symlinks"
240 //usage: IF_FEATURE_FIND_XDEV(
241 //usage: "\n -xdev Don't descend directories on other filesystems"
243 //usage: IF_FEATURE_FIND_MAXDEPTH(
244 //usage: "\n -maxdepth N Descend at most N levels. -maxdepth 0 applies"
245 //usage: "\n actions to command line arguments only"
246 //usage: "\n -mindepth N Don't act on first N levels"
248 //usage: IF_FEATURE_FIND_DEPTH(
249 //usage: "\n -depth Act on directory *after* traversing it"
252 //usage: "\nActions:"
253 //usage: IF_FEATURE_FIND_PAREN(
254 //usage: "\n ( ACTIONS ) Group actions for -o / -a"
256 //usage: IF_FEATURE_FIND_NOT(
257 //usage: "\n ! ACT Invert ACT's success/failure"
259 //usage: "\n ACT1 [-a] ACT2 If ACT1 fails, stop, else do ACT2"
260 //usage: "\n ACT1 -o ACT2 If ACT1 succeeds, stop, else do ACT2"
261 //usage: "\n Note: -a has higher priority than -o"
262 //usage: "\n -name PATTERN Match file name (w/o directory name) to PATTERN"
263 //usage: "\n -iname PATTERN Case insensitive -name"
264 //usage: IF_FEATURE_FIND_PATH(
265 //usage: "\n -path PATTERN Match path to PATTERN"
266 //usage: "\n -ipath PATTERN Case insensitive -path"
268 //usage: IF_FEATURE_FIND_REGEX(
269 //usage: "\n -regex PATTERN Match path to regex PATTERN"
271 //usage: IF_FEATURE_FIND_TYPE(
272 //usage: "\n -type X File type is X (one of: f,d,l,b,c,...)"
274 //usage: IF_FEATURE_FIND_PERM(
275 //usage: "\n -perm MASK At least one mask bit (+MASK), all bits (-MASK),"
276 //usage: "\n or exactly MASK bits are set in file's mode"
278 //usage: IF_FEATURE_FIND_MTIME(
279 //usage: "\n -mtime DAYS mtime is greater than (+N), less than (-N),"
280 //usage: "\n or exactly N days in the past"
282 //usage: IF_FEATURE_FIND_MMIN(
283 //usage: "\n -mmin MINS mtime is greater than (+N), less than (-N),"
284 //usage: "\n or exactly N minutes in the past"
286 //usage: IF_FEATURE_FIND_NEWER(
287 //usage: "\n -newer FILE mtime is more recent than FILE's"
289 //usage: IF_FEATURE_FIND_INUM(
290 //usage: "\n -inum N File has inode number N"
292 //usage: IF_FEATURE_FIND_USER(
293 //usage: "\n -user NAME/ID File is owned by given user"
295 //usage: IF_FEATURE_FIND_GROUP(
296 //usage: "\n -group NAME/ID File is owned by given group"
298 //usage: IF_FEATURE_FIND_SIZE(
299 //usage: "\n -size N[bck] File size is N (c:bytes,k:kbytes,b:512 bytes(def.))"
300 //usage: "\n +/-N: file size is bigger/smaller than N"
302 //usage: IF_FEATURE_FIND_LINKS(
303 //usage: "\n -links N Number of links is greater than (+N), less than (-N),"
304 //usage: "\n or exactly N"
306 //usage: IF_FEATURE_FIND_CONTEXT(
307 //usage: "\n -context CTX File has specified security context"
309 //usage: IF_FEATURE_FIND_PRUNE(
310 //usage: "\n -prune If current file is directory, don't descend into it"
312 //usage: "\nIf none of the following actions is specified, -print is assumed"
313 //usage: "\n -print Print file name"
314 //usage: IF_FEATURE_FIND_PRINT0(
315 //usage: "\n -print0 Print file name, NUL terminated"
317 //usage: IF_FEATURE_FIND_EXEC(
318 //usage: "\n -exec CMD ARG ; Run CMD with all instances of {} replaced by"
319 //usage: "\n file name. Fails if CMD exits with nonzero"
321 //usage: IF_FEATURE_FIND_DELETE(
322 //usage: "\n -delete Delete current file/directory. Turns on -depth option"
325 //usage:#define find_example_usage
326 //usage: "$ find / -name passwd\n"
327 //usage: "/etc/passwd\n"
331 #if ENABLE_FEATURE_FIND_REGEX
336 # define FNM_CASEFOLD 0
339 #define dbg(...) ((void)0)
340 /* #define dbg(...) bb_error_msg(__VA_ARGS__) */
342 /* This is a NOEXEC applet. Be very careful! */
345 typedef int (*action_fp
)(const char *fileName
, const struct stat
*statbuf
, void *) FAST_FUNC
;
349 #if ENABLE_FEATURE_FIND_NOT
354 #define ACTS(name, ...) typedef struct { action a; __VA_ARGS__ } action_##name;
356 static int FAST_FUNC func_##name(const char *fileName UNUSED_PARAM, \
357 const struct stat *statbuf UNUSED_PARAM, \
358 action_##name* ap UNUSED_PARAM)
361 ACTS(name
, const char *pattern
; bool iname
;)
362 IF_FEATURE_FIND_PATH( ACTS(path
, const char *pattern
; bool ipath
;))
363 IF_FEATURE_FIND_REGEX( ACTS(regex
, regex_t compiled_pattern
;))
364 IF_FEATURE_FIND_PRINT0( ACTS(print0
))
365 IF_FEATURE_FIND_TYPE( ACTS(type
, int type_mask
;))
366 IF_FEATURE_FIND_PERM( ACTS(perm
, char perm_char
; mode_t perm_mask
;))
367 IF_FEATURE_FIND_MTIME( ACTS(mtime
, char mtime_char
; unsigned mtime_days
;))
368 IF_FEATURE_FIND_MMIN( ACTS(mmin
, char mmin_char
; unsigned mmin_mins
;))
369 IF_FEATURE_FIND_NEWER( ACTS(newer
, time_t newer_mtime
;))
370 IF_FEATURE_FIND_INUM( ACTS(inum
, ino_t inode_num
;))
371 IF_FEATURE_FIND_USER( ACTS(user
, uid_t uid
;))
372 IF_FEATURE_FIND_SIZE( ACTS(size
, char size_char
; off_t size
;))
373 IF_FEATURE_FIND_CONTEXT(ACTS(context
, security_context_t context
;))
374 IF_FEATURE_FIND_PAREN( ACTS(paren
, action
***subexpr
;))
375 IF_FEATURE_FIND_PRUNE( ACTS(prune
))
376 IF_FEATURE_FIND_DELETE( ACTS(delete))
377 IF_FEATURE_FIND_EXEC( ACTS(exec
, char **exec_argv
; unsigned *subst_count
; int exec_argc
;))
378 IF_FEATURE_FIND_GROUP( ACTS(group
, gid_t gid
;))
379 IF_FEATURE_FIND_LINKS( ACTS(links
, char links_char
; int links_count
;))
382 IF_FEATURE_FIND_XDEV(dev_t
*xdev_dev
;)
383 IF_FEATURE_FIND_XDEV(int xdev_count
;)
384 #if ENABLE_FEATURE_FIND_MAXDEPTH
390 recurse_flags_t recurse_flags
;
392 #define G (*(struct globals*)&bb_common_bufsiz1)
393 #define INIT_G() do { \
394 struct G_sizecheck { \
395 char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \
397 /* we have to zero it out because of NOEXEC */ \
398 memset(&G, 0, sizeof(G)); \
399 IF_FEATURE_FIND_MAXDEPTH(G.minmaxdepth[1] = INT_MAX;) \
401 G.recurse_flags = ACTION_RECURSE; \
404 #if ENABLE_FEATURE_FIND_EXEC
405 static unsigned count_subst(const char *str
)
408 while ((str
= strstr(str
, "{}")) != NULL
) {
416 static char* subst(const char *src
, unsigned count
, const char* filename
)
418 char *buf
, *dst
, *end
;
419 size_t flen
= strlen(filename
);
420 /* we replace each '{}' with filename: growth by strlen-2 */
421 buf
= dst
= xmalloc(strlen(src
) + count
*(flen
-2) + 1);
422 while ((end
= strstr(src
, "{}"))) {
423 memcpy(dst
, src
, end
- src
);
426 memcpy(dst
, filename
, flen
);
434 /* Return values of ACTFs ('action functions') are a bit mask:
435 * bit 1=1: prune (use SKIP constant for setting it)
436 * bit 0=1: matched successfully (TRUE)
439 static int exec_actions(action
***appp
, const char *fileName
, const struct stat
*statbuf
)
446 /* "action group" is a set of actions ANDed together.
447 * groups are ORed together.
448 * We simply evaluate each group until we find one in which all actions
451 /* -prune is special: if it is encountered, then we won't
452 * descend into current directory. It doesn't matter whether
453 * action group (in which -prune sits) will succeed or not:
454 * find * -prune -name 'f*' -o -name 'm*' -- prunes every dir
455 * find * -name 'f*' -o -prune -name 'm*' -- prunes all dirs
456 * not starting with 'f' */
458 /* We invert TRUE bit (bit 0). Now 1 there means 'failure'.
459 * and bitwise OR in "rc |= TRUE ^ ap->f()" will:
460 * (1) make SKIP (-prune) bit stick; and (2) detect 'failure'.
461 * On return, bit is restored. */
464 while ((app
= appp
[++cur_group
]) != NULL
) {
465 rc
&= ~TRUE
; /* 'success' so far, clear TRUE bit */
468 ap
= app
[++cur_action
];
469 if (!ap
) /* all actions in group were successful */
470 return rc
^ TRUE
; /* restore TRUE bit */
471 rc
|= TRUE
^ ap
->f(fileName
, statbuf
, ap
);
472 #if ENABLE_FEATURE_FIND_NOT
473 if (ap
->invert
) rc
^= TRUE
;
475 dbg("grp %d action %d rc:0x%x", cur_group
, cur_action
, rc
);
476 if (rc
& TRUE
) /* current group failed, try next */
480 dbg("returning:0x%x", rc
^ TRUE
);
481 return rc
^ TRUE
; /* restore TRUE bit */
486 static char *strcpy_upcase(char *dst
, const char *src
)
490 unsigned char ch
= *src
++;
491 if (ch
>= 'a' && ch
<= 'z')
503 const char *tmp
= bb_basename(fileName
);
504 if (tmp
!= fileName
&& *tmp
== '\0') {
505 /* "foo/bar/". Oh no... go back to 'b' */
507 while (tmp
!= fileName
&& *--tmp
!= '/')
512 /* Was using FNM_PERIOD flag too,
513 * but somewhere between 4.1.20 and 4.4.0 GNU find stopped using it.
514 * find -name '*foo' should match .foo too:
517 return fnmatch(ap
->pattern
, tmp
, (ap
->iname
? FNM_CASEFOLD
: 0)) == 0;
520 tmp
= strcpy_upcase(alloca(strlen(tmp
) + 1), tmp
);
521 return fnmatch(ap
->pattern
, tmp
, 0) == 0;
525 #if ENABLE_FEATURE_FIND_PATH
529 return fnmatch(ap
->pattern
, fileName
, (ap
->ipath
? FNM_CASEFOLD
: 0)) == 0;
532 fileName
= strcpy_upcase(alloca(strlen(fileName
) + 1), fileName
);
533 return fnmatch(ap
->pattern
, fileName
, 0) == 0;
537 #if ENABLE_FEATURE_FIND_REGEX
541 if (regexec(&ap
->compiled_pattern
, fileName
, 1, &match
, 0 /*eflags*/))
542 return 0; /* no match */
544 return 0; /* match doesn't start at pos 0 */
545 if (fileName
[match
.rm_eo
])
546 return 0; /* match doesn't end exactly at end of pathname */
550 #if ENABLE_FEATURE_FIND_TYPE
553 return ((statbuf
->st_mode
& S_IFMT
) == ap
->type_mask
);
556 #if ENABLE_FEATURE_FIND_PERM
559 /* -perm +mode: at least one of perm_mask bits are set */
560 if (ap
->perm_char
== '+')
561 return (statbuf
->st_mode
& ap
->perm_mask
) != 0;
562 /* -perm -mode: all of perm_mask are set */
563 if (ap
->perm_char
== '-')
564 return (statbuf
->st_mode
& ap
->perm_mask
) == ap
->perm_mask
;
565 /* -perm mode: file mode must match perm_mask */
566 return (statbuf
->st_mode
& 07777) == ap
->perm_mask
;
569 #if ENABLE_FEATURE_FIND_MTIME
572 time_t file_age
= time(NULL
) - statbuf
->st_mtime
;
573 time_t mtime_secs
= ap
->mtime_days
* 24*60*60;
574 if (ap
->mtime_char
== '+')
575 return file_age
>= mtime_secs
+ 24*60*60;
576 if (ap
->mtime_char
== '-')
577 return file_age
< mtime_secs
;
578 /* just numeric mtime */
579 return file_age
>= mtime_secs
&& file_age
< (mtime_secs
+ 24*60*60);
582 #if ENABLE_FEATURE_FIND_MMIN
585 time_t file_age
= time(NULL
) - statbuf
->st_mtime
;
586 time_t mmin_secs
= ap
->mmin_mins
* 60;
587 if (ap
->mmin_char
== '+')
588 return file_age
>= mmin_secs
+ 60;
589 if (ap
->mmin_char
== '-')
590 return file_age
< mmin_secs
;
591 /* just numeric mmin */
592 return file_age
>= mmin_secs
&& file_age
< (mmin_secs
+ 60);
595 #if ENABLE_FEATURE_FIND_NEWER
598 return (ap
->newer_mtime
< statbuf
->st_mtime
);
601 #if ENABLE_FEATURE_FIND_INUM
604 return (statbuf
->st_ino
== ap
->inode_num
);
607 #if ENABLE_FEATURE_FIND_EXEC
611 #if ENABLE_USE_PORTABLE_CODE
612 char **argv
= alloca(sizeof(char*) * (ap
->exec_argc
+ 1));
613 #else /* gcc 4.3.1 generates smaller code: */
614 char *argv
[ap
->exec_argc
+ 1];
616 for (i
= 0; i
< ap
->exec_argc
; i
++)
617 argv
[i
] = subst(ap
->exec_argv
[i
], ap
->subst_count
[i
], fileName
);
618 argv
[i
] = NULL
; /* terminate the list */
620 rc
= spawn_and_wait(argv
);
622 bb_simple_perror_msg(argv
[0]);
627 return rc
== 0; /* return 1 if exitcode 0 */
630 #if ENABLE_FEATURE_FIND_USER
633 return (statbuf
->st_uid
== ap
->uid
);
636 #if ENABLE_FEATURE_FIND_GROUP
639 return (statbuf
->st_gid
== ap
->gid
);
642 #if ENABLE_FEATURE_FIND_PRINT0
645 printf("%s%c", fileName
, '\0');
654 #if ENABLE_FEATURE_FIND_PAREN
657 return exec_actions(ap
->subexpr
, fileName
, statbuf
);
660 #if ENABLE_FEATURE_FIND_SIZE
663 if (ap
->size_char
== '+')
664 return statbuf
->st_size
> ap
->size
;
665 if (ap
->size_char
== '-')
666 return statbuf
->st_size
< ap
->size
;
667 return statbuf
->st_size
== ap
->size
;
670 #if ENABLE_FEATURE_FIND_PRUNE
672 * -prune: if -depth is not given, return true and do not descend
673 * current dir; if -depth is given, return false with no effect.
675 * find dir -name 'asm-*' -prune -o -name '*.[chS]' -print
682 #if ENABLE_FEATURE_FIND_DELETE
686 if (S_ISDIR(statbuf
->st_mode
)) {
687 rc
= rmdir(fileName
);
689 rc
= unlink(fileName
);
692 bb_simple_perror_msg(fileName
);
696 #if ENABLE_FEATURE_FIND_CONTEXT
699 security_context_t con
;
702 if (G
.recurse_flags
& ACTION_FOLLOWLINKS
) {
703 rc
= getfilecon(fileName
, &con
);
705 rc
= lgetfilecon(fileName
, &con
);
709 rc
= strcmp(ap
->context
, con
);
714 #if ENABLE_FEATURE_FIND_LINKS
717 switch(ap
->links_char
) {
718 case '-' : return (statbuf
->st_nlink
< ap
->links_count
);
719 case '+' : return (statbuf
->st_nlink
> ap
->links_count
);
720 default: return (statbuf
->st_nlink
== ap
->links_count
);
725 static int FAST_FUNC
fileAction(const char *fileName
,
726 struct stat
*statbuf
,
727 void *userData UNUSED_PARAM
,
728 int depth
IF_NOT_FEATURE_FIND_MAXDEPTH(UNUSED_PARAM
))
732 #if ENABLE_FEATURE_FIND_MAXDEPTH
733 if (depth
< G
.minmaxdepth
[0])
734 return TRUE
; /* skip this, continue recursing */
735 if (depth
> G
.minmaxdepth
[1])
736 return SKIP
; /* stop recursing */
739 r
= exec_actions(G
.actions
, fileName
, statbuf
);
740 /* Had no explicit -print[0] or -exec? then print */
741 if ((r
& TRUE
) && G
.need_print
)
744 #if ENABLE_FEATURE_FIND_MAXDEPTH
745 if (S_ISDIR(statbuf
->st_mode
)) {
746 if (depth
== G
.minmaxdepth
[1])
750 #if ENABLE_FEATURE_FIND_XDEV
751 /* -xdev stops on mountpoints, but AFTER mountpoit itself
752 * is processed as usual */
753 if (S_ISDIR(statbuf
->st_mode
)) {
756 for (i
= 0; i
< G
.xdev_count
; i
++) {
757 if (G
.xdev_dev
[i
] == statbuf
->st_dev
)
766 /* Cannot return 0: our caller, recursive_action(),
767 * will perror() and skip dirs (if called on dir) */
768 return (r
& SKIP
) ? SKIP
: TRUE
;
772 #if ENABLE_FEATURE_FIND_TYPE
773 static int find_type(const char *type
)
779 else if (*type
== 'c')
781 else if (*type
== 'd')
783 else if (*type
== 'p')
785 else if (*type
== 'f')
787 else if (*type
== 'l')
789 else if (*type
== 's')
792 if (mask
== 0 || type
[1] != '\0')
793 bb_error_msg_and_die(bb_msg_invalid_arg
, type
, "-type");
799 #if ENABLE_FEATURE_FIND_PERM \
800 || ENABLE_FEATURE_FIND_MTIME || ENABLE_FEATURE_FIND_MMIN \
801 || ENABLE_FEATURE_FIND_SIZE || ENABLE_FEATURE_FIND_LINKS
802 static const char* plus_minus_num(const char* str
)
804 if (*str
== '-' || *str
== '+')
810 static action
*** parse_params(char **argv
)
814 IF_FEATURE_FIND_XDEV( OPT_XDEV
,)
815 IF_FEATURE_FIND_DEPTH( OPT_DEPTH
,)
818 IF_FEATURE_FIND_NOT( PARM_char_not
,)
822 IF_FEATURE_FIND_NOT( PARM_not
,)
825 IF_FEATURE_FIND_PRINT0( PARM_print0
,)
826 IF_FEATURE_FIND_PRUNE( PARM_prune
,)
827 IF_FEATURE_FIND_DELETE( PARM_delete
,)
828 IF_FEATURE_FIND_EXEC( PARM_exec
,)
829 IF_FEATURE_FIND_PAREN( PARM_char_brace
,)
830 /* All options/actions starting from here require argument */
833 IF_FEATURE_FIND_PATH( PARM_path
,)
835 /* -wholename is a synonym for -path */
836 /* We support it because Linux kernel's "make tags" uses it */
837 IF_FEATURE_FIND_PATH( PARM_wholename
,)
839 IF_FEATURE_FIND_PATH( PARM_ipath
,)
840 IF_FEATURE_FIND_REGEX( PARM_regex
,)
841 IF_FEATURE_FIND_TYPE( PARM_type
,)
842 IF_FEATURE_FIND_PERM( PARM_perm
,)
843 IF_FEATURE_FIND_MTIME( PARM_mtime
,)
844 IF_FEATURE_FIND_MMIN( PARM_mmin
,)
845 IF_FEATURE_FIND_NEWER( PARM_newer
,)
846 IF_FEATURE_FIND_INUM( PARM_inum
,)
847 IF_FEATURE_FIND_USER( PARM_user
,)
848 IF_FEATURE_FIND_GROUP( PARM_group
,)
849 IF_FEATURE_FIND_SIZE( PARM_size
,)
850 IF_FEATURE_FIND_CONTEXT(PARM_context
,)
851 IF_FEATURE_FIND_LINKS( PARM_links
,)
852 IF_FEATURE_FIND_MAXDEPTH(OPT_MINDEPTH
,OPT_MAXDEPTH
,)
855 static const char params
[] ALIGN1
=
857 IF_FEATURE_FIND_XDEV( "-xdev\0" )
858 IF_FEATURE_FIND_DEPTH( "-depth\0" )
861 IF_FEATURE_FIND_NOT( "!\0" )
865 IF_FEATURE_FIND_NOT( "-not\0" )
868 IF_FEATURE_FIND_PRINT0( "-print0\0" )
869 IF_FEATURE_FIND_PRUNE( "-prune\0" )
870 IF_FEATURE_FIND_DELETE( "-delete\0" )
871 IF_FEATURE_FIND_EXEC( "-exec\0" )
872 IF_FEATURE_FIND_PAREN( "(\0" )
873 /* All options/actions starting from here require argument */
876 IF_FEATURE_FIND_PATH( "-path\0" )
878 IF_FEATURE_FIND_PATH( "-wholename\0")
880 IF_FEATURE_FIND_PATH( "-ipath\0" )
881 IF_FEATURE_FIND_REGEX( "-regex\0" )
882 IF_FEATURE_FIND_TYPE( "-type\0" )
883 IF_FEATURE_FIND_PERM( "-perm\0" )
884 IF_FEATURE_FIND_MTIME( "-mtime\0" )
885 IF_FEATURE_FIND_MMIN( "-mmin\0" )
886 IF_FEATURE_FIND_NEWER( "-newer\0" )
887 IF_FEATURE_FIND_INUM( "-inum\0" )
888 IF_FEATURE_FIND_USER( "-user\0" )
889 IF_FEATURE_FIND_GROUP( "-group\0" )
890 IF_FEATURE_FIND_SIZE( "-size\0" )
891 IF_FEATURE_FIND_CONTEXT("-context\0")
892 IF_FEATURE_FIND_LINKS( "-links\0" )
893 IF_FEATURE_FIND_MAXDEPTH("-mindepth\0""-maxdepth\0")
897 unsigned cur_group
= 0;
898 unsigned cur_action
= 0;
899 IF_FEATURE_FIND_NOT( bool invert_flag
= 0; )
901 /* This is the only place in busybox where we use nested function.
902 * So far more standard alternatives were bigger. */
903 /* Auto decl suppresses "func without a prototype" warning: */
904 auto action
* alloc_action(int sizeof_struct
, action_fp f
);
905 action
* alloc_action(int sizeof_struct
, action_fp f
)
908 appp
[cur_group
] = xrealloc(appp
[cur_group
], (cur_action
+2) * sizeof(*appp
));
909 appp
[cur_group
][cur_action
++] = ap
= xzalloc(sizeof_struct
);
910 appp
[cur_group
][cur_action
] = NULL
;
912 IF_FEATURE_FIND_NOT( ap
->invert
= invert_flag
; )
913 IF_FEATURE_FIND_NOT( invert_flag
= 0; )
917 #define ALLOC_ACTION(name) (action_##name*)alloc_action(sizeof(action_##name), (action_fp) func_##name)
919 appp
= xzalloc(2 * sizeof(appp
[0])); /* appp[0],[1] == NULL */
922 const char *arg
= argv
[0];
923 int parm
= index_in_strings(params
, arg
);
924 const char *arg1
= argv
[1];
926 dbg("arg:'%s' arg1:'%s' parm:%d PARM_type:%d", arg
, arg1
, parm
, PARM_type
);
928 if (parm
>= PARM_name
) {
929 /* All options/actions starting from -name require argument */
931 bb_error_msg_and_die(bb_msg_requires_arg
, arg
);
935 /* We can use big switch() here, but on i386
936 * it doesn't give smaller code. Other arches? */
938 /* Options always return true. They always take effect
939 * rather than being processed only when their place in the
940 * expression is reached.
943 if (parm
== OPT_FOLLOW
) {
944 dbg("follow enabled: %d", __LINE__
);
945 G
.recurse_flags
|= ACTION_FOLLOWLINKS
| ACTION_DANGLING_OK
;
947 #if ENABLE_FEATURE_FIND_XDEV
948 else if (parm
== OPT_XDEV
) {
953 #if ENABLE_FEATURE_FIND_MAXDEPTH
954 else if (parm
== OPT_MINDEPTH
|| parm
== OPT_MINDEPTH
+ 1) {
956 G
.minmaxdepth
[parm
- OPT_MINDEPTH
] = xatoi_positive(arg1
);
959 #if ENABLE_FEATURE_FIND_DEPTH
960 else if (parm
== OPT_DEPTH
) {
962 G
.recurse_flags
|= ACTION_DEPTHFIRST
;
965 /* Actions are grouped by operators
966 * ( expr ) Force precedence
967 * ! expr True if expr is false
968 * -not expr Same as ! expr
969 * expr1 [-a[nd]] expr2 And; expr2 is not evaluated if expr1 is false
970 * expr1 -o[r] expr2 Or; expr2 is not evaluated if expr1 is true
971 * expr1 , expr2 List; both expr1 and expr2 are always evaluated
972 * We implement: (), -a, -o
975 else if (parm
== PARM_a
IF_DESKTOP(|| parm
== PARM_and
)) {
977 /* no further special handling required */
979 else if (parm
== PARM_o
IF_DESKTOP(|| parm
== PARM_or
)) {
981 /* start new OR group */
983 appp
= xrealloc(appp
, (cur_group
+2) * sizeof(*appp
));
984 /*appp[cur_group] = NULL; - already NULL */
985 appp
[cur_group
+1] = NULL
;
988 #if ENABLE_FEATURE_FIND_NOT
989 else if (parm
== PARM_char_not
IF_DESKTOP(|| parm
== PARM_not
)) {
990 /* also handles "find ! ! -name 'foo*'" */
992 dbg("invert_flag:%d", invert_flag
);
996 else if (parm
== PARM_print
) {
999 (void) ALLOC_ACTION(print
);
1001 #if ENABLE_FEATURE_FIND_PRINT0
1002 else if (parm
== PARM_print0
) {
1003 dbg("%d", __LINE__
);
1005 (void) ALLOC_ACTION(print0
);
1008 #if ENABLE_FEATURE_FIND_PRUNE
1009 else if (parm
== PARM_prune
) {
1010 dbg("%d", __LINE__
);
1011 (void) ALLOC_ACTION(prune
);
1014 #if ENABLE_FEATURE_FIND_DELETE
1015 else if (parm
== PARM_delete
) {
1016 dbg("%d", __LINE__
);
1018 G
.recurse_flags
|= ACTION_DEPTHFIRST
;
1019 (void) ALLOC_ACTION(delete);
1022 #if ENABLE_FEATURE_FIND_EXEC
1023 else if (parm
== PARM_exec
) {
1026 dbg("%d", __LINE__
);
1028 ap
= ALLOC_ACTION(exec
);
1029 ap
->exec_argv
= ++argv
; /* first arg after -exec */
1030 /*ap->exec_argc = 0; - ALLOC_ACTION did it */
1032 if (!*argv
) /* did not see ';' or '+' until end */
1033 bb_error_msg_and_die(bb_msg_requires_arg
, "-exec");
1034 // find -exec echo Foo ">{}<" ";"
1035 // executes "echo Foo >FILENAME<",
1036 // find -exec echo Foo ">{}<" "+"
1037 // executes "echo Foo FILENAME1 FILENAME2 FILENAME3...".
1038 // TODO (so far we treat "+" just like ";")
1039 if ((argv
[0][0] == ';' || argv
[0][0] == '+')
1040 && argv
[0][1] == '\0'
1047 if (ap
->exec_argc
== 0)
1048 bb_error_msg_and_die(bb_msg_requires_arg
, arg
);
1049 ap
->subst_count
= xmalloc(ap
->exec_argc
* sizeof(int));
1052 ap
->subst_count
[i
] = count_subst(ap
->exec_argv
[i
]);
1055 #if ENABLE_FEATURE_FIND_PAREN
1056 else if (parm
== PARM_char_brace
) {
1059 unsigned nested
= 1;
1061 dbg("%d", __LINE__
);
1065 bb_error_msg_and_die("unpaired '('");
1066 if (LONE_CHAR(*endarg
, '('))
1068 else if (LONE_CHAR(*endarg
, ')') && !--nested
) {
1073 ap
= ALLOC_ACTION(paren
);
1074 ap
->subexpr
= parse_params(argv
+ 1);
1075 *endarg
= (char*) ")"; /* restore NULLed parameter */
1079 else if (parm
== PARM_name
|| parm
== PARM_iname
) {
1081 dbg("%d", __LINE__
);
1082 ap
= ALLOC_ACTION(name
);
1084 ap
->iname
= (parm
== PARM_iname
);
1086 #if ENABLE_FEATURE_FIND_PATH
1087 else if (parm
== PARM_path
IF_DESKTOP(|| parm
== PARM_wholename
) || parm
== PARM_ipath
) {
1089 dbg("%d", __LINE__
);
1090 ap
= ALLOC_ACTION(path
);
1092 ap
->ipath
= (parm
== PARM_ipath
);
1095 #if ENABLE_FEATURE_FIND_REGEX
1096 else if (parm
== PARM_regex
) {
1098 dbg("%d", __LINE__
);
1099 ap
= ALLOC_ACTION(regex
);
1100 xregcomp(&ap
->compiled_pattern
, arg1
, 0 /*cflags*/);
1103 #if ENABLE_FEATURE_FIND_TYPE
1104 else if (parm
== PARM_type
) {
1106 ap
= ALLOC_ACTION(type
);
1107 ap
->type_mask
= find_type(arg1
);
1108 dbg("created:type mask:%x", ap
->type_mask
);
1111 #if ENABLE_FEATURE_FIND_PERM
1112 /* -perm BITS File's mode bits are exactly BITS (octal or symbolic).
1113 * Symbolic modes use mode 0 as a point of departure.
1114 * -perm -BITS All of the BITS are set in file's mode.
1115 * -perm +BITS At least one of the BITS is set in file's mode.
1117 else if (parm
== PARM_perm
) {
1119 dbg("%d", __LINE__
);
1120 ap
= ALLOC_ACTION(perm
);
1121 ap
->perm_char
= arg1
[0];
1122 arg1
= plus_minus_num(arg1
);
1123 /*ap->perm_mask = 0; - ALLOC_ACTION did it */
1124 if (!bb_parse_mode(arg1
, &ap
->perm_mask
))
1125 bb_error_msg_and_die("invalid mode '%s'", arg1
);
1128 #if ENABLE_FEATURE_FIND_MTIME
1129 else if (parm
== PARM_mtime
) {
1131 dbg("%d", __LINE__
);
1132 ap
= ALLOC_ACTION(mtime
);
1133 ap
->mtime_char
= arg1
[0];
1134 ap
->mtime_days
= xatoul(plus_minus_num(arg1
));
1137 #if ENABLE_FEATURE_FIND_MMIN
1138 else if (parm
== PARM_mmin
) {
1140 dbg("%d", __LINE__
);
1141 ap
= ALLOC_ACTION(mmin
);
1142 ap
->mmin_char
= arg1
[0];
1143 ap
->mmin_mins
= xatoul(plus_minus_num(arg1
));
1146 #if ENABLE_FEATURE_FIND_NEWER
1147 else if (parm
== PARM_newer
) {
1148 struct stat stat_newer
;
1150 dbg("%d", __LINE__
);
1151 ap
= ALLOC_ACTION(newer
);
1152 xstat(arg1
, &stat_newer
);
1153 ap
->newer_mtime
= stat_newer
.st_mtime
;
1156 #if ENABLE_FEATURE_FIND_INUM
1157 else if (parm
== PARM_inum
) {
1159 dbg("%d", __LINE__
);
1160 ap
= ALLOC_ACTION(inum
);
1161 ap
->inode_num
= xatoul(arg1
);
1164 #if ENABLE_FEATURE_FIND_USER
1165 else if (parm
== PARM_user
) {
1167 dbg("%d", __LINE__
);
1168 ap
= ALLOC_ACTION(user
);
1169 ap
->uid
= bb_strtou(arg1
, NULL
, 10);
1171 ap
->uid
= xuname2uid(arg1
);
1174 #if ENABLE_FEATURE_FIND_GROUP
1175 else if (parm
== PARM_group
) {
1177 dbg("%d", __LINE__
);
1178 ap
= ALLOC_ACTION(group
);
1179 ap
->gid
= bb_strtou(arg1
, NULL
, 10);
1181 ap
->gid
= xgroup2gid(arg1
);
1184 #if ENABLE_FEATURE_FIND_SIZE
1185 else if (parm
== PARM_size
) {
1186 /* -size n[bckw]: file uses n units of space
1187 * b (default): units are 512-byte blocks
1193 #define XATOU_SFX xatoull_sfx
1195 #define XATOU_SFX xatoul_sfx
1197 static const struct suffix_mult find_suffixes
[] = {
1206 dbg("%d", __LINE__
);
1207 ap
= ALLOC_ACTION(size
);
1208 ap
->size_char
= arg1
[0];
1209 ap
->size
= XATOU_SFX(plus_minus_num(arg1
), find_suffixes
);
1212 #if ENABLE_FEATURE_FIND_CONTEXT
1213 else if (parm
== PARM_context
) {
1215 dbg("%d", __LINE__
);
1216 ap
= ALLOC_ACTION(context
);
1217 /*ap->context = NULL; - ALLOC_ACTION did it */
1218 /* SELinux headers erroneously declare non-const parameter */
1219 if (selinux_raw_to_trans_context((char*)arg1
, &ap
->context
))
1220 bb_simple_perror_msg(arg1
);
1223 #if ENABLE_FEATURE_FIND_LINKS
1224 else if (parm
== PARM_links
) {
1226 dbg("%d", __LINE__
);
1227 ap
= ALLOC_ACTION(links
);
1228 ap
->links_char
= arg1
[0];
1229 ap
->links_count
= xatoul(plus_minus_num(arg1
));
1233 bb_error_msg("unrecognized: %s", arg
);
1238 dbg("exiting %s", __func__
);
1243 int find_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
1244 int find_main(int argc UNUSED_PARAM
, char **argv
)
1246 int i
, firstopt
, status
= EXIT_SUCCESS
;
1251 for (firstopt
= 0; argv
[firstopt
]; firstopt
++) {
1252 if (argv
[firstopt
][0] == '-')
1254 if (ENABLE_FEATURE_FIND_NOT
&& LONE_CHAR(argv
[firstopt
], '!'))
1256 if (ENABLE_FEATURE_FIND_PAREN
&& LONE_CHAR(argv
[firstopt
], '('))
1259 if (firstopt
== 0) {
1260 *--argv
= (char*)".";
1264 G
.actions
= parse_params(&argv
[firstopt
]);
1265 argv
[firstopt
] = NULL
;
1267 #if ENABLE_FEATURE_FIND_XDEV
1271 G
.xdev_count
= firstopt
;
1272 G
.xdev_dev
= xzalloc(G
.xdev_count
* sizeof(G
.xdev_dev
[0]));
1273 for (i
= 0; argv
[i
]; i
++) {
1274 /* not xstat(): shouldn't bomb out on
1275 * "find not_exist exist -xdev" */
1276 if (stat(argv
[i
], &stbuf
) == 0)
1277 G
.xdev_dev
[i
] = stbuf
.st_dev
;
1278 /* else G.xdev_dev[i] stays 0 and
1279 * won't match any real device dev_t
1285 for (i
= 0; argv
[i
]; i
++) {
1286 if (!recursive_action(argv
[i
],
1287 G
.recurse_flags
,/* flags */
1288 fileAction
, /* file action */
1289 fileAction
, /* dir action */
1290 NULL
, /* user data */
1293 status
= EXIT_FAILURE
;