/*- * Copyright (c) 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1997-2005 * Herbert Xu . All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include /* * Evaluate a command. */ #include "shell.h" #include "nodes.h" #include "syntax.h" #include "expand.h" #include "parser.h" #include "jobs.h" #include "eval.h" #include "builtins.h" #include "options.h" #include "exec.h" #include "redir.h" #include "input.h" #include "output.h" #include "trap.h" #include "var.h" #include "memalloc.h" #include "error.h" #include "show.h" #include "mystring.h" #ifndef SMALL #include "myhistedit.h" #endif int evalskip; /* set if we are skipping commands */ STATIC int skipcount; /* number of levels to skip */ MKINIT int loopnest; /* current loop nesting level */ static int funcline; /* starting line number of current function, or 0 if not in a function */ char *commandname; int exitstatus; /* exit status of last command */ int back_exitstatus; /* exit status of backquoted command */ int savestatus = -1; /* exit status of last command outside traps */ #if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) STATIC #endif void evaltreenr(union node *, int) __attribute__ ((__noreturn__)); STATIC int evalloop(union node *, int); STATIC int evalfor(union node *, int); STATIC int evalcase(union node *, int); STATIC int evalsubshell(union node *, int); STATIC void expredir(union node *); STATIC int evalpipe(union node *, int); #ifdef notyet STATIC int evalcommand(union node *, int, struct backcmd *); #else STATIC int evalcommand(union node *, int); #endif STATIC int evalbltin(const struct builtincmd *, int, char **, int); STATIC int evalfun(struct funcnode *, int, char **, int); STATIC void prehash(union node *); STATIC int eprintlist(struct output *, struct strlist *, int); STATIC int bltincmd(int, char **); STATIC const struct builtincmd bltin = { name: nullstr, builtin: bltincmd }; /* * Called to reset things after an exception. */ #ifdef mkinit INCLUDE "eval.h" RESET { evalskip = 0; loopnest = 0; if (savestatus >= 0) { exitstatus = savestatus; savestatus = -1; } } #endif /* * The eval commmand. */ static int evalcmd(int argc, char **argv, int flags) { char *p; char *concat; char **ap; if (argc > 1) { p = argv[1]; if (argc > 2) { STARTSTACKSTR(concat); ap = argv + 2; for (;;) { concat = stputs(p, concat); if ((p = *ap++) == NULL) break; STPUTC(' ', concat); } STPUTC('\0', concat); p = grabstackstr(concat); } return evalstring(p, flags & EV_TESTED); } return 0; } /* * Execute a command or commands contained in a string. */ int evalstring(char *s, int flags) { union node *n; struct stackmark smark; int status; s = sstrdup(s); setinputstring(s); setstackmark(&smark); status = 0; for (; (n = parsecmd(0)) != NEOF; popstackmark(&smark)) { int i; i = evaltree(n, flags & ~(parser_eof() ? 0 : EV_EXIT)); if (n) status = i; if (evalskip) break; } popstackmark(&smark); popfile(); stunalloc(s); return status; } /* * Evaluate a parse tree. The value is left in the global variable * exitstatus. */ int evaltree(union node *n, int flags) { int checkexit = 0; int (*evalfn)(union node *, int); unsigned isor; int status = 0; if (n == NULL) { TRACE(("evaltree(NULL) called\n")); goto out; } dotrap(); #ifndef SMALL displayhist = 1; /* show history substitutions done with fc */ #endif TRACE(("pid %d, evaltree(%p: %d, %d) called\n", getpid(), n, n->type, flags)); switch (n->type) { default: #ifdef DEBUG out1fmt("Node type = %d\n", n->type); #ifndef USE_GLIBC_STDIO flushout(out1); #endif break; #endif case NNOT: status = !evaltree(n->nnot.com, EV_TESTED); goto setstatus; case NREDIR: errlinno = lineno = n->nredir.linno; if (funcline) lineno -= funcline - 1; expredir(n->nredir.redirect); pushredir(n->nredir.redirect); status = redirectsafe(n->nredir.redirect, REDIR_PUSH) ?: evaltree(n->nredir.n, flags & EV_TESTED); if (n->nredir.redirect) popredir(0); goto setstatus; case NCMD: #ifdef notyet if (eflag && !(flags & EV_TESTED)) checkexit = ~0; status = evalcommand(n, flags, (struct backcmd *)NULL); goto setstatus; #else evalfn = evalcommand; checkexit: if (eflag && !(flags & EV_TESTED)) checkexit = ~0; goto calleval; #endif case NFOR: evalfn = evalfor; goto calleval; case NWHILE: case NUNTIL: evalfn = evalloop; goto calleval; case NSUBSHELL: case NBACKGND: evalfn = evalsubshell; goto checkexit; 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; status = evaltree(n->nbinary.ch1, (flags | ((isor >> 1) - 1)) & EV_TESTED); if (!status == isor || evalskip) break; n = n->nbinary.ch2; evaln: evalfn = evaltree; calleval: status = evalfn(n, flags); goto setstatus; case NIF: status = evaltree(n->nif.test, EV_TESTED); if (evalskip) break; if (!status) { n = n->nif.ifpart; goto evaln; } else if (n->nif.elsepart) { n = n->nif.elsepart; goto evaln; } status = 0; goto setstatus; case NDEFUN: defun(n); setstatus: exitstatus = status; break; } out: if (checkexit & status) goto exexit; dotrap(); if (flags & EV_EXIT) { exexit: exraise(EXEXIT); } return exitstatus; } #if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) STATIC #endif void evaltreenr(union node *n, int flags) #ifdef HAVE_ATTRIBUTE_ALIAS __attribute__ ((alias("evaltree"))); #else { evaltree(n, flags); abort(); } #endif static int skiploop(void) { int skip = evalskip; switch (skip) { case 0: break; case SKIPBREAK: case SKIPCONT: if (likely(--skipcount <= 0)) { evalskip = 0; break; } skip = SKIPBREAK; break; } return skip; } STATIC int evalloop(union node *n, int flags) { int skip; int status; loopnest++; status = 0; flags &= EV_TESTED; do { int i; i = evaltree(n->nbinary.ch1, EV_TESTED); skip = skiploop(); if (skip == SKIPFUNC) status = i; if (skip) continue; if (n->type != NWHILE) i = !i; if (i != 0) break; status = evaltree(n->nbinary.ch2, flags); skip = skiploop(); } while (!(skip & ~SKIPCONT)); loopnest--; return status; } STATIC int evalfor(union node *n, int flags) { struct arglist arglist; union node *argp; struct strlist *sp; struct stackmark smark; int status; errlinno = lineno = n->nfor.linno; if (funcline) lineno -= funcline - 1; setstackmark(&smark); arglist.lastp = &arglist.list; for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); } *arglist.lastp = NULL; status = 0; loopnest++; flags &= EV_TESTED; for (sp = arglist.list ; sp ; sp = sp->next) { setvar(n->nfor.var, sp->text, 0); status = evaltree(n->nfor.body, flags); if (skiploop() & ~SKIPCONT) break; } loopnest--; popstackmark(&smark); return status; } STATIC int evalcase(union node *n, int flags) { union node *cp; union node *patp; struct arglist arglist; struct stackmark smark; int status = 0; errlinno = lineno = n->ncase.linno; if (funcline) lineno -= funcline - 1; setstackmark(&smark); arglist.lastp = &arglist.list; expandarg(n->ncase.expr, &arglist, EXP_TILDE); 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)) { /* Ensure body is non-empty as otherwise * EV_EXIT may prevent us from setting the * exit status. */ if (evalskip == 0 && cp->nclist.body) { status = evaltree(cp->nclist.body, flags); } goto out; } } } out: popstackmark(&smark); return status; } /* * Kick off a subshell to evaluate a tree. */ STATIC int evalsubshell(union node *n, int flags) { struct job *jp; int backgnd = (n->type == NBACKGND); int status; errlinno = lineno = n->nredir.linno; if (funcline) lineno -= funcline - 1; expredir(n->nredir.redirect); if (!backgnd && flags & EV_EXIT && !have_traps()) goto nofork; INTOFF; jp = makejob(n, 1); if (forkshell(jp, n, backgnd) == 0) { INTON; 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); INTON; return status; } /* * Compute the names of the files in a redirection list. */ STATIC void expredir(union node *n) { union node *redir; for (redir = n ; redir ; redir = redir->nfile.next) { struct arglist 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); fixredir(redir, fn.list->text, 1); } break; } } } /* * 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 int evalpipe(union node *n, int flags) { struct job *jp; struct nodelist *lp; int pipelen; int prevfd; int pip[2]; int status = 0; TRACE(("evalpipe(0x%lx) called\n", (long)n)); pipelen = 0; for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) pipelen++; flags |= EV_EXIT; INTOFF; 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); sh_error("Pipe call failed"); } } if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { INTON; 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) { status = waitforjob(jp); TRACE(("evalpipe: job done exit status %d\n", status)); } INTON; return status; } /* * Execute a command inside back quotes. If it's a builtin command, we * want to save its output in a block obtained from malloc. Otherwise * we fork off a subprocess and get the output of the command via a pipe. * Should be called with interrupts off. */ void evalbackcmd(union node *n, struct backcmd *result) { int pip[2]; struct job *jp; result->fd = -1; result->buf = NULL; result->nleft = 0; result->jp = NULL; if (n == NULL) { goto out; } if (pipe(pip) < 0) sh_error("Pipe call failed"); jp = makejob(n, 1); if (forkshell(jp, n, FORK_NOJOB) == 0) { FORCEINTON; close(pip[0]); if (pip[1] != 1) { dup2(pip[1], 1); close(pip[1]); } ifsfree(); evaltreenr(n, EV_EXIT); /* NOTREACHED */ } close(pip[1]); result->fd = pip[0]; result->jp = jp; out: TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", result->fd, result->buf, result->nleft, result->jp)); } static char ** parse_command_args(char **argv, const char **path) { char *cp, c; for (;;) { cp = *++argv; if (!cp) return 0; if (*cp++ != '-') break; if (!(c = *cp++)) break; if (c == '-' && !*cp) { if (!*++argv) return 0; break; } do { switch (c) { case 'p': *path = defpath; break; default: /* run 'typecmd' for other options */ return 0; } } while ((c = *cp++)); } return argv; } /* * Execute a simple command. */ STATIC int #ifdef notyet evalcommand(union node *cmd, int flags, struct backcmd *backcmd) #else evalcommand(union node *cmd, int flags) #endif { struct localvar_list *localvar_stop; struct redirtab *redir_stop; struct stackmark smark; union node *argp; struct arglist arglist; struct arglist varlist; char **argv; int argc; struct strlist *sp; #ifdef notyet int pip[2]; #endif struct cmdentry cmdentry; struct job *jp; char *lastarg; const char *path; int spclbltin; int execcmd; int status; char **nargv; errlinno = lineno = cmd->ncmd.linno; if (funcline) lineno -= funcline - 1; /* First expand the arguments. */ TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); setstackmark(&smark); localvar_stop = pushlocalvars(); 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; for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) { struct strlist **spp; spp = arglist.lastp; expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); for (sp = *spp; sp; sp = sp->next) argc++; } /* Reserve one extra spot at the front for shellexec. */ nargv = stalloc(sizeof (char *) * (argc + 2)); argv = ++nargv; for (sp = arglist.list ; sp ; sp = sp->next) { TRACE(("evalcommand arg: %s\n", sp->text)); *nargv++ = sp->text; } *nargv = NULL; lastarg = NULL; if (iflag && funcline == 0 && argc > 0) lastarg = nargv[-1]; preverrout.fd = 2; expredir(cmd->ncmd.redirect); redir_stop = pushredir(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); mklocal((*spp)->text); /* * 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) { struct output *out; int sep; out = &preverrout; outstr(expandstr(ps4val()), out); sep = 0; sep = eprintlist(out, varlist.list, sep); eprintlist(out, arglist.list, sep); outcslow('\n', out); #ifdef FLUSHERR flushout(out); #endif } execcmd = 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; #ifdef FLUSHERR flushout(&errout); #endif goto bail; } /* implement bltin and command here */ if (cmdentry.cmdtype != CMDBUILTIN) break; if (spclbltin < 0) spclbltin = cmdentry.u.cmd->flags & BUILTIN_SPECIAL ; if (cmdentry.u.cmd == EXECCMD) execcmd++; if (cmdentry.u.cmd != COMMANDCMD) break; path = oldpath; nargv = parse_command_args(argv, &path); if (!nargv) break; argc -= nargv - argv; argv = nargv; cmd_flag |= DO_NOFUNC; } } if (status) { bail: exitstatus = status; /* We have a redirection error. */ if (spclbltin > 0) exraise(EXERROR); goto out; } jp = NULL; /* Execute the command. */ switch (cmdentry.cmdtype) { default: /* Fork off a child process if necessary. */ if (!(flags & EV_EXIT) || have_traps()) { INTOFF; jp = makejob(cmd, 1); if (forkshell(jp, cmd, FORK_FG) != 0) { INTON; break; } FORCEINTON; } listsetvar(varlist.list, VEXPORT|VSTACK); shellexec(argv, path, cmdentry.u.index); /* NOTREACHED */ case CMDBUILTIN: if (spclbltin > 0 || argc == 0) { poplocalvars(1); if (execcmd && argc > 1) listsetvar(varlist.list, VEXPORT); } if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) { if (exception == EXERROR && spclbltin <= 0) { FORCEINTON; break; } raise: longjmp(handler->loc, 1); } break; case CMDFUNCTION: poplocalvars(1); if (evalfun(cmdentry.u.func, argc, argv, flags)) goto raise; break; } status = waitforjob(jp); out: if (cmd->ncmd.redirect) popredir(execcmd); unwindredir(redir_stop); unwindlocalvars(localvar_stop); 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); return status; } STATIC int evalbltin(const struct builtincmd *cmd, int argc, char **argv, int flags) { char *volatile savecmdname; struct jmploc *volatile savehandler; struct jmploc jmploc; int status; int i; savecmdname = commandname; savehandler = handler; if ((i = setjmp(jmploc.loc))) goto cmddone; handler = &jmploc; commandname = argv[0]; argptr = argv + 1; optptr = NULL; /* initialize nextopt */ if (cmd == EVALCMD) status = evalcmd(argc, argv, flags); else status = (*cmd->builtin)(argc, argv); flushall(); status |= outerr(out1); exitstatus = status; cmddone: freestdout(); commandname = savecmdname; handler = savehandler; return i; } STATIC int evalfun(struct funcnode *func, int argc, char **argv, int flags) { volatile struct shparam saveparam; struct jmploc *volatile savehandler; struct jmploc jmploc; int e; int savefuncline; int saveloopnest; saveparam = shellparam; savefuncline = funcline; saveloopnest = loopnest; savehandler = handler; if ((e = setjmp(jmploc.loc))) { goto funcdone; } INTOFF; handler = &jmploc; shellparam.malloc = 0; func->count++; funcline = func->n.ndefun.linno; loopnest = 0; INTON; shellparam.nparam = argc - 1; shellparam.p = argv + 1; shellparam.optind = 1; shellparam.optoff = -1; pushlocalvars(); evaltree(func->n.ndefun.body, flags & EV_TESTED); poplocalvars(0); funcdone: INTOFF; loopnest = saveloopnest; funcline = savefuncline; freefunc(func); freeparam(&shellparam); shellparam = saveparam; handler = savehandler; INTON; evalskip &= ~(SKIPFUNC | SKIPFUNCDEF); return e; } /* * 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 prehash(union node *n) { struct cmdentry entry; if (n->type == NCMD && n->ncmd.args) if (goodname(n->ncmd.args->narg.text)) find_command(n->ncmd.args->narg.text, &entry, 0, pathval()); } /* * Builtin commands. Builtin commands whose functions are closely * tied to evaluation are implemented here. */ /* * No command given. */ STATIC int bltincmd(int argc, char **argv) { /* * Preserve exitstatus of a previous possible redirection * as POSIX mandates */ return back_exitstatus; } /* * 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. */ int breakcmd(int argc, char **argv) { int n = argc > 1 ? number(argv[1]) : 1; if (n <= 0) badnum(argv[1]); if (n > loopnest) n = loopnest; if (n > 0) { evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; skipcount = n; } return 0; } /* * The return command. */ int returncmd(int argc, char **argv) { int skip; int status; /* * If called outside a function, do what ksh does; * skip the rest of the file. */ if (argv[1]) { skip = SKIPFUNC; status = number(argv[1]); } else { skip = SKIPFUNCDEF; status = exitstatus; } evalskip = skip; return status; } int falsecmd(int argc, char **argv) { return 1; } int truecmd(int argc, char **argv) { return 0; } int execcmd(int argc, char **argv) { if (argc > 1) { iflag = 0; /* exit on error */ mflag = 0; optschanged(); shellexec(argv + 1, pathval(), 0); } return 0; } STATIC int eprintlist(struct output *out, struct strlist *sp, int sep) { while (sp) { const char *p; p = " %s" + (1 - sep); sep |= 1; outfmt(out, p, sp->text); sp = sp->next; } return sep; }