xref: /openbsd/usr.bin/find/function.c (revision 78b63d65)
1 /*	$OpenBSD: function.c,v 1.21 2001/11/19 19:02:13 mpech 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.21 2001/11/19 19:02:13 mpech 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 __P((enum ntype, int (*) __P((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_newer);
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_newer);
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 	char *group_from_gid();
1101 
1102 	return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1);
1103 }
1104 
1105 PLAN *
1106 c_nogroup()
1107 {
1108 	ftsoptions &= ~FTS_NOSTAT;
1109 
1110 	return (palloc(N_NOGROUP, f_nogroup));
1111 }
1112 
1113 /*
1114  * -nouser functions --
1115  *
1116  *	True if file belongs to a user ID for which the equivalent
1117  *	of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
1118  */
1119 int
1120 f_nouser(plan, entry)
1121 	PLAN *plan;
1122 	FTSENT *entry;
1123 {
1124 	char *user_from_uid();
1125 
1126 	return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1);
1127 }
1128 
1129 PLAN *
1130 c_nouser()
1131 {
1132 	ftsoptions &= ~FTS_NOSTAT;
1133 
1134 	return (palloc(N_NOUSER, f_nouser));
1135 }
1136 
1137 /*
1138  * -path functions --
1139  *
1140  *	True if the path of the filename being examined
1141  *	matches pattern using Pattern Matching Notation S3.14
1142  */
1143 int
1144 f_path(plan, entry)
1145 	PLAN *plan;
1146 	FTSENT *entry;
1147 {
1148 	return (!fnmatch(plan->c_data, entry->fts_path, 0));
1149 }
1150 
1151 PLAN *
1152 c_path(pattern)
1153 	char *pattern;
1154 {
1155 	PLAN *new;
1156 
1157 	new = palloc(N_NAME, f_path);
1158 	new->c_data = pattern;
1159 	return (new);
1160 }
1161 
1162 /*
1163  * -perm functions --
1164  *
1165  *	The mode argument is used to represent file mode bits.  If it starts
1166  *	with a leading digit, it's treated as an octal mode, otherwise as a
1167  *	symbolic mode.
1168  */
1169 int
1170 f_perm(plan, entry)
1171 	PLAN *plan;
1172 	FTSENT *entry;
1173 {
1174 	mode_t mode;
1175 
1176 	mode = entry->fts_statp->st_mode &
1177 	    (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
1178 	if (plan->flags == F_ATLEAST)
1179 		return ((plan->m_data | mode) == mode);
1180 	else
1181 		return (mode == plan->m_data);
1182 	/* NOTREACHED */
1183 }
1184 
1185 PLAN *
1186 c_perm(perm)
1187 	char *perm;
1188 {
1189 	PLAN *new;
1190 	mode_t *set;
1191 
1192 	ftsoptions &= ~FTS_NOSTAT;
1193 
1194 	new = palloc(N_PERM, f_perm);
1195 
1196 	if (*perm == '-') {
1197 		new->flags = F_ATLEAST;
1198 		++perm;
1199 	}
1200 
1201 	if ((set = setmode(perm)) == NULL)
1202 		errx(1, "-perm: %s: illegal mode string", perm);
1203 
1204 	new->m_data = getmode(set, 0);
1205 	free(set);
1206 	return (new);
1207 }
1208 
1209 /*
1210  * -print functions --
1211  *
1212  *	Always true, causes the current pathame to be written to
1213  *	standard output.
1214  */
1215 int
1216 f_print(plan, entry)
1217 	PLAN *plan;
1218 	FTSENT *entry;
1219 {
1220 	(void)printf("%s\n", entry->fts_path);
1221 	return(1);
1222 }
1223 
1224 /* ARGSUSED */
1225 int
1226 f_print0(plan, entry)
1227 	PLAN *plan;
1228 	FTSENT *entry;
1229 {
1230 	(void)fputs(entry->fts_path, stdout);
1231 	(void)fputc('\0', stdout);
1232 	return(1);
1233 }
1234 
1235 PLAN *
1236 c_print()
1237 {
1238 	isoutput = 1;
1239 
1240 	return(palloc(N_PRINT, f_print));
1241 }
1242 
1243 PLAN *
1244 c_print0()
1245 {
1246 	isoutput = 1;
1247 
1248 	return(palloc(N_PRINT0, f_print0));
1249 }
1250 
1251 /*
1252  * -prune functions --
1253  *
1254  *	Prune a portion of the hierarchy.
1255  */
1256 int
1257 f_prune(plan, entry)
1258 	PLAN *plan;
1259 	FTSENT *entry;
1260 {
1261 
1262 	if (fts_set(tree, entry, FTS_SKIP))
1263 		err(1, "%s", entry->fts_path);
1264 	return (1);
1265 }
1266 
1267 PLAN *
1268 c_prune()
1269 {
1270 	return (palloc(N_PRUNE, f_prune));
1271 }
1272 
1273 /*
1274  * -size n[c] functions --
1275  *
1276  *	True if the file size in bytes, divided by an implementation defined
1277  *	value and rounded up to the next integer, is n.  If n is followed by
1278  *	a c, the size is in bytes.
1279  */
1280 #define	FIND_SIZE	512
1281 static int divsize = 1;
1282 
1283 int
1284 f_size(plan, entry)
1285 	PLAN *plan;
1286 	FTSENT *entry;
1287 {
1288 	off_t size;
1289 
1290 	size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
1291 	    FIND_SIZE : entry->fts_statp->st_size;
1292 	COMPARE(size, plan->o_data);
1293 }
1294 
1295 PLAN *
1296 c_size(arg)
1297 	char *arg;
1298 {
1299 	PLAN *new;
1300 	char endch;
1301 
1302 	ftsoptions &= ~FTS_NOSTAT;
1303 
1304 	new = palloc(N_SIZE, f_size);
1305 	endch = 'c';
1306 	new->o_data = find_parsenum(new, "-size", arg, &endch);
1307 	if (endch == 'c')
1308 		divsize = 0;
1309 	return (new);
1310 }
1311 
1312 /*
1313  * -type c functions --
1314  *
1315  *	True if the type of the file is c, where c is b, c, d, p, or f for
1316  *	block special file, character special file, directory, FIFO, or
1317  *	regular file, respectively.
1318  */
1319 int
1320 f_type(plan, entry)
1321 	PLAN *plan;
1322 	FTSENT *entry;
1323 {
1324 	return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data);
1325 }
1326 
1327 PLAN *
1328 c_type(typestring)
1329 	char *typestring;
1330 {
1331 	PLAN *new;
1332 	mode_t  mask;
1333 
1334 	ftsoptions &= ~FTS_NOSTAT;
1335 
1336 	switch (typestring[0]) {
1337 #ifdef S_IFWHT
1338 	case 'W':
1339 		mask = S_IFWHT;
1340 		if ((ftsoptions & FTS_WHITEOUT) == 0)
1341 			warnx("-type W without -W is a no-op");
1342 		break;
1343 #endif
1344 	case 'b':
1345 		mask = S_IFBLK;
1346 		break;
1347 	case 'c':
1348 		mask = S_IFCHR;
1349 		break;
1350 	case 'd':
1351 		mask = S_IFDIR;
1352 		break;
1353 	case 'f':
1354 		mask = S_IFREG;
1355 		break;
1356 	case 'l':
1357 		mask = S_IFLNK;
1358 		break;
1359 	case 'p':
1360 		mask = S_IFIFO;
1361 		break;
1362 	case 's':
1363 		mask = S_IFSOCK;
1364 		break;
1365 	default:
1366 		errx(1, "-type: %s: unknown type", typestring);
1367 	}
1368 
1369 	new = palloc(N_TYPE, f_type);
1370 	new->m_data = mask;
1371 	return (new);
1372 }
1373 
1374 /*
1375  * -user uname functions --
1376  *
1377  *	True if the file belongs to the user uname.  If uname is numeric and
1378  *	an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1379  *	return a valid user name, uname is taken as a user ID.
1380  */
1381 int
1382 f_user(plan, entry)
1383 	PLAN *plan;
1384 	FTSENT *entry;
1385 {
1386 	return (entry->fts_statp->st_uid == plan->u_data);
1387 }
1388 
1389 PLAN *
1390 c_user(username)
1391 	char *username;
1392 {
1393 	PLAN *new;
1394 	struct passwd *p;
1395 	uid_t uid;
1396 
1397 	ftsoptions &= ~FTS_NOSTAT;
1398 
1399 	p = getpwnam(username);
1400 	if (p == NULL) {
1401 		uid = atoi(username);
1402 		if (uid == 0 && username[0] != '0')
1403 			errx(1, "-user: %s: no such user", username);
1404 	} else
1405 		uid = p->pw_uid;
1406 
1407 	new = palloc(N_USER, f_user);
1408 	new->u_data = uid;
1409 	return (new);
1410 }
1411 
1412 /*
1413  * -xdev functions --
1414  *
1415  *	Always true, causes find not to decend past directories that have a
1416  *	different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1417  */
1418 PLAN *
1419 c_xdev()
1420 {
1421 	ftsoptions |= FTS_XDEV;
1422 
1423 	return (palloc(N_XDEV, f_always_true));
1424 }
1425 
1426 /*
1427  * ( expression ) functions --
1428  *
1429  *	True if expression is true.
1430  */
1431 int
1432 f_expr(plan, entry)
1433 	PLAN *plan;
1434 	FTSENT *entry;
1435 {
1436 	PLAN *p;
1437 	int state;
1438 
1439 	for (p = plan->p_data[0];
1440 	    p && (state = (p->eval)(p, entry)); p = p->next);
1441 	return (state);
1442 }
1443 
1444 /*
1445  * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers.  They are
1446  * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1447  * to a N_EXPR node containing the expression and the ')' node is discarded.
1448  */
1449 PLAN *
1450 c_openparen()
1451 {
1452 	return (palloc(N_OPENPAREN, (int (*)())-1));
1453 }
1454 
1455 PLAN *
1456 c_closeparen()
1457 {
1458 	return (palloc(N_CLOSEPAREN, (int (*)())-1));
1459 }
1460 
1461 /*
1462  * ! expression functions --
1463  *
1464  *	Negation of a primary; the unary NOT operator.
1465  */
1466 int
1467 f_not(plan, entry)
1468 	PLAN *plan;
1469 	FTSENT *entry;
1470 {
1471 	PLAN *p;
1472 	int state;
1473 
1474 	for (p = plan->p_data[0];
1475 	    p && (state = (p->eval)(p, entry)); p = p->next);
1476 	return (!state);
1477 }
1478 
1479 PLAN *
1480 c_not()
1481 {
1482 	return (palloc(N_NOT, f_not));
1483 }
1484 
1485 /*
1486  * expression -o expression functions --
1487  *
1488  *	Alternation of primaries; the OR operator.  The second expression is
1489  * not evaluated if the first expression is true.
1490  */
1491 int
1492 f_or(plan, entry)
1493 	PLAN *plan;
1494 	FTSENT *entry;
1495 {
1496 	PLAN *p;
1497 	int state;
1498 
1499 	for (p = plan->p_data[0];
1500 	    p && (state = (p->eval)(p, entry)); p = p->next);
1501 
1502 	if (state)
1503 		return (1);
1504 
1505 	for (p = plan->p_data[1];
1506 	    p && (state = (p->eval)(p, entry)); p = p->next);
1507 	return (state);
1508 }
1509 
1510 PLAN *
1511 c_or()
1512 {
1513 	return (palloc(N_OR, f_or));
1514 }
1515 
1516 static PLAN *
1517 palloc(t, f)
1518 	enum ntype t;
1519 	int (*f) __P((PLAN *, FTSENT *));
1520 {
1521 	PLAN *new;
1522 
1523 	if ((new = calloc(1, sizeof(PLAN)))) {
1524 		new->type = t;
1525 		new->eval = f;
1526 		return (new);
1527 	}
1528 	err(1, NULL);
1529 	/* NOTREACHED */
1530 }
1531