xref: /illumos-gate/usr/src/cmd/find/find.c (revision 7c478bd9)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
30 /*	  All Rights Reserved  	*/
31 
32 
33 /*	Parts of this product may be derived from		*/
34 /*	Mortice Kern Systems Inc. and Berkeley 4.3 BSD systems.	*/
35 /*	licensed from  Mortice Kern Systems Inc. and 		*/
36 /*	the University of California.				*/
37 
38 /*
39  * Copyright 1985, 1990 by Mortice Kern Systems Inc.  All rights reserved.
40  */
41 
42 #include <stdio.h>
43 #include <errno.h>
44 #include <pwd.h>
45 #include <grp.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <sys/param.h>
49 #include <sys/acl.h>
50 #include <limits.h>
51 #include <unistd.h>
52 #include <stdlib.h>
53 #include <locale.h>
54 #include <string.h>
55 #include <strings.h>
56 #include <libgen.h>
57 #include <ctype.h>
58 #include <wait.h>
59 #include <fnmatch.h>
60 #include <langinfo.h>
61 #include <ftw.h>
62 
63 
64 #define	A_DAY		(long)(60*60*24)	/* a day full of seconds */
65 #define	BLKSIZ		512
66 #define	round(x, s)	(((x)+(s)-1)&~((s)-1))
67 #ifndef FTW_SLN
68 #define	FTW_SLN		7
69 #endif
70 #define	LINEBUF_SIZE		LINE_MAX	/* input or output lines */
71 #define	REMOTE_FS		"/etc/dfs/fstypes"
72 #define	N_FSTYPES		20
73 
74 /*
75  * This is the list of operations
76  * F_USER and F_GROUP are named to avoid conflict with USER and GROUP defined
77  * in sys/acl.h
78  */
79 
80 enum Command
81 {
82 	PRINT, DEPTH, LOCAL, MOUNT, ATIME, MTIME, CTIME, NEWER,
83 	NAME, F_USER, F_GROUP, INUM, SIZE, LINKS, PERM, EXEC, OK, CPIO, NCPIO,
84 	TYPE, AND, OR, NOT, LPAREN, RPAREN, CSIZE, VARARGS, FOLLOW,
85 	PRUNE, NOUSER, NOGRP, FSTYPE, LS, XATTR, ACL
86 };
87 
88 enum Type
89 {
90 	Unary, Id, Num, Str, Exec, Cpio, Op
91 };
92 
93 struct Args
94 {
95 	char		name[10];
96 	enum Command	action;
97 	enum Type	type;
98 };
99 
100 /*
101  * Except for pathnames, these are the only legal arguments
102  */
103 static struct Args commands[] =
104 {
105 	"!",		NOT,	Op,
106 	"(",		LPAREN,	Unary,
107 	")",		RPAREN,	Unary,
108 	"-a",		AND,	Op,
109 	"-atime",	ATIME,	Num,
110 	"-cpio",	CPIO,	Cpio,
111 	"-ctime",	CTIME,	Num,
112 	"-depth",	DEPTH,	Unary,
113 	"-exec",	EXEC,	Exec,
114 	"-follow",	FOLLOW, Unary,
115 	"-group",	F_GROUP,	Num,
116 	"-inum",	INUM,	Num,
117 	"-links",	LINKS,	Num,
118 	"-local",	LOCAL,	Unary,
119 	"-mount",	MOUNT,	Unary,
120 	"-mtime",	MTIME,	Num,
121 	"-name",	NAME,	Str,
122 	"-ncpio",	NCPIO,  Cpio,
123 	"-newer",	NEWER,	Str,
124 	"-o",		OR,	Op,
125 	"-ok",		OK,	Exec,
126 	"-perm",	PERM,	Num,
127 	"-print",	PRINT,	Unary,
128 	"-size",	SIZE,	Num,
129 	"-type",	TYPE,	Num,
130 	"-xdev",	MOUNT,	Unary,
131 	"-user",	F_USER,	Num,
132 	"-prune",	PRUNE,	Unary,
133 	"-nouser",	NOUSER,	Unary,
134 	"-nogroup",	NOGRP,	Unary,
135 	"-fstype",	FSTYPE,	Str,
136 	"-ls",		LS,	Unary,
137 	"-xattr",	XATTR,	Unary,
138 	"-acl",		ACL,	Unary,
139 	NULL,		0,	0
140 };
141 
142 union Item
143 {
144 	struct Node	*np;
145 	struct Arglist	*vp;
146 	time_t		t;
147 	char		*cp;
148 	char		**ap;
149 	long		l;
150 	int		i;
151 	long long	ll;
152 };
153 
154 struct Node
155 {
156 	struct Node	*next;
157 	enum Command	action;
158 	enum Type	type;
159 	union Item	first;
160 	union Item	second;
161 };
162 
163 /* if no -print, -exec or -ok replace "expression" with "(expression) -print" */
164 static	struct	Node PRINT_NODE = { 0, PRINT, 0, 0};
165 static	struct	Node LPAREN_NODE = { 0, LPAREN, 0, 0};
166 
167 
168 /*
169  * Prototype variable size arglist buffer
170  */
171 
172 struct Arglist
173 {
174 	struct Arglist	*next;
175 	char		*end;
176 	char		*nextstr;
177 	char		**firstvar;
178 	char		**nextvar;
179 	char		*arglist[1];
180 };
181 
182 
183 static int		compile();
184 static int		execute();
185 static int		doexec();
186 static struct Args	*lookup();
187 static int		ok();
188 static void		usage();
189 static struct Arglist	*varargs();
190 static int		list();
191 static char		*getgroup();
192 static FILE		*cmdopen();
193 static int		cmdclose();
194 static char		*getshell();
195 static void 		init_remote_fs();
196 static char		*getname();
197 static int		readmode();
198 static mode_t		getmode();
199 static char		*gettail();
200 
201 
202 static int		walkflags = FTW_CHDIR|FTW_PHYS|FTW_ANYERR;
203 static struct Node	*savetnode;
204 static struct Node	*topnode;
205 static struct Node	*freenode;	/* next free node we may use later */
206 static char		*cpio[] = { "cpio", "-o", 0 };
207 static char		*ncpio[] = { "cpio", "-oc", 0 };
208 static char		*cpiol[] = { "cpio", "-oL", 0 };
209 static char		*ncpiol[] = { "cpio", "-ocL", 0 };
210 static time_t		now;
211 static FILE		*output;
212 static char		*dummyarg = (char *)-1;
213 static int		lastval;
214 static int		varsize;
215 static struct Arglist	*lastlist;
216 static char		*cmdname;
217 static char		*remote_fstypes[N_FSTYPES+1];
218 static int		fstype_index = 0;
219 static int		action_expression = 0;	/* -print, -exec, or -ok */
220 static int		error = 0;
221 static int		paren_cnt = 0;	/* keeps track of parentheses */
222 static int		hflag = 0;
223 static int		lflag = 0;
224 static int		varexecrc = 0;
225 extern char		**environ;
226 
227 int
228 main(int argc, char **argv)
229 {
230 	char *cp;
231 	int c;
232 	int paths;
233 	char *cwdpath;
234 
235 	(void) setlocale(LC_ALL, "");
236 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
237 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
238 #endif
239 	(void) textdomain(TEXT_DOMAIN);
240 
241 	cmdname = argv[0];
242 	if (time(&now) == (time_t)(-1)) {
243 		(void) fprintf(stderr, gettext("%s: time() %s\n"),
244 			cmdname, strerror(errno));
245 		exit(1);
246 	}
247 	while ((c = getopt(argc, argv, "HL")) != -1) {
248 		switch (c) {
249 		case 'H':
250 			hflag = 1;
251 			lflag = 0;
252 			break;
253 		case 'L':
254 			hflag = 0;
255 			lflag = 1;
256 			break;
257 		case '?':
258 			usage();
259 			break;
260 		}
261 	}
262 
263 	argc -= optind;
264 	argv += optind;
265 
266 	if (argc < 1) {
267 		(void) fprintf(stderr,
268 		    gettext("%s: insufficient number of arguments\n"), cmdname);
269 		usage();
270 	}
271 
272 	for (paths = 0; (cp = argv[paths]) != 0; ++paths) {
273 		if (*cp == '-')
274 			break;
275 		else if ((*cp == '!' || *cp == '(') && *(cp+1) == 0)
276 			break;
277 	}
278 
279 	if (paths == 0) /* no path-list */
280 		usage();
281 
282 	output = stdout;
283 
284 	/* lflag is the same as -follow */
285 	if (lflag)
286 		walkflags &= ~FTW_PHYS;
287 
288 	/* allocate enough space for the compiler */
289 	topnode = malloc((argc + 1) * sizeof (struct Node));
290 	savetnode = malloc((argc + 1) * sizeof (struct Node));
291 	(void) memset(topnode, 0, (argc + 1) * sizeof (struct Node));
292 	(void) memset(savetnode, 0, (argc + 1) * sizeof (struct Node));
293 
294 	if (compile(argv + paths, topnode, &action_expression) == 0) {
295 		/* no expression, default to -print */
296 		(void) memcpy(topnode, &PRINT_NODE, sizeof (struct Node));
297 	} else if (!action_expression) {
298 		/*
299 		 * if no action expression, insert an LPAREN node above topnode,
300 		 * with a PRINT node as its next node
301 		 */
302 		struct Node *savenode;
303 
304 		if (freenode == NULL) {
305 			(void) fprintf(stderr, gettext("%s: can't append -print"
306 				" implicitly; try explicit -print option\n"),
307 				cmdname);
308 			exit(1);
309 		}
310 		savenode = topnode;
311 		topnode = freenode++;
312 		(void) memcpy(topnode, &LPAREN_NODE, sizeof (struct Node));
313 		topnode->next = freenode;
314 		topnode->first.np = savenode;
315 		(void) memcpy(topnode->next, &PRINT_NODE, sizeof (struct Node));
316 	}
317 	(void) memcpy(savetnode, topnode, ((argc + 1) * sizeof (struct Node)));
318 
319 	while (paths--) {
320 		char *curpath;
321 		struct stat sb;
322 
323 		curpath = *(argv++);
324 
325 		/*
326 		 * If -H is specified, it means we walk the first
327 		 * level (pathname on command line) logically, following
328 		 * symlinks, but lower levels are walked physically.
329 		 * We use our own secret interface to nftw() to change
330 		 * the from stat to lstat after the top level is walked.
331 		 */
332 		if (hflag) {
333 			if (stat(curpath, &sb) < 0 && errno == ENOENT)
334 				walkflags &= ~FTW_HOPTION;
335 			else
336 				walkflags |= FTW_HOPTION;
337 		}
338 
339 		/*
340 		 * We need this check as nftw needs a CWD and we have no
341 		 * way of returning back from that code with a meaningful
342 		 * error related to this
343 		 */
344 		if ((cwdpath = getcwd(NULL, PATH_MAX)) == NULL) {
345 			(void) fprintf(stderr,
346 				gettext("%s : cannot get the current working "
347 					"directory\n"), cmdname);
348 			exit(1);
349 		} else
350 			free(cwdpath);
351 
352 
353 		if (nftw(curpath, execute, 1000, walkflags)) {
354 			(void) fprintf(stderr,
355 			    gettext("%s: cannot open %s: %s\n"),
356 			    cmdname, curpath, strerror(errno));
357 			error = 1;
358 		}
359 
360 		if (paths > 1)
361 			(void) memcpy(topnode, savetnode,
362 			    ((argc + 1) * sizeof (struct Node)));
363 	}
364 
365 	/* execute any remaining variable length lists */
366 	while (lastlist) {
367 		if (lastlist->end != lastlist->nextstr) {
368 			*lastlist->nextvar = 0;
369 			(void) doexec((char *)0, lastlist->arglist);
370 		}
371 		lastlist = lastlist->next;
372 	}
373 	if (output != stdout)
374 		return (cmdclose(output));
375 	return ((varexecrc != 0) ? varexecrc : error);
376 }
377 
378 /*
379  * compile the arguments
380  */
381 
382 static int
383 compile(argv, np, actionp)
384 char **argv;
385 struct Node *np;
386 int *actionp;
387 {
388 	char *b;
389 	char **av;
390 	struct Node *oldnp = topnode;
391 	struct Args *argp;
392 	char **com;
393 	int i;
394 	enum Command wasop = PRINT;
395 
396 	for (av = argv; *av && (argp = lookup(*av)); av++) {
397 		np->next = 0;
398 		np->action = argp->action;
399 		np->type = argp->type;
400 		np->second.i = 0;
401 		if (argp->type == Op) {
402 			if (wasop == NOT || (wasop && np->action != NOT)) {
403 				(void) fprintf(stderr,
404 				gettext("%s: operand follows operand\n"),
405 						cmdname);
406 				exit(1);
407 			}
408 			if (np->action != NOT && oldnp == 0)
409 				goto err;
410 			wasop = argp->action;
411 		} else {
412 			wasop = PRINT;
413 			if (argp->type != Unary) {
414 				if (!(b = *++av)) {
415 					(void) fprintf(stderr,
416 					gettext("%s: incomplete statement\n"),
417 							cmdname);
418 					exit(1);
419 				}
420 				if (argp->type == Num) {
421 					if ((argp->action != PERM) ||
422 					    (*b != '+')) {
423 						if (*b == '+' || *b == '-') {
424 							np->second.i = *b;
425 							b++;
426 						}
427 					}
428 				}
429 			}
430 		}
431 		switch (argp->action) {
432 		case AND:
433 			break;
434 		case NOT:
435 			break;
436 		case OR:
437 			np->first.np = topnode;
438 			topnode = np;
439 			oldnp->next = 0;
440 			break;
441 
442 		case LPAREN: {
443 			struct Node *save = topnode;
444 			topnode = np+1;
445 			paren_cnt++;
446 			i = compile(++av, topnode, actionp);
447 			np->first.np = topnode;
448 			topnode = save;
449 			av += i;
450 			oldnp = np;
451 			np += i + 1;
452 			oldnp->next = np;
453 			continue;
454 		}
455 
456 		case RPAREN:
457 			if (paren_cnt <= 0) {
458 				(void) fprintf(stderr,
459 				    gettext("%s: unmatched ')'\n"),
460 				    cmdname);
461 				exit(1);
462 			}
463 			paren_cnt--;
464 			if (oldnp == 0)
465 				goto err;
466 			if (oldnp->type == Op) {
467 				(void) fprintf(stderr,
468 				    gettext("%s: cannot immediately"
469 				    " follow an operand with ')'\n"),
470 				    cmdname);
471 				exit(1);
472 			}
473 			oldnp->next = 0;
474 			return (av-argv);
475 
476 		case FOLLOW:
477 			walkflags &= ~FTW_PHYS;
478 			break;
479 		case MOUNT:
480 			walkflags |= FTW_MOUNT;
481 			break;
482 		case DEPTH:
483 			walkflags |= FTW_DEPTH;
484 			break;
485 
486 		case LOCAL:
487 			np->first.l = 0L;
488 			np->first.ll = 0LL;
489 			np->second.i = '+';
490 			/*
491 			 * Make it compatible to df -l for
492 			 * future enhancement. So, anything
493 			 * that is not remote, then it is
494 			 * local.
495 			 */
496 			init_remote_fs();
497 			break;
498 
499 		case SIZE:
500 			if (b[strlen(b)-1] == 'c')
501 				np->action = CSIZE;
502 			/*FALLTHROUGH*/
503 		case INUM:
504 			np->first.ll = atoll(b);
505 			break;
506 
507 		case CTIME:
508 		case MTIME:
509 		case ATIME:
510 		case LINKS:
511 			np->first.l = atol(b);
512 			break;
513 
514 		case F_USER:
515 		case F_GROUP: {
516 			struct	passwd	*pw;
517 			struct	group *gr;
518 			i = -1;
519 			if (argp->action == F_USER) {
520 				if ((pw = getpwnam(b)) != 0)
521 					i = (int)pw->pw_uid;
522 			} else {
523 				if ((gr = getgrnam(b)) != 0)
524 					i = (int)gr->gr_gid;
525 			}
526 			if (i == -1) {
527 				if (fnmatch("[0-9][0-9][0-9]*", b, 0) &&
528 						fnmatch("[0-9][0-9]", b, 0) &&
529 						fnmatch("[0-9]", b, 0)) {
530 					(void) fprintf(stderr, gettext(
531 					    "%s: cannot find %s name\n"),
532 						cmdname, *av);
533 					exit(1);
534 				}
535 				i = atoi(b);
536 			}
537 			np->first.l = i;
538 			break;
539 		}
540 
541 		case EXEC:
542 		case OK:
543 			walkflags &= ~FTW_CHDIR;
544 			np->first.ap = av;
545 			(*actionp)++;
546 			for (;;) {
547 				if ((b = *av) == 0) {
548 					(void) fprintf(stderr,
549 					gettext("%s: incomplete statement\n"),
550 						cmdname);
551 					exit(1);
552 				}
553 				if (strcmp(b, ";") == 0) {
554 					*av = 0;
555 					break;
556 				} else if (strcmp(b, "{}") == 0)
557 					*av = dummyarg;
558 				else if (strcmp(b, "+") == 0 &&
559 					av[-1] == dummyarg &&
560 					np->action == EXEC) {
561 					av[-1] = 0;
562 					np->first.vp = varargs(np->first.ap);
563 					np->action = VARARGS;
564 					break;
565 				}
566 				av++;
567 			}
568 			break;
569 
570 		case NAME:
571 			np->first.cp = b;
572 			break;
573 		case PERM:
574 			if (*b == '-')
575 				++b;
576 
577 			if (readmode(b) != NULL) {
578 				(void) fprintf(stderr, gettext(
579 				    "find: -perm: Bad permission string\n"));
580 				usage();
581 			}
582 			np->first.l = (long)getmode((mode_t)0);
583 			break;
584 		case TYPE:
585 			i = *b;
586 			np->first.l =
587 			    i == 'd' ? S_IFDIR :
588 			    i == 'b' ? S_IFBLK :
589 			    i == 'c' ? S_IFCHR :
590 #ifdef S_IFIFO
591 			    i == 'p' ? S_IFIFO :
592 #endif
593 			    i == 'f' ? S_IFREG :
594 #ifdef S_IFLNK
595 			    i == 'l' ? S_IFLNK :
596 #endif
597 #ifdef S_IFSOCK
598 			    i == 's' ? S_IFSOCK :
599 #endif
600 #ifdef S_IFDOOR
601 			    i == 'D' ? S_IFDOOR :
602 #endif
603 			    0;
604 			break;
605 
606 		case CPIO:
607 			if (walkflags & FTW_PHYS)
608 				com = cpio;
609 			else
610 				com = cpiol;
611 			goto common;
612 
613 		case NCPIO: {
614 			FILE *fd;
615 
616 			if (walkflags & FTW_PHYS)
617 				com = ncpio;
618 			else
619 				com = ncpiol;
620 		common:
621 			/* set up cpio */
622 			if ((fd = fopen(b, "w")) == NULL) {
623 				(void) fprintf(stderr,
624 					gettext("%s: cannot create %s\n"),
625 					cmdname, b);
626 				exit(1);
627 			}
628 
629 			np->first.l = (long)cmdopen("cpio", com, "w", fd);
630 			(void) fclose(fd);
631 			walkflags |= FTW_DEPTH;
632 			np->action = CPIO;
633 		}
634 			/*FALLTHROUGH*/
635 		case PRINT:
636 			(*actionp)++;
637 			break;
638 
639 		case NEWER: {
640 			struct stat statb;
641 			if (stat(b, &statb) < 0) {
642 				(void) fprintf(stderr,
643 					gettext("%s: cannot access %s\n"),
644 					cmdname, b);
645 				exit(1);
646 			}
647 			np->first.l = statb.st_mtime;
648 			np->second.i = '+';
649 			break;
650 		}
651 
652 		case PRUNE:
653 		case NOUSER:
654 		case NOGRP:
655 			break;
656 		case FSTYPE:
657 			np->first.cp = b;
658 			break;
659 		case LS:
660 			(*actionp)++;
661 			break;
662 		case XATTR:
663 			break;
664 		case ACL:
665 			break;
666 		}
667 
668 		oldnp = np++;
669 		oldnp->next = np;
670 	}
671 
672 	if ((*av) || (wasop))
673 		goto err;
674 
675 	if (paren_cnt != 0) {
676 		(void) fprintf(stderr, gettext("%s: unmatched '('\n"),
677 		cmdname);
678 		exit(1);
679 	}
680 
681 	/* just before returning, save next free node from the list */
682 	freenode = oldnp->next;
683 	oldnp->next = 0;
684 	return (av-argv);
685 err:
686 	if (*av)
687 		(void) fprintf(stderr,
688 		    gettext("%s: bad option %s\n"), cmdname, *av);
689 	else
690 		(void) fprintf(stderr, gettext("%s: bad option\n"), cmdname);
691 	usage();
692 	/*NOTREACHED*/
693 }
694 
695 /*
696  * print out a usage message
697  */
698 
699 static void
700 usage()
701 {
702 	(void) fprintf(stderr,
703 	    gettext("%s: [-H | -L] path-list predicate-list\n"), cmdname);
704 	exit(1);
705 }
706 
707 /*
708  * This is the function that gets executed at each node
709  */
710 
711 static int
712 execute(name, statb, type, state)
713 char *name;
714 struct stat *statb;
715 int type;
716 struct FTW *state;
717 {
718 	struct Node *np = topnode;
719 	int val;
720 	time_t t;
721 	long l;
722 	long long ll;
723 	int not = 1;
724 	char *filename;
725 
726 	if (type == FTW_NS) {
727 		(void) fprintf(stderr, gettext("%s: stat() error %s: %s\n"),
728 			cmdname, name, strerror(errno));
729 		error = 1;
730 		return (0);
731 	} else if (type == FTW_DNR) {
732 		(void) fprintf(stderr, gettext("%s: cannot read dir %s: %s\n"),
733 			cmdname, name, strerror(errno));
734 		error = 1;
735 		return (0);
736 	} else if (type == FTW_SLN && lflag == 0) {
737 		(void) fprintf(stderr,
738 			gettext("%s: cannot follow symbolic link %s: %s\n"),
739 			cmdname, name, strerror(errno));
740 		error = 1;
741 		return (0);
742 	}
743 
744 	while (np) {
745 		switch (np->action) {
746 		case NOT:
747 			not = !not;
748 			np = np->next;
749 			continue;
750 
751 		case AND:
752 			np = np->next;
753 			continue;
754 
755 		case OR:
756 			if (np->first.np == np) {
757 				/*
758 				 * handle naked OR (no term on left hand side)
759 				 */
760 				(void) fprintf(stderr,
761 				    gettext("%s: invalid -o construction\n"),
762 				    cmdname);
763 				exit(2);
764 			}
765 			/* FALLTHROUGH */
766 		case LPAREN: {
767 			struct Node *save = topnode;
768 			topnode = np->first.np;
769 			(void) execute(name, statb, type, state);
770 			val = lastval;
771 			topnode = save;
772 			if (np->action == OR) {
773 				if (val)
774 					return (0);
775 				val = 1;
776 			}
777 			break;
778 		}
779 
780 		case LOCAL: {
781 			int	nremfs;
782 			val = 1;
783 			/*
784 			 * If file system type matches the remote
785 			 * file system type, then it is not local.
786 			 */
787 			for (nremfs = 0; nremfs < fstype_index; nremfs++) {
788 				if (strcmp(remote_fstypes[nremfs],
789 						statb->st_fstype) == 0) {
790 					val = 0;
791 					break;
792 				}
793 			}
794 			break;
795 		}
796 
797 		case TYPE:
798 			l = (long)statb->st_mode&S_IFMT;
799 			goto num;
800 
801 		case PERM:
802 			l = (long)statb->st_mode&07777;
803 			if (np->second.i == '-')
804 				val = ((l&np->first.l) == np->first.l);
805 			else
806 				val = (l == np->first.l);
807 			break;
808 
809 		case INUM:
810 			ll = (long long)statb->st_ino;
811 			goto llnum;
812 		case NEWER:
813 			l = statb->st_mtime;
814 			goto num;
815 		case ATIME:
816 			t = statb->st_atime;
817 			goto days;
818 		case CTIME:
819 			t = statb->st_ctime;
820 			goto days;
821 		case MTIME:
822 			t = statb->st_mtime;
823 		days:
824 			l = (now-t)/A_DAY;
825 			goto num;
826 		case CSIZE:
827 			ll = (long long)statb->st_size;
828 			goto llnum;
829 		case SIZE:
830 			ll = (long long)round(statb->st_size, BLKSIZ)/BLKSIZ;
831 			goto llnum;
832 		case F_USER:
833 			l = (long)statb->st_uid;
834 			goto num;
835 		case F_GROUP:
836 			l = (long)statb->st_gid;
837 			goto num;
838 		case LINKS:
839 			l = (long)statb->st_nlink;
840 			goto num;
841 		llnum:
842 			if (np->second.i == '+')
843 				val = (ll > np->first.ll);
844 			else if (np->second.i == '-')
845 				val = (ll < np->first.ll);
846 			else
847 				val = (ll == np->first.ll);
848 			break;
849 		num:
850 			if (np->second.i == '+')
851 				val = (l > np->first.l);
852 			else if (np->second.i == '-')
853 				val = (l < np->first.l);
854 			else
855 				val = (l == np->first.l);
856 			break;
857 		case OK:
858 			val = ok(name, np->first.ap);
859 			break;
860 		case EXEC:
861 			val = doexec(name, np->first.ap);
862 			break;
863 
864 		case VARARGS: {
865 			struct Arglist *ap = np->first.vp;
866 			char *cp;
867 			cp = ap->nextstr - (strlen(name)+1);
868 			if (cp >= (char *)(ap->nextvar+3)) {
869 				/* there is room just copy the name */
870 				val = 1;
871 				(void) strcpy(cp, name);
872 				*ap->nextvar++ = cp;
873 				ap->nextstr = cp;
874 			} else {
875 				/* no more room, exec command */
876 				*ap->nextvar++ = name;
877 				*ap->nextvar = 0;
878 				val = 1;
879 				varexecrc = doexec((char *)0, ap->arglist);
880 				ap->nextstr = ap->end;
881 				ap->nextvar = ap->firstvar;
882 			}
883 			break;
884 		}
885 
886 		case DEPTH:
887 		case MOUNT:
888 		case FOLLOW:
889 			val = 1;
890 			break;
891 
892 		case NAME: {
893 			/*
894 			 * XPG4 find should not treat a leading '.' in a
895 			 * filename specially for pattern matching.
896 			 * /usr/bin/find  will not pattern match a leading
897 			 * '.' in a filename, unless '.' is explicitly
898 			 * specified.
899 			 */
900 #ifdef XPG4
901 			val = !fnmatch(np->first.cp,
902 			    name+state->base, 0);
903 #else
904 			val = !fnmatch(np->first.cp,
905 			    name+state->base, FNM_PERIOD);
906 #endif
907 			break;
908 		}
909 
910 		case PRUNE:
911 			if (type == FTW_D)
912 				state->quit = FTW_PRUNE;
913 			val = 1;
914 			break;
915 		case NOUSER:
916 			val = ((getpwuid(statb->st_uid)) == 0);
917 			break;
918 		case NOGRP:
919 			val = ((getgrgid(statb->st_gid)) == 0);
920 			break;
921 		case FSTYPE:
922 			val = (strcmp(np->first.cp, statb->st_fstype) == 0);
923 			break;
924 		case CPIO:
925 			output = (FILE *)np->first.l;
926 			(void) fprintf(output, "%s\n", name);
927 			val = 1;
928 			break;
929 		case PRINT:
930 			(void) fprintf(stdout, "%s\n", name);
931 			val = 1;
932 			break;
933 		case LS:
934 			(void) list(name, statb);
935 			val = 1;
936 			break;
937 		case XATTR:
938 			filename = gettail(name);
939 			val = (pathconf(filename, _PC_XATTR_EXISTS) == 1);
940 			break;
941 		case ACL:
942 			/*
943 			 * Need to get the tail of the file name, since we have
944 			 * already chdir()ed into the directory (performed in
945 			 * nftw()) of the file
946 			 */
947 			filename = gettail(name);
948 			val = (acl(filename, GETACLCNT, 0, NULL) >
949 			    MIN_ACL_ENTRIES);
950 			break;
951 		}
952 		/*
953 		 * evaluate 'val' and 'not' (exclusive-or)
954 		 * if no inversion (not == 1), return only when val == 0
955 		 * (primary not true). Otherwise, invert the primary
956 		 * and return when the primary is true.
957 		 * 'Lastval' saves the last result (fail or pass) when
958 		 * returning back to the calling routine.
959 		 */
960 		if (val^not) {
961 			lastval = 0;
962 			return (0);
963 		}
964 		lastval = 1;
965 		not = 1;
966 		np = np->next;
967 	}
968 	return (0);
969 }
970 
971 /*
972  * code for the -ok option
973  */
974 
975 static int
976 ok(name, argv)
977 char *name;
978 char *argv[];
979 {
980 	int c, yes = 0;
981 
982 	(void) fflush(stdout); 	/* to flush possible `-print' */
983 
984 	if ((*argv != dummyarg) && (strcmp(*argv, name)))
985 		(void) fprintf(stderr, "< %s ... %s >?   ", *argv, name);
986 	else
987 		(void) fprintf(stderr, "< {} ... %s >?   ", name);
988 
989 	(void) fflush(stderr);
990 	if ((c = tolower(getchar())) == *nl_langinfo(YESSTR))
991 		yes = 1;
992 	while (c != '\n')
993 		if (c == EOF)
994 			exit(2);
995 		else
996 			c = getchar();
997 	return (yes? doexec(name, argv): 0);
998 }
999 
1000 /*
1001  * execute argv with {} replaced by name
1002  */
1003 
1004 static int
1005 doexec(name, argv)
1006 char *name;
1007 char *argv[];
1008 {
1009 	char *cp;
1010 	char **av = argv;
1011 	int dummyseen = 0;
1012 	int r = 0;
1013 	pid_t pid;
1014 
1015 	(void) fflush(stdout);	  /* to flush possible `-print' */
1016 	if (name) {
1017 		while (cp = *av++) {
1018 			if (cp == dummyarg) {
1019 				dummyseen = 1;
1020 				av[-1] = name;
1021 			}
1022 
1023 		}
1024 	}
1025 	if (argv[0] == NULL)    /* null command line */
1026 		return (r);
1027 
1028 	if (pid = fork()) {
1029 		while (wait(&r) != pid);
1030 	} else /* child */ {
1031 		(void) execvp(argv[0], argv);
1032 		exit(1);
1033 	}
1034 	if (name && dummyseen) {
1035 		for (av = argv; cp = *av++; ) {
1036 			if (cp == name)
1037 				av[-1] = dummyarg;
1038 		}
1039 	}
1040 
1041 	return (!r);
1042 }
1043 
1044 
1045 /*
1046  *  Table lookup routine
1047  */
1048 static struct Args *
1049 lookup(word)
1050 char *word;
1051 {
1052 	struct Args *argp = commands;
1053 	int second;
1054 	if (word == 0 || *word == 0)
1055 		return (0);
1056 	second = word[1];
1057 	while (*argp->name) {
1058 		if (second == argp->name[1] && strcmp(word, argp->name) == 0)
1059 			return (argp);
1060 		argp++;
1061 	}
1062 	return (0);
1063 }
1064 
1065 
1066 /*
1067  * Get space for variable length argument list
1068  */
1069 
1070 static struct Arglist *
1071 varargs(com)
1072 char **com;
1073 {
1074 	struct Arglist *ap;
1075 	int n;
1076 	char **ep;
1077 	if (varsize == 0) {
1078 		n = 2*sizeof (char **);
1079 		for (ep = environ; *ep; ep++)
1080 			n += (strlen(*ep)+sizeof (ep) + 1);
1081 		varsize = sizeof (struct Arglist)+ARG_MAX-PATH_MAX-n-1;
1082 	}
1083 	ap = (struct Arglist *)malloc(varsize+1);
1084 	ap->end = (char *)ap + varsize;
1085 	ap->nextstr = ap->end;
1086 	ap->nextvar = ap->arglist;
1087 	while (*ap->nextvar++ = *com++);
1088 	ap->nextvar--;
1089 	ap->firstvar = ap->nextvar;
1090 	ap->next = lastlist;
1091 	lastlist = ap;
1092 	return (ap);
1093 }
1094 
1095 /*
1096  * filter command support
1097  * fork and exec cmd(argv) according to mode:
1098  *
1099  *	"r"	with fp as stdin of cmd (default stdin), cmd stdout returned
1100  *	"w"	with fp as stdout of cmd (default stdout), cmd stdin returned
1101  */
1102 
1103 #define	CMDERR	((1<<8)-1)	/* command error exit code		*/
1104 #define	MAXCMDS	8		/* max # simultaneous cmdopen()'s	*/
1105 
1106 static struct			/* info for each cmdopen()		*/
1107 {
1108 	FILE	*fp;		/* returned by cmdopen()		*/
1109 	pid_t	pid;		/* pid used by cmdopen()		*/
1110 } cmdproc[MAXCMDS];
1111 
1112 static FILE *
1113 cmdopen(cmd, argv, mode, fp)
1114 char	*cmd;
1115 char	**argv;
1116 char	*mode;
1117 FILE	*fp;
1118 {
1119 	int	proc;
1120 	int	cmdfd;
1121 	int	usrfd;
1122 	int		pio[2];
1123 
1124 	switch (*mode) {
1125 	case 'r':
1126 		cmdfd = 1;
1127 		usrfd = 0;
1128 		break;
1129 	case 'w':
1130 		cmdfd = 0;
1131 		usrfd = 1;
1132 		break;
1133 	default:
1134 		return (0);
1135 	}
1136 
1137 	for (proc = 0; proc < MAXCMDS; proc++)
1138 		if (!cmdproc[proc].fp)
1139 			break;
1140 	if (proc >= MAXCMDS)
1141 		return (0);
1142 
1143 	if (pipe(pio))
1144 		return (0);
1145 
1146 	switch (cmdproc[proc].pid = fork()) {
1147 	case -1:
1148 		return (0);
1149 	case 0:
1150 		if (fp && fileno(fp) != usrfd) {
1151 			(void) close(usrfd);
1152 			if (dup2(fileno(fp), usrfd) != usrfd)
1153 				_exit(CMDERR);
1154 			(void) close(fileno(fp));
1155 		}
1156 		(void) close(cmdfd);
1157 		if (dup2(pio[cmdfd], cmdfd) != cmdfd)
1158 			_exit(CMDERR);
1159 		(void) close(pio[cmdfd]);
1160 		(void) close(pio[usrfd]);
1161 		(void) execvp(cmd, argv);
1162 		if (errno == ENOEXEC) {
1163 			char	**p;
1164 			char		**v;
1165 
1166 			/*
1167 			 * assume cmd is a shell script
1168 			 */
1169 
1170 			p = argv;
1171 			while (*p++);
1172 			if (v = (char **)malloc((p - argv + 1) *
1173 					sizeof (char **))) {
1174 				p = v;
1175 				*p++ = cmd;
1176 				if (*argv) argv++;
1177 				while (*p++ = *argv++);
1178 				(void) execv(getshell(), v);
1179 			}
1180 		}
1181 		_exit(CMDERR);
1182 		/*NOTREACHED*/
1183 	default:
1184 		(void) close(pio[cmdfd]);
1185 		return (cmdproc[proc].fp = fdopen(pio[usrfd], mode));
1186 	}
1187 }
1188 
1189 /*
1190  * close a stream opened by cmdopen()
1191  * -1 returned if cmdopen() had a problem
1192  * otherwise exit() status of command is returned
1193  */
1194 
1195 static int
1196 cmdclose(fp)
1197 FILE	*fp;
1198 {
1199 	int	i;
1200 	pid_t	p, pid;
1201 	int		status;
1202 
1203 	for (i = 0; i < MAXCMDS; i++)
1204 		if (fp == cmdproc[i].fp) break;
1205 	if (i >= MAXCMDS)
1206 		return (-1);
1207 	(void) fclose(fp);
1208 	cmdproc[i].fp = 0;
1209 	pid = cmdproc[i].pid;
1210 	while ((p = wait(&status)) != pid && p != (pid_t)-1);
1211 	if (p == pid) {
1212 		status = (status >> 8) & CMDERR;
1213 		if (status == CMDERR)
1214 			status = -1;
1215 	}
1216 	else
1217 		status = -1;
1218 	return (status);
1219 }
1220 
1221 /*
1222  * return pointer to the full path name of the shell
1223  *
1224  * SHELL is read from the environment and must start with /
1225  *
1226  * if set-uid or set-gid then the executable and its containing
1227  * directory must not be writable by the real user
1228  *
1229  * /usr/bin/sh is returned by default
1230  */
1231 
1232 char *
1233 getshell()
1234 {
1235 	char	*s;
1236 	char	*sh;
1237 	uid_t	u;
1238 	int	j;
1239 
1240 	if (((sh = getenv("SHELL")) != 0) && *sh == '/') {
1241 		if (u = getuid()) {
1242 			if ((u != geteuid() || getgid() != getegid()) &&
1243 					!access(sh, 2))
1244 				goto defshell;
1245 			s = strrchr(sh, '/');
1246 			*s = 0;
1247 			j = access(sh, 2);
1248 			*s = '/';
1249 			if (!j) goto defshell;
1250 		}
1251 		return (sh);
1252 	}
1253 defshell:
1254 	return ("/usr/bin/sh");
1255 }
1256 
1257 /*
1258  * the following functions implement the added "-ls" option
1259  */
1260 
1261 #include <utmpx.h>
1262 #include <sys/mkdev.h>
1263 
1264 struct		utmpx utmpx;
1265 #define	NMAX	(sizeof (utmpx.ut_name))
1266 #define	SCPYN(a, b)	(void) strncpy(a, b, NMAX)
1267 
1268 #define	NUID	64
1269 #define	NGID	64
1270 
1271 static struct ncache {
1272 	int	id;
1273 	char	name[NMAX+1];
1274 } nc[NUID], gc[NGID];
1275 
1276 /*
1277  * This function assumes that the password file is hashed
1278  * (or some such) to allow fast access based on a name key.
1279  */
1280 static char *
1281 getname(uid_t uid)
1282 {
1283 	struct passwd *pw;
1284 	int cp;
1285 
1286 #if	(((NUID) & ((NUID) - 1)) != 0)
1287 	cp = uid % (NUID);
1288 #else
1289 	cp = uid & ((NUID) - 1);
1290 #endif
1291 	if (uid >= 0 && nc[cp].id == uid && nc[cp].name[0])
1292 		return (nc[cp].name);
1293 	pw = getpwuid(uid);
1294 	if (!pw)
1295 		return (0);
1296 	nc[cp].id = uid;
1297 	SCPYN(nc[cp].name, pw->pw_name);
1298 	return (nc[cp].name);
1299 }
1300 
1301 /*
1302  * This function assumes that the group file is hashed
1303  * (or some such) to allow fast access based on a name key.
1304  */
1305 static char *
1306 getgroup(gid_t gid)
1307 {
1308 	struct group *gr;
1309 	int cp;
1310 
1311 #if	(((NGID) & ((NGID) - 1)) != 0)
1312 	cp = gid % (NGID);
1313 #else
1314 	cp = gid & ((NGID) - 1);
1315 #endif
1316 	if (gid >= 0 && gc[cp].id == gid && gc[cp].name[0])
1317 		return (gc[cp].name);
1318 	gr = getgrgid(gid);
1319 	if (!gr)
1320 		return (0);
1321 	gc[cp].id = gid;
1322 	SCPYN(gc[cp].name, gr->gr_name);
1323 	return (gc[cp].name);
1324 }
1325 
1326 #define	permoffset(who)		((who) * 3)
1327 #define	permission(who, type)	((type) >> permoffset(who))
1328 #define	kbytes(bytes)		(((bytes) + 1023) / 1024)
1329 
1330 static int
1331 list(file, stp)
1332 	char *file;
1333 	struct stat *stp;
1334 {
1335 	char pmode[32], uname[32], gname[32], fsize[32], ftime[32];
1336 
1337 
1338 /*
1339  * Each line below contains the relevant permission (column 1) and character
1340  * shown when  the corresponding execute bit is either clear (column 2)
1341  * or set (column 3)
1342  * These permissions are as shown by ls(1b)
1343  */
1344 	static long special[] = {	S_ISUID, 'S', 's',
1345 					S_ISGID, 'S', 's',
1346 					S_ISVTX, 'T', 't' };
1347 
1348 	static time_t sixmonthsago = -1;
1349 #ifdef	S_IFLNK
1350 	char flink[MAXPATHLEN + 1];
1351 #endif
1352 	int who;
1353 	char *cp;
1354 	char *tailname;
1355 	time_t now;
1356 	long long ksize;
1357 
1358 	if (file == NULL || stp == NULL)
1359 		return (-1);
1360 
1361 	(void) time(&now);
1362 	if (sixmonthsago == -1)
1363 		sixmonthsago = now - 6L*30L*24L*60L*60L;
1364 
1365 	switch (stp->st_mode & S_IFMT) {
1366 #ifdef	S_IFDIR
1367 	case S_IFDIR:	/* directory */
1368 		pmode[0] = 'd';
1369 		break;
1370 #endif
1371 #ifdef	S_IFCHR
1372 	case S_IFCHR:	/* character special */
1373 		pmode[0] = 'c';
1374 		break;
1375 #endif
1376 #ifdef	S_IFBLK
1377 	case S_IFBLK:	/* block special */
1378 		pmode[0] = 'b';
1379 		break;
1380 #endif
1381 #ifdef	S_IFIFO
1382 	case S_IFIFO:	/* fifo special */
1383 		pmode[0] = 'p';
1384 		break;
1385 #endif
1386 #ifdef	S_IFLNK
1387 	case S_IFLNK:	/* symbolic link */
1388 		pmode[0] = 'l';
1389 		break;
1390 #endif
1391 #ifdef	S_IFSOCK
1392 	case S_IFSOCK:	/* socket */
1393 		pmode[0] = 's';
1394 		break;
1395 #endif
1396 #ifdef	S_IFDOOR
1397 	case S_IFDOOR:	/* door */
1398 		pmode[0] = 'D';
1399 		break;
1400 #endif
1401 #ifdef	S_IFREG
1402 	case S_IFREG:	/* regular */
1403 		pmode[0] = '-';
1404 		break;
1405 #endif
1406 	default:
1407 		pmode[0] = '?';
1408 		break;
1409 	}
1410 
1411 	for (who = 0; who < 3; who++) {
1412 		int is_exec =  stp->st_mode & permission(who, S_IEXEC)? 1 : 0;
1413 
1414 		if (stp->st_mode & permission(who, S_IREAD))
1415 			pmode[permoffset(who) + 1] = 'r';
1416 		else
1417 			pmode[permoffset(who) + 1] = '-';
1418 
1419 		if (stp->st_mode & permission(who, S_IWRITE))
1420 			pmode[permoffset(who) + 2] = 'w';
1421 		else
1422 			pmode[permoffset(who) + 2] = '-';
1423 
1424 		if (stp->st_mode & special[who * 3])
1425 			pmode[permoffset(who) + 3] =
1426 				special[who * 3 + 1 + is_exec];
1427 		else if (is_exec)
1428 			pmode[permoffset(who) + 3] = 'x';
1429 		else
1430 			pmode[permoffset(who) + 3] = '-';
1431 	}
1432 
1433 	/*
1434 	 * Need to get the tail of the file name, since we have
1435 	 * already chdir()ed into the directory of the file
1436 	 */
1437 
1438 	tailname = gettail(file);
1439 
1440 	if (acl(tailname, GETACLCNT, 0, NULL) > MIN_ACL_ENTRIES)
1441 		pmode[permoffset(who) + 1] = '+';
1442 	else
1443 		pmode[permoffset(who) + 1] = ' ';
1444 
1445 	pmode[permoffset(who) + 2] = '\0';
1446 
1447 	/*
1448 	 * Prepare uname and gname.  Always add a space afterwards
1449 	 * to keep columns from running together.
1450 	 */
1451 	cp = getname(stp->st_uid);
1452 	if (cp != NULL)
1453 		(void) sprintf(uname, "%-8s ", cp);
1454 	else
1455 		(void) sprintf(uname, "%-8ld ", stp->st_uid);
1456 
1457 	cp = getgroup(stp->st_gid);
1458 	if (cp != NULL)
1459 		(void) sprintf(gname, "%-8s ", cp);
1460 	else
1461 		(void) sprintf(gname, "%-8ld ", stp->st_gid);
1462 
1463 	if (pmode[0] == 'b' || pmode[0] == 'c')
1464 		(void) sprintf(fsize, "%3ld,%4ld",
1465 			major(stp->st_rdev), minor(stp->st_rdev));
1466 	else {
1467 		(void) sprintf(fsize, (stp->st_size < 100000000) ?
1468 			"%8lld" : "%lld", stp->st_size);
1469 #ifdef	S_IFLNK
1470 		if (pmode[0] == 'l') {
1471 
1472 
1473 			who = readlink(tailname, flink, sizeof (flink) - 1);
1474 
1475 			if (who >= 0)
1476 				flink[who] = '\0';
1477 			else
1478 				flink[0] = '\0';
1479 		}
1480 #endif
1481 	}
1482 
1483 	cp = ctime(&stp->st_mtime);
1484 	if (stp->st_mtime < sixmonthsago || stp->st_mtime > now)
1485 		(void) sprintf(ftime, "%-7.7s %-4.4s", cp + 4, cp + 20);
1486 	else
1487 		(void) sprintf(ftime, "%-12.12s", cp + 4);
1488 
1489 	(void) printf((stp->st_ino < 100000) ? "%5llu " :
1490 		"%llu ", stp->st_ino);  /* inode #	*/
1491 #ifdef	S_IFSOCK
1492 	ksize = (long long) kbytes(ldbtob(stp->st_blocks)); /* kbytes */
1493 #else
1494 	ksize = (long long) kbytes(stp->st_size); /* kbytes */
1495 #endif
1496 	(void) printf((ksize < 10000) ? "%4lld " : "%lld ", ksize);
1497 	(void) printf("%s %2ld %s%s%s %s %s%s%s\n",
1498 		pmode,					/* protection	*/
1499 		stp->st_nlink,				/* # of links	*/
1500 		uname,					/* owner	*/
1501 		gname,					/* group	*/
1502 		fsize,					/* # of bytes	*/
1503 		ftime,					/* modify time	*/
1504 		file,					/* name		*/
1505 #ifdef	S_IFLNK
1506 		(pmode[0] == 'l') ? " -> " : "",
1507 		(pmode[0] == 'l') ? flink  : ""		/* symlink	*/
1508 #else
1509 		"",
1510 		""
1511 #endif
1512 );
1513 
1514 	return (0);
1515 }
1516 
1517 static char *
1518 new_string(char *s)
1519 {
1520 	char *p = strdup(s);
1521 
1522 	if (p)
1523 		return (p);
1524 	(void) fprintf(stderr, gettext("%s: out of memory\n"), cmdname);
1525 	exit(1);
1526 	/*NOTREACHED*/
1527 }
1528 
1529 /*
1530  * Read remote file system types from REMOTE_FS into the
1531  * remote_fstypes array.
1532  */
1533 static void
1534 init_remote_fs()
1535 {
1536 	FILE    *fp;
1537 	char    line_buf[LINEBUF_SIZE];
1538 
1539 	if ((fp = fopen(REMOTE_FS, "r")) == NULL) {
1540 		(void) fprintf(stderr,
1541 			gettext("%s: Warning: can't open %s, ignored\n"),
1542 				REMOTE_FS, cmdname);
1543 		/* Use default string name for NFS */
1544 		remote_fstypes[fstype_index++] = "nfs";
1545 		return;
1546 	}
1547 
1548 	while (fgets(line_buf, sizeof (line_buf), fp) != NULL) {
1549 		char buf[LINEBUF_SIZE];
1550 
1551 		/* LINTED - unbounded string specifier */
1552 		(void) sscanf(line_buf, "%s", buf);
1553 		remote_fstypes[fstype_index++] = new_string(buf);
1554 
1555 		if (fstype_index == N_FSTYPES)
1556 			break;
1557 	}
1558 	(void) fclose(fp);
1559 }
1560 
1561 #define	NPERM	30			/* Largest machine */
1562 
1563 /*
1564  * The PERM struct is the machine that builds permissions.  The p_special
1565  * field contains what permissions need to be checked at run-time in
1566  * getmode().  This is one of 'X', 'u', 'g', or 'o'.  It contains '\0' to
1567  * indicate normal processing.
1568  */
1569 typedef	struct	PERMST	{
1570 	ushort_t	p_who;		/* Range of permission (e.g. ugo) */
1571 	ushort_t	p_perm;		/* Bits to turn on, off, assign */
1572 	uchar_t		p_op;		/* Operation: + - = */
1573 	uchar_t		p_special;	/* Special handling? */
1574 }	PERMST;
1575 
1576 #ifndef	S_ISVTX
1577 #define	S_ISVTX	0			/* Not .1 */
1578 #endif
1579 
1580 /* Mask values */
1581 #define	P_A	(S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO) /* allbits */
1582 #define	P_U	(S_ISUID|S_ISVTX|S_IRWXU)		/* user */
1583 #define	P_G	(S_ISGID|S_ISVTX|S_IRWXG)		/* group */
1584 #define	P_O	(S_ISVTX|S_IRWXO)			/* other */
1585 
1586 static	int	iswho(int c);
1587 static	int	isop(int c);
1588 static	int	isperm(PERMST *pp, int c);
1589 
1590 static	PERMST	machine[NPERM];		/* Permission construction machine */
1591 static	PERMST	*endp;			/* Last used PERM structure */
1592 
1593 static	uint_t	nowho;			/* No who for this mode (DOS kludge) */
1594 
1595 /*
1596  * Read an ASCII string containing the symbolic/octal mode and
1597  * compile an automaton that recognizes it.  The return value
1598  * is NULL if everything is OK, otherwise it is -1.
1599  */
1600 static int
1601 readmode(ascmode)
1602 const char *ascmode;
1603 {
1604 	const char *amode = ascmode;
1605 	PERMST *pp;
1606 	int seen_X;
1607 
1608 	nowho = 0;
1609 	seen_X = 0;
1610 	pp = &machine[0];
1611 	if (*amode >= '0' && *amode <= '7') {
1612 		int mode;
1613 
1614 		mode = 0;
1615 		while (*amode >= '0' && *amode <= '7')
1616 			mode = (mode<<3) + *amode++ - '0';
1617 		if (*amode != '\0')
1618 			return (-1);
1619 #if	S_ISUID != 04000 || S_ISGID != 02000 || \
1620 	S_IRUSR != 0400 || S_IWUSR != 0200 || S_IXUSR != 0100 || \
1621 	S_IRGRP != 0040 || S_IWGRP != 0020 || S_IXGRP != 0010 || \
1622 	S_IROTH != 0004 || S_IWOTH != 0002 || S_IXOTH != 0001
1623 		/*
1624 		 * There is no requirement of the octal mode bits being
1625 		 * the same as the S_ macros.
1626 		 */
1627 	{
1628 		mode_t mapping[] = {
1629 			S_IXOTH, S_IWOTH, S_IROTH,
1630 			S_IXGRP, S_IWGRP, S_IRGRP,
1631 			S_IXUSR, S_IWUSR, S_IRUSR,
1632 			S_ISGID, S_ISUID,
1633 			0
1634 		};
1635 		int i, newmode = 0;
1636 
1637 		for (i = 0; mapping[i] != 0; i++)
1638 			if (mode & (1<<i))
1639 				newmode |= mapping[i];
1640 		mode = newmode;
1641 	}
1642 #endif
1643 		pp->p_who = P_A;
1644 		pp->p_perm = mode;
1645 		pp->p_op = '=';
1646 	} else	for (;;) {
1647 		int t;
1648 		int who = 0;
1649 
1650 		while ((t = iswho(*amode)) != 0) {
1651 			++amode;
1652 			who |= t;
1653 		}
1654 		if (who == 0) {
1655 			mode_t currmask;
1656 			(void) umask(currmask = umask((mode_t)0));
1657 
1658 			/*
1659 			 * If no who specified, must use contents of
1660 			 * umask to determine which bits to flip.  This
1661 			 * is POSIX/V7/BSD behaviour, but not SVID.
1662 			 */
1663 			who = (~currmask)&P_A;
1664 			++nowho;
1665 		} else
1666 			nowho = 0;
1667 	samewho:
1668 		if (!isop(pp->p_op = *amode++))
1669 			return (-1);
1670 		pp->p_perm = 0;
1671 		pp->p_special = 0;
1672 		while ((t = isperm(pp, *amode)) != 0) {
1673 			if (pp->p_special == 'X') {
1674 				seen_X = 1;
1675 
1676 				if (pp->p_perm != 0) {
1677 					ushort_t op;
1678 
1679 					/*
1680 					 * Remember the 'who' for the previous
1681 					 * transformation.
1682 					 */
1683 					pp->p_who = who;
1684 					pp->p_special = 0;
1685 
1686 					op = pp->p_op;
1687 
1688 					/* Keep 'X' separate */
1689 					++pp;
1690 					pp->p_special = 'X';
1691 					pp->p_op = op;
1692 				}
1693 			} else if (seen_X) {
1694 				ushort_t op;
1695 
1696 				/* Remember the 'who' for the X */
1697 				pp->p_who = who;
1698 
1699 				op = pp->p_op;
1700 
1701 				/* Keep 'X' separate */
1702 				++pp;
1703 				pp->p_perm = 0;
1704 				pp->p_special = 0;
1705 				pp->p_op = op;
1706 			}
1707 			++amode;
1708 			pp->p_perm |= t;
1709 		}
1710 
1711 		/*
1712 		 * These returned 0, but were actually parsed, so
1713 		 * don't look at them again.
1714 		 */
1715 		switch (pp->p_special) {
1716 		case 'u':
1717 		case 'g':
1718 		case 'o':
1719 			++amode;
1720 			break;
1721 		}
1722 		pp->p_who = who;
1723 		switch (*amode) {
1724 		case '\0':
1725 			break;
1726 
1727 		case ',':
1728 			++amode;
1729 			++pp;
1730 			continue;
1731 
1732 		default:
1733 			++pp;
1734 			goto samewho;
1735 		}
1736 		break;
1737 	}
1738 	endp = pp;
1739 	return (NULL);
1740 }
1741 
1742 /*
1743  * Given a character from the mode, return the associated
1744  * value as who (user designation) mask or 0 if this isn't valid.
1745  */
1746 static int
1747 iswho(c)
1748 int c;
1749 {
1750 	switch (c) {
1751 	case 'a':
1752 		return (P_A);
1753 
1754 	case 'u':
1755 		return (P_U);
1756 
1757 	case 'g':
1758 		return (P_G);
1759 
1760 	case 'o':
1761 		return (P_O);
1762 
1763 	default:
1764 		return (0);
1765 	}
1766 	/* NOTREACHED */
1767 }
1768 
1769 /*
1770  * Return non-zero if this is a valid op code
1771  * in a symbolic mode.
1772  */
1773 static int
1774 isop(c)
1775 int c;
1776 {
1777 	switch (c) {
1778 	case '+':
1779 	case '-':
1780 	case '=':
1781 		return (1);
1782 
1783 	default:
1784 		return (0);
1785 	}
1786 	/* NOTREACHED */
1787 }
1788 
1789 /*
1790  * Return the permission bits implied by this character or 0
1791  * if it isn't valid.  Also returns 0 when the pseudo-permissions 'u', 'g', or
1792  * 'o' are used, and sets pp->p_special to the one used.
1793  */
1794 static int
1795 isperm(pp, c)
1796 PERMST *pp;
1797 int c;
1798 {
1799 	switch (c) {
1800 	case 'u':
1801 	case 'g':
1802 	case 'o':
1803 		pp->p_special = c;
1804 		return (0);
1805 
1806 	case 'r':
1807 		return (S_IRUSR|S_IRGRP|S_IROTH);
1808 
1809 	case 'w':
1810 		return (S_IWUSR|S_IWGRP|S_IWOTH);
1811 
1812 	case 'x':
1813 		return (S_IXUSR|S_IXGRP|S_IXOTH);
1814 
1815 #if S_ISVTX != 0
1816 	case 't':
1817 		return (S_ISVTX);
1818 #endif
1819 
1820 	case 'X':
1821 		pp->p_special = 'X';
1822 		return (S_IXUSR|S_IXGRP|S_IXOTH);
1823 
1824 #if S_ISVTX != 0
1825 	case 'a':
1826 		return (S_ISVTX);
1827 #endif
1828 
1829 	case 'h':
1830 		return (S_ISUID);
1831 
1832 	/*
1833 	 * This change makes:
1834 	 *	chmod +s file
1835 	 * set the system bit on dos but means that
1836 	 *	chmod u+s file
1837 	 *	chmod g+s file
1838 	 *	chmod a+s file
1839 	 * are all like UNIX.
1840 	 */
1841 	case 's':
1842 		return (nowho ? S_ISGID : S_ISGID|S_ISUID);
1843 
1844 	default:
1845 		return (0);
1846 	}
1847 	/* NOTREACHED */
1848 }
1849 
1850 /*
1851  * Execute the automaton that is created by readmode()
1852  * to generate the final mode that will be used.  This
1853  * code is passed a starting mode that is usually the original
1854  * mode of the file being changed (or 0).  Note that this mode must contain
1855  * the file-type bits as well, so that S_ISDIR will succeed on directories.
1856  */
1857 static mode_t
1858 getmode(mode_t startmode)
1859 {
1860 	PERMST *pp;
1861 	mode_t temp;
1862 	mode_t perm;
1863 
1864 	for (pp = &machine[0]; pp <= endp; ++pp) {
1865 		perm = (mode_t)0;
1866 		/*
1867 		 * For the special modes 'u', 'g' and 'o', the named portion
1868 		 * of the mode refers to after the previous clause has been
1869 		 * processed, while the 'X' mode refers to the contents of the
1870 		 * mode before any clauses have been processed.
1871 		 *
1872 		 * References: P1003.2/D11.2, Section 4.7.7,
1873 		 *  lines 2568-2570, 2578-2583
1874 		 */
1875 		switch (pp->p_special) {
1876 		case 'u':
1877 			temp = startmode & S_IRWXU;
1878 			if (temp & (S_IRUSR|S_IRGRP|S_IROTH))
1879 				perm |= ((S_IRUSR|S_IRGRP|S_IROTH) &
1880 				    pp->p_who);
1881 			if (temp & (S_IWUSR|S_IWGRP|S_IWOTH))
1882 				perm |= ((S_IWUSR|S_IWGRP|S_IWOTH) & pp->p_who);
1883 			if (temp & (S_IXUSR|S_IXGRP|S_IXOTH))
1884 				perm |= ((S_IXUSR|S_IXGRP|S_IXOTH) & pp->p_who);
1885 			break;
1886 
1887 		case 'g':
1888 			temp = startmode & S_IRWXG;
1889 			if (temp & (S_IRUSR|S_IRGRP|S_IROTH))
1890 				perm |= ((S_IRUSR|S_IRGRP|S_IROTH) & pp->p_who);
1891 			if (temp & (S_IWUSR|S_IWGRP|S_IWOTH))
1892 				perm |= ((S_IWUSR|S_IWGRP|S_IWOTH) & pp->p_who);
1893 			if (temp & (S_IXUSR|S_IXGRP|S_IXOTH))
1894 				perm |= ((S_IXUSR|S_IXGRP|S_IXOTH) & pp->p_who);
1895 			break;
1896 
1897 		case 'o':
1898 			temp = startmode & S_IRWXO;
1899 			if (temp & (S_IRUSR|S_IRGRP|S_IROTH))
1900 				perm |= ((S_IRUSR|S_IRGRP|S_IROTH) & pp->p_who);
1901 			if (temp & (S_IWUSR|S_IWGRP|S_IWOTH))
1902 				perm |= ((S_IWUSR|S_IWGRP|S_IWOTH) & pp->p_who);
1903 			if (temp & (S_IXUSR|S_IXGRP|S_IXOTH))
1904 				perm |= ((S_IXUSR|S_IXGRP|S_IXOTH) & pp->p_who);
1905 			break;
1906 
1907 		case 'X':
1908 			perm = pp->p_perm;
1909 			break;
1910 
1911 		default:
1912 			perm = pp->p_perm;
1913 			break;
1914 		}
1915 		switch (pp->p_op) {
1916 		case '-':
1917 			startmode &= ~(perm & pp->p_who);
1918 			break;
1919 
1920 		case '=':
1921 			startmode &= ~pp->p_who;
1922 			/* FALLTHROUGH */
1923 		case '+':
1924 			startmode |= (perm & pp->p_who);
1925 			break;
1926 		}
1927 	}
1928 	return (startmode);
1929 }
1930 
1931 /*
1932  * Returns the last component of a path name, unless it is
1933  * an absolute path, in which case it returns the whole path
1934  */
1935 static char
1936 *gettail(char *fname)
1937 {
1938 	char	*base = fname;
1939 
1940 	if (*fname != '/') {
1941 		if ((base = strrchr(fname, '/')) != NULL)
1942 			base++;
1943 		else
1944 			base = fname;
1945 	}
1946 	return (base);
1947 }
1948