ash: parser: Fix old-style command substitution here-document crash
[oweals/busybox.git] / shell / ash.c
index a6f777800d1d9049b31c2385e09fafb4bbd18b9d..83cac3fb0a5eb315bacc52a6930829a717a298ba 100644 (file)
@@ -404,11 +404,11 @@ struct globals_misc {
        volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */
        volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */
        volatile /*sig_atomic_t*/ smallint pending_sig; /* last pending signal */
-       smallint exception_type; /* kind of exception (0..5) */
-       /* exceptions */
+       smallint exception_type; /* kind of exception: */
 #define EXINT 0         /* SIGINT received */
 #define EXERROR 1       /* a generic error */
-#define EXEXIT 4        /* exit the shell */
+#define EXEND 3         /* exit the shell */
+#define EXEXIT 4        /* exit the shell via exitcmd */
 
        char nullstr[1];        /* zero length string */
 
@@ -1678,15 +1678,16 @@ popstackmark(struct stackmark *mark)
  * part of the block that has been used.
  */
 static void
-growstackblock(void)
+growstackblock(size_t min)
 {
        size_t newlen;
 
        newlen = g_stacknleft * 2;
        if (newlen < g_stacknleft)
                ash_msg_and_raise_error(bb_msg_memory_exhausted);
-       if (newlen < 128)
-               newlen += 128;
+       min = SHELL_ALIGN(min | 128);
+       if (newlen < min)
+               newlen += min;
 
        if (g_stacknxt == g_stackp->space && g_stackp != &stackbase) {
                struct stack_block *sp;
@@ -1736,16 +1737,15 @@ static void *
 growstackstr(void)
 {
        size_t len = stackblocksize();
-       growstackblock();
+       growstackblock(0);
        return (char *)stackblock() + len;
 }
 
 static char *
 growstackto(size_t len)
 {
-       while (stackblocksize() < len)
-               growstackblock();
-
+       if (stackblocksize() < len)
+               growstackblock(len);
        return stackblock();
 }
 
@@ -6070,26 +6070,6 @@ static struct ifsregion *ifslastp;
 /* holds expanded arg list */
 static struct arglist exparg;
 
-/*
- * Our own itoa().
- * cvtnum() is used even if math support is off (to prepare $? values and such).
- */
-static int
-cvtnum(arith_t num)
-{
-       int len;
-
-       /* 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;
-}
-
 /*
  * Break the argument string into pieces based upon IFS and add the
  * strings to the argument list.  The regions of the string to be
@@ -6347,16 +6327,18 @@ preglob(const char *pattern, int flag)
 /*
  * Put a string on the stack.
  */
-static void
+static size_t
 memtodest(const char *p, size_t len, int flags)
 {
        int syntax = flags & EXP_QUOTED ? DQSYNTAX : BASESYNTAX;
        char *q;
+       char *s;
 
        if (!len)
-               return;
+               return 0;
 
        q = makestrspace(len * 2, expdest);
+       s = q;
 
        do {
                unsigned char c = *p++;
@@ -6375,6 +6357,7 @@ memtodest(const char *p, size_t len, int flags)
        } while (--len);
 
        expdest = q;
+       return q - s;
 }
 
 static size_t
@@ -6385,6 +6368,22 @@ strtodest(const char *p, int flags)
        return len;
 }
 
+/*
+ * Our own itoa().
+ * cvtnum() is used even if math support is off (to prepare $? values and such).
+ */
+static int
+cvtnum(arith_t num, int flags)
+{
+       /* 32-bit and wider ints require buffer size of bytes*3 (or less) */
+       /* If narrower: worst case, 1-byte ints: need 5 bytes: "-127<NUL>" */
+       int len = (sizeof(arith_t) >= 4) ? sizeof(arith_t) * 3 : sizeof(arith_t) * 3 + 2;
+       char buf[len];
+
+       len = fmtstr(buf, len, ARITH_FMT, num);
+       return memtodest(buf, len, flags);
+}
+
 /*
  * Record the fact that we have to scan this region of the
  * string for IFS characters.
@@ -6683,7 +6682,7 @@ expari(int flag)
        if (flag & QUOTES_ESC)
                rmescapes(p + 1, 0, NULL);
 
-       len = cvtnum(ash_arith(p + 1));
+       len = cvtnum(ash_arith(p + 1), flag);
 
        if (!(flag & EXP_QUOTED))
                recordregion(begoff, begoff + len, 0);
@@ -7328,7 +7327,7 @@ varvalue(char *name, int varflags, int flags, int quoted)
                if (num == 0)
                        return -1;
  numvar:
-               len = cvtnum(num);
+               len = cvtnum(num, flags);
                goto check_1char_name;
        case '-':
                expdest = makestrspace(NOPTS, expdest);
@@ -7494,7 +7493,7 @@ evalvar(char *p, int flag)
                varunset(p, var, 0, 0);
 
        if (subtype == VSLENGTH) {
-               cvtnum(varlen > 0 ? varlen : 0);
+               cvtnum(varlen > 0 ? varlen : 0, flag);
                goto record;
        }
 
@@ -8237,7 +8236,7 @@ static void shellexec(char *prog, char **argv, const char *path, int idx)
        exitstatus = exerrno;
        TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n",
                prog, e, suppress_int));
-       ash_msg_and_raise(EXEXIT, "%s: %s", prog, errmsg(e, "not found"));
+       ash_msg_and_raise(EXEND, "%s: %s", prog, errmsg(e, "not found"));
        /* NOTREACHED */
 }
 
@@ -9091,6 +9090,7 @@ defun(union node *func)
 #define SKIPBREAK      (1 << 0)
 #define SKIPCONT       (1 << 1)
 #define SKIPFUNC       (1 << 2)
+#define SKIPFUNCDEF    (1 << 3)
 static smallint evalskip;       /* set to SKIPxxx if we are skipping commands */
 static int skipcount;           /* number of levels to skip */
 static int loopnest;            /* current loop nesting level */
@@ -9148,7 +9148,8 @@ dotrap(void)
                if (!p)
                        continue;
                evalstring(p, 0);
-               exitstatus = status;
+               if (evalskip != SKIPFUNC)
+                       exitstatus = status;
        }
 
        savestatus = last_status;
@@ -9289,9 +9290,9 @@ evaltree(union node *n, int flags)
        dotrap();
 
        if (checkexit & status)
-               raise_exception(EXEXIT);
+               raise_exception(EXEND);
        if (flags & EV_EXIT)
-               raise_exception(EXEXIT);
+               raise_exception(EXEND);
 
        popstackmark(&smark);
        TRACE(("leaving evaltree (no interrupts)\n"));
@@ -9783,7 +9784,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
        shellparam = saveparam;
        exception_handler = savehandler;
        INT_ON;
-       evalskip &= ~SKIPFUNC;
+       evalskip &= ~(SKIPFUNC | SKIPFUNCDEF);
        return e;
 }
 
@@ -9928,12 +9929,23 @@ execcmd(int argc UNUSED_PARAM, char **argv)
 static int FAST_FUNC
 returncmd(int argc UNUSED_PARAM, char **argv)
 {
+       int skip;
+       int status;
+
        /*
         * If called outside a function, do what ksh does;
         * skip the rest of the file.
         */
-       evalskip = SKIPFUNC;
-       return argv[1] ? number(argv[1]) : exitstatus;
+       if (argv[1]) {
+               skip = SKIPFUNC;
+               status = number(argv[1]);
+       } else {
+               skip = SKIPFUNCDEF;
+               status = exitstatus;
+       }
+       evalskip = skip;
+
+       return status;
 }
 
 /* Forward declarations for builtintab[] */
@@ -12865,9 +12877,9 @@ parsebackq: {
                if (readtoken() != TRP)
                        raise_error_unexpected_syntax(TRP);
                setinputstring(nullstr);
-               parseheredoc();
        }
 
+       parseheredoc();
        heredoclist = saveheredoclist;
 
        (*nlpp)->n = n;
@@ -13372,7 +13384,7 @@ cmdloop(int top)
                skip = evalskip;
 
                if (skip) {
-                       evalskip &= ~SKIPFUNC;
+                       evalskip &= ~(SKIPFUNC | SKIPFUNCDEF);
                        break;
                }
        }
@@ -14133,6 +14145,47 @@ ulimitcmd(int argc UNUSED_PARAM, char **argv)
 
 /* ============ main() and helpers */
 
+/*
+ * This routine is called when an error or an interrupt occurs in an
+ * interactive shell and control is returned to the main command loop
+ * but prior to exitshell.
+ */
+static void
+exitreset(void)
+{
+       /* from eval.c: */
+       if (savestatus >= 0) {
+               if (exception_type == EXEXIT || evalskip == SKIPFUNCDEF)
+                       exitstatus = savestatus;
+               savestatus = -1;
+       }
+       evalskip = 0;
+       loopnest = 0;
+
+       /* from expand.c: */
+       ifsfree();
+
+       /* from redir.c: */
+       unwindredir(NULL);
+}
+
+/*
+ * This routine is called when an error or an interrupt occurs in an
+ * interactive shell and control is returned to the main command loop.
+ * (In dash, this function is auto-generated by build machinery).
+ */
+static void
+reset(void)
+{
+       /* from input.c: */
+       g_parsefile->left_in_buffer = 0;
+       g_parsefile->left_in_line = 0;      /* clear input buffer */
+       popallfiles();
+
+       /* from var.c: */
+       unwindlocalvars(NULL);
+}
+
 /*
  * Called to exit the shell.
  */
@@ -14156,15 +14209,17 @@ exitshell(void)
                trap[0] = NULL;
                evalskip = 0;
                evalstring(p, 0);
+               evalskip = SKIPFUNCDEF;
                /*free(p); - we'll exit soon */
        }
  out:
+       exitreset();
        /* dash wraps setjobctl(0) in "if (setjmp(loc.loc) == 0) {...}".
         * our setjobctl(0) does not panic if tcsetpgrp fails inside it.
         */
        setjobctl(0);
        flush_stdout_stderr();
-       _exit(savestatus);
+       _exit(exitstatus);
        /* NOTREACHED */
 }
 
@@ -14324,46 +14379,6 @@ read_profile(const char *name)
        popfile();
 }
 
-/*
- * This routine is called when an error or an interrupt occurs in an
- * interactive shell and control is returned to the main command loop
- * but prior to exitshell.
- */
-static void
-exitreset(void)
-{
-       /* from eval.c: */
-       evalskip = 0;
-       loopnest = 0;
-       if (savestatus >= 0) {
-               exitstatus = savestatus;
-               savestatus = -1;
-       }
-
-       /* from expand.c: */
-       ifsfree();
-
-       /* from redir.c: */
-       unwindredir(NULL);
-}
-
-/*
- * This routine is called when an error or an interrupt occurs in an
- * interactive shell and control is returned to the main command loop.
- * (In dash, this function is auto-generated by build machinery).
- */
-static void
-reset(void)
-{
-       /* from input.c: */
-       g_parsefile->left_in_buffer = 0;
-       g_parsefile->left_in_line = 0;      /* clear input buffer */
-       popallfiles();
-
-       /* from var.c: */
-       unwindlocalvars(NULL);
-}
-
 #if PROFILE
 static short profile_buf[16384];
 extern int etext();
@@ -14411,7 +14426,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
 
                e = exception_type;
                s = state;
-               if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) {
+               if (e == EXEND || e == EXEXIT || s == 0 || iflag == 0 || shlvl) {
                        exitshell();
                }