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