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