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