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