From fef9ecb1bb595d3973c2c92ea997bec7363a7d18 Mon Sep 17 00:00:00 2001 From: nobu Date: Mon, 3 Apr 2017 00:10:50 +0000 Subject: 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. Based on patches by Avseyev at [ruby-core:79290]. [Feature #4532] * configure.in: check for pwrite(2). pread() is already used internally for IO.copy_stream. * io.c: implement wrappers for pread(2) and pwrite(2) and expose them in IO. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@58240 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- NEWS | 5 ++ configure.in | 1 + doc/contributors.rdoc | 3 ++ io.c | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/ruby/test_io.rb | 21 ++++++++ 5 files changed, 175 insertions(+) diff --git a/NEWS b/NEWS index 0a53aa2ee0..26b9401a4a 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,11 @@ with all sufficient information, see the ChangeLog file or Redmine * Integer.sqrt [Feature #13219] +* IO + + * IO#pread [Feature #4532] + * IO#pwrite [Feature #4532] + * Regexp * Update Onigmo 6.1.1. * Support absent operator https://github.com/k-takata/Onigmo/issues/82 diff --git a/configure.in b/configure.in index dcd56ba2f7..48a10811a0 100644 --- a/configure.in +++ b/configure.in @@ -2427,6 +2427,7 @@ AC_CHECK_FUNCS(posix_fadvise) AC_CHECK_FUNCS(posix_memalign) AC_CHECK_FUNCS(ppoll) AC_CHECK_FUNCS(pread) +AC_CHECK_FUNCS(pwrite) AC_CHECK_FUNCS(qsort_r) AC_CHECK_FUNCS(qsort_s) AC_CHECK_FUNCS(readlink) diff --git a/doc/contributors.rdoc b/doc/contributors.rdoc index eaec30d7d0..ec567cd35f 100644 --- a/doc/contributors.rdoc +++ b/doc/contributors.rdoc @@ -37,6 +37,9 @@ arton * He is the distributor of ActiveScriptRuby and experimental 1.9.0-x installers for win32. * Wrote patches for win32ole, gc.c, tmpdir.rb +Sergey Avseyev +* Added IO#pread and IO#pwrite. + == B Daniel Berger diff --git a/io.c b/io.c index 085d7df2c9..3ac6b97a0e 100644 --- a/io.c +++ b/io.c @@ -4832,6 +4832,148 @@ rb_io_sysread(int argc, VALUE *argv, VALUE io) return str; } +#if defined(HAVE_PREAD) || defined(HAVE_PWRITE) +struct prdwr_internal_arg { + int fd; + void *buf; + size_t count; + off_t offset; +}; +#endif /* HAVE_PREAD || HAVE_PWRITE */ + +#if defined(HAVE_PREAD) +static VALUE +internal_pread_func(void *arg) +{ + struct prdwr_internal_arg *p = arg; + return (VALUE)pread(p->fd, p->buf, p->count, p->offset); +} + +static VALUE +pread_internal_call(VALUE arg) +{ + struct prdwr_internal_arg *p = (struct prdwr_internal_arg *)arg; + return rb_thread_io_blocking_region(internal_pread_func, p, p->fd); +} + +/* + * call-seq: + * ios.pread(maxlen, offset[, outbuf]) -> string + * + * Reads maxlen bytes from ios 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 outbuf argument is present, it must + * reference a String, which will receive the data. + * Raises SystemCallError on error, EOFError + * at end of file and NotImplementedError if platform does not + * implement the system call. + * + * f = File.new("testfile") + * f.read #=> "This is line one\nThis is line two\n" + * f.pread(12, 0) #=> "This is line" + * f.pread(9, 8) #=> "line one\n" + */ +static VALUE +rb_io_pread(int argc, VALUE *argv, VALUE io) +{ + VALUE len, offset, str; + rb_io_t *fptr; + ssize_t n; + struct prdwr_internal_arg arg; + + rb_scan_args(argc, argv, "21", &len, &offset, &str); + arg.count = NUM2SIZET(len); + arg.offset = NUM2OFFT(offset); + + io_setstrbuf(&str, (long)arg.count); + if (arg.count == 0) return str; + arg.buf = RSTRING_PTR(str); + + GetOpenFile(io, fptr); + rb_io_check_byte_readable(fptr); + + arg.fd = fptr->fd; + rb_io_check_closed(fptr); + + rb_str_locktmp(str); + n = (ssize_t)rb_ensure(pread_internal_call, (VALUE)&arg, rb_str_unlocktmp, str); + + if (n == -1) { + rb_sys_fail_path(fptr->pathv); + } + io_set_read_length(str, n); + if (n == 0 && arg.count > 0) { + rb_eof_error(); + } + OBJ_TAINT(str); + + return str; +} +#else +# define rb_io_pread rb_f_notimplement +#endif /* HAVE_PREAD */ + +#if defined(HAVE_PWRITE) +static VALUE +internal_pwrite_func(void *ptr) +{ + struct prdwr_internal_arg *arg = ptr; + + return (VALUE)pwrite(arg->fd, arg->buf, arg->count, arg->offset); +} + +/* + * call-seq: + * ios.pwrite(string, offset) -> integer + * + * Writes the given string to ios at offset 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 SystemCallError on error and NotImplementedError + * if platform does not implement the system call. + * + * f = File.new("out", "w") + * f.pwrite("ABCDEF", 3) #=> 6 + * + * File.read("out") #=> "\u0000\u0000\u0000ABCDEF" + */ +static VALUE +rb_io_pwrite(VALUE io, VALUE offset, VALUE str) +{ + rb_io_t *fptr; + ssize_t n; + struct prdwr_internal_arg arg; + + if (!RB_TYPE_P(str, T_STRING)) + str = rb_obj_as_string(str); + + arg.buf = RSTRING_PTR(str); + arg.count = (size_t)RSTRING_LEN(str); + arg.offset = NUM2OFFT(offset); + + io = GetWriteIO(io); + GetOpenFile(io, fptr); + rb_io_check_writable(fptr); + arg.fd = fptr->fd; + + n = (ssize_t)rb_thread_io_blocking_region(internal_pwrite_func, &arg, fptr->fd); + RB_GC_GUARD(str); + + if (n == -1) rb_sys_fail_path(fptr->pathv); + + return SSIZET2NUM(n); +} +#else +# define rb_io_pwrite rb_f_notimplement +#endif /* HAVE_PWRITE */ + VALUE rb_io_binmode(VALUE io) { @@ -12464,6 +12606,9 @@ Init_IO(void) rb_define_method(rb_cIO, "syswrite", rb_io_syswrite, 1); rb_define_method(rb_cIO, "sysread", rb_io_sysread, -1); + rb_define_method(rb_cIO, "pread", rb_io_pread, -1); + rb_define_method(rb_cIO, "pwrite", rb_io_pwrite, 2); + rb_define_method(rb_cIO, "fileno", rb_io_fileno, 0); rb_define_alias(rb_cIO, "to_i", "fileno"); rb_define_method(rb_cIO, "to_io", rb_io_to_io, 0); diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index ca3f1e2d3b..d6cbf205e9 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -3525,5 +3525,26 @@ __END__ end end end + + def test_pread + make_tempfile { |t| + 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_raise(EOFError) { f.pread(1, f.size) } + end + } + end if IO.method_defined?(:pread) + + def test_pwrite + make_tempfile { |t| + 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?(:pread) and IO.method_defined?(:pwrite) end end -- cgit v1.2.3