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`.
MODIFY
mrbgems/mruby-io/include/mruby/ext/io.h
mrbgems/mruby-io/include/mruby/ext/io.h
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 */

MODIFY
mrbgems/mruby-io/src/io.c
mrbgems/mruby-io/src/io.c
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
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));
}

MODIFY
mrbgems/mruby-io/test/io.rb
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

MODIFY
mrbgems/mruby-io/test/mruby_io_test.c
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 <mruby/ext/io.h>
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));
}

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`.