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