sh: Reduce unnecessary forks with eval.
[dragonfly.git] / bin / sh / eval.c
index af87caa..ecc53f4 100644 (file)
@@ -34,8 +34,7 @@
  * SUCH DAMAGE.
  *
  * @(#)eval.c  8.9 (Berkeley) 6/8/95
- * $FreeBSD: src/bin/sh/eval.c,v 1.53 2006/06/15 07:57:05 stefanf Exp $
- * $DragonFly: src/bin/sh/eval.c,v 1.12 2007/02/04 19:45:24 corecode Exp $
+ * $FreeBSD: src/bin/sh/eval.c,v 1.110 2011/06/16 21:50:28 jilles Exp $
  */
 
 #include <sys/time.h>
 #endif
 
 
-/* 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 */
-
 int evalskip;                  /* set if we are skipping commands */
-STATIC int skipcount;          /* number of levels to skip */
+static int skipcount;          /* number of levels to skip */
 MKINIT int loopnest;           /* current loop nesting level */
 int funcnest;                  /* depth of function calls */
+static int builtin_flags;      /* evalcommand flags for builtins */
 
 
 const char *commandname;
@@ -93,14 +88,16 @@ int exitstatus;                     /* exit status of last command */
 int oexitstatus;               /* saved exit status */
 
 
-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 *);
-STATIC void evalcommand(union node *, int, struct backcmd *);
-STATIC void prehash(union node *);
+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 evalredir(union node *, int);
+static void expredir(union node *);
+static void evalpipe(union node *);
+static int is_valid_fast_cmdsubst(union node *n);
+static void evalcommand(union node *, int, struct backcmd *);
+static void prehash(union node *);
 
 
 /*
@@ -115,10 +112,6 @@ RESET {
        loopnest = 0;
        funcnest = 0;
 }
-
-SHELLPROC {
-       exitstatus = 0;
-}
 #endif
 
 
@@ -140,8 +133,7 @@ evalcmd(int argc, char **argv)
                         STARTSTACKSTR(concat);
                         ap = argv + 2;
                         for (;;) {
-                                while (*p)
-                                        STPUTC(*p++, concat);
+                                STPUTS(p, concat);
                                 if ((p = *ap++) == NULL)
                                         break;
                                 STPUTC(' ', concat);
@@ -149,8 +141,9 @@ evalcmd(int argc, char **argv)
                         STPUTC('\0', concat);
                         p = grabstackstr(concat);
                 }
-                evalstring(p);
-        }
+                evalstring(p, builtin_flags);
+        } else
+                exitstatus = 0;
         return exitstatus;
 }
 
@@ -160,23 +153,37 @@ evalcmd(int argc, char **argv)
  */
 
 void
-evalstring(char *s)
+evalstring(char *s, int flags)
 {
        union node *n;
        struct stackmark smark;
+       int flags_exit;
+       int any;
 
+       flags_exit = flags & EV_EXIT;
+       flags &= ~EV_EXIT;
+       any = 0;
        setstackmark(&smark);
        setinputstring(s, 1);
        while ((n = parsecmd(0)) != NEOF) {
-               evaltree(n, 0);
+               if (n != NULL && !nflag) {
+                       if (flags_exit && preadateof())
+                               evaltree(n, flags | EV_EXIT);
+                       else
+                               evaltree(n, flags);
+                       any = 1;
+               }
                popstackmark(&smark);
        }
        popfile();
        popstackmark(&smark);
+       if (!any)
+               exitstatus = 0;
+       if (flags_exit)
+               exraise(EXEXIT);
 }
 
 
-
 /*
  * Evaluate a parse tree.  The value is left in the global variable
  * exitstatus.
@@ -186,6 +193,7 @@ void
 evaltree(union node *n, int flags)
 {
        int do_etest;
+       union node *next;
 
        do_etest = 0;
        if (n == NULL) {
@@ -193,96 +201,99 @@ evaltree(union node *n, int flags)
                exitstatus = 0;
                goto out;
        }
+       do {
+               next = NULL;
 #ifndef NO_HISTORY
-       displayhist = 1;        /* show history substitutions done with fc */
+               displayhist = 1;        /* show history substitutions done with fc */
 #endif
-       TRACE(("evaltree(%p: %d) called\n", (void *)n, n->type));
-       switch (n->type) {
-       case NSEMI:
-               evaltree(n->nbinary.ch1, flags & ~EV_EXIT);
-               if (evalskip)
-                       goto out;
-               evaltree(n->nbinary.ch2, flags);
-               break;
-       case NAND:
-               evaltree(n->nbinary.ch1, EV_TESTED);
-               if (evalskip || exitstatus != 0) {
-                       goto out;
+               TRACE(("evaltree(%p: %d) called\n", (void *)n, n->type));
+               switch (n->type) {
+               case NSEMI:
+                       evaltree(n->nbinary.ch1, flags & ~EV_EXIT);
+                       if (evalskip)
+                               goto out;
+                       next = n->nbinary.ch2;
+                       break;
+               case NAND:
+                       evaltree(n->nbinary.ch1, EV_TESTED);
+                       if (evalskip || exitstatus != 0) {
+                               goto out;
+                       }
+                       next = n->nbinary.ch2;
+                       break;
+               case NOR:
+                       evaltree(n->nbinary.ch1, EV_TESTED);
+                       if (evalskip || exitstatus == 0)
+                               goto out;
+                       next = n->nbinary.ch2;
+                       break;
+               case NREDIR:
+                       evalredir(n, flags);
+                       break;
+               case NSUBSHELL:
+                       evalsubshell(n, flags);
+                       do_etest = !(flags & EV_TESTED);
+                       break;
+               case NBACKGND:
+                       evalsubshell(n, flags);
+                       break;
+               case NIF: {
+                       evaltree(n->nif.test, EV_TESTED);
+                       if (evalskip)
+                               goto out;
+                       if (exitstatus == 0)
+                               next = n->nif.ifpart;
+                       else if (n->nif.elsepart)
+                               next = n->nif.elsepart;
+                       else
+                               exitstatus = 0;
+                       break;
                }
-               evaltree(n->nbinary.ch2, flags);
-               break;
-       case NOR:
-               evaltree(n->nbinary.ch1, EV_TESTED);
-               if (evalskip || exitstatus == 0)
-                       goto out;
-               evaltree(n->nbinary.ch2, flags);
-               break;
-       case NREDIR:
-               expredir(n->nredir.redirect);
-               redirect(n->nredir.redirect, REDIR_PUSH);
-               evaltree(n->nredir.n, flags);
-               popredir();
-               break;
-       case NSUBSHELL:
-               evalsubshell(n, flags);
-               do_etest = !(flags & EV_TESTED);
-               break;
-       case NBACKGND:
-               evalsubshell(n, flags);
-               break;
-       case NIF: {
-               evaltree(n->nif.test, EV_TESTED);
-               if (evalskip)
-                       goto out;
-               if (exitstatus == 0)
-                       evaltree(n->nif.ifpart, flags);
-               else if (n->nif.elsepart)
-                       evaltree(n->nif.elsepart, flags);
-               else
+               case NWHILE:
+               case NUNTIL:
+                       evalloop(n, flags & ~EV_EXIT);
+                       break;
+               case NFOR:
+                       evalfor(n, flags & ~EV_EXIT);
+                       break;
+               case NCASE:
+                       evalcase(n, flags);
+                       break;
+               case NDEFUN:
+                       defun(n->narg.text, n->narg.next);
                        exitstatus = 0;
-               break;
-       }
-       case NWHILE:
-       case NUNTIL:
-               evalloop(n, flags & ~EV_EXIT);
-               break;
-       case NFOR:
-               evalfor(n, flags & ~EV_EXIT);
-               break;
-       case NCASE:
-               evalcase(n, flags);
-               break;
-       case NDEFUN:
-               defun(n->narg.text, n->narg.next);
-               exitstatus = 0;
-               break;
-       case NNOT:
-               evaltree(n->nnot.com, EV_TESTED);
-               exitstatus = !exitstatus;
-               break;
-
-       case NPIPE:
-               evalpipe(n);
-               do_etest = !(flags & EV_TESTED);
-               break;
-       case NCMD:
-               evalcommand(n, flags, NULL);
-               do_etest = !(flags & EV_TESTED);
-               break;
-       default:
-               out1fmt("Node type = %d\n", n->type);
-               flushout(&output);
-               break;
-       }
+                       break;
+               case NNOT:
+                       evaltree(n->nnot.com, EV_TESTED);
+                       exitstatus = !exitstatus;
+                       break;
+
+               case NPIPE:
+                       evalpipe(n);
+                       do_etest = !(flags & EV_TESTED);
+                       break;
+               case NCMD:
+                       evalcommand(n, flags, NULL);
+                       do_etest = !(flags & EV_TESTED);
+                       break;
+               default:
+                       out1fmt("Node type = %d\n", n->type);
+                       flushout(&output);
+                       break;
+               }
+               n = next;
+       } while (n != NULL);
 out:
        if (pendingsigs)
                dotrap();
-       if ((flags & EV_EXIT) || (eflag && exitstatus != 0 && do_etest))
+       if (eflag && exitstatus != 0 && do_etest)
                exitshell(exitstatus);
+       if (flags & EV_EXIT)
+               exraise(EXEXIT);
 }
 
 
-STATIC void
+static void
 evalloop(union node *n, int flags)
 {
        int status;
@@ -298,6 +309,8 @@ skipping:     if (evalskip == SKIPCONT && --skipcount <= 0) {
                        }
                        if (evalskip == SKIPBREAK && --skipcount <= 0)
                                evalskip = 0;
+                       if (evalskip == SKIPFUNC || evalskip == SKIPFILE)
+                               status = exitstatus;
                        break;
                }
                if (n->type == NWHILE) {
@@ -318,7 +331,7 @@ skipping:     if (evalskip == SKIPCONT && --skipcount <= 0) {
 
 
 
-STATIC void
+static void
 evalfor(union node *n, int flags)
 {
        struct arglist arglist;
@@ -358,7 +371,7 @@ out:
 
 
 
-STATIC void
+static void
 evalcase(union node *n, int flags)
 {
        union node *cp;
@@ -369,6 +382,7 @@ evalcase(union node *n, int flags)
        setstackmark(&smark);
        arglist.lastp = &arglist.list;
        oexitstatus = exitstatus;
+       exitstatus = 0;
        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) {
@@ -390,34 +404,76 @@ out:
  * Kick off a subshell to evaluate a tree.
  */
 
-STATIC void
+static void
 evalsubshell(union node *n, int flags)
 {
        struct job *jp;
        int backgnd = (n->type == NBACKGND);
 
+       oexitstatus = exitstatus;
        expredir(n->nredir.redirect);
-       jp = makejob(n, 1);
-       if (forkshell(jp, n, backgnd) == 0) {
+       if ((!backgnd && flags & EV_EXIT && !have_traps()) ||
+           forkshell(jp = makejob(n, 1), n, backgnd) == 0) {
                if (backgnd)
                        flags &=~ EV_TESTED;
                redirect(n->nredir.redirect, 0);
                evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */
-       }
-       if (! backgnd) {
+       } else if (!backgnd) {
                INTOFF;
                exitstatus = waitforjob(jp, NULL);
                INTON;
-       }
+       } else
+               exitstatus = 0;
 }
 
 
+/*
+ * Evaluate a redirected compound command.
+ */
+
+static void
+evalredir(union node *n, int flags)
+{
+       struct jmploc jmploc;
+       struct jmploc *savehandler;
+       volatile int in_redirect = 1;
+
+       oexitstatus = exitstatus;
+       expredir(n->nredir.redirect);
+       savehandler = handler;
+       if (setjmp(jmploc.loc)) {
+               int e;
+
+               handler = savehandler;
+               e = exception;
+               popredir();
+               if (e == EXERROR || e == EXEXEC) {
+                       if (in_redirect) {
+                               exitstatus = 2;
+                               return;
+                       }
+               }
+               longjmp(handler->loc, 1);
+       } else {
+               INTOFF;
+               handler = &jmploc;
+               redirect(n->nredir.redirect, REDIR_PUSH);
+               in_redirect = 0;
+               INTON;
+               evaltree(n->nredir.n, flags);
+       }
+       INTOFF;
+       handler = savehandler;
+       popredir();
+       INTON;
+}
+
 
 /*
  * Compute the names of the files in a redirection list.
  */
 
-STATIC void
+static void
 expredir(union node *n)
 {
        union node *redir;
@@ -425,7 +481,6 @@ expredir(union node *n)
        for (redir = n ; redir ; redir = redir->nfile.next) {
                struct arglist fn;
                fn.lastp = &fn.list;
-               oexitstatus = exitstatus;
                switch (redir->type) {
                case NFROM:
                case NTO:
@@ -438,7 +493,7 @@ expredir(union node *n)
                case NFROMFD:
                case NTOFD:
                        if (redir->ndup.vname) {
-                               expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
+                               expandarg(redir->ndup.vname, &fn, EXP_TILDE | EXP_REDIR);
                                fixredir(redir, fn.list->text, 1);
                        }
                        break;
@@ -455,7 +510,7 @@ expredir(union node *n)
  * of all the rest.)
  */
 
-STATIC void
+static void
 evalpipe(union node *n)
 {
        struct job *jp;
@@ -500,7 +555,8 @@ evalpipe(union node *n)
                if (prevfd >= 0)
                        close(prevfd);
                prevfd = pip[0];
-               close(pip[1]);
+               if (pip[1] != -1)
+                       close(pip[1]);
        }
        INTON;
        if (n->npipe.backgnd == 0) {
@@ -508,11 +564,19 @@ evalpipe(union node *n)
                exitstatus = waitforjob(jp, NULL);
                TRACE(("evalpipe:  job done exit status %d\n", exitstatus));
                INTON;
-       }
+       } else
+               exitstatus = 0;
 }
 
 
 
+static int
+is_valid_fast_cmdsubst(union node *n)
+{
+
+       return (n->type == NCMD);
+}
+
 /*
  * 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
@@ -526,6 +590,9 @@ evalbackcmd(union node *n, struct backcmd *result)
        int pip[2];
        struct job *jp;
        struct stackmark smark;         /* unnecessary */
+       struct jmploc jmploc;
+       struct jmploc *savehandler;
+       struct localvar *savelocalvars;
 
        setstackmark(&smark);
        result->fd = -1;
@@ -536,9 +603,30 @@ evalbackcmd(union node *n, struct backcmd *result)
                exitstatus = 0;
                goto out;
        }
-       if (n->type == NCMD) {
+       if (is_valid_fast_cmdsubst(n)) {
                exitstatus = oexitstatus;
-               evalcommand(n, EV_BACKCMD, result);
+               savelocalvars = localvars;
+               localvars = NULL;
+               forcelocal++;
+               savehandler = handler;
+               if (setjmp(jmploc.loc)) {
+                       if (exception == EXERROR || exception == EXEXEC)
+                               exitstatus = 2;
+                       else if (exception != 0) {
+                               handler = savehandler;
+                               forcelocal--;
+                               poplocalvars();
+                               localvars = savelocalvars;
+                               longjmp(handler->loc, 1);
+                       }
+               } else {
+                       handler = &jmploc;
+                       evalcommand(n, EV_BACKCMD, result);
+               }
+               handler = savehandler;
+               forcelocal--;
+               poplocalvars();
+               localvars = savelocalvars;
        } else {
                exitstatus = 0;
                if (pipe(pip) < 0)
@@ -563,13 +651,38 @@ out:
                result->fd, result->buf, result->nleft, result->jp));
 }
 
-
+/*
+ * Check if a builtin can safely be executed in the same process,
+ * even though it should be in a subshell (command substitution).
+ * Note that jobid, jobs, times and trap can show information not
+ * available in a child process; this is deliberate.
+ * The arguments should already have been expanded.
+ */
+static int
+safe_builtin(int idx, int argc, char **argv)
+{
+       if (idx == BLTINCMD || idx == COMMANDCMD || idx == ECHOCMD ||
+           idx == FALSECMD || idx == JOBIDCMD || idx == JOBSCMD ||
+           idx == KILLCMD || idx == PRINTFCMD || idx == PWDCMD ||
+           idx == TESTCMD || idx == TIMESCMD || idx == TRUECMD ||
+           idx == TYPECMD)
+               return (1);
+       if (idx == EXPORTCMD || idx == TRAPCMD || idx == ULIMITCMD ||
+           idx == UMASKCMD)
+               return (argc <= 1 || (argc == 2 && argv[1][0] == '-'));
+       if (idx == SETCMD)
+               return (argc <= 1 || (argc == 2 && (argv[1][0] == '-' ||
+                   argv[1][0] == '+') && argv[1][1] == 'o' &&
+                   argv[1][2] == '\0'));
+       return (0);
+}
 
 /*
  * Execute a simple command.
+ * Note: This may or may not return if (flags & EV_EXIT).
  */
 
-STATIC void
+static void
 evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
 {
        struct stackmark smark;
@@ -585,16 +698,18 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
        int mode;
        int pip[2];
        struct cmdentry cmdentry;
-       struct job *jp;
+       struct job *volatile jp;
        struct jmploc jmploc;
-       struct jmploc *volatile savehandler = NULL;
-       const char *volatile savecmdname;
-       volatile struct shparam saveparam;
-       struct localvar *volatile savelocalvars;
+       struct jmploc *savehandler;
+       const char *savecmdname;
+       struct shparam saveparam;
+       struct localvar *savelocalvars;
+       struct parsefile *savetopfile;
        volatile int e;
        char *volatile lastarg;
        int realstatus;
        volatile int do_clearcmdentry;
+       const char *path = pathval();
 
        /* First expand the arguments. */
        TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags));
@@ -602,19 +717,14 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
        arglist.lastp = &arglist.list;
        varlist.lastp = &varlist.list;
        varflag = 1;
+       jp = NULL;
        do_clearcmdentry = 0;
        oexitstatus = exitstatus;
        exitstatus = 0;
        for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) {
-               char *p = argp->narg.text;
-               if (varflag && is_name(*p)) {
-                       do {
-                               p++;
-                       } while (is_in_name(*p));
-                       if (*p == '=') {
-                               expandarg(argp, &varlist, EXP_VARTILDE);
-                               continue;
-                       }
+               if (varflag && isassignment(argp->narg.text)) {
+                       expandarg(argp, &varlist, EXP_VARTILDE);
+                       continue;
                }
                expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
                varflag = 0;
@@ -625,7 +735,9 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
        argc = 0;
        for (sp = arglist.list ; sp ; sp = sp->next)
                argc++;
-       argv = stalloc(sizeof (char *) * (argc + 1));
+       /* Add one slot at the beginning for tryexec(). */
+       argv = stalloc(sizeof (char *) * (argc + 2));
+       argv++;
 
        for (sp = arglist.list ; sp ; sp = sp->next) {
                TRACE(("evalcommand arg: %s\n", sp->text));
@@ -640,20 +752,36 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
        /* Print the command if xflag is set. */
        if (xflag) {
                char sep = 0;
-               out2str(ps4val());
+               const char *p, *ps4;
+               ps4 = expandstr(ps4val());
+               out2str(ps4 != NULL ? ps4 : ps4val());
                for (sp = varlist.list ; sp ; sp = sp->next) {
                        if (sep != 0)
-                               outc(' ', &errout);
-                       out2str(sp->text);
+                               out2c(' ');
+                       p = strchr(sp->text, '=');
+                       if (p != NULL) {
+                               p++;
+                               outbin(sp->text, p - sp->text, out2);
+                               out2qstr(p);
+                       } else
+                               out2qstr(sp->text);
                        sep = ' ';
                }
                for (sp = arglist.list ; sp ; sp = sp->next) {
                        if (sep != 0)
-                               outc(' ', &errout);
-                       out2str(sp->text);
+                               out2c(' ');
+                       /* Disambiguate command looking like assignment. */
+                       if (sp == arglist.list &&
+                                       strchr(sp->text, '=') != NULL &&
+                                       strchr(sp->text, '\'') == NULL) {
+                               out2c('\'');
+                               out2str(sp->text);
+                               out2c('\'');
+                       } else
+                               out2qstr(sp->text);
                        sep = ' ';
                }
-               outc('\n', &errout);
+               out2c('\n');
                flushout(&errout);
        }
 
@@ -662,10 +790,10 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
                /* Variable assignment(s) without command */
                cmdentry.cmdtype = CMDBUILTIN;
                cmdentry.u.index = BLTINCMD;
-               cmdentry.special = 1;
+               cmdentry.special = 0;
        } else {
                static const char PATH[] = "PATH=";
-               const char *path = pathval();
+               int cmd_flags = 0, bltinonly = 0;
 
                /*
                 * Modify the command lookup path, if a PATH= assignment
@@ -692,46 +820,81 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
                                 * bookinging effort, since most such runs add
                                 * directories in front of the new PATH.
                                 */
-                               clearcmdentry(0);
+                               clearcmdentry();
                                do_clearcmdentry = 1;
                        }
 
-               find_command(argv[0], &cmdentry, 1, path);
-               if (cmdentry.cmdtype == CMDUNKNOWN) {   /* command not found */
-                       exitstatus = 127;
-                       flushout(&errout);
-                       return;
-               }
-               /* implement the bltin builtin here */
-               if (cmdentry.cmdtype == CMDBUILTIN && cmdentry.u.index == BLTINCMD) {
-                       for (;;) {
-                               argv++;
-                               if (--argc == 0)
+               for (;;) {
+                       if (bltinonly) {
+                               cmdentry.u.index = find_builtin(*argv, &cmdentry.special);
+                               if (cmdentry.u.index < 0) {
+                                       cmdentry.u.index = BLTINCMD;
+                                       argv--;
+                                       argc++;
                                        break;
-                               if ((cmdentry.u.index = find_builtin(*argv,
-                                   &cmdentry.special)) < 0) {
-                                       outfmt(&errout, "%s: not found\n", *argv);
-                                       exitstatus = 127;
-                                       flushout(&errout);
-                                       return;
                                }
-                               if (cmdentry.u.index != BLTINCMD)
+                       } else
+                               find_command(argv[0], &cmdentry, cmd_flags, path);
+                       /* implement the bltin and command builtins here */
+                       if (cmdentry.cmdtype != CMDBUILTIN)
+                               break;
+                       if (cmdentry.u.index == BLTINCMD) {
+                               if (argc == 1)
                                        break;
-                       }
+                               argv++;
+                               argc--;
+                               bltinonly = 1;
+                       } else if (cmdentry.u.index == COMMANDCMD) {
+                               if (argc == 1)
+                                       break;
+                               if (!strcmp(argv[1], "-p")) {
+                                       if (argc == 2)
+                                               break;
+                                       if (argv[2][0] == '-') {
+                                               if (strcmp(argv[2], "--"))
+                                                       break;
+                                               if (argc == 3)
+                                                       break;
+                                               argv += 3;
+                                               argc -= 3;
+                                       } else {
+                                               argv += 2;
+                                               argc -= 2;
+                                       }
+                                       path = _PATH_STDPATH;
+                                       clearcmdentry();
+                                       do_clearcmdentry = 1;
+                               } else if (!strcmp(argv[1], "--")) {
+                                       if (argc == 2)
+                                               break;
+                                       argv += 2;
+                                       argc -= 2;
+                               } else if (argv[1][0] == '-')
+                                       break;
+                               else {
+                                       argv++;
+                                       argc--;
+                               }
+                               cmd_flags |= DO_NOFUNC;
+                               bltinonly = 0;
+                       } else
+                               break;
                }
+               /*
+                * Special builtins lose their special properties when
+                * called via 'command'.
+                */
+               if (cmd_flags & DO_NOFUNC)
+                       cmdentry.special = 0;
        }
 
        /* Fork off a child process if necessary. */
        if (cmd->ncmd.backgnd
-        || (cmdentry.cmdtype == CMDNORMAL
-           && ((flags & EV_EXIT) == 0 || Tflag))
+        || ((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN)
+           && ((flags & EV_EXIT) == 0 || have_traps()))
         || ((flags & EV_BACKCMD) != 0
-           && (cmdentry.cmdtype != CMDBUILTIN
-                || cmdentry.u.index == CDCMD
-                || cmdentry.u.index == DOTCMD
-                || cmdentry.u.index == EVALCMD))
-        || (cmdentry.cmdtype == CMDBUILTIN &&
-           cmdentry.u.index == COMMANDCMD)) {
+           && (cmdentry.cmdtype != CMDBUILTIN ||
+                !safe_builtin(cmdentry.u.index, argc, argv)))) {
                jp = makejob(cmd, 1);
                mode = cmd->ncmd.backgnd;
                if (flags & EV_BACKCMD) {
@@ -748,6 +911,7 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
                                dup2(pip[1], 1);
                                close(pip[1]);
                        }
+                       flags &= ~EV_BACKCMD;
                }
                flags |= EV_EXIT;
        }
@@ -758,7 +922,6 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
 #ifdef DEBUG
                trputs("Shell function:  ");  trargs(argv);
 #endif
-               redirect(cmd->ncmd.redirect, REDIR_PUSH);
                saveparam = shellparam;
                shellparam.malloc = 0;
                shellparam.reset = 1;
@@ -768,42 +931,43 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
                INTOFF;
                savelocalvars = localvars;
                localvars = NULL;
-               INTON;
+               reffunc(cmdentry.u.func);
+               savehandler = handler;
                if (setjmp(jmploc.loc)) {
-                       if (exception == EXSHELLPROC)
-                               freeparam(&saveparam);
-                       else {
-                               freeparam(&shellparam);
-                               shellparam = saveparam;
-                       }
+                       freeparam(&shellparam);
+                       shellparam = saveparam;
+                       popredir();
+                       unreffunc(cmdentry.u.func);
                        poplocalvars();
                        localvars = savelocalvars;
+                       funcnest--;
                        handler = savehandler;
                        longjmp(handler->loc, 1);
                }
-               savehandler = handler;
                handler = &jmploc;
+               funcnest++;
+               redirect(cmd->ncmd.redirect, REDIR_PUSH);
+               INTON;
                for (sp = varlist.list ; sp ; sp = sp->next)
                        mklocal(sp->text);
-               funcnest++;
-               if (flags & EV_TESTED)
-                       evaltree(cmdentry.u.func, EV_TESTED);
-               else
-                       evaltree(cmdentry.u.func, 0);
-               funcnest--;
+               exitstatus = oexitstatus;
+               evaltree(getfuncnode(cmdentry.u.func),
+                   flags & (EV_TESTED | EV_EXIT));
                INTOFF;
+               unreffunc(cmdentry.u.func);
                poplocalvars();
                localvars = savelocalvars;
                freeparam(&shellparam);
                shellparam = saveparam;
                handler = savehandler;
+               funcnest--;
                popredir();
                INTON;
                if (evalskip == SKIPFUNC) {
                        evalskip = 0;
                        skipcount = 0;
                }
-               if (flags & EV_EXIT)
+               if (jp)
                        exitshell(exitstatus);
        } else if (cmdentry.cmdtype == CMDBUILTIN) {
 #ifdef DEBUG
@@ -815,68 +979,79 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
                        memout.nextc = memout.buf;
                        memout.bufsize = 64;
                        mode |= REDIR_BACKQ;
+                       cmdentry.special = 0;
                }
                savecmdname = commandname;
+               savetopfile = getcurrentfile();
                cmdenviron = varlist.list;
                e = -1;
+               savehandler = handler;
                if (setjmp(jmploc.loc)) {
                        e = exception;
-                       exitstatus = (e == EXINT)? SIGINT+128 : 2;
+                       if (e == EXINT)
+                               exitstatus = SIGINT+128;
+                       else if (e != EXEXIT)
+                               exitstatus = 2;
                        goto cmddone;
                }
-               savehandler = handler;
                handler = &jmploc;
                redirect(cmd->ncmd.redirect, mode);
-               if (cmdentry.special)
-                       listsetvar(cmdenviron);
+               /*
+                * If there is no command word, redirection errors should
+                * not be fatal but assignment errors should.
+                */
+               if (argc == 0 && !(flags & EV_BACKCMD))
+                       cmdentry.special = 1;
+               listsetvar(cmdenviron, cmdentry.special ? 0 : VNOSET);
+               if (argc > 0)
+                       bltinsetlocale();
                commandname = argv[0];
                argptr = argv + 1;
-               optptr = NULL;                  /* initialize nextopt */
-               optind = 1;                     /* and getopt */
-               optreset = 1;
+               nextopt_optptr = NULL;          /* initialize nextopt */
+               builtin_flags = flags;
                exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv);
                flushall();
 cmddone:
+               if (argc > 0)
+                       bltinunsetlocale();
                cmdenviron = NULL;
                out1 = &output;
                out2 = &errout;
                freestdout();
-               if (e != EXSHELLPROC) {
-                       commandname = savecmdname;
-                       if (flags & EV_EXIT) {
-                               exitshell(exitstatus);
-                       }
-               }
                handler = savehandler;
-               if (e != -1) {
-                       if ((e != EXERROR && e != EXEXEC)
-                           || cmdentry.special)
-                               exraise(e);
-                       FORCEINTON;
-               }
-               if (cmdentry.u.index != EXECCMD)
-                       popredir();
+               commandname = savecmdname;
+               if (jp)
+                       exitshell(exitstatus);
                if (flags == EV_BACKCMD) {
                        backcmd->buf = memout.buf;
                        backcmd->nleft = memout.nextc - memout.buf;
                        memout.buf = NULL;
                }
+               if (cmdentry.u.index != EXECCMD)
+                       popredir();
+               if (e != -1) {
+                       if ((e != EXERROR && e != EXEXEC)
+                           || cmdentry.special)
+                               exraise(e);
+                       popfilesupto(savetopfile);
+                       if (flags != EV_BACKCMD)
+                               FORCEINTON;
+               }
        } else {
 #ifdef DEBUG
                trputs("normal command:  ");  trargs(argv);
 #endif
-               clearredir();
                redirect(cmd->ncmd.redirect, 0);
                for (sp = varlist.list ; sp ; sp = sp->next)
                        setvareq(sp->text, VEXPORT|VSTACK);
                envp = environment();
-               shellexec(argv, envp, pathval(), cmdentry.u.index);
+               shellexec(argv, envp, path, cmdentry.u.index);
                /*NOTREACHED*/
        }
        goto out;
 
 parent:        /* parent process gets here (if we forked) */
-       if (mode == 0) {        /* argument to fork */
+       if (mode == FORK_FG) {  /* argument to fork */
                INTOFF;
                exitstatus = waitforjob(jp, &realstatus);
                INTON;
@@ -884,17 +1059,18 @@ parent:  /* parent process gets here (if we forked) */
                        evalskip = SKIPBREAK;
                        skipcount = loopnest;
                }
-       } else if (mode == 2) {
+       } else if (mode == FORK_NOJOB) {
                backcmd->fd = pip[0];
                close(pip[1]);
                backcmd->jp = jp;
-       }
+       } else
+               exitstatus = 0;
 
 out:
        if (lastarg)
                setvar("_", lastarg, 0);
        if (do_clearcmdentry)
-               clearcmdentry(0);
+               clearcmdentry();
        popstackmark(&smark);
 }
 
@@ -907,7 +1083,7 @@ out:
  * check that the name will not be subject to expansion.
  */
 
-STATIC void
+static void
 prehash(union node *n)
 {
        struct cmdentry entry;
@@ -926,12 +1102,17 @@ prehash(union node *n)
  */
 
 /*
- * No command given, or a bltin command with no arguments.
+ * No command given, a bltin command with no arguments, or a bltin command
+ * with an invalid name.
  */
 
 int
-bltincmd(int argc __unused, char **argv __unused)
+bltincmd(int argc, char **argv)
 {
+       if (argc > 1) {
+               out2fmt_flush("%s: not found\n", argv[1]);
+               return 127;
+       }
        /*
         * Preserve exitstatus of a previous possible redirection
         * as POSIX mandates
@@ -971,23 +1152,18 @@ breakcmd(int argc, char **argv)
 int
 commandcmd(int argc, char **argv)
 {
-       static char stdpath[] = _PATH_STDPATH;
-       struct jmploc loc, *old;
-       struct strlist *sp;
-       const char *volatile path;
+       const char *path;
        int ch;
        int cmd = -1;
 
-       for (sp = cmdenviron; sp ; sp = sp->next)
-               setvareq(sp->text, VEXPORT|VSTACK);
-       path = pathval();
+       path = bltinlookup("PATH", 1);
 
        optind = optreset = 1;
        opterr = 0;
        while ((ch = getopt(argc, argv, "pvV")) != -1) {
                switch (ch) {
                case 'p':
-                       path = stdpath;
+                       path = _PATH_STDPATH;
                        break;
                case 'v':
                        cmd = TYPECMD_SMALLV;
@@ -1006,24 +1182,16 @@ commandcmd(int argc, char **argv)
        if (cmd != -1) {
                if (argc != 1)
                        error("wrong number of arguments");
-               return typecmd_impl(2, argv - 1, cmd);
-       }
-       if (argc != 0) {
-               old = handler;
-               handler = &loc;
-               if (setjmp(handler->loc) == 0)
-                       shellexec(argv, environment(), path, 0);
-               handler = old;
-               if (exception == EXEXEC)
-                       exit(exerrno);
-               exraise(exception);
+               return typecmd_impl(2, argv - 1, cmd, path);
        }
+       if (argc != 0)
+               error("commandcmd bad call");
 
        /*
         * Do nothing successfully if no command was specified;
         * ksh also does this.
         */
-       exit(0);
+       return(0);
 }
 
 
@@ -1065,6 +1233,12 @@ truecmd(int argc __unused, char **argv __unused)
 int
 execcmd(int argc, char **argv)
 {
+       /*
+        * Because we have historically not supported any options,
+        * only treat "--" specially.
+        */
+       if (argc > 1 && strcmp(argv[1], "--") == 0)
+               argc--, argv++;
        if (argc > 1) {
                struct strlist *sp;