ash: trivial fixes for compile failures
[platform/upstream/busybox.git] / shell / ash.c
index dcac4fe..d42316a 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
 
-#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 "math.h"
+#if ENABLE_SH_MATH_SUPPORT
+# include "math.h"
+#endif
 #if ENABLE_ASH_RANDOM_SUPPORT
 # include "random.h"
 #else
 # error "Do not even bother, ash will not run on NOMMU machine"
 #endif
 
-//applet:IF_ASH(APPLET(ash, _BB_DIR_BIN, _BB_SUID_DROP))
-//applet:IF_FEATURE_SH_IS_ASH(APPLET_ODDNAME(sh, ash, _BB_DIR_BIN, _BB_SUID_DROP, sh))
-//applet:IF_FEATURE_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, _BB_DIR_BIN, _BB_SUID_DROP, bash))
-
-//kbuild:lib-$(CONFIG_ASH)      += ash.o ash_ptr_hack.o shell_common.o
-//kbuild:lib-$(CONFIG_ASH_RANDOM_SUPPORT) += random.o
-
 //config:config ASH
 //config:      bool "ash"
 //config:      default y
 //config:      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:        Enable job control in the ash shell.
 //config:
 //config:config ASH_ALIAS
-//config:      bool "alias support"
+//config:      bool "Alias support"
 //config:      default y
 //config:      depends on ASH
 //config:      help
 //config:      default y
 //config:      depends on ASH
 //config:      help
-//config:        Enable getopts builtin in the ash shell.
+//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 to ash.
+//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 to ash.
+//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 to ash.
+//config:        Enable support for test builtin in ash.
 //config:
 //config:config ASH_CMDCMD
 //config:      bool "'command' command to override shell builtins"
 //config:      default n
 //config:      depends on ASH
 //config:      help
-//config:        Enable "check for new mail" in the ash shell.
+//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:        variable each time it is displayed.
 //config:
 
-//usage:#define ash_trivial_usage NOUSAGE_STR
-//usage:#define ash_full_usage ""
-//usage:#define sh_trivial_usage NOUSAGE_STR
-//usage:#define sh_full_usage ""
-//usage:#define bash_trivial_usage NOUSAGE_STR
-//usage:#define bash_full_usage ""
+//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. */
@@ -398,6 +403,9 @@ static const char *var_end(const char *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
@@ -953,7 +961,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);
@@ -962,8 +971,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);
@@ -1875,7 +1886,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
@@ -1887,6 +1900,10 @@ static const struct {
        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"      , changemail      },
@@ -1982,10 +1999,6 @@ extern struct globals_var *const ash_ptr_to_globals_var;
 # 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)
@@ -1995,24 +2008,26 @@ getoptsreset(const char *value)
 }
 #endif
 
+/* math.h has these, otherwise define our private copies */
+#if !ENABLE_SH_MATH_SUPPORT
+#define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
 /*
- * Return of a legal variable name (a letter or underscore followed by zero or
- * more letters, underscores, and digits).
+ * Return the pointer to the first char which is not part of a legal variable name
+ * (a letter or underscore followed by letters, underscores, and digits).
  */
-static char* FAST_FUNC
+static const char*
 endofname(const char *name)
 {
-       char *p;
-
-       p = (char *) name;
-       if (!is_name(*p))
-               return p;
-       while (*++p) {
-               if (!is_in_name(*p))
+       if (!is_name(*name))
+               return name;
+       while (*++name) {
+               if (!is_in_name(*name))
                        break;
        }
-       return p;
+       return name;
 }
+#endif
 
 /*
  * Compares two strings up to the first = or '\0'.  The first
@@ -2195,9 +2210,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);
@@ -2211,12 +2227,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);
@@ -2430,12 +2447,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;
 
@@ -3478,13 +3496,18 @@ setsignal(int signo)
        switch (new_act) {
        case S_CATCH:
                act.sa_handler = signal_handler;
-               act.sa_flags = 0; /* matters only if !DFL and !IGN */
-               sigfillset(&act.sa_mask); /* ditto */
                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;
@@ -3521,12 +3544,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 */
@@ -3542,14 +3565,14 @@ set_curjob(struct job *jp, unsigned mode)
        case CUR_RUNNING:
                /* newly created job or backgrounded job,
                   put after all stopped jobs. */
-               do {
+               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:
@@ -3722,7 +3745,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:
@@ -3733,7 +3756,7 @@ setjobctl(int on)
                        if (pgrp == getpgrp())
                                break;
                        killpg(0, SIGTTIN);
-               } while (1);
+               }
                initialpgrp = pgrp;
 
                setsignal(SIGTSTP);
@@ -3765,18 +3788,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]);
        }
@@ -3874,6 +3930,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)");
@@ -4208,8 +4265,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();
@@ -4705,7 +4763,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)
@@ -4731,7 +4789,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);
@@ -4938,6 +4996,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 */
 
@@ -4989,9 +5049,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);
@@ -5046,15 +5110,14 @@ 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, 0666);
                if (f < 0)
                        goto ecreate;
@@ -5065,7 +5128,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;
@@ -5073,13 +5135,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;
@@ -5434,25 +5494,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;
@@ -5510,13 +5562,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;
 }
@@ -5687,7 +5744,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;
@@ -5695,9 +5752,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;
                }
@@ -5706,8 +5763,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;
@@ -5862,7 +5919,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;
@@ -5884,9 +5941,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));
 }
 
@@ -5914,7 +5971,7 @@ expari(int quotes)
        p = expdest - 1;
        *p = '\0';
        p--;
-       do {
+       while (1) {
                int esc;
 
                while ((unsigned char)*p != CTLARI) {
@@ -5932,7 +5989,7 @@ expari(int quotes)
                }
 
                p -= esc + 1;
-       } while (1);
+       }
 
        begoff = p - start;
 
@@ -6001,7 +6058,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);
@@ -6015,9 +6072,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++;
@@ -6064,8 +6119,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;
@@ -6084,7 +6139,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';
@@ -6100,47 +6157,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;
@@ -6148,35 +6178,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;
@@ -6242,7 +6259,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;
@@ -6262,7 +6279,7 @@ 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
@@ -6273,7 +6290,7 @@ parse_sub_pattern(char *arg, int varflags)
        unsigned char c;
 
        //char *org_arg = arg;
-       //bb_error_msg("arg:'%s'", arg);
+       //bb_error_msg("arg:'%s' varflags:%x", arg, varflags);
        idx = arg;
        while (1) {
                c = *arg;
@@ -6287,9 +6304,20 @@ parse_sub_pattern(char *arg, int varflags)
                        }
                }
                *idx++ = c;
-               if (!(varflags & VSQUOTE) && 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);
@@ -6310,12 +6338,13 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
        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);
+       //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,
@@ -6423,9 +6452,9 @@ subevalvar(char *p, char *varname, 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;
 
@@ -6748,8 +6777,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;
@@ -6821,8 +6850,7 @@ evalvar(char *p, int flags, struct strlist *var_str_list)
                patloc = expdest - (char *)stackblock();
                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 - (
@@ -7224,6 +7252,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);
@@ -7232,6 +7261,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
@@ -7242,8 +7272,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;
@@ -7371,8 +7403,6 @@ 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)) {
@@ -7396,25 +7426,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;
        }
 }
@@ -7431,9 +7478,7 @@ 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, /*end:*/ NULL);
@@ -7443,8 +7488,16 @@ shellexec(char **argv, const char *path, int idx)
 #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) {
@@ -7654,7 +7707,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;
@@ -8245,7 +8298,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?]
@@ -8435,11 +8488,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:
@@ -8615,6 +8672,7 @@ 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
@@ -8771,7 +8829,7 @@ 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);
@@ -9253,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) {
@@ -9429,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';
 }
 
 
@@ -9579,12 +9637,28 @@ preadfd(void)
 #if ENABLE_FEATURE_EDITING
  retry:
        if (!iflag || g_parsefile->pf_fd != STDIN_FILENO)
-               nr = nonblock_safe_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1);
+               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, IBUFSIZ, 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:
+                */
+               reinit_unicode(lookupvar("LANG"));
+               nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ, timeout);
                if (nr == 0) {
                        /* Ctrl+C pressed */
                        if (trap[SIGINT]) {
@@ -9595,17 +9669,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->pf_fd, buf, IBUFSIZ - 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);
@@ -10124,7 +10205,7 @@ options(int cmdline)
                                        else if (*argptr == NULL)
                                                setparam(argptr);
                                }
-                               break;    /* "-" or  "--" terminates options */
+                               break;    /* "-" or "--" terminates options */
                        }
                }
                /* first char was + or - */
@@ -10226,10 +10307,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);
@@ -11060,7 +11141,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);
@@ -11076,162 +11156,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
-                                       /* 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);
-                                       }
-                                       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 '))'");
@@ -11469,6 +11543,8 @@ parsesub: {
                                c = pgetc();
 #if ENABLE_ASH_BASH_COMPAT
                                if (c == ':' || c == '$' || isdigit(c)) {
+//TODO: support more general format ${v:EXPR:EXPR},
+// where EXPR follows $(()) rules
                                        subtype = VSSUBSTR;
                                        pungetc();
                                        break; /* "goto do_pungetc" is bigger (!) */
@@ -11496,6 +11572,9 @@ parsesub: {
                        }
 #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 != '/')
@@ -11562,16 +11641,14 @@ parsebackq: {
                   treatment to some slashes, and then push the string and
                   reread it as input, interpreting it normally.  */
                char *pout;
-               int pc;
                size_t psavelen;
                char *pstr;
 
-
                STARTSTACKSTR(pout);
                for (;;) {
-                       if (needprompt) {
-                               setprompt(2);
-                       }
+                       int pc;
+
+                       setprompt_if(needprompt, 2);
                        pc = pgetc();
                        switch (pc) {
                        case '`':
@@ -11581,8 +11658,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
@@ -11745,9 +11821,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();
@@ -11764,8 +11838,7 @@ xxreadtoken(void)
                                break; /* return readtoken1(...) */
                        }
                        startlinno = ++g_parsefile->linno;
-                       if (doprompt)
-                               setprompt(2);
+                       setprompt_if(doprompt, 2);
                } else {
                        const char *p;
 
@@ -11811,9 +11884,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();
@@ -11829,8 +11900,7 @@ xxreadtoken(void)
                case '\\':
                        if (pgetc() == '\n') {
                                startlinno = ++g_parsefile->linno;
-                               if (doprompt)
-                                       setprompt(2);
+                               setprompt_if(doprompt, 2);
                                continue;
                        }
                        pungetc();
@@ -11956,8 +12026,7 @@ parsecmd(int interact)
 
        tokpushback = 0;
        doprompt = interact;
-       if (doprompt)
-               setprompt(doprompt);
+       setprompt_if(doprompt, doprompt);
        needprompt = 0;
        t = readtoken();
        if (t == TEOF)
@@ -11981,10 +12050,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;
@@ -12104,9 +12171,7 @@ cmdloop(int top)
                inter = 0;
                if (iflag && top) {
                        inter++;
-#if ENABLE_ASH_MAIL
                        chkmail();
-#endif
                }
                n = parsecmd(inter);
 #if DEBUG
@@ -12366,7 +12431,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;
                        }
                }
@@ -12739,6 +12804,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 */
@@ -12748,6 +12817,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);
@@ -12836,7 +12906,6 @@ ulimitcmd(int argc UNUSED_PARAM, char **argv)
 /*
  * Called to exit the shell.
  */
-static void exitshell(void) NORETURN;
 static void
 exitshell(void)
 {
@@ -12844,6 +12913,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)) {
@@ -12881,7 +12954,7 @@ init(void)
        /* 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: */
        {
@@ -12899,14 +12972,32 @@ init(void)
                setvar("PPID", utoa(getppid()), 0);
 
                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.
  */
@@ -12924,7 +13015,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);
        }
@@ -13054,10 +13145,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 */
@@ -13084,10 +13177,9 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
        if (iflag) {
                const char *hp = lookupvar("HISTFILE");
-
-               if (hp == NULL) {
+               if (!hp) {
                        hp = lookupvar("HOME");
-                       if (hp != NULL) {
+                       if (hp) {
                                char *defhp = concat_path_file(hp, ".ash_history");
                                setvar("HISTFILE", defhp, 0);
                                free(defhp);
@@ -13095,7 +13187,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                }
        }
 #endif
-       if (/* argv[0] && */ argv[0][0] == '-')
+       if (argv[0] && argv[0][0] == '-')
                isloginsh = 1;
        if (isloginsh) {
                state = 1;
@@ -13131,11 +13223,15 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
        }
 
        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)
                                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 */
@@ -13150,6 +13246,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                _mcleanup();
        }
 #endif
+       TRACE(("End of main reached\n"));
        exitshell();
        /* NOTREACHED */
 }