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