about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--ext/kgio/extconf.rb2
-rw-r--r--ext/kgio/kgio.h1
-rw-r--r--ext/kgio/kgio_ext.c1
-rw-r--r--ext/kgio/set_file_path.h26
-rw-r--r--ext/kgio/tryopen.c151
-rw-r--r--test/test_tryopen.rb73
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