exec.c revision 64704
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. All advertising materials mentioning features or use of this software
17 *    must display the following acknowledgement:
18 *	This product includes software developed by the University of
19 *	California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 *    may be used to endorse or promote products derived from this software
22 *    without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#ifndef lint
38#if 0
39static char sccsid[] = "@(#)exec.c	8.4 (Berkeley) 6/8/95";
40#endif
41static const char rcsid[] =
42  "$FreeBSD: head/bin/sh/exec.c 64704 2000-08-16 12:08:02Z cracauer $";
43#endif /* not lint */
44
45#include <sys/types.h>
46#include <sys/stat.h>
47#include <unistd.h>
48#include <fcntl.h>
49#include <errno.h>
50#include <stdlib.h>
51
52/*
53 * When commands are first encountered, they are entered in a hash table.
54 * This ensures that a full path search will not have to be done for them
55 * on each invocation.
56 *
57 * We should investigate converting to a linear search, even though that
58 * would make the command name "hash" a misnomer.
59 */
60
61#include "shell.h"
62#include "main.h"
63#include "nodes.h"
64#include "parser.h"
65#include "redir.h"
66#include "eval.h"
67#include "exec.h"
68#include "builtins.h"
69#include "var.h"
70#include "options.h"
71#include "input.h"
72#include "output.h"
73#include "syntax.h"
74#include "memalloc.h"
75#include "error.h"
76#include "init.h"
77#include "mystring.h"
78#include "show.h"
79#include "jobs.h"
80#include "alias.h"
81
82
83#define CMDTABLESIZE 31		/* should be prime */
84#define ARB 1			/* actual size determined at run time */
85
86
87
88struct tblentry {
89	struct tblentry *next;	/* next entry in hash chain */
90	union param param;	/* definition of builtin function */
91	short cmdtype;		/* index identifying command */
92	char rehash;		/* if set, cd done since entry created */
93	char cmdname[ARB];	/* name of command */
94};
95
96
97STATIC struct tblentry *cmdtable[CMDTABLESIZE];
98STATIC int builtinloc = -1;		/* index in path of %builtin, or -1 */
99int exerrno = 0;			/* Last exec error */
100
101
102STATIC void tryexec __P((char *, char **, char **));
103#ifndef BSD
104STATIC void execinterp __P((char **, char **));
105#endif
106STATIC void printentry __P((struct tblentry *, int));
107STATIC struct tblentry *cmdlookup __P((char *, int));
108STATIC void delete_cmd_entry __P((void));
109
110
111
112/*
113 * Exec a program.  Never returns.  If you change this routine, you may
114 * have to change the find_command routine as well.
115 */
116
117void
118shellexec(argv, envp, path, index)
119	char **argv, **envp;
120	char *path;
121	int index;
122{
123	char *cmdname;
124	int e;
125
126	if (strchr(argv[0], '/') != NULL) {
127		tryexec(argv[0], argv, envp);
128		e = errno;
129	} else {
130		e = ENOENT;
131		while ((cmdname = padvance(&path, argv[0])) != NULL) {
132			if (--index < 0 && pathopt == NULL) {
133				tryexec(cmdname, argv, envp);
134				if (errno != ENOENT && errno != ENOTDIR)
135					e = errno;
136			}
137			stunalloc(cmdname);
138		}
139	}
140
141	/* Map to POSIX errors */
142	switch (e) {
143	case EACCES:
144		exerrno = 126;
145		break;
146	case ENOENT:
147		exerrno = 127;
148		break;
149	default:
150		exerrno = 2;
151		break;
152	}
153	exerror(EXEXEC, "%s: %s", argv[0], errmsg(e, E_EXEC));
154}
155
156
157STATIC void
158tryexec(cmd, argv, envp)
159	char *cmd;
160	char **argv;
161	char **envp;
162	{
163	int e;
164#ifndef BSD
165	char *p;
166#endif
167
168#ifdef SYSV
169	do {
170		execve(cmd, argv, envp);
171	} while (errno == EINTR);
172#else
173	execve(cmd, argv, envp);
174#endif
175	e = errno;
176	if (e == ENOEXEC) {
177		initshellproc();
178		setinputfile(cmd, 0);
179		commandname = arg0 = savestr(argv[0]);
180#ifndef BSD
181		pgetc(); pungetc();		/* fill up input buffer */
182		p = parsenextc;
183		if (parsenleft > 2 && p[0] == '#' && p[1] == '!') {
184			argv[0] = cmd;
185			execinterp(argv, envp);
186		}
187#endif
188		setparam(argv + 1);
189		exraise(EXSHELLPROC);
190		/*NOTREACHED*/
191	}
192	errno = e;
193}
194
195
196#ifndef BSD
197/*
198 * Execute an interpreter introduced by "#!", for systems where this
199 * feature has not been built into the kernel.  If the interpreter is
200 * the shell, return (effectively ignoring the "#!").  If the execution
201 * of the interpreter fails, exit.
202 *
203 * This code peeks inside the input buffer in order to avoid actually
204 * reading any input.  It would benefit from a rewrite.
205 */
206
207#define NEWARGS 5
208
209STATIC void
210execinterp(argv, envp)
211	char **argv, **envp;
212	{
213	int n;
214	char *inp;
215	char *outp;
216	char c;
217	char *p;
218	char **ap;
219	char *newargs[NEWARGS];
220	int i;
221	char **ap2;
222	char **new;
223
224	n = parsenleft - 2;
225	inp = parsenextc + 2;
226	ap = newargs;
227	for (;;) {
228		while (--n >= 0 && (*inp == ' ' || *inp == '\t'))
229			inp++;
230		if (n < 0)
231			goto bad;
232		if ((c = *inp++) == '\n')
233			break;
234		if (ap == &newargs[NEWARGS])
235bad:		  error("Bad #! line");
236		STARTSTACKSTR(outp);
237		do {
238			STPUTC(c, outp);
239		} while (--n >= 0 && (c = *inp++) != ' ' && c != '\t' && c != '\n');
240		STPUTC('\0', outp);
241		n++, inp--;
242		*ap++ = grabstackstr(outp);
243	}
244	if (ap == newargs + 1) {	/* if no args, maybe no exec is needed */
245		p = newargs[0];
246		for (;;) {
247			if (equal(p, "sh") || equal(p, "ash")) {
248				return;
249			}
250			while (*p != '/') {
251				if (*p == '\0')
252					goto break2;
253				p++;
254			}
255			p++;
256		}
257break2:;
258	}
259	i = (char *)ap - (char *)newargs;		/* size in bytes */
260	if (i == 0)
261		error("Bad #! line");
262	for (ap2 = argv ; *ap2++ != NULL ; );
263	new = ckmalloc(i + ((char *)ap2 - (char *)argv));
264	ap = newargs, ap2 = new;
265	while ((i -= sizeof (char **)) >= 0)
266		*ap2++ = *ap++;
267	ap = argv;
268	while (*ap2++ = *ap++);
269	shellexec(new, envp, pathval(), 0);
270}
271#endif
272
273
274
275/*
276 * Do a path search.  The variable path (passed by reference) should be
277 * set to the start of the path before the first call; padvance will update
278 * this value as it proceeds.  Successive calls to padvance will return
279 * the possible path expansions in sequence.  If an option (indicated by
280 * a percent sign) appears in the path entry then the global variable
281 * pathopt will be set to point to it; otherwise pathopt will be set to
282 * NULL.
283 */
284
285char *pathopt;
286
287char *
288padvance(path, name)
289	char **path;
290	char *name;
291	{
292	char *p, *q;
293	char *start;
294	int len;
295
296	if (*path == NULL)
297		return NULL;
298	start = *path;
299	for (p = start ; *p && *p != ':' && *p != '%' ; p++);
300	len = p - start + strlen(name) + 2;	/* "2" is for '/' and '\0' */
301	while (stackblocksize() < len)
302		growstackblock();
303	q = stackblock();
304	if (p != start) {
305		memcpy(q, start, p - start);
306		q += p - start;
307		*q++ = '/';
308	}
309	strcpy(q, name);
310	pathopt = NULL;
311	if (*p == '%') {
312		pathopt = ++p;
313		while (*p && *p != ':')  p++;
314	}
315	if (*p == ':')
316		*path = p + 1;
317	else
318		*path = NULL;
319	return stalloc(len);
320}
321
322
323
324/*** Command hashing code ***/
325
326
327int
328hashcmd(argc, argv)
329	int argc __unused;
330	char **argv __unused;
331{
332	struct tblentry **pp;
333	struct tblentry *cmdp;
334	int c;
335	int verbose;
336	struct cmdentry entry;
337	char *name;
338
339	verbose = 0;
340	while ((c = nextopt("rv")) != '\0') {
341		if (c == 'r') {
342			clearcmdentry(0);
343		} else if (c == 'v') {
344			verbose++;
345		}
346	}
347	if (*argptr == NULL) {
348		for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
349			for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
350				printentry(cmdp, verbose);
351			}
352		}
353		return 0;
354	}
355	while ((name = *argptr) != NULL) {
356		if ((cmdp = cmdlookup(name, 0)) != NULL
357		 && (cmdp->cmdtype == CMDNORMAL
358		     || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)))
359			delete_cmd_entry();
360		find_command(name, &entry, 1, pathval());
361		if (verbose) {
362			if (entry.cmdtype != CMDUNKNOWN) {	/* if no error msg */
363				cmdp = cmdlookup(name, 0);
364				printentry(cmdp, verbose);
365			}
366			flushall();
367		}
368		argptr++;
369	}
370	return 0;
371}
372
373
374STATIC void
375printentry(cmdp, verbose)
376	struct tblentry *cmdp;
377	int verbose;
378	{
379	int index;
380	char *path;
381	char *name;
382
383	if (cmdp->cmdtype == CMDNORMAL) {
384		index = cmdp->param.index;
385		path = pathval();
386		do {
387			name = padvance(&path, cmdp->cmdname);
388			stunalloc(name);
389		} while (--index >= 0);
390		out1str(name);
391	} else if (cmdp->cmdtype == CMDBUILTIN) {
392		out1fmt("builtin %s", cmdp->cmdname);
393	} else if (cmdp->cmdtype == CMDFUNCTION) {
394		out1fmt("function %s", cmdp->cmdname);
395		if (verbose) {
396			INTOFF;
397			name = commandtext(cmdp->param.func);
398			out1c(' ');
399			out1str(name);
400			ckfree(name);
401			INTON;
402		}
403#ifdef DEBUG
404	} else {
405		error("internal error: cmdtype %d", cmdp->cmdtype);
406#endif
407	}
408	if (cmdp->rehash)
409		out1c('*');
410	out1c('\n');
411}
412
413
414
415/*
416 * Resolve a command name.  If you change this routine, you may have to
417 * change the shellexec routine as well.
418 */
419
420void
421find_command(name, entry, printerr, path)
422	char *name;
423	struct cmdentry *entry;
424	int printerr;
425	char *path;
426{
427	struct tblentry *cmdp;
428	int index;
429	int prev;
430	char *fullname;
431	struct stat statb;
432	int e;
433	int i;
434
435	/* If name contains a slash, don't use the hash table */
436	if (strchr(name, '/') != NULL) {
437		entry->cmdtype = CMDNORMAL;
438		entry->u.index = 0;
439		return;
440	}
441
442	/* If name is in the table, and not invalidated by cd, we're done */
443	if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->rehash == 0)
444		goto success;
445
446	/* If %builtin not in path, check for builtin next */
447	if (builtinloc < 0 && (i = find_builtin(name)) >= 0) {
448		INTOFF;
449		cmdp = cmdlookup(name, 1);
450		cmdp->cmdtype = CMDBUILTIN;
451		cmdp->param.index = i;
452		INTON;
453		goto success;
454	}
455
456	/* We have to search path. */
457	prev = -1;		/* where to start */
458	if (cmdp) {		/* doing a rehash */
459		if (cmdp->cmdtype == CMDBUILTIN)
460			prev = builtinloc;
461		else
462			prev = cmdp->param.index;
463	}
464
465	e = ENOENT;
466	index = -1;
467loop:
468	while ((fullname = padvance(&path, name)) != NULL) {
469		stunalloc(fullname);
470		index++;
471		if (pathopt) {
472			if (prefix("builtin", pathopt)) {
473				if ((i = find_builtin(name)) < 0)
474					goto loop;
475				INTOFF;
476				cmdp = cmdlookup(name, 1);
477				cmdp->cmdtype = CMDBUILTIN;
478				cmdp->param.index = i;
479				INTON;
480				goto success;
481			} else if (prefix("func", pathopt)) {
482				/* handled below */
483			} else {
484				goto loop;	/* ignore unimplemented options */
485			}
486		}
487		/* if rehash, don't redo absolute path names */
488		if (fullname[0] == '/' && index <= prev) {
489			if (index < prev)
490				goto loop;
491			TRACE(("searchexec \"%s\": no change\n", name));
492			goto success;
493		}
494		while (stat(fullname, &statb) < 0) {
495#ifdef SYSV
496			if (errno == EINTR)
497				continue;
498#endif
499			if (errno != ENOENT && errno != ENOTDIR)
500				e = errno;
501			goto loop;
502		}
503		e = EACCES;	/* if we fail, this will be the error */
504		if (!S_ISREG(statb.st_mode))
505			goto loop;
506		if (pathopt) {		/* this is a %func directory */
507			stalloc(strlen(fullname) + 1);
508			readcmdfile(fullname);
509			if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION)
510				error("%s not defined in %s", name, fullname);
511			stunalloc(fullname);
512			goto success;
513		}
514#ifdef notdef
515		if (statb.st_uid == geteuid()) {
516			if ((statb.st_mode & 0100) == 0)
517				goto loop;
518		} else if (statb.st_gid == getegid()) {
519			if ((statb.st_mode & 010) == 0)
520				goto loop;
521		} else {
522			if ((statb.st_mode & 01) == 0)
523				goto loop;
524		}
525#endif
526		TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
527		INTOFF;
528		cmdp = cmdlookup(name, 1);
529		cmdp->cmdtype = CMDNORMAL;
530		cmdp->param.index = index;
531		INTON;
532		goto success;
533	}
534
535	/* We failed.  If there was an entry for this command, delete it */
536	if (cmdp)
537		delete_cmd_entry();
538	if (printerr)
539		outfmt(out2, "%s: %s\n", name, errmsg(e, E_EXEC));
540	entry->cmdtype = CMDUNKNOWN;
541	return;
542
543success:
544	cmdp->rehash = 0;
545	entry->cmdtype = cmdp->cmdtype;
546	entry->u = cmdp->param;
547}
548
549
550
551/*
552 * Search the table of builtin commands.
553 */
554
555int
556find_builtin(name)
557	char *name;
558{
559	const struct builtincmd *bp;
560
561	for (bp = builtincmd ; bp->name ; bp++) {
562		if (*bp->name == *name && equal(bp->name, name))
563			return bp->code;
564	}
565	return -1;
566}
567
568
569
570/*
571 * Called when a cd is done.  Marks all commands so the next time they
572 * are executed they will be rehashed.
573 */
574
575void
576hashcd() {
577	struct tblentry **pp;
578	struct tblentry *cmdp;
579
580	for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
581		for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
582			if (cmdp->cmdtype == CMDNORMAL
583			 || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))
584				cmdp->rehash = 1;
585		}
586	}
587}
588
589
590
591/*
592 * Called before PATH is changed.  The argument is the new value of PATH;
593 * pathval() still returns the old value at this point.  Called with
594 * interrupts off.
595 */
596
597void
598changepath(newval)
599	const char *newval;
600{
601	const char *old, *new;
602	int index;
603	int firstchange;
604	int bltin;
605
606	old = pathval();
607	new = newval;
608	firstchange = 9999;	/* assume no change */
609	index = 0;
610	bltin = -1;
611	for (;;) {
612		if (*old != *new) {
613			firstchange = index;
614			if ((*old == '\0' && *new == ':')
615			 || (*old == ':' && *new == '\0'))
616				firstchange++;
617			old = new;	/* ignore subsequent differences */
618		}
619		if (*new == '\0')
620			break;
621		if (*new == '%' && bltin < 0 && prefix("builtin", new + 1))
622			bltin = index;
623		if (*new == ':') {
624			index++;
625		}
626		new++, old++;
627	}
628	if (builtinloc < 0 && bltin >= 0)
629		builtinloc = bltin;		/* zap builtins */
630	if (builtinloc >= 0 && bltin < 0)
631		firstchange = 0;
632	clearcmdentry(firstchange);
633	builtinloc = bltin;
634}
635
636
637/*
638 * Clear out command entries.  The argument specifies the first entry in
639 * PATH which has changed.
640 */
641
642void
643clearcmdentry(firstchange)
644	int firstchange;
645{
646	struct tblentry **tblp;
647	struct tblentry **pp;
648	struct tblentry *cmdp;
649
650	INTOFF;
651	for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
652		pp = tblp;
653		while ((cmdp = *pp) != NULL) {
654			if ((cmdp->cmdtype == CMDNORMAL &&
655			     cmdp->param.index >= firstchange)
656			 || (cmdp->cmdtype == CMDBUILTIN &&
657			     builtinloc >= firstchange)) {
658				*pp = cmdp->next;
659				ckfree(cmdp);
660			} else {
661				pp = &cmdp->next;
662			}
663		}
664	}
665	INTON;
666}
667
668
669/*
670 * Delete all functions.
671 */
672
673#ifdef mkinit
674MKINIT void deletefuncs();
675
676SHELLPROC {
677	deletefuncs();
678}
679#endif
680
681void
682deletefuncs() {
683	struct tblentry **tblp;
684	struct tblentry **pp;
685	struct tblentry *cmdp;
686
687	INTOFF;
688	for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
689		pp = tblp;
690		while ((cmdp = *pp) != NULL) {
691			if (cmdp->cmdtype == CMDFUNCTION) {
692				*pp = cmdp->next;
693				freefunc(cmdp->param.func);
694				ckfree(cmdp);
695			} else {
696				pp = &cmdp->next;
697			}
698		}
699	}
700	INTON;
701}
702
703
704
705/*
706 * Locate a command in the command hash table.  If "add" is nonzero,
707 * add the command to the table if it is not already present.  The
708 * variable "lastcmdentry" is set to point to the address of the link
709 * pointing to the entry, so that delete_cmd_entry can delete the
710 * entry.
711 */
712
713struct tblentry **lastcmdentry;
714
715
716STATIC struct tblentry *
717cmdlookup(name, add)
718	char *name;
719	int add;
720{
721	int hashval;
722	char *p;
723	struct tblentry *cmdp;
724	struct tblentry **pp;
725
726	p = name;
727	hashval = *p << 4;
728	while (*p)
729		hashval += *p++;
730	hashval &= 0x7FFF;
731	pp = &cmdtable[hashval % CMDTABLESIZE];
732	for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
733		if (equal(cmdp->cmdname, name))
734			break;
735		pp = &cmdp->next;
736	}
737	if (add && cmdp == NULL) {
738		INTOFF;
739		cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB
740					+ strlen(name) + 1);
741		cmdp->next = NULL;
742		cmdp->cmdtype = CMDUNKNOWN;
743		cmdp->rehash = 0;
744		strcpy(cmdp->cmdname, name);
745		INTON;
746	}
747	lastcmdentry = pp;
748	return cmdp;
749}
750
751/*
752 * Delete the command entry returned on the last lookup.
753 */
754
755STATIC void
756delete_cmd_entry() {
757	struct tblentry *cmdp;
758
759	INTOFF;
760	cmdp = *lastcmdentry;
761	*lastcmdentry = cmdp->next;
762	ckfree(cmdp);
763	INTON;
764}
765
766
767
768#ifdef notdef
769void
770getcmdentry(name, entry)
771	char *name;
772	struct cmdentry *entry;
773	{
774	struct tblentry *cmdp = cmdlookup(name, 0);
775
776	if (cmdp) {
777		entry->u = cmdp->param;
778		entry->cmdtype = cmdp->cmdtype;
779	} else {
780		entry->cmdtype = CMDUNKNOWN;
781		entry->u.index = 0;
782	}
783}
784#endif
785
786
787/*
788 * Add a new command entry, replacing any existing command entry for
789 * the same name.
790 */
791
792void
793addcmdentry(name, entry)
794	char *name;
795	struct cmdentry *entry;
796	{
797	struct tblentry *cmdp;
798
799	INTOFF;
800	cmdp = cmdlookup(name, 1);
801	if (cmdp->cmdtype == CMDFUNCTION) {
802		freefunc(cmdp->param.func);
803	}
804	cmdp->cmdtype = entry->cmdtype;
805	cmdp->param = entry->u;
806	INTON;
807}
808
809
810/*
811 * Define a shell function.
812 */
813
814void
815defun(name, func)
816	char *name;
817	union node *func;
818	{
819	struct cmdentry entry;
820
821	INTOFF;
822	entry.cmdtype = CMDFUNCTION;
823	entry.u.func = copyfunc(func);
824	addcmdentry(name, &entry);
825	INTON;
826}
827
828
829/*
830 * Delete a function if it exists.
831 */
832
833int
834unsetfunc(name)
835	char *name;
836	{
837	struct tblentry *cmdp;
838
839	if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) {
840		freefunc(cmdp->param.func);
841		delete_cmd_entry();
842		return (0);
843	}
844	return (1);
845}
846
847/*
848 * Locate and print what a word is...
849 */
850
851int
852typecmd(argc, argv)
853	int argc;
854	char **argv;
855{
856	struct cmdentry entry;
857	struct tblentry *cmdp;
858	char **pp;
859	struct alias *ap;
860	int i;
861	int error = 0;
862	extern char *const parsekwd[];
863
864	for (i = 1; i < argc; i++) {
865		out1str(argv[i]);
866		/* First look at the keywords */
867		for (pp = (char **)parsekwd; *pp; pp++)
868			if (**pp == *argv[i] && equal(*pp, argv[i]))
869				break;
870
871		if (*pp) {
872			out1str(" is a shell keyword\n");
873			continue;
874		}
875
876		/* Then look at the aliases */
877		if ((ap = lookupalias(argv[i], 1)) != NULL) {
878			out1fmt(" is an alias for %s\n", ap->val);
879			continue;
880		}
881
882		/* Then check if it is a tracked alias */
883		if ((cmdp = cmdlookup(argv[i], 0)) != NULL) {
884			entry.cmdtype = cmdp->cmdtype;
885			entry.u = cmdp->param;
886		}
887		else {
888			/* Finally use brute force */
889			find_command(argv[i], &entry, 0, pathval());
890		}
891
892		switch (entry.cmdtype) {
893		case CMDNORMAL: {
894			if (strchr(argv[i], '/') == NULL) {
895				char *path = pathval(), *name;
896				int j = entry.u.index;
897				do {
898					name = padvance(&path, argv[i]);
899					stunalloc(name);
900				} while (--j >= 0);
901				out1fmt(" is%s %s\n",
902				    cmdp ? " a tracked alias for" : "", name);
903			} else {
904				if (access(argv[i], X_OK) == 0)
905					out1fmt(" is %s\n", argv[i]);
906				else
907					out1fmt(": %s\n", strerror(errno));
908			}
909			break;
910		}
911		case CMDFUNCTION:
912			out1str(" is a shell function\n");
913			break;
914
915		case CMDBUILTIN:
916			out1str(" is a shell builtin\n");
917			break;
918
919		default:
920			out1str(": not found\n");
921			error |= 127;
922			break;
923		}
924	}
925	return error;
926}
927