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