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