hush: do not allow sh -c '{ echo boo }'
[oweals/busybox.git] / shell / ash.c
index bffb4a245f1c3426e066522238a86bb8612bc22d..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
 
 #include <sys/utsname.h> /* for setting $HOSTNAME */
 
 #include "busybox.h" /* for applet_names */
-#include "unicode.h"
 
+#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24
+/* Bionic at least up to version 24 has no glob() */
+# undef  ENABLE_ASH_INTERNAL_GLOB
+# define ENABLE_ASH_INTERNAL_GLOB 1
+#endif
+
+#if !ENABLE_ASH_INTERNAL_GLOB
+# include <glob.h>
+#endif
+
+#include "unicode.h"
 #include "shell_common.h"
 #if ENABLE_SH_MATH_SUPPORT
 # include "math.h"
 //config:        shell (by Herbert Xu), which was created by porting the 'ash' shell
 //config:        (written by Kenneth Almquist) from NetBSD.
 //config:
+//config:config ASH_OPTIMIZE_FOR_SIZE
+//config:      bool "Optimize for size instead of speed"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Compile ash for reduced size at the price of speed.
+//config:
+//config:config ASH_INTERNAL_GLOB
+//config:      bool "Use internal glob() implementation"
+//config:      default n
+//config:      depends on ASH
+//config:      help
+//config:        Do not use glob() function from libc, use internal implementation.
+//config:        Use this if you are getting "glob.h: No such file or directory"
+//config:        or similar build errors.
+//config:
+//config:config ASH_RANDOM_SUPPORT
+//config:      bool "Pseudorandom generator and $RANDOM variable"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Enable pseudorandom generator and dynamic variable "$RANDOM".
+//config:        Each read of "$RANDOM" will generate a new pseudorandom value.
+//config:        You can reset the generator by using a specified start value.
+//config:        After "unset RANDOM" the generator will switch off and this
+//config:        variable will no longer have special treatment.
+//config:
+//config:config ASH_EXPAND_PRMT
+//config:      bool "Expand prompt string"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        "PS#" may contain volatile content, such as backquote commands.
+//config:        This option recreates the prompt string from the environment
+//config:        variable each time it is displayed.
+//config:
 //config:config ASH_BASH_COMPAT
 //config:      bool "bash-compatible extensions"
 //config:      default y
 //config:      help
 //config:        Enable "check for new mail" function in the ash shell.
 //config:
-//config:config ASH_OPTIMIZE_FOR_SIZE
-//config:      bool "Optimize for size instead of speed"
-//config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Compile ash for reduced size at the price of speed.
-//config:
-//config:config ASH_RANDOM_SUPPORT
-//config:      bool "Pseudorandom generator and $RANDOM variable"
-//config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable pseudorandom generator and dynamic variable "$RANDOM".
-//config:        Each read of "$RANDOM" will generate a new pseudorandom value.
-//config:        You can reset the generator by using a specified start value.
-//config:        After "unset RANDOM" the generator will switch off and this
-//config:        variable will no longer have special treatment.
-//config:
-//config:config ASH_EXPAND_PRMT
-//config:      bool "Expand prompt string"
-//config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        "PS#" may contain volatile content, such as backquote commands.
-//config:        This option recreates the prompt string from the environment
-//config:        variable each time it is displayed.
-//config:
 
 //applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
 //applet:IF_FEATURE_SH_IS_ASH(APPLET_ODDNAME(sh, ash, BB_DIR_BIN, BB_SUID_DROP, sh))
@@ -258,8 +278,10 @@ struct jmploc {
 };
 
 struct globals_misc {
-       /* pid of main shell */
-       int rootpid;
+       uint8_t exitstatus;     /* exit status of last command */
+       uint8_t back_exitstatus;/* exit status of backquoted command */
+       smallint job_warning;   /* user was warned about stopped jobs (can be 2, 1 or 0). */
+       int rootpid;            /* pid of main shell */
        /* shell level: 0 for the main shell, 1 for its children, and so on */
        int shlvl;
 #define rootshell (!shlvl)
@@ -274,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 */
@@ -336,10 +355,12 @@ struct globals_misc {
        random_t random_gen;
 #endif
        pid_t backgndpid;        /* pid of last background process */
-       smallint job_warning;    /* user was warned about stopped jobs (can be 2, 1 or 0). */
 };
 extern struct globals_misc *const ash_ptr_to_globals_misc;
 #define G_misc (*ash_ptr_to_globals_misc)
+#define exitstatus        (G_misc.exitstatus )
+#define back_exitstatus   (G_misc.back_exitstatus )
+#define job_warning       (G_misc.job_warning)
 #define rootpid     (G_misc.rootpid    )
 #define shlvl       (G_misc.shlvl      )
 #define minusc      (G_misc.minusc     )
@@ -350,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    )
@@ -361,7 +383,6 @@ extern struct globals_misc *const ash_ptr_to_globals_misc;
 #define trap_ptr    (G_misc.trap_ptr   )
 #define random_gen  (G_misc.random_gen )
 #define backgndpid  (G_misc.backgndpid )
-#define job_warning (G_misc.job_warning)
 #define INIT_G_misc() do { \
        (*(struct globals_misc**)&ash_ptr_to_globals_misc) = xzalloc(sizeof(G_misc)); \
        barrier(); \
@@ -390,12 +411,11 @@ static void trace_vprintf(const char *fmt, va_list va);
 
 
 /* ============ Utility functions */
-#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0)
-
 #define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
 #define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
 
-static int isdigit_str9(const char *str)
+static int
+isdigit_str9(const char *str)
 {
        int maxlen = 9 + 1; /* max 9 digits: 999999999 */
        while (--maxlen && isdigit(*str))
@@ -403,7 +423,8 @@ static int isdigit_str9(const char *str)
        return (*str == '\0');
 }
 
-static const char *var_end(const char *var)
+static const char *
+var_end(const char *var)
 {
        while (*var)
                if (*var++ == '=')
@@ -422,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++; \
-       xbarrier(); \
+       barrier(); \
 } while (0)
+#endif
 
 /*
  * Called to raise an exception.  Since C doesn't include exceptions, we
@@ -452,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
@@ -462,24 +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);
        }
-       raise_exception(ex_type);
+       /* bash: ^C even on empty command line sets $? */
+       exitstatus = SIGINT + 128;
+       raise_exception(EXINT);
        /* NOTREACHED */
 }
 #if DEBUG
@@ -492,16 +517,23 @@ raise_interrupt(void)
 static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void
 int_on(void)
 {
-       xbarrier();
+       barrier();
        if (--suppress_int == 0 && pending_int) {
                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)
 {
-       xbarrier();
+       barrier();
        suppress_int = 0;
        if (pending_int)
                raise_interrupt();
@@ -511,7 +543,7 @@ force_int_on(void)
 #define SAVE_INT(v) ((v) = suppress_int)
 
 #define RESTORE_INT(v) do { \
-       xbarrier(); \
+       barrier(); \
        suppress_int = (v); \
        if (suppress_int == 0 && pending_int) \
                raise_interrupt(); \
@@ -828,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
@@ -1167,6 +1194,12 @@ struct strpush {
        struct alias *ap;       /* if push was associated with an alias */
 #endif
        char *string;           /* remember the string since it may change */
+
+       /* Remember last two characters for pungetc. */
+       int lastc[2];
+
+       /* Number of outstanding calls to pungetc. */
+       int unget;
 };
 
 struct parsefile {
@@ -1179,6 +1212,12 @@ struct parsefile {
        char *buf;              /* input buffer */
        struct strpush *strpush; /* for pushing strings at this level */
        struct strpush basestrpush; /* so pushing one is fast */
+
+       /* Remember last two characters for pungetc. */
+       int lastc[2];
+
+       /* Number of outstanding calls to pungetc. */
+       int unget;
 };
 
 static struct parsefile basepf;        /* top level input file */
@@ -1186,7 +1225,6 @@ static struct parsefile *g_parsefile = &basepf;  /* current input file */
 static int startlinno;                 /* line # where last token started */
 static char *commandname;              /* currently executing command */
 static struct strlist *cmdenviron;     /* environment for builtin command */
-static uint8_t exitstatus;             /* exit status of last command */
 
 
 /* ============ Message printing */
@@ -1216,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);
@@ -1358,27 +1395,22 @@ struct stackmark {
        struct stack_block *stackp;
        char *stacknxt;
        size_t stacknleft;
-       struct stackmark *marknext;
 };
 
 
 struct globals_memstack {
        struct stack_block *g_stackp; // = &stackbase;
-       struct stackmark *markp;
        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;
 #define G_memstack (*ash_ptr_to_globals_memstack)
 #define g_stackp     (G_memstack.g_stackp    )
-#define markp        (G_memstack.markp       )
 #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)); \
@@ -1387,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)
 
 
@@ -1458,20 +1489,31 @@ stunalloc(void *p)
  * Like strdup but works with the ash stack.
  */
 static char *
-ststrdup(const char *p)
+sstrdup(const char *p)
 {
        size_t len = strlen(p) + 1;
        return memcpy(stalloc(len), p, len);
 }
 
+static inline void
+grabstackblock(size_t len)
+{
+       stalloc(len);
+}
+
 static void
-setstackmark(struct stackmark *mark)
+pushstackmark(struct stackmark *mark, size_t len)
 {
        mark->stackp = g_stackp;
        mark->stacknxt = g_stacknxt;
        mark->stacknleft = g_stacknleft;
-       mark->marknext = markp;
-       markp = mark;
+       grabstackblock(len);
+}
+
+static void
+setstackmark(struct stackmark *mark)
+{
+       pushstackmark(mark, g_stacknxt == g_stackp->space && g_stackp != &stackbase);
 }
 
 static void
@@ -1483,7 +1525,6 @@ popstackmark(struct stackmark *mark)
                return;
 
        INT_OFF;
-       markp = mark->marknext;
        while (g_stackp != mark->stackp) {
                sp = g_stackp;
                g_stackp = sp->prev;
@@ -1516,14 +1557,11 @@ growstackblock(void)
                newlen += 128;
 
        if (g_stacknxt == g_stackp->space && g_stackp != &stackbase) {
-               struct stack_block *oldstackp;
-               struct stackmark *xmark;
                struct stack_block *sp;
                struct stack_block *prevstackp;
                size_t grosslen;
 
                INT_OFF;
-               oldstackp = g_stackp;
                sp = g_stackp;
                prevstackp = sp->prev;
                grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
@@ -1533,18 +1571,6 @@ growstackblock(void)
                g_stacknxt = sp->space;
                g_stacknleft = newlen;
                sstrend = sp->space + newlen;
-
-               /*
-                * Stack marks pointing to the start of the old block
-                * must be relocated to point to the new block
-                */
-               xmark = markp;
-               while (xmark != NULL && xmark->stackp == oldstackp) {
-                       xmark->stackp = g_stackp;
-                       xmark->stacknxt = g_stacknxt;
-                       xmark->stacknleft = g_stacknleft;
-                       xmark = xmark->marknext;
-               }
                INT_ON;
        } else {
                char *oldspace = g_stacknxt;
@@ -1557,14 +1583,6 @@ growstackblock(void)
        }
 }
 
-static void
-grabstackblock(size_t len)
-{
-       len = SHELL_ALIGN(len);
-       g_stacknxt += len;
-       g_stacknleft -= len;
-}
-
 /*
  * The following routines are somewhat easier to use than the above.
  * The user declares a variable of type STACKSTR, which may be declared
@@ -1586,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;
 }
@@ -1601,7 +1615,7 @@ static char *
 makestrspace(size_t newlen, char *p)
 {
        size_t len = p - g_stacknxt;
-       size_t size = stackblocksize();
+       size_t size;
 
        for (;;) {
                size_t nleft;
@@ -1920,7 +1934,7 @@ static const struct {
        { VSTRFIXED|VTEXTFIXED       , "PS2=> "    , NULL            },
        { VSTRFIXED|VTEXTFIXED       , "PS4=+ "    , NULL            },
 #if ENABLE_ASH_GETOPTS
-       { VSTRFIXED|VTEXTFIXED       , "OPTIND=1"  , getoptsreset    },
+       { VSTRFIXED|VTEXTFIXED       , defoptindvar, getoptsreset    },
 #endif
 #if ENABLE_ASH_RANDOM_SUPPORT
        { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random },
@@ -1939,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)];
@@ -1948,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      )
@@ -2009,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
@@ -2123,7 +2135,8 @@ lookupvar(const char *name)
        return NULL;
 }
 
-static void reinit_unicode_for_ash(void)
+static void
+reinit_unicode_for_ash(void)
 {
        /* Unicode support should be activated even if LANG is set
         * _during_ shell execution, not only if it was set when
@@ -2176,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);
                }
 
@@ -2247,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.
  */
@@ -2469,8 +2457,7 @@ setprompt_if(smallint do_set, int whichprompt)
                prompt = nullstr;
        }
 #if ENABLE_ASH_EXPAND_PRMT
-       setstackmark(&smark);
-       stalloc(stackblocksize());
+       pushstackmark(&smark, stackblocksize());
 #endif
        putprompt(expandstr(prompt));
 #if ENABLE_ASH_EXPAND_PRMT
@@ -2513,7 +2500,7 @@ updatepwd(const char *dir)
        char *cdcomppath;
        const char *lim;
 
-       cdcomppath = ststrdup(dir);
+       cdcomppath = sstrdup(dir);
        STARTSTACKSTR(new);
        if (*dir != '/') {
                if (curdir == nullstr)
@@ -2608,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
@@ -2656,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:
@@ -2673,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)) {
@@ -2688,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:
@@ -2808,18 +2795,27 @@ enum {
 static int
 SIT(int c, int syntax)
 {
-       static const char spec_symbls[] ALIGN1 = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~";
+       /* Used to also have '/' in this string: "\t\n !\"$&'()*-/:;<=>?[\\]`|}~" */
+       static const char spec_symbls[] ALIGN1 = "\t\n !\"$&'()*-:;<=>?[\\]`|}~";
+       /*
+        * This causes '/' to be prepended with CTLESC in dquoted string,
+        * making "./file"* treated incorrectly because we feed
+        * ".\/file*" string to glob(), confusing it (see expandmeta func).
+        * The "homegrown" glob implementation is okay with that,
+        * but glibc one isn't. With '/' always treated as CWORD,
+        * both work fine.
+        */
 # if ENABLE_ASH_ALIAS
        static const uint8_t syntax_index_table[] ALIGN1 = {
                1, 2, 1, 3, 4, 5, 1, 6,         /* "\t\n !\"$&'" */
-               7, 8, 3, 3, 3, 3, 1, 1,         /* "()*-/:;<" */
+               7, 8, 3, 3,/*3,*/3, 1, 1,       /* "()*-/:;<" */
                3, 1, 3, 3, 9, 3, 10, 1,        /* "=>?[\\]`|" */
                11, 3                           /* "}~" */
        };
 # else
        static const uint8_t syntax_index_table[] ALIGN1 = {
                0, 1, 0, 2, 3, 4, 0, 5,         /* "\t\n !\"$&'" */
-               6, 7, 2, 2, 2, 2, 0, 0,         /* "()*-/:;<" */
+               6, 7, 2, 2,/*2,*/2, 0, 0,       /* "()*-/:;<" */
                2, 0, 2, 2, 8, 2, 9, 0,         /* "=>?[\\]`|" */
                10, 2                           /* "}~" */
        };
@@ -2901,7 +2897,8 @@ static const uint8_t syntax_index_table[] ALIGN1 = {
        /*  44  "," */ CWORD_CWORD_CWORD_CWORD,
        /*  45  "-" */ CWORD_CCTL_CCTL_CWORD,
        /*  46  "." */ CWORD_CWORD_CWORD_CWORD,
-       /*  47  "/" */ CWORD_CCTL_CCTL_CWORD,
+/* "/" was CWORD_CCTL_CCTL_CWORD, see comment in SIT() function why this is changed: */
+       /*  47  "/" */ CWORD_CWORD_CWORD_CWORD,
        /*  48  "0" */ CWORD_CWORD_CWORD_CWORD,
        /*  49  "1" */ CWORD_CWORD_CWORD_CWORD,
        /*  50  "2" */ CWORD_CWORD_CWORD_CWORD,
@@ -3143,7 +3140,8 @@ static struct alias **atab; // [ATABSIZE];
 
 
 static struct alias **
-__lookupalias(const char *name) {
+__lookupalias(const char *name)
+{
        unsigned int hashval;
        struct alias **app;
        const char *p;
@@ -3325,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
@@ -3404,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) {
@@ -3412,8 +3415,6 @@ signal_handler(int signo)
                        raise_interrupt(); /* does not return */
                }
                pending_int = 1;
-       } else {
-               pending_sig = signo;
        }
 }
 
@@ -3471,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) {
@@ -3520,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
@@ -3643,7 +3643,7 @@ getjob(const char *name, int getctl)
 
        if (is_number(p)) {
                num = atoi(p);
-               if (num <= njobs) {
+               if (num > 0 && num <= njobs) {
                        jp = jobtab + num - 1;
                        if (jp->used)
                                goto gotit;
@@ -3741,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);
@@ -3944,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;
@@ -4036,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)
@@ -4214,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;
 
@@ -4233,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;
                }
        }
 
@@ -4267,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: ;
@@ -4276,6 +4327,9 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
 
  ret:
        return retval;
+ sigout:
+       retval = 128 + pending_sig;
+       return retval;
 }
 
 static struct job *
@@ -4640,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 */
@@ -4670,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)
 {
@@ -4820,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++;
@@ -4853,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)
 {
@@ -4982,8 +5038,7 @@ stoppedjobs(void)
 }
 
 
-/* ============ redir.c
- *
+/*
  * Code for dealing with input/output redirection.
  */
 
@@ -5101,8 +5156,26 @@ openredirect(union node *redir)
        char *fname;
        int f;
 
+       switch (redir->nfile.type) {
+/* Can't happen, our single caller does this itself */
+//     case NTOFD:
+//     case NFROMFD:
+//             return -1;
+       case NHERE:
+       case NXHERE:
+               return openhere(redir);
+       }
+
+       /* For N[X]HERE, reading redir->nfile.expfname would touch beyond
+        * allocated space. Do it only when we know it is safe.
+        */
        fname = redir->nfile.expfname;
+
        switch (redir->nfile.type) {
+       default:
+#if DEBUG
+               abort();
+#endif
        case NFROM:
                f = open(fname, O_RDONLY);
                if (f < 0)
@@ -5135,20 +5208,6 @@ openredirect(union node *redir)
                if (f < 0)
                        goto ecreate;
                break;
-       default:
-#if DEBUG
-               abort();
-#endif
-               /* Fall through to eliminate warning. */
-/* Our single caller does this itself */
-//     case NTOFD:
-//     case NFROMFD:
-//             f = -1;
-//             break;
-       case NHERE:
-       case NXHERE:
-               f = openhere(redir);
-               break;
        }
 
        return f;
@@ -5159,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);
        }
@@ -5196,13 +5256,16 @@ 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)
+static int
+need_to_remember(struct redirtab *rp, int fd)
 {
        int i;
 
@@ -5219,7 +5282,8 @@ static int need_to_remember(struct redirtab *rp, int fd)
 }
 
 /* "hidden" fd is a fd used to read scripts, or a copy of such */
-static int is_hidden_fd(struct redirtab *rp, int fd)
+static int
+is_hidden_fd(struct redirtab *rp, int fd)
 {
        int i;
        struct parsefile *pf;
@@ -5273,7 +5337,6 @@ redirect(union node *redir, int flags)
        int newfd;
        int copied_fd2 = -1;
 
-       g_nullredirs++;
        if (!redir) {
                return;
        }
@@ -5295,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;
@@ -5375,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
@@ -5408,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;
@@ -5424,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;
 }
@@ -5439,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)
 {
@@ -5509,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 */
@@ -5574,21 +5631,134 @@ cvtnum(arith_t num)
        return len;
 }
 
-static size_t
-esclen(const char *start, const char *p)
-{
-       size_t esc = 0;
-
-       while (p > start && (unsigned char)*--p == CTLESC) {
-               esc++;
-       }
-       return esc;
-}
-
 /*
- * Remove any CTLESC characters from a string.
- */
-static char *
+ * 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)
+{
+       size_t esc = 0;
+
+       while (p > start && (unsigned char)*--p == CTLESC) {
+               esc++;
+       }
+       return esc;
+}
+
+/*
+ * Remove any CTLESC characters from a string.
+ */
+static char *
 rmescapes(char *str, int flag)
 {
        static const char qchars[] ALIGN1 = {
@@ -5633,7 +5803,6 @@ rmescapes(char *str, int flag)
        while (*p) {
                if ((unsigned char)*p == CTLQUOTEMARK) {
 // Note: both inquotes and protect_against_glob only affect whether
-// CTLESC,<ch> gets converted to <ch> or to \<ch>
                        inquotes = ~inquotes;
                        p++;
                        protect_against_glob = globbing;
@@ -5641,6 +5810,10 @@ rmescapes(char *str, int flag)
                }
                if ((unsigned char)*p == CTLESC) {
                        p++;
+#if DEBUG
+                       if (*p == '\0')
+                               ash_msg_and_raise_error("CTLESC at EOL (shouldn't happen)");
+#endif
                        if (protect_against_glob) {
                                *q++ = '\\';
                        }
@@ -5697,11 +5870,14 @@ memtodest(const char *p, size_t len, int syntax, int quotes)
                unsigned char c = *p++;
                if (c) {
                        int n = SIT(c, syntax);
-                       if ((quotes & QUOTES_ESC) &&
-                                       ((n == CCTL) ||
-                                       (((quotes & EXP_FULL) || syntax != BASESYNTAX) &&
-                                       n == CBACK)))
+                       if ((quotes & QUOTES_ESC)
+                        && ((n == CCTL)
+                           ||  (((quotes & EXP_FULL) || syntax != BASESYNTAX)
+                               && n == CBACK)
+                               )
+                       ) {
                                USTPUTC(CTLESC, q);
+                       }
                } else if (!(quotes & QUOTES_KEEPNUL))
                        continue;
                USTPUTC(c, q);
@@ -5841,49 +6017,53 @@ struct backcmd {                /* result of evalbackcmd */
 };
 
 /* These forward decls are needed to use "eval" code for backticks handling: */
-static uint8_t back_exitstatus; /* exit status of backquoted command */
 #define EV_EXIT 01              /* exit after evaluating tree */
-static void evaltree(union node *, int);
+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));
@@ -5905,10 +6085,8 @@ expbackq(union node *cmd, int flag)
        struct stackmark smark;
 
        INT_OFF;
-       setstackmark(&smark);
-       dest = expdest;
-       startloc = dest - (char *)stackblock();
-       grabstackstr(dest);
+       startloc = expdest - (char *)stackblock();
+       pushstackmark(&smark, startloc);
        evalbackcmd(cmd, &in);
        popstackmark(&smark);
 
@@ -6292,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;
@@ -6301,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;
 
@@ -6435,7 +6610,8 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
                char *idx, *end;
 
                if (!repl) {
-                       if ((repl=strchr(str, CTLESC)))
+                       repl = strchr(str, CTLESC);
+                       if (repl)
                                *repl++ = '\0';
                        else
                                repl = nullstr;
@@ -6568,20 +6744,21 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
  * ash -c 'echo ${#1#}'  name:'1=#'
  */
 static NOINLINE ssize_t
-varvalue(char *name, int varflags, int flags, struct strlist *var_str_list)
+varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int *quotedp)
 {
        const char *p;
        int num;
        int i;
        ssize_t len = 0;
        int sep;
-       int quoted = flags & EXP_QUOTED;
+       int quoted = *quotedp;
        int subtype = varflags & VSTYPE;
        int discard = subtype == VSPLUS || subtype == VSLENGTH;
        int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL;
-       int syntax = quoted ? DQSYNTAX : BASESYNTAX;
+       int syntax;
 
-       sep = quoted ? ((flags & EXP_FULL) << CHAR_BIT) : 0;
+       sep = (flags & EXP_FULL) << CHAR_BIT;
+       syntax = quoted ? DQSYNTAX : BASESYNTAX;
 
        switch (*name) {
        case '$':
@@ -6615,21 +6792,21 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list)
                        raise_error_syntax("bad substitution");
 #endif
                break;
-       case '@': {
+       case '@':
+               if (quoted && sep)
+                       goto param;
+               /* fall through */
+       case '*': {
                char **ap;
                char sepc;
 
-               if (quoted && (flags & EXP_FULL)) {
-                       /* note: this is not meant as PEOF value */
-                       sep = 1 << CHAR_BIT;
-                       goto param;
-               }
-               /* fall through */
-       case '*':
-               sep = ifsset() ? (unsigned char)(ifsval()[0]) : ' ';
+               if (quoted)
+                       sep = 0;
+               sep |= ifsset() ? ifsval()[0] : ' ';
  param:
-               ap = shellparam.p;
                sepc = sep;
+               *quotedp = !sepc;
+               ap = shellparam.p;
                if (!ap)
                        return -1;
                while ((p = *ap++) != NULL) {
@@ -6641,7 +6818,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list)
                        }
                }
                break;
-       } /* case '@' and '*' */
+       } /* case '*' */
        case '0':
        case '1':
        case '2':
@@ -6713,7 +6890,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list)
  * input string.
  */
 static char *
-evalvar(char *p, int flags, struct strlist *var_str_list)
+evalvar(char *p, int flag, struct strlist *var_str_list)
 {
        char varflags;
        char subtype;
@@ -6726,14 +6903,18 @@ evalvar(char *p, int flags, struct strlist *var_str_list)
 
        varflags = (unsigned char) *p++;
        subtype = varflags & VSTYPE;
-       quoted = flags & EXP_QUOTED;
+
+       if (!subtype)
+               raise_error_syntax("bad substitution");
+
+       quoted = flag & EXP_QUOTED;
        var = p;
        easy = (!quoted || (*var == '@' && shellparam.nparam));
        startloc = expdest - (char *)stackblock();
        p = strchr(p, '=') + 1; //TODO: use var_end(p)?
 
  again:
-       varlen = varvalue(var, varflags, flags, var_str_list);
+       varlen = varvalue(var, varflags, flag, var_str_list, &quoted);
        if (varflags & VSNUL)
                varlen--;
 
@@ -6747,36 +6928,27 @@ evalvar(char *p, int flags, struct strlist *var_str_list)
                if (varlen < 0) {
                        argstr(
                                p,
-                               flags | EXP_TILDE | EXP_WORD,
+                               flag | EXP_TILDE | EXP_WORD,
                                var_str_list
                        );
                        goto end;
                }
-               if (easy)
-                       goto record;
-               goto end;
+               goto record;
        }
 
        if (subtype == VSASSIGN || subtype == VSQUESTION) {
-               if (varlen < 0) {
-                       if (subevalvar(p, var, /* strloc: */ 0,
-                                       subtype, startloc, varflags,
-                                       /* quotes: */ flags & ~QUOTES_ESC,
-                                       var_str_list)
-                       ) {
-                               varflags &= ~VSNUL;
-                               /*
-                                * Remove any recorded regions beyond
-                                * start of variable
-                                */
-                               removerecordregions(startloc);
-                               goto again;
-                       }
-                       goto end;
-               }
-               if (easy)
+               if (varlen >= 0)
                        goto record;
-               goto end;
+
+               subevalvar(p, var, 0, subtype, startloc, varflags,
+                          flag & ~QUOTES_ESC, var_str_list);
+               varflags &= ~VSNUL;
+               /*
+                * Remove any recorded regions beyond
+                * start of variable
+                */
+               removerecordregions(startloc);
+               goto again;
        }
 
        if (varlen < 0 && uflag)
@@ -6788,8 +6960,10 @@ evalvar(char *p, int flags, struct strlist *var_str_list)
        }
 
        if (subtype == VSNORMAL) {
-               if (easy)
-                       goto record;
+ record:
+               if (!easy)
+                       goto end;
+               recordregion(startloc, expdest - (char *)stackblock(), quoted);
                goto end;
        }
 
@@ -6818,7 +6992,7 @@ evalvar(char *p, int flags, struct strlist *var_str_list)
                STPUTC('\0', expdest);
                patloc = expdest - (char *)stackblock();
                if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
-                               startloc, varflags, flags, var_str_list)) {
+                               startloc, varflags, flag, var_str_list)) {
                        int amount = expdest - (
                                (char *)stackblock() + patloc - 1
                        );
@@ -6826,8 +7000,7 @@ evalvar(char *p, int flags, struct strlist *var_str_list)
                }
                /* Remove any recorded regions beyond start of variable */
                removerecordregions(startloc);
- record:
-               recordregion(startloc, expdest - (char *)stackblock(), quoted);
+               goto record;
        }
 
  end:
@@ -6853,129 +7026,113 @@ evalvar(char *p, int flags, struct strlist *var_str_list)
 }
 
 /*
- * 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.
+ * Add a file name to the list.
  */
 static void
-ifsbreakup(char *string, struct arglist *arglist)
+addfname(const char *name)
 {
-       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;
+       sp->text = sstrdup(name);
+       *exparg.lastp = sp;
+       exparg.lastp = &sp->next;
 }
 
+/* If we want to use glob() from libc... */
+#if !ENABLE_ASH_INTERNAL_GLOB
+
+/* Add the result of glob() to the list */
 static void
-ifsfree(void)
+addglob(const glob_t *pglob)
 {
-       struct ifsregion *p;
+       char **p = pglob->gl_pathv;
 
-       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;
+               addfname(*p);
+       } while (*++p);
 }
-
-/*
- * Add a file name to the list.
- */
 static void
-addfname(const char *name)
+expandmeta(struct strlist *str /*, int flag*/)
 {
-       struct strlist *sp;
+       /* TODO - EXP_REDIR */
 
-       sp = stzalloc(sizeof(*sp));
-       sp->text = ststrdup(name);
-       *exparg.lastp = sp;
-       exparg.lastp = &sp->next;
+       while (str) {
+               char *p;
+               glob_t pglob;
+               int i;
+
+               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
+// GLOB_NOCHECK: if no match, return unchanged pattern (sans \* escapes?)
+//
+// glibc 2.24.90 glob(GLOB_NOMAGIC) does not remove backslashes used for escaping:
+// if you pass it "file\?", it returns "file\?", not "file?", if no match.
+// Which means you need to unescape the string, right? Not so fast:
+// if there _is_ a file named "file\?" (with backslash), it is returned
+// as "file\?" too (whichever pattern you used to find it, say, "file*").
+// You DONT KNOW by looking at the result whether you need to unescape it.
+//
+// Worse, globbing of "file\?" in a directory with two files, "file?" and "file\?",
+// returns "file\?" - which is WRONG: "file\?" pattern matches "file?" file.
+// Without GLOB_NOMAGIC, this works correctly ("file?" is returned as a match).
+// With GLOB_NOMAGIC | GLOB_NOCHECK, this also works correctly.
+//             i = glob(p, GLOB_NOMAGIC | GLOB_NOCHECK, NULL, &pglob);
+//             i = glob(p, GLOB_NOMAGIC, NULL, &pglob);
+               i = glob(p, 0, NULL, &pglob);
+               //bb_error_msg("glob('%s'):%d '%s'...", p, i, pglob.gl_pathv ? pglob.gl_pathv[0] : "-");
+               if (p != str->text)
+                       free(p);
+               switch (i) {
+               case 0:
+#if 0 // glibc 2.24.90 bug? Patterns like "*/file", when match, don't set GLOB_MAGCHAR
+                       /* GLOB_MAGCHAR is set if *?[ chars were seen (GNU) */
+                       if (!(pglob.gl_flags & GLOB_MAGCHAR))
+                               goto nometa2;
+#endif
+                       addglob(&pglob);
+                       globfree(&pglob);
+                       INT_ON;
+                       break;
+               case GLOB_NOMATCH:
+ //nometa2:
+                       globfree(&pglob);
+                       INT_ON;
+ nometa:
+                       *exparg.lastp = str;
+                       rmescapes(str->text, 0);
+                       exparg.lastp = &str->next;
+                       break;
+               default:        /* GLOB_NOSPACE */
+                       globfree(&pglob);
+                       INT_ON;
+                       ash_msg_and_raise_error(bb_msg_memory_exhausted);
+               }
+               str = str->next;
+       }
 }
 
+#else
+/* ENABLE_ASH_INTERNAL_GLOB: Homegrown globbing code. (dash also has both, uses homegrown one.) */
+
 /*
  * Do metacharacter (i.e. *, ?, [...]) expansion.
  */
@@ -7179,7 +7336,8 @@ expandmeta(struct strlist *str /*, int flag*/)
                p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
                {
                        int i = strlen(str->text);
-                       expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
+//BUGGY estimation of how long expanded name can be
+                       expdir = ckmalloc(i < 2048 ? 2048 : i+1);
                }
                expmeta(expdir, expdir, p);
                free(expdir);
@@ -7204,6 +7362,7 @@ expandmeta(struct strlist *str /*, int flag*/)
                str = str->next;
        }
 }
+#endif /* ENABLE_ASH_INTERNAL_GLOB */
 
 /*
  * Perform variable substitution and command substitution on an argument,
@@ -7219,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));
@@ -7250,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();
 }
 
 /*
@@ -7265,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());
 }
@@ -7291,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;
@@ -7395,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
@@ -7418,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;
        }
 }
@@ -7438,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
@@ -7449,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
@@ -7493,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 */
 }
 
@@ -7765,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 *
@@ -7936,9 +8119,45 @@ typecmd(int argc UNUSED_PARAM, char **argv)
 }
 
 #if ENABLE_ASH_CMDCMD
+/* Is it "command [-p] PROG ARGS" bltin, no other opts? Return ptr to "PROG" if yes */
+static char **
+parse_command_args(char **argv, const char **path)
+{
+       char *cp, c;
+
+       for (;;) {
+               cp = *++argv;
+               if (!cp)
+                       return NULL;
+               if (*cp++ != '-')
+                       break;
+               c = *cp++;
+               if (!c)
+                       break;
+               if (c == '-' && !*cp) {
+                       if (!*++argv)
+                               return NULL;
+                       break;
+               }
+               do {
+                       switch (c) {
+                       case 'p':
+                               *path = bb_default_path;
+                               break;
+                       default:
+                               /* run 'typecmd' for other options */
+                               return NULL;
+                       }
+                       c = *cp++;
+               } while (c);
+       }
+       return argv;
+}
+
 static int FAST_FUNC
 commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 {
+       char *cmd;
        int c;
        enum {
                VERIFY_BRIEF = 1,
@@ -7946,38 +8165,40 @@ commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        } verify = 0;
        const char *path = NULL;
 
+       /* "command [-p] PROG ARGS" (that is, without -V or -v)
+        * never reaches this function.
+        */
+
        while ((c = nextopt("pvV")) != '\0')
                if (c == 'V')
                        verify |= VERIFY_VERBOSE;
                else if (c == 'v')
-                       verify |= VERIFY_BRIEF;
+                       /*verify |= VERIFY_BRIEF*/;
 #if DEBUG
                else if (c != 'p')
                        abort();
 #endif
                else
                        path = bb_default_path;
+
        /* Mimic bash: just "command -v" doesn't complain, it's a nop */
-       if (verify && (*argptr != NULL)) {
-               return describe_command(*argptr, path, verify - VERIFY_BRIEF);
-       }
+       cmd = *argptr;
+       if (/*verify && */ cmd)
+               return describe_command(cmd, path, verify /* - VERIFY_BRIEF*/);
 
        return 0;
 }
 #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 */
 #define EV_TESTED  02           /* exit status is checked; ignore -e flag */
-#define EV_BACKCMD 04           /* command executing within back quotes */
 
 static const uint8_t nodesize[N_NUMBER] ALIGN1 = {
        [NCMD     ] = SHELL_ALIGN(sizeof(struct ncmd)),
@@ -8011,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
@@ -8085,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 *);
@@ -8235,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;
 }
 
@@ -8251,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;
 }
 
@@ -8266,15 +8484,13 @@ defun(char *name, union node *func)
 #define SKIPBREAK      (1 << 0)
 #define SKIPCONT       (1 << 1)
 #define SKIPFUNC       (1 << 2)
-#define SKIPFILE       (1 << 3)
-#define SKIPEVAL       (1 << 4)
 static smallint evalskip;       /* set to SKIPxxx if we are skipping commands */
 static int skipcount;           /* number of levels to skip */
 static int funcnest;            /* depth of function calls */
 static int loopnest;            /* current loop nesting level */
 
 /* Forward decl way out to parsing code - dotrap needs it */
-static int evalstring(char *s, int mask);
+static int evalstring(char *s, int flags);
 
 /* Called to execute a trap.
  * Single callsite - at the end of evaltree().
@@ -8283,96 +8499,77 @@ static int evalstring(char *s, int mask);
  * Perhaps we should avoid entering new trap handlers
  * while we are executing a trap handler. [is it a TODO?]
  */
-static int
+static void
 dotrap(void)
 {
        uint8_t *g;
        int sig;
-       uint8_t savestatus;
+       uint8_t last_status;
 
-       savestatus = exitstatus;
+       if (!pending_sig)
+               return;
+
+       last_status = exitstatus;
        pending_sig = 0;
-       xbarrier();
+       barrier();
 
        TRACE(("dotrap entered\n"));
        for (sig = 1, g = gotsig; sig < NSIG; sig++, g++) {
-               int want_exexit;
-               char *t;
+               char *p;
 
-               if (*g == 0)
+               if (!*g)
                        continue;
-               t = trap[sig];
+
+               if (evalskip) {
+                       pending_sig = sig;
+                       break;
+               }
+
+               p = trap[sig];
                /* non-trapped SIGINT is handled separately by raise_interrupt,
                 * don't upset it by resetting gotsig[SIGINT-1] */
-               if (sig == SIGINT && !t)
+               if (sig == SIGINT && !p)
                        continue;
 
-               TRACE(("sig %d is active, will run handler '%s'\n", sig, t));
+               TRACE(("sig %d is active, will run handler '%s'\n", sig, p));
                *g = 0;
-               if (!t)
+               if (!p)
                        continue;
-               want_exexit = evalstring(t, SKIPEVAL);
-               exitstatus = savestatus;
-               if (want_exexit) {
-                       TRACE(("dotrap returns %d\n", want_exexit));
-                       return want_exexit;
-               }
+               evalstring(p, 0);
        }
-
-       TRACE(("dotrap returns 0\n"));
-       return 0;
+       exitstatus = last_status;
+       TRACE(("dotrap returns\n"));
 }
 
 /* forward declarations - evaluation is fairly recursive business... */
-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 int evalloop(union node *, int);
+static int evalfor(union node *, int);
+static int evalcase(union node *, int);
+static int evalsubshell(union node *, int);
 static void expredir(union node *);
-static void evalpipe(union node *, int);
-static void evalcommand(union node *, int);
-static int evalbltin(const struct builtincmd *, int, char **);
+static int evalpipe(union node *, int);
+static int evalcommand(union node *, int);
+static int evalbltin(const struct builtincmd *, int, char **, int);
 static void prehash(union node *);
 
 /*
  * Evaluate a parse tree.  The value is left in the global variable
  * exitstatus.
  */
-static void
+static int
 evaltree(union node *n, int flags)
 {
-       struct jmploc *volatile savehandler = exception_handler;
-       struct jmploc jmploc;
        int checkexit = 0;
-       void (*evalfn)(union node *, int);
-       int status;
-       int int_level;
-
-       SAVE_INT(int_level);
+       int (*evalfn)(union node *, int);
+       int status = 0;
 
        if (n == NULL) {
                TRACE(("evaltree(NULL) called\n"));
-               goto out1;
+               goto out;
        }
        TRACE(("evaltree(%p: %d, %d) called\n", n, n->type, flags));
 
-       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);
-               }
-       }
+       dotrap();
 
        switch (n->type) {
        default:
@@ -8382,17 +8579,16 @@ evaltree(union node *n, int flags)
                break;
 #endif
        case NNOT:
-               evaltree(n->nnot.com, EV_TESTED);
-               status = !exitstatus;
+               status = !evaltree(n->nnot.com, EV_TESTED);
                goto setstatus;
        case NREDIR:
                expredir(n->nredir.redirect);
                status = redirectsafe(n->nredir.redirect, REDIR_PUSH);
                if (!status) {
-                       evaltree(n->nredir.n, flags & EV_TESTED);
-                       status = exitstatus;
+                       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;
@@ -8410,7 +8606,7 @@ evaltree(union node *n, int flags)
        case NSUBSHELL:
        case NBACKGND:
                evalfn = evalsubshell;
-               goto calleval;
+               goto checkexit;
        case NPIPE:
                evalfn = evalpipe;
                goto checkexit;
@@ -8428,27 +8624,24 @@ evaltree(union node *n, int flags)
 #error NOR + 1 != NSEMI
 #endif
                unsigned is_or = n->type - NAND;
-               evaltree(
+               status = evaltree(
                        n->nbinary.ch1,
                        (flags | ((is_or >> 1) - 1)) & EV_TESTED
                );
-               if ((!exitstatus) == is_or)
+               if ((!status) == is_or || evalskip)
                        break;
-               if (!evalskip) {
-                       n = n->nbinary.ch2;
+               n = n->nbinary.ch2;
  evaln:
-                       evalfn = evaltree;
+               evalfn = evaltree;
  calleval:
-                       evalfn(n, flags);
-                       break;
-               }
-               break;
+               status = evalfn(n, flags);
+               goto setstatus;
        }
        case NIF:
-               evaltree(n->nif.test, EV_TESTED);
+               status = evaltree(n->nif.test, EV_TESTED);
                if (evalskip)
                        break;
-               if (exitstatus == 0) {
+               if (!status) {
                        n = n->nif.ifpart;
                        goto evaln;
                }
@@ -8456,136 +8649,141 @@ evaltree(union node *n, int flags)
                        n = n->nif.elsepart;
                        goto evaln;
                }
-               goto success;
-       case NDEFUN:
-               defun(n->narg.text, n->narg.next);
- success:
                status = 0;
+               goto setstatus;
+       case NDEFUN:
+               defun(n);
+               /* Not necessary. To test it:
+                * "false; f() { qwerty; }; echo $?" should print 0.
+                */
+               /* status = 0; */
  setstatus:
                exitstatus = status;
                break;
        }
-
  out:
-       exception_handler = savehandler;
-
- out1:
        /* Order of checks below is important:
         * signal handlers trigger before exit caused by "set -e".
         */
-       if (pending_sig && dotrap())
-               goto exexit;
-       if (checkexit & exitstatus)
-               evalskip |= SKIPEVAL;
+       dotrap();
 
-       if (flags & EV_EXIT) {
- exexit:
+       if (checkexit & status)
+               raise_exception(EXEXIT);
+       if (flags & EV_EXIT)
                raise_exception(EXEXIT);
-       }
 
-       RESTORE_INT(int_level);
        TRACE(("leaving evaltree (no interrupts)\n"));
+       return exitstatus;
 }
 
 #if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
 static
 #endif
-void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__));
+int evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__));
 
-static void
+static int
+skiploop(void)
+{
+       int skip = evalskip;
+
+       switch (skip) {
+       case 0:
+               break;
+       case SKIPBREAK:
+       case SKIPCONT:
+               if (--skipcount <= 0) {
+                       evalskip = 0;
+                       break;
+               }
+               skip = SKIPBREAK;
+               break;
+       }
+       return skip;
+}
+
+static int
 evalloop(union node *n, int flags)
 {
+       int skip;
        int status;
 
        loopnest++;
        status = 0;
        flags &= EV_TESTED;
-       for (;;) {
+       do {
                int i;
 
-               evaltree(n->nbinary.ch1, EV_TESTED);
-               if (evalskip) {
- skipping:
-                       if (evalskip == SKIPCONT && --skipcount <= 0) {
-                               evalskip = 0;
-                               continue;
-                       }
-                       if (evalskip == SKIPBREAK && --skipcount <= 0)
-                               evalskip = 0;
-                       break;
-               }
-               i = exitstatus;
+               i = evaltree(n->nbinary.ch1, EV_TESTED);
+               skip = skiploop();
+               if (skip == SKIPFUNC)
+                       status = i;
+               if (skip)
+                       continue;
                if (n->type != NWHILE)
                        i = !i;
                if (i != 0)
                        break;
-               evaltree(n->nbinary.ch2, flags);
-               status = exitstatus;
-               if (evalskip)
-                       goto skipping;
-       }
+               status = evaltree(n->nbinary.ch2, flags);
+               skip = skiploop();
+       } while (!(skip & ~SKIPCONT));
        loopnest--;
-       exitstatus = status;
+
+       return status;
 }
 
-static void
+static int
 evalfor(union node *n, int flags)
 {
        struct arglist arglist;
        union node *argp;
        struct strlist *sp;
        struct stackmark smark;
+       int status = 0;
 
        setstackmark(&smark);
        arglist.list = NULL;
        arglist.lastp = &arglist.list;
        for (argp = n->nfor.args; argp; argp = argp->narg.next) {
                expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
-               /* XXX */
-               if (evalskip)
-                       goto out;
        }
        *arglist.lastp = NULL;
 
-       exitstatus = 0;
        loopnest++;
        flags &= EV_TESTED;
        for (sp = arglist.list; sp; sp = sp->next) {
                setvar0(n->nfor.var, sp->text);
-               evaltree(n->nfor.body, flags);
-               if (evalskip) {
-                       if (evalskip == SKIPCONT && --skipcount <= 0) {
-                               evalskip = 0;
-                               continue;
-                       }
-                       if (evalskip == SKIPBREAK && --skipcount <= 0)
-                               evalskip = 0;
+               status = evaltree(n->nfor.body, flags);
+               if (skiploop() & ~SKIPCONT)
                        break;
-               }
        }
        loopnest--;
- out:
        popstackmark(&smark);
+
+       return status;
 }
 
-static void
+static int
 evalcase(union node *n, int flags)
 {
        union node *cp;
        union node *patp;
        struct arglist arglist;
        struct stackmark smark;
+       int status = 0;
 
        setstackmark(&smark);
        arglist.list = NULL;
        arglist.lastp = &arglist.list;
        expandarg(n->ncase.expr, &arglist, EXP_TILDE);
-       exitstatus = 0;
        for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) {
                for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) {
                        if (casematch(patp, arglist.list->text)) {
-                               if (evalskip == 0) {
-                                       evaltree(cp->nclist.body, flags);
+                               /* Ensure body is non-empty as otherwise
+                                * EV_EXIT may prevent us from setting the
+                                * exit status.
+                                */
+                               if (evalskip == 0 && cp->nclist.body) {
+                                       status = evaltree(cp->nclist.body, flags);
                                }
                                goto out;
                        }
@@ -8593,12 +8791,14 @@ evalcase(union node *n, int flags)
        }
  out:
        popstackmark(&smark);
+
+       return status;
 }
 
 /*
  * Kick off a subshell to evaluate a tree.
  */
-static void
+static int
 evalsubshell(union node *n, int flags)
 {
        struct job *jp;
@@ -8621,11 +8821,12 @@ evalsubshell(union node *n, int flags)
                evaltreenr(n->nredir.n, flags);
                /* never returns */
        }
+       /* parent */
        status = 0;
        if (!backgnd)
                status = waitforjob(jp);
-       exitstatus = status;
        INT_ON;
+       return status;
 }
 
 /*
@@ -8698,7 +8899,7 @@ expredir(union node *n)
  * of the shell, which make the last process in a pipeline the parent
  * of all the rest.)
  */
-static void
+static int
 evalpipe(union node *n, int flags)
 {
        struct job *jp;
@@ -8706,6 +8907,7 @@ evalpipe(union node *n, int flags)
        int pipelen;
        int prevfd;
        int pip[2];
+       int status = 0;
 
        TRACE(("evalpipe(0x%lx) called\n", (long)n));
        pipelen = 0;
@@ -8725,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]);
@@ -8740,6 +8943,7 @@ evalpipe(union node *n, int flags)
                        evaltreenr(lp->n, flags);
                        /* never returns */
                }
+               /* parent */
                if (prevfd >= 0)
                        close(prevfd);
                prevfd = pip[0];
@@ -8748,10 +8952,12 @@ evalpipe(union node *n, int flags)
                        close(pip[1]);
        }
        if (n->npipe.pipe_backgnd == 0) {
-               exitstatus = waitforjob(jp);
-               TRACE(("evalpipe:  job done exit status %d\n", exitstatus));
+               status = waitforjob(jp);
+               TRACE(("evalpipe:  job done exit status %d\n", status));
        }
        INT_ON;
+
+       return status;
 }
 
 /*
@@ -8850,12 +9056,12 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
 
        saveparam = shellparam;
        savelocalvars = localvars;
+       savehandler = exception_handler;
        e = setjmp(jmploc.loc);
        if (e) {
                goto funcdone;
        }
        INT_OFF;
-       savehandler = exception_handler;
        exception_handler = &jmploc;
        localvars = NULL;
        shellparam.malloced = 0;
@@ -8868,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--;
@@ -8883,42 +9089,6 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
        return e;
 }
 
-#if ENABLE_ASH_CMDCMD
-static char **
-parse_command_args(char **argv, const char **path)
-{
-       char *cp, c;
-
-       for (;;) {
-               cp = *++argv;
-               if (!cp)
-                       return NULL;
-               if (*cp++ != '-')
-                       break;
-               c = *cp++;
-               if (!c)
-                       break;
-               if (c == '-' && !*cp) {
-                       if (!*++argv)
-                               return NULL;
-                       break;
-               }
-               do {
-                       switch (c) {
-                       case 'p':
-                               *path = bb_default_path;
-                               break;
-                       default:
-                               /* run 'typecmd' for other options */
-                               return NULL;
-                       }
-                       c = *cp++;
-               } while (c);
-       }
-       return argv;
-}
-#endif
-
 /*
  * Make a variable a local variable.  When a variable is made local, it's
  * value and flags are saved in a localvar structure.  The saved values
@@ -8947,7 +9117,7 @@ mklocal(char *name)
                        /* else:
                         * it's a duplicate "local VAR" declaration, do nothing
                         */
-                       return;
+                       goto ret;
                }
                lvp = lvp->next;
        }
@@ -8986,6 +9156,7 @@ mklocal(char *name)
        lvp->vp = vp;
        lvp->next = localvars;
        localvars = lvp;
+ ret:
        INT_ON;
 }
 
@@ -9054,14 +9225,14 @@ returncmd(int argc UNUSED_PARAM, char **argv)
         * If called outside a function, do what ksh does;
         * skip the rest of the file.
         */
-       evalskip = funcnest ? SKIPFUNC : SKIPFILE;
+       evalskip = SKIPFUNC;
        return argv[1] ? number(argv[1]) : exitstatus;
 }
 
 /* Forward declarations for builtintab[] */
 static int breakcmd(int, char **) FAST_FUNC;
 static int dotcmd(int, char **) FAST_FUNC;
-static int evalcmd(int, char **) FAST_FUNC;
+static int evalcmd(int, char **, int) FAST_FUNC;
 static int exitcmd(int, char **) FAST_FUNC;
 static int exportcmd(int, char **) FAST_FUNC;
 #if ENABLE_ASH_GETOPTS
@@ -9131,7 +9302,7 @@ static const struct builtincmd builtintab[] = {
 #if ENABLE_ASH_BUILTIN_ECHO
        { BUILTIN_REGULAR       "echo"    , echocmd    },
 #endif
-       { BUILTIN_SPEC_REG      "eval"    , evalcmd    },
+       { BUILTIN_SPEC_REG      "eval"    , NULL       }, /*evalcmd() has a differing prototype*/
        { BUILTIN_SPEC_REG      "exec"    , execcmd    },
        { BUILTIN_SPEC_REG      "exit"    , exitcmd    },
        { BUILTIN_SPEC_REG_ASSG "export"  , exportcmd  },
@@ -9187,27 +9358,28 @@ static const struct builtincmd builtintab[] = {
 
 /* Should match the above table! */
 #define COMMANDCMD (builtintab + \
-       2 + \
-       1 * ENABLE_ASH_BUILTIN_TEST + \
-       1 * ENABLE_ASH_BUILTIN_TEST * ENABLE_ASH_BASH_COMPAT + \
-       1 * ENABLE_ASH_ALIAS + \
-       1 * ENABLE_ASH_JOB_CONTROL + \
-       3)
-#define EXECCMD (builtintab + \
-       2 + \
-       1 * ENABLE_ASH_BUILTIN_TEST + \
-       1 * ENABLE_ASH_BUILTIN_TEST * ENABLE_ASH_BASH_COMPAT + \
-       1 * ENABLE_ASH_ALIAS + \
-       1 * ENABLE_ASH_JOB_CONTROL + \
-       3 + \
-       1 * ENABLE_ASH_CMDCMD + \
-       1 + \
-       ENABLE_ASH_BUILTIN_ECHO + \
-       1)
+       /* . : */       2 + \
+       /* [ */         1 * ENABLE_ASH_BUILTIN_TEST + \
+       /* [[ */        1 * ENABLE_ASH_BUILTIN_TEST * ENABLE_ASH_BASH_COMPAT + \
+       /* alias */     1 * ENABLE_ASH_ALIAS + \
+       /* bg */        1 * ENABLE_ASH_JOB_CONTROL + \
+       /* break cd cddir  */   3)
+#define EVALCMD (COMMANDCMD + \
+       /* command */   1 * ENABLE_ASH_CMDCMD + \
+       /* continue */  1 + \
+       /* echo */      1 * ENABLE_ASH_BUILTIN_ECHO + \
+       0)
+#define EXECCMD (EVALCMD + \
+       /* eval */      1)
 
 /*
  * 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)
 {
@@ -9215,7 +9387,7 @@ find_builtin(const char *name)
 
        bp = bsearch(
                name, builtintab, ARRAY_SIZE(builtintab), sizeof(builtintab[0]),
-               pstrcmp
+               pstrcmp1
        );
        return bp;
 }
@@ -9238,7 +9410,7 @@ bltincmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
         * as POSIX mandates */
        return back_exitstatus;
 }
-static void
+static int
 evalcommand(union node *cmd, int flags)
 {
        static const struct builtincmd null_bltin = {
@@ -9293,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;
@@ -9374,6 +9548,9 @@ evalcommand(union node *cmd, int flags)
                                nargv = parse_command_args(argv, &path);
                                if (!nargv)
                                        break;
+                               /* It's "command [-p] PROG ARGS" (that is, no -Vv).
+                                * nargv => "PROG". path is updated if -p.
+                                */
                                argc -= nargv - argv;
                                argv = nargv;
                                cmd_flag |= DO_NOFUNC;
@@ -9407,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
@@ -9421,9 +9598,9 @@ evalcommand(union node *cmd, int flags)
                        jp = makejob(/*cmd,*/ 1);
                        if (forkshell(jp, cmd, FORK_FG) != 0) {
                                /* parent */
-                               exitstatus = waitforjob(jp);
+                               status = waitforjob(jp);
                                INT_ON;
-                               TRACE(("forked child exited with %d\n", exitstatus));
+                               TRACE(("forked child exited with %d\n", status));
                                break;
                        }
                        /* child */
@@ -9452,24 +9629,15 @@ evalcommand(union node *cmd, int flags)
                 * to reap the zombie and make kill detect that it's gone: */
                dowait(DOWAIT_NONBLOCK, NULL);
 
-               if (evalbltin(cmdentry.u.cmd, argc, argv)) {
-                       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 (evalbltin(cmdentry.u.cmd, argc, argv, flags)) {
+                       if (exception_type == EXERROR && spclbltin <= 0) {
+                               FORCE_INT_ON;
+                               goto readstatus;
                        }
-                       FORCE_INT_ON;
+ raise:
+                       longjmp(exception_handler->loc, 1);
                }
-               break;
+               goto readstatus;
 
        case CMDFUNCTION:
                listsetvar(varlist.list, 0);
@@ -9477,11 +9645,14 @@ evalcommand(union node *cmd, int flags)
                dowait(DOWAIT_NONBLOCK, NULL);
                if (evalfun(cmdentry.u.func, argc, argv, flags))
                        goto raise;
+ readstatus:
+               status = exitstatus;
                break;
        } /* 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...
@@ -9490,29 +9661,36 @@ evalcommand(union node *cmd, int flags)
                setvar0("_", lastarg);
        }
        popstackmark(&smark);
+
+       return status;
 }
 
 static int
-evalbltin(const struct builtincmd *cmd, int argc, char **argv)
+evalbltin(const struct builtincmd *cmd, int argc, char **argv, int flags)
 {
        char *volatile savecmdname;
        struct jmploc *volatile savehandler;
        struct jmploc jmploc;
+       int status;
        int i;
 
        savecmdname = commandname;
+       savehandler = exception_handler;
        i = setjmp(jmploc.loc);
        if (i)
                goto cmddone;
-       savehandler = exception_handler;
        exception_handler = &jmploc;
        commandname = argv[0];
        argptr = argv + 1;
        optptr = NULL;                  /* initialize nextopt */
-       exitstatus = (*cmd->builtin)(argc, argv);
+       if (cmd == EVALCMD)
+               status = evalcmd(argc, argv, flags);
+       else
+               status = (*cmd->builtin)(argc, argv);
        flush_stdout_stderr();
+       status |= ferror(stdout);
+       exitstatus = status;
  cmddone:
-       exitstatus |= ferror(stdout);
        clearerr(stdout);
        commandname = savecmdname;
        exception_handler = savehandler;
@@ -9576,8 +9754,7 @@ breakcmd(int argc UNUSED_PARAM, char **argv)
 }
 
 
-/* ============ input.c
- *
+/*
  * This implements the input routines used by the parser.
  */
 
@@ -9616,6 +9793,8 @@ pushstring(char *s, struct alias *ap)
        g_parsefile->strpush = sp;
        sp->prev_string = g_parsefile->next_to_pgetc;
        sp->prev_left_in_line = g_parsefile->left_in_line;
+       sp->unget = g_parsefile->unget;
+       memcpy(sp->lastc, g_parsefile->lastc, sizeof(sp->lastc));
 #if ENABLE_ASH_ALIAS
        sp->ap = ap;
        if (ap) {
@@ -9625,6 +9804,7 @@ pushstring(char *s, struct alias *ap)
 #endif
        g_parsefile->next_to_pgetc = s;
        g_parsefile->left_in_line = len;
+       g_parsefile->unget = 0;
        INT_ON;
 }
 
@@ -9652,17 +9832,14 @@ popstring(void)
 #endif
        g_parsefile->next_to_pgetc = sp->prev_string;
        g_parsefile->left_in_line = sp->prev_left_in_line;
+       g_parsefile->unget = sp->unget;
+       memcpy(g_parsefile->lastc, sp->lastc, sizeof(sp->lastc));
        g_parsefile->strpush = sp->prev;
        if (sp != &(g_parsefile->basestrpush))
                free(sp);
        INT_ON;
 }
 
-//FIXME: BASH_COMPAT with "...&" does TWO pungetc():
-//it peeks whether it is &>, and then pushes back both chars.
-//This function needs to save last *next_to_pgetc to buf[0]
-//to make two pungetc() reliable. Currently,
-// pgetc (out of buf: does preadfd), pgetc, pungetc, pungetc won't work...
 static int
 preadfd(void)
 {
@@ -9747,13 +9924,14 @@ preadfd(void)
  */
 //#define pgetc_debug(...) bb_error_msg(__VA_ARGS__)
 #define pgetc_debug(...) ((void)0)
+static int pgetc(void);
 static int
 preadbuffer(void)
 {
        char *q;
        int more;
 
-       while (g_parsefile->strpush) {
+       if (g_parsefile->strpush) {
 #if ENABLE_ASH_ALIAS
                if (g_parsefile->left_in_line == -1
                 && g_parsefile->strpush->ap
@@ -9765,13 +9943,7 @@ preadbuffer(void)
                }
 #endif
                popstring();
-               /* try "pgetc" now: */
-               pgetc_debug("preadbuffer internal pgetc at %d:%p'%s'",
-                               g_parsefile->left_in_line,
-                               g_parsefile->next_to_pgetc,
-                               g_parsefile->next_to_pgetc);
-               if (--g_parsefile->left_in_line >= 0)
-                       return (unsigned char)(*g_parsefile->next_to_pgetc++);
+               return pgetc();
        }
        /* on both branches above g_parsefile->left_in_line < 0.
         * "pgetc" needs refilling.
@@ -9850,27 +10022,41 @@ preadbuffer(void)
        return (unsigned char)*g_parsefile->next_to_pgetc++;
 }
 
-#define pgetc_as_macro() \
-       (--g_parsefile->left_in_line >= 0 \
-       ? (unsigned char)*g_parsefile->next_to_pgetc++ \
-       : preadbuffer() \
-       )
+static void
+nlprompt(void)
+{
+       g_parsefile->linno++;
+       setprompt_if(doprompt, 2);
+}
+static void
+nlnoprompt(void)
+{
+       g_parsefile->linno++;
+       needprompt = doprompt;
+}
 
 static int
 pgetc(void)
 {
-       pgetc_debug("pgetc_fast at %d:%p'%s'",
+       int c;
+
+       pgetc_debug("pgetc at %d:%p'%s'",
                        g_parsefile->left_in_line,
                        g_parsefile->next_to_pgetc,
                        g_parsefile->next_to_pgetc);
-       return pgetc_as_macro();
-}
+       if (g_parsefile->unget)
+               return g_parsefile->lastc[--g_parsefile->unget];
 
-#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
-# define pgetc_fast() pgetc()
-#else
-# define pgetc_fast() pgetc_as_macro()
-#endif
+       if (--g_parsefile->left_in_line >= 0)
+               c = (signed char)*g_parsefile->next_to_pgetc++;
+       else
+               c = preadbuffer();
+
+       g_parsefile->lastc[1] = g_parsefile->lastc[0];
+       g_parsefile->lastc[0] = c;
+
+       return c;
+}
 
 #if ENABLE_ASH_ALIAS
 static int
@@ -9878,11 +10064,11 @@ pgetc_without_PEOA(void)
 {
        int c;
        do {
-               pgetc_debug("pgetc_fast at %d:%p'%s'",
+               pgetc_debug("pgetc at %d:%p'%s'",
                                g_parsefile->left_in_line,
                                g_parsefile->next_to_pgetc,
                                g_parsefile->next_to_pgetc);
-               c = pgetc_fast();
+               c = pgetc();
        } while (c == PEOA);
        return c;
 }
@@ -9916,18 +10102,31 @@ pfgets(char *line, int len)
 }
 
 /*
- * Undo the last call to pgetc.  Only one character may be pushed back.
+ * Undo a call to pgetc.  Only two characters may be pushed back.
  * PEOF may be pushed back.
  */
 static void
 pungetc(void)
 {
-       g_parsefile->left_in_line++;
-       g_parsefile->next_to_pgetc--;
-       pgetc_debug("pushed back to %d:%p'%s'",
-                       g_parsefile->left_in_line,
-                       g_parsefile->next_to_pgetc,
-                       g_parsefile->next_to_pgetc);
+       g_parsefile->unget++;
+}
+
+/* This one eats backslash+newline */
+static int
+pgetc_eatbnl(void)
+{
+       int c;
+
+       while ((c = pgetc()) == '\\') {
+               if (pgetc() != '\n') {
+                       pungetc();
+                       break;
+               }
+
+               nlprompt();
+       }
+
+       return c;
 }
 
 /*
@@ -9944,6 +10143,7 @@ pushfile(void)
        pf->pf_fd = -1;
        /*pf->strpush = NULL; - ckzalloc did it */
        /*pf->basestrpush.prev = NULL;*/
+       /*pf->unget = 0;*/
        g_parsefile = pf;
 }
 
@@ -9952,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);
@@ -9994,7 +10197,6 @@ closescript(void)
 static void
 setinputfd(int fd, int push)
 {
-       close_on_exec_on(fd);
        if (push) {
                pushfile();
                g_parsefile->buf = NULL;
@@ -10015,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;
@@ -10053,8 +10252,7 @@ setinputstring(char *string)
 }
 
 
-/* ============ mail.c
- *
+/*
  * Routines to check for mail.
  */
 
@@ -10372,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;
@@ -10411,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");
@@ -10428,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);
@@ -10440,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;
 }
 
@@ -10475,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 */
 
@@ -10515,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;
 }
 
@@ -10573,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;
 
@@ -10955,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);
                }
@@ -11101,7 +11295,8 @@ parse_command(void)
 }
 
 #if ENABLE_ASH_BASH_COMPAT
-static int decode_dollar_squote(void)
+static int
+decode_dollar_squote(void)
 {
        static const char C_escapes[] ALIGN1 = "nrbtfav""x\\01234567";
        int c, cnt;
@@ -11169,7 +11364,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
        /* NB: syntax parameter fits into smallint */
        /* c parameter is an unsigned char or PEOF or PEOA */
        char *out;
-       int len;
+       size_t len;
        char line[EOFMARKLEN + 1];
        struct nodelist *bqlist;
        smallint quotef;
@@ -11212,25 +11407,31 @@ 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:
                        USTPUTC(c, out);
                        break;
                case CCTL:
-                       if (eofmark == NULL || dblquote)
-                               USTPUTC(CTLESC, out);
 #if ENABLE_ASH_BASH_COMPAT
                        if (c == '\\' && bash_dollar_squote) {
                                c = decode_dollar_squote();
+                               if (c == '\0') {
+                                       /* skip $'\000', $'\x00' (like bash) */
+                                       break;
+                               }
                                if (c & 0x100) {
-                                       USTPUTC('\\', out);
+                                       /* Unknown escape. Encode as '\z' */
                                        c = (unsigned char)c;
+                                       if (eofmark == NULL || dblquote)
+                                               USTPUTC(CTLESC, out);
+                                       USTPUTC('\\', out);
                                }
                        }
 #endif
+                       if (eofmark == NULL || dblquote)
+                               USTPUTC(CTLESC, out);
                        USTPUTC(c, out);
                        break;
                case CBACK:     /* backslash */
@@ -11240,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) {
@@ -11308,7 +11509,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                        if (parenlevel > 0) {
                                parenlevel--;
                        } else {
-                               if (pgetc() == ')') {
+                               if (pgetc_eatbnl() == ')') {
                                        c = CTLENDARI;
                                        if (--arinest == 0) {
                                                syntax = prevsyntax;
@@ -11335,6 +11536,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                        if (varnest == 0) {
 #if ENABLE_ASH_BASH_COMPAT
                                if (c == '&') {
+//Can't call pgetc_eatbnl() here, this requires three-deep pungetc()
                                        if (pgetc() == '>')
                                                c = 0x100 + '>'; /* flag &> */
                                        pungetc();
@@ -11345,7 +11547,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                        IF_ASH_ALIAS(if (c != PEOA))
                                USTPUTC(c, out);
                }
-               c = pgetc_fast();
+               c = pgetc();
        } /* for (;;) */
  endword:
 
@@ -11404,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);
                                }
@@ -11508,9 +11715,8 @@ parseredir: {
 parsesub: {
        unsigned char subtype;
        int typeloc;
-       int flags;
 
-       c = pgetc();
+       c = pgetc_eatbnl();
        if (c > 255 /* PEOA or PEOF */
         || (c != '(' && c != '{' && !is_name(c) && !is_special(c))
        ) {
@@ -11523,7 +11729,7 @@ parsesub: {
                pungetc();
        } else if (c == '(') {
                /* $(command) or $((arith)) */
-               if (pgetc() == '(') {
+               if (pgetc_eatbnl() == '(') {
 #if ENABLE_SH_MATH_SUPPORT
                        PARSEARITH();
 #else
@@ -11537,54 +11743,59 @@ 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();
-                       if (c == '#') {
-                               c = pgetc();
-                               if (c == '}')
-                                       c = '#'; /* ${#} - same as $# */
-                               else
-                                       subtype = VSLENGTH; /* ${#VAR} */
-                       } else {
-                               subtype = 0;
-                       }
+                       c = pgetc_eatbnl();
+                       subtype = 0;
                }
-               if (c <= 255 /* not PEOA or PEOF */ && is_name(c)) {
+ varname:
+               if (is_name(c)) {
                        /* $[{[#]]NAME[}] */
                        do {
                                STPUTC(c, out);
-                               c = pgetc();
-                       } while (c <= 255 /* not PEOA or PEOF */ && is_in_name(c));
+                               c = pgetc_eatbnl();
+                       } while (is_in_name(c));
                } else if (isdigit(c)) {
                        /* $[{[#]]NUM[}] */
                        do {
                                STPUTC(c, out);
-                               c = pgetc();
+                               c = pgetc_eatbnl();
                        } while (isdigit(c));
                } else if (is_special(c)) {
                        /* $[{[#]]<specialchar>[}] */
-                       USTPUTC(c, out);
-                       c = pgetc();
+                       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} */
                        /* c == first char after VAR */
                        switch (c) {
                        case ':':
-                               c = pgetc();
+                               c = pgetc_eatbnl();
 #if ENABLE_ASH_BASH_COMPAT
                                /* This check is only needed to not misinterpret
                                 * ${VAR:-WORD}, ${VAR:+WORD}, ${VAR:=WORD}, ${VAR:?WORD}
@@ -11593,25 +11804,25 @@ 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 '%':
                        case '#': {
                                int cc = c;
                                subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT);
-                               c = pgetc();
+                               c = pgetc_eatbnl();
                                if (c != cc)
-                                       goto do_pungetc;
+                                       goto badsub;
                                subtype++;
                                break;
                        }
@@ -11621,24 +11832,24 @@ parsesub: {
 //TODO: encode pattern and repl separately.
 // Currently ${v/$var_with_slash/repl} is horribly broken
                                subtype = VSREPLACE;
-                               c = pgetc();
+                               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;
 }
@@ -11693,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
@@ -11719,8 +11929,7 @@ parsebackq: {
                                raise_error_syntax("EOF in backquote substitution");
 
                        case '\n':
-                               g_parsefile->linno++;
-                               needprompt = doprompt;
+                               nlnoprompt();
                                break;
 
                        default:
@@ -11839,7 +12048,7 @@ xxreadtoken(void)
        setprompt_if(needprompt, 2);
        startlinno = g_parsefile->linno;
        for (;;) {                      /* until token or start of word found */
-               c = pgetc_fast();
+               c = pgetc();
                if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA))
                        continue;
 
@@ -11852,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);
@@ -11902,7 +12109,7 @@ xxreadtoken(void)
        setprompt_if(needprompt, 2);
        startlinno = g_parsefile->linno;
        for (;;) {      /* until token or start of word found */
-               c = pgetc_fast();
+               c = pgetc();
                switch (c) {
                case ' ': case '\t':
                IF_ASH_ALIAS(case PEOA:)
@@ -11914,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);
@@ -11993,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;
                }
        }
@@ -12014,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;
 }
@@ -12082,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;
@@ -12103,35 +12314,61 @@ expandstr(const char *ps)
  * Execute a command or commands contained in a string.
  */
 static int
-evalstring(char *s, int mask)
+evalstring(char *s, int flags)
 {
+       struct jmploc *volatile savehandler;
+       struct jmploc jmploc;
+       int ex;
+
        union node *n;
        struct stackmark smark;
-       int skip;
+       int status;
 
+       s = sstrdup(s);
        setinputstring(s);
        setstackmark(&smark);
 
-       skip = 0;
+       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) {
-               evaltree(n, 0);
+               int i;
+
+               i = evaltree(n, flags);
+               if (n)
+                       status = i;
                popstackmark(&smark);
-               skip = evalskip;
-               if (skip)
+               if (evalskip)
                        break;
        }
+ out:
+       popstackmark(&smark);
        popfile();
+       stunalloc(s);
 
-       skip &= mask;
-       evalskip = skip;
-       return skip;
+       exception_handler = savehandler;
+       if (ex)
+                longjmp(exception_handler->loc, ex);
+
+       return status;
 }
 
 /*
  * The eval command.
  */
 static int FAST_FUNC
-evalcmd(int argc UNUSED_PARAM, char **argv)
+evalcmd(int argc UNUSED_PARAM, char **argv, int flags)
 {
        char *p;
        char *concat;
@@ -12151,9 +12388,9 @@ evalcmd(int argc UNUSED_PARAM, char **argv)
                        STPUTC('\0', concat);
                        p = grabstackstr(concat);
                }
-               evalstring(p, ~SKIPEVAL);
+               return evalstring(p, flags & EV_TESTED);
        }
-       return exitstatus;
+       return 0;
 }
 
 /*
@@ -12167,6 +12404,7 @@ cmdloop(int top)
        union node *n;
        struct stackmark smark;
        int inter;
+       int status = 0;
        int numeof = 0;
 
        TRACE(("cmdloop(%d) called\n", top));
@@ -12198,20 +12436,24 @@ cmdloop(int top)
                        }
                        numeof++;
                } else if (nflag == 0) {
+                       int i;
+
                        /* job_warning can only be 2,1,0. Here 2->1, 1/0->0 */
                        job_warning >>= 1;
                        numeof = 0;
-                       evaltree(n, 0);
+                       i = evaltree(n, 0);
+                       if (n)
+                               status = i;
                }
                popstackmark(&smark);
                skip = evalskip;
 
                if (skip) {
-                       evalskip = 0;
-                       return skip & SKIPEVAL;
+                       evalskip &= ~SKIPFUNC;
+                       break;
                }
        }
-       return 0;
+       return status;
 }
 
 /*
@@ -12256,32 +12498,38 @@ find_dot_file(char *name)
 }
 
 static int FAST_FUNC
-dotcmd(int argc, char **argv)
+dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM)
 {
+       /* "false; . empty_file; echo $?" should print 0, not 1: */
+       int status = 0;
        char *fullname;
+       char **argv;
        struct strlist *sp;
        volatile struct shparam saveparam;
 
        for (sp = cmdenviron; sp; sp = sp->next)
                setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
 
-       if (!argv[1]) {
+       nextopt(nullstr); /* handle possible "--" */
+       argv = argptr;
+
+       if (!argv[0]) {
                /* bash says: "bash: .: filename argument required" */
                return 2; /* bash compat */
        }
 
-       /* "false; . empty_file; echo $?" should print 0, not 1: */
-       exitstatus = 0;
-
        /* This aborts if file isn't found, which is POSIXly correct.
         * bash returns exitcode 1 instead.
         */
-       fullname = find_dot_file(argv[1]);
-       argv += 2;
-       argc -= 2;
-       if (argc) { /* argc > 0, argv[0] != NULL */
+       fullname = find_dot_file(argv[0]);
+       argv++;
+       if (argv[0]) { /* . FILE ARGS, ARGS exist */
+               int argc;
                saveparam = shellparam;
                shellparam.malloced = 0;
+               argc = 1;
+               while (argv[argc])
+                       argc++;
                shellparam.nparam = argc;
                shellparam.p = argv;
        };
@@ -12291,15 +12539,15 @@ dotcmd(int argc, char **argv)
         */
        setinputfile(fullname, INPUT_PUSH_FILE);
        commandname = fullname;
-       cmdloop(0);
+       status = cmdloop(0);
        popfile();
 
-       if (argc) {
+       if (argv[0]) {
                freeparam(&shellparam);
                shellparam = saveparam;
        };
 
-       return exitstatus;
+       return status;
 }
 
 static int FAST_FUNC
@@ -12521,8 +12769,6 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path)
 }
 
 
-/* ============ trap.c */
-
 /*
  * The trap builtin.
  */
@@ -12577,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);
@@ -12628,7 +12875,7 @@ helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                }
        }
 # endif
-       out1fmt("\n\n");
+       newline_and_flush(stdout);
        return EXIT_SUCCESS;
 }
 #endif
@@ -12948,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;
        }
@@ -12964,12 +13206,16 @@ exitshell(void)
        p = trap[0];
        if (p) {
                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 */
 }
@@ -12977,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;
@@ -12996,11 +13241,14 @@ init(void)
 
                initvar();
                for (envp = environ; envp && *envp; envp++) {
-                       if (strchr(*envp, '=')) {
+                       p = endofname(*envp);
+                       if (p != *envp && *p == '=') {
                                setvareq(*envp, VEXPORT|VTEXTFIXED);
                        }
                }
 
+               setvareq((char*)defoptindvar, VTEXTFIXED);
+
                setvar0("PPID", utoa(getppid()));
 #if ENABLE_ASH_BASH_COMPAT
                p = lookupvar("SHLVL");
@@ -13102,24 +13350,22 @@ procargs(char **argv)
 }
 
 /*
- * Read /etc/profile or .profile.
+ * Read /etc/profile, ~/.profile, $ENV.
  */
 static void
 read_profile(const char *name)
 {
-       int skip;
-
+       name = expandstr(name);
        if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0)
                return;
-       skip = cmdloop(0);
+       cmdloop(0);
        popfile();
-       if (skip)
-               exitshell();
 }
 
 /*
  * 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)
@@ -13127,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
@@ -13153,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;
@@ -13182,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();
@@ -13203,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;
@@ -13224,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;
@@ -13238,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) {