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