+ 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;
+}
+
+/*
+ * Controls whether the shell is interactive or not.
+ */
+static void
+setinteractive(int on)
+{
+ static int is_interactive;
+
+ if (++on == is_interactive)
+ return;
+ is_interactive = on;
+ setsignal(SIGINT);
+ setsignal(SIGQUIT);
+ setsignal(SIGTERM);
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+ if (is_interactive > 1) {
+ /* Looks like they want an interactive shell */
+ static smallint do_banner;
+
+ if (!do_banner) {
+ out1fmt(
+ "\n\n"
+ "%s Built-in shell (ash)\n"
+ "Enter 'help' for a list of built-in commands."
+ "\n\n",
+ BB_BANNER);
+ do_banner = 1;
+ }
+ }
+#endif
+}
+
+#if ENABLE_FEATURE_EDITING_VI
+#define setvimode(on) do { \
+ if (on) line_input_state->flags |= VI_MODE; \
+ else line_input_state->flags &= ~VI_MODE; \
+} while (0)
+#else
+#define setvimode(on) viflag = 0 /* forcibly keep the option off */
+#endif
+
+static void
+optschanged(void)
+{
+#if DEBUG
+ opentrace();
+#endif
+ setinteractive(iflag);
+ setjobctl(mflag);
+ setvimode(viflag);
+}
+
+static struct localvar *localvars;
+
+/*
+ * Called after a function returns.
+ * Interrupts must be off.
+ */
+static void
+poplocalvars(void)
+{
+ struct localvar *lvp;
+ struct var *vp;
+
+ 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)
+{
+ volatile struct shparam saveparam;
+ struct localvar *volatile savelocalvars;
+ struct jmploc *volatile savehandler;
+ struct jmploc jmploc;
+ int e;
+
+ 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;
+ 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);
+ }
+ return argv;
+}
+#endif
+
+/*
+ * 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
+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)
+{
+ char *name;
+
+ argv = argptr;
+ while ((name = *argv++) != NULL) {
+ mklocal(name);
+ }
+ return 0;
+}
+
+static int
+falsecmd(int argc, char **argv)
+{
+ return 1;
+}
+
+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;
+}
+
+/*
+ * The return command.
+ */
+static int
+returncmd(int argc, char **argv)
+{
+ /*
+ * 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;
+}
+
+/* Forward declarations for builtintab[] */
+static int breakcmd(int, char **);
+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 exitcmd(int, char **);
+static int exportcmd(int, char **);
+#if ENABLE_ASH_GETOPTS
+static int getoptscmd(int, char **);
+#endif
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+static int helpcmd(int argc, char **argv);
+#endif
+#if ENABLE_ASH_MATH_SUPPORT
+static int letcmd(int, char **);
+#endif
+static int readcmd(int, char **);
+static int setcmd(int, char **);
+static int shiftcmd(int, char **);
+static int timescmd(int, char **);
+static int trapcmd(int, char **);
+static int umaskcmd(int, char **);
+static int unsetcmd(int, char **);
+static int ulimitcmd(int, char **);
+
+#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;
+}
+
+/*
+ * Execute a simple command.
+ */
+static int back_exitstatus; /* exit status of backquoted command */
+static int
+isassignment(const char *p)
+{
+ 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;
+}
+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;
+
+ 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 = pendingsig;
+ if (j)
+ exit_status = j + 128;
+ exitstatus = exit_status;
+
+ 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);
+}
+
+static int
+evalbltin(const struct builtincmd *cmd, int argc, char **argv)
+{
+ char *volatile savecmdname;
+ struct jmploc *volatile savehandler;
+ struct jmploc jmploc;
+ int i;
+
+ 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;
+}
+
+static int
+goodname(const char *p)
+{
+ return !*endofname(p);
+}
+
+
+/*
+ * 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 && 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.
+ */
+
+/*
+ * 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
+breakcmd(int argc, char **argv)
+{
+ int n = argc > 1 ? number(argv[1]) : 1;
+
+ 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;
+ }
+ return 0;
+}
+
+
+/* ============ input.c
+ *
+ * This implements the input routines used by the parser.
+ */
+
+#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */
+
+enum {
+ INPUT_PUSH_FILE = 1,
+ INPUT_NOFILE_OK = 2,
+};
+
+/*
+ * NEOF is returned by parsecmd when it encounters an end of file. It
+ * must be distinct from NULL, so we use the address of a variable that
+ * happens to be handy.
+ */
+static int plinno = 1; /* input line number */
+/* number of characters left in input buffer */
+static int parsenleft; /* copy of parsefile->nleft */
+static int parselleft; /* copy of parsefile->lleft */
+/* next character in input buffer */
+static char *parsenextc; /* copy of parsefile->nextc */
+
+static int checkkwd;
+/* values of checkkwd variable */
+#define CHKALIAS 0x1
+#define CHKKWD 0x2
+#define CHKNL 0x4
+
+static void
+popstring(void)
+{
+ struct strpush *sp = parsefile->strpush;
+
+ INT_OFF;
+#if ENABLE_ASH_ALIAS
+ if (sp->ap) {
+ if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') {
+ checkkwd |= CHKALIAS;