ash: cleanup part 1
diff --git a/shell/ash.c b/shell/ash.c
index e262c0e..379e8ab 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -503,6 +503,289 @@
 }
 
 
+/* ============ Memory allocation */
+
+/*
+ * 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),
+};
+
+struct stack_block {
+	struct stack_block *prev;
+	char space[MINSIZE];
+};
+
+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;
+
+#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);
+}
+
+/*
+ * Make a copy of a string in safe storage.
+ */
+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;
+
+	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;
+	}
+	p = stacknxt;
+	stacknxt += aligned;
+	stacknleft -= aligned;
+	return p;
+}
+
+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;
+
+	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;
+}
+
+/*
+ * 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;
+
+	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;
+
+		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);
+
+		/* 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 */
 
 
@@ -773,9 +1056,7 @@
 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 @@
 	"\1}",
 };
 
-static const char *tokname(int tok)
+static const char *
+tokname(int tok)
 {
 	static char buf[16];
 
@@ -878,24 +1160,20 @@
 	return buf;
 }
 
-/*      machdep.h    */
+/* 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);
+}
 
-/*
- * 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.
- */
-
+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 @@
 
 #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,14 +1671,9 @@
 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 */
 static int funcnest;                   /* depth of function calls */
@@ -1637,16 +1911,16 @@
 # 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 @@
 /*
  * 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 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 @@
 	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 @@
 #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,29 +2363,6 @@
 }
 
 
-/*
- * 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 ENABLE_ASH_ALIAS
 static struct alias *atab[ATABSIZE];
 
@@ -2179,13 +2382,13 @@
 		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 @@
 	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 @@
 				break;
 			/* fall through */
 		default:
-			new = stputs(p, new);
+			new = stack_putstr(p, new);
 			USTPUTC('/', new);
 		}
 		p = strtok(0, "/");
@@ -2582,7 +2785,7 @@
 		if (!val)
 			dir = s;
 	} else
-		dir = savestr(val);
+		dir = ckstrdup(val);
 	if (oldcur != dir && oldcur != nullstr) {
 		free(oldcur);
 	}
@@ -2621,70 +2824,6 @@
 };
 
 
-/*
- * 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
@@ -3993,15 +4132,6 @@
 
 
 /*
- * 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.
  */
 static struct builtincmd *
@@ -4606,7 +4736,7 @@
 		}
 		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 @@
 	name = stackblock();
 	TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n",
 		name, cmdnextc, cmdnextc));
-	return savestr(name);
+	return ckstrdup(name);
 }
 
 static void
@@ -7753,79 +7883,11 @@
 #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)
+static char *
+find_dot_file(char *name)
 {
 	char *fullname;
 	const char *path = pathval();
@@ -7851,322 +7913,6 @@
 	/* 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;
-	}
-	p = stacknxt;
-	stacknxt += aligned;
-	stacknleft -= aligned;
-	return p;
-}
-
-
-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;
-
-	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;
-}
-
-
-/*
- * 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;
-
-	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;
-
-		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);
-
-		/* 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 *
-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);
-}
-
 /*      mystring.c   */
 
 /*
@@ -8404,7 +8150,7 @@
 		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 @@
 	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 @@
 
 
 static char *
-nodesavestr(char *s)
+nodeckstrdup(char *s)
 {
 	char *rtn = funcstring;
 
@@ -8612,7 +8358,7 @@
 	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 @@
 	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 @@
 	}
 }
 
-static char peektoken(void)
+static char
+peektoken(void)
 {
 	int t;
 
@@ -10453,13 +10201,195 @@
 }
 
 
-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 @@
  * 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 @@
  * 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 @@
 	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 @@
 			if (LONE_DASH(action))
 				action = NULL;
 			else
-				action = savestr(action);
+				action = ckstrdup(action);
 		}
 		if (trap[signo])
 			free(trap[signo]);
@@ -11614,7 +11547,7 @@
 		*vpp = vp;
 	}
 	if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
-		s = savestr(s);
+		s = ckstrdup(s);
 	vp->text = s;
 	vp->flags = flags;
 }
@@ -13311,6 +13244,26 @@
 		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 @@
 	state = 3;
 	if (
 #ifndef linux
-		getuid() == geteuid() && getgid() == getegid() &&
+	 getuid() == geteuid() && getgid() == getegid() &&
 #endif
-		iflag
+	 iflag
 	) {
 		shinit = lookupvar("ENV");
 		if (shinit != NULL && *shinit != '\0') {