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