From be3672501ecde716dae723e887d4a9e4d731240c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 16 Nov 2011 18:37:37 -0800 Subject: connect,tryopen: set close-on-exec flag for new fds on Ruby 2.0+ All IO objects created by Kgio will have FD_CLOEXEC descriptor flag set on it when run under Ruby 2.0.0dev. This matches the upcoming behavior of Ruby 2.0.0dev for IO objects in the core and standard library. This change does not affect users on Ruby 1.9.3 and earlier. accept()-ed sockets in kgio have _always_ had FD_CLOEXEC set by default. --- ext/kgio/connect.c | 37 +++++++++++++++++++++++++++++-------- ext/kgio/extconf.rb | 2 ++ ext/kgio/tryopen.c | 6 +++++- test/test_tcp_connect.rb | 11 +++++++++++ test/test_tryopen.rb | 4 ++++ test/test_unix_connect.rb | 8 ++++++++ 6 files changed, 59 insertions(+), 9 deletions(-) diff --git a/ext/kgio/connect.c b/ext/kgio/connect.c index dd2f409..ff5e639 100644 --- a/ext/kgio/connect.c +++ b/ext/kgio/connect.c @@ -9,16 +9,30 @@ static void close_fail(int fd, const char *msg) rb_sys_fail(msg); } -#ifdef SOCK_NONBLOCK -# define MY_SOCK_STREAM (SOCK_STREAM|SOCK_NONBLOCK) +static int MY_SOCK_STREAM = +#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC) +# ifdef HAVE_RB_FD_FIX_CLOEXEC + (SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC) +# else + (SOCK_STREAM|SOCK_NONBLOCK) +# endif #else -# define MY_SOCK_STREAM SOCK_STREAM + SOCK_STREAM #endif /* ! SOCK_NONBLOCK */ +; + +/* do not set close-on-exec by default on Ruby <2.0.0 */ +#ifndef HAVE_RB_FD_FIX_CLOEXEC +# define rb_fd_fix_cloexec(fd) for (;0;) +#endif /* HAVE_RB_FD_FIX_CLOEXEC */ static VALUE my_connect(VALUE klass, int io_wait, int domain, void *addr, socklen_t addrlen) { - int fd = socket(domain, MY_SOCK_STREAM, 0); + int fd; + +retry: + fd = socket(domain, MY_SOCK_STREAM, 0); if (fd == -1) { switch (errno) { @@ -30,15 +44,22 @@ my_connect(VALUE klass, int io_wait, int domain, void *addr, socklen_t addrlen) errno = 0; rb_gc(); fd = socket(domain, MY_SOCK_STREAM, 0); + break; + case EINVAL: + if (MY_SOCK_STREAM != SOCK_STREAM) { + MY_SOCK_STREAM = SOCK_STREAM; + goto retry; + } } if (fd == -1) rb_sys_fail("socket"); } -#ifndef SOCK_NONBLOCK - if (fcntl(fd, F_SETFL, O_RDWR | O_NONBLOCK) == -1) - close_fail(fd, "fcntl(F_SETFL, O_RDWR | O_NONBLOCK)"); -#endif /* SOCK_NONBLOCK */ + if (MY_SOCK_STREAM == SOCK_STREAM) { + if (fcntl(fd, F_SETFL, O_RDWR | O_NONBLOCK) == -1) + close_fail(fd, "fcntl(F_SETFL, O_RDWR | O_NONBLOCK)"); + rb_fd_fix_cloexec(fd); + } if (connect(fd, addr, addrlen) == -1) { if (errno == EINPROGRESS) { diff --git a/ext/kgio/extconf.rb b/ext/kgio/extconf.rb index a76454d..fb680f7 100644 --- a/ext/kgio/extconf.rb +++ b/ext/kgio/extconf.rb @@ -42,6 +42,8 @@ have_type("struct RObject") and check_sizeof("struct RObject") check_sizeof("int") have_func('rb_io_ascii8bit_binmode') have_func('rb_update_max_fd') +have_func('rb_fd_fix_cloexec') +have_func('rb_cloexec_open') have_func('rb_thread_blocking_region') have_func('rb_thread_io_blocking_region') have_func('rb_str_set_len') diff --git a/ext/kgio/tryopen.c b/ext/kgio/tryopen.c index 16f47fb..85bbbc2 100644 --- a/ext/kgio/tryopen.c +++ b/ext/kgio/tryopen.c @@ -25,11 +25,15 @@ struct open_args { mode_t mode; }; +#ifndef HAVE_RB_CLOEXEC_OPEN +# define rb_cloexec_open(p,f,m) open((p),(f),(m)) +#endif + static VALUE nogvl_open(void *ptr) { struct open_args *o = ptr; - return (VALUE)open(o->pathname, o->flags, o->mode); + return (VALUE)rb_cloexec_open(o->pathname, o->flags, o->mode); } #ifndef HAVE_RB_THREAD_BLOCKING_REGION diff --git a/test/test_tcp_connect.rb b/test/test_tcp_connect.rb index 9756407..2dbe541 100644 --- a/test/test_tcp_connect.rb +++ b/test/test_tcp_connect.rb @@ -31,10 +31,17 @@ class TestKgioTcpConnect < Test::Unit::TestCase ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] assert_equal nil, sock.kgio_write("HELLO") + + sock.respond_to?(:close_on_exec?) and + assert_equal(RUBY_VERSION.to_f >= 2.0, sock.close_on_exec?) end def test_start sock = Kgio::Socket.start(@addr) + + sock.respond_to?(:close_on_exec?) and + assert_equal(RUBY_VERSION.to_f >= 2.0, sock.close_on_exec?) + assert_kind_of Kgio::Socket, sock ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] @@ -50,6 +57,10 @@ class TestKgioTcpConnect < Test::Unit::TestCase def test_tcp_socket_new sock = Kgio::TCPSocket.new(@host, @port) + + sock.respond_to?(:close_on_exec?) and + assert_equal(RUBY_VERSION.to_f >= 2.0, sock.close_on_exec?) + assert_instance_of Kgio::TCPSocket, sock ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] diff --git a/test/test_tryopen.rb b/test/test_tryopen.rb index 380026d..5a8efb2 100644 --- a/test/test_tryopen.rb +++ b/test/test_tryopen.rb @@ -7,6 +7,10 @@ class TestTryopen < Test::Unit::TestCase def test_tryopen_success tmp = Kgio::File.tryopen(__FILE__) + + tmp.respond_to?(:close_on_exec?) and + assert_equal(RUBY_VERSION.to_f >= 2.0, tmp.close_on_exec?) + assert_kind_of File, tmp assert_equal File.read(__FILE__), tmp.read assert_equal __FILE__, tmp.path diff --git a/test/test_unix_connect.rb b/test/test_unix_connect.rb index f99a877..b85f1f6 100644 --- a/test/test_unix_connect.rb +++ b/test/test_unix_connect.rb @@ -34,6 +34,10 @@ class TestKgioUnixConnect < Test::Unit::TestCase def test_unix_socket_new sock = Kgio::UNIXSocket.new(@path) + + sock.respond_to?(:close_on_exec?) and + assert_equal(RUBY_VERSION.to_f >= 2.0, sock.close_on_exec?) + assert_instance_of Kgio::UNIXSocket, sock ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] @@ -42,6 +46,10 @@ class TestKgioUnixConnect < Test::Unit::TestCase def test_new sock = Kgio::Socket.new(@addr) + + sock.respond_to?(:close_on_exec?) and + assert_equal(RUBY_VERSION.to_f >= 2.0, sock.close_on_exec?) + assert_instance_of Kgio::Socket, sock ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] -- cgit v1.2.3-24-ge0c7