]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - bin/sh/exec.c
net80211: correct length check in ieee80211_ies_expand()
[FreeBSD/FreeBSD.git] / bin / sh / exec.c
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. 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 <stdbool.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 "mystring.h"
75 #include "show.h"
76 #include "jobs.h"
77 #include "alias.h"
78
79
80 #define CMDTABLESIZE 31         /* should be prime */
81
82
83
84 struct 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         signed char cmdtype;    /* index identifying command */
89         char cmdname[];         /* name of command */
90 };
91
92
93 static struct tblentry *cmdtable[CMDTABLESIZE];
94 static int cmdtable_cd = 0;     /* cmdtable contains cd-dependent entries */
95 int exerrno = 0;                        /* Last exec error */
96
97
98 static void tryexec(char *, char **, char **);
99 static void printentry(struct tblentry *, int);
100 static struct tblentry *cmdlookup(const char *, int);
101 static void delete_cmd_entry(void);
102 static void addcmdentry(const char *, struct cmdentry *);
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  * The argv array may be changed and element argv[-1] should be writable.
111  */
112
113 void
114 shellexec(char **argv, char **envp, const char *path, int idx)
115 {
116         char *cmdname;
117         const char *opt;
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, &opt, argv[0])) != NULL) {
126                         if (--idx < 0 && opt == 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 bool
149 isbinary(const char *data, size_t len)
150 {
151         const char *nul, *p;
152         bool hasletter;
153
154         nul = memchr(data, '\0', len);
155         if (nul == NULL)
156                 return false;
157         /*
158          * POSIX says we shall allow execution if the initial part intended
159          * to be parsed by the shell consists of characters and does not
160          * contain the NUL character. This allows concatenating a shell
161          * script (ending with exec or exit) and a binary payload.
162          *
163          * In order to reject common binary files such as PNG images, check
164          * that there is a lowercase letter or expansion before the last
165          * newline before the NUL character, in addition to the check for
166          * the newline character suggested by POSIX.
167          */
168         hasletter = false;
169         for (p = data; *p != '\0'; p++) {
170                 if ((*p >= 'a' && *p <= 'z') || *p == '$' || *p == '`')
171                         hasletter = true;
172                 if (hasletter && *p == '\n')
173                         return false;
174         }
175         return true;
176 }
177
178
179 static void
180 tryexec(char *cmd, char **argv, char **envp)
181 {
182         int e, in;
183         ssize_t n;
184         char buf[256];
185
186         execve(cmd, argv, envp);
187         e = errno;
188         if (e == ENOEXEC) {
189                 INTOFF;
190                 in = open(cmd, O_RDONLY | O_NONBLOCK);
191                 if (in != -1) {
192                         n = pread(in, buf, sizeof buf, 0);
193                         close(in);
194                         if (n > 0 && isbinary(buf, n)) {
195                                 errno = ENOEXEC;
196                                 return;
197                         }
198                 }
199                 *argv = cmd;
200                 *--argv = __DECONST(char *, _PATH_BSHELL);
201                 execve(_PATH_BSHELL, argv, envp);
202         }
203         errno = e;
204 }
205
206 /*
207  * Do a path search.  The variable path (passed by reference) should be
208  * set to the start of the path before the first call; padvance will update
209  * this value as it proceeds.  Successive calls to padvance will return
210  * the possible path expansions in sequence.  If popt is not NULL, options
211  * are processed: if an option (indicated by a percent sign) appears in
212  * the path entry then *popt will be set to point to it; else *popt will be
213  * set to NULL.  If popt is NULL, percent signs are not special.
214  */
215
216 char *
217 padvance(const char **path, const char **popt, const char *name)
218 {
219         const char *p, *start;
220         char *q;
221         size_t len, namelen;
222
223         if (*path == NULL)
224                 return NULL;
225         start = *path;
226         if (popt != NULL)
227                 for (p = start; *p && *p != ':' && *p != '%'; p++)
228                         ; /* nothing */
229         else
230                 for (p = start; *p && *p != ':'; p++)
231                         ; /* nothing */
232         namelen = strlen(name);
233         len = p - start + namelen + 2;  /* "2" is for '/' and '\0' */
234         STARTSTACKSTR(q);
235         CHECKSTRSPACE(len, q);
236         if (p != start) {
237                 memcpy(q, start, p - start);
238                 q += p - start;
239                 *q++ = '/';
240         }
241         memcpy(q, name, namelen + 1);
242         if (popt != NULL) {
243                 if (*p == '%') {
244                         *popt = ++p;
245                         while (*p && *p != ':')  p++;
246                 } else
247                         *popt = NULL;
248         }
249         if (*p == ':')
250                 *path = p + 1;
251         else
252                 *path = NULL;
253         return stalloc(len);
254 }
255
256
257
258 /*** Command hashing code ***/
259
260
261 int
262 hashcmd(int argc __unused, char **argv __unused)
263 {
264         struct tblentry **pp;
265         struct tblentry *cmdp;
266         int c;
267         int verbose;
268         struct cmdentry entry;
269         char *name;
270         int errors;
271
272         errors = 0;
273         verbose = 0;
274         while ((c = nextopt("rv")) != '\0') {
275                 if (c == 'r') {
276                         clearcmdentry();
277                 } else if (c == 'v') {
278                         verbose++;
279                 }
280         }
281         if (*argptr == NULL) {
282                 for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
283                         for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
284                                 if (cmdp->cmdtype == CMDNORMAL)
285                                         printentry(cmdp, verbose);
286                         }
287                 }
288                 return 0;
289         }
290         while ((name = *argptr) != NULL) {
291                 if ((cmdp = cmdlookup(name, 0)) != NULL
292                  && cmdp->cmdtype == CMDNORMAL)
293                         delete_cmd_entry();
294                 find_command(name, &entry, DO_ERR, pathval());
295                 if (entry.cmdtype == CMDUNKNOWN)
296                         errors = 1;
297                 else if (verbose) {
298                         cmdp = cmdlookup(name, 0);
299                         if (cmdp != NULL)
300                                 printentry(cmdp, verbose);
301                         else {
302                                 outfmt(out2, "%s: not found\n", name);
303                                 errors = 1;
304                         }
305                         flushall();
306                 }
307                 argptr++;
308         }
309         return errors;
310 }
311
312
313 static void
314 printentry(struct tblentry *cmdp, int verbose)
315 {
316         int idx;
317         const char *path, *opt;
318         char *name;
319
320         if (cmdp->cmdtype == CMDNORMAL) {
321                 idx = cmdp->param.index;
322                 path = pathval();
323                 do {
324                         name = padvance(&path, &opt, cmdp->cmdname);
325                         stunalloc(name);
326                 } while (--idx >= 0);
327                 out1str(name);
328         } else if (cmdp->cmdtype == CMDBUILTIN) {
329                 out1fmt("builtin %s", cmdp->cmdname);
330         } else if (cmdp->cmdtype == CMDFUNCTION) {
331                 out1fmt("function %s", cmdp->cmdname);
332                 if (verbose) {
333                         INTOFF;
334                         name = commandtext(getfuncnode(cmdp->param.func));
335                         out1c(' ');
336                         out1str(name);
337                         ckfree(name);
338                         INTON;
339                 }
340 #ifdef DEBUG
341         } else {
342                 error("internal error: cmdtype %d", cmdp->cmdtype);
343 #endif
344         }
345         out1c('\n');
346 }
347
348
349
350 /*
351  * Resolve a command name.  If you change this routine, you may have to
352  * change the shellexec routine as well.
353  */
354
355 void
356 find_command(const char *name, struct cmdentry *entry, int act,
357     const char *path)
358 {
359         struct tblentry *cmdp, loc_cmd;
360         int idx;
361         const char *opt;
362         char *fullname;
363         struct stat statb;
364         int e;
365         int i;
366         int spec;
367         int cd;
368
369         /* If name contains a slash, don't use the hash table */
370         if (strchr(name, '/') != NULL) {
371                 entry->cmdtype = CMDNORMAL;
372                 entry->u.index = 0;
373                 entry->special = 0;
374                 return;
375         }
376
377         cd = 0;
378
379         /* If name is in the table, we're done */
380         if ((cmdp = cmdlookup(name, 0)) != NULL) {
381                 if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC)
382                         cmdp = NULL;
383                 else
384                         goto success;
385         }
386
387         /* Check for builtin next */
388         if ((i = find_builtin(name, &spec)) >= 0) {
389                 INTOFF;
390                 cmdp = cmdlookup(name, 1);
391                 if (cmdp->cmdtype == CMDFUNCTION)
392                         cmdp = &loc_cmd;
393                 cmdp->cmdtype = CMDBUILTIN;
394                 cmdp->param.index = i;
395                 cmdp->special = spec;
396                 INTON;
397                 goto success;
398         }
399
400         /* We have to search path. */
401
402         e = ENOENT;
403         idx = -1;
404         for (;(fullname = padvance(&path, &opt, name)) != NULL;
405             stunalloc(fullname)) {
406                 idx++;
407                 if (opt) {
408                         if (strncmp(opt, "func", 4) == 0) {
409                                 /* handled below */
410                         } else {
411                                 continue; /* ignore unimplemented options */
412                         }
413                 }
414                 if (fullname[0] != '/')
415                         cd = 1;
416                 if (stat(fullname, &statb) < 0) {
417                         if (errno != ENOENT && errno != ENOTDIR)
418                                 e = errno;
419                         continue;
420                 }
421                 e = EACCES;     /* if we fail, this will be the error */
422                 if (!S_ISREG(statb.st_mode))
423                         continue;
424                 if (opt) {              /* this is a %func directory */
425                         readcmdfile(fullname);
426                         if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION)
427                                 error("%s not defined in %s", name, fullname);
428                         stunalloc(fullname);
429                         goto success;
430                 }
431 #ifdef notdef
432                 if (statb.st_uid == geteuid()) {
433                         if ((statb.st_mode & 0100) == 0)
434                                 goto loop;
435                 } else if (statb.st_gid == getegid()) {
436                         if ((statb.st_mode & 010) == 0)
437                                 goto loop;
438                 } else {
439                         if ((statb.st_mode & 01) == 0)
440                                 goto loop;
441                 }
442 #endif
443                 TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
444                 INTOFF;
445                 stunalloc(fullname);
446                 cmdp = cmdlookup(name, 1);
447                 if (cmdp->cmdtype == CMDFUNCTION)
448                         cmdp = &loc_cmd;
449                 cmdp->cmdtype = CMDNORMAL;
450                 cmdp->param.index = idx;
451                 cmdp->special = 0;
452                 INTON;
453                 goto success;
454         }
455
456         if (act & DO_ERR) {
457                 if (e == ENOENT || e == ENOTDIR)
458                         outfmt(out2, "%s: not found\n", name);
459                 else
460                         outfmt(out2, "%s: %s\n", name, strerror(e));
461         }
462         entry->cmdtype = CMDUNKNOWN;
463         entry->u.index = 0;
464         entry->special = 0;
465         return;
466
467 success:
468         if (cd)
469                 cmdtable_cd = 1;
470         entry->cmdtype = cmdp->cmdtype;
471         entry->u = cmdp->param;
472         entry->special = cmdp->special;
473 }
474
475
476
477 /*
478  * Search the table of builtin commands.
479  */
480
481 int
482 find_builtin(const char *name, int *special)
483 {
484         const unsigned char *bp;
485         size_t len;
486
487         len = strlen(name);
488         for (bp = builtincmd ; *bp ; bp += 2 + bp[0]) {
489                 if (bp[0] == len && memcmp(bp + 2, name, len) == 0) {
490                         *special = (bp[1] & BUILTIN_SPECIAL) != 0;
491                         return bp[1] & ~BUILTIN_SPECIAL;
492                 }
493         }
494         return -1;
495 }
496
497
498
499 /*
500  * Called when a cd is done.  If any entry in cmdtable depends on the current
501  * directory, simply clear cmdtable completely.
502  */
503
504 void
505 hashcd(void)
506 {
507         if (cmdtable_cd)
508                 clearcmdentry();
509 }
510
511
512
513 /*
514  * Called before PATH is changed.  The argument is the new value of PATH;
515  * pathval() still returns the old value at this point.  Called with
516  * interrupts off.
517  */
518
519 void
520 changepath(const char *newval __unused)
521 {
522         clearcmdentry();
523 }
524
525
526 /*
527  * Clear out cached utility locations.
528  */
529
530 void
531 clearcmdentry(void)
532 {
533         struct tblentry **tblp;
534         struct tblentry **pp;
535         struct tblentry *cmdp;
536
537         INTOFF;
538         for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
539                 pp = tblp;
540                 while ((cmdp = *pp) != NULL) {
541                         if (cmdp->cmdtype == CMDNORMAL) {
542                                 *pp = cmdp->next;
543                                 ckfree(cmdp);
544                         } else {
545                                 pp = &cmdp->next;
546                         }
547                 }
548         }
549         cmdtable_cd = 0;
550         INTON;
551 }
552
553
554 /*
555  * Locate a command in the command hash table.  If "add" is nonzero,
556  * add the command to the table if it is not already present.  The
557  * variable "lastcmdentry" is set to point to the address of the link
558  * pointing to the entry, so that delete_cmd_entry can delete the
559  * entry.
560  */
561
562 static struct tblentry **lastcmdentry;
563
564
565 static struct tblentry *
566 cmdlookup(const char *name, int add)
567 {
568         unsigned int hashval;
569         const char *p;
570         struct tblentry *cmdp;
571         struct tblentry **pp;
572         size_t len;
573
574         p = name;
575         hashval = (unsigned char)*p << 4;
576         while (*p)
577                 hashval += *p++;
578         pp = &cmdtable[hashval % CMDTABLESIZE];
579         for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
580                 if (equal(cmdp->cmdname, name))
581                         break;
582                 pp = &cmdp->next;
583         }
584         if (add && cmdp == NULL) {
585                 INTOFF;
586                 len = strlen(name);
587                 cmdp = *pp = ckmalloc(sizeof (struct tblentry) + len + 1);
588                 cmdp->next = NULL;
589                 cmdp->cmdtype = CMDUNKNOWN;
590                 memcpy(cmdp->cmdname, name, len + 1);
591                 INTON;
592         }
593         lastcmdentry = pp;
594         return cmdp;
595 }
596
597 /*
598  * Delete the command entry returned on the last lookup.
599  */
600
601 static void
602 delete_cmd_entry(void)
603 {
604         struct tblentry *cmdp;
605
606         INTOFF;
607         cmdp = *lastcmdentry;
608         *lastcmdentry = cmdp->next;
609         ckfree(cmdp);
610         INTON;
611 }
612
613
614
615 /*
616  * Add a new command entry, replacing any existing command entry for
617  * the same name.
618  */
619
620 static void
621 addcmdentry(const char *name, struct cmdentry *entry)
622 {
623         struct tblentry *cmdp;
624
625         INTOFF;
626         cmdp = cmdlookup(name, 1);
627         if (cmdp->cmdtype == CMDFUNCTION) {
628                 unreffunc(cmdp->param.func);
629         }
630         cmdp->cmdtype = entry->cmdtype;
631         cmdp->param = entry->u;
632         cmdp->special = entry->special;
633         INTON;
634 }
635
636
637 /*
638  * Define a shell function.
639  */
640
641 void
642 defun(const char *name, union node *func)
643 {
644         struct cmdentry entry;
645
646         INTOFF;
647         entry.cmdtype = CMDFUNCTION;
648         entry.u.func = copyfunc(func);
649         entry.special = 0;
650         addcmdentry(name, &entry);
651         INTON;
652 }
653
654
655 /*
656  * Delete a function if it exists.
657  * Called with interrupts off.
658  */
659
660 int
661 unsetfunc(const char *name)
662 {
663         struct tblentry *cmdp;
664
665         if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) {
666                 unreffunc(cmdp->param.func);
667                 delete_cmd_entry();
668                 return (0);
669         }
670         return (0);
671 }
672
673
674 /*
675  * Check if a function by a certain name exists.
676  */
677 int
678 isfunc(const char *name)
679 {
680         struct tblentry *cmdp;
681         cmdp = cmdlookup(name, 0);
682         return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION);
683 }
684
685
686 /*
687  * Shared code for the following builtin commands:
688  *    type, command -v, command -V
689  */
690
691 int
692 typecmd_impl(int argc, char **argv, int cmd, const char *path)
693 {
694         struct cmdentry entry;
695         struct tblentry *cmdp;
696         const char *const *pp;
697         struct alias *ap;
698         int i;
699         int error1 = 0;
700
701         if (path != pathval())
702                 clearcmdentry();
703
704         for (i = 1; i < argc; i++) {
705                 /* First look at the keywords */
706                 for (pp = parsekwd; *pp; pp++)
707                         if (**pp == *argv[i] && equal(*pp, argv[i]))
708                                 break;
709
710                 if (*pp) {
711                         if (cmd == TYPECMD_SMALLV)
712                                 out1fmt("%s\n", argv[i]);
713                         else
714                                 out1fmt("%s is a shell keyword\n", argv[i]);
715                         continue;
716                 }
717
718                 /* Then look at the aliases */
719                 if ((ap = lookupalias(argv[i], 1)) != NULL) {
720                         if (cmd == TYPECMD_SMALLV) {
721                                 out1fmt("alias %s=", argv[i]);
722                                 out1qstr(ap->val);
723                                 outcslow('\n', out1);
724                         } else
725                                 out1fmt("%s is an alias for %s\n", argv[i],
726                                     ap->val);
727                         continue;
728                 }
729
730                 /* Then check if it is a tracked alias */
731                 if ((cmdp = cmdlookup(argv[i], 0)) != NULL) {
732                         entry.cmdtype = cmdp->cmdtype;
733                         entry.u = cmdp->param;
734                         entry.special = cmdp->special;
735                 }
736                 else {
737                         /* Finally use brute force */
738                         find_command(argv[i], &entry, 0, path);
739                 }
740
741                 switch (entry.cmdtype) {
742                 case CMDNORMAL: {
743                         if (strchr(argv[i], '/') == NULL) {
744                                 const char *path2 = path;
745                                 const char *opt2;
746                                 char *name;
747                                 int j = entry.u.index;
748                                 do {
749                                         name = padvance(&path2, &opt2, argv[i]);
750                                         stunalloc(name);
751                                 } while (--j >= 0);
752                                 if (cmd == TYPECMD_SMALLV)
753                                         out1fmt("%s\n", name);
754                                 else
755                                         out1fmt("%s is%s %s\n", argv[i],
756                                             (cmdp && cmd == TYPECMD_TYPE) ?
757                                                 " a tracked alias for" : "",
758                                             name);
759                         } else {
760                                 if (eaccess(argv[i], X_OK) == 0) {
761                                         if (cmd == TYPECMD_SMALLV)
762                                                 out1fmt("%s\n", argv[i]);
763                                         else
764                                                 out1fmt("%s is %s\n", argv[i],
765                                                     argv[i]);
766                                 } else {
767                                         if (cmd != TYPECMD_SMALLV)
768                                                 outfmt(out2, "%s: %s\n",
769                                                     argv[i], strerror(errno));
770                                         error1 |= 127;
771                                 }
772                         }
773                         break;
774                 }
775                 case CMDFUNCTION:
776                         if (cmd == TYPECMD_SMALLV)
777                                 out1fmt("%s\n", argv[i]);
778                         else
779                                 out1fmt("%s is a shell function\n", argv[i]);
780                         break;
781
782                 case CMDBUILTIN:
783                         if (cmd == TYPECMD_SMALLV)
784                                 out1fmt("%s\n", argv[i]);
785                         else if (entry.special)
786                                 out1fmt("%s is a special shell builtin\n",
787                                     argv[i]);
788                         else
789                                 out1fmt("%s is a shell builtin\n", argv[i]);
790                         break;
791
792                 default:
793                         if (cmd != TYPECMD_SMALLV)
794                                 outfmt(out2, "%s: not found\n", argv[i]);
795                         error1 |= 127;
796                         break;
797                 }
798         }
799
800         if (path != pathval())
801                 clearcmdentry();
802
803         return error1;
804 }
805
806 /*
807  * Locate and print what a word is...
808  */
809
810 int
811 typecmd(int argc, char **argv)
812 {
813         if (argc > 2 && strcmp(argv[1], "--") == 0)
814                 argc--, argv++;
815         return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1));
816 }