blob: 4f976d5528498a73f4ea5d7450ab261024936130
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 GPLv2, see file LICENSE in this source tree. |
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 | //config:config FIND |
57 | //config: bool "find" |
58 | //config: default y |
59 | //config: help |
60 | //config: find is used to search your system to find specified files. |
61 | //config: |
62 | //config:config FEATURE_FIND_PRINT0 |
63 | //config: bool "Enable -print0: NUL-terminated output" |
64 | //config: default y |
65 | //config: depends on FIND |
66 | //config: help |
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. |
71 | //config: |
72 | //config:config FEATURE_FIND_MTIME |
73 | //config: bool "Enable -mtime: modified time matching" |
74 | //config: default y |
75 | //config: depends on FIND |
76 | //config: help |
77 | //config: Allow searching based on the modification time of |
78 | //config: files, in days. |
79 | //config: |
80 | //config:config FEATURE_FIND_MMIN |
81 | //config: bool "Enable -mmin: modified time matching by minutes" |
82 | //config: default y |
83 | //config: depends on FIND |
84 | //config: help |
85 | //config: Allow searching based on the modification time of |
86 | //config: files, in minutes. |
87 | //config: |
88 | //config:config FEATURE_FIND_PERM |
89 | //config: bool "Enable -perm: permissions matching" |
90 | //config: default y |
91 | //config: depends on FIND |
92 | //config: help |
93 | //config: Enable searching based on file permissions. |
94 | //config: |
95 | //config:config FEATURE_FIND_TYPE |
96 | //config: bool "Enable -type: file type matching (file/dir/link/...)" |
97 | //config: default y |
98 | //config: depends on FIND |
99 | //config: help |
100 | //config: Enable searching based on file type (file, |
101 | //config: directory, socket, device, etc.). |
102 | //config: |
103 | //config:config FEATURE_FIND_XDEV |
104 | //config: bool "Enable -xdev: 'stay in filesystem'" |
105 | //config: default y |
106 | //config: depends on FIND |
107 | //config: help |
108 | //config: This option allows find to restrict searches to a single filesystem. |
109 | //config: |
110 | //config:config FEATURE_FIND_MAXDEPTH |
111 | //config: bool "Enable -mindepth N and -maxdepth N" |
112 | //config: default y |
113 | //config: depends on FIND |
114 | //config: help |
115 | //config: This option enables -mindepth N and -maxdepth N option. |
116 | //config: |
117 | //config:config FEATURE_FIND_NEWER |
118 | //config: bool "Enable -newer: compare file modification times" |
119 | //config: default y |
120 | //config: depends on FIND |
121 | //config: help |
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. |
124 | //config: |
125 | //config:config FEATURE_FIND_INUM |
126 | //config: bool "Enable -inum: inode number matching" |
127 | //config: default y |
128 | //config: depends on FIND |
129 | //config: help |
130 | //config: Support the 'find -inum' option for searching by inode number. |
131 | //config: |
132 | //config:config FEATURE_FIND_EXEC |
133 | //config: bool "Enable -exec: execute commands" |
134 | //config: default y |
135 | //config: depends on FIND |
136 | //config: help |
137 | //config: Support the 'find -exec' option for executing commands based upon |
138 | //config: the files matched. |
139 | //config: |
140 | //config:config FEATURE_FIND_EXEC_PLUS |
141 | //config: bool "Enable -exec ... {} +" |
142 | //config: default y |
143 | //config: depends on FEATURE_FIND_EXEC |
144 | //config: help |
145 | //config: Support the 'find -exec ... {} +' option for executing commands |
146 | //config: for all matched files at once. |
147 | //config: Without this option, -exec + is a synonym for -exec ; |
148 | //config: (IOW: it works correctly, but without expected speedup) |
149 | //config: |
150 | //config:config FEATURE_FIND_USER |
151 | //config: bool "Enable -user: username/uid matching" |
152 | //config: default y |
153 | //config: depends on FIND |
154 | //config: help |
155 | //config: Support the 'find -user' option for searching by username or uid. |
156 | //config: |
157 | //config:config FEATURE_FIND_GROUP |
158 | //config: bool "Enable -group: group/gid matching" |
159 | //config: default y |
160 | //config: depends on FIND |
161 | //config: help |
162 | //config: Support the 'find -group' option for searching by group name or gid. |
163 | //config: |
164 | //config:config FEATURE_FIND_NOT |
165 | //config: bool "Enable the 'not' (!) operator" |
166 | //config: default y |
167 | //config: depends on FIND |
168 | //config: help |
169 | //config: Support the '!' operator to invert the test results. |
170 | //config: If 'Enable full-blown desktop' is enabled, then will also support |
171 | //config: the non-POSIX notation '-not'. |
172 | //config: |
173 | //config:config FEATURE_FIND_DEPTH |
174 | //config: bool "Enable -depth" |
175 | //config: default y |
176 | //config: depends on FIND |
177 | //config: help |
178 | //config: Process each directory's contents before the directory itself. |
179 | //config: |
180 | //config:config FEATURE_FIND_PAREN |
181 | //config: bool "Enable parens in options" |
182 | //config: default y |
183 | //config: depends on FIND |
184 | //config: help |
185 | //config: Enable usage of parens '(' to specify logical order of arguments. |
186 | //config: |
187 | //config:config FEATURE_FIND_SIZE |
188 | //config: bool "Enable -size: file size matching" |
189 | //config: default y |
190 | //config: depends on FIND |
191 | //config: help |
192 | //config: Support the 'find -size' option for searching by file size. |
193 | //config: |
194 | //config:config FEATURE_FIND_PRUNE |
195 | //config: bool "Enable -prune: exclude subdirectories" |
196 | //config: default y |
197 | //config: depends on FIND |
198 | //config: help |
199 | //config: If the file is a directory, dont descend into it. Useful for |
200 | //config: exclusion .svn and CVS directories. |
201 | //config: |
202 | //config:config FEATURE_FIND_DELETE |
203 | //config: bool "Enable -delete: delete files/dirs" |
204 | //config: default y |
205 | //config: depends on FIND && FEATURE_FIND_DEPTH |
206 | //config: help |
207 | //config: Support the 'find -delete' option for deleting files and directories. |
208 | //config: WARNING: This option can do much harm if used wrong. Busybox will not |
209 | //config: try to protect the user from doing stupid things. Use with care. |
210 | //config: |
211 | //config:config FEATURE_FIND_PATH |
212 | //config: bool "Enable -path: match pathname with shell pattern" |
213 | //config: default y |
214 | //config: depends on FIND |
215 | //config: help |
216 | //config: The -path option matches whole pathname instead of just filename. |
217 | //config: |
218 | //config:config FEATURE_FIND_REGEX |
219 | //config: bool "Enable -regex: match pathname with regex" |
220 | //config: default y |
221 | //config: depends on FIND |
222 | //config: help |
223 | //config: The -regex option matches whole pathname against regular expression. |
224 | //config: |
225 | //config:config FEATURE_FIND_CONTEXT |
226 | //config: bool "Enable -context: security context matching" |
227 | //config: default n |
228 | //config: depends on FIND && SELINUX |
229 | //config: help |
230 | //config: Support the 'find -context' option for matching security context. |
231 | //config: |
232 | //config:config FEATURE_FIND_LINKS |
233 | //config: bool "Enable -links: link count matching" |
234 | //config: default y |
235 | //config: depends on FIND |
236 | //config: help |
237 | //config: Support the 'find -links' option for matching number of links. |
238 | |
239 | //applet:IF_FIND(APPLET_NOEXEC(find, find, BB_DIR_USR_BIN, BB_SUID_DROP, find)) |
240 | |
241 | //kbuild:lib-$(CONFIG_FIND) += find.o |
242 | |
243 | //usage:#define find_trivial_usage |
244 | //usage: "[-HL] [PATH]... [OPTIONS] [ACTIONS]" |
245 | //usage:#define find_full_usage "\n\n" |
246 | //usage: "Search for files and perform actions on them.\n" |
247 | //usage: "First failed action stops processing of current file.\n" |
248 | //usage: "Defaults: PATH is current directory, action is '-print'\n" |
249 | //usage: "\n -L,-follow Follow symlinks" |
250 | //usage: "\n -H ...on command line only" |
251 | //usage: IF_FEATURE_FIND_XDEV( |
252 | //usage: "\n -xdev Don't descend directories on other filesystems" |
253 | //usage: ) |
254 | //usage: IF_FEATURE_FIND_MAXDEPTH( |
255 | //usage: "\n -maxdepth N Descend at most N levels. -maxdepth 0 applies" |
256 | //usage: "\n actions to command line arguments only" |
257 | //usage: "\n -mindepth N Don't act on first N levels" |
258 | //usage: ) |
259 | //usage: IF_FEATURE_FIND_DEPTH( |
260 | //usage: "\n -depth Act on directory *after* traversing it" |
261 | //usage: ) |
262 | //usage: "\n" |
263 | //usage: "\nActions:" |
264 | //usage: IF_FEATURE_FIND_PAREN( |
265 | //usage: "\n ( ACTIONS ) Group actions for -o / -a" |
266 | //usage: ) |
267 | //usage: IF_FEATURE_FIND_NOT( |
268 | //usage: "\n ! ACT Invert ACT's success/failure" |
269 | //usage: ) |
270 | //usage: "\n ACT1 [-a] ACT2 If ACT1 fails, stop, else do ACT2" |
271 | //usage: "\n ACT1 -o ACT2 If ACT1 succeeds, stop, else do ACT2" |
272 | //usage: "\n Note: -a has higher priority than -o" |
273 | //usage: "\n -name PATTERN Match file name (w/o directory name) to PATTERN" |
274 | //usage: "\n -iname PATTERN Case insensitive -name" |
275 | //usage: IF_FEATURE_FIND_PATH( |
276 | //usage: "\n -path PATTERN Match path to PATTERN" |
277 | //usage: "\n -ipath PATTERN Case insensitive -path" |
278 | //usage: ) |
279 | //usage: IF_FEATURE_FIND_REGEX( |
280 | //usage: "\n -regex PATTERN Match path to regex PATTERN" |
281 | //usage: ) |
282 | //usage: IF_FEATURE_FIND_TYPE( |
283 | //usage: "\n -type X File type is X (one of: f,d,l,b,c,...)" |
284 | //usage: ) |
285 | //usage: IF_FEATURE_FIND_PERM( |
286 | //usage: "\n -perm MASK At least one mask bit (+MASK), all bits (-MASK)," |
287 | //usage: "\n or exactly MASK bits are set in file's mode" |
288 | //usage: ) |
289 | //usage: IF_FEATURE_FIND_MTIME( |
290 | //usage: "\n -mtime DAYS mtime is greater than (+N), less than (-N)," |
291 | //usage: "\n or exactly N days in the past" |
292 | //usage: ) |
293 | //usage: IF_FEATURE_FIND_MMIN( |
294 | //usage: "\n -mmin MINS mtime is greater than (+N), less than (-N)," |
295 | //usage: "\n or exactly N minutes in the past" |
296 | //usage: ) |
297 | //usage: IF_FEATURE_FIND_NEWER( |
298 | //usage: "\n -newer FILE mtime is more recent than FILE's" |
299 | //usage: ) |
300 | //usage: IF_FEATURE_FIND_INUM( |
301 | //usage: "\n -inum N File has inode number N" |
302 | //usage: ) |
303 | //usage: IF_FEATURE_FIND_USER( |
304 | //usage: "\n -user NAME/ID File is owned by given user" |
305 | //usage: ) |
306 | //usage: IF_FEATURE_FIND_GROUP( |
307 | //usage: "\n -group NAME/ID File is owned by given group" |
308 | //usage: ) |
309 | //usage: IF_FEATURE_FIND_SIZE( |
310 | //usage: "\n -size N[bck] File size is N (c:bytes,k:kbytes,b:512 bytes(def.))" |
311 | //usage: "\n +/-N: file size is bigger/smaller than N" |
312 | //usage: ) |
313 | //usage: IF_FEATURE_FIND_LINKS( |
314 | //usage: "\n -links N Number of links is greater than (+N), less than (-N)," |
315 | //usage: "\n or exactly N" |
316 | //usage: ) |
317 | //usage: IF_FEATURE_FIND_CONTEXT( |
318 | //usage: "\n -context CTX File has specified security context" |
319 | //usage: ) |
320 | //usage: IF_FEATURE_FIND_PRUNE( |
321 | //usage: "\n -prune If current file is directory, don't descend into it" |
322 | //usage: ) |
323 | //usage: "\nIf none of the following actions is specified, -print is assumed" |
324 | //usage: "\n -print Print file name" |
325 | //usage: IF_FEATURE_FIND_PRINT0( |
326 | //usage: "\n -print0 Print file name, NUL terminated" |
327 | //usage: ) |
328 | //usage: IF_FEATURE_FIND_EXEC( |
329 | //usage: "\n -exec CMD ARG ; Run CMD with all instances of {} replaced by" |
330 | //usage: "\n file name. Fails if CMD exits with nonzero" |
331 | //usage: ) |
332 | //usage: IF_FEATURE_FIND_EXEC_PLUS( |
333 | //usage: "\n -exec CMD ARG + Run CMD with {} replaced by list of file names" |
334 | //usage: ) |
335 | //usage: IF_FEATURE_FIND_DELETE( |
336 | //usage: "\n -delete Delete current file/directory. Turns on -depth option" |
337 | //usage: ) |
338 | //usage: |
339 | //usage:#define find_example_usage |
340 | //usage: "$ find / -name passwd\n" |
341 | //usage: "/etc/passwd\n" |
342 | |
343 | #include <fnmatch.h> |
344 | #include "libbb.h" |
345 | #include "common_bufsiz.h" |
346 | #if ENABLE_FEATURE_FIND_REGEX |
347 | # include "xregex.h" |
348 | #endif |
349 | /* GNUism: */ |
350 | #ifndef FNM_CASEFOLD |
351 | # define FNM_CASEFOLD 0 |
352 | #endif |
353 | |
354 | #if 1 |
355 | # define dbg(...) ((void)0) |
356 | #else |
357 | # define dbg(...) bb_error_msg(__VA_ARGS__) |
358 | #endif |
359 | |
360 | |
361 | /* This is a NOEXEC applet. Be very careful! */ |
362 | |
363 | |
364 | typedef int (*action_fp)(const char *fileName, const struct stat *statbuf, void *) FAST_FUNC; |
365 | |
366 | typedef struct { |
367 | action_fp f; |
368 | #if ENABLE_FEATURE_FIND_NOT |
369 | bool invert; |
370 | #endif |
371 | } action; |
372 | |
373 | #define ACTS(name, ...) typedef struct { action a; __VA_ARGS__ } action_##name; |
374 | #define ACTF(name) \ |
375 | static int FAST_FUNC func_##name(const char *fileName UNUSED_PARAM, \ |
376 | const struct stat *statbuf UNUSED_PARAM, \ |
377 | action_##name* ap UNUSED_PARAM) |
378 | |
379 | ACTS(print) |
380 | ACTS(name, const char *pattern; bool iname;) |
381 | IF_FEATURE_FIND_PATH( ACTS(path, const char *pattern; bool ipath;)) |
382 | IF_FEATURE_FIND_REGEX( ACTS(regex, regex_t compiled_pattern;)) |
383 | IF_FEATURE_FIND_PRINT0( ACTS(print0)) |
384 | IF_FEATURE_FIND_TYPE( ACTS(type, int type_mask;)) |
385 | IF_FEATURE_FIND_PERM( ACTS(perm, char perm_char; mode_t perm_mask;)) |
386 | IF_FEATURE_FIND_MTIME( ACTS(mtime, char mtime_char; unsigned mtime_days;)) |
387 | IF_FEATURE_FIND_MMIN( ACTS(mmin, char mmin_char; unsigned mmin_mins;)) |
388 | IF_FEATURE_FIND_NEWER( ACTS(newer, time_t newer_mtime;)) |
389 | IF_FEATURE_FIND_INUM( ACTS(inum, ino_t inode_num;)) |
390 | IF_FEATURE_FIND_USER( ACTS(user, uid_t uid;)) |
391 | IF_FEATURE_FIND_SIZE( ACTS(size, char size_char; filesize_t size;)) |
392 | IF_FEATURE_FIND_CONTEXT(ACTS(context, security_context_t context;)) |
393 | IF_FEATURE_FIND_PAREN( ACTS(paren, action ***subexpr;)) |
394 | IF_FEATURE_FIND_PRUNE( ACTS(prune)) |
395 | IF_FEATURE_FIND_DELETE( ACTS(delete)) |
396 | IF_FEATURE_FIND_EXEC( ACTS(exec, |
397 | char **exec_argv; /* -exec ARGS */ |
398 | unsigned *subst_count; |
399 | int exec_argc; /* count of ARGS */ |
400 | IF_FEATURE_FIND_EXEC_PLUS( |
401 | /* |
402 | * filelist is NULL if "exec ;" |
403 | * non-NULL if "exec +" |
404 | */ |
405 | char **filelist; |
406 | int filelist_idx; |
407 | int file_len; |
408 | ) |
409 | )) |
410 | IF_FEATURE_FIND_GROUP( ACTS(group, gid_t gid;)) |
411 | IF_FEATURE_FIND_LINKS( ACTS(links, char links_char; unsigned links_count;)) |
412 | |
413 | struct globals { |
414 | IF_FEATURE_FIND_XDEV(dev_t *xdev_dev;) |
415 | IF_FEATURE_FIND_XDEV(int xdev_count;) |
416 | #if ENABLE_FEATURE_FIND_MAXDEPTH |
417 | int minmaxdepth[2]; |
418 | #endif |
419 | action ***actions; |
420 | smallint need_print; |
421 | smallint xdev_on; |
422 | recurse_flags_t recurse_flags; |
423 | IF_FEATURE_FIND_EXEC_PLUS(unsigned max_argv_len;) |
424 | } FIX_ALIASING; |
425 | #define G (*(struct globals*)bb_common_bufsiz1) |
426 | #define INIT_G() do { \ |
427 | setup_common_bufsiz(); \ |
428 | BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \ |
429 | /* we have to zero it out because of NOEXEC */ \ |
430 | memset(&G, 0, sizeof(G)); \ |
431 | IF_FEATURE_FIND_MAXDEPTH(G.minmaxdepth[1] = INT_MAX;) \ |
432 | IF_FEATURE_FIND_EXEC_PLUS(G.max_argv_len = bb_arg_max() - 2048;) \ |
433 | G.need_print = 1; \ |
434 | G.recurse_flags = ACTION_RECURSE; \ |
435 | } while (0) |
436 | |
437 | /* Return values of ACTFs ('action functions') are a bit mask: |
438 | * bit 1=1: prune (use SKIP constant for setting it) |
439 | * bit 0=1: matched successfully (TRUE) |
440 | */ |
441 | |
442 | static int exec_actions(action ***appp, const char *fileName, const struct stat *statbuf) |
443 | { |
444 | int cur_group; |
445 | int cur_action; |
446 | int rc = 0; |
447 | action **app, *ap; |
448 | |
449 | /* "action group" is a set of actions ANDed together. |
450 | * groups are ORed together. |
451 | * We simply evaluate each group until we find one in which all actions |
452 | * succeed. */ |
453 | |
454 | /* -prune is special: if it is encountered, then we won't |
455 | * descend into current directory. It doesn't matter whether |
456 | * action group (in which -prune sits) will succeed or not: |
457 | * find * -prune -name 'f*' -o -name 'm*' -- prunes every dir |
458 | * find * -name 'f*' -o -prune -name 'm*' -- prunes all dirs |
459 | * not starting with 'f' */ |
460 | |
461 | /* We invert TRUE bit (bit 0). Now 1 there means 'failure'. |
462 | * and bitwise OR in "rc |= TRUE ^ ap->f()" will: |
463 | * (1) make SKIP (-prune) bit stick; and (2) detect 'failure'. |
464 | * On return, bit is restored. */ |
465 | |
466 | cur_group = -1; |
467 | while ((app = appp[++cur_group]) != NULL) { |
468 | rc &= ~TRUE; /* 'success' so far, clear TRUE bit */ |
469 | cur_action = -1; |
470 | while (1) { |
471 | ap = app[++cur_action]; |
472 | if (!ap) /* all actions in group were successful */ |
473 | return rc ^ TRUE; /* restore TRUE bit */ |
474 | rc |= TRUE ^ ap->f(fileName, statbuf, ap); |
475 | #if ENABLE_FEATURE_FIND_NOT |
476 | if (ap->invert) rc ^= TRUE; |
477 | #endif |
478 | dbg("grp %d action %d rc:0x%x", cur_group, cur_action, rc); |
479 | if (rc & TRUE) /* current group failed, try next */ |
480 | break; |
481 | } |
482 | } |
483 | dbg("returning:0x%x", rc ^ TRUE); |
484 | return rc ^ TRUE; /* restore TRUE bit */ |
485 | } |
486 | |
487 | #if !FNM_CASEFOLD |
488 | static char *strcpy_upcase(char *dst, const char *src) |
489 | { |
490 | char *d = dst; |
491 | while (1) { |
492 | unsigned char ch = *src++; |
493 | if (ch >= 'a' && ch <= 'z') |
494 | ch -= ('a' - 'A'); |
495 | *d++ = ch; |
496 | if (ch == '\0') |
497 | break; |
498 | } |
499 | return dst; |
500 | } |
501 | #endif |
502 | |
503 | ACTF(name) |
504 | { |
505 | int r; |
506 | const char *tmp = bb_basename(fileName); |
507 | /* GNU findutils: find DIR/ -name DIR |
508 | * prints "DIR/" (DIR// prints "DIR//" etc). |
509 | * Need to strip trailing "/". |
510 | * Such names can come only from top-level names, but |
511 | * we can't do this before recursive_action() call, |
512 | * since then "find FILE/ -name FILE" |
513 | * would also work (on non-directories), which is wrong. |
514 | */ |
515 | char *trunc_slash = NULL; |
516 | |
517 | if (*tmp == '\0') { |
518 | /* "foo/bar/[//...]" */ |
519 | while (tmp != fileName && tmp[-1] == '/') |
520 | tmp--; |
521 | if (tmp == fileName) { /* entire fileName is "//.."? */ |
522 | /* yes, convert "//..." to "/" |
523 | * Testcases: |
524 | * find / -maxdepth 1 -name /: prints / |
525 | * find // -maxdepth 1 -name /: prints // |
526 | * find / -maxdepth 1 -name //: prints nothing |
527 | * find // -maxdepth 1 -name //: prints nothing |
528 | */ |
529 | if (tmp[1]) |
530 | trunc_slash = (char*)tmp + 1; |
531 | } else { |
532 | /* no, it's "foo/bar/[//...]", go back to 'b' */ |
533 | trunc_slash = (char*)tmp; |
534 | while (tmp != fileName && tmp[-1] != '/') |
535 | tmp--; |
536 | } |
537 | } |
538 | |
539 | /* Was using FNM_PERIOD flag too, |
540 | * but somewhere between 4.1.20 and 4.4.0 GNU find stopped using it. |
541 | * find -name '*foo' should match .foo too: |
542 | */ |
543 | if (trunc_slash) *trunc_slash = '\0'; |
544 | #if FNM_CASEFOLD |
545 | r = fnmatch(ap->pattern, tmp, (ap->iname ? FNM_CASEFOLD : 0)); |
546 | #else |
547 | if (ap->iname) |
548 | tmp = strcpy_upcase(alloca(strlen(tmp) + 1), tmp); |
549 | r = fnmatch(ap->pattern, tmp, 0); |
550 | #endif |
551 | if (trunc_slash) *trunc_slash = '/'; |
552 | return r == 0; |
553 | } |
554 | |
555 | #if ENABLE_FEATURE_FIND_PATH |
556 | ACTF(path) |
557 | { |
558 | # if FNM_CASEFOLD |
559 | return fnmatch(ap->pattern, fileName, (ap->ipath ? FNM_CASEFOLD : 0)) == 0; |
560 | # else |
561 | if (ap->ipath) |
562 | fileName = strcpy_upcase(alloca(strlen(fileName) + 1), fileName); |
563 | return fnmatch(ap->pattern, fileName, 0) == 0; |
564 | # endif |
565 | } |
566 | #endif |
567 | #if ENABLE_FEATURE_FIND_REGEX |
568 | ACTF(regex) |
569 | { |
570 | regmatch_t match; |
571 | if (regexec(&ap->compiled_pattern, fileName, 1, &match, 0 /*eflags*/)) |
572 | return 0; /* no match */ |
573 | if (match.rm_so) |
574 | return 0; /* match doesn't start at pos 0 */ |
575 | if (fileName[match.rm_eo]) |
576 | return 0; /* match doesn't end exactly at end of pathname */ |
577 | return 1; |
578 | } |
579 | #endif |
580 | #if ENABLE_FEATURE_FIND_TYPE |
581 | ACTF(type) |
582 | { |
583 | return ((statbuf->st_mode & S_IFMT) == (unsigned) ap->type_mask); |
584 | } |
585 | #endif |
586 | #if ENABLE_FEATURE_FIND_PERM |
587 | ACTF(perm) |
588 | { |
589 | /* -perm [+/]mode: at least one of perm_mask bits are set */ |
590 | if (ap->perm_char == '+' || ap->perm_char == '/') |
591 | return (statbuf->st_mode & ap->perm_mask) != 0; |
592 | /* -perm -mode: all of perm_mask are set */ |
593 | if (ap->perm_char == '-') |
594 | return (statbuf->st_mode & ap->perm_mask) == ap->perm_mask; |
595 | /* -perm mode: file mode must match perm_mask */ |
596 | return (statbuf->st_mode & 07777) == ap->perm_mask; |
597 | } |
598 | #endif |
599 | #if ENABLE_FEATURE_FIND_MTIME |
600 | ACTF(mtime) |
601 | { |
602 | time_t file_age = time(NULL) - statbuf->st_mtime; |
603 | time_t mtime_secs = ap->mtime_days * 24*60*60; |
604 | if (ap->mtime_char == '+') |
605 | return file_age >= mtime_secs + 24*60*60; |
606 | if (ap->mtime_char == '-') |
607 | return file_age < mtime_secs; |
608 | /* just numeric mtime */ |
609 | return file_age >= mtime_secs && file_age < (mtime_secs + 24*60*60); |
610 | } |
611 | #endif |
612 | #if ENABLE_FEATURE_FIND_MMIN |
613 | ACTF(mmin) |
614 | { |
615 | time_t file_age = time(NULL) - statbuf->st_mtime; |
616 | time_t mmin_secs = ap->mmin_mins * 60; |
617 | if (ap->mmin_char == '+') |
618 | return file_age >= mmin_secs + 60; |
619 | if (ap->mmin_char == '-') |
620 | return file_age < mmin_secs; |
621 | /* just numeric mmin */ |
622 | return file_age >= mmin_secs && file_age < (mmin_secs + 60); |
623 | } |
624 | #endif |
625 | #if ENABLE_FEATURE_FIND_NEWER |
626 | ACTF(newer) |
627 | { |
628 | return ((time_t) ap->newer_mtime < (time_t) statbuf->st_mtime); |
629 | } |
630 | #endif |
631 | #if ENABLE_FEATURE_FIND_INUM |
632 | ACTF(inum) |
633 | { |
634 | return (statbuf->st_ino == ap->inode_num); |
635 | } |
636 | #endif |
637 | #if ENABLE_FEATURE_FIND_EXEC |
638 | static int do_exec(action_exec *ap, const char *fileName) |
639 | { |
640 | int i, rc; |
641 | # if ENABLE_FEATURE_FIND_EXEC_PLUS |
642 | int size = ap->exec_argc + ap->filelist_idx + 1; |
643 | # else |
644 | int size = ap->exec_argc + 1; |
645 | # endif |
646 | # if ENABLE_USE_PORTABLE_CODE |
647 | char **argv = alloca(sizeof(char*) * size); |
648 | # else /* gcc 4.3.1 generates smaller code: */ |
649 | char *argv[size]; |
650 | # endif |
651 | char **pp = argv; |
652 | |
653 | for (i = 0; i < ap->exec_argc; i++) { |
654 | const char *arg = ap->exec_argv[i]; |
655 | |
656 | # if ENABLE_FEATURE_FIND_EXEC_PLUS |
657 | if (ap->filelist) { |
658 | /* Handling "-exec +" |
659 | * Only one exec_argv[i] has substitution in it. |
660 | * Expand that one exec_argv[i] into file list. |
661 | */ |
662 | if (ap->subst_count[i] == 0) { |
663 | *pp++ = xstrdup(arg); |
664 | } else { |
665 | int j = 0; |
666 | while (ap->filelist[j]) { |
667 | /* 2nd arg here should be ap->subst_count[i], but it is always 1: */ |
668 | *pp++ = xmalloc_substitute_string(arg, 1, "{}", ap->filelist[j]); |
669 | free(ap->filelist[j]); |
670 | j++; |
671 | } |
672 | } |
673 | } else |
674 | # endif |
675 | { |
676 | /* Handling "-exec ;" */ |
677 | *pp++ = xmalloc_substitute_string(arg, ap->subst_count[i], "{}", fileName); |
678 | } |
679 | } |
680 | *pp = NULL; /* terminate the list */ |
681 | |
682 | # if ENABLE_FEATURE_FIND_EXEC_PLUS |
683 | if (ap->filelist) { |
684 | ap->filelist[0] = NULL; |
685 | ap->filelist_idx = 0; |
686 | ap->file_len = 0; |
687 | } |
688 | # endif |
689 | |
690 | rc = spawn_and_wait(argv); |
691 | if (rc < 0) |
692 | bb_simple_perror_msg(argv[0]); |
693 | |
694 | i = 0; |
695 | while (argv[i]) |
696 | free(argv[i++]); |
697 | return rc == 0; /* return 1 if exitcode 0 */ |
698 | } |
699 | ACTF(exec) |
700 | { |
701 | # if ENABLE_FEATURE_FIND_EXEC_PLUS |
702 | if (ap->filelist) { |
703 | int rc; |
704 | |
705 | ap->filelist = xrealloc_vector(ap->filelist, 8, ap->filelist_idx); |
706 | ap->filelist[ap->filelist_idx++] = xstrdup(fileName); |
707 | ap->file_len += strlen(fileName) + sizeof(char*) + 1; |
708 | /* If we have lots of files already, exec the command */ |
709 | rc = 1; |
710 | if (ap->file_len >= G.max_argv_len) |
711 | rc = do_exec(ap, NULL); |
712 | return rc; |
713 | } |
714 | # endif |
715 | return do_exec(ap, fileName); |
716 | } |
717 | # if ENABLE_FEATURE_FIND_EXEC_PLUS |
718 | static int flush_exec_plus(void) |
719 | { |
720 | action *ap; |
721 | action **app; |
722 | action ***appp = G.actions; |
723 | while ((app = *appp++) != NULL) { |
724 | while ((ap = *app++) != NULL) { |
725 | if (ap->f == (action_fp)func_exec) { |
726 | action_exec *ae = (void*)ap; |
727 | if (ae->filelist_idx != 0) { |
728 | int rc = do_exec(ae, NULL); |
729 | # if ENABLE_FEATURE_FIND_NOT |
730 | if (ap->invert) rc = !rc; |
731 | # endif |
732 | if (rc == 0) |
733 | return 1; |
734 | } |
735 | } |
736 | } |
737 | } |
738 | return 0; |
739 | } |
740 | # endif |
741 | #endif |
742 | #if ENABLE_FEATURE_FIND_USER |
743 | ACTF(user) |
744 | { |
745 | return (statbuf->st_uid == ap->uid); |
746 | } |
747 | #endif |
748 | #if ENABLE_FEATURE_FIND_GROUP |
749 | ACTF(group) |
750 | { |
751 | return (statbuf->st_gid == ap->gid); |
752 | } |
753 | #endif |
754 | #if ENABLE_FEATURE_FIND_PRINT0 |
755 | ACTF(print0) |
756 | { |
757 | printf("%s%c", fileName, '\0'); |
758 | return TRUE; |
759 | } |
760 | #endif |
761 | ACTF(print) |
762 | { |
763 | puts(fileName); |
764 | return TRUE; |
765 | } |
766 | #if ENABLE_FEATURE_FIND_PAREN |
767 | ACTF(paren) |
768 | { |
769 | return exec_actions(ap->subexpr, fileName, statbuf); |
770 | } |
771 | #endif |
772 | #if ENABLE_FEATURE_FIND_SIZE |
773 | ACTF(size) |
774 | { |
775 | if (ap->size_char == '+') |
776 | return statbuf->st_size > ap->size; |
777 | if (ap->size_char == '-') |
778 | return statbuf->st_size < ap->size; |
779 | return statbuf->st_size == ap->size; |
780 | } |
781 | #endif |
782 | #if ENABLE_FEATURE_FIND_PRUNE |
783 | /* |
784 | * -prune: if -depth is not given, return true and do not descend |
785 | * current dir; if -depth is given, return false with no effect. |
786 | * Example: |
787 | * find dir -name 'asm-*' -prune -o -name '*.[chS]' -print |
788 | */ |
789 | ACTF(prune) |
790 | { |
791 | return SKIP + TRUE; |
792 | } |
793 | #endif |
794 | #if ENABLE_FEATURE_FIND_DELETE |
795 | ACTF(delete) |
796 | { |
797 | int rc; |
798 | if (S_ISDIR(statbuf->st_mode)) { |
799 | /* "find . -delete" skips rmdir(".") */ |
800 | rc = 0; |
801 | if (NOT_LONE_CHAR(fileName, '.')) |
802 | rc = rmdir(fileName); |
803 | } else { |
804 | rc = unlink(fileName); |
805 | } |
806 | if (rc < 0) |
807 | bb_simple_perror_msg(fileName); |
808 | return TRUE; |
809 | } |
810 | #endif |
811 | #if ENABLE_FEATURE_FIND_CONTEXT |
812 | ACTF(context) |
813 | { |
814 | security_context_t con; |
815 | int rc; |
816 | |
817 | if (G.recurse_flags & ACTION_FOLLOWLINKS) { |
818 | rc = getfilecon(fileName, &con); |
819 | } else { |
820 | rc = lgetfilecon(fileName, &con); |
821 | } |
822 | if (rc < 0) |
823 | return FALSE; |
824 | rc = strcmp(ap->context, con); |
825 | freecon(con); |
826 | return rc == 0; |
827 | } |
828 | #endif |
829 | #if ENABLE_FEATURE_FIND_LINKS |
830 | ACTF(links) |
831 | { |
832 | switch(ap->links_char) { |
833 | case '-' : return (statbuf->st_nlink < ap->links_count); |
834 | case '+' : return (statbuf->st_nlink > ap->links_count); |
835 | default: return (statbuf->st_nlink == ap->links_count); |
836 | } |
837 | } |
838 | #endif |
839 | |
840 | static int FAST_FUNC fileAction(const char *fileName, |
841 | struct stat *statbuf, |
842 | void *userData UNUSED_PARAM, |
843 | int depth IF_NOT_FEATURE_FIND_MAXDEPTH(UNUSED_PARAM)) |
844 | { |
845 | int r; |
846 | int same_fs = 1; |
847 | |
848 | #if ENABLE_FEATURE_FIND_XDEV |
849 | if (S_ISDIR(statbuf->st_mode) && G.xdev_count) { |
850 | int i; |
851 | for (i = 0; i < G.xdev_count; i++) { |
852 | if (G.xdev_dev[i] == statbuf->st_dev) |
853 | goto found; |
854 | } |
855 | //bb_error_msg("'%s': not same fs", fileName); |
856 | same_fs = 0; |
857 | found: ; |
858 | } |
859 | #endif |
860 | |
861 | #if ENABLE_FEATURE_FIND_MAXDEPTH |
862 | if (depth < G.minmaxdepth[0]) { |
863 | if (same_fs) |
864 | return TRUE; /* skip this, continue recursing */ |
865 | return SKIP; /* stop recursing */ |
866 | } |
867 | if (depth > G.minmaxdepth[1]) |
868 | return SKIP; /* stop recursing */ |
869 | #endif |
870 | |
871 | r = exec_actions(G.actions, fileName, statbuf); |
872 | /* Had no explicit -print[0] or -exec? then print */ |
873 | if ((r & TRUE) && G.need_print) |
874 | puts(fileName); |
875 | |
876 | #if ENABLE_FEATURE_FIND_MAXDEPTH |
877 | if (S_ISDIR(statbuf->st_mode)) { |
878 | if (depth == G.minmaxdepth[1]) |
879 | return SKIP; |
880 | } |
881 | #endif |
882 | /* -xdev stops on mountpoints, but AFTER mountpoit itself |
883 | * is processed as usual */ |
884 | if (!same_fs) { |
885 | return SKIP; |
886 | } |
887 | |
888 | /* Cannot return 0: our caller, recursive_action(), |
889 | * will perror() and skip dirs (if called on dir) */ |
890 | return (r & SKIP) ? SKIP : TRUE; |
891 | } |
892 | |
893 | |
894 | #if ENABLE_FEATURE_FIND_TYPE |
895 | static int find_type(const char *type) |
896 | { |
897 | int mask = 0; |
898 | |
899 | if (*type == 'b') |
900 | mask = S_IFBLK; |
901 | else if (*type == 'c') |
902 | mask = S_IFCHR; |
903 | else if (*type == 'd') |
904 | mask = S_IFDIR; |
905 | else if (*type == 'p') |
906 | mask = S_IFIFO; |
907 | else if (*type == 'f') |
908 | mask = S_IFREG; |
909 | else if (*type == 'l') |
910 | mask = S_IFLNK; |
911 | else if (*type == 's') |
912 | mask = S_IFSOCK; |
913 | |
914 | if (mask == 0 || type[1] != '\0') |
915 | bb_error_msg_and_die(bb_msg_invalid_arg_to, type, "-type"); |
916 | |
917 | return mask; |
918 | } |
919 | #endif |
920 | |
921 | #if ENABLE_FEATURE_FIND_PERM \ |
922 | || ENABLE_FEATURE_FIND_MTIME || ENABLE_FEATURE_FIND_MMIN \ |
923 | || ENABLE_FEATURE_FIND_SIZE || ENABLE_FEATURE_FIND_LINKS |
924 | static const char* plus_minus_num(const char* str) |
925 | { |
926 | if (*str == '-' || *str == '+') |
927 | str++; |
928 | return str; |
929 | } |
930 | #endif |
931 | |
932 | /* Say no to GCCism */ |
933 | #define USE_NESTED_FUNCTION 0 |
934 | |
935 | #if !USE_NESTED_FUNCTION |
936 | struct pp_locals { |
937 | action*** appp; |
938 | unsigned cur_group; |
939 | unsigned cur_action; |
940 | IF_FEATURE_FIND_NOT( bool invert_flag; ) |
941 | }; |
942 | static action* alloc_action(struct pp_locals *ppl, int sizeof_struct, action_fp f) |
943 | { |
944 | action *ap = xzalloc(sizeof_struct); |
945 | action **app; |
946 | action ***group = &ppl->appp[ppl->cur_group]; |
947 | *group = app = xrealloc(*group, (ppl->cur_action+2) * sizeof(ppl->appp[0][0])); |
948 | app[ppl->cur_action++] = ap; |
949 | app[ppl->cur_action] = NULL; |
950 | ap->f = f; |
951 | IF_FEATURE_FIND_NOT( ap->invert = ppl->invert_flag; ) |
952 | IF_FEATURE_FIND_NOT( ppl->invert_flag = 0; ) |
953 | return ap; |
954 | } |
955 | #endif |
956 | |
957 | static action*** parse_params(char **argv) |
958 | { |
959 | enum { |
960 | OPT_FOLLOW , |
961 | IF_FEATURE_FIND_XDEV( OPT_XDEV ,) |
962 | IF_FEATURE_FIND_DEPTH( OPT_DEPTH ,) |
963 | PARM_a , |
964 | PARM_o , |
965 | IF_FEATURE_FIND_NOT( PARM_char_not ,) |
966 | #if ENABLE_DESKTOP |
967 | PARM_and , |
968 | PARM_or , |
969 | IF_FEATURE_FIND_NOT( PARM_not ,) |
970 | #endif |
971 | PARM_print , |
972 | IF_FEATURE_FIND_PRINT0( PARM_print0 ,) |
973 | IF_FEATURE_FIND_PRUNE( PARM_prune ,) |
974 | IF_FEATURE_FIND_DELETE( PARM_delete ,) |
975 | IF_FEATURE_FIND_EXEC( PARM_exec ,) |
976 | IF_FEATURE_FIND_PAREN( PARM_char_brace,) |
977 | /* All options/actions starting from here require argument */ |
978 | PARM_name , |
979 | PARM_iname , |
980 | IF_FEATURE_FIND_PATH( PARM_path ,) |
981 | #if ENABLE_DESKTOP |
982 | /* -wholename is a synonym for -path */ |
983 | /* We support it because Linux kernel's "make tags" uses it */ |
984 | IF_FEATURE_FIND_PATH( PARM_wholename ,) |
985 | #endif |
986 | IF_FEATURE_FIND_PATH( PARM_ipath ,) |
987 | IF_FEATURE_FIND_REGEX( PARM_regex ,) |
988 | IF_FEATURE_FIND_TYPE( PARM_type ,) |
989 | IF_FEATURE_FIND_PERM( PARM_perm ,) |
990 | IF_FEATURE_FIND_MTIME( PARM_mtime ,) |
991 | IF_FEATURE_FIND_MMIN( PARM_mmin ,) |
992 | IF_FEATURE_FIND_NEWER( PARM_newer ,) |
993 | IF_FEATURE_FIND_INUM( PARM_inum ,) |
994 | IF_FEATURE_FIND_USER( PARM_user ,) |
995 | IF_FEATURE_FIND_GROUP( PARM_group ,) |
996 | IF_FEATURE_FIND_SIZE( PARM_size ,) |
997 | IF_FEATURE_FIND_CONTEXT(PARM_context ,) |
998 | IF_FEATURE_FIND_LINKS( PARM_links ,) |
999 | IF_FEATURE_FIND_MAXDEPTH(OPT_MINDEPTH,OPT_MAXDEPTH,) |
1000 | }; |
1001 | |
1002 | static const char params[] ALIGN1 = |
1003 | "-follow\0" |
1004 | IF_FEATURE_FIND_XDEV( "-xdev\0" ) |
1005 | IF_FEATURE_FIND_DEPTH( "-depth\0" ) |
1006 | "-a\0" |
1007 | "-o\0" |
1008 | IF_FEATURE_FIND_NOT( "!\0" ) |
1009 | #if ENABLE_DESKTOP |
1010 | "-and\0" |
1011 | "-or\0" |
1012 | IF_FEATURE_FIND_NOT( "-not\0" ) |
1013 | #endif |
1014 | "-print\0" |
1015 | IF_FEATURE_FIND_PRINT0( "-print0\0" ) |
1016 | IF_FEATURE_FIND_PRUNE( "-prune\0" ) |
1017 | IF_FEATURE_FIND_DELETE( "-delete\0" ) |
1018 | IF_FEATURE_FIND_EXEC( "-exec\0" ) |
1019 | IF_FEATURE_FIND_PAREN( "(\0" ) |
1020 | /* All options/actions starting from here require argument */ |
1021 | "-name\0" |
1022 | "-iname\0" |
1023 | IF_FEATURE_FIND_PATH( "-path\0" ) |
1024 | #if ENABLE_DESKTOP |
1025 | IF_FEATURE_FIND_PATH( "-wholename\0") |
1026 | #endif |
1027 | IF_FEATURE_FIND_PATH( "-ipath\0" ) |
1028 | IF_FEATURE_FIND_REGEX( "-regex\0" ) |
1029 | IF_FEATURE_FIND_TYPE( "-type\0" ) |
1030 | IF_FEATURE_FIND_PERM( "-perm\0" ) |
1031 | IF_FEATURE_FIND_MTIME( "-mtime\0" ) |
1032 | IF_FEATURE_FIND_MMIN( "-mmin\0" ) |
1033 | IF_FEATURE_FIND_NEWER( "-newer\0" ) |
1034 | IF_FEATURE_FIND_INUM( "-inum\0" ) |
1035 | IF_FEATURE_FIND_USER( "-user\0" ) |
1036 | IF_FEATURE_FIND_GROUP( "-group\0" ) |
1037 | IF_FEATURE_FIND_SIZE( "-size\0" ) |
1038 | IF_FEATURE_FIND_CONTEXT("-context\0") |
1039 | IF_FEATURE_FIND_LINKS( "-links\0" ) |
1040 | IF_FEATURE_FIND_MAXDEPTH("-mindepth\0""-maxdepth\0") |
1041 | ; |
1042 | |
1043 | #if !USE_NESTED_FUNCTION |
1044 | struct pp_locals ppl; |
1045 | #define appp (ppl.appp ) |
1046 | #define cur_group (ppl.cur_group ) |
1047 | #define cur_action (ppl.cur_action ) |
1048 | #define invert_flag (ppl.invert_flag) |
1049 | #define ALLOC_ACTION(name) (action_##name*)alloc_action(&ppl, sizeof(action_##name), (action_fp) func_##name) |
1050 | #else |
1051 | action*** appp; |
1052 | unsigned cur_group; |
1053 | unsigned cur_action; |
1054 | IF_FEATURE_FIND_NOT( bool invert_flag; ) |
1055 | |
1056 | /* This is the only place in busybox where we use nested function. |
1057 | * So far more standard alternatives were bigger. */ |
1058 | /* Auto decl suppresses "func without a prototype" warning: */ |
1059 | auto action* alloc_action(int sizeof_struct, action_fp f); |
1060 | action* alloc_action(int sizeof_struct, action_fp f) |
1061 | { |
1062 | action *ap; |
1063 | appp[cur_group] = xrealloc(appp[cur_group], (cur_action+2) * sizeof(appp[0][0])); |
1064 | appp[cur_group][cur_action++] = ap = xzalloc(sizeof_struct); |
1065 | appp[cur_group][cur_action] = NULL; |
1066 | ap->f = f; |
1067 | IF_FEATURE_FIND_NOT( ap->invert = invert_flag; ) |
1068 | IF_FEATURE_FIND_NOT( invert_flag = 0; ) |
1069 | return ap; |
1070 | } |
1071 | #define ALLOC_ACTION(name) (action_##name*)alloc_action(sizeof(action_##name), (action_fp) func_##name) |
1072 | #endif |
1073 | |
1074 | cur_group = 0; |
1075 | cur_action = 0; |
1076 | IF_FEATURE_FIND_NOT( invert_flag = 0; ) |
1077 | appp = xzalloc(2 * sizeof(appp[0])); /* appp[0],[1] == NULL */ |
1078 | |
1079 | while (*argv) { |
1080 | const char *arg = argv[0]; |
1081 | int parm = index_in_strings(params, arg); |
1082 | const char *arg1 = argv[1]; |
1083 | |
1084 | dbg("arg:'%s' arg1:'%s' parm:%d PARM_type:%d", arg, arg1, parm, PARM_type); |
1085 | |
1086 | if (parm >= PARM_name) { |
1087 | /* All options/actions starting from -name require argument */ |
1088 | if (!arg1) |
1089 | bb_error_msg_and_die(bb_msg_requires_arg, arg); |
1090 | argv++; |
1091 | } |
1092 | |
1093 | /* We can use big switch() here, but on i386 |
1094 | * it doesn't give smaller code. Other arches? */ |
1095 | |
1096 | /* Options always return true. They always take effect |
1097 | * rather than being processed only when their place in the |
1098 | * expression is reached. |
1099 | */ |
1100 | /* Options */ |
1101 | if (parm == OPT_FOLLOW) { |
1102 | dbg("follow enabled: %d", __LINE__); |
1103 | G.recurse_flags |= ACTION_FOLLOWLINKS | ACTION_DANGLING_OK; |
1104 | } |
1105 | #if ENABLE_FEATURE_FIND_XDEV |
1106 | else if (parm == OPT_XDEV) { |
1107 | dbg("%d", __LINE__); |
1108 | G.xdev_on = 1; |
1109 | } |
1110 | #endif |
1111 | #if ENABLE_FEATURE_FIND_MAXDEPTH |
1112 | else if (parm == OPT_MINDEPTH || parm == OPT_MINDEPTH + 1) { |
1113 | dbg("%d", __LINE__); |
1114 | G.minmaxdepth[parm - OPT_MINDEPTH] = xatoi_positive(arg1); |
1115 | } |
1116 | #endif |
1117 | #if ENABLE_FEATURE_FIND_DEPTH |
1118 | else if (parm == OPT_DEPTH) { |
1119 | dbg("%d", __LINE__); |
1120 | G.recurse_flags |= ACTION_DEPTHFIRST; |
1121 | } |
1122 | #endif |
1123 | /* Actions are grouped by operators |
1124 | * ( expr ) Force precedence |
1125 | * ! expr True if expr is false |
1126 | * -not expr Same as ! expr |
1127 | * expr1 [-a[nd]] expr2 And; expr2 is not evaluated if expr1 is false |
1128 | * expr1 -o[r] expr2 Or; expr2 is not evaluated if expr1 is true |
1129 | * expr1 , expr2 List; both expr1 and expr2 are always evaluated |
1130 | * We implement: (), -a, -o |
1131 | */ |
1132 | /* Operators */ |
1133 | else if (parm == PARM_a IF_DESKTOP(|| parm == PARM_and)) { |
1134 | dbg("%d", __LINE__); |
1135 | /* no further special handling required */ |
1136 | } |
1137 | else if (parm == PARM_o IF_DESKTOP(|| parm == PARM_or)) { |
1138 | dbg("%d", __LINE__); |
1139 | /* start new OR group */ |
1140 | cur_group++; |
1141 | appp = xrealloc(appp, (cur_group+2) * sizeof(appp[0])); |
1142 | /*appp[cur_group] = NULL; - already NULL */ |
1143 | appp[cur_group+1] = NULL; |
1144 | cur_action = 0; |
1145 | } |
1146 | #if ENABLE_FEATURE_FIND_NOT |
1147 | else if (parm == PARM_char_not IF_DESKTOP(|| parm == PARM_not)) { |
1148 | /* also handles "find ! ! -name 'foo*'" */ |
1149 | invert_flag ^= 1; |
1150 | dbg("invert_flag:%d", invert_flag); |
1151 | } |
1152 | #endif |
1153 | /* Actions */ |
1154 | else if (parm == PARM_print) { |
1155 | dbg("%d", __LINE__); |
1156 | G.need_print = 0; |
1157 | (void) ALLOC_ACTION(print); |
1158 | } |
1159 | #if ENABLE_FEATURE_FIND_PRINT0 |
1160 | else if (parm == PARM_print0) { |
1161 | dbg("%d", __LINE__); |
1162 | G.need_print = 0; |
1163 | (void) ALLOC_ACTION(print0); |
1164 | } |
1165 | #endif |
1166 | #if ENABLE_FEATURE_FIND_PRUNE |
1167 | else if (parm == PARM_prune) { |
1168 | dbg("%d", __LINE__); |
1169 | (void) ALLOC_ACTION(prune); |
1170 | } |
1171 | #endif |
1172 | #if ENABLE_FEATURE_FIND_DELETE |
1173 | else if (parm == PARM_delete) { |
1174 | dbg("%d", __LINE__); |
1175 | G.need_print = 0; |
1176 | G.recurse_flags |= ACTION_DEPTHFIRST; |
1177 | (void) ALLOC_ACTION(delete); |
1178 | } |
1179 | #endif |
1180 | #if ENABLE_FEATURE_FIND_EXEC |
1181 | else if (parm == PARM_exec) { |
1182 | int i; |
1183 | action_exec *ap; |
1184 | IF_FEATURE_FIND_EXEC_PLUS(int all_subst = 0;) |
1185 | dbg("%d", __LINE__); |
1186 | G.need_print = 0; |
1187 | ap = ALLOC_ACTION(exec); |
1188 | ap->exec_argv = ++argv; /* first arg after -exec */ |
1189 | /*ap->exec_argc = 0; - ALLOC_ACTION did it */ |
1190 | while (1) { |
1191 | if (!*argv) /* did not see ';' or '+' until end */ |
1192 | bb_error_msg_and_die(bb_msg_requires_arg, "-exec"); |
1193 | // find -exec echo Foo ">{}<" ";" |
1194 | // executes "echo Foo >FILENAME<", |
1195 | // find -exec echo Foo ">{}<" "+" |
1196 | // executes "echo Foo FILENAME1 FILENAME2 FILENAME3...". |
1197 | if ((argv[0][0] == ';' || argv[0][0] == '+') |
1198 | && argv[0][1] == '\0' |
1199 | ) { |
1200 | # if ENABLE_FEATURE_FIND_EXEC_PLUS |
1201 | if (argv[0][0] == '+') |
1202 | ap->filelist = xzalloc(sizeof(ap->filelist[0])); |
1203 | # endif |
1204 | break; |
1205 | } |
1206 | argv++; |
1207 | ap->exec_argc++; |
1208 | } |
1209 | if (ap->exec_argc == 0) |
1210 | bb_error_msg_and_die(bb_msg_requires_arg, arg); |
1211 | ap->subst_count = xmalloc(ap->exec_argc * sizeof(int)); |
1212 | i = ap->exec_argc; |
1213 | while (i--) { |
1214 | ap->subst_count[i] = count_strstr(ap->exec_argv[i], "{}"); |
1215 | IF_FEATURE_FIND_EXEC_PLUS(all_subst += ap->subst_count[i];) |
1216 | } |
1217 | # if ENABLE_FEATURE_FIND_EXEC_PLUS |
1218 | /* |
1219 | * coreutils expects {} to appear only once in "-exec +" |
1220 | */ |
1221 | if (all_subst != 1 && ap->filelist) |
1222 | bb_error_msg_and_die("only one '{}' allowed for -exec +"); |
1223 | # endif |
1224 | } |
1225 | #endif |
1226 | #if ENABLE_FEATURE_FIND_PAREN |
1227 | else if (parm == PARM_char_brace) { |
1228 | action_paren *ap; |
1229 | char **endarg; |
1230 | unsigned nested = 1; |
1231 | |
1232 | dbg("%d", __LINE__); |
1233 | endarg = argv; |
1234 | while (1) { |
1235 | if (!*++endarg) |
1236 | bb_error_msg_and_die("unpaired '('"); |
1237 | if (LONE_CHAR(*endarg, '(')) |
1238 | nested++; |
1239 | else if (LONE_CHAR(*endarg, ')') && !--nested) { |
1240 | *endarg = NULL; |
1241 | break; |
1242 | } |
1243 | } |
1244 | ap = ALLOC_ACTION(paren); |
1245 | ap->subexpr = parse_params(argv + 1); |
1246 | *endarg = (char*) ")"; /* restore NULLed parameter */ |
1247 | argv = endarg; |
1248 | } |
1249 | #endif |
1250 | else if (parm == PARM_name || parm == PARM_iname) { |
1251 | action_name *ap; |
1252 | dbg("%d", __LINE__); |
1253 | ap = ALLOC_ACTION(name); |
1254 | ap->pattern = arg1; |
1255 | ap->iname = (parm == PARM_iname); |
1256 | } |
1257 | #if ENABLE_FEATURE_FIND_PATH |
1258 | else if (parm == PARM_path IF_DESKTOP(|| parm == PARM_wholename) || parm == PARM_ipath) { |
1259 | action_path *ap; |
1260 | dbg("%d", __LINE__); |
1261 | ap = ALLOC_ACTION(path); |
1262 | ap->pattern = arg1; |
1263 | ap->ipath = (parm == PARM_ipath); |
1264 | } |
1265 | #endif |
1266 | #if ENABLE_FEATURE_FIND_REGEX |
1267 | else if (parm == PARM_regex) { |
1268 | action_regex *ap; |
1269 | dbg("%d", __LINE__); |
1270 | ap = ALLOC_ACTION(regex); |
1271 | xregcomp(&ap->compiled_pattern, arg1, 0 /*cflags*/); |
1272 | } |
1273 | #endif |
1274 | #if ENABLE_FEATURE_FIND_TYPE |
1275 | else if (parm == PARM_type) { |
1276 | action_type *ap; |
1277 | ap = ALLOC_ACTION(type); |
1278 | ap->type_mask = find_type(arg1); |
1279 | dbg("created:type mask:%x", ap->type_mask); |
1280 | } |
1281 | #endif |
1282 | #if ENABLE_FEATURE_FIND_PERM |
1283 | /* -perm BITS File's mode bits are exactly BITS (octal or symbolic). |
1284 | * Symbolic modes use mode 0 as a point of departure. |
1285 | * -perm -BITS All of the BITS are set in file's mode. |
1286 | * -perm [+/]BITS At least one of the BITS is set in file's mode. |
1287 | */ |
1288 | else if (parm == PARM_perm) { |
1289 | action_perm *ap; |
1290 | dbg("%d", __LINE__); |
1291 | ap = ALLOC_ACTION(perm); |
1292 | ap->perm_char = arg1[0]; |
1293 | arg1 = (arg1[0] == '/' ? arg1+1 : plus_minus_num(arg1)); |
1294 | /*ap->perm_mask = 0; - ALLOC_ACTION did it */ |
1295 | ap->perm_mask = bb_parse_mode(arg1, ap->perm_mask); |
1296 | if (ap->perm_mask == (mode_t)-1) |
1297 | bb_error_msg_and_die("invalid mode '%s'", arg1); |
1298 | } |
1299 | #endif |
1300 | #if ENABLE_FEATURE_FIND_MTIME |
1301 | else if (parm == PARM_mtime) { |
1302 | action_mtime *ap; |
1303 | dbg("%d", __LINE__); |
1304 | ap = ALLOC_ACTION(mtime); |
1305 | ap->mtime_char = arg1[0]; |
1306 | ap->mtime_days = xatoul(plus_minus_num(arg1)); |
1307 | } |
1308 | #endif |
1309 | #if ENABLE_FEATURE_FIND_MMIN |
1310 | else if (parm == PARM_mmin) { |
1311 | action_mmin *ap; |
1312 | dbg("%d", __LINE__); |
1313 | ap = ALLOC_ACTION(mmin); |
1314 | ap->mmin_char = arg1[0]; |
1315 | ap->mmin_mins = xatoul(plus_minus_num(arg1)); |
1316 | } |
1317 | #endif |
1318 | #if ENABLE_FEATURE_FIND_NEWER |
1319 | else if (parm == PARM_newer) { |
1320 | struct stat stat_newer; |
1321 | action_newer *ap; |
1322 | dbg("%d", __LINE__); |
1323 | ap = ALLOC_ACTION(newer); |
1324 | xstat(arg1, &stat_newer); |
1325 | ap->newer_mtime = stat_newer.st_mtime; |
1326 | } |
1327 | #endif |
1328 | #if ENABLE_FEATURE_FIND_INUM |
1329 | else if (parm == PARM_inum) { |
1330 | action_inum *ap; |
1331 | dbg("%d", __LINE__); |
1332 | ap = ALLOC_ACTION(inum); |
1333 | ap->inode_num = xatoul(arg1); |
1334 | } |
1335 | #endif |
1336 | #if ENABLE_FEATURE_FIND_USER |
1337 | else if (parm == PARM_user) { |
1338 | action_user *ap; |
1339 | dbg("%d", __LINE__); |
1340 | ap = ALLOC_ACTION(user); |
1341 | ap->uid = bb_strtou(arg1, NULL, 10); |
1342 | if (errno) |
1343 | ap->uid = xuname2uid(arg1); |
1344 | } |
1345 | #endif |
1346 | #if ENABLE_FEATURE_FIND_GROUP |
1347 | else if (parm == PARM_group) { |
1348 | action_group *ap; |
1349 | dbg("%d", __LINE__); |
1350 | ap = ALLOC_ACTION(group); |
1351 | ap->gid = bb_strtou(arg1, NULL, 10); |
1352 | if (errno) |
1353 | ap->gid = xgroup2gid(arg1); |
1354 | } |
1355 | #endif |
1356 | #if ENABLE_FEATURE_FIND_SIZE |
1357 | else if (parm == PARM_size) { |
1358 | /* -size n[bckw]: file uses n units of space |
1359 | * b (default): units are 512-byte blocks |
1360 | * c: 1 byte |
1361 | * k: kilobytes |
1362 | * w: 2-byte words |
1363 | */ |
1364 | #if ENABLE_LFS |
1365 | #define XATOU_SFX xatoull_sfx |
1366 | #else |
1367 | #define XATOU_SFX xatoul_sfx |
1368 | #endif |
1369 | static const struct suffix_mult find_suffixes[] = { |
1370 | { "c", 1 }, |
1371 | { "w", 2 }, |
1372 | { "", 512 }, |
1373 | { "b", 512 }, |
1374 | { "k", 1024 }, |
1375 | { "", 0 } |
1376 | }; |
1377 | action_size *ap; |
1378 | dbg("%d", __LINE__); |
1379 | ap = ALLOC_ACTION(size); |
1380 | ap->size_char = arg1[0]; |
1381 | ap->size = XATOU_SFX(plus_minus_num(arg1), find_suffixes); |
1382 | } |
1383 | #endif |
1384 | #if ENABLE_FEATURE_FIND_CONTEXT |
1385 | else if (parm == PARM_context) { |
1386 | action_context *ap; |
1387 | dbg("%d", __LINE__); |
1388 | ap = ALLOC_ACTION(context); |
1389 | /*ap->context = NULL; - ALLOC_ACTION did it */ |
1390 | /* SELinux headers erroneously declare non-const parameter */ |
1391 | if (selinux_raw_to_trans_context((char*)arg1, &ap->context)) |
1392 | bb_simple_perror_msg(arg1); |
1393 | } |
1394 | #endif |
1395 | #if ENABLE_FEATURE_FIND_LINKS |
1396 | else if (parm == PARM_links) { |
1397 | action_links *ap; |
1398 | dbg("%d", __LINE__); |
1399 | ap = ALLOC_ACTION(links); |
1400 | ap->links_char = arg1[0]; |
1401 | ap->links_count = xatoul(plus_minus_num(arg1)); |
1402 | } |
1403 | #endif |
1404 | else { |
1405 | bb_error_msg("unrecognized: %s", arg); |
1406 | bb_show_usage(); |
1407 | } |
1408 | argv++; |
1409 | } |
1410 | dbg("exiting %s", __func__); |
1411 | return appp; |
1412 | #undef ALLOC_ACTION |
1413 | #undef appp |
1414 | #undef cur_action |
1415 | #undef invert_flag |
1416 | } |
1417 | |
1418 | int find_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
1419 | int find_main(int argc UNUSED_PARAM, char **argv) |
1420 | { |
1421 | int i, firstopt, status = EXIT_SUCCESS; |
1422 | char **past_HLP, *saved; |
1423 | |
1424 | INIT_G(); |
1425 | |
1426 | /* "find -type f" + getopt("+HLP") => disaster. |
1427 | * Need to avoid getopt running into a non-HLP option. |
1428 | * Do this by temporarily storing NULL there: |
1429 | */ |
1430 | past_HLP = argv; |
1431 | for (;;) { |
1432 | saved = *++past_HLP; |
1433 | if (!saved) |
1434 | break; |
1435 | if (saved[0] != '-') |
1436 | break; |
1437 | if (!saved[1]) |
1438 | break; /* it is "-" */ |
1439 | if ((saved+1)[strspn(saved+1, "HLP")] != '\0') |
1440 | break; |
1441 | } |
1442 | *past_HLP = NULL; |
1443 | /* "+": stop on first non-option */ |
1444 | i = getopt32(argv, "+HLP"); |
1445 | if (i & (1<<0)) |
1446 | G.recurse_flags |= ACTION_FOLLOWLINKS_L0 | ACTION_DANGLING_OK; |
1447 | if (i & (1<<1)) |
1448 | G.recurse_flags |= ACTION_FOLLOWLINKS | ACTION_DANGLING_OK; |
1449 | /* -P is default and is ignored */ |
1450 | argv = past_HLP; /* same result as "argv += optind;" */ |
1451 | *past_HLP = saved; |
1452 | |
1453 | for (firstopt = 0; argv[firstopt]; firstopt++) { |
1454 | if (argv[firstopt][0] == '-') |
1455 | break; |
1456 | if (ENABLE_FEATURE_FIND_NOT && LONE_CHAR(argv[firstopt], '!')) |
1457 | break; |
1458 | if (ENABLE_FEATURE_FIND_PAREN && LONE_CHAR(argv[firstopt], '(')) |
1459 | break; |
1460 | } |
1461 | if (firstopt == 0) { |
1462 | *--argv = (char*)"."; |
1463 | firstopt++; |
1464 | } |
1465 | |
1466 | G.actions = parse_params(&argv[firstopt]); |
1467 | argv[firstopt] = NULL; |
1468 | |
1469 | #if ENABLE_FEATURE_FIND_XDEV |
1470 | if (G.xdev_on) { |
1471 | struct stat stbuf; |
1472 | |
1473 | G.xdev_count = firstopt; |
1474 | G.xdev_dev = xzalloc(G.xdev_count * sizeof(G.xdev_dev[0])); |
1475 | for (i = 0; argv[i]; i++) { |
1476 | /* not xstat(): shouldn't bomb out on |
1477 | * "find not_exist exist -xdev" */ |
1478 | if (stat(argv[i], &stbuf) == 0) |
1479 | G.xdev_dev[i] = stbuf.st_dev; |
1480 | /* else G.xdev_dev[i] stays 0 and |
1481 | * won't match any real device dev_t |
1482 | */ |
1483 | } |
1484 | } |
1485 | #endif |
1486 | |
1487 | for (i = 0; argv[i]; i++) { |
1488 | if (!recursive_action(argv[i], |
1489 | G.recurse_flags,/* flags */ |
1490 | fileAction, /* file action */ |
1491 | fileAction, /* dir action */ |
1492 | NULL, /* user data */ |
1493 | 0) /* depth */ |
1494 | ) { |
1495 | status |= EXIT_FAILURE; |
1496 | } |
1497 | } |
1498 | |
1499 | IF_FEATURE_FIND_EXEC_PLUS(status |= flush_exec_plus();) |
1500 | return status; |
1501 | } |
1502 |