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