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