hush: fix "(sleep 1; exit 3) & sleep 2; echo $?; wait $!; echo $?"

function                                             old     new   delta
process_wait_result                                  414     426     +12
builtin_wait                                         283     291      +8
run_list                                             974     978      +4
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 3/0 up/down: 24/0)               Total: 24 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/shell/ash_test/ash-misc/wait6.right b/shell/ash_test/ash-misc/wait6.right
new file mode 100644
index 0000000..12decc1
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait6.right
@@ -0,0 +1,2 @@
+0
+3
diff --git a/shell/ash_test/ash-misc/wait6.tests b/shell/ash_test/ash-misc/wait6.tests
new file mode 100755
index 0000000..c237131
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait6.tests
@@ -0,0 +1,6 @@
+# In bash, "wait $!" extracts correct exitcode even if bg task has already exited
+# It prints 0, then 3:
+(sleep 0; exit 3) & sleep 1
+echo $?
+wait $!
+echo $?
diff --git a/shell/hush.c b/shell/hush.c
index 59bddbf..df96e6f 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -832,6 +832,7 @@
 	smallint exiting; /* used to prevent EXIT trap recursion */
 	/* These four support $?, $#, and $1 */
 	smalluint last_exitcode;
+	smalluint last_bg_pid_exitcode;
 #if ENABLE_HUSH_SET
 	/* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
 	smalluint global_args_malloced;
@@ -7387,10 +7388,13 @@
  found_pi_and_prognum:
 	if (dead) {
 		/* child exited */
-		pi->cmds[i].pid = 0;
-		pi->cmds[i].cmd_exitcode = WEXITSTATUS(status);
+		int rcode = WEXITSTATUS(status);
 		if (WIFSIGNALED(status))
-			pi->cmds[i].cmd_exitcode = 128 + WTERMSIG(status);
+			rcode = 128 + WTERMSIG(status);
+		pi->cmds[i].cmd_exitcode = rcode;
+		if (G.last_bg_pid == pi->cmds[i].pid)
+			G.last_bg_pid_exitcode = rcode;
+		pi->cmds[i].pid = 0;
 		pi->alive_cmds--;
 		if (!pi->alive_cmds) {
 			if (G_interactive_fd)
@@ -8215,6 +8219,7 @@
 #endif
 			/* Last command's pid goes to $! */
 			G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid;
+			G.last_bg_pid_exitcode = 0;
 			debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
 /* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */
 			rcode = EXIT_SUCCESS;
@@ -9909,6 +9914,7 @@
 		ret = waitpid(pid, &status, WNOHANG);
 		if (ret < 0) {
 			/* No */
+			ret = 127;
 			if (errno == ECHILD) {
 				if (G.last_bg_pid > 0 && pid == G.last_bg_pid) {
 					/* "wait $!" but last bg task has already exited. Try:
@@ -9916,7 +9922,7 @@
 					 * In bash it prints exitcode 0, then 3.
 					 * In dash, it is 127.
 					 */
-					/* ret = G.last_bg_pid_exitstatus - FIXME */
+					ret = G.last_bg_pid_exitcode;
 				} else {
 					/* Example: "wait 1". mimic bash message */
 					bb_error_msg("wait: pid %d is not a child of this shell", (int)pid);
@@ -9925,7 +9931,6 @@
 				/* ??? */
 				bb_perror_msg("wait %s", *argv);
 			}
-			ret = 127;
 			continue; /* bash checks all argv[] */
 		}
 		if (ret == 0) {
diff --git a/shell/hush_test/hush-misc/wait6.right b/shell/hush_test/hush-misc/wait6.right
new file mode 100644
index 0000000..12decc1
--- /dev/null
+++ b/shell/hush_test/hush-misc/wait6.right
@@ -0,0 +1,2 @@
+0
+3
diff --git a/shell/hush_test/hush-misc/wait6.tests b/shell/hush_test/hush-misc/wait6.tests
new file mode 100755
index 0000000..c237131
--- /dev/null
+++ b/shell/hush_test/hush-misc/wait6.tests
@@ -0,0 +1,6 @@
+# In bash, "wait $!" extracts correct exitcode even if bg task has already exited
+# It prints 0, then 3:
+(sleep 0; exit 3) & sleep 1
+echo $?
+wait $!
+echo $?