diff --git a/ext/io/event/selector/epoll.c b/ext/io/event/selector/epoll.c index 62fd850e..68769e4c 100644 --- a/ext/io/event/selector/epoll.c +++ b/ext/io/event/selector/epoll.c @@ -590,36 +590,29 @@ struct io_read_arguments { int descriptor; - VALUE buffer; + // The remaining writable buffer region after applying the requested offset. + void *base; + size_t size; + + // The minimum number of bytes requested by the caller. size_t length; - size_t offset; }; static VALUE io_read_loop(VALUE _arguments) { struct io_read_arguments *arguments = (struct io_read_arguments *)_arguments; - void *base; - size_t size; - rb_io_buffer_get_bytes_for_writing(arguments->buffer, &base, &size); - size_t length = arguments->length; - size_t offset = arguments->offset; size_t total = 0; - // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset. - if (offset > size) { - return rb_fiber_scheduler_io_result(-1, EINVAL); - } - - size_t maximum_size = size - offset; + size_t maximum_size = arguments->size; while (maximum_size) { - ssize_t result = read(arguments->descriptor, (char*)base+offset, maximum_size); + ssize_t result = read(arguments->descriptor, (char*)arguments->base+total, maximum_size); if (result > 0) { total += result; - offset += result; if ((size_t)result >= length) break; + maximum_size -= result; length -= result; } else if (result == 0) { break; @@ -628,8 +621,6 @@ VALUE io_read_loop(VALUE _arguments) { } else { return rb_fiber_scheduler_io_result(-1, errno); } - - maximum_size = size - offset; } return rb_fiber_scheduler_io_result(total, 0); @@ -645,11 +636,24 @@ VALUE io_read_ensure(VALUE _arguments) { } VALUE IO_Event_Selector_EPoll_io_read(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _length, VALUE _offset) { - int descriptor = IO_Event_Selector_io_descriptor(io); - size_t offset = NUM2SIZET(_offset); size_t length = NUM2SIZET(_length); + void *base; + size_t size; + rb_io_buffer_get_bytes_for_writing(buffer, &base, &size); + + if (offset > size) { + return rb_fiber_scheduler_io_result(-1, EINVAL); + } else if (offset == size) { + return rb_fiber_scheduler_io_result(0, 0); + } + + base = (char*)base + offset; + size -= offset; + + int descriptor = IO_Event_Selector_io_descriptor(io); + struct io_read_arguments io_read_arguments = { .self = self, .fiber = fiber, @@ -657,9 +661,9 @@ VALUE IO_Event_Selector_EPoll_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b .flags = IO_Event_Selector_nonblock_set(descriptor), .descriptor = descriptor, - .buffer = buffer, + .base = base, + .size = size, .length = length, - .offset = offset, }; RB_OBJ_WRITTEN(self, Qundef, fiber); @@ -689,40 +693,29 @@ struct io_write_arguments { int descriptor; - VALUE buffer; + // The remaining readable buffer region after applying the requested offset. + const void *base; + size_t size; + + // The minimum number of bytes requested by the caller. size_t length; - size_t offset; }; static VALUE io_write_loop(VALUE _arguments) { struct io_write_arguments *arguments = (struct io_write_arguments *)_arguments; - const void *base; - size_t size; - rb_io_buffer_get_bytes_for_reading(arguments->buffer, &base, &size); - size_t length = arguments->length; - size_t offset = arguments->offset; size_t total = 0; - if (length > size) { - rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!"); - } - - // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset. - if (offset > size) { - return rb_fiber_scheduler_io_result(-1, EINVAL); - } - - size_t maximum_size = size - offset; + size_t maximum_size = arguments->size; while (maximum_size) { - ssize_t result = write(arguments->descriptor, (char*)base+offset, maximum_size); + ssize_t result = write(arguments->descriptor, (char*)arguments->base+total, maximum_size); if (result > 0) { total += result; - offset += result; if ((size_t)result >= length) break; + maximum_size -= result; length -= result; } else if (result == 0) { break; @@ -731,8 +724,6 @@ VALUE io_write_loop(VALUE _arguments) { } else { return rb_fiber_scheduler_io_result(-1, errno); } - - maximum_size = size - offset; } return rb_fiber_scheduler_io_result(total, 0); @@ -748,11 +739,28 @@ VALUE io_write_ensure(VALUE _arguments) { }; VALUE IO_Event_Selector_EPoll_io_write(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _length, VALUE _offset) { - int descriptor = IO_Event_Selector_io_descriptor(io); - size_t length = NUM2SIZET(_length); size_t offset = NUM2SIZET(_offset); + const void *base; + size_t size; + rb_io_buffer_get_bytes_for_reading(buffer, &base, &size); + + if (length > size) { + rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!"); + } + + if (offset > size) { + return rb_fiber_scheduler_io_result(-1, EINVAL); + } else if (offset == size) { + return rb_fiber_scheduler_io_result(0, 0); + } + + base = (const char*)base + offset; + size -= offset; + + int descriptor = IO_Event_Selector_io_descriptor(io); + struct io_write_arguments io_write_arguments = { .self = self, .fiber = fiber, @@ -760,9 +768,9 @@ VALUE IO_Event_Selector_EPoll_io_write(VALUE self, VALUE fiber, VALUE io, VALUE .flags = IO_Event_Selector_nonblock_set(descriptor), .descriptor = descriptor, - .buffer = buffer, + .base = base, + .size = size, .length = length, - .offset = offset, }; RB_OBJ_WRITTEN(self, Qundef, fiber); diff --git a/ext/io/event/selector/kqueue.c b/ext/io/event/selector/kqueue.c index cca0ec5c..eae21290 100644 --- a/ext/io/event/selector/kqueue.c +++ b/ext/io/event/selector/kqueue.c @@ -578,40 +578,33 @@ struct io_read_arguments { int descriptor; - VALUE buffer; + // The remaining writable buffer region after applying the requested offset. + void *base; + size_t size; + + // The minimum number of bytes requested by the caller. size_t length; - size_t offset; }; static VALUE io_read_loop(VALUE _arguments) { struct io_read_arguments *arguments = (struct io_read_arguments *)_arguments; - void *base; - size_t size; - rb_io_buffer_get_bytes_for_writing(arguments->buffer, &base, &size); - size_t length = arguments->length; - size_t offset = arguments->offset; size_t total = 0; if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu)\n", arguments->descriptor, length); - // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset. - if (offset > size) { - return rb_fiber_scheduler_io_result(-1, EINVAL); - } - - size_t maximum_size = size - offset; + size_t maximum_size = arguments->size; while (maximum_size) { - if (DEBUG_IO_READ) fprintf(stderr, "read(%d, +%ld, %ld)\n", arguments->descriptor, offset, maximum_size); - ssize_t result = read(arguments->descriptor, (char*)base+offset, maximum_size); - if (DEBUG_IO_READ) fprintf(stderr, "read(%d, +%ld, %ld) -> %zd\n", arguments->descriptor, offset, maximum_size, result); + if (DEBUG_IO_READ) fprintf(stderr, "read(%d, +%ld, %ld)\n", arguments->descriptor, total, maximum_size); + ssize_t result = read(arguments->descriptor, (char*)arguments->base+total, maximum_size); + if (DEBUG_IO_READ) fprintf(stderr, "read(%d, +%ld, %ld) -> %zd\n", arguments->descriptor, total, maximum_size, result); if (result > 0) { total += result; - offset += result; if ((size_t)result >= length) break; + maximum_size -= result; length -= result; } else if (result == 0) { break; @@ -622,11 +615,9 @@ VALUE io_read_loop(VALUE _arguments) { if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu) -> errno=%d\n", arguments->descriptor, length, errno); return rb_fiber_scheduler_io_result(-1, errno); } - - maximum_size = size - offset; } - if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, offset); + if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, total); return rb_fiber_scheduler_io_result(total, 0); } @@ -643,11 +634,24 @@ VALUE IO_Event_Selector_KQueue_io_read(VALUE self, VALUE fiber, VALUE io, VALUE struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); - int descriptor = IO_Event_Selector_io_descriptor(io); - size_t length = NUM2SIZET(_length); size_t offset = NUM2SIZET(_offset); + void *base; + size_t size; + rb_io_buffer_get_bytes_for_writing(buffer, &base, &size); + + if (offset > size) { + return rb_fiber_scheduler_io_result(-1, EINVAL); + } else if (offset == size) { + return rb_fiber_scheduler_io_result(0, 0); + } + + base = (char*)base + offset; + size -= offset; + + int descriptor = IO_Event_Selector_io_descriptor(io); + struct io_read_arguments io_read_arguments = { .self = self, .fiber = fiber, @@ -655,9 +659,9 @@ VALUE IO_Event_Selector_KQueue_io_read(VALUE self, VALUE fiber, VALUE io, VALUE .flags = IO_Event_Selector_nonblock_set(descriptor), .descriptor = descriptor, - .buffer = buffer, + .base = base, + .size = size, .length = length, - .offset = offset, }; RB_OBJ_WRITTEN(self, Qundef, fiber); @@ -687,44 +691,33 @@ struct io_write_arguments { int descriptor; - VALUE buffer; + // The remaining readable buffer region after applying the requested offset. + const void *base; + size_t size; + + // The minimum number of bytes requested by the caller. size_t length; - size_t offset; }; static VALUE io_write_loop(VALUE _arguments) { struct io_write_arguments *arguments = (struct io_write_arguments *)_arguments; - const void *base; - size_t size; - rb_io_buffer_get_bytes_for_reading(arguments->buffer, &base, &size); - size_t length = arguments->length; - size_t offset = arguments->offset; size_t total = 0; - if (length > size) { - rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!"); - } - if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu)\n", arguments->descriptor, length); - // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset. - if (offset > size) { - return rb_fiber_scheduler_io_result(-1, EINVAL); - } - - size_t maximum_size = size - offset; + size_t maximum_size = arguments->size; while (maximum_size) { - if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld, length=%zu)\n", arguments->descriptor, offset, maximum_size, length); - ssize_t result = write(arguments->descriptor, (char*)base+offset, maximum_size); - if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld) -> %zd\n", arguments->descriptor, offset, maximum_size, result); + if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld, length=%zu)\n", arguments->descriptor, total, maximum_size, length); + ssize_t result = write(arguments->descriptor, (char*)arguments->base+total, maximum_size); + if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld) -> %zd\n", arguments->descriptor, total, maximum_size, result); if (result > 0) { total += result; - offset += result; if ((size_t)result >= length) break; + maximum_size -= result; length -= result; } else if (result == 0) { break; @@ -735,11 +728,9 @@ VALUE io_write_loop(VALUE _arguments) { if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu) -> errno=%d\n", arguments->descriptor, length, errno); return rb_fiber_scheduler_io_result(-1, errno); } - - maximum_size = size - offset; } - if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, offset); + if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, total); return rb_fiber_scheduler_io_result(total, 0); }; @@ -756,11 +747,28 @@ VALUE IO_Event_Selector_KQueue_io_write(VALUE self, VALUE fiber, VALUE io, VALUE struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); - int descriptor = IO_Event_Selector_io_descriptor(io); - size_t length = NUM2SIZET(_length); size_t offset = NUM2SIZET(_offset); + const void *base; + size_t size; + rb_io_buffer_get_bytes_for_reading(buffer, &base, &size); + + if (length > size) { + rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!"); + } + + if (offset > size) { + return rb_fiber_scheduler_io_result(-1, EINVAL); + } else if (offset == size) { + return rb_fiber_scheduler_io_result(0, 0); + } + + base = (const char*)base + offset; + size -= offset; + + int descriptor = IO_Event_Selector_io_descriptor(io); + struct io_write_arguments io_write_arguments = { .self = self, .fiber = fiber, @@ -768,9 +776,9 @@ VALUE IO_Event_Selector_KQueue_io_write(VALUE self, VALUE fiber, VALUE io, VALUE .flags = IO_Event_Selector_nonblock_set(descriptor), .descriptor = descriptor, - .buffer = buffer, + .base = base, + .size = size, .length = length, - .offset = offset, }; RB_OBJ_WRITTEN(self, Qundef, fiber); diff --git a/ext/io/event/selector/uring.c b/ext/io/event/selector/uring.c index dca77f5b..28128776 100644 --- a/ext/io/event/selector/uring.c +++ b/ext/io/event/selector/uring.c @@ -757,8 +757,6 @@ VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b struct IO_Event_Selector_URing *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector); - int descriptor = IO_Event_Selector_io_descriptor(io); - void *base; size_t size; rb_io_buffer_get_bytes_for_writing(buffer, &base, &size); @@ -766,13 +764,17 @@ VALUE IO_Event_Selector_URing_io_read(VALUE self, VALUE fiber, VALUE io, VALUE b size_t length = NUM2SIZET(_length); size_t offset = NUM2SIZET(_offset); size_t total = 0; - off_t from = io_seekable(descriptor); // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset. if (offset > size) { return rb_fiber_scheduler_io_result(-1, EINVAL); + } else if (offset == size) { + return rb_fiber_scheduler_io_result(0, 0); } + int descriptor = IO_Event_Selector_io_descriptor(io); + off_t from = io_seekable(descriptor); + size_t maximum_size = size - offset; // Are we performing a non-blocking read? @@ -827,8 +829,6 @@ VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE struct IO_Event_Selector_URing *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector); - int descriptor = IO_Event_Selector_io_descriptor(io); - void *base; size_t size; rb_io_buffer_get_bytes_for_writing(buffer, &base, &size); @@ -841,8 +841,12 @@ VALUE IO_Event_Selector_URing_io_pread(VALUE self, VALUE fiber, VALUE io, VALUE // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset. if (offset > size) { return rb_fiber_scheduler_io_result(-1, EINVAL); + } else if (offset == size) { + return rb_fiber_scheduler_io_result(0, 0); } + int descriptor = IO_Event_Selector_io_descriptor(io); + size_t maximum_size = size - offset; while (maximum_size) { int result = io_read(selector, fiber, descriptor, (char*)base+offset, maximum_size, from); @@ -945,8 +949,6 @@ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE struct IO_Event_Selector_URing *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector); - int descriptor = IO_Event_Selector_io_descriptor(io); - const void *base; size_t size; rb_io_buffer_get_bytes_for_reading(buffer, &base, &size); @@ -954,7 +956,6 @@ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE size_t length = NUM2SIZET(_length); size_t offset = NUM2SIZET(_offset); size_t total = 0; - off_t from = io_seekable(descriptor); if (length > size) { rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!"); @@ -963,8 +964,13 @@ VALUE IO_Event_Selector_URing_io_write(VALUE self, VALUE fiber, VALUE io, VALUE // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset. if (offset > size) { return rb_fiber_scheduler_io_result(-1, EINVAL); + } else if (offset == size) { + return rb_fiber_scheduler_io_result(0, 0); } - + + int descriptor = IO_Event_Selector_io_descriptor(io); + off_t from = io_seekable(descriptor); + size_t maximum_size = size - offset; while (maximum_size) { int result = io_write(selector, fiber, descriptor, (char*)base+offset, maximum_size, from); @@ -1005,8 +1011,6 @@ VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE struct IO_Event_Selector_URing *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_URing, &IO_Event_Selector_URing_Type, selector); - int descriptor = IO_Event_Selector_io_descriptor(io); - const void *base; size_t size; rb_io_buffer_get_bytes_for_reading(buffer, &base, &size); @@ -1023,8 +1027,12 @@ VALUE IO_Event_Selector_URing_io_pwrite(VALUE self, VALUE fiber, VALUE io, VALUE // Ensure offset is within the bounds of the buffer to avoid size_t underflow and out-of-bounds pointer arithmetic on (char *)base + offset. if (offset > size) { return rb_fiber_scheduler_io_result(-1, EINVAL); + } else if (offset == size) { + return rb_fiber_scheduler_io_result(0, 0); } - + + int descriptor = IO_Event_Selector_io_descriptor(io); + size_t maximum_size = size - offset; while (maximum_size) { int result = io_write(selector, fiber, descriptor, (char*)base+offset, maximum_size, from); diff --git a/lib/io/event/selector/select.rb b/lib/io/event/selector/select.rb index 5968f35a..e9a14213 100644 --- a/lib/io/event/selector/select.rb +++ b/lib/io/event/selector/select.rb @@ -199,6 +199,8 @@ def io_read(fiber, io, buffer, length, offset = 0) # Ensure offset is within the bounds of the buffer to avoid ArgumentError if offset > buffer.size return -Errno::EINVAL::Errno + elsif offset == buffer.size + return 0 end total = 0 @@ -234,6 +236,8 @@ def io_write(fiber, io, buffer, length, offset = 0) # Ensure offset is within the bounds of the buffer to avoid ArgumentError if offset > buffer.size return -Errno::EINVAL::Errno + elsif offset == buffer.size + return 0 end total = 0 diff --git a/test/io/event/selector/buffered_io.rb b/test/io/event/selector/buffered_io.rb index e948c66b..fa5d7c64 100644 --- a/test/io/event/selector/buffered_io.rb +++ b/test/io/event/selector/buffered_io.rb @@ -133,6 +133,19 @@ def be_again? selector.select(0) end + it "returns zero when read offset equals buffer size" do + input.close + buffer = IO::Buffer.new(64) + + reader = Fiber.new do + result = selector.io_read(Fiber.current, input, buffer, 1, 64) + expect(result).to be == 0 + end + + reader.transfer + selector.select(0) + end + it "returns EINVAL when write offset exceeds buffer size" do skip_if_ruby_platform(/mswin|mingw|cygwin/) @@ -147,6 +160,19 @@ def be_again? writer.transfer selector.select(0) end + + it "returns zero when write offset equals buffer size" do + output.close + buffer = IO::Buffer.new(64) + + writer = Fiber.new do + result = selector.io_write(Fiber.current, output, buffer, 1, 64) + expect(result).to be == 0 + end + + writer.transfer + selector.select(0) + end end end