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