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