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