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