xref: /netbsd-src/usr.bin/find/function.c (revision 93f9db1b75d415b78f73ed629beeb86235153473)
1 /*	$NetBSD: function.c,v 1.26 1998/11/06 23:21:01 christos 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 #include <sys/cdefs.h>
40 #ifndef lint
41 #if 0
42 static char sccsid[] = "from: @(#)function.c	8.10 (Berkeley) 5/4/95";
43 #else
44 __RCSID("$NetBSD: function.c,v 1.26 1998/11/06 23:21:01 christos Exp $");
45 #endif
46 #endif /* not lint */
47 
48 #include <sys/param.h>
49 #include <sys/stat.h>
50 #include <sys/wait.h>
51 #include <sys/mount.h>
52 
53 #include <err.h>
54 #include <errno.h>
55 #include <fnmatch.h>
56 #include <fts.h>
57 #include <grp.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	long	find_parsenum __P((PLAN *, char *, char *, char *));
81 	int	f_always_true __P((PLAN *, FTSENT *));
82 	int	f_atime __P((PLAN *, FTSENT *));
83 	int	f_ctime __P((PLAN *, FTSENT *));
84 	int	f_exec __P((PLAN *, FTSENT *));
85 	int	f_fstype __P((PLAN *, FTSENT *));
86 	int	f_group __P((PLAN *, FTSENT *));
87 	int	f_inum __P((PLAN *, FTSENT *));
88 	int	f_links __P((PLAN *, FTSENT *));
89 	int	f_ls __P((PLAN *, FTSENT *));
90 	int	f_mtime __P((PLAN *, FTSENT *));
91 	int	f_name __P((PLAN *, FTSENT *));
92 	int	f_newer __P((PLAN *, FTSENT *));
93 	int	f_nogroup __P((PLAN *, FTSENT *));
94 	int	f_nouser __P((PLAN *, FTSENT *));
95 	int	f_path __P((PLAN *, FTSENT *));
96 	int	f_perm __P((PLAN *, FTSENT *));
97 	int	f_print __P((PLAN *, FTSENT *));
98 	int	f_print0 __P((PLAN *, FTSENT *));
99 	int	f_prune __P((PLAN *, FTSENT *));
100 	int	f_size __P((PLAN *, FTSENT *));
101 	int	f_type __P((PLAN *, FTSENT *));
102 	int	f_user __P((PLAN *, FTSENT *));
103 	int	f_not __P((PLAN *, FTSENT *));
104 	int	f_or __P((PLAN *, FTSENT *));
105 static	PLAN   *palloc __P((enum ntype, int (*) __P((PLAN *, FTSENT *))));
106 
107 /*
108  * find_parsenum --
109  *	Parse a string of the form [+-]# and return the value.
110  */
111 static long
112 find_parsenum(plan, option, vp, endch)
113 	PLAN *plan;
114 	char *option, *vp, *endch;
115 {
116 	long value;
117 	char *endchar, *str;	/* Pointer to character ending conversion. */
118 
119 	/* Determine comparison from leading + or -. */
120 	str = vp;
121 	switch (*str) {
122 	case '+':
123 		++str;
124 		plan->flags = F_GREATER;
125 		break;
126 	case '-':
127 		++str;
128 		plan->flags = F_LESSTHAN;
129 		break;
130 	default:
131 		plan->flags = F_EQUAL;
132 		break;
133 	}
134 
135 	/*
136 	 * Convert the string with strtol().  Note, if strtol() returns zero
137 	 * and endchar points to the beginning of the string we know we have
138 	 * a syntax error.
139 	 */
140 	value = strtol(str, &endchar, 10);
141 	if (value == 0 && endchar == str)
142 		errx(1, "%s: %s: illegal numeric value", option, vp);
143 	if (endchar[0] && (endch == NULL || endchar[0] != *endch))
144 		errx(1, "%s: %s: illegal trailing character", option, vp);
145 	if (endch)
146 		*endch = endchar[0];
147 	return (value);
148 }
149 
150 /*
151  * The value of n for the inode times (atime, ctime, and mtime) is a range,
152  * i.e. n matches from (n - 1) to n 24 hour periods.  This interacts with
153  * -n, such that "-mtime -1" would be less than 0 days, which isn't what the
154  * user wanted.  Correct so that -1 is "less than 1".
155  */
156 #define	TIME_CORRECT(p, ttype)						\
157 	if ((p)->type == ttype && (p)->flags == F_LESSTHAN)		\
158 		++((p)->t_data);
159 
160 /*
161  * -atime n functions --
162  *
163  *	True if the difference between the file access time and the
164  *	current time is n 24 hour periods.
165  */
166 int
167 f_atime(plan, entry)
168 	PLAN *plan;
169 	FTSENT *entry;
170 {
171 	extern time_t now;
172 
173 	COMPARE((now - entry->fts_statp->st_atime +
174 	    SECSPERDAY - 1) / SECSPERDAY, plan->t_data);
175 }
176 
177 PLAN *
178 c_atime(argvp, isok)
179 	char ***argvp;
180 	int isok;
181 {
182 	char *arg = **argvp;
183 	PLAN *new;
184 
185 	(*argvp)++;
186 	ftsoptions &= ~FTS_NOSTAT;
187 
188 	new = palloc(N_ATIME, f_atime);
189 	new->t_data = find_parsenum(new, "-atime", arg, NULL);
190 	TIME_CORRECT(new, N_ATIME);
191 	return (new);
192 }
193 /*
194  * -ctime n functions --
195  *
196  *	True if the difference between the last change of file
197  *	status information and the current time is n 24 hour periods.
198  */
199 int
200 f_ctime(plan, entry)
201 	PLAN *plan;
202 	FTSENT *entry;
203 {
204 	extern time_t now;
205 
206 	COMPARE((now - entry->fts_statp->st_ctime +
207 	    SECSPERDAY - 1) / SECSPERDAY, plan->t_data);
208 }
209 
210 PLAN *
211 c_ctime(argvp, isok)
212 	char ***argvp;
213 	int isok;
214 {
215 	char *arg = **argvp;
216 	PLAN *new;
217 
218 	(*argvp)++;
219 	ftsoptions &= ~FTS_NOSTAT;
220 
221 	new = palloc(N_CTIME, f_ctime);
222 	new->t_data = find_parsenum(new, "-ctime", arg, NULL);
223 	TIME_CORRECT(new, N_CTIME);
224 	return (new);
225 }
226 
227 /*
228  * -depth functions --
229  *
230  *	Always true, causes descent of the directory hierarchy to be done
231  *	so that all entries in a directory are acted on before the directory
232  *	itself.
233  */
234 int
235 f_always_true(plan, entry)
236 	PLAN *plan;
237 	FTSENT *entry;
238 {
239 	return (1);
240 }
241 
242 PLAN *
243 c_depth(argvp, isok)
244 	char ***argvp;
245 	int isok;
246 {
247 	isdepth = 1;
248 
249 	return (palloc(N_DEPTH, f_always_true));
250 }
251 
252 /*
253  * [-exec | -ok] utility [arg ... ] ; functions --
254  *
255  *	True if the executed utility returns a zero value as exit status.
256  *	The end of the primary expression is delimited by a semicolon.  If
257  *	"{}" occurs anywhere, it gets replaced by the current pathname.
258  *	The current directory for the execution of utility is the same as
259  *	the current directory when the find utility was started.
260  *
261  *	The primary -ok is different in that it requests affirmation of the
262  *	user before executing the utility.
263  */
264 int
265 f_exec(plan, entry)
266 	PLAN *plan;
267 	FTSENT *entry;
268 {
269 	extern int dotfd;
270 	int cnt;
271 	pid_t pid;
272 	int status;
273 
274 	for (cnt = 0; plan->e_argv[cnt]; ++cnt)
275 		if (plan->e_len[cnt])
276 			brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
277 			    entry->fts_path, plan->e_len[cnt]);
278 
279 	if (plan->flags == F_NEEDOK && !queryuser(plan->e_argv))
280 		return (0);
281 
282 	/* don't mix output of command with find output */
283 	fflush(stdout);
284 	fflush(stderr);
285 
286 	switch (pid = vfork()) {
287 	case -1:
288 		err(1, "fork");
289 		/* NOTREACHED */
290 	case 0:
291 		if (fchdir(dotfd)) {
292 			warn("chdir");
293 			_exit(1);
294 		}
295 		execvp(plan->e_argv[0], plan->e_argv);
296 		warn("%s", plan->e_argv[0]);
297 		_exit(1);
298 	}
299 	pid = waitpid(pid, &status, 0);
300 	return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
301 }
302 
303 /*
304  * c_exec --
305  *	build three parallel arrays, one with pointers to the strings passed
306  *	on the command line, one with (possibly duplicated) pointers to the
307  *	argv array, and one with integer values that are lengths of the
308  *	strings, but also flags meaning that the string has to be massaged.
309  */
310 PLAN *
311 c_exec(argvp, isok)
312 	char ***argvp;
313 	int isok;
314 {
315 	PLAN *new;			/* node returned */
316 	int cnt;
317 	char **argv, **ap, *p;
318 
319 	isoutput = 1;
320 
321 	new = palloc(N_EXEC, f_exec);
322 	if (isok)
323 		new->flags = F_NEEDOK;
324 
325 	for (ap = argv = *argvp;; ++ap) {
326 		if (!*ap)
327 			errx(1,
328 			    "%s: no terminating \";\"", isok ? "-ok" : "-exec");
329 		if (**ap == ';')
330 			break;
331 	}
332 
333 	cnt = ap - *argvp + 1;
334 	new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
335 	new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
336 	new->e_len = (int *)emalloc((u_int)cnt * sizeof(int));
337 
338 	for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
339 		new->e_orig[cnt] = *argv;
340 		for (p = *argv; *p; ++p)
341 			if (p[0] == '{' && p[1] == '}') {
342 				new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN);
343 				new->e_len[cnt] = MAXPATHLEN;
344 				break;
345 			}
346 		if (!*p) {
347 			new->e_argv[cnt] = *argv;
348 			new->e_len[cnt] = 0;
349 		}
350 	}
351 	new->e_argv[cnt] = new->e_orig[cnt] = NULL;
352 
353 	*argvp = argv + 1;
354 	return (new);
355 }
356 
357 /*
358  * -follow functions --
359  *
360  *	Always true, causes symbolic links to be followed on a global
361  *	basis.
362  */
363 PLAN *
364 c_follow(argvp, isok)
365 	char ***argvp;
366 	int isok;
367 {
368 	ftsoptions &= ~FTS_PHYSICAL;
369 	ftsoptions |= FTS_LOGICAL;
370 
371 	return (palloc(N_FOLLOW, f_always_true));
372 }
373 
374 /*
375  * -fstype functions --
376  *
377  *	True if the file is of a certain type.
378  */
379 int
380 f_fstype(plan, entry)
381 	PLAN *plan;
382 	FTSENT *entry;
383 {
384 	static dev_t curdev;	/* need a guaranteed illegal dev value */
385 	static int first = 1;
386 	struct statfs sb;
387 	static short val;
388 	static char fstype[MFSNAMELEN];
389 	char *p, save[2];
390 
391 	/* Only check when we cross mount point. */
392 	if (first || curdev != entry->fts_statp->st_dev) {
393 		curdev = entry->fts_statp->st_dev;
394 
395 		/*
396 		 * Statfs follows symlinks; find wants the link's file system,
397 		 * not where it points.
398 		 */
399 		if (entry->fts_info == FTS_SL ||
400 		    entry->fts_info == FTS_SLNONE) {
401 			if ((p = strrchr(entry->fts_accpath, '/')) != NULL)
402 				++p;
403 			else
404 				p = entry->fts_accpath;
405 			save[0] = p[0];
406 			p[0] = '.';
407 			save[1] = p[1];
408 			p[1] = '\0';
409 
410 		} else
411 			p = NULL;
412 
413 		if (statfs(entry->fts_accpath, &sb))
414 			err(1, "%s", entry->fts_accpath);
415 
416 		if (p) {
417 			p[0] = save[0];
418 			p[1] = save[1];
419 		}
420 
421 		first = 0;
422 
423 		/*
424 		 * Further tests may need both of these values, so
425 		 * always copy both of them.
426 		 */
427 		val = sb.f_flags;
428 		strncpy(fstype, sb.f_fstypename, MFSNAMELEN);
429 	}
430 	switch (plan->flags) {
431 	case F_MTFLAG:
432 		return (val & plan->mt_data);
433 	case F_MTTYPE:
434 		return (strncmp(fstype, plan->c_data, MFSNAMELEN) == 0);
435 	default:
436 		abort();
437 	}
438 }
439 
440 PLAN *
441 c_fstype(argvp, isok)
442 	char ***argvp;
443 	int isok;
444 {
445 	char *arg = **argvp;
446 	PLAN *new;
447 
448 	(*argvp)++;
449 	ftsoptions &= ~FTS_NOSTAT;
450 
451 	new = palloc(N_FSTYPE, f_fstype);
452 
453 	switch (*arg) {
454 	case 'l':
455 		if (!strcmp(arg, "local")) {
456 			new->flags = F_MTFLAG;
457 			new->mt_data = MNT_LOCAL;
458 			return (new);
459 		}
460 		break;
461 	case 'r':
462 		if (!strcmp(arg, "rdonly")) {
463 			new->flags = F_MTFLAG;
464 			new->mt_data = MNT_RDONLY;
465 			return (new);
466 		}
467 		break;
468 	}
469 
470 	new->flags = F_MTTYPE;
471 	new->c_data = arg;
472 	return (new);
473 }
474 
475 /*
476  * -group gname functions --
477  *
478  *	True if the file belongs to the group gname.  If gname is numeric and
479  *	an equivalent of the getgrnam() function does not return a valid group
480  *	name, gname is taken as a group ID.
481  */
482 int
483 f_group(plan, entry)
484 	PLAN *plan;
485 	FTSENT *entry;
486 {
487 	return (entry->fts_statp->st_gid == plan->g_data);
488 }
489 
490 PLAN *
491 c_group(argvp, isok)
492 	char ***argvp;
493 	int isok;
494 {
495 	char *gname = **argvp;
496 	PLAN *new;
497 	struct group *g;
498 	gid_t gid;
499 
500 	(*argvp)++;
501 	ftsoptions &= ~FTS_NOSTAT;
502 
503 	g = getgrnam(gname);
504 	if (g == NULL) {
505 		gid = atoi(gname);
506 		if (gid == 0 && gname[0] != '0')
507 			errx(1, "-group: %s: no such group", gname);
508 	} else
509 		gid = g->gr_gid;
510 
511 	new = palloc(N_GROUP, f_group);
512 	new->g_data = gid;
513 	return (new);
514 }
515 
516 /*
517  * -inum n functions --
518  *
519  *	True if the file has inode # n.
520  */
521 int
522 f_inum(plan, entry)
523 	PLAN *plan;
524 	FTSENT *entry;
525 {
526 	COMPARE(entry->fts_statp->st_ino, plan->i_data);
527 }
528 
529 PLAN *
530 c_inum(argvp, isok)
531 	char ***argvp;
532 	int isok;
533 {
534 	char *arg = **argvp;
535 	PLAN *new;
536 
537 	(*argvp)++;
538 	ftsoptions &= ~FTS_NOSTAT;
539 
540 	new = palloc(N_INUM, f_inum);
541 	new->i_data = find_parsenum(new, "-inum", arg, NULL);
542 	return (new);
543 }
544 
545 /*
546  * -links n functions --
547  *
548  *	True if the file has n links.
549  */
550 int
551 f_links(plan, entry)
552 	PLAN *plan;
553 	FTSENT *entry;
554 {
555 	COMPARE(entry->fts_statp->st_nlink, plan->l_data);
556 }
557 
558 PLAN *
559 c_links(argvp, isok)
560 	char ***argvp;
561 	int isok;
562 {
563 	char *arg = **argvp;
564 	PLAN *new;
565 
566 	(*argvp)++;
567 	ftsoptions &= ~FTS_NOSTAT;
568 
569 	new = palloc(N_LINKS, f_links);
570 	new->l_data = (nlink_t)find_parsenum(new, "-links", arg, NULL);
571 	return (new);
572 }
573 
574 /*
575  * -ls functions --
576  *
577  *	Always true - prints the current entry to stdout in "ls" format.
578  */
579 int
580 f_ls(plan, entry)
581 	PLAN *plan;
582 	FTSENT *entry;
583 {
584 	printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp);
585 	return (1);
586 }
587 
588 PLAN *
589 c_ls(argvp, isok)
590 	char ***argvp;
591 	int isok;
592 {
593 	ftsoptions &= ~FTS_NOSTAT;
594 	isoutput = 1;
595 
596 	return (palloc(N_LS, f_ls));
597 }
598 
599 /*
600  * -mtime n functions --
601  *
602  *	True if the difference between the file modification time and the
603  *	current time is n 24 hour periods.
604  */
605 int
606 f_mtime(plan, entry)
607 	PLAN *plan;
608 	FTSENT *entry;
609 {
610 	extern time_t now;
611 
612 	COMPARE((now - entry->fts_statp->st_mtime + SECSPERDAY - 1) /
613 	    SECSPERDAY, plan->t_data);
614 }
615 
616 PLAN *
617 c_mtime(argvp, isok)
618 	char ***argvp;
619 	int isok;
620 {
621 	char *arg = **argvp;
622 	PLAN *new;
623 
624 	(*argvp)++;
625 	ftsoptions &= ~FTS_NOSTAT;
626 
627 	new = palloc(N_MTIME, f_mtime);
628 	new->t_data = find_parsenum(new, "-mtime", arg, NULL);
629 	TIME_CORRECT(new, N_MTIME);
630 	return (new);
631 }
632 
633 /*
634  * -name functions --
635  *
636  *	True if the basename of the filename being examined
637  *	matches pattern using Pattern Matching Notation S3.14
638  */
639 int
640 f_name(plan, entry)
641 	PLAN *plan;
642 	FTSENT *entry;
643 {
644 	return (!fnmatch(plan->c_data, entry->fts_name, 0));
645 }
646 
647 PLAN *
648 c_name(argvp, isok)
649 	char ***argvp;
650 	int isok;
651 {
652 	char *pattern = **argvp;
653 	PLAN *new;
654 
655 	(*argvp)++;
656 	new = palloc(N_NAME, f_name);
657 	new->c_data = pattern;
658 	return (new);
659 }
660 
661 /*
662  * -newer file functions --
663  *
664  *	True if the current file has been modified more recently
665  *	then the modification time of the file named by the pathname
666  *	file.
667  */
668 int
669 f_newer(plan, entry)
670 	PLAN *plan;
671 	FTSENT *entry;
672 {
673 	return (entry->fts_statp->st_mtime > plan->t_data);
674 }
675 
676 PLAN *
677 c_newer(argvp, isok)
678 	char ***argvp;
679 	int isok;
680 {
681 	char *filename = **argvp;
682 	PLAN *new;
683 	struct stat sb;
684 
685 	(*argvp)++;
686 	ftsoptions &= ~FTS_NOSTAT;
687 
688 	if (stat(filename, &sb))
689 		err(1, "%s", filename);
690 	new = palloc(N_NEWER, f_newer);
691 	new->t_data = sb.st_mtime;
692 	return (new);
693 }
694 
695 /*
696  * -nogroup functions --
697  *
698  *	True if file belongs to a user ID for which the equivalent
699  *	of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
700  */
701 int
702 f_nogroup(plan, entry)
703 	PLAN *plan;
704 	FTSENT *entry;
705 {
706 
707 	return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1);
708 }
709 
710 PLAN *
711 c_nogroup(argvp, isok)
712 	char ***argvp;
713 	int isok;
714 {
715 	ftsoptions &= ~FTS_NOSTAT;
716 
717 	return (palloc(N_NOGROUP, f_nogroup));
718 }
719 
720 /*
721  * -nouser functions --
722  *
723  *	True if file belongs to a user ID for which the equivalent
724  *	of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
725  */
726 int
727 f_nouser(plan, entry)
728 	PLAN *plan;
729 	FTSENT *entry;
730 {
731 
732 	return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1);
733 }
734 
735 PLAN *
736 c_nouser(argvp, isok)
737 	char ***argvp;
738 	int isok;
739 {
740 	ftsoptions &= ~FTS_NOSTAT;
741 
742 	return (palloc(N_NOUSER, f_nouser));
743 }
744 
745 /*
746  * -path functions --
747  *
748  *	True if the path of the filename being examined
749  *	matches pattern using Pattern Matching Notation S3.14
750  */
751 int
752 f_path(plan, entry)
753 	PLAN *plan;
754 	FTSENT *entry;
755 {
756 	return (!fnmatch(plan->c_data, entry->fts_path, 0));
757 }
758 
759 PLAN *
760 c_path(argvp, isok)
761 	char ***argvp;
762 	int isok;
763 {
764 	char *pattern = **argvp;
765 	PLAN *new;
766 
767 	(*argvp)++;
768 	new = palloc(N_NAME, f_path);
769 	new->c_data = pattern;
770 	return (new);
771 }
772 
773 /*
774  * -perm functions --
775  *
776  *	The mode argument is used to represent file mode bits.  If it starts
777  *	with a leading digit, it's treated as an octal mode, otherwise as a
778  *	symbolic mode.
779  */
780 int
781 f_perm(plan, entry)
782 	PLAN *plan;
783 	FTSENT *entry;
784 {
785 	mode_t mode;
786 
787 	mode = entry->fts_statp->st_mode &
788 	    (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
789 	if (plan->flags == F_ATLEAST)
790 		return ((plan->m_data | mode) == mode);
791 	else
792 		return (mode == plan->m_data);
793 	/* NOTREACHED */
794 }
795 
796 PLAN *
797 c_perm(argvp, isok)
798 	char ***argvp;
799 	int isok;
800 {
801 	char *perm = **argvp;
802 	PLAN *new;
803 	mode_t *set;
804 
805 	(*argvp)++;
806 	ftsoptions &= ~FTS_NOSTAT;
807 
808 	new = palloc(N_PERM, f_perm);
809 
810 	if (*perm == '-') {
811 		new->flags = F_ATLEAST;
812 		++perm;
813 	}
814 
815 	if ((set = setmode(perm)) == NULL)
816 		err(1, "-perm: %s: illegal mode string", perm);
817 
818 	new->m_data = getmode(set, 0);
819 	return (new);
820 }
821 
822 /*
823  * -print functions --
824  *
825  *	Always true, causes the current pathame to be written to
826  *	standard output.
827  */
828 int
829 f_print(plan, entry)
830 	PLAN *plan;
831 	FTSENT *entry;
832 {
833 	(void)printf("%s\n", entry->fts_path);
834 	return (1);
835 }
836 
837 int
838 f_print0(plan, entry)
839 	PLAN *plan;
840 	FTSENT *entry;
841 {
842 	(void)fputs(entry->fts_path, stdout);
843 	(void)fputc('\0', stdout);
844 	return (1);
845 }
846 
847 PLAN *
848 c_print(argvp, isok)
849 	char ***argvp;
850 	int isok;
851 {
852 	isoutput = 1;
853 
854 	return (palloc(N_PRINT, f_print));
855 }
856 
857 PLAN *
858 c_print0(argvp, isok)
859 	char ***argvp;
860 	int isok;
861 {
862 	isoutput = 1;
863 
864 	return (palloc(N_PRINT0, f_print0));
865 }
866 
867 /*
868  * -prune functions --
869  *
870  *	Prune a portion of the hierarchy.
871  */
872 int
873 f_prune(plan, entry)
874 	PLAN *plan;
875 	FTSENT *entry;
876 {
877 	extern FTS *tree;
878 
879 	if (fts_set(tree, entry, FTS_SKIP))
880 		err(1, "%s", entry->fts_path);
881 	return (1);
882 }
883 
884 PLAN *
885 c_prune(argvp, isok)
886 	char ***argvp;
887 	int isok;
888 {
889 	return (palloc(N_PRUNE, f_prune));
890 }
891 
892 /*
893  * -size n[c] functions --
894  *
895  *	True if the file size in bytes, divided by an implementation defined
896  *	value and rounded up to the next integer, is n.  If n is followed by
897  *	a c, the size is in bytes.
898  */
899 #define	FIND_SIZE	512
900 static int divsize = 1;
901 
902 int
903 f_size(plan, entry)
904 	PLAN *plan;
905 	FTSENT *entry;
906 {
907 	off_t size;
908 
909 	size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
910 	    FIND_SIZE : entry->fts_statp->st_size;
911 	COMPARE(size, plan->o_data);
912 }
913 
914 PLAN *
915 c_size(argvp, isok)
916 	char ***argvp;
917 	int isok;
918 {
919 	char *arg = **argvp;
920 	PLAN *new;
921 	char endch;
922 
923 	(*argvp)++;
924 	ftsoptions &= ~FTS_NOSTAT;
925 
926 	new = palloc(N_SIZE, f_size);
927 	endch = 'c';
928 	new->o_data = find_parsenum(new, "-size", arg, &endch);
929 	if (endch == 'c')
930 		divsize = 0;
931 	return (new);
932 }
933 
934 /*
935  * -type c functions --
936  *
937  *	True if the type of the file is c, where c is b, c, d, p, f or w
938  *	for block special file, character special file, directory, FIFO,
939  *	regular file or whiteout respectively.
940  */
941 int
942 f_type(plan, entry)
943 	PLAN *plan;
944 	FTSENT *entry;
945 {
946 	return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data);
947 }
948 
949 PLAN *
950 c_type(argvp, isok)
951 	char ***argvp;
952 	int isok;
953 {
954 	char *typestring = **argvp;
955 	PLAN *new;
956 	mode_t  mask = (mode_t)0;
957 
958 	(*argvp)++;
959 	ftsoptions &= ~FTS_NOSTAT;
960 
961 	switch (typestring[0]) {
962 #ifdef S_IFWHT
963       case 'W':
964 #ifdef FTS_WHITEOUT
965 	      ftsoptions |= FTS_WHITEOUT;
966 #endif
967               mask = S_IFWHT;
968               break;
969 #endif
970 	case 'b':
971 		mask = S_IFBLK;
972 		break;
973 	case 'c':
974 		mask = S_IFCHR;
975 		break;
976 	case 'd':
977 		mask = S_IFDIR;
978 		break;
979 	case 'f':
980 		mask = S_IFREG;
981 		break;
982 	case 'l':
983 		mask = S_IFLNK;
984 		break;
985 	case 'p':
986 		mask = S_IFIFO;
987 		break;
988 	case 's':
989 		mask = S_IFSOCK;
990 		break;
991 #ifdef FTS_WHITEOUT
992 	case 'w':
993 		mask = S_IFWHT;
994 		ftsoptions |= FTS_WHITEOUT;
995 		break;
996 #endif /* FTS_WHITEOUT */
997 	default:
998 		errx(1, "-type: %s: unknown type", typestring);
999 	}
1000 
1001 	new = palloc(N_TYPE, f_type);
1002 	new->m_data = mask;
1003 	return (new);
1004 }
1005 
1006 /*
1007  * -user uname functions --
1008  *
1009  *	True if the file belongs to the user uname.  If uname is numeric and
1010  *	an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1011  *	return a valid user name, uname is taken as a user ID.
1012  */
1013 int
1014 f_user(plan, entry)
1015 	PLAN *plan;
1016 	FTSENT *entry;
1017 {
1018 	return (entry->fts_statp->st_uid == plan->u_data);
1019 }
1020 
1021 PLAN *
1022 c_user(argvp, isok)
1023 	char ***argvp;
1024 	int isok;
1025 {
1026 	char *username = **argvp;
1027 	PLAN *new;
1028 	struct passwd *p;
1029 	uid_t uid;
1030 
1031 	(*argvp)++;
1032 	ftsoptions &= ~FTS_NOSTAT;
1033 
1034 	p = getpwnam(username);
1035 	if (p == NULL) {
1036 		uid = atoi(username);
1037 		if (uid == 0 && username[0] != '0')
1038 			errx(1, "-user: %s: no such user", username);
1039 	} else
1040 		uid = p->pw_uid;
1041 
1042 	new = palloc(N_USER, f_user);
1043 	new->u_data = uid;
1044 	return (new);
1045 }
1046 
1047 /*
1048  * -xdev functions --
1049  *
1050  *	Always true, causes find not to decend past directories that have a
1051  *	different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1052  */
1053 PLAN *
1054 c_xdev(argvp, isok)
1055 	char ***argvp;
1056 	int isok;
1057 {
1058 	ftsoptions |= FTS_XDEV;
1059 
1060 	return (palloc(N_XDEV, f_always_true));
1061 }
1062 
1063 /*
1064  * ( expression ) functions --
1065  *
1066  *	True if expression is true.
1067  */
1068 int
1069 f_expr(plan, entry)
1070 	PLAN *plan;
1071 	FTSENT *entry;
1072 {
1073 	PLAN *p;
1074 	int state;
1075 
1076 	state = 0;
1077 	for (p = plan->p_data[0];
1078 	    p && (state = (p->eval)(p, entry)); p = p->next);
1079 	return (state);
1080 }
1081 
1082 /*
1083  * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers.  They are
1084  * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1085  * to a N_EXPR node containing the expression and the ')' node is discarded.
1086  */
1087 PLAN *
1088 c_openparen(argvp, isok)
1089 	char ***argvp;
1090 	int isok;
1091 {
1092 	return (palloc(N_OPENPAREN, (int (*) __P((PLAN *, FTSENT *)))-1));
1093 }
1094 
1095 PLAN *
1096 c_closeparen(argvp, isok)
1097 	char ***argvp;
1098 	int isok;
1099 {
1100 	return (palloc(N_CLOSEPAREN, (int (*) __P((PLAN *, FTSENT *)))-1));
1101 }
1102 
1103 /*
1104  * ! expression functions --
1105  *
1106  *	Negation of a primary; the unary NOT operator.
1107  */
1108 int
1109 f_not(plan, entry)
1110 	PLAN *plan;
1111 	FTSENT *entry;
1112 {
1113 	PLAN *p;
1114 	int state;
1115 
1116 	state = 0;
1117 	for (p = plan->p_data[0];
1118 	    p && (state = (p->eval)(p, entry)); p = p->next);
1119 	return (!state);
1120 }
1121 
1122 PLAN *
1123 c_not(argvp, isok)
1124 	char ***argvp;
1125 	int isok;
1126 {
1127 	return (palloc(N_NOT, f_not));
1128 }
1129 
1130 /*
1131  * expression -o expression functions --
1132  *
1133  *	Alternation of primaries; the OR operator.  The second expression is
1134  * not evaluated if the first expression is true.
1135  */
1136 int
1137 f_or(plan, entry)
1138 	PLAN *plan;
1139 	FTSENT *entry;
1140 {
1141 	PLAN *p;
1142 	int state;
1143 
1144 	state = 0;
1145 	for (p = plan->p_data[0];
1146 	    p && (state = (p->eval)(p, entry)); p = p->next);
1147 
1148 	if (state)
1149 		return (1);
1150 
1151 	for (p = plan->p_data[1];
1152 	    p && (state = (p->eval)(p, entry)); p = p->next);
1153 	return (state);
1154 }
1155 
1156 PLAN *
1157 c_or(argvp, isok)
1158 	char ***argvp;
1159 	int isok;
1160 {
1161 	return (palloc(N_OR, f_or));
1162 }
1163 
1164 PLAN *
1165 c_null(argvp, isok)
1166 	char ***argvp;
1167 	int isok;
1168 {
1169 	return NULL;
1170 }
1171 
1172 static PLAN *
1173 palloc(t, f)
1174 	enum ntype t;
1175 	int (*f) __P((PLAN *, FTSENT *));
1176 {
1177 	PLAN *new;
1178 
1179 	if ((new = malloc(sizeof(PLAN))) == NULL)
1180 		err(1, "%s", "");
1181 	new->type = t;
1182 	new->eval = f;
1183 	new->flags = 0;
1184 	new->next = NULL;
1185 	return (new);
1186 }
1187