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