bfdd844979e1e9aed7847b42b03e17608565c0a3
[oweals/busybox.git] / findutils / find.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini find implementation for busybox
4  *
5  * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
6  *
7  * Reworked by David Douthitt <n9ubh@callsign.net> and
8  *  Matt Kraai <kraai@alumni.carnegiemellon.edu>.
9  *
10  * Licensed under the GPL version 2, see the file LICENSE in this tarball.
11  */
12
13 /* findutils-4.1.20:
14  *
15  * # find file.txt -exec 'echo {}' '{}  {}' ';'
16  * find: echo file.txt: No such file or directory
17  * # find file.txt -exec 'echo' '{}  {}' '; '
18  * find: missing argument to `-exec'
19  * # find file.txt -exec 'echo {}' '{}  {}' ';' junk
20  * find: paths must precede expression
21  * # find file.txt -exec 'echo {}' '{}  {}' ';' junk ';'
22  * find: paths must precede expression
23  * # find file.txt -exec 'echo' '{}  {}' ';'
24  * file.txt  file.txt
25  * (strace: execve("/bin/echo", ["echo", "file.txt  file.txt"], [ 30 vars ]))
26  * # find file.txt -exec 'echo' '{}  {}' ';' -print -exec pwd ';'
27  * file.txt  file.txt
28  * file.txt
29  * /tmp
30  * # find -name '*.c' -o -name '*.h'
31  * [shows files, *.c and *.h intermixed]
32  * # find file.txt -name '*f*' -o -name '*t*'
33  * file.txt
34  * # find file.txt -name '*z*' -o -name '*t*'
35  * file.txt
36  * # find file.txt -name '*f*' -o -name '*z*'
37  * file.txt
38  *
39  * # find t z -name '*t*' -print -o -name '*z*'
40  * t
41  * # find t z t z -name '*t*' -o -name '*z*' -print
42  * z
43  * z
44  * # find t z t z '(' -name '*t*' -o -name '*z*' ')' -o -print
45  * (no output)
46  */
47
48 #include <fnmatch.h>
49 #include "busybox.h"
50
51 USE_FEATURE_FIND_XDEV(static dev_t *xdev_dev;)
52 USE_FEATURE_FIND_XDEV(static int xdev_count;)
53
54 typedef int (*action_fp)(const char *fileName, struct stat *statbuf, void *);
55
56 typedef struct {
57         action_fp f;
58 } action;
59 #define ACTS(name, arg...) typedef struct { action a; arg; } action_##name;
60 #define ACTF(name)         static int func_##name(const char *fileName, struct stat *statbuf, action_##name* ap)
61                         ACTS(print)
62                         ACTS(name,  const char *pattern;)
63 USE_FEATURE_FIND_PRINT0(ACTS(print0))
64 USE_FEATURE_FIND_TYPE(  ACTS(type,  int type_mask;))
65 USE_FEATURE_FIND_PERM(  ACTS(perm,  char perm_char; mode_t perm_mask;))
66 USE_FEATURE_FIND_MTIME( ACTS(mtime, char mtime_char; unsigned mtime_days;))
67 USE_FEATURE_FIND_MMIN(  ACTS(mmin,  char mmin_char; unsigned mmin_mins;))
68 USE_FEATURE_FIND_NEWER( ACTS(newer, time_t newer_mtime;))
69 USE_FEATURE_FIND_INUM(  ACTS(inum,  ino_t inode_num;))
70 USE_FEATURE_FIND_EXEC(  ACTS(exec,  char **exec_argv; unsigned int *subst_count; int exec_argc;))
71 USE_FEATURE_FIND_USER(  ACTS(user,  int uid;))
72 USE_DESKTOP(            ACTS(paren, action ***subexpr;))
73 USE_DESKTOP(            ACTS(size,  off_t size;))
74 USE_DESKTOP(            ACTS(prune))
75
76 static action ***actions;
77 static smalluint need_print = 1;
78
79
80 #if ENABLE_FEATURE_FIND_EXEC
81 static unsigned int count_subst(const char *str)
82 {
83         unsigned int count = 0;
84         while ((str = strstr(str, "{}"))) {
85                 count++;
86                 str++;
87         }
88         return count;
89 }
90
91
92 static char* subst(const char *src, unsigned int count, const char* filename)
93 {
94         char *buf, *dst, *end;
95         int flen = strlen(filename);
96         /* we replace each '{}' with filename: growth by strlen-2 */
97         buf = dst = xmalloc(strlen(src) + count*(flen-2) + 1);
98         while ((end = strstr(src, "{}"))) {
99                 memcpy(dst, src, end - src);
100                 dst += end - src;
101                 src = end + 2;
102                 memcpy(dst, filename, flen);
103                 dst += flen;
104         }
105         strcpy(dst, src);
106         return buf;
107 }
108 #endif
109
110
111 static int exec_actions(action ***appp, const char *fileName, struct stat *statbuf)
112 {
113         int cur_group;
114         int cur_action;
115         int rc = TRUE;
116         action **app, *ap;
117
118         cur_group = -1;
119         while ((app = appp[++cur_group])) {
120                 cur_action = -1;
121                 do {
122                         ap = app[++cur_action];
123                 } while (ap && (rc = ap->f(fileName, statbuf, ap)));
124                 if (!ap) {
125                         /* all actions in group were successful */
126                         break;
127                 }
128         }
129         return rc;
130 }
131
132
133 ACTF(name)
134 {
135         const char *tmp = strrchr(fileName, '/');
136         if (tmp == NULL)
137                 tmp = fileName;
138         else
139                 tmp++;
140         return fnmatch(ap->pattern, tmp, FNM_PERIOD) == 0;
141 }
142 #if ENABLE_FEATURE_FIND_TYPE
143 ACTF(type)
144 {
145         return ((statbuf->st_mode & S_IFMT) == ap->type_mask);
146 }
147 #endif
148 #if ENABLE_FEATURE_FIND_PERM
149 ACTF(perm)
150 {
151         /* -perm +mode: at least one of perm_mask bits are set */
152         if (ap->perm_char == '+')
153                 return (statbuf->st_mode & ap->perm_mask) != 0;
154         /* -perm -mode: all of perm_mask are set */
155         if (ap->perm_char == '-')
156                 return (statbuf->st_mode & ap->perm_mask) == ap->perm_mask;
157         /* -perm mode: file mode must match perm_mask */
158         return (statbuf->st_mode & 07777) == ap->perm_mask;
159 }
160 #endif
161 #if ENABLE_FEATURE_FIND_MTIME
162 ACTF(mtime)
163 {
164         time_t file_age = time(NULL) - statbuf->st_mtime;
165         time_t mtime_secs = ap->mtime_days * 24*60*60;
166         if (ap->mtime_char == '+')
167                 return file_age >= mtime_secs + 24*60*60;
168         if (ap->mtime_char == '-')
169                 return file_age < mtime_secs;
170         /* just numeric mtime */
171         return file_age >= mtime_secs && file_age < (mtime_secs + 24*60*60);
172 }
173 #endif
174 #if ENABLE_FEATURE_FIND_MMIN
175 ACTF(mmin)
176 {
177         time_t file_age = time(NULL) - statbuf->st_mtime;
178         time_t mmin_secs = ap->mmin_mins * 60;
179         if (ap->mmin_char == '+')
180                 return file_age >= mmin_secs + 60;
181         if (ap->mmin_char == '-')
182                 return file_age < mmin_secs;
183         /* just numeric mmin */
184         return file_age >= mmin_secs && file_age < (mmin_secs + 60);
185 }
186 #endif
187 #if ENABLE_FEATURE_FIND_NEWER
188 ACTF(newer)
189 {
190         return (ap->newer_mtime < statbuf->st_mtime);
191 }
192 #endif
193 #if ENABLE_FEATURE_FIND_INUM
194 ACTF(inum)
195 {
196         return (statbuf->st_ino == ap->inode_num);
197 }
198 #endif
199 #if ENABLE_FEATURE_FIND_EXEC
200 ACTF(exec)
201 {
202         int i, rc;
203         char *argv[ap->exec_argc+1];
204         for (i = 0; i < ap->exec_argc; i++)
205                 argv[i] = subst(ap->exec_argv[i], ap->subst_count[i], fileName);
206         argv[i] = NULL; /* terminate the list */
207         rc = wait4pid(spawn(argv));
208         if (rc)
209                 bb_perror_msg("%s", argv[0]);
210         for (i = 0; i < ap->exec_argc; i++)
211                 free(argv[i]);
212         return rc == 0; /* return 1 if success */
213 }
214 #endif
215
216 #if ENABLE_FEATURE_FIND_USER
217 ACTF(user)
218 {
219         return (statbuf->st_uid == ap->uid);
220 }
221 #endif
222
223 #if ENABLE_FEATURE_FIND_PRINT0
224 ACTF(print0)
225 {
226         printf("%s%c", fileName, '\0');
227         return TRUE;
228 }
229 #endif
230
231 ACTF(print)
232 {
233         puts(fileName);
234         return TRUE;
235 }
236
237 #if ENABLE_DESKTOP
238 ACTF(paren)
239 {
240         return exec_actions(ap->subexpr, fileName, statbuf);
241 }
242
243 /*
244  * -prune: if -depth is not given, return true and do not descend
245  * current dir; if -depth is given, return false with no effect.
246  * Example:
247  * find dir -name 'asm-*' -prune -o -name '*.[chS]' -print
248  */
249 ACTF(prune)
250 {
251         return SKIP;
252 }
253
254 ACTF(size)
255 {
256         return statbuf->st_size == ap->size;
257 }
258 #endif
259
260
261 static int fileAction(const char *fileName, struct stat *statbuf, void* junk, int depth)
262 {
263         int rc;
264 #ifdef CONFIG_FEATURE_FIND_XDEV
265         if (S_ISDIR(statbuf->st_mode) && xdev_count) {
266                 int i;
267                 for (i = 0; i < xdev_count; i++) {
268                         if (xdev_dev[i] != statbuf->st_dev)
269                                 return SKIP;
270                 }
271         }
272 #endif
273         rc = exec_actions(actions, fileName, statbuf);
274         /* Had no explicit -print[0] or -exec? then print */
275         if (rc && need_print)
276                 puts(fileName);
277         /* Cannot return 0: our caller, recursive_action(),
278          * will perror() and skip dirs (if called on dir) */
279         return rc == 0 ? TRUE : rc;
280 }
281
282
283 #if ENABLE_FEATURE_FIND_TYPE
284 static int find_type(const char *type)
285 {
286         int mask = 0;
287
288         switch (type[0]) {
289         case 'b':
290                 mask = S_IFBLK;
291                 break;
292         case 'c':
293                 mask = S_IFCHR;
294                 break;
295         case 'd':
296                 mask = S_IFDIR;
297                 break;
298         case 'p':
299                 mask = S_IFIFO;
300                 break;
301         case 'f':
302                 mask = S_IFREG;
303                 break;
304         case 'l':
305                 mask = S_IFLNK;
306                 break;
307         case 's':
308                 mask = S_IFSOCK;
309                 break;
310         }
311
312         if (mask == 0 || type[1] != '\0')
313                 bb_error_msg_and_die(bb_msg_invalid_arg, type, "-type");
314
315         return mask;
316 }
317 #endif
318
319 #if ENABLE_FEATURE_FIND_PERM || ENABLE_FEATURE_FIND_MTIME \
320  || ENABLE_FEATURE_FIND_MMIN
321 static const char* plus_minus_num(const char* str)
322 {
323         if (*str == '-' || *str == '+')
324                 str++;
325         return str;
326 }
327 #endif
328
329 static action*** parse_params(char **argv)
330 {
331         action*** appp;
332         unsigned cur_group = 0;
333         unsigned cur_action = 0;
334
335         action* alloc_action(int sizeof_struct, action_fp f)
336         {
337                 action *ap;
338                 appp[cur_group] = xrealloc(appp[cur_group], (cur_action+2) * sizeof(*appp));
339                 appp[cur_group][cur_action++] = ap = xmalloc(sizeof_struct);
340                 appp[cur_group][cur_action] = NULL;
341                 ap->f = f;
342                 return ap;
343         }
344 #define ALLOC_ACTION(name) (action_##name*)alloc_action(sizeof(action_##name), (action_fp) func_##name)
345
346         appp = xzalloc(2 * sizeof(*appp)); /* appp[0],[1] == NULL */
347
348 // Actions have side effects and return a true or false value
349 // We implement: -print, -print0, -exec
350
351 // The rest are tests.
352
353 // Tests and actions are grouped by operators
354 // ( expr )              Force precedence
355 // ! expr                True if expr is false
356 // -not expr             Same as ! expr
357 // expr1 [-a[nd]] expr2  And; expr2 is not evaluated if expr1 is false
358 // expr1 -o[r] expr2     Or; expr2 is not evaluated if expr1 is true
359 // expr1 , expr2         List; both expr1 and expr2 are always evaluated
360 // We implement: (), -a, -o
361
362 //XXX: TODO: Use index_in_str_array here
363         while (*argv) {
364                 const char *arg = argv[0];
365                 const char *arg1 = argv[1];
366         /* --- Operators --- */
367                 if (strcmp(arg, "-a") == 0
368                     USE_DESKTOP(|| strcmp(arg, "-and") == 0)
369                 ) {
370                         /* no special handling required */
371                 }
372                 else if (strcmp(arg, "-o") == 0
373                          USE_DESKTOP(|| strcmp(arg, "-or") == 0)
374                 ) {
375                         /* start new OR group */
376                         cur_group++;
377                         appp = xrealloc(appp, (cur_group+2) * sizeof(*appp));
378                         appp[cur_group] = NULL;
379                         appp[cur_group+1] = NULL;
380                         cur_action = 0;
381                 }
382
383         /* --- Tests and actions --- */
384                 else if (strcmp(arg, "-print") == 0) {
385                         need_print = 0;
386                         (void) ALLOC_ACTION(print);
387                 }
388 #if ENABLE_FEATURE_FIND_PRINT0
389                 else if (strcmp(arg, "-print0") == 0) {
390                         need_print = 0;
391                         (void) ALLOC_ACTION(print0);
392                 }
393 #endif
394                 else if (strcmp(arg, "-name") == 0) {
395                         action_name *ap;
396                         if (!*++argv)
397                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
398                         ap = ALLOC_ACTION(name);
399                         ap->pattern = arg1;
400                 }
401 #if ENABLE_FEATURE_FIND_TYPE
402                 else if (strcmp(arg, "-type") == 0) {
403                         action_type *ap;
404                         if (!*++argv)
405                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
406                         ap = ALLOC_ACTION(type);
407                         ap->type_mask = find_type(arg1);
408                 }
409 #endif
410 #if ENABLE_FEATURE_FIND_PERM
411 /* TODO:
412  * -perm mode   File's permission bits are exactly mode (octal or symbolic).
413  *              Symbolic modes use mode 0 as a point of departure.
414  * -perm -mode  All of the permission bits mode are set for the file.
415  * -perm +mode  Any of the permission bits mode are set for the file.
416  */
417                 else if (strcmp(arg, "-perm") == 0) {
418                         action_perm *ap;
419                         if (!*++argv)
420                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
421                         ap = ALLOC_ACTION(perm);
422                         ap->perm_char = arg1[0];
423                         arg1 = plus_minus_num(arg1);
424                         ap->perm_mask = 0;
425                         if (!bb_parse_mode(arg1, &ap->perm_mask))
426                                 bb_error_msg_and_die("invalid mode: %s", arg1);
427                 }
428 #endif
429 #if ENABLE_FEATURE_FIND_MTIME
430                 else if (strcmp(arg, "-mtime") == 0) {
431                         action_mtime *ap;
432                         if (!*++argv)
433                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
434                         ap = ALLOC_ACTION(mtime);
435                         ap->mtime_char = arg1[0];
436                         ap->mtime_days = xatoul(plus_minus_num(arg1));
437                 }
438 #endif
439 #if ENABLE_FEATURE_FIND_MMIN
440                 else if (strcmp(arg, "-mmin") == 0) {
441                         action_mmin *ap;
442                         if (!*++argv)
443                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
444                         ap = ALLOC_ACTION(mmin);
445                         ap->mmin_char = arg1[0];
446                         ap->mmin_mins = xatoul(plus_minus_num(arg1));
447                 }
448 #endif
449 #if ENABLE_FEATURE_FIND_NEWER
450                 else if (strcmp(arg, "-newer") == 0) {
451                         action_newer *ap;
452                         struct stat stat_newer;
453                         if (!*++argv)
454                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
455                         xstat(arg1, &stat_newer);
456                         ap = ALLOC_ACTION(newer);
457                         ap->newer_mtime = stat_newer.st_mtime;
458                 }
459 #endif
460 #if ENABLE_FEATURE_FIND_INUM
461                 else if (strcmp(arg, "-inum") == 0) {
462                         action_inum *ap;
463                         if (!*++argv)
464                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
465                         ap = ALLOC_ACTION(inum);
466                         ap->inode_num = xatoul(arg1);
467                 }
468 #endif
469 #if ENABLE_FEATURE_FIND_EXEC
470                 else if (strcmp(arg, "-exec") == 0) {
471                         int i;
472                         action_exec *ap;
473                         need_print = 0;
474                         ap = ALLOC_ACTION(exec);
475                         ap->exec_argv = ++argv; /* first arg after -exec */
476                         ap->exec_argc = 0;
477                         while (1) {
478                                 if (!*argv) /* did not see ';' till end */
479                                         bb_error_msg_and_die(bb_msg_requires_arg, arg);
480                                 if (LONE_CHAR(argv[0], ';'))
481                                         break;
482                                 argv++;
483                                 ap->exec_argc++;
484                         }
485                         if (ap->exec_argc == 0)
486                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
487                         ap->subst_count = xmalloc(ap->exec_argc * sizeof(int));
488                         i = ap->exec_argc;
489                         while (i--)
490                                 ap->subst_count[i] = count_subst(ap->exec_argv[i]);
491                 }
492 #endif
493 #if ENABLE_FEATURE_FIND_USER
494                 else if (strcmp(arg, "-user") == 0) {
495                         action_user *ap;
496                         if (!*++argv)
497                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
498                         ap = ALLOC_ACTION(user);
499                         ap->uid = bb_strtou(arg1, NULL, 10);
500                         if (errno)
501                                 ap->uid = xuname2uid(arg1);
502                 }
503 #endif
504 #if ENABLE_DESKTOP
505                 else if (LONE_CHAR(arg, '(')) {
506                         action_paren *ap;
507                         char **endarg;
508                         unsigned nested = 1;
509
510                         endarg = argv;
511                         while (1) {
512                                 if (!*++endarg)
513                                         bb_error_msg_and_die("unpaired '('");
514                                 if (LONE_CHAR(*endarg, '('))
515                                         nested++;
516                                 else if (LONE_CHAR(*endarg, ')') && !--nested) {
517                                         *endarg = NULL;
518                                         break;
519                                 }
520                         }
521                         ap = ALLOC_ACTION(paren);
522                         ap->subexpr = parse_params(argv + 1);
523                         *endarg = (char*) ")"; /* restore NULLed parameter */
524                         argv = endarg;
525                 }
526                 else if (strcmp(arg, "-prune") == 0) {
527                         (void) ALLOC_ACTION(prune);
528                 }
529                 else if (strcmp(arg, "-size") == 0) {
530                         action_size *ap;
531                         if (!*++argv)
532                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
533                         ap = ALLOC_ACTION(size);
534                         ap->size = XATOOFF(arg1);
535                 }
536 #endif
537                 else
538                         bb_show_usage();
539                 argv++;
540         }
541
542         return appp;
543 #undef ALLOC_ACTION
544 }
545
546
547 int find_main(int argc, char **argv);
548 int find_main(int argc, char **argv)
549 {
550         int dereference = FALSE;
551         char *arg;
552         char **argp;
553         int i, firstopt, status = EXIT_SUCCESS;
554
555         for (firstopt = 1; firstopt < argc; firstopt++) {
556                 if (argv[firstopt][0] == '-')
557                         break;
558 #if ENABLE_DESKTOP
559                 if (LONE_CHAR(argv[firstopt], '('))
560                         break;
561 #endif
562         }
563         if (firstopt == 1) {
564                 argv[0] = (char*)".";
565                 argv--;
566                 firstopt++;
567         }
568
569 // All options always return true. They always take effect,
570 // rather than being processed only when their place in the
571 // expression is reached
572 // We implement: -follow, -xdev
573
574         /* Process options, and replace then with -a */
575         /* (-a will be ignored by recursive parser later) */
576         argp = &argv[firstopt];
577         while ((arg = argp[0])) {
578                 if (strcmp(arg, "-follow") == 0) {
579                         dereference = TRUE;
580                         argp[0] = (char*)"-a";
581                 }
582 #if ENABLE_FEATURE_FIND_XDEV
583                 else if (strcmp(arg, "-xdev") == 0) {
584                         struct stat stbuf;
585                         if (!xdev_count) {
586                                 xdev_count = firstopt - 1;
587                                 xdev_dev = xmalloc(xdev_count * sizeof(dev_t));
588                                 for (i = 1; i < firstopt; i++) {
589                                         /* not xstat(): shouldn't bomb out on
590                                          * "find not_exist exist -xdev" */
591                                         if (stat(argv[i], &stbuf))
592                                                 stbuf.st_dev = -1L;
593                                         xdev_dev[i-1] = stbuf.st_dev;
594                                 }
595                         }
596                         argp[0] = (char*)"-a";
597                 }
598 #endif
599                 argp++;
600         }
601
602         actions = parse_params(&argv[firstopt]);
603
604         for (i = 1; i < firstopt; i++) {
605                 if (!recursive_action(argv[i],
606                                 TRUE,           // recurse
607                                 dereference,    // follow links
608                                 FALSE,          // depth first
609                                 fileAction,     // file action
610                                 fileAction,     // dir action
611                                 NULL,           // user data
612                                 0))             // depth
613                         status = EXIT_FAILURE;
614         }
615         return status;
616 }