diff options
-rwxr-xr-x | GIT-VERSION-GEN | 2 | ||||
-rw-r--r-- | ext/kgio/extconf.rb | 2 | ||||
-rw-r--r-- | ext/kgio/kgio.h | 1 | ||||
-rw-r--r-- | ext/kgio/kgio_ext.c | 1 | ||||
-rw-r--r-- | ext/kgio/set_file_path.h | 26 | ||||
-rw-r--r-- | ext/kgio/tryopen.c | 151 | ||||
-rw-r--r-- | test/test_tryopen.rb | 73 |
7 files changed, 255 insertions, 1 deletions
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 12b7c6f..f4e1cb4 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.4.1.GIT +DEF_VER=v2.4.0.GIT LF=' ' diff --git a/ext/kgio/extconf.rb b/ext/kgio/extconf.rb index f44b8c8..f988ce1 100644 --- a/ext/kgio/extconf.rb +++ b/ext/kgio/extconf.rb @@ -19,12 +19,14 @@ if have_header('ruby/io.h') rubyio = %w(ruby.h ruby/io.h) have_struct_member("rb_io_t", "fd", rubyio) have_struct_member("rb_io_t", "mode", rubyio) + have_struct_member("rb_io_t", "pathv", rubyio) else rubyio = %w(ruby.h rubyio.h) rb_io_t = have_type("OpenFile", rubyio) ? "OpenFile" : "rb_io_t" have_struct_member(rb_io_t, "f", rubyio) have_struct_member(rb_io_t, "f2", rubyio) have_struct_member(rb_io_t, "mode", rubyio) + have_struct_member(rb_io_t, "path", rubyio) have_func('rb_fdopen') end have_type("struct RFile", rubyio) and check_sizeof("struct RFile", rubyio) diff --git a/ext/kgio/kgio.h b/ext/kgio/kgio.h index 9056cef..0ecfde0 100644 --- a/ext/kgio/kgio.h +++ b/ext/kgio/kgio.h @@ -36,6 +36,7 @@ void init_kgio_accept(void); void init_kgio_connect(void); void init_kgio_autopush(void); void init_kgio_poll(void); +void init_kgio_tryopen(void); void kgio_autopush_accept(VALUE, VALUE); void kgio_autopush_recv(VALUE); diff --git a/ext/kgio/kgio_ext.c b/ext/kgio/kgio_ext.c index f50b3c8..2365fd0 100644 --- a/ext/kgio/kgio_ext.c +++ b/ext/kgio/kgio_ext.c @@ -8,4 +8,5 @@ void Init_kgio_ext(void) init_kgio_accept(); init_kgio_autopush(); init_kgio_poll(); + init_kgio_tryopen(); } diff --git a/ext/kgio/set_file_path.h b/ext/kgio/set_file_path.h new file mode 100644 index 0000000..50fd338 --- /dev/null +++ b/ext/kgio/set_file_path.h @@ -0,0 +1,26 @@ +#if defined(HAVE_RB_IO_T) && \ + defined(HAVE_TYPE_STRUCT_RFILE) && \ + defined(HAVE_ST_PATHV) +/* MRI 1.9 */ +static void set_file_path(VALUE io, VALUE path) +{ + rb_io_t *fptr = RFILE(io)->fptr; + fptr->pathv = rb_str_new4(path); +} +#elif defined(HAVE_TYPE_OPENFILE) && \ + defined(HAVE_TYPE_STRUCT_RFILE) && \ + defined(HAVE_ST_PATH) +/* MRI 1.8 */ +#include "util.h" +static void set_file_path(VALUE io, VALUE path) +{ + OpenFile *fptr = RFILE(io)->fptr; + fptr->path = ruby_strdup(RSTRING_PTR(path)); +} +#else +/* Rubinius */ +static void set_file_path(VALUE io, VALUE path) +{ + rb_iv_set(io, "@path", rb_str_new4(path)); +} +#endif diff --git a/ext/kgio/tryopen.c b/ext/kgio/tryopen.c new file mode 100644 index 0000000..d6789f4 --- /dev/null +++ b/ext/kgio/tryopen.c @@ -0,0 +1,151 @@ +#include <ruby.h> +#ifdef HAVE_RUBY_IO_H +# include <ruby/io.h> +#else +# include <rubyio.h> +#endif + +#ifdef HAVE_RUBY_ST_H +# include <ruby/st.h> +#else +# include <st.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "set_file_path.h" + +static ID id_for_fd, id_to_path, id_path; +static st_table *errno2sym; + +struct open_args { + const char *pathname; + int flags; + mode_t mode; +}; + +static VALUE nogvl_open(void *ptr) +{ + struct open_args *o = ptr; + + return (VALUE)open(o->pathname, o->flags, o->mode); +} + +#ifndef HAVE_RB_THREAD_BLOCKING_REGION +# define RUBY_UBF_IO ((void *)(-1)) +# include "rubysig.h" +typedef void rb_unblock_function_t(void *); +typedef VALUE rb_blocking_function_t(void *); +static VALUE rb_thread_blocking_region( + rb_blocking_function_t *fn, void *data1, + rb_unblock_function_t *ubf, void *data2) +{ + VALUE rv; + + TRAP_BEG; /* for FIFO */ + rv = fn(data1); + TRAP_END; + + return rv; +} +#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ + +static VALUE s_tryopen(int argc, VALUE *argv, VALUE klass) +{ + int fd; + VALUE pathname, flags, mode; + struct open_args o; + int retried = 0; + VALUE rv; + + rb_scan_args(argc, argv, "12", &pathname, &flags, &mode); + if (rb_respond_to(pathname, id_to_path)) + pathname = rb_funcall(pathname, id_to_path, 0); + o.pathname = StringValueCStr(pathname); + + switch (TYPE(flags)) { + case T_NIL: o.flags = O_RDONLY; break; + case T_FIXNUM: o.flags = FIX2INT(flags); break; + case T_BIGNUM: o.flags = NUM2INT(flags); break; + default: rb_raise(rb_eArgError, "flags must be an Integer"); + } + switch (TYPE(mode)) { + case T_NIL: o.mode = 0666; break; + case T_FIXNUM: o.mode = FIX2INT(mode); break; + case T_BIGNUM: o.mode = NUM2INT(mode); break; + default: rb_raise(rb_eArgError, "mode must be an Integer"); + } + +retry: + fd = (int)rb_thread_blocking_region(nogvl_open, &o, RUBY_UBF_IO, 0); + if (fd == -1) { + if (errno == EMFILE || errno == ENFILE) { + rb_gc(); + if (retried) + rb_sys_fail(o.pathname); + retried = 1; + goto retry; + } + if (fd == -1) { + int saved_errno = errno; + + if (!st_lookup(errno2sym, (st_data_t)errno, &rv)) { + errno = saved_errno; + rb_sys_fail(o.pathname); + } + return rv; + } + } + rv = rb_funcall(klass, id_for_fd, 1, INT2FIX(fd)); + set_file_path(rv, pathname); + return rv; +} + +void init_kgio_tryopen(void) +{ + VALUE mKgio = rb_define_module("Kgio"); + VALUE cFile; + VALUE tmp; + VALUE *ptr; + long len; + + id_path = rb_intern("path"); + id_for_fd = rb_intern("for_fd"); + id_to_path = rb_intern("to_path"); + + cFile = rb_define_class_under(mKgio, "File", rb_cFile); + rb_define_singleton_method(cFile, "tryopen", s_tryopen, -1); + + if (!rb_funcall(cFile, rb_intern("method_defined?"), 1, + ID2SYM(id_to_path))) + rb_define_alias(cFile, "to_path", "path"); + + errno2sym = st_init_numtable(); + tmp = rb_funcall(rb_mErrno, rb_intern("constants"), 0); + ptr = RARRAY_PTR(tmp); + len = RARRAY_LEN(tmp); + for (; --len >= 0; ptr++) { + VALUE error; + ID const_id; + + switch (TYPE(*ptr)) { + case T_SYMBOL: const_id = SYM2ID(*ptr); break; + case T_STRING: const_id = rb_intern(RSTRING_PTR(*ptr)); break; + default: rb_bug("constant not a symbol or string"); + } + + error = rb_const_get(rb_mErrno, const_id); + if ((TYPE(error) != T_CLASS) || + !rb_const_defined(error, rb_intern("Errno"))) + continue; + + error = rb_const_get(error, rb_intern("Errno")); + switch (TYPE(error)) { + case T_FIXNUM: + case T_BIGNUM: + st_insert(errno2sym, (st_data_t)NUM2INT(error), + ID2SYM(const_id)); + } + } +} diff --git a/test/test_tryopen.rb b/test/test_tryopen.rb new file mode 100644 index 0000000..e60cb27 --- /dev/null +++ b/test/test_tryopen.rb @@ -0,0 +1,73 @@ +require 'tempfile' +require 'test/unit' +$-w = true +require 'kgio' + +class TestTryopen < Test::Unit::TestCase + + def test_tryopen_success + tmp = Kgio::File.tryopen(__FILE__) + assert_kind_of File, tmp + assert_equal File.read(__FILE__), tmp.read + assert_equal __FILE__, tmp.path + assert_equal __FILE__, tmp.to_path + assert_nothing_raised { tmp.close } + end + + def test_tryopen_ENOENT + tmp = Tempfile.new "tryopen" + path = tmp.path + tmp.close! + tmp = Kgio::File.tryopen(path) + assert_equal :ENOENT, tmp + end + + def test_tryopen_EPERM + tmp = Tempfile.new "tryopen" + File.chmod 0000, tmp.path + tmp = Kgio::File.tryopen(tmp.path) + assert_equal :EACCES, tmp + end + + def test_tryopen_readwrite + tmp = Tempfile.new "tryopen" + file = Kgio::File.tryopen(tmp.path, IO::RDWR) + file.syswrite "FOO" + assert_equal "FOO", tmp.sysread(3) + end + + def test_tryopen_mode + tmp = Tempfile.new "tryopen" + path = tmp.path + tmp.close! + file = Kgio::File.tryopen(path, IO::RDWR|IO::CREAT, 0000) + assert_equal 0100000, File.stat(path).mode + ensure + File.unlink path + end + + require "benchmark" + def test_benchmark + nr = 1000000 + tmp = Tempfile.new('tryopen') + file = tmp.path + Benchmark.bmbm do |x| + x.report("tryopen (OK)") do + nr.times { Kgio::File.tryopen(file).close } + end + x.report("open (OK)") do + nr.times { File.readable?(file) && File.open(file).close } + end + end + tmp.close! + assert_equal :ENOENT, Kgio::File.tryopen(file) + Benchmark.bmbm do |x| + x.report("tryopen (ENOENT)") do + nr.times { Kgio::File.tryopen(file) } + end + x.report("open (ENOENT)") do + nr.times { File.readable?(file) && File.open(file) } + end + end + end if ENV["BENCHMARK"] +end |