xref: /original-bsd/usr.bin/find/function.c (revision 188f7363)
1 /*-
2  * Copyright (c) 1990 The Regents of the University of California.
3  * 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  * %sccs.include.redist.c%
9  */
10 
11 #ifndef lint
12 static char sccsid[] = "@(#)function.c	5.1 (Berkeley) 04/16/90";
13 #endif /* not lint */
14 
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <sys/wait.h>
18 #include <sys/mount.h>
19 #include <grp.h>
20 #include <pwd.h>
21 #include <fts.h>
22 #include <unistd.h>
23 #include <tzfile.h>
24 #include <stdio.h>
25 #include "find.h"
26 
27 #define	FIND_EQUAL	0
28 #define	FIND_LESSTHAN	1
29 #define	FIND_GREATER	2
30 
31 #define	FIND_FALSE	0
32 #define	FIND_TRUE	1
33 
34 #define	COMPARE(a, b) { \
35 	switch(plan->flags) { \
36 	case FIND_EQUAL: \
37 		return(a == b); \
38 	case FIND_LESSTHAN: \
39 		return(a < b); \
40 	case FIND_GREATER: \
41 		return(a > b); \
42 	} \
43 	return(FIND_FALSE); \
44 }
45 
46 #define NEW(t, f) { \
47 	new = (PLAN *)emalloc(sizeof(PLAN)); \
48 	new->type = t; \
49 	new->eval = f; \
50 	new->flags = 0; \
51 	new->next = NULL; \
52 }
53 
54 /*
55  * find_parsenum --
56  *	Parse a string of the form [+-]# and return the value.
57  */
58 long
59 find_parsenum(plan, option, str, endch)
60 	PLAN *plan;
61 	char *option, *str, *endch;
62 {
63 	long value;
64 	char *endchar;		/* pointer to character ending conversion */
65 
66 	/* determine comparison from leading + or - */
67 	switch(*str) {
68 	case '+':
69 		++str;
70 		plan->flags = FIND_GREATER;
71 		break;
72 	case '-':
73 		++str;
74 		plan->flags = FIND_LESSTHAN;
75 		break;
76 	default:
77 		plan->flags = FIND_EQUAL;
78 		break;
79 	}
80 
81 	/*
82 	 * convert the string with strtol().  Note, if strtol() returns zero
83 	 * and endchar points to the beginning of the string we know we have
84 	 * a syntax error.
85 	 */
86 	value = strtol(str, &endchar, 10);
87 	if (!value && endchar == str ||
88 	    endchar[0] && (!endch || endchar[0] != *endch))
89 		bad_arg(option, "illegal numeric value");
90 	if (endch)
91 		*endch = endchar[0];
92 	return(value);
93 }
94 
95 /*
96  * -atime n functions --
97  *
98  *	True if the difference between the file access time and the
99  *	current time is n 24 hour periods.
100  *
101  */
102 f_atime(plan, entry)
103 	PLAN *plan;
104 	FTSENT *entry;
105 {
106 	extern time_t now;
107 
108 	COMPARE((now - entry->statb.st_atime + SECSPERDAY - 1) / SECSPERDAY,
109 	    plan->t_data);
110 }
111 
112 PLAN *
113 c_atime(arg)
114 	char *arg;
115 {
116 	PLAN *new;
117 
118 	ftsoptions &= ~FTS_NOSTAT;
119 
120 	NEW(T_ATIME, f_atime);
121 	new->t_data = find_parsenum(new, "-atime", arg, (char *)NULL);
122 	return(new);
123 }
124 /*
125  * -ctime n functions --
126  *
127  *	True if the difference between the last change of file
128  *	status information and the current time is n 24 hour periods.
129  */
130 f_ctime(plan, entry)
131 	PLAN *plan;
132 	FTSENT *entry;
133 {
134 	extern time_t now;
135 
136 	COMPARE((now - entry->statb.st_ctime + SECSPERDAY - 1) / SECSPERDAY,
137 	    plan->t_data);
138 }
139 
140 PLAN *
141 c_ctime(arg)
142 	char *arg;
143 {
144 	PLAN *new;
145 
146 	ftsoptions &= ~FTS_NOSTAT;
147 
148 	NEW(T_CTIME, f_ctime);
149 	new->t_data = find_parsenum(new, "-ctime", arg, (char *)NULL);
150 	return(new);
151 }
152 
153 /*
154  * -depth functions --
155  *
156  *	Always true, causes descent of the directory hierarchy to be done
157  *	so that all entries in a directory are acted on before the directory
158  *	itself.
159  */
160 /* ARGSUSED */
161 f_always_true(plan, entry)
162 	PLAN *plan;
163 	FTSENT *entry;
164 {
165 	return(FIND_TRUE);
166 }
167 
168 PLAN *
169 c_depth()
170 {
171 	extern int depth;
172 	PLAN *new;
173 
174 	depth = 1;
175 
176 	NEW(T_DEPTH, f_always_true);
177 	return(new);
178 }
179 
180 /*
181  * [-exec | -ok] utility [arg ... ] ; functions --
182  *
183  *	True if the executed utility returns a zero value as exit status.
184  *	The end of the primary expression is delimited by a semicolon.  If
185  *	"{}" occurs anywhere, it gets replaced by the current pathname.
186  *	The current directory for the execution of utility is the same as
187  *	the current directory when the find utility was started.
188  *
189  *	The primary -ok is different in that it requests affirmation of the
190  *	user before executing the utility.
191  */
192 f_exec(plan, entry)
193 	register PLAN *plan;
194 	FTSENT *entry;
195 {
196 	register int cnt;
197 	char *find_subst();
198 	union wait pstat;
199 	pid_t pid, waitpid();
200 
201 	for (cnt = 0; plan->e_argv[cnt]; ++cnt)
202 		if (plan->e_len[cnt])
203 			find_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
204 			    entry->path, plan->e_len[cnt]);
205 
206 	if (plan->flags && !find_queryuser(plan->e_argv))
207 		return(FIND_FALSE);
208 
209 	switch(pid = vfork()) {
210 	case -1:
211 		(void)fprintf(stderr, "find: fork: %s.\n", strerror(errno));
212 		exit(1);
213 		/* NOTREACHED */
214 	case 0:
215 		execvp(plan->e_argv[0], plan->e_argv);
216 		(void)fprintf(stderr,
217 		    "find: %s: %s.\n", plan->e_argv[0], strerror(errno));
218 		exit(1);
219 		/* NOTREACHED */
220 	}
221 	pid = waitpid(pid, &pstat, 0);
222 	return(pid == -1 || pstat.w_status ? FIND_FALSE : FIND_TRUE);
223 }
224 
225 /*
226  * c_exec --
227  *	build three parallel arrays, one with pointers to the strings passed
228  *	on the command line, one with (possibly duplicated) pointers to the
229  *	argv array, and one with integer values that are lengths of the
230  *	strings, but also flags meaning that the string has to be massaged.
231  */
232 PLAN *
233 c_exec(argvp, isok)
234 	char ***argvp;
235 	int isok;
236 {
237 	PLAN *new;			/* node returned */
238 	register int cnt;
239 	register char **argv, **ap, *p;
240 
241 	ftsoptions |= FTS_NOCHDIR;
242 	output_specified = 1;
243 
244 	NEW(T_EXEC, f_exec);
245 	new->flags = isok;
246 
247 	for (ap = argv = *argvp;; ++ap) {
248 		if (!*ap)
249 			bad_arg(isok ? "-ok" : "-exec", "no terminating \";\"");
250 		if (**ap == ';')
251 			break;
252 	}
253 
254 	cnt = ap - *argvp + 1;
255 	new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
256 	new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
257 	new->e_len = (int *)emalloc((u_int)cnt * sizeof(u_char));
258 
259 	for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
260 		new->e_orig[cnt] = *argv;
261 		for (p = *argv; *p; ++p)
262 			if (p[0] == '{' && p[1] == '}') {
263 				new->e_argv[cnt] = emalloc((u_int)1024);
264 				new->e_len[cnt] = 1024;
265 				break;
266 			}
267 		if (!*p) {
268 			new->e_argv[cnt] = *argv;
269 			new->e_len[cnt] = 0;
270 		}
271 	}
272 	new->e_argv[cnt] = new->e_orig[cnt] = NULL;
273 
274 	*argvp = argv + 1;
275 	return(new);
276 }
277 
278 /*
279  * -follow functions --
280  *
281  *	Always true, causes symbolic links to be followed on a global
282  *	basis.
283  */
284 PLAN *
285 c_follow()
286 {
287 	PLAN *new;
288 
289 	ftsoptions &= ~FTS_PHYSICAL;
290 	ftsoptions |= FTS_LOGICAL;
291 
292 	NEW(T_FOLLOW, f_always_true);
293 	return(new);
294 }
295 
296 /*
297  * -fstype functions --
298  *
299  *	True if the file is of a certain type.
300  */
301 f_fstype(plan, entry)
302 	PLAN *plan;
303 	FTSENT *entry;
304 {
305 	extern dev_t curdev;
306 	struct statfs sb;
307 	static short curtype;
308 
309 	/* only check when we cross mount point */
310 	if (curdev != entry->statb.st_dev) {
311 		if (statfs(entry->name, &sb)) {
312 			(void)fprintf(stderr, "find: %s: %s.\n",
313 			    entry->name, strerror(errno));
314 			exit(1);
315 		}
316 		curtype = sb.f_type;
317 	}
318 	return(plan->flags == curtype);
319 }
320 
321 PLAN *
322 c_fstype(arg)
323 	char *arg;
324 {
325 	register PLAN *new;
326 
327 	ftsoptions &= ~FTS_NOSTAT;
328 
329 	NEW(T_FSTYPE, f_fstype);
330 	switch(*arg) {
331 	case 'm':
332 		if (!strcmp(arg, "mfs")) {
333 			new->flags = MOUNT_MFS;
334 			return(new);
335 		}
336 		break;
337 	case 'n':
338 		if (!strcmp(arg, "nfs")) {
339 			new->flags = MOUNT_NFS;
340 			return(new);
341 		}
342 		break;
343 	case 'p':
344 		if (!strcmp(arg, "pc")) {
345 			new->flags = MOUNT_PC;
346 			return(new);
347 		}
348 		break;
349 	case 'u':
350 		if (!strcmp(arg, "ufs")) {
351 			new->flags = MOUNT_UFS;
352 			return(new);
353 		}
354 		break;
355 	}
356 	(void)fprintf(stderr, "find: unknown file type %s.\n", arg);
357 	exit(1);
358 	/* NOTREACHED */
359 }
360 
361 /*
362  * -group gname functions --
363  *
364  *	True if the file belongs to the group gname.  If gname is numeric and
365  *	an equivalent of the getgrnam() function does not return a valid group
366  *	name, gname is taken as a group ID.
367  */
368 f_group(plan, entry)
369 	PLAN *plan;
370 	FTSENT *entry;
371 {
372 	return(entry->statb.st_gid == plan->g_data ? FIND_TRUE : FIND_FALSE);
373 }
374 
375 PLAN *
376 c_group(gname)
377 	char *gname;
378 {
379 	PLAN *new;
380 	struct group *g;
381 	gid_t gid;
382 
383 	ftsoptions &= ~FTS_NOSTAT;
384 
385 	g = getgrnam(gname);
386 	if (g == NULL) {
387 		gid = atoi(gname);
388 		if (gid == 0 && gname[0] != '0')
389 			bad_arg("-group", "no such group");
390 	} else
391 		gid = g->gr_gid;
392 
393 	NEW(T_GROUP, f_group);
394 	new->g_data = gid;
395 	return(new);
396 }
397 
398 /*
399  * -inum n functions --
400  *
401  *	True if the file has inode # n.
402  */
403 f_inum(plan, entry)
404 	PLAN *plan;
405 	FTSENT *entry;
406 {
407 	COMPARE(entry->statb.st_ino, plan->i_data);
408 }
409 
410 PLAN *
411 c_inum(arg)
412 	char *arg;
413 {
414 	PLAN *new;
415 
416 	ftsoptions &= ~FTS_NOSTAT;
417 
418 	NEW(T_INUM, f_inum);
419 	new->i_data = find_parsenum(new, "-inum", arg, (char *)NULL);
420 	return(new);
421 }
422 
423 /*
424  * -links n functions --
425  *
426  *	True if the file has n links.
427  */
428 f_links(plan, entry)
429 	PLAN *plan;
430 	FTSENT *entry;
431 {
432 	COMPARE(entry->statb.st_nlink, plan->l_data);
433 }
434 
435 PLAN *
436 c_links(arg)
437 	char *arg;
438 {
439 	PLAN *new;
440 
441 	ftsoptions &= ~FTS_NOSTAT;
442 
443 	NEW(T_LINKS, f_links);
444 	new->l_data = find_parsenum(new, "-links", arg, (char *)NULL);
445 	return(new);
446 }
447 
448 /*
449  * -ls functions --
450  *
451  *	Always true - prints the current entry to stdout in "ls" format.
452  */
453 /* ARGSUSED */
454 f_ls(plan, entry)
455 	PLAN *plan;
456 	FTSENT *entry;
457 {
458 	printlong(entry->path, entry->accpath, &entry->statb);
459 	return(FIND_TRUE);
460 }
461 
462 PLAN *
463 c_ls()
464 {
465 	PLAN *new;
466 
467 	ftsoptions &= ~FTS_NOSTAT;
468 	output_specified = 1;
469 
470 	NEW(T_LS, f_ls);
471 	return(new);
472 }
473 
474 /*
475  * -name functions --
476  *
477  *	True if the basename of the filename being examined
478  *	matches pattern using Pattern Matching Notation S3.14
479  */
480 f_name(plan, entry)
481 	PLAN *plan;
482 	FTSENT *entry;
483 {
484 	return(fnmatch(plan->c_data, entry->name, FNM_QUOTE) ?
485 	    FIND_TRUE : FIND_FALSE);
486 }
487 
488 PLAN *
489 c_name(pattern)
490 	char *pattern;
491 {
492 	PLAN *new;
493 
494 	NEW(T_NAME, f_name);
495 	new->c_data = pattern;
496 	return(new);
497 }
498 
499 /*
500  * -newer file functions --
501  *
502  *	True if the current file has been modified more recently
503  *	then the modification time of the file named by the pathname
504  *	file.
505  */
506 f_newer(plan, entry)
507 	PLAN *plan;
508 	FTSENT *entry;
509 {
510 	return(entry->statb.st_mtime > plan->t_data ? FIND_TRUE : FIND_FALSE);
511 }
512 
513 PLAN *
514 c_newer(filename)
515 	char *filename;
516 {
517 	PLAN *new;
518 	struct stat sb;
519 
520 	ftsoptions &= ~FTS_NOSTAT;
521 
522 	if (stat(filename, &sb)) {
523 		(void)fprintf(stderr, "find: %s: %s.\n",
524 		    filename, strerror(errno));
525 		exit(1);
526 	}
527 	NEW(T_NEWER, f_newer);
528 	new->t_data = sb.st_mtime;
529 	return(new);
530 }
531 
532 /*
533  * -nogroup functions --
534  *
535  *	True if file belongs to a user ID for which the equivalent
536  *	of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
537  */
538 /* ARGSUSED */
539 f_nogroup(plan, entry)
540 	PLAN *plan;
541 	FTSENT *entry;
542 {
543 	return(group_from_gid(entry->statb.st_gid, 1) ? FIND_FALSE : FIND_TRUE);
544 }
545 
546 PLAN *
547 c_nogroup()
548 {
549 	PLAN *new;
550 
551 	ftsoptions &= ~FTS_NOSTAT;
552 
553 	NEW(T_NOGROUP, f_nogroup);
554 	return(new);
555 }
556 
557 /*
558  * -nouser functions --
559  *
560  *	True if file belongs to a user ID for which the equivalent
561  *	of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
562  */
563 /* ARGSUSED */
564 f_nouser(plan, entry)
565 	PLAN *plan;
566 	FTSENT *entry;
567 {
568 	return(user_from_uid(entry->statb.st_uid, 1) ? FIND_FALSE : FIND_TRUE);
569 }
570 
571 PLAN *
572 c_nouser()
573 {
574 	PLAN *new;
575 
576 	ftsoptions &= ~FTS_NOSTAT;
577 
578 	NEW(T_NOUSER, f_nouser);
579 	return(new);
580 }
581 
582 /*
583  * -perm functions --
584  *
585  *	The mode argument is used to represent file mode bits.  If it starts
586  *	with a leading digit, it's treated as an octal mode, otherwise as a
587  *	symbolic mode.
588  */
589 f_perm(plan, entry)
590 	PLAN *plan;
591 	FTSENT *entry;
592 {
593 	mode_t mode;
594 
595 	mode = entry->statb.st_mode &
596 	    (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
597 	if (plan->flags)
598 		return((plan->m_data | mode) == mode);
599 	else
600 		return(mode == plan->m_data);
601 	/* NOTREACHED */
602 }
603 
604 PLAN *
605 c_perm(perm)
606 	char *perm;
607 {
608 	PLAN *new;
609 
610 	ftsoptions &= ~FTS_NOSTAT;
611 
612 	NEW(T_PERM, f_perm);
613 
614 	if (*perm == '-') {
615 		new->flags = 1;
616 		++perm;
617 	}
618 
619 	if (setmode(perm))
620 		bad_arg("-perm", "illegal mode string");
621 
622 	new->m_data = getmode(0);
623 	return(new);
624 }
625 
626 /*
627  * -print functions --
628  *
629  *	Always true, causes the current pathame to be written to
630  *	standard output.
631  */
632 /* ARGSUSED */
633 f_print(plan, entry)
634 	PLAN *plan;
635 	FTSENT *entry;
636 {
637 	(void)printf("%s\n", entry->path);
638 	return(FIND_TRUE);
639 }
640 
641 PLAN *
642 c_print()
643 {
644 	PLAN *new;
645 
646 	output_specified = 1;
647 
648 	NEW(T_PRINT, f_print);
649 	return(new);
650 }
651 
652 /*
653  * -prune functions --
654  *
655  *	Prune a portion of the hierarchy.
656  */
657 /* ARGSUSED */
658 f_prune(plan, entry)
659 	PLAN *plan;
660 	FTSENT *entry;
661 {
662 	extern FTS *tree;
663 
664 	if (ftsset(tree, entry, FTS_SKIP)) {
665 		(void)fprintf(stderr,
666 		    "find: %s: %s.\n", entry->path, strerror(errno));
667 		exit(1);
668 	}
669 	return(FIND_TRUE);
670 }
671 
672 PLAN *
673 c_prune()
674 {
675 	PLAN *new;
676 
677 	NEW(T_PRUNE, f_prune);
678 	return(new);
679 }
680 
681 /*
682  * -size n[c] functions --
683  *
684  *	True if the file size in bytes, divided by an implementation defined
685  *	value and rounded up to the next integer, is n.  If n is followed by
686  *	a c, the size is in bytes.
687  */
688 #define	FIND_SIZE	512
689 static int divsize = 1;
690 
691 f_size(plan, entry)
692 	PLAN *plan;
693 	FTSENT *entry;
694 {
695 	off_t size;
696 
697 	size = divsize ?
698 	    (entry->statb.st_size + FIND_SIZE - 1) / FIND_SIZE :
699 	    entry->statb.st_size;
700 	COMPARE(size, plan->o_data);
701 }
702 
703 PLAN *
704 c_size(arg)
705 	char *arg;
706 {
707 	PLAN *new;
708 	char endch;
709 
710 	ftsoptions &= ~FTS_NOSTAT;
711 
712 	NEW(T_SIZE, f_size);
713 	new->o_data = find_parsenum(new, "-size", arg, &endch);
714 	if (endch == 'c')
715 		divsize = 0;
716 	return(new);
717 }
718 
719 /*
720  * -type c functions --
721  *
722  *	True if the type of the file is c, where c is b, c, d, p, or f for
723  *	block special file, character special file, directory, FIFO, or
724  *	regular file, respectively.
725  */
726 f_type(plan, entry)
727 	PLAN *plan;
728 	FTSENT *entry;
729 {
730 	return(entry->statb.st_mode & plan->m_data ? FIND_TRUE : FIND_FALSE);
731 }
732 
733 PLAN *
734 c_type(typestring)
735 	char *typestring;
736 {
737 	PLAN *new;
738 	mode_t  mask;
739 
740 	ftsoptions &= ~FTS_NOSTAT;
741 
742 	switch (typestring[0]) {
743 	case 'b':
744 		mask = S_IFBLK;
745 		break;
746 	case 'c':
747 		mask = S_IFCHR;
748 		break;
749 	case 'd':
750 		mask = S_IFDIR;
751 		break;
752 	case 'f':
753 		mask = S_IFREG;
754 		break;
755 	case 'l':
756 		mask = S_IFLNK;
757 		break;
758 	case 'p':
759 		mask = S_IFIFO;
760 		break;
761 	case 's':
762 		mask = S_IFSOCK;
763 		break;
764 	default:
765 		bad_arg("-type", "unknown type");
766 	}
767 
768 	NEW(T_TYPE, f_type);
769 	new->m_data = mask;
770 	return(new);
771 }
772 
773 /*
774  * -user uname functions --
775  *
776  *	True if the file belongs to the user uname.  If uname is numeric and
777  *	an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
778  *	return a valid user name, uname is taken as a user ID.
779  */
780 f_user(plan, entry)
781 	PLAN *plan;
782 	FTSENT *entry;
783 {
784 	return(entry->statb.st_uid == plan->u_data ? FIND_TRUE : FIND_FALSE);
785 }
786 
787 PLAN *
788 c_user(username)
789 	char *username;
790 {
791 	PLAN *new;
792 	struct passwd *p;
793 	uid_t uid;
794 
795 	ftsoptions &= ~FTS_NOSTAT;
796 
797 	p = getpwnam(username);
798 	if (p == NULL) {
799 		uid = atoi(username);
800 		if (uid == 0 && username[0] != '0')
801 			bad_arg("-user", "no such user");
802 	} else
803 		uid = p->pw_uid;
804 
805 	NEW(T_USER, f_user);
806 	new->u_data = uid;
807 	return(new);
808 }
809 
810 /*
811  * -xdev functions --
812  *
813  *	Always true, causes find not to decend past directories that have a
814  *	different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
815  *
816  *	Note: this checking is done in find_execute().
817  */
818 PLAN *
819 c_xdev()
820 {
821 	extern int xdev;
822 	PLAN *new;
823 
824 	xdev = 1;
825 	ftsoptions &= ~FTS_NOSTAT;
826 
827 	NEW(T_XDEV, f_always_true);
828 	return(new);
829 }
830 
831 /*
832  * ( expression ) functions --
833  *
834  *	True if expression is true.
835  */
836 f_expr(plan, entry)
837 	PLAN *plan;
838 	FTSENT *entry;
839 {
840 	register PLAN *p;
841 	register int state;
842 
843 	for (p = plan->p_data[0];
844 	    p && (state = (p->eval)(p, entry)); p = p->next);
845 	return(state);
846 }
847 
848 /*
849  * T_OPENPAREN and T_CLOSEPAREN nodes are temporary place markers.  They are
850  * eliminated during phase 2 of find_formplan() --- the '(' node is converted
851  * to a T_EXPR node containing the expression and the ')' node is discarded.
852  */
853 PLAN *
854 c_openparen()
855 {
856 	PLAN *new;
857 
858 	NEW(T_OPENPAREN, (int (*)())-1);
859 	return(new);
860 }
861 
862 PLAN *
863 c_closeparen()
864 {
865 	PLAN *new;
866 
867 	NEW(T_CLOSEPAREN, (int (*)())-1);
868 	return(new);
869 }
870 
871 /*
872  * -mtime n functions --
873  *
874  *	True if the difference between the file modification time and the
875  *	current time is n 24 hour periods.
876  */
877 f_mtime(plan, entry)
878 	PLAN *plan;
879 	FTSENT *entry;
880 {
881 	extern time_t now;
882 
883 	COMPARE((now - entry->statb.st_mtime + SECSPERDAY - 1) / SECSPERDAY,
884 	    plan->t_data);
885 }
886 
887 PLAN *
888 c_mtime(arg)
889 	char *arg;
890 {
891 	PLAN *new;
892 
893 	ftsoptions &= ~FTS_NOSTAT;
894 
895 	NEW(T_MTIME, f_mtime);
896 	new->t_data = find_parsenum(new, "-mtime", arg, (char *)NULL);
897 	return(new);
898 }
899 
900 /*
901  * ! expression functions --
902  *
903  *	Negation of a primary; the unary NOT operator.
904  */
905 f_not(plan, entry)
906 	PLAN *plan;
907 	FTSENT *entry;
908 {
909 	register PLAN *p;
910 	register int state;
911 
912 	for (p = plan->p_data[0];
913 	    p && (state = (p->eval)(p, entry)); p = p->next);
914 	return(!state);
915 }
916 
917 PLAN *
918 c_not()
919 {
920 	PLAN *new;
921 
922 	NEW(T_NOT, f_not);
923 	return(new);
924 }
925 
926 /*
927  * expression -o expression functions --
928  *
929  *	Alternation of primaries; the OR operator.  The second expression is
930  * not evaluated if the first expression is true.
931  */
932 f_or(plan, entry)
933 	PLAN *plan;
934 	FTSENT *entry;
935 {
936 	register PLAN *p;
937 	register int state;
938 
939 	for (p = plan->p_data[0];
940 	    p && (state = (p->eval)(p, entry)); p = p->next);
941 
942 	if (state)
943 		return(FIND_TRUE);
944 
945 	for (p = plan->p_data[1];
946 	    p && (state = (p->eval)(p, entry)); p = p->next);
947 	return(state);
948 }
949 
950 PLAN *
951 c_or()
952 {
953 	PLAN *new;
954 
955 	NEW(T_OR, f_or);
956 	return(new);
957 }
958