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