blob: 7aa752b9e158a19b1992919b9db481cf5a6bc008 [file] [log] [blame]
/*-
* Copyright (c) 1993
* The Regents of the University of California. All rights reserved.
* Copyright (c) 1997-2005
* Herbert Xu <herbert@gondor.apana.org.au>. 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 <zircon/status.h>
#include <zircon/syscalls.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
/*
* Evaluate a command.
*/
#include "process.h"
#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"
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 */
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();
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;
}
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()) {
redirect(n->nredir.redirect, 0);
evaltreenr(n->nredir.n, flags);
/* never returns */
}
INTOFF;
jp = makejob(n, 1);
zx_handle_t process;
const char* const* envp = (const char* const*)environment();
// Run in the foreground (of the subshell running in the background)
if (backgnd)
n->type = NSUBSHELL;
const char* errmsg = NULL;
zx_status_t exec_result = process_subshell(n, envp, &process, NULL, &errmsg);
if (exec_result == ZX_OK) {
/* Process-tracking management */
forkparent(jp, n, backgnd, process);
} else {
sh_error("Failed to create subshell (%s): %s", zx_status_get_string(exec_result), errmsg);
return exec_result;
}
status = 0;
if (! backgnd) {
status = process_await_termination(process, true);
zx_handle_close(process);
}
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 fds[3] = { STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO };
int pip[2] = { -1, -1 };
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);
for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
prehash(lp->n);
if (pip[0] > 0)
fds[0] = pip[0];
if (lp->next) {
if (pipe(pip) < 0)
sh_error("Pipe call failed");
fds[1] = pip[1];
} else {
fds[1] = STDOUT_FILENO;
}
zx_handle_t process;
const char* errmsg = NULL;
const char* const* envp = (const char* const*)environment();
zx_status_t status = process_subshell (lp->n, envp, &process, fds, &errmsg);
if (fds[0] != STDIN_FILENO)
close(fds[0]);
if (fds[1] != STDOUT_FILENO)
close(fds[1]);
if (status == ZX_OK) {
/* Process-tracking management */
forkparent(jp, lp->n, FORK_NOJOB, process);
} else {
freejob(jp);
sh_error("Failed to create shell: %s: %s", zx_status_get_string(status), errmsg);
}
}
if (n->npipe.backgnd == 0) {
status = waitforjob(jp);
TRACE(("evalpipe: job done exit status: %s\n", zx_status_get_string(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);
zx_handle_t process;
const char* errmsg = NULL;
const char* const* envp = (const char* const*)environment();
int fds[3] = { STDIN_FILENO, pip[1], STDERR_FILENO };
zx_status_t status = process_subshell(n, envp, &process, &fds[0], &errmsg);
close(pip[1]);
if (status != ZX_OK) {
freejob(jp);
sh_error("Failed to create subshell: %s: %s", zx_status_get_string(status), errmsg);
} else {
/* Process-tracking management */
forkparent(jp, n, FORK_NOJOB, process);
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;
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;
}
/* Execute the command. */
switch (cmdentry.cmdtype) {
default: {
zx_handle_t process = ZX_HANDLE_INVALID;
const char* errmsg = NULL;
status = process_launch(argc, (const char* const*)argv, path,
cmdentry.u.index, &process, &errmsg);
if (status) {
sh_error("Cannot create child process: %s: %s", zx_status_get_string(status), errmsg);
break;
}
settitle(argv[0]);
status = process_await_termination(process, true);
zx_handle_close(process);
settitle("sh");
break;
}
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;
goto readstatus;
}
raise:
longjmp(handler->loc, 1);
}
goto readstatus;
case CMDFUNCTION:
poplocalvars(1);
if (evalfun(cmdentry.u.func, argc, argv, flags))
goto raise;
readstatus:
status = exitstatus;
break;
}
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;
}