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