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