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