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