From 5651bfc204c02c3e2d687e114f257d7fb9e3c805 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Fri, 23 Feb 2007 21:08:58 +0000 Subject: ash: starting second round of cleanups. #1 --- shell/ash.c | 3858 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 1918 insertions(+), 1940 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 0e039322b..664a6fa16 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -408,6 +408,39 @@ out2str(const char *p) /* ============ Parsing structures */ +/* control characters in argument strings */ +#define CTLESC '\201' /* escape next character */ +#define CTLVAR '\202' /* variable defn */ +#define CTLENDVAR '\203' +#define CTLBACKQ '\204' +#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ +/* CTLBACKQ | CTLQUOTE == '\205' */ +#define CTLARI '\206' /* arithmetic expression */ +#define CTLENDARI '\207' +#define CTLQUOTEMARK '\210' + +/* variable substitution byte (follows CTLVAR) */ +#define VSTYPE 0x0f /* type of variable substitution */ +#define VSNUL 0x10 /* colon--treat the empty string as unset */ +#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ + +/* values of VSTYPE field */ +#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ +#define VSMINUS 0x2 /* ${var-text} */ +#define VSPLUS 0x3 /* ${var+text} */ +#define VSQUESTION 0x4 /* ${var?message} */ +#define VSASSIGN 0x5 /* ${var=text} */ +#define VSTRIMRIGHT 0x6 /* ${var%pattern} */ +#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ +#define VSTRIMLEFT 0x8 /* ${var#pattern} */ +#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ +#define VSLENGTH 0xa /* ${#var} */ + +/* values of checkkwd variable */ +#define CHKALIAS 0x1 +#define CHKKWD 0x2 +#define CHKNL 0x4 + #define NCMD 0 #define NPIPE 1 #define NREDIR 2 @@ -551,6 +584,16 @@ struct funcnode { union node n; }; +/* + * Free a parse tree. + */ +static void +freefunc(struct funcnode *f) +{ + if (f && --f->count < 0) + free(f); +} + /* ============ Debugging output */ @@ -886,11 +929,11 @@ showtree(union node *n) #endif /* DEBUG */ -/* ============ Parser data - * +/* ============ Parser data */ + +/* * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up. */ - struct strlist { struct strlist *next; char *text; @@ -1431,7 +1474,7 @@ single_quote(const char *s) } -/* ============ ... */ +/* ============ nextopt */ static char **argptr; /* argument list for builtin commands */ static char *optionarg; /* set by nextopt (like getopt) */ @@ -2348,47 +2391,7 @@ pwdcmd(int argc, char **argv) } -/* ============ Unsorted yet */ - - -/* parser.h */ - -/* control characters in argument strings */ -#define CTL_FIRST '\201' /* first 'special' character */ -#define CTLESC '\201' /* escape next character */ -#define CTLVAR '\202' /* variable defn */ -#define CTLENDVAR '\203' -#define CTLBACKQ '\204' -#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ -/* CTLBACKQ | CTLQUOTE == '\205' */ -#define CTLARI '\206' /* arithmetic expression */ -#define CTLENDARI '\207' -#define CTLQUOTEMARK '\210' -#define CTL_LAST '\210' /* last 'special' character */ - -/* variable substitution byte (follows CTLVAR) */ -#define VSTYPE 0x0f /* type of variable substitution */ -#define VSNUL 0x10 /* colon--treat the empty string as unset */ -#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ - -/* values of VSTYPE field */ -#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ -#define VSMINUS 0x2 /* ${var-text} */ -#define VSPLUS 0x3 /* ${var+text} */ -#define VSQUESTION 0x4 /* ${var?message} */ -#define VSASSIGN 0x5 /* ${var=text} */ -#define VSTRIMRIGHT 0x6 /* ${var%pattern} */ -#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ -#define VSTRIMLEFT 0x8 /* ${var#pattern} */ -#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ -#define VSLENGTH 0xa /* ${#var} */ - -/* values of checkkwd variable */ -#define CHKALIAS 0x1 -#define CHKKWD 0x2 -#define CHKNL 0x4 - -#define IBUFSIZ (BUFSIZ + 1) +/* ============ ... */ /* * NEOF is returned by parsecmd when it encounters an end of file. It @@ -2404,6 +2407,7 @@ static int parselleft; /* copy of parsefile->lleft */ /* next character in input buffer */ static char *parsenextc; /* copy of parsefile->nextc */ +#define IBUFSIZ (BUFSIZ + 1) #define basebuf bb_common_bufsiz1 /* buffer for top level input file */ static int tokpushback; /* last token pushed back */ @@ -2431,98 +2435,6 @@ static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' }; #define xlikely(x) __builtin_expect((x),1) -#define TEOF 0 -#define TNL 1 -#define TREDIR 2 -#define TWORD 3 -#define TSEMI 4 -#define TBACKGND 5 -#define TAND 6 -#define TOR 7 -#define TPIPE 8 -#define TLP 9 -#define TRP 10 -#define TENDCASE 11 -#define TENDBQUOTE 12 -#define TNOT 13 -#define TCASE 14 -#define TDO 15 -#define TDONE 16 -#define TELIF 17 -#define TELSE 18 -#define TESAC 19 -#define TFI 20 -#define TFOR 21 -#define TIF 22 -#define TIN 23 -#define TTHEN 24 -#define TUNTIL 25 -#define TWHILE 26 -#define TBEGIN 27 -#define TEND 28 - -/* first char is indicating which tokens mark the end of a list */ -static const char *const tokname_array[] = { - "\1end of file", - "\0newline", - "\0redirection", - "\0word", - "\0;", - "\0&", - "\0&&", - "\0||", - "\0|", - "\0(", - "\1)", - "\1;;", - "\1`", -#define KWDOFFSET 13 - /* the following are keywords */ - "\0!", - "\0case", - "\1do", - "\1done", - "\1elif", - "\1else", - "\1esac", - "\1fi", - "\0for", - "\0if", - "\0in", - "\1then", - "\0until", - "\0while", - "\0{", - "\1}", -}; - -static const char * -tokname(int tok) -{ - static char buf[16]; - - if (tok >= TSEMI) - buf[0] = '"'; - sprintf(buf + (tok >= TSEMI), "%s%c", - tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); - return buf; -} - -/* Wrapper around strcmp for qsort/bsearch/... */ -static int -pstrcmp(const void *a, const void *b) -{ - return strcmp((const char *) a, (*(const char *const *) b) + 1); -} - -static const char *const * -findkwd(const char *s) -{ - return bsearch(s, tokname_array + KWDOFFSET, - (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET, - sizeof(const char *), pstrcmp); -} - /* Syntax classes */ #define CWORD 0 /* character is nothing special */ #define CNL 1 /* newline character */ @@ -2966,131 +2878,40 @@ static const char syntax_index_table[258] = { #endif /* USE_SIT_FUNCTION */ -/* alias.c */ - -#define ATABSIZE 39 +/* exec.h */ -static int funcblocksize; /* size of structures in function */ -static int funcstringsize; /* size of strings in node */ -static void *funcblock; /* block to allocate function from */ -static char *funcstring; /* block to allocate strings from */ +#if ENABLE_ASH_MATH_SUPPORT_64 +typedef int64_t arith_t; +#define arith_t_type long long +#else +typedef long arith_t; +#define arith_t_type long +#endif -static const short nodesize[26] = { - SHELL_ALIGN(sizeof(struct ncmd)), - SHELL_ALIGN(sizeof(struct npipe)), - SHELL_ALIGN(sizeof(struct nredir)), - SHELL_ALIGN(sizeof(struct nredir)), - SHELL_ALIGN(sizeof(struct nredir)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nif)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nfor)), - SHELL_ALIGN(sizeof(struct ncase)), - SHELL_ALIGN(sizeof(struct nclist)), - SHELL_ALIGN(sizeof(struct narg)), - SHELL_ALIGN(sizeof(struct narg)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct ndup)), - SHELL_ALIGN(sizeof(struct ndup)), - SHELL_ALIGN(sizeof(struct nhere)), - SHELL_ALIGN(sizeof(struct nhere)), - SHELL_ALIGN(sizeof(struct nnot)), -}; +#if ENABLE_ASH_MATH_SUPPORT +static arith_t dash_arith(const char *); +static arith_t arith(const char *expr, int *perrcode); +#endif -static void calcsize(union node *); -static void sizenodelist(struct nodelist *); -static union node *copynode(union node *); -static struct nodelist *copynodelist(struct nodelist *); -static char *nodeckstrdup(char *); +#if ENABLE_ASH_RANDOM_SUPPORT +static unsigned long rseed; +# ifndef DYNAMIC_VAR +# define DYNAMIC_VAR +# endif +#endif -static int evalskip; /* set if we are skipping commands */ -static int skipcount; /* number of levels to skip */ -static int funcnest; /* depth of function calls */ -/* reasons for skipping commands (see comment on breakcmd routine) */ -#define SKIPBREAK (1 << 0) -#define SKIPCONT (1 << 1) -#define SKIPFUNC (1 << 2) -#define SKIPFILE (1 << 3) -#define SKIPEVAL (1 << 4) +/* jobs.h */ +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 -/* exec.h */ - -/* values of cmdtype */ -#define CMDUNKNOWN -1 /* no entry in table for command */ -#define CMDNORMAL 0 /* command is an executable program */ -#define CMDFUNCTION 1 /* command is a shell function */ -#define CMDBUILTIN 2 /* command is a shell builtin */ - -struct builtincmd { - const char *name; - int (*builtin)(int, char **); - /* unsigned flags; */ -}; - -struct cmdentry { - int cmdtype; - union param { - int index; - const struct builtincmd *cmd; - struct funcnode *func; - } u; -}; - -/* action to find_command() */ -#define DO_ERR 0x01 /* prints errors */ -#define DO_ABS 0x02 /* checks absolute paths */ -#define DO_NOFUNC 0x04 /* don't return shell functions, for command */ -#define DO_ALTPATH 0x08 /* using alternate path */ -#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ - -static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN; -static char *padvance(const char **, const char *); -static void find_command(char *, struct cmdentry *, int, const char *); -static struct builtincmd *find_builtin(const char *); -static void defun(char *, union node *); -static void unsetfunc(const char *); - -#if ENABLE_ASH_MATH_SUPPORT_64 -typedef int64_t arith_t; -#define arith_t_type long long -#else -typedef long arith_t; -#define arith_t_type long -#endif - -#if ENABLE_ASH_MATH_SUPPORT -static arith_t dash_arith(const char *); -static arith_t arith(const char *expr, int *perrcode); -#endif - -#if ENABLE_ASH_RANDOM_SUPPORT -static unsigned long rseed; -# ifndef DYNAMIC_VAR -# define DYNAMIC_VAR -# endif -#endif - - -/* jobs.h */ - -/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ -#define FORK_FG 0 -#define FORK_BG 1 -#define FORK_NOJOB 2 - -/* mode flags for showjob(s) */ -#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */ -#define SHOW_PID 0x04 /* include process pid */ -#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */ +/* mode flags for showjob(s) */ +#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */ +#define SHOW_PID 0x04 /* include process pid */ +#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */ /* @@ -3222,6 +3043,8 @@ static int is_safe_applet(char *name) #define ALIASINUSE 1 #define ALIASDEAD 2 +#define ATABSIZE 39 + struct alias { struct alias *next; char *name; @@ -4899,1314 +4722,1630 @@ casematch(union node *pattern, char *val) } -/* ============ eval.c */ +/* ============ find_command */ -/* flags in argument to evaltree */ -#define EV_EXIT 01 /* exit after evaluating tree */ -#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ -#define EV_BACKCMD 04 /* command executing within back quotes */ +struct builtincmd { + const char *name; + int (*builtin)(int, char **); + /* unsigned flags; */ +}; +#define IS_BUILTIN_SPECIAL(b) ((b)->name[0] & 1) +#define IS_BUILTIN_REGULAR(b) ((b)->name[0] & 2) +#define IS_BUILTIN_ASSIGN(b) ((b)->name[0] & 4) -/* forward declarations - evaluation is fairly recursive business... */ -static void evalloop(union node *, int); -static void evalfor(union node *, int); -static void evalcase(union node *, int); -static void evalsubshell(union node *, int); -static void expredir(union node *); -static void evalpipe(union node *, int); -static void evalcommand(union node *, int); -static int evalbltin(const struct builtincmd *, int, char **); -static int evalfun(struct funcnode *, int, char **, int); -static void prehash(union node *); +struct cmdentry { + int cmdtype; + union param { + int index; + const struct builtincmd *cmd; + struct funcnode *func; + } u; +}; +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDFUNCTION 1 /* command is a shell function */ +#define CMDBUILTIN 2 /* command is a shell builtin */ + +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_ABS 0x02 /* checks absolute paths */ +#define DO_NOFUNC 0x04 /* don't return shell functions, for command */ +#define DO_ALTPATH 0x08 /* using alternate path */ +#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ + +static void find_command(char *, struct cmdentry *, int, const char *); + + +/* ============ Hashing commands */ /* - * Evaluate a parse tree. The value is left in the global variable - * exitstatus. + * When commands are first encountered, they are entered in a hash table. + * This ensures that a full path search will not have to be done for them + * on each invocation. + * + * We should investigate converting to a linear search, even though that + * would make the command name "hash" a misnomer. */ -static void -evaltree(union node *n, int flags) -{ - int checkexit = 0; - void (*evalfn)(union node *, int); - unsigned isor; - int status; - if (n == NULL) { - TRACE(("evaltree(NULL) called\n")); - goto out; - } - TRACE(("pid %d, evaltree(%p: %d, %d) called\n", - getpid(), n, n->type, flags)); - switch (n->type) { - default: -#if DEBUG - out1fmt("Node type = %d\n", n->type); - fflush(stdout); - break; -#endif - case NNOT: - evaltree(n->nnot.com, EV_TESTED); - status = !exitstatus; - goto setstatus; - case NREDIR: - expredir(n->nredir.redirect); - status = redirectsafe(n->nredir.redirect, REDIR_PUSH); - if (!status) { - evaltree(n->nredir.n, flags & EV_TESTED); - status = exitstatus; - } - popredir(0); - goto setstatus; - case NCMD: - evalfn = evalcommand; - checkexit: - if (eflag && !(flags & EV_TESTED)) - checkexit = ~0; - goto calleval; - case NFOR: - evalfn = evalfor; - goto calleval; - case NWHILE: - case NUNTIL: - evalfn = evalloop; - goto calleval; - case NSUBSHELL: - case NBACKGND: - evalfn = evalsubshell; - goto calleval; - case NPIPE: - evalfn = evalpipe; - goto checkexit; - case NCASE: - evalfn = evalcase; - goto calleval; - case NAND: - case NOR: - case NSEMI: -#if NAND + 1 != NOR -#error NAND + 1 != NOR -#endif -#if NOR + 1 != NSEMI -#error NOR + 1 != NSEMI -#endif - isor = n->type - NAND; - evaltree( - n->nbinary.ch1, - (flags | ((isor >> 1) - 1)) & EV_TESTED - ); - if (!exitstatus == isor) - break; - if (!evalskip) { - n = n->nbinary.ch2; - evaln: - evalfn = evaltree; - calleval: - evalfn(n, flags); - break; - } - break; - case NIF: - evaltree(n->nif.test, EV_TESTED); - if (evalskip) - break; - if (exitstatus == 0) { - n = n->nif.ifpart; - goto evaln; - } else if (n->nif.elsepart) { - n = n->nif.elsepart; - goto evaln; - } - goto success; - case NDEFUN: - defun(n->narg.text, n->narg.next); - success: - status = 0; - setstatus: - exitstatus = status; - break; - } - out: - if ((checkexit & exitstatus)) - evalskip |= SKIPEVAL; - else if (pendingsigs && dotrap()) - goto exexit; - if (flags & EV_EXIT) { - exexit: - raise_exception(EXEXIT); - } -} +#define CMDTABLESIZE 31 /* should be prime */ +#define ARB 1 /* actual size determined at run time */ -#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) -static -#endif -void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + short cmdtype; /* index identifying command */ + char rehash; /* if set, cd done since entry created */ + char cmdname[ARB]; /* name of command */ +}; -static int loopnest; /* current loop nesting level */ +static struct tblentry *cmdtable[CMDTABLESIZE]; +static int builtinloc = -1; /* index in path of %builtin, or -1 */ static void -evalloop(union node *n, int flags) +tryexec(char *cmd, char **argv, char **envp) { - int status; - - loopnest++; - status = 0; - flags &= EV_TESTED; - for (;;) { - int i; + int repeated = 0; + struct BB_applet *a; + int argc = 0; + char **c; - evaltree(n->nbinary.ch1, EV_TESTED); - if (evalskip) { - skipping: - if (evalskip == SKIPCONT && --skipcount <= 0) { - evalskip = 0; - continue; - } - if (evalskip == SKIPBREAK && --skipcount <= 0) - evalskip = 0; - break; + if (strchr(cmd, '/') == NULL + && (a = find_applet_by_name(cmd)) != NULL + && is_safe_applet(cmd) + ) { + c = argv; + while (*c != NULL) { + c++; argc++; } - i = exitstatus; - if (n->type != NWHILE) - i = !i; - if (i != 0) - break; - evaltree(n->nbinary.ch2, flags); - status = exitstatus; - if (evalskip) - goto skipping; + applet_name = cmd; + exit(a->main(argc, argv)); } - loopnest--; - exitstatus = status; -} - -static void -evalfor(union node *n, int flags) -{ - struct arglist arglist; - union node *argp; - struct strlist *sp; - struct stackmark smark; - - setstackmark(&smark); - arglist.lastp = &arglist.list; - for (argp = n->nfor.args; argp; argp = argp->narg.next) { - expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD); - /* XXX */ - if (evalskip) - goto out; +#if ENABLE_FEATURE_SH_STANDALONE_SHELL + if (find_applet_by_name(cmd) != NULL) { + /* re-exec ourselves with the new arguments */ + execve(CONFIG_BUSYBOX_EXEC_PATH, argv, envp); + /* If they called chroot or otherwise made the binary no longer + * executable, fall through */ } - *arglist.lastp = NULL; +#endif - exitstatus = 0; - loopnest++; - flags &= EV_TESTED; - for (sp = arglist.list; sp; sp = sp->next) { - setvar(n->nfor.var, sp->text, 0); - evaltree(n->nfor.body, flags); - if (evalskip) { - if (evalskip == SKIPCONT && --skipcount <= 0) { - evalskip = 0; - continue; - } - if (evalskip == SKIPBREAK && --skipcount <= 0) - evalskip = 0; - break; - } + repeat: +#ifdef SYSV + do { + execve(cmd, argv, envp); + } while (errno == EINTR); +#else + execve(cmd, argv, envp); +#endif + if (repeated++) { + free(argv); + } else if (errno == ENOEXEC) { + char **ap; + char **new; + + for (ap = argv; *ap; ap++) + ; + ap = new = ckmalloc((ap - argv + 2) * sizeof(char *)); + ap[1] = cmd; + *ap = cmd = (char *)DEFAULT_SHELL; + ap += 2; + argv++; + while ((*ap++ = *argv++)) + ; + argv = new; + goto repeat; } - loopnest--; - out: - popstackmark(&smark); } +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + */ +#define environment() listvars(VEXPORT, VUNSET, 0) +static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN; static void -evalcase(union node *n, int flags) +shellexec(char **argv, const char *path, int idx) { - union node *cp; - union node *patp; - struct arglist arglist; - struct stackmark smark; + char *cmdname; + int e; + char **envp; + int exerrno; - setstackmark(&smark); - arglist.lastp = &arglist.list; - expandarg(n->ncase.expr, &arglist, EXP_TILDE); - exitstatus = 0; - for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) { - for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) { - if (casematch(patp, arglist.list->text)) { - if (evalskip == 0) { - evaltree(cp->nclist.body, flags); - } - goto out; + clearredir(1); + envp = environment(); + if (strchr(argv[0], '/') || is_safe_applet(argv[0]) +#if ENABLE_FEATURE_SH_STANDALONE_SHELL + || find_applet_by_name(argv[0]) +#endif + ) { + tryexec(argv[0], argv, envp); + e = errno; + } else { + e = ENOENT; + while ((cmdname = padvance(&path, argv[0])) != NULL) { + if (--idx < 0 && pathopt == NULL) { + tryexec(cmdname, argv, envp); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; } + stunalloc(cmdname); } } - out: - popstackmark(&smark); + + /* Map to POSIX errors */ + switch (e) { + case EACCES: + exerrno = 126; + break; + case ENOENT: + exerrno = 127; + break; + default: + exerrno = 2; + break; + } + exitstatus = exerrno; + TRACE(("shellexec failed for %s, errno %d, suppressint %d\n", + argv[0], e, suppressint )); + ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found")); + /* NOTREACHED */ } -/* - * Kick off a subshell to evaluate a tree. - */ static void -evalsubshell(union node *n, int flags) +printentry(struct tblentry *cmdp) { - struct job *jp; - int backgnd = (n->type == NBACKGND); - int status; + int idx; + const char *path; + char *name; - expredir(n->nredir.redirect); - if (!backgnd && flags & EV_EXIT && !trap[0]) - goto nofork; - INT_OFF; - jp = makejob(n, 1); - if (forkshell(jp, n, backgnd) == 0) { - INT_ON; - flags |= EV_EXIT; - if (backgnd) - flags &=~ EV_TESTED; - nofork: - redirect(n->nredir.redirect, 0); - evaltreenr(n->nredir.n, flags); - /* never returns */ - } - status = 0; - if (! backgnd) - status = waitforjob(jp); - exitstatus = status; - INT_ON; + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, cmdp->cmdname); + stunalloc(name); + } while (--idx >= 0); + out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr)); } /* - * Compute the names of the files in a redirection list. + * Clear out command entries. The argument specifies the first entry in + * PATH which has changed. */ static void -expredir(union node *n) +clearcmdentry(int firstchange) { - union node *redir; - - for (redir = n; redir; redir = redir->nfile.next) { - struct arglist fn; + struct tblentry **tblp; + struct tblentry **pp; + struct tblentry *cmdp; - memset(&fn, 0, sizeof(fn)); - fn.lastp = &fn.list; - switch (redir->type) { - case NFROMTO: - case NFROM: - case NTO: - case NCLOBBER: - case NAPPEND: - expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR); - redir->nfile.expfname = fn.list->text; - break; - case NFROMFD: - case NTOFD: - if (redir->ndup.vname) { - expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE); - if (fn.list == NULL) - ash_msg_and_raise_error("redir error"); - fixredir(redir, fn.list->text, 1); + INT_OFF; + for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) { + pp = tblp; + while ((cmdp = *pp) != NULL) { + if ((cmdp->cmdtype == CMDNORMAL && + cmdp->param.index >= firstchange) + || (cmdp->cmdtype == CMDBUILTIN && + builtinloc >= firstchange) + ) { + *pp = cmdp->next; + free(cmdp); + } else { + pp = &cmdp->next; } - break; } } + INT_ON; } /* - * Evaluate a pipeline. All the processes in the pipeline are children - * of the process creating the pipeline. (This differs from some versions - * of the shell, which make the last process in a pipeline the parent - * of all the rest.) + * Locate a command in the command hash table. If "add" is nonzero, + * add the command to the table if it is not already present. The + * variable "lastcmdentry" is set to point to the address of the link + * pointing to the entry, so that delete_cmd_entry can delete the + * entry. + * + * Interrupts must be off if called with add != 0. */ -static void -evalpipe(union node *n, int flags) +static struct tblentry **lastcmdentry; + +static struct tblentry * +cmdlookup(const char *name, int add) { - struct job *jp; - struct nodelist *lp; - int pipelen; - int prevfd; - int pip[2]; + unsigned int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + + p = name; + hashval = (unsigned char)*p << 4; + while (*p) + hashval += (unsigned char)*p++; + hashval &= 0x7FFF; + pp = &cmdtable[hashval % CMDTABLESIZE]; + for (cmdp = *pp; cmdp; cmdp = cmdp->next) { + if (strcmp(cmdp->cmdname, name) == 0) + break; + pp = &cmdp->next; + } + if (add && cmdp == NULL) { + cmdp = *pp = ckmalloc(sizeof(struct tblentry) - ARB + + strlen(name) + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + strcpy(cmdp->cmdname, name); + } + lastcmdentry = pp; + return cmdp; +} + +/* + * Delete the command entry returned on the last lookup. + */ +static void +delete_cmd_entry(void) +{ + struct tblentry *cmdp; - TRACE(("evalpipe(0x%lx) called\n", (long)n)); - pipelen = 0; - for (lp = n->npipe.cmdlist; lp; lp = lp->next) - pipelen++; - flags |= EV_EXIT; INT_OFF; - jp = makejob(n, pipelen); - prevfd = -1; - for (lp = n->npipe.cmdlist; lp; lp = lp->next) { - prehash(lp->n); - pip[1] = -1; - if (lp->next) { - if (pipe(pip) < 0) { - close(prevfd); - ash_msg_and_raise_error("Pipe call failed"); - } - } - if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { - INT_ON; - if (pip[1] >= 0) { - close(pip[0]); - } - if (prevfd > 0) { - dup2(prevfd, 0); - close(prevfd); - } - if (pip[1] > 1) { - dup2(pip[1], 1); - close(pip[1]); + cmdp = *lastcmdentry; + *lastcmdentry = cmdp->next; + if (cmdp->cmdtype == CMDFUNCTION) + freefunc(cmdp->param.func); + free(cmdp); + INT_ON; +} + +/* + * Add a new command entry, replacing any existing command entry for + * the same name - except special builtins. + */ +static void +addcmdentry(char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) { + freefunc(cmdp->param.func); + } + cmdp->cmdtype = entry->cmdtype; + cmdp->param = entry->u; + cmdp->rehash = 0; +} + +static int +hashcmd(int argc, char **argv) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + struct cmdentry entry; + char *name; + + while ((c = nextopt("r")) != '\0') { + clearcmdentry(0); + return 0; + } + if (*argptr == NULL) { + for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) { + for (cmdp = *pp; cmdp; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL) + printentry(cmdp); } - evaltreenr(lp->n, flags); - /* never returns */ } - if (prevfd >= 0) - close(prevfd); - prevfd = pip[0]; - close(pip[1]); + return 0; } - if (n->npipe.backgnd == 0) { - exitstatus = waitforjob(jp); - TRACE(("evalpipe: job done exit status %d\n", exitstatus)); + c = 0; + while ((name = *argptr) != NULL) { + cmdp = cmdlookup(name, 0); + if (cmdp != NULL + && (cmdp->cmdtype == CMDNORMAL + || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))) + delete_cmd_entry(); + find_command(name, &entry, DO_ERR, pathval()); + if (entry.cmdtype == CMDUNKNOWN) + c = 1; + argptr++; } - INT_ON; + return c; } -#if ENABLE_ASH_CMDCMD -static char ** -parse_command_args(char **argv, const char **path) +/* + * Called when a cd is done. Marks all commands so the next time they + * are executed they will be rehashed. + */ +static void +hashcd(void) { - char *cp, c; + struct tblentry **pp; + struct tblentry *cmdp; + + for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) { + for (cmdp = *pp; cmdp; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL || ( + cmdp->cmdtype == CMDBUILTIN && + !(IS_BUILTIN_REGULAR(cmdp->param.cmd)) && + builtinloc > 0 + )) + cmdp->rehash = 1; + } + } +} + +/* + * Fix command hash table when PATH changed. + * Called before PATH is changed. The argument is the new value of PATH; + * pathval() still returns the old value at this point. + * Called with interrupts off. + */ +static void +changepath(const char *newval) +{ + const char *old, *new; + int idx; + int firstchange; + int idx_bltin; + old = pathval(); + new = newval; + firstchange = 9999; /* assume no change */ + idx = 0; + idx_bltin = -1; for (;;) { - cp = *++argv; - if (!cp) - return 0; - if (*cp++ != '-') - break; - c = *cp++; - if (!c) - break; - if (c == '-' && !*cp) { - argv++; + if (*old != *new) { + firstchange = idx; + if ((*old == '\0' && *new == ':') + || (*old == ':' && *new == '\0')) + firstchange++; + old = new; /* ignore subsequent differences */ + } + if (*new == '\0') break; + if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin")) + idx_bltin = idx; + if (*new == ':') { + idx++; } - do { - switch (c) { - case 'p': - *path = defpath; - break; - default: - /* run 'typecmd' for other options */ - return 0; - } - c = *cp++; - } while (c); + new++, old++; } - return argv; + if (builtinloc < 0 && idx_bltin >= 0) + builtinloc = idx_bltin; /* zap builtins */ + if (builtinloc >= 0 && idx_bltin < 0) + firstchange = 0; + clearcmdentry(firstchange); + builtinloc = idx_bltin; } -#endif -/* Forward declarations for builtintab[] */ -#if JOBS -static int fg_bgcmd(int, char **); -#endif -static int breakcmd(int, char **); -#if ENABLE_ASH_CMDCMD -static int commandcmd(int, char **); -#endif -static int dotcmd(int, char **); -static int evalcmd(int, char **); -#if ENABLE_ASH_BUILTIN_ECHO -static int echocmd(int, char **); -#endif -#if ENABLE_ASH_BUILTIN_TEST -static int testcmd(int, char **); -#endif -static int execcmd(int, char **); -static int exitcmd(int, char **); -static int exportcmd(int, char **); -static int falsecmd(int, char **); -#if ENABLE_ASH_GETOPTS -static int getoptscmd(int, char **); -#endif -static int hashcmd(int, char **); -#if !ENABLE_FEATURE_SH_EXTRA_QUIET -static int helpcmd(int argc, char **argv); -#endif -#if JOBS -static int jobscmd(int, char **); -#endif -#if ENABLE_ASH_MATH_SUPPORT -static int letcmd(int, char **); -#endif -static int localcmd(int, char **); -static int pwdcmd(int, char **); -static int readcmd(int, char **); -static int returncmd(int, char **); -static int setcmd(int, char **); -static int shiftcmd(int, char **); -static int timescmd(int, char **); -static int trapcmd(int, char **); -static int truecmd(int, char **); -static int typecmd(int, char **); -static int umaskcmd(int, char **); -static int unsetcmd(int, char **); -static int waitcmd(int, char **); -static int ulimitcmd(int, char **); -#if JOBS -static int killcmd(int, char **); -#endif - -#define BUILTIN_NOSPEC "0" -#define BUILTIN_SPECIAL "1" -#define BUILTIN_REGULAR "2" -#define BUILTIN_SPEC_REG "3" -#define BUILTIN_ASSIGN "4" -#define BUILTIN_SPEC_ASSG "5" -#define BUILTIN_REG_ASSG "6" -#define BUILTIN_SPEC_REG_ASSG "7" - -#define IS_BUILTIN_SPECIAL(b) ((b)->name[0] & 1) -#define IS_BUILTIN_REGULAR(b) ((b)->name[0] & 2) -#define IS_BUILTIN_ASSIGN(b) ((b)->name[0] & 4) +#define TEOF 0 +#define TNL 1 +#define TREDIR 2 +#define TWORD 3 +#define TSEMI 4 +#define TBACKGND 5 +#define TAND 6 +#define TOR 7 +#define TPIPE 8 +#define TLP 9 +#define TRP 10 +#define TENDCASE 11 +#define TENDBQUOTE 12 +#define TNOT 13 +#define TCASE 14 +#define TDO 15 +#define TDONE 16 +#define TELIF 17 +#define TELSE 18 +#define TESAC 19 +#define TFI 20 +#define TFOR 21 +#define TIF 22 +#define TIN 23 +#define TTHEN 24 +#define TUNTIL 25 +#define TWHILE 26 +#define TBEGIN 27 +#define TEND 28 -/* make sure to keep these in proper order since it is searched via bsearch() */ -static const struct builtincmd builtintab[] = { - { BUILTIN_SPEC_REG ".", dotcmd }, - { BUILTIN_SPEC_REG ":", truecmd }, -#if ENABLE_ASH_BUILTIN_TEST - { BUILTIN_REGULAR "[", testcmd }, - { BUILTIN_REGULAR "[[", testcmd }, -#endif -#if ENABLE_ASH_ALIAS - { BUILTIN_REG_ASSG "alias", aliascmd }, -#endif -#if JOBS - { BUILTIN_REGULAR "bg", fg_bgcmd }, -#endif - { BUILTIN_SPEC_REG "break", breakcmd }, - { BUILTIN_REGULAR "cd", cdcmd }, - { BUILTIN_NOSPEC "chdir", cdcmd }, -#if ENABLE_ASH_CMDCMD - { BUILTIN_REGULAR "command", commandcmd }, -#endif - { BUILTIN_SPEC_REG "continue", breakcmd }, -#if ENABLE_ASH_BUILTIN_ECHO - { BUILTIN_REGULAR "echo", echocmd }, -#endif - { BUILTIN_SPEC_REG "eval", evalcmd }, - { BUILTIN_SPEC_REG "exec", execcmd }, - { BUILTIN_SPEC_REG "exit", exitcmd }, - { BUILTIN_SPEC_REG_ASSG "export", exportcmd }, - { BUILTIN_REGULAR "false", falsecmd }, -#if JOBS - { BUILTIN_REGULAR "fg", fg_bgcmd }, -#endif -#if ENABLE_ASH_GETOPTS - { BUILTIN_REGULAR "getopts", getoptscmd }, -#endif - { BUILTIN_NOSPEC "hash", hashcmd }, -#if !ENABLE_FEATURE_SH_EXTRA_QUIET - { BUILTIN_NOSPEC "help", helpcmd }, -#endif -#if JOBS - { BUILTIN_REGULAR "jobs", jobscmd }, - { BUILTIN_REGULAR "kill", killcmd }, -#endif -#if ENABLE_ASH_MATH_SUPPORT - { BUILTIN_NOSPEC "let", letcmd }, -#endif - { BUILTIN_ASSIGN "local", localcmd }, - { BUILTIN_NOSPEC "pwd", pwdcmd }, - { BUILTIN_REGULAR "read", readcmd }, - { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd }, - { BUILTIN_SPEC_REG "return", returncmd }, - { BUILTIN_SPEC_REG "set", setcmd }, - { BUILTIN_SPEC_REG "shift", shiftcmd }, - { BUILTIN_SPEC_REG "source", dotcmd }, -#if ENABLE_ASH_BUILTIN_TEST - { BUILTIN_REGULAR "test", testcmd }, -#endif - { BUILTIN_SPEC_REG "times", timescmd }, - { BUILTIN_SPEC_REG "trap", trapcmd }, - { BUILTIN_REGULAR "true", truecmd }, - { BUILTIN_NOSPEC "type", typecmd }, - { BUILTIN_NOSPEC "ulimit", ulimitcmd }, - { BUILTIN_REGULAR "umask", umaskcmd }, -#if ENABLE_ASH_ALIAS - { BUILTIN_REGULAR "unalias", unaliascmd }, -#endif - { BUILTIN_SPEC_REG "unset", unsetcmd }, - { BUILTIN_REGULAR "wait", waitcmd }, +/* first char is indicating which tokens mark the end of a list */ +static const char *const tokname_array[] = { + "\1end of file", + "\0newline", + "\0redirection", + "\0word", + "\0;", + "\0&", + "\0&&", + "\0||", + "\0|", + "\0(", + "\1)", + "\1;;", + "\1`", +#define KWDOFFSET 13 + /* the following are keywords */ + "\0!", + "\0case", + "\1do", + "\1done", + "\1elif", + "\1else", + "\1esac", + "\1fi", + "\0for", + "\0if", + "\0in", + "\1then", + "\0until", + "\0while", + "\0{", + "\1}", }; -#define NUMBUILTINS (sizeof(builtintab) / sizeof(builtintab[0])) +static const char * +tokname(int tok) +{ + static char buf[16]; -#define COMMANDCMD (builtintab + 5 + \ - 2 * ENABLE_ASH_BUILTIN_TEST + \ - ENABLE_ASH_ALIAS + \ - ENABLE_ASH_JOB_CONTROL) -#define EXECCMD (builtintab + 7 + \ - 2 * ENABLE_ASH_BUILTIN_TEST + \ - ENABLE_ASH_ALIAS + \ - ENABLE_ASH_JOB_CONTROL + \ - ENABLE_ASH_CMDCMD + \ - ENABLE_ASH_BUILTIN_ECHO) + if (tok >= TSEMI) + buf[0] = '"'; + sprintf(buf + (tok >= TSEMI), "%s%c", + tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); + return buf; +} -/* - * Execute a simple command. - */ -static int back_exitstatus; /* exit status of backquoted command */ +/* Wrapper around strcmp for qsort/bsearch/... */ static int -isassignment(const char *p) +pstrcmp(const void *a, const void *b) { - const char *q = endofname(p); - if (p == q) - return 0; - return *q == '='; + return strcmp((const char *) a, (*(const char *const *) b) + 1); } -static int -bltincmd(int argc, char **argv) + +static const char *const * +findkwd(const char *s) { - /* Preserve exitstatus of a previous possible redirection - * as POSIX mandates */ - return back_exitstatus; + return bsearch(s, tokname_array + KWDOFFSET, + (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET, + sizeof(const char *), pstrcmp); } -static void -evalcommand(union node *cmd, int flags) -{ - static const struct builtincmd bltin = { - "\0\0", bltincmd - }; - struct stackmark smark; - union node *argp; - struct arglist arglist; - struct arglist varlist; - char **argv; - int argc; - const struct strlist *sp; - struct cmdentry cmdentry; - struct job *jp; - char *lastarg; - const char *path; - int spclbltin; - int cmd_is_exec; - int status; - char **nargv; - struct builtincmd *bcmd; - int pseudovarflag = 0; - /* First expand the arguments. */ - TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); - setstackmark(&smark); - back_exitstatus = 0; - - cmdentry.cmdtype = CMDBUILTIN; - cmdentry.u.cmd = &bltin; - varlist.lastp = &varlist.list; - *varlist.lastp = NULL; - arglist.lastp = &arglist.list; - *arglist.lastp = NULL; +/* + * Locate and print what a word is... + */ +#if ENABLE_ASH_CMDCMD +static int +describe_command(char *command, int describe_command_verbose) +#else +#define describe_command_verbose 1 +static int +describe_command(char *command) +#endif +{ + struct cmdentry entry; + struct tblentry *cmdp; +#if ENABLE_ASH_ALIAS + const struct alias *ap; +#endif + const char *path = pathval(); - argc = 0; - if (cmd->ncmd.args) { - bcmd = find_builtin(cmd->ncmd.args->narg.text); - pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd); + if (describe_command_verbose) { + out1str(command); } - for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) { - struct strlist **spp; - - spp = arglist.lastp; - if (pseudovarflag && isassignment(argp->narg.text)) - expandarg(argp, &arglist, EXP_VARTILDE); - else - expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + /* First look at the keywords */ + if (findkwd(command)) { + out1str(describe_command_verbose ? " is a shell keyword" : command); + goto out; + } - for (sp = *spp; sp; sp = sp->next) - argc++; +#if ENABLE_ASH_ALIAS + /* Then look at the aliases */ + ap = lookupalias(command, 0); + if (ap != NULL) { + if (describe_command_verbose) { + out1fmt(" is an alias for %s", ap->val); + } else { + out1str("alias "); + printalias(ap); + return 0; + } + goto out; + } +#endif + /* Then check if it is a tracked alias */ + cmdp = cmdlookup(command, 0); + if (cmdp != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + } else { + /* Finally use brute force */ + find_command(command, &entry, DO_ABS, path); } - argv = nargv = stalloc(sizeof(char *) * (argc + 1)); - for (sp = arglist.list; sp; sp = sp->next) { - TRACE(("evalcommand arg: %s\n", sp->text)); - *nargv++ = sp->text; + switch (entry.cmdtype) { + case CMDNORMAL: { + int j = entry.u.index; + char *p; + if (j == -1) { + p = command; + } else { + do { + p = padvance(&path, command); + stunalloc(p); + } while (--j >= 0); + } + if (describe_command_verbose) { + out1fmt(" is%s %s", + (cmdp ? " a tracked alias for" : nullstr), p + ); + } else { + out1str(p); + } + break; } - *nargv = NULL; - lastarg = NULL; - if (iflag && funcnest == 0 && argc > 0) - lastarg = nargv[-1]; + case CMDFUNCTION: + if (describe_command_verbose) { + out1str(" is a shell function"); + } else { + out1str(command); + } + break; - preverrout_fd = 2; - expredir(cmd->ncmd.redirect); - status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2); + case CMDBUILTIN: + if (describe_command_verbose) { + out1fmt(" is a %sshell builtin", + IS_BUILTIN_SPECIAL(entry.u.cmd) ? + "special " : nullstr + ); + } else { + out1str(command); + } + break; - path = vpath.text; - for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) { - struct strlist **spp; - char *p; + default: + if (describe_command_verbose) { + out1str(": not found\n"); + } + return 127; + } + out: + outstr("\n", stdout); + return 0; +} - spp = varlist.lastp; - expandarg(argp, &varlist, EXP_VARTILDE); +static int +typecmd(int argc, char **argv) +{ + int i; + int err = 0; - /* - * Modify the command lookup path, if a PATH= assignment - * is present - */ - p = (*spp)->text; - if (varequal(p, path)) - path = p; + for (i = 1; i < argc; i++) { +#if ENABLE_ASH_CMDCMD + err |= describe_command(argv[i], 1); +#else + err |= describe_command(argv[i]); +#endif } + return err; +} - /* Print the command if xflag is set. */ - if (xflag) { - int n; - const char *p = " %s"; +#if ENABLE_ASH_CMDCMD +static int +commandcmd(int argc, char **argv) +{ + int c; + enum { + VERIFY_BRIEF = 1, + VERIFY_VERBOSE = 2, + } verify = 0; - p++; - dprintf(preverrout_fd, p, expandstr(ps4val())); + while ((c = nextopt("pvV")) != '\0') + if (c == 'V') + verify |= VERIFY_VERBOSE; + else if (c == 'v') + verify |= VERIFY_BRIEF; +#if DEBUG + else if (c != 'p') + abort(); +#endif + if (verify) + return describe_command(*argptr, verify - VERIFY_BRIEF); - sp = varlist.list; - for (n = 0; n < 2; n++) { - while (sp) { - dprintf(preverrout_fd, p, sp->text); - sp = sp->next; - if (*p == '%') { - p--; - } - } - sp = arglist.list; - } - full_write(preverrout_fd, "\n", 1); - } + return 0; +} +#endif - cmd_is_exec = 0; - spclbltin = -1; - /* Now locate the command. */ - if (argc) { - const char *oldpath; - int cmd_flag = DO_ERR; +/* ============ eval.c */ - path += 5; - oldpath = path; - for (;;) { - find_command(argv[0], &cmdentry, cmd_flag, path); - if (cmdentry.cmdtype == CMDUNKNOWN) { - status = 127; - flush_stderr(); - goto bail; - } +static int funcblocksize; /* size of structures in function */ +static int funcstringsize; /* size of strings in node */ +static void *funcblock; /* block to allocate function from */ +static char *funcstring; /* block to allocate strings from */ - /* implement bltin and command here */ - if (cmdentry.cmdtype != CMDBUILTIN) - break; - if (spclbltin < 0) - spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd); - if (cmdentry.u.cmd == EXECCMD) - cmd_is_exec++; -#if ENABLE_ASH_CMDCMD - if (cmdentry.u.cmd == COMMANDCMD) { - path = oldpath; - nargv = parse_command_args(argv, &path); - if (!nargv) - break; - argc -= nargv - argv; - argv = nargv; - cmd_flag |= DO_NOFUNC; - } else -#endif - break; - } - } +/* flags in argument to evaltree */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ +#define EV_BACKCMD 04 /* command executing within back quotes */ + +static const short nodesize[26] = { + SHELL_ALIGN(sizeof(struct ncmd)), + SHELL_ALIGN(sizeof(struct npipe)), + SHELL_ALIGN(sizeof(struct nredir)), + SHELL_ALIGN(sizeof(struct nredir)), + SHELL_ALIGN(sizeof(struct nredir)), + SHELL_ALIGN(sizeof(struct nbinary)), + SHELL_ALIGN(sizeof(struct nbinary)), + SHELL_ALIGN(sizeof(struct nbinary)), + SHELL_ALIGN(sizeof(struct nif)), + SHELL_ALIGN(sizeof(struct nbinary)), + SHELL_ALIGN(sizeof(struct nbinary)), + SHELL_ALIGN(sizeof(struct nfor)), + SHELL_ALIGN(sizeof(struct ncase)), + SHELL_ALIGN(sizeof(struct nclist)), + SHELL_ALIGN(sizeof(struct narg)), + SHELL_ALIGN(sizeof(struct narg)), + SHELL_ALIGN(sizeof(struct nfile)), + SHELL_ALIGN(sizeof(struct nfile)), + SHELL_ALIGN(sizeof(struct nfile)), + SHELL_ALIGN(sizeof(struct nfile)), + SHELL_ALIGN(sizeof(struct nfile)), + SHELL_ALIGN(sizeof(struct ndup)), + SHELL_ALIGN(sizeof(struct ndup)), + SHELL_ALIGN(sizeof(struct nhere)), + SHELL_ALIGN(sizeof(struct nhere)), + SHELL_ALIGN(sizeof(struct nnot)), +}; - if (status) { - /* We have a redirection error. */ - if (spclbltin > 0) - raise_exception(EXERROR); - bail: - exitstatus = status; - goto out; +static void calcsize(union node *n); + +static void +sizenodelist(struct nodelist *lp) +{ + while (lp) { + funcblocksize += SHELL_ALIGN(sizeof(struct nodelist)); + calcsize(lp->n); + lp = lp->next; } +} - /* Execute the command. */ - switch (cmdentry.cmdtype) { - default: - /* Fork off a child process if necessary. */ - if (!(flags & EV_EXIT) || trap[0]) { - INT_OFF; - jp = makejob(cmd, 1); - if (forkshell(jp, cmd, FORK_FG) != 0) { - exitstatus = waitforjob(jp); - INT_ON; - break; - } - FORCE_INT_ON; - } - listsetvar(varlist.list, VEXPORT|VSTACK); - shellexec(argv, path, cmdentry.u.index); - /* NOTREACHED */ +static void +calcsize(union node *n) +{ + if (n == NULL) + return; + funcblocksize += nodesize[n->type]; + switch (n->type) { + case NCMD: + calcsize(n->ncmd.redirect); + calcsize(n->ncmd.args); + calcsize(n->ncmd.assign); + break; + case NPIPE: + sizenodelist(n->npipe.cmdlist); + break; + case NREDIR: + case NBACKGND: + case NSUBSHELL: + calcsize(n->nredir.redirect); + calcsize(n->nredir.n); + break; + case NAND: + case NOR: + case NSEMI: + case NWHILE: + case NUNTIL: + calcsize(n->nbinary.ch2); + calcsize(n->nbinary.ch1); + break; + case NIF: + calcsize(n->nif.elsepart); + calcsize(n->nif.ifpart); + calcsize(n->nif.test); + break; + case NFOR: + funcstringsize += strlen(n->nfor.var) + 1; + calcsize(n->nfor.body); + calcsize(n->nfor.args); + break; + case NCASE: + calcsize(n->ncase.cases); + calcsize(n->ncase.expr); + break; + case NCLIST: + calcsize(n->nclist.body); + calcsize(n->nclist.pattern); + calcsize(n->nclist.next); + break; + case NDEFUN: + case NARG: + sizenodelist(n->narg.backquote); + funcstringsize += strlen(n->narg.text) + 1; + calcsize(n->narg.next); + break; + case NTO: + case NCLOBBER: + case NFROM: + case NFROMTO: + case NAPPEND: + calcsize(n->nfile.fname); + calcsize(n->nfile.next); + break; + case NTOFD: + case NFROMFD: + calcsize(n->ndup.vname); + calcsize(n->ndup.next); + break; + case NHERE: + case NXHERE: + calcsize(n->nhere.doc); + calcsize(n->nhere.next); + break; + case NNOT: + calcsize(n->nnot.com); + break; + }; +} - case CMDBUILTIN: - cmdenviron = varlist.list; - if (cmdenviron) { - struct strlist *list = cmdenviron; - int i = VNOSET; - if (spclbltin > 0 || argc == 0) { - i = 0; - if (cmd_is_exec && argc > 1) - i = VEXPORT; - } - listsetvar(list, i); - } - if (evalbltin(cmdentry.u.cmd, argc, argv)) { - int exit_status; - int i, j; +static char * +nodeckstrdup(char *s) +{ + char *rtn = funcstring; - i = exception; - if (i == EXEXIT) - goto raise; + strcpy(funcstring, s); + funcstring += strlen(s) + 1; + return rtn; +} - exit_status = 2; - j = 0; - if (i == EXINT) - j = SIGINT; - if (i == EXSIG) - j = pendingsigs; - if (j) - exit_status = j + 128; - exitstatus = exit_status; +static union node *copynode(union node *); - if (i == EXINT || spclbltin > 0) { - raise: - longjmp(exception_handler->loc, 1); - } - FORCE_INT_ON; - } - break; +static struct nodelist * +copynodelist(struct nodelist *lp) +{ + struct nodelist *start; + struct nodelist **lpp; - case CMDFUNCTION: - listsetvar(varlist.list, 0); - if (evalfun(cmdentry.u.func, argc, argv, flags)) - goto raise; - break; + lpp = &start; + while (lp) { + *lpp = funcblock; + funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n); + lp = lp->next; + lpp = &(*lpp)->next; } - - out: - popredir(cmd_is_exec); - if (lastarg) - /* dsl: I think this is intended to be used to support - * '_' in 'vi' command mode during line editing... - * However I implemented that within libedit itself. - */ - setvar("_", lastarg, 0); - popstackmark(&smark); + *lpp = NULL; + return start; } -static int -evalbltin(const struct builtincmd *cmd, int argc, char **argv) +static union node * +copynode(union node *n) { - char *volatile savecmdname; - struct jmploc *volatile savehandler; - struct jmploc jmploc; - int i; + union node *new; - savecmdname = commandname; - i = setjmp(jmploc.loc); - if (i) - goto cmddone; - savehandler = exception_handler; - exception_handler = &jmploc; - commandname = argv[0]; - argptr = argv + 1; - optptr = NULL; /* initialize nextopt */ - exitstatus = (*cmd->builtin)(argc, argv); - flush_stdout_stderr(); - cmddone: - exitstatus |= ferror(stdout); - clearerr(stdout); - commandname = savecmdname; - exsig = 0; - exception_handler = savehandler; + if (n == NULL) + return NULL; + new = funcblock; + funcblock = (char *) funcblock + nodesize[n->type]; - return i; + switch (n->type) { + case NCMD: + new->ncmd.redirect = copynode(n->ncmd.redirect); + new->ncmd.args = copynode(n->ncmd.args); + new->ncmd.assign = copynode(n->ncmd.assign); + break; + case NPIPE: + new->npipe.cmdlist = copynodelist(n->npipe.cmdlist); + new->npipe.backgnd = n->npipe.backgnd; + break; + case NREDIR: + case NBACKGND: + case NSUBSHELL: + new->nredir.redirect = copynode(n->nredir.redirect); + new->nredir.n = copynode(n->nredir.n); + break; + case NAND: + case NOR: + case NSEMI: + case NWHILE: + case NUNTIL: + new->nbinary.ch2 = copynode(n->nbinary.ch2); + new->nbinary.ch1 = copynode(n->nbinary.ch1); + break; + case NIF: + new->nif.elsepart = copynode(n->nif.elsepart); + new->nif.ifpart = copynode(n->nif.ifpart); + new->nif.test = copynode(n->nif.test); + break; + case NFOR: + new->nfor.var = nodeckstrdup(n->nfor.var); + new->nfor.body = copynode(n->nfor.body); + new->nfor.args = copynode(n->nfor.args); + break; + case NCASE: + new->ncase.cases = copynode(n->ncase.cases); + new->ncase.expr = copynode(n->ncase.expr); + break; + case NCLIST: + new->nclist.body = copynode(n->nclist.body); + new->nclist.pattern = copynode(n->nclist.pattern); + new->nclist.next = copynode(n->nclist.next); + break; + case NDEFUN: + case NARG: + new->narg.backquote = copynodelist(n->narg.backquote); + new->narg.text = nodeckstrdup(n->narg.text); + new->narg.next = copynode(n->narg.next); + break; + case NTO: + case NCLOBBER: + case NFROM: + case NFROMTO: + case NAPPEND: + new->nfile.fname = copynode(n->nfile.fname); + new->nfile.fd = n->nfile.fd; + new->nfile.next = copynode(n->nfile.next); + break; + case NTOFD: + case NFROMFD: + new->ndup.vname = copynode(n->ndup.vname); + new->ndup.dupfd = n->ndup.dupfd; + new->ndup.fd = n->ndup.fd; + new->ndup.next = copynode(n->ndup.next); + break; + case NHERE: + case NXHERE: + new->nhere.doc = copynode(n->nhere.doc); + new->nhere.fd = n->nhere.fd; + new->nhere.next = copynode(n->nhere.next); + break; + case NNOT: + new->nnot.com = copynode(n->nnot.com); + break; + }; + new->type = n->type; + return new; } -static struct localvar *localvars; - /* - * Called after a function returns. - * Interrupts must be off. + * Make a copy of a parse tree. */ -static void -poplocalvars(void) +static struct funcnode * +copyfunc(union node *n) { - struct localvar *lvp; - struct var *vp; + struct funcnode *f; + size_t blocksize; - while ((lvp = localvars) != NULL) { - localvars = lvp->next; - vp = lvp->vp; - TRACE(("poplocalvar %s", vp ? vp->text : "-")); - if (vp == NULL) { /* $- saved */ - memcpy(optlist, lvp->text, sizeof(optlist)); - free((char*)lvp->text); - optschanged(); - } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { - unsetvar(vp->text); - } else { - if (vp->func) - (*vp->func)(strchrnul(lvp->text, '=') + 1); - if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) - free((char*)vp->text); - vp->flags = lvp->flags; - vp->text = lvp->text; - } - free(lvp); - } + funcblocksize = offsetof(struct funcnode, n); + funcstringsize = 0; + calcsize(n); + blocksize = funcblocksize; + f = ckmalloc(blocksize + funcstringsize); + funcblock = (char *) f + offsetof(struct funcnode, n); + funcstring = (char *) f + blocksize; + copynode(n); + f->count = 0; + return f; } /* - * Free a parse tree. + * Define a shell function. */ static void -freefunc(struct funcnode *f) -{ - if (f && --f->count < 0) - free(f); -} - -static int -evalfun(struct funcnode *func, int argc, char **argv, int flags) +defun(char *name, union node *func) { - volatile struct shparam saveparam; - struct localvar *volatile savelocalvars; - struct jmploc *volatile savehandler; - struct jmploc jmploc; - int e; + struct cmdentry entry; - saveparam = shellparam; - savelocalvars = localvars; - e = setjmp(jmploc.loc); - if (e) { - goto funcdone; - } - INT_OFF; - savehandler = exception_handler; - exception_handler = &jmploc; - localvars = NULL; - shellparam.malloc = 0; - func->count++; - funcnest++; - INT_ON; - shellparam.nparam = argc - 1; - shellparam.p = argv + 1; -#if ENABLE_ASH_GETOPTS - shellparam.optind = 1; - shellparam.optoff = -1; -#endif - evaltree(&func->n, flags & EV_TESTED); -funcdone: INT_OFF; - funcnest--; - freefunc(func); - poplocalvars(); - localvars = savelocalvars; - freeparam(&shellparam); - shellparam = saveparam; - exception_handler = savehandler; + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); + addcmdentry(name, &entry); INT_ON; - evalskip &= ~SKIPFUNC; - return e; } +static int evalskip; /* set if we are skipping commands */ +/* reasons for skipping commands (see comment on breakcmd routine) */ +#define SKIPBREAK (1 << 0) +#define SKIPCONT (1 << 1) +#define SKIPFUNC (1 << 2) +#define SKIPFILE (1 << 3) +#define SKIPEVAL (1 << 4) +static int skipcount; /* number of levels to skip */ +static int funcnest; /* depth of function calls */ -static int -goodname(const char *p) -{ - return !*endofname(p); -} - +/* forward declarations - evaluation is fairly recursive business... */ +static void evalloop(union node *, int); +static void evalfor(union node *, int); +static void evalcase(union node *, int); +static void evalsubshell(union node *, int); +static void expredir(union node *); +static void evalpipe(union node *, int); +static void evalcommand(union node *, int); +static int evalbltin(const struct builtincmd *, int, char **); +static void prehash(union node *); /* - * Search for a command. This is called before we fork so that the - * location of the command will be available in the parent as well as - * the child. The check for "goodname" is an overly conservative - * check that the name will not be subject to expansion. + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. */ static void -prehash(union node *n) +evaltree(union node *n, int flags) { - struct cmdentry entry; + int checkexit = 0; + void (*evalfn)(union node *, int); + unsigned isor; + int status; + if (n == NULL) { + TRACE(("evaltree(NULL) called\n")); + goto out; + } + TRACE(("pid %d, evaltree(%p: %d, %d) called\n", + getpid(), n, n->type, flags)); + switch (n->type) { + default: +#if DEBUG + out1fmt("Node type = %d\n", n->type); + fflush(stdout); + break; +#endif + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + status = !exitstatus; + goto setstatus; + case NREDIR: + expredir(n->nredir.redirect); + status = redirectsafe(n->nredir.redirect, REDIR_PUSH); + if (!status) { + evaltree(n->nredir.n, flags & EV_TESTED); + status = exitstatus; + } + popredir(0); + goto setstatus; + case NCMD: + evalfn = evalcommand; + checkexit: + if (eflag && !(flags & EV_TESTED)) + checkexit = ~0; + goto calleval; + case NFOR: + evalfn = evalfor; + goto calleval; + case NWHILE: + case NUNTIL: + evalfn = evalloop; + goto calleval; + case NSUBSHELL: + case NBACKGND: + evalfn = evalsubshell; + goto calleval; + case NPIPE: + evalfn = evalpipe; + goto checkexit; + case NCASE: + evalfn = evalcase; + goto calleval; + case NAND: + case NOR: + case NSEMI: +#if NAND + 1 != NOR +#error NAND + 1 != NOR +#endif +#if NOR + 1 != NSEMI +#error NOR + 1 != NSEMI +#endif + isor = n->type - NAND; + evaltree( + n->nbinary.ch1, + (flags | ((isor >> 1) - 1)) & EV_TESTED + ); + if (!exitstatus == isor) + break; + if (!evalskip) { + n = n->nbinary.ch2; + evaln: + evalfn = evaltree; + calleval: + evalfn(n, flags); + break; + } + break; + case NIF: + evaltree(n->nif.test, EV_TESTED); + if (evalskip) + break; + if (exitstatus == 0) { + n = n->nif.ifpart; + goto evaln; + } else if (n->nif.elsepart) { + n = n->nif.elsepart; + goto evaln; + } + goto success; + case NDEFUN: + defun(n->narg.text, n->narg.next); + success: + status = 0; + setstatus: + exitstatus = status; + break; + } + out: + if ((checkexit & exitstatus)) + evalskip |= SKIPEVAL; + else if (pendingsigs && dotrap()) + goto exexit; - if (n->type == NCMD && n->ncmd.args && goodname(n->ncmd.args->narg.text)) - find_command(n->ncmd.args->narg.text, &entry, 0, pathval()); + if (flags & EV_EXIT) { + exexit: + raise_exception(EXEXIT); + } } +#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) +static +#endif +void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); -/* - * Builtin commands. Builtin commands whose functions are closely - * tied to evaluation are implemented here. - */ - -/* - * Handle break and continue commands. Break, continue, and return are - * all handled by setting the evalskip flag. The evaluation routines - * above all check this flag, and if it is set they start skipping - * commands rather than executing them. The variable skipcount is - * the number of loops to break/continue, or the number of function - * levels to return. (The latter is always 1.) It should probably - * be an error to break out of more loops than exist, but it isn't - * in the standard shell so we don't make it one here. - */ +static int loopnest; /* current loop nesting level */ -static int -breakcmd(int argc, char **argv) +static void +evalloop(union node *n, int flags) { - int n = argc > 1 ? number(argv[1]) : 1; + int status; - if (n <= 0) - ash_msg_and_raise_error(illnum, argv[1]); - if (n > loopnest) - n = loopnest; - if (n > 0) { - evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; - skipcount = n; + loopnest++; + status = 0; + flags &= EV_TESTED; + for (;;) { + int i; + + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip) { + skipping: + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + i = exitstatus; + if (n->type != NWHILE) + i = !i; + if (i != 0) + break; + evaltree(n->nbinary.ch2, flags); + status = exitstatus; + if (evalskip) + goto skipping; } - return 0; + loopnest--; + exitstatus = status; } -/* - * The return command. - */ -static int -returncmd(int argc, char **argv) +static void +evalfor(union node *n, int flags) { - /* - * If called outside a function, do what ksh does; - * skip the rest of the file. - */ - evalskip = funcnest ? SKIPFUNC : SKIPFILE; - return argv[1] ? number(argv[1]) : exitstatus; -} + struct arglist arglist; + union node *argp; + struct strlist *sp; + struct stackmark smark; -static int -falsecmd(int argc, char **argv) -{ - return 1; -} + setstackmark(&smark); + arglist.lastp = &arglist.list; + for (argp = n->nfor.args; argp; argp = argp->narg.next) { + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD); + /* XXX */ + if (evalskip) + goto out; + } + *arglist.lastp = NULL; -static int -truecmd(int argc, char **argv) -{ - return 0; + exitstatus = 0; + loopnest++; + flags &= EV_TESTED; + for (sp = arglist.list; sp; sp = sp->next) { + setvar(n->nfor.var, sp->text, 0); + evaltree(n->nfor.body, flags); + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + } + loopnest--; + out: + popstackmark(&smark); } -static int -execcmd(int argc, char **argv) +static void +evalcase(union node *n, int flags) { - if (argc > 1) { - iflag = 0; /* exit on error */ - mflag = 0; - optschanged(); - shellexec(argv + 1, pathval(), 0); + union node *cp; + union node *patp; + struct arglist arglist; + struct stackmark smark; + + setstackmark(&smark); + arglist.lastp = &arglist.list; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + exitstatus = 0; + for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) { + if (casematch(patp, arglist.list->text)) { + if (evalskip == 0) { + evaltree(cp->nclist.body, flags); + } + goto out; + } + } } - return 0; + out: + popstackmark(&smark); } - -/* ============ Executing commands */ - /* - * When commands are first encountered, they are entered in a hash table. - * This ensures that a full path search will not have to be done for them - * on each invocation. - * - * We should investigate converting to a linear search, even though that - * would make the command name "hash" a misnomer. + * Kick off a subshell to evaluate a tree. */ - -#define CMDTABLESIZE 31 /* should be prime */ -#define ARB 1 /* actual size determined at run time */ - -struct tblentry { - struct tblentry *next; /* next entry in hash chain */ - union param param; /* definition of builtin function */ - short cmdtype; /* index identifying command */ - char rehash; /* if set, cd done since entry created */ - char cmdname[ARB]; /* name of command */ -}; - -static struct tblentry *cmdtable[CMDTABLESIZE]; -static int builtinloc = -1; /* index in path of %builtin, or -1 */ - static void -tryexec(char *cmd, char **argv, char **envp) +evalsubshell(union node *n, int flags) { - int repeated = 0; - struct BB_applet *a; - int argc = 0; - char **c; - - if (strchr(cmd, '/') == NULL - && (a = find_applet_by_name(cmd)) != NULL - && is_safe_applet(cmd) - ) { - c = argv; - while (*c != NULL) { - c++; argc++; - } - applet_name = cmd; - exit(a->main(argc, argv)); - } -#if ENABLE_FEATURE_SH_STANDALONE_SHELL - if (find_applet_by_name(cmd) != NULL) { - /* re-exec ourselves with the new arguments */ - execve(CONFIG_BUSYBOX_EXEC_PATH, argv, envp); - /* If they called chroot or otherwise made the binary no longer - * executable, fall through */ - } -#endif - - repeat: -#ifdef SYSV - do { - execve(cmd, argv, envp); - } while (errno == EINTR); -#else - execve(cmd, argv, envp); -#endif - if (repeated++) { - free(argv); - } else if (errno == ENOEXEC) { - char **ap; - char **new; + struct job *jp; + int backgnd = (n->type == NBACKGND); + int status; - for (ap = argv; *ap; ap++) - ; - ap = new = ckmalloc((ap - argv + 2) * sizeof(char *)); - ap[1] = cmd; - *ap = cmd = (char *)DEFAULT_SHELL; - ap += 2; - argv++; - while ((*ap++ = *argv++)) - ; - argv = new; - goto repeat; + expredir(n->nredir.redirect); + if (!backgnd && flags & EV_EXIT && !trap[0]) + goto nofork; + INT_OFF; + jp = makejob(n, 1); + if (forkshell(jp, n, backgnd) == 0) { + INT_ON; + flags |= EV_EXIT; + if (backgnd) + flags &=~ EV_TESTED; + nofork: + redirect(n->nredir.redirect, 0); + evaltreenr(n->nredir.n, flags); + /* never returns */ } + status = 0; + if (! backgnd) + status = waitforjob(jp); + exitstatus = status; + INT_ON; } /* - * Exec a program. Never returns. If you change this routine, you may - * have to change the find_command routine as well. + * Compute the names of the files in a redirection list. */ -#define environment() listvars(VEXPORT, VUNSET, 0) -static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN; static void -shellexec(char **argv, const char *path, int idx) +expredir(union node *n) { - char *cmdname; - int e; - char **envp; - int exerrno; + union node *redir; - clearredir(1); - envp = environment(); - if (strchr(argv[0], '/') || is_safe_applet(argv[0]) -#if ENABLE_FEATURE_SH_STANDALONE_SHELL - || find_applet_by_name(argv[0]) -#endif - ) { - tryexec(argv[0], argv, envp); - e = errno; - } else { - e = ENOENT; - while ((cmdname = padvance(&path, argv[0])) != NULL) { - if (--idx < 0 && pathopt == NULL) { - tryexec(cmdname, argv, envp); - if (errno != ENOENT && errno != ENOTDIR) - e = errno; + for (redir = n; redir; redir = redir->nfile.next) { + struct arglist fn; + + memset(&fn, 0, sizeof(fn)); + fn.lastp = &fn.list; + switch (redir->type) { + case NFROMTO: + case NFROM: + case NTO: + case NCLOBBER: + case NAPPEND: + expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR); + redir->nfile.expfname = fn.list->text; + break; + case NFROMFD: + case NTOFD: + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE); + if (fn.list == NULL) + ash_msg_and_raise_error("redir error"); + fixredir(redir, fn.list->text, 1); } - stunalloc(cmdname); + break; } } - - /* Map to POSIX errors */ - switch (e) { - case EACCES: - exerrno = 126; - break; - case ENOENT: - exerrno = 127; - break; - default: - exerrno = 2; - break; - } - exitstatus = exerrno; - TRACE(("shellexec failed for %s, errno %d, suppressint %d\n", - argv[0], e, suppressint )); - ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found")); - /* NOTREACHED */ -} - -static void -printentry(struct tblentry *cmdp) -{ - int idx; - const char *path; - char *name; - - idx = cmdp->param.index; - path = pathval(); - do { - name = padvance(&path, cmdp->cmdname); - stunalloc(name); - } while (--idx >= 0); - out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr)); } /* - * Clear out command entries. The argument specifies the first entry in - * PATH which has changed. + * Evaluate a pipeline. All the processes in the pipeline are children + * of the process creating the pipeline. (This differs from some versions + * of the shell, which make the last process in a pipeline the parent + * of all the rest.) */ static void -clearcmdentry(int firstchange) +evalpipe(union node *n, int flags) { - struct tblentry **tblp; - struct tblentry **pp; - struct tblentry *cmdp; + struct job *jp; + struct nodelist *lp; + int pipelen; + int prevfd; + int pip[2]; + TRACE(("evalpipe(0x%lx) called\n", (long)n)); + pipelen = 0; + for (lp = n->npipe.cmdlist; lp; lp = lp->next) + pipelen++; + flags |= EV_EXIT; INT_OFF; - for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) { - pp = tblp; - while ((cmdp = *pp) != NULL) { - if ((cmdp->cmdtype == CMDNORMAL && - cmdp->param.index >= firstchange) - || (cmdp->cmdtype == CMDBUILTIN && - builtinloc >= firstchange) - ) { - *pp = cmdp->next; - free(cmdp); - } else { - pp = &cmdp->next; + jp = makejob(n, pipelen); + prevfd = -1; + for (lp = n->npipe.cmdlist; lp; lp = lp->next) { + prehash(lp->n); + pip[1] = -1; + if (lp->next) { + if (pipe(pip) < 0) { + close(prevfd); + ash_msg_and_raise_error("Pipe call failed"); + } + } + if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { + INT_ON; + if (pip[1] >= 0) { + close(pip[0]); + } + if (prevfd > 0) { + dup2(prevfd, 0); + close(prevfd); + } + if (pip[1] > 1) { + dup2(pip[1], 1); + close(pip[1]); } + evaltreenr(lp->n, flags); + /* never returns */ } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + close(pip[1]); + } + if (n->npipe.backgnd == 0) { + exitstatus = waitforjob(jp); + TRACE(("evalpipe: job done exit status %d\n", exitstatus)); } INT_ON; } +static struct localvar *localvars; + /* - * Locate a command in the command hash table. If "add" is nonzero, - * add the command to the table if it is not already present. The - * variable "lastcmdentry" is set to point to the address of the link - * pointing to the entry, so that delete_cmd_entry can delete the - * entry. - * - * Interrupts must be off if called with add != 0. + * Called after a function returns. + * Interrupts must be off. */ -static struct tblentry **lastcmdentry; +static void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; -static struct tblentry * -cmdlookup(const char *name, int add) + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + TRACE(("poplocalvar %s", vp ? vp->text : "-")); + if (vp == NULL) { /* $- saved */ + memcpy(optlist, lvp->text, sizeof(optlist)); + free((char*)lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + unsetvar(vp->text); + } else { + if (vp->func) + (*vp->func)(strchrnul(lvp->text, '=') + 1); + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + free((char*)vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + } + free(lvp); + } +} + +static int +evalfun(struct funcnode *func, int argc, char **argv, int flags) { - unsigned int hashval; - const char *p; - struct tblentry *cmdp; - struct tblentry **pp; + volatile struct shparam saveparam; + struct localvar *volatile savelocalvars; + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int e; - p = name; - hashval = (unsigned char)*p << 4; - while (*p) - hashval += (unsigned char)*p++; - hashval &= 0x7FFF; - pp = &cmdtable[hashval % CMDTABLESIZE]; - for (cmdp = *pp; cmdp; cmdp = cmdp->next) { - if (strcmp(cmdp->cmdname, name) == 0) + saveparam = shellparam; + savelocalvars = localvars; + e = setjmp(jmploc.loc); + if (e) { + goto funcdone; + } + INT_OFF; + savehandler = exception_handler; + exception_handler = &jmploc; + localvars = NULL; + shellparam.malloc = 0; + func->count++; + funcnest++; + INT_ON; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; +#if ENABLE_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif + evaltree(&func->n, flags & EV_TESTED); +funcdone: + INT_OFF; + funcnest--; + freefunc(func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + exception_handler = savehandler; + INT_ON; + evalskip &= ~SKIPFUNC; + return e; +} + +#if ENABLE_ASH_CMDCMD +static char ** +parse_command_args(char **argv, const char **path) +{ + char *cp, c; + + for (;;) { + cp = *++argv; + if (!cp) + return 0; + if (*cp++ != '-') break; - pp = &cmdp->next; - } - if (add && cmdp == NULL) { - cmdp = *pp = ckmalloc(sizeof(struct tblentry) - ARB - + strlen(name) + 1); - cmdp->next = NULL; - cmdp->cmdtype = CMDUNKNOWN; - strcpy(cmdp->cmdname, name); + c = *cp++; + if (!c) + break; + if (c == '-' && !*cp) { + argv++; + break; + } + do { + switch (c) { + case 'p': + *path = defpath; + break; + default: + /* run 'typecmd' for other options */ + return 0; + } + c = *cp++; + } while (c); } - lastcmdentry = pp; - return cmdp; + return argv; } +#endif /* - * Delete the command entry returned on the last lookup. + * Make a variable a local variable. When a variable is made local, it's + * value and flags are saved in a localvar structure. The saved values + * will be restored when the shell function returns. We handle the name + * "-" as a special case. */ static void -delete_cmd_entry(void) +mklocal(char *name) { - struct tblentry *cmdp; + struct localvar *lvp; + struct var **vpp; + struct var *vp; INT_OFF; - cmdp = *lastcmdentry; - *lastcmdentry = cmdp->next; - if (cmdp->cmdtype == CMDFUNCTION) - freefunc(cmdp->param.func); - free(cmdp); + lvp = ckmalloc(sizeof(struct localvar)); + if (LONE_DASH(name)) { + char *p; + p = ckmalloc(sizeof(optlist)); + lvp->text = memcpy(p, optlist, sizeof(optlist)); + vp = NULL; + } else { + char *eq; + + vpp = hashvar(name); + vp = *findvar(vpp, name); + eq = strchr(name, '='); + if (vp == NULL) { + if (eq) + setvareq(name, VSTRFIXED); + else + setvar(name, NULL, VSTRFIXED); + vp = *vpp; /* the new variable */ + lvp->flags = VUNSET; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (eq) + setvareq(name, 0); + } + } + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; INT_ON; } /* - * Add a new command entry, replacing any existing command entry for - * the same name - except special builtins. + * The "local" command. */ -static void -addcmdentry(char *name, struct cmdentry *entry) +static int +localcmd(int argc, char **argv) { - struct tblentry *cmdp; + char *name; - cmdp = cmdlookup(name, 1); - if (cmdp->cmdtype == CMDFUNCTION) { - freefunc(cmdp->param.func); + argv = argptr; + while ((name = *argv++) != NULL) { + mklocal(name); } - cmdp->cmdtype = entry->cmdtype; - cmdp->param = entry->u; - cmdp->rehash = 0; + return 0; } -static int -hashcmd(int argc, char **argv) -{ - struct tblentry **pp; - struct tblentry *cmdp; - int c; - struct cmdentry entry; - char *name; +/* Forward declarations for builtintab[] */ +#if JOBS +static int fg_bgcmd(int, char **); +#endif +static int breakcmd(int, char **); +#if ENABLE_ASH_CMDCMD +static int commandcmd(int, char **); +#endif +static int dotcmd(int, char **); +static int evalcmd(int, char **); +#if ENABLE_ASH_BUILTIN_ECHO +static int echocmd(int, char **); +#endif +#if ENABLE_ASH_BUILTIN_TEST +static int testcmd(int, char **); +#endif +static int execcmd(int, char **); +static int exitcmd(int, char **); +static int exportcmd(int, char **); +static int falsecmd(int, char **); +#if ENABLE_ASH_GETOPTS +static int getoptscmd(int, char **); +#endif +static int hashcmd(int, char **); +#if !ENABLE_FEATURE_SH_EXTRA_QUIET +static int helpcmd(int argc, char **argv); +#endif +#if JOBS +static int jobscmd(int, char **); +#endif +#if ENABLE_ASH_MATH_SUPPORT +static int letcmd(int, char **); +#endif +static int pwdcmd(int, char **); +static int readcmd(int, char **); +static int returncmd(int, char **); +static int setcmd(int, char **); +static int shiftcmd(int, char **); +static int timescmd(int, char **); +static int trapcmd(int, char **); +static int truecmd(int, char **); +static int typecmd(int, char **); +static int umaskcmd(int, char **); +static int unsetcmd(int, char **); +static int waitcmd(int, char **); +static int ulimitcmd(int, char **); +#if JOBS +static int killcmd(int, char **); +#endif - while ((c = nextopt("r")) != '\0') { - clearcmdentry(0); - return 0; - } - if (*argptr == NULL) { - for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) { - for (cmdp = *pp; cmdp; cmdp = cmdp->next) { - if (cmdp->cmdtype == CMDNORMAL) - printentry(cmdp); - } - } - return 0; - } - c = 0; - while ((name = *argptr) != NULL) { - cmdp = cmdlookup(name, 0); - if (cmdp != NULL - && (cmdp->cmdtype == CMDNORMAL - || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))) - delete_cmd_entry(); - find_command(name, &entry, DO_ERR, pathval()); - if (entry.cmdtype == CMDUNKNOWN) - c = 1; - argptr++; - } - return c; +#define BUILTIN_NOSPEC "0" +#define BUILTIN_SPECIAL "1" +#define BUILTIN_REGULAR "2" +#define BUILTIN_SPEC_REG "3" +#define BUILTIN_ASSIGN "4" +#define BUILTIN_SPEC_ASSG "5" +#define BUILTIN_REG_ASSG "6" +#define BUILTIN_SPEC_REG_ASSG "7" + +/* make sure to keep these in proper order since it is searched via bsearch() */ +static const struct builtincmd builtintab[] = { + { BUILTIN_SPEC_REG ".", dotcmd }, + { BUILTIN_SPEC_REG ":", truecmd }, +#if ENABLE_ASH_BUILTIN_TEST + { BUILTIN_REGULAR "[", testcmd }, + { BUILTIN_REGULAR "[[", testcmd }, +#endif +#if ENABLE_ASH_ALIAS + { BUILTIN_REG_ASSG "alias", aliascmd }, +#endif +#if JOBS + { BUILTIN_REGULAR "bg", fg_bgcmd }, +#endif + { BUILTIN_SPEC_REG "break", breakcmd }, + { BUILTIN_REGULAR "cd", cdcmd }, + { BUILTIN_NOSPEC "chdir", cdcmd }, +#if ENABLE_ASH_CMDCMD + { BUILTIN_REGULAR "command", commandcmd }, +#endif + { BUILTIN_SPEC_REG "continue", breakcmd }, +#if ENABLE_ASH_BUILTIN_ECHO + { BUILTIN_REGULAR "echo", echocmd }, +#endif + { BUILTIN_SPEC_REG "eval", evalcmd }, + { BUILTIN_SPEC_REG "exec", execcmd }, + { BUILTIN_SPEC_REG "exit", exitcmd }, + { BUILTIN_SPEC_REG_ASSG "export", exportcmd }, + { BUILTIN_REGULAR "false", falsecmd }, +#if JOBS + { BUILTIN_REGULAR "fg", fg_bgcmd }, +#endif +#if ENABLE_ASH_GETOPTS + { BUILTIN_REGULAR "getopts", getoptscmd }, +#endif + { BUILTIN_NOSPEC "hash", hashcmd }, +#if !ENABLE_FEATURE_SH_EXTRA_QUIET + { BUILTIN_NOSPEC "help", helpcmd }, +#endif +#if JOBS + { BUILTIN_REGULAR "jobs", jobscmd }, + { BUILTIN_REGULAR "kill", killcmd }, +#endif +#if ENABLE_ASH_MATH_SUPPORT + { BUILTIN_NOSPEC "let", letcmd }, +#endif + { BUILTIN_ASSIGN "local", localcmd }, + { BUILTIN_NOSPEC "pwd", pwdcmd }, + { BUILTIN_REGULAR "read", readcmd }, + { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd }, + { BUILTIN_SPEC_REG "return", returncmd }, + { BUILTIN_SPEC_REG "set", setcmd }, + { BUILTIN_SPEC_REG "shift", shiftcmd }, + { BUILTIN_SPEC_REG "source", dotcmd }, +#if ENABLE_ASH_BUILTIN_TEST + { BUILTIN_REGULAR "test", testcmd }, +#endif + { BUILTIN_SPEC_REG "times", timescmd }, + { BUILTIN_SPEC_REG "trap", trapcmd }, + { BUILTIN_REGULAR "true", truecmd }, + { BUILTIN_NOSPEC "type", typecmd }, + { BUILTIN_NOSPEC "ulimit", ulimitcmd }, + { BUILTIN_REGULAR "umask", umaskcmd }, +#if ENABLE_ASH_ALIAS + { BUILTIN_REGULAR "unalias", unaliascmd }, +#endif + { BUILTIN_SPEC_REG "unset", unsetcmd }, + { BUILTIN_REGULAR "wait", waitcmd }, +}; + +#define NUMBUILTINS (sizeof(builtintab) / sizeof(builtintab[0])) + +#define COMMANDCMD (builtintab + 5 + \ + 2 * ENABLE_ASH_BUILTIN_TEST + \ + ENABLE_ASH_ALIAS + \ + ENABLE_ASH_JOB_CONTROL) +#define EXECCMD (builtintab + 7 + \ + 2 * ENABLE_ASH_BUILTIN_TEST + \ + ENABLE_ASH_ALIAS + \ + ENABLE_ASH_JOB_CONTROL + \ + ENABLE_ASH_CMDCMD + \ + ENABLE_ASH_BUILTIN_ECHO) + +/* + * Search the table of builtin commands. + */ +static struct builtincmd * +find_builtin(const char *name) +{ + struct builtincmd *bp; + + bp = bsearch( + name, builtintab, NUMBUILTINS, sizeof(builtintab[0]), + pstrcmp + ); + return bp; } /* @@ -6394,283 +6533,377 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) } /* - * Search the table of builtin commands. + * Execute a simple command. */ -static struct builtincmd * -find_builtin(const char *name) +static int back_exitstatus; /* exit status of backquoted command */ +static int +isassignment(const char *p) { - struct builtincmd *bp; - - bp = bsearch( - name, builtintab, NUMBUILTINS, sizeof(builtintab[0]), - pstrcmp - ); - return bp; + const char *q = endofname(p); + if (p == q) + return 0; + return *q == '='; +} +static int +bltincmd(int argc, char **argv) +{ + /* Preserve exitstatus of a previous possible redirection + * as POSIX mandates */ + return back_exitstatus; } - -/* - * Called when a cd is done. Marks all commands so the next time they - * are executed they will be rehashed. - */ static void -hashcd(void) +evalcommand(union node *cmd, int flags) { - struct tblentry **pp; - struct tblentry *cmdp; + static const struct builtincmd bltin = { + "\0\0", bltincmd + }; + struct stackmark smark; + union node *argp; + struct arglist arglist; + struct arglist varlist; + char **argv; + int argc; + const struct strlist *sp; + struct cmdentry cmdentry; + struct job *jp; + char *lastarg; + const char *path; + int spclbltin; + int cmd_is_exec; + int status; + char **nargv; + struct builtincmd *bcmd; + int pseudovarflag = 0; + + /* First expand the arguments. */ + TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); + setstackmark(&smark); + back_exitstatus = 0; + + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.cmd = &bltin; + varlist.lastp = &varlist.list; + *varlist.lastp = NULL; + arglist.lastp = &arglist.list; + *arglist.lastp = NULL; + + argc = 0; + if (cmd->ncmd.args) { + bcmd = find_builtin(cmd->ncmd.args->narg.text); + pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd); + } + + for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) { + struct strlist **spp; + + spp = arglist.lastp; + if (pseudovarflag && isassignment(argp->narg.text)) + expandarg(argp, &arglist, EXP_VARTILDE); + else + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + + for (sp = *spp; sp; sp = sp->next) + argc++; + } + + argv = nargv = stalloc(sizeof(char *) * (argc + 1)); + for (sp = arglist.list; sp; sp = sp->next) { + TRACE(("evalcommand arg: %s\n", sp->text)); + *nargv++ = sp->text; + } + *nargv = NULL; + + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = nargv[-1]; + + preverrout_fd = 2; + expredir(cmd->ncmd.redirect); + status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2); + + path = vpath.text; + for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) { + struct strlist **spp; + char *p; + + spp = varlist.lastp; + expandarg(argp, &varlist, EXP_VARTILDE); + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + p = (*spp)->text; + if (varequal(p, path)) + path = p; + } + + /* Print the command if xflag is set. */ + if (xflag) { + int n; + const char *p = " %s"; + + p++; + dprintf(preverrout_fd, p, expandstr(ps4val())); + + sp = varlist.list; + for (n = 0; n < 2; n++) { + while (sp) { + dprintf(preverrout_fd, p, sp->text); + sp = sp->next; + if (*p == '%') { + p--; + } + } + sp = arglist.list; + } + full_write(preverrout_fd, "\n", 1); + } + + cmd_is_exec = 0; + spclbltin = -1; + + /* Now locate the command. */ + if (argc) { + const char *oldpath; + int cmd_flag = DO_ERR; + + path += 5; + oldpath = path; + for (;;) { + find_command(argv[0], &cmdentry, cmd_flag, path); + if (cmdentry.cmdtype == CMDUNKNOWN) { + status = 127; + flush_stderr(); + goto bail; + } + + /* implement bltin and command here */ + if (cmdentry.cmdtype != CMDBUILTIN) + break; + if (spclbltin < 0) + spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd); + if (cmdentry.u.cmd == EXECCMD) + cmd_is_exec++; +#if ENABLE_ASH_CMDCMD + if (cmdentry.u.cmd == COMMANDCMD) { + path = oldpath; + nargv = parse_command_args(argv, &path); + if (!nargv) + break; + argc -= nargv - argv; + argv = nargv; + cmd_flag |= DO_NOFUNC; + } else +#endif + break; + } + } + + if (status) { + /* We have a redirection error. */ + if (spclbltin > 0) + raise_exception(EXERROR); + bail: + exitstatus = status; + goto out; + } + + /* Execute the command. */ + switch (cmdentry.cmdtype) { + default: + /* Fork off a child process if necessary. */ + if (!(flags & EV_EXIT) || trap[0]) { + INT_OFF; + jp = makejob(cmd, 1); + if (forkshell(jp, cmd, FORK_FG) != 0) { + exitstatus = waitforjob(jp); + INT_ON; + break; + } + FORCE_INT_ON; + } + listsetvar(varlist.list, VEXPORT|VSTACK); + shellexec(argv, path, cmdentry.u.index); + /* NOTREACHED */ + + case CMDBUILTIN: + cmdenviron = varlist.list; + if (cmdenviron) { + struct strlist *list = cmdenviron; + int i = VNOSET; + if (spclbltin > 0 || argc == 0) { + i = 0; + if (cmd_is_exec && argc > 1) + i = VEXPORT; + } + listsetvar(list, i); + } + if (evalbltin(cmdentry.u.cmd, argc, argv)) { + int exit_status; + int i, j; + + i = exception; + if (i == EXEXIT) + goto raise; + + exit_status = 2; + j = 0; + if (i == EXINT) + j = SIGINT; + if (i == EXSIG) + j = pendingsigs; + if (j) + exit_status = j + 128; + exitstatus = exit_status; - for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) { - for (cmdp = *pp; cmdp; cmdp = cmdp->next) { - if (cmdp->cmdtype == CMDNORMAL || ( - cmdp->cmdtype == CMDBUILTIN && - !(IS_BUILTIN_REGULAR(cmdp->param.cmd)) && - builtinloc > 0 - )) - cmdp->rehash = 1; + if (i == EXINT || spclbltin > 0) { + raise: + longjmp(exception_handler->loc, 1); + } + FORCE_INT_ON; } + break; + + case CMDFUNCTION: + listsetvar(varlist.list, 0); + if (evalfun(cmdentry.u.func, argc, argv, flags)) + goto raise; + break; } + + out: + popredir(cmd_is_exec); + if (lastarg) + /* dsl: I think this is intended to be used to support + * '_' in 'vi' command mode during line editing... + * However I implemented that within libedit itself. + */ + setvar("_", lastarg, 0); + popstackmark(&smark); } -/* - * Fix command hash table when PATH changed. - * Called before PATH is changed. The argument is the new value of PATH; - * pathval() still returns the old value at this point. - * Called with interrupts off. - */ -static void -changepath(const char *newval) +static int +evalbltin(const struct builtincmd *cmd, int argc, char **argv) { - const char *old, *new; - int idx; - int firstchange; - int idx_bltin; + char *volatile savecmdname; + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int i; - old = pathval(); - new = newval; - firstchange = 9999; /* assume no change */ - idx = 0; - idx_bltin = -1; - for (;;) { - if (*old != *new) { - firstchange = idx; - if ((*old == '\0' && *new == ':') - || (*old == ':' && *new == '\0')) - firstchange++; - old = new; /* ignore subsequent differences */ - } - if (*new == '\0') - break; - if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin")) - idx_bltin = idx; - if (*new == ':') { - idx++; - } - new++, old++; - } - if (builtinloc < 0 && idx_bltin >= 0) - builtinloc = idx_bltin; /* zap builtins */ - if (builtinloc >= 0 && idx_bltin < 0) - firstchange = 0; - clearcmdentry(firstchange); - builtinloc = idx_bltin; -} + savecmdname = commandname; + i = setjmp(jmploc.loc); + if (i) + goto cmddone; + savehandler = exception_handler; + exception_handler = &jmploc; + commandname = argv[0]; + argptr = argv + 1; + optptr = NULL; /* initialize nextopt */ + exitstatus = (*cmd->builtin)(argc, argv); + flush_stdout_stderr(); + cmddone: + exitstatus |= ferror(stdout); + clearerr(stdout); + commandname = savecmdname; + exsig = 0; + exception_handler = savehandler; + return i; +} -/* - * Make a copy of a parse tree. - */ -static struct funcnode * -copyfunc(union node *n) +static int +goodname(const char *p) { - struct funcnode *f; - size_t blocksize; - - funcblocksize = offsetof(struct funcnode, n); - funcstringsize = 0; - calcsize(n); - blocksize = funcblocksize; - f = ckmalloc(blocksize + funcstringsize); - funcblock = (char *) f + offsetof(struct funcnode, n); - funcstring = (char *) f + blocksize; - copynode(n); - f->count = 0; - return f; + return !*endofname(p); } + /* - * Define a shell function. + * Search for a command. This is called before we fork so that the + * location of the command will be available in the parent as well as + * the child. The check for "goodname" is an overly conservative + * check that the name will not be subject to expansion. */ static void -defun(char *name, union node *func) +prehash(union node *n) { struct cmdentry entry; - INT_OFF; - entry.cmdtype = CMDFUNCTION; - entry.u.func = copyfunc(func); - addcmdentry(name, &entry); - INT_ON; + if (n->type == NCMD && n->ncmd.args && goodname(n->ncmd.args->narg.text)) + find_command(n->ncmd.args->narg.text, &entry, 0, pathval()); } + /* - * Delete a function if it exists. + * Builtin commands. Builtin commands whose functions are closely + * tied to evaluation are implemented here. */ -static void -unsetfunc(const char *name) -{ - struct tblentry *cmdp; - - cmdp = cmdlookup(name, 0); - if (cmdp!= NULL && cmdp->cmdtype == CMDFUNCTION) - delete_cmd_entry(); -} - /* - * Locate and print what a word is... + * Handle break and continue commands. Break, continue, and return are + * all handled by setting the evalskip flag. The evaluation routines + * above all check this flag, and if it is set they start skipping + * commands rather than executing them. The variable skipcount is + * the number of loops to break/continue, or the number of function + * levels to return. (The latter is always 1.) It should probably + * be an error to break out of more loops than exist, but it isn't + * in the standard shell so we don't make it one here. */ -#if ENABLE_ASH_CMDCMD -static int -describe_command(char *command, int describe_command_verbose) -#else -#define describe_command_verbose 1 + static int -describe_command(char *command) -#endif +breakcmd(int argc, char **argv) { - struct cmdentry entry; - struct tblentry *cmdp; -#if ENABLE_ASH_ALIAS - const struct alias *ap; -#endif - const char *path = pathval(); - - if (describe_command_verbose) { - out1str(command); - } - - /* First look at the keywords */ - if (findkwd(command)) { - out1str(describe_command_verbose ? " is a shell keyword" : command); - goto out; - } - -#if ENABLE_ASH_ALIAS - /* Then look at the aliases */ - ap = lookupalias(command, 0); - if (ap != NULL) { - if (describe_command_verbose) { - out1fmt(" is an alias for %s", ap->val); - } else { - out1str("alias "); - printalias(ap); - return 0; - } - goto out; - } -#endif - /* Then check if it is a tracked alias */ - cmdp = cmdlookup(command, 0); - if (cmdp != NULL) { - entry.cmdtype = cmdp->cmdtype; - entry.u = cmdp->param; - } else { - /* Finally use brute force */ - find_command(command, &entry, DO_ABS, path); - } - - switch (entry.cmdtype) { - case CMDNORMAL: { - int j = entry.u.index; - char *p; - if (j == -1) { - p = command; - } else { - do { - p = padvance(&path, command); - stunalloc(p); - } while (--j >= 0); - } - if (describe_command_verbose) { - out1fmt(" is%s %s", - (cmdp ? " a tracked alias for" : nullstr), p - ); - } else { - out1str(p); - } - break; - } - - case CMDFUNCTION: - if (describe_command_verbose) { - out1str(" is a shell function"); - } else { - out1str(command); - } - break; - - case CMDBUILTIN: - if (describe_command_verbose) { - out1fmt(" is a %sshell builtin", - IS_BUILTIN_SPECIAL(entry.u.cmd) ? - "special " : nullstr - ); - } else { - out1str(command); - } - break; + int n = argc > 1 ? number(argv[1]) : 1; - default: - if (describe_command_verbose) { - out1str(": not found\n"); - } - return 127; + if (n <= 0) + ash_msg_and_raise_error(illnum, argv[1]); + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; + skipcount = n; } - out: - outstr("\n", stdout); return 0; } +/* + * The return command. + */ static int -typecmd(int argc, char **argv) +returncmd(int argc, char **argv) { - int i; - int err = 0; - - for (i = 1; i < argc; i++) { -#if ENABLE_ASH_CMDCMD - err |= describe_command(argv[i], 1); -#else - err |= describe_command(argv[i]); -#endif - } - return err; + /* + * If called outside a function, do what ksh does; + * skip the rest of the file. + */ + evalskip = funcnest ? SKIPFUNC : SKIPFILE; + return argv[1] ? number(argv[1]) : exitstatus; } -#if ENABLE_ASH_CMDCMD static int -commandcmd(int argc, char **argv) +falsecmd(int argc, char **argv) { - int c; - enum { - VERIFY_BRIEF = 1, - VERIFY_VERBOSE = 2, - } verify = 0; + return 1; +} - while ((c = nextopt("pvV")) != '\0') - if (c == 'V') - verify |= VERIFY_VERBOSE; - else if (c == 'v') - verify |= VERIFY_BRIEF; -#if DEBUG - else if (c != 'p') - abort(); -#endif - if (verify) - return describe_command(*argptr, verify - VERIFY_BRIEF); +static int +truecmd(int argc, char **argv) +{ + return 0; +} +static int +execcmd(int argc, char **argv) +{ + if (argc > 1) { + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + shellexec(argv + 1, pathval(), 0); + } return 0; } -#endif /* ============ input.c @@ -8437,314 +8670,109 @@ stoppedjobs(void) job_warning = 2; retval++; } - out: - return retval; -} - - -/* ============ mail.c - * - * Routines to check for mail. - */ -#if ENABLE_ASH_MAIL - -#define MAXMBOXES 10 - -/* times of mailboxes */ -static time_t mailtime[MAXMBOXES]; -/* Set if MAIL or MAILPATH is changed. */ -static int mail_var_path_changed; - -/* - * Print appropriate message(s) if mail has arrived. - * If mail_var_path_changed is set, - * then the value of MAIL has mail_var_path_changed, - * so we just update the values. - */ -static void -chkmail(void) -{ - const char *mpath; - char *p; - char *q; - time_t *mtp; - struct stackmark smark; - struct stat statb; - - setstackmark(&smark); - mpath = mpathset() ? mpathval() : mailval(); - for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) { - p = padvance(&mpath, nullstr); - if (p == NULL) - break; - if (*p == '\0') - continue; - for (q = p; *q; q++); -#if DEBUG - if (q[-1] != '/') - abort(); -#endif - q[-1] = '\0'; /* delete trailing '/' */ - if (stat(p, &statb) < 0) { - *mtp = 0; - continue; - } - if (!mail_var_path_changed && statb.st_mtime != *mtp) { - fprintf( - stderr, snlfmt, - pathopt ? pathopt : "you have mail" - ); - } - *mtp = statb.st_mtime; - } - mail_var_path_changed = 0; - popstackmark(&smark); -} - -static void -changemail(const char *val) -{ - mail_var_path_changed++; -} -#endif /* ASH_MAIL */ - - -/* ============ ??? */ - -/* - * Take commands from a file. To be compatible we should do a path - * search for the file, which is necessary to find sub-commands. - */ -static char * -find_dot_file(char *name) -{ - char *fullname; - const char *path = pathval(); - struct stat statb; - - /* don't try this for absolute or relative paths */ - if (strchr(name, '/')) - return name; - - while ((fullname = padvance(&path, name)) != NULL) { - if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { - /* - * Don't bother freeing here, since it will - * be freed by the caller. - */ - return fullname; - } - stunalloc(fullname); - } - - /* not found in the PATH */ - ash_msg_and_raise_error("%s: not found", name); - /* NOTREACHED */ -} - -static void -calcsize(union node *n) -{ - if (n == NULL) - return; - funcblocksize += nodesize[n->type]; - switch (n->type) { - case NCMD: - calcsize(n->ncmd.redirect); - calcsize(n->ncmd.args); - calcsize(n->ncmd.assign); - break; - case NPIPE: - sizenodelist(n->npipe.cmdlist); - break; - case NREDIR: - case NBACKGND: - case NSUBSHELL: - calcsize(n->nredir.redirect); - calcsize(n->nredir.n); - break; - case NAND: - case NOR: - case NSEMI: - case NWHILE: - case NUNTIL: - calcsize(n->nbinary.ch2); - calcsize(n->nbinary.ch1); - break; - case NIF: - calcsize(n->nif.elsepart); - calcsize(n->nif.ifpart); - calcsize(n->nif.test); - break; - case NFOR: - funcstringsize += strlen(n->nfor.var) + 1; - calcsize(n->nfor.body); - calcsize(n->nfor.args); - break; - case NCASE: - calcsize(n->ncase.cases); - calcsize(n->ncase.expr); - break; - case NCLIST: - calcsize(n->nclist.body); - calcsize(n->nclist.pattern); - calcsize(n->nclist.next); - break; - case NDEFUN: - case NARG: - sizenodelist(n->narg.backquote); - funcstringsize += strlen(n->narg.text) + 1; - calcsize(n->narg.next); - break; - case NTO: - case NCLOBBER: - case NFROM: - case NFROMTO: - case NAPPEND: - calcsize(n->nfile.fname); - calcsize(n->nfile.next); - break; - case NTOFD: - case NFROMFD: - calcsize(n->ndup.vname); - calcsize(n->ndup.next); - break; - case NHERE: - case NXHERE: - calcsize(n->nhere.doc); - calcsize(n->nhere.next); - break; - case NNOT: - calcsize(n->nnot.com); - break; - }; -} - -static void -sizenodelist(struct nodelist *lp) -{ - while (lp) { - funcblocksize += SHELL_ALIGN(sizeof(struct nodelist)); - calcsize(lp->n); - lp = lp->next; - } + out: + return retval; } -static union node * -copynode(union node *n) -{ - union node *new; - if (n == NULL) - return NULL; - new = funcblock; - funcblock = (char *) funcblock + nodesize[n->type]; +/* ============ mail.c + * + * Routines to check for mail. + */ +#if ENABLE_ASH_MAIL - switch (n->type) { - case NCMD: - new->ncmd.redirect = copynode(n->ncmd.redirect); - new->ncmd.args = copynode(n->ncmd.args); - new->ncmd.assign = copynode(n->ncmd.assign); - break; - case NPIPE: - new->npipe.cmdlist = copynodelist(n->npipe.cmdlist); - new->npipe.backgnd = n->npipe.backgnd; - break; - case NREDIR: - case NBACKGND: - case NSUBSHELL: - new->nredir.redirect = copynode(n->nredir.redirect); - new->nredir.n = copynode(n->nredir.n); - break; - case NAND: - case NOR: - case NSEMI: - case NWHILE: - case NUNTIL: - new->nbinary.ch2 = copynode(n->nbinary.ch2); - new->nbinary.ch1 = copynode(n->nbinary.ch1); - break; - case NIF: - new->nif.elsepart = copynode(n->nif.elsepart); - new->nif.ifpart = copynode(n->nif.ifpart); - new->nif.test = copynode(n->nif.test); - break; - case NFOR: - new->nfor.var = nodeckstrdup(n->nfor.var); - new->nfor.body = copynode(n->nfor.body); - new->nfor.args = copynode(n->nfor.args); - break; - case NCASE: - new->ncase.cases = copynode(n->ncase.cases); - new->ncase.expr = copynode(n->ncase.expr); - break; - case NCLIST: - new->nclist.body = copynode(n->nclist.body); - new->nclist.pattern = copynode(n->nclist.pattern); - new->nclist.next = copynode(n->nclist.next); - break; - case NDEFUN: - case NARG: - new->narg.backquote = copynodelist(n->narg.backquote); - new->narg.text = nodeckstrdup(n->narg.text); - new->narg.next = copynode(n->narg.next); - break; - case NTO: - case NCLOBBER: - case NFROM: - case NFROMTO: - case NAPPEND: - new->nfile.fname = copynode(n->nfile.fname); - new->nfile.fd = n->nfile.fd; - new->nfile.next = copynode(n->nfile.next); - break; - case NTOFD: - case NFROMFD: - new->ndup.vname = copynode(n->ndup.vname); - new->ndup.dupfd = n->ndup.dupfd; - new->ndup.fd = n->ndup.fd; - new->ndup.next = copynode(n->ndup.next); - break; - case NHERE: - case NXHERE: - new->nhere.doc = copynode(n->nhere.doc); - new->nhere.fd = n->nhere.fd; - new->nhere.next = copynode(n->nhere.next); - break; - case NNOT: - new->nnot.com = copynode(n->nnot.com); - break; - }; - new->type = n->type; - return new; -} +#define MAXMBOXES 10 -static struct nodelist * -copynodelist(struct nodelist *lp) +/* times of mailboxes */ +static time_t mailtime[MAXMBOXES]; +/* Set if MAIL or MAILPATH is changed. */ +static int mail_var_path_changed; + +/* + * Print appropriate message(s) if mail has arrived. + * If mail_var_path_changed is set, + * then the value of MAIL has mail_var_path_changed, + * so we just update the values. + */ +static void +chkmail(void) { - struct nodelist *start; - struct nodelist **lpp; + const char *mpath; + char *p; + char *q; + time_t *mtp; + struct stackmark smark; + struct stat statb; - lpp = &start; - while (lp) { - *lpp = funcblock; - funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist)); - (*lpp)->n = copynode(lp->n); - lp = lp->next; - lpp = &(*lpp)->next; + setstackmark(&smark); + mpath = mpathset() ? mpathval() : mailval(); + for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) { + p = padvance(&mpath, nullstr); + if (p == NULL) + break; + if (*p == '\0') + continue; + for (q = p; *q; q++); +#if DEBUG + if (q[-1] != '/') + abort(); +#endif + q[-1] = '\0'; /* delete trailing '/' */ + if (stat(p, &statb) < 0) { + *mtp = 0; + continue; + } + if (!mail_var_path_changed && statb.st_mtime != *mtp) { + fprintf( + stderr, snlfmt, + pathopt ? pathopt : "you have mail" + ); + } + *mtp = statb.st_mtime; } - *lpp = NULL; - return start; + mail_var_path_changed = 0; + popstackmark(&smark); +} + +static void +changemail(const char *val) +{ + mail_var_path_changed++; } +#endif /* ASH_MAIL */ + +/* ============ ??? */ + +/* + * Take commands from a file. To be compatible we should do a path + * search for the file, which is necessary to find sub-commands. + */ static char * -nodeckstrdup(char *s) +find_dot_file(char *name) { - char *rtn = funcstring; + char *fullname; + const char *path = pathval(); + struct stat statb; - strcpy(funcstring, s); - funcstring += strlen(s) + 1; - return rtn; + /* don't try this for absolute or relative paths */ + if (strchr(name, '/')) + return name; + + while ((fullname = padvance(&path, name)) != NULL) { + if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { + /* + * Don't bother freeing here, since it will + * be freed by the caller. + */ + return fullname; + } + stunalloc(fullname); + } + + /* not found in the PATH */ + ash_msg_and_raise_error("%s: not found", name); + /* NOTREACHED */ } /* @@ -8819,7 +8847,6 @@ minus_o(char *name, int val) optlist[i] ? "on" : "off"); } - static void setoption(int flag, int val) { @@ -11450,65 +11477,16 @@ exportcmd(int argc, char **argv) } /* - * Make a variable a local variable. When a variable is made local, it's - * value and flags are saved in a localvar structure. The saved values - * will be restored when the shell function returns. We handle the name - * "-" as a special case. + * Delete a function if it exists. */ static void -mklocal(char *name) -{ - struct localvar *lvp; - struct var **vpp; - struct var *vp; - - INT_OFF; - lvp = ckmalloc(sizeof(struct localvar)); - if (LONE_DASH(name)) { - char *p; - p = ckmalloc(sizeof(optlist)); - lvp->text = memcpy(p, optlist, sizeof(optlist)); - vp = NULL; - } else { - char *eq; - - vpp = hashvar(name); - vp = *findvar(vpp, name); - eq = strchr(name, '='); - if (vp == NULL) { - if (eq) - setvareq(name, VSTRFIXED); - else - setvar(name, NULL, VSTRFIXED); - vp = *vpp; /* the new variable */ - lvp->flags = VUNSET; - } else { - lvp->text = vp->text; - lvp->flags = vp->flags; - vp->flags |= VSTRFIXED|VTEXTFIXED; - if (eq) - setvareq(name, 0); - } - } - lvp->vp = vp; - lvp->next = localvars; - localvars = lvp; - INT_ON; -} - -/* - * The "local" command. - */ -static int -localcmd(int argc, char **argv) +unsetfunc(const char *name) { - char *name; + struct tblentry *cmdp; - argv = argptr; - while ((name = *argv++) != NULL) { - mklocal(name); - } - return 0; + cmdp = cmdlookup(name, 0); + if (cmdp!= NULL && cmdp->cmdtype == CMDFUNCTION) + delete_cmd_entry(); } /* -- cgit v1.2.3