ash: cleanup part 1
authorDenis Vlasenko <vda.linux@googlemail.com>
Fri, 23 Feb 2007 01:03:40 +0000 (01:03 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Fri, 23 Feb 2007 01:03:40 +0000 (01:03 -0000)
shell/ash.c

index e262c0e13abe9bfb4ddc6dcb25fdcd62a38bdb7b..379e8ab7f6257cc2774a5c18730bcb7a7870e40d 100644 (file)
@@ -503,109 +503,392 @@ errmsg(int e, const char *em)
 }
 
 
-/* ============ Unsorted yet */
-
+/* ============ Memory allocation */
 
-static void setpwd(const char *, int);
+/*
+ * It appears that grabstackstr() will barf with such alignments
+ * because stalloc() will return a string allocated in a new stackblock.
+ */
+#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)
+enum {
+       /* Most machines require the value returned from malloc to be aligned
+        * in some way.  The following macro will get this right
+        * on many machines.  */
+       SHELL_SIZE = sizeof(union {int i; char *cp; double d; }) - 1,
+       /* Minimum size of a block */
+       MINSIZE  = SHELL_ALIGN(504),
+};
 
-/*      expand.h     */
+struct stack_block {
+       struct stack_block *prev;
+       char space[MINSIZE];
+};
 
-struct strlist {
-       struct strlist *next;
-       char *text;
+struct stackmark {
+       struct stack_block *stackp;
+       char *stacknxt;
+       size_t stacknleft;
+       struct stackmark *marknext;
 };
 
+static struct stack_block stackbase;
+static struct stack_block *stackp = &stackbase;
+static struct stackmark *markp;
+static char *stacknxt = stackbase.space;
+static size_t stacknleft = MINSIZE;
+static char *sstrend = stackbase.space + MINSIZE;
+static int herefd = -1;
 
-struct arglist {
-       struct strlist *list;
-       struct strlist **lastp;
-};
+#define stackblock() ((void *)stacknxt)
+#define stackblocksize() stacknleft
+
+static void *
+ckrealloc(void * p, size_t nbytes)
+{
+       p = realloc(p, nbytes);
+       if (!p)
+               ash_msg_and_raise_error(bb_msg_memory_exhausted);
+       return p;
+}
+
+static void *
+ckmalloc(size_t nbytes)
+{
+       return ckrealloc(NULL, nbytes);
+}
 
 /*
- * expandarg() flags
+ * Make a copy of a string in safe storage.
  */
-#define EXP_FULL        0x1     /* perform word splitting & file globbing */
-#define EXP_TILDE       0x2     /* do normal tilde expansion */
-#define EXP_VARTILDE    0x4     /* expand tildes in an assignment */
-#define EXP_REDIR       0x8     /* file glob for a redirection (1 match only) */
-#define EXP_CASE        0x10    /* keeps quotes around for CASE pattern */
-#define EXP_RECORD      0x20    /* need to record arguments for ifs breakup */
-#define EXP_VARTILDE2   0x40    /* expand tildes after colons only */
-#define EXP_WORD        0x80    /* expand word in parameter expansion */
-#define EXP_QWORD       0x100   /* expand word in quoted parameter expansion */
+static char *
+ckstrdup(const char *s)
+{
+       char *p = strdup(s);
+       if (!p)
+               ash_msg_and_raise_error(bb_msg_memory_exhausted);
+       return p;
+}
 
+/*
+ * Parse trees for commands are allocated in lifo order, so we use a stack
+ * to make this more efficient, and also to avoid all sorts of exception
+ * handling code to handle interrupts in the middle of a parse.
+ *
+ * The size 504 was chosen because the Ultrix malloc handles that size
+ * well.
+ */
+static void *
+stalloc(size_t nbytes)
+{
+       char *p;
+       size_t aligned;
 
-union node;
-static void expandarg(union node *, struct arglist *, int);
-#define rmescapes(p) _rmescapes((p), 0)
-static char *_rmescapes(char *, int);
-static int casematch(union node *, char *);
+       aligned = SHELL_ALIGN(nbytes);
+       if (aligned > stacknleft) {
+               size_t len;
+               size_t blocksize;
+               struct stack_block *sp;
 
-#if ENABLE_ASH_MATH_SUPPORT
-static void expari(int);
-#endif
+               blocksize = aligned;
+               if (blocksize < MINSIZE)
+                       blocksize = MINSIZE;
+               len = sizeof(struct stack_block) - MINSIZE + blocksize;
+               if (len < blocksize)
+                       ash_msg_and_raise_error(bb_msg_memory_exhausted);
+               INT_OFF;
+               sp = ckmalloc(len);
+               sp->prev = stackp;
+               stacknxt = sp->space;
+               stacknleft = blocksize;
+               sstrend = stacknxt + blocksize;
+               stackp = sp;
+               INT_ON;
+       }
+       p = stacknxt;
+       stacknxt += aligned;
+       stacknleft -= aligned;
+       return p;
+}
 
-/*      eval.h       */
+static void
+stunalloc(void *p)
+{
+#if DEBUG
+       if (!p || (stacknxt < (char *)p) || ((char *)p < stackp->space)) {
+               write(2, "stunalloc\n", 10);
+               abort();
+       }
+#endif
+       stacknleft += stacknxt - (char *)p;
+       stacknxt = p;
+}
 
+static void
+setstackmark(struct stackmark *mark)
+{
+       mark->stackp = stackp;
+       mark->stacknxt = stacknxt;
+       mark->stacknleft = stacknleft;
+       mark->marknext = markp;
+       markp = mark;
+}
 
+static void
+popstackmark(struct stackmark *mark)
+{
+       struct stack_block *sp;
 
-struct backcmd {                /* result of evalbackcmd */
-       int fd;                 /* file descriptor to read from */
-       char *buf;              /* buffer */
-       int nleft;              /* number of chars in buffer */
-       struct job *jp;         /* job structure for command */
-};
+       INT_OFF;
+       markp = mark->marknext;
+       while (stackp != mark->stackp) {
+               sp = stackp;
+               stackp = sp->prev;
+               free(sp);
+       }
+       stacknxt = mark->stacknxt;
+       stacknleft = mark->stacknleft;
+       sstrend = mark->stacknxt + mark->stacknleft;
+       INT_ON;
+}
 
 /*
- * This file was generated by the mknodes program.
+ * When the parser reads in a string, it wants to stick the string on the
+ * stack and only adjust the stack pointer when it knows how big the
+ * string is.  Stackblock (defined in stack.h) returns a pointer to a block
+ * of space on top of the stack and stackblocklen returns the length of
+ * this block.  Growstackblock will grow this space by at least one byte,
+ * possibly moving it (like realloc).  Grabstackblock actually allocates the
+ * part of the block that has been used.
  */
+static void
+growstackblock(void)
+{
+       size_t newlen;
 
-#define NCMD 0
-#define NPIPE 1
-#define NREDIR 2
-#define NBACKGND 3
-#define NSUBSHELL 4
-#define NAND 5
-#define NOR 6
-#define NSEMI 7
-#define NIF 8
-#define NWHILE 9
-#define NUNTIL 10
-#define NFOR 11
-#define NCASE 12
-#define NCLIST 13
-#define NDEFUN 14
-#define NARG 15
-#define NTO 16
-#define NCLOBBER 17
-#define NFROM 18
-#define NFROMTO 19
-#define NAPPEND 20
-#define NTOFD 21
-#define NFROMFD 22
-#define NHERE 23
-#define NXHERE 24
-#define NNOT 25
+       newlen = stacknleft * 2;
+       if (newlen < stacknleft)
+               ash_msg_and_raise_error(bb_msg_memory_exhausted);
+       if (newlen < 128)
+               newlen += 128;
 
+       if (stacknxt == stackp->space && stackp != &stackbase) {
+               struct stack_block *oldstackp;
+               struct stackmark *xmark;
+               struct stack_block *sp;
+               struct stack_block *prevstackp;
+               size_t grosslen;
 
-struct ncmd {
-       int type;
-       union node *assign;
-       union node *args;
-       union node *redirect;
-};
+               INT_OFF;
+               oldstackp = stackp;
+               sp = stackp;
+               prevstackp = sp->prev;
+               grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
+               sp = ckrealloc(sp, grosslen);
+               sp->prev = prevstackp;
+               stackp = sp;
+               stacknxt = sp->space;
+               stacknleft = newlen;
+               sstrend = sp->space + newlen;
 
-struct npipe {
-       int type;
-       int backgnd;
-       struct nodelist *cmdlist;
-};
+               /*
+                * Stack marks pointing to the start of the old block
+                * must be relocated to point to the new block
+                */
+               xmark = markp;
+               while (xmark != NULL && xmark->stackp == oldstackp) {
+                       xmark->stackp = stackp;
+                       xmark->stacknxt = stacknxt;
+                       xmark->stacknleft = stacknleft;
+                       xmark = xmark->marknext;
+               }
+               INT_ON;
+       } else {
+               char *oldspace = stacknxt;
+               int oldlen = stacknleft;
+               char *p = stalloc(newlen);
 
-struct nredir {
-       int type;
-       union node *n;
-       union node *redirect;
-};
+               /* free the space we just allocated */
+               stacknxt = memcpy(p, oldspace, oldlen);
+               stacknleft += newlen;
+       }
+}
+
+static void
+grabstackblock(size_t len)
+{
+       len = SHELL_ALIGN(len);
+       stacknxt += len;
+       stacknleft -= len;
+}
+
+/*
+ * The following routines are somewhat easier to use than the above.
+ * The user declares a variable of type STACKSTR, which may be declared
+ * to be a register.  The macro STARTSTACKSTR initializes things.  Then
+ * the user uses the macro STPUTC to add characters to the string.  In
+ * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is
+ * grown as necessary.  When the user is done, she can just leave the
+ * string there and refer to it using stackblock().  Or she can allocate
+ * the space for it using grabstackstr().  If it is necessary to allow
+ * someone else to use the stack temporarily and then continue to grow
+ * the string, the user should use grabstack to allocate the space, and
+ * then call ungrabstr(p) to return to the previous mode of operation.
+ *
+ * USTPUTC is like STPUTC except that it doesn't check for overflow.
+ * CHECKSTACKSPACE can be called before USTPUTC to ensure that there
+ * is space for at least one character.
+ */
+static void *
+growstackstr(void)
+{
+       size_t len = stackblocksize();
+       if (herefd >= 0 && len >= 1024) {
+               full_write(herefd, stackblock(), len);
+               return stackblock();
+       }
+       growstackblock();
+       return stackblock() + len;
+}
+
+/*
+ * Called from CHECKSTRSPACE.
+ */
+static char *
+makestrspace(size_t newlen, char *p)
+{
+       size_t len = p - stacknxt;
+       size_t size = stackblocksize();
+
+       for (;;) {
+               size_t nleft;
+
+               size = stackblocksize();
+               nleft = size - len;
+               if (nleft >= newlen)
+                       break;
+               growstackblock();
+       }
+       return stackblock() + len;
+}
+
+static char *
+stack_nputstr(const char *s, size_t n, char *p)
+{
+       p = makestrspace(n, p);
+       p = memcpy(p, s, n) + n;
+       return p;
+}
+
+static char *
+stack_putstr(const char *s, char *p)
+{
+       return stack_nputstr(s, strlen(s), p);
+}
+
+
+/* ============ Unsorted yet */
+
+
+static void setpwd(const char *, int);
+
+/*      expand.h     */
+
+struct strlist {
+       struct strlist *next;
+       char *text;
+};
+
+
+struct arglist {
+       struct strlist *list;
+       struct strlist **lastp;
+};
+
+/*
+ * expandarg() flags
+ */
+#define EXP_FULL        0x1     /* perform word splitting & file globbing */
+#define EXP_TILDE       0x2     /* do normal tilde expansion */
+#define EXP_VARTILDE    0x4     /* expand tildes in an assignment */
+#define EXP_REDIR       0x8     /* file glob for a redirection (1 match only) */
+#define EXP_CASE        0x10    /* keeps quotes around for CASE pattern */
+#define EXP_RECORD      0x20    /* need to record arguments for ifs breakup */
+#define EXP_VARTILDE2   0x40    /* expand tildes after colons only */
+#define EXP_WORD        0x80    /* expand word in parameter expansion */
+#define EXP_QWORD       0x100   /* expand word in quoted parameter expansion */
+
+
+union node;
+static void expandarg(union node *, struct arglist *, int);
+#define rmescapes(p) _rmescapes((p), 0)
+static char *_rmescapes(char *, int);
+static int casematch(union node *, char *);
+
+#if ENABLE_ASH_MATH_SUPPORT
+static void expari(int);
+#endif
+
+/*      eval.h       */
+
+
+
+struct backcmd {                /* result of evalbackcmd */
+       int fd;                 /* file descriptor to read from */
+       char *buf;              /* buffer */
+       int nleft;              /* number of chars in buffer */
+       struct job *jp;         /* job structure for command */
+};
+
+/*
+ * This file was generated by the mknodes program.
+ */
+
+#define NCMD 0
+#define NPIPE 1
+#define NREDIR 2
+#define NBACKGND 3
+#define NSUBSHELL 4
+#define NAND 5
+#define NOR 6
+#define NSEMI 7
+#define NIF 8
+#define NWHILE 9
+#define NUNTIL 10
+#define NFOR 11
+#define NCASE 12
+#define NCLIST 13
+#define NDEFUN 14
+#define NARG 15
+#define NTO 16
+#define NCLOBBER 17
+#define NFROM 18
+#define NFROMTO 19
+#define NAPPEND 20
+#define NTOFD 21
+#define NFROMFD 22
+#define NHERE 23
+#define NXHERE 24
+#define NNOT 25
+
+
+struct ncmd {
+       int type;
+       union node *assign;
+       union node *args;
+       union node *redirect;
+};
+
+struct npipe {
+       int type;
+       int backgnd;
+       struct nodelist *cmdlist;
+};
+
+struct nredir {
+       int type;
+       union node *n;
+       union node *redirect;
+};
 
 struct nbinary {
        int type;
@@ -773,9 +1056,7 @@ static union node *redirnode;
 static struct heredoc *heredoc;
 static int quoteflag;                  /* set if (part of) last token was quoted */
 
-static union node *parsecmd(int);
 static void fixredir(union node *, const char *, int);
-static const char *const *findkwd(const char *);
 static char *endofname(const char *);
 
 /*      shell.h   */
@@ -867,7 +1148,8 @@ static const char *const tokname_array[] = {
        "\1}",
 };
 
-static const char *tokname(int tok)
+static const char *
+tokname(int tok)
 {
        static char buf[16];
 
@@ -878,24 +1160,20 @@ static const char *tokname(int tok)
        return buf;
 }
 
-/*      machdep.h    */
-
-/*
- * Most machines require the value returned from malloc to be aligned
- * in some way.  The following macro will get this right on many machines.
- */
-
-#define SHELL_SIZE (sizeof(union {int i; char *cp; double d; }) - 1)
-/*
- * It appears that grabstackstr() will barf with such alignments
- * because stalloc() will return a string allocated in a new stackblock.
- */
-#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)
-
-/*
- * This file was generated by the mksyntax program.
- */
+/* Wrapper around strcmp for qsort/bsearch/... */
+static int
+pstrcmp(const void *a, const void *b)
+{
+       return strcmp((const char *) a, (*(const char *const *) b) + 1);
+}
 
+static const char *const *
+findkwd(const char *s)
+{
+       return bsearch(s, tokname_array + KWDOFFSET,
+                       (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET,
+                       sizeof(const char *), pstrcmp);
+}
 
 /* Syntax classes */
 #define CWORD 0                 /* character is nothing special */
@@ -1006,7 +1284,8 @@ static const char S_I_T[][3] = {
 
 #define U_C(c) ((unsigned char)(c))
 
-static int SIT(int c, int syntax)
+static int
+SIT(int c, int syntax)
 {
        static const char spec_symbls[] = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~";
 #if ENABLE_ASH_ALIAS
@@ -1392,13 +1671,8 @@ static void calcsize(union node *);
 static void sizenodelist(struct nodelist *);
 static union node *copynode(union node *);
 static struct nodelist *copynodelist(struct nodelist *);
-static char *nodesavestr(char *);
-
+static char *nodeckstrdup(char *);
 
-static int evalstring(char *, int mask);
-union node;     /* BLETCH for ansi C */
-static void evaltree(union node *, int);
-static void evalbackcmd(union node *, struct backcmd *);
 
 static int evalskip;                   /* set if we are skipping commands */
 static int skipcount;           /* number of levels to skip */
@@ -1637,16 +1911,16 @@ static void change_random(const char *);
 # endif
 #endif
 
-/*      init.h        */
-
-static void reset(void);
-
 /*      var.h     */
 
 /*
  * Shell variables.
  */
 
+#if ENABLE_ASH_GETOPTS
+static void getoptsreset(const char *);
+#endif
+
 /* flags */
 #define VEXPORT         0x01    /* variable is exported */
 #define VREADONLY       0x02    /* variable cannot be modified */
@@ -1684,11 +1958,6 @@ static struct localvar *localvars;
 /*
  * Shell variables.
  */
-
-#if ENABLE_ASH_GETOPTS
-static void getoptsreset(const char *);
-#endif
-
 #if ENABLE_LOCALE_SUPPORT
 static void change_lc_all(const char *value);
 static void change_lc_ctype(const char *value);
@@ -1950,46 +2219,6 @@ static void showjobs(FILE *, int);
 
 static void readcmdfile(char *);
  
-/*      memalloc.h        */
-
-
-struct stackmark {
-       struct stack_block *stackp;
-       char *stacknxt;
-       size_t stacknleft;
-       struct stackmark *marknext;
-};
-
-/* minimum size of a block */
-#define MINSIZE SHELL_ALIGN(504)
-
-struct stack_block {
-       struct stack_block *prev;
-       char space[MINSIZE];
-};
-
-static struct stack_block stackbase;
-static struct stack_block *stackp = &stackbase;
-static struct stackmark *markp;
-static char *stacknxt = stackbase.space;
-static size_t stacknleft = MINSIZE;
-static char *sstrend = stackbase.space + MINSIZE;
-static int herefd = -1;
-
-
-static void *ckmalloc(size_t);
-static void *ckrealloc(void *, size_t);
-static char *savestr(const char *);
-static void *stalloc(size_t);
-static void stunalloc(void *);
-static void setstackmark(struct stackmark *);
-static void popstackmark(struct stackmark *);
-static void growstackblock(void);
-static void *growstackstr(void);
-static char *makestrspace(size_t, char *);
-static char *stnputs(const char *, size_t, char *);
-static char *stputs(const char *, char *);
-
 
 static char *_STPUTC(int c, char *p)
 {
@@ -1999,8 +2228,6 @@ static char *_STPUTC(int c, char *p)
        return p;
 }
 
-#define stackblock() ((void *)stacknxt)
-#define stackblocksize() stacknleft
 #define STARTSTACKSTR(p) ((p) = stackblock())
 #define STPUTC(c, p) ((p) = _STPUTC((c), (p)))
 #define CHECKSTRSPACE(n, p) \
@@ -2070,7 +2297,6 @@ static int nextopt(const char *);
 #define REDIR_PUSH 01           /* save previous values of file descriptors */
 #define REDIR_SAVEFD2 03       /* set preverrout */
 
-union node;
 static void redirect(union node *, int);
 static void popredir(int);
 static void clearredir(int);
@@ -2137,40 +2363,17 @@ static int is_safe_applet(char *name)
 }
 
 
-/*
- * This routine is called when an error or an interrupt occurs in an
- * interactive shell and control is returned to the main command loop.
- */
+#if ENABLE_ASH_ALIAS
+static struct alias *atab[ATABSIZE];
+
+static void setalias(const char *, const char *);
+static struct alias *freealias(struct alias *);
+static struct alias **__lookupalias(const char *);
+
 static void
-reset(void)
+setalias(const char *name, const char *val)
 {
-       /* from eval.c: */
-       evalskip = 0;
-       loopnest = 0;
-
-       /* from input.c: */
-       parselleft = parsenleft = 0;      /* clear input buffer */
-       popallfiles();
-
-       /* from parser.c: */
-       tokpushback = 0;
-       checkkwd = 0;
-
-       /* from redir.c: */
-       clearredir(0);
-}
-
-#if ENABLE_ASH_ALIAS
-static struct alias *atab[ATABSIZE];
-
-static void setalias(const char *, const char *);
-static struct alias *freealias(struct alias *);
-static struct alias **__lookupalias(const char *);
-
-static void
-setalias(const char *name, const char *val)
-{
-       struct alias *ap, **app;
+       struct alias *ap, **app;
 
        app = __lookupalias(name);
        ap = *app;
@@ -2179,13 +2382,13 @@ setalias(const char *name, const char *val)
                if (!(ap->flag & ALIASINUSE)) {
                        free(ap->val);
                }
-               ap->val = savestr(val);
+               ap->val = ckstrdup(val);
                ap->flag &= ~ALIASDEAD;
        } else {
                /* not found */
                ap = ckmalloc(sizeof(struct alias));
-               ap->name = savestr(name);
-               ap->val = savestr(val);
+               ap->name = ckstrdup(name);
+               ap->val = ckstrdup(val);
                ap->flag = 0;
                ap->next = 0;
                *app = ap;
@@ -2461,7 +2664,7 @@ static const char * updatepwd(const char *dir)
        if (*dir != '/') {
                if (curdir == nullstr)
                        return 0;
-               new = stputs(curdir, new);
+               new = stack_putstr(curdir, new);
        }
        new = makestrspace(strlen(dir) + 2, new);
        lim = stackblock() + 1;
@@ -2494,7 +2697,7 @@ static const char * updatepwd(const char *dir)
                                break;
                        /* fall through */
                default:
-                       new = stputs(p, new);
+                       new = stack_putstr(p, new);
                        USTPUTC('/', new);
                }
                p = strtok(0, "/");
@@ -2582,7 +2785,7 @@ setpwd(const char *val, int setold)
                if (!val)
                        dir = s;
        } else
-               dir = savestr(val);
+               dir = ckstrdup(val);
        if (oldcur != dir && oldcur != nullstr) {
                free(oldcur);
        }
@@ -2621,70 +2824,6 @@ static const struct builtincmd bltin = {
 };
 
 
-/*
- * Called to reset things after an exception.
- */
-
-/*
- * The eval command.
- */
-static int
-evalcmd(int argc, char **argv)
-{
-       char *p;
-       char *concat;
-       char **ap;
-
-       if (argc > 1) {
-               p = argv[1];
-               if (argc > 2) {
-                       STARTSTACKSTR(concat);
-                       ap = argv + 2;
-                       for (;;) {
-                               concat = stputs(p, concat);
-                               p = *ap++;
-                               if (p == NULL)
-                                       break;
-                               STPUTC(' ', concat);
-                       }
-                       STPUTC('\0', concat);
-                       p = grabstackstr(concat);
-               }
-               evalstring(p, ~SKIPEVAL);
-
-       }
-       return exitstatus;
-}
-
-
-/*
- * Execute a command or commands contained in a string.
- */
-static int
-evalstring(char *s, int mask)
-{
-       union node *n;
-       struct stackmark smark;
-       int skip;
-
-       setinputstring(s);
-       setstackmark(&smark);
-
-       skip = 0;
-       while ((n = parsecmd(0)) != NEOF) {
-               evaltree(n, 0);
-               popstackmark(&smark);
-               skip = evalskip;
-               if (skip)
-                       break;
-       }
-       popfile();
-
-       skip &= mask;
-       evalskip = skip;
-       return skip;
-}
-
 
 /*
  * Evaluate a parse tree.  The value is left in the global variable
@@ -3992,15 +4131,6 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path)
 }
 
 
-/*
- * Wrapper around strcmp for qsort/bsearch/...
- */
-static int pstrcmp(const void *a, const void *b)
-{
-       return strcmp((const char *) a, (*(const char *const *) b) + 1);
-}
-
-
 /*
  * Search the table of builtin commands.
  */
@@ -4606,7 +4736,7 @@ argstr(char *p, int flag)
                }
                if (length > 0) {
                        int newloc;
-                       expdest = stnputs(p, length, expdest);
+                       expdest = stack_nputstr(p, length, expdest);
                        newloc = expdest - (char *)stackblock();
                        if (breakall && !inquotes && newloc > startloc) {
                                recordregion(startloc, newloc, 0);
@@ -7370,7 +7500,7 @@ commandtext(union node *n)
        name = stackblock();
        TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n",
                name, cmdnextc, cmdnextc));
-       return savestr(name);
+       return ckstrdup(name);
 }
 
 static void
@@ -7579,592 +7709,208 @@ cmdputs(const char *s)
                        str = "$(...)";
                        goto dostr;
                case CTLBACKQ+CTLQUOTE:
-                       str = "\"$(...)\"";
-                       goto dostr;
-#if ENABLE_ASH_MATH_SUPPORT
-               case CTLARI:
-                       str = "$((";
-                       goto dostr;
-               case CTLENDARI:
-                       str = "))";
-                       goto dostr;
-#endif
-               case CTLQUOTEMARK:
-                       quoted ^= 1;
-                       c = '"';
-                       break;
-               case '=':
-                       if (subtype == 0)
-                               break;
-                       if ((subtype & VSTYPE) != VSNORMAL)
-                               quoted <<= 1;
-                       str = vstype[subtype & VSTYPE];
-                       if (subtype & VSNUL)
-                               c = ':';
-                       else
-                               goto checkstr;
-                       break;
-               case '\'':
-               case '\\':
-               case '"':
-               case '$':
-                       /* These can only happen inside quotes */
-                       cc[0] = c;
-                       str = cc;
-                       c = '\\';
-                       break;
-               default:
-                       break;
-               }
-               USTPUTC(c, nextc);
- checkstr:
-               if (!str)
-                       continue;
- dostr:
-               while ((c = *str++)) {
-                       USTPUTC(c, nextc);
-               }
-       }
-       if (quoted & 1) {
-               USTPUTC('"', nextc);
-       }
-       *nextc = 0;
-       cmdnextc = nextc;
-}
-
-
-static void
-showpipe(struct job *jp, FILE *out)
-{
-       struct procstat *sp;
-       struct procstat *spend;
-
-       spend = jp->ps + jp->nprocs;
-       for (sp = jp->ps + 1; sp < spend; sp++)
-               fprintf(out, " | %s", sp->cmd);
-       outcslow('\n', out);
-       flush_stdout_stderr();
-}
-
-static void
-xtcsetpgrp(int fd, pid_t pgrp)
-{
-       if (tcsetpgrp(fd, pgrp))
-               ash_msg_and_raise_error("Cannot set tty process group (%m)");
-}
-#endif /* JOBS */
-
-static int
-getstatus(struct job *job)
-{
-       int status;
-       int retval;
-
-       status = job->ps[job->nprocs - 1].status;
-       retval = WEXITSTATUS(status);
-       if (!WIFEXITED(status)) {
-#if JOBS
-               retval = WSTOPSIG(status);
-               if (!WIFSTOPPED(status))
-#endif
-               {
-                       /* XXX: limits number of signals */
-                       retval = WTERMSIG(status);
-#if JOBS
-                       if (retval == SIGINT)
-                               job->sigint = 1;
-#endif
-               }
-               retval += 128;
-       }
-       TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n",
-               jobno(job), job->nprocs, status, retval));
-       return retval;
-}
-
-#if ENABLE_ASH_MAIL
-/*      mail.c       */
-
-/*
- * Routines to check for mail.  (Perhaps make part of main.c?)
- */
-
-#define MAXMBOXES 10
-
-/* times of mailboxes */
-static time_t mailtime[MAXMBOXES];
-/* Set if MAIL or MAILPATH is changed. */
-static int mail_var_path_changed;
-
-
-/*
- * Print appropriate message(s) if mail has arrived.
- * If mail_var_path_changed is set,
- * then the value of MAIL has mail_var_path_changed,
- * so we just update the values.
- */
-static void
-chkmail(void)
-{
-       const char *mpath;
-       char *p;
-       char *q;
-       time_t *mtp;
-       struct stackmark smark;
-       struct stat statb;
-
-       setstackmark(&smark);
-       mpath = mpathset() ? mpathval() : mailval();
-       for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) {
-               p = padvance(&mpath, nullstr);
-               if (p == NULL)
-                       break;
-               if (*p == '\0')
-                       continue;
-               for (q = p; *q; q++);
-#if DEBUG
-               if (q[-1] != '/')
-                       abort();
-#endif
-               q[-1] = '\0';                   /* delete trailing '/' */
-               if (stat(p, &statb) < 0) {
-                       *mtp = 0;
-                       continue;
-               }
-               if (!mail_var_path_changed && statb.st_mtime != *mtp) {
-                       fprintf(
-                               stderr, snlfmt,
-                               pathopt ? pathopt : "you have mail"
-                       );
-               }
-               *mtp = statb.st_mtime;
-       }
-       mail_var_path_changed = 0;
-       popstackmark(&smark);
-}
-
-
-static void
-changemail(const char *val)
-{
-       mail_var_path_changed++;
-}
-
-#endif /* ASH_MAIL */
-
-/*
- * Read and execute commands.  "Top" is nonzero for the top level command
- * loop; it turns on prompting if the shell is interactive.
- */
-static int
-cmdloop(int top)
-{
-       union node *n;
-       struct stackmark smark;
-       int inter;
-       int numeof = 0;
-
-       TRACE(("cmdloop(%d) called\n", top));
-       for (;;) {
-               int skip;
-
-               setstackmark(&smark);
-#if JOBS
-               if (jobctl)
-                       showjobs(stderr, SHOW_CHANGED);
-#endif
-               inter = 0;
-               if (iflag && top) {
-                       inter++;
-#if ENABLE_ASH_MAIL
-                       chkmail();
-#endif
-               }
-               n = parsecmd(inter);
-               /* showtree(n); DEBUG */
-               if (n == NEOF) {
-                       if (!top || numeof >= 50)
-                               break;
-                       if (!stoppedjobs()) {
-                               if (!Iflag)
-                                       break;
-                               out2str("\nUse \"exit\" to leave shell.\n");
-                       }
-                       numeof++;
-               } else if (nflag == 0) {
-                       job_warning = (job_warning == 2) ? 1 : 0;
-                       numeof = 0;
-                       evaltree(n, 0);
-               }
-               popstackmark(&smark);
-               skip = evalskip;
-
-               if (skip) {
-                       evalskip = 0;
-                       return skip & SKIPEVAL;
-               }
-       }
-
-       return 0;
-}
-
-
-/*
- * Read a file containing shell functions.
- */
-static void
-readcmdfile(char *name)
-{
-       setinputfile(name, INPUT_PUSH_FILE);
-       cmdloop(0);
-       popfile();
-}
-
-
-/*
- * Take commands from a file.  To be compatible we should do a path
- * search for the file, which is necessary to find sub-commands.
- */
-static char * find_dot_file(char *name)
-{
-       char *fullname;
-       const char *path = pathval();
-       struct stat statb;
-
-       /* don't try this for absolute or relative paths */
-       if (strchr(name, '/'))
-               return name;
-
-       while ((fullname = padvance(&path, name)) != NULL) {
-               if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
-                       /*
-                        * Don't bother freeing here, since it will
-                        * be freed by the caller.
-                        */
-                       return fullname;
-               }
-               stunalloc(fullname);
-       }
-
-       /* not found in the PATH */
-       ash_msg_and_raise_error("%s: not found", name);
-       /* NOTREACHED */
-}
-
-static int dotcmd(int argc, char **argv)
-{
-       struct strlist *sp;
-       volatile struct shparam saveparam;
-       int status = 0;
-
-       for (sp = cmdenviron; sp; sp = sp->next)
-               setvareq(xstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
-
-       if (argc >= 2) {        /* That's what SVR2 does */
-               char *fullname;
-
-               fullname = find_dot_file(argv[1]);
-
-               if (argc > 2) {
-                       saveparam = shellparam;
-                       shellparam.malloc = 0;
-                       shellparam.nparam = argc - 2;
-                       shellparam.p = argv + 2;
-               };
-
-               setinputfile(fullname, INPUT_PUSH_FILE);
-               commandname = fullname;
-               cmdloop(0);
-               popfile();
-
-               if (argc > 2) {
-                       freeparam(&shellparam);
-                       shellparam = saveparam;
-               };
-               status = exitstatus;
-       }
-       return status;
-}
-
-
-static int
-exitcmd(int argc, char **argv)
-{
-       if (stoppedjobs())
-               return 0;
-       if (argc > 1)
-               exitstatus = number(argv[1]);
-       raise_exception(EXEXIT);
-       /* NOTREACHED */
-}
-
-#if ENABLE_ASH_BUILTIN_ECHO
-static int
-echocmd(int argc, char **argv)
-{
-       return bb_echo(argv);
-}
-#endif
-
-#if ENABLE_ASH_BUILTIN_TEST
-static int
-testcmd(int argc, char **argv)
-{
-       return bb_test(argc, argv);
-}
-#endif
-
-/*      memalloc.c        */
-
-/*
- * Same for malloc, realloc, but returns an error when out of space.
- */
-static void *
-ckrealloc(void * p, size_t nbytes)
-{
-       p = realloc(p, nbytes);
-       if (p == NULL)
-               ash_msg_and_raise_error(bb_msg_memory_exhausted);
-       return p;
-}
-
-static void *
-ckmalloc(size_t nbytes)
-{
-       return ckrealloc(NULL, nbytes);
-}
-
-/*
- * Make a copy of a string in safe storage.
- */
-static char *
-savestr(const char *s)
-{
-       char *p = strdup(s);
-       if (!p)
-               ash_msg_and_raise_error(bb_msg_memory_exhausted);
-       return p;
-}
-
-
-/*
- * Parse trees for commands are allocated in lifo order, so we use a stack
- * to make this more efficient, and also to avoid all sorts of exception
- * handling code to handle interrupts in the middle of a parse.
- *
- * The size 504 was chosen because the Ultrix malloc handles that size
- * well.
- */
-static void *
-stalloc(size_t nbytes)
-{
-       char *p;
-       size_t aligned;
-
-       aligned = SHELL_ALIGN(nbytes);
-       if (aligned > stacknleft) {
-               size_t len;
-               size_t blocksize;
-               struct stack_block *sp;
-
-               blocksize = aligned;
-               if (blocksize < MINSIZE)
-                       blocksize = MINSIZE;
-               len = sizeof(struct stack_block) - MINSIZE + blocksize;
-               if (len < blocksize)
-                       ash_msg_and_raise_error(bb_msg_memory_exhausted);
-               INT_OFF;
-               sp = ckmalloc(len);
-               sp->prev = stackp;
-               stacknxt = sp->space;
-               stacknleft = blocksize;
-               sstrend = stacknxt + blocksize;
-               stackp = sp;
-               INT_ON;
+                       str = "\"$(...)\"";
+                       goto dostr;
+#if ENABLE_ASH_MATH_SUPPORT
+               case CTLARI:
+                       str = "$((";
+                       goto dostr;
+               case CTLENDARI:
+                       str = "))";
+                       goto dostr;
+#endif
+               case CTLQUOTEMARK:
+                       quoted ^= 1;
+                       c = '"';
+                       break;
+               case '=':
+                       if (subtype == 0)
+                               break;
+                       if ((subtype & VSTYPE) != VSNORMAL)
+                               quoted <<= 1;
+                       str = vstype[subtype & VSTYPE];
+                       if (subtype & VSNUL)
+                               c = ':';
+                       else
+                               goto checkstr;
+                       break;
+               case '\'':
+               case '\\':
+               case '"':
+               case '$':
+                       /* These can only happen inside quotes */
+                       cc[0] = c;
+                       str = cc;
+                       c = '\\';
+                       break;
+               default:
+                       break;
+               }
+               USTPUTC(c, nextc);
+ checkstr:
+               if (!str)
+                       continue;
+ dostr:
+               while ((c = *str++)) {
+                       USTPUTC(c, nextc);
+               }
        }
-       p = stacknxt;
-       stacknxt += aligned;
-       stacknleft -= aligned;
-       return p;
+       if (quoted & 1) {
+               USTPUTC('"', nextc);
+       }
+       *nextc = 0;
+       cmdnextc = nextc;
 }
 
 
 static void
-stunalloc(void *p)
+showpipe(struct job *jp, FILE *out)
 {
-#if DEBUG
-       if (!p || (stacknxt < (char *)p) || ((char *)p < stackp->space)) {
-               write(2, "stunalloc\n", 10);
-               abort();
-       }
-#endif
-       stacknleft += stacknxt - (char *)p;
-       stacknxt = p;
-}
+       struct procstat *sp;
+       struct procstat *spend;
 
+       spend = jp->ps + jp->nprocs;
+       for (sp = jp->ps + 1; sp < spend; sp++)
+               fprintf(out, " | %s", sp->cmd);
+       outcslow('\n', out);
+       flush_stdout_stderr();
+}
 
 static void
-setstackmark(struct stackmark *mark)
+xtcsetpgrp(int fd, pid_t pgrp)
 {
-       mark->stackp = stackp;
-       mark->stacknxt = stacknxt;
-       mark->stacknleft = stacknleft;
-       mark->marknext = markp;
-       markp = mark;
+       if (tcsetpgrp(fd, pgrp))
+               ash_msg_and_raise_error("Cannot set tty process group (%m)");
 }
+#endif /* JOBS */
 
-
-static void
-popstackmark(struct stackmark *mark)
+static int
+getstatus(struct job *job)
 {
-       struct stack_block *sp;
+       int status;
+       int retval;
 
-       INT_OFF;
-       markp = mark->marknext;
-       while (stackp != mark->stackp) {
-               sp = stackp;
-               stackp = sp->prev;
-               free(sp);
+       status = job->ps[job->nprocs - 1].status;
+       retval = WEXITSTATUS(status);
+       if (!WIFEXITED(status)) {
+#if JOBS
+               retval = WSTOPSIG(status);
+               if (!WIFSTOPPED(status))
+#endif
+               {
+                       /* XXX: limits number of signals */
+                       retval = WTERMSIG(status);
+#if JOBS
+                       if (retval == SIGINT)
+                               job->sigint = 1;
+#endif
+               }
+               retval += 128;
        }
-       stacknxt = mark->stacknxt;
-       stacknleft = mark->stacknleft;
-       sstrend = mark->stacknxt + mark->stacknleft;
-       INT_ON;
+       TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n",
+               jobno(job), job->nprocs, status, retval));
+       return retval;
 }
 
+#if ENABLE_ASH_MAIL
+/*      mail.c       */
 
 /*
- * When the parser reads in a string, it wants to stick the string on the
- * stack and only adjust the stack pointer when it knows how big the
- * string is.  Stackblock (defined in stack.h) returns a pointer to a block
- * of space on top of the stack and stackblocklen returns the length of
- * this block.  Growstackblock will grow this space by at least one byte,
- * possibly moving it (like realloc).  Grabstackblock actually allocates the
- * part of the block that has been used.
+ * Routines to check for mail.  (Perhaps make part of main.c?)
  */
-static void
-growstackblock(void)
-{
-       size_t newlen;
 
-       newlen = stacknleft * 2;
-       if (newlen < stacknleft)
-               ash_msg_and_raise_error(bb_msg_memory_exhausted);
-       if (newlen < 128)
-               newlen += 128;
+#define MAXMBOXES 10
 
-       if (stacknxt == stackp->space && stackp != &stackbase) {
-               struct stack_block *oldstackp;
-               struct stackmark *xmark;
-               struct stack_block *sp;
-               struct stack_block *prevstackp;
-               size_t grosslen;
+/* times of mailboxes */
+static time_t mailtime[MAXMBOXES];
+/* Set if MAIL or MAILPATH is changed. */
+static int mail_var_path_changed;
 
-               INT_OFF;
-               oldstackp = stackp;
-               sp = stackp;
-               prevstackp = sp->prev;
-               grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
-               sp = ckrealloc(sp, grosslen);
-               sp->prev = prevstackp;
-               stackp = sp;
-               stacknxt = sp->space;
-               stacknleft = newlen;
-               sstrend = sp->space + newlen;
 
-               /*
-                * Stack marks pointing to the start of the old block
-                * must be relocated to point to the new block
-                */
-               xmark = markp;
-               while (xmark != NULL && xmark->stackp == oldstackp) {
-                       xmark->stackp = stackp;
-                       xmark->stacknxt = stacknxt;
-                       xmark->stacknleft = stacknleft;
-                       xmark = xmark->marknext;
-               }
-               INT_ON;
-       } else {
-               char *oldspace = stacknxt;
-               int oldlen = stacknleft;
-               char *p = stalloc(newlen);
+/*
+ * Print appropriate message(s) if mail has arrived.
+ * If mail_var_path_changed is set,
+ * then the value of MAIL has mail_var_path_changed,
+ * so we just update the values.
+ */
+static void
+chkmail(void)
+{
+       const char *mpath;
+       char *p;
+       char *q;
+       time_t *mtp;
+       struct stackmark smark;
+       struct stat statb;
 
-               /* free the space we just allocated */
-               stacknxt = memcpy(p, oldspace, oldlen);
-               stacknleft += newlen;
+       setstackmark(&smark);
+       mpath = mpathset() ? mpathval() : mailval();
+       for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) {
+               p = padvance(&mpath, nullstr);
+               if (p == NULL)
+                       break;
+               if (*p == '\0')
+                       continue;
+               for (q = p; *q; q++);
+#if DEBUG
+               if (q[-1] != '/')
+                       abort();
+#endif
+               q[-1] = '\0';                   /* delete trailing '/' */
+               if (stat(p, &statb) < 0) {
+                       *mtp = 0;
+                       continue;
+               }
+               if (!mail_var_path_changed && statb.st_mtime != *mtp) {
+                       fprintf(
+                               stderr, snlfmt,
+                               pathopt ? pathopt : "you have mail"
+                       );
+               }
+               *mtp = statb.st_mtime;
        }
-}
-
-static void grabstackblock(size_t len)
-{
-       len = SHELL_ALIGN(len);
-       stacknxt += len;
-       stacknleft -= len;
+       mail_var_path_changed = 0;
+       popstackmark(&smark);
 }
 
 
-/*
- * The following routines are somewhat easier to use than the above.
- * The user declares a variable of type STACKSTR, which may be declared
- * to be a register.  The macro STARTSTACKSTR initializes things.  Then
- * the user uses the macro STPUTC to add characters to the string.  In
- * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is
- * grown as necessary.  When the user is done, she can just leave the
- * string there and refer to it using stackblock().  Or she can allocate
- * the space for it using grabstackstr().  If it is necessary to allow
- * someone else to use the stack temporarily and then continue to grow
- * the string, the user should use grabstack to allocate the space, and
- * then call ungrabstr(p) to return to the previous mode of operation.
- *
- * USTPUTC is like STPUTC except that it doesn't check for overflow.
- * CHECKSTACKSPACE can be called before USTPUTC to ensure that there
- * is space for at least one character.
- */
-static void *
-growstackstr(void)
+static void
+changemail(const char *val)
 {
-       size_t len = stackblocksize();
-       if (herefd >= 0 && len >= 1024) {
-               full_write(herefd, stackblock(), len);
-               return stackblock();
-       }
-       growstackblock();
-       return stackblock() + len;
+       mail_var_path_changed++;
 }
 
+#endif /* ASH_MAIL */
+
 /*
- * Called from CHECKSTRSPACE.
+ * Take commands from a file.  To be compatible we should do a path
+ * search for the file, which is necessary to find sub-commands.
  */
 static char *
-makestrspace(size_t newlen, char *p)
+find_dot_file(char *name)
 {
-       size_t len = p - stacknxt;
-       size_t size = stackblocksize();
+       char *fullname;
+       const char *path = pathval();
+       struct stat statb;
 
-       for (;;) {
-               size_t nleft;
+       /* don't try this for absolute or relative paths */
+       if (strchr(name, '/'))
+               return name;
 
-               size = stackblocksize();
-               nleft = size - len;
-               if (nleft >= newlen)
-                       break;
-               growstackblock();
+       while ((fullname = padvance(&path, name)) != NULL) {
+               if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
+                       /*
+                        * Don't bother freeing here, since it will
+                        * be freed by the caller.
+                        */
+                       return fullname;
+               }
+               stunalloc(fullname);
        }
-       return stackblock() + len;
-}
-
-static char *
-stnputs(const char *s, size_t n, char *p)
-{
-       p = makestrspace(n, p);
-       p = memcpy(p, s, n) + n;
-       return p;
-}
 
-static char *
-stputs(const char *s, char *p)
-{
-       return stnputs(s, strlen(s), p);
+       /* not found in the PATH */
+       ash_msg_and_raise_error("%s: not found", name);
+       /* NOTREACHED */
 }
 
 /*      mystring.c   */
@@ -8404,7 +8150,7 @@ copynode(union node *n)
                new->nif.test = copynode(n->nif.test);
                break;
        case NFOR:
-               new->nfor.var = nodesavestr(n->nfor.var);
+               new->nfor.var = nodeckstrdup(n->nfor.var);
                new->nfor.body = copynode(n->nfor.body);
                new->nfor.args = copynode(n->nfor.args);
                break;
@@ -8420,7 +8166,7 @@ copynode(union node *n)
        case NDEFUN:
        case NARG:
                new->narg.backquote = copynodelist(n->narg.backquote);
-               new->narg.text = nodesavestr(n->narg.text);
+               new->narg.text = nodeckstrdup(n->narg.text);
                new->narg.next = copynode(n->narg.next);
                break;
        case NTO:
@@ -8474,7 +8220,7 @@ copynodelist(struct nodelist *lp)
 
 
 static char *
-nodesavestr(char *s)
+nodeckstrdup(char *s)
 {
        char *rtn = funcstring;
 
@@ -8612,7 +8358,7 @@ setparam(char **argv)
        for (nparam = 0; argv[nparam]; nparam++);
        ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap));
        while (*argv) {
-               *ap++ = savestr(*argv++);
+               *ap++ = ckstrdup(*argv++);
        }
        *ap = NULL;
        freeparam(&shellparam);
@@ -9411,7 +9157,8 @@ makename(void)
        return n;
 }
 
-static void fixredir(union node *n, const char *text, int err)
+static void
+fixredir(union node *n, const char *text, int err)
 {
        TRACE(("Fix redir %s %d\n", text, err));
        if (!err)
@@ -9491,7 +9238,8 @@ parseheredoc(void)
        }
 }
 
-static char peektoken(void)
+static char
+peektoken(void)
 {
        int t;
 
@@ -10453,13 +10201,195 @@ static void setprompt(int whichprompt)
 }
 
 
-static const char *const *findkwd(const char *s)
+/*
+ * Execute a command or commands contained in a string.
+ */
+static int
+evalstring(char *s, int mask)
 {
-       return bsearch(s, tokname_array + KWDOFFSET,
-                       (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET,
-                       sizeof(const char *), pstrcmp);
+       union node *n;
+       struct stackmark smark;
+       int skip;
+
+       setinputstring(s);
+       setstackmark(&smark);
+
+       skip = 0;
+       while ((n = parsecmd(0)) != NEOF) {
+               evaltree(n, 0);
+               popstackmark(&smark);
+               skip = evalskip;
+               if (skip)
+                       break;
+       }
+       popfile();
+
+       skip &= mask;
+       evalskip = skip;
+       return skip;
+}
+
+/*
+ * The eval command.
+ */
+static int
+evalcmd(int argc, char **argv)
+{
+       char *p;
+       char *concat;
+       char **ap;
+
+       if (argc > 1) {
+               p = argv[1];
+               if (argc > 2) {
+                       STARTSTACKSTR(concat);
+                       ap = argv + 2;
+                       for (;;) {
+                               concat = stack_putstr(p, concat);
+                               p = *ap++;
+                               if (p == NULL)
+                                       break;
+                               STPUTC(' ', concat);
+                       }
+                       STPUTC('\0', concat);
+                       p = grabstackstr(concat);
+               }
+               evalstring(p, ~SKIPEVAL);
+
+       }
+       return exitstatus;
+}
+
+/*
+ * Read and execute commands.  "Top" is nonzero for the top level command
+ * loop; it turns on prompting if the shell is interactive.
+ */
+static int
+cmdloop(int top)
+{
+       union node *n;
+       struct stackmark smark;
+       int inter;
+       int numeof = 0;
+
+       TRACE(("cmdloop(%d) called\n", top));
+       for (;;) {
+               int skip;
+
+               setstackmark(&smark);
+#if JOBS
+               if (jobctl)
+                       showjobs(stderr, SHOW_CHANGED);
+#endif
+               inter = 0;
+               if (iflag && top) {
+                       inter++;
+#if ENABLE_ASH_MAIL
+                       chkmail();
+#endif
+               }
+               n = parsecmd(inter);
+               /* showtree(n); DEBUG */
+               if (n == NEOF) {
+                       if (!top || numeof >= 50)
+                               break;
+                       if (!stoppedjobs()) {
+                               if (!Iflag)
+                                       break;
+                               out2str("\nUse \"exit\" to leave shell.\n");
+                       }
+                       numeof++;
+               } else if (nflag == 0) {
+                       job_warning = (job_warning == 2) ? 1 : 0;
+                       numeof = 0;
+                       evaltree(n, 0);
+               }
+               popstackmark(&smark);
+               skip = evalskip;
+
+               if (skip) {
+                       evalskip = 0;
+                       return skip & SKIPEVAL;
+               }
+       }
+       return 0;
+}
+
+static int
+dotcmd(int argc, char **argv)
+{
+       struct strlist *sp;
+       volatile struct shparam saveparam;
+       int status = 0;
+
+       for (sp = cmdenviron; sp; sp = sp->next)
+               setvareq(xstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
+
+       if (argc >= 2) {        /* That's what SVR2 does */
+               char *fullname;
+
+               fullname = find_dot_file(argv[1]);
+
+               if (argc > 2) {
+                       saveparam = shellparam;
+                       shellparam.malloc = 0;
+                       shellparam.nparam = argc - 2;
+                       shellparam.p = argv + 2;
+               };
+
+               setinputfile(fullname, INPUT_PUSH_FILE);
+               commandname = fullname;
+               cmdloop(0);
+               popfile();
+
+               if (argc > 2) {
+                       freeparam(&shellparam);
+                       shellparam = saveparam;
+               };
+               status = exitstatus;
+       }
+       return status;
+}
+
+static int
+exitcmd(int argc, char **argv)
+{
+       if (stoppedjobs())
+               return 0;
+       if (argc > 1)
+               exitstatus = number(argv[1]);
+       raise_exception(EXEXIT);
+       /* NOTREACHED */
+}
+
+#if ENABLE_ASH_BUILTIN_ECHO
+static int
+echocmd(int argc, char **argv)
+{
+       return bb_echo(argv);
+}
+#endif
+
+#if ENABLE_ASH_BUILTIN_TEST
+static int
+testcmd(int argc, char **argv)
+{
+       return bb_test(argc, argv);
+}
+#endif
+
+/*
+ * Read a file containing shell functions.
+ */
+static void
+readcmdfile(char *name)
+{
+       setinputfile(name, INPUT_PUSH_FILE);
+       cmdloop(0);
+       popfile();
 }
 
+
 /*      redir.c      */
 
 /*
@@ -10477,7 +10407,8 @@ static const char *const *findkwd(const char *s)
  * Open a file in noclobber mode.
  * The code was copied from bash.
  */
-static int noclobberopen(const char *fname)
+static int
+noclobberopen(const char *fname)
 {
        int r, fd;
        struct stat finfo, finfo2;
@@ -10536,7 +10467,8 @@ static int noclobberopen(const char *fname)
  * data to a pipe.  If the document is short, we can stuff the data in
  * the pipe without forking.
  */
-static int openhere(union node *redir)
+static int
+openhere(union node *redir)
 {
        int pip[2];
        size_t len = 0;
@@ -10633,7 +10565,8 @@ openredirect(union node *redir)
        ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file"));
 }
 
-static void dupredirect(union node *redir, int f)
+static void
+dupredirect(union node *redir, int f)
 {
        int fd = redir->nfile.fd;
 
@@ -11208,7 +11141,7 @@ trapcmd(int argc, char **argv)
                        if (LONE_DASH(action))
                                action = NULL;
                        else
-                               action = savestr(action);
+                               action = ckstrdup(action);
                }
                if (trap[signo])
                        free(trap[signo]);
@@ -11614,7 +11547,7 @@ setvareq(char *s, int flags)
                *vpp = vp;
        }
        if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
-               s = savestr(s);
+               s = ckstrdup(s);
        vp->text = s;
        vp->flags = flags;
 }
@@ -13311,6 +13244,26 @@ read_profile(const char *name)
                exitshell();
 }
 
+/*
+ * This routine is called when an error or an interrupt occurs in an
+ * interactive shell and control is returned to the main command loop.
+ */
+static void
+reset(void)
+{
+       /* from eval.c: */
+       evalskip = 0;
+       loopnest = 0;
+       /* from input.c: */
+       parselleft = parsenleft = 0;      /* clear input buffer */
+       popallfiles();
+       /* from parser.c: */
+       tokpushback = 0;
+       checkkwd = 0;
+       /* from redir.c: */
+       clearredir(0);
+}
+
 #if PROFILE
 static short profile_buf[16384];
 extern int etext();
@@ -13404,9 +13357,9 @@ int ash_main(int argc, char **argv)
        state = 3;
        if (
 #ifndef linux
-               getuid() == geteuid() && getgid() == getegid() &&
+        getuid() == geteuid() && getgid() == getegid() &&
 #endif
-               iflag
+        iflag
        ) {
                shinit = lookupvar("ENV");
                if (shinit != NULL && *shinit != '\0') {