From 30cf18119558637e9fb9b40e3d8aa3615d625ccb Mon Sep 17 00:00:00 2001
From: Eric Wong <normalperson@yhbt.net>
Date: Sun, 27 Mar 2011 21:57:56 -0700
Subject: [PATCH] add IO#pread and IO#pwrite methods

These methods are useful for safe/concurrent file I/O in
multi-thread/process environments and also fairly standard
nowadays especially in systems supporting pthreads.

pread() is already used internally for IO.copy_stream
---
 configure.in         |    2 +-
 io.c                 |  134 ++++++++++++++++++++++++++++++++++++++++++++++++++
 test/ruby/test_io.rb |   21 ++++++++
 3 files changed, 156 insertions(+), 1 deletions(-)

diff --git a/configure.in b/configure.in
index 01e59f9..8c09fc5 100644
--- a/configure.in
+++ b/configure.in
@@ -1311,7 +1311,7 @@ AC_CHECK_FUNCS(fmod killpg wait4 waitpid fork spawnv syscall __syscall chroot ge
 	      setsid telldir seekdir fchmod cosh sinh tanh log2 round\
 	      setuid setgid daemon select_large_fdset setenv unsetenv\
               mktime timegm gmtime_r clock_gettime gettimeofday\
-              pread sendfile shutdown sigaltstack dl_iterate_phdr)
+              pread pwrite sendfile shutdown sigaltstack dl_iterate_phdr)
 
 AC_CACHE_CHECK(for unsetenv returns a value, rb_cv_unsetenv_return_value,
   [AC_TRY_COMPILE([
diff --git a/io.c b/io.c
index 064d1a2..1686c5e 100644
--- a/io.c
+++ b/io.c
@@ -3925,6 +3925,133 @@ rb_io_syswrite(VALUE io, VALUE str)
     return LONG2FIX(n);
 }
 
+#if defined(HAVE_PREAD) || defined(HAVE_PWRITE)
+struct prdwr_args {
+    int fd;
+    void *buf;
+    size_t count;
+    off_t offset;
+};
+#endif
+
+#if defined(HAVE_PREAD)
+static VALUE
+nogvl_pread(void *ptr)
+{
+    struct prdwr_args *args = ptr;
+
+    return (VALUE)pread(args->fd, args->buf, args->count, args->offset);
+}
+
+/*
+ *  call-seq:
+ *     ios.pread(maxlen, offset[, outbuf])    -> string
+ *
+ *  Reads <i>maxlen</i> bytes from <em>ios</em> using the pread system call
+ *  and returns them as a string without modifying the underlying
+ *  descriptor offset.  This is advantageous compared to combining IO#seek
+ *  and IO#read in that it is atomic, allowing multiple threads/process to
+ *  share the same IO object for reading the file at various locations.
+ *  This bypasses any userspace buffering of the IO layer.
+ *  If the optional <i>outbuf</i> argument is present, it must
+ *  reference a String, which will receive the data.
+ *  Raises <code>SystemCallError</code> on error and <code>EOFError</code>
+ *  at end of file.
+ *  Not implemented on all platforms.
+ *
+ *     f = File.new("testfile")
+ *     f.pread(16, 0)   #=> "This is line one"
+ */
+static VALUE
+rb_io_pread(int argc, VALUE *argv, VALUE io)
+{
+    VALUE len, offset, str;
+    rb_io_t *fptr;
+    ssize_t n;
+    struct prdwr_args args;
+
+    rb_scan_args(argc, argv, "21", &len, &offset, &str);
+    args.count = NUM2SIZET(len);
+    args.offset = NUM2OFFT(offset);
+
+    io_setstrbuf(&str, (long)args.count);
+    if (args.count == 0) return str;
+    args.buf = RSTRING_PTR(str);
+
+    GetOpenFile(io, fptr);
+    rb_io_check_byte_readable(fptr);
+
+    args.fd = fptr->fd;
+    rb_io_check_closed(fptr);
+
+    rb_str_locktmp(str);
+    n = (ssize_t)rb_thread_io_blocking_region(nogvl_pread, &args, fptr->fd);
+    rb_str_unlocktmp(str);
+
+    if (n == -1) {
+	rb_sys_fail_path(fptr->pathv);
+    }
+    rb_str_set_len(str, n);
+    if (n == 0 && args.count > 0) {
+	rb_eof_error();
+    }
+    rb_str_resize(str, n);
+    OBJ_TAINT(str);
+
+    return str;
+}
+#endif /* HAVE_PREAD */
+
+#if defined(HAVE_PWRITE)
+static VALUE
+nogvl_pwrite(void *ptr)
+{
+    struct prdwr_args *args = ptr;
+
+    return (VALUE)pwrite(args->fd, args->buf, args->count, args->offset);
+}
+
+/*
+ *  call-seq:
+ *     ios.pwrite(offset, string)    -> integer
+ *
+ *  Writes the given string to <em>ios</em> at <i>offset</i> using pwrite()
+ *  system call.  This is advantageous to combining IO#seek and IO#write
+ *  in that it is atomic, allowing multiple threads/process to share the
+ *  same IO object for reading the file at various locations.
+ *  This bypasses any userspace buffering of the IO layer.
+ *  Returns the number of bytes written.
+ *  Raises <code>SystemCallError</code> on error.
+ *
+ *     f = File.new("out", "w")
+ *     f.pwrite(3, "ABCDEF")   #=> 6
+ */
+static VALUE
+rb_io_pwrite(VALUE io, VALUE offset, VALUE string)
+{
+    rb_io_t *fptr;
+    ssize_t n;
+    struct prdwr_args args;
+
+    rb_secure(4);
+    string = rb_obj_as_string(string);
+    args.buf = RSTRING_PTR(string);
+    args.count = (size_t)RSTRING_LEN(string);
+    args.offset = NUM2OFFT(offset);
+
+    io = GetWriteIO(io);
+    GetOpenFile(io, fptr);
+    rb_io_check_writable(fptr);
+    args.fd = fptr->fd;
+
+    n = (ssize_t)rb_thread_io_blocking_region(nogvl_pwrite, &args, fptr->fd);
+
+    if (n == -1) rb_sys_fail_path(fptr->pathv);
+
+    return SSIZET2NUM(n);
+}
+#endif /* HAVE_PWRITE */
+
 /*
  *  call-seq:
  *     ios.sysread(maxlen[, outbuf])    -> string
@@ -10409,6 +10536,13 @@ Init_IO(void)
     rb_define_method(rb_cIO, "flush", rb_io_flush, 0);
     rb_define_method(rb_cIO, "tell", rb_io_tell, 0);
     rb_define_method(rb_cIO, "seek", rb_io_seek_m, -1);
+
+#ifdef HAVE_PREAD
+    rb_define_method(rb_cIO, "pread", rb_io_pread, -1);
+#endif
+#ifdef HAVE_PWRITE
+    rb_define_method(rb_cIO, "pwrite", rb_io_pwrite, 2);
+#endif
     rb_define_const(rb_cIO, "SEEK_SET", INT2FIX(SEEK_SET));
     rb_define_const(rb_cIO, "SEEK_CUR", INT2FIX(SEEK_CUR));
     rb_define_const(rb_cIO, "SEEK_END", INT2FIX(SEEK_END));
diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb
index 6b8e6b5..0c46c6d 100644
--- a/test/ruby/test_io.rb
+++ b/test/ruby/test_io.rb
@@ -1809,4 +1809,25 @@ End
       Process.waitpid2(pid)
     end
   end
+
+  def test_pread
+    t = make_tempfile
+
+    open(t.path) do |f|
+      assert_equal "bar", f.pread(3, 4)
+      buf = "asdf"
+      assert_equal "bar", f.pread(3, 4, buf)
+      assert_equal "bar", buf
+      assert_raises(EOFError) { f.pread(1, f.size) }
+    end
+  end if IO.method_defined?(:pread)
+
+  def test_pwrite
+    t = make_tempfile
+
+    open(t.path, IO::RDWR) do |f|
+      assert_equal 3, f.pwrite(4, "ooo")
+      assert_equal "ooo", f.pread(3, 4)
+    end
+  end if IO.method_defined?(:pwrite) && IO.method_defined?(:pread)
 end
-- 
1.7.4.1.416.ged9c5

