1 /*-
2 * Copyright (c) 1991, 1993
3 * The Regents of the University of California. All rights reserved.
4 * Copyright (c) 1997-2005
5 * Herbert Xu <herbert@gondor.apana.org.au>. 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 <stdlib.h>
40 #ifdef HAVE_PATHS_H
41 #include <paths.h>
42 #endif
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 "output.h"
64 #include "syntax.h"
65 #include "memalloc.h"
66 #include "error.h"
67 #include "init.h"
68 #include "mystring.h"
69 #include "show.h"
70 #include "jobs.h"
71 #include "alias.h"
72 #include "system.h"
73
74
75 #define CMDTABLESIZE 31 /* should be prime */
76 #define ARB 1 /* actual size determined at run time */
77
78
79
80 struct tblentry {
81 struct tblentry *next; /* next entry in hash chain */
82 union param param; /* definition of builtin function */
83 short cmdtype; /* index identifying command */
84 char rehash; /* if set, cd done since entry created */
85 char cmdname[ARB]; /* name of command */
86 };
87
88
89 STATIC struct tblentry *cmdtable[CMDTABLESIZE];
90 STATIC int builtinloc = -1; /* index in path of %builtin, or -1 */
91
92
93 STATIC void tryexec(char *, char **, char **);
94 STATIC void printentry(struct tblentry *);
95 STATIC void clearcmdentry(int);
96 STATIC struct tblentry *cmdlookup(const char *, int);
97 STATIC void delete_cmd_entry(void);
98 STATIC void addcmdentry(char *, struct cmdentry *);
99 STATIC int describe_command(struct output *, char *, const char *, int);
100
101
102 /*
103 * Exec a program. Never returns. If you change this routine, you may
104 * have to change the find_command routine as well.
105 */
106
107 void
shellexec(char ** argv,const char * path,int idx)108 shellexec(char **argv, const char *path, int idx)
109 {
110 char *cmdname;
111 int e;
112 char **envp;
113 int exerrno;
114
115 envp = environment();
116 if (strchr(argv[0], '/') != NULL) {
117 tryexec(argv[0], argv, envp);
118 e = errno;
119 } else {
120 e = ENOENT;
121 while ((cmdname = padvance(&path, argv[0])) != NULL) {
122 if (--idx < 0 && pathopt == NULL) {
123 tryexec(cmdname, argv, envp);
124 if (errno != ENOENT && errno != ENOTDIR)
125 e = errno;
126 }
127 stunalloc(cmdname);
128 }
129 }
130
131 /* Map to POSIX errors */
132 switch (e) {
133 case EACCES:
134 exerrno = 126;
135 break;
136 case ENOENT:
137 exerrno = 127;
138 break;
139 default:
140 exerrno = 2;
141 break;
142 }
143 exitstatus = exerrno;
144 TRACE(("shellexec failed for %s, errno %d, suppressint %d\n",
145 argv[0], e, suppressint ));
146 exerror(EXEXIT, "%s: %s", argv[0], errmsg(e, E_EXEC));
147 /* NOTREACHED */
148 }
149
150
151 STATIC void
tryexec(char * cmd,char ** argv,char ** envp)152 tryexec(char *cmd, char **argv, char **envp)
153 {
154 char *const path_bshell = _PATH_BSHELL;
155
156 repeat:
157 #ifdef SYSV
158 do {
159 execve(cmd, argv, envp);
160 } while (errno == EINTR);
161 #else
162 execve(cmd, argv, envp);
163 #endif
164 if (cmd != path_bshell && errno == ENOEXEC) {
165 *argv-- = cmd;
166 *argv = cmd = path_bshell;
167 goto repeat;
168 }
169 }
170
171
172
173 /*
174 * Do a path search. The variable path (passed by reference) should be
175 * set to the start of the path before the first call; padvance will update
176 * this value as it proceeds. Successive calls to padvance will return
177 * the possible path expansions in sequence. If an option (indicated by
178 * a percent sign) appears in the path entry then the global variable
179 * pathopt will be set to point to it; otherwise pathopt will be set to
180 * NULL.
181 */
182
183 const char *pathopt;
184
185 char *
padvance(const char ** path,const char * name)186 padvance(const char **path, const char *name)
187 {
188 const char *p;
189 char *q;
190 const char *start;
191 size_t len;
192
193 if (*path == NULL)
194 return NULL;
195 start = *path;
196 for (p = start ; *p && *p != ':' && *p != '%' ; p++);
197 len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */
198 while (stackblocksize() < len)
199 growstackblock();
200 q = stackblock();
201 if (p != start) {
202 memcpy(q, start, p - start);
203 q += p - start;
204 *q++ = '/';
205 }
206 strcpy(q, name);
207 pathopt = NULL;
208 if (*p == '%') {
209 pathopt = ++p;
210 while (*p && *p != ':') p++;
211 }
212 if (*p == ':')
213 *path = p + 1;
214 else
215 *path = NULL;
216 return stalloc(len);
217 }
218
219
220
221 /*** Command hashing code ***/
222
223 void
hashiter(void (* fn)(struct cmdentry *,void *),void * token)224 hashiter(void (*fn) (struct cmdentry *, void *), void *token)
225 {
226 struct tblentry **pp;
227 struct tblentry *cmdp;
228 struct cmdentry entry;
229 for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
230 for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
231 entry.cmdtype = cmdp->cmdtype;
232 entry.u = cmdp->param;
233 fn(&entry, token);
234 }
235 }
236 }
237
238 int
hashcmd(int argc,char ** argv)239 hashcmd(int argc, char **argv)
240 {
241 struct tblentry **pp;
242 struct tblentry *cmdp;
243 int c;
244 struct cmdentry entry;
245 char *name;
246
247 while ((c = nextopt("r")) != '\0') {
248 clearcmdentry(0);
249 return 0;
250 }
251 if (*argptr == NULL) {
252 for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
253 for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
254 if (cmdp->cmdtype == CMDNORMAL)
255 printentry(cmdp);
256 }
257 }
258 return 0;
259 }
260 c = 0;
261 while ((name = *argptr) != NULL) {
262 if ((cmdp = cmdlookup(name, 0)) != NULL
263 && (cmdp->cmdtype == CMDNORMAL
264 || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)))
265 delete_cmd_entry();
266 find_command(name, &entry, DO_ERR, pathval());
267 if (entry.cmdtype == CMDUNKNOWN)
268 c = 1;
269 argptr++;
270 }
271 return c;
272 }
273
274
275 STATIC void
printentry(struct tblentry * cmdp)276 printentry(struct tblentry *cmdp)
277 {
278 int idx;
279 const char *path;
280 char *name;
281
282 idx = cmdp->param.index;
283 path = pathval();
284 do {
285 name = padvance(&path, cmdp->cmdname);
286 stunalloc(name);
287 } while (--idx >= 0);
288 out1str(name);
289 out1fmt(snlfmt, cmdp->rehash ? "*" : nullstr);
290 }
291
292
293
294 /*
295 * Resolve a command name. If you change this routine, you may have to
296 * change the shellexec routine as well.
297 */
298
299 void
find_command(char * name,struct cmdentry * entry,int act,const char * path)300 find_command(char *name, struct cmdentry *entry, int act, const char *path)
301 {
302 struct tblentry *cmdp;
303 int idx;
304 int prev;
305 char *fullname;
306 struct stat statb;
307 int e;
308 int updatetbl;
309 struct builtincmd *bcmd;
310
311 /* If name contains a slash, don't use PATH or hash table */
312 if (strchr(name, '/') != NULL) {
313 entry->u.index = -1;
314 if (act & DO_ABS) {
315 while (stat(name, &statb) < 0) {
316 #ifdef SYSV
317 if (errno == EINTR)
318 continue;
319 #endif
320 entry->cmdtype = CMDUNKNOWN;
321 return;
322 }
323 }
324 entry->cmdtype = CMDNORMAL;
325 return;
326 }
327
328 updatetbl = (path == pathval());
329 if (!updatetbl) {
330 act |= DO_ALTPATH;
331 if (strstr(path, "%builtin") != NULL)
332 act |= DO_ALTBLTIN;
333 }
334
335 /* If name is in the table, check answer will be ok */
336 if ((cmdp = cmdlookup(name, 0)) != NULL) {
337 int bit;
338
339 switch (cmdp->cmdtype) {
340 default:
341 #if DEBUG
342 abort();
343 #endif
344 case CMDNORMAL:
345 bit = DO_ALTPATH;
346 break;
347 case CMDFUNCTION:
348 bit = DO_NOFUNC;
349 break;
350 case CMDBUILTIN:
351 bit = DO_ALTBLTIN;
352 break;
353 }
354 if (act & bit) {
355 updatetbl = 0;
356 cmdp = NULL;
357 } else if (cmdp->rehash == 0)
358 /* if not invalidated by cd, we're done */
359 goto success;
360 }
361
362 /* If %builtin not in path, check for builtin next */
363 bcmd = find_builtin(name);
364 if (bcmd && !(bcmd->flags & BUILTIN_WEAK) &&
365 (bcmd->flags & BUILTIN_REGULAR || (
366 act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : builtinloc <= 0
367 )))
368 goto builtin_success;
369
370 /* We have to search path. */
371 prev = -1; /* where to start */
372 if (cmdp && cmdp->rehash) { /* doing a rehash */
373 if (cmdp->cmdtype == CMDBUILTIN)
374 prev = builtinloc;
375 else
376 prev = cmdp->param.index;
377 }
378
379 e = ENOENT;
380 idx = -1;
381 loop:
382 while ((fullname = padvance(&path, name)) != NULL) {
383 stunalloc(fullname);
384 idx++;
385 if (pathopt) {
386 if (prefix(pathopt, "builtin")) {
387 if (bcmd)
388 goto builtin_success;
389 continue;
390 } else if (!(act & DO_NOFUNC) &&
391 prefix(pathopt, "func")) {
392 /* handled below */
393 } else {
394 /* ignore unimplemented options */
395 continue;
396 }
397 }
398 /* if rehash, don't redo absolute path names */
399 if (fullname[0] == '/' && idx <= prev) {
400 if (idx < prev)
401 continue;
402 TRACE(("searchexec \"%s\": no change\n", name));
403 goto success;
404 }
405 while (stat(fullname, &statb) < 0) {
406 #ifdef SYSV
407 if (errno == EINTR)
408 continue;
409 #endif
410 if (errno != ENOENT && errno != ENOTDIR)
411 e = errno;
412 goto loop;
413 }
414 e = EACCES; /* if we fail, this will be the error */
415 if (!S_ISREG(statb.st_mode))
416 continue;
417 if (pathopt) { /* this is a %func directory */
418 stalloc(strlen(fullname) + 1);
419 readcmdfile(fullname);
420 if ((cmdp = cmdlookup(name, 0)) == NULL ||
421 cmdp->cmdtype != CMDFUNCTION)
422 sh_error("%s not defined in %s", name,
423 fullname);
424 stunalloc(fullname);
425 goto success;
426 }
427 #ifdef notdef
428 /* XXX this code stops root executing stuff, and is buggy
429 if you need a group from the group list. */
430 if (statb.st_uid == geteuid()) {
431 if ((statb.st_mode & 0100) == 0)
432 goto loop;
433 } else if (statb.st_gid == getegid()) {
434 if ((statb.st_mode & 010) == 0)
435 goto loop;
436 } else {
437 if ((statb.st_mode & 01) == 0)
438 goto loop;
439 }
440 #endif
441 TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
442 if (!updatetbl) {
443 entry->cmdtype = CMDNORMAL;
444 entry->u.index = idx;
445 return;
446 }
447 INTOFF;
448 cmdp = cmdlookup(name, 1);
449 cmdp->cmdtype = CMDNORMAL;
450 cmdp->param.index = idx;
451 INTON;
452 goto success;
453 }
454
455 if (bcmd && (bcmd->flags & BUILTIN_WEAK))
456 goto builtin_success;
457
458 /* We failed. If there was an entry for this command, delete it */
459 if (cmdp && updatetbl)
460 delete_cmd_entry();
461 if (act & DO_ERR)
462 sh_warnx("%s: %s", name, errmsg(e, E_EXEC));
463 entry->cmdtype = CMDUNKNOWN;
464 return;
465
466 builtin_success:
467 if (!updatetbl) {
468 entry->cmdtype = CMDBUILTIN;
469 entry->u.cmd = bcmd;
470 return;
471 }
472 INTOFF;
473 cmdp = cmdlookup(name, 1);
474 cmdp->cmdtype = CMDBUILTIN;
475 cmdp->param.cmd = bcmd;
476 INTON;
477 success:
478 cmdp->rehash = 0;
479 entry->cmdtype = cmdp->cmdtype;
480 entry->u = cmdp->param;
481 }
482
483
484
485 /*
486 * Search the table of builtin commands.
487 */
488
489 struct builtincmd *
find_builtin(const char * name)490 find_builtin(const char *name)
491 {
492 struct builtincmd *bp;
493 bp = bsearch(
494 &name, builtincmd, NUMBUILTINS, sizeof(struct builtincmd),
495 pstrcmp
496 );
497 return bp;
498 }
499
500
501
502 /*
503 * Called when a cd is done. Marks all commands so the next time they
504 * are executed they will be rehashed.
505 */
506
507 void
hashcd(void)508 hashcd(void)
509 {
510 struct tblentry **pp;
511 struct tblentry *cmdp;
512
513 for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
514 for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
515 if (cmdp->cmdtype == CMDNORMAL || (
516 cmdp->cmdtype == CMDBUILTIN &&
517 !(cmdp->param.cmd->flags & BUILTIN_REGULAR) &&
518 builtinloc > 0
519 ))
520 cmdp->rehash = 1;
521 }
522 }
523 }
524
525
526
527 /*
528 * Fix command hash table when PATH changed.
529 * Called before PATH is changed. The argument is the new value of PATH;
530 * pathval() still returns the old value at this point.
531 * Called with interrupts off.
532 */
533
534 void
changepath(const char * newval)535 changepath(const char *newval)
536 {
537 const char *old, *new;
538 int idx;
539 int firstchange;
540 int bltin;
541
542 old = pathval();
543 new = newval;
544 firstchange = 9999; /* assume no change */
545 idx = 0;
546 bltin = -1;
547 for (;;) {
548 if (*old != *new) {
549 firstchange = idx;
550 if ((*old == '\0' && *new == ':')
551 || (*old == ':' && *new == '\0'))
552 firstchange++;
553 old = new; /* ignore subsequent differences */
554 }
555 if (*new == '\0')
556 break;
557 if (*new == '%' && bltin < 0 && prefix(new + 1, "builtin"))
558 bltin = idx;
559 if (*new == ':') {
560 idx++;
561 }
562 new++, old++;
563 }
564 if (builtinloc < 0 && bltin >= 0)
565 builtinloc = bltin; /* zap builtins */
566 if (builtinloc >= 0 && bltin < 0)
567 firstchange = 0;
568 clearcmdentry(firstchange);
569 builtinloc = bltin;
570 }
571
572
573 /*
574 * Clear out command entries. The argument specifies the first entry in
575 * PATH which has changed.
576 */
577
578 STATIC void
clearcmdentry(int firstchange)579 clearcmdentry(int firstchange)
580 {
581 struct tblentry **tblp;
582 struct tblentry **pp;
583 struct tblentry *cmdp;
584
585 INTOFF;
586 for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
587 pp = tblp;
588 while ((cmdp = *pp) != NULL) {
589 if ((cmdp->cmdtype == CMDNORMAL &&
590 cmdp->param.index >= firstchange)
591 || (cmdp->cmdtype == CMDBUILTIN &&
592 builtinloc >= firstchange)) {
593 *pp = cmdp->next;
594 ckfree(cmdp);
595 } else {
596 pp = &cmdp->next;
597 }
598 }
599 }
600 INTON;
601 }
602
603
604
605 /*
606 * Locate a command in the command hash table. If "add" is nonzero,
607 * add the command to the table if it is not already present. The
608 * variable "lastcmdentry" is set to point to the address of the link
609 * pointing to the entry, so that delete_cmd_entry can delete the
610 * entry.
611 *
612 * Interrupts must be off if called with add != 0.
613 */
614
615 struct tblentry **lastcmdentry;
616
617
618 STATIC struct tblentry *
cmdlookup(const char * name,int add)619 cmdlookup(const char *name, int add)
620 {
621 unsigned int hashval;
622 const char *p;
623 struct tblentry *cmdp;
624 struct tblentry **pp;
625
626 p = name;
627 hashval = (unsigned char)*p << 4;
628 while (*p)
629 hashval += (unsigned char)*p++;
630 hashval &= 0x7FFF;
631 pp = &cmdtable[hashval % CMDTABLESIZE];
632 for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
633 if (equal(cmdp->cmdname, name))
634 break;
635 pp = &cmdp->next;
636 }
637 if (add && cmdp == NULL) {
638 cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB
639 + strlen(name) + 1);
640 cmdp->next = NULL;
641 cmdp->cmdtype = CMDUNKNOWN;
642 strcpy(cmdp->cmdname, name);
643 }
644 lastcmdentry = pp;
645 return cmdp;
646 }
647
648 /*
649 * Delete the command entry returned on the last lookup.
650 */
651
652 STATIC void
delete_cmd_entry(void)653 delete_cmd_entry(void)
654 {
655 struct tblentry *cmdp;
656
657 INTOFF;
658 cmdp = *lastcmdentry;
659 *lastcmdentry = cmdp->next;
660 if (cmdp->cmdtype == CMDFUNCTION)
661 freefunc(cmdp->param.func);
662 ckfree(cmdp);
663 INTON;
664 }
665
666
667
668 #ifdef notdef
669 void
getcmdentry(char * name,struct cmdentry * entry)670 getcmdentry(char *name, struct cmdentry *entry)
671 {
672 struct tblentry *cmdp = cmdlookup(name, 0);
673
674 if (cmdp) {
675 entry->u = cmdp->param;
676 entry->cmdtype = cmdp->cmdtype;
677 } else {
678 entry->cmdtype = CMDUNKNOWN;
679 entry->u.index = 0;
680 }
681 }
682 #endif
683
684
685 /*
686 * Add a new command entry, replacing any existing command entry for
687 * the same name - except special builtins.
688 */
689
690 STATIC void
addcmdentry(char * name,struct cmdentry * entry)691 addcmdentry(char *name, struct cmdentry *entry)
692 {
693 struct tblentry *cmdp;
694
695 cmdp = cmdlookup(name, 1);
696 if (cmdp->cmdtype == CMDFUNCTION) {
697 freefunc(cmdp->param.func);
698 }
699 cmdp->cmdtype = entry->cmdtype;
700 cmdp->param = entry->u;
701 cmdp->rehash = 0;
702 }
703
704
705 /*
706 * Define a shell function.
707 */
708
709 void
defun(union node * func)710 defun(union node *func)
711 {
712 struct cmdentry entry;
713
714 INTOFF;
715 entry.cmdtype = CMDFUNCTION;
716 entry.u.func = copyfunc(func);
717 addcmdentry(func->ndefun.text, &entry);
718 INTON;
719 }
720
721
722 /*
723 * Delete a function if it exists.
724 */
725
726 void
unsetfunc(const char * name)727 unsetfunc(const char *name)
728 {
729 struct tblentry *cmdp;
730
731 if ((cmdp = cmdlookup(name, 0)) != NULL &&
732 cmdp->cmdtype == CMDFUNCTION)
733 delete_cmd_entry();
734 }
735
736 /*
737 * Locate and print what a word is...
738 */
739
740 int
typecmd(int argc,char ** argv)741 typecmd(int argc, char **argv)
742 {
743 int i;
744 int err = 0;
745
746 for (i = 1; i < argc; i++) {
747 err |= describe_command(out1, argv[i], NULL, 1);
748 }
749 return err;
750 }
751
752 STATIC int
describe_command(out,command,path,verbose)753 describe_command(out, command, path, verbose)
754 struct output *out;
755 char *command;
756 const char *path;
757 int verbose;
758 {
759 struct cmdentry entry;
760 struct tblentry *cmdp;
761 const struct alias *ap;
762
763 path = path ?: pathval();
764
765 if (verbose) {
766 outstr(command, out);
767 }
768
769 /* First look at the keywords */
770 if (findkwd(command)) {
771 outstr(verbose ? " is a shell keyword" : command, out);
772 goto out;
773 }
774
775 /* Then look at the aliases */
776 if ((ap = lookupalias(command, 0)) != NULL) {
777 if (verbose) {
778 outfmt(out, " is an alias for %s", ap->val);
779 } else {
780 outstr("alias ", out);
781 printalias(ap);
782 return 0;
783 }
784 goto out;
785 }
786
787 /* Then check if it is a tracked alias */
788 if ((cmdp = cmdlookup(command, 0)) != NULL) {
789 entry.cmdtype = cmdp->cmdtype;
790 entry.u = cmdp->param;
791 } else {
792 /* Finally use brute force */
793 find_command(command, &entry, DO_ABS, path);
794 }
795
796 switch (entry.cmdtype) {
797 case CMDNORMAL: {
798 int j = entry.u.index;
799 char *p;
800 if (j == -1) {
801 p = command;
802 } else {
803 do {
804 p = padvance(&path, command);
805 stunalloc(p);
806 } while (--j >= 0);
807 }
808 if (verbose) {
809 outfmt(
810 out, " is%s %s",
811 cmdp ? " a tracked alias for" : nullstr, p
812 );
813 } else {
814 outstr(p, out);
815 }
816 break;
817 }
818
819 case CMDFUNCTION:
820 if (verbose) {
821 outstr(" is a shell function", out);
822 } else {
823 outstr(command, out);
824 }
825 break;
826
827 case CMDBUILTIN:
828 if (verbose) {
829 outfmt(
830 out, " is a %sshell builtin",
831 entry.u.cmd->flags & BUILTIN_SPECIAL ?
832 "special " : nullstr
833 );
834 } else {
835 outstr(command, out);
836 }
837 break;
838
839 default:
840 if (verbose) {
841 outstr(": not found\n", out);
842 }
843 return 127;
844 }
845
846 out:
847 outc('\n', out);
848 return 0;
849 }
850
851 int
commandcmd(argc,argv)852 commandcmd(argc, argv)
853 int argc;
854 char **argv;
855 {
856 char *cmd;
857 int c;
858 enum {
859 VERIFY_BRIEF = 1,
860 VERIFY_VERBOSE = 2,
861 } verify = 0;
862 const char *path = NULL;
863
864 while ((c = nextopt("pvV")) != '\0')
865 if (c == 'V')
866 verify |= VERIFY_VERBOSE;
867 else if (c == 'v')
868 verify |= VERIFY_BRIEF;
869 #ifdef DEBUG
870 else if (c != 'p')
871 abort();
872 #endif
873 else
874 path = defpath;
875
876 cmd = *argptr;
877 if (verify && cmd)
878 return describe_command(out1, cmd, path, verify - VERIFY_BRIEF);
879
880 return 0;
881 }
882