ash: handle "A=1 A=2 B=$A; echo $B". closes bug 947.

diff --git a/shell/ash.c b/shell/ash.c
index 10eb90d..4f9caa4 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -42,19 +42,18 @@
  * a quit signal will generate a core dump.
  */
 #define DEBUG 0
-#define IFS_BROKEN
 #define PROFILE 0
-#if ENABLE_ASH_JOB_CONTROL
-#define JOBS 1
-#else
-#define JOBS 0
-#endif
+
+#define IFS_BROKEN
+
+#define JOBS ENABLE_ASH_JOB_CONTROL
 
 #if DEBUG
 #ifndef _GNU_SOURCE
 #define _GNU_SOURCE
 #endif
 #endif
+
 #include "busybox.h" /* for applet_names */
 #include <paths.h>
 #include <setjmp.h>
@@ -5501,15 +5500,19 @@
 #endif
 
 /* argstr needs it */
-static char *evalvar(char *p, int flag);
+static char *evalvar(char *p, int flag, struct strlist *var_str_list);
 
 /*
  * Perform variable and command substitution.  If EXP_FULL is set, output CTLESC
  * characters to allow for further processing.  Otherwise treat
  * $@ like $* since no splitting will be performed.
+ *
+ * var_str_list (can be NULL) is a list of "VAR=val" strings which take precedence
+ * over shell varables. Needed for "A=a B=$A; echo $B" case - we use it
+ * for correct expansion of "B=$A" word.
  */
 static void
-argstr(char *p, int flag)
+argstr(char *p, int flag, struct strlist *var_str_list)
 {
 	static const char spclchars[] ALIGN1 = {
 		'=',
@@ -5611,7 +5614,7 @@
 					p[5] == CTLQUOTEMARK
 				))
 			) {
-				p = evalvar(p + 1, flag) + 1;
+				p = evalvar(p + 1, flag, /* var_str_list: */ NULL) + 1;
 				goto start;
 			}
 			inquotes = !inquotes;
@@ -5627,7 +5630,7 @@
 			length++;
 			goto addquote;
 		case CTLVAR:
-			p = evalvar(p, flag);
+			p = evalvar(p, flag, var_str_list);
 			goto start;
 		case CTLBACKQ:
 			c = 0;
@@ -5731,7 +5734,8 @@
 }
 
 static const char *
-subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags, int quotes)
+subevalvar(char *p, char *str, int strloc, int subtype,
+		int startloc, int varflags, int quotes, struct strlist *var_str_list)
 {
 	char *startp;
 	char *loc;
@@ -5743,7 +5747,8 @@
 	char *(*scan)(char *, char *, char *, char *, int , int);
 
 	herefd = -1;
-	argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0);
+	argstr(p, (subtype != VSASSIGN && subtype != VSQUESTION) ? EXP_CASE : 0,
+			var_str_list);
 	STPUTC('\0', expdest);
 	herefd = saveherefd;
 	argbackq = saveargbackq;
@@ -5802,7 +5807,7 @@
  * Add the value of a specialized variable to the stack string.
  */
 static ssize_t
-varvalue(char *name, int varflags, int flags)
+varvalue(char *name, int varflags, int flags, struct strlist *var_str_list)
 {
 	int num;
 	char *p;
@@ -5899,6 +5904,30 @@
 		p = num ? shellparam.p[num - 1] : arg0;
 		goto value;
 	default:
+		/* NB: name has form "VAR=..." */
+
+		/* "A=a B=$A" case: var_str_list is a list of "A=a" strings
+		 * which should be considered before we check variables. */
+		if (var_str_list) {
+			unsigned name_len = (strchrnul(name, '=') - name) + 1;
+			p = NULL;
+			do {
+				char *str = var_str_list->text;
+				char *eq = strchr(str, '=');
+				if (!eq) /* stop at first non-assignment */
+					break;
+				eq++;
+				if (name_len == (eq - str)
+				 && strncmp(str, name, name_len) == 0) {
+					p = eq;
+					/* goto value; - WRONG! */
+					/* think "A=1 A=2 B=$A" */
+				}
+				var_str_list = var_str_list->next;
+			} while (var_str_list);
+			if (p)
+				goto value;
+		}
 		p = lookupvar(name);
  value:
 		if (!p)
@@ -5920,20 +5949,17 @@
  * input string.
  */
 static char *
-evalvar(char *p, int flag)
+evalvar(char *p, int flag, struct strlist *var_str_list)
 {
-	int subtype;
-	int varflags;
+	char varflags;
+	char subtype;
+	char quoted;
+	char easy;
 	char *var;
 	int patloc;
-	int c;
 	int startloc;
 	ssize_t varlen;
-	int easy;
-	int quotes;
-	int quoted;
 
-	quotes = flag & (EXP_FULL | EXP_CASE);
 	varflags = *p++;
 	subtype = varflags & VSTYPE;
 	quoted = varflags & VSQUOTE;
@@ -5943,7 +5969,7 @@
 	p = strchr(p, '=') + 1;
 
  again:
-	varlen = varvalue(var, varflags, flag);
+	varlen = varvalue(var, varflags, flag, var_str_list);
 	if (varflags & VSNUL)
 		varlen--;
 
@@ -5957,7 +5983,8 @@
 		if (varlen < 0) {
 			argstr(
 				p, flag | EXP_TILDE |
-					(quoted ?  EXP_QWORD : EXP_WORD)
+					(quoted ?  EXP_QWORD : EXP_WORD),
+				var_str_list
 			);
 			goto end;
 		}
@@ -5968,7 +5995,11 @@
 
 	if (subtype == VSASSIGN || subtype == VSQUESTION) {
 		if (varlen < 0) {
-			if (subevalvar(p, var, 0, subtype, startloc, varflags, 0)) {
+			if (subevalvar(p, var, /* strloc: */ 0,
+					subtype, startloc, varflags,
+					/* quotes: */ 0,
+					var_str_list)
+			) {
 				varflags &= ~VSNUL;
 				/*
 				 * Remove any recorded regions beyond
@@ -5993,10 +6024,8 @@
 	}
 
 	if (subtype == VSNORMAL) {
-		if (!easy)
-			goto end;
- record:
-		recordregion(startloc, expdest - (char *)stackblock(), quoted);
+		if (easy)
+			goto record;
 		goto end;
 	}
 
@@ -6019,8 +6048,11 @@
 		 */
 		STPUTC('\0', expdest);
 		patloc = expdest - (char *)stackblock();
-		if (subevalvar(p, NULL, patloc, subtype,
-				startloc, varflags, quotes) == 0) {
+		if (0 == subevalvar(p, /* str: */ NULL, patloc, subtype,
+				startloc, varflags,
+				/* quotes: */ flag & (EXP_FULL | EXP_CASE),
+				var_str_list)
+		) {
 			int amount = expdest - (
 				(char *)stackblock() + patloc - 1
 			);
@@ -6028,14 +6060,15 @@
 		}
 		/* Remove any recorded regions beyond start of variable */
 		removerecordregions(startloc);
-		goto record;
+ record:
+		recordregion(startloc, expdest - (char *)stackblock(), quoted);
 	}
 
  end:
 	if (subtype != VSNORMAL) {      /* skip to end of alternative */
 		int nesting = 1;
 		for (;;) {
-			c = *p++;
+			char c = *p++;
 			if (c == CTLESC)
 				p++;
 			else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
@@ -6420,7 +6453,8 @@
 	STARTSTACKSTR(expdest);
 	ifsfirst.next = NULL;
 	ifslastp = NULL;
-	argstr(arg->narg.text, flag);
+	argstr(arg->narg.text, flag,
+			/* var_str_list: */ arglist ? arglist->list : NULL);
 	p = _STPUTC('\0', expdest);
 	expdest = p - 1;
 	if (arglist == NULL) {
@@ -6486,7 +6520,8 @@
 	argbackq = pattern->narg.backquote;
 	STARTSTACKSTR(expdest);
 	ifslastp = NULL;
-	argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
+	argstr(pattern->narg.text, EXP_TILDE | EXP_CASE,
+			/* var_str_list: */ NULL);
 	STACKSTRNUL(expdest);
 	result = patmatch(stackblock(), val);
 	popstackmark(&smark);
@@ -8249,8 +8284,8 @@
 static void
 evalcommand(union node *cmd, int flags)
 {
-	static const struct builtincmd bltin = {
-		"\0\0", bltincmd
+	static const struct builtincmd null_bltin = {
+		"\0\0", bltincmd /* why three NULs? */
 	};
 	struct stackmark smark;
 	union node *argp;
@@ -8276,7 +8311,7 @@
 	back_exitstatus = 0;
 
 	cmdentry.cmdtype = CMDBUILTIN;
-	cmdentry.u.cmd = &bltin;
+	cmdentry.u.cmd = &null_bltin;
 	varlist.lastp = &varlist.list;
 	*varlist.lastp = NULL;
 	arglist.lastp = &arglist.list;
@@ -8352,7 +8387,7 @@
 			}
 			sp = arglist.list;
 		}
-		full_write(preverrout_fd, "\n", 1);
+		safe_write(preverrout_fd, "\n", 1);
 	}
 
 	cmd_is_exec = 0;
diff --git a/shell/ash_test/ash-vars/var1.right b/shell/ash_test/ash-vars/var1.right
new file mode 100644
index 0000000..2a01291
--- /dev/null
+++ b/shell/ash_test/ash-vars/var1.right
@@ -0,0 +1,6 @@
+a=a A=a
+a=a A=a
+a= A=
+a= A=
+a=a A=a
+a=a A=a
diff --git a/shell/ash_test/ash-vars/var1.tests b/shell/ash_test/ash-vars/var1.tests
new file mode 100755
index 0000000..802e489
--- /dev/null
+++ b/shell/ash_test/ash-vars/var1.tests
@@ -0,0 +1,14 @@
+# check that first assignment has proper effect on second one
+
+(
+a=a A=$a
+echo a=$a A=$A
+)
+(a=a A=$a; echo a=$a A=$A)
+(a=a A=$a echo a=$a A=$A)
+(a=a A=$a /bin/echo a=$a A=$A)
+
+f() { echo a=$a A=$A; }
+
+(a=a A=$a f)
+(a=a A=$a; f)
diff --git a/shell/ash_test/ash-vars/var2.right b/shell/ash_test/ash-vars/var2.right
new file mode 100644
index 0000000..8fed138
--- /dev/null
+++ b/shell/ash_test/ash-vars/var2.right
@@ -0,0 +1 @@
+bus/usb/1/2
diff --git a/shell/ash_test/ash-vars/var2.tests b/shell/ash_test/ash-vars/var2.tests
new file mode 100755
index 0000000..07feaeb
--- /dev/null
+++ b/shell/ash_test/ash-vars/var2.tests
@@ -0,0 +1 @@
+X=usbdev1.2 X=${X#usbdev} B=${X%%.*} D=${X#*.}; echo bus/usb/$B/$D
diff --git a/shell/ash_test/run-all b/shell/ash_test/run-all
index 416900a..e023338 100755
--- a/shell/ash_test/run-all
+++ b/shell/ash_test/run-all
@@ -17,6 +17,7 @@
 do_test()
 {
     test -d "$1" || return 0
+    echo do_test "$1"
     (
     cd "$1" || { echo "cannot cd $1!"; exit 1; }
     for x in run-*; do
@@ -53,7 +54,6 @@
     modules=`ls -d ash-*`
 
     for module in $modules; do
-	echo do_test $module
 	do_test $module
     done
 else