From a7344320998fab8822c9f89744bf6a102e478d09 Mon Sep 17 00:00:00 2001
From: Eric Wong <normalperson@yhbt.net>
Date: Sun, 20 Oct 2013 04:03:40 +0000
Subject: [PATCH] io.c: make IO#reopen("pathname") atomic

Since rb_sysopen releases the GVL, calling close(fptr->fd) would
leave a window where accessing the file from another thread can hit
IOError on a closed stream

Instead, create a new, temporary FD via rb_sysopen and call
rb_cloexec_dup2 on it to atomically replace the file fptr->fd points
to.  This leaves no possible window where fptr->fd is invalid to
userspace (even for any threads running w/o GVL).

Without this patch, I need to maintain separate code paths for
atomically reopening (e.g. log rotation) std{in,out,err} vs
other files in a multithreaded application.
---
 io.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/io.c b/io.c
index 47b86ed..66e19aa 100644
--- a/io.c
+++ b/io.c
@@ -6665,10 +6665,16 @@ rb_io_reopen(int argc, VALUE *argv, VALUE file)
         }
     }
     else {
-        if (close(fptr->fd) < 0)
+        int tmpfd = rb_sysopen(fptr->pathv, oflags, 0666);
+        int err = 0;
+
+        if (rb_cloexec_dup2(tmpfd, fptr->fd) < 0)
+            err = errno;
+        (void)close(tmpfd);
+        if (err) {
+            errno = err;
             rb_sys_fail_path(fptr->pathv);
-        fptr->fd = -1;
-        fptr->fd = rb_sysopen(fptr->pathv, oflags, 0666);
+        }
     }
 
     return file;
-- 
1.8.4.483.g7fe67e6.dirty

