hush: rename hush-redir/redir3.tests (ash has redir3.tests which id different)
[oweals/busybox.git] / shell / ash.c
index 29d1d57cad44bccb0f07d6f612cea2cbd130b7ef..b7635a82332c3f108becf4a83b8f5dcb57dbaa54 100644 (file)
  *
  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
-
-/*
- * The following should be set to reflect the type of system you have:
- *      JOBS -> 1 if you have Berkeley job control, 0 otherwise.
- *      define SYSV if you are running under System V.
- *      define DEBUG=1 to compile in debugging ('set -o debug' to turn on)
- *      define DEBUG=2 to compile in and turn on debugging.
- *
- * When debugging is on (DEBUG is 1 and "set -o debug" was executed),
- * debugging info will be written to ./trace and a quit signal
- * will generate a core dump.
- */
-#define DEBUG 0
-/* Tweak debug output verbosity here */
-#define DEBUG_TIME 0
-#define DEBUG_PID 1
-#define DEBUG_SIG 1
-
-#define PROFILE 0
-
-#define JOBS ENABLE_ASH_JOB_CONTROL
-
-#include <setjmp.h>
-#include <fnmatch.h>
-#include <sys/times.h>
-#include <sys/utsname.h> /* for setting $HOSTNAME */
-
-#include "busybox.h" /* for applet_names */
-
-#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"
-#endif
-#if ENABLE_ASH_RANDOM_SUPPORT
-# include "random.h"
-#else
-# define CLEAR_RANDOM_T(rnd) ((void)0)
-#endif
-
-#include "NUM_APPLETS.h"
-#if NUM_APPLETS == 1
-/* STANDALONE does not make sense, and won't compile */
-# undef CONFIG_FEATURE_SH_STANDALONE
-# undef ENABLE_FEATURE_SH_STANDALONE
-# undef IF_FEATURE_SH_STANDALONE
-# undef IF_NOT_FEATURE_SH_STANDALONE
-# define ENABLE_FEATURE_SH_STANDALONE 0
-# define IF_FEATURE_SH_STANDALONE(...)
-# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
-#endif
-
-#ifndef PIPE_BUF
-# define PIPE_BUF 4096           /* amount of buffering in a pipe */
-#endif
-
-#if !BB_MMU
-# error "Do not even bother, ash will not run on NOMMU machine"
-#endif
-
 //config:config ASH
 //config:      bool "ash"
 //config:      default y
 //config:        shell (by Herbert Xu), which was created by porting the 'ash' shell
 //config:        (written by Kenneth Almquist) from NetBSD.
 //config:
+//config:# ash options
+//config:# note: Don't remove !NOMMU part in the next line; it would break
+//config:# menuconfig's indenting.
+//config:if !NOMMU && (ASH || SH_IS_ASH || BASH_IS_ASH)
+//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:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:
 //config:config ASH_INTERNAL_GLOB
 //config:      bool "Use internal glob() implementation"
-//config:      default n
-//config:      depends on ASH
+//config:      default y       # Y is bigger, but because of uclibc glob() bug, let Y be default for now
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_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:        Note that as of now (2017-01), uclibc and musl glob() both have bugs
+//config:        which would break ash if you select N here.
+//config:
+//config:config ASH_BASH_COMPAT
+//config:      bool "bash-compatible extensions"
+//config:      default y
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_JOB_CONTROL
+//config:      bool "Job control"
+//config:      default y
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_ALIAS
+//config:      bool "Alias support"
+//config:      default y
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:
 //config:config ASH_RANDOM_SUPPORT
 //config:      bool "Pseudorandom generator and $RANDOM variable"
 //config:      default y
-//config:      depends on ASH
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:      help
 //config:        Enable pseudorandom generator and dynamic variable "$RANDOM".
 //config:        Each read of "$RANDOM" will generate a new pseudorandom value.
 //config:config ASH_EXPAND_PRMT
 //config:      bool "Expand prompt string"
 //config:      default y
-//config:      depends on ASH
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:      help
-//config:        "PS#" may contain volatile content, such as backquote commands.
+//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:      depends on ASH
-//config:      help
-//config:        Enable bash-compatible extensions.
-//config:
 //config:config ASH_IDLE_TIMEOUT
-//config:      bool "Idle timeout variable"
-//config:      default n
-//config:      depends on ASH
-//config:      help
-//config:        Enables bash-like auto-logout after $TMOUT seconds of idle time.
-//config:
-//config:config ASH_JOB_CONTROL
-//config:      bool "Job control"
-//config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable job control in the ash shell.
-//config:
-//config:config ASH_ALIAS
-//config:      bool "Alias support"
+//config:      bool "Idle timeout variable $TMOUT"
 //config:      default y
-//config:      depends on ASH
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:      help
-//config:        Enable alias support in the ash shell.
+//config:        Enable bash-like auto-logout after $TMOUT seconds of idle time.
 //config:
-//config:config ASH_GETOPTS
-//config:      bool "Builtin getopt to parse positional parameters"
+//config:config ASH_MAIL
+//config:      bool "Check for new mail in interactive shell"
 //config:      default y
-//config:      depends on ASH
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:      help
-//config:        Enable support for getopts builtin in ash.
+//config:        Enable "check for new mail" function:
+//config:        if set, $MAIL file and $MAILPATH list of files
+//config:        are checked for mtime changes, and "you have mail"
+//config:        message is printed if change is detected.
 //config:
-//config:config ASH_BUILTIN_ECHO
-//config:      bool "Builtin version of 'echo'"
+//config:config ASH_ECHO
+//config:      bool "echo builtin"
 //config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable support for echo builtin in ash.
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:
-//config:config ASH_BUILTIN_PRINTF
-//config:      bool "Builtin version of 'printf'"
+//config:config ASH_PRINTF
+//config:      bool "printf builtin"
 //config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable support for printf builtin in ash.
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:
-//config:config ASH_BUILTIN_TEST
-//config:      bool "Builtin version of 'test'"
+//config:config ASH_TEST
+//config:      bool "test builtin"
 //config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable support for test builtin in ash.
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:
 //config:config ASH_HELP
 //config:      bool "help builtin"
 //config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable help builtin in ash.
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:
-//config:config ASH_CMDCMD
-//config:      bool "'command' command to override shell builtins"
+//config:config ASH_GETOPTS
+//config:      bool "getopts builtin"
 //config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable support for the ash 'command' builtin, which allows
-//config:        you to run the specified command with the specified arguments,
-//config:        even when there is an ash builtin command with the same name.
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:
-//config:config ASH_MAIL
-//config:      bool "Check for new mail on interactive shells"
-//config:      default n
-//config:      depends on ASH
+//config:config ASH_CMDCMD
+//config:      bool "command builtin"
+//config:      default y
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
 //config:      help
-//config:        Enable "check for new mail" function in the ash shell.
+//config:        Enable support for the 'command' builtin, which allows
+//config:        you to run the specified command or builtin,
+//config:        even when there is a function with the same name.
 //config:
+//config:endif # ash options
 
 //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))
-//applet:IF_FEATURE_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, BB_DIR_BIN, BB_SUID_DROP, bash))
+//                      APPLET_ODDNAME:name  main location    suid_type     help
+//applet:IF_SH_IS_ASH(  APPLET_ODDNAME(sh,   ash, BB_DIR_BIN, BB_SUID_DROP, ash))
+//applet:IF_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, BB_DIR_BIN, BB_SUID_DROP, ash))
 
 //kbuild:lib-$(CONFIG_ASH) += ash.o ash_ptr_hack.o shell_common.o
+//kbuild:lib-$(CONFIG_SH_IS_ASH) += ash.o ash_ptr_hack.o shell_common.o
+//kbuild:lib-$(CONFIG_BASH_IS_ASH) += ash.o ash_ptr_hack.o shell_common.o
 //kbuild:lib-$(CONFIG_ASH_RANDOM_SUPPORT) += random.o
 
+/*
+ * DEBUG=1 to compile in debugging ('set -o debug' turns on)
+ * DEBUG=2 to compile in and turn on debugging.
+ * When debugging is on ("set -o debug" was executed, or DEBUG=2),
+ * debugging info is written to ./trace, quit signal generates core dump.
+ */
+#define DEBUG 0
+/* Tweak debug output verbosity here */
+#define DEBUG_TIME 0
+#define DEBUG_PID 1
+#define DEBUG_SIG 1
+#define DEBUG_INTONOFF 0
+
+#define PROFILE 0
+
+#define JOBS ENABLE_ASH_JOB_CONTROL
+
+#include <setjmp.h>
+#include <fnmatch.h>
+#include <sys/times.h>
+#include <sys/utsname.h> /* for setting $HOSTNAME */
+#include "busybox.h" /* for applet_names */
+
+/* So far, all bash compat is controlled by one config option */
+/* Separate defines document which part of code implements what */
+/* function keyword */
+#define    BASH_FUNCTION        ENABLE_ASH_BASH_COMPAT
+#define IF_BASH_FUNCTION            IF_ASH_BASH_COMPAT
+/* &>file */
+#define    BASH_REDIR_OUTPUT    ENABLE_ASH_BASH_COMPAT
+#define IF_BASH_REDIR_OUTPUT        IF_ASH_BASH_COMPAT
+/* $'...' */
+#define    BASH_DOLLAR_SQUOTE   ENABLE_ASH_BASH_COMPAT
+#define IF_BASH_DOLLAR_SQUOTE       IF_ASH_BASH_COMPAT
+#define    BASH_PATTERN_SUBST   ENABLE_ASH_BASH_COMPAT
+#define IF_BASH_PATTERN_SUBST       IF_ASH_BASH_COMPAT
+#define    BASH_SUBSTR          ENABLE_ASH_BASH_COMPAT
+#define IF_BASH_SUBSTR              IF_ASH_BASH_COMPAT
+/* [[ EXPR ]] */
+#define    BASH_TEST2           (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST)
+#define    BASH_SOURCE          ENABLE_ASH_BASH_COMPAT
+#define    BASH_PIPEFAIL        ENABLE_ASH_BASH_COMPAT
+#define    BASH_HOSTNAME_VAR    ENABLE_ASH_BASH_COMPAT
+#define    BASH_SHLVL_VAR       ENABLE_ASH_BASH_COMPAT
+
+#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 && defined(__UCLIBC__)
+# error uClibc glob() is buggy, use ASH_INTERNAL_GLOB.
+# error The bug is: for "$PWD"/<pattern> ash will escape e.g. dashes in "$PWD"
+# error with backslash, even ones which do not need to be: "/a-b" -> "/a\-b"
+# error glob() should unbackslash them and match. uClibc does not unbackslash,
+# error fails to match dirname, subsequently not expanding <pattern> in it.
+// Testcase:
+// if (glob("/etc/polkit\\-1", 0, NULL, &pglob)) - this returns 0 on uclibc, no bug
+// if (glob("/etc/polkit\\-1/*", 0, NULL, &pglob)) printf("uclibc bug!\n");
+#endif
+
+#if !ENABLE_ASH_INTERNAL_GLOB
+# include <glob.h>
+#endif
+
+#include "unicode.h"
+#include "shell_common.h"
+#if ENABLE_FEATURE_SH_MATH
+# include "math.h"
+#endif
+#if ENABLE_ASH_RANDOM_SUPPORT
+# include "random.h"
+#else
+# define CLEAR_RANDOM_T(rnd) ((void)0)
+#endif
+
+#include "NUM_APPLETS.h"
+#if NUM_APPLETS == 1
+/* STANDALONE does not make sense, and won't compile */
+# undef CONFIG_FEATURE_SH_STANDALONE
+# undef ENABLE_FEATURE_SH_STANDALONE
+# undef IF_FEATURE_SH_STANDALONE
+# undef IF_NOT_FEATURE_SH_STANDALONE
+# define ENABLE_FEATURE_SH_STANDALONE 0
+# define IF_FEATURE_SH_STANDALONE(...)
+# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
+#endif
+
+#ifndef PIPE_BUF
+# define PIPE_BUF 4096           /* amount of buffering in a pipe */
+#endif
+
+#if !BB_MMU
+# error "Do not even bother, ash will not run on NOMMU machine"
+#endif
+
 
 /* ============ Hash table sizes. Configurable. */
 
@@ -244,7 +267,7 @@ static const char *const optletters_optnames[] = {
        "b"   "notify",
        "u"   "nounset",
        "\0"  "vi"
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PIPEFAIL
        ,"\0"  "pipefail"
 #endif
 #if DEBUG
@@ -295,15 +318,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 EXEXIT 4        /* exit the shell */
-#define EXSIG 5         /* trapped signal in wait(1) */
 
        smallint isloginsh;
        char nullstr[1];        /* zero length string */
@@ -323,14 +344,14 @@ struct globals_misc {
 #define bflag optlist[11]
 #define uflag optlist[12]
 #define viflag optlist[13]
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PIPEFAIL
 # define pipefail optlist[14]
 #else
 # define pipefail 0
 #endif
 #if DEBUG
-# define nolog optlist[14 + ENABLE_ASH_BASH_COMPAT]
-# define debug optlist[15 + ENABLE_ASH_BASH_COMPAT]
+# define nolog optlist[14 + BASH_PIPEFAIL]
+# define debug optlist[15 + BASH_PIPEFAIL]
 #endif
 
        /* trap handler commands */
@@ -372,6 +393,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    )
@@ -443,10 +465,18 @@ static void exitshell(void) NORETURN;
  * much more efficient and portable.  (But hacking the kernel is so much
  * more fun than worrying about efficiency and portability. :-))
  */
-#define INT_OFF do { \
+#if DEBUG_INTONOFF
+# define INT_OFF do { \
+       TRACE(("%s:%d INT_OFF(%d)\n", __func__, __LINE__, suppress_int)); \
+       suppress_int++; \
+       barrier(); \
+} while (0)
+#else
+# define INT_OFF do { \
        suppress_int++; \
        barrier(); \
 } while (0)
+#endif
 
 /*
  * Called to raise an exception.  Since C doesn't include exceptions, we
@@ -483,26 +513,20 @@ static void raise_interrupt(void) NORETURN;
 static void
 raise_interrupt(void)
 {
-       int ex_type;
-
        pending_int = 0;
        /* Signal is not automatically unmasked after it is raised,
         * do it ourself - unmask all signals */
        sigprocmask_allsigs(SIG_UNBLOCK);
        /* pending_sig = 0; - now done in signal_handler() */
 
-       ex_type = EXSIG;
-       if (gotsig[SIGINT - 1] && !trap[SIGINT]) {
-               if (!(rootshell && iflag)) {
-                       /* Kill ourself with SIGINT */
-                       signal(SIGINT, SIG_DFL);
-                       raise(SIGINT);
-               }
-               ex_type = EXINT;
+       if (!(rootshell && iflag)) {
+               /* Kill ourself with SIGINT */
+               signal(SIGINT, SIG_DFL);
+               raise(SIGINT);
        }
        /* bash: ^C even on empty command line sets $? */
        exitstatus = SIGINT + 128;
-       raise_exception(ex_type);
+       raise_exception(EXINT);
        /* NOTREACHED */
 }
 #if DEBUG
@@ -520,7 +544,14 @@ int_on(void)
                raise_interrupt();
        }
 }
-#define INT_ON int_on()
+#if DEBUG_INTONOFF
+# define INT_ON do { \
+       TRACE(("%s:%d INT_ON(%d)\n", __func__, __LINE__, suppress_int-1)); \
+       int_on(); \
+} while (0)
+#else
+# define INT_ON int_on()
+#endif
 static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void
 force_int_on(void)
 {
@@ -641,8 +672,10 @@ out2str(const char *p)
 #define VSTRIMLEFT      0x8     /* ${var#pattern} */
 #define VSTRIMLEFTMAX   0x9     /* ${var##pattern} */
 #define VSLENGTH        0xa     /* ${#var} */
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SUBSTR
 #define VSSUBSTR        0xc     /* ${var:position:length} */
+#endif
+#if BASH_PATTERN_SUBST
 #define VSREPLACE       0xd     /* ${var/pattern/replacement} */
 #define VSREPLACEALL    0xe     /* ${var//pattern/replacement} */
 #endif
@@ -669,7 +702,7 @@ static const char dolatstr[] ALIGN1 = {
 #define NDEFUN   14
 #define NARG     15
 #define NTO      16
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
 #define NTO2     17
 #endif
 #define NCLOBBER 18
@@ -851,13 +884,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
@@ -1084,7 +1112,7 @@ shcmd(union node *cmd, FILE *fp)
                case NTO:      s = ">>"+1; dftfd = 1; break;
                case NCLOBBER: s = ">|"; dftfd = 1; break;
                case NAPPEND:  s = ">>"; dftfd = 1; break;
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
                case NTO2:
 #endif
                case NTOFD:    s = ">&"; dftfd = 1; break;
@@ -1250,11 +1278,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);
@@ -1270,6 +1297,8 @@ ash_msg_and_raise_error(const char *msg, ...)
 {
        va_list ap;
 
+       exitstatus = 2;
+
        va_start(ap, msg);
        ash_vmsg_and_raise(EXERROR, msg, ap);
        /* NOTREACHED */
@@ -1400,7 +1429,6 @@ struct globals_memstack {
        char *g_stacknxt; // = stackbase.space;
        char *sstrend; // = stackbase.space + MINSIZE;
        size_t g_stacknleft; // = MINSIZE;
-       int    herefd; // = -1;
        struct stack_block stackbase;
 };
 extern struct globals_memstack *const ash_ptr_to_globals_memstack;
@@ -1409,7 +1437,6 @@ extern struct globals_memstack *const ash_ptr_to_globals_memstack;
 #define g_stacknxt   (G_memstack.g_stacknxt  )
 #define sstrend      (G_memstack.sstrend     )
 #define g_stacknleft (G_memstack.g_stacknleft)
-#define herefd       (G_memstack.herefd      )
 #define stackbase    (G_memstack.stackbase   )
 #define INIT_G_memstack() do { \
        (*(struct globals_memstack**)&ash_ptr_to_globals_memstack) = xzalloc(sizeof(G_memstack)); \
@@ -1418,7 +1445,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)
 
 
@@ -1495,12 +1521,10 @@ sstrdup(const char *p)
        return memcpy(stalloc(len), p, len);
 }
 
-static void
+static inline void
 grabstackblock(size_t len)
 {
-       len = SHELL_ALIGN(len);
-       g_stacknxt += len;
-       g_stacknleft -= len;
+       stalloc(len);
 }
 
 static void
@@ -1606,10 +1630,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;
 }
@@ -1959,7 +1979,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)];
@@ -1968,7 +1987,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      )
@@ -2029,7 +2047,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
@@ -2143,6 +2161,7 @@ lookupvar(const char *name)
        return NULL;
 }
 
+#if ENABLE_UNICODE_SUPPORT
 static void
 reinit_unicode_for_ash(void)
 {
@@ -2159,6 +2178,9 @@ reinit_unicode_for_ash(void)
                reinit_unicode(s);
        }
 }
+#else
+# define reinit_unicode_for_ash() ((void)0)
+#endif
 
 /*
  * Search the environment of a builtin command.
@@ -2197,6 +2219,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);
                }
 
@@ -2268,32 +2291,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.
  */
@@ -2461,12 +2458,8 @@ putprompt(const char *s)
 }
 #endif
 
-#if ENABLE_ASH_EXPAND_PRMT
 /* expandstr() needs parsing machinery, so it is far away ahead... */
 static const char *expandstr(const char *ps);
-#else
-#define expandstr(s) s
-#endif
 
 static void
 setprompt_if(smallint do_set, int whichprompt)
@@ -2491,10 +2484,10 @@ setprompt_if(smallint do_set, int whichprompt)
        }
 #if ENABLE_ASH_EXPAND_PRMT
        pushstackmark(&smark, stackblocksize());
-#endif
        putprompt(expandstr(prompt));
-#if ENABLE_ASH_EXPAND_PRMT
        popstackmark(&smark);
+#else
+       putprompt(prompt);
 #endif
 }
 
@@ -2628,7 +2621,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
@@ -2676,7 +2669,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:
@@ -2693,13 +2686,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)) {
@@ -2708,9 +2695,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:
@@ -2765,7 +2758,7 @@ pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 
 #define USE_SIT_FUNCTION ENABLE_ASH_OPTIMIZE_FOR_SIZE
 
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
 # define SIT_ITEM(a,b,c,d) (a | (b << 4) | (c << 8) | (d << 12))
 #else
 # define SIT_ITEM(a,b,c,d) (a | (b << 4) | (c << 8))
@@ -3146,7 +3139,18 @@ static const uint8_t syntax_index_table[] ALIGN1 = {
 # endif
 };
 
+#if 1
 # define SIT(c, syntax) ((S_I_T[syntax_index_table[c]] >> ((syntax)*4)) & 0xf)
+#else /* debug version, caught one signed char bug */
+# define SIT(c, syntax) \
+       ({ \
+               if ((c) < 0 || (c) > (PEOF + ENABLE_ASH_ALIAS)) \
+                       bb_error_msg_and_die("line:%d c:%d", __LINE__, (c)); \
+               if ((syntax) < 0 || (syntax) > (2 + ENABLE_FEATURE_SH_MATH)) \
+                       bb_error_msg_and_die("line:%d c:%d", __LINE__, (c)); \
+               ((S_I_T[syntax_index_table[c]] >> ((syntax)*4)) & 0xf); \
+       })
+#endif
 
 #endif  /* !USE_SIT_FUNCTION */
 
@@ -3337,11 +3341,9 @@ unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 {
        int i;
 
-       while ((i = nextopt("a")) != '\0') {
-               if (i == 'a') {
-                       rmaliases();
-                       return 0;
-               }
+       while (nextopt("a") != '\0') {
+               rmaliases();
+               return 0;
        }
        for (i = 0; *argptr; argptr++) {
                if (unalias(*argptr)) {
@@ -3385,12 +3387,13 @@ struct job {
 #if JOBS
        int stopstatus;         /* status of a stopped job */
 #endif
-       uint32_t
-               nprocs: 16,     /* number of processes */
-               state: 8,
+       unsigned nprocs;        /* number of processes */
+
 #define JOBRUNNING      0       /* at least one proc running */
 #define JOBSTOPPED      1       /* all procs are stopped */
 #define JOBDONE         2       /* all procs are completed */
+       unsigned
+               state: 8,
 #if JOBS
                sigint: 1,      /* job was killed by SIGINT */
                jobctl: 1,      /* job running under job control */
@@ -3433,7 +3436,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) {
@@ -3441,8 +3451,6 @@ signal_handler(int signo)
                        raise_interrupt(); /* does not return */
                }
                pending_int = 1;
-       } else {
-               pending_sig = signo;
        }
 }
 
@@ -3500,6 +3508,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) {
@@ -3549,10 +3560,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
@@ -3567,25 +3574,91 @@ static struct job *curjob; //lots
 /* number of presumed living untracked jobs */
 static int jobless; //4
 
+#if 0
+/* Bash has a feature: it restores termios after a successful wait for
+ * a foreground job which had at least one stopped or sigkilled member.
+ * The probable rationale is that SIGSTOP and SIGKILL can preclude task from
+ * properly restoring tty state. Should we do this too?
+ * A reproducer: ^Z an interactive python:
+ *
+ * # python
+ * Python 2.7.12 (...)
+ * >>> ^Z
+ *     { python leaves tty in -icanon -echo state. We do survive that... }
+ *  [1]+  Stopped                    python
+ *     { ...however, next program (python #2) does not survive it well: }
+ * # python
+ * Python 2.7.12 (...)
+ * >>> Traceback (most recent call last):
+ *     { above, I typed "qwerty<CR>", but -echo state is still in effect }
+ *   File "<stdin>", line 1, in <module>
+ * NameError: name 'qwerty' is not defined
+ *
+ * The implementation below is modeled on bash code and seems to work.
+ * However, I'm not sure we should do this. For one: what if I'd fg
+ * the stopped python instead? It'll be confused by "restored" tty state.
+ */
+static struct termios shell_tty_info;
 static void
-set_curjob(struct job *jp, unsigned mode)
+get_tty_state(void)
 {
-       struct job *jp1;
-       struct job **jpp, **curp;
-
-       /* first remove from list */
-       jpp = curp = &curjob;
-       while (1) {
-               jp1 = *jpp;
-               if (jp1 == jp)
-                       break;
-               jpp = &jp1->prev_job;
-       }
-       *jpp = jp1->prev_job;
-
-       /* Then re-insert in correct position */
-       jpp = curp;
-       switch (mode) {
+       if (rootshell && ttyfd >= 0)
+               tcgetattr(ttyfd, &shell_tty_info);
+}
+static void
+set_tty_state(void)
+{
+       /* if (rootshell) - caller ensures this */
+       if (ttyfd >= 0)
+               tcsetattr(ttyfd, TCSADRAIN, &shell_tty_info);
+}
+static int
+job_signal_status(struct job *jp)
+{
+       int status;
+       unsigned i;
+       struct procstat *ps = jp->ps;
+       for (i = 0; i < jp->nprocs; i++) {
+               status = ps[i].ps_status;
+               if (WIFSIGNALED(status) || WIFSTOPPED(status))
+                       return status;
+       }
+       return 0;
+}
+static void
+restore_tty_if_stopped_or_signaled(struct job *jp)
+{
+//TODO: check what happens if we come from waitforjob() in expbackq()
+       if (rootshell) {
+               int s = job_signal_status(jp);
+               if (s) /* WIFSIGNALED(s) || WIFSTOPPED(s) */
+                       set_tty_state();
+       }
+}
+#else
+# define get_tty_state() ((void)0)
+# define restore_tty_if_stopped_or_signaled(jp) ((void)0)
+#endif
+
+static void
+set_curjob(struct job *jp, unsigned mode)
+{
+       struct job *jp1;
+       struct job **jpp, **curp;
+
+       /* first remove from list */
+       jpp = curp = &curjob;
+       while (1) {
+               jp1 = *jpp;
+               if (jp1 == jp)
+                       break;
+               jpp = &jp1->prev_job;
+       }
+       *jpp = jp1->prev_job;
+
+       /* Then re-insert in correct position */
+       jpp = curp;
+       switch (mode) {
        default:
 #if DEBUG
                abort();
@@ -3770,12 +3843,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, don't */
                        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);
@@ -3898,8 +3971,10 @@ restartjob(struct job *jp, int mode)
                goto out;
        jp->state = JOBRUNNING;
        pgid = jp->ps[0].ps_pid;
-       if (mode == FORK_FG)
+       if (mode == FORK_FG) {
+               get_tty_state();
                xtcsetpgrp(ttyfd, pgid);
+       }
        killpg(pgid, SIGCONT);
        ps = jp->ps;
        i = jp->nprocs;
@@ -3973,27 +4048,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;
@@ -4065,15 +4201,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)
@@ -4243,9 +4370,6 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
        int retval;
        struct job *jp;
 
-       if (pending_sig)
-               raise_exception(EXSIG);
-
        nextopt(nullstr);
        retval = 0;
 
@@ -4262,21 +4386,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;
                }
        }
 
@@ -4296,8 +4419,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: ;
@@ -4305,6 +4431,9 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
 
  ret:
        return retval;
+ sigout:
+       retval = 128 + pending_sig;
+       return retval;
 }
 
 static struct job *
@@ -4379,7 +4508,7 @@ makejob(/*union node *node,*/ int nprocs)
        memset(jp, 0, sizeof(*jp));
 #if JOBS
        /* jp->jobctl is a bitfield.
-        * "jp->jobctl |= jobctl" likely to give awful code */
+        * "jp->jobctl |= doing_jobctl" likely to give awful code */
        if (doing_jobctl)
                jp->jobctl = 1;
 #endif
@@ -4408,7 +4537,8 @@ cmdputs(const char *s)
        static const char vstype[VSTYPE + 1][3] = {
                "", "}", "-", "+", "?", "=",
                "%", "%%", "#", "##"
-               IF_ASH_BASH_COMPAT(, ":", "/", "//")
+               IF_BASH_SUBSTR(, ":")
+               IF_BASH_PATTERN_SUBST(, "/", "//")
        };
 
        const char *p, *str;
@@ -4442,7 +4572,7 @@ cmdputs(const char *s)
                case CTLBACKQ:
                        str = "$(...)";
                        goto dostr;
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
                case CTLARI:
                        str = "$((";
                        goto dostr;
@@ -4635,7 +4765,7 @@ cmdtxt(union node *n)
        case NAPPEND:
                p = ">>";
                goto redir;
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
        case NTO2:
 #endif
        case NTOFD:
@@ -4669,8 +4799,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 */
@@ -4699,25 +4828,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)
 {
@@ -4849,6 +4979,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++;
@@ -4882,6 +5013,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)
 {
@@ -4971,6 +5103,8 @@ waitforjob(struct job *jp)
 #if JOBS
        if (jp->jobctl) {
                xtcsetpgrp(ttyfd, rootpid);
+               restore_tty_if_stopped_or_signaled(jp);
+
                /*
                 * This is truly gross.
                 * If we're doing job control, then we did a TIOCSPGRP which
@@ -5011,8 +5145,7 @@ stoppedjobs(void)
 }
 
 
-/* ============ redir.c
- *
+/*
  * Code for dealing with input/output redirection.
  */
 
@@ -5161,7 +5294,7 @@ openredirect(union node *redir)
                        goto ecreate;
                break;
        case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
        case NTO2:
 #endif
                /* Take care of noclobber mode. */
@@ -5192,31 +5325,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);
        }
@@ -5229,11 +5363,13 @@ struct two_fd_t {
 };
 struct redirtab {
        struct redirtab *next;
-       int nullredirs;
        int pair_count;
        struct two_fd_t two_fd[];
 };
 #define redirlist (G_var.redirlist)
+enum {
+       COPYFD_RESTORE = (int)~(INT_MAX),
+};
 
 static int
 need_to_remember(struct redirtab *rp, int fd)
@@ -5308,7 +5444,6 @@ redirect(union node *redir, int flags)
        int newfd;
        int copied_fd2 = -1;
 
-       g_nullredirs++;
        if (!redir) {
                return;
        }
@@ -5320,7 +5455,7 @@ redirect(union node *redir, int flags)
                union node *tmp = redir;
                do {
                        sv_pos++;
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
                        if (tmp->nfile.type == NTO2)
                                sv_pos++;
 #endif
@@ -5330,8 +5465,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;
@@ -5364,7 +5497,7 @@ redirect(union node *redir, int flags)
                                continue;
                        }
                }
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
  redirect_more:
 #endif
                if (need_to_remember(sv, fd)) {
@@ -5372,11 +5505,11 @@ redirect(union node *redir, int flags)
                        /* Careful to not accidentally "save"
                         * to the same fd as right side fd in N>&M */
                        int minfd = right_fd < 10 ? 10 : right_fd + 1;
+#if defined(F_DUPFD_CLOEXEC)
+                       i = fcntl(fd, F_DUPFD_CLOEXEC, minfd);
+#else
                        i = fcntl(fd, F_DUPFD, minfd);
-/* You'd expect copy to be CLOEXECed. Currently these extra "saved" fds
- * are closed in popredir() in the child, preventing them from leaking
- * into child. (popredir() also cleans up the mess in case of failures)
- */
+#endif
                        if (i == -1) {
                                i = errno;
                                if (i != EBADF) {
@@ -5391,6 +5524,9 @@ redirect(union node *redir, int flags)
  remember_to_close:
                                i = CLOSED;
                        } else { /* fd is open, save its copy */
+#if !defined(F_DUPFD_CLOEXEC)
+                               fcntl(i, F_SETFD, FD_CLOEXEC);
+#endif
                                /* "exec fd>&-" should not close fds
                                 * which point to script file(s).
                                 * Force them to be restored afterwards */
@@ -5410,16 +5546,16 @@ 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);
-#if ENABLE_ASH_BASH_COMPAT
+                       dup2_or_raise(newfd, fd);
+#if BASH_REDIR_OUTPUT
                        if (!(redir->nfile.type == NTO2 && fd == 2))
 #endif
                                close(newfd);
                }
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
                if (redir->nfile.type == NTO2 && fd == 1) {
                        /* We already redirected it to fd 1, now copy it to 2 */
                        newfd = 1;
@@ -5443,7 +5579,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;
@@ -5459,13 +5595,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;
 }
@@ -5474,20 +5609,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)
 {
@@ -5516,7 +5637,7 @@ redirectsafe(union node *redir, int flags)
  * We have to deal with backquotes, shell variables, and file metacharacters.
  */
 
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
 static arith_t
 ash_arith(const char *s)
 {
@@ -5604,7 +5725,7 @@ static struct arglist exparg;
 /*
  * Our own itoa().
  */
-#if !ENABLE_SH_MATH_SUPPORT
+#if !ENABLE_FEATURE_SH_MATH
 /* cvtnum() is used even if math support is off (to prepare $? values and such) */
 typedef long arith_t;
 # define ARITH_FMT "%ld"
@@ -5620,6 +5741,119 @@ cvtnum(arith_t num)
        return len;
 }
 
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list.  The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ */
+static void
+ifsbreakup(char *string, struct arglist *arglist)
+{
+       struct ifsregion *ifsp;
+       struct strlist *sp;
+       char *start;
+       char *p;
+       char *q;
+       const char *ifs, *realifs;
+       int ifsspc;
+       int nulonly;
+
+       start = string;
+       if (ifslastp != NULL) {
+               ifsspc = 0;
+               nulonly = 0;
+               realifs = ifsset() ? ifsval() : defifs;
+               ifsp = &ifsfirst;
+               do {
+                       p = string + ifsp->begoff;
+                       nulonly = ifsp->nulonly;
+                       ifs = nulonly ? nullstr : realifs;
+                       ifsspc = 0;
+                       while (p < string + ifsp->endoff) {
+                               q = p;
+                               if ((unsigned char)*p == CTLESC)
+                                       p++;
+                               if (!strchr(ifs, *p)) {
+                                       p++;
+                                       continue;
+                               }
+                               if (!nulonly)
+                                       ifsspc = (strchr(defifs, *p) != NULL);
+                               /* Ignore IFS whitespace at start */
+                               if (q == start && ifsspc) {
+                                       p++;
+                                       start = p;
+                                       continue;
+                               }
+                               *q = '\0';
+                               sp = stzalloc(sizeof(*sp));
+                               sp->text = start;
+                               *arglist->lastp = sp;
+                               arglist->lastp = &sp->next;
+                               p++;
+                               if (!nulonly) {
+                                       for (;;) {
+                                               if (p >= string + ifsp->endoff) {
+                                                       break;
+                                               }
+                                               q = p;
+                                               if ((unsigned char)*p == CTLESC)
+                                                       p++;
+                                               if (strchr(ifs, *p) == NULL) {
+                                                       p = q;
+                                                       break;
+                                               }
+                                               if (strchr(defifs, *p) == NULL) {
+                                                       if (ifsspc) {
+                                                               p++;
+                                                               ifsspc = 0;
+                                                       } else {
+                                                               p = q;
+                                                               break;
+                                                       }
+                                               } else
+                                                       p++;
+                                       }
+                               }
+                               start = p;
+                       } /* while */
+                       ifsp = ifsp->next;
+               } while (ifsp != NULL);
+               if (nulonly)
+                       goto add;
+       }
+
+       if (!*start)
+               return;
+
+ add:
+       sp = stzalloc(sizeof(*sp));
+       sp->text = start;
+       *arglist->lastp = sp;
+       arglist->lastp = &sp->next;
+}
+
+static void
+ifsfree(void)
+{
+       struct ifsregion *p = ifsfirst.next;
+
+       if (!p)
+               goto out;
+
+       INT_OFF;
+       do {
+               struct ifsregion *ifsp;
+               ifsp = p->next;
+               free(p);
+               p = ifsp;
+       } while (p);
+       ifsfirst.next = NULL;
+       INT_ON;
+ out:
+       ifslastp = NULL;
+}
+
 static size_t
 esclen(const char *start, const char *p)
 {
@@ -5638,15 +5872,15 @@ static char *
 rmescapes(char *str, int flag)
 {
        static const char qchars[] ALIGN1 = {
-               IF_ASH_BASH_COMPAT('/',) CTLESC, CTLQUOTEMARK, '\0' };
+               IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' };
 
        char *p, *q, *r;
        unsigned inquotes;
        unsigned protect_against_glob;
        unsigned globbing;
-       IF_ASH_BASH_COMPAT(unsigned slash = flag & RMESCAPE_SLASH;)
+       IF_BASH_PATTERN_SUBST(unsigned slash = flag & RMESCAPE_SLASH;)
 
-       p = strpbrk(str, qchars IF_ASH_BASH_COMPAT(+ !slash));
+       p = strpbrk(str, qchars IF_BASH_PATTERN_SUBST(+ !slash));
        if (!p)
                return str;
 
@@ -5679,6 +5913,7 @@ 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;
@@ -5691,14 +5926,40 @@ rmescapes(char *str, int flag)
                                ash_msg_and_raise_error("CTLESC at EOL (shouldn't happen)");
 #endif
                        if (protect_against_glob) {
-                               *q++ = '\\';
+                               /*
+                                * We used to trust glob() and fnmatch() to eat
+                                * superfluous escapes (\z where z has no
+                                * special meaning anyway). But this causes
+                                * bugs such as string of one greek letter rho
+                                * (unicode-encoded as two bytes "cf,81")
+                                * getting encoded as "cf,CTLESC,81"
+                                * and here, converted to "cf,\,81" -
+                                * which does not go well with some flavors
+                                * of fnmatch() in unicode locales
+                                * (for example, glibc <= 2.22).
+                                *
+                                * Lets add "\" only on the chars which need it.
+                                * Testcases for less obvious chars are shown.
+                                */
+                               if (*p == '*'
+                                || *p == '?'
+                                || *p == '['
+                                || *p == '\\' /* case '\' in \\ ) echo ok;; *) echo WRONG;; esac */
+                                || *p == ']' /* case ']' in [a\]] ) echo ok;; *) echo WRONG;; esac */
+                                || *p == '-' /* case '-' in [a\-c]) echo ok;; *) echo WRONG;; esac */
+                                || *p == '!' /* case '!' in [\!] ) echo ok;; *) echo WRONG;; esac */
+                               /* Some libc support [^negate], that's why "^" also needs love */
+                                || *p == '^' /* case '^' in [\^] ) echo ok;; *) echo WRONG;; esac */
+                               ) {
+                                       *q++ = '\\';
+                               }
                        }
                } else if (*p == '\\' && !inquotes) {
                        /* naked back slash */
                        protect_against_glob = 0;
                        goto copy;
                }
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PATTERN_SUBST
                else if (*p == '/' && slash) {
                        /* stop handling globbing and mark location of slash */
                        globbing = slash = 0;
@@ -5745,14 +6006,15 @@ memtodest(const char *p, size_t len, int syntax, int quotes)
        do {
                unsigned char c = *p++;
                if (c) {
-                       int n = SIT(c, syntax);
-                       if ((quotes & QUOTES_ESC)
-                        && ((n == CCTL)
-                           ||  (((quotes & EXP_FULL) || syntax != BASESYNTAX)
-                               && n == CBACK)
-                               )
-                       ) {
-                               USTPUTC(CTLESC, q);
+                       if (quotes & QUOTES_ESC) {
+                               int n = SIT(c, syntax);
+                               if (n == CCTL
+                                || (((quotes & EXP_FULL) || syntax != BASESYNTAX)
+                                    && n == CBACK
+                                   )
+                               ) {
+                                       USTPUTC(CTLESC, q);
+                               }
                        }
                } else if (!(quotes & QUOTES_KEEPNUL))
                        continue;
@@ -5899,42 +6161,47 @@ static int evaltree(union node *, int);
 static void FAST_FUNC
 evalbackcmd(union node *n, struct backcmd *result)
 {
-       int saveherefd;
+       int pip[2];
+       struct job *jp;
 
        result->fd = -1;
        result->buf = NULL;
        result->nleft = 0;
        result->jp = NULL;
-       if (n == NULL)
+       if (n == NULL) {
                goto out;
+       }
 
-       saveherefd = herefd;
-       herefd = -1;
-
-       {
-               int pip[2];
-               struct job *jp;
-
-               if (pipe(pip) < 0)
-                       ash_msg_and_raise_error("pipe call failed");
-               jp = makejob(/*n,*/ 1);
-               if (forkshell(jp, n, FORK_NOJOB) == 0) {
-                       FORCE_INT_ON;
-                       close(pip[0]);
-                       if (pip[1] != 1) {
-                               /*close(1);*/
-                               copyfd(pip[1], 1 | COPYFD_EXACT);
-                               close(pip[1]);
-                       }
-                       eflag = 0;
-                       evaltree(n, EV_EXIT); /* actually evaltreenr... */
-                       /* NOTREACHED */
+       if (pipe(pip) < 0)
+               ash_msg_and_raise_error("pipe call failed");
+       jp = makejob(/*n,*/ 1);
+       if (forkshell(jp, n, FORK_NOJOB) == 0) {
+               /* child */
+               FORCE_INT_ON;
+               close(pip[0]);
+               if (pip[1] != 1) {
+                       /*close(1);*/
+                       dup2_or_raise(pip[1], 1);
+                       close(pip[1]);
                }
-               close(pip[1]);
-               result->fd = pip[0];
-               result->jp = jp;
+/* TODO: eflag clearing makes the following not abort:
+ *  ash -c 'set -e; z=$(false;echo foo); echo $z'
+ * which is what bash does (unless it is in POSIX mode).
+ * dash deleted "eflag = 0" line in the commit
+ *  Date: Mon, 28 Jun 2010 17:11:58 +1000
+ *  [EVAL] Don't clear eflag in evalbackcmd
+ * For now, preserve bash-like behavior, it seems to be somewhat more useful:
+ */
+               eflag = 0;
+               ifsfree();
+               evaltree(n, EV_EXIT); /* actually evaltreenr... */
+               /* NOTREACHED */
        }
-       herefd = saveherefd;
+       /* parent */
+       close(pip[1]);
+       result->fd = pip[0];
+       result->jp = jp;
+
  out:
        TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
                result->fd, result->buf, result->nleft, result->jp));
@@ -5998,7 +6265,7 @@ expbackq(union node *cmd, int flag)
                stackblock() + startloc));
 }
 
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
 /*
  * Expand arithmetic expression.  Backup to start of expression,
  * evaluate, place result in (backed up) result, adjust string position.
@@ -6066,7 +6333,7 @@ static char *evalvar(char *p, int flags, struct strlist *var_str_list);
  * $@ like $* since no splitting will be performed.
  *
  * var_str_list (can be NULL) is a list of "VAR=val" strings which take precedence
- * over shell varables. Needed for "A=a B=$A; echo $B" case - we use it
+ * over shell variables. Needed for "A=a B=$A; echo $B" case - we use it
  * for correct expansion of "B=$A" word.
  */
 static void
@@ -6080,7 +6347,7 @@ argstr(char *p, int flags, struct strlist *var_str_list)
                CTLESC,
                CTLVAR,
                CTLBACKQ,
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
                CTLENDARI,
 #endif
                '\0'
@@ -6116,7 +6383,7 @@ argstr(char *p, int flags, struct strlist *var_str_list)
                c = p[length];
                if (c) {
                        if (!(c & 0x80)
-                       IF_SH_MATH_SUPPORT(|| c == CTLENDARI)
+                       IF_FEATURE_SH_MATH(|| c == CTLENDARI)
                        ) {
                                /* c == '=' || c == ':' || c == CTLENDARI */
                                length++;
@@ -6196,7 +6463,7 @@ argstr(char *p, int flags, struct strlist *var_str_list)
                        expbackq(argbackq->n, flags | inquotes);
                        argbackq = argbackq->next;
                        goto start;
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
                case CTLENDARI:
                        p--;
                        expari(flags | inquotes);
@@ -6276,8 +6543,8 @@ scanright(char *startp, char *rmesc, char *rmescend,
                if (try2optimize) {
                        /* Maybe we can optimize this:
                         * if pattern ends with unescaped *, we can avoid checking
-                        * shorter strings: if "foo*" doesnt match "raw_value_of_v",
-                        * it wont match truncated "raw_value_of_" strings too.
+                        * shorter strings: if "foo*" doesn't match "raw_value_of_v",
+                        * it won't match truncated "raw_value_of_" strings too.
                         */
                        unsigned plen = strlen(pattern);
                        /* Does it end with "*"? */
@@ -6339,23 +6606,20 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
        char *loc;
        char *rmesc, *rmescend;
        char *str;
-       IF_ASH_BASH_COMPAT(char *repl = NULL;)
-       IF_ASH_BASH_COMPAT(int pos, len, orig_len;)
-       int saveherefd = herefd;
+       IF_BASH_SUBSTR(int pos, len, orig_len;)
        int amount, resetloc;
-       IF_ASH_BASH_COMPAT(int workloc;)
+       IF_BASH_PATTERN_SUBST(int workloc;)
+       IF_BASH_PATTERN_SUBST(char *repl = NULL;)
        int zero;
        char *(*scan)(char*, char*, char*, char*, int, int);
 
        //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;
 
@@ -6370,7 +6634,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
                varunset(p, varname, startp, varflags);
                /* NOTREACHED */
 
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SUBSTR
        case VSSUBSTR:
 //TODO: support more general format ${v:EXPR:EXPR},
 // where EXPR follows $(()) rules
@@ -6439,17 +6703,19 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
                amount = loc - expdest;
                STADJUST(amount, expdest);
                return loc;
-#endif
+#endif /* BASH_SUBSTR */
        }
 
        resetloc = expdest - (char *)stackblock();
 
+#if BASH_PATTERN_SUBST
        /* We'll comeback here if we grow the stack while handling
         * a VSREPLACE or VSREPLACEALL, since our pointers into the
         * stack will need rebasing, and we'll need to remove our work
         * areas each time
         */
- IF_ASH_BASH_COMPAT(restart:)
+ restart:
+#endif
 
        amount = expdest - ((char *)stackblock() + resetloc);
        STADJUST(-amount, expdest);
@@ -6474,11 +6740,11 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
         * RMESCAPE_SLASH causes preglob to work differently on the pattern
         * and string.  It's only used on the first call.
         */
-       preglob(str, IF_ASH_BASH_COMPAT(
+       preglob(str, IF_BASH_PATTERN_SUBST(
                (subtype == VSREPLACE || subtype == VSREPLACEALL) && !repl ?
-                       RMESCAPE_SLASH :) 0);
+                       RMESCAPE_SLASH : ) 0);
 
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PATTERN_SUBST
        workloc = expdest - (char *)stackblock();
        if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
                char *idx, *end;
@@ -6579,7 +6845,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
                STADJUST(-amount, expdest);
                return startp;
        }
-#endif /* ENABLE_ASH_BASH_COMPAT */
+#endif /* BASH_PATTERN_SUBST */
 
        subtype -= VSTRIMRIGHT;
 #if DEBUG
@@ -6777,6 +7043,10 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
 
        varflags = (unsigned char) *p++;
        subtype = varflags & VSTYPE;
+
+       if (!subtype)
+               raise_error_syntax("bad substitution");
+
        quoted = flag & EXP_QUOTED;
        var = p;
        easy = (!quoted || (*var == '@' && shellparam.nparam));
@@ -6843,166 +7113,58 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
        case VSTRIMLEFTMAX:
        case VSTRIMRIGHT:
        case VSTRIMRIGHTMAX:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SUBSTR
        case VSSUBSTR:
-       case VSREPLACE:
-       case VSREPLACEALL:
-#endif
-               break;
-       default:
-               abort();
-       }
 #endif
-
-       if (varlen >= 0) {
-               /*
-                * Terminate the string and start recording the pattern
-                * right after it
-                */
-               STPUTC('\0', expdest);
-               patloc = expdest - (char *)stackblock();
-               if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
-                               startloc, varflags, flag, var_str_list)) {
-                       int amount = expdest - (
-                               (char *)stackblock() + patloc - 1
-                       );
-                       STADJUST(-amount, expdest);
-               }
-               /* Remove any recorded regions beyond start of variable */
-               removerecordregions(startloc);
-               goto record;
-       }
-
- end:
-       if (subtype != VSNORMAL) {      /* skip to end of alternative */
-               int nesting = 1;
-               for (;;) {
-                       unsigned char c = *p++;
-                       if (c == CTLESC)
-                               p++;
-                       else if (c == CTLBACKQ) {
-                               if (varlen >= 0)
-                                       argbackq = argbackq->next;
-                       } else if (c == CTLVAR) {
-                               if ((*p++ & VSTYPE) != VSNORMAL)
-                                       nesting++;
-                       } else if (c == CTLENDVAR) {
-                               if (--nesting == 0)
-                                       break;
-                       }
-               }
-       }
-       return p;
-}
-
-/*
- * Break the argument string into pieces based upon IFS and add the
- * strings to the argument list.  The regions of the string to be
- * searched for IFS characters have been stored by recordregion.
- */
-static void
-ifsbreakup(char *string, struct arglist *arglist)
-{
-       struct ifsregion *ifsp;
-       struct strlist *sp;
-       char *start;
-       char *p;
-       char *q;
-       const char *ifs, *realifs;
-       int ifsspc;
-       int nulonly;
-
-       start = string;
-       if (ifslastp != NULL) {
-               ifsspc = 0;
-               nulonly = 0;
-               realifs = ifsset() ? ifsval() : defifs;
-               ifsp = &ifsfirst;
-               do {
-                       p = string + ifsp->begoff;
-                       nulonly = ifsp->nulonly;
-                       ifs = nulonly ? nullstr : realifs;
-                       ifsspc = 0;
-                       while (p < string + ifsp->endoff) {
-                               q = p;
-                               if ((unsigned char)*p == CTLESC)
-                                       p++;
-                               if (!strchr(ifs, *p)) {
-                                       p++;
-                                       continue;
-                               }
-                               if (!nulonly)
-                                       ifsspc = (strchr(defifs, *p) != NULL);
-                               /* Ignore IFS whitespace at start */
-                               if (q == start && ifsspc) {
-                                       p++;
-                                       start = p;
-                                       continue;
-                               }
-                               *q = '\0';
-                               sp = stzalloc(sizeof(*sp));
-                               sp->text = start;
-                               *arglist->lastp = sp;
-                               arglist->lastp = &sp->next;
-                               p++;
-                               if (!nulonly) {
-                                       for (;;) {
-                                               if (p >= string + ifsp->endoff) {
-                                                       break;
-                                               }
-                                               q = p;
-                                               if ((unsigned char)*p == CTLESC)
-                                                       p++;
-                                               if (strchr(ifs, *p) == NULL) {
-                                                       p = q;
-                                                       break;
-                                               }
-                                               if (strchr(defifs, *p) == NULL) {
-                                                       if (ifsspc) {
-                                                               p++;
-                                                               ifsspc = 0;
-                                                       } else {
-                                                               p = q;
-                                                               break;
-                                                       }
-                                               } else
-                                                       p++;
-                                       }
-                               }
-                               start = p;
-                       } /* while */
-                       ifsp = ifsp->next;
-               } while (ifsp != NULL);
-               if (nulonly)
-                       goto add;
+#if BASH_PATTERN_SUBST
+       case VSREPLACE:
+       case VSREPLACEALL:
+#endif
+               break;
+       default:
+               abort();
        }
+#endif
 
-       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;
+       if (varlen >= 0) {
+               /*
+                * Terminate the string and start recording the pattern
+                * right after it
+                */
+               STPUTC('\0', expdest);
+               patloc = expdest - (char *)stackblock();
+               if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
+                               startloc, varflags, flag, var_str_list)) {
+                       int amount = expdest - (
+                               (char *)stackblock() + patloc - 1
+                       );
+                       STADJUST(-amount, expdest);
+               }
+               /* Remove any recorded regions beyond start of variable */
+               removerecordregions(startloc);
+               goto record;
+       }
 
-       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;
+ end:
+       if (subtype != VSNORMAL) {      /* skip to end of alternative */
+               int nesting = 1;
+               for (;;) {
+                       unsigned char c = *p++;
+                       if (c == CTLESC)
+                               p++;
+                       else if (c == CTLBACKQ) {
+                               if (varlen >= 0)
+                                       argbackq = argbackq->next;
+                       } else if (c == CTLVAR) {
+                               if ((*p++ & VSTYPE) != VSNORMAL)
+                                       nesting++;
+                       } else if (c == CTLENDVAR) {
+                               if (--nesting == 0)
+                                       break;
+                       }
+               }
+       }
+       return p;
 }
 
 /*
@@ -7019,6 +7181,57 @@ addfname(const char *name)
        exparg.lastp = &sp->next;
 }
 
+/* Avoid glob() (and thus, stat() et al) for words like "echo" */
+static int
+hasmeta(const char *p)
+{
+       static const char chars[] ALIGN1 = {
+               '*', '?', '[', '\\', CTLQUOTEMARK, CTLESC, 0
+       };
+
+       for (;;) {
+               p = strpbrk(p, chars);
+               if (!p)
+                       break;
+               switch ((unsigned char) *p) {
+               case CTLQUOTEMARK:
+                       for (;;) {
+                               p++;
+                               if (*p == CTLQUOTEMARK)
+                                       break;
+                               if (*p == CTLESC)
+                                       p++;
+                               if (*p == '\0') /* huh? */
+                                       return 0;
+                       }
+                       break;
+               case '\\':
+               case CTLESC:
+                       p++;
+                       if (*p == '\0')
+                               return 0;
+                       break;
+               case '[':
+                       if (!strchr(p + 1, ']')) {
+                               /* It's not a properly closed [] pattern,
+                                * but other metas may follow. Continue checking.
+                                * my[file* _is_ globbed by bash
+                                * and matches filenames like "my[file1".
+                                */
+                               break;
+                       }
+                       /* fallthrough */
+               default:
+               /* case '*': */
+               /* case '?': */
+                       return 1;
+               }
+               p++;
+       }
+
+       return 0;
+}
+
 /* If we want to use glob() from libc... */
 #if !ENABLE_ASH_INTERNAL_GLOB
 
@@ -7044,6 +7257,10 @@ expandmeta(struct strlist *str /*, int flag*/)
 
                if (fflag)
                        goto nometa;
+
+               if (!hasmeta(str->text))
+                       goto nometa;
+
                INT_OFF;
                p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
 // GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match
@@ -7054,7 +7271,7 @@ expandmeta(struct strlist *str /*, int flag*/)
 // 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.
+// You DON'T 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.
@@ -7280,9 +7497,6 @@ expsort(struct strlist *str)
 static void
 expandmeta(struct strlist *str /*, int flag*/)
 {
-       static const char metachars[] ALIGN1 = {
-               '*', '?', '[', 0
-       };
        /* TODO - EXP_REDIR */
 
        while (str) {
@@ -7293,7 +7507,7 @@ expandmeta(struct strlist *str /*, int flag*/)
 
                if (fflag)
                        goto nometa;
-               if (!strpbrk(str->text, metachars))
+               if (!hasmeta(str->text))
                        goto nometa;
                savelastp = exparg.lastp;
 
@@ -7343,15 +7557,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));
@@ -7374,13 +7587,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();
 }
 
 /*
@@ -7389,7 +7603,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());
 }
@@ -7400,7 +7613,9 @@ expandhere(union node *arg, int fd)
 static int
 patmatch(char *pattern, const char *string)
 {
-       return pmatch(preglob(pattern, 0), string);
+       char *p = preglob(pattern, 0);
+       //bb_error_msg("fnmatch(pattern:'%s',str:'%s')", p, string);
+       return pmatch(p, string);
 }
 
 /*
@@ -7415,10 +7630,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;
@@ -7519,13 +7734,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
@@ -7542,19 +7751,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;
        }
 }
@@ -7562,10 +7765,10 @@ 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
-shellexec(char **argv, const char *path, int idx)
+static void shellexec(char *prog, char **argv, const char *path, int idx) NORETURN;
+static void shellexec(char *prog, char **argv, const char *path, int idx)
 {
        char *cmdname;
        int e;
@@ -7573,14 +7776,13 @@ 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 (strchr(prog, '/') != NULL
 #if ENABLE_FEATURE_SH_STANDALONE
-        || (applet_no = find_applet_by_name(argv[0])) >= 0
+        || (applet_no = find_applet_by_name(prog)) >= 0
 #endif
        ) {
-               tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) argv[0], argv, envp);
+               tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) prog, argv, envp);
                if (applet_no >= 0) {
                        /* We tried execing ourself, but it didn't work.
                         * Maybe /proc/self/exe doesn't exist?
@@ -7592,7 +7794,7 @@ shellexec(char **argv, const char *path, int idx)
        } else {
  try_PATH:
                e = ENOENT;
-               while ((cmdname = path_advance(&path, argv[0])) != NULL) {
+               while ((cmdname = path_advance(&path, prog)) != NULL) {
                        if (--idx < 0 && pathopt == NULL) {
                                tryexec(IF_FEATURE_SH_STANDALONE(-1,) cmdname, argv, envp);
                                if (errno != ENOENT && errno != ENOTDIR)
@@ -7616,8 +7818,8 @@ 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(EXEXIT, "%s: %s", argv[0], errmsg(e, "not found"));
+               prog, e, suppress_int));
+       ash_msg_and_raise(EXEXIT, "%s: %s", prog, errmsg(e, "not found"));
        /* NOTREACHED */
 }
 
@@ -7876,7 +8078,7 @@ enum {
        TESAC,
        TFI,
        TFOR,
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
        TFUNCTION,
 #endif
        TIF,
@@ -7914,7 +8116,7 @@ enum {
        /* 19 */ | (1u << TESAC)
        /* 20 */ | (1u << TFI)
        /* 21 */ | (0u << TFOR)
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
        /* 22 */ | (0u << TFUNCTION)
 #endif
        /* 23 */ | (0u << TIF)
@@ -7952,7 +8154,7 @@ static const char *const tokname_array[] = {
        "esac",
        "fi",
        "for",
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
        "function",
 #endif
        "if",
@@ -7986,7 +8188,6 @@ static int
 describe_command(char *command, const char *path, int describe_command_verbose)
 {
        struct cmdentry entry;
-       struct tblentry *cmdp;
 #if ENABLE_ASH_ALIAS
        const struct alias *ap;
 #endif
@@ -8016,15 +8217,8 @@ describe_command(char *command, const char *path, int describe_command_verbose)
                goto out;
        }
 #endif
-       /* Then check if it is a tracked alias */
-       cmdp = cmdlookup(command, 0);
-       if (cmdp != NULL) {
-               entry.cmdtype = cmdp->cmdtype;
-               entry.u = cmdp->param;
-       } else {
-               /* Finally use brute force */
-               find_command(command, &entry, DO_ABS, path);
-       }
+       /* Brute force */
+       find_command(command, &entry, DO_ABS, path);
 
        switch (entry.cmdtype) {
        case CMDNORMAL: {
@@ -8039,9 +8233,7 @@ describe_command(char *command, const char *path, int describe_command_verbose)
                        } while (--j >= 0);
                }
                if (describe_command_verbose) {
-                       out1fmt(" is%s %s",
-                               (cmdp ? " a tracked alias for" : nullstr), p
-                       );
+                       out1fmt(" is %s", p);
                } else {
                        out1str(p);
                }
@@ -8196,7 +8388,7 @@ static const uint8_t nodesize[N_NUMBER] ALIGN1 = {
        [NDEFUN   ] = SHELL_ALIGN(sizeof(struct narg)),
        [NARG     ] = SHELL_ALIGN(sizeof(struct narg)),
        [NTO      ] = SHELL_ALIGN(sizeof(struct nfile)),
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
        [NTO2     ] = SHELL_ALIGN(sizeof(struct nfile)),
 #endif
        [NCLOBBER ] = SHELL_ALIGN(sizeof(struct nfile)),
@@ -8278,7 +8470,7 @@ calcsize(int funcblocksize, union node *n)
                funcblocksize = calcsize(funcblocksize, n->narg.next);
                break;
        case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
        case NTO2:
 #endif
        case NCLOBBER:
@@ -8392,7 +8584,7 @@ copynode(union node *n)
                new->narg.next = copynode(n->narg.next);
                break;
        case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
        case NTO2:
 #endif
        case NCLOBBER:
@@ -8447,14 +8639,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;
 }
 
@@ -8537,41 +8729,18 @@ static void prehash(union node *);
 static int
 evaltree(union node *n, int flags)
 {
-       struct jmploc *volatile savehandler = exception_handler;
-       struct jmploc jmploc;
        int checkexit = 0;
        int (*evalfn)(union node *, int);
        int status = 0;
-       int int_level;
-
-       SAVE_INT(int_level);
 
        if (n == NULL) {
                TRACE(("evaltree(NULL) called\n"));
-               goto out1;
+               goto out;
        }
        TRACE(("evaltree(%p: %d, %d) called\n", n, n->type, flags));
 
        dotrap();
 
-       exception_handler = &jmploc;
-       {
-               int err = setjmp(jmploc.loc);
-               if (err) {
-                       /* if it was a signal, check for trap handlers */
-                       if (exception_type == EXSIG) {
-                               TRACE(("exception %d (EXSIG) in evaltree, err=%d\n",
-                                               exception_type, err));
-                               goto out;
-                       }
-                       /* continue on the way out */
-                       TRACE(("exception %d in evaltree, propagating err=%d\n",
-                                       exception_type, err));
-                       exception_handler = savehandler;
-                       longjmp(exception_handler->loc, err);
-               }
-       }
-
        switch (n->type) {
        default:
 #if DEBUG
@@ -8588,7 +8757,8 @@ evaltree(union node *n, int flags)
                if (!status) {
                        status = evaltree(n->nredir.n, flags & EV_TESTED);
                }
-               popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */);
+               if (n->nredir.redirect)
+                       popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */);
                goto setstatus;
        case NCMD:
                evalfn = evalcommand;
@@ -8604,11 +8774,9 @@ evaltree(union node *n, int flags)
                evalfn = evalloop;
                goto calleval;
        case NSUBSHELL:
-               evalfn = evalsubshell;
-               goto checkexit;
        case NBACKGND:
                evalfn = evalsubshell;
-               goto calleval;
+               goto checkexit;
        case NPIPE:
                evalfn = evalpipe;
                goto checkexit;
@@ -8654,7 +8822,7 @@ evaltree(union node *n, int flags)
                status = 0;
                goto setstatus;
        case NDEFUN:
-               defun(n->narg.text, n->narg.next);
+               defun(n);
                /* Not necessary. To test it:
                 * "false; f() { qwerty; }; echo $?" should print 0.
                 */
@@ -8663,11 +8831,7 @@ evaltree(union node *n, int flags)
                exitstatus = status;
                break;
        }
-
  out:
-       exception_handler = savehandler;
-
- out1:
        /* Order of checks below is important:
         * signal handlers trigger before exit caused by "set -e".
         */
@@ -8678,9 +8842,7 @@ evaltree(union node *n, int flags)
        if (flags & EV_EXIT)
                raise_exception(EXEXIT);
 
-       RESTORE_INT(int_level);
        TRACE(("leaving evaltree (no interrupts)\n"));
-
        return exitstatus;
 }
 
@@ -8810,13 +8972,15 @@ static int
 evalsubshell(union node *n, int flags)
 {
        struct job *jp;
-       int backgnd = (n->type == NBACKGND);
+       int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */
        int status;
 
        expredir(n->nredir.redirect);
        if (!backgnd && (flags & EV_EXIT) && !may_have_traps)
                goto nofork;
        INT_OFF;
+       if (backgnd == FORK_FG)
+               get_tty_state();
        jp = makejob(/*n,*/ 1);
        if (forkshell(jp, n, backgnd) == 0) {
                /* child */
@@ -8829,8 +8993,9 @@ evalsubshell(union node *n, int flags)
                evaltreenr(n->nredir.n, flags);
                /* never returns */
        }
+       /* parent */
        status = 0;
-       if (!backgnd)
+       if (backgnd == FORK_FG)
                status = waitforjob(jp);
        INT_ON;
        return status;
@@ -8854,14 +9019,14 @@ expredir(union node *n)
                case NFROMTO:
                case NFROM:
                case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
                case NTO2:
 #endif
                case NCLOBBER:
                case NAPPEND:
                        expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
                        TRACE(("expredir expanded to '%s'\n", fn.list->text));
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
  store_expfname:
 #endif
 #if 0
@@ -8883,7 +9048,7 @@ expredir(union node *n)
                                expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
                                if (fn.list == NULL)
                                        ash_msg_and_raise_error("redir error");
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
 //FIXME: we used expandarg with different args!
                                if (!isdigit_str9(fn.list->text)) {
                                        /* >&file, not >&fd */
@@ -8922,6 +9087,8 @@ evalpipe(union node *n, int flags)
                pipelen++;
        flags |= EV_EXIT;
        INT_OFF;
+       if (n->npipe.pipe_backgnd == 0)
+               get_tty_state();
        jp = makejob(/*n,*/ pipelen);
        prevfd = -1;
        for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
@@ -8934,6 +9101,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]);
@@ -8949,6 +9117,7 @@ evalpipe(union node *n, int flags)
                        evaltreenr(lp->n, flags);
                        /* never returns */
                }
+               /* parent */
                if (prevfd >= 0)
                        close(prevfd);
                prevfd = pip[0];
@@ -9079,7 +9248,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--;
@@ -9122,7 +9291,7 @@ mklocal(char *name)
                        /* else:
                         * it's a duplicate "local VAR" declaration, do nothing
                         */
-                       return;
+                       goto ret;
                }
                lvp = lvp->next;
        }
@@ -9161,6 +9330,7 @@ mklocal(char *name)
        lvp->vp = vp;
        lvp->next = localvars;
        localvars = lvp;
+ ret:
        INT_ON;
 }
 
@@ -9197,7 +9367,14 @@ truecmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 static int FAST_FUNC
 execcmd(int argc UNUSED_PARAM, char **argv)
 {
-       if (argv[1]) {
+       optionarg = NULL;
+       while (nextopt("a:") != '\0')
+               /* nextopt() sets optionarg to "-a ARGV0" */;
+
+       argv = argptr;
+       if (argv[0]) {
+               char *prog;
+
                iflag = 0;              /* exit on error */
                mflag = 0;
                optschanged();
@@ -9213,7 +9390,10 @@ execcmd(int argc UNUSED_PARAM, char **argv)
                /*setsignal(SIGTSTP); - unnecessary because of mflag=0 */
                /*setsignal(SIGTTOU); - unnecessary because of mflag=0 */
 
-               shellexec(argv + 1, pathval(), 0);
+               prog = argv[0];
+               if (optionarg)
+                       argv[0] = optionarg;
+               shellexec(prog, argv, pathval(), 0);
                /* NOTREACHED */
        }
        return 0;
@@ -9248,7 +9428,7 @@ static int helpcmd(int, char **) FAST_FUNC;
 #if MAX_HISTORY
 static int historycmd(int, char **) FAST_FUNC;
 #endif
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
 static int letcmd(int, char **) FAST_FUNC;
 #endif
 static int readcmd(int, char **) FAST_FUNC;
@@ -9270,13 +9450,13 @@ static int ulimitcmd(int, char **) FAST_FUNC;
 #define BUILTIN_SPEC_REG_ASSG   "7"
 
 /* Stubs for calling non-FAST_FUNC's */
-#if ENABLE_ASH_BUILTIN_ECHO
+#if ENABLE_ASH_ECHO
 static int FAST_FUNC echocmd(int argc, char **argv)   { return echo_main(argc, argv); }
 #endif
-#if ENABLE_ASH_BUILTIN_PRINTF
+#if ENABLE_ASH_PRINTF
 static int FAST_FUNC printfcmd(int argc, char **argv) { return printf_main(argc, argv); }
 #endif
-#if ENABLE_ASH_BUILTIN_TEST
+#if ENABLE_ASH_TEST || BASH_TEST2
 static int FAST_FUNC testcmd(int argc, char **argv)   { return test_main(argc, argv); }
 #endif
 
@@ -9284,11 +9464,11 @@ static int FAST_FUNC testcmd(int argc, char **argv)   { return test_main(argc, a
 static const struct builtincmd builtintab[] = {
        { BUILTIN_SPEC_REG      "."       , dotcmd     },
        { BUILTIN_SPEC_REG      ":"       , truecmd    },
-#if ENABLE_ASH_BUILTIN_TEST
+#if ENABLE_ASH_TEST
        { BUILTIN_REGULAR       "["       , testcmd    },
-#if ENABLE_ASH_BASH_COMPAT
-       { BUILTIN_REGULAR       "[["      , testcmd    },
 #endif
+#if BASH_TEST2
+       { BUILTIN_REGULAR       "[["      , testcmd    },
 #endif
 #if ENABLE_ASH_ALIAS
        { BUILTIN_REG_ASSG      "alias"   , aliascmd   },
@@ -9303,7 +9483,7 @@ static const struct builtincmd builtintab[] = {
        { BUILTIN_REGULAR       "command" , commandcmd },
 #endif
        { BUILTIN_SPEC_REG      "continue", breakcmd   },
-#if ENABLE_ASH_BUILTIN_ECHO
+#if ENABLE_ASH_ECHO
        { BUILTIN_REGULAR       "echo"    , echocmd    },
 #endif
        { BUILTIN_SPEC_REG      "eval"    , NULL       }, /*evalcmd() has a differing prototype*/
@@ -9328,11 +9508,11 @@ static const struct builtincmd builtintab[] = {
        { BUILTIN_REGULAR       "jobs"    , jobscmd    },
        { BUILTIN_REGULAR       "kill"    , killcmd    },
 #endif
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
        { BUILTIN_NOSPEC        "let"     , letcmd     },
 #endif
        { BUILTIN_ASSIGN        "local"   , localcmd   },
-#if ENABLE_ASH_BUILTIN_PRINTF
+#if ENABLE_ASH_PRINTF
        { BUILTIN_REGULAR       "printf"  , printfcmd  },
 #endif
        { BUILTIN_NOSPEC        "pwd"     , pwdcmd     },
@@ -9341,10 +9521,10 @@ static const struct builtincmd builtintab[] = {
        { BUILTIN_SPEC_REG      "return"  , returncmd  },
        { BUILTIN_SPEC_REG      "set"     , setcmd     },
        { BUILTIN_SPEC_REG      "shift"   , shiftcmd   },
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SOURCE
        { BUILTIN_SPEC_REG      "source"  , dotcmd     },
 #endif
-#if ENABLE_ASH_BUILTIN_TEST
+#if ENABLE_ASH_TEST
        { BUILTIN_REGULAR       "test"    , testcmd    },
 #endif
        { BUILTIN_SPEC_REG      "times"   , timescmd   },
@@ -9363,15 +9543,15 @@ 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_TEST + \
+       /* [[ */        1 * BASH_TEST2 + \
        /* 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 + \
+       /* echo */      1 * ENABLE_ASH_ECHO + \
        0)
 #define EXECCMD (EVALCMD + \
        /* eval */      1)
@@ -9469,7 +9649,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;
@@ -9563,11 +9745,13 @@ evalcommand(union node *cmd, int flags)
        }
 
        if (status) {
+ bail:
+               exitstatus = status;
+
                /* We have a redirection error. */
                if (spclbltin > 0)
                        raise_exception(EXERROR);
- bail:
-               exitstatus = status;
+
                goto out;
        }
 
@@ -9597,6 +9781,7 @@ evalcommand(union node *cmd, int flags)
                if (!(flags & EV_EXIT) || may_have_traps) {
                        /* No, forking off a child is necessary */
                        INT_OFF;
+                       get_tty_state();
                        jp = makejob(/*cmd,*/ 1);
                        if (forkshell(jp, cmd, FORK_FG) != 0) {
                                /* parent */
@@ -9610,7 +9795,7 @@ evalcommand(union node *cmd, int flags)
                        /* fall through to exec'ing external program */
                }
                listsetvar(varlist.list, VEXPORT|VSTACK);
-               shellexec(argv, path, cmdentry.u.index);
+               shellexec(argv[0], argv, path, cmdentry.u.index);
                /* NOTREACHED */
        } /* default */
        case CMDBUILTIN:
@@ -9632,21 +9817,12 @@ evalcommand(union node *cmd, int flags)
                dowait(DOWAIT_NONBLOCK, NULL);
 
                if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) {
-                       int exit_status;
-                       int i = exception_type;
-                       if (i == EXEXIT)
-                               goto raise;
-                       exit_status = 2;
-                       if (i == EXINT)
-                               exit_status = 128 + SIGINT;
-                       if (i == EXSIG)
-                               exit_status = 128 + pending_sig;
-                       exitstatus = exit_status;
-                       if (i == EXINT || spclbltin > 0) {
- raise:
-                               longjmp(exception_handler->loc, 1);
+                       if (exception_type == EXERROR && spclbltin <= 0) {
+                               FORCE_INT_ON;
+                               goto readstatus;
                        }
-                       FORCE_INT_ON;
+ raise:
+                       longjmp(exception_handler->loc, 1);
                }
                goto readstatus;
 
@@ -9662,7 +9838,8 @@ evalcommand(union node *cmd, int flags)
        } /* switch */
 
  out:
-       popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
+       if (cmd->ncmd.redirect)
+               popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
        if (lastarg) {
                /* dsl: I think this is intended to be used to support
                 * '_' in 'vi' command mode during line editing...
@@ -9764,8 +9941,7 @@ breakcmd(int argc UNUSED_PARAM, char **argv)
 }
 
 
-/* ============ input.c
- *
+/*
  * This implements the input routines used by the parser.
  */
 
@@ -9880,13 +10056,16 @@ preadfd(void)
                reinit_unicode_for_ash();
                nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ, timeout);
                if (nr == 0) {
-                       /* Ctrl+C pressed */
+                       /* ^C pressed, "convert" to SIGINT */
+                       write(STDOUT_FILENO, "^C", 2);
                        if (trap[SIGINT]) {
                                buf[0] = '\n';
                                buf[1] = '\0';
                                raise(SIGINT);
                                return 1;
                        }
+                       exitstatus = 128 + SIGINT;
+                       bb_putchar('\n');
                        goto retry;
                }
                if (nr < 0) {
@@ -10059,7 +10238,7 @@ pgetc(void)
                return g_parsefile->lastc[--g_parsefile->unget];
 
        if (--g_parsefile->left_in_line >= 0)
-               c = (signed char)*g_parsefile->next_to_pgetc++;
+               c = (unsigned char)*g_parsefile->next_to_pgetc++;
        else
                c = preadbuffer();
 
@@ -10163,6 +10342,9 @@ popfile(void)
 {
        struct parsefile *pf = g_parsefile;
 
+       if (pf == &basepf)
+               return;
+
        INT_OFF;
        if (pf->pf_fd >= 0)
                close(pf->pf_fd);
@@ -10205,7 +10387,6 @@ closescript(void)
 static void
 setinputfd(int fd, int push)
 {
-       close_on_exec_on(fd);
        if (push) {
                pushfile();
                g_parsefile->buf = NULL;
@@ -10226,7 +10407,6 @@ static int
 setinputfile(const char *fname, int flags)
 {
        int fd;
-       int fd2;
 
        INT_OFF;
        fd = open(fname, O_RDONLY);
@@ -10236,13 +10416,10 @@ setinputfile(const char *fname, int flags)
                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;
@@ -10265,8 +10442,7 @@ setinputstring(char *string)
 }
 
 
-/* ============ mail.c
- *
+/*
  * Routines to check for mail.
  */
 
@@ -10584,25 +10760,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;
@@ -10623,7 +10799,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");
@@ -10640,7 +10816,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);
@@ -10652,23 +10828,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;
 }
 
@@ -10687,20 +10860,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 */
 
@@ -10995,10 +11167,10 @@ simplecmd(void)
        union node *vars, **vpp;
        union node **rpp, *redir;
        int savecheckkwd;
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_TEST2
        smallint double_brackets_flag = 0;
-       smallint function_flag = 0;
 #endif
+       IF_BASH_FUNCTION(smallint function_flag = 0;)
 
        args = NULL;
        app = &args;
@@ -11013,12 +11185,14 @@ simplecmd(void)
                checkkwd = savecheckkwd;
                t = readtoken();
                switch (t) {
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
                case TFUNCTION:
                        if (peektoken() != TWORD)
                                raise_error_unexpected_syntax(TWORD);
                        function_flag = 1;
                        break;
+#endif
+#if BASH_TEST2
                case TAND: /* "&&" */
                case TOR: /* "||" */
                        if (!double_brackets_flag) {
@@ -11032,7 +11206,7 @@ simplecmd(void)
                        n->type = NARG;
                        /*n->narg.next = NULL; - stzalloc did it */
                        n->narg.text = wordtext;
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_TEST2
                        if (strcmp("[[", wordtext) == 0)
                                double_brackets_flag = 1;
                        else if (strcmp("]]", wordtext) == 0)
@@ -11047,7 +11221,7 @@ simplecmd(void)
                                app = &n->narg.next;
                                savecheckkwd = 0;
                        }
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
                        if (function_flag) {
                                checkkwd = CHKNL | CHKKWD;
                                switch (peektoken()) {
@@ -11077,7 +11251,7 @@ simplecmd(void)
                        parsefname();   /* read name of redirection file */
                        break;
                case TLP:
- IF_ASH_BASH_COMPAT(do_func:)
+ IF_BASH_FUNCTION(do_func:)
                        if (args && app == &args->narg.next
                         && !vars && !redir
                        ) {
@@ -11085,7 +11259,7 @@ simplecmd(void)
                                const char *name;
 
                                /* We have a function */
-                               if (IF_ASH_BASH_COMPAT(!function_flag &&) readtoken() != TRP)
+                               if (IF_BASH_FUNCTION(!function_flag &&) readtoken() != TRP)
                                        raise_error_unexpected_syntax(TRP);
                                name = n->narg.text;
                                if (!goodname(name)
@@ -11098,7 +11272,7 @@ simplecmd(void)
                                n->narg.next = parse_command();
                                return n;
                        }
-                       IF_ASH_BASH_COMPAT(function_flag = 0;)
+                       IF_BASH_FUNCTION(function_flag = 0;)
                        /* fall through */
                default:
                        tokpushback = 1;
@@ -11279,7 +11453,7 @@ parse_command(void)
                n1 = list(0);
                t = TEND;
                break;
-       IF_ASH_BASH_COMPAT(case TFUNCTION:)
+       IF_BASH_FUNCTION(case TFUNCTION:)
        case TWORD:
        case TREDIR:
                tokpushback = 1;
@@ -11312,7 +11486,7 @@ parse_command(void)
        return n1;
 }
 
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_DOLLAR_SQUOTE
 static int
 decode_dollar_squote(void)
 {
@@ -11388,30 +11562,26 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
        smallint quotef;
        smallint dblquote;
        smallint oldstyle;
-       smallint prevsyntax; /* syntax before arithmetic */
-#if ENABLE_ASH_EXPAND_PRMT
+       IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */
        smallint pssyntax;   /* we are expanding a prompt string */
-#endif
        int varnest;         /* levels of variables expansion */
-       int arinest;         /* levels of arithmetic expansion */
-       int parenlevel;      /* levels of parens in arithmetic */
+       IF_FEATURE_SH_MATH(int arinest;)    /* levels of arithmetic expansion */
+       IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
        int dqvarnest;       /* levels of variables expansion within double quotes */
 
-       IF_ASH_BASH_COMPAT(smallint bash_dollar_squote = 0;)
+       IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
 
        startlinno = g_parsefile->linno;
        bqlist = NULL;
        quotef = 0;
-       prevsyntax = 0;
-#if ENABLE_ASH_EXPAND_PRMT
+       IF_FEATURE_SH_MATH(prevsyntax = 0;)
        pssyntax = (syntax == PSSYNTAX);
        if (pssyntax)
                syntax = DQSYNTAX;
-#endif
        dblquote = (syntax == DQSYNTAX);
        varnest = 0;
-       arinest = 0;
-       parenlevel = 0;
+       IF_FEATURE_SH_MATH(arinest = 0;)
+       IF_FEATURE_SH_MATH(parenlevel = 0;)
        dqvarnest = 0;
 
        STARTSTACKSTR(out);
@@ -11432,7 +11602,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                        USTPUTC(c, out);
                        break;
                case CCTL:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_DOLLAR_SQUOTE
                        if (c == '\\' && bash_dollar_squote) {
                                c = decode_dollar_squote();
                                if (c == '\0') {
@@ -11461,12 +11631,10 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                        } else if (c == '\n') {
                                nlprompt();
                        } else {
-#if ENABLE_ASH_EXPAND_PRMT
                                if (c == '$' && pssyntax) {
                                        USTPUTC(CTLESC, out);
                                        USTPUTC('\\', out);
                                }
-#endif
                                /* Backslash is retained if we are in "str" and next char isn't special */
                                if (dblquote
                                 && c != '\\'
@@ -11493,7 +11661,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                        dblquote = 1;
                        goto quotemark;
                case CENDQUOTE:
-                       IF_ASH_BASH_COMPAT(bash_dollar_squote = 0;)
+                       IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;)
                        if (eofmark != NULL && varnest == 0) {
                                USTPUTC(c, out);
                        } else {
@@ -11518,7 +11686,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                        }
                        USTPUTC(c, out);
                        break;
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
                case CLP:       /* '(' in arithmetic */
                        parenlevel++;
                        USTPUTC(c, out);
@@ -11552,7 +11720,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                        break;
                default:
                        if (varnest == 0) {
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
                                if (c == '&') {
 //Can't call pgetc_eatbnl() here, this requires three-deep pungetc()
                                        if (pgetc() == '>')
@@ -11569,7 +11737,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
        } /* for (;;) */
  endword:
 
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
        if (syntax == ARISYNTAX)
                raise_error_syntax("missing '))'");
 #endif
@@ -11584,7 +11752,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
        len = out - (char *)stackblock();
        out = stackblock();
        if (eofmark == NULL) {
-               if ((c == '>' || c == '<' IF_ASH_BASH_COMPAT( || c == 0x100 + '>'))
+               if ((c == '>' || c == '<' IF_BASH_REDIR_OUTPUT( || c == 0x100 + '>'))
                 && quotef == 0
                ) {
                        if (isdigit_str9(out)) {
@@ -11624,11 +11792,17 @@ 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;
                                        nlnoprompt();
                                } else {
@@ -11666,7 +11840,7 @@ parseredir: {
                        pungetc();
                }
        }
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
        else if (c == 0x100 + '>') { /* this flags &> redirection */
                np->nfile.fd = 1;
                pgetc(); /* this is '>', no need to check */
@@ -11727,13 +11901,12 @@ parseredir: {
 parsesub: {
        unsigned char subtype;
        int typeloc;
-       int flags;
 
        c = pgetc_eatbnl();
        if (c > 255 /* PEOA or PEOF */
         || (c != '(' && c != '{' && !is_name(c) && !is_special(c))
        ) {
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_DOLLAR_SQUOTE
                if (syntax != DQSYNTAX && c == '\'')
                        bash_dollar_squote = 1;
                else
@@ -11743,7 +11916,7 @@ parsesub: {
        } else if (c == '(') {
                /* $(command) or $((arith)) */
                if (pgetc_eatbnl() == '(') {
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
                        PARSEARITH();
 #else
                        raise_error_syntax("you disabled math support for $((arith)) syntax");
@@ -11756,26 +11929,19 @@ parsesub: {
                /* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
                USTPUTC(CTLVAR, out);
                typeloc = out - (char *)stackblock();
-               USTPUTC(VSNORMAL, out);
+               STADJUST(1, out);
                subtype = VSNORMAL;
                if (c == '{') {
                        c = pgetc_eatbnl();
-                       if (c == '#') {
-                               c = pgetc_eatbnl();
-                               if (c == '}')
-                                       c = '#'; /* ${#} - same as $# */
-                               else
-                                       subtype = VSLENGTH; /* ${#VAR} */
-                       } else {
-                               subtype = 0;
-                       }
+                       subtype = 0;
                }
-               if (c <= 255 /* not PEOA or PEOF */ && is_name(c)) {
+ varname:
+               if (is_name(c)) {
                        /* $[{[#]]NAME[}] */
                        do {
                                STPUTC(c, out);
                                c = pgetc_eatbnl();
-                       } while (c <= 255 /* not PEOA or PEOF */ && is_in_name(c));
+                       } while (is_in_name(c));
                } else if (isdigit(c)) {
                        /* $[{[#]]NUM[}] */
                        do {
@@ -11784,19 +11950,31 @@ parsesub: {
                        } while (isdigit(c));
                } else if (is_special(c)) {
                        /* $[{[#]]<specialchar>[}] */
-                       USTPUTC(c, out);
+                       int cc = c;
+
                        c = pgetc_eatbnl();
+                       if (!subtype && cc == '#') {
+                               subtype = VSLENGTH;
+                               if (c == '_' || isalnum(c))
+                                       goto varname;
+                               cc = c;
+                               c = pgetc_eatbnl();
+                               if (cc == '}' || c != '}') {
+                                       pungetc();
+                                       subtype = 0;
+                                       c = cc;
+                                       cc = '#';
+                               }
+                       }
+                       USTPUTC(cc, out);
                } else {
- badsub:
-                       raise_error_syntax("bad substitution");
+                       goto badsub;
                }
                if (c != '}' && subtype == VSLENGTH) {
                        /* ${#VAR didn't end with } */
                        goto badsub;
                }
 
-               STPUTC('=', out);
-               flags = 0;
                if (subtype == 0) {
                        static const char types[] ALIGN1 = "}-+?=";
                        /* ${VAR...} but not $VAR or ${#VAR} */
@@ -11804,7 +11982,7 @@ parsesub: {
                        switch (c) {
                        case ':':
                                c = pgetc_eatbnl();
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SUBSTR
                                /* This check is only needed to not misinterpret
                                 * ${VAR:-WORD}, ${VAR:+WORD}, ${VAR:=WORD}, ${VAR:?WORD}
                                 * constructs.
@@ -11812,16 +11990,16 @@ parsesub: {
                                if (!strchr(types, c)) {
                                        subtype = VSSUBSTR;
                                        pungetc();
-                                       break; /* "goto do_pungetc" is bigger (!) */
+                                       break; /* "goto badsub" is bigger (!) */
                                }
 #endif
-                               flags = VSNUL;
+                               subtype = VSNUL;
                                /*FALLTHROUGH*/
                        default: {
                                const char *p = strchr(types, c);
                                if (p == NULL)
-                                       goto badsub;
-                               subtype = p - types + VSNORMAL;
+                                       break;
+                               subtype |= p - types + VSNORMAL;
                                break;
                        }
                        case '%':
@@ -11830,11 +12008,11 @@ parsesub: {
                                subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT);
                                c = pgetc_eatbnl();
                                if (c != cc)
-                                       goto do_pungetc;
+                                       goto badsub;
                                subtype++;
                                break;
                        }
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PATTERN_SUBST
                        case '/':
                                /* ${v/[/]pattern/repl} */
 //TODO: encode pattern and repl separately.
@@ -11842,22 +12020,22 @@ parsesub: {
                                subtype = VSREPLACE;
                                c = pgetc_eatbnl();
                                if (c != '/')
-                                       goto do_pungetc;
+                                       goto badsub;
                                subtype++; /* VSREPLACEALL */
                                break;
 #endif
                        }
                } else {
do_pungetc:
badsub:
                        pungetc();
                }
-               ((unsigned char *)stackblock())[typeloc] = subtype | flags;
+               ((unsigned char *)stackblock())[typeloc] = subtype;
                if (subtype != VSNORMAL) {
                        varnest++;
-                       if (dblquote) {
+                       if (dblquote)
                                dqvarnest++;
-                       }
                }
+               STPUTC('=', out);
        }
        goto parsesub_return;
 }
@@ -11993,7 +12171,7 @@ parsebackq: {
        goto parsebackq_newreturn;
 }
 
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
 /*
  * Parse an arithmetic expansion (indicate start of one and set state)
  */
@@ -12089,7 +12267,7 @@ xxreadtoken(void)
                                                p += xxreadtoken_doubles + 1;
                                        } else {
                                                pungetc();
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
                                                if (c == '&' && cc == '>') /* &> */
                                                        break; /* return readtoken1(...) */
 #endif
@@ -12290,16 +12468,21 @@ parseheredoc(void)
 /*
  * called by editline -- any expansions to the prompt should be added here.
  */
-#if ENABLE_ASH_EXPAND_PRMT
 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;
@@ -12310,7 +12493,6 @@ expandstr(const char *ps)
        expandarg(&n, NULL, EXP_QUOTED);
        return stackblock();
 }
-#endif
 
 /*
  * Execute a command or commands contained in a string.
@@ -12318,6 +12500,10 @@ expandstr(const char *ps)
 static int
 evalstring(char *s, int flags)
 {
+       struct jmploc *volatile savehandler;
+       struct jmploc jmploc;
+       int ex;
+
        union node *n;
        struct stackmark smark;
        int status;
@@ -12327,6 +12513,19 @@ evalstring(char *s, int flags)
        setstackmark(&smark);
 
        status = 0;
+       /* On exception inside execution loop, we must popfile().
+        * Try interactively:
+        *      readonly a=a
+        *      command eval "a=b"  # throws "is read only" error
+        * "command BLTIN" is not supposed to abort (even in non-interactive use).
+        * But if we skip popfile(), we hit EOF in eval's string, and exit.
+        */
+       savehandler = exception_handler;
+       ex = setjmp(jmploc.loc);
+       if (ex)
+               goto out;
+       exception_handler = &jmploc;
+
        while ((n = parsecmd(0)) != NODE_EOF) {
                int i;
 
@@ -12337,10 +12536,15 @@ evalstring(char *s, int flags)
                if (evalskip)
                        break;
        }
+ out:
        popstackmark(&smark);
        popfile();
        stunalloc(s);
 
+       exception_handler = savehandler;
+       if (ex)
+                longjmp(exception_handler->loc, ex);
+
        return status;
 }
 
@@ -12451,16 +12655,7 @@ find_dot_file(char *name)
        if (strchr(name, '/'))
                return name;
 
-       /* IIRC standards do not say whether . is to be searched.
-        * And it is even smaller this way, making it unconditional for now:
-        */
-       if (1) { /* ENABLE_ASH_BASH_COMPAT */
-               fullname = name;
-               goto try_cur_dir;
-       }
-
        while ((fullname = path_advance(&path, name)) != NULL) {
- try_cur_dir:
                if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
                        /*
                         * Don't bother freeing here, since it will
@@ -12484,6 +12679,7 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM)
        int status = 0;
        char *fullname;
        char **argv;
+       char *args_need_save;
        struct strlist *sp;
        volatile struct shparam saveparam;
 
@@ -12503,7 +12699,8 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM)
         */
        fullname = find_dot_file(argv[0]);
        argv++;
-       if (argv[0]) { /* . FILE ARGS, ARGS exist */
+       args_need_save = argv[0];
+       if (args_need_save) { /* ". FILE ARGS", and ARGS are not empty */
                int argc;
                saveparam = shellparam;
                shellparam.malloced = 0;
@@ -12522,7 +12719,7 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM)
        status = cmdloop(0);
        popfile();
 
-       if (argv[0]) {
+       if (args_need_save) {
                freeparam(&shellparam);
                shellparam = saveparam;
        };
@@ -12793,7 +12990,7 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        exitcode = 0;
        while (*ap) {
                signo = get_signum(*ap);
-               if (signo < 0) {
+               if (signo < 0 || signo >= NSIG) {
                        /* Mimic bash message exactly */
                        ash_msg("%s: invalid signal specification", *ap);
                        exitcode = 1;
@@ -12803,12 +13000,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);
@@ -12897,7 +13095,7 @@ exportcmd(int argc UNUSED_PARAM, char **argv)
        }
        flag_off = ~flag_off;
 
-       /*if (opt_p_not_specified) - bash doesnt check this. Try "export -p NAME" */
+       /*if (opt_p_not_specified) - bash doesn't check this. Try "export -p NAME" */
        {
                aptr = argptr;
                name = *aptr;
@@ -13001,7 +13199,7 @@ timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        return 0;
 }
 
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
 /*
  * The let builtin. Partially stolen from GNU Bash, the Bourne Again SHell.
  * Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc.
@@ -13077,6 +13275,7 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        /* "read -s" needs to save/restore termios, can't allow ^C
         * to jump out of it.
         */
+ again:
        INT_OFF;
        r = shell_builtin_read(setvar0,
                argptr,
@@ -13089,6 +13288,12 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        );
        INT_ON;
 
+       if ((uintptr_t)r == 1 && errno == EINTR) {
+               /* to get SIGCHLD: sleep 1 & read x; echo $x */
+               if (pending_sig == 0)
+                       goto again;
+       }
+
        if ((uintptr_t)r > 1)
                ash_msg_and_raise_error(r);
 
@@ -13189,12 +13394,12 @@ exitshell(void)
                evalstring(p, 0);
                /*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 */
 }
@@ -13205,7 +13410,9 @@ init(void)
        /* we will never free this */
        basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ);
 
-       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 $$"
         */
@@ -13227,9 +13434,11 @@ init(void)
                setvareq((char*)defoptindvar, VTEXTFIXED);
 
                setvar0("PPID", utoa(getppid()));
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SHLVL_VAR
                p = lookupvar("SHLVL");
                setvar("SHLVL", utoa((p ? atoi(p) : 0) + 1), VEXPORT);
+#endif
+#if BASH_HOSTNAME_VAR
                if (!lookupvar("HOSTNAME")) {
                        struct utsname uts;
                        uname(&uts);
@@ -13254,15 +13463,6 @@ init(void)
 //usage:#define ash_full_usage "\n\n"
 //usage:       "Unix shell interpreter"
 
-//usage:#if ENABLE_FEATURE_SH_IS_ASH
-//usage:# define sh_trivial_usage ash_trivial_usage
-//usage:# define sh_full_usage    ash_full_usage
-//usage:#endif
-//usage:#if ENABLE_FEATURE_BASH_IS_ASH
-//usage:# define bash_trivial_usage ash_trivial_usage
-//usage:# define bash_full_usage    ash_full_usage
-//usage:#endif
-
 /*
  * Process the shell command line arguments.
  */
@@ -13301,7 +13501,7 @@ procargs(char **argv)
 #if DEBUG == 2
        debug = 1;
 #endif
-       /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */
+       /* POSIX 1003.2: first arg after "-c CMD" is $0, remainder $1... */
        if (xminusc) {
                minusc = *xargv++;
                if (*xargv)
@@ -13327,11 +13527,12 @@ procargs(char **argv)
 }
 
 /*
- * Read /etc/profile or .profile.
+ * Read /etc/profile, ~/.profile, $ENV.
  */
 static void
 read_profile(const char *name)
 {
+       name = expandstr(name);
        if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0)
                return;
        cmdloop(0);
@@ -13341,6 +13542,7 @@ read_profile(const char *name)
 /*
  * This routine is called when an error or an interrupt occurs in an
  * interactive shell and control is returned to the main command loop.
+ * (In dash, this function is auto-generated by build machinery).
  */
 static void
 reset(void)
@@ -13348,15 +13550,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
@@ -13374,7 +13579,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;
@@ -13422,16 +13626,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;
@@ -13443,11 +13646,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;
@@ -13457,11 +13657,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) {
@@ -13482,9 +13682,11 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                        if (!hp) {
                                hp = lookupvar("HOME");
                                if (hp) {
+                                       INT_OFF;
                                        hp = concat_path_file(hp, ".ash_history");
                                        setvar0("HISTFILE", hp);
                                        free((char*)hp);
+                                       INT_ON;
                                        hp = lookupvar("HISTFILE");
                                }
                        }