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