hush: make expand_vars_to_list() a bit more sane
[oweals/busybox.git] / shell / ash.c
index 4c1b5e409e5416b05a2591cebe4ed0a338ce8676..051cc671fcef4a21d68a36ccea38c8251ce476fc 100644 (file)
 
 #define JOBS ENABLE_ASH_JOB_CONTROL
 
-#include <setjmp.h>
 #include <fnmatch.h>
 #include <sys/times.h>
 #include <sys/utsname.h> /* for setting $HOSTNAME */
 #define IF_BASH_PATTERN_SUBST       IF_ASH_BASH_COMPAT
 #define    BASH_SUBSTR          ENABLE_ASH_BASH_COMPAT
 #define IF_BASH_SUBSTR              IF_ASH_BASH_COMPAT
-/* [[ EXPR ]] */
+/* BASH_TEST2: [[ EXPR ]]
+ * Status of [[ support:
+ * We replace && and || with -a and -o
+ * TODO:
+ * singleword+noglob expansion:
+ *   v='a b'; [[ $v = 'a b' ]]; echo 0:$?
+ *   [[ /bin/n* ]]; echo 0:$?
+ * -a/-o are not AND/OR ops! (they are just strings)
+ * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc)
+ * = is glob match operator, not equality operator: STR = GLOB
+ * (in GLOB, quoting is significant on char-by-char basis: a*cd"*")
+ * == same as =
+ * add =~ regex match operator: STR =~ REGEX
+ */
 #define    BASH_TEST2           (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST)
 #define    BASH_SOURCE          ENABLE_ASH_BASH_COMPAT
 #define    BASH_PIPEFAIL        ENABLE_ASH_BASH_COMPAT
@@ -255,6 +267,12 @@ typedef long arith_t;
 # define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
 #endif
 
+#ifndef F_DUPFD_CLOEXEC
+# define F_DUPFD_CLOEXEC F_DUPFD
+#endif
+#ifndef O_CLOEXEC
+# define O_CLOEXEC 0
+#endif
 #ifndef PIPE_BUF
 # define PIPE_BUF 4096           /* amount of buffering in a pipe */
 #endif
@@ -3969,12 +3987,13 @@ setjobctl(int on)
                                        goto out;
                }
                /* fd is a tty at this point */
-               fd = fcntl(fd, F_DUPFD, 10);
+               fd = fcntl(fd, F_DUPFD_CLOEXEC, 10);
                if (ofd >= 0) /* if it is "/dev/tty", close. If 0/1/2, don't */
                        close(ofd);
                if (fd < 0)
                        goto out; /* F_DUPFD failed */
-               close_on_exec_on(fd);
+               if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */
+                       close_on_exec_on(fd);
                while (1) { /* while we are in the background */
                        pgrp = tcgetpgrp(fd);
                        if (pgrp < 0) {
@@ -5384,7 +5403,7 @@ openredirect(union node *redir)
                                f = open(fname, O_WRONLY, 0666);
                                if (f < 0)
                                        goto ecreate;
-                               if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) {
+                               if (!fstat(f, &sb) && S_ISREG(sb.st_mode)) {
                                        close(f);
                                        errno = EEXIST;
                                        goto ecreate;
@@ -5424,13 +5443,14 @@ savefd(int from)
        int newfd;
        int err;
 
-       newfd = fcntl(from, F_DUPFD, 10);
+       newfd = fcntl(from, F_DUPFD_CLOEXEC, 10);
        err = newfd < 0 ? errno : 0;
        if (err != EBADF) {
                if (err)
                        ash_msg_and_raise_perror("%d", from);
                close(from);
-               fcntl(newfd, F_SETFD, FD_CLOEXEC);
+               if (F_DUPFD_CLOEXEC == F_DUPFD)
+                       close_on_exec_on(newfd);
        }
 
        return newfd;
@@ -5448,12 +5468,15 @@ dup2_or_raise(int from, int to)
        return newfd;
 }
 static int
-fcntl_F_DUPFD(int fd, int avoid_fd)
+dup_CLOEXEC(int fd, int avoid_fd)
 {
        int newfd;
  repeat:
-       newfd = fcntl(fd, F_DUPFD, avoid_fd + 1);
-       if (newfd < 0) {
+       newfd = fcntl(fd, F_DUPFD_CLOEXEC, avoid_fd + 1);
+       if (newfd >= 0) {
+               if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */
+                       close_on_exec_on(newfd);
+       } else { /* newfd < 0 */
                if (errno == EBUSY)
                        goto repeat;
                if (errno == EINTR)
@@ -5466,7 +5489,7 @@ xdup_CLOEXEC_and_close(int fd, int avoid_fd)
 {
        int newfd;
  repeat:
-       newfd = fcntl(fd, F_DUPFD, avoid_fd + 1);
+       newfd = fcntl(fd, F_DUPFD_CLOEXEC, avoid_fd + 1);
        if (newfd < 0) {
                if (errno == EBUSY)
                        goto repeat;
@@ -5477,7 +5500,8 @@ xdup_CLOEXEC_and_close(int fd, int avoid_fd)
                        return fd;
                ash_msg_and_raise_perror("%d", newfd);
        }
-       fcntl(newfd, F_SETFD, FD_CLOEXEC);
+       if (F_DUPFD_CLOEXEC == F_DUPFD)
+               close_on_exec_on(newfd);
        close(fd);
        return newfd;
 }
@@ -5569,7 +5593,7 @@ save_fd_on_redirect(int fd, int avoid_fd, struct redirtab *sq)
        for (i = 0; sq->two_fd[i].orig_fd != EMPTY; i++) {
                /* If we collide with an already moved fd... */
                if (fd == sq->two_fd[i].moved_to) {
-                       new_fd = fcntl_F_DUPFD(fd, avoid_fd);
+                       new_fd = dup_CLOEXEC(fd, avoid_fd);
                        sq->two_fd[i].moved_to = new_fd;
                        TRACE(("redirect_fd %d: already busy, moving to %d\n", fd, new_fd));
                        if (new_fd < 0) /* what? */
@@ -5584,7 +5608,7 @@ save_fd_on_redirect(int fd, int avoid_fd, struct redirtab *sq)
        }
 
        /* If this fd is open, we move and remember it; if it's closed, new_fd = CLOSED (-1) */
-       new_fd = fcntl_F_DUPFD(fd, avoid_fd);
+       new_fd = dup_CLOEXEC(fd, avoid_fd);
        TRACE(("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, new_fd));
        if (new_fd < 0) {
                if (errno != EBADF)
@@ -5876,10 +5900,9 @@ static int substr_atoi(const char *s)
  * performs globbing, and thus diverges from what we do).
  */
 #define EXP_CASE        0x10    /* keeps quotes around for CASE pattern */
-#define EXP_QPAT        0x20    /* pattern in quoted parameter expansion */
-#define EXP_VARTILDE2   0x40    /* expand tildes after colons only */
-#define EXP_WORD        0x80    /* expand word in parameter expansion */
-#define EXP_QUOTED      0x100   /* expand word in double quotes */
+#define EXP_VARTILDE2   0x20    /* expand tildes after colons only */
+#define EXP_WORD        0x40    /* expand word in parameter expansion */
+#define EXP_QUOTED      0x80    /* expand word in double quotes */
 /*
  * rmescape() flags
  */
@@ -5889,7 +5912,7 @@ static int substr_atoi(const char *s)
 #define RMESCAPE_HEAP   0x10    /* Malloc strings instead of stalloc */
 
 /* Add CTLESC when necessary. */
-#define QUOTES_ESC     (EXP_FULL | EXP_CASE | EXP_QPAT)
+#define QUOTES_ESC     (EXP_FULL | EXP_CASE)
 /* Do not skip NUL characters. */
 #define QUOTES_KEEPNUL EXP_TILDE
 
@@ -5964,7 +5987,10 @@ ifsbreakup(char *string, struct arglist *arglist)
                realifs = ifsset() ? ifsval() : defifs;
                ifsp = &ifsfirst;
                do {
+                       int afternul;
+
                        p = string + ifsp->begoff;
+                       afternul = nulonly;
                        nulonly = ifsp->nulonly;
                        ifs = nulonly ? nullstr : realifs;
                        ifsspc = 0;
@@ -5976,7 +6002,7 @@ ifsbreakup(char *string, struct arglist *arglist)
                                        p++;
                                        continue;
                                }
-                               if (!nulonly)
+                               if (!(afternul || nulonly))
                                        ifsspc = (strchr(defifs, *p) != NULL);
                                /* Ignore IFS whitespace at start */
                                if (q == start && ifsspc) {
@@ -6078,7 +6104,6 @@ rmescapes(char *str, int flag, int *slash_position)
                IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' };
 
        char *p, *q, *r;
-       unsigned inquotes;
        unsigned protect_against_glob;
        unsigned globbing;
 
@@ -6109,18 +6134,21 @@ rmescapes(char *str, int flag, int *slash_position)
                }
        }
 
-       inquotes = 0;
        globbing = flag & RMESCAPE_GLOB;
        protect_against_glob = globbing;
        while (*p) {
                if ((unsigned char)*p == CTLQUOTEMARK) {
-// Note: both inquotes and protect_against_glob only affect whether
+// Note: protect_against_glob only affect whether
 // CTLESC,<ch> gets converted to <ch> or to \<ch>
-                       inquotes = ~inquotes;
                        p++;
                        protect_against_glob = globbing;
                        continue;
                }
+               if (*p == '\\') {
+                       /* naked back slash */
+                       protect_against_glob = 0;
+                       goto copy;
+               }
                if ((unsigned char)*p == CTLESC) {
                        p++;
 #if DEBUG
@@ -6146,20 +6174,16 @@ rmescapes(char *str, int flag, int *slash_position)
                                if (*p == '*'
                                 || *p == '?'
                                 || *p == '['
-                                || *p == '\\' /* case '\' in \\ ) echo ok;; *) echo WRONG;; esac */
-                                || *p == ']' /* case ']' in [a\]] ) echo ok;; *) echo WRONG;; esac */
-                                || *p == '-' /* case '-' in [a\-c]) echo ok;; *) echo WRONG;; esac */
-                                || *p == '!' /* case '!' in [\!] ) echo ok;; *) echo WRONG;; esac */
+                                || *p == '\\' /* case '\' in \\    ) echo ok;; *) echo WRONG;; esac */
+                                || *p == ']'  /* case ']' in [a\]] ) echo ok;; *) echo WRONG;; esac */
+                                || *p == '-'  /* case '-' in [a\-c]) echo ok;; *) echo WRONG;; esac */
+                                || *p == '!'  /* case '!' in [\!]  ) echo ok;; *) echo WRONG;; esac */
                                /* Some libc support [^negate], that's why "^" also needs love */
-                                || *p == '^' /* case '^' in [\^] ) echo ok;; *) echo WRONG;; esac */
+                                || *p == '^'  /* case '^' in [\^]  ) echo ok;; *) echo WRONG;; esac */
                                ) {
                                        *q++ = '\\';
                                }
                        }
-               } else if (*p == '\\' && !inquotes) {
-                       /* naked back slash */
-                       protect_against_glob = 0;
-                       goto copy;
                }
 #if BASH_PATTERN_SUBST
                else if (slash_position && p == str + *slash_position) {
@@ -6641,12 +6665,12 @@ argstr(char *p, int flags)
                case CTLENDVAR: /* ??? */
                        goto breakloop;
                case CTLQUOTEMARK:
-                       inquotes ^= EXP_QUOTED;
                        /* "$@" syntax adherence hack */
-                       if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
-                               p = evalvar(p + 1, flags | inquotes) + 1;
+                       if (!inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
+                               p = evalvar(p + 1, flags | EXP_QUOTED) + 1;
                                goto start;
                        }
+                       inquotes ^= EXP_QUOTED;
  addquote:
                        if (flags & QUOTES_ESC) {
                                p--;
@@ -6657,16 +6681,6 @@ argstr(char *p, int flags)
                case CTLESC:
                        startloc++;
                        length++;
-
-                       /*
-                        * Quoted parameter expansion pattern: remove quote
-                        * unless inside inner quotes or we have a literal
-                        * backslash.
-                        */
-                       if (((flags | inquotes) & (EXP_QPAT | EXP_QUOTED)) ==
-                           EXP_QPAT && *p != '\\')
-                               break;
-
                        goto addquote;
                case CTLVAR:
                        TRACE(("argstr: evalvar('%s')\n", p));
@@ -6857,15 +6871,24 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
        }
 #endif
        argstr_flags = EXP_TILDE;
-       if (subtype != VSASSIGN && subtype != VSQUESTION)
-               argstr_flags |= (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE);
+       if (subtype != VSASSIGN
+        && subtype != VSQUESTION
+#if BASH_SUBSTR
+        && subtype != VSSUBSTR
+#endif
+       ) {
+               /* EXP_CASE keeps CTLESC's */
+               argstr_flags = EXP_TILDE | EXP_CASE;
+       }
        argstr(p, argstr_flags);
+       //bb_error_msg("str0:'%s'", (char *)stackblock() + strloc);
 #if BASH_PATTERN_SUBST
        slash_pos = -1;
        if (repl) {
                slash_pos = expdest - ((char *)stackblock() + strloc);
                STPUTC('/', expdest);
-               argstr(repl + 1, argstr_flags);
+               //bb_error_msg("repl+1:'%s'", repl + 1);
+               argstr(repl + 1, EXP_TILDE); /* EXP_TILDE: echo "${v/x/~}" expands ~ ! */
                *repl = '/';
        }
 #endif
@@ -7180,7 +7203,7 @@ varvalue(char *name, int varflags, int flags, int *quotedp)
        case '-':
                expdest = makestrspace(NOPTS, expdest);
                for (i = NOPTS - 1; i >= 0; i--) {
-                       if (optlist[i]) {
+                       if (optlist[i] && optletters(i)) {
                                USTPUTC(optletters(i), expdest);
                                len++;
                        }
@@ -7427,13 +7450,13 @@ hasmeta(const char *p)
                p = strpbrk(p, chars);
                if (!p)
                        break;
-               switch ((unsigned char) *p) {
+               switch ((unsigned char)*p) {
                case CTLQUOTEMARK:
                        for (;;) {
                                p++;
-                               if (*p == CTLQUOTEMARK)
+                               if ((unsigned char)*p == CTLQUOTEMARK)
                                        break;
-                               if (*p == CTLESC)
+                               if ((unsigned char)*p == CTLESC)
                                        p++;
                                if (*p == '\0') /* huh? */
                                        return 0;
@@ -7552,9 +7575,16 @@ expandmeta(struct strlist *str /*, int flag*/)
 /*
  * Do metacharacter (i.e. *, ?, [...]) expansion.
  */
+typedef struct exp_t {
+       char *dir;
+       unsigned dir_max;
+} exp_t;
 static void
-expmeta(char *expdir, char *enddir, char *name)
+expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len)
 {
+#define expdir exp->dir
+#define expdir_max exp->dir_max
+       char *enddir = expdir + expdir_len;
        char *p;
        const char *cp;
        char *start;
@@ -7597,15 +7627,15 @@ expmeta(char *expdir, char *enddir, char *name)
                }
        }
        if (metaflag == 0) {    /* we've reached the end of the file name */
-               if (enddir != expdir)
-                       metaflag++;
+               if (!expdir_len)
+                       return;
                p = name;
                do {
                        if (*p == '\\')
                                p++;
                        *enddir++ = *p;
                } while (*p++);
-               if (metaflag == 0 || lstat(expdir, &statb) >= 0)
+               if (lstat(expdir, &statb) == 0)
                        addfname(expdir);
                return;
        }
@@ -7618,19 +7648,14 @@ expmeta(char *expdir, char *enddir, char *name)
                        *enddir++ = *p++;
                } while (p < start);
        }
-       if (enddir == expdir) {
+       *enddir = '\0';
+       cp = expdir;
+       expdir_len = enddir - cp;
+       if (!expdir_len)
                cp = ".";
-       } else if (enddir == expdir + 1 && *expdir == '/') {
-               cp = "/";
-       } else {
-               cp = expdir;
-               enddir[-1] = '\0';
-       }
        dirp = opendir(cp);
        if (dirp == NULL)
                return;
-       if (enddir != expdir)
-               enddir[-1] = '/';
        if (*endname == 0) {
                atend = 1;
        } else {
@@ -7638,6 +7663,7 @@ expmeta(char *expdir, char *enddir, char *name)
                *endname = '\0';
                endname += esc + 1;
        }
+       name_len -= endname - name;
        matchdot = 0;
        p = start;
        if (*p == '\\')
@@ -7652,16 +7678,30 @@ expmeta(char *expdir, char *enddir, char *name)
                                strcpy(enddir, dp->d_name);
                                addfname(expdir);
                        } else {
-                               for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';)
-                                       continue;
-                               p[-1] = '/';
-                               expmeta(expdir, p, endname);
+                               unsigned offset;
+                               unsigned len;
+
+                               p = stpcpy(enddir, dp->d_name);
+                               *p = '/';
+
+                               offset = p - expdir + 1;
+                               len = offset + name_len + NAME_MAX;
+                               if (len > expdir_max) {
+                                       len += PATH_MAX;
+                                       expdir = ckrealloc(expdir, len);
+                                       expdir_max = len;
+                               }
+
+                               expmeta(exp, endname, name_len, offset);
+                               enddir = expdir + expdir_len;
                        }
                }
        }
        closedir(dirp);
        if (!atend)
                endname[-esc - 1] = esc ? '\\' : '/';
+#undef expdir
+#undef expdir_max
 }
 
 static struct strlist *
@@ -7734,10 +7774,11 @@ expandmeta(struct strlist *str /*, int flag*/)
        /* TODO - EXP_REDIR */
 
        while (str) {
-               char *expdir;
+               exp_t exp;
                struct strlist **savelastp;
                struct strlist *sp;
                char *p;
+               unsigned len;
 
                if (fflag)
                        goto nometa;
@@ -7747,13 +7788,12 @@ expandmeta(struct strlist *str /*, int flag*/)
 
                INT_OFF;
                p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
-               {
-                       int i = strlen(str->text);
-//BUGGY estimation of how long expanded name can be
-                       expdir = ckmalloc(i < 2048 ? 2048 : i+1);
-               }
-               expmeta(expdir, expdir, p);
-               free(expdir);
+               len = strlen(p);
+               exp.dir_max = len + PATH_MAX;
+               exp.dir = ckmalloc(exp.dir_max);
+
+               expmeta(&exp, p, len, 0);
+               free(exp.dir);
                if (p != str->text)
                        free(p);
                INT_ON;
@@ -10657,6 +10697,34 @@ pgetc_eatbnl(void)
        return c;
 }
 
+struct synstack {
+       smalluint syntax;
+       uint8_t innerdq   :1;
+       uint8_t varpushed :1;
+       uint8_t dblquote  :1;
+       int varnest;            /* levels of variables expansion */
+       int dqvarnest;          /* levels of variables expansion within double quotes */
+       int parenlevel;         /* levels of parens in arithmetic */
+       struct synstack *prev;
+       struct synstack *next;
+};
+
+static void
+synstack_push(struct synstack **stack, struct synstack *next, int syntax)
+{
+       memset(next, 0, sizeof(*next));
+       next->syntax = syntax;
+       next->next = *stack;
+       (*stack)->prev = next;
+       *stack = next;
+}
+
+static ALWAYS_INLINE void
+synstack_pop(struct synstack **stack)
+{
+       *stack = (*stack)->next;
+}
+
 /*
  * To handle the "." command, a stack of input files is used.  Pushfile
  * adds a new entry to the stack and popfile restores the previous level.
@@ -10747,7 +10815,7 @@ setinputfile(const char *fname, int flags)
        int fd;
 
        INT_OFF;
-       fd = open(fname, O_RDONLY);
+       fd = open(fname, O_RDONLY | O_CLOEXEC);
        if (fd < 0) {
                if (flags & INPUT_NOFILE_OK)
                        goto out;
@@ -10756,8 +10824,9 @@ setinputfile(const char *fname, int flags)
        }
        if (fd < 10)
                fd = savefd(fd);
-       else
+       else if (O_CLOEXEC == 0) /* old libc */
                close_on_exec_on(fd);
+
        setinputfd(fd, flags & INPUT_PUSH_FILE);
  out:
        INT_ON;
@@ -11572,10 +11641,12 @@ simplecmd(void)
                                case TLP:
                                        function_flag = 0;
                                        break;
+# if BASH_TEST2
                                case TWORD:
                                        if (strcmp("[[", wordtext) == 0)
                                                goto do_func;
                                        /* fall through */
+# endif
                                default:
                                        raise_error_unexpected_syntax(-1);
                                }
@@ -11623,7 +11694,8 @@ simplecmd(void)
        *vpp = NULL;
        *rpp = NULL;
        n = stzalloc(sizeof(struct ncmd));
-       n->type = NCMD;
+       if (NCMD != 0)
+               n->type = NCMD;
        n->ncmd.linno = savelinno;
        n->ncmd.args = args;
        n->ncmd.assign = vars;
@@ -11915,19 +11987,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
        size_t len;
        struct nodelist *bqlist;
        smallint quotef;
-       smallint dblquote;
        smallint oldstyle;
-       IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */
        smallint pssyntax;   /* we are expanding a prompt string */
-       int varnest;         /* levels of variables expansion */
-       IF_FEATURE_SH_MATH(int arinest;)    /* levels of arithmetic expansion */
-       IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
-       int dqvarnest;       /* levels of variables expansion within double quotes */
        IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
+       /* syntax stack */
+       struct synstack synbase = { };
+       struct synstack *synstack = &synbase;
 
-       bqlist = NULL;
-       quotef = 0;
-       IF_FEATURE_SH_MATH(prevsyntax = 0;)
 #if ENABLE_ASH_EXPAND_PRMT
        pssyntax = (syntax == PSSYNTAX);
        if (pssyntax)
@@ -11935,11 +12001,12 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
 #else
        pssyntax = 0; /* constant */
 #endif
-       dblquote = (syntax == DQSYNTAX);
-       varnest = 0;
-       IF_FEATURE_SH_MATH(arinest = 0;)
-       IF_FEATURE_SH_MATH(parenlevel = 0;)
-       dqvarnest = 0;
+       synstack->syntax = syntax;
+
+       if (syntax == DQSYNTAX)
+               synstack->dblquote = 1;
+       quotef = 0;
+       bqlist = NULL;
 
        STARTSTACKSTR(out);
  loop:
@@ -11947,10 +12014,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
        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)) {
+               switch (SIT(c, synstack->syntax)) {
                case CNL:       /* '\n' */
-                       if (syntax == BASESYNTAX)
+                       if (synstack->syntax == BASESYNTAX
+                        && !synstack->varnest
+                       ) {
                                goto endword;   /* exit outer loop */
+                       }
                        USTPUTC(c, out);
                        nlprompt();
                        c = pgetc();
@@ -11969,13 +12039,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                                if (c & 0x100) {
                                        /* Unknown escape. Encode as '\z' */
                                        c = (unsigned char)c;
-                                       if (eofmark == NULL || dblquote)
+                                       if (eofmark == NULL || synstack->dblquote)
                                                USTPUTC(CTLESC, out);
                                        USTPUTC('\\', out);
                                }
                        }
 #endif
-                       if (eofmark == NULL || dblquote)
+                       if (!eofmark || synstack->dblquote || synstack->varnest)
                                USTPUTC(CTLESC, out);
                        USTPUTC(c, out);
                        break;
@@ -11992,13 +12062,17 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                                        USTPUTC(CTLESC, out);
                                        USTPUTC('\\', out);
                                }
-                               /* Backslash is retained if we are in "str" and next char isn't special */
-                               if (dblquote
+                               /* Backslash is retained if we are in "str"
+                                * and next char isn't dquote-special.
+                                */
+                               if (synstack->dblquote
                                 && c != '\\'
                                 && c != '`'
                                 && c != '$'
-                                && (c != '"' || eofmark != NULL)
+                                && (c != '"' || (eofmark != NULL && !synstack->varnest))
+                                && (c != '}' || !synstack->varnest)
                                ) {
+                                       USTPUTC(CTLESC, out); /* protect '\' from glob */
                                        USTPUTC('\\', out);
                                }
                                USTPUTC(CTLESC, out);
@@ -12007,56 +12081,62 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                        }
                        break;
                case CSQUOTE:
-                       syntax = SQSYNTAX;
+                       synstack->syntax = SQSYNTAX;
  quotemark:
                        if (eofmark == NULL) {
                                USTPUTC(CTLQUOTEMARK, out);
                        }
                        break;
                case CDQUOTE:
-                       syntax = DQSYNTAX;
-                       dblquote = 1;
+                       synstack->syntax = DQSYNTAX;
+                       synstack->dblquote = 1;
+ toggledq:
+                       if (synstack->varnest)
+                               synstack->innerdq ^= 1;
                        goto quotemark;
                case CENDQUOTE:
                        IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;)
-                       if (eofmark != NULL && varnest == 0) {
+                       if (eofmark != NULL && synstack->varnest == 0) {
                                USTPUTC(c, out);
-                       } else {
-                               if (dqvarnest == 0) {
-                                       syntax = BASESYNTAX;
-                                       dblquote = 0;
-                               }
-                               quotef = 1;
-                               goto quotemark;
+                               break;
                        }
-                       break;
+
+                       if (synstack->dqvarnest == 0) {
+                               synstack->syntax = BASESYNTAX;
+                               synstack->dblquote = 0;
+                       }
+
+                       quotef = 1;
+
+                       if (c == '"')
+                               goto toggledq;
+
+                       goto quotemark;
                case CVAR:      /* '$' */
                        PARSESUB();             /* parse substitution */
                        break;
                case CENDVAR:   /* '}' */
-                       if (varnest > 0) {
-                               varnest--;
-                               if (dqvarnest > 0) {
-                                       dqvarnest--;
-                               }
+                       if (!synstack->innerdq && synstack->varnest > 0) {
+                               if (!--synstack->varnest && synstack->varpushed)
+                                       synstack_pop(&synstack);
+                               else if (synstack->dqvarnest > 0)
+                                       synstack->dqvarnest--;
                                c = CTLENDVAR;
                        }
                        USTPUTC(c, out);
                        break;
 #if ENABLE_FEATURE_SH_MATH
                case CLP:       /* '(' in arithmetic */
-                       parenlevel++;
+                       synstack->parenlevel++;
                        USTPUTC(c, out);
                        break;
                case CRP:       /* ')' in arithmetic */
-                       if (parenlevel > 0) {
-                               parenlevel--;
+                       if (synstack->parenlevel > 0) {
+                               synstack->parenlevel--;
                        } else {
                                if (pgetc_eatbnl() == ')') {
                                        c = CTLENDARI;
-                                       if (--arinest == 0) {
-                                               syntax = prevsyntax;
-                                       }
+                                       synstack_pop(&synstack);
                                } else {
                                        /*
                                         * unbalanced parens
@@ -12069,6 +12149,12 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                        break;
 #endif
                case CBQUOTE:   /* '`' */
+                       if (checkkwd & CHKEOFMARK) {
+                               quotef = 1;
+                               USTPUTC('`', out);
+                               break;
+                       }
+
                        PARSEBACKQOLD();
                        break;
                case CENDFILE:
@@ -12076,7 +12162,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                case CIGN:
                        break;
                default:
-                       if (varnest == 0) {
+                       if (synstack->varnest == 0) {
 #if BASH_REDIR_OUTPUT
                                if (c == '&') {
 //Can't call pgetc_eatbnl() here, this requires three-deep pungetc()
@@ -12095,12 +12181,12 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
  endword:
 
 #if ENABLE_FEATURE_SH_MATH
-       if (syntax == ARISYNTAX)
+       if (synstack->syntax == ARISYNTAX)
                raise_error_syntax("missing '))'");
 #endif
-       if (syntax != BASESYNTAX && eofmark == NULL)
+       if (synstack->syntax != BASESYNTAX && eofmark == NULL)
                raise_error_syntax("unterminated quoted string");
-       if (varnest != 0) {
+       if (synstack->varnest != 0) {
                /* { */
                raise_error_syntax("missing '}'");
        }
@@ -12201,7 +12287,7 @@ parseredir: {
        np = stzalloc(sizeof(struct nfile));
        if (c == '>') {
                np->nfile.fd = 1;
-               c = pgetc();
+               c = pgetc_eatbnl();
                if (c == '>')
                        np->type = NAPPEND;
                else if (c == '|')
@@ -12223,7 +12309,7 @@ parseredir: {
 #endif
        else { /* c == '<' */
                /*np->nfile.fd = 0; - stzalloc did it */
-               c = pgetc();
+               c = pgetc_eatbnl();
                switch (c) {
                case '<':
                        if (sizeof(struct nfile) != sizeof(struct nhere)) {
@@ -12233,7 +12319,7 @@ parseredir: {
                        np->type = NHERE;
                        heredoc = stzalloc(sizeof(struct heredoc));
                        heredoc->here = np;
-                       c = pgetc();
+                       c = pgetc_eatbnl();
                        if (c == '-') {
                                heredoc->striptabs = 1;
                        } else {
@@ -12282,7 +12368,7 @@ parsesub: {
         || (c != '(' && c != '{' && !is_name(c) && !is_special(c))
        ) {
 #if BASH_DOLLAR_SQUOTE
-               if (syntax != DQSYNTAX && c == '\'')
+               if (synstack->syntax != DQSYNTAX && c == '\'')
                        bash_dollar_squote = 1;
                else
 #endif
@@ -12302,6 +12388,8 @@ parsesub: {
                }
        } else {
                /* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
+               smalluint newsyn = synstack->syntax;
+
                USTPUTC(CTLVAR, out);
                typeloc = out - (char *)stackblock();
                STADJUST(1, out);
@@ -12360,6 +12448,8 @@ parsesub: {
                        static const char types[] ALIGN1 = "}-+?=";
                        /* ${VAR...} but not $VAR or ${#VAR} */
                        /* c == first char after VAR */
+                       int cc = c;
+
                        switch (c) {
                        case ':':
                                c = pgetc_eatbnl();
@@ -12384,21 +12474,24 @@ parsesub: {
                                break;
                        }
                        case '%':
-                       case '#': {
-                               int cc = c;
+                       case '#':
                                subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT);
                                c = pgetc_eatbnl();
-                               if (c != cc)
-                                       goto badsub;
-                               subtype++;
+                               if (c == cc)
+                                       subtype++;
+                               else
+                                       pungetc();
+
+                               newsyn = BASESYNTAX;
                                break;
-                       }
 #if BASH_PATTERN_SUBST
                        case '/':
                                /* ${v/[/]pattern/repl} */
 //TODO: encode pattern and repl separately.
-// Currently ${v/$var_with_slash/repl} is horribly broken
+// Currently cases like: v=1;echo ${v/$((1/1))/ONE}
+// are broken (should print "ONE")
                                subtype = VSREPLACE;
+                               newsyn = BASESYNTAX;
                                c = pgetc_eatbnl();
                                if (c != '/')
                                        goto badsub;
@@ -12410,11 +12503,26 @@ parsesub: {
  badsub:
                        pungetc();
                }
+
+               if (newsyn == ARISYNTAX)
+                       newsyn = DQSYNTAX;
+
+               if ((newsyn != synstack->syntax || synstack->innerdq)
+                && subtype != VSNORMAL
+               ) {
+                       synstack_push(&synstack,
+                               synstack->prev ?: alloca(sizeof(*synstack)),
+                               newsyn);
+
+                       synstack->varpushed = 1;
+                       synstack->dblquote = newsyn != BASESYNTAX;
+               }
+
                ((unsigned char *)stackblock())[typeloc] = subtype;
                if (subtype != VSNORMAL) {
-                       varnest++;
-                       if (dblquote)
-                               dqvarnest++;
+                       synstack->varnest++;
+                       if (synstack->dblquote)
+                               synstack->dqvarnest++;
                }
                STPUTC('=', out);
        }
@@ -12463,25 +12571,15 @@ parsebackq: {
                        int pc;
 
                        setprompt_if(needprompt, 2);
-                       pc = pgetc();
+                       pc = pgetc_eatbnl();
                        switch (pc) {
                        case '`':
                                goto done;
 
                        case '\\':
-                               pc = pgetc();
-                               if (pc == '\n') {
-                                       nlprompt();
-                                       /*
-                                        * If eating a newline, avoid putting
-                                        * the newline into the new character
-                                        * stream (via the STPUTC after the
-                                        * switch).
-                                        */
-                                       continue;
-                               }
+                               pc = pgetc(); /* or pgetc_eatbnl()? why (example)? */
                                if (pc != '\\' && pc != '`' && pc != '$'
-                                && (!dblquote || pc != '"')
+                                && (!synstack->dblquote || pc != '"')
                                ) {
                                        STPUTC('\\', pout);
                                }
@@ -12556,10 +12654,11 @@ parsebackq: {
  * Parse an arithmetic expansion (indicate start of one and set state)
  */
 parsearith: {
-       if (++arinest == 1) {
-               prevsyntax = syntax;
-               syntax = ARISYNTAX;
-       }
+
+       synstack_push(&synstack,
+                       synstack->prev ?: alloca(sizeof(*synstack)),
+                       ARISYNTAX);
+       synstack->dblquote = 1;
        USTPUTC(CTLARI, out);
        goto parsearith_return;
 }
@@ -12611,7 +12710,7 @@ xxreadtoken(void)
        }
        setprompt_if(needprompt, 2);
        for (;;) {                      /* until token or start of word found */
-               c = pgetc();
+               c = pgetc_eatbnl();
                if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA))
                        continue;
 
@@ -12620,11 +12719,7 @@ xxreadtoken(void)
                                continue;
                        pungetc();
                } else if (c == '\\') {
-                       if (pgetc() != '\n') {
-                               pungetc();
-                               break; /* return readtoken1(...) */
-                       }
-                       nlprompt();
+                       break; /* return readtoken1(...) */
                } else {
                        const char *p;
 
@@ -12639,7 +12734,7 @@ xxreadtoken(void)
                                        break; /* return readtoken1(...) */
 
                                if ((int)(p - xxreadtoken_chars) >= xxreadtoken_singles) {
-                                       int cc = pgetc();
+                                       int cc = pgetc_eatbnl();
                                        if (cc == c) {    /* double occurrence? */
                                                p += xxreadtoken_doubles + 1;
                                        } else {
@@ -12671,7 +12766,7 @@ xxreadtoken(void)
        }
        setprompt_if(needprompt, 2);
        for (;;) {      /* until token or start of word found */
-               c = pgetc();
+               c = pgetc_eatbnl();
                switch (c) {
                case ' ': case '\t':
                IF_ASH_ALIAS(case PEOA:)
@@ -12681,30 +12776,23 @@ xxreadtoken(void)
                                continue;
                        pungetc();
                        continue;
-               case '\\':
-                       if (pgetc() == '\n') {
-                               nlprompt();
-                               continue;
-                       }
-                       pungetc();
-                       goto breakloop;
                case '\n':
                        nlnoprompt();
                        RETURN(TNL);
                case PEOF:
                        RETURN(TEOF);
                case '&':
-                       if (pgetc() == '&')
+                       if (pgetc_eatbnl() == '&')
                                RETURN(TAND);
                        pungetc();
                        RETURN(TBACKGND);
                case '|':
-                       if (pgetc() == '|')
+                       if (pgetc_eatbnl() == '|')
                                RETURN(TOR);
                        pungetc();
                        RETURN(TPIPE);
                case ';':
-                       if (pgetc() == ';')
+                       if (pgetc_eatbnl() == ';')
                                RETURN(TENDCASE);
                        pungetc();
                        RETURN(TSEMI);
@@ -12712,11 +12800,9 @@ xxreadtoken(void)
                        RETURN(TLP);
                case ')':
                        RETURN(TRP);
-               default:
-                       goto breakloop;
                }
+               break;
        }
- breakloop:
        return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
 #undef RETURN
 }
@@ -13897,7 +13983,7 @@ init(void)
 
 
 //usage:#define ash_trivial_usage
-//usage:       "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
+//usage:       "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]"
 //usage:#define ash_full_usage "\n\n"
 //usage:       "Unix shell interpreter"