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