]> git.saurik.com Git - apple/shell_cmds.git/blob - find/function.c
eb2c65ad5baf823cff5cb1d71e4f1863661ebc41
[apple/shell_cmds.git] / find / function.c
1 /*-
2 * Copyright (c) 1990, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Cimarron D. Taylor of the University of California, Berkeley.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37 #ifndef lint
38 #if 0
39 static const char sccsid[] = "@(#)function.c 8.10 (Berkeley) 5/4/95";
40 #else
41 static const char rcsid[] =
42 "$FreeBSD: src/usr.bin/find/function.c,v 1.22.2.9 2001/09/19 09:44:24 ru Exp $";
43 #endif
44 #endif /* not lint */
45
46 #include <sys/param.h>
47 #include <sys/ucred.h>
48 #include <sys/stat.h>
49 #include <sys/wait.h>
50 #include <sys/mount.h>
51 #include <sys/timeb.h>
52 #include <sys/types.h>
53 #include <sys/sysctl.h>
54
55 #include <dirent.h>
56 #include <err.h>
57 #include <errno.h>
58 #include <fnmatch.h>
59 #include <fts.h>
60 #include <grp.h>
61 #include <pwd.h>
62 #include <regex.h>
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <unistd.h>
67
68 #include "find.h"
69
70 #ifdef __APPLE__
71 #include "get_compat.h"
72 #else
73 #define COMPAT_MODE(func, mode) 1
74 #endif
75
76 time_t get_date __P((char *date, struct timeb *now));
77
78 #define COMPARE(a, b) { \
79 switch (plan->flags & F_ELG_MASK) { \
80 case F_EQUAL: \
81 return (a == b); \
82 case F_LESSTHAN: \
83 return (a < b); \
84 case F_GREATER: \
85 return (a > b); \
86 default: \
87 abort(); \
88 } \
89 }
90
91 static PLAN *
92 palloc(option)
93 OPTION *option;
94 {
95 PLAN *new;
96
97 if ((new = malloc(sizeof(PLAN))) == NULL)
98 err(1, NULL);
99 new->execute = option->execute;
100 new->flags = option->flags;
101 new->next = NULL;
102 return new;
103 }
104
105 /*
106 * find_parsenum --
107 * Parse a string of the form [+-]# and return the value.
108 */
109 static long long
110 find_parsenum(plan, option, vp, endch)
111 PLAN *plan;
112 char *option, *vp, *endch;
113 {
114 long long value;
115 char *endchar, *str; /* Pointer to character ending conversion. */
116
117 /* Determine comparison from leading + or -. */
118 str = vp;
119 switch (*str) {
120 case '+':
121 ++str;
122 plan->flags |= F_GREATER;
123 break;
124 case '-':
125 ++str;
126 plan->flags |= F_LESSTHAN;
127 break;
128 default:
129 plan->flags |= F_EQUAL;
130 break;
131 }
132
133 /*
134 * Convert the string with strtoq(). Note, if strtoq() returns zero
135 * and endchar points to the beginning of the string we know we have
136 * a syntax error.
137 */
138 value = strtoq(str, &endchar, 10);
139 if (value == 0 && endchar == str)
140 errx(1, "%s: %s: illegal numeric value", option, vp);
141 if (endchar[0] && (endch == NULL || endchar[0] != *endch))
142 errx(1, "%s: %s: illegal trailing character", option, vp);
143 if (endch)
144 *endch = endchar[0];
145 return value;
146 }
147
148 /*
149 * nextarg --
150 * Check that another argument still exists, return a pointer to it,
151 * and increment the argument vector pointer.
152 */
153 static char *
154 nextarg(option, argvp)
155 OPTION *option;
156 char ***argvp;
157 {
158 char *arg;
159
160 if ((arg = **argvp) == 0)
161 errx(1, "%s: requires additional arguments", option->name);
162 (*argvp)++;
163 return arg;
164 } /* nextarg() */
165
166 /*
167 * The value of n for the inode times (atime, ctime, and mtime) is a range,
168 * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with
169 * -n, such that "-mtime -1" would be less than 0 days, which isn't what the
170 * user wanted. Correct so that -1 is "less than 1".
171 */
172 #define TIME_CORRECT(p) \
173 if (((p)->flags & F_ELG_MASK) == F_LESSTHAN) \
174 ++((p)->t_data);
175
176 /*
177 * -[acm]min n functions --
178 *
179 * True if the difference between the
180 * file access time (-amin)
181 * last change of file status information (-cmin)
182 * file modification time (-mmin)
183 * and the current time is n min periods.
184 */
185 int
186 f_Xmin(plan, entry)
187 PLAN *plan;
188 FTSENT *entry;
189 {
190 extern time_t now;
191
192 if (plan->flags & F_TIME_C) {
193 COMPARE((now - entry->fts_statp->st_ctime +
194 60 - 1) / 60, plan->t_data);
195 } else if (plan->flags & F_TIME_A) {
196 COMPARE((now - entry->fts_statp->st_atime +
197 60 - 1) / 60, plan->t_data);
198 } else {
199 COMPARE((now - entry->fts_statp->st_mtime +
200 60 - 1) / 60, plan->t_data);
201 }
202 }
203
204 PLAN *
205 c_Xmin(option, argvp)
206 OPTION *option;
207 char ***argvp;
208 {
209 char *nmins;
210 PLAN *new;
211
212 nmins = nextarg(option, argvp);
213 ftsoptions &= ~FTS_NOSTAT;
214
215 new = palloc(option);
216 new->t_data = find_parsenum(new, option->name, nmins, NULL);
217 TIME_CORRECT(new);
218 return new;
219 }
220
221 /*
222 * -[acm]time n functions --
223 *
224 * True if the difference between the
225 * file access time (-atime)
226 * last change of file status information (-ctime)
227 * file modification time (-mtime)
228 * and the current time is n 24 hour periods.
229 */
230
231 int
232 f_Xtime(plan, entry)
233 PLAN *plan;
234 FTSENT *entry;
235 {
236 extern time_t now;
237
238 int fudge = COMPAT_MODE("bin/find", "unix2003") ? 0 : 86400 - 1;
239
240 if (plan->flags & F_TIME_C) {
241 COMPARE((now - entry->fts_statp->st_ctime +
242 fudge) / 86400, plan->t_data);
243 } else if (plan->flags & F_TIME_A) {
244 COMPARE((now - entry->fts_statp->st_atime +
245 fudge) / 86400, plan->t_data);
246 } else {
247 COMPARE((now - entry->fts_statp->st_mtime +
248 fudge) / 86400, plan->t_data);
249 }
250 }
251
252 PLAN *
253 c_Xtime(option, argvp)
254 OPTION *option;
255 char ***argvp;
256 {
257 char *ndays;
258 PLAN *new;
259
260 ndays = nextarg(option, argvp);
261 ftsoptions &= ~FTS_NOSTAT;
262
263 new = palloc(option);
264 new->t_data = find_parsenum(new, option->name, ndays, NULL);
265 TIME_CORRECT(new);
266 return new;
267 }
268
269 /*
270 * -maxdepth/-mindepth n functions --
271 *
272 * Does the same as -prune if the level of the current file is
273 * greater/less than the specified maximum/minimum depth.
274 *
275 * Note that -maxdepth and -mindepth are handled specially in
276 * find_execute() so their f_* functions are set to f_always_true().
277 */
278 PLAN *
279 c_mXXdepth(option, argvp)
280 OPTION *option;
281 char ***argvp;
282 {
283 char *dstr;
284 PLAN *new;
285
286 dstr = nextarg(option, argvp);
287 if (dstr[0] == '-')
288 /* all other errors handled by find_parsenum() */
289 errx(1, "%s: %s: value must be positive", option->name, dstr);
290
291 new = palloc(option);
292 if (option->flags & F_MAXDEPTH)
293 maxdepth = find_parsenum(new, option->name, dstr, NULL);
294 else
295 mindepth = find_parsenum(new, option->name, dstr, NULL);
296 return new;
297 }
298
299 /*
300 * -delete functions --
301 *
302 * True always. Makes its best shot and continues on regardless.
303 */
304 int
305 f_delete(plan, entry)
306 PLAN *plan;
307 FTSENT *entry;
308 {
309 /* ignore these from fts */
310 if (strcmp(entry->fts_accpath, ".") == 0 ||
311 strcmp(entry->fts_accpath, "..") == 0)
312 return 1;
313
314 /* sanity check */
315 if (isdepth == 0 || /* depth off */
316 (ftsoptions & FTS_NOSTAT) || /* not stat()ing */
317 !(ftsoptions & FTS_PHYSICAL) || /* physical off */
318 (ftsoptions & FTS_LOGICAL)) /* or finally, logical on */
319 errx(1, "-delete: insecure options got turned on");
320
321 /* Potentially unsafe - do not accept relative paths whatsoever */
322 if (strchr(entry->fts_accpath, '/') != NULL)
323 errx(1, "-delete: %s: relative path potentially not safe",
324 entry->fts_accpath);
325
326 /* Turn off user immutable bits if running as root */
327 if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
328 !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
329 geteuid() == 0)
330 chflags(entry->fts_accpath,
331 entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
332
333 /* rmdir directories, unlink everything else */
334 if (S_ISDIR(entry->fts_statp->st_mode)) {
335 if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY)
336 warn("-delete: rmdir(%s)", entry->fts_path);
337 } else {
338 if (unlink(entry->fts_accpath) < 0)
339 warn("-delete: unlink(%s)", entry->fts_path);
340 }
341
342 /* "succeed" */
343 return 1;
344 }
345
346 PLAN *
347 c_delete(option, argvp)
348 OPTION *option;
349 char ***argvp;
350 {
351
352 ftsoptions &= ~FTS_NOSTAT; /* no optimise */
353 ftsoptions |= FTS_PHYSICAL; /* disable -follow */
354 ftsoptions &= ~FTS_LOGICAL; /* disable -follow */
355 isoutput = 1; /* possible output */
356 isdepth = 1; /* -depth implied */
357
358 return palloc(option);
359 }
360
361
362 /*
363 * -depth functions --
364 *
365 * Always true, causes descent of the directory hierarchy to be done
366 * so that all entries in a directory are acted on before the directory
367 * itself.
368 */
369 int
370 f_always_true(plan, entry)
371 PLAN *plan;
372 FTSENT *entry;
373 {
374 return 1;
375 }
376
377 PLAN *
378 c_depth(option, argvp)
379 OPTION *option;
380 char ***argvp;
381 {
382 isdepth = 1;
383
384 return palloc(option);
385 }
386
387 /*
388 * -empty functions --
389 *
390 * True if the file or directory is empty
391 */
392 int
393 f_empty(plan, entry)
394 PLAN *plan;
395 FTSENT *entry;
396 {
397 if (S_ISREG(entry->fts_statp->st_mode) &&
398 entry->fts_statp->st_size == 0)
399 return 1;
400 if (S_ISDIR(entry->fts_statp->st_mode)) {
401 struct dirent *dp;
402 int empty;
403 DIR *dir;
404
405 empty = 1;
406 dir = opendir(entry->fts_accpath);
407 if (dir == NULL)
408 err(1, "%s", entry->fts_accpath);
409 for (dp = readdir(dir); dp; dp = readdir(dir))
410 if (dp->d_name[0] != '.' ||
411 (dp->d_name[1] != '\0' &&
412 (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) {
413 empty = 0;
414 break;
415 }
416 closedir(dir);
417 return empty;
418 }
419 return 0;
420 }
421
422 PLAN *
423 c_empty(option, argvp)
424 OPTION *option;
425 char ***argvp;
426 {
427 ftsoptions &= ~FTS_NOSTAT;
428
429 return palloc(option);
430 }
431
432 /*
433 * [-exec | -execdir | -ok] utility [arg ... ] ; functions --
434 *
435 * True if the executed utility returns a zero value as exit status.
436 * The end of the primary expression is delimited by a semicolon. If
437 * "{}" occurs anywhere, it gets replaced by the current pathname,
438 * or, in the case of -execdir, the current basename (filename
439 * without leading directory prefix). For -exec and -ok,
440 * the current directory for the execution of utility is the same as
441 * the current directory when the find utility was started, whereas
442 * for -execdir, it is the directory the file resides in.
443 *
444 * The primary -ok differs from -exec in that it requests affirmation
445 * of the user before executing the utility.
446 */
447 int
448 f_exec(plan, entry)
449 register PLAN *plan;
450 FTSENT *entry;
451 {
452 extern int dotfd;
453 register int cnt;
454 pid_t pid;
455 int status;
456 char *file = NULL;
457 static char **plus_path = NULL;
458 static int plus_path_cnt = 0;
459 static unsigned long exec_after = 0;
460 static unsigned long path_so_far = 0;
461 struct _ex saved_ex;
462
463 if (entry) {
464 /* XXX - if file/dir ends in '/' this will not work -- can it? */
465 if ((plan->flags & F_EXECDIR) &&
466 (file = strrchr(entry->fts_path, '/'))) {
467 file++;
468 } else {
469 file = entry->fts_path;
470 }
471
472 if (plan->flags & F_CLEANUP) {
473 plus_path = realloc(plus_path, sizeof(char *) * ++plus_path_cnt);
474 if (!plus_path) errx(1, "out of memory");
475 plus_path[plus_path_cnt -1] = strdup(file);
476 if (!plus_path[plus_path_cnt -1]) errx(1, "out of memory");
477
478 path_so_far += sizeof(char *) + strlen(file);
479
480 if (!exec_after) {
481 size_t l = sizeof(exec_after);
482 int rc =
483 sysctlbyname("kern.argmax", &exec_after, &l, NULL, NULL);
484 if (rc < 0) {
485 /* Something conservitave */
486 exec_after = 16 * 1024;
487 }
488 }
489 if (path_so_far >= exec_after) {
490 return f_exec(plan, NULL);
491 }
492
493 return 1;
494 }
495 } else {
496 if (!plus_path) return 1;
497 saved_ex = plan->p_un.ex;
498 int new_len = 0;
499 int ocnt;
500 for(cnt = 0; plan->e_argv[cnt]; ++cnt) {
501 if (plan->e_len[cnt]) {
502 new_len += plus_path_cnt;
503 } else {
504 new_len++;
505 }
506 }
507 new_len++;
508
509 plan->e_argv = malloc(sizeof(char *) * new_len);
510 if (!plan->e_argv) errx(1, "out of memory");
511
512 for(ocnt = cnt = 0; saved_ex._e_argv[ocnt]; ++ocnt) {
513 if (saved_ex._e_len[ocnt]) {
514 int ppi;
515 for(ppi = 0; ppi < plus_path_cnt; ++ppi) {
516 plan->e_argv[cnt++] = plus_path[ppi];
517 }
518 #if 0
519 memmove(plan->e_argv + cnt, *plus_path,
520 sizeof(char *) * plus_path_cnt);
521 cnt += plus_path_cnt;
522 #endif
523 } else {
524 plan->e_argv[cnt++] = saved_ex._e_argv[ocnt];
525 }
526 }
527 plan->e_argv[cnt++] = NULL;
528 }
529
530 if (!(plan->flags & F_CLEANUP)) for (cnt = 0; plan->e_argv[cnt]; ++cnt)
531 if (plan->e_len[cnt])
532 brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
533 file, plan->e_len[cnt]);
534
535 if ((plan->flags & F_NEEDOK) && !queryuser(plan->e_argv))
536 return 0;
537
538 /* make sure find output is interspersed correctly with subprocesses */
539 fflush(stdout);
540 fflush(stderr);
541
542 switch (pid = fork()) {
543 case -1:
544 err(1, "fork");
545 /* NOTREACHED */
546 case 0:
547 /* change dir back from where we started */
548 if (!(plan->flags & F_EXECDIR) && fchdir(dotfd)) {
549 warn("chdir");
550 _exit(1);
551 }
552 execvp(plan->e_argv[0], plan->e_argv);
553 warn("%s", plan->e_argv[0]);
554 _exit(1);
555 }
556 pid = waitpid(pid, &status, 0);
557 if (plus_path) {
558 int i;
559 for(i = 0; i < plus_path_cnt; ++i) {
560 free(plus_path[i]);
561 }
562 free(plus_path);
563 plus_path = NULL;
564 plus_path_cnt = 0;
565 plan->p_un.ex = saved_ex;
566 path_so_far = 0;
567 if (WIFEXITED(status) && WEXITSTATUS(status)) {
568 exit(WEXITSTATUS(status));
569 }
570 }
571 return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
572 }
573
574 /*
575 * c_exec, c_execdir, c_ok --
576 * build three parallel arrays, one with pointers to the strings passed
577 * on the command line, one with (possibly duplicated) pointers to the
578 * argv array, and one with integer values that are lengths of the
579 * strings, but also flags meaning that the string has to be massaged.
580 */
581 PLAN *
582 c_exec(option, argvp)
583 OPTION *option;
584 char ***argvp;
585 {
586 PLAN *new; /* node returned */
587 register int cnt;
588 register char **argv, **ap, *p;
589 int plus_armed = 0;
590
591 /* XXX - was in c_execdir, but seems unnecessary!?
592 ftsoptions &= ~FTS_NOSTAT;
593 */
594 isoutput = 1;
595
596 /* XXX - this is a change from the previous coding */
597 new = palloc(option);
598
599 for (ap = argv = *argvp;; ++ap) {
600 if (!*ap)
601 errx(1,
602 "%s: no terminating \";\"", option->name);
603 if (**ap == '{' && ap[0][1] == '}' && ap[0][2] == '\0') {
604 plus_armed = COMPAT_MODE("bin/find", "unix2003");
605 continue;
606 }
607 if (**ap == ';') {
608 break;
609 }
610 if (plus_armed && **ap == '+') {
611 new->flags |= F_CLEANUP;
612 break;
613 }
614 plus_armed = 0;
615 }
616
617 cnt = ap - *argvp + 1;
618 new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
619 new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
620 new->e_len = (int *)emalloc((u_int)cnt * sizeof(int));
621
622 for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
623 new->e_orig[cnt] = *argv;
624 for (p = *argv; *p; ++p)
625 if (p[0] == '{' && p[1] == '}') {
626 new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN);
627 new->e_len[cnt] = MAXPATHLEN;
628 break;
629 }
630 if (!*p) {
631 new->e_argv[cnt] = *argv;
632 new->e_len[cnt] = 0;
633 }
634 }
635 new->e_argv[cnt] = new->e_orig[cnt] = NULL;
636
637 *argvp = argv + 1;
638 return new;
639 }
640
641 int
642 f_flags(plan, entry)
643 PLAN *plan;
644 FTSENT *entry;
645 {
646 u_long flags;
647
648 flags = entry->fts_statp->st_flags;
649 if (plan->flags & F_ATLEAST)
650 return (flags | plan->fl_flags) == flags &&
651 !(flags & plan->fl_notflags);
652 else if (plan->flags & F_ANY)
653 return (flags & plan->fl_flags) ||
654 (flags | plan->fl_notflags) != flags;
655 else
656 return flags == plan->fl_flags &&
657 !(plan->fl_flags & plan->fl_notflags);
658 }
659
660 PLAN *
661 c_flags(option, argvp)
662 OPTION *option;
663 char ***argvp;
664 {
665 char *flags_str;
666 PLAN *new;
667 u_long flags, notflags;
668
669 flags_str = nextarg(option, argvp);
670 ftsoptions &= ~FTS_NOSTAT;
671
672 new = palloc(option);
673
674 if (*flags_str == '-') {
675 new->flags |= F_ATLEAST;
676 flags_str++;
677 } else if (*flags_str == '+') {
678 new->flags |= F_ANY;
679 flags_str++;
680 }
681 if (strtofflags(&flags_str, &flags, &notflags) == 1)
682 errx(1, "%s: %s: illegal flags string", option->name, flags_str);
683
684 new->fl_flags = flags;
685 new->fl_notflags = notflags;
686 return new;
687 }
688
689 /*
690 * -follow functions --
691 *
692 * Always true, causes symbolic links to be followed on a global
693 * basis.
694 */
695 PLAN *
696 c_follow(option, argvp)
697 OPTION *option;
698 char ***argvp;
699 {
700 ftsoptions &= ~FTS_PHYSICAL;
701 ftsoptions |= FTS_LOGICAL;
702
703 return palloc(option);
704 }
705
706 /*
707 * -fstype functions --
708 *
709 * True if the file is of a certain type.
710 */
711 int
712 f_fstype(plan, entry)
713 PLAN *plan;
714 FTSENT *entry;
715 {
716 static dev_t curdev; /* need a guaranteed illegal dev value */
717 static int first = 1;
718 struct statfs sb;
719 static int val_type, val_flags;
720 char *p, save[2];
721
722 /* Only check when we cross mount point. */
723 if (first || curdev != entry->fts_statp->st_dev) {
724 curdev = entry->fts_statp->st_dev;
725
726 /*
727 * Statfs follows symlinks; find wants the link's file system,
728 * not where it points.
729 */
730 if (entry->fts_info == FTS_SL ||
731 entry->fts_info == FTS_SLNONE) {
732 if ((p = strrchr(entry->fts_accpath, '/')) != NULL)
733 ++p;
734 else
735 p = entry->fts_accpath;
736 save[0] = p[0];
737 p[0] = '.';
738 save[1] = p[1];
739 p[1] = '\0';
740 } else
741 p = NULL;
742
743 if (statfs(entry->fts_accpath, &sb))
744 err(1, "%s", entry->fts_accpath);
745
746 if (p) {
747 p[0] = save[0];
748 p[1] = save[1];
749 }
750
751 first = 0;
752
753 /*
754 * Further tests may need both of these values, so
755 * always copy both of them.
756 */
757 val_flags = sb.f_flags;
758 val_type = sb.f_type;
759 }
760 switch (plan->flags & F_MTMASK) {
761 case F_MTFLAG:
762 return (val_flags & plan->mt_data) != 0;
763 case F_MTTYPE:
764 return (val_type == plan->mt_data);
765 default:
766 abort();
767 }
768 }
769
770 #if !defined(__NetBSD__)
771 PLAN *
772 c_fstype(option, argvp)
773 OPTION *option;
774 char ***argvp;
775 {
776 char *fsname;
777 register PLAN *new;
778 struct vfsconf vfc;
779
780 fsname = nextarg(option, argvp);
781 ftsoptions &= ~FTS_NOSTAT;
782
783 new = palloc(option);
784
785 /*
786 * Check first for a filesystem name.
787 */
788 if (getvfsbyname(fsname, &vfc) == 0) {
789 new->flags |= F_MTTYPE;
790 new->mt_data = vfc.vfc_typenum;
791 return new;
792 }
793
794 switch (*fsname) {
795 case 'l':
796 if (!strcmp(fsname, "local")) {
797 new->flags |= F_MTFLAG;
798 new->mt_data = MNT_LOCAL;
799 return new;
800 }
801 break;
802 case 'r':
803 if (!strcmp(fsname, "rdonly")) {
804 new->flags |= F_MTFLAG;
805 new->mt_data = MNT_RDONLY;
806 return new;
807 }
808 break;
809 }
810
811 errx(1, "%s: unknown file type", fsname);
812 /* NOTREACHED */
813 }
814 #endif /* __NetBSD__ */
815
816 /*
817 * -group gname functions --
818 *
819 * True if the file belongs to the group gname. If gname is numeric and
820 * an equivalent of the getgrnam() function does not return a valid group
821 * name, gname is taken as a group ID.
822 */
823 int
824 f_group(plan, entry)
825 PLAN *plan;
826 FTSENT *entry;
827 {
828 return entry->fts_statp->st_gid == plan->g_data;
829 }
830
831 PLAN *
832 c_group(option, argvp)
833 OPTION *option;
834 char ***argvp;
835 {
836 char *gname;
837 PLAN *new;
838 struct group *g;
839 gid_t gid;
840
841 gname = nextarg(option, argvp);
842 ftsoptions &= ~FTS_NOSTAT;
843
844 g = getgrnam(gname);
845 if (g == NULL) {
846 gid = atoi(gname);
847 if (gid == 0 && gname[0] != '0')
848 errx(1, "%s: %s: no such group", option->name, gname);
849 } else
850 gid = g->gr_gid;
851
852 new = palloc(option);
853 new->g_data = gid;
854 return new;
855 }
856
857 /*
858 * -inum n functions --
859 *
860 * True if the file has inode # n.
861 */
862 int
863 f_inum(plan, entry)
864 PLAN *plan;
865 FTSENT *entry;
866 {
867 COMPARE(entry->fts_statp->st_ino, plan->i_data);
868 }
869
870 PLAN *
871 c_inum(option, argvp)
872 OPTION *option;
873 char ***argvp;
874 {
875 char *inum_str;
876 PLAN *new;
877
878 inum_str = nextarg(option, argvp);
879 ftsoptions &= ~FTS_NOSTAT;
880
881 new = palloc(option);
882 new->i_data = find_parsenum(new, option->name, inum_str, NULL);
883 return new;
884 }
885
886 /*
887 * -links n functions --
888 *
889 * True if the file has n links.
890 */
891 int
892 f_links(plan, entry)
893 PLAN *plan;
894 FTSENT *entry;
895 {
896 COMPARE(entry->fts_statp->st_nlink, plan->l_data);
897 }
898
899 PLAN *
900 c_links(option, argvp)
901 OPTION *option;
902 char ***argvp;
903 {
904 char *nlinks;
905 PLAN *new;
906
907 nlinks = nextarg(option, argvp);
908 ftsoptions &= ~FTS_NOSTAT;
909
910 new = palloc(option);
911 new->l_data = (nlink_t)find_parsenum(new, option->name, nlinks, NULL);
912 return new;
913 }
914
915 /*
916 * -ls functions --
917 *
918 * Always true - prints the current entry to stdout in "ls" format.
919 */
920 int
921 f_ls(plan, entry)
922 PLAN *plan;
923 FTSENT *entry;
924 {
925 printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp);
926 return 1;
927 }
928
929 PLAN *
930 c_ls(option, argvp)
931 OPTION *option;
932 char ***argvp;
933 {
934 ftsoptions &= ~FTS_NOSTAT;
935 isoutput = 1;
936
937 return palloc(option);
938 }
939
940 /*
941 * -name functions --
942 *
943 * True if the basename of the filename being examined
944 * matches pattern using Pattern Matching Notation S3.14
945 */
946 int
947 f_name(plan, entry)
948 PLAN *plan;
949 FTSENT *entry;
950 {
951 return !fnmatch(plan->c_data, entry->fts_name,
952 plan->flags & F_IGNCASE ? FNM_CASEFOLD : 0);
953 }
954
955 PLAN *
956 c_name(option, argvp)
957 OPTION *option;
958 char ***argvp;
959 {
960 char *pattern;
961 PLAN *new;
962
963 pattern = nextarg(option, argvp);
964 new = palloc(option);
965 new->c_data = pattern;
966 return new;
967 }
968
969 /*
970 * -newer file functions --
971 *
972 * True if the current file has been modified more recently
973 * then the modification time of the file named by the pathname
974 * file.
975 */
976 int
977 f_newer(plan, entry)
978 PLAN *plan;
979 FTSENT *entry;
980 {
981 if (plan->flags & F_TIME_C)
982 return entry->fts_statp->st_ctime > plan->t_data;
983 else if (plan->flags & F_TIME_A)
984 return entry->fts_statp->st_atime > plan->t_data;
985 else
986 return entry->fts_statp->st_mtime > plan->t_data;
987 }
988
989 PLAN *
990 c_newer(option, argvp)
991 OPTION *option;
992 char ***argvp;
993 {
994 char *fn_or_tspec;
995 PLAN *new;
996 struct stat sb;
997
998 fn_or_tspec = nextarg(option, argvp);
999 ftsoptions &= ~FTS_NOSTAT;
1000
1001 new = palloc(option);
1002 /* compare against what */
1003 if (option->flags & F_TIME2_T) {
1004 new->t_data = get_date(fn_or_tspec, (struct timeb *) 0);
1005 if (new->t_data == (time_t) -1)
1006 errx(1, "Can't parse date/time: %s", fn_or_tspec);
1007 } else {
1008 if (stat(fn_or_tspec, &sb))
1009 err(1, "%s", fn_or_tspec);
1010 if (option->flags & F_TIME2_C)
1011 new->t_data = sb.st_ctime;
1012 else if (option->flags & F_TIME2_A)
1013 new->t_data = sb.st_atime;
1014 else
1015 new->t_data = sb.st_mtime;
1016 }
1017 return new;
1018 }
1019
1020 /*
1021 * -nogroup functions --
1022 *
1023 * True if file belongs to a user ID for which the equivalent
1024 * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
1025 */
1026 int
1027 f_nogroup(plan, entry)
1028 PLAN *plan;
1029 FTSENT *entry;
1030 {
1031 return group_from_gid(entry->fts_statp->st_gid, 1) == NULL;
1032 }
1033
1034 PLAN *
1035 c_nogroup(option, argvp)
1036 OPTION *option;
1037 char ***argvp;
1038 {
1039 ftsoptions &= ~FTS_NOSTAT;
1040
1041 return palloc(option);
1042 }
1043
1044 /*
1045 * -nouser functions --
1046 *
1047 * True if file belongs to a user ID for which the equivalent
1048 * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
1049 */
1050 int
1051 f_nouser(plan, entry)
1052 PLAN *plan;
1053 FTSENT *entry;
1054 {
1055 return user_from_uid(entry->fts_statp->st_uid, 1) == NULL;
1056 }
1057
1058 PLAN *
1059 c_nouser(option, argvp)
1060 OPTION *option;
1061 char ***argvp;
1062 {
1063 ftsoptions &= ~FTS_NOSTAT;
1064
1065 return palloc(option);
1066 }
1067
1068 /*
1069 * -path functions --
1070 *
1071 * True if the path of the filename being examined
1072 * matches pattern using Pattern Matching Notation S3.14
1073 */
1074 int
1075 f_path(plan, entry)
1076 PLAN *plan;
1077 FTSENT *entry;
1078 {
1079 return !fnmatch(plan->c_data, entry->fts_path,
1080 plan->flags & F_IGNCASE ? FNM_CASEFOLD : 0);
1081 }
1082
1083 /* c_path is the same as c_name */
1084
1085 /*
1086 * -perm functions --
1087 *
1088 * The mode argument is used to represent file mode bits. If it starts
1089 * with a leading digit, it's treated as an octal mode, otherwise as a
1090 * symbolic mode.
1091 */
1092 int
1093 f_perm(plan, entry)
1094 PLAN *plan;
1095 FTSENT *entry;
1096 {
1097 mode_t mode;
1098
1099 mode = entry->fts_statp->st_mode &
1100 (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
1101 if (plan->flags & F_ATLEAST)
1102 return (plan->m_data | mode) == mode;
1103 else if (plan->flags & F_ANY)
1104 return (mode & plan->m_data);
1105 else
1106 return mode == plan->m_data;
1107 /* NOTREACHED */
1108 }
1109
1110 PLAN *
1111 c_perm(option, argvp)
1112 OPTION *option;
1113 char ***argvp;
1114 {
1115 char *perm;
1116 PLAN *new;
1117 mode_t *set;
1118
1119 perm = nextarg(option, argvp);
1120 ftsoptions &= ~FTS_NOSTAT;
1121
1122 new = palloc(option);
1123
1124 if (*perm == '-') {
1125 new->flags |= F_ATLEAST;
1126 ++perm;
1127 } else if (*perm == '+') {
1128 if ((set = setmode(perm +1)) != NULL) {
1129 new->flags |= F_ANY;
1130 ++perm;
1131 free(set);
1132 }
1133 }
1134
1135 if ((set = setmode(perm)) == NULL)
1136 errx(1, "%s: %s: illegal mode string", option->name, perm);
1137
1138 new->m_data = getmode(set, 0);
1139 free(set);
1140 return new;
1141 }
1142
1143 /*
1144 * -print functions --
1145 *
1146 * Always true, causes the current pathame to be written to
1147 * standard output.
1148 */
1149 int
1150 f_print(plan, entry)
1151 PLAN *plan;
1152 FTSENT *entry;
1153 {
1154 (void)puts(entry->fts_path);
1155 return 1;
1156 }
1157
1158 PLAN *
1159 c_print(option, argvp)
1160 OPTION *option;
1161 char ***argvp;
1162 {
1163 isoutput = 1;
1164
1165 return palloc(option);
1166 }
1167
1168 /*
1169 * -print0 functions --
1170 *
1171 * Always true, causes the current pathame to be written to
1172 * standard output followed by a NUL character
1173 */
1174 int
1175 f_print0(plan, entry)
1176 PLAN *plan;
1177 FTSENT *entry;
1178 {
1179 fputs(entry->fts_path, stdout);
1180 fputc('\0', stdout);
1181 return 1;
1182 }
1183
1184 /* c_print0 is the same as c_print */
1185
1186 /*
1187 * -prune functions --
1188 *
1189 * Prune a portion of the hierarchy.
1190 */
1191 int
1192 f_prune(plan, entry)
1193 PLAN *plan;
1194 FTSENT *entry;
1195 {
1196 extern FTS *tree;
1197
1198 if (fts_set(tree, entry, FTS_SKIP))
1199 err(1, "%s", entry->fts_path);
1200 return 1;
1201 }
1202
1203 /* c_prune == c_simple */
1204
1205 /*
1206 * -regex functions --
1207 *
1208 * True if the whole path of the file matches pattern using
1209 * regular expression.
1210 */
1211 int
1212 f_regex(plan, entry)
1213 PLAN *plan;
1214 FTSENT *entry;
1215 {
1216 char *str;
1217 size_t len;
1218 regex_t *pre;
1219 regmatch_t pmatch;
1220 int errcode;
1221 char errbuf[LINE_MAX];
1222 int matched;
1223
1224 pre = plan->re_data;
1225 str = entry->fts_path;
1226 len = strlen(str);
1227 matched = 0;
1228
1229 pmatch.rm_so = 0;
1230 pmatch.rm_eo = len;
1231
1232 errcode = regexec(pre, str, 1, &pmatch, REG_STARTEND);
1233
1234 if (errcode != 0 && errcode != REG_NOMATCH) {
1235 regerror(errcode, pre, errbuf, sizeof errbuf);
1236 errx(1, "%s: %s",
1237 plan->flags & F_IGNCASE ? "-iregex" : "-regex", errbuf);
1238 }
1239
1240 if (errcode == 0 && pmatch.rm_so == 0 && pmatch.rm_eo == len)
1241 matched = 1;
1242
1243 return matched;
1244 }
1245
1246 PLAN *
1247 c_regex(option, argvp)
1248 OPTION *option;
1249 char ***argvp;
1250 {
1251 PLAN *new;
1252 char *pattern;
1253 regex_t *pre;
1254 int errcode;
1255 char errbuf[LINE_MAX];
1256
1257 if ((pre = malloc(sizeof(regex_t))) == NULL)
1258 err(1, NULL);
1259
1260 pattern = nextarg(option, argvp);
1261
1262 if ((errcode = regcomp(pre, pattern,
1263 regexp_flags | (option->flags & F_IGNCASE ? REG_ICASE : 0))) != 0) {
1264 regerror(errcode, pre, errbuf, sizeof errbuf);
1265 errx(1, "%s: %s: %s",
1266 option->flags & F_IGNCASE ? "-iregex" : "-regex",
1267 pattern, errbuf);
1268 }
1269
1270 new = palloc(option);
1271 new->re_data = pre;
1272
1273 return new;
1274 }
1275
1276 /* c_simple covers c_prune, c_openparen, c_closeparen, c_not, c_or */
1277
1278 PLAN *
1279 c_simple(option, argvp)
1280 OPTION *option;
1281 char ***argvp;
1282 {
1283 return palloc(option);
1284 }
1285
1286 /*
1287 * -size n[c] functions --
1288 *
1289 * True if the file size in bytes, divided by an implementation defined
1290 * value and rounded up to the next integer, is n. If n is followed by
1291 * a c, the size is in bytes.
1292 */
1293 #define FIND_SIZE 512
1294 static int divsize = 1;
1295
1296 int
1297 f_size(plan, entry)
1298 PLAN *plan;
1299 FTSENT *entry;
1300 {
1301 off_t size;
1302
1303 size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
1304 FIND_SIZE : entry->fts_statp->st_size;
1305 COMPARE(size, plan->o_data);
1306 }
1307
1308 PLAN *
1309 c_size(option, argvp)
1310 OPTION *option;
1311 char ***argvp;
1312 {
1313 char *size_str;
1314 PLAN *new;
1315 char endch;
1316
1317 size_str = nextarg(option, argvp);
1318 ftsoptions &= ~FTS_NOSTAT;
1319
1320 new = palloc(option);
1321 endch = 'c';
1322 new->o_data = find_parsenum(new, option->name, size_str, &endch);
1323 if (endch == 'c')
1324 divsize = 0;
1325 return new;
1326 }
1327
1328 /*
1329 * -type c functions --
1330 *
1331 * True if the type of the file is c, where c is b, c, d, p, f or w
1332 * for block special file, character special file, directory, FIFO,
1333 * regular file or whiteout respectively.
1334 */
1335 int
1336 f_type(plan, entry)
1337 PLAN *plan;
1338 FTSENT *entry;
1339 {
1340 return (entry->fts_statp->st_mode & S_IFMT) == plan->m_data;
1341 }
1342
1343 PLAN *
1344 c_type(option, argvp)
1345 OPTION *option;
1346 char ***argvp;
1347 {
1348 char *typestring;
1349 PLAN *new;
1350 mode_t mask;
1351
1352 typestring = nextarg(option, argvp);
1353 ftsoptions &= ~FTS_NOSTAT;
1354
1355 switch (typestring[0]) {
1356 case 'b':
1357 mask = S_IFBLK;
1358 break;
1359 case 'c':
1360 mask = S_IFCHR;
1361 break;
1362 case 'd':
1363 mask = S_IFDIR;
1364 break;
1365 case 'f':
1366 mask = S_IFREG;
1367 break;
1368 case 'l':
1369 mask = S_IFLNK;
1370 break;
1371 case 'p':
1372 mask = S_IFIFO;
1373 break;
1374 case 's':
1375 mask = S_IFSOCK;
1376 break;
1377 #ifdef FTS_WHITEOUT
1378 case 'w':
1379 mask = S_IFWHT;
1380 ftsoptions |= FTS_WHITEOUT;
1381 break;
1382 #endif /* FTS_WHITEOUT */
1383 default:
1384 errx(1, "%s: %s: unknown type", option->name, typestring);
1385 }
1386
1387 new = palloc(option);
1388 new->m_data = mask;
1389 return new;
1390 }
1391
1392 /*
1393 * -user uname functions --
1394 *
1395 * True if the file belongs to the user uname. If uname is numeric and
1396 * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1397 * return a valid user name, uname is taken as a user ID.
1398 */
1399 int
1400 f_user(plan, entry)
1401 PLAN *plan;
1402 FTSENT *entry;
1403 {
1404 return entry->fts_statp->st_uid == plan->u_data;
1405 }
1406
1407 PLAN *
1408 c_user(option, argvp)
1409 OPTION *option;
1410 char ***argvp;
1411 {
1412 char *username;
1413 PLAN *new;
1414 struct passwd *p;
1415 uid_t uid;
1416
1417 username = nextarg(option, argvp);
1418 ftsoptions &= ~FTS_NOSTAT;
1419
1420 p = getpwnam(username);
1421 if (p == NULL) {
1422 uid = atoi(username);
1423 if (uid == 0 && username[0] != '0')
1424 errx(1, "%s: %s: no such user", option->name, username);
1425 } else
1426 uid = p->pw_uid;
1427
1428 new = palloc(option);
1429 new->u_data = uid;
1430 return new;
1431 }
1432
1433 /*
1434 * -xdev functions --
1435 *
1436 * Always true, causes find not to decend past directories that have a
1437 * different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1438 */
1439 PLAN *
1440 c_xdev(option, argvp)
1441 OPTION *option;
1442 char ***argvp;
1443 {
1444 ftsoptions |= FTS_XDEV;
1445
1446 return palloc(option);
1447 }
1448
1449 /*
1450 * ( expression ) functions --
1451 *
1452 * True if expression is true.
1453 */
1454 int
1455 f_expr(plan, entry)
1456 PLAN *plan;
1457 FTSENT *entry;
1458 {
1459 register PLAN *p;
1460 register int state = 0;
1461
1462 for (p = plan->p_data[0];
1463 p && (state = (p->execute)(p, entry)); p = p->next);
1464 return state;
1465 }
1466
1467 /*
1468 * f_openparen and f_closeparen nodes are temporary place markers. They are
1469 * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1470 * to a f_expr node containing the expression and the ')' node is discarded.
1471 * The functions themselves are only used as constants.
1472 */
1473
1474 int
1475 f_openparen(plan, entry)
1476 PLAN *plan;
1477 FTSENT *entry;
1478 {
1479 abort();
1480 }
1481
1482 int
1483 f_closeparen(plan, entry)
1484 PLAN *plan;
1485 FTSENT *entry;
1486 {
1487 abort();
1488 }
1489
1490 /* c_openparen == c_simple */
1491 /* c_closeparen == c_simple */
1492
1493 /*
1494 * AND operator. Since AND is implicit, no node is allocated.
1495 */
1496 PLAN *
1497 c_and(option, argvp)
1498 OPTION *option;
1499 char ***argvp;
1500 {
1501 return NULL;
1502 }
1503
1504 /*
1505 * ! expression functions --
1506 *
1507 * Negation of a primary; the unary NOT operator.
1508 */
1509 int
1510 f_not(plan, entry)
1511 PLAN *plan;
1512 FTSENT *entry;
1513 {
1514 register PLAN *p;
1515 register int state = 0;
1516
1517 for (p = plan->p_data[0];
1518 p && (state = (p->execute)(p, entry)); p = p->next);
1519 return !state;
1520 }
1521
1522 /* c_not == c_simple */
1523
1524 /*
1525 * expression -o expression functions --
1526 *
1527 * Alternation of primaries; the OR operator. The second expression is
1528 * not evaluated if the first expression is true.
1529 */
1530 int
1531 f_or(plan, entry)
1532 PLAN *plan;
1533 FTSENT *entry;
1534 {
1535 register PLAN *p;
1536 register int state = 0;
1537
1538 for (p = plan->p_data[0];
1539 p && (state = (p->execute)(p, entry)); p = p->next);
1540
1541 if (state)
1542 return 1;
1543
1544 for (p = plan->p_data[1];
1545 p && (state = (p->execute)(p, entry)); p = p->next);
1546 return state;
1547 }
1548
1549 /* c_or == c_simple */