aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2017-07-07 22:07:28 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2017-07-07 22:07:28 +0200
commit2db74610cdf8ffb4f9ed99b62c755377d3cc48ea (patch)
tree6835b5b3e84d3d9c5b511053df692db8bfab8792 /shell
parent69a5ec9dccfd183cdf6bee7b994336670755cd47 (diff)
downloadbusybox-w32-2db74610cdf8ffb4f9ed99b62c755377d3cc48ea.tar.gz
busybox-w32-2db74610cdf8ffb4f9ed99b62c755377d3cc48ea.tar.bz2
busybox-w32-2db74610cdf8ffb4f9ed99b62c755377d3cc48ea.zip
hush: fix two redirection testcase failures
function old new delta save_fds_on_redirect 183 256 +73 fcntl_F_DUPFD - 46 +46 restore_redirects 74 96 +22 xdup_and_close 51 72 +21 setup_redirects 196 200 +4 hush_main 988 983 -5 static.C 12 - -12 run_pipe 1595 1551 -44 ------------------------------------------------------------------------------ (add/remove: 1/1 grow/shrink: 4/2 up/down: 166/-61) Total: 105 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'shell')
-rw-r--r--shell/hush.c179
-rw-r--r--shell/hush_test/hush-redir/redir3.right3
-rw-r--r--shell/hush_test/hush-redir/redir_to_bad_fd.right3
3 files changed, 117 insertions, 68 deletions
diff --git a/shell/hush.c b/shell/hush.c
index cf6d8cd9f..59bddbfff 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -402,6 +402,7 @@
402#define debug_printf_expand(...) do {} while (0) 402#define debug_printf_expand(...) do {} while (0)
403#define debug_printf_varexp(...) do {} while (0) 403#define debug_printf_varexp(...) do {} while (0)
404#define debug_printf_glob(...) do {} while (0) 404#define debug_printf_glob(...) do {} while (0)
405#define debug_printf_redir(...) do {} while (0)
405#define debug_printf_list(...) do {} while (0) 406#define debug_printf_list(...) do {} while (0)
406#define debug_printf_subst(...) do {} while (0) 407#define debug_printf_subst(...) do {} while (0)
407#define debug_printf_clean(...) do {} while (0) 408#define debug_printf_clean(...) do {} while (0)
@@ -1146,6 +1147,10 @@ static const struct built_in_command bltins2[] = {
1146# define DEBUG_GLOB 0 1147# define DEBUG_GLOB 0
1147#endif 1148#endif
1148 1149
1150#ifndef debug_printf_redir
1151# define debug_printf_redir(...) (indent(), fdprintf(2, __VA_ARGS__))
1152#endif
1153
1149#ifndef debug_printf_list 1154#ifndef debug_printf_list
1150# define debug_printf_list(...) (indent(), fdprintf(2, __VA_ARGS__)) 1155# define debug_printf_list(...) (indent(), fdprintf(2, __VA_ARGS__))
1151#endif 1156#endif
@@ -1381,12 +1386,30 @@ static void free_strings(char **strings)
1381 free(strings); 1386 free(strings);
1382} 1387}
1383 1388
1389static int fcntl_F_DUPFD(int fd, int avoid_fd)
1390{
1391 int newfd;
1392 repeat:
1393 newfd = fcntl(fd, F_DUPFD, avoid_fd + 1);
1394 if (newfd < 0) {
1395 if (errno == EBUSY)
1396 goto repeat;
1397 if (errno == EINTR)
1398 goto repeat;
1399 }
1400 return newfd;
1401}
1384 1402
1385static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC) 1403static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC, int avoid_fd)
1386{ 1404{
1387 /* We avoid taking stdio fds. Mimicking ash: use fds above 9 */ 1405 int newfd;
1388 int newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, 10); 1406 repeat:
1407 newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, avoid_fd + 1);
1389 if (newfd < 0) { 1408 if (newfd < 0) {
1409 if (errno == EBUSY)
1410 goto repeat;
1411 if (errno == EINTR)
1412 goto repeat;
1390 /* fd was not open? */ 1413 /* fd was not open? */
1391 if (errno == EBADF) 1414 if (errno == EBADF)
1392 return fd; 1415 return fd;
@@ -1424,13 +1447,14 @@ static void fclose_and_forget(FILE *fp)
1424 } 1447 }
1425 fclose(fp); 1448 fclose(fp);
1426} 1449}
1427static int save_FILEs_on_redirect(int fd) 1450static int save_FILEs_on_redirect(int fd, int avoid_fd)
1428{ 1451{
1429 struct FILE_list *fl = G.FILE_list; 1452 struct FILE_list *fl = G.FILE_list;
1430 while (fl) { 1453 while (fl) {
1431 if (fd == fl->fd) { 1454 if (fd == fl->fd) {
1432 /* We use it only on script files, they are all CLOEXEC */ 1455 /* We use it only on script files, they are all CLOEXEC */
1433 fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC); 1456 fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC, avoid_fd);
1457 debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd);
1434 return 1; 1458 return 1;
1435 } 1459 }
1436 fl = fl->next; 1460 fl = fl->next;
@@ -1443,6 +1467,7 @@ static void restore_redirected_FILEs(void)
1443 while (fl) { 1467 while (fl) {
1444 int should_be = fileno(fl->fp); 1468 int should_be = fileno(fl->fp);
1445 if (fl->fd != should_be) { 1469 if (fl->fd != should_be) {
1470 debug_printf_redir("restoring script fd from %d to %d\n", fl->fd, should_be);
1446 xmove_fd(fl->fd, should_be); 1471 xmove_fd(fl->fd, should_be);
1447 fl->fd = should_be; 1472 fl->fd = should_be;
1448 } 1473 }
@@ -6518,77 +6543,108 @@ static void setup_heredoc(struct redir_struct *redir)
6518 wait(NULL); /* wait till child has died */ 6543 wait(NULL); /* wait till child has died */
6519} 6544}
6520 6545
6521/* fd: redirect wants this fd to be used (e.g. 3>file). 6546struct squirrel {
6522 * Move all conflicting internally used fds, 6547 int orig_fd;
6523 * and remember them so that we can restore them later. 6548 int moved_to;
6524 */ 6549 /* moved_to = n: fd was moved to n; restore back to orig_fd after redir */
6525static int save_fds_on_redirect(int fd, int squirrel[3]) 6550 /* moved_to = -1: fd was opened by redirect; close orig_fd after redir */
6551};
6552
6553static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
6526{ 6554{
6527 if (squirrel) { 6555 int i = 0;
6528 /* Handle redirects of fds 0,1,2 */
6529 6556
6530 /* If we collide with an already moved stdio fd... */ 6557 if (sq) while (sq[i].orig_fd >= 0) {
6531 if (fd == squirrel[0]) { 6558 /* If we collide with an already moved fd... */
6532 squirrel[0] = xdup_and_close(squirrel[0], F_DUPFD); 6559 if (fd == sq[i].moved_to) {
6533 return 1; 6560 sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd);
6534 } 6561 debug_printf_redir("redirect_fd %d: already busy, moving to %d\n", fd, sq[i].moved_to);
6535 if (fd == squirrel[1]) { 6562 if (sq[i].moved_to < 0) /* what? */
6536 squirrel[1] = xdup_and_close(squirrel[1], F_DUPFD);
6537 return 1;
6538 }
6539 if (fd == squirrel[2]) {
6540 squirrel[2] = xdup_and_close(squirrel[2], F_DUPFD);
6541 return 1;
6542 }
6543 /* If we are about to redirect stdio fd, and did not yet move it... */
6544 if (fd <= 2 && squirrel[fd] < 0) {
6545 /* We avoid taking stdio fds */
6546 squirrel[fd] = fcntl(fd, F_DUPFD, 10);
6547 if (squirrel[fd] < 0 && errno != EBADF)
6548 xfunc_die(); 6563 xfunc_die();
6549 return 0; /* "we did not close fd" */ 6564 return sq;
6565 }
6566 if (fd == sq[i].orig_fd) {
6567 /* Example: echo Hello >/dev/null 1>&2 */
6568 debug_printf_redir("redirect_fd %d: already moved\n", fd);
6569 return sq;
6550 } 6570 }
6571 i++;
6551 } 6572 }
6552 6573
6574 sq = xrealloc(sq, (i + 2) * sizeof(sq[0]));
6575 sq[i].orig_fd = fd;
6576 /* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */
6577 sq[i].moved_to = fcntl_F_DUPFD(fd, avoid_fd);
6578 debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, sq[i].moved_to);
6579 if (sq[i].moved_to < 0 && errno != EBADF)
6580 xfunc_die();
6581 sq[i+1].orig_fd = -1; /* end marker */
6582 return sq;
6583}
6584
6585/* fd: redirect wants this fd to be used (e.g. 3>file).
6586 * Move all conflicting internally used fds,
6587 * and remember them so that we can restore them later.
6588 */
6589static int save_fds_on_redirect(int fd, int avoid_fd, struct squirrel **sqp)
6590{
6591 if (avoid_fd < 9) /* the important case here is that it can be -1 */
6592 avoid_fd = 9;
6593
6553#if ENABLE_HUSH_INTERACTIVE 6594#if ENABLE_HUSH_INTERACTIVE
6554 if (fd != 0 && fd == G.interactive_fd) { 6595 if (fd != 0 && fd == G.interactive_fd) {
6555 G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC); 6596 G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC, avoid_fd);
6556 return 1; 6597 debug_printf_redir("redirect_fd %d: matches interactive_fd, moving it to %d\n", fd, G.interactive_fd);
6598 return 1; /* "we closed fd" */
6557 } 6599 }
6558#endif 6600#endif
6559
6560 /* Are we called from setup_redirects(squirrel==NULL)? Two cases: 6601 /* Are we called from setup_redirects(squirrel==NULL)? Two cases:
6561 * (1) Redirect in a forked child. No need to save FILEs' fds, 6602 * (1) Redirect in a forked child. No need to save FILEs' fds,
6562 * we aren't going to use them anymore, ok to trash. 6603 * we aren't going to use them anymore, ok to trash.
6563 * (2) "exec 3>FILE". Bummer. We can save FILEs' fds, 6604 * (2) "exec 3>FILE". Bummer. We can save script FILEs' fds,
6564 * but how are we doing to use them? 6605 * but how are we doing to restore them?
6565 * "fileno(fd) = new_fd" can't be done. 6606 * "fileno(fd) = new_fd" can't be done.
6566 */ 6607 */
6567 if (!squirrel) 6608 if (!sqp)
6568 return 0; 6609 return 0;
6569 6610
6570 return save_FILEs_on_redirect(fd); 6611 /* If this one of script's fds? */
6612 if (save_FILEs_on_redirect(fd, avoid_fd))
6613 return 1; /* yes. "we closed fd" */
6614
6615 /* Check whether it collides with any open fds (e.g. stdio), save fds as needed */
6616 *sqp = add_squirrel(*sqp, fd, avoid_fd);
6617 return 0; /* "we did not close fd" */
6571} 6618}
6572 6619
6573static void restore_redirects(int squirrel[3]) 6620static void restore_redirects(struct squirrel *sq)
6574{ 6621{
6575 int i, fd; 6622
6576 for (i = 0; i <= 2; i++) { 6623 if (sq) {
6577 fd = squirrel[i]; 6624 int i = 0;
6578 if (fd != -1) { 6625 while (sq[i].orig_fd >= 0) {
6579 /* We simply die on error */ 6626 if (sq[i].moved_to >= 0) {
6580 xmove_fd(fd, i); 6627 /* We simply die on error */
6628 debug_printf_redir("restoring redirected fd from %d to %d\n", sq[i].moved_to, sq[i].orig_fd);
6629 xmove_fd(sq[i].moved_to, sq[i].orig_fd);
6630 } else {
6631 /* cmd1 9>FILE; cmd2_should_see_fd9_closed */
6632 debug_printf_redir("restoring redirected fd %d: closing it\n", sq[i].orig_fd);
6633 close(sq[i].orig_fd);
6634 }
6635 i++;
6581 } 6636 }
6637 free(sq);
6582 } 6638 }
6583 6639
6584 /* Moved G.interactive_fd stays on new fd, not doing anything for it */ 6640 /* If moved, G.interactive_fd stays on new fd, not restoring it */
6585 6641
6586 restore_redirected_FILEs(); 6642 restore_redirected_FILEs();
6587} 6643}
6588 6644
6589/* squirrel != NULL means we squirrel away copies of stdin, stdout, 6645/* squirrel != NULL means we squirrel away copies of stdin, stdout,
6590 * and stderr if they are redirected. */ 6646 * and stderr if they are redirected. */
6591static int setup_redirects(struct command *prog, int squirrel[]) 6647static int setup_redirects(struct command *prog, struct squirrel **sqp)
6592{ 6648{
6593 int openfd, mode; 6649 int openfd, mode;
6594 struct redir_struct *redir; 6650 struct redir_struct *redir;
@@ -6596,7 +6652,7 @@ static int setup_redirects(struct command *prog, int squirrel[])
6596 for (redir = prog->redirects; redir; redir = redir->next) { 6652 for (redir = prog->redirects; redir; redir = redir->next) {
6597 if (redir->rd_type == REDIRECT_HEREDOC2) { 6653 if (redir->rd_type == REDIRECT_HEREDOC2) {
6598 /* "rd_fd<<HERE" case */ 6654 /* "rd_fd<<HERE" case */
6599 save_fds_on_redirect(redir->rd_fd, squirrel); 6655 save_fds_on_redirect(redir->rd_fd, /*avoid:*/ 0, sqp);
6600 /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ 6656 /* for REDIRECT_HEREDOC2, rd_filename holds _contents_
6601 * of the heredoc */ 6657 * of the heredoc */
6602 debug_printf_parse("set heredoc '%s'\n", 6658 debug_printf_parse("set heredoc '%s'\n",
@@ -6635,7 +6691,7 @@ static int setup_redirects(struct command *prog, int squirrel[])
6635 } 6691 }
6636 6692
6637 if (openfd != redir->rd_fd) { 6693 if (openfd != redir->rd_fd) {
6638 int closed = save_fds_on_redirect(redir->rd_fd, squirrel); 6694 int closed = save_fds_on_redirect(redir->rd_fd, /*avoid:*/ openfd, sqp);
6639 if (openfd == REDIRFD_CLOSE) { 6695 if (openfd == REDIRFD_CLOSE) {
6640 /* "rd_fd >&-" means "close me" */ 6696 /* "rd_fd >&-" means "close me" */
6641 if (!closed) { 6697 if (!closed) {
@@ -7497,14 +7553,14 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe)
7497static int redirect_and_varexp_helper(char ***new_env_p, 7553static int redirect_and_varexp_helper(char ***new_env_p,
7498 struct variable **old_vars_p, 7554 struct variable **old_vars_p,
7499 struct command *command, 7555 struct command *command,
7500 int squirrel[3], 7556 struct squirrel **sqp,
7501 char **argv_expanded) 7557 char **argv_expanded)
7502{ 7558{
7503 /* setup_redirects acts on file descriptors, not FILEs. 7559 /* setup_redirects acts on file descriptors, not FILEs.
7504 * This is perfect for work that comes after exec(). 7560 * This is perfect for work that comes after exec().
7505 * Is it really safe for inline use? Experimentally, 7561 * Is it really safe for inline use? Experimentally,
7506 * things seem to work. */ 7562 * things seem to work. */
7507 int rcode = setup_redirects(command, squirrel); 7563 int rcode = setup_redirects(command, sqp);
7508 if (rcode == 0) { 7564 if (rcode == 0) {
7509 char **new_env = expand_assignments(command->argv, command->assignment_cnt); 7565 char **new_env = expand_assignments(command->argv, command->assignment_cnt);
7510 *new_env_p = new_env; 7566 *new_env_p = new_env;
@@ -7524,8 +7580,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
7524 struct command *command; 7580 struct command *command;
7525 char **argv_expanded; 7581 char **argv_expanded;
7526 char **argv; 7582 char **argv;
7527 /* it is not always needed, but we aim to smaller code */ 7583 struct squirrel *squirrel = NULL;
7528 int squirrel[] = { -1, -1, -1 };
7529 int rcode; 7584 int rcode;
7530 7585
7531 debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds); 7586 debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds);
@@ -7582,7 +7637,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
7582 /* { list } */ 7637 /* { list } */
7583 debug_printf("non-subshell group\n"); 7638 debug_printf("non-subshell group\n");
7584 rcode = 1; /* exitcode if redir failed */ 7639 rcode = 1; /* exitcode if redir failed */
7585 if (setup_redirects(command, squirrel) == 0) { 7640 if (setup_redirects(command, &squirrel) == 0) {
7586 debug_printf_exec(": run_list\n"); 7641 debug_printf_exec(": run_list\n");
7587 rcode = run_list(command->group) & 0xff; 7642 rcode = run_list(command->group) & 0xff;
7588 } 7643 }
@@ -7609,7 +7664,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
7609 /* Ensure redirects take effect (that is, create files). 7664 /* Ensure redirects take effect (that is, create files).
7610 * Try "a=t >file" */ 7665 * Try "a=t >file" */
7611#if 0 /* A few cases in testsuite fail with this code. FIXME */ 7666#if 0 /* A few cases in testsuite fail with this code. FIXME */
7612 rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, squirrel, /*argv_expanded:*/ NULL); 7667 rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, &squirrel, /*argv_expanded:*/ NULL);
7613 /* Set shell variables */ 7668 /* Set shell variables */
7614 if (new_env) { 7669 if (new_env) {
7615 argv = new_env; 7670 argv = new_env;
@@ -7631,7 +7686,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
7631 7686
7632#else /* Older, bigger, but more correct code */ 7687#else /* Older, bigger, but more correct code */
7633 7688
7634 rcode = setup_redirects(command, squirrel); 7689 rcode = setup_redirects(command, &squirrel);
7635 restore_redirects(squirrel); 7690 restore_redirects(squirrel);
7636 /* Set shell variables */ 7691 /* Set shell variables */
7637 if (G_x_mode) 7692 if (G_x_mode)
@@ -7694,7 +7749,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
7694 goto clean_up_and_ret1; 7749 goto clean_up_and_ret1;
7695 } 7750 }
7696 } 7751 }
7697 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); 7752 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded);
7698 if (rcode == 0) { 7753 if (rcode == 0) {
7699 if (!funcp) { 7754 if (!funcp) {
7700 debug_printf_exec(": builtin '%s' '%s'...\n", 7755 debug_printf_exec(": builtin '%s' '%s'...\n",
@@ -7723,10 +7778,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
7723 unset_vars(new_env); 7778 unset_vars(new_env);
7724 add_vars(old_vars); 7779 add_vars(old_vars);
7725/* clean_up_and_ret0: */ 7780/* clean_up_and_ret0: */
7726
7727//FIXME: this restores stdio fds, but does not close other redirects!
7728//Example: after "echo TEST 9>/dev/null" fd#9 is not closed!
7729//The squirreling code needs rework to remember all fds, not just 0,1,2.
7730 restore_redirects(squirrel); 7781 restore_redirects(squirrel);
7731 clean_up_and_ret1: 7782 clean_up_and_ret1:
7732 free(argv_expanded); 7783 free(argv_expanded);
@@ -7739,7 +7790,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
7739 if (ENABLE_FEATURE_SH_NOFORK) { 7790 if (ENABLE_FEATURE_SH_NOFORK) {
7740 int n = find_applet_by_name(argv_expanded[0]); 7791 int n = find_applet_by_name(argv_expanded[0]);
7741 if (n >= 0 && APPLET_IS_NOFORK(n)) { 7792 if (n >= 0 && APPLET_IS_NOFORK(n)) {
7742 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); 7793 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded);
7743 if (rcode == 0) { 7794 if (rcode == 0) {
7744 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", 7795 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
7745 argv_expanded[0], argv_expanded[1]); 7796 argv_expanded[0], argv_expanded[1]);
@@ -8694,7 +8745,7 @@ int hush_main(int argc, char **argv)
8694 G_saved_tty_pgrp = 0; 8745 G_saved_tty_pgrp = 0;
8695 8746
8696 /* try to dup stdin to high fd#, >= 255 */ 8747 /* try to dup stdin to high fd#, >= 255 */
8697 G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); 8748 G_interactive_fd = fcntl_F_DUPFD(STDIN_FILENO, 254);
8698 if (G_interactive_fd < 0) { 8749 if (G_interactive_fd < 0) {
8699 /* try to dup to any fd */ 8750 /* try to dup to any fd */
8700 G_interactive_fd = dup(STDIN_FILENO); 8751 G_interactive_fd = dup(STDIN_FILENO);
@@ -8767,7 +8818,7 @@ int hush_main(int argc, char **argv)
8767#elif ENABLE_HUSH_INTERACTIVE 8818#elif ENABLE_HUSH_INTERACTIVE
8768 /* No job control compiled in, only prompt/line editing */ 8819 /* No job control compiled in, only prompt/line editing */
8769 if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) { 8820 if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
8770 G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); 8821 G_interactive_fd = fcntl_F_DUPFD(STDIN_FILENO, 254);
8771 if (G_interactive_fd < 0) { 8822 if (G_interactive_fd < 0) {
8772 /* try to dup to any fd */ 8823 /* try to dup to any fd */
8773 G_interactive_fd = dup(STDIN_FILENO); 8824 G_interactive_fd = dup(STDIN_FILENO);
diff --git a/shell/hush_test/hush-redir/redir3.right b/shell/hush_test/hush-redir/redir3.right
index fd641a8ea..e3c878b7d 100644
--- a/shell/hush_test/hush-redir/redir3.right
+++ b/shell/hush_test/hush-redir/redir3.right
@@ -1,3 +1,2 @@
1TEST 1TEST
2./redir3.tests: line 4: 9: Bad file descriptor 2hush: can't duplicate file descriptor: Bad file descriptor
3Output to fd#9: 1
diff --git a/shell/hush_test/hush-redir/redir_to_bad_fd.right b/shell/hush_test/hush-redir/redir_to_bad_fd.right
index 43b8af293..936911ce5 100644
--- a/shell/hush_test/hush-redir/redir_to_bad_fd.right
+++ b/shell/hush_test/hush-redir/redir_to_bad_fd.right
@@ -1,2 +1 @@
1./redir_to_bad_fd.tests: line 2: 10: Bad file descriptor hush: can't duplicate file descriptor: Bad file descriptor
2OK