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