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