ash: force inlining of a trivial function
[oweals/busybox.git] / shell / ash.c
index 8d8cc466bc7d593778394139d7685ed912bcc610..f74fbd72f7b11d811730a34e12ee02253ce5e067 100644 (file)
  *
  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
+//config:config ASH
+//config:      bool "ash (77 kb)"
+//config:      default y
+//config:      depends on !NOMMU
+//config:      help
+//config:      The most complete and most pedantically correct shell included with
+//config:      busybox. This shell is actually a derivative of the Debian 'dash'
+//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 || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_INTERNAL_GLOB
+//config:      bool "Use internal glob() implementation"
+//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 || 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:      You can reset the generator by using a specified start value.
+//config:      After "unset RANDOM" the generator will switch off and this
+//config:      variable will no longer have special treatment.
+//config:
+//config:config ASH_EXPAND_PRMT
+//config:      bool "Expand prompt string"
+//config:      default y
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:      help
+//config:      $PS# may contain volatile content, such as backquote commands.
+//config:      This option recreates the prompt string from the environment
+//config:      variable each time it is displayed.
+//config:
+//config:config ASH_IDLE_TIMEOUT
+//config:      bool "Idle timeout variable $TMOUT"
+//config:      default y
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:      help
+//config:      Enable bash-like auto-logout after $TMOUT seconds of idle time.
+//config:
+//config:config ASH_MAIL
+//config:      bool "Check for new mail in interactive shell"
+//config:      default y
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:      help
+//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_ECHO
+//config:      bool "echo builtin"
+//config:      default y
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_PRINTF
+//config:      bool "printf builtin"
+//config:      default y
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_TEST
+//config:      bool "test builtin"
+//config:      default y
+//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 || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_GETOPTS
+//config:      bool "getopts builtin"
+//config:      default y
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:
+//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 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_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
 
 /*
- * 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.
+ * 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 */
 #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_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
 # include "math.h"
+#else
+typedef long arith_t;
+# define ARITH_FMT "%ld"
 #endif
 #if ENABLE_ASH_RANDOM_SUPPORT
 # include "random.h"
 # error "Do not even bother, ash will not run on NOMMU machine"
 #endif
 
-//config:config ASH
-//config:      bool "ash"
-//config:      default y
-//config:      depends on !NOMMU
-//config:      help
-//config:        Tha 'ash' shell adds about 60k in the default configuration and is
-//config:        the most complete and most pedantically correct shell included with
-//config:        busybox. This shell is actually a derivative of the Debian 'dash'
-//config:        shell (by Herbert Xu), which was created by porting the 'ash' shell
-//config:        (written by Kenneth Almquist) from NetBSD.
-//config:
-//config:config ASH_OPTIMIZE_FOR_SIZE
-//config:      bool "Optimize for size instead of speed"
-//config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Compile ash for reduced size at the price of speed.
-//config:
-//config:config ASH_INTERNAL_GLOB
-//config:      bool "Use internal glob() implementation"
-//config:      default n
-//config:      depends on ASH
-//config:      help
-//config:        Do not use glob() function from libc, use internal implementation.
-//config:        Use this if you are getting "glob.h: No such file or directory"
-//config:        or similar build errors.
-//config:
-//config:config ASH_RANDOM_SUPPORT
-//config:      bool "Pseudorandom generator and $RANDOM variable"
-//config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable pseudorandom generator and dynamic variable "$RANDOM".
-//config:        Each read of "$RANDOM" will generate a new pseudorandom value.
-//config:        You can reset the generator by using a specified start value.
-//config:        After "unset RANDOM" the generator will switch off and this
-//config:        variable will no longer have special treatment.
-//config:
-//config:config ASH_EXPAND_PRMT
-//config:      bool "Expand prompt string"
-//config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        "PS#" may contain volatile content, such as backquote commands.
-//config:        This option recreates the prompt string from the environment
-//config:        variable each time it is displayed.
-//config:
-//config:config ASH_BASH_COMPAT
-//config:      bool "bash-compatible extensions"
-//config:      default y
-//config:      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:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable alias support in the ash shell.
-//config:
-//config:config ASH_GETOPTS
-//config:      bool "Builtin getopt to parse positional parameters"
-//config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable support for getopts builtin in ash.
-//config:
-//config:config ASH_BUILTIN_ECHO
-//config:      bool "Builtin version of 'echo'"
-//config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable support for echo builtin in ash.
-//config:
-//config:config ASH_BUILTIN_PRINTF
-//config:      bool "Builtin version of 'printf'"
-//config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable support for printf builtin in ash.
-//config:
-//config:config ASH_BUILTIN_TEST
-//config:      bool "Builtin version of 'test'"
-//config:      default y
-//config:      depends on ASH
-//config:      help
-//config:        Enable support for test builtin in 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:
-//config:config ASH_CMDCMD
-//config:      bool "'command' command to override shell builtins"
-//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:
-//config:config ASH_MAIL
-//config:      bool "Check for new mail on interactive shells"
-//config:      default n
-//config:      depends on ASH
-//config:      help
-//config:        Enable "check for new mail" function in the ash shell.
-//config:
-
-//applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
-//applet:IF_FEATURE_SH_IS_ASH(APPLET_ODDNAME(sh, ash, BB_DIR_BIN, BB_SUID_DROP, sh))
-//applet:IF_FEATURE_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, BB_DIR_BIN, BB_SUID_DROP, bash))
-
-//kbuild:lib-$(CONFIG_ASH) += ash.o ash_ptr_hack.o shell_common.o
-//kbuild:lib-$(CONFIG_ASH_RANDOM_SUPPORT) += random.o
-
 
 /* ============ Hash table sizes. Configurable. */
 
@@ -245,7 +269,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
@@ -322,14 +346,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 */
@@ -600,8 +624,8 @@ fmtstr(char *outbuf, size_t length, const char *fmt, ...)
        va_list ap;
        int ret;
 
-       va_start(ap, fmt);
        INT_OFF;
+       va_start(ap, fmt);
        ret = vsnprintf(outbuf, length, fmt, ap);
        va_end(ap);
        INT_ON;
@@ -650,8 +674,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
@@ -678,7 +704,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
@@ -1088,7 +1114,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;
@@ -1224,7 +1250,6 @@ static struct parsefile basepf;        /* top level input file */
 static struct parsefile *g_parsefile = &basepf;  /* current input file */
 static int startlinno;                 /* line # where last token started */
 static char *commandname;              /* currently executing command */
-static struct strlist *cmdenviron;     /* environment for builtin command */
 
 
 /* ============ Message printing */
@@ -1273,6 +1298,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 */
@@ -1633,7 +1660,7 @@ static char *
 stack_nputstr(const char *s, size_t n, char *p)
 {
        p = makestrspace(n, p);
-       p = (char *)memcpy(p, s, n) + n;
+       p = (char *)mempcpy(p, s, n);
        return p;
 }
 
@@ -1717,7 +1744,7 @@ number(const char *s)
 }
 
 /*
- * Produce a possibly single quoted string suitable as input to the shell.
+ * Produce a single quoted string suitable as input to the shell.
  * The return string is allocated on the stack.
  */
 static char *
@@ -1736,7 +1763,7 @@ single_quote(const char *s)
                q = p = makestrspace(len + 3, p);
 
                *q++ = '\'';
-               q = (char *)memcpy(q, s, len) + len;
+               q = (char *)mempcpy(q, s, len);
                *q++ = '\'';
                s += len;
 
@@ -1750,7 +1777,7 @@ single_quote(const char *s)
                q = p = makestrspace(len + 3, p);
 
                *q++ = '"';
-               q = (char *)memcpy(q, s - len, len) + len;
+               q = (char *)mempcpy(q, s - len, len);
                *q++ = '"';
 
                STADJUST(q - p, p);
@@ -1761,6 +1788,44 @@ single_quote(const char *s)
        return stackblock();
 }
 
+/*
+ * Produce a possibly single quoted string suitable as input to the shell.
+ * If quoting was done, the return string is allocated on the stack,
+ * otherwise a pointer to the original string is returned.
+ */
+static const char *
+maybe_single_quote(const char *s)
+{
+       const char *p = s;
+
+       while (*p) {
+               /* Assuming ACSII */
+               /* quote ctrl_chars space !"#$%&'()* */
+               if (*p < '+')
+                       goto need_quoting;
+               /* quote ;<=>? */
+               if (*p >= ';' && *p <= '?')
+                       goto need_quoting;
+               /* quote `[\ */
+               if (*p == '`')
+                       goto need_quoting;
+               if (*p == '[')
+                       goto need_quoting;
+               if (*p == '\\')
+                       goto need_quoting;
+               /* quote {|}~ DEL and high bytes */
+               if (*p > 'z')
+                       goto need_quoting;
+               /* Not quoting these: +,-./ 0-9 :@ A-Z ]^_ a-z */
+               /* TODO: maybe avoid quoting % */
+               p++;
+       }
+       return s;
+
+ need_quoting:
+       return single_quote(s);
+}
+
 
 /* ============ nextopt */
 
@@ -2135,6 +2200,7 @@ lookupvar(const char *name)
        return NULL;
 }
 
+#if ENABLE_UNICODE_SUPPORT
 static void
 reinit_unicode_for_ash(void)
 {
@@ -2151,19 +2217,16 @@ reinit_unicode_for_ash(void)
                reinit_unicode(s);
        }
 }
+#else
+# define reinit_unicode_for_ash() ((void)0)
+#endif
 
 /*
  * Search the environment of a builtin command.
  */
-static const char *
+static ALWAYS_INLINE const char *
 bltinlookup(const char *name)
 {
-       struct strlist *sp;
-
-       for (sp = cmdenviron; sp; sp = sp->next) {
-               if (varcmp(sp->text, name) == 0)
-                       return var_end(sp->text);
-       }
        return lookupvar(name);
 }
 
@@ -2174,14 +2237,15 @@ bltinlookup(const char *name)
  * will go away.
  * Called with interrupts off.
  */
-static void
+static struct var *
 setvareq(char *s, int flags)
 {
        struct var *vp, **vpp;
 
        vpp = hashvar(s);
        flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
-       vp = *findvar(vpp, s);
+       vpp = findvar(vpp, s);
+       vp = *vpp;
        if (vp) {
                if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
                        const char *n;
@@ -2194,7 +2258,7 @@ setvareq(char *s, int flags)
                }
 
                if (flags & VNOSET)
-                       return;
+                       goto out;
 
                if (vp->var_func && !(flags & VNOFUNC))
                        vp->var_func(var_end(s));
@@ -2202,11 +2266,22 @@ setvareq(char *s, int flags)
                if (!(vp->flags & (VTEXTFIXED|VSTACK)))
                        free((char*)vp->var_text);
 
+               if (((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) | (vp->flags & VSTRFIXED)) == VUNSET) {
+                       *vpp = vp->next;
+                       free(vp);
+ out_free:
+                       if ((flags & (VTEXTFIXED|VSTACK|VNOSAVE)) == VNOSAVE)
+                               free(s);
+                       goto out;
+               }
+
                flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
        } else {
                /* variable s is not found */
                if (flags & VNOSET)
-                       return;
+                       goto out;
+               if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
+                       goto out_free;
                vp = ckzalloc(sizeof(*vp));
                vp->next = *vpp;
                /*vp->func = NULL; - ckzalloc did it */
@@ -2216,13 +2291,16 @@ setvareq(char *s, int flags)
                s = ckstrdup(s);
        vp->var_text = s;
        vp->flags = flags;
+
+ out:
+       return vp;
 }
 
 /*
  * Set the value of a variable.  The flags argument is ored with the
  * flags of the variable.  If val is NULL, the variable is unset.
  */
-static void
+static struct var *
 setvar(const char *name, const char *val, int flags)
 {
        const char *q;
@@ -2230,6 +2308,7 @@ setvar(const char *name, const char *val, int flags)
        char *nameeq;
        size_t namelen;
        size_t vallen;
+       struct var *vp;
 
        q = endofname(name);
        p = strchrnul(q, '=');
@@ -2245,14 +2324,16 @@ setvar(const char *name, const char *val, int flags)
 
        INT_OFF;
        nameeq = ckmalloc(namelen + vallen + 2);
-       p = memcpy(nameeq, name, namelen) + namelen;
+       p = mempcpy(nameeq, name, namelen);
        if (val) {
                *p++ = '=';
-               p = memcpy(p, val, vallen) + vallen;
+               p = mempcpy(p, val, vallen);
        }
        *p = '\0';
-       setvareq(nameeq, flags | VNOSAVE);
+       vp = setvareq(nameeq, flags | VNOSAVE);
        INT_ON;
+
+       return vp;
 }
 
 static void FAST_FUNC
@@ -2264,43 +2345,10 @@ setvar0(const char *name, const char *val)
 /*
  * Unset the specified variable.
  */
-static int
+static void
 unsetvar(const char *s)
 {
-       struct var **vpp;
-       struct var *vp;
-       int retval;
-
-       vpp = findvar(hashvar(s), s);
-       vp = *vpp;
-       retval = 2;
-       if (vp) {
-               int flags = vp->flags;
-
-               retval = 1;
-               if (flags & VREADONLY)
-                       goto out;
-#if ENABLE_ASH_RANDOM_SUPPORT
-               vp->flags &= ~VDYNAMIC;
-#endif
-               if (flags & VUNSET)
-                       goto ok;
-               if ((flags & VSTRFIXED) == 0) {
-                       INT_OFF;
-                       if ((flags & (VTEXTFIXED|VSTACK)) == 0)
-                               free((char*)vp->var_text);
-                       *vpp = vp->next;
-                       free(vp);
-                       INT_ON;
-               } else {
-                       setvar0(s, NULL);
-                       vp->flags &= ~VEXPORT;
-               }
- ok:
-               retval = 0;
-       }
- out:
-       return retval;
+       setvar(s, NULL, 0);
 }
 
 /*
@@ -2383,8 +2431,7 @@ path_advance(const char **path, const char *name)
                growstackblock();
        q = stackblock();
        if (p != start) {
-               memcpy(q, start, p - start);
-               q += p - start;
+               q = mempcpy(q, start, p - start);
                *q++ = '/';
        }
        strcpy(q, name);
@@ -2428,12 +2475,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)
@@ -2458,10 +2501,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
 }
 
@@ -2732,7 +2775,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))
@@ -3120,7 +3163,7 @@ static const uint8_t syntax_index_table[] ALIGN1 = {
        ({ \
                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_SH_MATH_SUPPORT)) \
+               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); \
        })
@@ -3315,11 +3358,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)) {
@@ -3363,12 +3404,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 */
@@ -3549,6 +3591,72 @@ 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
+get_tty_state(void)
+{
+       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)
 {
@@ -3754,7 +3862,7 @@ setjobctl(int on)
                }
                /* fd is a tty at this point */
                fd = fcntl(fd, F_DUPFD, 10);
-               if (ofd >= 0) /* if it is "/dev/tty", close. If 0/1/2, dont */
+               if (ofd >= 0) /* if it is "/dev/tty", close. If 0/1/2, don't */
                        close(ofd);
                if (fd < 0)
                        goto out; /* F_DUPFD failed */
@@ -3880,8 +3988,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;
@@ -3929,15 +4039,19 @@ sprint_status48(char *s, int status, int sigonly)
 
        col = 0;
        if (!WIFEXITED(status)) {
-               if (JOBS && WIFSTOPPED(status))
+#if JOBS
+               if (WIFSTOPPED(status))
                        st = WSTOPSIG(status);
                else
+#endif
                        st = WTERMSIG(status);
                if (sigonly) {
                        if (st == SIGINT || st == SIGPIPE)
                                goto out;
-                       if (JOBS && WIFSTOPPED(status))
+#if JOBS
+                       if (WIFSTOPPED(status))
                                goto out;
+#endif
                }
                st &= 0x7f;
 //TODO: use bbox's get_signame? strsignal adds ~600 bytes to text+rodata
@@ -4089,8 +4203,10 @@ dowait(int block, struct job *job)
                goto out;
        }
        /* The process wasn't found in job list */
-       if (JOBS && !WIFSTOPPED(status))
+#if JOBS
+       if (!WIFSTOPPED(status))
                jobless--;
+#endif
  out:
        INT_ON;
 
@@ -4415,7 +4531,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
@@ -4444,7 +4560,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;
@@ -4478,7 +4595,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;
@@ -4671,7 +4788,7 @@ cmdtxt(union node *n)
        case NAPPEND:
                p = ">>";
                goto redir;
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
        case NTO2:
 #endif
        case NTOFD:
@@ -5009,6 +5126,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
@@ -5058,68 +5177,6 @@ stoppedjobs(void)
 #define EMPTY -2                /* marks an unused slot in redirtab */
 #define CLOSED -3               /* marks a slot of previously-closed fd */
 
-/*
- * Open a file in noclobber mode.
- * The code was copied from bash.
- */
-static int
-noclobberopen(const char *fname)
-{
-       int r, fd;
-       struct stat finfo, finfo2;
-
-       /*
-        * If the file exists and is a regular file, return an error
-        * immediately.
-        */
-       r = stat(fname, &finfo);
-       if (r == 0 && S_ISREG(finfo.st_mode)) {
-               errno = EEXIST;
-               return -1;
-       }
-
-       /*
-        * If the file was not present (r != 0), make sure we open it
-        * exclusively so that if it is created before we open it, our open
-        * will fail.  Make sure that we do not truncate an existing file.
-        * Note that we don't turn on O_EXCL unless the stat failed -- if the
-        * file was not a regular file, we leave O_EXCL off.
-        */
-       if (r != 0)
-               return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
-       fd = open(fname, O_WRONLY|O_CREAT, 0666);
-
-       /* If the open failed, return the file descriptor right away. */
-       if (fd < 0)
-               return fd;
-
-       /*
-        * OK, the open succeeded, but the file may have been changed from a
-        * non-regular file to a regular file between the stat and the open.
-        * We are assuming that the O_EXCL open handles the case where FILENAME
-        * did not exist and is symlinked to an existing file between the stat
-        * and open.
-        */
-
-       /*
-        * If we can open it and fstat the file descriptor, and neither check
-        * revealed that it was a regular file, and the file has not been
-        * replaced, return the file descriptor.
-        */
-       if (fstat(fd, &finfo2) == 0
-        && !S_ISREG(finfo2.st_mode)
-        && finfo.st_dev == finfo2.st_dev
-        && finfo.st_ino == finfo2.st_ino
-       ) {
-               return fd;
-       }
-
-       /* The file has been replaced.  badness. */
-       close(fd);
-       errno = EEXIST;
-       return -1;
-}
-
 /*
  * Handle here documents.  Normally we fork off a process to write the
  * data to a pipe.  If the document is short, we can stuff the data in
@@ -5164,6 +5221,7 @@ openhere(union node *redir)
 static int
 openredirect(union node *redir)
 {
+       struct stat sb;
        char *fname;
        int f;
 
@@ -5198,14 +5256,28 @@ 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. */
                if (Cflag) {
-                       f = noclobberopen(fname);
-                       if (f < 0)
+                       if (stat(fname, &sb) < 0) {
+                               f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
+                               if (f < 0)
+                                       goto ecreate;
+                       } else if (!S_ISREG(sb.st_mode)) {
+                               f = open(fname, O_WRONLY, 0666);
+                               if (f < 0)
+                                       goto ecreate;
+                               if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) {
+                                       close(f);
+                                       errno = EEXIST;
+                                       goto ecreate;
+                               }
+                       } else {
+                               errno = EEXIST;
                                goto ecreate;
+                       }
                        break;
                }
                /* FALLTHROUGH */
@@ -5359,7 +5431,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
@@ -5401,7 +5473,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)) {
@@ -5409,11 +5481,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) {
@@ -5428,6 +5500,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 */
@@ -5451,12 +5526,12 @@ redirect(union node *redir, int flags)
                        }
                } else if (fd != newfd) { /* move newfd to fd */
                        dup2_or_raise(newfd, fd);
-#if ENABLE_ASH_BASH_COMPAT
+#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;
@@ -5538,7 +5613,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)
 {
@@ -5592,7 +5667,7 @@ ash_arith(const char *s)
 #define RMESCAPE_SLASH  0x20    /* Stop globbing after slash */
 
 /* Add CTLESC when necessary. */
-#define QUOTES_ESC     (EXP_FULL | EXP_CASE | EXP_QPAT | EXP_REDIR)
+#define QUOTES_ESC     (EXP_FULL | EXP_CASE | EXP_QPAT)
 /* Do not skip NUL characters. */
 #define QUOTES_KEEPNUL EXP_TILDE
 
@@ -5625,19 +5700,20 @@ static struct arglist exparg;
 
 /*
  * Our own itoa().
+ * cvtnum() is used even if math support is off (to prepare $? values and such).
  */
-#if !ENABLE_SH_MATH_SUPPORT
-/* cvtnum() is used even if math support is off (to prepare $? values and such) */
-typedef long arith_t;
-# define ARITH_FMT "%ld"
-#endif
 static int
 cvtnum(arith_t num)
 {
        int len;
 
-       expdest = makestrspace(sizeof(arith_t)*3 + 2, expdest);
-       len = fmtstr(expdest, sizeof(arith_t)*3 + 2, ARITH_FMT, num);
+       /* 32-bit and wider ints require buffer size of bytes*3 (or less) */
+       len = sizeof(arith_t) * 3;
+       /* If narrower: worst case, 1-byte ints: need 5 bytes: "-127<NUL>" */
+       if (sizeof(arith_t) < 4) len += 2;
+
+       expdest = makestrspace(len, expdest);
+       len = fmtstr(expdest, len, ARITH_FMT, num);
        STADJUST(len, expdest);
        return len;
 }
@@ -5773,15 +5849,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;
 
@@ -5804,7 +5880,7 @@ rmescapes(char *str, int flag)
                }
                q = r;
                if (len > 0) {
-                       q = (char *)memcpy(q, str, len) + len;
+                       q = (char *)mempcpy(q, str, len);
                }
        }
 
@@ -5814,6 +5890,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;
@@ -5826,14 +5903,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;
@@ -6029,7 +6132,9 @@ struct backcmd {                /* result of evalbackcmd */
 };
 
 /* These forward decls are needed to use "eval" code for backticks handling: */
-#define EV_EXIT 01              /* exit after evaluating tree */
+/* flags in argument to evaltree */
+#define EV_EXIT    01           /* exit after evaluating tree */
+#define EV_TESTED  02           /* exit status is checked; ignore -e flag */
 static int evaltree(union node *, int);
 
 static void FAST_FUNC
@@ -6139,7 +6244,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.
@@ -6199,19 +6304,15 @@ expari(int flag)
 #endif
 
 /* argstr needs it */
-static char *evalvar(char *p, int flags, struct strlist *var_str_list);
+static char *evalvar(char *p, int flags);
 
 /*
  * Perform variable and command substitution.  If EXP_FULL is set, output CTLESC
  * characters to allow for further processing.  Otherwise treat
  * $@ 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
- * for correct expansion of "B=$A" word.
  */
 static void
-argstr(char *p, int flags, struct strlist *var_str_list)
+argstr(char *p, int flags)
 {
        static const char spclchars[] ALIGN1 = {
                '=',
@@ -6221,7 +6322,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'
@@ -6257,7 +6358,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++;
@@ -6304,7 +6405,7 @@ argstr(char *p, int flags, struct strlist *var_str_list)
                        inquotes ^= EXP_QUOTED;
                        /* "$@" syntax adherence hack */
                        if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
-                               p = evalvar(p + 1, flags | inquotes, /* var_str_list: */ NULL) + 1;
+                               p = evalvar(p + 1, flags | inquotes) + 1;
                                goto start;
                        }
  addquote:
@@ -6330,14 +6431,14 @@ argstr(char *p, int flags, struct strlist *var_str_list)
                        goto addquote;
                case CTLVAR:
                        TRACE(("argstr: evalvar('%s')\n", p));
-                       p = evalvar(p, flags | inquotes, var_str_list);
+                       p = evalvar(p, flags | inquotes);
                        TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock()));
                        goto start;
                case CTLBACKQ:
                        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);
@@ -6417,8 +6518,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 "*"? */
@@ -6472,7 +6573,7 @@ varunset(const char *end, const char *var, const char *umsg, int varflags)
 
 static const char *
 subevalvar(char *p, char *varname, int strloc, int subtype,
-               int startloc, int varflags, int flag, struct strlist *var_str_list)
+               int startloc, int varflags, int flag)
 {
        struct nodelist *saveargbackq = argbackq;
        int quotes = flag & QUOTES_ESC;
@@ -6480,10 +6581,9 @@ 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 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);
 
@@ -6491,8 +6591,8 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
        //              p, varname, strloc, subtype, startloc, varflags, quotes);
 
        argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ?
-                       (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0),
-                       var_str_list);
+                       (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0)
+       );
        STPUTC('\0', expdest);
        argbackq = saveargbackq;
        startp = (char *)stackblock() + startloc;
@@ -6508,15 +6608,24 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
                varunset(p, varname, startp, varflags);
                /* NOTREACHED */
 
-#if ENABLE_ASH_BASH_COMPAT
-       case VSSUBSTR:
-//TODO: support more general format ${v:EXPR:EXPR},
-// where EXPR follows $(()) rules
+#if BASH_SUBSTR
+       case VSSUBSTR: {
+               int pos, len, orig_len;
+               char *colon;
+
                loc = str = stackblock() + strloc;
+
+# if !ENABLE_FEATURE_SH_MATH
+#  define ash_arith number
+# endif
                /* Read POS in ${var:POS:LEN} */
-               pos = atoi(loc); /* number(loc) errors out on "1:4" */
-               len = str - startp - 1;
+               colon = strchr(loc, ':');
+               if (colon) *colon = '\0';
+               pos = ash_arith(loc);
+               if (colon) *colon = ':';
 
+               /* Read LEN in ${var:POS:LEN} */
+               len = str - startp - 1;
                /* *loc != '\0', guaranteed by parser */
                if (quotes) {
                        char *ptr;
@@ -6530,25 +6639,21 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
                        }
                }
                orig_len = len;
-
                if (*loc++ == ':') {
                        /* ${var::LEN} */
-                       len = number(loc);
+                       len = ash_arith(loc);
                } else {
                        /* Skip POS in ${var:POS:LEN} */
                        len = orig_len;
                        while (*loc && *loc != ':') {
-                               /* TODO?
-                                * bash complains on: var=qwe; echo ${var:1a:123}
-                               if (!isdigit(*loc))
-                                       ash_msg_and_raise_error(msg_illnum, str);
-                                */
                                loc++;
                        }
                        if (*loc++ == ':') {
-                               len = number(loc);
+                               len = ash_arith(loc);
                        }
                }
+#  undef ash_arith
+
                if (pos < 0) {
                        /* ${VAR:$((-n)):l} starts n chars from the end */
                        pos = orig_len + pos;
@@ -6556,12 +6661,16 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
                if ((unsigned)pos >= orig_len) {
                        /* apart from obvious ${VAR:999999:l},
                         * covers ${VAR:$((-9999999)):l} - result is ""
-                        * (bash-compat)
+                        * (bash compat)
                         */
                        pos = 0;
                        len = 0;
                }
-               if (len > (orig_len - pos))
+               if (len < 0) {
+                       /* ${VAR:N:-M} sets LEN to strlen()-M */
+                       len = (orig_len - pos) + len;
+               }
+               if ((unsigned)len > (orig_len - pos))
                        len = orig_len - pos;
 
                for (str = startp; pos; str++, pos--) {
@@ -6577,17 +6686,20 @@ 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);
@@ -6612,13 +6724,14 @@ 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) {
+               int len;
                char *idx, *end;
 
                if (!repl) {
@@ -6717,7 +6830,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
@@ -6756,7 +6869,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
  * ash -c 'echo ${#1#}'  name:'1=#'
  */
 static NOINLINE ssize_t
-varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int *quotedp)
+varvalue(char *name, int varflags, int flags, int *quotedp)
 {
        const char *p;
        int num;
@@ -6848,31 +6961,6 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int
                goto value;
        default:
                /* NB: name has form "VAR=..." */
-
-               /* "A=a B=$A" case: var_str_list is a list of "A=a" strings
-                * which should be considered before we check variables. */
-               if (var_str_list) {
-                       unsigned name_len = (strchrnul(name, '=') - name) + 1;
-                       p = NULL;
-                       do {
-                               char *str, *eq;
-                               str = var_str_list->text;
-                               eq = strchr(str, '=');
-                               if (!eq) /* stop at first non-assignment */
-                                       break;
-                               eq++;
-                               if (name_len == (unsigned)(eq - str)
-                                && strncmp(str, name, name_len) == 0
-                               ) {
-                                       p = eq;
-                                       /* goto value; - WRONG! */
-                                       /* think "A=1 A=2 B=$A" */
-                               }
-                               var_str_list = var_str_list->next;
-                       } while (var_str_list);
-                       if (p)
-                               goto value;
-               }
                p = lookupvar(name);
  value:
                if (!p)
@@ -6902,7 +6990,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int
  * input string.
  */
 static char *
-evalvar(char *p, int flag, struct strlist *var_str_list)
+evalvar(char *p, int flag)
 {
        char varflags;
        char subtype;
@@ -6926,7 +7014,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
        p = strchr(p, '=') + 1; //TODO: use var_end(p)?
 
  again:
-       varlen = varvalue(var, varflags, flag, var_str_list, &quoted);
+       varlen = varvalue(var, varflags, flag, &quoted);
        if (varflags & VSNUL)
                varlen--;
 
@@ -6940,8 +7028,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
                if (varlen < 0) {
                        argstr(
                                p,
-                               flag | EXP_TILDE | EXP_WORD,
-                               var_str_list
+                               flag | EXP_TILDE | EXP_WORD
                        );
                        goto end;
                }
@@ -6953,7 +7040,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
                        goto record;
 
                subevalvar(p, var, 0, subtype, startloc, varflags,
-                          flag & ~QUOTES_ESC, var_str_list);
+                          flag & ~QUOTES_ESC);
                varflags &= ~VSNUL;
                /*
                 * Remove any recorded regions beyond
@@ -6985,8 +7072,10 @@ 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:
+#endif
+#if BASH_PATTERN_SUBST
        case VSREPLACE:
        case VSREPLACEALL:
 #endif
@@ -7004,7 +7093,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
                STPUTC('\0', expdest);
                patloc = expdest - (char *)stackblock();
                if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
-                               startloc, varflags, flag, var_str_list)) {
+                               startloc, varflags, flag)) {
                        int amount = expdest - (
                                (char *)stackblock() + patloc - 1
                        );
@@ -7051,6 +7140,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
 
@@ -7077,20 +7217,9 @@ expandmeta(struct strlist *str /*, int flag*/)
                if (fflag)
                        goto nometa;
 
-               /* Avoid glob() (and thus, stat() et al) for words like "echo" */
-               p = str->text;
-               while (*p) {
-                       if (*p == '*')
-                               goto need_glob;
-                       if (*p == '?')
-                               goto need_glob;
-                       if (*p == '[')
-                               goto need_glob;
-                       p++;
-               }
-               goto nometa;
+               if (!hasmeta(str->text))
+                       goto nometa;
 
- need_glob:
                INT_OFF;
                p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
 // GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match
@@ -7101,7 +7230,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.
@@ -7327,9 +7456,6 @@ expsort(struct strlist *str)
 static void
 expandmeta(struct strlist *str /*, int flag*/)
 {
-       static const char metachars[] ALIGN1 = {
-               '*', '?', '[', 0
-       };
        /* TODO - EXP_REDIR */
 
        while (str) {
@@ -7340,7 +7466,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;
 
@@ -7391,8 +7517,7 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
        argbackq = arg->narg.backquote;
        STARTSTACKSTR(expdest);
        TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag));
-       argstr(arg->narg.text, flag,
-                       /* var_str_list: */ arglist ? arglist->list : NULL);
+       argstr(arg->narg.text, flag);
        p = _STPUTC('\0', expdest);
        expdest = p - 1;
        if (arglist == NULL) {
@@ -7411,10 +7536,6 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
                exparg.lastp = &exparg.list;
                expandmeta(exparg.list /*, flag*/);
        } else {
-               if (flag & EXP_REDIR) { /*XXX - for now, just remove escapes */
-                       rmescapes(p, 0);
-                       TRACE(("expandarg: rmescapes:'%s'\n", p));
-               }
                sp = stzalloc(sizeof(*sp));
                sp->text = p;
                *exparg.lastp = sp;
@@ -7446,7 +7567,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);
 }
 
 /*
@@ -7461,8 +7584,7 @@ casematch(union node *pattern, char *val)
        setstackmark(&smark);
        argbackq = pattern->narg.backquote;
        STARTSTACKSTR(expdest);
-       argstr(pattern->narg.text, EXP_TILDE | EXP_CASE,
-                       /* var_str_list: */ NULL);
+       argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
        STACKSTRNUL(expdest);
        ifsfree();
        result = patmatch(stackblock(), val);
@@ -7548,7 +7670,8 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
                        clearenv();
                        while (*envp)
                                putenv(*envp++);
-                       run_applet_no_and_exit(applet_no, argv);
+                       popredir(/*drop:*/ 1, /*restore:*/ 0);
+                       run_applet_no_and_exit(applet_no, cmd, argv);
                }
                /* re-exec ourselves with the new arguments */
                execve(bb_busybox_exec_path, argv, envp);
@@ -7598,9 +7721,8 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
  * 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;
@@ -7609,12 +7731,12 @@ shellexec(char **argv, const char *path, int idx)
        int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */
 
        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?
@@ -7626,7 +7748,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)
@@ -7650,8 +7772,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 */
 }
 
@@ -7910,7 +8032,7 @@ enum {
        TESAC,
        TFI,
        TFOR,
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
        TFUNCTION,
 #endif
        TIF,
@@ -7948,7 +8070,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)
@@ -7986,7 +8108,7 @@ static const char *const tokname_array[] = {
        "esac",
        "fi",
        "for",
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
        "function",
 #endif
        "if",
@@ -8020,7 +8142,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
@@ -8050,15 +8171,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: {
@@ -8073,9 +8187,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);
                }
@@ -8208,10 +8320,6 @@ commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 static void *funcblock;         /* block to allocate function from */
 static char *funcstring_end;    /* end of block to allocate strings from */
 
-/* flags in argument to evaltree */
-#define EV_EXIT    01           /* exit after evaluating tree */
-#define EV_TESTED  02           /* exit status is checked; ignore -e flag */
-
 static const uint8_t nodesize[N_NUMBER] ALIGN1 = {
        [NCMD     ] = SHELL_ALIGN(sizeof(struct ncmd)),
        [NPIPE    ] = SHELL_ALIGN(sizeof(struct npipe)),
@@ -8230,7 +8338,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)),
@@ -8312,7 +8420,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:
@@ -8426,7 +8534,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:
@@ -8814,13 +8922,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 */
@@ -8835,7 +8945,7 @@ evalsubshell(union node *n, int flags)
        }
        /* parent */
        status = 0;
-       if (!backgnd)
+       if (backgnd == FORK_FG)
                status = waitforjob(jp);
        INT_ON;
        return status;
@@ -8859,14 +8969,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
@@ -8888,7 +8998,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 */
@@ -8927,6 +9037,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) {
@@ -9023,27 +9135,57 @@ optschanged(void)
 #endif
 }
 
-static struct localvar *localvars;
+struct localvar_list {
+       struct localvar_list *next;
+       struct localvar *lv;
+};
+
+static struct localvar_list *localvar_stack;
 
 /*
  * Called after a function returns.
  * Interrupts must be off.
  */
 static void
-poplocalvars(void)
+poplocalvars(int keep)
 {
-       struct localvar *lvp;
+       struct localvar_list *ll;
+       struct localvar *lvp, *next;
        struct var *vp;
 
-       while ((lvp = localvars) != NULL) {
-               localvars = lvp->next;
+       INT_OFF;
+       ll = localvar_stack;
+       localvar_stack = ll->next;
+
+       next = ll->lv;
+       free(ll);
+
+       while ((lvp = next) != NULL) {
+               next = lvp->next;
                vp = lvp->vp;
                TRACE(("poplocalvar %s\n", vp ? vp->var_text : "-"));
-               if (vp == NULL) {       /* $- saved */
+               if (keep) {
+                       int bits = VSTRFIXED;
+
+                       if (lvp->flags != VUNSET) {
+                               if (vp->var_text == lvp->text)
+                                       bits |= VTEXTFIXED;
+                               else if (!(lvp->flags & (VTEXTFIXED|VSTACK)))
+                                       free((char*)lvp->text);
+                       }
+
+                       vp->flags &= ~bits;
+                       vp->flags |= (lvp->flags & bits);
+
+                       if ((vp->flags &
+                            (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
+                               unsetvar(vp->var_text);
+               } else if (vp == NULL) {        /* $- saved */
                        memcpy(optlist, lvp->text, sizeof(optlist));
                        free((char*)lvp->text);
                        optschanged();
-               } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+               } else if (lvp->flags == VUNSET) {
+                       vp->flags &= ~(VSTRFIXED|VREADONLY);
                        unsetvar(vp->var_text);
                } else {
                        if (vp->var_func)
@@ -9055,19 +9197,43 @@ poplocalvars(void)
                }
                free(lvp);
        }
+       INT_ON;
+}
+
+/*
+ * Create a new localvar environment.
+ */
+static struct localvar_list *
+pushlocalvars(void)
+{
+       struct localvar_list *ll;
+
+       INT_OFF;
+       ll = ckzalloc(sizeof(*ll));
+       /*ll->lv = NULL; - zalloc did it */
+       ll->next = localvar_stack;
+       localvar_stack = ll;
+       INT_ON;
+
+       return ll->next;
+}
+
+static void
+unwindlocalvars(struct localvar_list *stop)
+{
+       while (localvar_stack != stop)
+               poplocalvars(0);
 }
 
 static int
 evalfun(struct funcnode *func, int argc, char **argv, int flags)
 {
        volatile struct shparam saveparam;
-       struct localvar *volatile savelocalvars;
        struct jmploc *volatile savehandler;
        struct jmploc jmploc;
        int e;
 
        saveparam = shellparam;
-       savelocalvars = localvars;
        savehandler = exception_handler;
        e = setjmp(jmploc.loc);
        if (e) {
@@ -9075,7 +9241,6 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
        }
        INT_OFF;
        exception_handler = &jmploc;
-       localvars = NULL;
        shellparam.malloced = 0;
        func->count++;
        funcnest++;
@@ -9086,13 +9251,13 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
        shellparam.optind = 1;
        shellparam.optoff = -1;
 #endif
+       pushlocalvars();
        evaltree(func->n.narg.next, flags & EV_TESTED);
+       poplocalvars(0);
  funcdone:
        INT_OFF;
        funcnest--;
        freefunc(func);
-       poplocalvars();
-       localvars = savelocalvars;
        freeparam(&shellparam);
        shellparam = saveparam;
        exception_handler = savehandler;
@@ -9121,7 +9286,7 @@ mklocal(char *name)
         * x=0; f() { local x=1; echo $x; local x; echo $x; }; f; echo $x
         * x=0; f() { local x=1; echo $x; local x=2; echo $x; }; f; echo $x
         */
-       lvp = localvars;
+       lvp = localvar_stack->lv;
        while (lvp) {
                if (lvp->vp && varcmp(lvp->vp->var_text, name) == 0) {
                        if (eq)
@@ -9146,10 +9311,9 @@ mklocal(char *name)
                if (vp == NULL) {
                        /* variable did not exist yet */
                        if (eq)
-                               setvareq(name, VSTRFIXED);
+                               vp = setvareq(name, VSTRFIXED);
                        else
-                               setvar(name, NULL, VSTRFIXED);
-                       vp = *vpp;      /* the new variable */
+                               vp = setvar(name, NULL, VSTRFIXED);
                        lvp->flags = VUNSET;
                } else {
                        lvp->text = vp->var_text;
@@ -9166,8 +9330,8 @@ mklocal(char *name)
                }
        }
        lvp->vp = vp;
-       lvp->next = localvars;
-       localvars = lvp;
+       lvp->next = localvar_stack->lv;
+       localvar_stack->lv = lvp;
  ret:
        INT_ON;
 }
@@ -9180,7 +9344,7 @@ localcmd(int argc UNUSED_PARAM, char **argv)
 {
        char *name;
 
-       if (!funcnest)
+       if (!localvar_stack)
                ash_msg_and_raise_error("not in a function");
 
        argv = argptr;
@@ -9205,7 +9369,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();
@@ -9221,7 +9392,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;
@@ -9256,7 +9430,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;
@@ -9278,13 +9452,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
 
@@ -9292,11 +9466,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
+#endif
+#if BASH_TEST2
        { BUILTIN_REGULAR       "[["      , testcmd    },
-# endif
 #endif
 #if ENABLE_ASH_ALIAS
        { BUILTIN_REG_ASSG      "alias"   , aliascmd   },
@@ -9311,7 +9485,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*/
@@ -9336,11 +9510,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
+       { BUILTIN_SPEC_REG_ASSG "local"   , localcmd   },
+#if ENABLE_ASH_PRINTF
        { BUILTIN_REGULAR       "printf"  , printfcmd  },
 #endif
        { BUILTIN_NOSPEC        "pwd"     , pwdcmd     },
@@ -9349,10 +9523,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   },
@@ -9371,15 +9545,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)
@@ -9428,6 +9602,7 @@ evalcommand(union node *cmd, int flags)
        static const struct builtincmd null_bltin = {
                "\0\0", bltincmd /* why three NULs? */
        };
+       struct localvar_list *localvar_stop;
        struct stackmark smark;
        union node *argp;
        struct arglist arglist;
@@ -9449,6 +9624,7 @@ evalcommand(union node *cmd, int flags)
        /* First expand the arguments. */
        TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
        setstackmark(&smark);
+       localvar_stop = pushlocalvars();
        back_exitstatus = 0;
 
        cmdentry.cmdtype = CMDBUILTIN;
@@ -9502,6 +9678,8 @@ evalcommand(union node *cmd, int flags)
                spp = varlist.lastp;
                expandarg(argp, &varlist, EXP_VARTILDE);
 
+               mklocal((*spp)->text);
+
                /*
                 * Modify the command lookup path, if a PATH= assignment
                 * is present
@@ -9513,18 +9691,36 @@ evalcommand(union node *cmd, int flags)
 
        /* Print the command if xflag is set. */
        if (xflag) {
-               int n;
-               const char *p = " %s" + 1;
+               const char *pfx = "";
+
+               fdprintf(preverrout_fd, "%s", expandstr(ps4val()));
 
-               fdprintf(preverrout_fd, p, expandstr(ps4val()));
                sp = varlist.list;
-               for (n = 0; n < 2; n++) {
-                       while (sp) {
-                               fdprintf(preverrout_fd, p, sp->text);
-                               sp = sp->next;
-                               p = " %s";
-                       }
-                       sp = arglist.list;
+               while (sp) {
+                       char *varval = sp->text;
+                       char *eq = strchrnul(varval, '=');
+                       if (*eq)
+                               eq++;
+                       fdprintf(preverrout_fd, "%s%.*s%s",
+                               pfx,
+                               (int)(eq - varval), varval,
+                               maybe_single_quote(eq)
+                       );
+                       sp = sp->next;
+                       pfx = " ";
+               }
+
+               sp = arglist.list;
+               while (sp) {
+                       fdprintf(preverrout_fd, "%s%s",
+                               pfx,
+                               /* always quote if matches reserved word: */
+                               findkwd(sp->text)
+                               ? single_quote(sp->text)
+                               : maybe_single_quote(sp->text)
+                       );
+                       sp = sp->next;
+                       pfx = " ";
                }
                safe_write(preverrout_fd, "\n", 1);
        }
@@ -9573,11 +9769,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;
        }
 
@@ -9607,6 +9805,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 */
@@ -9620,21 +9819,16 @@ 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:
-               cmdenviron = varlist.list;
-               if (cmdenviron) {
-                       struct strlist *list = cmdenviron;
-                       int i = VNOSET;
-                       if (spclbltin > 0 || argc == 0) {
-                               i = 0;
-                               if (cmd_is_exec && argc > 1)
-                                       i = VEXPORT;
-                       }
-                       listsetvar(list, i);
+               if (spclbltin > 0 || argc == 0) {
+                       poplocalvars(1);
+                       if (cmd_is_exec && argc > 1)
+                               listsetvar(varlist.list, VEXPORT);
                }
+
                /* Tight loop with builtins only:
                 * "while kill -0 $child; do true; done"
                 * will never exit even if $child died, unless we do this
@@ -9652,7 +9846,7 @@ evalcommand(union node *cmd, int flags)
                goto readstatus;
 
        case CMDFUNCTION:
-               listsetvar(varlist.list, 0);
+               poplocalvars(1);
                /* See above for the rationale */
                dowait(DOWAIT_NONBLOCK, NULL);
                if (evalfun(cmdentry.u.func, argc, argv, flags))
@@ -9665,6 +9859,7 @@ evalcommand(union node *cmd, int flags)
  out:
        if (cmd->ncmd.redirect)
                popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
+       unwindlocalvars(localvar_stop);
        if (lastarg) {
                /* dsl: I think this is intended to be used to support
                 * '_' in 'vi' command mode during line editing...
@@ -10992,10 +11187,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;
@@ -11010,12 +11205,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) {
@@ -11029,7 +11226,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)
@@ -11044,7 +11241,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()) {
@@ -11074,7 +11271,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
                        ) {
@@ -11082,7 +11279,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)
@@ -11095,7 +11292,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;
@@ -11276,7 +11473,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;
@@ -11309,7 +11506,7 @@ parse_command(void)
        return n1;
 }
 
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_DOLLAR_SQUOTE
 static int
 decode_dollar_squote(void)
 {
@@ -11385,30 +11582,26 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
        smallint quotef;
        smallint dblquote;
        smallint oldstyle;
-       IF_SH_MATH_SUPPORT(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 */
-       IF_SH_MATH_SUPPORT(int arinest;)    /* levels of arithmetic expansion */
-       IF_SH_MATH_SUPPORT(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;
-       IF_SH_MATH_SUPPORT(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;
-       IF_SH_MATH_SUPPORT(arinest = 0;)
-       IF_SH_MATH_SUPPORT(parenlevel = 0;)
+       IF_FEATURE_SH_MATH(arinest = 0;)
+       IF_FEATURE_SH_MATH(parenlevel = 0;)
        dqvarnest = 0;
 
        STARTSTACKSTR(out);
@@ -11429,7 +11622,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') {
@@ -11458,12 +11651,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 != '\\'
@@ -11490,7 +11681,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 {
@@ -11515,7 +11706,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);
@@ -11549,7 +11740,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() == '>')
@@ -11566,7 +11757,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
@@ -11581,7 +11772,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)) {
@@ -11669,7 +11860,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 */
@@ -11735,7 +11926,7 @@ parsesub: {
        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
@@ -11745,10 +11936,10 @@ 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");
+                       raise_error_syntax("support for $((arith)) is disabled");
 #endif
                } else {
                        pungetc();
@@ -11811,7 +12002,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.
@@ -11841,7 +12032,7 @@ parsesub: {
                                subtype++;
                                break;
                        }
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PATTERN_SUBST
                        case '/':
                                /* ${v/[/]pattern/repl} */
 //TODO: encode pattern and repl separately.
@@ -12000,7 +12191,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)
  */
@@ -12096,7 +12287,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
@@ -12297,7 +12488,6 @@ 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)
 {
@@ -12323,7 +12513,12 @@ expandstr(const char *ps)
        expandarg(&n, NULL, EXP_QUOTED);
        return stackblock();
 }
-#endif
+
+static inline int
+parser_eof(void)
+{
+       return tokpushback && lasttoken == TEOF;
+}
 
 /*
  * Execute a command or commands contained in a string.
@@ -12360,7 +12555,7 @@ evalstring(char *s, int flags)
        while ((n = parsecmd(0)) != NODE_EOF) {
                int i;
 
-               i = evaltree(n, flags);
+               i = evaltree(n, flags & ~(parser_eof() ? 0 : EV_EXIT));
                if (n)
                        status = i;
                popstackmark(&smark);
@@ -12486,16 +12681,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
@@ -12519,11 +12705,13 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM)
        int status = 0;
        char *fullname;
        char **argv;
-       struct strlist *sp;
+       char *args_need_save;
        volatile struct shparam saveparam;
 
-       for (sp = cmdenviron; sp; sp = sp->next)
-               setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
+//???
+//     struct strlist *sp;
+//     for (sp = cmdenviron; sp; sp = sp->next)
+//             setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
 
        nextopt(nullstr); /* handle possible "--" */
        argv = argptr;
@@ -12538,7 +12726,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;
@@ -12557,7 +12746,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;
        };
@@ -12822,9 +13011,14 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                return 0;
        }
 
+       /* Why the second check?
+        * "trap NUM [sig2]..." is the same as "trap - NUM [sig2]..."
+        * In this case, NUM is signal no, not an action.
+        */
        action = NULL;
-       if (ap[1])
+       if (ap[1] && !is_number(ap[0]))
                action = *ap++;
+
        exitcode = 0;
        while (*ap) {
                signo = get_signum(*ap);
@@ -12933,7 +13127,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;
@@ -12986,7 +13180,6 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        char **ap;
        int i;
        int flag = 0;
-       int ret = 0;
 
        while ((i = nextopt("vf")) != 0) {
                flag = i;
@@ -12994,15 +13187,13 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 
        for (ap = argptr; *ap; ap++) {
                if (flag != 'f') {
-                       i = unsetvar(*ap);
-                       ret |= i;
-                       if (!(i & 2))
-                               continue;
+                       unsetvar(*ap);
+                       continue;
                }
                if (flag != 'v')
                        unsetfunc(*ap);
        }
-       return ret & 1;
+       return 0;
 }
 
 static const unsigned char timescmd_str[] ALIGN1 = {
@@ -13037,7 +13228,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.
@@ -13113,6 +13304,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,
@@ -13125,6 +13317,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);
 
@@ -13265,9 +13463,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);
@@ -13292,15 +13492,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.
  */
@@ -13339,7 +13530,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)
@@ -13400,6 +13591,9 @@ reset(void)
        /* from redir.c: */
        while (redirlist)
                popredir(/*drop:*/ 0, /*restore:*/ 0);
+
+       /* from var.c: */
+       unwindlocalvars(NULL);
 }
 
 #if PROFILE
@@ -13510,7 +13704,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                // if (!sflag) g_parsefile->pf_fd = -1;
                // ^^ not necessary since now we special-case fd 0
                // in is_hidden_fd() to not be considered "hidden fd"
-               evalstring(minusc, 0);
+               evalstring(minusc, sflag ? 0 : EV_EXIT);
        }
 
        if (sflag || minusc == NULL) {
@@ -13520,9 +13714,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");
                                }
                        }