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