Issue #14434 has been updated by jeremyevans0 (Jeremy Evans).


I tried updating this patch for the current master branch, but it breaks `test_reopen_inherit`:

```
  1) Failure:
TestIO#test_reopen_inherit [/ruby/test/ruby/test_io.rb:2385]:
<"outerr"> expected but was
<"">.
```

@nobu, do you think there is a better way to fix this?  I'm not sure the current behavior is a bug, if the pipe is broken, it seems reasonable for `IO#reopen` to raise an exception.

Here's the patch I used:

```diff
diff --git a/io.c b/io.c
index 0d6e217857..e41ca74db7 100644
--- a/io.c
+++ b/io.c
@@ -7372,8 +7372,7 @@ io_reopen(VALUE io, VALUE nfile)
        }
     }
     if (fptr->mode & FMODE_WRITABLE) {
-        if (io_fflush(fptr) < 0)
-            rb_sys_fail_on_write(fptr);
+        fptr_finalize_flush(fptr, TRUE, FALSE, 0);
     }
     else {
         flush_before_seek(fptr);
@@ -7399,7 +7398,8 @@ io_reopen(VALUE io, VALUE nfile)
     if (fd != fd2) {
        if (IS_PREP_STDIO(fptr) || fd <= 2 || !fptr->stdio_file) {
            /* need to keep FILE objects of stdin, stdout and stderr */
-           if (rb_cloexec_dup2(fd2, fd) < 0)
+            fd = (fd < 0) ? rb_cloexec_dup(fd2) : rb_cloexec_dup2(fd2, fd);
+            if (fd < 0)
                rb_sys_fail_path(orig->pathv);
             rb_update_max_fd(fd);
        }
```

----------------------------------------
Bug #14434: IO#reopen fails after EPIPE
https://bugs.ruby-lang.org/issues/14434#change-87522

* Author: nobu (Nobuyoshi Nakada)
* Status: Open
* Priority: Normal
* Backport: 2.3: UNKNOWN, 2.4: UNKNOWN, 2.5: UNKNOWN
----------------------------------------
Consider the following code, which emulates `yes | head -1`.

```ruby
IO.popen("head -1", "w") do |f|
  f.sync = false
  stdout = STDOUT.dup
  STDOUT.reopen(f)
  loop{puts "y"} rescue break $!
ensure
  STDOUT.reopen(stdout)
end
```

This fails with `Errno::EPIPE` in `IO#reopen`.

```
Traceback (most recent call last):
	4: from -:1:in `<main>'
	3: from -:1:in `popen'
	2: from -:7:in `block in <main>'
	1: from -:7:in `ensure in block in <main>'
-:7:in `reopen': Broken pipe (Errno::EPIPE)
```

BTW, this "broken pipe" IO can't even close.

```ruby
r, w = IO.pipe
w.sync = false # make buffered
w.print("foo")
r.close
# w.reopen(STDOUT) rescue p $! #=> #<Errno::EPIPE: Broken pipe>
w.close rescue p $! #=> #<Errno::EPIPE: Broken pipe>
```

This is caused by flushing buffered data.
I think there is no way to recover "broken pipe" FD, so nothing can be done for the remained data and should be discarded gently.

```diff
diff --git i/io.c w/io.c
index 0a4e66f5ed..5eca6762f2 100644
--- i/io.c
+++ w/io.c
@@ -7150,8 +7150,7 @@ io_reopen(VALUE io, VALUE nfile)
 	}
     }
     if (fptr->mode & FMODE_WRITABLE) {
-        if (io_fflush(fptr) < 0)
-            rb_sys_fail(0);
+        fptr_finalize_flush(fptr, TRUE, FALSE);
     }
     else {
 	io_tell(fptr);
@@ -7177,7 +7176,8 @@ io_reopen(VALUE io, VALUE nfile)
     if (fd != fd2) {
 	if (IS_PREP_STDIO(fptr) || fd <= 2 || !fptr->stdio_file) {
 	    /* need to keep FILE objects of stdin, stdout and stderr */
-	    if (rb_cloexec_dup2(fd2, fd) < 0)
+	    fd = (fd < 0) ? rb_cloexec_dup(fd2) : rb_cloexec_dup2(fd2, fd);
+	    if (fd < 0)
 		rb_sys_fail_path(orig->pathv);
             rb_update_max_fd(fd);
 	}
```



-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>