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