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