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