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