hush: fix a case when redirect to a closed fd #1 is not restoring (closing) it

function                                             old     new   delta
setup_redirects                                      200     245     +45
append_squirrel                                        -      41     +41
save_fds_on_redirect                                 256     221     -35
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 1/1 up/down: 86/-35)             Total: 51 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/shell/hush.c b/shell/hush.c
index 309ed21..20b0923 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -6643,8 +6643,18 @@
 	/* moved_to = -1: fd was opened by redirect; close orig_fd after redir */
 };
 
+static struct squirrel *append_squirrel(struct squirrel *sq, int i, int orig, int moved)
+{
+	sq = xrealloc(sq, (i + 2) * sizeof(sq[0]));
+	sq[i].orig_fd = orig;
+	sq[i].moved_to = moved;
+	sq[i+1].orig_fd = -1; /* end marker */
+	return sq;
+}
+
 static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
 {
+	int moved_to;
 	int i = 0;
 
 	if (sq) while (sq[i].orig_fd >= 0) {
@@ -6664,15 +6674,12 @@
 		i++;
 	}
 
-	sq = xrealloc(sq, (i + 2) * sizeof(sq[0]));
-	sq[i].orig_fd = fd;
 	/* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */
-	sq[i].moved_to = fcntl_F_DUPFD(fd, avoid_fd);
-	debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, sq[i].moved_to);
-	if (sq[i].moved_to < 0 && errno != EBADF)
+	moved_to = fcntl_F_DUPFD(fd, avoid_fd);
+	debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, moved_to);
+	if (moved_to < 0 && errno != EBADF)
 		xfunc_die();
-	sq[i+1].orig_fd = -1; /* end marker */
-	return sq;
+	return append_squirrel(sq, i, fd, moved_to);
 }
 
 /* fd: redirect wants this fd to be used (e.g. 3>file).
@@ -6778,6 +6785,19 @@
 				 */
 				return 1;
 			}
+			if (openfd == redir->rd_fd && sqp) {
+				/* open() gave us precisely the fd we wanted.
+				 * This means that this fd was not busy
+				 * (not opened to anywhere).
+				 * Remember to close it on restore:
+				 */
+				struct squirrel *sq = *sqp;
+				int i = 0;
+			        if (sq) while (sq[i].orig_fd >= 0)
+					i++;
+				*sqp = append_squirrel(sq, i, openfd, -1); /* -1 = "it was closed" */
+				debug_printf_redir("redir to previously closed fd %d\n", openfd);
+			}
 		} else {
 			/* "rd_fd<*>rd_dup" or "rd_fd<*>-" cases */
 			openfd = redir->rd_dup;
diff --git a/shell/hush_test/hush-redir/redir.right b/shell/hush_test/hush-redir/redir.right
new file mode 100644
index 0000000..4de5ec7
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir.right
@@ -0,0 +1,2 @@
+hush: write error: Bad file descriptor
+TEST
diff --git a/shell/hush_test/hush-redir/redir.tests b/shell/hush_test/hush-redir/redir.tests
new file mode 100755
index 0000000..7a1a668
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir.tests
@@ -0,0 +1,6 @@
+# test: closed fds should stay closed
+exec 1>&-
+echo TEST >TEST
+echo JUNK # lost: stdout is closed
+cat TEST >&2
+rm TEST