xref: /freebsd/usr.bin/find/function.c (revision 5521ff5a4d1929056e7ffc982fac3341ca54df7c)
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 	new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
519 	new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
520 	new->e_len = (int *)emalloc((u_int)cnt * sizeof(int));
521 
522 	for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
523 		new->e_orig[cnt] = *argv;
524 		for (p = *argv; *p; ++p)
525 			if (p[0] == '{' && p[1] == '}') {
526 				new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN);
527 				new->e_len[cnt] = MAXPATHLEN;
528 				break;
529 			}
530 		if (!*p) {
531 			new->e_argv[cnt] = *argv;
532 			new->e_len[cnt] = 0;
533 		}
534 	}
535 	new->e_argv[cnt] = new->e_orig[cnt] = NULL;
536 
537 	*argvp = argv + 1;
538 	return new;
539 }
540 
541 int
542 f_flags(plan, entry)
543 	PLAN *plan;
544 	FTSENT *entry;
545 {
546 	u_long flags;
547 
548 	flags = entry->fts_statp->st_flags &
549 	    (UF_NODUMP | UF_IMMUTABLE | UF_APPEND | UF_OPAQUE |
550 	     SF_ARCHIVED | SF_IMMUTABLE | SF_APPEND);
551 	if (plan->flags & F_ATLEAST)
552 		/* note that plan->fl_flags always is a subset of
553 		   plan->fl_mask */
554 		return (flags & plan->fl_mask) == plan->fl_flags;
555 	else if (plan->flags & F_ANY)
556 		return flags & plan->fl_mask;
557 	else
558 		return flags == plan->fl_flags;
559 	/* NOTREACHED */
560 }
561 
562 PLAN *
563 c_flags(option, argvp)
564 	OPTION *option;
565 	char ***argvp;
566 {
567 	char *flags_str;
568 	PLAN *new;
569 	u_long flags, notflags;
570 
571 	flags_str = nextarg(option, argvp);
572 	ftsoptions &= ~FTS_NOSTAT;
573 
574 	new = palloc(option);
575 
576 	if (*flags_str == '-') {
577 		new->flags |= F_ATLEAST;
578 		flags_str++;
579 	}
580 	if (strtofflags(&flags_str, &flags, &notflags) == 1)
581 		errx(1, "%s: %s: illegal flags string", option->name, flags_str);
582 
583 	new->fl_flags = flags;
584 	new->fl_mask = flags | notflags;
585 	return new;
586 }
587 
588 /*
589  * -follow functions --
590  *
591  *	Always true, causes symbolic links to be followed on a global
592  *	basis.
593  */
594 PLAN *
595 c_follow(option, argvp)
596 	OPTION *option;
597 	char ***argvp;
598 {
599 	ftsoptions &= ~FTS_PHYSICAL;
600 	ftsoptions |= FTS_LOGICAL;
601 
602 	return palloc(option);
603 }
604 
605 /*
606  * -fstype functions --
607  *
608  *	True if the file is of a certain type.
609  */
610 int
611 f_fstype(plan, entry)
612 	PLAN *plan;
613 	FTSENT *entry;
614 {
615 	static dev_t curdev;	/* need a guaranteed illegal dev value */
616 	static int first = 1;
617 	struct statfs sb;
618 	static int val_type, val_flags;
619 	char *p, save[2];
620 
621 	if ((plan->flags & F_MTMASK) == F_MTUNKNOWN)
622 		return 0;
623 
624 	/* Only check when we cross mount point. */
625 	if (first || curdev != entry->fts_statp->st_dev) {
626 		curdev = entry->fts_statp->st_dev;
627 
628 		/*
629 		 * Statfs follows symlinks; find wants the link's file system,
630 		 * not where it points.
631 		 */
632 		if (entry->fts_info == FTS_SL ||
633 		    entry->fts_info == FTS_SLNONE) {
634 			if ((p = strrchr(entry->fts_accpath, '/')) != NULL)
635 				++p;
636 			else
637 				p = entry->fts_accpath;
638 			save[0] = p[0];
639 			p[0] = '.';
640 			save[1] = p[1];
641 			p[1] = '\0';
642 		} else
643 			p = NULL;
644 
645 		if (statfs(entry->fts_accpath, &sb))
646 			err(1, "%s", entry->fts_accpath);
647 
648 		if (p) {
649 			p[0] = save[0];
650 			p[1] = save[1];
651 		}
652 
653 		first = 0;
654 
655 		/*
656 		 * Further tests may need both of these values, so
657 		 * always copy both of them.
658 		 */
659 		val_flags = sb.f_flags;
660 		val_type = sb.f_type;
661 	}
662 	switch (plan->flags & F_MTMASK) {
663 	case F_MTFLAG:
664 		return val_flags & plan->mt_data;
665 	case F_MTTYPE:
666 		return val_type == plan->mt_data;
667 	default:
668 		abort();
669 	}
670 }
671 
672 #if !defined(__NetBSD__)
673 PLAN *
674 c_fstype(option, argvp)
675 	OPTION *option;
676 	char ***argvp;
677 {
678 	char *fsname;
679 	register PLAN *new;
680 	struct vfsconf vfc;
681 
682 	fsname = nextarg(option, argvp);
683 	ftsoptions &= ~FTS_NOSTAT;
684 
685 	new = palloc(option);
686 
687 	/*
688 	 * Check first for a filesystem name.
689 	 */
690 	if (getvfsbyname(fsname, &vfc) == 0) {
691 		new->flags |= F_MTTYPE;
692 		new->mt_data = vfc.vfc_typenum;
693 		return new;
694 	}
695 
696 	switch (*fsname) {
697 	case 'l':
698 		if (!strcmp(fsname, "local")) {
699 			new->flags |= F_MTFLAG;
700 			new->mt_data = MNT_LOCAL;
701 			return new;
702 		}
703 		break;
704 	case 'r':
705 		if (!strcmp(fsname, "rdonly")) {
706 			new->flags |= F_MTFLAG;
707 			new->mt_data = MNT_RDONLY;
708 			return new;
709 		}
710 		break;
711 	}
712 
713 	/*
714 	 * We need to make filesystem checks for filesystems
715 	 * that exists but aren't in the kernel work.
716 	 */
717 	fprintf(stderr, "Warning: Unknown filesystem type %s\n", fsname);
718 	new->flags |= F_MTUNKNOWN;
719 	return new;
720 }
721 #endif /* __NetBSD__ */
722 
723 /*
724  * -group gname functions --
725  *
726  *	True if the file belongs to the group gname.  If gname is numeric and
727  *	an equivalent of the getgrnam() function does not return a valid group
728  *	name, gname is taken as a group ID.
729  */
730 int
731 f_group(plan, entry)
732 	PLAN *plan;
733 	FTSENT *entry;
734 {
735 	return entry->fts_statp->st_gid == plan->g_data;
736 }
737 
738 PLAN *
739 c_group(option, argvp)
740 	OPTION *option;
741 	char ***argvp;
742 {
743 	char *gname;
744 	PLAN *new;
745 	struct group *g;
746 	gid_t gid;
747 
748 	gname = nextarg(option, argvp);
749 	ftsoptions &= ~FTS_NOSTAT;
750 
751 	g = getgrnam(gname);
752 	if (g == NULL) {
753 		gid = atoi(gname);
754 		if (gid == 0 && gname[0] != '0')
755 			errx(1, "%s: %s: no such group", option->name, gname);
756 	} else
757 		gid = g->gr_gid;
758 
759 	new = palloc(option);
760 	new->g_data = gid;
761 	return new;
762 }
763 
764 /*
765  * -inum n functions --
766  *
767  *	True if the file has inode # n.
768  */
769 int
770 f_inum(plan, entry)
771 	PLAN *plan;
772 	FTSENT *entry;
773 {
774 	COMPARE(entry->fts_statp->st_ino, plan->i_data);
775 }
776 
777 PLAN *
778 c_inum(option, argvp)
779 	OPTION *option;
780 	char ***argvp;
781 {
782 	char *inum_str;
783 	PLAN *new;
784 
785 	inum_str = nextarg(option, argvp);
786 	ftsoptions &= ~FTS_NOSTAT;
787 
788 	new = palloc(option);
789 	new->i_data = find_parsenum(new, option->name, inum_str, NULL);
790 	return new;
791 }
792 
793 /*
794  * -links n functions --
795  *
796  *	True if the file has n links.
797  */
798 int
799 f_links(plan, entry)
800 	PLAN *plan;
801 	FTSENT *entry;
802 {
803 	COMPARE(entry->fts_statp->st_nlink, plan->l_data);
804 }
805 
806 PLAN *
807 c_links(option, argvp)
808 	OPTION *option;
809 	char ***argvp;
810 {
811 	char *nlinks;
812 	PLAN *new;
813 
814 	nlinks = nextarg(option, argvp);
815 	ftsoptions &= ~FTS_NOSTAT;
816 
817 	new = palloc(option);
818 	new->l_data = (nlink_t)find_parsenum(new, option->name, nlinks, NULL);
819 	return new;
820 }
821 
822 /*
823  * -ls functions --
824  *
825  *	Always true - prints the current entry to stdout in "ls" format.
826  */
827 int
828 f_ls(plan, entry)
829 	PLAN *plan;
830 	FTSENT *entry;
831 {
832 	printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp);
833 	return 1;
834 }
835 
836 PLAN *
837 c_ls(option, argvp)
838 	OPTION *option;
839 	char ***argvp;
840 {
841 	ftsoptions &= ~FTS_NOSTAT;
842 	isoutput = 1;
843 
844 	return palloc(option);
845 }
846 
847 /*
848  * -name functions --
849  *
850  *	True if the basename of the filename being examined
851  *	matches pattern using Pattern Matching Notation S3.14
852  */
853 int
854 f_name(plan, entry)
855 	PLAN *plan;
856 	FTSENT *entry;
857 {
858 	return !fnmatch(plan->c_data, entry->fts_name,
859 	    plan->flags & F_IGNCASE ? FNM_CASEFOLD : 0);
860 }
861 
862 PLAN *
863 c_name(option, argvp)
864 	OPTION *option;
865 	char ***argvp;
866 {
867 	char *pattern;
868 	PLAN *new;
869 
870 	pattern = nextarg(option, argvp);
871 	new = palloc(option);
872 	new->c_data = pattern;
873 	return new;
874 }
875 
876 /*
877  * -newer file functions --
878  *
879  *	True if the current file has been modified more recently
880  *	then the modification time of the file named by the pathname
881  *	file.
882  */
883 int
884 f_newer(plan, entry)
885 	PLAN *plan;
886 	FTSENT *entry;
887 {
888 	if (plan->flags & F_TIME_C)
889 		return entry->fts_statp->st_ctime > plan->t_data;
890 	else if (plan->flags & F_TIME_A)
891 		return entry->fts_statp->st_atime > plan->t_data;
892 	else
893 		return entry->fts_statp->st_mtime > plan->t_data;
894 }
895 
896 PLAN *
897 c_newer(option, argvp)
898 	OPTION *option;
899 	char ***argvp;
900 {
901 	char *fn_or_tspec;
902 	PLAN *new;
903 	struct stat sb;
904 
905 	fn_or_tspec = nextarg(option, argvp);
906 	ftsoptions &= ~FTS_NOSTAT;
907 
908 	new = palloc(option);
909 	/* compare against what */
910 	if (option->flags & F_TIME2_T) {
911 		new->t_data = get_date(fn_or_tspec, (struct timeb *) 0);
912 		if (new->t_data == (time_t) -1)
913 			errx(1, "Can't parse date/time: %s", fn_or_tspec);
914 	} else {
915 		if (stat(fn_or_tspec, &sb))
916 			err(1, "%s", fn_or_tspec);
917 		if (option->flags & F_TIME2_C)
918 			new->t_data = sb.st_ctime;
919 		else if (option->flags & F_TIME2_A)
920 			new->t_data = sb.st_atime;
921 		else
922 			new->t_data = sb.st_mtime;
923 	}
924 	return new;
925 }
926 
927 /*
928  * -nogroup functions --
929  *
930  *	True if file belongs to a user ID for which the equivalent
931  *	of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
932  */
933 int
934 f_nogroup(plan, entry)
935 	PLAN *plan;
936 	FTSENT *entry;
937 {
938 	return group_from_gid(entry->fts_statp->st_gid, 1) == NULL;
939 }
940 
941 PLAN *
942 c_nogroup(option, argvp)
943 	OPTION *option;
944 	char ***argvp;
945 {
946 	ftsoptions &= ~FTS_NOSTAT;
947 
948 	return palloc(option);
949 }
950 
951 /*
952  * -nouser functions --
953  *
954  *	True if file belongs to a user ID for which the equivalent
955  *	of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
956  */
957 int
958 f_nouser(plan, entry)
959 	PLAN *plan;
960 	FTSENT *entry;
961 {
962 	return user_from_uid(entry->fts_statp->st_uid, 1) == NULL;
963 }
964 
965 PLAN *
966 c_nouser(option, argvp)
967 	OPTION *option;
968 	char ***argvp;
969 {
970 	ftsoptions &= ~FTS_NOSTAT;
971 
972 	return palloc(option);
973 }
974 
975 /*
976  * -path functions --
977  *
978  *	True if the path of the filename being examined
979  *	matches pattern using Pattern Matching Notation S3.14
980  */
981 int
982 f_path(plan, entry)
983 	PLAN *plan;
984 	FTSENT *entry;
985 {
986 	return !fnmatch(plan->c_data, entry->fts_path,
987 	    plan->flags & F_IGNCASE ? FNM_CASEFOLD : 0);
988 }
989 
990 /* c_path is the same as c_name */
991 
992 /*
993  * -perm functions --
994  *
995  *	The mode argument is used to represent file mode bits.  If it starts
996  *	with a leading digit, it's treated as an octal mode, otherwise as a
997  *	symbolic mode.
998  */
999 int
1000 f_perm(plan, entry)
1001 	PLAN *plan;
1002 	FTSENT *entry;
1003 {
1004 	mode_t mode;
1005 
1006 	mode = entry->fts_statp->st_mode &
1007 	    (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
1008 	if (plan->flags & F_ATLEAST)
1009 		return (plan->m_data | mode) == mode;
1010 	else
1011 		return mode == plan->m_data;
1012 	/* NOTREACHED */
1013 }
1014 
1015 PLAN *
1016 c_perm(option, argvp)
1017 	OPTION *option;
1018 	char ***argvp;
1019 {
1020 	char *perm;
1021 	PLAN *new;
1022 	mode_t *set;
1023 
1024 	perm = nextarg(option, argvp);
1025 	ftsoptions &= ~FTS_NOSTAT;
1026 
1027 	new = palloc(option);
1028 
1029 	if (*perm == '-') {
1030 		new->flags |= F_ATLEAST;
1031 		++perm;
1032 	} else if (*perm == '+') {
1033 		new->flags |= F_ANY;
1034 		++perm;
1035 	}
1036 
1037 	if ((set = setmode(perm)) == NULL)
1038 		errx(1, "%s: %s: illegal mode string", option->name, perm);
1039 
1040 	new->m_data = getmode(set, 0);
1041 	free(set);
1042 	return new;
1043 }
1044 
1045 /*
1046  * -print functions --
1047  *
1048  *	Always true, causes the current pathame to be written to
1049  *	standard output.
1050  */
1051 int
1052 f_print(plan, entry)
1053 	PLAN *plan;
1054 	FTSENT *entry;
1055 {
1056 	(void)puts(entry->fts_path);
1057 	return 1;
1058 }
1059 
1060 PLAN *
1061 c_print(option, argvp)
1062 	OPTION *option;
1063 	char ***argvp;
1064 {
1065 	isoutput = 1;
1066 
1067 	return palloc(option);
1068 }
1069 
1070 /*
1071  * -print0 functions --
1072  *
1073  *	Always true, causes the current pathame to be written to
1074  *	standard output followed by a NUL character
1075  */
1076 int
1077 f_print0(plan, entry)
1078 	PLAN *plan;
1079 	FTSENT *entry;
1080 {
1081 	fputs(entry->fts_path, stdout);
1082 	fputc('\0', stdout);
1083 	return 1;
1084 }
1085 
1086 /* c_print0 is the same as c_print */
1087 
1088 /*
1089  * -prune functions --
1090  *
1091  *	Prune a portion of the hierarchy.
1092  */
1093 int
1094 f_prune(plan, entry)
1095 	PLAN *plan;
1096 	FTSENT *entry;
1097 {
1098 	extern FTS *tree;
1099 
1100 	if (fts_set(tree, entry, FTS_SKIP))
1101 		err(1, "%s", entry->fts_path);
1102 	return 1;
1103 }
1104 
1105 /* c_prune == c_simple */
1106 
1107 /*
1108  * -regex functions --
1109  *
1110  *	True if the whole path of the file matches pattern using
1111  *	regular expression.
1112  */
1113 int
1114 f_regex(plan, entry)
1115 	PLAN *plan;
1116 	FTSENT *entry;
1117 {
1118 	char *str;
1119 	size_t len;
1120 	regex_t *pre;
1121 	regmatch_t pmatch;
1122 	int errcode;
1123 	char errbuf[LINE_MAX];
1124 	int matched;
1125 
1126 	pre = plan->re_data;
1127 	str = entry->fts_path;
1128 	len = strlen(str);
1129 	matched = 0;
1130 
1131 	pmatch.rm_so = 0;
1132 	pmatch.rm_eo = len;
1133 
1134 	errcode = regexec(pre, str, 1, &pmatch, REG_STARTEND);
1135 
1136 	if (errcode != 0 && errcode != REG_NOMATCH) {
1137 		regerror(errcode, pre, errbuf, sizeof errbuf);
1138 		errx(1, "%s: %s",
1139 		     plan->flags & F_IGNCASE ? "-iregex" : "-regex", errbuf);
1140 	}
1141 
1142 	if (errcode == 0 && pmatch.rm_so == 0 && pmatch.rm_eo == len)
1143 		matched = 1;
1144 
1145 	return matched;
1146 }
1147 
1148 PLAN *
1149 c_regex(option, argvp)
1150 	OPTION *option;
1151 	char ***argvp;
1152 {
1153 	PLAN *new;
1154 	char *pattern;
1155 	regex_t *pre;
1156 	int errcode;
1157 	char errbuf[LINE_MAX];
1158 
1159 	if ((pre = malloc(sizeof(regex_t))) == NULL)
1160 		err(1, NULL);
1161 
1162 	pattern = nextarg(option, argvp);
1163 
1164 	if ((errcode = regcomp(pre, pattern,
1165 	    regexp_flags | (option->flags & F_IGNCASE ? REG_ICASE : 0))) != 0) {
1166 		regerror(errcode, pre, errbuf, sizeof errbuf);
1167 		errx(1, "%s: %s: %s",
1168 		     option->flags & F_IGNCASE ? "-iregex" : "-regex",
1169 		     pattern, errbuf);
1170 	}
1171 
1172 	new = palloc(option);
1173 	new->re_data = pre;
1174 
1175 	return new;
1176 }
1177 
1178 /* c_simple covers c_prune, c_openparen, c_closeparen, c_not, c_or */
1179 
1180 PLAN *
1181 c_simple(option, argvp)
1182 	OPTION *option;
1183 	char ***argvp;
1184 {
1185 	return palloc(option);
1186 }
1187 
1188 /*
1189  * -size n[c] functions --
1190  *
1191  *	True if the file size in bytes, divided by an implementation defined
1192  *	value and rounded up to the next integer, is n.  If n is followed by
1193  *	a c, the size is in bytes.
1194  */
1195 #define	FIND_SIZE	512
1196 static int divsize = 1;
1197 
1198 int
1199 f_size(plan, entry)
1200 	PLAN *plan;
1201 	FTSENT *entry;
1202 {
1203 	off_t size;
1204 
1205 	size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
1206 	    FIND_SIZE : entry->fts_statp->st_size;
1207 	COMPARE(size, plan->o_data);
1208 }
1209 
1210 PLAN *
1211 c_size(option, argvp)
1212 	OPTION *option;
1213 	char ***argvp;
1214 {
1215 	char *size_str;
1216 	PLAN *new;
1217 	char endch;
1218 
1219 	size_str = nextarg(option, argvp);
1220 	ftsoptions &= ~FTS_NOSTAT;
1221 
1222 	new = palloc(option);
1223 	endch = 'c';
1224 	new->o_data = find_parsenum(new, option->name, size_str, &endch);
1225 	if (endch == 'c')
1226 		divsize = 0;
1227 	return new;
1228 }
1229 
1230 /*
1231  * -type c functions --
1232  *
1233  *	True if the type of the file is c, where c is b, c, d, p, f or w
1234  *	for block special file, character special file, directory, FIFO,
1235  *	regular file or whiteout respectively.
1236  */
1237 int
1238 f_type(plan, entry)
1239 	PLAN *plan;
1240 	FTSENT *entry;
1241 {
1242 	return (entry->fts_statp->st_mode & S_IFMT) == plan->m_data;
1243 }
1244 
1245 PLAN *
1246 c_type(option, argvp)
1247 	OPTION *option;
1248 	char ***argvp;
1249 {
1250 	char *typestring;
1251 	PLAN *new;
1252 	mode_t  mask;
1253 
1254 	typestring = nextarg(option, argvp);
1255 	ftsoptions &= ~FTS_NOSTAT;
1256 
1257 	switch (typestring[0]) {
1258 	case 'b':
1259 		mask = S_IFBLK;
1260 		break;
1261 	case 'c':
1262 		mask = S_IFCHR;
1263 		break;
1264 	case 'd':
1265 		mask = S_IFDIR;
1266 		break;
1267 	case 'f':
1268 		mask = S_IFREG;
1269 		break;
1270 	case 'l':
1271 		mask = S_IFLNK;
1272 		break;
1273 	case 'p':
1274 		mask = S_IFIFO;
1275 		break;
1276 	case 's':
1277 		mask = S_IFSOCK;
1278 		break;
1279 #ifdef FTS_WHITEOUT
1280 	case 'w':
1281 		mask = S_IFWHT;
1282 		ftsoptions |= FTS_WHITEOUT;
1283 		break;
1284 #endif /* FTS_WHITEOUT */
1285 	default:
1286 		errx(1, "%s: %s: unknown type", option->name, typestring);
1287 	}
1288 
1289 	new = palloc(option);
1290 	new->m_data = mask;
1291 	return new;
1292 }
1293 
1294 /*
1295  * -user uname functions --
1296  *
1297  *	True if the file belongs to the user uname.  If uname is numeric and
1298  *	an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
1299  *	return a valid user name, uname is taken as a user ID.
1300  */
1301 int
1302 f_user(plan, entry)
1303 	PLAN *plan;
1304 	FTSENT *entry;
1305 {
1306 	return entry->fts_statp->st_uid == plan->u_data;
1307 }
1308 
1309 PLAN *
1310 c_user(option, argvp)
1311 	OPTION *option;
1312 	char ***argvp;
1313 {
1314 	char *username;
1315 	PLAN *new;
1316 	struct passwd *p;
1317 	uid_t uid;
1318 
1319 	username = nextarg(option, argvp);
1320 	ftsoptions &= ~FTS_NOSTAT;
1321 
1322 	p = getpwnam(username);
1323 	if (p == NULL) {
1324 		uid = atoi(username);
1325 		if (uid == 0 && username[0] != '0')
1326 			errx(1, "%s: %s: no such user", option->name, username);
1327 	} else
1328 		uid = p->pw_uid;
1329 
1330 	new = palloc(option);
1331 	new->u_data = uid;
1332 	return new;
1333 }
1334 
1335 /*
1336  * -xdev functions --
1337  *
1338  *	Always true, causes find not to decend past directories that have a
1339  *	different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
1340  */
1341 PLAN *
1342 c_xdev(option, argvp)
1343 	OPTION *option;
1344 	char ***argvp;
1345 {
1346 	ftsoptions |= FTS_XDEV;
1347 
1348 	return palloc(option);
1349 }
1350 
1351 /*
1352  * ( expression ) functions --
1353  *
1354  *	True if expression is true.
1355  */
1356 int
1357 f_expr(plan, entry)
1358 	PLAN *plan;
1359 	FTSENT *entry;
1360 {
1361 	register PLAN *p;
1362 	register int state = 0;
1363 
1364 	for (p = plan->p_data[0];
1365 	    p && (state = (p->execute)(p, entry)); p = p->next);
1366 	return state;
1367 }
1368 
1369 /*
1370  * f_openparen and f_closeparen nodes are temporary place markers.  They are
1371  * eliminated during phase 2 of find_formplan() --- the '(' node is converted
1372  * to a f_expr node containing the expression and the ')' node is discarded.
1373  * The functions themselves are only used as constants.
1374  */
1375 
1376 int
1377 f_openparen(plan, entry)
1378 	PLAN *plan;
1379 	FTSENT *entry;
1380 {
1381 	abort();
1382 }
1383 
1384 int
1385 f_closeparen(plan, entry)
1386 	PLAN *plan;
1387 	FTSENT *entry;
1388 {
1389 	abort();
1390 }
1391 
1392 /* c_openparen == c_simple */
1393 /* c_closeparen == c_simple */
1394 
1395 /*
1396  * AND operator. Since AND is implicit, no node is allocated.
1397  */
1398 PLAN *
1399 c_and(option, argvp)
1400 	OPTION *option;
1401 	char ***argvp;
1402 {
1403 	return NULL;
1404 }
1405 
1406 /*
1407  * ! expression functions --
1408  *
1409  *	Negation of a primary; the unary NOT operator.
1410  */
1411 int
1412 f_not(plan, entry)
1413 	PLAN *plan;
1414 	FTSENT *entry;
1415 {
1416 	register PLAN *p;
1417 	register int state = 0;
1418 
1419 	for (p = plan->p_data[0];
1420 	    p && (state = (p->execute)(p, entry)); p = p->next);
1421 	return !state;
1422 }
1423 
1424 /* c_not == c_simple */
1425 
1426 /*
1427  * expression -o expression functions --
1428  *
1429  *	Alternation of primaries; the OR operator.  The second expression is
1430  * not evaluated if the first expression is true.
1431  */
1432 int
1433 f_or(plan, entry)
1434 	PLAN *plan;
1435 	FTSENT *entry;
1436 {
1437 	register PLAN *p;
1438 	register int state = 0;
1439 
1440 	for (p = plan->p_data[0];
1441 	    p && (state = (p->execute)(p, entry)); p = p->next);
1442 
1443 	if (state)
1444 		return 1;
1445 
1446 	for (p = plan->p_data[1];
1447 	    p && (state = (p->execute)(p, entry)); p = p->next);
1448 	return state;
1449 }
1450 
1451 /* c_or == c_simple */
1452