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