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