xref: /freebsd/bin/sh/exec.c (revision 61e21613)
1 /*-
2  * Copyright (c) 1991, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Kenneth Almquist.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <unistd.h>
36 #include <fcntl.h>
37 #include <errno.h>
38 #include <paths.h>
39 #include <stdbool.h>
40 #include <stdlib.h>
41 
42 /*
43  * When commands are first encountered, they are entered in a hash table.
44  * This ensures that a full path search will not have to be done for them
45  * on each invocation.
46  *
47  * We should investigate converting to a linear search, even though that
48  * would make the command name "hash" a misnomer.
49  */
50 
51 #include "shell.h"
52 #include "main.h"
53 #include "nodes.h"
54 #include "parser.h"
55 #include "redir.h"
56 #include "eval.h"
57 #include "exec.h"
58 #include "builtins.h"
59 #include "var.h"
60 #include "options.h"
61 #include "input.h"
62 #include "output.h"
63 #include "syntax.h"
64 #include "memalloc.h"
65 #include "error.h"
66 #include "mystring.h"
67 #include "show.h"
68 #include "jobs.h"
69 #include "alias.h"
70 
71 
72 #define CMDTABLESIZE 31		/* should be prime */
73 
74 
75 
76 struct tblentry {
77 	struct tblentry *next;	/* next entry in hash chain */
78 	union param param;	/* definition of builtin function */
79 	int special;		/* flag for special builtin commands */
80 	signed char cmdtype;	/* index identifying command */
81 	char cmdname[];		/* name of command */
82 };
83 
84 
85 static struct tblentry *cmdtable[CMDTABLESIZE];
86 static int cmdtable_cd = 0;	/* cmdtable contains cd-dependent entries */
87 
88 
89 static void tryexec(char *, char **, char **);
90 static void printentry(struct tblentry *, int);
91 static struct tblentry *cmdlookup(const char *, int);
92 static void delete_cmd_entry(void);
93 static void addcmdentry(const char *, struct cmdentry *);
94 
95 
96 
97 /*
98  * Exec a program.  Never returns.  If you change this routine, you may
99  * have to change the find_command routine as well.
100  *
101  * The argv array may be changed and element argv[-1] should be writable.
102  */
103 
104 void
105 shellexec(char **argv, char **envp, const char *path, int idx)
106 {
107 	char *cmdname;
108 	const char *opt;
109 	int e;
110 
111 	if (strchr(argv[0], '/') != NULL) {
112 		tryexec(argv[0], argv, envp);
113 		e = errno;
114 	} else {
115 		e = ENOENT;
116 		while ((cmdname = padvance(&path, &opt, argv[0])) != NULL) {
117 			if (--idx < 0 && opt == NULL) {
118 				tryexec(cmdname, argv, envp);
119 				if (errno != ENOENT && errno != ENOTDIR)
120 					e = errno;
121 				if (e == ENOEXEC)
122 					break;
123 			}
124 			stunalloc(cmdname);
125 		}
126 	}
127 
128 	/* Map to POSIX errors */
129 	if (e == ENOENT || e == ENOTDIR)
130 		errorwithstatus(127, "%s: not found", argv[0]);
131 	else
132 		errorwithstatus(126, "%s: %s", argv[0], strerror(e));
133 }
134 
135 
136 static bool
137 isbinary(const char *data, size_t len)
138 {
139 	const char *nul, *p;
140 	bool hasletter;
141 
142 	nul = memchr(data, '\0', len);
143 	if (nul == NULL)
144 		return false;
145 	/*
146 	 * POSIX says we shall allow execution if the initial part intended
147 	 * to be parsed by the shell consists of characters and does not
148 	 * contain the NUL character. This allows concatenating a shell
149 	 * script (ending with exec or exit) and a binary payload.
150 	 *
151 	 * In order to reject common binary files such as PNG images, check
152 	 * that there is a lowercase letter or expansion before the last
153 	 * newline before the NUL character, in addition to the check for
154 	 * the newline character suggested by POSIX.
155 	 */
156 	hasletter = false;
157 	for (p = data; *p != '\0'; p++) {
158 		if ((*p >= 'a' && *p <= 'z') || *p == '$' || *p == '`')
159 			hasletter = true;
160 		if (hasletter && *p == '\n')
161 			return false;
162 	}
163 	return true;
164 }
165 
166 
167 static void
168 tryexec(char *cmd, char **argv, char **envp)
169 {
170 	int e, in;
171 	ssize_t n;
172 	char buf[256];
173 
174 	execve(cmd, argv, envp);
175 	e = errno;
176 	if (e == ENOEXEC) {
177 		INTOFF;
178 		in = open(cmd, O_RDONLY | O_NONBLOCK);
179 		if (in != -1) {
180 			n = pread(in, buf, sizeof buf, 0);
181 			close(in);
182 			if (n > 0 && isbinary(buf, n)) {
183 				errno = ENOEXEC;
184 				return;
185 			}
186 		}
187 		*argv = cmd;
188 		*--argv = __DECONST(char *, _PATH_BSHELL);
189 		execve(_PATH_BSHELL, argv, envp);
190 	}
191 	errno = e;
192 }
193 
194 /*
195  * Do a path search.  The variable path (passed by reference) should be
196  * set to the start of the path before the first call; padvance will update
197  * this value as it proceeds.  Successive calls to padvance will return
198  * the possible path expansions in sequence.  If popt is not NULL, options
199  * are processed: if an option (indicated by a percent sign) appears in
200  * the path entry then *popt will be set to point to it; else *popt will be
201  * set to NULL.  If popt is NULL, percent signs are not special.
202  */
203 
204 char *
205 padvance(const char **path, const char **popt, const char *name)
206 {
207 	const char *p, *start;
208 	char *q;
209 	size_t len, namelen;
210 
211 	if (*path == NULL)
212 		return NULL;
213 	start = *path;
214 	if (popt != NULL)
215 		for (p = start; *p && *p != ':' && *p != '%'; p++)
216 			; /* nothing */
217 	else
218 		for (p = start; *p && *p != ':'; p++)
219 			; /* nothing */
220 	namelen = strlen(name);
221 	len = p - start + namelen + 2;	/* "2" is for '/' and '\0' */
222 	STARTSTACKSTR(q);
223 	CHECKSTRSPACE(len, q);
224 	if (p != start) {
225 		memcpy(q, start, p - start);
226 		q += p - start;
227 		*q++ = '/';
228 	}
229 	memcpy(q, name, namelen + 1);
230 	if (popt != NULL) {
231 		if (*p == '%') {
232 			*popt = ++p;
233 			while (*p && *p != ':')  p++;
234 		} else
235 			*popt = NULL;
236 	}
237 	if (*p == ':')
238 		*path = p + 1;
239 	else
240 		*path = NULL;
241 	return stalloc(len);
242 }
243 
244 
245 
246 /*** Command hashing code ***/
247 
248 
249 int
250 hashcmd(int argc __unused, char **argv __unused)
251 {
252 	struct tblentry **pp;
253 	struct tblentry *cmdp;
254 	int c;
255 	int verbose;
256 	struct cmdentry entry;
257 	char *name;
258 	int errors;
259 
260 	errors = 0;
261 	verbose = 0;
262 	while ((c = nextopt("rv")) != '\0') {
263 		if (c == 'r') {
264 			clearcmdentry();
265 		} else if (c == 'v') {
266 			verbose++;
267 		}
268 	}
269 	if (*argptr == NULL) {
270 		for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
271 			for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
272 				if (cmdp->cmdtype == CMDNORMAL)
273 					printentry(cmdp, verbose);
274 			}
275 		}
276 		return 0;
277 	}
278 	while ((name = *argptr) != NULL) {
279 		if ((cmdp = cmdlookup(name, 0)) != NULL
280 		 && cmdp->cmdtype == CMDNORMAL)
281 			delete_cmd_entry();
282 		find_command(name, &entry, DO_ERR, pathval());
283 		if (entry.cmdtype == CMDUNKNOWN)
284 			errors = 1;
285 		else if (verbose) {
286 			cmdp = cmdlookup(name, 0);
287 			if (cmdp != NULL)
288 				printentry(cmdp, verbose);
289 			else {
290 				outfmt(out2, "%s: not found\n", name);
291 				errors = 1;
292 			}
293 			flushall();
294 		}
295 		argptr++;
296 	}
297 	return errors;
298 }
299 
300 
301 static void
302 printentry(struct tblentry *cmdp, int verbose)
303 {
304 	int idx;
305 	const char *path, *opt;
306 	char *name;
307 
308 	if (cmdp->cmdtype == CMDNORMAL) {
309 		idx = cmdp->param.index;
310 		path = pathval();
311 		do {
312 			name = padvance(&path, &opt, cmdp->cmdname);
313 			stunalloc(name);
314 		} while (--idx >= 0);
315 		out1str(name);
316 	} else if (cmdp->cmdtype == CMDBUILTIN) {
317 		out1fmt("builtin %s", cmdp->cmdname);
318 	} else if (cmdp->cmdtype == CMDFUNCTION) {
319 		out1fmt("function %s", cmdp->cmdname);
320 		if (verbose) {
321 			INTOFF;
322 			name = commandtext(getfuncnode(cmdp->param.func));
323 			out1c(' ');
324 			out1str(name);
325 			ckfree(name);
326 			INTON;
327 		}
328 #ifdef DEBUG
329 	} else {
330 		error("internal error: cmdtype %d", cmdp->cmdtype);
331 #endif
332 	}
333 	out1c('\n');
334 }
335 
336 
337 
338 /*
339  * Resolve a command name.  If you change this routine, you may have to
340  * change the shellexec routine as well.
341  */
342 
343 void
344 find_command(const char *name, struct cmdentry *entry, int act,
345     const char *path)
346 {
347 	struct tblentry *cmdp, loc_cmd;
348 	int idx;
349 	const char *opt;
350 	char *fullname;
351 	struct stat statb;
352 	int e;
353 	int i;
354 	int spec;
355 	int cd;
356 
357 	/* If name contains a slash, don't use the hash table */
358 	if (strchr(name, '/') != NULL) {
359 		entry->cmdtype = CMDNORMAL;
360 		entry->u.index = 0;
361 		entry->special = 0;
362 		return;
363 	}
364 
365 	cd = 0;
366 
367 	/* If name is in the table, we're done */
368 	if ((cmdp = cmdlookup(name, 0)) != NULL) {
369 		if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC)
370 			cmdp = NULL;
371 		else
372 			goto success;
373 	}
374 
375 	/* Check for builtin next */
376 	if ((i = find_builtin(name, &spec)) >= 0) {
377 		INTOFF;
378 		cmdp = cmdlookup(name, 1);
379 		if (cmdp->cmdtype == CMDFUNCTION)
380 			cmdp = &loc_cmd;
381 		cmdp->cmdtype = CMDBUILTIN;
382 		cmdp->param.index = i;
383 		cmdp->special = spec;
384 		INTON;
385 		goto success;
386 	}
387 
388 	/* We have to search path. */
389 
390 	e = ENOENT;
391 	idx = -1;
392 	for (;(fullname = padvance(&path, &opt, name)) != NULL;
393 	    stunalloc(fullname)) {
394 		idx++;
395 		if (opt) {
396 			if (strncmp(opt, "func", 4) == 0) {
397 				/* handled below */
398 			} else {
399 				continue; /* ignore unimplemented options */
400 			}
401 		}
402 		if (fullname[0] != '/')
403 			cd = 1;
404 		if (stat(fullname, &statb) < 0) {
405 			if (errno != ENOENT && errno != ENOTDIR)
406 				e = errno;
407 			continue;
408 		}
409 		e = EACCES;	/* if we fail, this will be the error */
410 		if (!S_ISREG(statb.st_mode))
411 			continue;
412 		if (opt) {		/* this is a %func directory */
413 			readcmdfile(fullname, -1 /* verify */);
414 			if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION)
415 				error("%s not defined in %s", name, fullname);
416 			stunalloc(fullname);
417 			goto success;
418 		}
419 #ifdef notdef
420 		if (statb.st_uid == geteuid()) {
421 			if ((statb.st_mode & 0100) == 0)
422 				goto loop;
423 		} else if (statb.st_gid == getegid()) {
424 			if ((statb.st_mode & 010) == 0)
425 				goto loop;
426 		} else {
427 			if ((statb.st_mode & 01) == 0)
428 				goto loop;
429 		}
430 #endif
431 		TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
432 		INTOFF;
433 		stunalloc(fullname);
434 		cmdp = cmdlookup(name, 1);
435 		if (cmdp->cmdtype == CMDFUNCTION)
436 			cmdp = &loc_cmd;
437 		cmdp->cmdtype = CMDNORMAL;
438 		cmdp->param.index = idx;
439 		cmdp->special = 0;
440 		INTON;
441 		goto success;
442 	}
443 
444 	if (act & DO_ERR) {
445 		if (e == ENOENT || e == ENOTDIR)
446 			outfmt(out2, "%s: not found\n", name);
447 		else
448 			outfmt(out2, "%s: %s\n", name, strerror(e));
449 	}
450 	entry->cmdtype = CMDUNKNOWN;
451 	entry->u.index = 0;
452 	entry->special = 0;
453 	return;
454 
455 success:
456 	if (cd)
457 		cmdtable_cd = 1;
458 	entry->cmdtype = cmdp->cmdtype;
459 	entry->u = cmdp->param;
460 	entry->special = cmdp->special;
461 }
462 
463 
464 
465 /*
466  * Search the table of builtin commands.
467  */
468 
469 int
470 find_builtin(const char *name, int *special)
471 {
472 	const unsigned char *bp;
473 	size_t len;
474 
475 	len = strlen(name);
476 	for (bp = builtincmd ; *bp ; bp += 2 + bp[0]) {
477 		if (bp[0] == len && memcmp(bp + 2, name, len) == 0) {
478 			*special = (bp[1] & BUILTIN_SPECIAL) != 0;
479 			return bp[1] & ~BUILTIN_SPECIAL;
480 		}
481 	}
482 	return -1;
483 }
484 
485 
486 
487 /*
488  * Called when a cd is done.  If any entry in cmdtable depends on the current
489  * directory, simply clear cmdtable completely.
490  */
491 
492 void
493 hashcd(void)
494 {
495 	if (cmdtable_cd)
496 		clearcmdentry();
497 }
498 
499 
500 
501 /*
502  * Called before PATH is changed.  The argument is the new value of PATH;
503  * pathval() still returns the old value at this point.  Called with
504  * interrupts off.
505  */
506 
507 void
508 changepath(const char *newval __unused)
509 {
510 	clearcmdentry();
511 }
512 
513 
514 /*
515  * Clear out cached utility locations.
516  */
517 
518 void
519 clearcmdentry(void)
520 {
521 	struct tblentry **tblp;
522 	struct tblentry **pp;
523 	struct tblentry *cmdp;
524 
525 	INTOFF;
526 	for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
527 		pp = tblp;
528 		while ((cmdp = *pp) != NULL) {
529 			if (cmdp->cmdtype == CMDNORMAL) {
530 				*pp = cmdp->next;
531 				ckfree(cmdp);
532 			} else {
533 				pp = &cmdp->next;
534 			}
535 		}
536 	}
537 	cmdtable_cd = 0;
538 	INTON;
539 }
540 
541 
542 static unsigned int
543 hashname(const char *p)
544 {
545 	unsigned int hashval;
546 
547 	hashval = (unsigned char)*p << 4;
548 	while (*p)
549 		hashval += *p++;
550 
551 	return (hashval % CMDTABLESIZE);
552 }
553 
554 
555 /*
556  * Locate a command in the command hash table.  If "add" is nonzero,
557  * add the command to the table if it is not already present.  The
558  * variable "lastcmdentry" is set to point to the address of the link
559  * pointing to the entry, so that delete_cmd_entry can delete the
560  * entry.
561  */
562 
563 static struct tblentry **lastcmdentry;
564 
565 
566 static struct tblentry *
567 cmdlookup(const char *name, int add)
568 {
569 	struct tblentry *cmdp;
570 	struct tblentry **pp;
571 	size_t len;
572 
573 	pp = &cmdtable[hashname(name)];
574 	for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
575 		if (equal(cmdp->cmdname, name))
576 			break;
577 		pp = &cmdp->next;
578 	}
579 	if (add && cmdp == NULL) {
580 		INTOFF;
581 		len = strlen(name);
582 		cmdp = *pp = ckmalloc(sizeof (struct tblentry) + len + 1);
583 		cmdp->next = NULL;
584 		cmdp->cmdtype = CMDUNKNOWN;
585 		memcpy(cmdp->cmdname, name, len + 1);
586 		INTON;
587 	}
588 	lastcmdentry = pp;
589 	return cmdp;
590 }
591 
592 const void *
593 itercmd(const void *entry, struct cmdentry *result)
594 {
595 	const struct tblentry *e = entry;
596 	size_t i = 0;
597 
598 	if (e != NULL) {
599 		if (e->next != NULL) {
600 			e = e->next;
601 			goto success;
602 		}
603 		i = hashname(e->cmdname) + 1;
604 	}
605 	for (; i < CMDTABLESIZE; i++)
606 		if ((e = cmdtable[i]) != NULL)
607 			goto success;
608 
609 	return (NULL);
610 success:
611 	result->cmdtype = e->cmdtype;
612 	result->cmdname = e->cmdname;
613 
614 	return (e);
615 }
616 
617 /*
618  * Delete the command entry returned on the last lookup.
619  */
620 
621 static void
622 delete_cmd_entry(void)
623 {
624 	struct tblentry *cmdp;
625 
626 	INTOFF;
627 	cmdp = *lastcmdentry;
628 	*lastcmdentry = cmdp->next;
629 	ckfree(cmdp);
630 	INTON;
631 }
632 
633 
634 
635 /*
636  * Add a new command entry, replacing any existing command entry for
637  * the same name.
638  */
639 
640 static void
641 addcmdentry(const char *name, struct cmdentry *entry)
642 {
643 	struct tblentry *cmdp;
644 
645 	INTOFF;
646 	cmdp = cmdlookup(name, 1);
647 	if (cmdp->cmdtype == CMDFUNCTION) {
648 		unreffunc(cmdp->param.func);
649 	}
650 	cmdp->cmdtype = entry->cmdtype;
651 	cmdp->param = entry->u;
652 	cmdp->special = entry->special;
653 	INTON;
654 }
655 
656 
657 /*
658  * Define a shell function.
659  */
660 
661 void
662 defun(const char *name, union node *func)
663 {
664 	struct cmdentry entry;
665 
666 	INTOFF;
667 	entry.cmdtype = CMDFUNCTION;
668 	entry.u.func = copyfunc(func);
669 	entry.special = 0;
670 	addcmdentry(name, &entry);
671 	INTON;
672 }
673 
674 
675 /*
676  * Delete a function if it exists.
677  * Called with interrupts off.
678  */
679 
680 int
681 unsetfunc(const char *name)
682 {
683 	struct tblentry *cmdp;
684 
685 	if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) {
686 		unreffunc(cmdp->param.func);
687 		delete_cmd_entry();
688 		return (0);
689 	}
690 	return (0);
691 }
692 
693 
694 /*
695  * Check if a function by a certain name exists.
696  */
697 int
698 isfunc(const char *name)
699 {
700 	struct tblentry *cmdp;
701 	cmdp = cmdlookup(name, 0);
702 	return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION);
703 }
704 
705 
706 static void
707 print_absolute_path(const char *name)
708 {
709 	const char *pwd;
710 
711 	if (*name != '/' && (pwd = lookupvar("PWD")) != NULL && *pwd != '\0') {
712 		out1str(pwd);
713 		if (strcmp(pwd, "/") != 0)
714 			outcslow('/', out1);
715 	}
716 	out1str(name);
717 	outcslow('\n', out1);
718 }
719 
720 
721 /*
722  * Shared code for the following builtin commands:
723  *    type, command -v, command -V
724  */
725 
726 int
727 typecmd_impl(int argc, char **argv, int cmd, const char *path)
728 {
729 	struct cmdentry entry;
730 	struct tblentry *cmdp;
731 	const char *const *pp;
732 	struct alias *ap;
733 	int i;
734 	int error1 = 0;
735 
736 	if (path != pathval())
737 		clearcmdentry();
738 
739 	for (i = 1; i < argc; i++) {
740 		/* First look at the keywords */
741 		for (pp = parsekwd; *pp; pp++)
742 			if (**pp == *argv[i] && equal(*pp, argv[i]))
743 				break;
744 
745 		if (*pp) {
746 			if (cmd == TYPECMD_SMALLV)
747 				out1fmt("%s\n", argv[i]);
748 			else
749 				out1fmt("%s is a shell keyword\n", argv[i]);
750 			continue;
751 		}
752 
753 		/* Then look at the aliases */
754 		if ((ap = lookupalias(argv[i], 1)) != NULL) {
755 			if (cmd == TYPECMD_SMALLV) {
756 				out1fmt("alias %s=", argv[i]);
757 				out1qstr(ap->val);
758 				outcslow('\n', out1);
759 			} else
760 				out1fmt("%s is an alias for %s\n", argv[i],
761 				    ap->val);
762 			continue;
763 		}
764 
765 		/* Then check if it is a tracked alias */
766 		if ((cmdp = cmdlookup(argv[i], 0)) != NULL) {
767 			entry.cmdtype = cmdp->cmdtype;
768 			entry.u = cmdp->param;
769 			entry.special = cmdp->special;
770 		}
771 		else {
772 			/* Finally use brute force */
773 			find_command(argv[i], &entry, 0, path);
774 		}
775 
776 		switch (entry.cmdtype) {
777 		case CMDNORMAL: {
778 			if (strchr(argv[i], '/') == NULL) {
779 				const char *path2 = path;
780 				const char *opt2;
781 				char *name;
782 				int j = entry.u.index;
783 				do {
784 					name = padvance(&path2, &opt2, argv[i]);
785 					stunalloc(name);
786 				} while (--j >= 0);
787 				if (cmd != TYPECMD_SMALLV)
788 					out1fmt("%s is%s ", argv[i],
789 					    (cmdp && cmd == TYPECMD_TYPE) ?
790 						" a tracked alias for" : "");
791 				print_absolute_path(name);
792 			} else {
793 				if (eaccess(argv[i], X_OK) == 0) {
794 					if (cmd != TYPECMD_SMALLV)
795 						out1fmt("%s is ", argv[i]);
796 					print_absolute_path(argv[i]);
797 				} else {
798 					if (cmd != TYPECMD_SMALLV)
799 						outfmt(out2, "%s: %s\n",
800 						    argv[i], strerror(errno));
801 					error1 |= 127;
802 				}
803 			}
804 			break;
805 		}
806 		case CMDFUNCTION:
807 			if (cmd == TYPECMD_SMALLV)
808 				out1fmt("%s\n", argv[i]);
809 			else
810 				out1fmt("%s is a shell function\n", argv[i]);
811 			break;
812 
813 		case CMDBUILTIN:
814 			if (cmd == TYPECMD_SMALLV)
815 				out1fmt("%s\n", argv[i]);
816 			else if (entry.special)
817 				out1fmt("%s is a special shell builtin\n",
818 				    argv[i]);
819 			else
820 				out1fmt("%s is a shell builtin\n", argv[i]);
821 			break;
822 
823 		default:
824 			if (cmd != TYPECMD_SMALLV)
825 				outfmt(out2, "%s: not found\n", argv[i]);
826 			error1 |= 127;
827 			break;
828 		}
829 	}
830 
831 	if (path != pathval())
832 		clearcmdentry();
833 
834 	return error1;
835 }
836 
837 /*
838  * Locate and print what a word is...
839  */
840 
841 int
842 typecmd(int argc, char **argv)
843 {
844 	if (argc > 2 && strcmp(argv[1], "--") == 0)
845 		argc--, argv++;
846 	return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1));
847 }
848