hush: do not allow sh -c '{ echo boo }'
[oweals/busybox.git] / shell / ash.c
index 40b3ef3e3a946d61413619aeaf4ac074937d2f27..87f2127a1f491d81da431e2692a2bbe8cb94a0dd 100644 (file)
@@ -32,6 +32,7 @@
 #define DEBUG_TIME 0
 #define DEBUG_PID 1
 #define DEBUG_SIG 1
+#define DEBUG_INTONOFF 0
 
 #define PROFILE 0
 
@@ -295,16 +296,13 @@ struct globals_misc {
 
        volatile int suppress_int; /* counter */
        volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */
-       /* last pending signal */
-       volatile /*sig_atomic_t*/ smallint pending_sig;
+       volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */
+       volatile /*sig_atomic_t*/ smallint pending_sig; /* last pending signal */
        smallint exception_type; /* kind of exception (0..5) */
        /* exceptions */
 #define EXINT 0         /* SIGINT received */
 #define EXERROR 1       /* a generic error */
-#define EXSHELLPROC 2   /* execute a shell procedure */
-#define EXEXEC 3        /* command execution failed */
 #define EXEXIT 4        /* exit the shell */
-#define EXSIG 5         /* trapped signal in wait(1) */
 
        smallint isloginsh;
        char nullstr[1];        /* zero length string */
@@ -373,6 +371,7 @@ extern struct globals_misc *const ash_ptr_to_globals_misc;
 #define exception_type    (G_misc.exception_type   )
 #define suppress_int      (G_misc.suppress_int     )
 #define pending_int       (G_misc.pending_int      )
+#define got_sigchld       (G_misc.got_sigchld      )
 #define pending_sig       (G_misc.pending_sig      )
 #define isloginsh   (G_misc.isloginsh  )
 #define nullstr     (G_misc.nullstr    )
@@ -444,10 +443,18 @@ static void exitshell(void) NORETURN;
  * much more efficient and portable.  (But hacking the kernel is so much
  * more fun than worrying about efficiency and portability. :-))
  */
-#define INT_OFF do { \
+#if DEBUG_INTONOFF
+# define INT_OFF do { \
+       TRACE(("%s:%d INT_OFF(%d)\n", __func__, __LINE__, suppress_int)); \
        suppress_int++; \
        barrier(); \
 } while (0)
+#else
+# define INT_OFF do { \
+       suppress_int++; \
+       barrier(); \
+} while (0)
+#endif
 
 /*
  * Called to raise an exception.  Since C doesn't include exceptions, we
@@ -474,7 +481,7 @@ raise_exception(int e)
 #endif
 
 /*
- * Called from trap.c when a SIGINT is received.  (If the user specifies
+ * Called when a SIGINT is received.  (If the user specifies
  * that SIGINT is to be trapped or ignored using the trap builtin, then
  * this routine is not called.)  Suppressint is nonzero when interrupts
  * are held using the INT_OFF macro.  (The test for iflag is just
@@ -484,26 +491,20 @@ static void raise_interrupt(void) NORETURN;
 static void
 raise_interrupt(void)
 {
-       int ex_type;
-
        pending_int = 0;
        /* Signal is not automatically unmasked after it is raised,
         * do it ourself - unmask all signals */
        sigprocmask_allsigs(SIG_UNBLOCK);
        /* pending_sig = 0; - now done in signal_handler() */
 
-       ex_type = EXSIG;
-       if (gotsig[SIGINT - 1] && !trap[SIGINT]) {
-               if (!(rootshell && iflag)) {
-                       /* Kill ourself with SIGINT */
-                       signal(SIGINT, SIG_DFL);
-                       raise(SIGINT);
-               }
-               ex_type = EXINT;
+       if (!(rootshell && iflag)) {
+               /* Kill ourself with SIGINT */
+               signal(SIGINT, SIG_DFL);
+               raise(SIGINT);
        }
        /* bash: ^C even on empty command line sets $? */
        exitstatus = SIGINT + 128;
-       raise_exception(ex_type);
+       raise_exception(EXINT);
        /* NOTREACHED */
 }
 #if DEBUG
@@ -521,7 +522,14 @@ int_on(void)
                raise_interrupt();
        }
 }
-#define INT_ON int_on()
+#if DEBUG_INTONOFF
+# define INT_ON do { \
+       TRACE(("%s:%d INT_ON(%d)\n", __func__, __LINE__, suppress_int-1)); \
+       int_on(); \
+} while (0)
+#else
+# define INT_ON int_on()
+#endif
 static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void
 force_int_on(void)
 {
@@ -852,13 +860,8 @@ trace_vprintf(const char *fmt, va_list va)
 {
        if (debug != 1)
                return;
-       if (DEBUG_TIME)
-               fprintf(tracefile, "%u ", (int) time(NULL));
-       if (DEBUG_PID)
-               fprintf(tracefile, "[%u] ", (int) getpid());
-       if (DEBUG_SIG)
-               fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pending_sig, pending_int, suppress_int);
        vfprintf(tracefile, fmt, va);
+       fprintf(tracefile, "\n");
 }
 
 static void
@@ -1251,11 +1254,10 @@ ash_vmsg_and_raise(int cond, const char *msg, va_list ap)
 {
 #if DEBUG
        if (msg) {
-               TRACE(("ash_vmsg_and_raise(%d, \"", cond));
+               TRACE(("ash_vmsg_and_raise(%d):", cond));
                TRACEV((msg, ap));
-               TRACE(("\") pid=%d\n", getpid()));
        } else
-               TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid()));
+               TRACE(("ash_vmsg_and_raise(%d):NULL\n", cond));
        if (msg)
 #endif
                ash_vmsg(msg, ap);
@@ -1401,7 +1403,6 @@ struct globals_memstack {
        char *g_stacknxt; // = stackbase.space;
        char *sstrend; // = stackbase.space + MINSIZE;
        size_t g_stacknleft; // = MINSIZE;
-       int    herefd; // = -1;
        struct stack_block stackbase;
 };
 extern struct globals_memstack *const ash_ptr_to_globals_memstack;
@@ -1410,7 +1411,6 @@ extern struct globals_memstack *const ash_ptr_to_globals_memstack;
 #define g_stacknxt   (G_memstack.g_stacknxt  )
 #define sstrend      (G_memstack.sstrend     )
 #define g_stacknleft (G_memstack.g_stacknleft)
-#define herefd       (G_memstack.herefd      )
 #define stackbase    (G_memstack.stackbase   )
 #define INIT_G_memstack() do { \
        (*(struct globals_memstack**)&ash_ptr_to_globals_memstack) = xzalloc(sizeof(G_memstack)); \
@@ -1419,7 +1419,6 @@ extern struct globals_memstack *const ash_ptr_to_globals_memstack;
        g_stacknxt = stackbase.space; \
        g_stacknleft = MINSIZE; \
        sstrend = stackbase.space + MINSIZE; \
-       herefd = -1; \
 } while (0)
 
 
@@ -1496,12 +1495,10 @@ sstrdup(const char *p)
        return memcpy(stalloc(len), p, len);
 }
 
-static void
+static inline void
 grabstackblock(size_t len)
 {
-       len = SHELL_ALIGN(len);
-       g_stacknxt += len;
-       g_stacknleft -= len;
+       stalloc(len);
 }
 
 static void
@@ -1607,10 +1604,6 @@ static void *
 growstackstr(void)
 {
        size_t len = stackblocksize();
-       if (herefd >= 0 && len >= 1024) {
-               full_write(herefd, stackblock(), len);
-               return stackblock();
-       }
        growstackblock();
        return (char *)stackblock() + len;
 }
@@ -1960,7 +1953,6 @@ struct redirtab;
 struct globals_var {
        struct shparam shellparam;      /* $@ current positional parameters */
        struct redirtab *redirlist;
-       int g_nullredirs;
        int preverrout_fd;   /* save fd2 before print debug if xflag is set. */
        struct var *vartab[VTABSIZE];
        struct var varinit[ARRAY_SIZE(varinit_data)];
@@ -1969,7 +1961,6 @@ extern struct globals_var *const ash_ptr_to_globals_var;
 #define G_var (*ash_ptr_to_globals_var)
 #define shellparam    (G_var.shellparam   )
 //#define redirlist     (G_var.redirlist    )
-#define g_nullredirs  (G_var.g_nullredirs )
 #define preverrout_fd (G_var.preverrout_fd)
 #define vartab        (G_var.vartab       )
 #define varinit       (G_var.varinit      )
@@ -2030,7 +2021,7 @@ extern struct globals_var *const ash_ptr_to_globals_var;
 static void FAST_FUNC
 getoptsreset(const char *value)
 {
-       shellparam.optind = number(value);
+       shellparam.optind = number(value) ?: 1;
        shellparam.optoff = -1;
 }
 #endif
@@ -2198,6 +2189,7 @@ setvareq(char *s, int flags)
                        if (flags & VNOSAVE)
                                free(s);
                        n = vp->var_text;
+                       exitstatus = 1;
                        ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
                }
 
@@ -2269,32 +2261,6 @@ setvar0(const char *name, const char *val)
        setvar(name, val, 0);
 }
 
-#if ENABLE_ASH_GETOPTS
-/*
- * Safe version of setvar, returns 1 on success 0 on failure.
- */
-static int
-setvarsafe(const char *name, const char *val, int flags)
-{
-       int err;
-       volatile int saveint;
-       struct jmploc *volatile savehandler = exception_handler;
-       struct jmploc jmploc;
-
-       SAVE_INT(saveint);
-       if (setjmp(jmploc.loc))
-               err = 1;
-       else {
-               exception_handler = &jmploc;
-               setvar(name, val, flags);
-               err = 0;
-       }
-       exception_handler = savehandler;
-       RESTORE_INT(saveint);
-       return err;
-}
-#endif
-
 /*
  * Unset the specified variable.
  */
@@ -2629,7 +2595,7 @@ setpwd(const char *val, int setold)
 static void hashcd(void);
 
 /*
- * Actually do the chdir.  We also call hashcd to let the routines in exec.c
+ * Actually do the chdir.  We also call hashcd to let other routines
  * know that the current directory has changed.
  */
 static int
@@ -2677,7 +2643,7 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        if (!dest)
                dest = nullstr;
        if (*dest == '/')
-               goto step7;
+               goto step6;
        if (*dest == '.') {
                c = dest[1];
  dotdot:
@@ -2694,13 +2660,7 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        if (!*dest)
                dest = ".";
        path = bltinlookup("CDPATH");
-       if (!path) {
- step6:
- step7:
-               p = dest;
-               goto docd;
-       }
-       do {
+       while (path) {
                c = *path;
                p = path_advance(&path, dest);
                if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
@@ -2709,9 +2669,15 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
  docd:
                        if (!docd(p, flags))
                                goto out;
-                       break;
+                       goto err;
                }
-       } while (path);
+       }
+
+ step6:
+       p = dest;
+       goto docd;
+
+ err:
        ash_msg_and_raise_error("can't cd to %s", dest);
        /* NOTREACHED */
  out:
@@ -3357,8 +3323,6 @@ unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 #endif /* ASH_ALIAS */
 
 
-/* ============ jobs.c */
-
 /* Mode argument to forkshell.  Don't change FORK_FG or FORK_BG. */
 #define FORK_FG    0
 #define FORK_BG    1
@@ -3436,7 +3400,14 @@ ignoresig(int signo)
 static void
 signal_handler(int signo)
 {
+       if (signo == SIGCHLD) {
+               got_sigchld = 1;
+               if (!trap[SIGCHLD])
+                       return;
+       }
+
        gotsig[signo - 1] = 1;
+       pending_sig = signo;
 
        if (signo == SIGINT && !trap[SIGINT]) {
                if (!suppress_int) {
@@ -3444,8 +3415,6 @@ signal_handler(int signo)
                        raise_interrupt(); /* does not return */
                }
                pending_int = 1;
-       } else {
-               pending_sig = signo;
        }
 }
 
@@ -3503,6 +3472,9 @@ setsignal(int signo)
 //whereas we have to restore it to what shell got on entry
 //from the parent. See comment above
 
+       if (signo == SIGCHLD)
+               new_act = S_CATCH;
+
        t = &sigmode[signo - 1];
        cur_act = *t;
        if (cur_act == 0) {
@@ -3552,10 +3524,6 @@ setsignal(int signo)
 #define CUR_RUNNING 1
 #define CUR_STOPPED 0
 
-/* mode flags for dowait */
-#define DOWAIT_NONBLOCK WNOHANG
-#define DOWAIT_BLOCK    0
-
 #if JOBS
 /* pgrp of shell on invocation */
 static int initialpgrp; //references:2
@@ -3773,12 +3741,12 @@ setjobctl(int on)
                                if (--fd < 0)
                                        goto out;
                }
+               /* fd is a tty at this point */
                fd = fcntl(fd, F_DUPFD, 10);
-               if (ofd >= 0)
+               if (ofd >= 0) /* if it is "/dev/tty", close. If 0/1/2, dont */
                        close(ofd);
                if (fd < 0)
-                       goto out;
-               /* fd is a tty at this point */
+                       goto out; /* F_DUPFD failed */
                close_on_exec_on(fd);
                while (1) { /* while we are in the background */
                        pgrp = tcgetpgrp(fd);
@@ -3976,27 +3944,88 @@ sprint_status48(char *s, int status, int sigonly)
 }
 
 static int
-dowait(int wait_flags, struct job *job)
+wait_block_or_sig(int *status)
+{
+       int pid;
+
+       do {
+               sigset_t mask;
+
+               /* Poll all children for changes in their state */
+               got_sigchld = 0;
+               /* if job control is active, accept stopped processes too */
+               pid = waitpid(-1, status, doing_jobctl ? (WNOHANG|WUNTRACED) : WNOHANG);
+               if (pid != 0)
+                       break; /* Error (e.g. EINTR, ECHILD) or pid */
+
+               /* Children exist, but none are ready. Sleep until interesting signal */
+#if 1
+               sigfillset(&mask);
+               sigprocmask(SIG_SETMASK, &mask, &mask);
+               while (!got_sigchld && !pending_sig)
+                       sigsuspend(&mask);
+               sigprocmask(SIG_SETMASK, &mask, NULL);
+#else /* unsafe: a signal can set pending_sig after check, but before pause() */
+               while (!got_sigchld && !pending_sig)
+                       pause();
+#endif
+
+               /* If it was SIGCHLD, poll children again */
+       } while (got_sigchld);
+
+       return pid;
+}
+
+#define DOWAIT_NONBLOCK 0
+#define DOWAIT_BLOCK    1
+#define DOWAIT_BLOCK_OR_SIG 2
+
+static int
+dowait(int block, struct job *job)
 {
        int pid;
        int status;
        struct job *jp;
-       struct job *thisjob;
+       struct job *thisjob = NULL;
 
-       TRACE(("dowait(0x%x) called\n", wait_flags));
+       TRACE(("dowait(0x%x) called\n", block));
 
-       /* Do a wait system call. If job control is compiled in, we accept
-        * stopped processes. wait_flags may have WNOHANG, preventing blocking.
-        * NB: _not_ safe_waitpid, we need to detect EINTR */
-       if (doing_jobctl)
-               wait_flags |= WUNTRACED;
-       pid = waitpid(-1, &status, wait_flags);
+       /* It's wrong to call waitpid() outside of INT_OFF region:
+        * signal can arrive just after syscall return and handler can
+        * longjmp away, losing stop/exit notification processing.
+        * Thus, for "jobs" builtin, and for waiting for a fg job,
+        * we call waitpid() (blocking or non-blocking) inside INT_OFF.
+        *
+        * However, for "wait" builtin it is wrong to simply call waitpid()
+        * in INT_OFF region: "wait" needs to wait for any running job
+        * to change state, but should exit on any trap too.
+        * In INT_OFF region, a signal just before syscall entry can set
+        * pending_sig variables, but we can't check them, and we would
+        * either enter a sleeping waitpid() (BUG), or need to busy-loop.
+        *
+        * Because of this, we run inside INT_OFF, but use a special routine
+        * which combines waitpid() and sigsuspend().
+        * This is the reason why we need to have a handler for SIGCHLD:
+        * SIG_DFL handler does not wake sigsuspend().
+        */
+       INT_OFF;
+       if (block == DOWAIT_BLOCK_OR_SIG) {
+               pid = wait_block_or_sig(&status);
+       } else {
+               int wait_flags = 0;
+               if (block == DOWAIT_NONBLOCK)
+                       wait_flags = WNOHANG;
+               /* if job control is active, accept stopped processes too */
+               if (doing_jobctl)
+                       wait_flags |= WUNTRACED;
+               /* NB: _not_ safe_waitpid, we need to detect EINTR */
+               pid = waitpid(-1, &status, wait_flags);
+       }
        TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n",
                                pid, status, errno, strerror(errno)));
        if (pid <= 0)
-               return pid;
+               goto out;
 
-       INT_OFF;
        thisjob = NULL;
        for (jp = curjob; jp; jp = jp->prev_job) {
                int jobstate;
@@ -4068,15 +4097,6 @@ dowait(int wait_flags, struct job *job)
        return pid;
 }
 
-static int
-blocking_wait_with_raise_on_sig(void)
-{
-       pid_t pid = dowait(DOWAIT_BLOCK, NULL);
-       if (pid <= 0 && pending_sig)
-               raise_exception(EXSIG);
-       return pid;
-}
-
 #if JOBS
 static void
 showjob(struct job *jp, int mode)
@@ -4246,9 +4266,6 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
        int retval;
        struct job *jp;
 
-       if (pending_sig)
-               raise_exception(EXSIG);
-
        nextopt(nullstr);
        retval = 0;
 
@@ -4265,21 +4282,20 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
                                jp->waited = 1;
                                jp = jp->prev_job;
                        }
-                       blocking_wait_with_raise_on_sig();
        /* man bash:
         * "When bash is waiting for an asynchronous command via
         * the wait builtin, the reception of a signal for which a trap
         * has been set will cause the wait builtin to return immediately
         * with an exit status greater than 128, immediately after which
         * the trap is executed."
-        *
-        * blocking_wait_with_raise_on_sig raises signal handlers
-        * if it gets no pid (pid < 0). However,
-        * if child sends us a signal *and immediately exits*,
-        * blocking_wait_with_raise_on_sig gets pid > 0
-        * and does not handle pending_sig. Check this case: */
+        */
+                       dowait(DOWAIT_BLOCK_OR_SIG, NULL);
+       /* if child sends us a signal *and immediately exits*,
+        * dowait() returns pid > 0. Check this case,
+        * not "if (dowait() < 0)"!
+        */
                        if (pending_sig)
-                               raise_exception(EXSIG);
+                               goto sigout;
                }
        }
 
@@ -4299,8 +4315,11 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
                        job = getjob(*argv, 0);
                }
                /* loop until process terminated or stopped */
-               while (job->state == JOBRUNNING)
-                       blocking_wait_with_raise_on_sig();
+               while (job->state == JOBRUNNING) {
+                       dowait(DOWAIT_BLOCK_OR_SIG, NULL);
+                       if (pending_sig)
+                               goto sigout;
+               }
                job->waited = 1;
                retval = getstatus(job);
  repeat: ;
@@ -4308,6 +4327,9 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
 
  ret:
        return retval;
+ sigout:
+       retval = 128 + pending_sig;
+       return retval;
 }
 
 static struct job *
@@ -4672,8 +4694,7 @@ commandtext(union node *n)
        STARTSTACKSTR(cmdnextc);
        cmdtxt(n);
        name = stackblock();
-       TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n",
-                       name, cmdnextc, cmdnextc));
+       TRACE(("commandtext: name %p, end %p\n", name, cmdnextc));
        return ckstrdup(name);
 }
 #endif /* JOBS */
@@ -4702,25 +4723,26 @@ clear_traps(void)
 {
        char **tp;
 
+       INT_OFF;
        for (tp = trap; tp < &trap[NSIG]; tp++) {
                if (*tp && **tp) {      /* trap not NULL or "" (SIG_IGN) */
-                       INT_OFF;
                        if (trap_ptr == trap)
                                free(*tp);
                        /* else: it "belongs" to trap_ptr vector, don't free */
                        *tp = NULL;
                        if ((tp - trap) != 0)
                                setsignal(tp - trap);
-                       INT_ON;
                }
        }
        may_have_traps = 0;
+       INT_ON;
 }
 
 /* Lives far away from here, needed for forkchild */
 static void closescript(void);
 
 /* Called after fork(), in child */
+/* jp and n are NULL when called by openhere() for heredoc support */
 static NOINLINE void
 forkchild(struct job *jp, union node *n, int mode)
 {
@@ -4852,6 +4874,7 @@ forkparent(struct job *jp, union node *n, int mode, pid_t pid)
 {
        TRACE(("In parent shell: child = %d\n", pid));
        if (!jp) {
+               /* jp is NULL when called by openhere() for heredoc support */
                while (jobless && dowait(DOWAIT_NONBLOCK, NULL) > 0)
                        continue;
                jobless++;
@@ -4885,6 +4908,7 @@ forkparent(struct job *jp, union node *n, int mode, pid_t pid)
        }
 }
 
+/* jp and n are NULL when called by openhere() for heredoc support */
 static int
 forkshell(struct job *jp, union node *n, int mode)
 {
@@ -5014,8 +5038,7 @@ stoppedjobs(void)
 }
 
 
-/* ============ redir.c
- *
+/*
  * Code for dealing with input/output redirection.
  */
 
@@ -5195,31 +5218,32 @@ openredirect(union node *redir)
 }
 
 /*
- * Copy a file descriptor to be >= to.  Returns -1
- * if the source file descriptor is closed, EMPTY if there are no unused
- * file descriptors left.
+ * Copy a file descriptor to be >= 10. Throws exception on error.
  */
-/* 0x800..00: bit to set in "to" to request dup2 instead of fcntl(F_DUPFD).
- * old code was doing close(to) prior to copyfd() to achieve the same */
-enum {
-       COPYFD_EXACT   = (int)~(INT_MAX),
-       COPYFD_RESTORE = (int)((unsigned)COPYFD_EXACT >> 1),
-};
 static int
-copyfd(int from, int to)
+savefd(int from)
 {
        int newfd;
+       int err;
 
-       if (to & COPYFD_EXACT) {
-               to &= ~COPYFD_EXACT;
-               /*if (from != to)*/
-                       newfd = dup2(from, to);
-       } else {
-               newfd = fcntl(from, F_DUPFD, to);
+       newfd = fcntl(from, F_DUPFD, 10);
+       err = newfd < 0 ? errno : 0;
+       if (err != EBADF) {
+               if (err)
+                       ash_msg_and_raise_error("%d: %m", from);
+               close(from);
+               fcntl(newfd, F_SETFD, FD_CLOEXEC);
        }
+
+       return newfd;
+}
+static int
+dup2_or_raise(int from, int to)
+{
+       int newfd;
+
+       newfd = (from != to) ? dup2(from, to) : to;
        if (newfd < 0) {
-               if (errno == EMFILE)
-                       return EMPTY;
                /* Happens when source fd is not open: try "echo >&99" */
                ash_msg_and_raise_error("%d: %m", from);
        }
@@ -5232,11 +5256,13 @@ struct two_fd_t {
 };
 struct redirtab {
        struct redirtab *next;
-       int nullredirs;
        int pair_count;
        struct two_fd_t two_fd[];
 };
 #define redirlist (G_var.redirlist)
+enum {
+       COPYFD_RESTORE = (int)~(INT_MAX),
+};
 
 static int
 need_to_remember(struct redirtab *rp, int fd)
@@ -5311,7 +5337,6 @@ redirect(union node *redir, int flags)
        int newfd;
        int copied_fd2 = -1;
 
-       g_nullredirs++;
        if (!redir) {
                return;
        }
@@ -5333,8 +5358,6 @@ redirect(union node *redir, int flags)
                sv->next = redirlist;
                sv->pair_count = sv_pos;
                redirlist = sv;
-               sv->nullredirs = g_nullredirs - 1;
-               g_nullredirs = 0;
                while (sv_pos > 0) {
                        sv_pos--;
                        sv->two_fd[sv_pos].orig = sv->two_fd[sv_pos].copy = EMPTY;
@@ -5413,10 +5436,10 @@ redirect(union node *redir, int flags)
                                if (fd != -1)
                                        close(fd);
                        } else {
-                               copyfd(redir->ndup.dupfd, fd | COPYFD_EXACT);
+                               dup2_or_raise(redir->ndup.dupfd, fd);
                        }
                } else if (fd != newfd) { /* move newfd to fd */
-                       copyfd(newfd, fd | COPYFD_EXACT);
+                       dup2_or_raise(newfd, fd);
 #if ENABLE_ASH_BASH_COMPAT
                        if (!(redir->nfile.type == NTO2 && fd == 2))
 #endif
@@ -5446,7 +5469,7 @@ popredir(int drop, int restore)
        struct redirtab *rp;
        int i;
 
-       if (--g_nullredirs >= 0 || redirlist == NULL)
+       if (redirlist == NULL)
                return;
        INT_OFF;
        rp = redirlist;
@@ -5462,13 +5485,12 @@ popredir(int drop, int restore)
                        if (!drop || (restore && (copy & COPYFD_RESTORE))) {
                                copy &= ~COPYFD_RESTORE;
                                /*close(fd);*/
-                               copyfd(copy, fd | COPYFD_EXACT);
+                               dup2_or_raise(copy, fd);
                        }
                        close(copy & ~COPYFD_RESTORE);
                }
        }
        redirlist = rp->next;
-       g_nullredirs = rp->nullredirs;
        free(rp);
        INT_ON;
 }
@@ -5477,20 +5499,6 @@ popredir(int drop, int restore)
  * Undo all redirections.  Called on error or interrupt.
  */
 
-/*
- * Discard all saved file descriptors.
- */
-static void
-clearredir(int drop)
-{
-       for (;;) {
-               g_nullredirs = 0;
-               if (!redirlist)
-                       break;
-               popredir(drop, /*restore:*/ 0);
-       }
-}
-
 static int
 redirectsafe(union node *redir, int flags)
 {
@@ -5547,6 +5555,17 @@ ash_arith(const char *s)
 #define EXP_TILDE       0x2     /* do normal tilde expansion */
 #define EXP_VARTILDE    0x4     /* expand tildes in an assignment */
 #define EXP_REDIR       0x8     /* file glob for a redirection (1 match only) */
+/* ^^^^^^^^^^^^^^ this is meant to support constructs such as "cmd >file*.txt"
+ * POSIX says for this case:
+ *  Pathname expansion shall not be performed on the word by a
+ *  non-interactive shell; an interactive shell may perform it, but shall
+ *  do so only when the expansion would result in one word.
+ * Currently, our code complies to the above rule by never globbing
+ * redirection filenames.
+ * Bash performs globbing, unless it is non-interactive and in POSIX mode.
+ * (this means that on a typical Linux distro, bash almost always
+ * performs globbing, and thus diverges from what we do).
+ */
 #define EXP_CASE        0x10    /* keeps quotes around for CASE pattern */
 #define EXP_QPAT        0x20    /* pattern in quoted parameter expansion */
 #define EXP_VARTILDE2   0x40    /* expand tildes after colons only */
@@ -5612,6 +5631,119 @@ cvtnum(arith_t num)
        return len;
 }
 
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list.  The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ */
+static void
+ifsbreakup(char *string, struct arglist *arglist)
+{
+       struct ifsregion *ifsp;
+       struct strlist *sp;
+       char *start;
+       char *p;
+       char *q;
+       const char *ifs, *realifs;
+       int ifsspc;
+       int nulonly;
+
+       start = string;
+       if (ifslastp != NULL) {
+               ifsspc = 0;
+               nulonly = 0;
+               realifs = ifsset() ? ifsval() : defifs;
+               ifsp = &ifsfirst;
+               do {
+                       p = string + ifsp->begoff;
+                       nulonly = ifsp->nulonly;
+                       ifs = nulonly ? nullstr : realifs;
+                       ifsspc = 0;
+                       while (p < string + ifsp->endoff) {
+                               q = p;
+                               if ((unsigned char)*p == CTLESC)
+                                       p++;
+                               if (!strchr(ifs, *p)) {
+                                       p++;
+                                       continue;
+                               }
+                               if (!nulonly)
+                                       ifsspc = (strchr(defifs, *p) != NULL);
+                               /* Ignore IFS whitespace at start */
+                               if (q == start && ifsspc) {
+                                       p++;
+                                       start = p;
+                                       continue;
+                               }
+                               *q = '\0';
+                               sp = stzalloc(sizeof(*sp));
+                               sp->text = start;
+                               *arglist->lastp = sp;
+                               arglist->lastp = &sp->next;
+                               p++;
+                               if (!nulonly) {
+                                       for (;;) {
+                                               if (p >= string + ifsp->endoff) {
+                                                       break;
+                                               }
+                                               q = p;
+                                               if ((unsigned char)*p == CTLESC)
+                                                       p++;
+                                               if (strchr(ifs, *p) == NULL) {
+                                                       p = q;
+                                                       break;
+                                               }
+                                               if (strchr(defifs, *p) == NULL) {
+                                                       if (ifsspc) {
+                                                               p++;
+                                                               ifsspc = 0;
+                                                       } else {
+                                                               p = q;
+                                                               break;
+                                                       }
+                                               } else
+                                                       p++;
+                                       }
+                               }
+                               start = p;
+                       } /* while */
+                       ifsp = ifsp->next;
+               } while (ifsp != NULL);
+               if (nulonly)
+                       goto add;
+       }
+
+       if (!*start)
+               return;
+
+ add:
+       sp = stzalloc(sizeof(*sp));
+       sp->text = start;
+       *arglist->lastp = sp;
+       arglist->lastp = &sp->next;
+}
+
+static void
+ifsfree(void)
+{
+       struct ifsregion *p = ifsfirst.next;
+
+       if (!p)
+               goto out;
+
+       INT_OFF;
+       do {
+               struct ifsregion *ifsp;
+               ifsp = p->next;
+               free(p);
+               p = ifsp;
+       } while (p);
+       ifsfirst.next = NULL;
+       INT_ON;
+ out:
+       ifslastp = NULL;
+}
+
 static size_t
 esclen(const char *start, const char *p)
 {
@@ -5891,42 +6023,47 @@ static int evaltree(union node *, int);
 static void FAST_FUNC
 evalbackcmd(union node *n, struct backcmd *result)
 {
-       int saveherefd;
+       int pip[2];
+       struct job *jp;
 
        result->fd = -1;
        result->buf = NULL;
        result->nleft = 0;
        result->jp = NULL;
-       if (n == NULL)
+       if (n == NULL) {
                goto out;
+       }
 
-       saveherefd = herefd;
-       herefd = -1;
-
-       {
-               int pip[2];
-               struct job *jp;
-
-               if (pipe(pip) < 0)
-                       ash_msg_and_raise_error("pipe call failed");
-               jp = makejob(/*n,*/ 1);
-               if (forkshell(jp, n, FORK_NOJOB) == 0) {
-                       FORCE_INT_ON;
-                       close(pip[0]);
-                       if (pip[1] != 1) {
-                               /*close(1);*/
-                               copyfd(pip[1], 1 | COPYFD_EXACT);
-                               close(pip[1]);
-                       }
-                       eflag = 0;
-                       evaltree(n, EV_EXIT); /* actually evaltreenr... */
-                       /* NOTREACHED */
+       if (pipe(pip) < 0)
+               ash_msg_and_raise_error("pipe call failed");
+       jp = makejob(/*n,*/ 1);
+       if (forkshell(jp, n, FORK_NOJOB) == 0) {
+               /* child */
+               FORCE_INT_ON;
+               close(pip[0]);
+               if (pip[1] != 1) {
+                       /*close(1);*/
+                       dup2_or_raise(pip[1], 1);
+                       close(pip[1]);
                }
-               close(pip[1]);
-               result->fd = pip[0];
-               result->jp = jp;
+/* TODO: eflag clearing makes the following not abort:
+ *  ash -c 'set -e; z=$(false;echo foo); echo $z'
+ * which is what bash does (unless it is in POSIX mode).
+ * dash deleted "eflag = 0" line in the commit
+ *  Date: Mon, 28 Jun 2010 17:11:58 +1000
+ *  [EVAL] Don't clear eflag in evalbackcmd
+ * For now, preserve bash-like behavior, it seems to be somewhat more useful:
+ */
+               eflag = 0;
+               ifsfree();
+               evaltree(n, EV_EXIT); /* actually evaltreenr... */
+               /* NOTREACHED */
        }
-       herefd = saveherefd;
+       /* parent */
+       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));
@@ -6333,7 +6470,6 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
        char *str;
        IF_ASH_BASH_COMPAT(char *repl = NULL;)
        IF_ASH_BASH_COMPAT(int pos, len, orig_len;)
-       int saveherefd = herefd;
        int amount, resetloc;
        IF_ASH_BASH_COMPAT(int workloc;)
        int zero;
@@ -6342,12 +6478,10 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
        //bb_error_msg("subevalvar(p:'%s',varname:'%s',strloc:%d,subtype:%d,startloc:%d,varflags:%x,quotes:%d)",
        //              p, varname, strloc, subtype, startloc, varflags, quotes);
 
-       herefd = -1;
        argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ?
                        (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0),
                        var_str_list);
        STPUTC('\0', expdest);
-       herefd = saveherefd;
        argbackq = saveargbackq;
        startp = (char *)stackblock() + startloc;
 
@@ -6769,6 +6903,10 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
 
        varflags = (unsigned char) *p++;
        subtype = varflags & VSTYPE;
+
+       if (!subtype)
+               raise_error_syntax("bad substitution");
+
        quoted = flag & EXP_QUOTED;
        var = p;
        easy = (!quoted || (*var == '@' && shellparam.nparam));
@@ -6887,116 +7025,6 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
        return p;
 }
 
-/*
- * Break the argument string into pieces based upon IFS and add the
- * strings to the argument list.  The regions of the string to be
- * searched for IFS characters have been stored by recordregion.
- */
-static void
-ifsbreakup(char *string, struct arglist *arglist)
-{
-       struct ifsregion *ifsp;
-       struct strlist *sp;
-       char *start;
-       char *p;
-       char *q;
-       const char *ifs, *realifs;
-       int ifsspc;
-       int nulonly;
-
-       start = string;
-       if (ifslastp != NULL) {
-               ifsspc = 0;
-               nulonly = 0;
-               realifs = ifsset() ? ifsval() : defifs;
-               ifsp = &ifsfirst;
-               do {
-                       p = string + ifsp->begoff;
-                       nulonly = ifsp->nulonly;
-                       ifs = nulonly ? nullstr : realifs;
-                       ifsspc = 0;
-                       while (p < string + ifsp->endoff) {
-                               q = p;
-                               if ((unsigned char)*p == CTLESC)
-                                       p++;
-                               if (!strchr(ifs, *p)) {
-                                       p++;
-                                       continue;
-                               }
-                               if (!nulonly)
-                                       ifsspc = (strchr(defifs, *p) != NULL);
-                               /* Ignore IFS whitespace at start */
-                               if (q == start && ifsspc) {
-                                       p++;
-                                       start = p;
-                                       continue;
-                               }
-                               *q = '\0';
-                               sp = stzalloc(sizeof(*sp));
-                               sp->text = start;
-                               *arglist->lastp = sp;
-                               arglist->lastp = &sp->next;
-                               p++;
-                               if (!nulonly) {
-                                       for (;;) {
-                                               if (p >= string + ifsp->endoff) {
-                                                       break;
-                                               }
-                                               q = p;
-                                               if ((unsigned char)*p == CTLESC)
-                                                       p++;
-                                               if (strchr(ifs, *p) == NULL) {
-                                                       p = q;
-                                                       break;
-                                               }
-                                               if (strchr(defifs, *p) == NULL) {
-                                                       if (ifsspc) {
-                                                               p++;
-                                                               ifsspc = 0;
-                                                       } else {
-                                                               p = q;
-                                                               break;
-                                                       }
-                                               } else
-                                                       p++;
-                                       }
-                               }
-                               start = p;
-                       } /* while */
-                       ifsp = ifsp->next;
-               } while (ifsp != NULL);
-               if (nulonly)
-                       goto add;
-       }
-
-       if (!*start)
-               return;
-
- add:
-       sp = stzalloc(sizeof(*sp));
-       sp->text = start;
-       *arglist->lastp = sp;
-       arglist->lastp = &sp->next;
-}
-
-static void
-ifsfree(void)
-{
-       struct ifsregion *p;
-
-       INT_OFF;
-       p = ifsfirst.next;
-       do {
-               struct ifsregion *ifsp;
-               ifsp = p->next;
-               free(p);
-               p = ifsp;
-       } while (p);
-       ifslastp = NULL;
-       ifsfirst.next = NULL;
-       INT_ON;
-}
-
 /*
  * Add a file name to the list.
  */
@@ -7036,6 +7064,21 @@ expandmeta(struct strlist *str /*, int flag*/)
 
                if (fflag)
                        goto nometa;
+
+               /* Avoid glob() (and thus, stat() et al) for words like "echo" */
+               p = str->text;
+               while (*p) {
+                       if (*p == '*')
+                               goto need_glob;
+                       if (*p == '?')
+                               goto need_glob;
+                       if (*p == '[')
+                               goto need_glob;
+                       p++;
+               }
+               goto nometa;
+
+ need_glob:
                INT_OFF;
                p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
 // GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match
@@ -7335,15 +7378,14 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
 
        argbackq = arg->narg.backquote;
        STARTSTACKSTR(expdest);
-       ifsfirst.next = NULL;
-       ifslastp = NULL;
        TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag));
        argstr(arg->narg.text, flag,
                        /* var_str_list: */ arglist ? arglist->list : NULL);
        p = _STPUTC('\0', expdest);
        expdest = p - 1;
        if (arglist == NULL) {
-               return;                 /* here document expanded */
+               /* here document expanded */
+               goto out;
        }
        p = grabstackstr(p);
        TRACE(("expandarg: p:'%s'\n", p));
@@ -7366,13 +7408,14 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
                *exparg.lastp = sp;
                exparg.lastp = &sp->next;
        }
-       if (ifsfirst.next)
-               ifsfree();
        *exparg.lastp = NULL;
        if (exparg.list) {
                *arglist->lastp = exparg.list;
                arglist->lastp = exparg.lastp;
        }
+
+ out:
+       ifsfree();
 }
 
 /*
@@ -7381,7 +7424,6 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
 static void
 expandhere(union node *arg, int fd)
 {
-       herefd = fd;
        expandarg(arg, (struct arglist *)NULL, EXP_QUOTED);
        full_write(fd, stackblock(), expdest - (char *)stackblock());
 }
@@ -7407,10 +7449,10 @@ casematch(union node *pattern, char *val)
        setstackmark(&smark);
        argbackq = pattern->narg.backquote;
        STARTSTACKSTR(expdest);
-       ifslastp = NULL;
        argstr(pattern->narg.text, EXP_TILDE | EXP_CASE,
                        /* var_str_list: */ NULL);
        STACKSTRNUL(expdest);
+       ifsfree();
        result = patmatch(stackblock(), val);
        popstackmark(&smark);
        return result;
@@ -7511,13 +7553,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
 #else
        execve(cmd, argv, envp);
 #endif
-       if (cmd == (char*) bb_busybox_exec_path) {
-               /* We already visited ENOEXEC branch below, don't do it again */
-//TODO: try execve(initial_argv0_of_shell, argv, envp) before giving up?
-               free(argv);
-               return;
-       }
-       if (errno == ENOEXEC) {
+       if (cmd != (char*) bb_busybox_exec_path && errno == ENOEXEC) {
                /* Run "cmd" as a shell script:
                 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
                 * "If the execve() function fails with ENOEXEC, the shell
@@ -7534,19 +7570,13 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
                 * message and exit code 126. For one, this prevents attempts
                 * to interpret foreign ELF binaries as shell scripts.
                 */
-               char **ap;
-               char **new;
-
-               for (ap = argv; *ap; ap++)
-                       continue;
-               new = ckmalloc((ap - argv + 2) * sizeof(new[0]));
-               new[0] = (char*) "ash";
-               new[1] = cmd;
-               ap = new + 2;
-               while ((*ap++ = *++argv) != NULL)
-                       continue;
+               argv[0] = cmd;
                cmd = (char*) bb_busybox_exec_path;
-               argv = new;
+               /* NB: this is only possible because all callers of shellexec()
+                * ensure that the argv[-1] slot exists!
+                */
+               argv--;
+               argv[0] = (char*) "ash";
                goto repeat;
        }
 }
@@ -7554,6 +7584,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
 /*
  * Exec a program.  Never returns.  If you change this routine, you may
  * have to change the find_command routine as well.
+ * argv[-1] must exist and be writable! See tryexec() for why.
  */
 static void shellexec(char **, const char *, int) NORETURN;
 static void
@@ -7565,7 +7596,6 @@ shellexec(char **argv, const char *path, int idx)
        int exerrno;
        int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */
 
-       clearredir(/*drop:*/ 1);
        envp = listvars(VEXPORT, VUNSET, /*end:*/ NULL);
        if (strchr(argv[0], '/') != NULL
 #if ENABLE_FEATURE_SH_STANDALONE
@@ -7609,7 +7639,7 @@ shellexec(char **argv, const char *path, int idx)
        exitstatus = exerrno;
        TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n",
                argv[0], e, suppress_int));
-       ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found"));
+       ash_msg_and_raise(EXEXIT, "%s: %s", argv[0], errmsg(e, "not found"));
        /* NOTREACHED */
 }
 
@@ -7881,49 +7911,86 @@ enum {
 };
 typedef smallint token_id_t;
 
-/* first char is indicating which tokens mark the end of a list */
+/* Nth bit indicates if token marks the end of a list */
+enum {
+       tokendlist = 0
+       /*  0 */ | (1u << TEOF)
+       /*  1 */ | (0u << TNL)
+       /*  2 */ | (0u << TREDIR)
+       /*  3 */ | (0u << TWORD)
+       /*  4 */ | (0u << TSEMI)
+       /*  5 */ | (0u << TBACKGND)
+       /*  6 */ | (0u << TAND)
+       /*  7 */ | (0u << TOR)
+       /*  8 */ | (0u << TPIPE)
+       /*  9 */ | (0u << TLP)
+       /* 10 */ | (1u << TRP)
+       /* 11 */ | (1u << TENDCASE)
+       /* 12 */ | (1u << TENDBQUOTE)
+       /* 13 */ | (0u << TNOT)
+       /* 14 */ | (0u << TCASE)
+       /* 15 */ | (1u << TDO)
+       /* 16 */ | (1u << TDONE)
+       /* 17 */ | (1u << TELIF)
+       /* 18 */ | (1u << TELSE)
+       /* 19 */ | (1u << TESAC)
+       /* 20 */ | (1u << TFI)
+       /* 21 */ | (0u << TFOR)
+#if ENABLE_ASH_BASH_COMPAT
+       /* 22 */ | (0u << TFUNCTION)
+#endif
+       /* 23 */ | (0u << TIF)
+       /* 24 */ | (0u << TIN)
+       /* 25 */ | (1u << TTHEN)
+       /* 26 */ | (0u << TUNTIL)
+       /* 27 */ | (0u << TWHILE)
+       /* 28 */ | (0u << TBEGIN)
+       /* 29 */ | (1u << TEND)
+       , /* thus far 29 bits used */
+};
+
 static const char *const tokname_array[] = {
-       "\1end of file",
-       "\0newline",
-       "\0redirection",
-       "\0word",
-       "\0;",
-       "\0&",
-       "\0&&",
-       "\0||",
-       "\0|",
-       "\0(",
-       "\1)",
-       "\1;;",
-       "\1`",
+       "end of file",
+       "newline",
+       "redirection",
+       "word",
+       ";",
+       "&",
+       "&&",
+       "||",
+       "|",
+       "(",
+       ")",
+       ";;",
+       "`",
 #define KWDOFFSET 13
        /* the following are keywords */
-       "\0!",
-       "\0case",
-       "\1do",
-       "\1done",
-       "\1elif",
-       "\1else",
-       "\1esac",
-       "\1fi",
-       "\0for",
+       "!",
+       "case",
+       "do",
+       "done",
+       "elif",
+       "else",
+       "esac",
+       "fi",
+       "for",
 #if ENABLE_ASH_BASH_COMPAT
-       "\0function",
-#endif
-       "\0if",
-       "\0in",
-       "\1then",
-       "\0until",
-       "\0while",
-       "\0{",
-       "\1}",
+       "function",
+#endif
+       "if",
+       "in",
+       "then",
+       "until",
+       "while",
+       "{",
+       "}",
 };
 
 /* Wrapper around strcmp for qsort/bsearch/... */
 static int
 pstrcmp(const void *a, const void *b)
 {
-       return strcmp((char*) a, (*(char**) b) + 1);
+       return strcmp((char*)a, *(char**)b);
 }
 
 static const char *const *
@@ -8124,12 +8191,10 @@ commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 #endif
 
 
-/* ============ eval.c */
-
-static int funcblocksize;       /* size of structures in function */
-static int funcstringsize;      /* size of strings in node */
+/*static int funcblocksize;     // size of structures in function */
+/*static int funcstringsize;    // size of strings in node */
 static void *funcblock;         /* block to allocate function from */
-static char *funcstring;        /* block to allocate strings from */
+static char *funcstring_end;    /* end of block to allocate strings from */
 
 /* flags in argument to evaltree */
 #define EV_EXIT    01           /* exit after evaluating tree */
@@ -8167,71 +8232,72 @@ static const uint8_t nodesize[N_NUMBER] ALIGN1 = {
        [NNOT     ] = SHELL_ALIGN(sizeof(struct nnot)),
 };
 
-static void calcsize(union node *n);
+static int calcsize(int funcblocksize, union node *n);
 
-static void
-sizenodelist(struct nodelist *lp)
+static int
+sizenodelist(int funcblocksize, struct nodelist *lp)
 {
        while (lp) {
                funcblocksize += SHELL_ALIGN(sizeof(struct nodelist));
-               calcsize(lp->n);
+               funcblocksize = calcsize(funcblocksize, lp->n);
                lp = lp->next;
        }
+       return funcblocksize;
 }
 
-static void
-calcsize(union node *n)
+static int
+calcsize(int funcblocksize, union node *n)
 {
        if (n == NULL)
-               return;
+               return funcblocksize;
        funcblocksize += nodesize[n->type];
        switch (n->type) {
        case NCMD:
-               calcsize(n->ncmd.redirect);
-               calcsize(n->ncmd.args);
-               calcsize(n->ncmd.assign);
+               funcblocksize = calcsize(funcblocksize, n->ncmd.redirect);
+               funcblocksize = calcsize(funcblocksize, n->ncmd.args);
+               funcblocksize = calcsize(funcblocksize, n->ncmd.assign);
                break;
        case NPIPE:
-               sizenodelist(n->npipe.cmdlist);
+               funcblocksize = sizenodelist(funcblocksize, n->npipe.cmdlist);
                break;
        case NREDIR:
        case NBACKGND:
        case NSUBSHELL:
-               calcsize(n->nredir.redirect);
-               calcsize(n->nredir.n);
+               funcblocksize = calcsize(funcblocksize, n->nredir.redirect);
+               funcblocksize = calcsize(funcblocksize, n->nredir.n);
                break;
        case NAND:
        case NOR:
        case NSEMI:
        case NWHILE:
        case NUNTIL:
-               calcsize(n->nbinary.ch2);
-               calcsize(n->nbinary.ch1);
+               funcblocksize = calcsize(funcblocksize, n->nbinary.ch2);
+               funcblocksize = calcsize(funcblocksize, n->nbinary.ch1);
                break;
        case NIF:
-               calcsize(n->nif.elsepart);
-               calcsize(n->nif.ifpart);
-               calcsize(n->nif.test);
+               funcblocksize = calcsize(funcblocksize, n->nif.elsepart);
+               funcblocksize = calcsize(funcblocksize, n->nif.ifpart);
+               funcblocksize = calcsize(funcblocksize, n->nif.test);
                break;
        case NFOR:
-               funcstringsize += strlen(n->nfor.var) + 1;
-               calcsize(n->nfor.body);
-               calcsize(n->nfor.args);
+               funcblocksize += SHELL_ALIGN(strlen(n->nfor.var) + 1); /* was funcstringsize += ... */
+               funcblocksize = calcsize(funcblocksize, n->nfor.body);
+               funcblocksize = calcsize(funcblocksize, n->nfor.args);
                break;
        case NCASE:
-               calcsize(n->ncase.cases);
-               calcsize(n->ncase.expr);
+               funcblocksize = calcsize(funcblocksize, n->ncase.cases);
+               funcblocksize = calcsize(funcblocksize, n->ncase.expr);
                break;
        case NCLIST:
-               calcsize(n->nclist.body);
-               calcsize(n->nclist.pattern);
-               calcsize(n->nclist.next);
+               funcblocksize = calcsize(funcblocksize, n->nclist.body);
+               funcblocksize = calcsize(funcblocksize, n->nclist.pattern);
+               funcblocksize = calcsize(funcblocksize, n->nclist.next);
                break;
        case NDEFUN:
        case NARG:
-               sizenodelist(n->narg.backquote);
-               funcstringsize += strlen(n->narg.text) + 1;
-               calcsize(n->narg.next);
+               funcblocksize = sizenodelist(funcblocksize, n->narg.backquote);
+               funcblocksize += SHELL_ALIGN(strlen(n->narg.text) + 1); /* was funcstringsize += ... */
+               funcblocksize = calcsize(funcblocksize, n->narg.next);
                break;
        case NTO:
 #if ENABLE_ASH_BASH_COMPAT
@@ -8241,33 +8307,31 @@ calcsize(union node *n)
        case NFROM:
        case NFROMTO:
        case NAPPEND:
-               calcsize(n->nfile.fname);
-               calcsize(n->nfile.next);
+               funcblocksize = calcsize(funcblocksize, n->nfile.fname);
+               funcblocksize = calcsize(funcblocksize, n->nfile.next);
                break;
        case NTOFD:
        case NFROMFD:
-               calcsize(n->ndup.vname);
-               calcsize(n->ndup.next);
+               funcblocksize = calcsize(funcblocksize, n->ndup.vname);
+               funcblocksize = calcsize(funcblocksize, n->ndup.next);
        break;
        case NHERE:
        case NXHERE:
-               calcsize(n->nhere.doc);
-               calcsize(n->nhere.next);
+               funcblocksize = calcsize(funcblocksize, n->nhere.doc);
+               funcblocksize = calcsize(funcblocksize, n->nhere.next);
                break;
        case NNOT:
-               calcsize(n->nnot.com);
+               funcblocksize = calcsize(funcblocksize, n->nnot.com);
                break;
        };
+       return funcblocksize;
 }
 
 static char *
 nodeckstrdup(char *s)
 {
-       char *rtn = funcstring;
-
-       strcpy(funcstring, s);
-       funcstring += strlen(s) + 1;
-       return rtn;
+       funcstring_end -= SHELL_ALIGN(strlen(s) + 1);
+       return strcpy(funcstring_end, s);
 }
 
 static union node *copynode(union node *);
@@ -8391,15 +8455,13 @@ copyfunc(union node *n)
        struct funcnode *f;
        size_t blocksize;
 
-       funcblocksize = offsetof(struct funcnode, n);
-       funcstringsize = 0;
-       calcsize(n);
-       blocksize = funcblocksize;
-       f = ckmalloc(blocksize + funcstringsize);
+       /*funcstringsize = 0;*/
+       blocksize = offsetof(struct funcnode, n) + calcsize(0, n);
+       f = ckzalloc(blocksize /* + funcstringsize */);
        funcblock = (char *) f + offsetof(struct funcnode, n);
-       funcstring = (char *) f + blocksize;
+       funcstring_end = (char *) f + blocksize;
        copynode(n);
-       f->count = 0;
+       /* f->count = 0; - ckzalloc did it */
        return f;
 }
 
@@ -8407,14 +8469,14 @@ copyfunc(union node *n)
  * Define a shell function.
  */
 static void
-defun(char *name, union node *func)
+defun(union node *func)
 {
        struct cmdentry entry;
 
        INT_OFF;
        entry.cmdtype = CMDFUNCTION;
        entry.u.func = copyfunc(func);
-       addcmdentry(name, &entry);
+       addcmdentry(func->narg.text, &entry);
        INT_ON;
 }
 
@@ -8497,41 +8559,18 @@ static void prehash(union node *);
 static int
 evaltree(union node *n, int flags)
 {
-       struct jmploc *volatile savehandler = exception_handler;
-       struct jmploc jmploc;
        int checkexit = 0;
        int (*evalfn)(union node *, int);
        int status = 0;
-       int int_level;
-
-       SAVE_INT(int_level);
 
        if (n == NULL) {
                TRACE(("evaltree(NULL) called\n"));
-               goto out1;
+               goto out;
        }
        TRACE(("evaltree(%p: %d, %d) called\n", n, n->type, flags));
 
        dotrap();
 
-       exception_handler = &jmploc;
-       {
-               int err = setjmp(jmploc.loc);
-               if (err) {
-                       /* if it was a signal, check for trap handlers */
-                       if (exception_type == EXSIG) {
-                               TRACE(("exception %d (EXSIG) in evaltree, err=%d\n",
-                                               exception_type, err));
-                               goto out;
-                       }
-                       /* continue on the way out */
-                       TRACE(("exception %d in evaltree, propagating err=%d\n",
-                                       exception_type, err));
-                       exception_handler = savehandler;
-                       longjmp(exception_handler->loc, err);
-               }
-       }
-
        switch (n->type) {
        default:
 #if DEBUG
@@ -8548,7 +8587,8 @@ evaltree(union node *n, int flags)
                if (!status) {
                        status = evaltree(n->nredir.n, flags & EV_TESTED);
                }
-               popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */);
+               if (n->nredir.redirect)
+                       popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */);
                goto setstatus;
        case NCMD:
                evalfn = evalcommand;
@@ -8564,11 +8604,9 @@ evaltree(union node *n, int flags)
                evalfn = evalloop;
                goto calleval;
        case NSUBSHELL:
-               evalfn = evalsubshell;
-               goto checkexit;
        case NBACKGND:
                evalfn = evalsubshell;
-               goto calleval;
+               goto checkexit;
        case NPIPE:
                evalfn = evalpipe;
                goto checkexit;
@@ -8590,7 +8628,7 @@ evaltree(union node *n, int flags)
                        n->nbinary.ch1,
                        (flags | ((is_or >> 1) - 1)) & EV_TESTED
                );
-               if (!status == is_or || evalskip)
+               if ((!status) == is_or || evalskip)
                        break;
                n = n->nbinary.ch2;
  evaln:
@@ -8614,7 +8652,7 @@ evaltree(union node *n, int flags)
                status = 0;
                goto setstatus;
        case NDEFUN:
-               defun(n->narg.text, n->narg.next);
+               defun(n);
                /* Not necessary. To test it:
                 * "false; f() { qwerty; }; echo $?" should print 0.
                 */
@@ -8623,11 +8661,7 @@ evaltree(union node *n, int flags)
                exitstatus = status;
                break;
        }
-
  out:
-       exception_handler = savehandler;
-
- out1:
        /* Order of checks below is important:
         * signal handlers trigger before exit caused by "set -e".
         */
@@ -8638,9 +8672,7 @@ evaltree(union node *n, int flags)
        if (flags & EV_EXIT)
                raise_exception(EXEXIT);
 
-       RESTORE_INT(int_level);
        TRACE(("leaving evaltree (no interrupts)\n"));
-
        return exitstatus;
 }
 
@@ -8789,6 +8821,7 @@ evalsubshell(union node *n, int flags)
                evaltreenr(n->nredir.n, flags);
                /* never returns */
        }
+       /* parent */
        status = 0;
        if (!backgnd)
                status = waitforjob(jp);
@@ -8894,6 +8927,7 @@ evalpipe(union node *n, int flags)
                        }
                }
                if (forkshell(jp, lp->n, n->npipe.pipe_backgnd) == 0) {
+                       /* child */
                        INT_ON;
                        if (pip[1] >= 0) {
                                close(pip[0]);
@@ -8909,6 +8943,7 @@ evalpipe(union node *n, int flags)
                        evaltreenr(lp->n, flags);
                        /* never returns */
                }
+               /* parent */
                if (prevfd >= 0)
                        close(prevfd);
                prevfd = pip[0];
@@ -9039,7 +9074,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
        shellparam.optind = 1;
        shellparam.optoff = -1;
 #endif
-       evaltree(&func->n, flags & EV_TESTED);
+       evaltree(func->n.narg.next, flags & EV_TESTED);
  funcdone:
        INT_OFF;
        funcnest--;
@@ -9082,7 +9117,7 @@ mklocal(char *name)
                        /* else:
                         * it's a duplicate "local VAR" declaration, do nothing
                         */
-                       return;
+                       goto ret;
                }
                lvp = lvp->next;
        }
@@ -9121,6 +9156,7 @@ mklocal(char *name)
        lvp->vp = vp;
        lvp->next = localvars;
        localvars = lvp;
+ ret:
        INT_ON;
 }
 
@@ -9339,6 +9375,11 @@ static const struct builtincmd builtintab[] = {
 /*
  * Search the table of builtin commands.
  */
+static int
+pstrcmp1(const void *a, const void *b)
+{
+       return strcmp((char*)a, *(char**)b + 1);
+}
 static struct builtincmd *
 find_builtin(const char *name)
 {
@@ -9346,7 +9387,7 @@ find_builtin(const char *name)
 
        bp = bsearch(
                name, builtintab, ARRAY_SIZE(builtintab), sizeof(builtintab[0]),
-               pstrcmp
+               pstrcmp1
        );
        return bp;
 }
@@ -9424,7 +9465,9 @@ evalcommand(union node *cmd, int flags)
                        argc++;
        }
 
-       argv = nargv = stalloc(sizeof(char *) * (argc + 1));
+       /* 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;
@@ -9541,7 +9584,7 @@ evalcommand(union node *cmd, int flags)
                if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) {
                        listsetvar(varlist.list, VEXPORT|VSTACK);
                        /* run <applet>_main() */
-                       exitstatus = run_nofork_applet(applet_no, argv);
+                       status = run_nofork_applet(applet_no, argv);
                        break;
                }
 #endif
@@ -9587,21 +9630,12 @@ evalcommand(union node *cmd, int flags)
                dowait(DOWAIT_NONBLOCK, NULL);
 
                if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) {
-                       int exit_status;
-                       int i = exception_type;
-                       if (i == EXEXIT || i == EXEXEC)
-                               goto raise;
-                       exit_status = 2;
-                       if (i == EXINT)
-                               exit_status = 128 + SIGINT;
-                       if (i == EXSIG)
-                               exit_status = 128 + pending_sig;
-                       exitstatus = exit_status;
-                       if (i == EXINT || spclbltin > 0) {
- raise:
-                               longjmp(exception_handler->loc, 1);
+                       if (exception_type == EXERROR && spclbltin <= 0) {
+                               FORCE_INT_ON;
+                               goto readstatus;
                        }
-                       FORCE_INT_ON;
+ raise:
+                       longjmp(exception_handler->loc, 1);
                }
                goto readstatus;
 
@@ -9617,7 +9651,8 @@ evalcommand(union node *cmd, int flags)
        } /* switch */
 
  out:
-       popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
+       if (cmd->ncmd.redirect)
+               popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
        if (lastarg) {
                /* dsl: I think this is intended to be used to support
                 * '_' in 'vi' command mode during line editing...
@@ -9719,8 +9754,7 @@ breakcmd(int argc UNUSED_PARAM, char **argv)
 }
 
 
-/* ============ input.c
- *
+/*
  * This implements the input routines used by the parser.
  */
 
@@ -9988,6 +10022,19 @@ preadbuffer(void)
        return (unsigned char)*g_parsefile->next_to_pgetc++;
 }
 
+static void
+nlprompt(void)
+{
+       g_parsefile->linno++;
+       setprompt_if(doprompt, 2);
+}
+static void
+nlnoprompt(void)
+{
+       g_parsefile->linno++;
+       needprompt = doprompt;
+}
+
 static int
 pgetc(void)
 {
@@ -10076,8 +10123,7 @@ pgetc_eatbnl(void)
                        break;
                }
 
-               g_parsefile->linno++;
-               setprompt_if(doprompt, 2);
+               nlprompt();
        }
 
        return c;
@@ -10106,6 +10152,9 @@ popfile(void)
 {
        struct parsefile *pf = g_parsefile;
 
+       if (pf == &basepf)
+               return;
+
        INT_OFF;
        if (pf->pf_fd >= 0)
                close(pf->pf_fd);
@@ -10148,7 +10197,6 @@ closescript(void)
 static void
 setinputfd(int fd, int push)
 {
-       close_on_exec_on(fd);
        if (push) {
                pushfile();
                g_parsefile->buf = NULL;
@@ -10169,22 +10217,19 @@ static int
 setinputfile(const char *fname, int flags)
 {
        int fd;
-       int fd2;
 
        INT_OFF;
        fd = open(fname, O_RDONLY);
        if (fd < 0) {
                if (flags & INPUT_NOFILE_OK)
                        goto out;
+               exitstatus = 127;
                ash_msg_and_raise_error("can't open '%s'", fname);
        }
-       if (fd < 10) {
-               fd2 = copyfd(fd, 10);
-               close(fd);
-               if (fd2 < 0)
-                       ash_msg_and_raise_error("out of file descriptors");
-               fd = fd2;
-       }
+       if (fd < 10)
+               fd = savefd(fd);
+       else
+               close_on_exec_on(fd);
        setinputfd(fd, flags & INPUT_PUSH_FILE);
  out:
        INT_ON;
@@ -10207,8 +10252,7 @@ setinputstring(char *string)
 }
 
 
-/* ============ mail.c
- *
+/*
  * Routines to check for mail.
  */
 
@@ -10526,25 +10570,25 @@ change_random(const char *value)
 
 #if ENABLE_ASH_GETOPTS
 static int
-getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff)
+getopts(char *optstr, char *optvar, char **optfirst)
 {
        char *p, *q;
        char c = '?';
        int done = 0;
-       int err = 0;
        char sbuf[2];
        char **optnext;
+       int ind = shellparam.optind;
+       int off = shellparam.optoff;
 
        sbuf[1] = '\0';
 
-       if (*param_optind < 1)
-               return 1;
-       optnext = optfirst + *param_optind - 1;
+       shellparam.optind = -1;
+       optnext = optfirst + ind - 1;
 
-       if (*param_optind <= 1 || *optoff < 0 || (int)strlen(optnext[-1]) < *optoff)
+       if (ind <= 1 || off < 0 || (int)strlen(optnext[-1]) < off)
                p = NULL;
        else
-               p = optnext[-1] + *optoff;
+               p = optnext[-1] + off;
        if (p == NULL || *p == '\0') {
                /* Current word is done, advance */
                p = *optnext;
@@ -10565,7 +10609,7 @@ getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *opt
                        if (optstr[0] == ':') {
                                sbuf[0] = c;
                                /*sbuf[1] = '\0'; - already is */
-                               err |= setvarsafe("OPTARG", sbuf, 0);
+                               setvar0("OPTARG", sbuf);
                        } else {
                                fprintf(stderr, "Illegal option -%c\n", c);
                                unsetvar("OPTARG");
@@ -10582,7 +10626,7 @@ getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *opt
                        if (optstr[0] == ':') {
                                sbuf[0] = c;
                                /*sbuf[1] = '\0'; - already is */
-                               err |= setvarsafe("OPTARG", sbuf, 0);
+                               setvar0("OPTARG", sbuf);
                                c = ':';
                        } else {
                                fprintf(stderr, "No arg for -%c option\n", c);
@@ -10594,23 +10638,20 @@ getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *opt
 
                if (p == *optnext)
                        optnext++;
-               err |= setvarsafe("OPTARG", p, 0);
+               setvar0("OPTARG", p);
                p = NULL;
        } else
-               err |= setvarsafe("OPTARG", nullstr, 0);
+               setvar0("OPTARG", nullstr);
  out:
-       *optoff = p ? p - *(optnext - 1) : -1;
-       *param_optind = optnext - optfirst + 1;
-       err |= setvarsafe("OPTIND", itoa(*param_optind), VNOFUNC);
+       ind = optnext - optfirst + 1;
+       setvar("OPTIND", itoa(ind), VNOFUNC);
        sbuf[0] = c;
        /*sbuf[1] = '\0'; - already is */
-       err |= setvarsafe(optvar, sbuf, 0);
-       if (err) {
-               *param_optind = 1;
-               *optoff = -1;
-               flush_stdout_stderr();
-               raise_exception(EXERROR);
-       }
+       setvar0(optvar, sbuf);
+
+       shellparam.optoff = p ? p - *(optnext - 1) : -1;
+       shellparam.optind = ind;
+
        return done;
 }
 
@@ -10629,20 +10670,19 @@ getoptscmd(int argc, char **argv)
                ash_msg_and_raise_error("usage: getopts optstring var [arg]");
        if (argc == 3) {
                optbase = shellparam.p;
-               if (shellparam.optind > shellparam.nparam + 1) {
+               if ((unsigned)shellparam.optind > shellparam.nparam + 1) {
                        shellparam.optind = 1;
                        shellparam.optoff = -1;
                }
        } else {
                optbase = &argv[3];
-               if (shellparam.optind > argc - 2) {
+               if ((unsigned)shellparam.optind > argc - 2) {
                        shellparam.optind = 1;
                        shellparam.optoff = -1;
                }
        }
 
-       return getopts(argv[1], argv[2], optbase, &shellparam.optind,
-                       &shellparam.optoff);
+       return getopts(argv[1], argv[2], optbase);
 }
 #endif /* ASH_GETOPTS */
 
@@ -10669,8 +10709,8 @@ static const char *
 tokname(char *buf, int tok)
 {
        if (tok < TSEMI)
-               return tokname_array[tok] + 1;
-       sprintf(buf, "\"%s\"", tokname_array[tok] + 1);
+               return tokname_array[tok];
+       sprintf(buf, "\"%s\"", tokname_array[tok]);
        return buf;
 }
 
@@ -10727,7 +10767,7 @@ list(int nlflag)
                }
 
                checkkwd = CHKNL | CHKKWD | CHKALIAS;
-               if (nlflag == 2 && tokname_array[peektoken()][0])
+               if (nlflag == 2 && ((1 << peektoken()) & tokendlist))
                        return n1;
                nlflag |= 2;
 
@@ -11109,7 +11149,7 @@ parse_command(void)
                n1->nbinary.ch1 = list(0);
                got = readtoken();
                if (got != TDO) {
-                       TRACE(("expecting DO got '%s' %s\n", tokname_array[got] + 1,
+                       TRACE(("expecting DO got '%s' %s\n", tokname_array[got],
                                        got == TWORD ? wordtext : ""));
                        raise_error_unexpected_syntax(TDO);
                }
@@ -11367,8 +11407,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                        if (syntax == BASESYNTAX)
                                goto endword;   /* exit outer loop */
                        USTPUTC(c, out);
-                       g_parsefile->linno++;
-                       setprompt_if(doprompt, 2);
+                       nlprompt();
                        c = pgetc();
                        goto loop;              /* continue outer loop */
                case CWORD:
@@ -11402,7 +11441,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                                USTPUTC('\\', out);
                                pungetc();
                        } else if (c == '\n') {
-                               setprompt_if(doprompt, 2);
+                               nlprompt();
                        } else {
 #if ENABLE_ASH_EXPAND_PRMT
                                if (c == '$' && pssyntax) {
@@ -11567,14 +11606,19 @@ checkend: {
                if (c == *eofmark) {
                        if (pfgets(line, sizeof(line)) != NULL) {
                                char *p, *q;
+                               int cc;
 
                                p = line;
-                               for (q = eofmark + 1; *q && *p == *q; p++, q++)
-                                       continue;
-                               if (*p == '\n' && *q == '\0') {
+                               for (q = eofmark + 1;; p++, q++) {
+                                       cc = *p;
+                                       if (cc == '\n')
+                                               cc = 0;
+                                       if (!*q || cc != *q)
+                                               break;
+                               }
+                               if (cc == *q) {
                                        c = PEOF;
-                                       g_parsefile->linno++;
-                                       needprompt = doprompt;
+                                       nlnoprompt();
                                } else {
                                        pushstring(line, NULL);
                                }
@@ -11671,7 +11715,6 @@ parseredir: {
 parsesub: {
        unsigned char subtype;
        int typeloc;
-       int flags;
 
        c = pgetc_eatbnl();
        if (c > 255 /* PEOA or PEOF */
@@ -11700,26 +11743,19 @@ parsesub: {
                /* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
                USTPUTC(CTLVAR, out);
                typeloc = out - (char *)stackblock();
-               USTPUTC(VSNORMAL, out);
+               STADJUST(1, out);
                subtype = VSNORMAL;
                if (c == '{') {
                        c = pgetc_eatbnl();
-                       if (c == '#') {
-                               c = pgetc_eatbnl();
-                               if (c == '}')
-                                       c = '#'; /* ${#} - same as $# */
-                               else
-                                       subtype = VSLENGTH; /* ${#VAR} */
-                       } else {
-                               subtype = 0;
-                       }
+                       subtype = 0;
                }
-               if (c <= 255 /* not PEOA or PEOF */ && is_name(c)) {
+ varname:
+               if (is_name(c)) {
                        /* $[{[#]]NAME[}] */
                        do {
                                STPUTC(c, out);
                                c = pgetc_eatbnl();
-                       } while (c <= 255 /* not PEOA or PEOF */ && is_in_name(c));
+                       } while (is_in_name(c));
                } else if (isdigit(c)) {
                        /* $[{[#]]NUM[}] */
                        do {
@@ -11728,19 +11764,31 @@ parsesub: {
                        } while (isdigit(c));
                } else if (is_special(c)) {
                        /* $[{[#]]<specialchar>[}] */
-                       USTPUTC(c, out);
+                       int cc = c;
+
                        c = pgetc_eatbnl();
+                       if (!subtype && cc == '#') {
+                               subtype = VSLENGTH;
+                               if (c == '_' || isalnum(c))
+                                       goto varname;
+                               cc = c;
+                               c = pgetc_eatbnl();
+                               if (cc == '}' || c != '}') {
+                                       pungetc();
+                                       subtype = 0;
+                                       c = cc;
+                                       cc = '#';
+                               }
+                       }
+                       USTPUTC(cc, out);
                } else {
- badsub:
-                       raise_error_syntax("bad substitution");
+                       goto badsub;
                }
                if (c != '}' && subtype == VSLENGTH) {
                        /* ${#VAR didn't end with } */
                        goto badsub;
                }
 
-               STPUTC('=', out);
-               flags = 0;
                if (subtype == 0) {
                        static const char types[] ALIGN1 = "}-+?=";
                        /* ${VAR...} but not $VAR or ${#VAR} */
@@ -11756,16 +11804,16 @@ parsesub: {
                                if (!strchr(types, c)) {
                                        subtype = VSSUBSTR;
                                        pungetc();
-                                       break; /* "goto do_pungetc" is bigger (!) */
+                                       break; /* "goto badsub" is bigger (!) */
                                }
 #endif
-                               flags = VSNUL;
+                               subtype = VSNUL;
                                /*FALLTHROUGH*/
                        default: {
                                const char *p = strchr(types, c);
                                if (p == NULL)
-                                       goto badsub;
-                               subtype = p - types + VSNORMAL;
+                                       break;
+                               subtype |= p - types + VSNORMAL;
                                break;
                        }
                        case '%':
@@ -11774,7 +11822,7 @@ parsesub: {
                                subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT);
                                c = pgetc_eatbnl();
                                if (c != cc)
-                                       goto do_pungetc;
+                                       goto badsub;
                                subtype++;
                                break;
                        }
@@ -11786,22 +11834,22 @@ parsesub: {
                                subtype = VSREPLACE;
                                c = pgetc_eatbnl();
                                if (c != '/')
-                                       goto do_pungetc;
+                                       goto badsub;
                                subtype++; /* VSREPLACEALL */
                                break;
 #endif
                        }
                } else {
do_pungetc:
badsub:
                        pungetc();
                }
-               ((unsigned char *)stackblock())[typeloc] = subtype | flags;
+               ((unsigned char *)stackblock())[typeloc] = subtype;
                if (subtype != VSNORMAL) {
                        varnest++;
-                       if (dblquote) {
+                       if (dblquote)
                                dqvarnest++;
-                       }
                }
+               STPUTC('=', out);
        }
        goto parsesub_return;
 }
@@ -11856,8 +11904,7 @@ parsebackq: {
                        case '\\':
                                pc = pgetc();
                                if (pc == '\n') {
-                                       g_parsefile->linno++;
-                                       setprompt_if(doprompt, 2);
+                                       nlprompt();
                                        /*
                                         * If eating a newline, avoid putting
                                         * the newline into the new character
@@ -11882,8 +11929,7 @@ parsebackq: {
                                raise_error_syntax("EOF in backquote substitution");
 
                        case '\n':
-                               g_parsefile->linno++;
-                               needprompt = doprompt;
+                               nlnoprompt();
                                break;
 
                        default:
@@ -12015,16 +12061,14 @@ xxreadtoken(void)
                                pungetc();
                                break; /* return readtoken1(...) */
                        }
-                       startlinno = ++g_parsefile->linno;
-                       setprompt_if(doprompt, 2);
+                       nlprompt();
                } else {
                        const char *p;
 
                        p = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1;
                        if (c != PEOF) {
                                if (c == '\n') {
-                                       g_parsefile->linno++;
-                                       needprompt = doprompt;
+                                       nlnoprompt();
                                }
 
                                p = strchr(xxreadtoken_chars, c);
@@ -12077,15 +12121,13 @@ xxreadtoken(void)
                        continue;
                case '\\':
                        if (pgetc() == '\n') {
-                               startlinno = ++g_parsefile->linno;
-                               setprompt_if(doprompt, 2);
+                               nlprompt();
                                continue;
                        }
                        pungetc();
                        goto breakloop;
                case '\n':
-                       g_parsefile->linno++;
-                       needprompt = doprompt;
+                       nlnoprompt();
                        RETURN(TNL);
                case PEOF:
                        RETURN(TEOF);
@@ -12156,7 +12198,7 @@ readtoken(void)
                pp = findkwd(wordtext);
                if (pp) {
                        lasttoken = t = pp - tokname_array;
-                       TRACE(("keyword '%s' recognized\n", tokname_array[t] + 1));
+                       TRACE(("keyword '%s' recognized\n", tokname_array[t]));
                        goto out;
                }
        }
@@ -12177,9 +12219,9 @@ readtoken(void)
        checkkwd = 0;
 #if DEBUG
        if (!alreadyseen)
-               TRACE(("token '%s' %s\n", tokname_array[t] + 1, t == TWORD ? wordtext : ""));
+               TRACE(("token '%s' %s\n", tokname_array[t], t == TWORD ? wordtext : ""));
        else
-               TRACE(("reread token '%s' %s\n", tokname_array[t] + 1, t == TWORD ? wordtext : ""));
+               TRACE(("reread token '%s' %s\n", tokname_array[t], t == TWORD ? wordtext : ""));
 #endif
        return t;
 }
@@ -12245,11 +12287,17 @@ static const char *
 expandstr(const char *ps)
 {
        union node n;
+       int saveprompt;
 
        /* XXX Fix (char *) cast. It _is_ a bug. ps is variable's value,
         * and token processing _can_ alter it (delete NULs etc). */
        setinputstring((char *)ps);
+
+       saveprompt = doprompt;
+       doprompt = 0;
        readtoken1(pgetc(), PSSYNTAX, nullstr, 0);
+       doprompt = saveprompt;
+
        popfile();
 
        n.narg.type = NARG;
@@ -12268,6 +12316,10 @@ expandstr(const char *ps)
 static int
 evalstring(char *s, int flags)
 {
+       struct jmploc *volatile savehandler;
+       struct jmploc jmploc;
+       int ex;
+
        union node *n;
        struct stackmark smark;
        int status;
@@ -12277,6 +12329,19 @@ evalstring(char *s, int flags)
        setstackmark(&smark);
 
        status = 0;
+       /* On exception inside execution loop, we must popfile().
+        * Try interactively:
+        *      readonly a=a
+        *      command eval "a=b"  # throws "is read only" error
+        * "command BLTIN" is not supposed to abort (even in non-interactive use).
+        * But if we skip popfile(), we hit EOF in eval's string, and exit.
+        */
+       savehandler = exception_handler;
+       ex = setjmp(jmploc.loc);
+       if (ex)
+               goto out;
+       exception_handler = &jmploc;
+
        while ((n = parsecmd(0)) != NODE_EOF) {
                int i;
 
@@ -12287,10 +12352,15 @@ evalstring(char *s, int flags)
                if (evalskip)
                        break;
        }
+ out:
        popstackmark(&smark);
        popfile();
        stunalloc(s);
 
+       exception_handler = savehandler;
+       if (ex)
+                longjmp(exception_handler->loc, ex);
+
        return status;
 }
 
@@ -12699,8 +12769,6 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path)
 }
 
 
-/* ============ trap.c */
-
 /*
  * The trap builtin.
  */
@@ -12755,12 +12823,13 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                if (action) {
                        if (LONE_DASH(action))
                                action = NULL;
-                       else
+                       else {
+                               if (action[0]) /* not NULL and not "" and not "-" */
+                                       may_have_traps = 1;
                                action = ckstrdup(action);
+                       }
                }
                free(trap[signo]);
-               if (action)
-                       may_have_traps = 1;
                trap[signo] = action;
                if (signo != 0)
                        setsignal(signo);
@@ -12806,7 +12875,7 @@ helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                }
        }
 # endif
-       out1fmt("\n\n");
+       newline_and_flush(stdout);
        return EXIT_SUCCESS;
 }
 #endif
@@ -13126,15 +13195,10 @@ exitshell(void)
 #if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
        save_history(line_input_state);
 #endif
-
        status = exitstatus;
        TRACE(("pid %d, exitshell(%d)\n", getpid(), status));
        if (setjmp(loc.loc)) {
                if (exception_type == EXEXIT)
-/* dash bug: it just does _exit(exitstatus) here
- * but we have to do setjobctl(0) first!
- * (bug is still not fixed in dash-0.5.3 - if you run dash
- * under Midnight Commander, on exit from dash MC is backgrounded) */
                        status = exitstatus;
                goto out;
        }
@@ -13144,11 +13208,14 @@ exitshell(void)
                trap[0] = NULL;
                evalskip = 0;
                evalstring(p, 0);
-               free(p);
+               /*free(p); - we'll exit soon */
        }
-       flush_stdout_stderr();
  out:
+       /* dash wraps setjobctl(0) in "if (setjmp(loc.loc) == 0) {...}".
+        * our setjobctl(0) does not panic if tcsetpgrp fails inside it.
+        */
        setjobctl(0);
+       flush_stdout_stderr();
        _exit(status);
        /* NOTREACHED */
 }
@@ -13156,18 +13223,17 @@ exitshell(void)
 static void
 init(void)
 {
-       /* from input.c: */
        /* we will never free this */
        basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ);
 
-       /* from trap.c: */
-       signal(SIGCHLD, SIG_DFL);
+       sigmode[SIGCHLD - 1] = S_DFL;
+       setsignal(SIGCHLD);
+
        /* bash re-enables SIGHUP which is SIG_IGNed on entry.
         * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$"
         */
        signal(SIGHUP, SIG_DFL);
 
-       /* from var.c: */
        {
                char **envp;
                const char *p;
@@ -13284,11 +13350,12 @@ procargs(char **argv)
 }
 
 /*
- * Read /etc/profile or .profile.
+ * Read /etc/profile, ~/.profile, $ENV.
  */
 static void
 read_profile(const char *name)
 {
+       name = expandstr(name);
        if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0)
                return;
        cmdloop(0);
@@ -13298,6 +13365,7 @@ read_profile(const char *name)
 /*
  * This routine is called when an error or an interrupt occurs in an
  * interactive shell and control is returned to the main command loop.
+ * (In dash, this function is auto-generated by build machinery).
  */
 static void
 reset(void)
@@ -13305,15 +13373,18 @@ reset(void)
        /* from eval.c: */
        evalskip = 0;
        loopnest = 0;
+
+       /* from expand.c: */
+       ifsfree();
+
        /* from input.c: */
        g_parsefile->left_in_buffer = 0;
        g_parsefile->left_in_line = 0;      /* clear input buffer */
        popallfiles();
-       /* from parser.c: */
-       tokpushback = 0;
-       checkkwd = 0;
+
        /* from redir.c: */
-       clearredir(/*drop:*/ 0);
+       while (redirlist)
+               popredir(/*drop:*/ 0, /*restore:*/ 0);
 }
 
 #if PROFILE
@@ -13331,7 +13402,6 @@ extern int etext();
 int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int ash_main(int argc UNUSED_PARAM, char **argv)
 {
-       const char *shinit;
        volatile smallint state;
        struct jmploc jmploc;
        struct stackmark smark;
@@ -13360,8 +13430,6 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                reset();
 
                e = exception_type;
-               if (e == EXERROR)
-                       exitstatus = 2;
                s = state;
                if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) {
                        exitshell();
@@ -13381,16 +13449,15 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                goto state4;
        }
        exception_handler = &jmploc;
-#if DEBUG
-       opentrace();
-       TRACE(("Shell args: "));
-       trace_puts_args(argv);
-#endif
        rootpid = getpid();
 
        init();
        setstackmark(&smark);
        procargs(argv);
+#if DEBUG
+       TRACE(("Shell args: "));
+       trace_puts_args(argv);
+#endif
 
        if (argv[0] && argv[0][0] == '-')
                isloginsh = 1;
@@ -13402,11 +13469,8 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
  state1:
                state = 2;
                hp = lookupvar("HOME");
-               if (hp) {
-                       hp = concat_path_file(hp, ".profile");
-                       read_profile(hp);
-                       free((char*)hp);
-               }
+               if (hp)
+                       read_profile("$HOME/.profile");
        }
  state2:
        state = 3;
@@ -13416,11 +13480,11 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
 #endif
         iflag
        ) {
-               shinit = lookupvar("ENV");
-               if (shinit != NULL && *shinit != '\0') {
+               const char *shinit = lookupvar("ENV");
+               if (shinit != NULL && *shinit != '\0')
                        read_profile(shinit);
-               }
        }
+       popstackmark(&smark);
  state3:
        state = 4;
        if (minusc) {