hush: fix wrong prompt problem on empty interactive commants
[oweals/busybox.git] / shell / ash.c
index 03904bbf8eea432e7aab308703a0ae5380e42ce3..6f03ac1c69b07aa35306a8fd8e1a05513729f4fc 100644 (file)
@@ -13,7 +13,7 @@
  * Copyright (c) 1997-2005 Herbert Xu <herbert@gondor.apana.org.au>
  * was re-ported from NetBSD and debianized.
  *
- * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
 
 /*
 
 #define JOBS ENABLE_ASH_JOB_CONTROL
 
-#if DEBUG
-# ifndef _GNU_SOURCE
-#  define _GNU_SOURCE
-# endif
-#endif
-
 #include "busybox.h" /* for applet_names */
-//TODO: pull in some .h and find out do we have SINGLE_APPLET_MAIN?
-//#include "applet_tables.h" doesn't work
 #include <paths.h>
 #include <setjmp.h>
 #include <fnmatch.h>
+#include <sys/times.h>
 
 #include "shell_common.h"
-#include "builtin_read.h"
-#include "math.h"
+#if ENABLE_SH_MATH_SUPPORT
+# include "math.h"
+#endif
 #if ENABLE_ASH_RANDOM_SUPPORT
 # include "random.h"
 #else
 # define CLEAR_RANDOM_T(rnd) ((void)0)
 #endif
 
-#if defined SINGLE_APPLET_MAIN
+#include "NUM_APPLETS.h"
+#if NUM_APPLETS == 1
 /* STANDALONE does not make sense, and won't compile */
 # undef CONFIG_FEATURE_SH_STANDALONE
 # undef ENABLE_FEATURE_SH_STANDALONE
 # undef IF_FEATURE_SH_STANDALONE
-# undef IF_NOT_FEATURE_SH_STANDALONE(...)
+# undef IF_NOT_FEATURE_SH_STANDALONE
 # define ENABLE_FEATURE_SH_STANDALONE 0
 # define IF_FEATURE_SH_STANDALONE(...)
 # define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
 # error "Do not even bother, ash will not run on NOMMU machine"
 #endif
 
+//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
+
+//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_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_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 getopts builtin in the ash shell.
+//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 to 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 to 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 to 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" in the ash shell.
+//config:
+//config:config ASH_OPTIMIZE_FOR_SIZE
+//config:      bool "Optimize for size instead of speed"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Compile ash for reduced size at the price of speed.
+//config:
+//config:config ASH_RANDOM_SUPPORT
+//config:      bool "Pseudorandom generator and $RANDOM variable"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Enable pseudorandom generator and dynamic variable "$RANDOM".
+//config:        Each read of "$RANDOM" will generate a new pseudorandom value.
+//config:        You can reset the generator by using a specified start value.
+//config:        After "unset RANDOM" the generator will switch off and this
+//config:        variable will no longer have special treatment.
+//config:
+//config:config ASH_EXPAND_PRMT
+//config:      bool "Expand prompt string"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        "PS#" may contain volatile content, such as backquote commands.
+//config:        This option recreates the prompt string from the environment
+//config:        variable each time it is displayed.
+//config:
+
+//usage:#define ash_trivial_usage NOUSAGE_STR
+//usage:#define ash_full_usage ""
+//usage:#define sh_trivial_usage NOUSAGE_STR
+//usage:#define sh_full_usage ""
+//usage:#define bash_trivial_usage NOUSAGE_STR
+//usage:#define bash_full_usage ""
+
 
 /* ============ Hash table sizes. Configurable. */
 
@@ -119,9 +232,7 @@ enum { NOPTS = ARRAY_SIZE(optletters_optnames) };
 
 /* ============ Misc data */
 
-static const char homestr[] ALIGN1 = "HOME";
-static const char snlfmt[] ALIGN1 = "%s\n";
-static const char msg_illnum[] ALIGN1 = "Illegal number: %s";
+#define msg_illnum "Illegal number: %s"
 
 /*
  * We enclose jmp_buf in a structure so that we can declare pointers to
@@ -206,6 +317,7 @@ struct globals_misc {
 
        /* indicates specified signal received */
        uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */
+       uint8_t may_have_traps; /* 0: definitely no traps are set, 1: some traps may be set */
        char *trap[NSIG];
        char **trap_ptr;        /* used only by "trap hack" */
 
@@ -234,6 +346,7 @@ extern struct globals_misc *const ash_ptr_to_globals_misc;
 #define optlist     (G_misc.optlist    )
 #define sigmode     (G_misc.sigmode    )
 #define gotsig      (G_misc.gotsig     )
+#define may_have_traps    (G_misc.may_have_traps   )
 #define trap        (G_misc.trap       )
 #define trap_ptr    (G_misc.trap_ptr   )
 #define random_gen  (G_misc.random_gen )
@@ -277,6 +390,14 @@ static int isdigit_str9(const char *str)
        return (*str == '\0');
 }
 
+static const char *var_end(const char *var)
+{
+       while (*var)
+               if (*var++ == '=')
+                       break;
+       return var;
+}
+
 
 /* ============ Interrupts / exceptions */
 /*
@@ -331,7 +452,7 @@ raise_interrupt(void)
        /* Signal is not automatically unmasked after it is raised,
         * do it ourself - unmask all signals */
        sigprocmask_allsigs(SIG_UNBLOCK);
-       /* pending_sig = 0; - now done in onsig() */
+       /* pending_sig = 0; - now done in signal_handler() */
 
        ex_type = EXSIG;
        if (gotsig[SIGINT - 1] && !trap[SIGINT]) {
@@ -834,7 +955,8 @@ sharg(union node *arg, FILE *fp)
        for (p = arg->narg.text; *p; p++) {
                switch ((unsigned char)*p) {
                case CTLESC:
-                       putc(*++p, fp);
+                       p++;
+                       putc(*p, fp);
                        break;
                case CTLVAR:
                        putc('$', fp);
@@ -843,8 +965,10 @@ sharg(union node *arg, FILE *fp)
                        if (subtype == VSLENGTH)
                                putc('#', fp);
 
-                       while (*p != '=')
-                               putc(*p++, fp);
+                       while (*p != '=') {
+                               putc(*p, fp);
+                               p++;
+                       }
 
                        if (subtype & VSNUL)
                                putc(':', fp);
@@ -1036,7 +1160,7 @@ struct strpush {
 struct parsefile {
        struct parsefile *prev; /* preceding file on stack */
        int linno;              /* current line */
-       int fd;                 /* file descriptor (or -1 if string) */
+       int pf_fd;              /* file descriptor (or -1 if string) */
        int left_in_line;       /* number of chars left in this line */
        int left_in_buffer;     /* number of chars left in this buffer past the line */
        char *next_to_pgetc;    /* next char in buffer */
@@ -1062,7 +1186,7 @@ ash_vmsg(const char *msg, va_list ap)
        if (commandname) {
                if (strcmp(arg0, commandname))
                        fprintf(stderr, "%s: ", commandname);
-               if (!iflag || g_parsefile->fd)
+               if (!iflag || g_parsefile->pf_fd > 0)
                        fprintf(stderr, "line %d: ", startlinno);
        }
        vfprintf(stderr, msg, ap);
@@ -1710,8 +1834,8 @@ static void FAST_FUNC getoptsreset(const char *value);
 struct var {
        struct var *next;               /* next entry in hash list */
        int flags;                      /* flags are defined above */
-       const char *text;               /* name=value */
-       void (*func)(const char *) FAST_FUNC; /* function to be called when  */
+       const char *var_text;           /* name=value */
+       void (*var_func)(const char *) FAST_FUNC; /* function to be called when  */
                                        /* the variable gets set/unset */
 };
 
@@ -1765,13 +1889,13 @@ static void change_random(const char *) FAST_FUNC;
 
 static const struct {
        int flags;
-       const char *text;
-       void (*func)(const char *) FAST_FUNC;
+       const char *var_text;
+       void (*var_func)(const char *) FAST_FUNC;
 } varinit_data[] = {
        { VSTRFIXED|VTEXTFIXED       , defifsvar   , NULL            },
 #if ENABLE_ASH_MAIL
-       { VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0"    , changemail      },
-       { VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail      },
+       { VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL"      , changemail      },
+       { VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH"  , changemail      },
 #endif
        { VSTRFIXED|VTEXTFIXED       , bb_PATH_root_path, changepath },
        { VSTRFIXED|VTEXTFIXED       , "PS1=$ "    , NULL            },
@@ -1781,14 +1905,14 @@ static const struct {
        { VSTRFIXED|VTEXTFIXED       , "OPTIND=1"  , getoptsreset    },
 #endif
 #if ENABLE_ASH_RANDOM_SUPPORT
-       { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random },
+       { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random },
 #endif
 #if ENABLE_LOCALE_SUPPORT
-       { VSTRFIXED|VTEXTFIXED|VUNSET, "LC_ALL\0"  , change_lc_all   },
-       { VSTRFIXED|VTEXTFIXED|VUNSET, "LC_CTYPE\0", change_lc_ctype },
+       { VSTRFIXED|VTEXTFIXED|VUNSET, "LC_ALL"    , change_lc_all   },
+       { VSTRFIXED|VTEXTFIXED|VUNSET, "LC_CTYPE"  , change_lc_ctype },
 #endif
 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
-       { VSTRFIXED|VTEXTFIXED|VUNSET, "HISTFILE\0", NULL            },
+       { VSTRFIXED|VTEXTFIXED|VUNSET, "HISTFILE"  , NULL            },
 #endif
 };
 
@@ -1815,9 +1939,9 @@ extern struct globals_var *const ash_ptr_to_globals_var;
        (*(struct globals_var**)&ash_ptr_to_globals_var) = xzalloc(sizeof(G_var)); \
        barrier(); \
        for (i = 0; i < ARRAY_SIZE(varinit_data); i++) { \
-               varinit[i].flags = varinit_data[i].flags; \
-               varinit[i].text  = varinit_data[i].text; \
-               varinit[i].func  = varinit_data[i].func; \
+               varinit[i].flags    = varinit_data[i].flags; \
+               varinit[i].var_text = varinit_data[i].var_text; \
+               varinit[i].var_func = varinit_data[i].var_func; \
        } \
 } while (0)
 
@@ -1848,25 +1972,21 @@ extern struct globals_var *const ash_ptr_to_globals_var;
  * They have to skip over the name.  They return the null string
  * for unset variables.
  */
-#define ifsval()        (vifs.text + 4)
+#define ifsval()        (vifs.var_text + 4)
 #define ifsset()        ((vifs.flags & VUNSET) == 0)
 #if ENABLE_ASH_MAIL
-# define mailval()      (vmail.text + 5)
-# define mpathval()     (vmpath.text + 9)
+# define mailval()      (vmail.var_text + 5)
+# define mpathval()     (vmpath.var_text + 9)
 # define mpathset()     ((vmpath.flags & VUNSET) == 0)
 #endif
-#define pathval()       (vpath.text + 5)
-#define ps1val()        (vps1.text + 4)
-#define ps2val()        (vps2.text + 4)
-#define ps4val()        (vps4.text + 4)
+#define pathval()       (vpath.var_text + 5)
+#define ps1val()        (vps1.var_text + 4)
+#define ps2val()        (vps2.var_text + 4)
+#define ps4val()        (vps4.var_text + 4)
 #if ENABLE_ASH_GETOPTS
-# define optindval()    (voptind.text + 7)
+# define optindval()    (voptind.var_text + 7)
 #endif
 
-
-#define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
-#define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
-
 #if ENABLE_ASH_GETOPTS
 static void FAST_FUNC
 getoptsreset(const char *value)
@@ -1876,24 +1996,26 @@ getoptsreset(const char *value)
 }
 #endif
 
+/* math.h has these, otherwise define our private copies */
+#if !ENABLE_SH_MATH_SUPPORT
+#define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
 /*
- * Return of a legal variable name (a letter or underscore followed by zero or
- * more letters, underscores, and digits).
+ * Return the pointer to the first char which is not part of a legal variable name
+ * (a letter or underscore followed by letters, underscores, and digits).
  */
-static char* FAST_FUNC
+static const char*
 endofname(const char *name)
 {
-       char *p;
-
-       p = (char *) name;
-       if (!is_name(*p))
-               return p;
-       while (*++p) {
-               if (!is_in_name(*p))
+       if (!is_name(*name))
+               return name;
+       while (*++name) {
+               if (!is_in_name(*name))
                        break;
        }
-       return p;
+       return name;
 }
+#endif
 
 /*
  * Compares two strings up to the first = or '\0'.  The first
@@ -1919,12 +2041,6 @@ varcmp(const char *p, const char *q)
        return c - d;
 }
 
-static int
-varequal(const char *a, const char *b)
-{
-       return !varcmp(a, b);
-}
-
 /*
  * Find the appropriate entry in the hash table from the name.
  */
@@ -1959,15 +2075,15 @@ initvar(void)
         * PS1 depends on uid
         */
 #if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT
-       vps1.text = "PS1=\\w \\$ ";
+       vps1.var_text = "PS1=\\w \\$ ";
 #else
        if (!geteuid())
-               vps1.text = "PS1=# ";
+               vps1.var_text = "PS1=# ";
 #endif
        vp = varinit;
        end = vp + ARRAY_SIZE(varinit);
        do {
-               vpp = hashvar(vp->text);
+               vpp = hashvar(vp->var_text);
                vp->next = *vpp;
                *vpp = vp;
        } while (++vp < end);
@@ -1977,7 +2093,7 @@ static struct var **
 findvar(struct var **vpp, const char *name)
 {
        for (; *vpp; vpp = &(*vpp)->next) {
-               if (varequal((*vpp)->text, name)) {
+               if (varcmp((*vpp)->var_text, name) == 0) {
                        break;
                }
        }
@@ -2001,11 +2117,11 @@ lookupvar(const char *name)
         * As soon as they're unset, they're no longer dynamic, and dynamic
         * lookup will no longer happen at that point. -- PFM.
         */
-               if ((v->flags & VDYNAMIC))
-                       (*v->func)(NULL);
+               if (v->flags & VDYNAMIC)
+                       v->var_func(NULL);
 #endif
                if (!(v->flags & VUNSET))
-                       return strchrnul(v->text, '=') + 1;
+                       return var_end(v->var_text);
        }
        return NULL;
 }
@@ -2019,8 +2135,8 @@ bltinlookup(const char *name)
        struct strlist *sp;
 
        for (sp = cmdenviron; sp; sp = sp->next) {
-               if (varequal(sp->text, name))
-                       return strchrnul(sp->text, '=') + 1;
+               if (varcmp(sp->text, name) == 0)
+                       return var_end(sp->text);
        }
        return lookupvar(name);
 }
@@ -2046,24 +2162,24 @@ setvareq(char *s, int flags)
 
                        if (flags & VNOSAVE)
                                free(s);
-                       n = vp->text;
+                       n = vp->var_text;
                        ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
                }
 
                if (flags & VNOSET)
                        return;
 
-               if (vp->func && (flags & VNOFUNC) == 0)
-                       (*vp->func)(strchrnul(s, '=') + 1);
+               if (vp->var_func && !(flags & VNOFUNC))
+                       vp->var_func(var_end(s));
 
-               if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
-                       free((char*)vp->text);
+               if (!(vp->flags & (VTEXTFIXED|VSTACK)))
+                       free((char*)vp->var_text);
 
                flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
        } else {
+               /* variable s is not found */
                if (flags & VNOSET)
                        return;
-               /* not found */
                vp = ckzalloc(sizeof(*vp));
                vp->next = *vpp;
                /*vp->func = NULL; - ckzalloc did it */
@@ -2071,7 +2187,7 @@ setvareq(char *s, int flags)
        }
        if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
                s = ckstrdup(s);
-       vp->text = s;
+       vp->var_text = s;
        vp->flags = flags;
 }
 
@@ -2082,9 +2198,10 @@ setvareq(char *s, int flags)
 static void
 setvar(const char *name, const char *val, int flags)
 {
-       char *p, *q;
-       size_t namelen;
+       const char *q;
+       char *p;
        char *nameeq;
+       size_t namelen;
        size_t vallen;
 
        q = endofname(name);
@@ -2098,12 +2215,13 @@ setvar(const char *name, const char *val, int flags)
        } else {
                vallen = strlen(val);
        }
+
        INT_OFF;
        nameeq = ckmalloc(namelen + vallen + 2);
-       p = (char *)memcpy(nameeq, name, namelen) + namelen;
+       p = memcpy(nameeq, name, namelen) + namelen;
        if (val) {
                *p++ = '=';
-               p = (char *)memcpy(p, val, vallen) + vallen;
+               p = memcpy(p, val, vallen) + vallen;
        }
        *p = '\0';
        setvareq(nameeq, flags | VNOSAVE);
@@ -2169,7 +2287,7 @@ unsetvar(const char *s)
                if ((flags & VSTRFIXED) == 0) {
                        INT_OFF;
                        if ((flags & (VTEXTFIXED|VSTACK)) == 0)
-                               free((char*)vp->text);
+                               free((char*)vp->var_text);
                        *vpp = vp->next;
                        free(vp);
                        INT_ON;
@@ -2221,7 +2339,7 @@ listvars(int on, int off, char ***end)
                        if ((vp->flags & mask) == on) {
                                if (ep == stackstrend())
                                        ep = growstackstr();
-                               *ep++ = (char *) vp->text;
+                               *ep++ = (char*)vp->var_text;
                        }
                }
        } while (++vpp < vartab + VTABSIZE);
@@ -2317,12 +2435,13 @@ static const char *expandstr(const char *ps);
 #endif
 
 static void
-setprompt(int whichprompt)
+setprompt_if(smallint do_set, int whichprompt)
 {
        const char *prompt;
-#if ENABLE_ASH_EXPAND_PRMT
-       struct stackmark smark;
-#endif
+       IF_ASH_EXPAND_PRMT(struct stackmark smark;)
+
+       if (!do_set)
+               return;
 
        needprompt = 0;
 
@@ -2482,7 +2601,7 @@ static void hashcd(void);
 static int
 docd(const char *dest, int flags)
 {
-       const char *dir = 0;
+       const char *dir = NULL;
        int err;
 
        TRACE(("docd(\"%s\", %d) called\n", dest, flags));
@@ -2516,7 +2635,7 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        flags = cdopt();
        dest = *argptr;
        if (!dest)
-               dest = bltinlookup(homestr);
+               dest = bltinlookup("HOME");
        else if (LONE_DASH(dest)) {
                dest = bltinlookup("OLDPWD");
                flags |= CD_PRINT;
@@ -2563,7 +2682,7 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        /* NOTREACHED */
  out:
        if (flags & CD_PRINT)
-               out1fmt(snlfmt, curdir);
+               out1fmt("%s\n", curdir);
        return 0;
 }
 
@@ -2579,7 +2698,7 @@ pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                        setpwd(dir, 0);
                dir = physdir;
        }
-       out1fmt(snlfmt, dir);
+       out1fmt("%s\n", dir);
        return 0;
 }
 
@@ -2587,9 +2706,7 @@ pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 /* ============ ... */
 
 
-#define IBUFSIZ COMMON_BUFSIZE
-/* buffer for top level input file */
-#define basebuf bb_common_bufsiz1
+#define IBUFSIZ (ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 1024)
 
 /* Syntax classes */
 #define CWORD     0             /* character is nothing special */
@@ -3268,10 +3385,10 @@ ignoresig(int signo)
 }
 
 /*
- * Signal handler. Only one usage site - in setsignal()
+ * Only one usage site - in setsignal()
  */
 static void
-onsig(int signo)
+signal_handler(int signo)
 {
        gotsig[signo - 1] = 1;
 
@@ -3366,7 +3483,7 @@ setsignal(int signo)
        act.sa_handler = SIG_DFL;
        switch (new_act) {
        case S_CATCH:
-               act.sa_handler = onsig;
+               act.sa_handler = signal_handler;
                act.sa_flags = 0; /* matters only if !DFL and !IGN */
                sigfillset(&act.sa_mask); /* ditto */
                break;
@@ -3872,9 +3989,9 @@ dowait(int wait_flags, struct job *job)
 }
 
 static int
-blocking_wait_with_raise_on_sig(struct job *job)
+blocking_wait_with_raise_on_sig(void)
 {
-       pid_t pid = dowait(DOWAIT_BLOCK, job);
+       pid_t pid = dowait(DOWAIT_BLOCK, NULL);
        if (pid <= 0 && pending_sig)
                raise_exception(EXSIG);
        return pid;
@@ -4067,14 +4184,21 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
                                jp->waited = 1;
                                jp = jp->prev_job;
                        }
+                       blocking_wait_with_raise_on_sig();
        /* man bash:
         * "When bash is waiting for an asynchronous command via
         * the wait builtin, the reception of a signal for which a trap
         * has been set will cause the wait builtin to return immediately
         * with an exit status greater than 128, immediately after which
         * the trap is executed."
-        * Do we do it that way? */
-                       blocking_wait_with_raise_on_sig(NULL);
+        *
+        * blocking_wait_with_raise_on_sig raises signal handlers
+        * if it gets no pid (pid < 0). However,
+        * if child sends us a signal *and immediately exits*,
+        * blocking_wait_with_raise_on_sig gets pid > 0
+        * and does not handle pending_sig. Check this case: */
+                       if (pending_sig)
+                               raise_exception(EXSIG);
                }
        }
 
@@ -4094,7 +4218,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
                        job = getjob(*argv, 0);
                /* loop until process terminated or stopped */
                while (job->state == JOBRUNNING)
-                       blocking_wait_with_raise_on_sig(NULL);
+                       blocking_wait_with_raise_on_sig();
                job->waited = 1;
                retval = getstatus(job);
  repeat: ;
@@ -4515,6 +4639,7 @@ clear_traps(void)
                        INT_ON;
                }
        }
+       may_have_traps = 0;
 }
 
 /* Lives far away from here, needed for forkchild */
@@ -4870,9 +4995,13 @@ noclobberopen(const char *fname)
         * 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)
+       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);
@@ -4936,7 +5065,7 @@ openredirect(union node *redir)
                break;
        case NFROMTO:
                fname = redir->nfile.expfname;
-               f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666);
+               f = open(fname, O_RDWR|O_CREAT, 0666);
                if (f < 0)
                        goto ecreate;
                break;
@@ -5056,15 +5185,26 @@ static int is_hidden_fd(struct redirtab *rp, int fd)
 
        if (fd == -1)
                return 0;
+       /* Check open scripts' fds */
        pf = g_parsefile;
        while (pf) {
-               if (fd == pf->fd) {
+               /* We skip pf_fd == 0 case because of the following case:
+                * $ ash  # running ash interactively
+                * $ . ./script.sh
+                * and in script.sh: "exec 9>&0".
+                * Even though top-level pf_fd _is_ 0,
+                * it's still ok to use it: "read" builtin uses it,
+                * why should we cripple "exec" builtin?
+                */
+               if (pf->pf_fd > 0 && fd == pf->pf_fd) {
                        return 1;
                }
                pf = pf->prev;
        }
+
        if (!rp)
                return 0;
+       /* Check saved fds of redirects */
        fd |= COPYFD_RESTORE;
        for (i = 0; i < rp->pair_count; i++) {
                if (rp->two_fd[i].copy == fd) {
@@ -5077,9 +5217,7 @@ static int is_hidden_fd(struct redirtab *rp, int fd)
 /*
  * Process a list of redirection commands.  If the REDIR_PUSH flag is set,
  * old file descriptors are stashed away so that the redirection can be
- * undone by calling popredir.  If the REDIR_BACKQ flag is set, then the
- * standard output, and the standard error if it becomes a duplicate of
- * stdout, is saved in memory.
+ * undone by calling popredir.
  */
 /* flags passed to redirect */
 #define REDIR_PUSH    01        /* save previous values of file descriptors */
@@ -5125,13 +5263,15 @@ redirect(union node *redir, int flags)
        }
 
        do {
+               int right_fd = -1;
                fd = redir->nfile.fd;
                if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
-                       int right_fd = redir->ndup.dupfd;
+                       right_fd = redir->ndup.dupfd;
+                       //bb_error_msg("doing %d > %d", fd, right_fd);
                        /* redirect from/to same file descriptor? */
                        if (right_fd == fd)
                                continue;
-                       /* echo >&10 and 10 is a fd opened to the sh script? */
+                       /* "echo >&10" and 10 is a fd opened to a sh script? */
                        if (is_hidden_fd(sv, right_fd)) {
                                errno = EBADF; /* as if it is closed */
                                ash_msg_and_raise_error("%d: %m", right_fd);
@@ -5153,7 +5293,10 @@ redirect(union node *redir, int flags)
 #endif
                if (need_to_remember(sv, fd)) {
                        /* Copy old descriptor */
-                       i = fcntl(fd, F_DUPFD, 10);
+                       /* 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;
+                       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)
@@ -5301,25 +5444,17 @@ redirectsafe(union node *redir, int flags)
 static arith_t
 ash_arith(const char *s)
 {
-       arith_eval_hooks_t math_hooks;
+       arith_state_t math_state;
        arith_t result;
-       int errcode = 0;
 
-       math_hooks.lookupvar = lookupvar;
-       math_hooks.setvar    = setvar2;
-       math_hooks.endofname = endofname;
+       math_state.lookupvar = lookupvar;
+       math_state.setvar    = setvar2;
+       //math_state.endofname = endofname;
 
        INT_OFF;
-       result = arith(s, &errcode, &math_hooks);
-       if (errcode < 0) {
-               if (errcode == -3)
-                       ash_msg_and_raise_error("exponent less than 0");
-               if (errcode == -2)
-                       ash_msg_and_raise_error("divide by zero");
-               if (errcode == -5)
-                       ash_msg_and_raise_error("expression recursion loop detected");
-               raise_error_syntax(s);
-       }
+       result = arith(&math_state, s);
+       if (math_state.errmsg)
+               ash_msg_and_raise_error(math_state.errmsg);
        INT_ON;
 
        return result;
@@ -5377,13 +5512,18 @@ static struct arglist exparg;
 /*
  * Our own itoa().
  */
+#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(32, expdest);
-       len = fmtstr(expdest, 32, arith_t_fmt, num);
+       len = fmtstr(expdest, 32, ARITH_FMT, num);
        STADJUST(len, expdest);
        return len;
 }
@@ -5423,7 +5563,11 @@ rmescapes(char *str, int flag)
                size_t fulllen = len + strlen(p) + 1;
 
                if (flag & RMESCAPE_GROW) {
+                       int strloc = str - (char *)stackblock();
                        r = makestrspace(fulllen, expdest);
+                       /* p and str may be invalidated by makestrspace */
+                       str = (char *)stackblock() + strloc;
+                       p = str + len;
                } else if (flag & RMESCAPE_HEAP) {
                        r = ckmalloc(fulllen);
                } else {
@@ -5550,7 +5694,7 @@ removerecordregions(int endoff)
                return;
 
        if (ifsfirst.endoff > endoff) {
-               while (ifsfirst.next != NULL) {
+               while (ifsfirst.next) {
                        struct ifsregion *ifsp;
                        INT_OFF;
                        ifsp = ifsfirst.next->next;
@@ -5558,9 +5702,9 @@ removerecordregions(int endoff)
                        ifsfirst.next = ifsp;
                        INT_ON;
                }
-               if (ifsfirst.begoff > endoff)
+               if (ifsfirst.begoff > endoff) {
                        ifslastp = NULL;
-               else {
+               else {
                        ifslastp = &ifsfirst;
                        ifsfirst.endoff = endoff;
                }
@@ -5569,8 +5713,8 @@ removerecordregions(int endoff)
 
        ifslastp = &ifsfirst;
        while (ifslastp->next && ifslastp->next->begoff < endoff)
-               ifslastp=ifslastp->next;
-       while (ifslastp->next != NULL) {
+               ifslastp = ifslastp->next;
+       while (ifslastp->next) {
                struct ifsregion *ifsp;
                INT_OFF;
                ifsp = ifslastp->next->next;
@@ -5612,7 +5756,7 @@ exptilde(char *startp, char *p, int flags)
  done:
        *p = '\0';
        if (*name == '\0') {
-               home = lookupvar(homestr);
+               home = lookupvar("HOME");
        } else {
                pw = getpwnam(name);
                if (pw == NULL)
@@ -5747,9 +5891,9 @@ expbackq(union node *cmd, int quoted, int quotes)
 
        if (quoted == 0)
                recordregion(startloc, dest - (char *)stackblock(), 0);
-       TRACE(("evalbackq: size=%d: \"%.*s\"\n",
-               (dest - (char *)stackblock()) - startloc,
-               (dest - (char *)stackblock()) - startloc,
+       TRACE(("evalbackq: size:%d:'%.*s'\n",
+               (int)((dest - (char *)stackblock()) - startloc),
+               (int)((dest - (char *)stackblock()) - startloc),
                stackblock() + startloc));
 }
 
@@ -5864,7 +6008,7 @@ argstr(char *p, int flags, struct strlist *var_str_list)
                flags &= ~EXP_TILDE;
  tilde:
                q = p;
-               if (*q == CTLESC && (flags & EXP_QWORD))
+               if ((unsigned char)*q == CTLESC && (flags & EXP_QWORD))
                        q++;
                if (*q == '~')
                        p = exptilde(p, q, flags);
@@ -5878,9 +6022,7 @@ argstr(char *p, int flags, struct strlist *var_str_list)
                c = p[length];
                if (c) {
                        if (!(c & 0x80)
-#if ENABLE_SH_MATH_SUPPORT
-                        || c == CTLENDARI
-#endif
+                       IF_SH_MATH_SUPPORT(|| c == CTLENDARI)
                        ) {
                                /* c == '=' || c == ':' || c == CTLENDARI */
                                length++;
@@ -5927,8 +6069,8 @@ argstr(char *p, int flags, struct strlist *var_str_list)
                        /* "$@" syntax adherence hack */
                        if (!inquotes
                         && memcmp(p, dolatstr, 4) == 0
-                        && (  p[4] == CTLQUOTEMARK
-                           || (p[4] == CTLENDVAR && p[5] == CTLQUOTEMARK)
+                        && (  p[4] == (char)CTLQUOTEMARK
+                           || (p[4] == (char)CTLENDVAR && p[5] == (char)CTLQUOTEMARK)
                            )
                        ) {
                                p = evalvar(p + 1, flags, /* var_str_list: */ NULL) + 1;
@@ -5963,47 +6105,20 @@ argstr(char *p, int flags, struct strlist *var_str_list)
 #endif
                }
        }
- breakloop:
-       ;
+ breakloop: ;
 }
 
 static char *
-scanleft(char *startp, char *rmesc, char *rmescend UNUSED_PARAM, char *str, int quotes,
-       int zero)
-{
-// This commented out code was added by James Simmons <jsimmons@infradead.org>
-// as part of a larger change when he added support for ${var/a/b}.
-// However, it broke # and % operators:
-//
-//var=ababcdcd
-//                 ok       bad
-//echo ${var#ab}   abcdcd   abcdcd
-//echo ${var##ab}  abcdcd   abcdcd
-//echo ${var#a*b}  abcdcd   ababcdcd  (!)
-//echo ${var##a*b} cdcd     cdcd
-//echo ${var#?}    babcdcd  ababcdcd  (!)
-//echo ${var##?}   babcdcd  babcdcd
-//echo ${var#*}    ababcdcd babcdcd   (!)
-//echo ${var##*}
-//echo ${var%cd}   ababcd   ababcd
-//echo ${var%%cd}  ababcd   abab      (!)
-//echo ${var%c*d}  ababcd   ababcd
-//echo ${var%%c*d} abab     ababcdcd  (!)
-//echo ${var%?}    ababcdc  ababcdc
-//echo ${var%%?}   ababcdc  ababcdcd  (!)
-//echo ${var%*}    ababcdcd ababcdcd
-//echo ${var%%*}
-//
-// Commenting it back out helped. Remove it completely if it really
-// is not needed.
-
-       char *loc, *loc2; //, *full;
+scanleft(char *startp, char *rmesc, char *rmescend UNUSED_PARAM,
+               char *pattern, int quotes, int zero)
+{
+       char *loc, *loc2;
        char c;
 
        loc = startp;
        loc2 = rmesc;
        do {
-               int match; // = strlen(str);
+               int match;
                const char *s = loc2;
 
                c = *loc2;
@@ -6011,53 +6126,76 @@ scanleft(char *startp, char *rmesc, char *rmescend UNUSED_PARAM, char *str, int
                        *loc2 = '\0';
                        s = rmesc;
                }
-               match = pmatch(str, s); // this line was deleted
-
-//             // chop off end if its '*'
-//             full = strrchr(str, '*');
-//             if (full && full != str)
-//                     match--;
-//
-//             // If str starts with '*' replace with s.
-//             if ((*str == '*') && strlen(s) >= match) {
-//                     full = xstrdup(s);
-//                     strncpy(full+strlen(s)-match+1, str+1, match-1);
-//             } else
-//                     full = xstrndup(str, match);
-//             match = strncmp(s, full, strlen(full));
-//             free(full);
-//
+               match = pmatch(pattern, s);
+
                *loc2 = c;
-               if (match) // if (!match)
+               if (match)
                        return loc;
                if (quotes && (unsigned char)*loc == CTLESC)
                        loc++;
                loc++;
                loc2++;
        } while (c);
-       return 0;
+       return NULL;
 }
 
 static char *
-scanright(char *startp, char *rmesc, char *rmescend, char *str, int quotes,
-       int zero)
+scanright(char *startp, char *rmesc, char *rmescend,
+               char *pattern, int quotes, int match_at_start)
 {
+#if !ENABLE_ASH_OPTIMIZE_FOR_SIZE
+       int try2optimize = match_at_start;
+#endif
        int esc = 0;
        char *loc;
        char *loc2;
 
-       for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) {
+       /* If we called by "${v/pattern/repl}" or "${v//pattern/repl}":
+        * startp="escaped_value_of_v" rmesc="raw_value_of_v"
+        * rmescend=""(ptr to NUL in rmesc) pattern="pattern" quotes=match_at_start=1
+        * Logic:
+        * loc starts at NUL at the end of startp, loc2 starts at the end of rmesc,
+        * and on each iteration they go back two/one char until they reach the beginning.
+        * We try to find a match in "raw_value_of_v", "raw_value_of_", "raw_value_of" etc.
+        */
+       /* TODO: document in what other circumstances we are called. */
+
+       for (loc = pattern - 1, loc2 = rmescend; loc >= startp; loc2--) {
                int match;
                char c = *loc2;
                const char *s = loc2;
-               if (zero) {
+               if (match_at_start) {
                        *loc2 = '\0';
                        s = rmesc;
                }
-               match = pmatch(str, s);
+               match = pmatch(pattern, s);
+               //bb_error_msg("pmatch(pattern:'%s',s:'%s'):%d", pattern, s, match);
                *loc2 = c;
                if (match)
                        return loc;
+#if !ENABLE_ASH_OPTIMIZE_FOR_SIZE
+               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.
+                        */
+                       unsigned plen = strlen(pattern);
+                       /* Does it end with "*"? */
+                       if (plen != 0 && pattern[--plen] == '*') {
+                               /* "xxxx*" is not escaped */
+                               /* "xxx\*" is escaped */
+                               /* "xx\\*" is not escaped */
+                               /* "x\\\*" is escaped */
+                               int slashes = 0;
+                               while (plen != 0 && pattern[--plen] == '\\')
+                                       slashes++;
+                               if (!(slashes & 1))
+                                       break; /* ends with unescaped "*" */
+                       }
+                       try2optimize = 0;
+               }
+#endif
                loc--;
                if (quotes) {
                        if (--esc < 0) {
@@ -6069,7 +6207,7 @@ scanright(char *startp, char *rmesc, char *rmescend, char *str, int quotes,
                        }
                }
        }
-       return 0;
+       return NULL;
 }
 
 static void varunset(const char *, const char *, const char *, int) NORETURN;
@@ -6089,16 +6227,18 @@ varunset(const char *end, const char *var, const char *umsg, int varflags)
                        msg = umsg;
                }
        }
-       ash_msg_and_raise_error("%.*s: %s%s", end - var - 1, var, msg, tail);
+       ash_msg_and_raise_error("%.*s: %s%s", (int)(end - var - 1), var, msg, tail);
 }
 
 #if ENABLE_ASH_BASH_COMPAT
 static char *
-parse_sub_pattern(char *arg, int inquotes)
+parse_sub_pattern(char *arg, int varflags)
 {
        char *idx, *repl = NULL;
        unsigned char c;
 
+       //char *org_arg = arg;
+       //bb_error_msg("arg:'%s' varflags:%x", arg, varflags);
        idx = arg;
        while (1) {
                c = *arg;
@@ -6112,32 +6252,47 @@ parse_sub_pattern(char *arg, int inquotes)
                        }
                }
                *idx++ = c;
-               if (!inquotes && c == '\\' && arg[1] == '\\')
-                       arg++; /* skip both \\, not just first one */
                arg++;
+               /*
+                * Example: v='ab\c'; echo ${v/\\b/_\\_\z_}
+                * The result is a_\_z_c (not a\_\_z_c)!
+                *
+                * Enable debug prints in this function and you'll see:
+                * ash: arg:'\\b/_\\_z_' varflags:d
+                * ash: pattern:'\\b' repl:'_\_z_'
+                * That is, \\b is interpreted as \\b, but \\_ as \_!
+                * IOW: search pattern and replace string treat backslashes
+                * differently! That is the reason why we check repl below:
+                */
+               if (c == '\\' && *arg == '\\' && repl && !(varflags & VSQUOTE))
+                       arg++; /* skip both '\', not just first one */
        }
        *idx = c; /* NUL */
+       //bb_error_msg("pattern:'%s' repl:'%s'", org_arg, repl);
 
        return repl;
 }
 #endif /* ENABLE_ASH_BASH_COMPAT */
 
 static const char *
-subevalvar(char *p, char *str, int strloc, int subtype,
+subevalvar(char *p, char *varname, int strloc, int subtype,
                int startloc, int varflags, int quotes, struct strlist *var_str_list)
 {
        struct nodelist *saveargbackq = argbackq;
        char *startp;
        char *loc;
        char *rmesc, *rmescend;
-       IF_ASH_BASH_COMPAT(char *repl = NULL;)
-       IF_ASH_BASH_COMPAT(char null = '\0';)
+       char *str;
+       IF_ASH_BASH_COMPAT(const char *repl = NULL;)
        IF_ASH_BASH_COMPAT(int pos, len, orig_len;)
        int saveherefd = herefd;
        int amount, workloc, resetloc;
        int zero;
        char *(*scan)(char*, char*, char*, char*, int, int);
 
+       //bb_error_msg("subevalvar(p:'%s',varname:'%s',strloc:%d,subtype:%d,startloc:%d,varflags:%x,quotes:%d)",
+       //              p, varname, strloc, subtype, startloc, varflags, quotes);
+
        herefd = -1;
        argstr(p, (subtype != VSASSIGN && subtype != VSQUESTION) ? EXP_CASE : 0,
                        var_str_list);
@@ -6148,11 +6303,15 @@ subevalvar(char *p, char *str, int strloc, int subtype,
 
        switch (subtype) {
        case VSASSIGN:
-               setvar(str, startp, 0);
+               setvar(varname, startp, 0);
                amount = startp - expdest;
                STADJUST(amount, expdest);
                return startp;
 
+       case VSQUESTION:
+               varunset(p, varname, startp, varflags);
+               /* NOTREACHED */
+
 #if ENABLE_ASH_BASH_COMPAT
        case VSSUBSTR:
                loc = str = stackblock() + strloc;
@@ -6213,11 +6372,8 @@ subevalvar(char *p, char *str, int strloc, int subtype,
                STADJUST(amount, expdest);
                return loc;
 #endif
-
-       case VSQUESTION:
-               varunset(p, str, startp, varflags);
-               /* NOTREACHED */
        }
+
        resetloc = expdest - (char *)stackblock();
 
        /* We'll comeback here if we grow the stack while handling
@@ -6247,26 +6403,30 @@ subevalvar(char *p, char *str, int strloc, int subtype,
 
 #if ENABLE_ASH_BASH_COMPAT
        if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
-               char *idx, *end, *restart_detect;
+               char *idx, *end;
 
                if (!repl) {
-                       repl = parse_sub_pattern(str, varflags & VSQUOTE);
+                       repl = parse_sub_pattern(str, varflags);
+                       //bb_error_msg("repl:'%s'", repl);
                        if (!repl)
-                               repl = &null;
+                               repl = nullstr;
                }
 
                /* If there's no pattern to match, return the expansion unmolested */
-               if (*str == '\0')
-                       return 0;
+               if (str[0] == '\0')
+                       return NULL;
 
                len = 0;
                idx = startp;
                end = str - 1;
                while (idx < end) {
+ try_to_match:
                        loc = scanright(idx, rmesc, rmescend, str, quotes, 1);
+                       //bb_error_msg("scanright('%s'):'%s'", str, loc);
                        if (!loc) {
                                /* No match, advance */
-                               restart_detect = stackblock();
+                               char *restart_detect = stackblock();
+ skip_matching:
                                STPUTC(*idx, expdest);
                                if (quotes && (unsigned char)*idx == CTLESC) {
                                        idx++;
@@ -6278,7 +6438,16 @@ subevalvar(char *p, char *str, int strloc, int subtype,
                                idx++;
                                len++;
                                rmesc++;
-                               continue;
+                               /* continue; - prone to quadratic behavior, smarter code: */
+                               if (idx >= end)
+                                       break;
+                               if (str[0] == '*') {
+                                       /* Pattern is "*foo". If "*foo" does not match "long_string",
+                                        * it would never match "ong_string" etc, no point in trying.
+                                        */
+                                       goto skip_matching;
+                               }
+                               goto try_to_match;
                        }
 
                        if (subtype == VSREPLACEALL) {
@@ -6292,8 +6461,13 @@ subevalvar(char *p, char *str, int strloc, int subtype,
                                idx = loc;
                        }
 
-                       for (loc = repl; *loc; loc++) {
-                               restart_detect = stackblock();
+                       //bb_error_msg("repl:'%s'", repl);
+                       for (loc = (char*)repl; *loc; loc++) {
+                               char *restart_detect = stackblock();
+                               if (quotes && *loc == '\\') {
+                                       STPUTC(CTLESC, expdest);
+                                       len++;
+                               }
                                STPUTC(*loc, expdest);
                                if (stackblock() != restart_detect)
                                        goto restart;
@@ -6301,8 +6475,9 @@ subevalvar(char *p, char *str, int strloc, int subtype,
                        }
 
                        if (subtype == VSREPLACE) {
+                               //bb_error_msg("tail:'%s', quotes:%x", idx, quotes);
                                while (*idx) {
-                                       restart_detect = stackblock();
+                                       char *restart_detect = stackblock();
                                        STPUTC(*idx, expdest);
                                        if (stackblock() != restart_detect)
                                                goto restart;
@@ -6316,11 +6491,11 @@ subevalvar(char *p, char *str, int strloc, int subtype,
                /* We've put the replaced text into a buffer at workloc, now
                 * move it to the right place and adjust the stack.
                 */
-               startp = stackblock() + startloc;
                STPUTC('\0', expdest);
-               memmove(startp, stackblock() + workloc, len);
-               startp[len++] = '\0';
-               amount = expdest - ((char *)stackblock() + startloc + len - 1);
+               startp = (char *)stackblock() + startloc;
+               memmove(startp, (char *)stackblock() + workloc, len + 1);
+               //bb_error_msg("startp:'%s'", startp);
+               amount = expdest - (startp + len);
                STADJUST(-amount, expdest);
                return startp;
        }
@@ -6331,7 +6506,7 @@ subevalvar(char *p, char *str, int strloc, int subtype,
        if (subtype < 0 || subtype > 7)
                abort();
 #endif
-       /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */
+       /* zero = (subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX) */
        zero = subtype >> 1;
        /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */
        scan = (subtype & 1) ^ zero ? scanleft : scanright;
@@ -6533,7 +6708,7 @@ evalvar(char *p, int flags, struct strlist *var_str_list)
        var = p;
        easy = (!quoted || (*var == '@' && shellparam.nparam));
        startloc = expdest - (char *)stackblock();
-       p = strchr(p, '=') + 1;
+       p = strchr(p, '=') + 1; //TODO: use var_end(p)?
 
  again:
        varlen = varvalue(var, varflags, flags, var_str_list);
@@ -6549,8 +6724,8 @@ evalvar(char *p, int flags, struct strlist *var_str_list)
  vsplus:
                if (varlen < 0) {
                        argstr(
-                               p, flags | EXP_TILDE |
-                                       (quoted ? EXP_QWORD : EXP_WORD),
+                               p,
+                               flags | (quoted ? EXP_TILDE|EXP_QWORD : EXP_TILDE|EXP_WORD),
                                var_str_list
                        );
                        goto end;
@@ -6620,7 +6795,7 @@ evalvar(char *p, int flags, struct strlist *var_str_list)
                 */
                STPUTC('\0', expdest);
                patloc = expdest - (char *)stackblock();
-               if (0 == subevalvar(p, /* str: */ NULL, patloc, subtype,
+               if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
                                startloc, varflags,
 //TODO: | EXP_REDIR too? All other such places do it too
                                /* quotes: */ flags & (EXP_FULL | EXP_CASE),
@@ -6783,13 +6958,11 @@ addfname(const char *name)
        exparg.lastp = &sp->next;
 }
 
-static char *expdir;
-
 /*
  * Do metacharacter (i.e. *, ?, [...]) expansion.
  */
 static void
-expmeta(char *enddir, char *name)
+expmeta(char *expdir, char *enddir, char *name)
 {
        char *p;
        const char *cp;
@@ -6888,7 +7061,7 @@ expmeta(char *enddir, char *name)
                                for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';)
                                        continue;
                                p[-1] = '/';
-                               expmeta(p, endname);
+                               expmeta(expdir, p, endname);
                        }
                }
        }
@@ -6970,6 +7143,7 @@ expandmeta(struct strlist *str /*, int flag*/)
        /* TODO - EXP_REDIR */
 
        while (str) {
+               char *expdir;
                struct strlist **savelastp;
                struct strlist *sp;
                char *p;
@@ -6986,8 +7160,7 @@ expandmeta(struct strlist *str /*, int flag*/)
                        int i = strlen(str->text);
                        expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
                }
-
-               expmeta(expdir, p);
+               expmeta(expdir, expdir, p);
                free(expdir);
                if (p != str->text)
                        free(p);
@@ -7179,6 +7352,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
 #if ENABLE_FEATURE_SH_STANDALONE
        if (applet_no >= 0) {
                if (APPLET_IS_NOEXEC(applet_no)) {
+                       clearenv();
                        while (*envp)
                                putenv(*envp++);
                        run_applet_no_and_exit(applet_no, argv);
@@ -7238,7 +7412,7 @@ shellexec(char **argv, const char *path, int idx)
 #endif
 
        clearredir(/*drop:*/ 1);
-       envp = listvars(VEXPORT, VUNSET, 0);
+       envp = listvars(VEXPORT, VUNSET, /*end:*/ NULL);
        if (strchr(argv[0], '/') != NULL
 #if ENABLE_FEATURE_SH_STANDALONE
         || (applet_no = find_applet_by_name(argv[0])) >= 0
@@ -7456,7 +7630,7 @@ hashcd(void)
                for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
                        if (cmdp->cmdtype == CMDNORMAL
                         || (cmdp->cmdtype == CMDBUILTIN
-                            && !IS_BUILTIN_REGULAR(cmdp->param.cmd)
+                            && !IS_BUILTIN_REGULAR(cmdp->param.cmd)
                             && builtinloc > 0)
                        ) {
                                cmdp->rehash = 1;
@@ -7487,8 +7661,10 @@ changepath(const char *new)
                if (*old != *new) {
                        firstchange = idx;
                        if ((*old == '\0' && *new == ':')
-                        || (*old == ':' && *new == '\0'))
+                        || (*old == ':' && *new == '\0')
+                       ) {
                                firstchange++;
+                       }
                        old = new;      /* ignore subsequent differences */
                }
                if (*new == '\0')
@@ -7497,7 +7673,8 @@ changepath(const char *new)
                        idx_bltin = idx;
                if (*new == ':')
                        idx++;
-               new++, old++;
+               new++;
+               old++;
        }
        if (builtinloc < 0 && idx_bltin >= 0)
                builtinloc = idx_bltin;             /* zap builtins */
@@ -7573,23 +7750,6 @@ static const char *const tokname_array[] = {
        "\1}",
 };
 
-static const char *
-tokname(int tok)
-{
-       static char buf[16];
-
-//try this:
-//if (tok < TSEMI) return tokname_array[tok] + 1;
-//sprintf(buf, "\"%s\"", tokname_array[tok] + 1);
-//return buf;
-
-       if (tok >= TSEMI)
-               buf[0] = '"';
-       sprintf(buf + (tok >= TSEMI), "%s%c",
-                       tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0));
-       return buf;
-}
-
 /* Wrapper around strcmp for qsort/bsearch/... */
 static int
 pstrcmp(const void *a, const void *b)
@@ -8061,7 +8221,7 @@ static int evalstring(char *s, int mask);
 
 /* Called to execute a trap.
  * Single callsite - at the end of evaltree().
- * If we return non-zero, exaltree raises EXEXIT exception.
+ * If we return non-zero, evaltree raises EXEXIT exception.
  *
  * Perhaps we should avoid entering new trap handlers
  * while we are executing a trap handler. [is it a TODO?]
@@ -8251,11 +8411,15 @@ evaltree(union node *n, int flags)
 
  out:
        exception_handler = savehandler;
+
  out1:
+       /* Order of checks below is important:
+        * signal handlers trigger before exit caused by "set -e".
+        */
+       if (pending_sig && dotrap())
+               goto exexit;
        if (checkexit & exitstatus)
                evalskip |= SKIPEVAL;
-       else if (pending_sig && dotrap())
-               goto exexit;
 
        if (flags & EV_EXIT) {
  exexit:
@@ -8385,15 +8549,16 @@ evalsubshell(union node *n, int flags)
        int status;
 
        expredir(n->nredir.redirect);
-       if (!backgnd && flags & EV_EXIT && !trap[0])
+       if (!backgnd && (flags & EV_EXIT) && !may_have_traps)
                goto nofork;
        INT_OFF;
        jp = makejob(/*n,*/ 1);
        if (forkshell(jp, n, backgnd) == 0) {
+               /* child */
                INT_ON;
                flags |= EV_EXIT;
                if (backgnd)
-                       flags &=EV_TESTED;
+                       flags &= ~EV_TESTED;
  nofork:
                redirect(n->nredir.redirect, 0);
                evaltreenr(n->nredir.n, flags);
@@ -8586,20 +8751,20 @@ poplocalvars(void)
        while ((lvp = localvars) != NULL) {
                localvars = lvp->next;
                vp = lvp->vp;
-               TRACE(("poplocalvar %s\n", vp ? vp->text : "-"));
+               TRACE(("poplocalvar %s\n", vp ? vp->var_text : "-"));
                if (vp == NULL) {       /* $- saved */
                        memcpy(optlist, lvp->text, sizeof(optlist));
                        free((char*)lvp->text);
                        optschanged();
                } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
-                       unsetvar(vp->text);
+                       unsetvar(vp->var_text);
                } else {
-                       if (vp->func)
-                               (*vp->func)(strchrnul(lvp->text, '=') + 1);
+                       if (vp->var_func)
+                               vp->var_func(var_end(lvp->text));
                        if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
-                               free((char*)vp->text);
+                               free((char*)vp->var_text);
                        vp->flags = lvp->flags;
-                       vp->text = lvp->text;
+                       vp->var_text = lvp->text;
                }
                free(lvp);
        }
@@ -8718,7 +8883,7 @@ mklocal(char *name)
                        vp = *vpp;      /* the new variable */
                        lvp->flags = VUNSET;
                } else {
-                       lvp->text = vp->text;
+                       lvp->text = vp->var_text;
                        lvp->flags = vp->flags;
                        vp->flags |= VSTRFIXED|VTEXTFIXED;
                        if (eq)
@@ -8830,77 +8995,79 @@ static int FAST_FUNC testcmd(int argc, char **argv)   { return test_main(argc, a
 
 /* Keep these in proper order since it is searched via bsearch() */
 static const struct builtincmd builtintab[] = {
-       { BUILTIN_SPEC_REG      ".", dotcmd },
-       { BUILTIN_SPEC_REG      ":", truecmd },
+       { BUILTIN_SPEC_REG      "."       , dotcmd     },
+       { BUILTIN_SPEC_REG      ":"       , truecmd    },
 #if ENABLE_ASH_BUILTIN_TEST
-       { BUILTIN_REGULAR       "[", testcmd },
+       { BUILTIN_REGULAR       "["       , testcmd    },
 #if ENABLE_ASH_BASH_COMPAT
-       { BUILTIN_REGULAR       "[[", testcmd },
+       { BUILTIN_REGULAR       "[["      , testcmd    },
 #endif
 #endif
 #if ENABLE_ASH_ALIAS
-       { BUILTIN_REG_ASSG      "alias", aliascmd },
+       { BUILTIN_REG_ASSG      "alias"   , aliascmd   },
 #endif
 #if JOBS
-       { BUILTIN_REGULAR       "bg", fg_bgcmd },
+       { BUILTIN_REGULAR       "bg"      , fg_bgcmd   },
 #endif
-       { BUILTIN_SPEC_REG      "break", breakcmd },
-       { BUILTIN_REGULAR       "cd", cdcmd },
-       { BUILTIN_NOSPEC        "chdir", cdcmd },
+       { BUILTIN_SPEC_REG      "break"   , breakcmd   },
+       { BUILTIN_REGULAR       "cd"      , cdcmd      },
+       { BUILTIN_NOSPEC        "chdir"   , cdcmd      },
 #if ENABLE_ASH_CMDCMD
-       { BUILTIN_REGULAR       "command", commandcmd },
+       { BUILTIN_REGULAR       "command" , commandcmd },
 #endif
-       { BUILTIN_SPEC_REG      "continue", breakcmd },
+       { BUILTIN_SPEC_REG      "continue", breakcmd   },
 #if ENABLE_ASH_BUILTIN_ECHO
-       { BUILTIN_REGULAR       "echo", echocmd },
+       { BUILTIN_REGULAR       "echo"    , echocmd    },
 #endif
-       { BUILTIN_SPEC_REG      "eval", evalcmd },
-       { BUILTIN_SPEC_REG      "exec", execcmd },
-       { BUILTIN_SPEC_REG      "exit", exitcmd },
-       { BUILTIN_SPEC_REG_ASSG "export", exportcmd },
-       { BUILTIN_REGULAR       "false", falsecmd },
+       { BUILTIN_SPEC_REG      "eval"    , evalcmd    },
+       { BUILTIN_SPEC_REG      "exec"    , execcmd    },
+       { BUILTIN_SPEC_REG      "exit"    , exitcmd    },
+       { BUILTIN_SPEC_REG_ASSG "export"  , exportcmd  },
+       { BUILTIN_REGULAR       "false"   , falsecmd   },
 #if JOBS
-       { BUILTIN_REGULAR       "fg", fg_bgcmd },
+       { BUILTIN_REGULAR       "fg"      , fg_bgcmd   },
 #endif
 #if ENABLE_ASH_GETOPTS
-       { BUILTIN_REGULAR       "getopts", getoptscmd },
+       { BUILTIN_REGULAR       "getopts" , getoptscmd },
 #endif
-       { BUILTIN_NOSPEC        "hash", hashcmd },
+       { BUILTIN_NOSPEC        "hash"    , hashcmd    },
 #if !ENABLE_FEATURE_SH_EXTRA_QUIET
-       { BUILTIN_NOSPEC        "help", helpcmd },
+       { BUILTIN_NOSPEC        "help"    , helpcmd    },
 #endif
 #if JOBS
-       { BUILTIN_REGULAR       "jobs", jobscmd },
-       { BUILTIN_REGULAR       "kill", killcmd },
+       { BUILTIN_REGULAR       "jobs"    , jobscmd    },
+       { BUILTIN_REGULAR       "kill"    , killcmd    },
 #endif
 #if ENABLE_SH_MATH_SUPPORT
-       { BUILTIN_NOSPEC        "let", letcmd },
+       { BUILTIN_NOSPEC        "let"     , letcmd     },
 #endif
-       { BUILTIN_ASSIGN        "local", localcmd },
+       { BUILTIN_ASSIGN        "local"   , localcmd   },
 #if ENABLE_ASH_BUILTIN_PRINTF
-       { BUILTIN_REGULAR       "printf", printfcmd },
-#endif
-       { BUILTIN_NOSPEC        "pwd", pwdcmd },
-       { BUILTIN_REGULAR       "read", readcmd },
-       { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd },
-       { BUILTIN_SPEC_REG      "return", returncmd },
-       { BUILTIN_SPEC_REG      "set", setcmd },
-       { BUILTIN_SPEC_REG      "shift", shiftcmd },
-       { BUILTIN_SPEC_REG      "source", dotcmd },
+       { BUILTIN_REGULAR       "printf"  , printfcmd  },
+#endif
+       { BUILTIN_NOSPEC        "pwd"     , pwdcmd     },
+       { BUILTIN_REGULAR       "read"    , readcmd    },
+       { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd  },
+       { BUILTIN_SPEC_REG      "return"  , returncmd  },
+       { BUILTIN_SPEC_REG      "set"     , setcmd     },
+       { BUILTIN_SPEC_REG      "shift"   , shiftcmd   },
+#if ENABLE_ASH_BASH_COMPAT
+       { BUILTIN_SPEC_REG      "source"  , dotcmd     },
+#endif
 #if ENABLE_ASH_BUILTIN_TEST
-       { BUILTIN_REGULAR       "test", testcmd },
-#endif
-       { BUILTIN_SPEC_REG      "times", timescmd },
-       { BUILTIN_SPEC_REG      "trap", trapcmd },
-       { BUILTIN_REGULAR       "true", truecmd },
-       { BUILTIN_NOSPEC        "type", typecmd },
-       { BUILTIN_NOSPEC        "ulimit", ulimitcmd },
-       { BUILTIN_REGULAR       "umask", umaskcmd },
+       { BUILTIN_REGULAR       "test"    , testcmd    },
+#endif
+       { BUILTIN_SPEC_REG      "times"   , timescmd   },
+       { BUILTIN_SPEC_REG      "trap"    , trapcmd    },
+       { BUILTIN_REGULAR       "true"    , truecmd    },
+       { BUILTIN_NOSPEC        "type"    , typecmd    },
+       { BUILTIN_NOSPEC        "ulimit"  , ulimitcmd  },
+       { BUILTIN_REGULAR       "umask"   , umaskcmd   },
 #if ENABLE_ASH_ALIAS
-       { BUILTIN_REGULAR       "unalias", unaliascmd },
+       { BUILTIN_REGULAR       "unalias" , unaliascmd },
 #endif
-       { BUILTIN_SPEC_REG      "unset", unsetcmd },
-       { BUILTIN_REGULAR       "wait", waitcmd },
+       { BUILTIN_SPEC_REG      "unset"   , unsetcmd   },
+       { BUILTIN_REGULAR       "wait"    , waitcmd    },
 };
 
 /* Should match the above table! */
@@ -9026,7 +9193,7 @@ evalcommand(union node *cmd, int flags)
        expredir(cmd->ncmd.redirect);
        status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2);
 
-       path = vpath.text;
+       path = vpath.var_text;
        for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
                struct strlist **spp;
                char *p;
@@ -9039,26 +9206,22 @@ evalcommand(union node *cmd, int flags)
                 * is present
                 */
                p = (*spp)->text;
-               if (varequal(p, path))
+               if (varcmp(p, path) == 0)
                        path = p;
        }
 
        /* Print the command if xflag is set. */
        if (xflag) {
                int n;
-               const char *p = " %s";
+               const char *p = " %s" + 1;
 
-               p++;
                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;
-                               if (*p == '%') {
-                                       p--;
-                               }
+                               p = " %s";
                        }
                        sp = arglist.list;
                }
@@ -9116,12 +9279,14 @@ evalcommand(union node *cmd, int flags)
 
        /* Execute the command. */
        switch (cmdentry.cmdtype) {
-       default:
+       default: {
 
 #if ENABLE_FEATURE_SH_NOFORK
-/* Hmmm... shouldn't it happen somewhere in forkshell() instead?
- * Why "fork off a child process if necessary" doesn't apply to NOFORK? */
-       {
+/* (1) BUG: if variables are set, we need to fork, or save/restore them
+ *     around run_nofork_applet() call.
+ * (2) Should this check also be done in forkshell()?
+ *     (perhaps it should, so that "VAR=VAL nofork" at least avoids exec...)
+ */
                /* find_command() encodes applet_no as (-2 - applet_no) */
                int applet_no = (- cmdentry.u.index - 2);
                if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) {
@@ -9130,24 +9295,30 @@ evalcommand(union node *cmd, int flags)
                        exitstatus = run_nofork_applet(applet_no, argv);
                        break;
                }
-       }
 #endif
-               /* Fork off a child process if necessary. */
-               if (!(flags & EV_EXIT) || trap[0]) {
+               /* Can we avoid forking off? For example, very last command
+                * in a script or a subshell does not need forking,
+                * we can just exec it.
+                */
+               if (!(flags & EV_EXIT) || may_have_traps) {
+                       /* No, forking off a child is necessary */
                        INT_OFF;
                        jp = makejob(/*cmd,*/ 1);
                        if (forkshell(jp, cmd, FORK_FG) != 0) {
+                               /* parent */
                                exitstatus = waitforjob(jp);
                                INT_ON;
                                TRACE(("forked child exited with %d\n", exitstatus));
                                break;
                        }
+                       /* child */
                        FORCE_INT_ON;
+                       /* fall through to exec'ing external program */
                }
                listsetvar(varlist.list, VEXPORT|VSTACK);
                shellexec(argv, path, cmdentry.u.index);
                /* NOTREACHED */
-
+       } /* default */
        case CMDBUILTIN:
                cmdenviron = varlist.list;
                if (cmdenviron) {
@@ -9192,7 +9363,8 @@ evalcommand(union node *cmd, int flags)
                if (evalfun(cmdentry.u.func, argc, argv, flags))
                        goto raise;
                break;
-       }
+
+       } /* switch */
 
  out:
        popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
@@ -9237,7 +9409,7 @@ evalbltin(const struct builtincmd *cmd, int argc, char **argv)
 static int
 goodname(const char *p)
 {
-       return !*endofname(p);
+       return endofname(p)[0] == '\0';
 }
 
 
@@ -9386,13 +9558,13 @@ preadfd(void)
        g_parsefile->next_to_pgetc = buf;
 #if ENABLE_FEATURE_EDITING
  retry:
-       if (!iflag || g_parsefile->fd != STDIN_FILENO)
-               nr = nonblock_safe_read(g_parsefile->fd, buf, BUFSIZ - 1);
+       if (!iflag || g_parsefile->pf_fd != STDIN_FILENO)
+               nr = nonblock_safe_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1);
        else {
 #if ENABLE_FEATURE_TAB_COMPLETION
                line_input_state->path_lookup = pathval();
 #endif
-               nr = read_line_input(cmdedit_prompt, buf, BUFSIZ, line_input_state);
+               nr = read_line_input(cmdedit_prompt, buf, IBUFSIZ, line_input_state);
                if (nr == 0) {
                        /* Ctrl+C pressed */
                        if (trap[SIGINT]) {
@@ -9409,7 +9581,7 @@ preadfd(void)
                }
        }
 #else
-       nr = nonblock_safe_read(g_parsefile->fd, buf, BUFSIZ - 1);
+       nr = nonblock_safe_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1);
 #endif
 
 #if 0
@@ -9636,7 +9808,7 @@ pushfile(void)
 
        pf = ckzalloc(sizeof(*pf));
        pf->prev = g_parsefile;
-       pf->fd = -1;
+       pf->pf_fd = -1;
        /*pf->strpush = NULL; - ckzalloc did it */
        /*pf->basestrpush.prev = NULL;*/
        g_parsefile = pf;
@@ -9648,8 +9820,8 @@ popfile(void)
        struct parsefile *pf = g_parsefile;
 
        INT_OFF;
-       if (pf->fd >= 0)
-               close(pf->fd);
+       if (pf->pf_fd >= 0)
+               close(pf->pf_fd);
        free(pf->buf);
        while (pf->strpush)
                popstring();
@@ -9676,9 +9848,9 @@ static void
 closescript(void)
 {
        popallfiles();
-       if (g_parsefile->fd > 0) {
-               close(g_parsefile->fd);
-               g_parsefile->fd = 0;
+       if (g_parsefile->pf_fd > 0) {
+               close(g_parsefile->pf_fd);
+               g_parsefile->pf_fd = 0;
        }
 }
 
@@ -9694,7 +9866,7 @@ setinputfd(int fd, int push)
                pushfile();
                g_parsefile->buf = NULL;
        }
-       g_parsefile->fd = fd;
+       g_parsefile->pf_fd = fd;
        if (g_parsefile->buf == NULL)
                g_parsefile->buf = ckmalloc(IBUFSIZ);
        g_parsefile->left_in_buffer = 0;
@@ -9799,7 +9971,7 @@ chkmail(void)
                }
                if (!mail_var_path_changed && statb.st_mtime != *mtp) {
                        fprintf(
-                               stderr, snlfmt,
+                               stderr, "%s\n",
                                pathopt ? pathopt : "you have mail"
                        );
                }
@@ -10057,7 +10229,7 @@ change_random(const char *value)
                /* "get", generate */
                t = next_random(&random_gen);
                /* set without recursion */
-               setvar(vrandom.text, utoa(t), VNOFUNC);
+               setvar(vrandom.var_text, utoa(t), VNOFUNC);
                vrandom.flags &= ~VNOFUNC;
        } else {
                /* set/reset */
@@ -10208,7 +10380,16 @@ static struct nodelist *backquotelist;
 static union node *redirnode;
 static struct heredoc *heredoc;
 
-/*
+static const char *
+tokname(char *buf, int tok)
+{
+       if (tok < TSEMI)
+               return tokname_array[tok] + 1;
+       sprintf(buf, "\"%s\"", tokname_array[tok] + 1);
+       return buf;
+}
+
+/* raise_error_unexpected_syntax:
  * Called when an unexpected token is read during the parse.  The argument
  * is the token that is expected, or -1 if more than one type of token can
  * occur at this point.
@@ -10218,11 +10399,12 @@ static void
 raise_error_unexpected_syntax(int token)
 {
        char msg[64];
+       char buf[16];
        int l;
 
-       l = sprintf(msg, "unexpected %s", tokname(lasttoken));
+       l = sprintf(msg, "unexpected %s", tokname(buf, lasttoken));
        if (token >= 0)
-               sprintf(msg + l, " (expecting %s)", tokname(token));
+               sprintf(msg + l, " (expecting %s)", tokname(buf, token));
        raise_error_syntax(msg);
        /* NOTREACHED */
 }
@@ -10610,7 +10792,7 @@ parse_command(void)
                n1->nbinary.ch1 = list(0);
                got = readtoken();
                if (got != TDO) {
-                       TRACE(("expecting DO got %s %s\n", tokname(got),
+                       TRACE(("expecting DO got '%s' %s\n", tokname_array[got] + 1,
                                        got == TWORD ? wordtext : ""));
                        raise_error_unexpected_syntax(TDO);
                }
@@ -10858,7 +11040,6 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
        startlinno = g_parsefile->linno;
        bqlist = NULL;
        quotef = 0;
-       oldstyle = 0;
        prevsyntax = 0;
 #if ENABLE_ASH_EXPAND_PRMT
        pssyntax = (syntax == PSSYNTAX);
@@ -10874,160 +11055,156 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
        STARTSTACKSTR(out);
  loop:
        /* For each line, until end of word */
-       {
-               CHECKEND();     /* set c to PEOF if at end of here document */
-               for (;;) {      /* until end of line or end of word */
-                       CHECKSTRSPACE(4, out);  /* permit 4 calls to USTPUTC */
-                       switch (SIT(c, syntax)) {
-                       case CNL:       /* '\n' */
-                               if (syntax == BASESYNTAX)
-                                       goto endword;   /* exit outer loop */
-                               USTPUTC(c, out);
-                               g_parsefile->linno++;
-                               if (doprompt)
-                                       setprompt(2);
-                               c = pgetc();
-                               goto loop;              /* continue outer loop */
-                       case CWORD:
-                               USTPUTC(c, out);
-                               break;
-                       case CCTL:
-                               if (eofmark == NULL || dblquote)
-                                       USTPUTC(CTLESC, out);
+       CHECKEND();     /* set c to PEOF if at end of here document */
+       for (;;) {      /* until end of line or end of word */
+               CHECKSTRSPACE(4, out);  /* permit 4 calls to USTPUTC */
+               switch (SIT(c, syntax)) {
+               case CNL:       /* '\n' */
+                       if (syntax == BASESYNTAX)
+                               goto endword;   /* exit outer loop */
+                       USTPUTC(c, out);
+                       g_parsefile->linno++;
+                       setprompt_if(doprompt, 2);
+                       c = pgetc();
+                       goto loop;              /* continue outer loop */
+               case CWORD:
+                       USTPUTC(c, out);
+                       break;
+               case CCTL:
+                       if (eofmark == NULL || dblquote)
+                               USTPUTC(CTLESC, out);
 #if ENABLE_ASH_BASH_COMPAT
-                               if (c == '\\' && bash_dollar_squote) {
-                                       c = decode_dollar_squote();
-                                       if (c & 0x100) {
-                                               USTPUTC('\\', out);
-                                               c = (unsigned char)c;
-                                       }
+                       if (c == '\\' && bash_dollar_squote) {
+                               c = decode_dollar_squote();
+                               if (c & 0x100) {
+                                       USTPUTC('\\', out);
+                                       c = (unsigned char)c;
                                }
+                       }
 #endif
-                               USTPUTC(c, out);
-                               break;
-                       case CBACK:     /* backslash */
-                               c = pgetc_without_PEOA();
-                               if (c == PEOF) {
+                       USTPUTC(c, out);
+                       break;
+               case CBACK:     /* backslash */
+                       c = pgetc_without_PEOA();
+                       if (c == PEOF) {
+                               USTPUTC(CTLESC, out);
+                               USTPUTC('\\', out);
+                               pungetc();
+                       } else if (c == '\n') {
+                               setprompt_if(doprompt, 2);
+                       } else {
+#if ENABLE_ASH_EXPAND_PRMT
+                               if (c == '$' && pssyntax) {
                                        USTPUTC(CTLESC, out);
                                        USTPUTC('\\', out);
-                                       pungetc();
-                               } else if (c == '\n') {
-                                       if (doprompt)
-                                               setprompt(2);
-                               } else {
-#if ENABLE_ASH_EXPAND_PRMT
-                                       if (c == '$' && pssyntax) {
-                                               USTPUTC(CTLESC, out);
-                                               USTPUTC('\\', out);
-                                       }
+                               }
 #endif
-                                       if (dblquote && c != '\\'
-                                        && c != '`' && c != '$'
-                                        && (c != '"' || eofmark != NULL)
-                                       ) {
-                                               USTPUTC(CTLESC, out);
-                                               USTPUTC('\\', out);
-                                       }
-                                       if (SIT(c, SQSYNTAX) == CCTL)
-                                               USTPUTC(CTLESC, out);
-                                       USTPUTC(c, out);
-                                       quotef = 1;
+                               /* Backslash is retained if we are in "str" and next char isn't special */
+                               if (dblquote
+                                && c != '\\'
+                                && c != '`'
+                                && c != '$'
+                                && (c != '"' || eofmark != NULL)
+                               ) {
+                                       USTPUTC(CTLESC, out);
+                                       USTPUTC('\\', out);
                                }
-                               break;
-                       case CSQUOTE:
-                               syntax = SQSYNTAX;
+                               if (SIT(c, SQSYNTAX) == CCTL)
+                                       USTPUTC(CTLESC, out);
+                               USTPUTC(c, out);
+                               quotef = 1;
+                       }
+                       break;
+               case CSQUOTE:
+                       syntax = SQSYNTAX;
  quotemark:
-                               if (eofmark == NULL) {
-                                       USTPUTC(CTLQUOTEMARK, out);
+                       if (eofmark == NULL) {
+                               USTPUTC(CTLQUOTEMARK, out);
+                       }
+                       break;
+               case CDQUOTE:
+                       syntax = DQSYNTAX;
+                       dblquote = 1;
+                       goto quotemark;
+               case CENDQUOTE:
+                       IF_ASH_BASH_COMPAT(bash_dollar_squote = 0;)
+                       if (eofmark != NULL && arinest == 0
+                        && varnest == 0
+                       ) {
+                               USTPUTC(c, out);
+                       } else {
+                               if (dqvarnest == 0) {
+                                       syntax = BASESYNTAX;
+                                       dblquote = 0;
                                }
-                               break;
-                       case CDQUOTE:
-                               syntax = DQSYNTAX;
-                               dblquote = 1;
+                               quotef = 1;
                                goto quotemark;
-                       case CENDQUOTE:
-                               IF_ASH_BASH_COMPAT(bash_dollar_squote = 0;)
-                               if (eofmark != NULL && arinest == 0
-                                && varnest == 0
-                               ) {
-                                       USTPUTC(c, out);
-                               } else {
-                                       if (dqvarnest == 0) {
-                                               syntax = BASESYNTAX;
-                                               dblquote = 0;
-                                       }
-                                       quotef = 1;
-                                       goto quotemark;
-                               }
-                               break;
-                       case CVAR:      /* '$' */
-                               PARSESUB();             /* parse substitution */
-                               break;
-                       case CENDVAR:   /* '}' */
-                               if (varnest > 0) {
-                                       varnest--;
-                                       if (dqvarnest > 0) {
-                                               dqvarnest--;
-                                       }
-                                       USTPUTC(CTLENDVAR, out);
-                               } else {
-                                       USTPUTC(c, out);
+                       }
+                       break;
+               case CVAR:      /* '$' */
+                       PARSESUB();             /* parse substitution */
+                       break;
+               case CENDVAR:   /* '}' */
+                       if (varnest > 0) {
+                               varnest--;
+                               if (dqvarnest > 0) {
+                                       dqvarnest--;
                                }
-                               break;
+                               c = CTLENDVAR;
+                       }
+                       USTPUTC(c, out);
+                       break;
 #if ENABLE_SH_MATH_SUPPORT
-                       case CLP:       /* '(' in arithmetic */
-                               parenlevel++;
-                               USTPUTC(c, out);
-                               break;
-                       case CRP:       /* ')' in arithmetic */
-                               if (parenlevel > 0) {
-                                       USTPUTC(c, out);
-                                       --parenlevel;
-                               } else {
-                                       if (pgetc() == ')') {
-                                               if (--arinest == 0) {
-                                                       USTPUTC(CTLENDARI, out);
-                                                       syntax = prevsyntax;
-                                                       dblquote = (syntax == DQSYNTAX);
-                                               } else
-                                                       USTPUTC(')', out);
-                                       } else {
-                                               /*
-                                                * unbalanced parens
-                                                *  (don't 2nd guess - no error)
-                                                */
-                                               pungetc();
-                                               USTPUTC(')', out);
+               case CLP:       /* '(' in arithmetic */
+                       parenlevel++;
+                       USTPUTC(c, out);
+                       break;
+               case CRP:       /* ')' in arithmetic */
+                       if (parenlevel > 0) {
+                               parenlevel--;
+                       } else {
+                               if (pgetc() == ')') {
+                                       if (--arinest == 0) {
+                                               syntax = prevsyntax;
+                                               dblquote = (syntax == DQSYNTAX);
+                                               c = CTLENDARI;
                                        }
+                               } else {
+                                       /*
+                                        * unbalanced parens
+                                        * (don't 2nd guess - no error)
+                                        */
+                                       pungetc();
                                }
-                               break;
+                       }
+                       USTPUTC(c, out);
+                       break;
 #endif
-                       case CBQUOTE:   /* '`' */
-                               PARSEBACKQOLD();
-                               break;
-                       case CENDFILE:
-                               goto endword;           /* exit outer loop */
-                       case CIGN:
-                               break;
-                       default:
-                               if (varnest == 0) {
+               case CBQUOTE:   /* '`' */
+                       PARSEBACKQOLD();
+                       break;
+               case CENDFILE:
+                       goto endword;           /* exit outer loop */
+               case CIGN:
+                       break;
+               default:
+                       if (varnest == 0) {
 #if ENABLE_ASH_BASH_COMPAT
-                                       if (c == '&') {
-                                               if (pgetc() == '>')
-                                                       c = 0x100 + '>'; /* flag &> */
-                                               pungetc();
-                                       }
-#endif
-                                       goto endword;   /* exit outer loop */
+                               if (c == '&') {
+                                       if (pgetc() == '>')
+                                               c = 0x100 + '>'; /* flag &> */
+                                       pungetc();
                                }
-                               IF_ASH_ALIAS(if (c != PEOA))
-                                       USTPUTC(c, out);
-
+#endif
+                               goto endword;   /* exit outer loop */
                        }
-                       c = pgetc_fast();
-               } /* for (;;) */
-       }
+                       IF_ASH_ALIAS(if (c != PEOA))
+                               USTPUTC(c, out);
+               }
+               c = pgetc_fast();
+       } /* for (;;) */
  endword:
+
 #if ENABLE_SH_MATH_SUPPORT
        if (syntax == ARISYNTAX)
                raise_error_syntax("missing '))'");
@@ -11188,8 +11365,6 @@ parsesub: {
        unsigned char subtype;
        int typeloc;
        int flags;
-       char *p;
-       static const char types[] ALIGN1 = "}-+?=";
 
        c = pgetc();
        if (c > 255 /* PEOA or PEOF */
@@ -11202,7 +11377,8 @@ parsesub: {
 #endif
                        USTPUTC('$', out);
                pungetc();
-       } else if (c == '(') {  /* $(command) or $((arith)) */
+       } else if (c == '(') {
+               /* $(command) or $((arith)) */
                if (pgetc() == '(') {
 #if ENABLE_SH_MATH_SUPPORT
                        PARSEARITH();
@@ -11214,6 +11390,7 @@ parsesub: {
                        PARSEBACKQNEW();
                }
        } else {
+               /* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
                USTPUTC(CTLVAR, out);
                typeloc = out - (char *)stackblock();
                USTPUTC(VSNORMAL, out);
@@ -11223,76 +11400,90 @@ parsesub: {
                        if (c == '#') {
                                c = pgetc();
                                if (c == '}')
-                                       c = '#';
+                                       c = '#'; /* ${#} - same as $# */
                                else
-                                       subtype = VSLENGTH;
-                       } else
+                                       subtype = VSLENGTH; /* ${#VAR} */
+                       } else {
                                subtype = 0;
+                       }
                }
                if (c <= 255 /* not PEOA or PEOF */ && is_name(c)) {
+                       /* $[{[#]]NAME[}] */
                        do {
                                STPUTC(c, out);
                                c = pgetc();
                        } while (c <= 255 /* not PEOA or PEOF */ && is_in_name(c));
                } else if (isdigit(c)) {
+                       /* $[{[#]]NUM[}] */
                        do {
                                STPUTC(c, out);
                                c = pgetc();
                        } while (isdigit(c));
                } else if (is_special(c)) {
+                       /* $[{[#]]<specialchar>[}] */
                        USTPUTC(c, out);
                        c = pgetc();
                } else {
  badsub:
                        raise_error_syntax("bad substitution");
                }
-               if (c != '}' && subtype == VSLENGTH)
+               if (c != '}' && subtype == VSLENGTH) {
+                       /* ${#VAR didn't end with } */
                        goto badsub;
+               }
 
                STPUTC('=', out);
                flags = 0;
                if (subtype == 0) {
+                       /* ${VAR...} but not $VAR or ${#VAR} */
+                       /* c == first char after VAR */
                        switch (c) {
                        case ':':
                                c = pgetc();
 #if ENABLE_ASH_BASH_COMPAT
                                if (c == ':' || c == '$' || isdigit(c)) {
-                                       pungetc();
+//TODO: support more general format ${v:EXPR:EXPR},
+// where EXPR follows $(()) rules
                                        subtype = VSSUBSTR;
-                                       break;
+                                       pungetc();
+                                       break; /* "goto do_pungetc" is bigger (!) */
                                }
 #endif
                                flags = VSNUL;
                                /*FALLTHROUGH*/
-                       default:
-                               p = strchr(types, c);
+                       default: {
+                               static const char types[] ALIGN1 = "}-+?=";
+                               const char *p = strchr(types, c);
                                if (p == NULL)
                                        goto badsub;
                                subtype = p - types + VSNORMAL;
                                break;
+                       }
                        case '%':
                        case '#': {
                                int cc = c;
-                               subtype = c == '#' ? VSTRIMLEFT : VSTRIMRIGHT;
+                               subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT);
                                c = pgetc();
-                               if (c == cc)
-                                       subtype++;
-                               else
-                                       pungetc();
+                               if (c != cc)
+                                       goto do_pungetc;
+                               subtype++;
                                break;
                        }
 #if ENABLE_ASH_BASH_COMPAT
                        case '/':
+                               /* ${v/[/]pattern/repl} */
+//TODO: encode pattern and repl separately.
+// Currently ${v/$var_with_slash/repl} is horribly broken
                                subtype = VSREPLACE;
                                c = pgetc();
-                               if (c == '/')
-                                       subtype++; /* VSREPLACEALL */
-                               else
-                                       pungetc();
+                               if (c != '/')
+                                       goto do_pungetc;
+                               subtype++; /* VSREPLACEALL */
                                break;
 #endif
                        }
                } else {
+ do_pungetc:
                        pungetc();
                }
                if (dblquote || arinest)
@@ -11349,16 +11540,14 @@ parsebackq: {
                   treatment to some slashes, and then push the string and
                   reread it as input, interpreting it normally.  */
                char *pout;
-               int pc;
                size_t psavelen;
                char *pstr;
 
-
                STARTSTACKSTR(pout);
                for (;;) {
-                       if (needprompt) {
-                               setprompt(2);
-                       }
+                       int pc;
+
+                       setprompt_if(needprompt, 2);
                        pc = pgetc();
                        switch (pc) {
                        case '`':
@@ -11368,8 +11557,7 @@ parsebackq: {
                                pc = pgetc();
                                if (pc == '\n') {
                                        g_parsefile->linno++;
-                                       if (doprompt)
-                                               setprompt(2);
+                                       setprompt_if(doprompt, 2);
                                        /*
                                         * If eating a newline, avoid putting
                                         * the newline into the new character
@@ -11532,9 +11720,7 @@ xxreadtoken(void)
                tokpushback = 0;
                return lasttoken;
        }
-       if (needprompt) {
-               setprompt(2);
-       }
+       setprompt_if(needprompt, 2);
        startlinno = g_parsefile->linno;
        for (;;) {                      /* until token or start of word found */
                c = pgetc_fast();
@@ -11551,8 +11737,7 @@ xxreadtoken(void)
                                break; /* return readtoken1(...) */
                        }
                        startlinno = ++g_parsefile->linno;
-                       if (doprompt)
-                               setprompt(2);
+                       setprompt_if(doprompt, 2);
                } else {
                        const char *p;
 
@@ -11598,9 +11783,7 @@ xxreadtoken(void)
                tokpushback = 0;
                return lasttoken;
        }
-       if (needprompt) {
-               setprompt(2);
-       }
+       setprompt_if(needprompt, 2);
        startlinno = g_parsefile->linno;
        for (;;) {      /* until token or start of word found */
                c = pgetc_fast();
@@ -11616,8 +11799,7 @@ xxreadtoken(void)
                case '\\':
                        if (pgetc() == '\n') {
                                startlinno = ++g_parsefile->linno;
-                               if (doprompt)
-                                       setprompt(2);
+                               setprompt_if(doprompt, 2);
                                continue;
                        }
                        pungetc();
@@ -11694,7 +11876,7 @@ readtoken(void)
                pp = findkwd(wordtext);
                if (pp) {
                        lasttoken = t = pp - tokname_array;
-                       TRACE(("keyword %s recognized\n", tokname(t)));
+                       TRACE(("keyword '%s' recognized\n", tokname_array[t] + 1));
                        goto out;
                }
        }
@@ -11715,9 +11897,9 @@ readtoken(void)
        checkkwd = 0;
 #if DEBUG
        if (!alreadyseen)
-               TRACE(("token %s %s\n", tokname(t), t == TWORD ? wordtext : ""));
+               TRACE(("token '%s' %s\n", tokname_array[t] + 1, t == TWORD ? wordtext : ""));
        else
-               TRACE(("reread token %s %s\n", tokname(t), t == TWORD ? wordtext : ""));
+               TRACE(("reread token '%s' %s\n", tokname_array[t] + 1, t == TWORD ? wordtext : ""));
 #endif
        return t;
 }
@@ -11743,8 +11925,7 @@ parsecmd(int interact)
 
        tokpushback = 0;
        doprompt = interact;
-       if (doprompt)
-               setprompt(doprompt);
+       setprompt_if(doprompt, doprompt);
        needprompt = 0;
        t = readtoken();
        if (t == TEOF)
@@ -11768,10 +11949,8 @@ parseheredoc(void)
        heredoclist = NULL;
 
        while (here) {
-               if (needprompt) {
-                       setprompt(2);
-               }
-               readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
+               setprompt_if(needprompt, 2);
+               readtoken1(pgetc(), here->here->type == NHERE ? SQSYNTAX : DQSYNTAX,
                                here->eofmark, here->striptabs);
                n = stzalloc(sizeof(struct narg));
                n->narg.type = NARG;
@@ -11862,7 +12041,6 @@ evalcmd(int argc UNUSED_PARAM, char **argv)
                        p = grabstackstr(concat);
                }
                evalstring(p, ~SKIPEVAL);
-
        }
        return exitstatus;
 }
@@ -11971,36 +12149,43 @@ find_dot_file(char *name)
 static int FAST_FUNC
 dotcmd(int argc, char **argv)
 {
+       char *fullname;
        struct strlist *sp;
        volatile struct shparam saveparam;
-       int status = 0;
 
        for (sp = cmdenviron; sp; sp = sp->next)
                setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
 
-       if (argv[1]) {        /* That's what SVR2 does */
-               char *fullname = find_dot_file(argv[1]);
-               argv += 2;
-               argc -= 2;
-               if (argc) { /* argc > 0, argv[0] != NULL */
-                       saveparam = shellparam;
-                       shellparam.malloced = 0;
-                       shellparam.nparam = argc;
-                       shellparam.p = argv;
-               };
-
-               setinputfile(fullname, INPUT_PUSH_FILE);
-               commandname = fullname;
-               cmdloop(0);
-               popfile();
-
-               if (argc) {
-                       freeparam(&shellparam);
-                       shellparam = saveparam;
-               };
-               status = exitstatus;
+       if (!argv[1]) {
+               /* bash says: "bash: .: filename argument required" */
+               return 2; /* bash compat */
        }
-       return status;
+
+       /* "false; . empty_file; echo $?" should print 0, not 1: */
+       exitstatus = 0;
+
+       fullname = find_dot_file(argv[1]);
+
+       argv += 2;
+       argc -= 2;
+       if (argc) { /* argc > 0, argv[0] != NULL */
+               saveparam = shellparam;
+               shellparam.malloced = 0;
+               shellparam.nparam = argc;
+               shellparam.p = argv;
+       };
+
+       setinputfile(fullname, INPUT_PUSH_FILE);
+       commandname = fullname;
+       cmdloop(0);
+       popfile();
+
+       if (argc) {
+               freeparam(&shellparam);
+               shellparam = saveparam;
+       };
+
+       return exitstatus;
 }
 
 static int FAST_FUNC
@@ -12147,7 +12332,7 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path)
                        }
                        if ((act & DO_NOFUNC)
                         || !prefix(pathopt, "func")
-                       ) {     /* ignore unimplemented options */
+                       ) {     /* ignore unimplemented options */
                                continue;
                        }
                }
@@ -12232,7 +12417,7 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 {
        char *action;
        char **ap;
-       int signo;
+       int signo, exitcode;
 
        nextopt(nullstr);
        ap = argptr;
@@ -12265,10 +12450,15 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        action = NULL;
        if (ap[1])
                action = *ap++;
+       exitcode = 0;
        while (*ap) {
                signo = get_signum(*ap);
-               if (signo < 0)
-                       ash_msg_and_raise_error("%s: bad trap", *ap);
+               if (signo < 0) {
+                       /* Mimic bash message exactly */
+                       ash_msg("%s: invalid signal specification", *ap);
+                       exitcode = 1;
+                       goto next;
+               }
                INT_OFF;
                if (action) {
                        if (LONE_DASH(action))
@@ -12277,13 +12467,16 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                                action = ckstrdup(action);
                }
                free(trap[signo]);
+               if (action)
+                       may_have_traps = 1;
                trap[signo] = action;
                if (signo != 0)
                        setsignal(signo);
                INT_ON;
+ next:
                ap++;
        }
-       return 0;
+       return exitcode;
 }
 
 
@@ -12373,7 +12566,7 @@ unsetfunc(const char *name)
        struct tblentry *cmdp;
 
        cmdp = cmdlookup(name, 0);
-       if (cmdp!= NULL && cmdp->cmdtype == CMDFUNCTION)
+       if (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION)
                delete_cmd_entry();
 }
 
@@ -12390,7 +12583,7 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        int flag = 0;
        int ret = 0;
 
-       while ((i = nextopt("vf")) != '\0') {
+       while ((i = nextopt("vf")) != 0) {
                flag = i;
        }
 
@@ -12407,11 +12600,6 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        return ret & 1;
 }
 
-
-/*      setmode.c      */
-
-#include <sys/times.h>
-
 static const unsigned char timescmd_str[] ALIGN1 = {
        ' ',  offsetof(struct tms, tms_utime),
        '\n', offsetof(struct tms, tms_stime),
@@ -12419,11 +12607,10 @@ static const unsigned char timescmd_str[] ALIGN1 = {
        '\n', offsetof(struct tms, tms_cstime),
        0
 };
-
 static int FAST_FUNC
 timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 {
-       long clk_tck, s, t;
+       unsigned long clk_tck, s, t;
        const unsigned char *p;
        struct tms buf;
 
@@ -12434,18 +12621,20 @@ timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        do {
                t = *(clock_t *)(((char *) &buf) + p[1]);
                s = t / clk_tck;
-               out1fmt("%ldm%ld.%.3lds%c",
-                       s/60, s%60,
-                       ((t - s * clk_tck) * 1000) / clk_tck,
+               t = t % clk_tck;
+               out1fmt("%lum%lu.%03lus%c",
+                       s / 60, s % 60,
+                       (t * 1000) / clk_tck,
                        p[0]);
-       } while (*(p += 2));
+               p += 2;
+       } while (*p);
 
        return 0;
 }
 
 #if ENABLE_SH_MATH_SUPPORT
 /*
- * The let builtin. partial stolen from GNU Bash, the Bourne Again SHell.
+ * The let builtin. Partially stolen from GNU Bash, the Bourne Again SHell.
  * Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc.
  *
  * Copyright (C) 2003 Vladimir Oleynik <dzo@simtreas.ru>
@@ -12464,18 +12653,6 @@ letcmd(int argc UNUSED_PARAM, char **argv)
 
        return !i;
 }
-#endif /* SH_MATH_SUPPORT */
-
-
-/* ============ miscbltin.c
- *
- * Miscellaneous builtins.
- */
-
-#undef rflag
-
-#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 1
-typedef enum __rlimit_resource rlim_t;
 #endif
 
 /*
@@ -12614,219 +12791,10 @@ umaskcmd(int argc UNUSED_PARAM, char **argv)
        return 0;
 }
 
-/*
- * ulimit builtin
- *
- * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and
- * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with
- * ash by J.T. Conklin.
- *
- * Public domain.
- */
-struct limits {
-       uint8_t cmd;          /* RLIMIT_xxx fit into it */
-       uint8_t factor_shift; /* shift by to get rlim_{cur,max} values */
-       char    option;
-};
-
-static const struct limits limits_tbl[] = {
-#ifdef RLIMIT_CPU
-       { RLIMIT_CPU,        0, 't' },
-#endif
-#ifdef RLIMIT_FSIZE
-       { RLIMIT_FSIZE,      9, 'f' },
-#endif
-#ifdef RLIMIT_DATA
-       { RLIMIT_DATA,      10, 'd' },
-#endif
-#ifdef RLIMIT_STACK
-       { RLIMIT_STACK,     10, 's' },
-#endif
-#ifdef RLIMIT_CORE
-       { RLIMIT_CORE,       9, 'c' },
-#endif
-#ifdef RLIMIT_RSS
-       { RLIMIT_RSS,       10, 'm' },
-#endif
-#ifdef RLIMIT_MEMLOCK
-       { RLIMIT_MEMLOCK,   10, 'l' },
-#endif
-#ifdef RLIMIT_NPROC
-       { RLIMIT_NPROC,      0, 'p' },
-#endif
-#ifdef RLIMIT_NOFILE
-       { RLIMIT_NOFILE,     0, 'n' },
-#endif
-#ifdef RLIMIT_AS
-       { RLIMIT_AS,        10, 'v' },
-#endif
-#ifdef RLIMIT_LOCKS
-       { RLIMIT_LOCKS,      0, 'w' },
-#endif
-};
-static const char limits_name[] =
-#ifdef RLIMIT_CPU
-       "time(seconds)" "\0"
-#endif
-#ifdef RLIMIT_FSIZE
-       "file(blocks)" "\0"
-#endif
-#ifdef RLIMIT_DATA
-       "data(kb)" "\0"
-#endif
-#ifdef RLIMIT_STACK
-       "stack(kb)" "\0"
-#endif
-#ifdef RLIMIT_CORE
-       "coredump(blocks)" "\0"
-#endif
-#ifdef RLIMIT_RSS
-       "memory(kb)" "\0"
-#endif
-#ifdef RLIMIT_MEMLOCK
-       "locked memory(kb)" "\0"
-#endif
-#ifdef RLIMIT_NPROC
-       "process" "\0"
-#endif
-#ifdef RLIMIT_NOFILE
-       "nofiles" "\0"
-#endif
-#ifdef RLIMIT_AS
-       "vmemory(kb)" "\0"
-#endif
-#ifdef RLIMIT_LOCKS
-       "locks" "\0"
-#endif
-;
-
-enum limtype { SOFT = 0x1, HARD = 0x2 };
-
-static void
-printlim(enum limtype how, const struct rlimit *limit,
-                       const struct limits *l)
-{
-       rlim_t val;
-
-       val = limit->rlim_max;
-       if (how & SOFT)
-               val = limit->rlim_cur;
-
-       if (val == RLIM_INFINITY)
-               out1fmt("unlimited\n");
-       else {
-               val >>= l->factor_shift;
-               out1fmt("%lld\n", (long long) val);
-       }
-}
-
 static int FAST_FUNC
-ulimitcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+ulimitcmd(int argc UNUSED_PARAM, char **argv)
 {
-       rlim_t val;
-       enum limtype how = SOFT | HARD;
-       const struct limits *l;
-       int set, all = 0;
-       int optc, what;
-       struct rlimit limit;
-
-       what = 'f';
-       while ((optc = nextopt("HSa"
-#ifdef RLIMIT_CPU
-                               "t"
-#endif
-#ifdef RLIMIT_FSIZE
-                               "f"
-#endif
-#ifdef RLIMIT_DATA
-                               "d"
-#endif
-#ifdef RLIMIT_STACK
-                               "s"
-#endif
-#ifdef RLIMIT_CORE
-                               "c"
-#endif
-#ifdef RLIMIT_RSS
-                               "m"
-#endif
-#ifdef RLIMIT_MEMLOCK
-                               "l"
-#endif
-#ifdef RLIMIT_NPROC
-                               "p"
-#endif
-#ifdef RLIMIT_NOFILE
-                               "n"
-#endif
-#ifdef RLIMIT_AS
-                               "v"
-#endif
-#ifdef RLIMIT_LOCKS
-                               "w"
-#endif
-                                       )) != '\0')
-               switch (optc) {
-               case 'H':
-                       how = HARD;
-                       break;
-               case 'S':
-                       how = SOFT;
-                       break;
-               case 'a':
-                       all = 1;
-                       break;
-               default:
-                       what = optc;
-               }
-
-       for (l = limits_tbl; l->option != what; l++)
-               continue;
-
-       set = *argptr ? 1 : 0;
-       val = 0;
-       if (set) {
-               char *p = *argptr;
-
-               if (all || argptr[1])
-                       ash_msg_and_raise_error("too many arguments");
-               if (strncmp(p, "unlimited\n", 9) == 0)
-                       val = RLIM_INFINITY;
-               else {
-                       if (sizeof(val) == sizeof(int))
-                               val = bb_strtou(p, NULL, 10);
-                       else if (sizeof(val) == sizeof(long))
-                               val = bb_strtoul(p, NULL, 10);
-                       else
-                               val = bb_strtoull(p, NULL, 10);
-                       if (errno)
-                               ash_msg_and_raise_error("bad number");
-                       val <<= l->factor_shift;
-               }
-       }
-       if (all) {
-               const char *lname = limits_name;
-               for (l = limits_tbl; l != &limits_tbl[ARRAY_SIZE(limits_tbl)]; l++) {
-                       getrlimit(l->cmd, &limit);
-                       out1fmt("%-20s ", lname);
-                       lname += strlen(lname) + 1;
-                       printlim(how, &limit, l);
-               }
-               return 0;
-       }
-
-       getrlimit(l->cmd, &limit);
-       if (set) {
-               if (how & HARD)
-                       limit.rlim_max = val;
-               if (how & SOFT)
-                       limit.rlim_cur = val;
-               if (setrlimit(l->cmd, &limit) < 0)
-                       ash_msg_and_raise_error("error setting limit (%m)");
-       } else {
-               printlim(how, &limit, l);
-       }
-       return 0;
+       return shell_builtin_ulimit(argv);
 }
 
 /* ============ main() and helpers */
@@ -12871,14 +12839,15 @@ static void
 init(void)
 {
        /* from input.c: */
-       basepf.next_to_pgetc = basepf.buf = basebuf;
+       /* we will never free this */
+       basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ);
 
        /* from trap.c: */
        signal(SIGCHLD, SIG_DFL);
        /* bash re-enables SIGHUP which is SIG_IGNed on entry.
         * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$"
         */
-        signal(SIGHUP, SIG_DFL);
+       signal(SIGHUP, SIG_DFL);
 
        /* from var.c: */
        {
@@ -13051,10 +13020,12 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                if (e == EXERROR)
                        exitstatus = 2;
                s = state;
-               if (e == EXEXIT || s == 0 || iflag == 0 || shlvl)
+               if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) {
                        exitshell();
-               if (e == EXINT)
+               }
+               if (e == EXINT) {
                        outcslow('\n', stderr);
+               }
 
                popstackmark(&smark);
                FORCE_INT_ON; /* enable interrupts */
@@ -13092,7 +13063,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                }
        }
 #endif
-       if (/* argv[0] && */ argv[0][0] == '-')
+       if (argv[0] && argv[0][0] == '-')
                isloginsh = 1;
        if (isloginsh) {
                state = 1;
@@ -13121,8 +13092,9 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                 * Ensure we don't falsely claim that 0 (stdin)
                 * is one of stacked source fds.
                 * Testcase: ash -c 'exec 1>&0' must not complain. */
-               if (!sflag)
-                       g_parsefile->fd = -1;
+               // 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);
        }
 
@@ -13146,6 +13118,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                _mcleanup();
        }
 #endif
+       TRACE(("End of main reached\n"));
        exitshell();
        /* NOTREACHED */
 }