find: small improvement
[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  *
27  * bboxed find rev 16467: above - works, below - doesn't
28  *
29  * # find file.txt -exec 'echo' '{}  {}' ';' -print -exec pwd ';'
30  * file.txt  file.txt
31  * file.txt
32  * /tmp
33  * # find -name '*.c' -o -name '*.h'
34  * [shows files, *.c and *.h intermixed]
35  */
36
37 #include "busybox.h"
38 #include <fnmatch.h>
39
40 static char *pattern;
41 #if ENABLE_FEATURE_FIND_PRINT0
42 static char printsep = '\n';
43 #endif
44
45 #if ENABLE_FEATURE_FIND_TYPE
46 static int type_mask = 0;
47 #endif
48
49 #if ENABLE_FEATURE_FIND_PERM
50 static char perm_char = 0;
51 static int perm_mask = 0;
52 #endif
53
54 #if ENABLE_FEATURE_FIND_MTIME
55 static char mtime_char;
56 static int mtime_days;
57 #endif
58
59 #if ENABLE_FEATURE_FIND_MMIN
60 static char mmin_char;
61 static int mmin_mins;
62 #endif
63
64 #if ENABLE_FEATURE_FIND_XDEV
65 static dev_t *xdev_dev;
66 static int xdev_count = 0;
67 #endif
68
69 #if ENABLE_FEATURE_FIND_NEWER
70 static time_t newer_mtime;
71 #endif
72
73 #if ENABLE_FEATURE_FIND_INUM
74 static ino_t inode_num;
75 #endif
76
77 #if ENABLE_FEATURE_FIND_EXEC
78 static char **exec_argv;
79 static int *subst_count;
80 static int exec_argc;
81 #endif
82
83 static int count_subst(const char *str)
84 {
85         int count = 0;
86         while ((str = strstr(str, "{}"))) {
87                 count++;
88                 str++;
89         }
90         return count;
91 }
92
93
94 static char* subst(const char *src, int count, const char* filename)
95 {
96         char *buf, *dst, *end;
97         int flen = strlen(filename);
98         /* we replace each '{}' with filename: growth by strlen-2 */
99         buf = dst = xmalloc(strlen(src) + count*(flen-2) + 1);
100         while ((end = strstr(src, "{}"))) {
101                 memcpy(dst, src, end - src);
102                 dst += end - src;
103                 src = end + 2;
104                 memcpy(dst, filename, flen);
105                 dst += flen;
106         }
107         strcpy(dst, src);
108         return buf;
109 }
110
111
112 static int fileAction(const char *fileName, struct stat *statbuf, void* junk, int depth)
113 {
114 #if ENABLE_FEATURE_FIND_XDEV
115         if (S_ISDIR(statbuf->st_mode) && xdev_count) {
116                 int i;
117                 for (i=0; i<xdev_count; i++) {
118                         if (xdev_dev[i] != statbuf->st_dev)
119                                 return SKIP;
120                 }
121         }
122 #endif
123         if (pattern != NULL) {
124                 const char *tmp = strrchr(fileName, '/');
125
126                 if (tmp == NULL)
127                         tmp = fileName;
128                 else
129                         tmp++;
130                 if (fnmatch(pattern, tmp, FNM_PERIOD) != 0)
131                         goto no_match;
132         }
133 #if ENABLE_FEATURE_FIND_TYPE
134         if (type_mask != 0) {
135                 if (!((statbuf->st_mode & S_IFMT) == type_mask))
136                         goto no_match;
137         }
138 #endif
139 #if ENABLE_FEATURE_FIND_PERM
140         if (perm_mask != 0) {
141                 if (!((isdigit(perm_char) && (statbuf->st_mode & 07777) == perm_mask) ||
142                          (perm_char == '-' && (statbuf->st_mode & perm_mask) == perm_mask) ||
143                          (perm_char == '+' && (statbuf->st_mode & perm_mask) != 0)))
144                         goto no_match;
145         }
146 #endif
147 #if ENABLE_FEATURE_FIND_MTIME
148         if (mtime_char != 0) {
149                 time_t file_age = time(NULL) - statbuf->st_mtime;
150                 time_t mtime_secs = mtime_days * 24 * 60 * 60;
151                 if (!((isdigit(mtime_char) && file_age >= mtime_secs &&
152                                                 file_age < mtime_secs + 24 * 60 * 60) ||
153                                 (mtime_char == '+' && file_age >= mtime_secs + 24 * 60 * 60) ||
154                                 (mtime_char == '-' && file_age < mtime_secs)))
155                         goto no_match;
156         }
157 #endif
158 #if ENABLE_FEATURE_FIND_MMIN
159         if (mmin_char != 0) {
160                 time_t file_age = time(NULL) - statbuf->st_mtime;
161                 time_t mmin_secs = mmin_mins * 60;
162                 if (!((isdigit(mmin_char) && file_age >= mmin_secs &&
163                                                 file_age < mmin_secs + 60) ||
164                                 (mmin_char == '+' && file_age >= mmin_secs + 60) ||
165                                 (mmin_char == '-' && file_age < mmin_secs)))
166                         goto no_match;
167         }
168 #endif
169 #if ENABLE_FEATURE_FIND_NEWER
170         if (newer_mtime != 0) {
171                 time_t file_age = newer_mtime - statbuf->st_mtime;
172                 if (file_age >= 0)
173                         goto no_match;
174         }
175 #endif
176 #if ENABLE_FEATURE_FIND_INUM
177         if (inode_num != 0) {
178                 if (!(statbuf->st_ino == inode_num))
179                         goto no_match;
180         }
181 #endif
182 #if ENABLE_FEATURE_FIND_EXEC
183         if (exec_argc) {
184                 int i;
185                 char *argv[exec_argc+1];
186                 for (i = 0; i < exec_argc; i++)
187                         argv[i] = subst(exec_argv[i], subst_count[i], fileName);
188                 argv[i] = NULL; /* terminate the list */
189                 errno = 0;
190                 wait4pid(spawn(argv));
191                 if (errno)
192                         bb_perror_msg("%s", argv[0]);
193                 for (i = 0; i < exec_argc; i++)
194                         free(argv[i]);
195                 goto no_match;
196         }
197 #endif
198
199 #if ENABLE_FEATURE_FIND_PRINT0
200         printf("%s%c", fileName, printsep);
201 #else
202         puts(fileName);
203 #endif
204  no_match:
205         return TRUE;
206 }
207
208 #if ENABLE_FEATURE_FIND_TYPE
209 static int find_type(char *type)
210 {
211         int mask = 0;
212
213         switch (type[0]) {
214         case 'b':
215                 mask = S_IFBLK;
216                 break;
217         case 'c':
218                 mask = S_IFCHR;
219                 break;
220         case 'd':
221                 mask = S_IFDIR;
222                 break;
223         case 'p':
224                 mask = S_IFIFO;
225                 break;
226         case 'f':
227                 mask = S_IFREG;
228                 break;
229         case 'l':
230                 mask = S_IFLNK;
231                 break;
232         case 's':
233                 mask = S_IFSOCK;
234                 break;
235         }
236
237         if (mask == 0 || type[1] != '\0')
238                 bb_error_msg_and_die(bb_msg_invalid_arg, type, "-type");
239
240         return mask;
241 }
242 #endif
243
244 int find_main(int argc, char **argv)
245 {
246         int dereference = FALSE;
247         int i, j, firstopt, status = EXIT_SUCCESS;
248
249         for (firstopt = 1; firstopt < argc; firstopt++) {
250                 if (argv[firstopt][0] == '-')
251                         break;
252         }
253
254         /* Parse any options */
255         for (i = firstopt; i < argc; i++) {
256                 char *arg = argv[i];
257                 char *arg1 = argv[i+1];
258                 if (strcmp(arg, "-follow") == 0)
259                         dereference = TRUE;
260                 else if (strcmp(arg, "-print") == 0) {
261                         ;
262                 }
263 #if ENABLE_FEATURE_FIND_PRINT0
264                 else if (strcmp(arg, "-print0") == 0)
265                         printsep = '\0';
266 #endif
267                 else if (strcmp(arg, "-name") == 0) {
268                         if (++i == argc)
269                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
270                         pattern = arg1;
271 #if ENABLE_FEATURE_FIND_TYPE
272                 } else if (strcmp(arg, "-type") == 0) {
273                         if (++i == argc)
274                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
275                         type_mask = find_type(arg1);
276 #endif
277 #if ENABLE_FEATURE_FIND_PERM
278 /* TODO:
279  * -perm mode   File's permission bits are exactly mode (octal or symbolic).
280  *              Symbolic modes use mode 0 as a point of departure.
281  * -perm -mode  All of the permission bits mode are set for the file.
282  * -perm +mode  Any of the permission bits mode are set for the file.
283  */
284                 } else if (strcmp(arg, "-perm") == 0) {
285                         if (++i == argc)
286                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
287                         perm_mask = xstrtol_range(arg1, 8, 0, 07777);
288                         perm_char = arg1[0];
289                         if (perm_char == '-')
290                                 perm_mask = -perm_mask;
291 #endif
292 #if ENABLE_FEATURE_FIND_MTIME
293                 } else if (strcmp(arg, "-mtime") == 0) {
294                         if (++i == argc)
295                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
296                         mtime_days = xatol(arg1);
297                         mtime_char = arg1[0];
298                         if (mtime_char == '-')
299                                 mtime_days = -mtime_days;
300 #endif
301 #if ENABLE_FEATURE_FIND_MMIN
302                 } else if (strcmp(arg, "-mmin") == 0) {
303                         if (++i == argc)
304                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
305                         mmin_mins = xatol(arg1);
306                         mmin_char = arg1[0];
307                         if (mmin_char == '-')
308                                 mmin_mins = -mmin_mins;
309 #endif
310 #if ENABLE_FEATURE_FIND_XDEV
311                 } else if (strcmp(arg, "-xdev") == 0) {
312                         struct stat stbuf;
313
314                         xdev_count = (firstopt - 1) ? (firstopt - 1) : 1;
315                         xdev_dev = xmalloc(xdev_count * sizeof(dev_t));
316
317                         if (firstopt == 1) {
318                                 xstat(".", &stbuf);
319                                 xdev_dev[0] = stbuf.st_dev;
320                         } else {
321                                 for (j = 1; j < firstopt; i++) {
322                                         xstat(argv[j], &stbuf);
323                                         xdev_dev[j-1] = stbuf.st_dev;
324                                 }
325                         }
326 #endif
327 #if ENABLE_FEATURE_FIND_NEWER
328                 } else if (strcmp(arg, "-newer") == 0) {
329                         struct stat stat_newer;
330                         if (++i == argc)
331                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
332                         xstat(arg1, &stat_newer);
333                         newer_mtime = stat_newer.st_mtime;
334 #endif
335 #if ENABLE_FEATURE_FIND_INUM
336                 } else if (strcmp(arg, "-inum") == 0) {
337                         if (++i == argc)
338                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
339                         inode_num = xatoul(arg1);
340 #endif
341 #if ENABLE_FEATURE_FIND_EXEC
342                 } else if (strcmp(arg, "-exec") == 0) {
343                         i++; /* now: argv[i] is the first arg after -exec */
344                         exec_argv = &argv[i];
345                         exec_argc = i;
346                         while (1) {
347                                 if (i == argc) /* did not see ';' till end */
348                                         bb_error_msg_and_die(bb_msg_requires_arg, arg);
349                                 if (argv[i][0] == ';' && argv[i][1] == '\0')
350                                         break;
351                                 i++;
352                         }
353                         exec_argc = i - exec_argc; /* number of --exec arguments */
354                         if (exec_argc == 0)
355                                 bb_error_msg_and_die(bb_msg_requires_arg, arg);
356                         subst_count = xmalloc(exec_argc * sizeof(int));
357                         j = exec_argc;
358                         while (j--)
359                                 subst_count[j] = count_subst(exec_argv[j]);
360 #endif
361                 } else
362                         bb_show_usage();
363         }
364
365         if (firstopt == 1) {
366                 static const char *const dot[] = { ".", NULL };
367                 firstopt++;
368                 argv = (char**)dot - 1;
369         }
370         for (i = 1; i < firstopt; i++) {
371                 if (!recursive_action(argv[i],
372                                 TRUE,           // recurse
373                                 dereference,    // follow links
374                                 FALSE,          // depth first
375                                 fileAction,     // file action
376                                 fileAction,     // dir action
377                                 NULL,           // user data
378                                 0))             // depth
379                         status = EXIT_FAILURE;
380         }
381         return status;
382 }