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