ash: force inlining of a trivial function
[oweals/busybox.git] / shell / ash.c
index a461bb7df7b4f9f9cf52d284e30e4eebbcab2229..f74fbd72f7b11d811730a34e12ee02253ce5e067 100644 (file)
 #include "shell_common.h"
 #if ENABLE_FEATURE_SH_MATH
 # include "math.h"
+#else
+typedef long arith_t;
+# define ARITH_FMT "%ld"
 #endif
 #if ENABLE_ASH_RANDOM_SUPPORT
 # include "random.h"
@@ -621,8 +624,8 @@ fmtstr(char *outbuf, size_t length, const char *fmt, ...)
        va_list ap;
        int ret;
 
-       va_start(ap, fmt);
        INT_OFF;
+       va_start(ap, fmt);
        ret = vsnprintf(outbuf, length, fmt, ap);
        va_end(ap);
        INT_ON;
@@ -1247,7 +1250,6 @@ static struct parsefile basepf;        /* top level input file */
 static struct parsefile *g_parsefile = &basepf;  /* current input file */
 static int startlinno;                 /* line # where last token started */
 static char *commandname;              /* currently executing command */
-static struct strlist *cmdenviron;     /* environment for builtin command */
 
 
 /* ============ Message printing */
@@ -1658,7 +1660,7 @@ static char *
 stack_nputstr(const char *s, size_t n, char *p)
 {
        p = makestrspace(n, p);
-       p = (char *)memcpy(p, s, n) + n;
+       p = (char *)mempcpy(p, s, n);
        return p;
 }
 
@@ -1761,7 +1763,7 @@ single_quote(const char *s)
                q = p = makestrspace(len + 3, p);
 
                *q++ = '\'';
-               q = (char *)memcpy(q, s, len) + len;
+               q = (char *)mempcpy(q, s, len);
                *q++ = '\'';
                s += len;
 
@@ -1775,7 +1777,7 @@ single_quote(const char *s)
                q = p = makestrspace(len + 3, p);
 
                *q++ = '"';
-               q = (char *)memcpy(q, s - len, len) + len;
+               q = (char *)mempcpy(q, s - len, len);
                *q++ = '"';
 
                STADJUST(q - p, p);
@@ -1788,9 +1790,6 @@ single_quote(const char *s)
 
 /*
  * Produce a possibly single quoted string suitable as input to the shell.
- * If 'conditional' is nonzero, quoting is only done if the string contains
- * non-shellsafe characters, or is identical to a shell keyword (reserved
- * word); if it is zero, quoting is always done.
  * If quoting was done, the return string is allocated on the stack,
  * otherwise a pointer to the original string is returned.
  */
@@ -2225,15 +2224,9 @@ reinit_unicode_for_ash(void)
 /*
  * Search the environment of a builtin command.
  */
-static const char *
+static ALWAYS_INLINE const char *
 bltinlookup(const char *name)
 {
-       struct strlist *sp;
-
-       for (sp = cmdenviron; sp; sp = sp->next) {
-               if (varcmp(sp->text, name) == 0)
-                       return var_end(sp->text);
-       }
        return lookupvar(name);
 }
 
@@ -2244,14 +2237,15 @@ bltinlookup(const char *name)
  * will go away.
  * Called with interrupts off.
  */
-static void
+static struct var *
 setvareq(char *s, int flags)
 {
        struct var *vp, **vpp;
 
        vpp = hashvar(s);
        flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
-       vp = *findvar(vpp, s);
+       vpp = findvar(vpp, s);
+       vp = *vpp;
        if (vp) {
                if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
                        const char *n;
@@ -2264,7 +2258,7 @@ setvareq(char *s, int flags)
                }
 
                if (flags & VNOSET)
-                       return;
+                       goto out;
 
                if (vp->var_func && !(flags & VNOFUNC))
                        vp->var_func(var_end(s));
@@ -2272,11 +2266,22 @@ setvareq(char *s, int flags)
                if (!(vp->flags & (VTEXTFIXED|VSTACK)))
                        free((char*)vp->var_text);
 
+               if (((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) | (vp->flags & VSTRFIXED)) == VUNSET) {
+                       *vpp = vp->next;
+                       free(vp);
+ out_free:
+                       if ((flags & (VTEXTFIXED|VSTACK|VNOSAVE)) == VNOSAVE)
+                               free(s);
+                       goto out;
+               }
+
                flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
        } else {
                /* variable s is not found */
                if (flags & VNOSET)
-                       return;
+                       goto out;
+               if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
+                       goto out_free;
                vp = ckzalloc(sizeof(*vp));
                vp->next = *vpp;
                /*vp->func = NULL; - ckzalloc did it */
@@ -2286,13 +2291,16 @@ setvareq(char *s, int flags)
                s = ckstrdup(s);
        vp->var_text = s;
        vp->flags = flags;
+
+ out:
+       return vp;
 }
 
 /*
  * Set the value of a variable.  The flags argument is ored with the
  * flags of the variable.  If val is NULL, the variable is unset.
  */
-static void
+static struct var *
 setvar(const char *name, const char *val, int flags)
 {
        const char *q;
@@ -2300,6 +2308,7 @@ setvar(const char *name, const char *val, int flags)
        char *nameeq;
        size_t namelen;
        size_t vallen;
+       struct var *vp;
 
        q = endofname(name);
        p = strchrnul(q, '=');
@@ -2315,14 +2324,16 @@ setvar(const char *name, const char *val, int flags)
 
        INT_OFF;
        nameeq = ckmalloc(namelen + vallen + 2);
-       p = memcpy(nameeq, name, namelen) + namelen;
+       p = mempcpy(nameeq, name, namelen);
        if (val) {
                *p++ = '=';
-               p = memcpy(p, val, vallen) + vallen;
+               p = mempcpy(p, val, vallen);
        }
        *p = '\0';
-       setvareq(nameeq, flags | VNOSAVE);
+       vp = setvareq(nameeq, flags | VNOSAVE);
        INT_ON;
+
+       return vp;
 }
 
 static void FAST_FUNC
@@ -2334,43 +2345,10 @@ setvar0(const char *name, const char *val)
 /*
  * Unset the specified variable.
  */
-static int
+static void
 unsetvar(const char *s)
 {
-       struct var **vpp;
-       struct var *vp;
-       int retval;
-
-       vpp = findvar(hashvar(s), s);
-       vp = *vpp;
-       retval = 2;
-       if (vp) {
-               int flags = vp->flags;
-
-               retval = 1;
-               if (flags & VREADONLY)
-                       goto out;
-#if ENABLE_ASH_RANDOM_SUPPORT
-               vp->flags &= ~VDYNAMIC;
-#endif
-               if (flags & VUNSET)
-                       goto ok;
-               if ((flags & VSTRFIXED) == 0) {
-                       INT_OFF;
-                       if ((flags & (VTEXTFIXED|VSTACK)) == 0)
-                               free((char*)vp->var_text);
-                       *vpp = vp->next;
-                       free(vp);
-                       INT_ON;
-               } else {
-                       setvar0(s, NULL);
-                       vp->flags &= ~VEXPORT;
-               }
- ok:
-               retval = 0;
-       }
- out:
-       return retval;
+       setvar(s, NULL, 0);
 }
 
 /*
@@ -2453,8 +2431,7 @@ path_advance(const char **path, const char *name)
                growstackblock();
        q = stackblock();
        if (p != start) {
-               memcpy(q, start, p - start);
-               q += p - start;
+               q = mempcpy(q, start, p - start);
                *q++ = '/';
        }
        strcpy(q, name);
@@ -5200,68 +5177,6 @@ stoppedjobs(void)
 #define EMPTY -2                /* marks an unused slot in redirtab */
 #define CLOSED -3               /* marks a slot of previously-closed fd */
 
-/*
- * Open a file in noclobber mode.
- * The code was copied from bash.
- */
-static int
-noclobberopen(const char *fname)
-{
-       int r, fd;
-       struct stat finfo, finfo2;
-
-       /*
-        * If the file exists and is a regular file, return an error
-        * immediately.
-        */
-       r = stat(fname, &finfo);
-       if (r == 0 && S_ISREG(finfo.st_mode)) {
-               errno = EEXIST;
-               return -1;
-       }
-
-       /*
-        * If the file was not present (r != 0), make sure we open it
-        * exclusively so that if it is created before we open it, our open
-        * will fail.  Make sure that we do not truncate an existing file.
-        * Note that we don't turn on O_EXCL unless the stat failed -- if the
-        * file was not a regular file, we leave O_EXCL off.
-        */
-       if (r != 0)
-               return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
-       fd = open(fname, O_WRONLY|O_CREAT, 0666);
-
-       /* If the open failed, return the file descriptor right away. */
-       if (fd < 0)
-               return fd;
-
-       /*
-        * OK, the open succeeded, but the file may have been changed from a
-        * non-regular file to a regular file between the stat and the open.
-        * We are assuming that the O_EXCL open handles the case where FILENAME
-        * did not exist and is symlinked to an existing file between the stat
-        * and open.
-        */
-
-       /*
-        * If we can open it and fstat the file descriptor, and neither check
-        * revealed that it was a regular file, and the file has not been
-        * replaced, return the file descriptor.
-        */
-       if (fstat(fd, &finfo2) == 0
-        && !S_ISREG(finfo2.st_mode)
-        && finfo.st_dev == finfo2.st_dev
-        && finfo.st_ino == finfo2.st_ino
-       ) {
-               return fd;
-       }
-
-       /* The file has been replaced.  badness. */
-       close(fd);
-       errno = EEXIST;
-       return -1;
-}
-
 /*
  * Handle here documents.  Normally we fork off a process to write the
  * data to a pipe.  If the document is short, we can stuff the data in
@@ -5306,6 +5221,7 @@ openhere(union node *redir)
 static int
 openredirect(union node *redir)
 {
+       struct stat sb;
        char *fname;
        int f;
 
@@ -5345,9 +5261,23 @@ openredirect(union node *redir)
 #endif
                /* Take care of noclobber mode. */
                if (Cflag) {
-                       f = noclobberopen(fname);
-                       if (f < 0)
+                       if (stat(fname, &sb) < 0) {
+                               f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
+                               if (f < 0)
+                                       goto ecreate;
+                       } else if (!S_ISREG(sb.st_mode)) {
+                               f = open(fname, O_WRONLY, 0666);
+                               if (f < 0)
+                                       goto ecreate;
+                               if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) {
+                                       close(f);
+                                       errno = EEXIST;
+                                       goto ecreate;
+                               }
+                       } else {
+                               errno = EEXIST;
                                goto ecreate;
+                       }
                        break;
                }
                /* FALLTHROUGH */
@@ -5737,7 +5667,7 @@ ash_arith(const char *s)
 #define RMESCAPE_SLASH  0x20    /* Stop globbing after slash */
 
 /* Add CTLESC when necessary. */
-#define QUOTES_ESC     (EXP_FULL | EXP_CASE | EXP_QPAT | EXP_REDIR)
+#define QUOTES_ESC     (EXP_FULL | EXP_CASE | EXP_QPAT)
 /* Do not skip NUL characters. */
 #define QUOTES_KEEPNUL EXP_TILDE
 
@@ -5770,19 +5700,20 @@ static struct arglist exparg;
 
 /*
  * Our own itoa().
+ * cvtnum() is used even if math support is off (to prepare $? values and such).
  */
-#if !ENABLE_FEATURE_SH_MATH
-/* cvtnum() is used even if math support is off (to prepare $? values and such) */
-typedef long arith_t;
-# define ARITH_FMT "%ld"
-#endif
 static int
 cvtnum(arith_t num)
 {
        int len;
 
-       expdest = makestrspace(sizeof(arith_t)*3 + 2, expdest);
-       len = fmtstr(expdest, sizeof(arith_t)*3 + 2, ARITH_FMT, num);
+       /* 32-bit and wider ints require buffer size of bytes*3 (or less) */
+       len = sizeof(arith_t) * 3;
+       /* If narrower: worst case, 1-byte ints: need 5 bytes: "-127<NUL>" */
+       if (sizeof(arith_t) < 4) len += 2;
+
+       expdest = makestrspace(len, expdest);
+       len = fmtstr(expdest, len, ARITH_FMT, num);
        STADJUST(len, expdest);
        return len;
 }
@@ -5949,7 +5880,7 @@ rmescapes(char *str, int flag)
                }
                q = r;
                if (len > 0) {
-                       q = (char *)memcpy(q, str, len) + len;
+                       q = (char *)mempcpy(q, str, len);
                }
        }
 
@@ -6201,7 +6132,9 @@ struct backcmd {                /* result of evalbackcmd */
 };
 
 /* These forward decls are needed to use "eval" code for backticks handling: */
-#define EV_EXIT 01              /* exit after evaluating tree */
+/* flags in argument to evaltree */
+#define EV_EXIT    01           /* exit after evaluating tree */
+#define EV_TESTED  02           /* exit status is checked; ignore -e flag */
 static int evaltree(union node *, int);
 
 static void FAST_FUNC
@@ -6371,19 +6304,15 @@ expari(int flag)
 #endif
 
 /* argstr needs it */
-static char *evalvar(char *p, int flags, struct strlist *var_str_list);
+static char *evalvar(char *p, int flags);
 
 /*
  * Perform variable and command substitution.  If EXP_FULL is set, output CTLESC
  * characters to allow for further processing.  Otherwise treat
  * $@ like $* since no splitting will be performed.
- *
- * var_str_list (can be NULL) is a list of "VAR=val" strings which take precedence
- * over shell variables. Needed for "A=a B=$A; echo $B" case - we use it
- * for correct expansion of "B=$A" word.
  */
 static void
-argstr(char *p, int flags, struct strlist *var_str_list)
+argstr(char *p, int flags)
 {
        static const char spclchars[] ALIGN1 = {
                '=',
@@ -6476,7 +6405,7 @@ argstr(char *p, int flags, struct strlist *var_str_list)
                        inquotes ^= EXP_QUOTED;
                        /* "$@" syntax adherence hack */
                        if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
-                               p = evalvar(p + 1, flags | inquotes, /* var_str_list: */ NULL) + 1;
+                               p = evalvar(p + 1, flags | inquotes) + 1;
                                goto start;
                        }
  addquote:
@@ -6502,7 +6431,7 @@ argstr(char *p, int flags, struct strlist *var_str_list)
                        goto addquote;
                case CTLVAR:
                        TRACE(("argstr: evalvar('%s')\n", p));
-                       p = evalvar(p, flags | inquotes, var_str_list);
+                       p = evalvar(p, flags | inquotes);
                        TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock()));
                        goto start;
                case CTLBACKQ:
@@ -6644,7 +6573,7 @@ varunset(const char *end, const char *var, const char *umsg, int varflags)
 
 static const char *
 subevalvar(char *p, char *varname, int strloc, int subtype,
-               int startloc, int varflags, int flag, struct strlist *var_str_list)
+               int startloc, int varflags, int flag)
 {
        struct nodelist *saveargbackq = argbackq;
        int quotes = flag & QUOTES_ESC;
@@ -6662,8 +6591,8 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
        //              p, varname, strloc, subtype, startloc, varflags, quotes);
 
        argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ?
-                       (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0),
-                       var_str_list);
+                       (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0)
+       );
        STPUTC('\0', expdest);
        argbackq = saveargbackq;
        startp = (char *)stackblock() + startloc;
@@ -6940,7 +6869,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
  * ash -c 'echo ${#1#}'  name:'1=#'
  */
 static NOINLINE ssize_t
-varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int *quotedp)
+varvalue(char *name, int varflags, int flags, int *quotedp)
 {
        const char *p;
        int num;
@@ -7032,31 +6961,6 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int
                goto value;
        default:
                /* NB: name has form "VAR=..." */
-
-               /* "A=a B=$A" case: var_str_list is a list of "A=a" strings
-                * which should be considered before we check variables. */
-               if (var_str_list) {
-                       unsigned name_len = (strchrnul(name, '=') - name) + 1;
-                       p = NULL;
-                       do {
-                               char *str, *eq;
-                               str = var_str_list->text;
-                               eq = strchr(str, '=');
-                               if (!eq) /* stop at first non-assignment */
-                                       break;
-                               eq++;
-                               if (name_len == (unsigned)(eq - str)
-                                && strncmp(str, name, name_len) == 0
-                               ) {
-                                       p = eq;
-                                       /* goto value; - WRONG! */
-                                       /* think "A=1 A=2 B=$A" */
-                               }
-                               var_str_list = var_str_list->next;
-                       } while (var_str_list);
-                       if (p)
-                               goto value;
-               }
                p = lookupvar(name);
  value:
                if (!p)
@@ -7086,7 +6990,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int
  * input string.
  */
 static char *
-evalvar(char *p, int flag, struct strlist *var_str_list)
+evalvar(char *p, int flag)
 {
        char varflags;
        char subtype;
@@ -7110,7 +7014,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
        p = strchr(p, '=') + 1; //TODO: use var_end(p)?
 
  again:
-       varlen = varvalue(var, varflags, flag, var_str_list, &quoted);
+       varlen = varvalue(var, varflags, flag, &quoted);
        if (varflags & VSNUL)
                varlen--;
 
@@ -7124,8 +7028,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
                if (varlen < 0) {
                        argstr(
                                p,
-                               flag | EXP_TILDE | EXP_WORD,
-                               var_str_list
+                               flag | EXP_TILDE | EXP_WORD
                        );
                        goto end;
                }
@@ -7137,7 +7040,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
                        goto record;
 
                subevalvar(p, var, 0, subtype, startloc, varflags,
-                          flag & ~QUOTES_ESC, var_str_list);
+                          flag & ~QUOTES_ESC);
                varflags &= ~VSNUL;
                /*
                 * Remove any recorded regions beyond
@@ -7190,7 +7093,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
                STPUTC('\0', expdest);
                patloc = expdest - (char *)stackblock();
                if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
-                               startloc, varflags, flag, var_str_list)) {
+                               startloc, varflags, flag)) {
                        int amount = expdest - (
                                (char *)stackblock() + patloc - 1
                        );
@@ -7614,8 +7517,7 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
        argbackq = arg->narg.backquote;
        STARTSTACKSTR(expdest);
        TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag));
-       argstr(arg->narg.text, flag,
-                       /* var_str_list: */ arglist ? arglist->list : NULL);
+       argstr(arg->narg.text, flag);
        p = _STPUTC('\0', expdest);
        expdest = p - 1;
        if (arglist == NULL) {
@@ -7634,10 +7536,6 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
                exparg.lastp = &exparg.list;
                expandmeta(exparg.list /*, flag*/);
        } else {
-               if (flag & EXP_REDIR) { /*XXX - for now, just remove escapes */
-                       rmescapes(p, 0);
-                       TRACE(("expandarg: rmescapes:'%s'\n", p));
-               }
                sp = stzalloc(sizeof(*sp));
                sp->text = p;
                *exparg.lastp = sp;
@@ -7686,8 +7584,7 @@ casematch(union node *pattern, char *val)
        setstackmark(&smark);
        argbackq = pattern->narg.backquote;
        STARTSTACKSTR(expdest);
-       argstr(pattern->narg.text, EXP_TILDE | EXP_CASE,
-                       /* var_str_list: */ NULL);
+       argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
        STACKSTRNUL(expdest);
        ifsfree();
        result = patmatch(stackblock(), val);
@@ -7773,6 +7670,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
                        clearenv();
                        while (*envp)
                                putenv(*envp++);
+                       popredir(/*drop:*/ 1, /*restore:*/ 0);
                        run_applet_no_and_exit(applet_no, cmd, argv);
                }
                /* re-exec ourselves with the new arguments */
@@ -8422,10 +8320,6 @@ commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 static void *funcblock;         /* block to allocate function from */
 static char *funcstring_end;    /* end of block to allocate strings from */
 
-/* flags in argument to evaltree */
-#define EV_EXIT    01           /* exit after evaluating tree */
-#define EV_TESTED  02           /* exit status is checked; ignore -e flag */
-
 static const uint8_t nodesize[N_NUMBER] ALIGN1 = {
        [NCMD     ] = SHELL_ALIGN(sizeof(struct ncmd)),
        [NPIPE    ] = SHELL_ALIGN(sizeof(struct npipe)),
@@ -9241,27 +9135,57 @@ optschanged(void)
 #endif
 }
 
-static struct localvar *localvars;
+struct localvar_list {
+       struct localvar_list *next;
+       struct localvar *lv;
+};
+
+static struct localvar_list *localvar_stack;
 
 /*
  * Called after a function returns.
  * Interrupts must be off.
  */
 static void
-poplocalvars(void)
+poplocalvars(int keep)
 {
-       struct localvar *lvp;
+       struct localvar_list *ll;
+       struct localvar *lvp, *next;
        struct var *vp;
 
-       while ((lvp = localvars) != NULL) {
-               localvars = lvp->next;
+       INT_OFF;
+       ll = localvar_stack;
+       localvar_stack = ll->next;
+
+       next = ll->lv;
+       free(ll);
+
+       while ((lvp = next) != NULL) {
+               next = lvp->next;
                vp = lvp->vp;
                TRACE(("poplocalvar %s\n", vp ? vp->var_text : "-"));
-               if (vp == NULL) {       /* $- saved */
+               if (keep) {
+                       int bits = VSTRFIXED;
+
+                       if (lvp->flags != VUNSET) {
+                               if (vp->var_text == lvp->text)
+                                       bits |= VTEXTFIXED;
+                               else if (!(lvp->flags & (VTEXTFIXED|VSTACK)))
+                                       free((char*)lvp->text);
+                       }
+
+                       vp->flags &= ~bits;
+                       vp->flags |= (lvp->flags & bits);
+
+                       if ((vp->flags &
+                            (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
+                               unsetvar(vp->var_text);
+               } else if (vp == NULL) {        /* $- saved */
                        memcpy(optlist, lvp->text, sizeof(optlist));
                        free((char*)lvp->text);
                        optschanged();
-               } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+               } else if (lvp->flags == VUNSET) {
+                       vp->flags &= ~(VSTRFIXED|VREADONLY);
                        unsetvar(vp->var_text);
                } else {
                        if (vp->var_func)
@@ -9273,19 +9197,43 @@ poplocalvars(void)
                }
                free(lvp);
        }
+       INT_ON;
+}
+
+/*
+ * Create a new localvar environment.
+ */
+static struct localvar_list *
+pushlocalvars(void)
+{
+       struct localvar_list *ll;
+
+       INT_OFF;
+       ll = ckzalloc(sizeof(*ll));
+       /*ll->lv = NULL; - zalloc did it */
+       ll->next = localvar_stack;
+       localvar_stack = ll;
+       INT_ON;
+
+       return ll->next;
+}
+
+static void
+unwindlocalvars(struct localvar_list *stop)
+{
+       while (localvar_stack != stop)
+               poplocalvars(0);
 }
 
 static int
 evalfun(struct funcnode *func, int argc, char **argv, int flags)
 {
        volatile struct shparam saveparam;
-       struct localvar *volatile savelocalvars;
        struct jmploc *volatile savehandler;
        struct jmploc jmploc;
        int e;
 
        saveparam = shellparam;
-       savelocalvars = localvars;
        savehandler = exception_handler;
        e = setjmp(jmploc.loc);
        if (e) {
@@ -9293,7 +9241,6 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
        }
        INT_OFF;
        exception_handler = &jmploc;
-       localvars = NULL;
        shellparam.malloced = 0;
        func->count++;
        funcnest++;
@@ -9304,13 +9251,13 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
        shellparam.optind = 1;
        shellparam.optoff = -1;
 #endif
+       pushlocalvars();
        evaltree(func->n.narg.next, flags & EV_TESTED);
+       poplocalvars(0);
  funcdone:
        INT_OFF;
        funcnest--;
        freefunc(func);
-       poplocalvars();
-       localvars = savelocalvars;
        freeparam(&shellparam);
        shellparam = saveparam;
        exception_handler = savehandler;
@@ -9339,7 +9286,7 @@ mklocal(char *name)
         * x=0; f() { local x=1; echo $x; local x; echo $x; }; f; echo $x
         * x=0; f() { local x=1; echo $x; local x=2; echo $x; }; f; echo $x
         */
-       lvp = localvars;
+       lvp = localvar_stack->lv;
        while (lvp) {
                if (lvp->vp && varcmp(lvp->vp->var_text, name) == 0) {
                        if (eq)
@@ -9364,10 +9311,9 @@ mklocal(char *name)
                if (vp == NULL) {
                        /* variable did not exist yet */
                        if (eq)
-                               setvareq(name, VSTRFIXED);
+                               vp = setvareq(name, VSTRFIXED);
                        else
-                               setvar(name, NULL, VSTRFIXED);
-                       vp = *vpp;      /* the new variable */
+                               vp = setvar(name, NULL, VSTRFIXED);
                        lvp->flags = VUNSET;
                } else {
                        lvp->text = vp->var_text;
@@ -9384,8 +9330,8 @@ mklocal(char *name)
                }
        }
        lvp->vp = vp;
-       lvp->next = localvars;
-       localvars = lvp;
+       lvp->next = localvar_stack->lv;
+       localvar_stack->lv = lvp;
  ret:
        INT_ON;
 }
@@ -9398,7 +9344,7 @@ localcmd(int argc UNUSED_PARAM, char **argv)
 {
        char *name;
 
-       if (!funcnest)
+       if (!localvar_stack)
                ash_msg_and_raise_error("not in a function");
 
        argv = argptr;
@@ -9567,7 +9513,7 @@ static const struct builtincmd builtintab[] = {
 #if ENABLE_FEATURE_SH_MATH
        { BUILTIN_NOSPEC        "let"     , letcmd     },
 #endif
-       { BUILTIN_ASSIGN        "local"   , localcmd   },
+       { BUILTIN_SPEC_REG_ASSG "local"   , localcmd   },
 #if ENABLE_ASH_PRINTF
        { BUILTIN_REGULAR       "printf"  , printfcmd  },
 #endif
@@ -9656,6 +9602,7 @@ evalcommand(union node *cmd, int flags)
        static const struct builtincmd null_bltin = {
                "\0\0", bltincmd /* why three NULs? */
        };
+       struct localvar_list *localvar_stop;
        struct stackmark smark;
        union node *argp;
        struct arglist arglist;
@@ -9677,6 +9624,7 @@ evalcommand(union node *cmd, int flags)
        /* First expand the arguments. */
        TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
        setstackmark(&smark);
+       localvar_stop = pushlocalvars();
        back_exitstatus = 0;
 
        cmdentry.cmdtype = CMDBUILTIN;
@@ -9730,6 +9678,8 @@ evalcommand(union node *cmd, int flags)
                spp = varlist.lastp;
                expandarg(argp, &varlist, EXP_VARTILDE);
 
+               mklocal((*spp)->text);
+
                /*
                 * Modify the command lookup path, if a PATH= assignment
                 * is present
@@ -9873,17 +9823,12 @@ evalcommand(union node *cmd, int flags)
                /* NOTREACHED */
        } /* default */
        case CMDBUILTIN:
-               cmdenviron = varlist.list;
-               if (cmdenviron) {
-                       struct strlist *list = cmdenviron;
-                       int i = VNOSET;
-                       if (spclbltin > 0 || argc == 0) {
-                               i = 0;
-                               if (cmd_is_exec && argc > 1)
-                                       i = VEXPORT;
-                       }
-                       listsetvar(list, i);
+               if (spclbltin > 0 || argc == 0) {
+                       poplocalvars(1);
+                       if (cmd_is_exec && argc > 1)
+                               listsetvar(varlist.list, VEXPORT);
                }
+
                /* Tight loop with builtins only:
                 * "while kill -0 $child; do true; done"
                 * will never exit even if $child died, unless we do this
@@ -9901,7 +9846,7 @@ evalcommand(union node *cmd, int flags)
                goto readstatus;
 
        case CMDFUNCTION:
-               listsetvar(varlist.list, 0);
+               poplocalvars(1);
                /* See above for the rationale */
                dowait(DOWAIT_NONBLOCK, NULL);
                if (evalfun(cmdentry.u.func, argc, argv, flags))
@@ -9914,6 +9859,7 @@ evalcommand(union node *cmd, int flags)
  out:
        if (cmd->ncmd.redirect)
                popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
+       unwindlocalvars(localvar_stop);
        if (lastarg) {
                /* dsl: I think this is intended to be used to support
                 * '_' in 'vi' command mode during line editing...
@@ -12568,6 +12514,12 @@ expandstr(const char *ps)
        return stackblock();
 }
 
+static inline int
+parser_eof(void)
+{
+       return tokpushback && lasttoken == TEOF;
+}
+
 /*
  * Execute a command or commands contained in a string.
  */
@@ -12603,7 +12555,7 @@ evalstring(char *s, int flags)
        while ((n = parsecmd(0)) != NODE_EOF) {
                int i;
 
-               i = evaltree(n, flags);
+               i = evaltree(n, flags & ~(parser_eof() ? 0 : EV_EXIT));
                if (n)
                        status = i;
                popstackmark(&smark);
@@ -12754,11 +12706,12 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM)
        char *fullname;
        char **argv;
        char *args_need_save;
-       struct strlist *sp;
        volatile struct shparam saveparam;
 
-       for (sp = cmdenviron; sp; sp = sp->next)
-               setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
+//???
+//     struct strlist *sp;
+//     for (sp = cmdenviron; sp; sp = sp->next)
+//             setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
 
        nextopt(nullstr); /* handle possible "--" */
        argv = argptr;
@@ -13058,13 +13011,18 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                return 0;
        }
 
+       /* Why the second check?
+        * "trap NUM [sig2]..." is the same as "trap - NUM [sig2]..."
+        * In this case, NUM is signal no, not an action.
+        */
        action = NULL;
-       if (ap[1])
+       if (ap[1] && !is_number(ap[0]))
                action = *ap++;
+
        exitcode = 0;
        while (*ap) {
                signo = get_signum(*ap);
-               if (signo < 0 || signo >= NSIG) {
+               if (signo < 0) {
                        /* Mimic bash message exactly */
                        ash_msg("%s: invalid signal specification", *ap);
                        exitcode = 1;
@@ -13222,7 +13180,6 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        char **ap;
        int i;
        int flag = 0;
-       int ret = 0;
 
        while ((i = nextopt("vf")) != 0) {
                flag = i;
@@ -13230,15 +13187,13 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 
        for (ap = argptr; *ap; ap++) {
                if (flag != 'f') {
-                       i = unsetvar(*ap);
-                       ret |= i;
-                       if (!(i & 2))
-                               continue;
+                       unsetvar(*ap);
+                       continue;
                }
                if (flag != 'v')
                        unsetfunc(*ap);
        }
-       return ret & 1;
+       return 0;
 }
 
 static const unsigned char timescmd_str[] ALIGN1 = {
@@ -13636,6 +13591,9 @@ reset(void)
        /* from redir.c: */
        while (redirlist)
                popredir(/*drop:*/ 0, /*restore:*/ 0);
+
+       /* from var.c: */
+       unwindlocalvars(NULL);
 }
 
 #if PROFILE
@@ -13746,7 +13704,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                // if (!sflag) g_parsefile->pf_fd = -1;
                // ^^ not necessary since now we special-case fd 0
                // in is_hidden_fd() to not be considered "hidden fd"
-               evalstring(minusc, 0);
+               evalstring(minusc, sflag ? 0 : EV_EXIT);
        }
 
        if (sflag || minusc == NULL) {