From 4c6d524c473ebb9174d0183dc1d1ac0530337314 Mon Sep 17 00:00:00 2001 From: dearblue Date: Sat, 1 Feb 2020 16:25:39 +0900 Subject: Implement `IO#pread` and `IO#pwrite` It is available by default in environments where `__unix__` is defined. Other environments are enabled by defining `MRB_WITH_IO_PREAD_PWRITE` (requires an implementation of `pread()` and `pwrite()` functions). In any case, you can disable it by defining `MRB_WITHOUT_IO_PREAD_PWRITE`. --- mrbgems/mruby-io/include/mruby/ext/io.h | 8 +++ mrbgems/mruby-io/src/io.c | 113 +++++++++++++++++++++++++++++--- mrbgems/mruby-io/test/io.rb | 28 ++++++++ mrbgems/mruby-io/test/mruby_io_test.c | 9 +++ 4 files changed, 150 insertions(+), 8 deletions(-) diff --git a/mrbgems/mruby-io/include/mruby/ext/io.h b/mrbgems/mruby-io/include/mruby/ext/io.h index 4bcbbe914..c4eccb936 100644 --- a/mrbgems/mruby-io/include/mruby/ext/io.h +++ b/mrbgems/mruby-io/include/mruby/ext/io.h @@ -9,6 +9,14 @@ extern "C" { #endif +#if defined(MRB_WITHOUT_IO_PREAD_PWRITE) +# undef MRB_WITH_IO_PREAD_PWRITE +#elif !defined(MRB_WITH_IO_PREAD_PWRITE) +# if defined(__unix__) +# define MRB_WITH_IO_PREAD_PWRITE +# endif +#endif + struct mrb_io { int fd; /* file descriptor, or -1 */ int fd2; /* file descriptor to write if it's different from fd, or -1 */ diff --git a/mrbgems/mruby-io/src/io.c b/mrbgems/mruby-io/src/io.c index 4be41ef31..b7f70a5f8 100644 --- a/mrbgems/mruby-io/src/io.c +++ b/mrbgems/mruby-io/src/io.c @@ -31,6 +31,7 @@ typedef long ftime_t; typedef long fsuseconds_t; typedef int fmode_t; + typedef int mrb_io_read_write_size; #ifndef O_TMPFILE #define O_TMPFILE O_TEMPORARY @@ -43,6 +44,7 @@ typedef time_t ftime_t; typedef suseconds_t fsuseconds_t; typedef mode_t fmode_t; + typedef ssize_t mrb_io_read_write_size; #endif #ifdef _MSC_VER @@ -845,15 +847,35 @@ mrb_io_s_sysopen(mrb_state *mrb, mrb_value klass) return mrb_fixnum_value(fd); } +static mrb_value mrb_io_sysread_common(mrb_state *mrb, + mrb_io_read_write_size (*readfunc)(int, void *, fsize_t, off_t), + mrb_value io, mrb_value buf, mrb_int maxlen, off_t offset); + +static mrb_io_read_write_size +mrb_sysread_dummy(int fd, void *buf, fsize_t nbytes, off_t offset) +{ + return (mrb_io_read_write_size)read(fd, buf, nbytes); +} + mrb_value mrb_io_sysread(mrb_state *mrb, mrb_value io) { - struct mrb_io *fptr; mrb_value buf = mrb_nil_value(); mrb_int maxlen; - int ret; mrb_get_args(mrb, "i|S", &maxlen, &buf); + + return mrb_io_sysread_common(mrb, mrb_sysread_dummy, io, buf, maxlen, 0); +} + +static mrb_value +mrb_io_sysread_common(mrb_state *mrb, + mrb_io_read_write_size (*readfunc)(int, void *, fsize_t, off_t), + mrb_value io, mrb_value buf, mrb_int maxlen, off_t offset) +{ + struct mrb_io *fptr; + int ret; + if (maxlen < 0) { mrb_raise(mrb, E_ARGUMENT_ERROR, "negative expanding string size"); } @@ -875,7 +897,7 @@ mrb_io_sysread(mrb_state *mrb, mrb_value io) if (!fptr->readable) { mrb_raise(mrb, E_IO_ERROR, "not opened for reading"); } - ret = read(fptr->fd, RSTRING_PTR(buf), (fsize_t)maxlen); + ret = readfunc(fptr->fd, RSTRING_PTR(buf), (fsize_t)maxlen, offset); switch (ret) { case 0: /* EOF */ if (maxlen == 0) { @@ -925,11 +947,31 @@ mrb_io_sysseek(mrb_state *mrb, mrb_value io) } } +static mrb_value mrb_io_syswrite_common(mrb_state *mrb, + mrb_io_read_write_size (*writefunc)(int, const void *, fsize_t, off_t), + mrb_value io, mrb_value buf, off_t offset); + +static mrb_io_read_write_size +mrb_syswrite_dummy(int fd, const void *buf, fsize_t nbytes, off_t offset) +{ + return (mrb_io_read_write_size)write(fd, buf, nbytes); +} + mrb_value mrb_io_syswrite(mrb_state *mrb, mrb_value io) +{ + mrb_value buf; + + mrb_get_args(mrb, "S", &buf); + + return mrb_io_syswrite_common(mrb, mrb_syswrite_dummy, io, buf, 0); +} + +static mrb_value mrb_io_syswrite_common(mrb_state *mrb, + mrb_io_read_write_size (*writefunc)(int, const void *, fsize_t, off_t), + mrb_value io, mrb_value buf, off_t offset) { struct mrb_io *fptr; - mrb_value str, buf; int fd, length; fptr = io_get_open_fptr(mrb, io); @@ -937,15 +979,12 @@ mrb_io_syswrite(mrb_state *mrb, mrb_value io) mrb_raise(mrb, E_IO_ERROR, "not opened for writing"); } - mrb_get_args(mrb, "S", &str); - buf = str; - if (fptr->fd2 == -1) { fd = fptr->fd; } else { fd = fptr->fd2; } - length = write(fd, RSTRING_PTR(buf), (fsize_t)RSTRING_LEN(buf)); + length = writefunc(fd, RSTRING_PTR(buf), (fsize_t)RSTRING_LEN(buf), offset); if (length == -1) { mrb_sys_fail(mrb, 0); } @@ -1328,6 +1367,62 @@ mrb_io_sync(mrb_state *mrb, mrb_value self) return mrb_bool_value(fptr->sync); } +#ifndef MRB_WITH_IO_PREAD_PWRITE +# define mrb_io_pread mrb_notimplement_m +# define mrb_io_pwrite mrb_notimplement_m +#else +static off_t +value2off(mrb_state *mrb, mrb_value offv) +{ + switch (mrb_type(offv)) { +#ifndef MRB_WITHOUT_FLOAT + case MRB_TT_FLOAT: + { + mrb_float tmp = mrb_float(offv); + if (tmp < INT64_MIN || tmp > INT64_MAX) { + /* fall through to use convert by `mrb_int()` (and raise error if out of range) */ + } else { + return (off_t)tmp; + } + } + /* fall through */ +#endif /* MRB_WITHOUT_FLOAT */ + default: + return (off_t)mrb_int(mrb, offv); + } +} + +/* + * call-seq: + * pread(maxlen, offset, outbuf = "") -> outbuf + */ +static mrb_value +mrb_io_pread(mrb_state *mrb, mrb_value io) +{ + mrb_value buf = mrb_nil_value(); + mrb_value off; + mrb_int maxlen; + + mrb_get_args(mrb, "io|S!", &maxlen, &off, &buf); + + return mrb_io_sysread_common(mrb, pread, io, buf, maxlen, value2off(mrb, off)); +} + +/* + * call-seq: + * pwrite(buffer, offset) -> wrote_bytes + */ +static mrb_value +mrb_io_pwrite(mrb_state *mrb, mrb_value io) +{ + mrb_value buf, off; + + mrb_get_args(mrb, "So", &buf, &off); + + return mrb_io_syswrite_common(mrb, pwrite, io, buf, value2off(mrb, off)); +} +#endif /* MRB_WITH_IO_PREAD_PWRITE */ + static mrb_value io_bufread(mrb_state *mrb, mrb_value self) { @@ -1383,6 +1478,8 @@ mrb_init_io(mrb_state *mrb) mrb_define_method(mrb, io, "closed?", mrb_io_closed, MRB_ARGS_NONE()); /* 15.2.20.5.2 */ mrb_define_method(mrb, io, "pid", mrb_io_pid, MRB_ARGS_NONE()); /* 15.2.20.5.2 */ mrb_define_method(mrb, io, "fileno", mrb_io_fileno_m, MRB_ARGS_NONE()); + mrb_define_method(mrb, io, "pread", mrb_io_pread, MRB_ARGS_ANY()); /* ruby 2.5 feature */ + mrb_define_method(mrb, io, "pwrite", mrb_io_pwrite, MRB_ARGS_ANY()); /* ruby 2.5 feature */ mrb_define_class_method(mrb, io, "_bufread", io_bufread, MRB_ARGS_REQ(2)); } diff --git a/mrbgems/mruby-io/test/io.rb b/mrbgems/mruby-io/test/io.rb index e3024cf9a..458d2cdc2 100644 --- a/mrbgems/mruby-io/test/io.rb +++ b/mrbgems/mruby-io/test/io.rb @@ -564,6 +564,34 @@ assert('IO#sysseek') do end end +assert('IO#pread') do + skip "IO#pread is not implemented on this configuration" unless MRubyIOTestUtil::MRB_WITH_IO_PREAD_PWRITE + + IO.open(IO.sysopen($mrbtest_io_rfname, 'r'), 'r') do |io| + assert_equal $mrbtest_io_msg.byteslice(5, 8), io.pread(8, 5) + assert_equal 0, io.pos + assert_equal $mrbtest_io_msg.byteslice(1, 5), io.pread(5, 1) + assert_equal 0, io.pos + assert_raise(RuntimeError) { io.pread(20, -9) } + end +end + +assert('IO#pwrite') do + skip "IO#pwrite is not implemented on this configuration" unless MRubyIOTestUtil::MRB_WITH_IO_PREAD_PWRITE + + IO.open(IO.sysopen($mrbtest_io_wfname, 'w+'), 'w+') do |io| + assert_equal 6, io.pwrite("Warld!", 7) + assert_equal 0, io.pos + assert_equal 7, io.pwrite("Hello, ", 0) + assert_equal 0, io.pos + assert_equal "Hello, Warld!", io.read + assert_equal 6, io.pwrite("world!", 7) + assert_equal 13, io.pos + io.pos = 0 + assert_equal "Hello, world!", io.read + end +end + assert('IO.pipe') do begin called = false diff --git a/mrbgems/mruby-io/test/mruby_io_test.c b/mrbgems/mruby-io/test/mruby_io_test.c index 7e272d45a..581472eaa 100644 --- a/mrbgems/mruby-io/test/mruby_io_test.c +++ b/mrbgems/mruby-io/test/mruby_io_test.c @@ -64,6 +64,7 @@ mkdtemp(char *temp) #include "mruby/error.h" #include "mruby/string.h" #include "mruby/variable.h" +#include static mrb_value mrb_io_test_io_setup(mrb_state *mrb, mrb_value self) @@ -219,6 +220,12 @@ mrb_io_win_p(mrb_state *mrb, mrb_value klass) #endif } +#ifdef MRB_WITH_IO_PREAD_PWRITE +# define MRB_WITH_IO_PREAD_PWRITE_ENABLED TRUE +#else +# define MRB_WITH_IO_PREAD_PWRITE_ENABLED FALSE +#endif + void mrb_mruby_io_gem_test(mrb_state* mrb) { @@ -229,4 +236,6 @@ mrb_mruby_io_gem_test(mrb_state* mrb) mrb_define_class_method(mrb, io_test, "mkdtemp", mrb_io_test_mkdtemp, MRB_ARGS_REQ(1)); mrb_define_class_method(mrb, io_test, "rmdir", mrb_io_test_rmdir, MRB_ARGS_REQ(1)); mrb_define_class_method(mrb, io_test, "win?", mrb_io_win_p, MRB_ARGS_NONE()); + + mrb_define_const(mrb, io_test, "MRB_WITH_IO_PREAD_PWRITE", mrb_bool_value(MRB_WITH_IO_PREAD_PWRITE_ENABLED)); } -- cgit v1.2.3