1/*	$OpenBSD: function.c,v 1.55 2023/08/11 04:45:05 guenther Exp $	*/
2
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#include <sys/stat.h>
36#include <sys/wait.h>
37#include <sys/mount.h>
38
39#include <dirent.h>
40#include <err.h>
41#include <errno.h>
42#include <fcntl.h>
43#include <fnmatch.h>
44#include <fts.h>
45#include <grp.h>
46#include <libgen.h>
47#include <limits.h>
48#include <pwd.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <unistd.h>
53
54#include "find.h"
55#include "extern.h"
56
57#define	COMPARE(a, b) {							\
58	switch (plan->flags) {						\
59	case F_EQUAL:							\
60		return (a == b);					\
61	case F_LESSTHAN:						\
62		return (a < b);						\
63	case F_GREATER:							\
64		return (a > b);						\
65	default:							\
66		abort();						\
67	}								\
68}
69
70static PLAN *palloc(enum ntype, int (*)(PLAN *, FTSENT *));
71static long long find_parsenum(PLAN *plan, char *option, char *vp, char *endch);
72static void run_f_exec(PLAN *plan);
73static PLAN *palloc(enum ntype t, int (*f)(PLAN *, FTSENT *));
74
75int	f_amin(PLAN *, FTSENT *);
76int	f_atime(PLAN *, FTSENT *);
77int	f_cmin(PLAN *, FTSENT *);
78int	f_ctime(PLAN *, FTSENT *);
79int	f_always_true(PLAN *, FTSENT *);
80int	f_empty(PLAN *, FTSENT *);
81int	f_exec(PLAN *, FTSENT *);
82int	f_execdir(PLAN *, FTSENT *);
83int	f_flags(PLAN *, FTSENT *);
84int	f_fstype(PLAN *, FTSENT *);
85int	f_group(PLAN *, FTSENT *);
86int	f_inum(PLAN *, FTSENT *);
87int	f_empty(PLAN *, FTSENT *);
88int	f_links(PLAN *, FTSENT *);
89int	f_ls(PLAN *, FTSENT *);
90int	f_maxdepth(PLAN *, FTSENT *);
91int	f_mindepth(PLAN *, FTSENT *);
92int	f_mtime(PLAN *, FTSENT *);
93int	f_mmin(PLAN *, FTSENT *);
94int	f_name(PLAN *, FTSENT *);
95int	f_iname(PLAN *, FTSENT *);
96int	f_newer(PLAN *, FTSENT *);
97int	f_anewer(PLAN *, FTSENT *);
98int	f_cnewer(PLAN *, FTSENT *);
99int	f_nogroup(PLAN *, FTSENT *);
100int	f_nouser(PLAN *, FTSENT *);
101int	f_path(PLAN *, FTSENT *);
102int	f_perm(PLAN *, FTSENT *);
103int	f_print(PLAN *, FTSENT *);
104int	f_print0(PLAN *, FTSENT *);
105int	f_prune(PLAN *, FTSENT *);
106int	f_size(PLAN *, FTSENT *);
107int	f_type(PLAN *, FTSENT *);
108int	f_user(PLAN *, FTSENT *);
109int	f_expr(PLAN *, FTSENT *);
110int	f_not(PLAN *, FTSENT *);
111int	f_or(PLAN *, FTSENT *);
112
113extern int dotfd;
114extern time_t now;
115extern FTS *tree;
116
117/*
118 * find_parsenum --
119 *	Parse a string of the form [+-]# and return the value.
120 */
121static long long
122find_parsenum(PLAN *plan, char *option, char *vp, char *endch)
123{
124	long long value;
125	char *endchar, *str;	/* Pointer to character ending conversion. */
126
127	/* Determine comparison from leading + or -. */
128	str = vp;
129	switch (*str) {
130	case '+':
131		++str;
132		plan->flags = F_GREATER;
133		break;
134	case '-':
135		++str;
136		plan->flags = F_LESSTHAN;
137		break;
138	default:
139		plan->flags = F_EQUAL;
140		break;
141	}
142
143	/*
144	 * Convert the string with strtoll().  Note, if strtoll() returns
145	 * zero and endchar points to the beginning of the string we know
146	 * we have a syntax error.
147	 */
148	value = strtoll(str, &endchar, 10);
149	if (value == 0 && endchar == str)
150		errx(1, "%s: %s: illegal numeric value", option, vp);
151	if (endchar[0] && (endch == NULL || endchar[0] != *endch))
152		errx(1, "%s: %s: illegal trailing character", option, vp);
153	if (endch)
154		*endch = endchar[0];
155	return (value);
156}
157
158/*
159 * The value of n for the inode times (atime, ctime, and mtime) is a range,
160 * i.e. n matches from (n - 1) to n 24 hour periods.  This interacts with
161 * -n, such that "-mtime -1" would be less than 0 days, which isn't what the
162 * user wanted.  Correct so that -1 is "less than 1".
163 */
164#define	TIME_CORRECT(p, ttype)						\
165	if ((p)->type == ttype && (p)->flags == F_LESSTHAN)		\
166		++((p)->sec_data);
167
168/*
169 * -amin n functions --
170 *
171 *     True if the difference between the file access time and the
172 *     current time is n min periods.
173 */
174int
175f_amin(PLAN *plan, FTSENT *entry)
176{
177	extern time_t now;
178
179	COMPARE((now - entry->fts_statp->st_atime +
180	    60 - 1) / 60, plan->sec_data);
181}
182
183PLAN *
184c_amin(char *arg, char ***ignored, int unused)
185{
186	PLAN *new;
187
188	ftsoptions &= ~FTS_NOSTAT;
189
190	new = palloc(N_AMIN, f_amin);
191	new->sec_data = find_parsenum(new, "-amin", arg, NULL);
192	TIME_CORRECT(new, N_AMIN);
193	return (new);
194}
195
196/*
197 * -atime n functions --
198 *
199 *	True if the difference between the file access time and the
200 *	current time is n 24 hour periods.
201 */
202int
203f_atime(PLAN *plan, FTSENT *entry)
204{
205
206	COMPARE((now - entry->fts_statp->st_atime +
207	    SECSPERDAY - 1) / SECSPERDAY, plan->sec_data);
208}
209
210PLAN *
211c_atime(char *arg, char ***ignored, int unused)
212{
213	PLAN *new;
214
215	ftsoptions &= ~FTS_NOSTAT;
216
217	new = palloc(N_ATIME, f_atime);
218	new->sec_data = find_parsenum(new, "-atime", arg, NULL);
219	TIME_CORRECT(new, N_ATIME);
220	return (new);
221}
222
223/*
224 * -cmin n functions --
225 *
226 *     True if the difference between the last change of file
227 *     status information and the current time is n min periods.
228 */
229int
230f_cmin(PLAN *plan, FTSENT *entry)
231{
232	extern time_t now;
233
234	COMPARE((now - entry->fts_statp->st_ctime +
235	    60 - 1) / 60, plan->sec_data);
236}
237
238PLAN *
239c_cmin(char *arg, char ***ignored, int unused)
240{
241	PLAN *new;
242
243	ftsoptions &= ~FTS_NOSTAT;
244
245	new = palloc(N_CMIN, f_cmin);
246	new->sec_data = find_parsenum(new, "-cmin", arg, NULL);
247	TIME_CORRECT(new, N_CMIN);
248	return (new);
249}
250
251/*
252 * -ctime n functions --
253 *
254 *	True if the difference between the last change of file
255 *	status information and the current time is n 24 hour periods.
256 */
257int
258f_ctime(PLAN *plan, FTSENT *entry)
259{
260
261	COMPARE((now - entry->fts_statp->st_ctime +
262	    SECSPERDAY - 1) / SECSPERDAY, plan->sec_data);
263}
264
265PLAN *
266c_ctime(char *arg, char ***ignored, int unused)
267{
268	PLAN *new;
269
270	ftsoptions &= ~FTS_NOSTAT;
271
272	new = palloc(N_CTIME, f_ctime);
273	new->sec_data = find_parsenum(new, "-ctime", arg, NULL);
274	TIME_CORRECT(new, N_CTIME);
275	return (new);
276}
277
278/*
279 * -depth functions --
280 *
281 *	Always true, causes descent of the directory hierarchy to be done
282 *	so that all entries in a directory are acted on before the directory
283 *	itself.
284 */
285int
286f_always_true(PLAN *plan, FTSENT *entry)
287{
288	return (1);
289}
290
291PLAN *
292c_depth(char *ignore, char ***ignored, int unused)
293{
294	isdepth = 1;
295
296	return (palloc(N_DEPTH, f_always_true));
297}
298
299/*
300 * -delete functions
301 */
302int
303f_delete(PLAN *plan, FTSENT *entry)
304{
305
306	/* can't delete these */
307	if (strcmp(entry->fts_accpath, ".") == 0 ||
308	    strcmp(entry->fts_accpath, "..") == 0)
309		return 1;
310
311	/* sanity check */
312	if (isdepth == 0 ||                     /* depth off */
313	    (ftsoptions & FTS_NOSTAT))          /* not stat()ing */
314		errx(1, "-delete: insecure options got turned on");
315	if (!(ftsoptions & FTS_PHYSICAL) ||     /* physical off */
316	    (ftsoptions & FTS_LOGICAL))         /* or finally, logical on */
317		errx(1, "-delete: forbidden when symlinks are followed");
318
319	/* Potentially unsafe - do not accept relative paths whatsoever */
320	if (entry->fts_level > FTS_ROOTLEVEL &&
321	    strchr(entry->fts_accpath, '/') != NULL)
322		errx(1, "-delete: %s: relative path potentially not safe",
323		    entry->fts_accpath);
324#if 0
325	/* Turn off user immutable bits if running as root */
326	if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
327	    !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
328	    geteuid() == 0)
329		lchflags(entry->fts_accpath,
330		    entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
331#endif
332	/* rmdir directories, unlink everything else */
333	if (S_ISDIR(entry->fts_statp->st_mode)) {
334		if (rmdir(entry->fts_accpath) == -1 && errno != ENOTEMPTY)
335			warn("-delete: rmdir(%s)", entry->fts_path);
336	} else {
337		if (unlink(entry->fts_accpath) == -1)
338			warn("-delete: unlink(%s)", entry->fts_path);
339
340	}
341
342	return 1;
343}
344
345PLAN *
346c_delete(char *ignore, char ***ignored, int unused)
347{
348	ftsoptions &= ~FTS_NOSTAT;
349	isoutput = 1;
350	isdelete = 1;
351	isdepth = 1;
352
353	return (palloc(N_DELETE, f_delete));
354}
355
356/*
357 * -empty functions --
358 *
359 *	True if the file or directory is empty
360 */
361int
362f_empty(PLAN *plan, FTSENT *entry)
363{
364	if (S_ISREG(entry->fts_statp->st_mode) && entry->fts_statp->st_size == 0)
365		return (1);
366	if (S_ISDIR(entry->fts_statp->st_mode)) {
367		struct dirent *dp;
368		int empty;
369		DIR *dir;
370
371		empty = 1;
372		dir = opendir(entry->fts_accpath);
373		if (dir == NULL)
374			return (0);
375		for (dp = readdir(dir); dp; dp = readdir(dir))
376			if (dp->d_name[0] != '.' ||
377			    (dp->d_name[1] != '\0' &&
378			     (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) {
379				empty = 0;
380				break;
381			}
382		closedir(dir);
383		return (empty);
384	}
385	return (0);
386}
387
388PLAN *
389c_empty(char *ignore, char ***ignored, int unused)
390{
391	ftsoptions &= ~FTS_NOSTAT;
392
393	return (palloc(N_EMPTY, f_empty));
394}
395
396/*
397 * [-exec | -ok] utility [arg ... ] ; functions --
398 * [-exec | -ok] utility [arg ... ] {} + functions --
399 *
400 *	If the end of the primary expression is delimited by a
401 *	semicolon: true if the executed utility returns a zero value
402 *	as exit status.  If "{}" occurs anywhere, it gets replaced by
403 *	the current pathname.
404 *
405 *	If the end of the primary expression is delimited by a plus
406 *	sign: always true. Pathnames for which the primary is
407 *	evaluated shall be aggregated into sets. The utility will be
408 *	executed once per set, with "{}" replaced by the entire set of
409 *	pathnames (as if xargs). "{}" must appear last.
410 *
411 *	The current directory for the execution of utility is the same
412 *	as the current directory when the find utility was started.
413 *
414 *	The primary -ok is different in that it requests affirmation
415 *	of the user before executing the utility.
416 */
417int
418f_exec(PLAN *plan, FTSENT *entry)
419{
420	int cnt, l;
421	pid_t pid;
422	int status;
423
424	if (plan->flags & F_PLUSSET) {
425		/*
426		 * Confirm sufficient buffer space, then copy the path
427		 * to the buffer.
428		 */
429		l = strlen(entry->fts_path);
430		if (plan->ep_p + l < plan->ep_ebp) {
431			plan->ep_bxp[plan->ep_narg++] = plan->ep_p;
432			strlcpy(plan->ep_p, entry->fts_path, l + 1);
433			plan->ep_p += l + 1;
434
435			if (plan->ep_narg == plan->ep_maxargs)
436				run_f_exec(plan);
437		} else {
438			/*
439			 * Without sufficient space to copy in the next
440			 * argument, run the command to empty out the
441			 * buffer before re-attepting the copy.
442			 */
443			run_f_exec(plan);
444			if (plan->ep_p + l < plan->ep_ebp) {
445				plan->ep_bxp[plan->ep_narg++] = plan->ep_p;
446				strlcpy(plan->ep_p, entry->fts_path, l + 1);
447				plan->ep_p += l + 1;
448			} else
449				errx(1, "insufficient space for argument");
450		}
451		return (1);
452	} else {
453		for (cnt = 0; plan->e_argv[cnt]; ++cnt)
454			if (plan->e_len[cnt])
455				brace_subst(plan->e_orig[cnt],
456				    &plan->e_argv[cnt],
457				    entry->fts_path,
458				    plan->e_len[cnt]);
459		if (plan->flags & F_NEEDOK && !queryuser(plan->e_argv))
460			return (0);
461
462		/* don't mix output of command with find output */
463		fflush(stdout);
464		fflush(stderr);
465
466		switch (pid = vfork()) {
467		case -1:
468			err(1, "fork");
469			/* NOTREACHED */
470		case 0:
471			if (fchdir(dotfd)) {
472				warn("chdir");
473				_exit(1);
474			}
475			execvp(plan->e_argv[0], plan->e_argv);
476			warn("%s", plan->e_argv[0]);
477			_exit(1);
478		}
479		pid = waitpid(pid, &status, 0);
480		return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
481	}
482}
483
484static void
485run_f_exec(PLAN *plan)
486{
487	pid_t pid;
488	int rval, status;
489
490	/* Ensure arg list is null terminated. */
491	plan->ep_bxp[plan->ep_narg] = NULL;
492
493	/* Don't mix output of command with find output. */
494	fflush(stdout);
495	fflush(stderr);
496
497	switch (pid = vfork()) {
498	case -1:
499		err(1, "vfork");
500		/* NOTREACHED */
501	case 0:
502		if (fchdir(dotfd)) {
503			warn("chdir");
504			_exit(1);
505		}
506		execvp(plan->e_argv[0], plan->e_argv);
507		warn("%s", plan->e_argv[0]);
508		_exit(1);
509	}
510
511	/* Clear out the argument list. */
512	plan->ep_narg = 0;
513	plan->ep_bxp[plan->ep_narg] = NULL;
514	/* As well as the argument buffer. */
515	plan->ep_p = plan->ep_bbp;
516	*plan->ep_p = '\0';
517
518	pid = waitpid(pid, &status, 0);
519	if (WIFEXITED(status))
520		rval = WEXITSTATUS(status);
521	else
522		rval = -1;
523
524	/*
525	 * If we have a non-zero exit status, preserve it so find(1) can
526	 * later exit with it.
527	 */
528	if (rval)
529		plan->ep_rval = rval;
530}
531
532/*
533 * c_exec --
534 *	build three parallel arrays, one with pointers to the strings passed
535 *	on the command line, one with (possibly duplicated) pointers to the
536 *	argv array, and one with integer values that are lengths of the
537 *	strings, but also flags meaning that the string has to be massaged.
538 *
539 *	If -exec ... {} +, use only the first array, but make it large
540 *	enough to hold 5000 args (cf. src/usr.bin/xargs/xargs.c for a
541 *	discussion), and then allocate space for args.
542 */
543PLAN *
544c_exec(char *unused, char ***argvp, int isok)
545{
546	PLAN *new;			/* node returned */
547	int cnt, brace, lastbrace;
548	char **argv, **ap, *p;
549
550	/* make sure the current directory is readable */
551	if (dotfd == -1)
552		errx(1, "%s: cannot open \".\"", isok ? "-ok" : "-exec");
553
554	isoutput = 1;
555
556	new = palloc(N_EXEC, f_exec);
557	if (isok)
558		new->flags |= F_NEEDOK;
559
560	/*
561	 * Terminate if we encounter an arg exactly equal to ";", or an
562	 * arg exactly equal to "+" following an arg exactly equal to
563	 * "{}".
564	 */
565	for (ap = argv = *argvp, brace = 0;; ++ap) {
566		if (!*ap)
567			errx(1, "%s: no terminating \";\" or \"{} +\"",
568			    isok ? "-ok" : "-exec");
569		lastbrace = brace;
570		brace = 0;
571		if (strcmp(*ap, "{}") == 0)
572			brace = 1;
573		if (strcmp(*ap, ";") == 0)
574			break;
575		if (strcmp(*ap, "+") == 0 && lastbrace) {
576			new->flags |= F_PLUSSET;
577			break;
578		}
579	}
580
581
582	/*
583	 * POSIX says -ok ... {} + "need not be supported," and it does
584	 * not make much sense anyway.
585	 */
586	if (new->flags & F_NEEDOK && new->flags & F_PLUSSET)
587		errx(1, "-ok: terminating \"+\" not permitted.");
588
589	if (new->flags & F_PLUSSET) {
590		long arg_max;
591		extern char **environ;
592		char **ep;
593		u_int c, bufsize;
594
595		cnt = ap - *argvp - 1;			/* units are words */
596		new->ep_maxargs = 5000;
597		new->e_argv = ereallocarray(NULL,
598		    (size_t)(cnt + new->ep_maxargs), sizeof(char **));
599
600		/* We start stuffing arguments after the user's last one. */
601		new->ep_bxp = &new->e_argv[cnt];
602		new->ep_narg = 0;
603
604		/*
605		 * Compute the maximum space we can use for arguments
606		 * passed to execve(2).
607		 */
608		arg_max = sysconf(_SC_ARG_MAX);
609		if (arg_max == -1)
610			err(1, "-exec: sysconf(_SC_ARG_MAX) failed");
611		for (ep = environ; *ep != NULL; ep++) {
612			/* 1 byte for each '\0' */
613			arg_max -= strlen(*ep) + 1 + sizeof(*ep);
614		}
615
616		/*
617		 * Count up the space of the user's arguments, and
618		 * subtract that from what we allocate.
619		 */
620		for (argv = *argvp, c = 0, cnt = 0;
621		     argv < ap;
622		     ++argv, ++cnt) {
623			c += strlen(*argv) + 1;
624			new->e_argv[cnt] = *argv;
625		}
626		if (arg_max < 4 * 1024 + c)
627			errx(1, "-exec: no space left to run child command");
628		bufsize = arg_max - 4 * 1024 - c;
629
630		/*
631		 * Allocate, and then initialize current, base, and
632		 * end pointers.
633		 */
634		new->ep_p = new->ep_bbp = malloc(bufsize + 1);
635		new->ep_ebp = new->ep_bbp + bufsize - 1;
636		new->ep_rval = 0;
637	} else { /* !F_PLUSSET */
638		cnt = ap - *argvp + 1;
639		new->e_argv = ereallocarray(NULL, cnt, sizeof(char *));
640		new->e_orig = ereallocarray(NULL, cnt, sizeof(char *));
641		new->e_len = ereallocarray(NULL, cnt, sizeof(int));
642
643		for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
644			new->e_orig[cnt] = *argv;
645			for (p = *argv; *p; ++p)
646				if (p[0] == '{' && p[1] == '}') {
647					new->e_argv[cnt] =
648						emalloc((u_int)PATH_MAX);
649					new->e_len[cnt] = PATH_MAX;
650					break;
651				}
652			if (!*p) {
653				new->e_argv[cnt] = *argv;
654				new->e_len[cnt] = 0;
655			}
656		}
657		new->e_orig[cnt] = NULL;
658	}
659
660	new->e_argv[cnt] = NULL;
661	*argvp = argv + 1;
662	return (new);
663}
664
665/*
666 * -execdir utility [arg ... ] ; functions --
667 *
668 *	True if the executed utility returns a zero value as exit status.
669 *	The end of the primary expression is delimited by a semicolon.  If
670 *	"{}" occurs anywhere, it gets replaced by the unqualified pathname.
671 *	The current directory for the execution of utility is the same as
672 *	the directory where the file lives.
673 */
674int
675f_execdir(PLAN *plan, FTSENT *entry)
676{
677	int cnt;
678	pid_t pid;
679	int status, fd;
680	char base[PATH_MAX];
681
682	/* fts(3) does not chdir for the root level so we do it ourselves. */
683	if (entry->fts_level == FTS_ROOTLEVEL) {
684		if ((fd = open(".", O_RDONLY)) == -1) {
685			warn("cannot open \".\"");
686			return (0);
687		}
688		if (chdir(entry->fts_accpath)) {
689			(void) close(fd);
690			return (0);
691		}
692	}
693
694	/* Substitute basename(path) for {} since cwd is it's parent dir */
695	(void)strncpy(base, basename(entry->fts_path), sizeof(base) - 1);
696	base[sizeof(base) - 1] = '\0';
697	for (cnt = 0; plan->e_argv[cnt]; ++cnt)
698		if (plan->e_len[cnt])
699			brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
700			    base, plan->e_len[cnt]);
701
702	/* don't mix output of command with find output */
703	fflush(stdout);
704	fflush(stderr);
705
706	switch (pid = vfork()) {
707	case -1:
708		err(1, "fork");
709		/* NOTREACHED */
710	case 0:
711		execvp(plan->e_argv[0], plan->e_argv);
712		warn("%s", plan->e_argv[0]);
713		_exit(1);
714	}
715
716	/* Undo the above... */
717	if (entry->fts_level == FTS_ROOTLEVEL) {
718		if (fchdir(fd) == -1) {
719			warn("unable to chdir back to starting directory");
720			(void) close(fd);
721			return (0);
722		}
723		(void) close(fd);
724	}
725
726	pid = waitpid(pid, &status, 0);
727	return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
728}
729
730/*
731 * c_execdir --
732 *	build three parallel arrays, one with pointers to the strings passed
733 *	on the command line, one with (possibly duplicated) pointers to the
734 *	argv array, and one with integer values that are lengths of the
735 *	strings, but also flags meaning that the string has to be massaged.
736 */
737PLAN *
738c_execdir(char *ignored, char ***argvp, int unused)
739{
740	PLAN *new;			/* node returned */
741	int cnt;
742	char **argv, **ap, *p;
743
744	ftsoptions &= ~FTS_NOSTAT;
745	isoutput = 1;
746
747	new = palloc(N_EXECDIR, f_execdir);
748
749	for (ap = argv = *argvp;; ++ap) {
750		if (!*ap)
751			errx(1,
752			    "-execdir: no terminating \";\"");
753		if (**ap == ';')
754			break;
755	}
756
757	cnt = ap - *argvp + 1;
758	new->e_argv = ereallocarray(NULL, cnt, sizeof(char *));
759	new->e_orig = ereallocarray(NULL, cnt, sizeof(char *));
760	new->e_len = ereallocarray(NULL, cnt, sizeof(int));
761
762	for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
763		new->e_orig[cnt] = *argv;
764		for (p = *argv; *p; ++p)
765			if (p[0] == '{' && p[1] == '}') {
766				new->e_argv[cnt] = emalloc((u_int)PATH_MAX);
767				new->e_len[cnt] = PATH_MAX;
768				break;
769			}
770		if (!*p) {
771			new->e_argv[cnt] = *argv;
772			new->e_len[cnt] = 0;
773		}
774	}
775	new->e_argv[cnt] = new->e_orig[cnt] = NULL;
776
777	*argvp = argv + 1;
778	return (new);
779}
780
781/*
782 * -flags functions --
783 *
784 *	The flags argument is used to represent file flags bits.
785 */
786int
787f_flags(PLAN *plan, FTSENT *entry)
788{
789	u_int flags;
790
791	flags = entry->fts_statp->st_flags &
792	    (UF_NODUMP | UF_IMMUTABLE | UF_APPEND | UF_OPAQUE |
793	     SF_ARCHIVED | SF_IMMUTABLE | SF_APPEND);
794	if (plan->flags == F_ATLEAST)
795		/* note that plan->fl_flags always is a subset of
796		   plan->fl_mask */
797		return ((flags & plan->fl_mask) == plan->fl_flags);
798	else
799		return (flags == plan->fl_flags);
800	/* NOTREACHED */
801}
802
803PLAN *
804c_flags(char *flags_str, char ***ignored, int unused)
805{
806	PLAN *new;
807	u_int32_t flags, notflags;
808
809	ftsoptions &= ~FTS_NOSTAT;
810
811	new = palloc(N_FLAGS, f_flags);
812
813	if (*flags_str == '-') {
814		new->flags = F_ATLEAST;
815		++flags_str;
816	}
817
818	if (strtofflags(&flags_str, &flags, &notflags) == 1)
819		errx(1, "-flags: %s: illegal flags string", flags_str);
820
821	new->fl_flags = flags;
822	new->fl_mask = flags | notflags;
823	return (new);
824}
825
826/*
827 * -follow functions --
828 *
829 *	Always true, causes symbolic links to be followed on a global
830 *	basis.
831 */
832PLAN *
833c_follow(char *ignore, char ***ignored, int unused)
834{
835	ftsoptions &= ~FTS_PHYSICAL;
836	ftsoptions |= FTS_LOGICAL;
837
838	return (palloc(N_FOLLOW, f_always_true));
839}
840
841/*
842 * -fstype functions --
843 *
844 *	True if the file is of a certain type.
845 */
846int
847f_fstype(PLAN *plan, FTSENT *entry)
848{
849	static dev_t curdev;	/* need a guaranteed illegal dev value */
850	static int first = 1;
851	struct statfs sb;
852	static short val;
853	static char fstype[MFSNAMELEN];
854	char *p, save[2];
855
856	/* Only check when we cross mount point. */
857	if (first || curdev != entry->fts_statp->st_dev) {
858		curdev = entry->fts_statp->st_dev;
859
860		/*
861		 * Statfs follows symlinks; find wants the link's file system,
862		 * not where it points.
863		 */
864		if (entry->fts_info == FTS_SL ||
865		    entry->fts_info == FTS_SLNONE) {
866			if ((p = strrchr(entry->fts_accpath, '/')))
867				++p;
868			else
869				p = entry->fts_accpath;
870			save[0] = p[0];
871			p[0] = '.';
872			save[1] = p[1];
873			p[1] = '\0';
874
875		} else
876			p = NULL;
877
878		if (statfs(entry->fts_accpath, &sb))
879			err(1, "%s", entry->fts_accpath);
880
881		if (p) {
882			p[0] = save[0];
883			p[1] = save[1];
884		}
885
886		first = 0;
887
888		/*
889		 * Further tests may need both of these values, so
890		 * always copy both of them.
891		 */
892		val = sb.f_flags;
893		strncpy(fstype, sb.f_fstypename, MFSNAMELEN);
894	}
895	switch (plan->flags) {
896	case F_MTFLAG:
897		return (val & plan->mt_data);
898	case F_MTTYPE:
899		return (strncmp(fstype, plan->c_data, MFSNAMELEN) == 0);
900	default:
901		abort();
902	}
903}
904
905PLAN *
906c_fstype(char *arg, char ***ignored, int unused)
907{
908	PLAN *new;
909
910	ftsoptions &= ~FTS_NOSTAT;
911
912	new = palloc(N_FSTYPE, f_fstype);
913	switch (*arg) {
914	case 'l':
915		if (!strcmp(arg, "local")) {
916			new->flags = F_MTFLAG;
917			new->mt_data = MNT_LOCAL;
918			return (new);
919		}
920		break;
921	case 'r':
922		if (!strcmp(arg, "rdonly")) {
923			new->flags = F_MTFLAG;
924			new->mt_data = MNT_RDONLY;
925			return (new);
926		}
927		break;
928	}
929
930	new->flags = F_MTTYPE;
931	new->c_data = arg;
932	return (new);
933}
934
935/*
936 * -group gname functions --
937 *
938 *	True if the file belongs to the group gname.  If gname is numeric and
939 *	an equivalent of the getgrnam() function does not return a valid group
940 *	name, gname is taken as a group ID.
941 */
942int
943f_group(PLAN *plan, FTSENT *entry)
944{
945	return (entry->fts_statp->st_gid == plan->g_data);
946}
947
948PLAN *
949c_group(char *gname, char ***ignored, int unused)
950{
951	PLAN *new;
952	gid_t gid;
953
954	ftsoptions &= ~FTS_NOSTAT;
955
956	if (gid_from_group(gname, &gid) == -1) {
957		const char *errstr;
958
959		gid = strtonum(gname, 0, GID_MAX, &errstr);
960		if (errstr)
961			errx(1, "-group: %s: no such group", gname);
962	}
963
964	new = palloc(N_GROUP, f_group);
965	new->g_data = gid;
966	return (new);
967}
968
969/*
970 * -inum n functions --
971 *
972 *	True if the file has inode # n.
973 */
974int
975f_inum(PLAN *plan, FTSENT *entry)
976{
977	COMPARE(entry->fts_statp->st_ino, plan->i_data);
978}
979
980PLAN *
981c_inum(char *arg, char ***ignored, int unused)
982{
983	long long inum;
984	PLAN *new;
985
986	ftsoptions &= ~FTS_NOSTAT;
987
988	new = palloc(N_INUM, f_inum);
989	inum = find_parsenum(new, "-inum", arg, NULL);
990	if (inum != (ino_t)inum)
991		errx(1, "-inum: %s: number too great", arg);
992	new->i_data = inum;
993	return (new);
994}
995
996/*
997 * -links n functions --
998 *
999 *	True if the file has n links.
1000 */
1001int
1002f_links(PLAN *plan, FTSENT *entry)
1003{
1004	COMPARE(entry->fts_statp->st_nlink, plan->l_data);
1005}
1006
1007PLAN *
1008c_links(char *arg, char ***ignored, int unused)
1009{
1010	PLAN *new;
1011	long long nlink;
1012
1013	ftsoptions &= ~FTS_NOSTAT;
1014
1015	new = palloc(N_LINKS, f_links);
1016	nlink = find_parsenum(new, "-links", arg, NULL);
1017	if (nlink != (nlink_t)nlink)
1018		errx(1, "-links: %s: number too great", arg);
1019	new->l_data = nlink;
1020	return (new);
1021}
1022
1023/*
1024 * -ls functions --
1025 *
1026 *	Always true - prints the current entry to stdout in "ls" format.
1027 */
1028int
1029f_ls(PLAN *plan, FTSENT *entry)
1030{
1031	printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp);
1032	return (1);
1033}
1034
1035PLAN *
1036c_ls(char *ignore, char ***ignored, int unused)
1037{
1038	ftsoptions &= ~FTS_NOSTAT;
1039	isoutput = 1;
1040
1041	return (palloc(N_LS, f_ls));
1042}
1043
1044/*
1045 * - maxdepth n functions --
1046 *
1047 *	True if the current search depth is less than or equal to the
1048 *	maximum depth specified
1049 */
1050int
1051f_maxdepth(PLAN *plan, FTSENT *entry)
1052{
1053
1054	if (entry->fts_level >= plan->max_data)
1055		fts_set(tree, entry, FTS_SKIP);
1056	return (entry->fts_level <= plan->max_data);
1057}
1058
1059PLAN *
1060c_maxdepth(char *arg, char ***ignored, int unused)
1061{
1062	PLAN *new;
1063	const char *errstr = NULL;
1064
1065	new = palloc(N_MAXDEPTH, f_maxdepth);
1066	new->max_data = strtonum(arg, 0, FTS_MAXLEVEL, &errstr);
1067	if (errstr)
1068		errx(1, "%s: maxdepth value %s", arg, errstr);
1069	return (new);
1070}
1071
1072/*
1073 * - mindepth n functions --
1074 *
1075 *	True if the current search depth is greater than or equal to the
1076 *	minimum depth specified
1077 */
1078int
1079f_mindepth(PLAN *plan, FTSENT *entry)
1080{
1081
1082	return (entry->fts_level >= plan->min_data);
1083}
1084
1085PLAN *
1086c_mindepth(char *arg, char ***ignored, int unused)
1087{
1088	PLAN *new;
1089	const char *errstr = NULL;
1090
1091	new = palloc(N_MINDEPTH, f_mindepth);
1092	new->min_data = strtonum(arg, 0, INT_MAX, &errstr);
1093	if (errstr)
1094		errx(1, "-mindepth: %s: value %s", arg, errstr);
1095	return (new);
1096}
1097
1098/*
1099 * -mtime n functions --
1100 *
1101 *	True if the difference between the file modification time and the
1102 *	current time is n 24 hour periods.
1103 */
1104int
1105f_mtime(PLAN *plan, FTSENT *entry)
1106{
1107
1108	COMPARE((now - entry->fts_statp->st_mtime + SECSPERDAY - 1) /
1109	    SECSPERDAY, plan->sec_data);
1110}
1111
1112PLAN *
1113c_mtime(char *arg, char ***ignored, int unused)
1114{
1115	PLAN *new;
1116
1117	ftsoptions &= ~FTS_NOSTAT;
1118
1119	new = palloc(N_MTIME, f_mtime);
1120	new->sec_data = find_parsenum(new, "-mtime", arg, NULL);
1121	TIME_CORRECT(new, N_MTIME);
1122	return (new);
1123}
1124
1125/*
1126 * -mmin n functions --
1127 *
1128 *     True if the difference between the file modification time and the
1129 *     current time is n min periods.
1130 */
1131int
1132f_mmin(PLAN *plan, FTSENT *entry)
1133{
1134	extern time_t now;
1135
1136	COMPARE((now - entry->fts_statp->st_mtime + 60 - 1) /
1137	    60, plan->sec_data);
1138}
1139
1140PLAN *
1141c_mmin(char *arg, char ***ignored, int unused)
1142{
1143	PLAN *new;
1144
1145	ftsoptions &= ~FTS_NOSTAT;
1146
1147	new = palloc(N_MMIN, f_mmin);
1148	new->sec_data = find_parsenum(new, "-mmin", arg, NULL);
1149	TIME_CORRECT(new, N_MMIN);
1150	return (new);
1151}
1152
1153/*
1154 * -name functions --
1155 *
1156 *	True if the basename of the filename being examined
1157 *	matches pattern using Pattern Matching Notation S3.14
1158 */
1159int
1160f_name(PLAN *plan, FTSENT *entry)
1161{
1162	return (!fnmatch(plan->c_data, entry->fts_name, 0));
1163}
1164
1165PLAN *
1166c_name(char *pattern, char ***ignored, int unused)
1167{
1168	PLAN *new;
1169
1170	new = palloc(N_NAME, f_name);
1171	new->c_data = pattern;
1172	return (new);
1173}
1174
1175/*
1176 * -iname functions --
1177 *
1178 *	Similar to -name, but does case insensitive matching
1179 *
1180 */
1181int
1182f_iname(PLAN *plan, FTSENT *entry)
1183{
1184	return (!fnmatch(plan->c_data, entry->fts_name, FNM_CASEFOLD));
1185}
1186
1187PLAN *
1188c_iname(char *pattern, char ***ignored, int unused)
1189{
1190	PLAN *new;
1191
1192	new = palloc(N_INAME, f_iname);
1193	new->c_data = pattern;
1194	return (new);
1195}
1196
1197/*
1198 * -newer file functions --
1199 *
1200 *	True if the current file has been modified more recently
1201 *	then the modification time of the file named by the pathname
1202 *	file.
1203 */
1204int
1205f_newer(PLAN *plan, FTSENT *entry)
1206{
1207
1208	return (entry->fts_statp->st_mtim.tv_sec > plan->t_data.tv_sec ||
1209	    (entry->fts_statp->st_mtim.tv_sec == plan->t_data.tv_sec &&
1210	    entry->fts_statp->st_mtim.tv_nsec > plan->t_data.tv_nsec));
1211}
1212
1213PLAN *
1214c_newer(char *filename, char ***ignored, int unused)
1215{
1216	PLAN *new;
1217	struct stat sb;
1218
1219	ftsoptions &= ~FTS_NOSTAT;
1220
1221	if (stat(filename, &sb))
1222		err(1, "%s", filename);
1223	new = palloc(N_NEWER, f_newer);
1224	memcpy(&new->t_data, &sb.st_mtim, sizeof(struct timespec));
1225	return (new);
1226}
1227
1228/*
1229 * -anewer file functions --
1230 *
1231 *	True if the current file has been accessed more recently
1232 *	then the access time of the file named by the pathname
1233 *	file.
1234 */
1235int
1236f_anewer(PLAN *plan, FTSENT *entry)
1237{
1238
1239	return (entry->fts_statp->st_atim.tv_sec > plan->t_data.tv_sec ||
1240	    (entry->fts_statp->st_atim.tv_sec == plan->t_data.tv_sec &&
1241	    entry->fts_statp->st_atim.tv_nsec > plan->t_data.tv_nsec));
1242}
1243
1244PLAN *
1245c_anewer(char *filename, char ***ignored, int unused)
1246{
1247	PLAN *new;
1248	struct stat sb;
1249
1250	ftsoptions &= ~FTS_NOSTAT;
1251
1252	if (stat(filename, &sb))
1253		err(1, "%s", filename);
1254	new = palloc(N_NEWER, f_anewer);
1255	memcpy(&new->t_data, &sb.st_atim, sizeof(struct timespec));
1256	return (new);
1257}
1258
1259/*
1260 * -cnewer file functions --
1261 *
1262 *	True if the current file has been changed more recently
1263 *	then the inode change time of the file named by the pathname
1264 *	file.
1265 */
1266int
1267f_cnewer(PLAN *plan, FTSENT *entry)
1268{
1269
1270	return (entry->fts_statp->st_ctim.tv_sec > plan->t_data.tv_sec ||
1271	    (entry->fts_statp->st_ctim.tv_sec == plan->t_data.tv_sec &&
1272	    entry->fts_statp->st_ctim.tv_nsec > plan->t_data.tv_nsec));
1273}
1274
1275PLAN *
1276c_cnewer(char *filename, char ***ignored, int unused)
1277{
1278	PLAN *new;
1279	struct stat sb;
1280
1281	ftsoptions &= ~FTS_NOSTAT;
1282
1283	if (stat(filename, &sb))
1284		err(1, "%s", filename);
1285	new = palloc(N_NEWER, f_cnewer);
1286	memcpy(&new->t_data, &sb.st_ctim, sizeof(struct timespec));
1287	return (new);
1288}
1289
1290/*
1291 * -nogroup functions --
1292 *
1293 *	True if file belongs to a user ID for which the equivalent
1294 *	of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
1295 */
1296int
1297f_nogroup(PLAN *plan, FTSENT *entry)
1298{
1299	return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1);
1300}
1301
1302PLAN *
1303c_nogroup(char *ignore, char ***ignored, int unused)
1304{
1305	ftsoptions &= ~FTS_NOSTAT;
1306
1307	return (palloc(N_NOGROUP, f_nogroup));
1308}
1309
1310/*
1311 * -nouser functions --
1312 *
1313 *	True if file belongs to a user ID for which the equivalent
1314 *	of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
1315 */
1316int
1317f_nouser(PLAN *plan, FTSENT *entry)
1318{
1319	return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1);
1320}
1321
1322PLAN *
1323c_nouser(char *ignore, char ***ignored, int unused)
1324{
1325	ftsoptions &= ~FTS_NOSTAT;
1326
1327	return (palloc(N_NOUSER, f_nouser));
1328}
1329
1330/*
1331 * -path functions --
1332 *
1333 *	True if the path of the filename being examined
1334 *	matches pattern using Pattern Matching Notation S3.14
1335 */
1336int
1337f_path(PLAN *plan, FTSENT *entry)
1338{
1339	return (!fnmatch(plan->c_data, entry->fts_path, 0));
1340}
1341
1342PLAN *
1343c_path(char *pattern, char ***ignored, int unused)
1344{
1345	PLAN *new;
1346
1347	new = palloc(N_NAME, f_path);
1348	new->c_data = pattern;
1349	return (new);
1350}
1351
1352/*
1353 * -perm functions --
1354 *
1355 *	The mode argument is used to represent file mode bits.  If it starts
1356 *	with a leading digit, it's treated as an octal mode, otherwise as a
1357 *	symbolic mode.
1358 */
1359int
1360f_perm(PLAN *plan, FTSENT *entry)
1361{
1362	mode_t mode;
1363
1364	mode = entry->fts_statp->st_mode &
1365	    (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
1366	if (plan->flags == F_ATLEAST)
1367		return ((plan->m_data | mode) == mode);
1368	else
1369		return (mode == plan->m_data);
1370	/* NOTREACHED */
1371}
1372
1373PLAN *
1374c_perm(char *perm, char ***ignored, int unused)
1375{
1376	PLAN *new;
1377	void *set;
1378
1379	ftsoptions &= ~FTS_NOSTAT;
1380
1381	new = palloc(N_PERM, f_perm);
1382
1383	if (*perm == '-') {
1384		new->flags = F_ATLEAST;
1385		++perm;
1386	}
1387
1388	if ((set = setmode(perm)) == NULL)
1389		errx(1, "-perm: %s: illegal mode string", perm);
1390
1391	new->m_data = getmode(set, 0);
1392	free(set);
1393	return (new);
1394}
1395
1396/*
1397 * -print functions --
1398 *
1399 *	Always true, causes the current pathname to be written to
1400 *	standard output.
1401 */
1402int
1403f_print(PLAN *plan, FTSENT *entry)
1404{
1405	(void)printf("%s\n", entry->fts_path);
1406	return(1);
1407}
1408
1409int
1410f_print0(PLAN *plan, FTSENT *entry)
1411{
1412	(void)fputs(entry->fts_path, stdout);
1413	(void)fputc('\0', stdout);
1414	return(1);
1415}
1416
1417PLAN *
1418c_print(char *ignore, char ***ignored, int unused)
1419{
1420	isoutput = 1;
1421
1422	return(palloc(N_PRINT, f_print));
1423}
1424
1425PLAN *
1426c_print0(char *ignore, char ***ignored, int unused)
1427{
1428	isoutput = 1;
1429
1430	return(palloc(N_PRINT0, f_print0));
1431}
1432
1433/*
1434 * -prune functions --
1435 *
1436 *	Prune a portion of the hierarchy.
1437 */
1438int
1439f_prune(PLAN *plan, FTSENT *entry)
1440{
1441
1442	if (fts_set(tree, entry, FTS_SKIP))
1443		err(1, "%s", entry->fts_path);
1444	return (1);
1445}
1446
1447PLAN *
1448c_prune(char *ignore, char ***ignored, int unused)
1449{
1450	return (palloc(N_PRUNE, f_prune));
1451}
1452
1453/*
1454 * -size n[c] functions --
1455 *
1456 *	True if the file size in bytes, divided by an implementation defined
1457 *	value and rounded up to the next integer, is n.  If n is followed by
1458 *	a c, the size is in bytes.
1459 */
1460#define	FIND_SIZE	512
1461static int divsize = 1;
1462
1463int
1464f_size(PLAN *plan, FTSENT *entry)
1465{
1466	off_t size;
1467
1468	size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
1469	    FIND_SIZE : entry->fts_statp->st_size;
1470	COMPARE(size, plan->o_data);
1471}
1472
1473PLAN *
1474c_size(char *arg, char ***ignored, int unused)
1475{
1476	PLAN *new;
1477	char endch;
1478
1479	ftsoptions &= ~FTS_NOSTAT;
1480
1481	new = palloc(N_SIZE, f_size);
1482	endch = 'c';
1483	new->o_data = find_parsenum(new, "-size", arg, &endch);
1484	if (endch == 'c')
1485		divsize = 0;
1486	return (new);
1487}
1488
1489/*
1490 * -type c functions --
1491 *
1492 *	True if the type of the file is c, where c is b, c, d, p, or f for
1493 *	block special file, character special file, directory, FIFO, or
1494 *	regular file, respectively.
1495 */
1496int
1497f_type(PLAN *plan, FTSENT *entry)
1498{
1499	return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data);
1500}
1501
1502PLAN *
1503c_type(char *typestring, char ***ignored, int unused)
1504{
1505	PLAN *new;
1506	mode_t  mask;
1507
1508	ftsoptions &= ~FTS_NOSTAT;
1509
1510	switch (typestring[0]) {
1511	case 'b':
1512		mask = S_IFBLK;
1513		break;
1514	case 'c':
1515		mask = S_IFCHR;
1516		break;
1517	case 'd':
1518		mask = S_IFDIR;
1519		break;
1520	case 'f':
1521		mask = S_IFREG;
1522		break;
1523	case 'l':
1524		mask = S_IFLNK;
1525		break;
1526	case 'p':
1527		mask = S_IFIFO;
1528		break;
1529	case 's':
1530		mask = S_IFSOCK;
1531		break;
1532	default:
1533		errx(1, "-type: %s: unknown type", typestring);
1534	}
1535
1536	new = palloc(N_TYPE, f_type);
1537	new->m_data = mask;
1538	return (new);
1539}
1540
1541/*
1542 * -user uname functions --
1543 *
1544 *	True if the file belongs to the user uname.  If uname is numeric and
1545 *	an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1546 *	return a valid user name, uname is taken as a user ID.
1547 */
1548int
1549f_user(PLAN *plan, FTSENT *entry)
1550{
1551	return (entry->fts_statp->st_uid == plan->u_data);
1552}
1553
1554PLAN *
1555c_user(char *username, char ***ignored, int unused)
1556{
1557	PLAN *new;
1558	uid_t uid;
1559
1560	ftsoptions &= ~FTS_NOSTAT;
1561
1562	if (uid_from_user(username, &uid) == -1) {
1563		const char *errstr;
1564
1565		uid = strtonum(username, 0, UID_MAX, &errstr);
1566		if (errstr)
1567			errx(1, "-user: %s: no such user", username);
1568	}
1569
1570	new = palloc(N_USER, f_user);
1571	new->u_data = uid;
1572	return (new);
1573}
1574
1575/*
1576 * -xdev functions --
1577 *
1578 *	Always true, causes find not to descend past directories that have a
1579 *	different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1580 */
1581PLAN *
1582c_xdev(char *ignore, char ***ignored, int unused)
1583{
1584	ftsoptions |= FTS_XDEV;
1585
1586	return (palloc(N_XDEV, f_always_true));
1587}
1588
1589/*
1590 * ( expression ) functions --
1591 *
1592 *	True if expression is true.
1593 */
1594int
1595f_expr(PLAN *plan, FTSENT *entry)
1596{
1597	PLAN *p;
1598	int state;
1599
1600	for (p = plan->p_data[0];
1601	    p && (state = (p->eval)(p, entry)); p = p->next);
1602	return (state);
1603}
1604
1605/*
1606 * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers.  They are
1607 * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1608 * to a N_EXPR node containing the expression and the ')' node is discarded.
1609 */
1610PLAN *
1611c_openparen(char *ignore, char ***ignored, int unused)
1612{
1613	return (palloc(N_OPENPAREN, (int (*)(PLAN *, FTSENT *))-1));
1614}
1615
1616PLAN *
1617c_closeparen(char *ignore, char ***ignored, int unused)
1618{
1619	return (palloc(N_CLOSEPAREN, (int (*)(PLAN *, FTSENT *))-1));
1620}
1621
1622/*
1623 * ! expression functions --
1624 *
1625 *	Negation of a primary; the unary NOT operator.
1626 */
1627int
1628f_not(PLAN *plan, FTSENT *entry)
1629{
1630	PLAN *p;
1631	int state;
1632
1633	for (p = plan->p_data[0];
1634	    p && (state = (p->eval)(p, entry)); p = p->next);
1635	return (!state);
1636}
1637
1638PLAN *
1639c_not(char *ignore, char ***ignored, int unused)
1640{
1641	return (palloc(N_NOT, f_not));
1642}
1643
1644/*
1645 * expression -o expression functions --
1646 *
1647 *	Alternation of primaries; the OR operator.  The second expression is
1648 * not evaluated if the first expression is true.
1649 */
1650int
1651f_or(PLAN *plan, FTSENT *entry)
1652{
1653	PLAN *p;
1654	int state;
1655
1656	for (p = plan->p_data[0];
1657	    p && (state = (p->eval)(p, entry)); p = p->next);
1658
1659	if (state)
1660		return (1);
1661
1662	for (p = plan->p_data[1];
1663	    p && (state = (p->eval)(p, entry)); p = p->next);
1664	return (state);
1665}
1666
1667PLAN *
1668c_or(char *ignore, char ***ignored, int unused)
1669{
1670	return (palloc(N_OR, f_or));
1671}
1672
1673
1674/*
1675 * plan_cleanup --
1676 *	Check and see if the specified plan has any residual state,
1677 *	and if so, clean it up as appropriate.
1678 *
1679 *	At the moment, only N_EXEC has state. Two kinds: 1)
1680 *	lists of files to feed to subprocesses 2) State on exit
1681 *	status of past subprocesses.
1682 */
1683int
1684plan_cleanup(PLAN *plan, void *arg)
1685{
1686	if (plan->type==N_EXEC && plan->ep_narg)
1687		run_f_exec(plan);
1688
1689	return plan->ep_rval;		/* Passed save exit-status up chain */
1690}
1691
1692
1693static PLAN *
1694palloc(enum ntype t, int (*f)(PLAN *, FTSENT *))
1695{
1696	PLAN *new;
1697
1698	if ((new = calloc(1, sizeof(PLAN)))) {
1699		new->type = t;
1700		new->eval = f;
1701		return (new);
1702	}
1703	err(1, NULL);
1704	/* NOTREACHED */
1705}
1706