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