diff options
Diffstat (limited to 'io_buffer.c')
-rw-r--r-- | io_buffer.c | 2027 |
1 files changed, 1220 insertions, 807 deletions
diff --git a/io_buffer.c b/io_buffer.c index c5f0b193c3..e6ca61bdae 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -34,6 +34,18 @@ size_t RUBY_IO_BUFFER_DEFAULT_SIZE; #include <sys/mman.h> #endif +enum { + RB_IO_BUFFER_HEXDUMP_DEFAULT_WIDTH = 16, + + RB_IO_BUFFER_INSPECT_HEXDUMP_MAXIMUM_SIZE = 256, + RB_IO_BUFFER_INSPECT_HEXDUMP_WIDTH = 16, + + // This is used to validate the flags given by the user. + RB_IO_BUFFER_FLAGS_MASK = RB_IO_BUFFER_EXTERNAL | RB_IO_BUFFER_INTERNAL | RB_IO_BUFFER_MAPPED | RB_IO_BUFFER_SHARED | RB_IO_BUFFER_LOCKED | RB_IO_BUFFER_PRIVATE | RB_IO_BUFFER_READONLY, + + RB_IO_BUFFER_DEBUG = 0, +}; + struct rb_io_buffer { void *base; size_t size; @@ -59,7 +71,8 @@ io_buffer_map_memory(size_t size, int flags) int mmap_flags = MAP_ANONYMOUS; if (flags & RB_IO_BUFFER_SHARED) { mmap_flags |= MAP_SHARED; - } else { + } + else { mmap_flags |= MAP_PRIVATE; } @@ -74,7 +87,7 @@ io_buffer_map_memory(size_t size, int flags) } static void -io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, rb_off_t offset, enum rb_io_buffer_flags flags) +io_buffer_map_file(struct rb_io_buffer *buffer, int descriptor, size_t size, rb_off_t offset, enum rb_io_buffer_flags flags) { #if defined(_WIN32) HANDLE file = (HANDLE)_get_osfhandle(descriptor); @@ -83,26 +96,28 @@ io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, rb_of DWORD protect = PAGE_READONLY, access = FILE_MAP_READ; if (flags & RB_IO_BUFFER_READONLY) { - data->flags |= RB_IO_BUFFER_READONLY; + buffer->flags |= RB_IO_BUFFER_READONLY; } else { protect = PAGE_READWRITE; access = FILE_MAP_WRITE; } - HANDLE mapping = CreateFileMapping(file, NULL, protect, 0, 0, NULL); - if (!mapping) rb_sys_fail("io_buffer_map_descriptor:CreateFileMapping"); - if (flags & RB_IO_BUFFER_PRIVATE) { - access |= FILE_MAP_COPY; - data->flags |= RB_IO_BUFFER_PRIVATE; + protect = PAGE_WRITECOPY; + access = FILE_MAP_COPY; + buffer->flags |= RB_IO_BUFFER_PRIVATE; } else { - // This buffer refers to external data. - data->flags |= RB_IO_BUFFER_EXTERNAL; - data->flags |= RB_IO_BUFFER_SHARED; + // This buffer refers to external buffer. + buffer->flags |= RB_IO_BUFFER_EXTERNAL; + buffer->flags |= RB_IO_BUFFER_SHARED; } + HANDLE mapping = CreateFileMapping(file, NULL, protect, 0, 0, NULL); + if (RB_IO_BUFFER_DEBUG) fprintf(stderr, "io_buffer_map_file:CreateFileMapping -> %p\n", mapping); + if (!mapping) rb_sys_fail("io_buffer_map_descriptor:CreateFileMapping"); + void *base = MapViewOfFile(mapping, access, (DWORD)(offset >> 32), (DWORD)(offset & 0xFFFFFFFF), size); if (!base) { @@ -110,24 +125,25 @@ io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, rb_of rb_sys_fail("io_buffer_map_file:MapViewOfFile"); } - data->mapping = mapping; + buffer->mapping = mapping; #else int protect = PROT_READ, access = 0; if (flags & RB_IO_BUFFER_READONLY) { - data->flags |= RB_IO_BUFFER_READONLY; + buffer->flags |= RB_IO_BUFFER_READONLY; } else { protect |= PROT_WRITE; } if (flags & RB_IO_BUFFER_PRIVATE) { - data->flags |= RB_IO_BUFFER_PRIVATE; + buffer->flags |= RB_IO_BUFFER_PRIVATE; + access |= MAP_PRIVATE; } else { - // This buffer refers to external data. - data->flags |= RB_IO_BUFFER_EXTERNAL; - data->flags |= RB_IO_BUFFER_SHARED; + // This buffer refers to external buffer. + buffer->flags |= RB_IO_BUFFER_EXTERNAL; + buffer->flags |= RB_IO_BUFFER_SHARED; access |= MAP_SHARED; } @@ -138,20 +154,11 @@ io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, rb_of } #endif - data->base = base; - data->size = size; + buffer->base = base; + buffer->size = size; - data->flags |= RB_IO_BUFFER_MAPPED; -} - -static inline void -io_buffer_unmap(void* base, size_t size) -{ -#ifdef _WIN32 - VirtualFree(base, 0, MEM_RELEASE); -#else - munmap(base, size); -#endif + buffer->flags |= RB_IO_BUFFER_MAPPED; + buffer->flags |= RB_IO_BUFFER_FILE; } static void @@ -171,18 +178,18 @@ io_buffer_experimental(void) } static void -io_buffer_zero(struct rb_io_buffer *data) +io_buffer_zero(struct rb_io_buffer *buffer) { - data->base = NULL; - data->size = 0; + buffer->base = NULL; + buffer->size = 0; #if defined(_WIN32) - data->mapping = NULL; + buffer->mapping = NULL; #endif - data->source = Qnil; + buffer->source = Qnil; } static void -io_buffer_initialize(struct rb_io_buffer *data, void *base, size_t size, enum rb_io_buffer_flags flags, VALUE source) +io_buffer_initialize(VALUE self, struct rb_io_buffer *buffer, void *base, size_t size, enum rb_io_buffer_flags flags, VALUE source) { if (base) { // If we are provided a pointer, we use it. @@ -205,73 +212,84 @@ io_buffer_initialize(struct rb_io_buffer *data, void *base, size_t size, enum rb return; } - data->base = base; - data->size = size; - data->flags = flags; - data->source = source; + buffer->base = base; + buffer->size = size; + buffer->flags = flags; + RB_OBJ_WRITE(self, &buffer->source, source); + +#if defined(_WIN32) + buffer->mapping = NULL; +#endif } -static int -io_buffer_free(struct rb_io_buffer *data) +static void +io_buffer_free(struct rb_io_buffer *buffer) { - if (data->base) { - if (data->flags & RB_IO_BUFFER_INTERNAL) { - free(data->base); + if (buffer->base) { + if (buffer->flags & RB_IO_BUFFER_INTERNAL) { + free(buffer->base); } - if (data->flags & RB_IO_BUFFER_MAPPED) { - io_buffer_unmap(data->base, data->size); + if (buffer->flags & RB_IO_BUFFER_MAPPED) { +#ifdef _WIN32 + if (buffer->flags & RB_IO_BUFFER_FILE) { + UnmapViewOfFile(buffer->base); + } + else { + VirtualFree(buffer->base, 0, MEM_RELEASE); + } +#else + munmap(buffer->base, buffer->size); +#endif } // Previously we had this, but we found out due to the way GC works, we // can't refer to any other Ruby objects here. - // if (RB_TYPE_P(data->source, T_STRING)) { - // rb_str_unlocktmp(data->source); + // if (RB_TYPE_P(buffer->source, T_STRING)) { + // rb_str_unlocktmp(buffer->source); // } - data->base = NULL; + buffer->base = NULL; + + buffer->size = 0; + buffer->flags = 0; + buffer->source = Qnil; + } #if defined(_WIN32) - if (data->mapping) { - CloseHandle(data->mapping); - data->mapping = NULL; + if (buffer->mapping) { + if (RB_IO_BUFFER_DEBUG) fprintf(stderr, "io_buffer_free:CloseHandle -> %p\n", buffer->mapping); + if (!CloseHandle(buffer->mapping)) { + fprintf(stderr, "io_buffer_free:GetLastError -> %lu\n", GetLastError()); } -#endif - data->size = 0; - data->flags = 0; - data->source = Qnil; - - return 1; + buffer->mapping = NULL; } - - return 0; +#endif } void -rb_io_buffer_type_mark(void *_data) +rb_io_buffer_type_mark(void *_buffer) { - struct rb_io_buffer *data = _data; - rb_gc_mark(data->source); + struct rb_io_buffer *buffer = _buffer; + rb_gc_mark(buffer->source); } void -rb_io_buffer_type_free(void *_data) +rb_io_buffer_type_free(void *_buffer) { - struct rb_io_buffer *data = _data; - - io_buffer_free(data); + struct rb_io_buffer *buffer = _buffer; - free(data); + io_buffer_free(buffer); } size_t -rb_io_buffer_type_size(const void *_data) +rb_io_buffer_type_size(const void *_buffer) { - const struct rb_io_buffer *data = _data; + const struct rb_io_buffer *buffer = _buffer; size_t total = sizeof(struct rb_io_buffer); - if (data->flags) { - total += data->size; + if (buffer->flags) { + total += buffer->size; } return total; @@ -285,34 +303,178 @@ static const rb_data_type_t rb_io_buffer_type = { .dsize = rb_io_buffer_type_size, }, .data = NULL, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, }; +static inline enum rb_io_buffer_flags +io_buffer_extract_flags(VALUE argument) +{ + if (rb_int_negative_p(argument)) { + rb_raise(rb_eArgError, "Flags can't be negative!"); + } + + enum rb_io_buffer_flags flags = RB_NUM2UINT(argument); + + // We deliberately ignore unknown flags. Any future flags which are exposed this way should be safe to ignore. + return flags & RB_IO_BUFFER_FLAGS_MASK; +} + +// Extract an offset argument, which must be a non-negative integer. +static inline size_t +io_buffer_extract_offset(VALUE argument) +{ + if (rb_int_negative_p(argument)) { + rb_raise(rb_eArgError, "Offset can't be negative!"); + } + + return NUM2SIZET(argument); +} + +// Extract a length argument, which must be a non-negative integer. +// Length is generally considered a mutable property of an object and +// semantically should be considered a subset of "size" as a concept. +static inline size_t +io_buffer_extract_length(VALUE argument) +{ + if (rb_int_negative_p(argument)) { + rb_raise(rb_eArgError, "Length can't be negative!"); + } + + return NUM2SIZET(argument); +} + +// Extract a size argument, which must be a non-negative integer. +// Size is generally considered an immutable property of an object. +static inline size_t +io_buffer_extract_size(VALUE argument) +{ + if (rb_int_negative_p(argument)) { + rb_raise(rb_eArgError, "Size can't be negative!"); + } + + return NUM2SIZET(argument); +} + +// Extract a width argument, which must be a non-negative integer, and must be +// at least the given minimum. +static inline size_t +io_buffer_extract_width(VALUE argument, size_t minimum) +{ + if (rb_int_negative_p(argument)) { + rb_raise(rb_eArgError, "Width can't be negative!"); + } + + size_t width = NUM2SIZET(argument); + + if (width < minimum) { + rb_raise(rb_eArgError, "Width must be at least %" PRIuSIZE "!", minimum); + } + + return width; +} + +// Compute the default length for a buffer, given an offset into that buffer. +// The default length is the size of the buffer minus the offset. The offset +// must be less than the size of the buffer otherwise the length will be +// invalid; in that case, an ArgumentError exception will be raised. +static inline size_t +io_buffer_default_length(const struct rb_io_buffer *buffer, size_t offset) +{ + if (offset > buffer->size) { + rb_raise(rb_eArgError, "The given offset is bigger than the buffer size!"); + } + + // Note that the "length" is computed by the size the offset. + return buffer->size - offset; +} + +// Extract the optional length and offset arguments, returning the buffer. +// The length and offset are optional, but if they are provided, they must be +// positive integers. If the length is not provided, the default length is +// computed from the buffer size and offset. If the offset is not provided, it +// defaults to zero. +static inline struct rb_io_buffer * +io_buffer_extract_length_offset(VALUE self, int argc, VALUE argv[], size_t *length, size_t *offset) +{ + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + if (argc >= 2 && !NIL_P(argv[1])) { + *offset = io_buffer_extract_offset(argv[1]); + } + else { + *offset = 0; + } + + if (argc >= 1 && !NIL_P(argv[0])) { + *length = io_buffer_extract_length(argv[0]); + } + else { + *length = io_buffer_default_length(buffer, *offset); + } + + return buffer; +} + +// Extract the optional offset and length arguments, returning the buffer. +// Similar to `io_buffer_extract_length_offset` but with the order of arguments +// reversed. +// +// After much consideration, I decided to accept both forms. +// The `(offset, length)` order is more natural when referring about data, +// while the `(length, offset)` order is more natural when referring to +// read/write operations. In many cases, with the latter form, `offset` +// is usually not supplied. +static inline struct rb_io_buffer * +io_buffer_extract_offset_length(VALUE self, int argc, VALUE argv[], size_t *offset, size_t *length) +{ + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + if (argc >= 1 && !NIL_P(argv[0])) { + *offset = io_buffer_extract_offset(argv[0]); + } + else { + *offset = 0; + } + + if (argc >= 2 && !NIL_P(argv[1])) { + *length = io_buffer_extract_length(argv[1]); + } + else { + *length = io_buffer_default_length(buffer, *offset); + } + + return buffer; +} + VALUE rb_io_buffer_type_allocate(VALUE self) { - struct rb_io_buffer *data = NULL; - VALUE instance = TypedData_Make_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + VALUE instance = TypedData_Make_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_zero(data); + io_buffer_zero(buffer); return instance; } -static VALUE -io_buffer_for_make_instance(VALUE klass, VALUE string) +static VALUE io_buffer_for_make_instance(VALUE klass, VALUE string, enum rb_io_buffer_flags flags) { VALUE instance = rb_io_buffer_type_allocate(klass); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, buffer); - enum rb_io_buffer_flags flags = RB_IO_BUFFER_EXTERNAL; + flags |= RB_IO_BUFFER_EXTERNAL; if (RB_OBJ_FROZEN(string)) flags |= RB_IO_BUFFER_READONLY; - io_buffer_initialize(data, RSTRING_PTR(string), RSTRING_LEN(string), flags, string); + if (!(flags & RB_IO_BUFFER_READONLY)) + rb_str_modify(string); + + io_buffer_initialize(instance, buffer, RSTRING_PTR(string), RSTRING_LEN(string), flags, string); return instance; } @@ -321,6 +483,7 @@ struct io_buffer_for_yield_instance_arguments { VALUE klass; VALUE string; VALUE instance; + enum rb_io_buffer_flags flags; }; static VALUE @@ -328,9 +491,9 @@ io_buffer_for_yield_instance(VALUE _arguments) { struct io_buffer_for_yield_instance_arguments *arguments = (struct io_buffer_for_yield_instance_arguments *)_arguments; - rb_str_locktmp(arguments->string); + arguments->instance = io_buffer_for_make_instance(arguments->klass, arguments->string, arguments->flags); - arguments->instance = io_buffer_for_make_instance(arguments->klass, arguments->string); + rb_str_locktmp(arguments->string); return rb_yield(arguments->instance); } @@ -354,17 +517,18 @@ io_buffer_for_yield_instance_ensure(VALUE _arguments) * IO::Buffer.for(string) -> readonly io_buffer * IO::Buffer.for(string) {|io_buffer| ... read/write io_buffer ...} * - * Creates a IO::Buffer from the given string's memory. Without a block a - * frozen internal copy of the string is created efficiently and used as the - * buffer source. When a block is provided, the buffer is associated directly - * with the string's internal data and updating the buffer will update the - * string. + * Creates a zero-copy IO::Buffer from the given string's memory. Without a + * block a frozen internal copy of the string is created efficiently and used + * as the buffer source. When a block is provided, the buffer is associated + * directly with the string's internal buffer and updating the buffer will + * update the string. * * Until #free is invoked on the buffer, either explicitly or via the garbage * collector, the source string will be locked and cannot be modified. * * If the string is frozen, it will create a read-only buffer which cannot be - * modified. + * modified. If the string is shared, it may trigger a copy-on-write when + * using the block form. * * string = 'test' * buffer = IO::Buffer.for(string) @@ -396,6 +560,7 @@ rb_io_buffer_type_for(VALUE klass, VALUE string) .klass = klass, .string = string, .instance = Qnil, + .flags = 0, }; return rb_ensure(io_buffer_for_yield_instance, (VALUE)&arguments, io_buffer_for_yield_instance_ensure, (VALUE)&arguments); @@ -403,19 +568,48 @@ rb_io_buffer_type_for(VALUE klass, VALUE string) else { // This internally returns the source string if it's already frozen. string = rb_str_tmp_frozen_acquire(string); - return io_buffer_for_make_instance(klass, string); + return io_buffer_for_make_instance(klass, string, RB_IO_BUFFER_READONLY); } } +/* + * call-seq: + * IO::Buffer.string(length) {|io_buffer| ... read/write io_buffer ...} -> string + * + * Creates a new string of the given length and yields a zero-copy IO::Buffer + * instance to the block which uses the string as a source. The block is + * expected to write to the buffer and the string will be returned. + * + * IO::Buffer.string(4) do |buffer| + * buffer.set_string("Ruby") + * end + * # => "Ruby" + */ +VALUE +rb_io_buffer_type_string(VALUE klass, VALUE length) +{ + VALUE string = rb_str_new(NULL, RB_NUM2LONG(length)); + + struct io_buffer_for_yield_instance_arguments arguments = { + .klass = klass, + .string = string, + .instance = Qnil, + }; + + rb_ensure(io_buffer_for_yield_instance, (VALUE)&arguments, io_buffer_for_yield_instance_ensure, (VALUE)&arguments); + + return string; +} + VALUE rb_io_buffer_new(void *base, size_t size, enum rb_io_buffer_flags flags) { VALUE instance = rb_io_buffer_type_allocate(rb_cIOBuffer); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_initialize(data, base, size, flags, Qnil); + io_buffer_initialize(instance, buffer, base, size, flags, Qnil); return instance; } @@ -427,12 +621,12 @@ rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags VALUE instance = rb_io_buffer_type_allocate(rb_cIOBuffer); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, buffer); int descriptor = rb_io_descriptor(io); - io_buffer_map_file(data, descriptor, size, offset, flags); + io_buffer_map_file(buffer, descriptor, size, offset, flags); return instance; } @@ -449,8 +643,6 @@ rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags * mapping, you need to open a file in read-write mode, and explicitly pass * +flags+ argument without IO::Buffer::IMMUTABLE. * - * Example: - * * File.write('test.txt', 'test') * * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) @@ -486,7 +678,7 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass) size_t size; if (argc >= 2 && !RB_NIL_P(argv[1])) { - size = RB_NUM2SIZE(argv[1]); + size = io_buffer_extract_size(argv[1]); } else { rb_off_t file_size = rb_file_size(io); @@ -505,6 +697,7 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass) } } + // This is the file offset, not the buffer offset: rb_off_t offset = 0; if (argc >= 3) { offset = NUM2OFFT(argv[2]); @@ -512,7 +705,7 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass) enum rb_io_buffer_flags flags = 0; if (argc >= 4) { - flags = RB_NUM2UINT(argv[3]); + flags = io_buffer_extract_flags(argv[3]); } return rb_io_buffer_map(io, size, offset, flags); @@ -540,8 +733,6 @@ io_flags_for_size(size_t size) * on Windows). The behavior can be forced by passing IO::Buffer::MAPPED * as a second parameter. * - * Examples - * * buffer = IO::Buffer.new(4) * # => * # #<IO::Buffer 0x000055b34497ea10+4 INTERNAL> @@ -562,13 +753,12 @@ rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self) rb_check_arity(argc, 0, 2); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); size_t size; - if (argc > 0) { - size = RB_NUM2SIZE(argv[0]); + size = io_buffer_extract_size(argv[0]); } else { size = RUBY_IO_BUFFER_DEFAULT_SIZE; @@ -576,13 +766,13 @@ rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self) enum rb_io_buffer_flags flags = 0; if (argc >= 2) { - flags = RB_NUM2UINT(argv[1]); + flags = io_buffer_extract_flags(argv[1]); } else { flags |= io_flags_for_size(size); } - io_buffer_initialize(data, NULL, size, flags, Qnil); + io_buffer_initialize(self, buffer, NULL, size, flags, Qnil); return self; } @@ -617,17 +807,93 @@ io_buffer_validate_slice(VALUE source, void *base, size_t size) } static int -io_buffer_validate(struct rb_io_buffer *data) +io_buffer_validate(struct rb_io_buffer *buffer) { - if (data->source != Qnil) { + if (buffer->source != Qnil) { // Only slices incur this overhead, unfortunately... better safe than sorry! - return io_buffer_validate_slice(data->source, data->base, data->size); + return io_buffer_validate_slice(buffer->source, buffer->base, buffer->size); } else { return 1; } } +enum rb_io_buffer_flags +rb_io_buffer_get_bytes(VALUE self, void **base, size_t *size) +{ + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + if (io_buffer_validate(buffer)) { + if (buffer->base) { + *base = buffer->base; + *size = buffer->size; + + return buffer->flags; + } + } + + *base = NULL; + *size = 0; + + return 0; +} + +// Internal function for accessing bytes for writing, wil +static inline void +io_buffer_get_bytes_for_writing(struct rb_io_buffer *buffer, void **base, size_t *size) +{ + if (buffer->flags & RB_IO_BUFFER_READONLY) { + rb_raise(rb_eIOBufferAccessError, "Buffer is not writable!"); + } + + if (!io_buffer_validate(buffer)) { + rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!"); + } + + if (buffer->base) { + *base = buffer->base; + *size = buffer->size; + } else { + *base = NULL; + *size = 0; + } +} + +void +rb_io_buffer_get_bytes_for_writing(VALUE self, void **base, size_t *size) +{ + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + io_buffer_get_bytes_for_writing(buffer, base, size); +} + +static void +io_buffer_get_bytes_for_reading(struct rb_io_buffer *buffer, const void **base, size_t *size) +{ + if (!io_buffer_validate(buffer)) { + rb_raise(rb_eIOBufferInvalidatedError, "Buffer has been invalidated!"); + } + + if (buffer->base) { + *base = buffer->base; + *size = buffer->size; + } else { + *base = NULL; + *size = 0; + } +} + +void +rb_io_buffer_get_bytes_for_reading(VALUE self, const void **base, size_t *size) +{ + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + io_buffer_get_bytes_for_reading(buffer, base, size); +} + /* * call-seq: to_s -> string * @@ -640,60 +906,93 @@ io_buffer_validate(struct rb_io_buffer *data) VALUE rb_io_buffer_to_s(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE result = rb_str_new_cstr("#<"); rb_str_append(result, rb_class_name(CLASS_OF(self))); - rb_str_catf(result, " %p+%"PRIdSIZE, data->base, data->size); + rb_str_catf(result, " %p+%"PRIdSIZE, buffer->base, buffer->size); - if (data->base == NULL) { + if (buffer->base == NULL) { rb_str_cat2(result, " NULL"); } - if (data->flags & RB_IO_BUFFER_EXTERNAL) { + if (buffer->flags & RB_IO_BUFFER_EXTERNAL) { rb_str_cat2(result, " EXTERNAL"); } - if (data->flags & RB_IO_BUFFER_INTERNAL) { + if (buffer->flags & RB_IO_BUFFER_INTERNAL) { rb_str_cat2(result, " INTERNAL"); } - if (data->flags & RB_IO_BUFFER_MAPPED) { + if (buffer->flags & RB_IO_BUFFER_MAPPED) { rb_str_cat2(result, " MAPPED"); } - if (data->flags & RB_IO_BUFFER_SHARED) { + if (buffer->flags & RB_IO_BUFFER_FILE) { + rb_str_cat2(result, " FILE"); + } + + if (buffer->flags & RB_IO_BUFFER_SHARED) { rb_str_cat2(result, " SHARED"); } - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_str_cat2(result, " LOCKED"); } - if (data->flags & RB_IO_BUFFER_READONLY) { + if (buffer->flags & RB_IO_BUFFER_PRIVATE) { + rb_str_cat2(result, " PRIVATE"); + } + + if (buffer->flags & RB_IO_BUFFER_READONLY) { rb_str_cat2(result, " READONLY"); } - if (data->source != Qnil) { + if (buffer->source != Qnil) { rb_str_cat2(result, " SLICE"); } - if (!io_buffer_validate(data)) { + if (!io_buffer_validate(buffer)) { rb_str_cat2(result, " INVALID"); } return rb_str_cat2(result, ">"); } +// Compute the output size of a hexdump of the given width (bytes per line), total size, and whether it is the first line in the output. +// This is used to preallocate the output string. +inline static size_t +io_buffer_hexdump_output_size(size_t width, size_t size, int first) +{ + // The preview on the right hand side is 1:1: + size_t total = size; + + size_t whole_lines = (size / width); + size_t partial_line = (size % width) ? 1 : 0; + + // For each line: + // 1 byte 10 bytes 1 byte width*3 bytes 1 byte size bytes + // (newline) (address) (space) (hexdump ) (space) (preview) + total += (whole_lines + partial_line) * (1 + 10 + width*3 + 1 + 1); + + // If the hexdump is the first line, one less newline will be emitted: + if (size && first) total -= 1; + + return total; +} + +// Append a hexdump of the given width (bytes per line), base address, size, and whether it is the first line in the output. +// If the hexdump is not the first line, it will prepend a newline if there is any output at all. +// If formatting here is adjusted, please update io_buffer_hexdump_output_size accordingly. static VALUE -io_buffer_hexdump(VALUE string, size_t width, char *base, size_t size, int first) +io_buffer_hexdump(VALUE string, size_t width, const char *base, size_t length, size_t offset, int first) { char *text = alloca(width+1); text[width] = '\0'; - for (size_t offset = 0; offset < size; offset += width) { + for (; offset < length; offset += width) { memset(text, '\0', width); if (first) { rb_str_catf(string, "0x%08" PRIxSIZE " ", offset); @@ -704,7 +1003,7 @@ io_buffer_hexdump(VALUE string, size_t width, char *base, size_t size, int first } for (size_t i = 0; i < width; i += 1) { - if (offset+i < size) { + if (offset+i < length) { unsigned char value = ((unsigned char*)base)[offset+i]; if (value < 127 && isprint(value)) { @@ -727,35 +1026,40 @@ io_buffer_hexdump(VALUE string, size_t width, char *base, size_t size, int first return string; } -static VALUE -rb_io_buffer_hexdump(VALUE self) -{ - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); - - VALUE result = Qnil; - - if (io_buffer_validate(data) && data->base) { - result = rb_str_buf_new(data->size*3 + (data->size/16)*12 + 1); - - io_buffer_hexdump(result, 16, data->base, data->size, 1); - } - - return result; -} - +/* + * call-seq: inspect -> string + * + * Inspect the buffer and report useful information about it's internal state. + * Only a limited portion of the buffer will be displayed in a hexdump style + * format. + * + * buffer = IO::Buffer.for("Hello World") + * puts buffer.inspect + * # #<IO::Buffer 0x000000010198ccd8+11 EXTERNAL READONLY SLICE> + * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World + */ VALUE rb_io_buffer_inspect(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE result = rb_io_buffer_to_s(self); - if (io_buffer_validate(data)) { - // Limit the maximum size genearted by inspect. - if (data->size <= 256) { - io_buffer_hexdump(result, 16, data->base, data->size, 0); + if (io_buffer_validate(buffer)) { + // Limit the maximum size generated by inspect: + size_t size = buffer->size; + int clamped = 0; + + if (size > RB_IO_BUFFER_INSPECT_HEXDUMP_MAXIMUM_SIZE) { + size = RB_IO_BUFFER_INSPECT_HEXDUMP_MAXIMUM_SIZE; + clamped = 1; + } + + io_buffer_hexdump(result, RB_IO_BUFFER_INSPECT_HEXDUMP_WIDTH, buffer->base, size, 0, 0); + + if (clamped) { + rb_str_catf(result, "\n(and %" PRIuSIZE " more bytes not printed)", buffer->size - size); } } @@ -771,42 +1075,50 @@ rb_io_buffer_inspect(VALUE self) VALUE rb_io_buffer_size(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return SIZET2NUM(data->size); + return SIZET2NUM(buffer->size); } /* * call-seq: valid? -> true or false * - * Returns whether the buffer data is accessible. + * Returns whether the buffer buffer is accessible. * - * A buffer becomes invalid if it is a slice of another buffer which has been - * freed. + * A buffer becomes invalid if it is a slice of another buffer (or string) + * which has been freed or re-allocated at a different address. */ static VALUE rb_io_buffer_valid_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(io_buffer_validate(data)); + return RBOOL(io_buffer_validate(buffer)); } /* * call-seq: null? -> true or false * - * If the buffer was freed with #free or was never allocated in the first - * place. + * If the buffer was freed with #free, transferred with #transfer, or was + * never allocated in the first place. + * + * buffer = IO::Buffer.new(0) + * buffer.null? #=> true + * + * buffer = IO::Buffer.new(4) + * buffer.null? #=> false + * buffer.free + * buffer.null? #=> true */ static VALUE rb_io_buffer_null_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->base == NULL); + return RBOOL(buffer->base == NULL); } /* @@ -819,10 +1131,10 @@ rb_io_buffer_null_p(VALUE self) static VALUE rb_io_buffer_empty_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->size == 0); + return RBOOL(buffer->size == 0); } /* @@ -839,10 +1151,10 @@ rb_io_buffer_empty_p(VALUE self) static VALUE rb_io_buffer_external_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->flags & RB_IO_BUFFER_EXTERNAL); + return RBOOL(buffer->flags & RB_IO_BUFFER_EXTERNAL); } /* @@ -864,10 +1176,10 @@ rb_io_buffer_external_p(VALUE self) static VALUE rb_io_buffer_internal_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->flags & RB_IO_BUFFER_INTERNAL); + return RBOOL(buffer->flags & RB_IO_BUFFER_INTERNAL); } /* @@ -886,10 +1198,10 @@ rb_io_buffer_internal_p(VALUE self) static VALUE rb_io_buffer_mapped_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->flags & RB_IO_BUFFER_MAPPED); + return RBOOL(buffer->flags & RB_IO_BUFFER_MAPPED); } /* @@ -898,14 +1210,30 @@ rb_io_buffer_mapped_p(VALUE self) * If the buffer is _shared_, meaning it references memory that can be shared * with other processes (and thus might change without being modified * locally). + * + * # Create a test file: + * File.write('test.txt', 'test') + * + * # Create a shared mapping from the given file, the file must be opened in + * # read-write mode unless we also specify IO::Buffer::READONLY: + * buffer = IO::Buffer.map(File.open('test.txt', 'r+'), nil, 0) + * # => #<IO::Buffer 0x00007f1bffd5e000+4 EXTERNAL MAPPED SHARED> + * + * # Write to the buffer, which will modify the mapped file: + * buffer.set_string('b', 0) + * # => 1 + * + * # The file itself is modified: + * File.read('test.txt') + * # => "best" */ static VALUE rb_io_buffer_shared_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->flags & RB_IO_BUFFER_SHARED); + return RBOOL(buffer->flags & RB_IO_BUFFER_SHARED); } /* @@ -918,8 +1246,6 @@ rb_io_buffer_shared_p(VALUE self) * Locking is not thread safe, but is a semantic used to ensure buffers don't * move while being used by a system call. * - * Example: - * * buffer.locked do * buffer.write(io) # theoretical system call interface * end @@ -927,19 +1253,50 @@ rb_io_buffer_shared_p(VALUE self) static VALUE rb_io_buffer_locked_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->flags & RB_IO_BUFFER_LOCKED); + return RBOOL(buffer->flags & RB_IO_BUFFER_LOCKED); +} + +/* call-seq: private? -> true or false + * + * If the buffer is _private_, meaning modifications to the buffer will not + * be replicated to the underlying file mapping. + * + * # Create a test file: + * File.write('test.txt', 'test') + * + * # Create a private mapping from the given file. Note that the file here + * # is opened in read-only mode, but it doesn't matter due to the private + * # mapping: + * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::PRIVATE) + * # => #<IO::Buffer 0x00007fce63f11000+4 MAPPED PRIVATE> + * + * # Write to the buffer (invoking CoW of the underlying file buffer): + * buffer.set_string('b', 0) + * # => 1 + * + * # The file itself is not modified: + * File.read('test.txt') + * # => "test" + */ +static VALUE +rb_io_buffer_private_p(VALUE self) +{ + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + return RBOOL(buffer->flags & RB_IO_BUFFER_PRIVATE); } int rb_io_buffer_readonly_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return data->flags & RB_IO_BUFFER_READONLY; + return buffer->flags & RB_IO_BUFFER_READONLY; } /* @@ -956,32 +1313,44 @@ io_buffer_readonly_p(VALUE self) return RBOOL(rb_io_buffer_readonly_p(self)); } -VALUE -rb_io_buffer_lock(VALUE self) +static void +io_buffer_lock(struct rb_io_buffer *buffer) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); - - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Buffer already locked!"); } - data->flags |= RB_IO_BUFFER_LOCKED; - - return self; + buffer->flags |= RB_IO_BUFFER_LOCKED; } VALUE -rb_io_buffer_unlock(VALUE self) +rb_io_buffer_lock(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + io_buffer_lock(buffer); + + return self; +} - if (!(data->flags & RB_IO_BUFFER_LOCKED)) { +static void +io_buffer_unlock(struct rb_io_buffer *buffer) +{ + if (!(buffer->flags & RB_IO_BUFFER_LOCKED)) { rb_raise(rb_eIOBufferLockedError, "Buffer not locked!"); } - data->flags &= ~RB_IO_BUFFER_LOCKED; + buffer->flags &= ~RB_IO_BUFFER_LOCKED; +} + +VALUE +rb_io_buffer_unlock(VALUE self) +{ + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + io_buffer_unlock(buffer); return self; } @@ -989,11 +1358,11 @@ rb_io_buffer_unlock(VALUE self) int rb_io_buffer_try_unlock(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - if (data->flags & RB_IO_BUFFER_LOCKED) { - data->flags &= ~RB_IO_BUFFER_LOCKED; + if (buffer->flags & RB_IO_BUFFER_LOCKED) { + buffer->flags &= ~RB_IO_BUFFER_LOCKED; return 1; } @@ -1014,8 +1383,6 @@ rb_io_buffer_try_unlock(VALUE self) * non-blocking system calls. You can only share a buffer between threads with * appropriate synchronisation techniques. * - * Example: - * * buffer = IO::Buffer.new(4) * buffer.locked? #=> false * @@ -1035,18 +1402,18 @@ rb_io_buffer_try_unlock(VALUE self) VALUE rb_io_buffer_locked(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Buffer already locked!"); } - data->flags |= RB_IO_BUFFER_LOCKED; + buffer->flags |= RB_IO_BUFFER_LOCKED; VALUE result = rb_yield(self); - data->flags &= ~RB_IO_BUFFER_LOCKED; + buffer->flags &= ~RB_IO_BUFFER_LOCKED; return result; } @@ -1063,8 +1430,6 @@ rb_io_buffer_locked(VALUE self) * * You can resize a freed buffer to re-allocate it. * - * Example: - * * buffer = IO::Buffer.for('test') * buffer.free * # => #<IO::Buffer 0x0000000000000000+0 NULL> @@ -1081,14 +1446,25 @@ rb_io_buffer_locked(VALUE self) VALUE rb_io_buffer_free(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Buffer is locked!"); } - io_buffer_free(data); + io_buffer_free(buffer); + + return self; +} + +VALUE rb_io_buffer_free_locked(VALUE self) +{ + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + io_buffer_unlock(buffer); + io_buffer_free(buffer); return self; } @@ -1096,36 +1472,82 @@ rb_io_buffer_free(VALUE self) // Validate that access to the buffer is within bounds, assuming you want to // access length bytes from the specified offset. static inline void -io_buffer_validate_range(struct rb_io_buffer *data, size_t offset, size_t length) +io_buffer_validate_range(struct rb_io_buffer *buffer, size_t offset, size_t length) { - if (offset + length > data->size) { - rb_raise(rb_eArgError, "Specified offset+length exceeds data size!"); + // We assume here that offset + length won't overflow: + if (offset + length > buffer->size) { + rb_raise(rb_eArgError, "Specified offset+length is bigger than the buffer size!"); } } +/* + * call-seq: hexdump([offset, [length, [width]]]) -> string + * + * Returns a human-readable string representation of the buffer. The exact + * format is subject to change. + * + * buffer = IO::Buffer.for("Hello World") + * puts buffer.hexdump + * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World + * + * As buffers are usually fairly big, you may want to limit the output by + * specifying the offset and length: + * + * puts buffer.hexdump(6, 5) + * # 0x00000006 57 6f 72 6c 64 World + */ static VALUE -rb_io_buffer_slice(struct rb_io_buffer *data, VALUE self, size_t offset, size_t length) +rb_io_buffer_hexdump(int argc, VALUE *argv, VALUE self) { - io_buffer_validate_range(data, offset, length); + rb_check_arity(argc, 0, 3); + + size_t offset, length; + struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length); + + size_t width = RB_IO_BUFFER_HEXDUMP_DEFAULT_WIDTH; + if (argc >= 3) { + width = io_buffer_extract_width(argv[2], 1); + } + + // This may raise an exception if the offset/length is invalid: + io_buffer_validate_range(buffer, offset, length); + + VALUE result = Qnil; + + if (io_buffer_validate(buffer) && buffer->base) { + result = rb_str_buf_new(io_buffer_hexdump_output_size(width, length, 1)); + + io_buffer_hexdump(result, width, buffer->base, offset+length, offset, 1); + } + + return result; +} + +static VALUE +rb_io_buffer_slice(struct rb_io_buffer *buffer, VALUE self, size_t offset, size_t length) +{ + io_buffer_validate_range(buffer, offset, length); VALUE instance = rb_io_buffer_type_allocate(rb_class_of(self)); struct rb_io_buffer *slice = NULL; TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, slice); - slice->base = (char*)data->base + offset; + slice->base = (char*)buffer->base + offset; slice->size = length; // The source should be the root buffer: - if (data->source != Qnil) - slice->source = data->source; - else - slice->source = self; + if (buffer->source != Qnil) { + RB_OBJ_WRITE(instance, &slice->source, buffer->source); + } + else { + RB_OBJ_WRITE(instance, &slice->source, self); + } return instance; } /* - * call-seq: slice([offset = 0, [length]]) -> io_buffer + * call-seq: slice([offset, [length]]) -> io_buffer * * Produce another IO::Buffer which is a slice (or view into) the current one * starting at +offset+ bytes and going for +length+ bytes. @@ -1143,8 +1565,6 @@ rb_io_buffer_slice(struct rb_io_buffer *data, VALUE self, size_t offset, size_t * Raises RuntimeError if the <tt>offset+length</tt> is out of the current * buffer's bounds. * - * Example: - * * string = 'test' * buffer = IO::Buffer.for(string) * @@ -1185,115 +1605,17 @@ io_buffer_slice(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 2); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); - - size_t offset = 0, length = 0; - - if (argc > 0) { - if (rb_int_negative_p(argv[0])) { - rb_raise(rb_eArgError, "Offset can't be negative!"); - } - - offset = NUM2SIZET(argv[0]); - } - - if (argc > 1) { - if (rb_int_negative_p(argv[1])) { - rb_raise(rb_eArgError, "Length can't be negative!"); - } - - length = NUM2SIZET(argv[1]); - } else { - length = data->size - offset; - } - - return rb_io_buffer_slice(data, self, offset, length); -} - -int -rb_io_buffer_get_bytes(VALUE self, void **base, size_t *size) -{ - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); - - if (io_buffer_validate(data)) { - if (data->base) { - *base = data->base; - *size = data->size; - - return data->flags; - } - } - - *base = NULL; - *size = 0; - - return 0; -} - -static inline void -io_buffer_get_bytes_for_writing(struct rb_io_buffer *data, void **base, size_t *size) -{ - if (data->flags & RB_IO_BUFFER_READONLY) { - rb_raise(rb_eIOBufferAccessError, "Buffer is not writable!"); - } - - if (!io_buffer_validate(data)) { - rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!"); - } - - if (data->base) { - *base = data->base; - *size = data->size; - - return; - } - - rb_raise(rb_eIOBufferAllocationError, "The buffer is not allocated!"); -} - -void -rb_io_buffer_get_bytes_for_writing(VALUE self, void **base, size_t *size) -{ - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); - - io_buffer_get_bytes_for_writing(data, base, size); -} - -static void -io_buffer_get_bytes_for_reading(struct rb_io_buffer *data, const void **base, size_t *size) -{ - if (!io_buffer_validate(data)) { - rb_raise(rb_eIOBufferInvalidatedError, "Buffer has been invalidated!"); - } - - if (data->base) { - *base = data->base; - *size = data->size; + size_t offset, length; + struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length); - return; - } - - rb_raise(rb_eIOBufferAllocationError, "The buffer is not allocated!"); -} - -void -rb_io_buffer_get_bytes_for_reading(VALUE self, const void **base, size_t *size) -{ - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); - - io_buffer_get_bytes_for_reading(data, base, size); + return rb_io_buffer_slice(buffer, self, offset, length); } /* * call-seq: transfer -> new_io_buffer * - * Transfers ownership to a new buffer, deallocating the current one. - * - * Example: + * Transfers ownership of the underlying memory to a new buffer, causing the + * current buffer to become uninitialized. * * buffer = IO::Buffer.new('test') * other = buffer.transfer @@ -1310,10 +1632,10 @@ rb_io_buffer_get_bytes_for_reading(VALUE self, const void **base, size_t *size) VALUE rb_io_buffer_transfer(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Cannot transfer ownership of locked buffer!"); } @@ -1321,91 +1643,96 @@ rb_io_buffer_transfer(VALUE self) struct rb_io_buffer *transferred; TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, transferred); - *transferred = *data; - io_buffer_zero(data); + *transferred = *buffer; + io_buffer_zero(buffer); return instance; } static void -io_buffer_resize_clear(struct rb_io_buffer *data, void* base, size_t size) +io_buffer_resize_clear(struct rb_io_buffer *buffer, void* base, size_t size) { - if (size > data->size) { - memset((unsigned char*)base+data->size, 0, size - data->size); + if (size > buffer->size) { + memset((unsigned char*)base+buffer->size, 0, size - buffer->size); } } static void -io_buffer_resize_copy(struct rb_io_buffer *data, size_t size) +io_buffer_resize_copy(VALUE self, struct rb_io_buffer *buffer, size_t size) { // Slow path: struct rb_io_buffer resized; - io_buffer_initialize(&resized, NULL, size, io_flags_for_size(size), Qnil); + io_buffer_initialize(self, &resized, NULL, size, io_flags_for_size(size), Qnil); - if (data->base) { - size_t preserve = data->size; + if (buffer->base) { + size_t preserve = buffer->size; if (preserve > size) preserve = size; - memcpy(resized.base, data->base, preserve); + memcpy(resized.base, buffer->base, preserve); - io_buffer_resize_clear(data, resized.base, size); + io_buffer_resize_clear(buffer, resized.base, size); } - io_buffer_free(data); - *data = resized; + io_buffer_free(buffer); + *buffer = resized; } void rb_io_buffer_resize(VALUE self, size_t size) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Cannot resize locked buffer!"); } - if (data->base == NULL) { - io_buffer_initialize(data, NULL, size, io_flags_for_size(size), Qnil); + if (buffer->base == NULL) { + io_buffer_initialize(self, buffer, NULL, size, io_flags_for_size(size), Qnil); return; } - if (data->flags & RB_IO_BUFFER_EXTERNAL) { + if (buffer->flags & RB_IO_BUFFER_EXTERNAL) { rb_raise(rb_eIOBufferAccessError, "Cannot resize external buffer!"); } #if defined(HAVE_MREMAP) && defined(MREMAP_MAYMOVE) - if (data->flags & RB_IO_BUFFER_MAPPED) { - void *base = mremap(data->base, data->size, size, MREMAP_MAYMOVE); + if (buffer->flags & RB_IO_BUFFER_MAPPED) { + void *base = mremap(buffer->base, buffer->size, size, MREMAP_MAYMOVE); if (base == MAP_FAILED) { rb_sys_fail("rb_io_buffer_resize:mremap"); } - io_buffer_resize_clear(data, base, size); + io_buffer_resize_clear(buffer, base, size); - data->base = base; - data->size = size; + buffer->base = base; + buffer->size = size; return; } #endif - if (data->flags & RB_IO_BUFFER_INTERNAL) { - void *base = realloc(data->base, size); + if (buffer->flags & RB_IO_BUFFER_INTERNAL) { + if (size == 0) { + io_buffer_free(buffer); + return; + } + + void *base = realloc(buffer->base, size); if (!base) { rb_sys_fail("rb_io_buffer_resize:realloc"); } - io_buffer_resize_clear(data, base, size); + io_buffer_resize_clear(buffer, base, size); - data->base = base; - data->size = size; + buffer->base = base; + buffer->size = size; return; } - io_buffer_resize_copy(data, size); + io_buffer_resize_copy(self, buffer, size); } /* @@ -1429,7 +1756,7 @@ rb_io_buffer_resize(VALUE self, size_t size) static VALUE io_buffer_resize(VALUE self, VALUE size) { - rb_io_buffer_resize(self, NUM2SIZET(size)); + rb_io_buffer_resize(self, io_buffer_extract_size(size)); return self; } @@ -1567,9 +1894,9 @@ IO_BUFFER_DECLARE_TYPE(F64, double, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, r #undef IO_BUFFER_DECLARE_TYPE static inline size_t -io_buffer_data_type_size(ID data_type) +io_buffer_buffer_type_size(ID buffer_type) { -#define IO_BUFFER_DATA_TYPE_SIZE(name) if (data_type == RB_IO_BUFFER_DATA_TYPE_##name) return RB_IO_BUFFER_DATA_TYPE_##name##_SIZE; +#define IO_BUFFER_DATA_TYPE_SIZE(name) if (buffer_type == RB_IO_BUFFER_DATA_TYPE_##name) return RB_IO_BUFFER_DATA_TYPE_##name##_SIZE; IO_BUFFER_DATA_TYPE_SIZE(U8) IO_BUFFER_DATA_TYPE_SIZE(S8) IO_BUFFER_DATA_TYPE_SIZE(u16) @@ -1595,34 +1922,33 @@ io_buffer_data_type_size(ID data_type) /* * call-seq: - * size_of(data_type) -> byte size - * size_of(array of data_type) -> byte size + * size_of(buffer_type) -> byte size + * size_of(array of buffer_type) -> byte size * - * Returns the size of the given data type(s) in bytes. - * - * Example: + * Returns the size of the given buffer type(s) in bytes. * * IO::Buffer.size_of(:u32) # => 4 * IO::Buffer.size_of([:u32, :u32]) # => 8 */ static VALUE -io_buffer_size_of(VALUE klass, VALUE data_type) +io_buffer_size_of(VALUE klass, VALUE buffer_type) { - if (RB_TYPE_P(data_type, T_ARRAY)) { + if (RB_TYPE_P(buffer_type, T_ARRAY)) { size_t total = 0; - for (long i = 0; i < RARRAY_LEN(data_type); i++) { - total += io_buffer_data_type_size(RB_SYM2ID(RARRAY_AREF(data_type, i))); + for (long i = 0; i < RARRAY_LEN(buffer_type); i++) { + total += io_buffer_buffer_type_size(RB_SYM2ID(RARRAY_AREF(buffer_type, i))); } return SIZET2NUM(total); - } else { - return SIZET2NUM(io_buffer_data_type_size(RB_SYM2ID(data_type))); + } + else { + return SIZET2NUM(io_buffer_buffer_type_size(RB_SYM2ID(buffer_type))); } } static inline VALUE -rb_io_buffer_get_value(const void* base, size_t size, ID data_type, size_t *offset) +rb_io_buffer_get_value(const void* base, size_t size, ID buffer_type, size_t *offset) { -#define IO_BUFFER_GET_VALUE(name) if (data_type == RB_IO_BUFFER_DATA_TYPE_##name) return io_buffer_read_##name(base, size, offset); +#define IO_BUFFER_GET_VALUE(name) if (buffer_type == RB_IO_BUFFER_DATA_TYPE_##name) return io_buffer_read_##name(base, size, offset); IO_BUFFER_GET_VALUE(U8) IO_BUFFER_GET_VALUE(S8) @@ -1651,9 +1977,9 @@ rb_io_buffer_get_value(const void* base, size_t size, ID data_type, size_t *offs } /* - * call-seq: get_value(data_type, offset) -> numeric + * call-seq: get_value(buffer_type, offset) -> numeric * - * Read from buffer a value of +type+ at +offset+. +data_type+ should be one + * Read from buffer a value of +type+ at +offset+. +buffer_type+ should be one * of symbols: * * * +:U8+: unsigned integer, 1 byte @@ -1675,12 +2001,10 @@ rb_io_buffer_get_value(const void* base, size_t size, ID data_type, size_t *offs * * +:f64+: double, 8 bytes, little-endian * * +:F64+: double, 8 bytes, big-endian * - * A data type refers specifically to the type of binary data that is stored - * in the buffer. For example, a +:u32+ data type is a 32-bit unsigned + * A buffer type refers specifically to the type of binary buffer that is stored + * in the buffer. For example, a +:u32+ buffer type is a 32-bit unsigned * integer in little-endian format. * - * Example: - * * string = [1.5].pack('f') * # => "\x00\x00\xC0?" * IO::Buffer.for(string).get_value(:f32, 0) @@ -1691,7 +2015,7 @@ io_buffer_get_value(VALUE self, VALUE type, VALUE _offset) { const void *base; size_t size; - size_t offset = NUM2SIZET(_offset); + size_t offset = io_buffer_extract_offset(_offset); rb_io_buffer_get_bytes_for_reading(self, &base, &size); @@ -1699,34 +2023,32 @@ io_buffer_get_value(VALUE self, VALUE type, VALUE _offset) } /* - * call-seq: get_values(data_types, offset) -> array + * call-seq: get_values(buffer_types, offset) -> array * - * Similar to #get_value, except that it can handle multiple data types and + * Similar to #get_value, except that it can handle multiple buffer types and * returns an array of values. * - * Example: - * * string = [1.5, 2.5].pack('ff') * IO::Buffer.for(string).get_values([:f32, :f32], 0) * # => [1.5, 2.5] */ static VALUE -io_buffer_get_values(VALUE self, VALUE data_types, VALUE _offset) +io_buffer_get_values(VALUE self, VALUE buffer_types, VALUE _offset) { - size_t offset = NUM2SIZET(_offset); + size_t offset = io_buffer_extract_offset(_offset); const void *base; size_t size; rb_io_buffer_get_bytes_for_reading(self, &base, &size); - if (!RB_TYPE_P(data_types, T_ARRAY)) { - rb_raise(rb_eArgError, "Argument data_types should be an array!"); + if (!RB_TYPE_P(buffer_types, T_ARRAY)) { + rb_raise(rb_eArgError, "Argument buffer_types should be an array!"); } - VALUE array = rb_ary_new_capa(RARRAY_LEN(data_types)); + VALUE array = rb_ary_new_capa(RARRAY_LEN(buffer_types)); - for (long i = 0; i < RARRAY_LEN(data_types); i++) { - VALUE type = rb_ary_entry(data_types, i); + for (long i = 0; i < RARRAY_LEN(buffer_types); i++) { + VALUE type = rb_ary_entry(buffer_types, i); VALUE value = rb_io_buffer_get_value(base, size, RB_SYM2ID(type), &offset); rb_ary_push(array, value); } @@ -1734,18 +2056,50 @@ io_buffer_get_values(VALUE self, VALUE data_types, VALUE _offset) return array; } +// Extract a count argument, which must be a positive integer. +// Count is generally considered relative to the number of things. +static inline size_t +io_buffer_extract_count(VALUE argument) +{ + if (rb_int_negative_p(argument)) { + rb_raise(rb_eArgError, "Count can't be negative!"); + } + + return NUM2SIZET(argument); +} + +static inline void +io_buffer_extract_offset_count(ID buffer_type, size_t size, int argc, VALUE *argv, size_t *offset, size_t *count) +{ + if (argc >= 1) { + *offset = io_buffer_extract_offset(argv[0]); + } + else { + *offset = 0; + } + + if (argc >= 2) { + *count = io_buffer_extract_count(argv[1]); + } + else { + if (*offset > size) { + rb_raise(rb_eArgError, "The given offset is bigger than the buffer size!"); + } + + *count = (size - *offset) / io_buffer_buffer_type_size(buffer_type); + } +} + /* * call-seq: - * each(data_type, [offset, [count]]) {|offset, value| ...} -> self - * each(data_type, [offset, [count]]) -> enumerator + * each(buffer_type, [offset, [count]]) {|offset, value| ...} -> self + * each(buffer_type, [offset, [count]]) -> enumerator * - * Iterates over the buffer, yielding each +value+ of +data_type+ starting + * Iterates over the buffer, yielding each +value+ of +buffer_type+ starting * from +offset+. * * If +count+ is given, only +count+ values will be yielded. * - * Example: - * * IO::Buffer.for("Hello World").each(:U8, 2, 2) do |offset, value| * puts "#{offset}: #{value}" * end @@ -1762,30 +2116,20 @@ io_buffer_each(int argc, VALUE *argv, VALUE self) rb_io_buffer_get_bytes_for_reading(self, &base, &size); - ID data_type; + ID buffer_type; if (argc >= 1) { - data_type = RB_SYM2ID(argv[0]); - } else { - data_type = RB_IO_BUFFER_DATA_TYPE_U8; + buffer_type = RB_SYM2ID(argv[0]); } - - size_t offset; - if (argc >= 2) { - offset = NUM2SIZET(argv[1]); - } else { - offset = 0; + else { + buffer_type = RB_IO_BUFFER_DATA_TYPE_U8; } - size_t count; - if (argc >= 3) { - count = NUM2SIZET(argv[2]); - } else { - count = (size - offset) / io_buffer_data_type_size(data_type); - } + size_t offset, count; + io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count); for (size_t i = 0; i < count; i++) { size_t current_offset = offset; - VALUE value = rb_io_buffer_get_value(base, size, data_type, &offset); + VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset); rb_yield_values(2, SIZET2NUM(current_offset), value); } @@ -1793,14 +2137,12 @@ io_buffer_each(int argc, VALUE *argv, VALUE self) } /* - * call-seq: values(data_type, [offset, [count]]) -> array + * call-seq: values(buffer_type, [offset, [count]]) -> array * - * Returns an array of values of +data_type+ starting from +offset+. + * Returns an array of values of +buffer_type+ starting from +offset+. * * If +count+ is given, only +count+ values will be returned. * - * Example: - * * IO::Buffer.for("Hello World").values(:U8, 2, 2) * # => [108, 108] */ @@ -1812,31 +2154,21 @@ io_buffer_values(int argc, VALUE *argv, VALUE self) rb_io_buffer_get_bytes_for_reading(self, &base, &size); - ID data_type; + ID buffer_type; if (argc >= 1) { - data_type = RB_SYM2ID(argv[0]); - } else { - data_type = RB_IO_BUFFER_DATA_TYPE_U8; + buffer_type = RB_SYM2ID(argv[0]); } - - size_t offset; - if (argc >= 2) { - offset = NUM2SIZET(argv[1]); - } else { - offset = 0; + else { + buffer_type = RB_IO_BUFFER_DATA_TYPE_U8; } - size_t count; - if (argc >= 3) { - count = NUM2SIZET(argv[2]); - } else { - count = (size - offset) / io_buffer_data_type_size(data_type); - } + size_t offset, count; + io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count); VALUE array = rb_ary_new_capa(count); for (size_t i = 0; i < count; i++) { - VALUE value = rb_io_buffer_get_value(base, size, data_type, &offset); + VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset); rb_ary_push(array, value); } @@ -1852,8 +2184,6 @@ io_buffer_values(int argc, VALUE *argv, VALUE self) * * If +count+ is given, only +count+ bytes will be yielded. * - * Example: - * * IO::Buffer.for("Hello World").each_byte(2, 2) do |offset, byte| * puts "#{offset}: #{byte}" * end @@ -1870,19 +2200,8 @@ io_buffer_each_byte(int argc, VALUE *argv, VALUE self) rb_io_buffer_get_bytes_for_reading(self, &base, &size); - size_t offset; - if (argc >= 2) { - offset = NUM2SIZET(argv[1]); - } else { - offset = 0; - } - - size_t count; - if (argc >= 3) { - count = NUM2SIZET(argv[2]); - } else { - count = (size - offset); - } + size_t offset, count; + io_buffer_extract_offset_count(RB_IO_BUFFER_DATA_TYPE_U8, size, argc-1, argv+1, &offset, &count); for (size_t i = 0; i < count; i++) { unsigned char *value = (unsigned char *)base + i + offset; @@ -1893,9 +2212,9 @@ io_buffer_each_byte(int argc, VALUE *argv, VALUE self) } static inline void -rb_io_buffer_set_value(const void* base, size_t size, ID data_type, size_t *offset, VALUE value) +rb_io_buffer_set_value(const void* base, size_t size, ID buffer_type, size_t *offset, VALUE value) { -#define IO_BUFFER_SET_VALUE(name) if (data_type == RB_IO_BUFFER_DATA_TYPE_##name) {io_buffer_write_##name(base, size, offset, value); return;} +#define IO_BUFFER_SET_VALUE(name) if (buffer_type == RB_IO_BUFFER_DATA_TYPE_##name) {io_buffer_write_##name(base, size, offset, value); return;} IO_BUFFER_SET_VALUE(U8); IO_BUFFER_SET_VALUE(S8); @@ -1958,7 +2277,7 @@ io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value) { void *base; size_t size; - size_t offset = NUM2SIZET(_offset); + size_t offset = io_buffer_extract_offset(_offset); rb_io_buffer_get_bytes_for_writing(self, &base, &size); @@ -1968,14 +2287,12 @@ io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value) } /* - * call-seq: set_values(data_types, offset, values) -> offset + * call-seq: set_values(buffer_types, offset, values) -> offset * - * Write +values+ of +data_types+ at +offset+ to the buffer. +data_types+ + * Write +values+ of +buffer_types+ at +offset+ to the buffer. +buffer_types+ * should be an array of symbols as described in #get_value. +values+ should * be an array of values to write. * - * Example: - * * buffer = IO::Buffer.new(8) * buffer.set_values([:U8, :U16], 0, [1, 2]) * buffer @@ -1984,28 +2301,28 @@ io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value) * # 0x00000000 01 00 02 00 00 00 00 00 ........ */ static VALUE -io_buffer_set_values(VALUE self, VALUE data_types, VALUE _offset, VALUE values) +io_buffer_set_values(VALUE self, VALUE buffer_types, VALUE _offset, VALUE values) { - if (!RB_TYPE_P(data_types, T_ARRAY)) { - rb_raise(rb_eArgError, "Argument data_types should be an array!"); + if (!RB_TYPE_P(buffer_types, T_ARRAY)) { + rb_raise(rb_eArgError, "Argument buffer_types should be an array!"); } if (!RB_TYPE_P(values, T_ARRAY)) { rb_raise(rb_eArgError, "Argument values should be an array!"); } - if (RARRAY_LEN(data_types) != RARRAY_LEN(values)) { - rb_raise(rb_eArgError, "Argument data_types and values should have the same length!"); + if (RARRAY_LEN(buffer_types) != RARRAY_LEN(values)) { + rb_raise(rb_eArgError, "Argument buffer_types and values should have the same length!"); } - size_t offset = NUM2SIZET(_offset); + size_t offset = io_buffer_extract_offset(_offset); void *base; size_t size; rb_io_buffer_get_bytes_for_writing(self, &base, &size); - for (long i = 0; i < RARRAY_LEN(data_types); i++) { - VALUE type = rb_ary_entry(data_types, i); + for (long i = 0; i < RARRAY_LEN(buffer_types); i++) { + VALUE type = rb_ary_entry(buffer_types, i); VALUE value = rb_ary_entry(values, i); rb_io_buffer_set_value(base, size, RB_SYM2ID(type), &offset, value); } @@ -2014,16 +2331,16 @@ io_buffer_set_values(VALUE self, VALUE data_types, VALUE _offset, VALUE values) } static void -io_buffer_memcpy(struct rb_io_buffer *data, size_t offset, const void *source_base, size_t source_offset, size_t source_size, size_t length) +io_buffer_memcpy(struct rb_io_buffer *buffer, size_t offset, const void *source_base, size_t source_offset, size_t source_size, size_t length) { void *base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); - io_buffer_validate_range(data, offset, length); + io_buffer_validate_range(buffer, offset, length); if (source_offset + length > source_size) { - rb_raise(rb_eArgError, "The computed source range exceeds the size of the source!"); + rb_raise(rb_eArgError, "The computed source range exceeds the size of the source buffer!"); } memcpy((unsigned char*)base+offset, (unsigned char*)source_base+source_offset, length); @@ -2031,23 +2348,20 @@ io_buffer_memcpy(struct rb_io_buffer *data, size_t offset, const void *source_ba // (offset, length, source_offset) -> length static VALUE -io_buffer_copy_from(struct rb_io_buffer *data, const void *source_base, size_t source_size, int argc, VALUE *argv) +io_buffer_copy_from(struct rb_io_buffer *buffer, const void *source_base, size_t source_size, int argc, VALUE *argv) { - size_t offset; + size_t offset = 0; size_t length; size_t source_offset; // The offset we copy into the buffer: if (argc >= 1) { - offset = NUM2SIZET(argv[0]); - } - else { - offset = 0; + offset = io_buffer_extract_offset(argv[0]); } // The offset we start from within the string: if (argc >= 3) { - source_offset = NUM2SIZET(argv[2]); + source_offset = io_buffer_extract_offset(argv[2]); if (source_offset > source_size) { rb_raise(rb_eArgError, "The given source offset is bigger than the source itself!"); @@ -2059,14 +2373,14 @@ io_buffer_copy_from(struct rb_io_buffer *data, const void *source_base, size_t s // The length we are going to copy: if (argc >= 2 && !RB_NIL_P(argv[1])) { - length = NUM2SIZET(argv[1]); + length = io_buffer_extract_length(argv[1]); } else { // Default to the source offset -> source size: length = source_size - source_offset; } - io_buffer_memcpy(data, offset, source_base, source_offset, source_size, length); + io_buffer_memcpy(buffer, offset, source_base, source_offset, source_size, length); return SIZET2NUM(length); } @@ -2091,25 +2405,25 @@ io_buffer_copy_from(struct rb_io_buffer *data, const void *source_base, size_t s static VALUE rb_io_buffer_initialize_copy(VALUE self, VALUE source) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); const void *source_base; size_t source_size; rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size); - io_buffer_initialize(data, NULL, source_size, io_flags_for_size(source_size), Qnil); + io_buffer_initialize(self, buffer, NULL, source_size, io_flags_for_size(source_size), Qnil); - return io_buffer_copy_from(data, source_base, source_size, 0, NULL); + return io_buffer_copy_from(buffer, source_base, source_size, 0, NULL); } /* * call-seq: * copy(source, [offset, [length, [source_offset]]]) -> size * - * Efficiently copy data from a source IO::Buffer into the buffer, - * at +offset+ using +memcpy+. For copying String instances, see #set_string. + * Efficiently copy from a source IO::Buffer into the buffer, at +offset+ + * using +memcpy+. For copying String instances, see #set_string. * * buffer = IO::Buffer.new(32) * # => @@ -2118,14 +2432,14 @@ rb_io_buffer_initialize_copy(VALUE self, VALUE source) * # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * * * buffer.copy(IO::Buffer.for("test"), 8) - * # => 4 -- size of data copied + * # => 4 -- size of buffer copied * buffer * # => * # #<IO::Buffer 0x0000555f5cf8fe40+32 INTERNAL> * # 0x00000000 00 00 00 00 00 00 00 00 74 65 73 74 00 00 00 00 ........test.... * # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * * - * #copy can be used to put data into strings associated with buffer: + * #copy can be used to put buffer into strings associated with buffer: * * string= "data: " * # => "data: " @@ -2151,20 +2465,20 @@ rb_io_buffer_initialize_copy(VALUE self, VALUE source) * File.read('test.txt') * # => "boom" * - * Attempt to copy the data which will need place outside of buffer's + * Attempt to copy the buffer which will need place outside of buffer's * bounds will fail: * * buffer = IO::Buffer.new(2) * buffer.copy(IO::Buffer.for('test'), 0) - * # in `copy': Specified offset+length exceeds source size! (ArgumentError) + * # in `copy': Specified offset+length is bigger than the buffer size! (ArgumentError) */ static VALUE io_buffer_copy(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 4); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE source = argv[0]; const void *source_base; @@ -2172,7 +2486,7 @@ io_buffer_copy(int argc, VALUE *argv, VALUE self) rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size); - return io_buffer_copy_from(data, source_base, source_size, argc-1, argv+1); + return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1); } /* @@ -2194,33 +2508,22 @@ io_buffer_get_string(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 3); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + size_t offset, length; + struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length); const void *base; size_t size; - io_buffer_get_bytes_for_reading(data, &base, &size); - - size_t offset = 0; - size_t length = size; - rb_encoding *encoding = rb_ascii8bit_encoding(); - - if (argc >= 1) { - offset = NUM2SIZET(argv[0]); - } - - if (argc >= 2 && !RB_NIL_P(argv[1])) { - length = NUM2SIZET(argv[1]); - } - else { - length = size - offset; - } + io_buffer_get_bytes_for_reading(buffer, &base, &size); + rb_encoding *encoding; if (argc >= 3) { encoding = rb_find_encoding(argv[2]); } + else { + encoding = rb_ascii8bit_encoding(); + } - io_buffer_validate_range(data, offset, length); + io_buffer_validate_range(buffer, offset, length); return rb_enc_str_new((const char*)base + offset, length, encoding); } @@ -2228,15 +2531,15 @@ io_buffer_get_string(int argc, VALUE *argv, VALUE self) /* * call-seq: set_string(string, [offset, [length, [source_offset]]]) -> size * - * Efficiently copy data from a source String into the buffer, - * at +offset+ using +memcpy+. + * Efficiently copy from a source String into the buffer, at +offset+ using + * +memcpy+. * * buf = IO::Buffer.new(8) * # => * # #<IO::Buffer 0x0000557412714a20+8 INTERNAL> * # 0x00000000 00 00 00 00 00 00 00 00 ........ * - * # set data starting from offset 1, take 2 bytes starting from string's + * # set buffer starting from offset 1, take 2 bytes starting from string's * # second * buf.set_string('test', 1, 2, 1) * # => 2 @@ -2253,28 +2556,28 @@ io_buffer_set_string(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 4); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE string = rb_str_to_str(argv[0]); const void *source_base = RSTRING_PTR(string); size_t source_size = RSTRING_LEN(string); - return io_buffer_copy_from(data, source_base, source_size, argc-1, argv+1); + return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1); } void rb_io_buffer_clear(VALUE self, uint8_t value, size_t offset, size_t length) { + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + void *base; size_t size; + io_buffer_get_bytes_for_writing(buffer, &base, &size); - rb_io_buffer_get_bytes_for_writing(self, &base, &size); - - if (offset + length > size) { - rb_raise(rb_eArgError, "The given offset + length out of bounds!"); - } + io_buffer_validate_range(buffer, offset, length); memset((char*)base + offset, value, length); } @@ -2315,26 +2618,13 @@ io_buffer_clear(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 3); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); - uint8_t value = 0; if (argc >= 1) { value = NUM2UINT(argv[0]); } - size_t offset = 0; - if (argc >= 2) { - offset = NUM2SIZET(argv[1]); - } - - size_t length; - if (argc >= 3) { - length = NUM2SIZET(argv[2]); - } - else { - length = data->size - offset; - } + size_t offset, length; + io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length); rb_io_buffer_clear(self, value, offset, length); @@ -2366,18 +2656,90 @@ io_buffer_default_size(size_t page_size) return platform_agnostic_default_size; } +struct io_buffer_blocking_region_argument { + struct rb_io_buffer *buffer; + rb_blocking_function_t *function; + void *data; + int descriptor; +}; + +static VALUE +io_buffer_blocking_region_begin(VALUE _argument) +{ + struct io_buffer_blocking_region_argument *argument = (void*)_argument; + + return rb_thread_io_blocking_region(argument->function, argument->data, argument->descriptor); +} + +static VALUE +io_buffer_blocking_region_ensure(VALUE _argument) +{ + struct io_buffer_blocking_region_argument *argument = (void*)_argument; + + io_buffer_unlock(argument->buffer); + + return Qnil; +} + +static VALUE +io_buffer_blocking_region(struct rb_io_buffer *buffer, rb_blocking_function_t *function, void *data, int descriptor) +{ + struct io_buffer_blocking_region_argument argument = { + .buffer = buffer, + .function = function, + .data = data, + .descriptor = descriptor, + }; + + // If the buffer is already locked, we can skip the ensure (unlock): + if (buffer->flags & RB_IO_BUFFER_LOCKED) { + return io_buffer_blocking_region_begin((VALUE)&argument); + } + else { + // The buffer should be locked for the duration of the blocking region: + io_buffer_lock(buffer); + + return rb_ensure(io_buffer_blocking_region_begin, (VALUE)&argument, io_buffer_blocking_region_ensure, (VALUE)&argument); + } +} + struct io_buffer_read_internal_argument { + // The file descriptor to read from: int descriptor; - void *base; + // The base pointer to read from: + char *base; + // The size of the buffer: size_t size; + // The minimum number of bytes to read: + size_t length; }; static VALUE io_buffer_read_internal(void *_argument) { + size_t total = 0; struct io_buffer_read_internal_argument *argument = _argument; - ssize_t result = read(argument->descriptor, argument->base, argument->size); - return rb_fiber_scheduler_io_result(result, errno); + + while (true) { + ssize_t result = read(argument->descriptor, argument->base, argument->size); + + if (result < 0) { + return rb_fiber_scheduler_io_result(result, errno); + } + else if (result == 0) { + return rb_fiber_scheduler_io_result(total, 0); + } + else { + total += result; + + if (total >= argument->length) { + return rb_fiber_scheduler_io_result(total, 0); + } + + argument->base = argument->base + result; + argument->size = argument->size - result; + } + } } VALUE @@ -2385,48 +2747,50 @@ rb_io_buffer_read(VALUE self, VALUE io, size_t length, size_t offset) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - VALUE result = rb_fiber_scheduler_io_read(scheduler, io, self, SIZET2NUM(length), SIZET2NUM(offset)); + VALUE result = rb_fiber_scheduler_io_read(scheduler, io, self, length, offset); if (!UNDEF_P(result)) { return result; } } - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_validate_range(data, offset, length); + io_buffer_validate_range(buffer, offset, length); int descriptor = rb_io_descriptor(io); void * base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); base = (unsigned char*)base + offset; + size = size - offset; struct io_buffer_read_internal_argument argument = { .descriptor = descriptor, .base = base, - .size = length, + .size = size, + .length = length, }; - return rb_thread_io_blocking_region(io_buffer_read_internal, &argument, descriptor); + return io_buffer_blocking_region(buffer, io_buffer_read_internal, &argument, descriptor); } /* * call-seq: read(io, [length, [offset]]) -> read length or -errno * - * Read at most +length+ bytes from +io+ into the buffer, starting at + * Read at least +length+ bytes from the +io+, into the buffer starting at * +offset+. If an error occurs, return <tt>-errno</tt>. * - * If +length+ is not given, read until the end of the buffer. + * If +length+ is not given or +nil+, it defaults to the size of the buffer + * minus the offset, i.e. the entire buffer. * - * If +offset+ is not given, read from the beginning of the buffer. + * If +length+ is zero, exactly one <tt>read</tt> operation will occur. * - * If +length+ is 0, read nothing. - * - * Example: + * If +offset+ is not given, it defaults to zero, i.e. the beginning of the + * buffer. * * IO::Buffer.for('test') do |buffer| * p buffer @@ -2443,61 +2807,56 @@ rb_io_buffer_read(VALUE self, VALUE io, size_t length, size_t offset) static VALUE io_buffer_read(int argc, VALUE *argv, VALUE self) { - rb_check_arity(argc, 2, 3); + rb_check_arity(argc, 1, 3); VALUE io = argv[0]; - size_t length; - if (argc >= 2) { - if (rb_int_negative_p(argv[1])) { - rb_raise(rb_eArgError, "Length can't be negative!"); - } - - length = NUM2SIZET(argv[1]); - } - - size_t offset = 0; - if (argc >= 3) { - if (rb_int_negative_p(argv[2])) { - rb_raise(rb_eArgError, "Offset can't be negative!"); - } - - offset = NUM2SIZET(argv[2]); - } + size_t length, offset; + io_buffer_extract_length_offset(self, argc-1, argv+1, &length, &offset); return rb_io_buffer_read(self, io, length, offset); } struct io_buffer_pread_internal_argument { + // The file descriptor to read from: int descriptor; - void *base; + // The base pointer to read from: + char *base; + // The size of the buffer: size_t size; + // The minimum number of bytes to read: + size_t length; + // The offset to read from: off_t offset; }; static VALUE io_buffer_pread_internal(void *_argument) { + size_t total = 0; struct io_buffer_pread_internal_argument *argument = _argument; -#if defined(HAVE_PREAD) - ssize_t result = pread(argument->descriptor, argument->base, argument->size, argument->offset); -#else - // This emulation is not thread safe. - rb_off_t offset = lseek(argument->descriptor, 0, SEEK_CUR); - if (offset == (rb_off_t)-1) - return rb_fiber_scheduler_io_result(-1, errno); - - if (lseek(argument->descriptor, argument->offset, SEEK_SET) == (rb_off_t)-1) - return rb_fiber_scheduler_io_result(-1, errno); + while (true) { + ssize_t result = pread(argument->descriptor, argument->base, argument->size, argument->offset); - ssize_t result = read(argument->descriptor, argument->base, argument->size); + if (result < 0) { + return rb_fiber_scheduler_io_result(result, errno); + } + else if (result == 0) { + return rb_fiber_scheduler_io_result(total, 0); + } + else { + total += result; - if (lseek(argument->descriptor, offset, SEEK_SET) == (rb_off_t)-1) - return rb_fiber_scheduler_io_result(-1, errno); -#endif + if (total >= argument->length) { + return rb_fiber_scheduler_io_result(total, 0); + } - return rb_fiber_scheduler_io_result(result, errno); + argument->base = argument->base + result; + argument->size = argument->size - result; + argument->offset = argument->offset + result; + } + } } VALUE @@ -2505,50 +2864,52 @@ rb_io_buffer_pread(VALUE self, VALUE io, rb_off_t from, size_t length, size_t of { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - VALUE result = rb_fiber_scheduler_io_pread(scheduler, io, OFFT2NUM(from), self, SIZET2NUM(length), SIZET2NUM(offset)); + VALUE result = rb_fiber_scheduler_io_pread(scheduler, io, from, self, length, offset); if (!UNDEF_P(result)) { return result; } } - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_validate_range(data, offset, length); + io_buffer_validate_range(buffer, offset, length); int descriptor = rb_io_descriptor(io); void * base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); + + base = (unsigned char*)base + offset; + size = size - offset; struct io_buffer_pread_internal_argument argument = { .descriptor = descriptor, - - // Move the base pointer to the offset: - .base = (unsigned char*)base + offset, - - // And the size to the length of data we want to read: - .size = length, - - // From the offset in the file we want to read from: + .base = base, + .size = size, + .length = length, .offset = from, }; - return rb_thread_io_blocking_region(io_buffer_pread_internal, &argument, descriptor); + return io_buffer_blocking_region(buffer, io_buffer_pread_internal, &argument, descriptor); } /* - * call-seq: pread(io, from, length, [offset]) -> read length or -errno + * call-seq: pread(io, from, [length, [offset]]) -> read length or -errno * - * Read at most +length+ bytes from +io+ into the buffer, starting at - * +from+, and put it in buffer starting from specified +offset+. - * If an error occurs, return <tt>-errno</tt>. + * Read at least +length+ bytes from the +io+ starting at the specified +from+ + * position, into the buffer starting at +offset+. If an error occurs, + * return <tt>-errno</tt>. + * + * If +length+ is not given or +nil+, it defaults to the size of the buffer + * minus the offset, i.e. the entire buffer. * - * If +offset+ is not given, put it at the beginning of the buffer. + * If +length+ is zero, exactly one <tt>pread</tt> operation will occur. * - * Example: + * If +offset+ is not given, it defaults to zero, i.e. the beginning of the + * buffer. * * IO::Buffer.for('test') do |buffer| * p buffer @@ -2568,41 +2929,54 @@ rb_io_buffer_pread(VALUE self, VALUE io, rb_off_t from, size_t length, size_t of static VALUE io_buffer_pread(int argc, VALUE *argv, VALUE self) { - rb_check_arity(argc, 3, 4); + rb_check_arity(argc, 2, 4); VALUE io = argv[0]; rb_off_t from = NUM2OFFT(argv[1]); - size_t length; - if (rb_int_negative_p(argv[2])) { - rb_raise(rb_eArgError, "Length can't be negative!"); - } - length = NUM2SIZET(argv[2]); - - size_t offset = 0; - if (argc >= 4) { - if (rb_int_negative_p(argv[3])) { - rb_raise(rb_eArgError, "Offset can't be negative!"); - } - - offset = NUM2SIZET(argv[3]); - } + size_t length, offset; + io_buffer_extract_length_offset(self, argc-2, argv+2, &length, &offset); return rb_io_buffer_pread(self, io, from, length, offset); } struct io_buffer_write_internal_argument { + // The file descriptor to write to: int descriptor; - const void *base; + // The base pointer to write from: + const char *base; + // The size of the buffer: size_t size; + // The minimum length to write: + size_t length; }; static VALUE io_buffer_write_internal(void *_argument) { + size_t total = 0; struct io_buffer_write_internal_argument *argument = _argument; - ssize_t result = write(argument->descriptor, argument->base, argument->size); - return rb_fiber_scheduler_io_result(result, errno); + + while (true) { + ssize_t result = write(argument->descriptor, argument->base, argument->size); + + if (result < 0) { + return rb_fiber_scheduler_io_result(result, errno); + } + else if (result == 0) { + return rb_fiber_scheduler_io_result(total, 0); + } + else { + total += result; + + if (total >= argument->length) { + return rb_fiber_scheduler_io_result(total, 0); + } + + argument->base = argument->base + result; + argument->size = argument->size - result; + } + } } VALUE @@ -2610,43 +2984,50 @@ rb_io_buffer_write(VALUE self, VALUE io, size_t length, size_t offset) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - VALUE result = rb_fiber_scheduler_io_write(scheduler, io, self, SIZET2NUM(length), SIZET2NUM(offset)); + VALUE result = rb_fiber_scheduler_io_write(scheduler, io, self, length, offset); if (!UNDEF_P(result)) { return result; } } - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_validate_range(data, offset, length); + io_buffer_validate_range(buffer, offset, length); int descriptor = rb_io_descriptor(io); const void * base; size_t size; - io_buffer_get_bytes_for_reading(data, &base, &size); + io_buffer_get_bytes_for_reading(buffer, &base, &size); - base = (unsigned char *)base + offset; + base = (unsigned char*)base + offset; + size = size - offset; struct io_buffer_write_internal_argument argument = { .descriptor = descriptor, .base = base, - .size = length, + .size = size, + .length = length, }; - return rb_thread_io_blocking_region(io_buffer_write_internal, &argument, descriptor); + return io_buffer_blocking_region(buffer, io_buffer_write_internal, &argument, descriptor); } /* - * call-seq: write(io, length, [offset]) -> written length or -errno + * call-seq: write(io, [length, [offset]]) -> written length or -errno + * + * Write at least +length+ bytes from the buffer starting at +offset+, into the +io+. + * If an error occurs, return <tt>-errno</tt>. + * + * If +length+ is not given or +nil+, it defaults to the size of the buffer + * minus the offset, i.e. the entire buffer. * - * Writes +length+ bytes from buffer into +io+, starting at - * +offset+ in the buffer. If an error occurs, return <tt>-errno</tt>. + * If +length+ is zero, exactly one <tt>write</tt> operation will occur. * - * If +offset+ is not given, the bytes are taken from the beginning - * of the buffer. + * If +offset+ is not given, it defaults to zero, i.e. the beginning of the + * buffer. * * out = File.open('output.txt', 'wb') * IO::Buffer.for('1234567').write(out, 3) @@ -2656,61 +3037,55 @@ rb_io_buffer_write(VALUE self, VALUE io, size_t length, size_t offset) static VALUE io_buffer_write(int argc, VALUE *argv, VALUE self) { - rb_check_arity(argc, 2, 3); + rb_check_arity(argc, 1, 3); VALUE io = argv[0]; - size_t length; - if (argc >= 2) { - if (rb_int_negative_p(argv[1])) { - rb_raise(rb_eArgError, "Length can't be negative!"); - } - - length = NUM2SIZET(argv[1]); - } - - size_t offset = 0; - if (argc >= 3) { - if (rb_int_negative_p(argv[2])) { - rb_raise(rb_eArgError, "Offset can't be negative!"); - } - - offset = NUM2SIZET(argv[2]); - } + size_t length, offset; + io_buffer_extract_length_offset(self, argc-1, argv+1, &length, &offset); return rb_io_buffer_write(self, io, length, offset); } - struct io_buffer_pwrite_internal_argument { + // The file descriptor to write to: int descriptor; - const void *base; + // The base pointer to write from: + const char *base; + // The size of the buffer: size_t size; + // The minimum length to write: + size_t length; + // The offset to write to: off_t offset; }; static VALUE io_buffer_pwrite_internal(void *_argument) { + size_t total = 0; struct io_buffer_pwrite_internal_argument *argument = _argument; -#if defined(HAVE_PWRITE) - ssize_t result = pwrite(argument->descriptor, argument->base, argument->size, argument->offset); -#else - // This emulation is not thread safe. - rb_off_t offset = lseek(argument->descriptor, 0, SEEK_CUR); - if (offset == (rb_off_t)-1) - return rb_fiber_scheduler_io_result(-1, errno); - - if (lseek(argument->descriptor, argument->offset, SEEK_SET) == (rb_off_t)-1) - return rb_fiber_scheduler_io_result(-1, errno); + while (true) { + ssize_t result = pwrite(argument->descriptor, argument->base, argument->size, argument->offset); - ssize_t result = write(argument->descriptor, argument->base, argument->size); + if (result < 0) { + return rb_fiber_scheduler_io_result(result, errno); + } + else if (result == 0) { + return rb_fiber_scheduler_io_result(total, 0); + } + else { + total += result; - if (lseek(argument->descriptor, offset, SEEK_SET) == (rb_off_t)-1) - return rb_fiber_scheduler_io_result(-1, errno); -#endif + if (total >= argument->length) { + return rb_fiber_scheduler_io_result(total, 0); + } - return rb_fiber_scheduler_io_result(result, errno); + argument->base = argument->base + result; + argument->size = argument->size - result; + argument->offset = argument->offset + result; + } + } } VALUE @@ -2718,49 +3093,63 @@ rb_io_buffer_pwrite(VALUE self, VALUE io, rb_off_t from, size_t length, size_t o { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - VALUE result = rb_fiber_scheduler_io_pwrite(scheduler, io, OFFT2NUM(from), self, SIZET2NUM(length), SIZET2NUM(offset)); + VALUE result = rb_fiber_scheduler_io_pwrite(scheduler, io, from, self, length, offset); if (!UNDEF_P(result)) { return result; } } - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_validate_range(data, offset, length); + io_buffer_validate_range(buffer, offset, length); int descriptor = rb_io_descriptor(io); const void * base; size_t size; - io_buffer_get_bytes_for_reading(data, &base, &size); + io_buffer_get_bytes_for_reading(buffer, &base, &size); + + base = (unsigned char*)base + offset; + size = size - offset; struct io_buffer_pwrite_internal_argument argument = { .descriptor = descriptor, // Move the base pointer to the offset: - .base = (unsigned char *)base + offset, + .base = base, - // And the size to the length of data we want to read: - .size = length, + // And the size to the length of buffer we want to read: + .size = size, + + // And the length of the buffer we want to write: + .length = length, // And the offset in the file we want to write from: .offset = from, }; - return rb_thread_io_blocking_region(io_buffer_pwrite_internal, &argument, descriptor); + return io_buffer_blocking_region(buffer, io_buffer_pwrite_internal, &argument, descriptor); } /* - * call-seq: pwrite(io, from, length, [offset]) -> written length or -errno + * call-seq: pwrite(io, from, [length, [offset]]) -> written length or -errno + * + * Write at least +length+ bytes from the buffer starting at +offset+, into + * the +io+ starting at the specified +from+ position. If an error occurs, + * return <tt>-errno</tt>. * - * Writes +length+ bytes from buffer into +io+, starting at - * +offset+ in the buffer. If an error occurs, return <tt>-errno</tt>. + * If +length+ is not given or +nil+, it defaults to the size of the buffer + * minus the offset, i.e. the entire buffer. * - * If +offset+ is not given, the bytes are taken from the beginning of the - * buffer. If the +offset+ is given and is beyond the end of the file, the - * gap will be filled with null (0 value) bytes. + * If +length+ is zero, exactly one <tt>pwrite</tt> operation will occur. + * + * If +offset+ is not given, it defaults to zero, i.e. the beginning of the + * buffer. + * + * If the +from+ position is beyond the end of the file, the gap will be + * filled with null (0 value) bytes. * * out = File.open('output.txt', File::RDWR) # open for read/write, no truncation * IO::Buffer.for('1234567').pwrite(out, 2, 3, 1) @@ -2771,25 +3160,13 @@ rb_io_buffer_pwrite(VALUE self, VALUE io, rb_off_t from, size_t length, size_t o static VALUE io_buffer_pwrite(int argc, VALUE *argv, VALUE self) { - rb_check_arity(argc, 3, 4); + rb_check_arity(argc, 2, 4); VALUE io = argv[0]; rb_off_t from = NUM2OFFT(argv[1]); - size_t length; - if (rb_int_negative_p(argv[2])) { - rb_raise(rb_eArgError, "Length can't be negative!"); - } - length = NUM2SIZET(argv[2]); - - size_t offset = 0; - if (argc >= 4) { - if (rb_int_negative_p(argv[3])) { - rb_raise(rb_eArgError, "Offset can't be negative!"); - } - - offset = NUM2SIZET(argv[3]); - } + size_t length, offset; + io_buffer_extract_length_offset(self, argc-2, argv+2, &length, &offset); return rb_io_buffer_pwrite(self, io, from, length, offset); } @@ -2824,19 +3201,19 @@ memory_and(unsigned char * restrict output, unsigned char * restrict base, size_ static VALUE io_buffer_and(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); + io_buffer_check_mask(mask_buffer); - VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); - struct rb_io_buffer *output_data = NULL; - TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); + struct rb_io_buffer *output_buffer = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); - memory_and(output_data->base, data->base, data->size, mask_data->base, mask_data->size); + memory_and(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size); return output; } @@ -2864,19 +3241,19 @@ memory_or(unsigned char * restrict output, unsigned char * restrict base, size_t static VALUE io_buffer_or(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); + io_buffer_check_mask(mask_buffer); - VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); - struct rb_io_buffer *output_data = NULL; - TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); + struct rb_io_buffer *output_buffer = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); - memory_or(output_data->base, data->base, data->size, mask_data->base, mask_data->size); + memory_or(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size); return output; } @@ -2904,19 +3281,19 @@ memory_xor(unsigned char * restrict output, unsigned char * restrict base, size_ static VALUE io_buffer_xor(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); + io_buffer_check_mask(mask_buffer); - VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); - struct rb_io_buffer *output_data = NULL; - TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); + struct rb_io_buffer *output_buffer = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); - memory_xor(output_data->base, data->base, data->size, mask_data->base, mask_data->size); + memory_xor(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size); return output; } @@ -2944,14 +3321,14 @@ memory_not(unsigned char * restrict output, unsigned char * restrict base, size_ static VALUE io_buffer_not(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); - struct rb_io_buffer *output_data = NULL; - TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); + struct rb_io_buffer *output_buffer = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); - memory_not(output_data->base, data->base, data->size); + memory_not(output_buffer->base, buffer->base, buffer->size); return output; } @@ -2970,7 +3347,7 @@ static inline void io_buffer_check_overlaps(struct rb_io_buffer *a, struct rb_io_buffer *b) { if (io_buffer_overlaps(a, b)) - rb_raise(rb_eIOBufferMaskError, "Mask overlaps source data!"); + rb_raise(rb_eIOBufferMaskError, "Mask overlaps source buffer!"); } static void @@ -3001,20 +3378,20 @@ memory_and_inplace(unsigned char * restrict base, size_t size, unsigned char * r static VALUE io_buffer_and_inplace(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); - io_buffer_check_overlaps(data, mask_data); + io_buffer_check_mask(mask_buffer); + io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); - memory_and_inplace(base, size, mask_data->base, mask_data->size); + memory_and_inplace(base, size, mask_buffer->base, mask_buffer->size); return self; } @@ -3047,20 +3424,20 @@ memory_or_inplace(unsigned char * restrict base, size_t size, unsigned char * re static VALUE io_buffer_or_inplace(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); - io_buffer_check_overlaps(data, mask_data); + io_buffer_check_mask(mask_buffer); + io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); - memory_or_inplace(base, size, mask_data->base, mask_data->size); + memory_or_inplace(base, size, mask_buffer->base, mask_buffer->size); return self; } @@ -3093,20 +3470,20 @@ memory_xor_inplace(unsigned char * restrict base, size_t size, unsigned char * r static VALUE io_buffer_xor_inplace(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); - io_buffer_check_overlaps(data, mask_data); + io_buffer_check_mask(mask_buffer); + io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); - memory_xor_inplace(base, size, mask_data->base, mask_data->size); + memory_xor_inplace(base, size, mask_buffer->base, mask_buffer->size); return self; } @@ -3139,12 +3516,12 @@ memory_not_inplace(unsigned char * restrict base, size_t size) static VALUE io_buffer_not_inplace(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); void *base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); memory_not_inplace(base, size); @@ -3154,24 +3531,28 @@ io_buffer_not_inplace(VALUE self) /* * Document-class: IO::Buffer * - * IO::Buffer is a low-level efficient buffer for input/output. There are three - * ways of using buffer: + * IO::Buffer is a efficient zero-copy buffer for input/output. There are + * typical use cases: * - * * Create an empty buffer with ::new, fill it with data using #copy or - * #set_value, #set_string, get data with #get_string; + * * Create an empty buffer with ::new, fill it with buffer using #copy or + * #set_value, #set_string, get buffer with #get_string or write it directly + * to some file with #write. * * Create a buffer mapped to some string with ::for, then it could be used * both for reading with #get_string or #get_value, and writing (writing will - * change the source string, too); + * change the source string, too). * * Create a buffer mapped to some file with ::map, then it could be used for * reading and writing the underlying file. + * * Create a string of a fixed size with ::string, then #read into it, or + * modify it using #set_value. * * Interaction with string and file memory is performed by efficient low-level * C mechanisms like `memcpy`. * * The class is meant to be an utility for implementing more high-level mechanisms - * like Fiber::SchedulerInterface#io_read and Fiber::SchedulerInterface#io_write. + * like Fiber::Scheduler#io_read and Fiber::Scheduler#io_write and parsing binary + * protocols. * - * <b>Examples of usage:</b> + * == Examples of Usage * * Empty buffer: * @@ -3191,25 +3572,23 @@ io_buffer_not_inplace(VALUE self) * \Buffer from string: * * string = 'data' - * buffer = IO::Buffer.for(string) - * # => - * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE> - * # ... - * buffer - * # => - * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE> - * # 0x00000000 64 61 74 61 data - * - * buffer.get_string(2) # read content starting from offset 2 - * # => "ta" - * buffer.set_string('---', 1) # write content, starting from offset 1 - * # => 3 - * buffer - * # => - * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE> - * # 0x00000000 64 2d 2d 2d d--- - * string # original string changed, too - * # => "d---" + * IO::Buffer.for(string) do |buffer| + * buffer + * # => + * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE> + * # 0x00000000 64 61 74 61 data + * + * buffer.get_string(2) # read content starting from offset 2 + * # => "ta" + * buffer.set_string('---', 1) # write content, starting from offset 1 + * # => 3 + * buffer + * # => + * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE> + * # 0x00000000 64 2d 2d 2d d--- + * string # original string changed, too + * # => "d---" + * end * * \Buffer from file: * @@ -3232,20 +3611,33 @@ io_buffer_not_inplace(VALUE self) * File.read('test.txt') * # => "t--- data" * - * <b>The class is experimental and the interface is subject to change.</b> + * <b>The class is experimental and the interface is subject to change, this + * is especially true of file mappings which may be removed entirely in + * the future.</b> */ void Init_IO_Buffer(void) { rb_cIOBuffer = rb_define_class_under(rb_cIO, "Buffer", rb_cObject); + + /* Raised when an operation would resize or re-allocate a locked buffer. */ rb_eIOBufferLockedError = rb_define_class_under(rb_cIOBuffer, "LockedError", rb_eRuntimeError); + + /* Raised when the buffer cannot be allocated for some reason, or you try to use a buffer that's not allocated. */ rb_eIOBufferAllocationError = rb_define_class_under(rb_cIOBuffer, "AllocationError", rb_eRuntimeError); + + /* Raised when you try to write to a read-only buffer, or resize an external buffer. */ rb_eIOBufferAccessError = rb_define_class_under(rb_cIOBuffer, "AccessError", rb_eRuntimeError); + + /* Raised if you try to access a buffer slice which no longer references a valid memory range of the underlying source. */ rb_eIOBufferInvalidatedError = rb_define_class_under(rb_cIOBuffer, "InvalidatedError", rb_eRuntimeError); + + /* Raised if the mask given to a binary operation is invalid, e.g. zero length or overlaps the target buffer. */ rb_eIOBufferMaskError = rb_define_class_under(rb_cIOBuffer, "MaskError", rb_eArgError); rb_define_alloc_func(rb_cIOBuffer, rb_io_buffer_type_allocate); rb_define_singleton_method(rb_cIOBuffer, "for", rb_io_buffer_type_for, 1); + rb_define_singleton_method(rb_cIOBuffer, "string", rb_io_buffer_type_string, 1); #ifdef _WIN32 SYSTEM_INFO info; @@ -3257,37 +3649,57 @@ Init_IO_Buffer(void) RUBY_IO_BUFFER_DEFAULT_SIZE = io_buffer_default_size(RUBY_IO_BUFFER_PAGE_SIZE); - // Efficient sizing of mapped buffers: + /* The operating system page size. Used for efficient page-aligned memory allocations. */ rb_define_const(rb_cIOBuffer, "PAGE_SIZE", SIZET2NUM(RUBY_IO_BUFFER_PAGE_SIZE)); + + /* The default buffer size, typically a (small) multiple of the PAGE_SIZE. + Can be explicitly specified by setting the RUBY_IO_BUFFER_DEFAULT_SIZE + environment variable. */ rb_define_const(rb_cIOBuffer, "DEFAULT_SIZE", SIZET2NUM(RUBY_IO_BUFFER_DEFAULT_SIZE)); rb_define_singleton_method(rb_cIOBuffer, "map", io_buffer_map, -1); - // General use: rb_define_method(rb_cIOBuffer, "initialize", rb_io_buffer_initialize, -1); rb_define_method(rb_cIOBuffer, "initialize_copy", rb_io_buffer_initialize_copy, 1); rb_define_method(rb_cIOBuffer, "inspect", rb_io_buffer_inspect, 0); - rb_define_method(rb_cIOBuffer, "hexdump", rb_io_buffer_hexdump, 0); + rb_define_method(rb_cIOBuffer, "hexdump", rb_io_buffer_hexdump, -1); rb_define_method(rb_cIOBuffer, "to_s", rb_io_buffer_to_s, 0); rb_define_method(rb_cIOBuffer, "size", rb_io_buffer_size, 0); rb_define_method(rb_cIOBuffer, "valid?", rb_io_buffer_valid_p, 0); - // Ownership: rb_define_method(rb_cIOBuffer, "transfer", rb_io_buffer_transfer, 0); - // Flags: + /* Indicates that the memory in the buffer is owned by someone else. See #external? for more details. */ rb_define_const(rb_cIOBuffer, "EXTERNAL", RB_INT2NUM(RB_IO_BUFFER_EXTERNAL)); + + /* Indicates that the memory in the buffer is owned by the buffer. See #internal? for more details. */ rb_define_const(rb_cIOBuffer, "INTERNAL", RB_INT2NUM(RB_IO_BUFFER_INTERNAL)); + + /* Indicates that the memory in the buffer is mapped by the operating system. See #mapped? for more details. */ rb_define_const(rb_cIOBuffer, "MAPPED", RB_INT2NUM(RB_IO_BUFFER_MAPPED)); + + /* Indicates that the memory in the buffer is also mapped such that it can be shared with other processes. See #shared? for more details. */ rb_define_const(rb_cIOBuffer, "SHARED", RB_INT2NUM(RB_IO_BUFFER_SHARED)); + + /* Indicates that the memory in the buffer is locked and cannot be resized or freed. See #locked? and #locked for more details. */ rb_define_const(rb_cIOBuffer, "LOCKED", RB_INT2NUM(RB_IO_BUFFER_LOCKED)); + + /* Indicates that the memory in the buffer is mapped privately and changes won't be replicated to the underlying file. See #private? for more details. */ rb_define_const(rb_cIOBuffer, "PRIVATE", RB_INT2NUM(RB_IO_BUFFER_PRIVATE)); + + /* Indicates that the memory in the buffer is read only, and attempts to modify it will fail. See #readonly? for more details.*/ rb_define_const(rb_cIOBuffer, "READONLY", RB_INT2NUM(RB_IO_BUFFER_READONLY)); - // Endian: + /* Refers to little endian byte order, where the least significant byte is stored first. See #get_value for more details. */ rb_define_const(rb_cIOBuffer, "LITTLE_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_LITTLE_ENDIAN)); + + /* Refers to big endian byte order, where the most significant byte is stored first. See #get_value for more details. */ rb_define_const(rb_cIOBuffer, "BIG_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_BIG_ENDIAN)); + + /* Refers to the byte order of the host machine. See #get_value for more details. */ rb_define_const(rb_cIOBuffer, "HOST_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_HOST_ENDIAN)); + + /* Refers to network byte order, which is the same as big endian. See #get_value for more details. */ rb_define_const(rb_cIOBuffer, "NETWORK_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_NETWORK_ENDIAN)); rb_define_method(rb_cIOBuffer, "null?", rb_io_buffer_null_p, 0); @@ -3297,6 +3709,7 @@ Init_IO_Buffer(void) rb_define_method(rb_cIOBuffer, "mapped?", rb_io_buffer_mapped_p, 0); rb_define_method(rb_cIOBuffer, "shared?", rb_io_buffer_shared_p, 0); rb_define_method(rb_cIOBuffer, "locked?", rb_io_buffer_locked_p, 0); + rb_define_method(rb_cIOBuffer, "private?", rb_io_buffer_private_p, 0); rb_define_method(rb_cIOBuffer, "readonly?", io_buffer_readonly_p, 0); // Locking to prevent changes while using pointer: @@ -3354,7 +3767,7 @@ Init_IO_Buffer(void) rb_define_method(rb_cIOBuffer, "get_string", io_buffer_get_string, -1); rb_define_method(rb_cIOBuffer, "set_string", io_buffer_set_string, -1); - // Binary data manipulations: + // Binary buffer manipulations: rb_define_method(rb_cIOBuffer, "&", io_buffer_and, 1); rb_define_method(rb_cIOBuffer, "|", io_buffer_or, 1); rb_define_method(rb_cIOBuffer, "^", io_buffer_xor, 1); |