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