Bump to version 1.22.1
[platform/upstream/busybox.git] / shell / ash.c
index ef22da1..71ef9a6 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.
  */
 
 /*
@@ -23,8 +23,9 @@
  *      define DEBUG=1 to compile in debugging ('set -o debug' to turn on)
  *      define DEBUG=2 to compile in and turn on debugging.
  *
- * When debugging is on, debugging info will be written to ./trace and
- * a quit signal will generate a core dump.
+ * When debugging is on (DEBUG is 1 and "set -o debug" was executed),
+ * debugging info will be written to ./trace and a quit signal
+ * will generate a core dump.
  */
 #define DEBUG 0
 /* Tweak debug output verbosity here */
 
 #define JOBS ENABLE_ASH_JOB_CONTROL
 
-#if DEBUG
-# ifndef _GNU_SOURCE
-#  define _GNU_SOURCE
-# endif
-#endif
-
-#include "busybox.h" /* for applet_names */
 #include <paths.h>
 #include <setjmp.h>
 #include <fnmatch.h>
+#include <sys/times.h>
+
+#include "busybox.h" /* for applet_names */
+#include "unicode.h"
 
 #include "shell_common.h"
-#include "builtin_read.h"
-#include "builtin_ulimit.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
 
-#define SKIP_definitions 1
-#include "applet_tables.h"
-#undef SKIP_definitions
+#include "NUM_APPLETS.h"
 #if NUM_APPLETS == 1
 /* STANDALONE does not make sense, and won't compile */
 # undef CONFIG_FEATURE_SH_STANDALONE
 # error "Do not even bother, ash will not run on NOMMU machine"
 #endif
 
+//config:config ASH
+//config:      bool "ash"
+//config:      default y
+//config:      depends on !NOMMU
+//config:      help
+//config:        Tha 'ash' shell adds about 60k in the default configuration and is
+//config:        the most complete and most pedantically correct shell included with
+//config:        busybox. This shell is actually a derivative of the Debian 'dash'
+//config:        shell (by Herbert Xu), which was created by porting the 'ash' shell
+//config:        (written by Kenneth Almquist) from NetBSD.
+//config:
+//config:config ASH_BASH_COMPAT
+//config:      bool "bash-compatible extensions"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Enable bash-compatible extensions.
+//config:
+//config:config ASH_IDLE_TIMEOUT
+//config:      bool "Idle timeout variable"
+//config:      default n
+//config:      depends on ASH
+//config:      help
+//config:        Enables bash-like auto-logout after $TMOUT seconds of idle time.
+//config:
+//config:config ASH_JOB_CONTROL
+//config:      bool "Job control"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Enable job control in the ash shell.
+//config:
+//config:config ASH_ALIAS
+//config:      bool "Alias support"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Enable alias support in the ash shell.
+//config:
+//config:config ASH_GETOPTS
+//config:      bool "Builtin getopt to parse positional parameters"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Enable support for getopts builtin in ash.
+//config:
+//config:config ASH_BUILTIN_ECHO
+//config:      bool "Builtin version of 'echo'"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Enable support for echo builtin in ash.
+//config:
+//config:config ASH_BUILTIN_PRINTF
+//config:      bool "Builtin version of 'printf'"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Enable support for printf builtin in ash.
+//config:
+//config:config ASH_BUILTIN_TEST
+//config:      bool "Builtin version of 'test'"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Enable support for test builtin in ash.
+//config:
+//config:config ASH_CMDCMD
+//config:      bool "'command' command to override shell builtins"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Enable support for the ash 'command' builtin, which allows
+//config:        you to run the specified command with the specified arguments,
+//config:        even when there is an ash builtin command with the same name.
+//config:
+//config:config ASH_MAIL
+//config:      bool "Check for new mail on interactive shells"
+//config:      default n
+//config:      depends on ASH
+//config:      help
+//config:        Enable "check for new mail" function in the ash shell.
+//config:
+//config:config ASH_OPTIMIZE_FOR_SIZE
+//config:      bool "Optimize for size instead of speed"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Compile ash for reduced size at the price of speed.
+//config:
+//config:config ASH_RANDOM_SUPPORT
+//config:      bool "Pseudorandom generator and $RANDOM variable"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        Enable pseudorandom generator and dynamic variable "$RANDOM".
+//config:        Each read of "$RANDOM" will generate a new pseudorandom value.
+//config:        You can reset the generator by using a specified start value.
+//config:        After "unset RANDOM" the generator will switch off and this
+//config:        variable will no longer have special treatment.
+//config:
+//config:config ASH_EXPAND_PRMT
+//config:      bool "Expand prompt string"
+//config:      default y
+//config:      depends on ASH
+//config:      help
+//config:        "PS#" may contain volatile content, such as backquote commands.
+//config:        This option recreates the prompt string from the environment
+//config:        variable each time it is displayed.
+//config:
+
+//applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
+//applet:IF_FEATURE_SH_IS_ASH(APPLET_ODDNAME(sh, ash, BB_DIR_BIN, BB_SUID_DROP, sh))
+//applet:IF_FEATURE_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, BB_DIR_BIN, BB_SUID_DROP, bash))
+
+//kbuild:lib-$(CONFIG_ASH) += ash.o ash_ptr_hack.o shell_common.o
+//kbuild:lib-$(CONFIG_ASH_RANDOM_SUPPORT) += random.o
+
 
 /* ============ Hash table sizes. Configurable. */
 
@@ -121,9 +235,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
@@ -208,6 +320,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" */
 
@@ -236,6 +349,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 )
@@ -271,6 +385,9 @@ static void trace_vprintf(const char *fmt, va_list va);
 /* ============ Utility functions */
 #define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0)
 
+#define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
+
 static int isdigit_str9(const char *str)
 {
        int maxlen = 9 + 1; /* max 9 digits: 999999999 */
@@ -279,8 +396,19 @@ 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 */
+
+static void exitshell(void) NORETURN;
+
 /*
  * These macros allow the user to suspend the handling of interrupt signals
  * over a period of time.  This is similar to SIGHOLD or to sigblock, but
@@ -333,7 +461,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]) {
@@ -836,7 +964,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);
@@ -845,8 +974,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);
@@ -1038,7 +1169,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 */
@@ -1064,7 +1195,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);
@@ -1712,8 +1843,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 */
 };
 
@@ -1758,7 +1889,9 @@ change_lc_ctype(const char *value)
 #endif
 #if ENABLE_ASH_MAIL
 static void chkmail(void);
-static void changemail(const char *) FAST_FUNC;
+static void changemail(const char *var_value) FAST_FUNC;
+#else
+# define chkmail()  ((void)0)
 #endif
 static void changepath(const char *) FAST_FUNC;
 #if ENABLE_ASH_RANDOM_SUPPORT
@@ -1767,13 +1900,17 @@ 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[] = {
+       /*
+        * Note: VEXPORT would not work correctly here for NOFORK applets:
+        * some environment strings may be constant.
+        */
        { 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            },
@@ -1783,14 +1920,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
 };
 
@@ -1817,9 +1954,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)
 
@@ -1850,25 +1987,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)
@@ -1879,25 +2012,6 @@ getoptsreset(const char *value)
 #endif
 
 /*
- * Return of a legal variable name (a letter or underscore followed by zero or
- * more letters, underscores, and digits).
- */
-static char* FAST_FUNC
-endofname(const char *name)
-{
-       char *p;
-
-       p = (char *) name;
-       if (!is_name(*p))
-               return p;
-       while (*++p) {
-               if (!is_in_name(*p))
-                       break;
-       }
-       return p;
-}
-
-/*
  * Compares two strings up to the first = or '\0'.  The first
  * string must be terminated by '='; the second may be terminated by
  * either '=' or '\0'.
@@ -1921,12 +2035,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.
  */
@@ -1961,15 +2069,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);
@@ -1979,7 +2087,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;
                }
        }
@@ -2003,11 +2111,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;
 }
@@ -2021,8 +2129,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);
 }
@@ -2048,24 +2156,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 */
@@ -2073,7 +2181,7 @@ setvareq(char *s, int flags)
        }
        if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
                s = ckstrdup(s);
-       vp->text = s;
+       vp->var_text = s;
        vp->flags = flags;
 }
 
@@ -2084,9 +2192,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);
@@ -2100,12 +2209,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);
@@ -2171,12 +2281,12 @@ 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;
                } else {
-                       setvar(s, 0, 0);
+                       setvar2(s, 0);
                        vp->flags &= ~VEXPORT;
                }
  ok:
@@ -2223,7 +2333,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);
@@ -2319,12 +2429,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;
 
@@ -2518,7 +2629,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;
@@ -2565,7 +2676,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;
 }
 
@@ -2581,7 +2692,7 @@ pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                        setpwd(dir, 0);
                dir = physdir;
        }
-       out1fmt(snlfmt, dir);
+       out1fmt("%s\n", dir);
        return 0;
 }
 
@@ -2589,9 +2700,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 */
@@ -3270,10 +3379,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;
 
@@ -3368,14 +3477,19 @@ setsignal(int signo)
        act.sa_handler = SIG_DFL;
        switch (new_act) {
        case S_CATCH:
-               act.sa_handler = onsig;
-               act.sa_flags = 0; /* matters only if !DFL and !IGN */
-               sigfillset(&act.sa_mask); /* ditto */
+               act.sa_handler = signal_handler;
                break;
        case S_IGN:
                act.sa_handler = SIG_IGN;
                break;
        }
+
+       /* flags and mask matter only if !DFL and !IGN, but we do it
+        * for all cases for more deterministic behavior:
+        */
+       act.sa_flags = 0;
+       sigfillset(&act.sa_mask);
+
        sigaction_set(signo, &act);
 
        *t = new_act;
@@ -3412,12 +3526,12 @@ set_curjob(struct job *jp, unsigned mode)
 
        /* first remove from list */
        jpp = curp = &curjob;
-       do {
+       while (1) {
                jp1 = *jpp;
                if (jp1 == jp)
                        break;
                jpp = &jp1->prev_job;
-       } while (1);
+       }
        *jpp = jp1->prev_job;
 
        /* Then re-insert in correct position */
@@ -3432,15 +3546,16 @@ set_curjob(struct job *jp, unsigned mode)
                break;
        case CUR_RUNNING:
                /* newly created job or backgrounded job,
-                  put after all stopped jobs. */
-               do {
+                * put after all stopped jobs.
+                */
+               while (1) {
                        jp1 = *jpp;
 #if JOBS
                        if (!jp1 || jp1->state != JOBSTOPPED)
 #endif
                                break;
                        jpp = &jp1->prev_job;
-               } while (1);
+               }
                /* FALLTHROUGH */
 #if JOBS
        case CUR_STOPPED:
@@ -3613,7 +3728,7 @@ setjobctl(int on)
                        goto out;
                /* fd is a tty at this point */
                close_on_exec_on(fd);
-               do { /* while we are in the background */
+               while (1) { /* while we are in the background */
                        pgrp = tcgetpgrp(fd);
                        if (pgrp < 0) {
  out:
@@ -3624,7 +3739,7 @@ setjobctl(int on)
                        if (pgrp == getpgrp())
                                break;
                        killpg(0, SIGTTIN);
-               } while (1);
+               }
                initialpgrp = pgrp;
 
                setsignal(SIGTSTP);
@@ -3656,18 +3771,51 @@ setjobctl(int on)
 static int FAST_FUNC
 killcmd(int argc, char **argv)
 {
-       int i = 1;
        if (argv[1] && strcmp(argv[1], "-l") != 0) {
+               int i = 1;
                do {
                        if (argv[i][0] == '%') {
-                               struct job *jp = getjob(argv[i], 0);
-                               unsigned pid = jp->ps[0].ps_pid;
-                               /* Enough space for ' -NNN<nul>' */
-                               argv[i] = alloca(sizeof(int)*3 + 3);
-                               /* kill_main has matching code to expect
-                                * leading space. Needed to not confuse
-                                * negative pids with "kill -SIGNAL_NO" syntax */
-                               sprintf(argv[i], " -%u", pid);
+                               /*
+                                * "kill %N" - job kill
+                                * Converting to pgrp / pid kill
+                                */
+                               struct job *jp;
+                               char *dst;
+                               int j, n;
+
+                               jp = getjob(argv[i], 0);
+                               /*
+                                * In jobs started under job control, we signal
+                                * entire process group by kill -PGRP_ID.
+                                * This happens, f.e., in interactive shell.
+                                *
+                                * Otherwise, we signal each child via
+                                * kill PID1 PID2 PID3.
+                                * Testcases:
+                                * sh -c 'sleep 1|sleep 1 & kill %1'
+                                * sh -c 'true|sleep 2 & sleep 1; kill %1'
+                                * sh -c 'true|sleep 1 & sleep 2; kill %1'
+                                */
+                               n = jp->nprocs; /* can't be 0 (I hope) */
+                               if (jp->jobctl)
+                                       n = 1;
+                               dst = alloca(n * sizeof(int)*4);
+                               argv[i] = dst;
+                               for (j = 0; j < n; j++) {
+                                       struct procstat *ps = &jp->ps[j];
+                                       /* Skip non-running and not-stopped members
+                                        * (i.e. dead members) of the job
+                                        */
+                                       if (ps->ps_status != -1 && !WIFSTOPPED(ps->ps_status))
+                                               continue;
+                                       /*
+                                        * kill_main has matching code to expect
+                                        * leading space. Needed to not confuse
+                                        * negative pids with "kill -SIGNAL_NO" syntax
+                                        */
+                                       dst += sprintf(dst, jp->jobctl ? " -%u" : " %u", (int)ps->ps_pid);
+                               }
+                               *dst = '\0';
                        }
                } while (argv[++i]);
        }
@@ -3765,6 +3913,7 @@ sprint_status(char *s, int status, int sigonly)
 #endif
                }
                st &= 0x7f;
+//TODO: use bbox's get_signame? strsignal adds ~600 bytes to text+rodata
                col = fmtstr(s, 32, strsignal(st));
                if (WCOREDUMP(status)) {
                        col += fmtstr(s + col, 16, " (core dumped)");
@@ -4099,8 +4248,9 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
                                        break;
                                job = job->prev_job;
                        }
-               } else
+               } else {
                        job = getjob(*argv, 0);
+               }
                /* loop until process terminated or stopped */
                while (job->state == JOBRUNNING)
                        blocking_wait_with_raise_on_sig();
@@ -4524,6 +4674,7 @@ clear_traps(void)
                        INT_ON;
                }
        }
+       may_have_traps = 0;
 }
 
 /* Lives far away from here, needed for forkchild */
@@ -4595,7 +4746,7 @@ forkchild(struct job *jp, union node *n, int mode)
 #if JOBS
        /* do job control only in root shell */
        doing_jobctl = 0;
-       if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) {
+       if (mode != FORK_NOJOB && jp->jobctl && oldlvl == 0) {
                pid_t pgrp;
 
                if (jp->nprocs == 0)
@@ -4621,7 +4772,7 @@ forkchild(struct job *jp, union node *n, int mode)
                                ash_msg_and_raise_error("can't open '%s'", bb_dev_null);
                }
        }
-       if (!oldlvl) {
+       if (oldlvl == 0) {
                if (iflag) { /* why if iflag only? */
                        setsignal(SIGINT);
                        setsignal(SIGTERM);
@@ -4828,6 +4979,8 @@ stoppedjobs(void)
  * Code for dealing with input/output redirection.
  */
 
+#undef EMPTY
+#undef CLOSED
 #define EMPTY -2                /* marks an unused slot in redirtab */
 #define CLOSED -3               /* marks a slot of previously-closed fd */
 
@@ -4879,9 +5032,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,16 +5093,15 @@ openredirect(union node *redir)
        char *fname;
        int f;
 
+       fname = redir->nfile.expfname;
        switch (redir->nfile.type) {
        case NFROM:
-               fname = redir->nfile.expfname;
                f = open(fname, O_RDONLY);
                if (f < 0)
                        goto eopen;
                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;
@@ -4955,7 +5111,6 @@ openredirect(union node *redir)
 #endif
                /* Take care of noclobber mode. */
                if (Cflag) {
-                       fname = redir->nfile.expfname;
                        f = noclobberopen(fname);
                        if (f < 0)
                                goto ecreate;
@@ -4963,13 +5118,11 @@ openredirect(union node *redir)
                }
                /* FALLTHROUGH */
        case NCLOBBER:
-               fname = redir->nfile.expfname;
                f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666);
                if (f < 0)
                        goto ecreate;
                break;
        case NAPPEND:
-               fname = redir->nfile.expfname;
                f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666);
                if (f < 0)
                        goto ecreate;
@@ -5065,15 +5218,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) {
@@ -5086,9 +5250,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 */
@@ -5134,13 +5296,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);
@@ -5162,7 +5326,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)
@@ -5310,25 +5477,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;
@@ -5386,13 +5545,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;
 }
@@ -5563,7 +5727,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;
@@ -5571,9 +5735,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;
                }
@@ -5582,8 +5746,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;
@@ -5625,7 +5789,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)
@@ -5738,7 +5902,7 @@ expbackq(union node *cmd, int quoted, int quotes)
  read:
                if (in.fd < 0)
                        break;
-               i = nonblock_safe_read(in.fd, buf, sizeof(buf));
+               i = nonblock_immune_read(in.fd, buf, sizeof(buf), /*loop_on_EINTR:*/ 1);
                TRACE(("expbackq: read returns %d\n", i));
                if (i <= 0)
                        break;
@@ -5760,9 +5924,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));
 }
 
@@ -5790,7 +5954,7 @@ expari(int quotes)
        p = expdest - 1;
        *p = '\0';
        p--;
-       do {
+       while (1) {
                int esc;
 
                while ((unsigned char)*p != CTLARI) {
@@ -5808,7 +5972,7 @@ expari(int quotes)
                }
 
                p -= esc + 1;
-       } while (1);
+       }
 
        begoff = p - start;
 
@@ -5877,7 +6041,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);
@@ -5891,9 +6055,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++;
@@ -5940,8 +6102,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;
@@ -5960,7 +6122,9 @@ argstr(char *p, int flags, struct strlist *var_str_list)
                        length++;
                        goto addquote;
                case CTLVAR:
+                       TRACE(("argstr: evalvar('%s')\n", p));
                        p = evalvar(p, flags, var_str_list);
+                       TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock()));
                        goto start;
                case CTLBACKQ:
                        c = '\0';
@@ -5976,47 +6140,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;
@@ -6024,35 +6161,22 @@ 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 *pattern, int quotes, int match_at_start)
+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;
@@ -6118,7 +6242,7 @@ scanright(char *startp, char *rmesc, char *rmescend, char *pattern, int quotes,
                        }
                }
        }
-       return 0;
+       return NULL;
 }
 
 static void varunset(const char *, const char *, const char *, int) NORETURN;
@@ -6138,16 +6262,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;
@@ -6161,32 +6287,48 @@ 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 amount, resetloc;
+       IF_ASH_BASH_COMPAT(int workloc;)
        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);
@@ -6197,11 +6339,15 @@ subevalvar(char *p, char *str, int strloc, int subtype,
 
        switch (subtype) {
        case VSASSIGN:
-               setvar(str, startp, 0);
+               setvar2(varname, startp);
                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;
@@ -6262,11 +6408,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
@@ -6292,21 +6435,22 @@ subevalvar(char *p, char *str, int strloc, int subtype,
        rmescend--;
        str = (char *)stackblock() + strloc;
        preglob(str, varflags & VSQUOTE, 0);
-       workloc = expdest - (char *)stackblock();
 
 #if ENABLE_ASH_BASH_COMPAT
+       workloc = expdest - (char *)stackblock();
        if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
                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] == '\0')
-                       return 0;
+                       return NULL;
 
                len = 0;
                idx = startp;
@@ -6314,6 +6458,7 @@ subevalvar(char *p, char *str, int strloc, int subtype,
                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 */
                                char *restart_detect = stackblock();
@@ -6352,8 +6497,13 @@ subevalvar(char *p, char *str, int strloc, int subtype,
                                idx = loc;
                        }
 
-                       for (loc = repl; *loc; loc++) {
+                       //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;
@@ -6361,6 +6511,7 @@ subevalvar(char *p, char *str, int strloc, int subtype,
                        }
 
                        if (subtype == VSREPLACE) {
+                               //bb_error_msg("tail:'%s', quotes:%x", idx, quotes);
                                while (*idx) {
                                        char *restart_detect = stackblock();
                                        STPUTC(*idx, expdest);
@@ -6376,11 +6527,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;
        }
@@ -6593,7 +6744,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);
@@ -6609,8 +6760,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;
@@ -6680,10 +6831,9 @@ 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),
+                               /* quotes: */ flags & (EXP_FULL | EXP_CASE | EXP_REDIR),
                                var_str_list)
                ) {
                        int amount = expdest - (
@@ -6843,13 +6993,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;
@@ -6948,7 +7096,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);
                        }
                }
        }
@@ -7030,6 +7178,7 @@ expandmeta(struct strlist *str /*, int flag*/)
        /* TODO - EXP_REDIR */
 
        while (str) {
+               char *expdir;
                struct strlist **savelastp;
                struct strlist *sp;
                char *p;
@@ -7046,8 +7195,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);
@@ -7087,6 +7235,7 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
        STARTSTACKSTR(expdest);
        ifsfirst.next = NULL;
        ifslastp = NULL;
+       TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag));
        argstr(arg->narg.text, flag,
                        /* var_str_list: */ arglist ? arglist->list : NULL);
        p = _STPUTC('\0', expdest);
@@ -7095,6 +7244,7 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
                return;                 /* here document expanded */
        }
        p = grabstackstr(p);
+       TRACE(("expandarg: p:'%s'\n", p));
        exparg.lastp = &exparg.list;
        /*
         * TODO - EXP_REDIR
@@ -7105,8 +7255,10 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
                exparg.lastp = &exparg.list;
                expandmeta(exparg.list /*, flag*/);
        } else {
-               if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
+               if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
                        rmescapes(p, 0);
+                       TRACE(("expandarg: rmescapes:'%s'\n", p));
+               }
                sp = stzalloc(sizeof(*sp));
                sp->text = p;
                *exparg.lastp = sp;
@@ -7234,11 +7386,10 @@ static int builtinloc = -1;     /* index in path of %builtin, or -1 */
 static void
 tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **envp)
 {
-       int repeated = 0;
-
 #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);
@@ -7258,25 +7409,42 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
 #else
        execve(cmd, argv, envp);
 #endif
-       if (repeated) {
+       if (cmd == (char*) bb_busybox_exec_path) {
+               /* We already visited ENOEXEC branch below, don't do it again */
+//TODO: try execve(initial_argv0_of_shell, argv, envp) before giving up?
                free(argv);
                return;
        }
        if (errno == ENOEXEC) {
+               /* Run "cmd" as a shell script:
+                * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
+                * "If the execve() function fails with ENOEXEC, the shell
+                * shall execute a command equivalent to having a shell invoked
+                * with the command name as its first operand,
+                * with any remaining arguments passed to the new shell"
+                *
+                * That is, do not use $SHELL, user's shell, or /bin/sh;
+                * just call ourselves.
+                *
+                * Note that bash reads ~80 chars of the file, and if it sees
+                * a zero byte before it sees newline, it doesn't try to
+                * interpret it, but fails with "cannot execute binary file"
+                * message and exit code 126. For one, this prevents attempts
+                * to interpret foreign ELF binaries as shell scripts.
+                */
                char **ap;
                char **new;
 
                for (ap = argv; *ap; ap++)
                        continue;
-               ap = new = ckmalloc((ap - argv + 2) * sizeof(ap[0]));
-               ap[1] = cmd;
-               ap[0] = cmd = (char *)DEFAULT_SHELL;
-               ap += 2;
-               argv++;
-               while ((*ap++ = *argv++) != NULL)
+               new = ckmalloc((ap - argv + 2) * sizeof(new[0]));
+               new[0] = (char*) "ash";
+               new[1] = cmd;
+               ap = new + 2;
+               while ((*ap++ = *++argv) != NULL)
                        continue;
+               cmd = (char*) bb_busybox_exec_path;
                argv = new;
-               repeated++;
                goto repeat;
        }
 }
@@ -7293,20 +7461,26 @@ shellexec(char **argv, const char *path, int idx)
        int e;
        char **envp;
        int exerrno;
-#if ENABLE_FEATURE_SH_STANDALONE
-       int applet_no = -1;
-#endif
+       int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */
 
        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
 #endif
        ) {
                tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) argv[0], argv, envp);
+               if (applet_no >= 0) {
+                       /* We tried execing ourself, but it didn't work.
+                        * Maybe /proc/self/exe doesn't exist?
+                        * Try $PATH search.
+                        */
+                       goto try_PATH;
+               }
                e = errno;
        } else {
+ try_PATH:
                e = ENOENT;
                while ((cmdname = path_advance(&path, argv[0])) != NULL) {
                        if (--idx < 0 && pathopt == NULL) {
@@ -7516,7 +7690,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;
@@ -7547,8 +7721,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')
@@ -7557,7 +7733,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 */
@@ -7633,23 +7810,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)
@@ -8121,7 +8281,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?]
@@ -8311,11 +8471,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:
@@ -8390,7 +8554,7 @@ evalfor(union node *n, int flags)
        loopnest++;
        flags &= EV_TESTED;
        for (sp = arglist.list; sp; sp = sp->next) {
-               setvar(n->nfor.var, sp->text, 0);
+               setvar2(n->nfor.var, sp->text);
                evaltree(n->nfor.body, flags);
                if (evalskip) {
                        if (evalskip == SKIPCONT && --skipcount <= 0) {
@@ -8445,15 +8609,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);
@@ -8490,9 +8655,21 @@ expredir(union node *n)
                case NCLOBBER:
                case NAPPEND:
                        expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
+                       TRACE(("expredir expanded to '%s'\n", fn.list->text));
 #if ENABLE_ASH_BASH_COMPAT
  store_expfname:
 #endif
+#if 0
+// By the design of stack allocator, the loop of this kind:
+//     while true; do while true; do break; done </dev/null; done
+// will look like a memory leak: ash plans to free expfname's
+// of "/dev/null" as soon as it finishes running the loop
+// (in this case, never).
+// This "fix" is wrong:
+                       if (redir->nfile.expfname)
+                               stunalloc(redir->nfile.expfname);
+// It results in corrupted state of stacked allocations.
+#endif
                        redir->nfile.expfname = fn.list->text;
                        break;
                case NFROMFD:
@@ -8646,20 +8823,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);
        }
@@ -8778,7 +8955,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)
@@ -8856,6 +9033,9 @@ static int getoptscmd(int, char **) FAST_FUNC;
 #if !ENABLE_FEATURE_SH_EXTRA_QUIET
 static int helpcmd(int, char **) FAST_FUNC;
 #endif
+#if MAX_HISTORY
+static int historycmd(int, char **) FAST_FUNC;
+#endif
 #if ENABLE_SH_MATH_SUPPORT
 static int letcmd(int, char **) FAST_FUNC;
 #endif
@@ -8929,6 +9109,9 @@ static const struct builtincmd builtintab[] = {
 #if !ENABLE_FEATURE_SH_EXTRA_QUIET
        { BUILTIN_NOSPEC        "help"    , helpcmd    },
 #endif
+#if MAX_HISTORY
+       { BUILTIN_NOSPEC        "history" , historycmd },
+#endif
 #if JOBS
        { BUILTIN_REGULAR       "jobs"    , jobscmd    },
        { BUILTIN_REGULAR       "kill"    , killcmd    },
@@ -8946,7 +9129,9 @@ static const struct builtincmd builtintab[] = {
        { BUILTIN_SPEC_REG      "return"  , returncmd  },
        { BUILTIN_SPEC_REG      "set"     , setcmd     },
        { BUILTIN_SPEC_REG      "shift"   , shiftcmd   },
+#if ENABLE_ASH_BASH_COMPAT
        { BUILTIN_SPEC_REG      "source"  , dotcmd     },
+#endif
 #if ENABLE_ASH_BUILTIN_TEST
        { BUILTIN_REGULAR       "test"    , testcmd    },
 #endif
@@ -9086,7 +9271,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;
@@ -9099,26 +9284,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;
                }
@@ -9130,11 +9311,11 @@ evalcommand(union node *cmd, int flags)
 
        /* Now locate the command. */
        if (argc) {
-               const char *oldpath;
                int cmd_flag = DO_ERR;
-
+#if ENABLE_ASH_CMDCMD
+               const char *oldpath = path + 5;
+#endif
                path += 5;
-               oldpath = path;
                for (;;) {
                        find_command(argv[0], &cmdentry, cmd_flag, path);
                        if (cmdentry.cmdtype == CMDUNKNOWN) {
@@ -9176,12 +9357,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)) {
@@ -9190,24 +9373,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) {
@@ -9252,7 +9441,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);
@@ -9261,7 +9451,7 @@ evalcommand(union node *cmd, int flags)
                 * '_' in 'vi' command mode during line editing...
                 * However I implemented that within libedit itself.
                 */
-               setvar("_", lastarg, 0);
+               setvar2("_", lastarg);
        }
        popstackmark(&smark);
 }
@@ -9297,7 +9487,7 @@ evalbltin(const struct builtincmd *cmd, int argc, char **argv)
 static int
 goodname(const char *p)
 {
-       return !*endofname(p);
+       return endofname(p)[0] == '\0';
 }
 
 
@@ -9446,13 +9636,34 @@ 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_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1, /*loop_on_EINTR:*/ 1);
        else {
-#if ENABLE_FEATURE_TAB_COMPLETION
+               int timeout = -1;
+# if ENABLE_ASH_IDLE_TIMEOUT
+               if (iflag) {
+                       const char *tmout_var = lookupvar("TMOUT");
+                       if (tmout_var) {
+                               timeout = atoi(tmout_var) * 1000;
+                               if (timeout <= 0)
+                                       timeout = -1;
+                       }
+               }
+# endif
+# if ENABLE_FEATURE_TAB_COMPLETION
                line_input_state->path_lookup = pathval();
-#endif
-               nr = read_line_input(cmdedit_prompt, buf, BUFSIZ, line_input_state);
+# endif
+               /* Unicode support should be activated even if LANG is set
+                * _during_ shell execution, not only if it was set when
+                * shell was started. Therefore, re-check LANG every time:
+                */
+               {
+                       const char *s = lookupvar("LC_ALL");
+                       if (!s) s = lookupvar("LC_CTYPE");
+                       if (!s) s = lookupvar("LANG");
+                       reinit_unicode(s);
+               }
+               nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ, timeout);
                if (nr == 0) {
                        /* Ctrl+C pressed */
                        if (trap[SIGINT]) {
@@ -9463,17 +9674,24 @@ preadfd(void)
                        }
                        goto retry;
                }
-               if (nr < 0 && errno == 0) {
-                       /* Ctrl+D pressed */
-                       nr = 0;
+               if (nr < 0) {
+                       if (errno == 0) {
+                               /* Ctrl+D pressed */
+                               nr = 0;
+                       }
+# if ENABLE_ASH_IDLE_TIMEOUT
+                       else if (errno == EAGAIN && timeout > 0) {
+                               printf("\007timed out waiting for input: auto-logout\n");
+                               exitshell();
+                       }
+# endif
                }
        }
 #else
-       nr = nonblock_safe_read(g_parsefile->fd, buf, BUFSIZ - 1);
+       nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1, /*loop_on_EINTR:*/ 1);
 #endif
 
-#if 0
-/* nonblock_safe_read() handles this problem */
+#if 0 /* disabled: nonblock_immune_read() handles this problem */
        if (nr < 0) {
                if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
                        int flags = fcntl(0, F_GETFL);
@@ -9696,7 +9914,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;
@@ -9708,8 +9926,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();
@@ -9736,9 +9954,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;
        }
 }
 
@@ -9754,7 +9972,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;
@@ -9859,7 +10077,7 @@ chkmail(void)
                }
                if (!mail_var_path_changed && statb.st_mtime != *mtp) {
                        fprintf(
-                               stderr, snlfmt,
+                               stderr, "%s\n",
                                pathopt ? pathopt : "you have mail"
                        );
                }
@@ -9992,7 +10210,7 @@ options(int cmdline)
                                        else if (*argptr == NULL)
                                                setparam(argptr);
                                }
-                               break;    /* "-" or  "--" terminates options */
+                               break;    /* "-" or "--" terminates options */
                        }
                }
                /* first char was + or - */
@@ -10094,10 +10312,10 @@ setcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 
        if (!argv[1])
                return showvars(nullstr, 0, VUNSET);
+
        INT_OFF;
-       retval = 1;
-       if (!options(0)) { /* if no parse error... */
-               retval = 0;
+       retval = options(/*cmdline:*/ 0);
+       if (retval == 0) { /* if no parse error... */
                optschanged();
                if (*argptr != NULL) {
                        setparam(argptr);
@@ -10117,7 +10335,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 */
@@ -10268,7 +10486,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.
@@ -10278,11 +10505,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 */
 }
@@ -10670,7 +10898,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);
                }
@@ -10918,7 +11146,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);
@@ -10934,160 +11161,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 '))'");
@@ -11248,8 +11471,6 @@ parsesub: {
        unsigned char subtype;
        int typeloc;
        int flags;
-       char *p;
-       static const char types[] ALIGN1 = "}-+?=";
 
        c = pgetc();
        if (c > 255 /* PEOA or PEOF */
@@ -11262,7 +11483,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();
@@ -11274,6 +11496,7 @@ parsesub: {
                        PARSEBACKQNEW();
                }
        } else {
+               /* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
                USTPUTC(CTLVAR, out);
                typeloc = out - (char *)stackblock();
                USTPUTC(VSNORMAL, out);
@@ -11283,76 +11506,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)
@@ -11406,19 +11643,18 @@ parsebackq: {
        INT_ON;
        if (oldstyle) {
                /* We must read until the closing backquote, giving special
-                  treatment to some slashes, and then push the string and
-                  reread it as input, interpreting it normally.  */
+                * 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 '`':
@@ -11428,8 +11664,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
@@ -11592,9 +11827,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();
@@ -11611,8 +11844,7 @@ xxreadtoken(void)
                                break; /* return readtoken1(...) */
                        }
                        startlinno = ++g_parsefile->linno;
-                       if (doprompt)
-                               setprompt(2);
+                       setprompt_if(doprompt, 2);
                } else {
                        const char *p;
 
@@ -11658,9 +11890,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();
@@ -11676,8 +11906,7 @@ xxreadtoken(void)
                case '\\':
                        if (pgetc() == '\n') {
                                startlinno = ++g_parsefile->linno;
-                               if (doprompt)
-                                       setprompt(2);
+                               setprompt_if(doprompt, 2);
                                continue;
                        }
                        pungetc();
@@ -11754,7 +11983,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;
                }
        }
@@ -11775,9 +12004,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;
 }
@@ -11803,8 +12032,7 @@ parsecmd(int interact)
 
        tokpushback = 0;
        doprompt = interact;
-       if (doprompt)
-               setprompt(doprompt);
+       setprompt_if(doprompt, doprompt);
        needprompt = 0;
        t = readtoken();
        if (t == TEOF)
@@ -11828,10 +12056,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;
@@ -11922,7 +12148,6 @@ evalcmd(int argc UNUSED_PARAM, char **argv)
                        p = grabstackstr(concat);
                }
                evalstring(p, ~SKIPEVAL);
-
        }
        return exitstatus;
 }
@@ -11952,9 +12177,7 @@ cmdloop(int top)
                inter = 0;
                if (iflag && top) {
                        inter++;
-#if ENABLE_ASH_MAIL
                        chkmail();
-#endif
                }
                n = parsecmd(inter);
 #if DEBUG
@@ -12031,36 +12254,48 @@ 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;
+
+       /* This aborts if file isn't found, which is POSIXly correct.
+        * bash returns exitcode 1 instead.
+        */
+       fullname = find_dot_file(argv[1]);
+       argv += 2;
+       argc -= 2;
+       if (argc) { /* argc > 0, argv[0] != NULL */
+               saveparam = shellparam;
+               shellparam.malloced = 0;
+               shellparam.nparam = argc;
+               shellparam.p = argv;
+       };
+
+       /* This aborts if file can't be opened, which is POSIXly correct.
+        * bash returns exitcode 1 instead.
+        */
+       setinputfile(fullname, INPUT_PUSH_FILE);
+       commandname = fullname;
+       cmdloop(0);
+       popfile();
+
+       if (argc) {
+               freeparam(&shellparam);
+               shellparam = saveparam;
+       };
+
+       return exitstatus;
 }
 
 static int FAST_FUNC
@@ -12207,7 +12442,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;
                        }
                }
@@ -12342,6 +12577,8 @@ 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);
@@ -12394,6 +12631,15 @@ helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 }
 #endif /* FEATURE_SH_EXTRA_QUIET */
 
+#if MAX_HISTORY
+static int FAST_FUNC
+historycmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+       show_history(line_input_state);
+       return EXIT_SUCCESS;
+}
+#endif
+
 /*
  * The export and readonly commands.
  */
@@ -12404,9 +12650,27 @@ exportcmd(int argc UNUSED_PARAM, char **argv)
        char *name;
        const char *p;
        char **aptr;
-       int flag = argv[0][0] == 'r' ? VREADONLY : VEXPORT;
+       char opt;
+       int flag;
+       int flag_off;
+
+       /* "readonly" in bash accepts, but ignores -n.
+        * We do the same: it saves a conditional in nextopt's param.
+        */
+       flag_off = 0;
+       while ((opt = nextopt("np")) != '\0') {
+               if (opt == 'n')
+                       flag_off = VEXPORT;
+       }
+       flag = VEXPORT;
+       if (argv[0][0] == 'r') {
+               flag = VREADONLY;
+               flag_off = 0; /* readonly ignores -n */
+       }
+       flag_off = ~flag_off;
 
-       if (nextopt("p") != 'p') {
+       /*if (opt_p_not_specified) - bash doesnt check this. Try "export -p NAME" */
+       {
                aptr = argptr;
                name = *aptr;
                if (name) {
@@ -12417,15 +12681,19 @@ exportcmd(int argc UNUSED_PARAM, char **argv)
                                } else {
                                        vp = *findvar(hashvar(name), name);
                                        if (vp) {
-                                               vp->flags |= flag;
+                                               vp->flags = ((vp->flags | flag) & flag_off);
                                                continue;
                                        }
                                }
-                               setvar(name, p, flag);
+                               setvar(name, p, (flag & flag_off));
                        } while ((name = *++aptr) != NULL);
                        return 0;
                }
        }
+
+       /* No arguments. Show the list of exported or readonly vars.
+        * -n is ignored.
+        */
        showvars(argv[0], flag, 0);
        return 0;
 }
@@ -12439,7 +12707,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();
 }
 
@@ -12456,7 +12724,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;
        }
 
@@ -12473,11 +12741,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),
@@ -12485,11 +12748,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;
 
@@ -12500,18 +12762,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>
@@ -12530,18 +12794,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
 
 /*
@@ -12594,6 +12846,10 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                }
        }
 
+       /* "read -s" needs to save/restore termios, can't allow ^C
+        * to jump out of it.
+        */
+       INT_OFF;
        r = shell_builtin_read(setvar2,
                argptr,
                bltinlookup("IFS"), /* can be NULL */
@@ -12603,6 +12859,7 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                opt_t,
                opt_u
        );
+       INT_ON;
 
        if ((uintptr_t)r > 1)
                ash_msg_and_raise_error(r);
@@ -12691,7 +12948,6 @@ ulimitcmd(int argc UNUSED_PARAM, char **argv)
 /*
  * Called to exit the shell.
  */
-static void exitshell(void) NORETURN;
 static void
 exitshell(void)
 {
@@ -12699,6 +12955,10 @@ exitshell(void)
        char *p;
        int status;
 
+#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+       save_history(line_input_state);
+#endif
+
        status = exitstatus;
        TRACE(("pid %d, exitshell(%d)\n", getpid(), status));
        if (setjmp(loc.loc)) {
@@ -12728,14 +12988,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: */
        {
@@ -12750,17 +13011,38 @@ init(void)
                        }
                }
 
-               setvar("PPID", utoa(getppid()), 0);
-
+               setvar2("PPID", utoa(getppid()));
+#if ENABLE_ASH_BASH_COMPAT
+               p = lookupvar("SHLVL");
+               setvar2("SHLVL", utoa(p ? atoi(p) + 1 : 1));
+#endif
                p = lookupvar("PWD");
-               if (p)
+               if (p) {
                        if (*p != '/' || stat(p, &st1) || stat(".", &st2)
-                        || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino)
+                        || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino
+                       ) {
                                p = '\0';
+                       }
+               }
                setpwd(p, 0);
        }
 }
 
+
+//usage:#define ash_trivial_usage
+//usage:       "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
+//usage:#define ash_full_usage "\n\n"
+//usage:       "Unix shell interpreter"
+
+//usage:#if ENABLE_FEATURE_SH_IS_ASH
+//usage:# define sh_trivial_usage ash_trivial_usage
+//usage:# define sh_full_usage    ash_full_usage
+//usage:#endif
+//usage:#if ENABLE_FEATURE_BASH_IS_ASH
+//usage:# define bash_trivial_usage ash_trivial_usage
+//usage:# define bash_full_usage    ash_full_usage
+//usage:#endif
+
 /*
  * Process the shell command line arguments.
  */
@@ -12778,7 +13060,7 @@ procargs(char **argv)
        for (i = 0; i < NOPTS; i++)
                optlist[i] = 2;
        argptr = xargv;
-       if (options(1)) {
+       if (options(/*cmdline:*/ 1)) {
                /* it already printed err message */
                raise_exception(EXERROR);
        }
@@ -12908,10 +13190,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 */
@@ -12935,28 +13219,21 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
        setstackmark(&smark);
        procargs(argv);
 
-#if ENABLE_FEATURE_EDITING_SAVEHISTORY
-       if (iflag) {
-               const char *hp = lookupvar("HISTFILE");
-
-               if (hp == NULL) {
-                       hp = lookupvar("HOME");
-                       if (hp != NULL) {
-                               char *defhp = concat_path_file(hp, ".ash_history");
-                               setvar("HISTFILE", defhp, 0);
-                               free(defhp);
-                       }
-               }
-       }
-#endif
-       if (/* argv[0] && */ argv[0][0] == '-')
+       if (argv[0] && argv[0][0] == '-')
                isloginsh = 1;
        if (isloginsh) {
+               const char *hp;
+
                state = 1;
                read_profile("/etc/profile");
  state1:
                state = 2;
-               read_profile(".profile");
+               hp = lookupvar("HOME");
+               if (hp) {
+                       hp = concat_path_file(hp, ".profile");
+                       read_profile(hp);
+                       free((char*)hp);
+               }
        }
  state2:
        state = 3;
@@ -12978,17 +13255,31 @@ 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);
        }
 
        if (sflag || minusc == NULL) {
-#if defined MAX_HISTORY && MAX_HISTORY > 0 && ENABLE_FEATURE_EDITING_SAVEHISTORY
+#if MAX_HISTORY > 0 && ENABLE_FEATURE_EDITING_SAVEHISTORY
                if (iflag) {
                        const char *hp = lookupvar("HISTFILE");
+                       if (!hp) {
+                               hp = lookupvar("HOME");
+                               if (hp) {
+                                       hp = concat_path_file(hp, ".ash_history");
+                                       setvar2("HISTFILE", hp);
+                                       free((char*)hp);
+                                       hp = lookupvar("HISTFILE");
+                               }
+                       }
                        if (hp)
                                line_input_state->hist_file = hp;
+# if ENABLE_FEATURE_SH_HISTFILESIZE
+                       hp = lookupvar("HISTFILESIZE");
+                       line_input_state->max_history = size_from_HISTFILESIZE(hp);
+# endif
                }
 #endif
  state4: /* XXX ??? - why isn't this before the "if" statement */
@@ -13003,6 +13294,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                _mcleanup();
        }
 #endif
+       TRACE(("End of main reached\n"));
        exitshell();
        /* NOTREACHED */
 }