/********************************************************************** io_buffer.c Copyright (C) 2021 Samuel Grant Dawson Williams **********************************************************************/ #include "ruby/io/buffer.h" #include "ruby/fiber/scheduler.h" #include "ruby/memory_view.h" // For `rb_nogvl`. #include "ruby/thread.h" #include "internal.h" #include "internal/array.h" #include "internal/bits.h" #include "internal/error.h" #include "internal/gc.h" #include "internal/numeric.h" #include "internal/string.h" #include "internal/io.h" #include "internal/io_buffer.h" VALUE rb_cIOBuffer; VALUE rb_cIOBufferStorage; VALUE rb_cIOBufferSlice; VALUE rb_eIOBufferLockedError; VALUE rb_eIOBufferAllocationError; VALUE rb_eIOBufferAccessError; VALUE rb_eIOBufferInvalidatedError; VALUE rb_eIOBufferMaskError; size_t RUBY_IO_BUFFER_PAGE_SIZE; size_t RUBY_IO_BUFFER_MAP_ALIGNMENT; size_t RUBY_IO_BUFFER_DEFAULT_SIZE; #ifdef _WIN32 #else #include #include #endif enum { RB_IO_BUFFER_HEXDUMP_DEFAULT_WIDTH = 16, RB_IO_BUFFER_HEXDUMP_MAXIMUM_WIDTH = 1024, 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_PRIVATE | RB_IO_BUFFER_READONLY, RB_IO_BUFFER_ALLOCATION_FLAGS = RB_IO_BUFFER_INTERNAL | RB_IO_BUFFER_MAPPED, RB_IO_BUFFER_MAPPING_FLAGS = RB_IO_BUFFER_SHARED | RB_IO_BUFFER_PRIVATE, // Private representation tag, never accepted or returned as a public flag. IO_BUFFER_SLICE = 1 << 9, RB_IO_BUFFER_DEBUG = 0, }; struct rb_io_buffer_view { // Without a source (source == Qnil), this is an absolute pointer to owned // or borrowed memory. Ownership is determined by the flags, not by the // presence of a source. // // With a source (String or IO::Buffer), this is a byte offset into it. // The absolute pointer is resolved as `source_base + offset` on demand // (see io_buffer_try_get_bytes), so source relocation preserves the // logical range. Use io_buffer_slice_offset() to read the offset. void *base; size_t size; enum rb_io_buffer_flags flags; VALUE source; }; struct rb_io_buffer_storage { struct rb_io_buffer_view view; // Only Storage objects carry allocation-lifetime state, even for borrowed // storage. Slice objects contain only the common view header above. size_t lock_count; #ifdef _WIN32 HANDLE mapping; #endif }; static bool io_buffer_slice_p(const struct rb_io_buffer_view *buffer) { return (buffer->flags & IO_BUFFER_SLICE) != 0; } // Checked downcast of this view; unlike io_buffer_lock_owner, it does not // traverse sources. Only Storage objects contain the storage-management tail. static struct rb_io_buffer_storage * io_buffer_storage_from_view(struct rb_io_buffer_view *view) { if (io_buffer_slice_p(view)) { rb_raise(rb_eIOBufferAccessError, "Slice does not manage an allocation!"); } return (struct rb_io_buffer_storage *)view; } static inline void * io_buffer_map_memory(size_t size, int flags) { #if defined(_WIN32) void * base = VirtualAlloc(0, size, MEM_COMMIT, PAGE_READWRITE); if (!base) { rb_sys_fail("io_buffer_map_memory:VirtualAlloc"); } #else int mmap_flags = MAP_ANONYMOUS; if (flags & RB_IO_BUFFER_SHARED) { mmap_flags |= MAP_SHARED; } else { mmap_flags |= MAP_PRIVATE; } void * base = mmap(NULL, size, PROT_READ | PROT_WRITE, mmap_flags, -1, 0); if (base == MAP_FAILED) { rb_sys_fail("io_buffer_map_memory:mmap"); } ruby_annotate_mmap(base, size, "Ruby:io_buffer_map_memory"); #endif return base; } static void io_buffer_storage_map_file(struct rb_io_buffer_storage *storage, int descriptor, size_t size, rb_off_t offset, enum rb_io_buffer_flags flags) { struct rb_io_buffer_view *buffer = &storage->view; #if defined(_WIN32) HANDLE file = (HANDLE)_get_osfhandle(descriptor); if (!file) rb_sys_fail("io_buffer_map_descriptor:_get_osfhandle"); DWORD protect = PAGE_READONLY, access = FILE_MAP_READ; if (flags & RB_IO_BUFFER_READONLY) { buffer->flags |= RB_IO_BUFFER_READONLY; } else { protect = PAGE_READWRITE; access = FILE_MAP_WRITE; } if (flags & RB_IO_BUFFER_PRIVATE) { protect = PAGE_WRITECOPY; access = FILE_MAP_COPY; buffer->flags |= RB_IO_BUFFER_PRIVATE; } else { // 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_storage_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) { CloseHandle(mapping); rb_sys_fail("io_buffer_storage_map_file:MapViewOfFile"); } storage->mapping = mapping; #else int protect = PROT_READ, access = 0; if (flags & RB_IO_BUFFER_READONLY) { buffer->flags |= RB_IO_BUFFER_READONLY; } else { protect |= PROT_WRITE; } if (flags & RB_IO_BUFFER_PRIVATE) { buffer->flags |= RB_IO_BUFFER_PRIVATE; access |= MAP_PRIVATE; } else { // This buffer refers to external buffer. buffer->flags |= RB_IO_BUFFER_EXTERNAL; buffer->flags |= RB_IO_BUFFER_SHARED; access |= MAP_SHARED; } void *base = mmap(NULL, size, protect, access, descriptor, offset); if (base == MAP_FAILED) { rb_sys_fail("io_buffer_storage_map_file:mmap"); } #endif buffer->base = base; buffer->size = size; buffer->flags |= RB_IO_BUFFER_MAPPED; buffer->flags |= RB_IO_BUFFER_FILE; } static void io_buffer_experimental(void) { static int warned = 0; if (warned) return; warned = 1; if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL)) { rb_category_warn(RB_WARN_CATEGORY_EXPERIMENTAL, "IO::Buffer is experimental and both the Ruby and C interface may change in the future!" ); } } static void io_buffer_storage_zero(struct rb_io_buffer_storage *storage) { storage->view.base = NULL; storage->view.size = 0; storage->view.flags = 0; storage->view.source = Qnil; storage->lock_count = 0; #if defined(_WIN32) storage->mapping = NULL; #endif } static void io_buffer_storage_initialize(VALUE self, struct rb_io_buffer_storage *storage, void *base, size_t size, enum rb_io_buffer_flags flags, VALUE source) { struct rb_io_buffer_view *buffer = &storage->view; if (source != Qnil) { // The buffer is backed by another object (e.g. a String). Here `base` // is a byte *offset* into that source rather than an absolute pointer, // and the memory is not owned by this buffer. The absolute base is // resolved on demand as `source_base + offset` (see // io_buffer_try_get_bytes), so it stays valid if the source moves. } else if (base) { // If we are provided a pointer, we use it. } else if (size) { // If we are provided a non-zero size, we allocate it: if (flags & RB_IO_BUFFER_INTERNAL) { base = calloc(size, 1); } else if (flags & RB_IO_BUFFER_MAPPED) { base = io_buffer_map_memory(size, flags); } if (!base) { rb_raise(rb_eIOBufferAllocationError, "Could not allocate buffer!"); } } else { // Otherwise we don't do anything. return; } buffer->base = base; buffer->size = size; // Public flags cannot change the payload's representation. buffer->flags = flags & ~IO_BUFFER_SLICE; storage->lock_count = 0; RB_OBJ_WRITE(self, &buffer->source, source); #if defined(_WIN32) storage->mapping = NULL; #endif } static void io_buffer_storage_release(struct rb_io_buffer_storage *storage) { struct rb_io_buffer_view *buffer = &storage->view; if (buffer->base) { if (buffer->flags & RB_IO_BUFFER_INTERNAL) { free(buffer->base); } 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(buffer->source, T_STRING)) { // rb_str_unlocktmp(buffer->source); // } } #if defined(_WIN32) if (storage->mapping) { if (RB_IO_BUFFER_DEBUG) fprintf(stderr, "io_buffer_storage_release:CloseHandle -> %p\n", storage->mapping); if (!CloseHandle(storage->mapping)) { fprintf(stderr, "io_buffer_storage_release:GetLastError -> %lu\n", GetLastError()); } storage->mapping = NULL; } #endif io_buffer_storage_zero(storage); } static void rb_io_buffer_type_mark(void *_buffer) { struct rb_io_buffer_view *buffer = _buffer; if (buffer->source != Qnil) { if (RB_TYPE_P(buffer->source, T_STRING)) { // The `source` String has to be pinned, because the `base` may point to the embedded String content, // which can be otherwise moved by GC compaction. rb_gc_mark(buffer->source); } else { rb_gc_mark_movable(buffer->source); } } } static void rb_io_buffer_type_compact(void *_buffer) { struct rb_io_buffer_view *buffer = _buffer; if (buffer->source != Qnil) { if (RB_TYPE_P(buffer->source, T_STRING)) { // The `source` String has to be pinned, because the `base` may point to the embedded String content, // which can be otherwise moved by GC compaction. } else { rb_gc_update_moved(&buffer->source); } } } static void rb_io_buffer_type_free(void *_buffer) { struct rb_io_buffer_view *buffer = _buffer; if (!io_buffer_slice_p(buffer)) io_buffer_storage_release(io_buffer_storage_from_view(buffer)); } static size_t rb_io_buffer_type_size(const void *_buffer) { const struct rb_io_buffer_view *buffer = _buffer; size_t total = io_buffer_slice_p(buffer) ? sizeof(struct rb_io_buffer_view) : sizeof(struct rb_io_buffer_storage); if (buffer->flags & RB_IO_BUFFER_ALLOCATION_FLAGS) { total += buffer->size; } return total; } static const rb_data_type_t rb_io_buffer_type = { .wrap_struct_name = "IO::Buffer", .function = { .dmark = rb_io_buffer_type_mark, .dfree = rb_io_buffer_type_free, .dsize = rb_io_buffer_type_size, .dcompact = rb_io_buffer_type_compact, }, .data = NULL, .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, }; int rb_io_buffer_p(VALUE self) { return rb_typeddata_is_kind_of(self, &rb_io_buffer_type); } // Shared byte-access interface: accepts both concrete Buffer representations. static struct rb_io_buffer_view * get_io_buffer_view(VALUE self) { struct rb_io_buffer_view *view; TypedData_Get_Struct(self, struct rb_io_buffer_view, &rb_io_buffer_type, view); return view; } // Allocation-management interface: requires Storage. static struct rb_io_buffer_storage * get_io_buffer_storage(VALUE self) { return io_buffer_storage_from_view(get_io_buffer_view(self)); } // Whether this buffer owns an allocation and is responsible for releasing it. // Slices, String-backed buffers, and raw borrowed-memory wrappers do not own // their memory. A source-less buffer is not necessarily an owning buffer. static inline bool io_buffer_owning_p(const struct rb_io_buffer_view *buffer) { return !io_buffer_slice_p(buffer) && (buffer->flags & RB_IO_BUFFER_ALLOCATION_FLAGS) != 0; } // For a slice (io_buffer_slice_p), the `base` field stores the byte offset of // the slice within its immediate source rather than an absolute pointer. static inline size_t io_buffer_slice_offset(const struct rb_io_buffer_view *buffer) { return (uintptr_t)buffer->base; } // Follow the source chain to the buffer which owns the allocation lock count. // String-backed buffers manage their own lock count and pin their String source. static struct rb_io_buffer_storage * io_buffer_lock_owner(struct rb_io_buffer_view *buffer) { while (io_buffer_slice_p(buffer)) { if (NIL_P(buffer->source)) rb_raise(rb_eIOBufferInvalidatedError, "Uninitialized slice!"); buffer = get_io_buffer_view(buffer->source); } return io_buffer_storage_from_view(buffer); } static bool io_buffer_locked(struct rb_io_buffer_view *buffer) { if (io_buffer_slice_p(buffer) && NIL_P(buffer->source)) return false; return io_buffer_lock_owner(buffer)->lock_count > 0; } 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; } static inline enum rb_io_buffer_flags io_buffer_flags_for_map(enum rb_io_buffer_flags flags) { if (flags & RB_IO_BUFFER_INTERNAL) { rb_raise(rb_eArgError, "IO::Buffer::INTERNAL can't be used with IO::Buffer.map!"); } if (flags & RB_IO_BUFFER_EXTERNAL) { rb_raise(rb_eArgError, "IO::Buffer::EXTERNAL can't be used with IO::Buffer.map!"); } if ((flags & RB_IO_BUFFER_MAPPING_FLAGS) == RB_IO_BUFFER_MAPPING_FLAGS) { rb_raise(rb_eArgError, "Flags can't include both IO::Buffer::SHARED and IO::Buffer::PRIVATE!"); } return flags; } // 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 an amount, which must be a non-negative integer. static inline size_t io_buffer_extract_amount(VALUE argument) { if (rb_int_negative_p(argument)) { rb_raise(rb_eArgError, "Amount 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 and at most RB_IO_BUFFER_HEXDUMP_MAXIMUM_WIDTH. 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); } if (width > RB_IO_BUFFER_HEXDUMP_MAXIMUM_WIDTH) { rb_raise(rb_eArgError, "Width must be at most %" PRIuSIZE "!", (size_t)RB_IO_BUFFER_HEXDUMP_MAXIMUM_WIDTH); } 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_view *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 offset and length arguments, returning the buffer. // The offset and length are optional, but if they are provided, they must be // positive integers. If the offset is not provided, it defaults to zero. If // the length is not provided, it defaults to the buffer size minus the offset. static inline struct rb_io_buffer_view * io_buffer_extract_offset_length(VALUE self, int argc, VALUE argv[], size_t *offset, size_t *length) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); 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) { io_buffer_experimental(); struct rb_io_buffer_storage *buffer = NULL; VALUE instance = TypedData_Make_Struct(self, struct rb_io_buffer_storage, &rb_io_buffer_type, buffer); io_buffer_storage_zero(buffer); return instance; } static VALUE io_buffer_slice_allocate(VALUE klass) { struct rb_io_buffer_view *buffer; VALUE instance = TypedData_Make_Struct(klass, struct rb_io_buffer_view, &rb_io_buffer_type, buffer); buffer->flags = (enum rb_io_buffer_flags)IO_BUFFER_SLICE; buffer->source = Qnil; return instance; } /* * call-seq: * IO::Buffer.new([size, [flags]]) -> storage * * Creates an IO::Buffer::Storage with the given size and flags. Buffer is the * common byte-view interface; this factory creates one storage-bearing object, * not a separate view and allocation. See IO::Buffer::Storage.new. */ static VALUE io_buffer_s_new(int argc, VALUE *argv, VALUE klass) { if (klass == rb_cIOBuffer) klass = rb_cIOBufferStorage; return rb_class_new_instance_kw(argc, argv, klass, RB_PASS_CALLED_KEYWORDS); } static VALUE io_buffer_storage_class(VALUE klass) { if (klass == rb_cIOBuffer) return rb_cIOBufferStorage; if (RTEST(rb_class_inherited_p(klass, rb_cIOBufferStorage))) return klass; rb_raise(rb_eTypeError, "Storage factory requires IO::Buffer::Storage!"); } 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); flags |= RB_IO_BUFFER_EXTERNAL; if (RB_OBJ_FROZEN(string)) flags |= RB_IO_BUFFER_READONLY; if (!(flags & RB_IO_BUFFER_READONLY)) rb_str_modify(string); // The whole String, starting at offset zero. struct rb_io_buffer_storage *buffer = get_io_buffer_storage(instance); io_buffer_storage_initialize(instance, buffer, NULL, RSTRING_LEN(string), flags, string); return instance; } struct io_buffer_for_yield_instance_arguments { VALUE klass; VALUE string; VALUE instance; enum rb_io_buffer_flags flags; }; static VALUE io_buffer_for_yield_instance(VALUE _arguments) { struct io_buffer_for_yield_instance_arguments *arguments = (struct io_buffer_for_yield_instance_arguments *)_arguments; arguments->instance = io_buffer_for_make_instance(arguments->klass, arguments->string, arguments->flags); if (!RB_OBJ_FROZEN(arguments->string)) { rb_str_locktmp(arguments->string); } return rb_yield(arguments->instance); } static VALUE io_buffer_for_yield_instance_ensure(VALUE _arguments) { struct io_buffer_for_yield_instance_arguments *arguments = (struct io_buffer_for_yield_instance_arguments *)_arguments; if (arguments->instance != Qnil) { rb_io_buffer_free(arguments->instance); } if (!RB_OBJ_FROZEN(arguments->string)) { rb_str_unlocktmp(arguments->string); } return Qnil; } struct io_buffer_for_callback_arguments { VALUE klass; VALUE string; VALUE instance; enum rb_io_buffer_flags flags; int locked; VALUE (*callback)(VALUE, VALUE); VALUE argument; }; static VALUE rb_io_buffer_locked_ensure(VALUE self); struct io_buffer_for_locked_callback_arguments { VALUE buffer; VALUE (*callback)(VALUE, VALUE); VALUE argument; }; static VALUE io_buffer_for_locked_callback_call(VALUE _arguments) { struct io_buffer_for_locked_callback_arguments *arguments = (void *)_arguments; return arguments->callback(arguments->buffer, arguments->argument); } static VALUE io_buffer_for_locked_callback(VALUE buffer, VALUE (*callback)(VALUE, VALUE), VALUE argument) { struct io_buffer_for_locked_callback_arguments arguments = { .buffer = buffer, .callback = callback, .argument = argument, }; rb_io_buffer_lock(buffer); return rb_ensure(io_buffer_for_locked_callback_call, (VALUE)&arguments, rb_io_buffer_locked_ensure, buffer); } static VALUE io_buffer_for_callback_call(VALUE _arguments) { struct io_buffer_for_callback_arguments *arguments = (struct io_buffer_for_callback_arguments *)_arguments; arguments->instance = io_buffer_for_make_instance(arguments->klass, arguments->string, arguments->flags); if (!RB_OBJ_FROZEN(arguments->string)) { rb_str_locktmp(arguments->string); arguments->locked = 1; } return io_buffer_for_locked_callback(arguments->instance, arguments->callback, arguments->argument); } static VALUE io_buffer_for_callback_ensure(VALUE _arguments) { struct io_buffer_for_callback_arguments *arguments = (struct io_buffer_for_callback_arguments *)_arguments; if (arguments->instance != Qnil) { rb_io_buffer_free(arguments->instance); } if (arguments->locked) { rb_str_unlocktmp(arguments->string); } return Qnil; } VALUE rb_io_buffer_for_reading(VALUE string_or_buffer, VALUE (*callback)(VALUE, VALUE), VALUE argument) { if (rb_io_buffer_p(string_or_buffer)) { return io_buffer_for_locked_callback(string_or_buffer, callback, argument); } else if (RB_TYPE_P(string_or_buffer, T_STRING)) { StringValue(string_or_buffer); struct io_buffer_for_callback_arguments arguments = { .klass = rb_cIOBufferStorage, .string = string_or_buffer, .instance = Qnil, .flags = RB_IO_BUFFER_READONLY, .locked = 0, .callback = callback, .argument = argument, }; return rb_ensure(io_buffer_for_callback_call, (VALUE)&arguments, io_buffer_for_callback_ensure, (VALUE)&arguments); } else { rb_raise(rb_eTypeError, "expected String or IO::Buffer, not %"PRIsVALUE, rb_obj_class(string_or_buffer)); } } /* Forward declaration: io_buffer_readonly_p is defined later in this file. */ static int io_buffer_readonly_p(const struct rb_io_buffer_view *buffer); VALUE rb_io_buffer_for_writing(VALUE string_or_buffer, VALUE (*callback)(VALUE, VALUE), VALUE argument) { if (rb_io_buffer_p(string_or_buffer)) { struct rb_io_buffer_view *buffer = get_io_buffer_view(string_or_buffer); if (io_buffer_readonly_p(buffer)) { rb_raise(rb_eArgError, "buffer is read-only"); } return io_buffer_for_locked_callback(string_or_buffer, callback, argument); } else if (RB_TYPE_P(string_or_buffer, T_STRING)) { StringValue(string_or_buffer); struct io_buffer_for_callback_arguments arguments = { .klass = rb_cIOBufferStorage, .string = string_or_buffer, .instance = Qnil, .flags = 0, .locked = 0, .callback = callback, .argument = argument, }; return rb_ensure(io_buffer_for_callback_call, (VALUE)&arguments, io_buffer_for_callback_ensure, (VALUE)&arguments); } else { rb_raise(rb_eTypeError, "expected String or IO::Buffer, not %"PRIsVALUE, rb_obj_class(string_or_buffer)); } } /* * call-seq: * IO::Buffer.for(string) -> readonly io_buffer * IO::Buffer.for(string) {|io_buffer| ... read/write io_buffer ...} * * Creates a zero-copy IO::Buffer from the given string's memory. Without a * block, a frozen snapshot of the string is used as the buffer source, so * later changes to the original string do not affect the buffer. When a block * is provided, the buffer is associated directly with the string's internal * buffer and updating the buffer will update the string. * * In the block form, the string is locked and cannot be modified while the * block is executing. * * If the string is frozen, it will create a read-only buffer which cannot be * 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) * buffer.external? #=> true * * buffer.get_string(0, 1) * # => "t" * string * # => "test" * * buffer.resize(100) * # in `resize': Cannot resize external buffer! (IO::Buffer::AccessError) * * IO::Buffer.for(string) do |buffer| * buffer.set_string("T") * string * # => "Test" * end */ VALUE rb_io_buffer_type_for(VALUE klass, VALUE string) { klass = io_buffer_storage_class(klass); StringValue(string); // If the string is frozen, both code paths are okay. // If the string is not frozen, if a block is not given, it must be frozen. if (rb_block_given_p()) { struct io_buffer_for_yield_instance_arguments arguments = { .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); } else { // Use a Ruby-visible frozen snapshot as the backing source. A hidden // temporary frozen String cannot be returned by IO::Buffer#source. string = rb_str_new_frozen(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) { klass = io_buffer_storage_class(klass); VALUE string = rb_str_new(NULL, RB_NUM2LEN(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_cIOBufferStorage); struct rb_io_buffer_storage *buffer = get_io_buffer_storage(instance); io_buffer_storage_initialize(instance, buffer, base, size, flags, Qnil); return instance; } VALUE rb_io_buffer_new_locked(void *base, size_t size, enum rb_io_buffer_flags flags) { VALUE instance = rb_io_buffer_new(base, size, flags); rb_io_buffer_lock(instance); return instance; } static VALUE io_buffer_storage_map(VALUE klass, VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags flags) { if (UNLIKELY(offset < 0)) { rb_raise(rb_eArgError, "Offset (%" PRIsVALUE ") can't be negative!", OFFT2NUM(offset)); } if (UNLIKELY((uintmax_t)offset % RUBY_IO_BUFFER_MAP_ALIGNMENT != 0)) { rb_raise(rb_eArgError, "Offset (%" PRIsVALUE ") must be a multiple of IO::Buffer::MAP_ALIGNMENT (%" PRIuSIZE ")!", OFFT2NUM(offset), RUBY_IO_BUFFER_MAP_ALIGNMENT); } VALUE instance = rb_io_buffer_type_allocate(klass); int descriptor = rb_io_descriptor(io); struct rb_io_buffer_storage *storage = get_io_buffer_storage(instance); io_buffer_storage_map_file(storage, descriptor, size, offset, flags); return instance; } VALUE rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags flags) { return io_buffer_storage_map(rb_cIOBufferStorage, io, size, offset, flags); } /* * call-seq: IO::Buffer.map(file, [size, [offset, [flags]]]) -> io_buffer * * Create an IO::Buffer for reading from +file+ by memory-mapping the file. * +file+ should be a +File+ instance, opened for reading or reading and writing. * * Optional +size+ and +offset+ of mapping can be specified. The +offset+ must * be a multiple of IO::Buffer::MAP_ALIGNMENT. The +size+ does not need to be * aligned. Trying to map an empty file or specify +size+ of 0 will raise an * error. * * By default, the buffer is writable and expects the file to be writable. * It is also shared, so several processes can use the same mapping. * * The mapping mode may be explicitly selected with IO::Buffer::SHARED or * IO::Buffer::PRIVATE, but the two flags are mutually exclusive. * IO::Buffer::MAPPED is accepted but redundant because this method always * creates a mapped buffer. IO::Buffer::INTERNAL and IO::Buffer::EXTERNAL * cannot be specified. * * You can pass IO::Buffer::READONLY in +flags+ argument to make a read-only buffer; * this allows to work with files opened only for reading. * Specifying IO::Buffer::PRIVATE in +flags+ creates a private mapping, * which will not impact other processes or the underlying file. * It also allows updating a buffer created from a read-only file. * * File.write('test.txt', 'test') * * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) * # => # * * buffer.readonly? # => true * * buffer.get_string * # => "test" * * buffer.set_string('b', 0) * # 'IO::Buffer#set_string': Buffer is not writable! (IO::Buffer::AccessError) * * # create read/write mapping: length 4 bytes, offset 0, flags 0 * buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 4, 0) * buffer.set_string('b', 0) * # => 1 * * # Check it * File.read('test.txt') * # => "best" * * Note that some operating systems may not have cache coherency between mapped * buffers and file reads. */ static VALUE io_buffer_map(int argc, VALUE *argv, VALUE klass) { klass = io_buffer_storage_class(klass); rb_check_arity(argc, 1, 4); // We might like to handle a string path? VALUE io = argv[0]; rb_off_t file_size = rb_file_size(io); // Compiler can confirm that we handled file_size <= 0 case: if (UNLIKELY(file_size <= 0)) { rb_raise(rb_eArgError, "Invalid negative or zero file size!"); } // Here, we assume that file_size is positive: else if (UNLIKELY((uintmax_t)file_size > SIZE_MAX)) { rb_raise(rb_eArgError, "File larger than address space!"); } size_t size; if (argc >= 2 && !RB_NIL_P(argv[1])) { size = io_buffer_extract_size(argv[1]); if (UNLIKELY(size == 0)) { rb_raise(rb_eArgError, "Size can't be zero!"); } if (UNLIKELY(size > (size_t)file_size)) { rb_raise(rb_eArgError, "Size (%" PRIuSIZE ") can't be larger than " "file size (%" PRIuSIZE ")", size, (size_t)file_size); } } else { // This conversion should be safe: size = (size_t)file_size; } // This is the file offset, not the buffer offset: rb_off_t offset = 0; if (argc >= 3) { offset = NUM2OFFT(argv[2]); if (UNLIKELY(offset < 0)) { rb_raise(rb_eArgError, "Offset (%" PRIsVALUE ") can't be negative!", argv[2]); } if (UNLIKELY(offset >= file_size)) { rb_raise(rb_eArgError, "Offset (%" PRIsVALUE ") can't be larger than " "file size (%" PRIuSIZE ")", argv[2], (size_t)file_size); } if (RB_NIL_P(argv[1])) { // Decrease size if it's set from the actual file size: size = (size_t)(file_size - offset); } else if (UNLIKELY((size_t)(file_size - offset) < size)) { size_t maximum_offset = ((size_t)file_size - size) / RUBY_IO_BUFFER_MAP_ALIGNMENT * RUBY_IO_BUFFER_MAP_ALIGNMENT; rb_raise(rb_eArgError, "Offset (%" PRIsVALUE ") can't be larger than " "%" PRIuSIZE " for requested size (%" PRIuSIZE ")", argv[2], maximum_offset, size); } } enum rb_io_buffer_flags flags = 0; if (argc >= 4) { flags = io_buffer_extract_flags(argv[3]); } flags = io_buffer_flags_for_map(flags); return io_buffer_storage_map(klass, io, size, offset, flags); } // Compute the optimal allocation flags for a buffer of the given size. static inline enum rb_io_buffer_flags io_flags_for_size(size_t size) { if (size >= RUBY_IO_BUFFER_PAGE_SIZE) { return RB_IO_BUFFER_MAPPED; } return RB_IO_BUFFER_INTERNAL; } static inline enum rb_io_buffer_flags io_buffer_flags_for_new(enum rb_io_buffer_flags flags, size_t size) { if (size == 0) { // A null buffer has no allocation and therefore no allocation flags: return 0; } if (!(flags & RB_IO_BUFFER_ALLOCATION_FLAGS)) { if (flags & RB_IO_BUFFER_MAPPING_FLAGS) { // Mapping properties imply a mapped allocation: flags |= RB_IO_BUFFER_MAPPED; } else { // No explicit allocation mode was given, so infer one from size: flags |= io_flags_for_size(size); } } enum rb_io_buffer_flags allocation = flags & RB_IO_BUFFER_ALLOCATION_FLAGS; RUBY_ASSERT(allocation != 0); if ((unsigned int)allocation == RB_IO_BUFFER_ALLOCATION_FLAGS) { rb_raise(rb_eArgError, "Flags can't include both IO::Buffer::INTERNAL and IO::Buffer::MAPPED!"); } if (flags & RB_IO_BUFFER_EXTERNAL) { rb_raise(rb_eArgError, "IO::Buffer::EXTERNAL can't be used with IO::Buffer.new!"); } if ((flags & RB_IO_BUFFER_MAPPING_FLAGS) && allocation != RB_IO_BUFFER_MAPPED) { rb_raise(rb_eArgError, "IO::Buffer::SHARED and IO::Buffer::PRIVATE require IO::Buffer::MAPPED!"); } if ((flags & RB_IO_BUFFER_MAPPING_FLAGS) == RB_IO_BUFFER_MAPPING_FLAGS) { rb_raise(rb_eArgError, "Flags can't include both IO::Buffer::SHARED and IO::Buffer::PRIVATE!"); } return flags; } /* * call-seq: IO::Buffer.new([size = DEFAULT_SIZE, [flags]]) -> io_buffer * * Create a new zero-filled IO::Buffer of +size+ bytes. * By default, the buffer will be _internal_: directly allocated chunk * of the memory. But if the requested +size+ is more than OS-specific * IO::Buffer::PAGE_SIZE, the buffer would be allocated using the * virtual memory mechanism (anonymous +mmap+ on Unix, +VirtualAlloc+ * on Windows). The behavior can be forced by passing IO::Buffer::MAPPED * as a second parameter. * * IO::Buffer::SHARED and IO::Buffer::PRIVATE imply IO::Buffer::MAPPED and are * mutually exclusive. Otherwise, if +flags+ do not include an allocation * mode, IO::Buffer::INTERNAL or IO::Buffer::MAPPED is inferred from the * requested size. The two allocation modes are mutually exclusive. * * buffer = IO::Buffer.new(4) * # => * # # * # 0x00000000 00 00 00 00 .... * * buffer.get_string(0, 1) # => "\x00" * * buffer.set_string("test") * buffer * # => * # # * # 0x00000000 74 65 73 74 test */ VALUE rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self) { rb_check_frozen(self); rb_check_arity(argc, 0, 2); size_t size; if (argc > 0) { size = io_buffer_extract_size(argv[0]); } else { size = RUBY_IO_BUFFER_DEFAULT_SIZE; } enum rb_io_buffer_flags flags = 0; if (argc >= 2) { flags = io_buffer_extract_flags(argv[1]); } flags = io_buffer_flags_for_new(flags, size); struct rb_io_buffer_storage *buffer = get_io_buffer_storage(self); if (buffer->lock_count) rb_raise(rb_eIOBufferLockedError, "Cannot initialize locked buffer!"); io_buffer_storage_release(buffer); io_buffer_storage_initialize(self, buffer, NULL, size, flags, Qnil); return self; } // Resolve `source_base + buffer->base`, with a source base of zero for // source-less buffers (see struct rb_io_buffer_view). Source-backed ranges are // checked against the source's current size, so they survive relocation and // can become valid again after the source regrows. // Returns non-zero if valid, setting `*base`/`*size` (NULL/0 when invalid). // This only inspects metadata and does not enforce read/write permissions; // the accessors below check permissions and expose the appropriate pointer type. // Valid empty ranges may resolve to NULL. Keep the storage locked while using // a resolved pointer across calls that can release the GVL or invoke Ruby. static int io_buffer_try_get_bytes(const struct rb_io_buffer_view *buffer, void **base, size_t *size) { size_t length = buffer->size; size_t offset = 0; void *source_base = NULL; while (buffer->source != Qnil) { const struct rb_io_buffer_view *source_buffer = NULL; size_t source_size; if (io_buffer_slice_p(buffer)) { source_buffer = get_io_buffer_view(buffer->source); source_size = source_buffer->size; } else { // A String-backed buffer is rooted in the pinned String. RSTRING_GETMEM(buffer->source, source_base, source_size); } size_t relative_offset = io_buffer_slice_offset(buffer); if (relative_offset > source_size || buffer->size > source_size - relative_offset || relative_offset > SIZE_MAX - offset) { *base = NULL; *size = 0; return 0; } offset += relative_offset; if (!source_buffer) { *base = source_base ? (char *)source_base + offset : NULL; *size = length; return 1; } buffer = source_buffer; } // A source-less buffer (allocated, mapped, or borrowed) contributes the // absolute base pointer. The accumulated offset selects a range within it. if (io_buffer_slice_p(buffer)) { // A slice with no source can only be an uninitialized object. *base = NULL; *size = 0; return 0; } *base = buffer->base ? (char *)buffer->base + offset : NULL; *size = length; return 1; } static int io_buffer_validate(const struct rb_io_buffer_view *buffer) { void *base; size_t size; return io_buffer_try_get_bytes(buffer, &base, &size); } enum rb_io_buffer_flags rb_io_buffer_get_bytes(VALUE self, void **base, size_t *size) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); if (io_buffer_try_get_bytes(buffer, base, size)) { enum rb_io_buffer_flags flags = buffer->flags & ~IO_BUFFER_SLICE; if (io_buffer_readonly_p(buffer)) flags |= RB_IO_BUFFER_READONLY; return flags; } return 0; } static inline void io_buffer_get_bytes_for_writing(const struct rb_io_buffer_view *buffer, void **base, size_t *size) { if (io_buffer_readonly_p(buffer)) { rb_raise(rb_eIOBufferAccessError, "Buffer is not writable!"); } if (!io_buffer_validate(buffer)) { rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!"); } io_buffer_try_get_bytes(buffer, base, size); } static void io_buffer_validate_for_writing(const struct rb_io_buffer_view *buffer) { void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); } static struct rb_io_buffer_view * get_io_buffer_view_for_writing(VALUE self) { rb_check_frozen(self); struct rb_io_buffer_view *buffer = get_io_buffer_view(self); io_buffer_validate_for_writing(buffer); return buffer; } void rb_io_buffer_get_bytes_for_writing(VALUE self, void **base, size_t *size) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); io_buffer_get_bytes_for_writing(buffer, base, size); } static void io_buffer_get_bytes_for_reading(const struct rb_io_buffer_view *buffer, const void **base, size_t *size) { void *writable_base = NULL; if (!io_buffer_try_get_bytes(buffer, &writable_base, size)) { rb_raise(rb_eIOBufferInvalidatedError, "Buffer has been invalidated!"); } *base = writable_base; } static void io_buffer_validate_for_reading(const struct rb_io_buffer_view *buffer) { const void *base; size_t size; io_buffer_get_bytes_for_reading(buffer, &base, &size); } void rb_io_buffer_get_bytes_for_reading(VALUE self, const void **base, size_t *size) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); io_buffer_get_bytes_for_reading(buffer, base, size); } /* * call-seq: to_s -> string * * Short representation of the buffer. It includes the address, size and * symbolic flags. This format is subject to change. * * puts IO::Buffer.new(4) # uses to_s internally * # # */ VALUE rb_io_buffer_to_s(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); VALUE result = rb_str_new_cstr("#<"); rb_str_append(result, rb_class_name(CLASS_OF(self))); // Resolve the current base (following slice indirection) for display: void *base = NULL; size_t size = 0; io_buffer_try_get_bytes(buffer, &base, &size); rb_str_catf(result, " %p+%"PRIdSIZE, base, buffer->size); if (base == NULL) { rb_str_cat2(result, " NULL"); } if (buffer->flags & RB_IO_BUFFER_EXTERNAL) { rb_str_cat2(result, " EXTERNAL"); } if (buffer->flags & RB_IO_BUFFER_INTERNAL) { rb_str_cat2(result, " INTERNAL"); } if (buffer->flags & RB_IO_BUFFER_MAPPED) { rb_str_cat2(result, " MAPPED"); } 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 (io_buffer_locked(buffer)) { rb_str_cat2(result, " LOCKED"); } if (buffer->flags & RB_IO_BUFFER_PRIVATE) { rb_str_cat2(result, " PRIVATE"); } if (io_buffer_readonly_p(buffer)) { rb_str_cat2(result, " READONLY"); } if (buffer->source != Qnil) { rb_str_cat2(result, " SLICE"); } 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, const char *base, size_t length, size_t offset, int first) { char *text = alloca(width+1); text[width] = '\0'; for (; offset < length; offset += width) { memset(text, '\0', width); if (first) { rb_str_catf(string, "0x%08" PRIxSIZE " ", offset); first = 0; } else { rb_str_catf(string, "\n0x%08" PRIxSIZE " ", offset); } for (size_t i = 0; i < width; i += 1) { if (offset+i < length) { unsigned char value = ((unsigned char*)base)[offset+i]; if (value < 127 && isprint(value)) { text[i] = (char)value; } else { text[i] = '.'; } rb_str_catf(string, " %02x", value); } else { rb_str_cat2(string, " "); } } rb_str_catf(string, " %s", text); } return string; } /* * 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 * # # * # 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_view *buffer = get_io_buffer_view(self); VALUE result = rb_io_buffer_to_s(self); void *base = NULL; size_t total = 0; if (io_buffer_try_get_bytes(buffer, &base, &total) && base) { // 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, base, size, 0, 0); if (clamped) { rb_str_catf(result, "\n(and %" PRIuSIZE " more bytes not printed)", buffer->size - size); } } return result; } /* * call-seq: size -> integer * * Returns the size of the buffer that was explicitly set (on creation with ::new * or on #resize), or deduced on buffer's creation from string or file. */ VALUE rb_io_buffer_size(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); return SIZET2NUM(buffer->size); } /* * call-seq: valid? -> true or false * * A buffer without a source is always valid, including a null buffer. A * source-backed buffer is valid when its offset and length fit within its * source's current size. * * Relocating a source does not invalidate a source-backed buffer. Freeing, * transferring, or shrinking the source can make it invalid; if the same * source later grows to include the range again, the buffer becomes valid * and refers to the current contents at its original offset. * * An empty source-backed range at offset zero can be valid even when its * source has no storage. #valid?, #null? and #empty? describe distinct * properties: a buffer can be valid, null, and empty at the same time. */ static VALUE rb_io_buffer_valid_p(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); return RBOOL(io_buffer_validate(buffer)); } /* * call-seq: null? -> true or false * * Returns whether the buffer has no recorded base address. * * A buffer is null if it was freed with #free, transferred with #transfer, or * was never allocated in the first place. A zero-sized buffer or slice may * have a non-null address, so #null? and #empty? are distinct properties. * * 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_view *buffer = get_io_buffer_view(self); void *base = NULL; size_t size = 0; io_buffer_try_get_bytes(buffer, &base, &size); return RBOOL(base == NULL); } /* * call-seq: empty? -> true or false * * Returns whether the buffer has zero size. * * A buffer can be empty but have a non-null address, for example a zero-sized * slice or a buffer created with ::for from an empty string. Therefore * #empty? does not imply #null?. */ static VALUE rb_io_buffer_empty_p(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); return RBOOL(buffer->size == 0); } /* * call-seq: external? -> true or false * * The buffer is _external_ if it references the memory which is not * allocated or mapped by the buffer itself. * * A buffer created using ::for has an external reference to the string's * memory. * * External buffer can't be resized. */ static VALUE rb_io_buffer_external_p(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); return RBOOL(buffer->flags & RB_IO_BUFFER_EXTERNAL); } /* * call-seq: internal? -> true or false * * If the buffer is _internal_, meaning it references memory allocated by the * buffer itself. * * An internal buffer is not associated with any external memory (e.g. string) * or file mapping. * * Internal buffers are created using ::new and is the default when the * requested size is less than the IO::Buffer::PAGE_SIZE and it was not * requested to be mapped on creation. * * Internal buffers can be resized. Slices remain valid if their ranges still * fit within the resized buffer, including when its storage is relocated. */ static VALUE rb_io_buffer_internal_p(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); return RBOOL(buffer->flags & RB_IO_BUFFER_INTERNAL); } /* * call-seq: mapped? -> true or false * * If the buffer is _mapped_, meaning it references memory mapped by the * buffer. * * Mapped buffers are either anonymous, if created by ::new with the * IO::Buffer::MAPPED flag or if the size was at least IO::Buffer::PAGE_SIZE, * or backed by a file if created with ::map. * * Mapped buffers can usually be resized. Slices remain valid if their ranges * still fit within the resized buffer, including when its mapping is moved. */ static VALUE rb_io_buffer_mapped_p(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); return RBOOL(buffer->flags & RB_IO_BUFFER_MAPPED); } /* * call-seq: shared? -> true or false * * 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) * # => # * * # 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_view *buffer = get_io_buffer_view(self); return RBOOL(buffer->flags & RB_IO_BUFFER_SHARED); } /* * call-seq: locked? -> true or false * * If the buffer is _locked_, its underlying allocation cannot be resized, * freed or transferred. Locks are shared with slices and may be nested. * * Locking is a lifetime mechanism used to ensure buffers don't move while * being used by a system call or other native operation. * * buffer.locked do * buffer.write(io) # theoretical system call interface * end */ static VALUE rb_io_buffer_locked_p(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); return RBOOL(io_buffer_locked(buffer)); } /* 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) * # => # * * # 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_view *buffer = get_io_buffer_view(self); return RBOOL(buffer->flags & RB_IO_BUFFER_PRIVATE); } static int io_buffer_readonly_p(const struct rb_io_buffer_view *buffer) { for (;;) { if (buffer->flags & RB_IO_BUFFER_READONLY) return 1; VALUE source = buffer->source; if (NIL_P(source)) return 0; if (OBJ_FROZEN(source)) return 1; if (RB_TYPE_P(source, T_STRING)) return 0; buffer = get_io_buffer_view(source); } } int rb_io_buffer_readonly_p(VALUE self) { const struct rb_io_buffer_view *buffer = get_io_buffer_view(self); return io_buffer_readonly_p(buffer); } /* * call-seq: readonly? -> true or false * * If the buffer is read only, meaning the buffer cannot be modified using * #set_value, #set_string or #copy and similar. * * A buffer created by IO::Buffer.for without a block is read-only, as is one * backed by a frozen string or a read-only file. * * A slice derives read-only access from its current source. Replacing the * source's storage can therefore change the slice's read-only status. */ static VALUE io_buffer_readonly(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); return RBOOL(io_buffer_readonly_p(buffer)); } static void io_buffer_lock(struct rb_io_buffer_view *buffer) { struct rb_io_buffer_storage *owner = io_buffer_lock_owner(buffer); if (owner->lock_count == SIZE_MAX) { rb_raise(rb_eIOBufferLockedError, "It's locks all the way down!"); } owner->lock_count += 1; } VALUE rb_io_buffer_lock(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); io_buffer_lock(buffer); return self; } static void io_buffer_unlock(struct rb_io_buffer_view *buffer) { struct rb_io_buffer_storage *owner = io_buffer_lock_owner(buffer); if (owner->lock_count == 0) { rb_raise(rb_eIOBufferLockedError, "Buffer not locked!"); } owner->lock_count -= 1; } VALUE rb_io_buffer_unlock(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); io_buffer_unlock(buffer); return self; } int rb_io_buffer_try_unlock(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); struct rb_io_buffer_storage *owner = io_buffer_lock_owner(buffer); if (owner->lock_count > 0) { owner->lock_count -= 1; return 1; } return 0; } static VALUE rb_io_buffer_locked_ensure(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); io_buffer_unlock(buffer); return Qnil; } struct io_buffer_readable_bytes_arguments { VALUE self; VALUE (*callback)(const void *base, size_t size, VALUE argument); VALUE argument; }; static VALUE io_buffer_readable_bytes_call(VALUE _arguments) { struct io_buffer_readable_bytes_arguments *arguments = (void *)_arguments; const void *base; size_t size; rb_io_buffer_get_bytes_for_reading(arguments->self, &base, &size); return arguments->callback(base, size, arguments->argument); } VALUE rb_io_buffer_locked_for_reading(VALUE self, VALUE (*callback)(const void *base, size_t size, VALUE argument), VALUE argument) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); io_buffer_validate_for_reading(buffer); struct io_buffer_readable_bytes_arguments arguments = { .self = self, .callback = callback, .argument = argument, }; rb_io_buffer_lock(self); return rb_ensure(io_buffer_readable_bytes_call, (VALUE)&arguments, rb_io_buffer_locked_ensure, self); } struct io_buffer_writable_bytes_arguments { VALUE self; VALUE (*callback)(void *base, size_t size, VALUE argument); VALUE argument; }; static VALUE io_buffer_writable_bytes_call(VALUE _arguments) { struct io_buffer_writable_bytes_arguments *arguments = (void *)_arguments; void *base; size_t size; rb_io_buffer_get_bytes_for_writing(arguments->self, &base, &size); return arguments->callback(base, size, arguments->argument); } VALUE rb_io_buffer_locked_for_writing(VALUE self, VALUE (*callback)(void *base, size_t size, VALUE argument), VALUE argument) { get_io_buffer_view_for_writing(self); struct io_buffer_writable_bytes_arguments arguments = { .self = self, .callback = callback, .argument = argument, }; rb_io_buffer_lock(self); return rb_ensure(io_buffer_writable_bytes_call, (VALUE)&arguments, rb_io_buffer_locked_ensure, self); } /* * call-seq: locked { ... } * * Prevents the buffer or its buffer source from being moved or freed while * the block is executing. Locks are nested and shared with slices backed by * the same buffer source. The source remains locked until every nested lock * has been released. * * Locking protects allocation lifetime; it does not serialize access to the * bytes. Code that shares mutable buffer contents between threads must still * use appropriate synchronization. * * buffer = IO::Buffer.new(4) * buffer.locked? #=> false * * Fiber.schedule do * buffer.locked do * buffer.write(io) # theoretical system call interface * end * end * * Fiber.schedule do * buffer.locked do * buffer.set_string("test", 0) # Nested locking is allowed. * end * end */ VALUE rb_io_buffer_locked(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); // Only yield the block for a currently valid view. In particular, an // invalid slice should not lock its source. io_buffer_validate_for_reading(buffer); io_buffer_lock(buffer); return rb_ensure(rb_yield, self, rb_io_buffer_locked_ensure, self); } VALUE rb_io_buffer_free(VALUE self) { struct rb_io_buffer_storage *buffer = get_io_buffer_storage(self); if (buffer->lock_count) { rb_raise(rb_eIOBufferLockedError, "Buffer is locked!"); } io_buffer_storage_release(buffer); return self; } /* * call-seq: free -> self * * If the buffer references memory, release it back to the operating system. * * for a _mapped_ buffer (e.g. from file): unmap. * * for a buffer created from scratch: free memory. * * for a buffer created from string: undo the association. * * After releasing any referenced memory, the buffer is reset to a valid, * empty, null state. It has no backing storage and its size is zero. * Zero-length operations remain valid, while operations requiring bytes fail * normal bounds checking. * * Repeated calls on an unlocked, unfrozen Storage are harmless and return * +self+. You can resize the buffer to allocate new storage. This method is * not available on Slice, which never manages storage. * * buffer = IO::Buffer.for('test') * buffer.free * # => # * * buffer.null? # => true * buffer.empty? # => true * buffer.valid? # => true * buffer.get_string # => "" * * buffer.get_value(:U8, 0) # raises ArgumentError * * A frozen buffer cannot be freed, as that would release the memory its * contents live in: * * buffer = IO::Buffer.for('test').freeze * buffer.free * # in `free': can't modify frozen IO::Buffer (FrozenError) */ static VALUE io_buffer_free(VALUE self) { rb_check_frozen(self); return rb_io_buffer_free(self); } VALUE rb_io_buffer_free_locked(VALUE self) { struct rb_io_buffer_storage *buffer = get_io_buffer_storage(self); // This function is used to invalidate temporary wrappers around borrowed // memory. If another lock remains, the owner cannot safely end the // lifetime of that memory while another operation still retains it. if (buffer->lock_count != 1) { rb_bug("rb_io_buffer_free_locked: expected lock count 1, got %" PRIuSIZE, buffer->lock_count); } io_buffer_unlock(&buffer->view); io_buffer_storage_release(buffer); return self; } static bool size_sum_is_bigger_than(size_t a, size_t b, size_t x) { struct rbimpl_size_overflow_tag size = rbimpl_size_add_overflow(a, b); return size.overflowed || size.result > x; } // 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(const struct rb_io_buffer_view *buffer, size_t offset, size_t length) { io_buffer_validate_for_reading(buffer); if (size_sum_is_bigger_than(offset, length, buffer->size)) { rb_raise(rb_eArgError, "Specified offset+length is bigger than the buffer size!"); } } /* * call-seq: hexdump([offset, [length, [width]]]) -> string or nil * * Returns a human-readable string representation of the buffer. The exact * format is subject to change. * * Returns +nil+ if the buffer does not reference any memory, that is, if * #null? returns +true+ (for example after #free or #transfer). * * 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_hexdump(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 3); size_t offset, length; struct rb_io_buffer_view *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; void *base = NULL; size_t size = 0; if (io_buffer_try_get_bytes(buffer, &base, &size) && base) { result = rb_str_buf_new(io_buffer_hexdump_output_size(width, length, 1)); io_buffer_hexdump(result, width, base, offset+length, offset, 1); } return result; } static VALUE rb_io_buffer_slice(struct rb_io_buffer_view *buffer, VALUE self, size_t offset, size_t length) { io_buffer_validate_range(buffer, offset, length); VALUE instance = io_buffer_slice_allocate(rb_cIOBufferSlice); struct rb_io_buffer_view *slice = get_io_buffer_view(instance); slice->size = length; // Retain the immediate parent and store an offset into its current view. // Address resolution follows the source chain when the bytes are accessed. slice->base = (void *)(uintptr_t)offset; RB_OBJ_WRITE(instance, &slice->source, self); return instance; } /* * call-seq: IO::Buffer::Slice.new(source, [offset, [length]]) -> slice * * Creates a parent-relative view of a Buffer or another Slice. The source is * retained, not copied. Resizing or advancing this view never allocates storage. */ static VALUE io_buffer_slice_initialize(int argc, VALUE *argv, VALUE self) { rb_check_frozen(self); rb_check_arity(argc, 1, 3); struct rb_io_buffer_view *slice = get_io_buffer_view(self); if (!NIL_P(slice->source)) rb_raise(rb_eRuntimeError, "Slice is already initialized!"); VALUE source = argv[0]; size_t offset, length; struct rb_io_buffer_view *parent = io_buffer_extract_offset_length(source, argc-1, argv+1, &offset, &length); io_buffer_validate_range(parent, offset, length); slice = get_io_buffer_view(self); if (!NIL_P(slice->source)) rb_raise(rb_eRuntimeError, "Slice is already initialized!"); slice->base = (void *)(uintptr_t)offset; slice->size = length; RB_OBJ_WRITE(self, &slice->source, source); return self; } /* * call-seq: * dup -> slice * clone -> slice * * Copies the view, retaining the same source, offset, and size without * copying the underlying bytes. Advancing or resizing the copy does not * change the original view; writes through either view affect shared storage. * * buffer = IO::Buffer.new(6) * buffer.set_string("abcdef") * slice = buffer.slice(1, 3) * copy = slice.dup * copy.source.equal?(slice.source) # => true * copy.advance(1) * copy.get_string # => "cd" * slice.get_string # => "bcd" * copy.set_string("XY") * buffer.get_string # => "abXYef" * * This differs from IO::Buffer::Storage#dup, which copies the bytes into independent * storage. The usual Object#dup and Object#clone frozen-state rules apply. */ static VALUE io_buffer_slice_initialize_copy(VALUE self, VALUE other) { if (self == other) return self; rb_check_frozen(self); struct rb_io_buffer_view *slice = get_io_buffer_view(self); const struct rb_io_buffer_view *original = get_io_buffer_view(other); if (!io_buffer_slice_p(original)) rb_raise(rb_eTypeError, "Expected IO::Buffer::Slice!"); if (!NIL_P(slice->source)) rb_raise(rb_eRuntimeError, "Slice is already initialized!"); slice->base = original->base; slice->size = original->size; slice->flags = original->flags; RB_OBJ_WRITE(self, &slice->source, original->source); return self; } /* * call-seq: source -> io_buffer, string, or nil * * Returns the object backing this view, or +nil+ for a source-less buffer. * A slice's source is the buffer on which #slice was called, including when * that buffer is itself a slice. The source is retained while the view lives. * * root = IO::Buffer.new(8) * parent = root.slice(1, 6) * child = parent.slice(1, 2) * child.source.equal?(parent) # => true * parent.source.equal?(root) # => true * root.source # => nil * * For a String-backed buffer the source is its backing String. Without a * block, IO::Buffer.for may use a frozen internal copy rather than the * original String. A source-less buffer may own or borrow its memory; * a +nil+ source does not imply allocation ownership. * * An invalid slice still retains and returns its source. Storage#free or * Storage#transfer clears that Storage object's source; Slice has neither operation. * There is no source setter. */ static VALUE io_buffer_source(VALUE self) { return get_io_buffer_view(self)->source; } /* * 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. * * Slicing does not copy memory. The slice retains +self+ as its source and * tracks a logical offset and length within that view. Nested slices retain * their immediate parent rather than being flattened. * * A slice becomes invalid if its source is freed, transferred, resized so * that the slice is outside its bounds, or otherwise invalidated. It becomes * valid again if the source becomes valid and the range fits within it. * Reallocating the underlying storage does not invalidate the slice. * * If the offset is not given, it will be zero. If the offset is negative, it * will raise an ArgumentError. * * If the length is not given, the slice will be as long as the original * buffer minus the specified offset. If the length is negative, it will raise * an ArgumentError. * * Raises RuntimeError if the offset+length is out of the current * buffer's bounds. * * string = 'test' * buffer = IO::Buffer.for(string).dup * * slice = buffer.slice * # => * # # * # 0x00000000 74 65 73 74 test * * buffer.slice(2) * # => * # # * # 0x00000000 73 74 st * * slice = buffer.slice(1, 2) * # => * # # * # 0x00000000 65 73 es * * # Put "o" into 0s position of the slice * slice.set_string('o', 0) * slice * # => * # # * # 0x00000000 6f 73 os * * # it is also visible at position 1 of the original buffer * buffer * # => * # # * # 0x00000000 74 6f 73 74 tost */ static VALUE io_buffer_slice(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 2); size_t offset, length; struct rb_io_buffer_view *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length); return rb_io_buffer_slice(buffer, self, offset, length); } VALUE rb_io_buffer_transfer(VALUE self) { struct rb_io_buffer_storage *buffer = get_io_buffer_storage(self); if (buffer->lock_count) { rb_raise(rb_eIOBufferLockedError, "Cannot transfer ownership of locked buffer!"); } VALUE instance = rb_io_buffer_type_allocate(rb_class_of(self)); struct rb_io_buffer_storage *transferred = get_io_buffer_storage(instance); *transferred = *buffer; io_buffer_storage_zero(buffer); return instance; } /* * call-seq: transfer -> new_io_buffer * * Transfers ownership of the underlying memory to a new buffer, causing the * current buffer to become uninitialized. * * buffer = IO::Buffer.for('test') * other = buffer.transfer * other * # => * # # * # 0x00000000 74 65 73 74 test * buffer * # => * # # * buffer.null? * # => true * * A frozen buffer cannot transfer ownership, as that would leave it * uninitialized: * * buffer = IO::Buffer.for('test').freeze * buffer.transfer * # in `transfer': can't modify frozen IO::Buffer (FrozenError) */ static VALUE io_buffer_transfer(VALUE self) { rb_check_frozen(self); return rb_io_buffer_transfer(self); } static void io_buffer_storage_resize_clear(const struct rb_io_buffer_storage *storage, void* base, size_t size) { if (size > storage->view.size) { memset((unsigned char*)base+storage->view.size, 0, size - storage->view.size); } } static void io_buffer_storage_resize_copy(VALUE self, struct rb_io_buffer_storage *storage, size_t size) { // Allocate and copy before releasing the old storage. struct rb_io_buffer_storage resized = {0}; enum rb_io_buffer_flags flags = io_flags_for_size(size) | (storage->view.flags & RB_IO_BUFFER_READONLY); io_buffer_storage_initialize(self, &resized, NULL, size, flags, Qnil); if (storage->view.base) { size_t preserve = storage->view.size; if (preserve > size) preserve = size; memcpy(resized.view.base, storage->view.base, preserve); io_buffer_storage_resize_clear(storage, resized.view.base, size); } io_buffer_storage_release(storage); *storage = resized; } void rb_io_buffer_advance(VALUE self, size_t amount) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); if (io_buffer_owning_p(buffer)) { rb_raise(rb_eIOBufferAccessError, "Cannot advance an owning buffer!"); } if (!io_buffer_validate(buffer)) { rb_raise(rb_eIOBufferInvalidatedError, "Buffer has been invalidated!"); } if (amount > buffer->size) { rb_raise(rb_eArgError, "Advance amount exceeds buffer size!"); } if (buffer->source != Qnil) { // Source-backed buffers store an offset into their immediate source. // Advance the offset without changing any ancestor's range. buffer->base = (void *)(uintptr_t)(io_buffer_slice_offset(buffer) + amount); } else if (buffer->base) { // Source-less borrowed buffers store their pointer directly. buffer->base = (char *)buffer->base + amount; } buffer->size -= amount; } static void io_buffer_resize_slice(struct rb_io_buffer_view *slice, size_t size) { if (NIL_P(slice->source)) rb_raise(rb_eIOBufferInvalidatedError, "Uninitialized slice!"); struct rb_io_buffer_view *source = get_io_buffer_view(slice->source); void *source_base = NULL; size_t source_size = 0; if (!io_buffer_try_get_bytes(source, &source_base, &source_size)) { rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!"); } size_t offset = io_buffer_slice_offset(slice); if (offset > source_size) { rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!"); } if (size > source_size - offset) { rb_raise(rb_eArgError, "Resized slice exceeds its source buffer!"); } // Validate the requested range rather than the current range so that // shrinking a slice can restore its validity after the source shrinks. slice->size = size; } static void io_buffer_storage_resize(VALUE self, struct rb_io_buffer_storage *storage, size_t size) { struct rb_io_buffer_view *buffer = &storage->view; io_buffer_validate_for_reading(buffer); if (storage->lock_count) { rb_raise(rb_eIOBufferLockedError, "Cannot resize locked buffer!"); } // An external buffer (including a String-backed buffer, whose base is an // offset into the source) does not own its memory and cannot be resized. // This must be checked before the empty-buffer case below, since a // String-backed buffer at offset 0 has a NULL `base`. if (buffer->flags & RB_IO_BUFFER_EXTERNAL) { rb_raise(rb_eIOBufferAccessError, "Cannot resize external buffer!"); } if (buffer->base == NULL) { io_buffer_storage_initialize(self, storage, NULL, size, io_flags_for_size(size), Qnil); return; } if (size == 0) { io_buffer_storage_release(storage); return; } #if defined(HAVE_MREMAP) && defined(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_storage_resize_clear(storage, base, size); buffer->base = base; buffer->size = size; return; } #endif if (buffer->flags & RB_IO_BUFFER_INTERNAL) { void *base = realloc(buffer->base, size); if (!base) { rb_sys_fail("rb_io_buffer_resize:realloc"); } io_buffer_storage_resize_clear(storage, base, size); buffer->base = base; buffer->size = size; return; } io_buffer_storage_resize_copy(self, storage, size); } void rb_io_buffer_resize(VALUE self, size_t size) { struct rb_io_buffer_view *view = get_io_buffer_view(self); if (io_buffer_slice_p(view)) { io_buffer_resize_slice(view, size); } else { io_buffer_storage_resize(self, io_buffer_storage_from_view(view), size); } } /* * call-seq: resize(new_size) -> self * * Resizes a buffer to a +new_size+ bytes, preserving its content. * Depending on the old and new size, the memory area associated with * the buffer might be either extended, or rellocated at different * address with content being copied. * * buffer = IO::Buffer.new(4) * buffer.set_string("test", 0) * buffer.resize(8) # resize to 8 bytes * # => * # # * # 0x00000000 74 65 73 74 00 00 00 00 test.... * * When the buffer is a slice, resizing changes the size of the view without * modifying the source buffer or allocating new storage. The resized view * must remain within the source buffer. Growing the view exposes the existing * bytes in the source; they are not cleared. Because the source allocation * does not change, a slice can be resized while its source is locked. * * External owning buffers (created with ::for), and locked owning buffers * cannot be resized. Frozen buffers cannot be resized. */ static VALUE io_buffer_resize(VALUE self, VALUE size) { rb_check_frozen(self); rb_io_buffer_resize(self, io_buffer_extract_size(size)); return self; } /* * call-seq: advance(amount) -> self * * Advances the beginning of a non-owning buffer view by +amount+ bytes, * reducing its size by the same amount. The backing memory is not moved or * modified. The buffer must refer to memory managed elsewhere or be a slice; * a buffer which owns its allocation cannot be advanced. * * The source and allocation lock count are unchanged. Advancing is allowed * while the bytes are read-only or the allocation is locked, because it only * changes the view. If +amount+ equals the current size, the buffer becomes * an empty view at its previous end. * * Raises ArgumentError if +amount+ is negative or exceeds the current size, * IO::Buffer::InvalidatedError if the view is invalid, or * IO::Buffer::AccessError if the buffer owns its allocation. A frozen buffer * cannot be advanced. * * buffer = IO::Buffer.for("test") * buffer.advance(1) * buffer.get_string # => "est" */ static VALUE io_buffer_advance(VALUE self, VALUE amount) { rb_check_frozen(self); size_t size = io_buffer_extract_amount(amount); // Argument conversion may call Ruby code which freezes the receiver. rb_check_frozen(self); rb_io_buffer_advance(self, size); return self; } /* * call-seq: <=>(other) -> integer * * Returns a negative integer, zero, or a positive integer if the receiver is * less than, equal to, or greater than +other+, respectively. * * Buffers are compared by size first, and if the sizes are equal, by the exact * contents of the memory they are referencing using +memcmp+. Only the sign of * the returned integer is meaningful; the result of +memcmp+ is returned as is. * * IO::Buffer.for("abc") <=> IO::Buffer.for("abc") # => 0 * IO::Buffer.for("abc") <=> IO::Buffer.for("ab") # => 1 * IO::Buffer.for("abc") <=> IO::Buffer.for("abd") # => -1 */ static VALUE rb_io_buffer_compare(VALUE self, VALUE other) { const void *ptr1, *ptr2; size_t size1, size2; rb_io_buffer_get_bytes_for_reading(self, &ptr1, &size1); rb_io_buffer_get_bytes_for_reading(other, &ptr2, &size2); if (size1 < size2) { return RB_INT2NUM(-1); } if (size1 > size2) { return RB_INT2NUM(1); } if (size1 == 0) { return RB_INT2NUM(0); } RUBY_ASSERT(ptr1 != NULL); RUBY_ASSERT(ptr2 != NULL); return RB_INT2NUM(memcmp(ptr1, ptr2, size1)); } static void io_buffer_validate_type(size_t size, size_t offset, size_t extend) { if (size_sum_is_bigger_than(offset, extend, size)) { rb_raise(rb_eArgError, "Type extends beyond end of buffer! (offset=%"PRIdSIZE" > size=%"PRIdSIZE")", offset, size); } } // Lower case: little endian. // Upper case: big endian (network endian). // // :U8 | unsigned 8-bit integer. // :S8 | signed 8-bit integer. // // :u16, :U16 | unsigned 16-bit integer. // :s16, :S16 | signed 16-bit integer. // // :u32, :U32 | unsigned 32-bit integer. // :s32, :S32 | signed 32-bit integer. // // :u64, :U64 | unsigned 64-bit integer. // :s64, :S64 | signed 64-bit integer. // // :u128, :U128 | unsigned 128-bit integer. // :s128, :S128 | signed 128-bit integer. // // :f32, :F32 | 32-bit floating point number. // :f64, :F64 | 64-bit floating point number. #define ruby_swap8(value) value union swapf32 { uint32_t integral; float value; }; static float ruby_swapf32(float value) { union swapf32 swap = {.value = value}; swap.integral = ruby_swap32(swap.integral); return swap.value; } union swapf64 { uint64_t integral; double value; }; static double ruby_swapf64(double value) { union swapf64 swap = {.value = value}; swap.integral = ruby_swap64(swap.integral); return swap.value; } // Structures and conversion functions are now in numeric.h/numeric.c // Unified swap function for 128-bit integers (works with both signed and unsigned) // Since both rb_uint128_t and rb_int128_t have the same memory layout, // we can use a union to make the swap function work with both types static inline rb_uint128_t ruby_swap128_uint(rb_uint128_t x) { rb_uint128_t result; #ifdef HAVE_UINT128_T #if __has_builtin(__builtin_bswap128) result.value = __builtin_bswap128(x.value); #else // Manual byte swap for 128-bit integers uint64_t low = (uint64_t)x.value; uint64_t high = (uint64_t)(x.value >> 64); low = ruby_swap64(low); high = ruby_swap64(high); result.value = ((uint128_t)low << 64) | high; #endif #else // Fallback swap function using two 64-bit integers // For big-endian data on little-endian host (or vice versa): // 1. Swap bytes within each 64-bit part // 2. Swap the order of the parts (since big-endian stores high first, little-endian stores low first) result.parts.low = ruby_swap64(x.parts.high); result.parts.high = ruby_swap64(x.parts.low); #endif return result; } static inline rb_int128_t ruby_swap128_int(rb_int128_t x) { union uint128_int128_conversion conversion = { .int128 = x }; conversion.uint128 = ruby_swap128_uint(conversion.uint128); return conversion.int128; } #define IO_BUFFER_VALIDATE_TYPE_FOR_WRITING(buffer, base, size, offset, type) \ (io_buffer_get_bytes_for_writing(buffer, &(base), &(size)), \ io_buffer_validate_type(size, offset, sizeof(type))) #define IO_BUFFER_DECLARE_TYPE(name, type, endian, wrap, unwrap, swap) \ static ID RB_IO_BUFFER_DATA_TYPE_##name; \ \ static VALUE \ io_buffer_read_##name(const void* base, size_t size, size_t *offset) \ { \ io_buffer_validate_type(size, *offset, sizeof(type)); \ type value; \ memcpy(&value, (char*)base + *offset, sizeof(type)); \ if (endian != RB_IO_BUFFER_HOST_ENDIAN) value = swap(value); \ *offset += sizeof(type); \ return wrap(value); \ } \ \ static void \ io_buffer_write_##name(struct rb_io_buffer_view* buffer, size_t *offset, VALUE _value) \ { \ void* base; size_t size; \ IO_BUFFER_VALIDATE_TYPE_FOR_WRITING(buffer, base, size, *offset, type); \ type value = unwrap(_value); \ IO_BUFFER_VALIDATE_TYPE_FOR_WRITING(buffer, base, size, *offset, type); \ if (endian != RB_IO_BUFFER_HOST_ENDIAN) value = swap(value); \ memcpy((char*)base + *offset, &value, sizeof(type)); \ *offset += sizeof(type); \ } \ \ enum { \ RB_IO_BUFFER_DATA_TYPE_##name##_SIZE = sizeof(type) \ }; IO_BUFFER_DECLARE_TYPE(U8, uint8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap8) IO_BUFFER_DECLARE_TYPE(S8, int8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap8) IO_BUFFER_DECLARE_TYPE(u16, uint16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16) IO_BUFFER_DECLARE_TYPE(U16, uint16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16) IO_BUFFER_DECLARE_TYPE(s16, int16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16) IO_BUFFER_DECLARE_TYPE(S16, int16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16) IO_BUFFER_DECLARE_TYPE(u32, uint32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32) IO_BUFFER_DECLARE_TYPE(U32, uint32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32) IO_BUFFER_DECLARE_TYPE(s32, int32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32) IO_BUFFER_DECLARE_TYPE(S32, int32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32) IO_BUFFER_DECLARE_TYPE(u64, uint64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_ULL2NUM, RB_NUM2ULL, ruby_swap64) IO_BUFFER_DECLARE_TYPE(U64, uint64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_ULL2NUM, RB_NUM2ULL, ruby_swap64) IO_BUFFER_DECLARE_TYPE(s64, int64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64) IO_BUFFER_DECLARE_TYPE(S64, int64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64) IO_BUFFER_DECLARE_TYPE(u128, rb_uint128_t, RB_IO_BUFFER_LITTLE_ENDIAN, rb_uint128_to_numeric, rb_numeric_to_uint128, ruby_swap128_uint) IO_BUFFER_DECLARE_TYPE(U128, rb_uint128_t, RB_IO_BUFFER_BIG_ENDIAN, rb_uint128_to_numeric, rb_numeric_to_uint128, ruby_swap128_uint) IO_BUFFER_DECLARE_TYPE(s128, rb_int128_t, RB_IO_BUFFER_LITTLE_ENDIAN, rb_int128_to_numeric, rb_numeric_to_int128, ruby_swap128_int) IO_BUFFER_DECLARE_TYPE(S128, rb_int128_t, RB_IO_BUFFER_BIG_ENDIAN, rb_int128_to_numeric, rb_numeric_to_int128, ruby_swap128_int) IO_BUFFER_DECLARE_TYPE(f32, float, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32) IO_BUFFER_DECLARE_TYPE(F32, float, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32) IO_BUFFER_DECLARE_TYPE(f64, double, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64) IO_BUFFER_DECLARE_TYPE(F64, double, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64) #undef IO_BUFFER_DECLARE_TYPE static inline size_t io_buffer_buffer_type_size(ID buffer_type) { #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) IO_BUFFER_DATA_TYPE_SIZE(U16) IO_BUFFER_DATA_TYPE_SIZE(s16) IO_BUFFER_DATA_TYPE_SIZE(S16) IO_BUFFER_DATA_TYPE_SIZE(u32) IO_BUFFER_DATA_TYPE_SIZE(U32) IO_BUFFER_DATA_TYPE_SIZE(s32) IO_BUFFER_DATA_TYPE_SIZE(S32) IO_BUFFER_DATA_TYPE_SIZE(u64) IO_BUFFER_DATA_TYPE_SIZE(U64) IO_BUFFER_DATA_TYPE_SIZE(s64) IO_BUFFER_DATA_TYPE_SIZE(S64) IO_BUFFER_DATA_TYPE_SIZE(u128) IO_BUFFER_DATA_TYPE_SIZE(U128) IO_BUFFER_DATA_TYPE_SIZE(s128) IO_BUFFER_DATA_TYPE_SIZE(S128) IO_BUFFER_DATA_TYPE_SIZE(f32) IO_BUFFER_DATA_TYPE_SIZE(F32) IO_BUFFER_DATA_TYPE_SIZE(f64) IO_BUFFER_DATA_TYPE_SIZE(F64) #undef IO_BUFFER_DATA_TYPE_SIZE rb_raise(rb_eArgError, "Invalid type name!"); } static inline ID io_buffer_type_id(VALUE name) { Check_Type(name, T_SYMBOL); if (!STATIC_SYM_P(name)) return 0; return rb_sym2id(name); } #define TYPE_ID(name) io_buffer_type_id(name) /* * call-seq: * size_of(buffer_type) -> byte size * size_of(array of buffer_type) -> byte size * * 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 buffer_type) { if (RB_TYPE_P(buffer_type, T_ARRAY)) { size_t total = 0; for (rb_len_t i = 0; i < RARRAY_LEN(buffer_type); i++) { total += io_buffer_buffer_type_size(TYPE_ID(RARRAY_AREF(buffer_type, i))); } return SIZET2NUM(total); } else { return SIZET2NUM(io_buffer_buffer_type_size(TYPE_ID(buffer_type))); } } static inline VALUE rb_io_buffer_get_value(const void* base, size_t size, ID buffer_type, size_t *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) IO_BUFFER_GET_VALUE(u16) IO_BUFFER_GET_VALUE(U16) IO_BUFFER_GET_VALUE(s16) IO_BUFFER_GET_VALUE(S16) IO_BUFFER_GET_VALUE(u32) IO_BUFFER_GET_VALUE(U32) IO_BUFFER_GET_VALUE(s32) IO_BUFFER_GET_VALUE(S32) IO_BUFFER_GET_VALUE(u64) IO_BUFFER_GET_VALUE(U64) IO_BUFFER_GET_VALUE(s64) IO_BUFFER_GET_VALUE(S64) IO_BUFFER_GET_VALUE(u128) IO_BUFFER_GET_VALUE(U128) IO_BUFFER_GET_VALUE(s128) IO_BUFFER_GET_VALUE(S128) IO_BUFFER_GET_VALUE(f32) IO_BUFFER_GET_VALUE(F32) IO_BUFFER_GET_VALUE(f64) IO_BUFFER_GET_VALUE(F64) #undef IO_BUFFER_GET_VALUE rb_raise(rb_eArgError, "Invalid type name!"); } /* * call-seq: get_value(buffer_type, offset) -> numeric * * Read from buffer a value of +type+ at +offset+. +buffer_type+ should be one * of symbols: * * * +:U8+: unsigned integer, 1 byte * * +:S8+: signed integer, 1 byte * * +:u16+: unsigned integer, 2 bytes, little-endian * * +:U16+: unsigned integer, 2 bytes, big-endian * * +:s16+: signed integer, 2 bytes, little-endian * * +:S16+: signed integer, 2 bytes, big-endian * * +:u32+: unsigned integer, 4 bytes, little-endian * * +:U32+: unsigned integer, 4 bytes, big-endian * * +:s32+: signed integer, 4 bytes, little-endian * * +:S32+: signed integer, 4 bytes, big-endian * * +:u64+: unsigned integer, 8 bytes, little-endian * * +:U64+: unsigned integer, 8 bytes, big-endian * * +:s64+: signed integer, 8 bytes, little-endian * * +:S64+: signed integer, 8 bytes, big-endian * * +:u128+: unsigned integer, 16 bytes, little-endian * * +:U128+: unsigned integer, 16 bytes, big-endian * * +:s128+: signed integer, 16 bytes, little-endian * * +:S128+: signed integer, 16 bytes, big-endian * * +:f32+: float, 4 bytes, little-endian * * +:F32+: float, 4 bytes, big-endian * * +:f64+: double, 8 bytes, little-endian * * +:F64+: double, 8 bytes, big-endian * * 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. * * string = [1.5].pack('f') * # => "\x00\x00\xC0?" * IO::Buffer.for(string).get_value(:f32, 0) * # => 1.5 */ static VALUE io_buffer_get_value(VALUE self, VALUE type, VALUE _offset) { const void *base; size_t size; size_t offset = io_buffer_extract_offset(_offset); rb_io_buffer_get_bytes_for_reading(self, &base, &size); return rb_io_buffer_get_value(base, size, TYPE_ID(type), &offset); } /* * call-seq: get_values(buffer_types, offset) -> array * * Similar to #get_value, except that it can handle multiple buffer types and * returns an array of values. * * 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 buffer_types, VALUE _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(buffer_types, T_ARRAY)) { rb_raise(rb_eArgError, "Argument buffer_types should be an array!"); } VALUE array = rb_ary_new_capa(RARRAY_LEN(buffer_types)); for (rb_len_t 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, TYPE_ID(type), &offset); rb_ary_push(array, value); } 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); } } struct io_buffer_each_arguments { VALUE self; int argc; VALUE *argv; }; static VALUE io_buffer_each_locked(VALUE _arguments) { struct io_buffer_each_arguments *arguments = (void *)_arguments; VALUE self = arguments->self; int argc = arguments->argc; VALUE *argv = arguments->argv; const void *base; size_t size; rb_io_buffer_get_bytes_for_reading(self, &base, &size); ID buffer_type; if (argc >= 1) { buffer_type = TYPE_ID(argv[0]); } else { buffer_type = RB_IO_BUFFER_DATA_TYPE_U8; } 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, buffer_type, &offset); rb_yield_values(2, SIZET2NUM(current_offset), value); } return self; } /* * call-seq: * each(buffer_type, [offset, [count]]) {|offset, value| ...} -> self * each(buffer_type, [offset, [count]]) -> enumerator * * Iterates over the buffer, yielding each +value+ of +buffer_type+ starting * from +offset+. * * If +count+ is given, only +count+ values will be yielded. * * IO::Buffer.for("Hello World").each(:U8, 2, 2) do |offset, value| * puts "#{offset}: #{value}" * end * # 2: 108 * # 3: 108 */ static VALUE io_buffer_each(int argc, VALUE *argv, VALUE self) { RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS); struct io_buffer_each_arguments arguments = { .self = self, .argc = argc, .argv = argv, }; rb_io_buffer_lock(self); return rb_ensure(io_buffer_each_locked, (VALUE)&arguments, rb_io_buffer_locked_ensure, self); } /* * call-seq: values(buffer_type, [offset, [count]]) -> array * * Returns an array of values of +buffer_type+ starting from +offset+. * * If +count+ is given, only +count+ values will be returned. * * IO::Buffer.for("Hello World").values(:U8, 2, 2) * # => [108, 108] */ static VALUE io_buffer_values(int argc, VALUE *argv, VALUE self) { const void *base; size_t size; rb_io_buffer_get_bytes_for_reading(self, &base, &size); ID buffer_type; if (argc >= 1) { buffer_type = TYPE_ID(argv[0]); } else { buffer_type = RB_IO_BUFFER_DATA_TYPE_U8; } 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, buffer_type, &offset); rb_ary_push(array, value); } return array; } static VALUE io_buffer_each_byte_locked(VALUE _arguments) { struct io_buffer_each_arguments *arguments = (void *)_arguments; VALUE self = arguments->self; int argc = arguments->argc; VALUE *argv = arguments->argv; const void *base; size_t size; rb_io_buffer_get_bytes_for_reading(self, &base, &size); size_t offset, count; io_buffer_extract_offset_count(RB_IO_BUFFER_DATA_TYPE_U8, size, argc, argv, &offset, &count); if (size_sum_is_bigger_than(offset, count, size)) { rb_raise(rb_eArgError, "Specified offset+count is bigger than the buffer size!"); } for (size_t i = 0; i < count; i++) { unsigned char *value = (unsigned char *)base + i + offset; rb_yield(RB_INT2FIX(*value)); } return self; } /* * call-seq: * each_byte([offset, [count]]) {|byte| ...} -> self * each_byte([offset, [count]]) -> enumerator * * Iterates over the buffer, yielding each byte starting from +offset+. * * If +count+ is given, only +count+ bytes will be yielded. * * IO::Buffer.for("Hello World").each_byte(2, 2) do |offset, byte| * puts "#{offset}: #{byte}" * end * # 2: 108 * # 3: 108 */ static VALUE io_buffer_each_byte(int argc, VALUE *argv, VALUE self) { RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS); struct io_buffer_each_arguments arguments = { .self = self, .argc = argc, .argv = argv, }; rb_io_buffer_lock(self); return rb_ensure(io_buffer_each_byte_locked, (VALUE)&arguments, rb_io_buffer_locked_ensure, self); } static inline void rb_io_buffer_set_value(struct rb_io_buffer_view *buffer, VALUE buffer_type, size_t *offset, VALUE value) { ID type = TYPE_ID(buffer_type); #define IO_BUFFER_SET_VALUE(name) if (type == RB_IO_BUFFER_DATA_TYPE_##name) {io_buffer_write_##name(buffer, offset, value); return;} IO_BUFFER_SET_VALUE(U8); IO_BUFFER_SET_VALUE(S8); IO_BUFFER_SET_VALUE(u16); IO_BUFFER_SET_VALUE(U16); IO_BUFFER_SET_VALUE(s16); IO_BUFFER_SET_VALUE(S16); IO_BUFFER_SET_VALUE(u32); IO_BUFFER_SET_VALUE(U32); IO_BUFFER_SET_VALUE(s32); IO_BUFFER_SET_VALUE(S32); IO_BUFFER_SET_VALUE(u64); IO_BUFFER_SET_VALUE(U64); IO_BUFFER_SET_VALUE(s64); IO_BUFFER_SET_VALUE(S64); IO_BUFFER_SET_VALUE(u128); IO_BUFFER_SET_VALUE(U128); IO_BUFFER_SET_VALUE(s128); IO_BUFFER_SET_VALUE(S128); IO_BUFFER_SET_VALUE(f32); IO_BUFFER_SET_VALUE(F32); IO_BUFFER_SET_VALUE(f64); IO_BUFFER_SET_VALUE(F64); #undef IO_BUFFER_SET_VALUE rb_raise(rb_eArgError, "Invalid type name!"); } struct io_buffer_set_value_arguments { struct rb_io_buffer_view *buffer; size_t offset; VALUE type, value; }; /* * call-seq: set_value(type, offset, value) -> offset * * Write to a buffer a +value+ of +type+ at +offset+. +type+ should be one of * symbols described in #get_value. Returns the offset just after the written * value. * * buffer = IO::Buffer.new(8) * # => * # # * # 0x00000000 00 00 00 00 00 00 00 00 * * buffer.set_value(:U8, 1, 111) * # => 2 * * buffer * # => * # # * # 0x00000000 00 6f 00 00 00 00 00 00 .o...... * * Note that if the +type+ is integer and +value+ is Float, the implicit truncation is performed: * * buffer = IO::Buffer.new(8) * buffer.set_value(:U32, 0, 2.5) * * buffer * # => * # # * # 0x00000000 00 00 00 02 00 00 00 00 * # ^^ the same as if we'd pass just integer 2 */ static VALUE io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value) { struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self); size_t offset = io_buffer_extract_offset(_offset); rb_io_buffer_set_value(buffer, type, &offset, value); return SIZET2NUM(offset); } /* * call-seq: set_values(buffer_types, offset, values) -> offset * * 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. Returns the offset just after the last * written value. * * buffer = IO::Buffer.new(8) * buffer.set_values([:U8, :U16], 0, [1, 2]) * # => 3 * buffer * # => * # # * # 0x00000000 01 00 02 00 00 00 00 00 ........ */ static VALUE io_buffer_set_values(VALUE self, VALUE buffer_types, VALUE _offset, VALUE values) { struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self); if (!RB_TYPE_P(buffer_types, T_ARRAY)) { rb_raise(rb_eArgError, "Argument buffer_types should be an array!"); } size_t offset = io_buffer_extract_offset(_offset); if (!RB_TYPE_P(values, T_ARRAY)) { rb_raise(rb_eArgError, "Argument values should be an array!"); } if (RARRAY_LEN(buffer_types) != RARRAY_LEN(values)) { rb_raise(rb_eArgError, "Argument buffer_types and values should have the same length!"); } for (rb_len_t 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(buffer, type, &offset, value); } return SIZET2NUM(offset); } static size_t IO_BUFFER_BLOCKING_SIZE = 1024*1024; struct io_buffer_memmove_arguments { unsigned char * destination; const unsigned char * source; size_t length; }; static void * io_buffer_memmove_blocking(void *data) { struct io_buffer_memmove_arguments *arguments = (struct io_buffer_memmove_arguments *)data; memmove(arguments->destination, arguments->source, arguments->length); return NULL; } static void io_buffer_memmove_unblock(void *data) { // No safe way to interrupt. } static void io_buffer_memmove(void *base, size_t size, size_t offset, const void *source_base, size_t source_offset, size_t source_size, size_t length) { if (size_sum_is_bigger_than(offset, length, size)) { rb_raise(rb_eArgError, "Specified offset+length is bigger than the buffer size!"); } if (size_sum_is_bigger_than(source_offset, length, source_size)) { rb_raise(rb_eArgError, "The computed source range exceeds the size of the source buffer!"); } if (length == 0) return; RUBY_ASSERT(base != NULL); RUBY_ASSERT(source_base != NULL); struct io_buffer_memmove_arguments arguments = { .destination = (unsigned char*)base+offset, .source = (unsigned char*)source_base+source_offset, .length = length }; if (arguments.length >= IO_BUFFER_BLOCKING_SIZE) { rb_nogvl(io_buffer_memmove_blocking, &arguments, io_buffer_memmove_unblock, &arguments, RB_NOGVL_OFFLOAD_SAFE); } else if (arguments.length != 0) { memmove(arguments.destination, arguments.source, arguments.length); } } static void io_buffer_extract_copy_arguments(size_t source_size, int argc, VALUE *argv, size_t *offset, size_t *length, size_t *source_offset) { // The offset we copy into the buffer: if (argc >= 1) { *offset = io_buffer_extract_offset(argv[0]); } else { *offset = 0; } // The offset we start from within the string: if (argc >= 3) { *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!"); } } else { *source_offset = 0; } // The length we are going to copy: if (argc >= 2 && !RB_NIL_P(argv[1])) { *length = io_buffer_extract_length(argv[1]); } else { // Default to the source offset -> source size: *length = source_size - *source_offset; } } // (offset, length, source_offset) -> length static VALUE io_buffer_copy_from(struct rb_io_buffer_view *buffer, const void *source_base, size_t source_size, int argc, VALUE *argv) { size_t offset, length, source_offset; io_buffer_extract_copy_arguments(source_size, argc, argv, &offset, &length, &source_offset); void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); if (length >= IO_BUFFER_BLOCKING_SIZE) { io_buffer_lock(buffer); } io_buffer_memmove(base, size, offset, source_base, source_offset, source_size, length); if (length >= IO_BUFFER_BLOCKING_SIZE) { io_buffer_unlock(buffer); } return SIZET2NUM(length); } struct io_buffer_copy_arguments { VALUE destination; const void *source_base; size_t source_size; int argc; VALUE *argv; }; // This is the innermost callback for IO::Buffer#copy. At this point the source // is locked for reading and the destination is locked for writing, so both // pointers and sizes remain valid while arguments are extracted, ranges are // validated, and memmove potentially releases the GVL. static VALUE io_buffer_copy_to(void *base, size_t size, VALUE _arguments) { struct io_buffer_copy_arguments *arguments = (void *)_arguments; size_t offset, length, source_offset; io_buffer_extract_copy_arguments(arguments->source_size, arguments->argc, arguments->argv, &offset, &length, &source_offset); io_buffer_memmove(base, size, offset, arguments->source_base, source_offset, arguments->source_size, length); return SIZET2NUM(length); } // This callback runs while the source is locked for reading. Retain its bytes // in the callback arguments, then enter the destination's writable scope. The // source scope remains active until that nested scope returns. static VALUE io_buffer_copy_from_readable(const void *base, size_t size, VALUE _arguments) { struct io_buffer_copy_arguments *arguments = (void *)_arguments; arguments->source_base = base; arguments->source_size = size; return rb_io_buffer_locked_for_writing(arguments->destination, io_buffer_copy_to, _arguments); } static VALUE io_buffer_initialize_copy_from(const void *base, size_t size, VALUE self) { struct rb_io_buffer_storage *buffer = get_io_buffer_storage(self); if (buffer->lock_count) rb_raise(rb_eIOBufferLockedError, "Cannot initialize locked buffer!"); io_buffer_storage_release(buffer); io_buffer_storage_initialize(self, buffer, NULL, size, io_flags_for_size(size), Qnil); struct io_buffer_copy_arguments arguments = { .destination = self, .source_base = base, .source_size = size, .argc = 0, .argv = NULL, }; // The source remains locked by the outer readable scope while the newly // initialized destination is locked and populated by io_buffer_copy_to. return rb_io_buffer_locked_for_writing(self, io_buffer_copy_to, (VALUE)&arguments); } /* * call-seq: * dup -> io_buffer * clone -> io_buffer * * Make an internal copy of the source buffer. Updates to the copy will not * affect the source buffer. * * source = IO::Buffer.for("Hello World") * # => * # # * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World * buffer = source.dup * # => * # # * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World */ static VALUE rb_io_buffer_initialize_copy(VALUE self, VALUE source) { if (self == source) return self; rb_check_frozen(self); return rb_io_buffer_locked_for_reading(source, io_buffer_initialize_copy_from, self); } /* * call-seq: * copy(source, [offset, [length, [source_offset]]]) -> size * * Efficiently copy from a source IO::Buffer into the buffer, at +offset+ * using +memmove+. For copying String instances, see #set_string. * * buffer = IO::Buffer.new(32) * # => * # # * # 0x00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * # 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 buffer copied * buffer * # => * # # * # 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 buffer into strings associated with buffer: * * string = "data: " * # => "data: " * buffer = IO::Buffer.for(string) do |buffer| * buffer.copy(IO::Buffer.for("test"), 5) * end * # => 4 * string * # => "data:test" * * Attempt to copy into a read-only buffer will fail: * * File.write('test.txt', 'test') * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) * buffer.copy(IO::Buffer.for("test"), 8) * # in `copy': Buffer is not writable! (IO::Buffer::AccessError) * * See ::map for details of creation of mutable file mappings, this will * work: * * buffer = IO::Buffer.map(File.open('test.txt', 'r+')) * buffer.copy(IO::Buffer.for("boom"), 0) * # => 4 * File.read('test.txt') * # => "boom" * * 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 is bigger than the buffer size! (ArgumentError) * * It is safe to copy between memory regions that overlaps each other. * In such case, the data is copied as if the data was first copied from the source buffer to * a temporary buffer, and then copied from the temporary buffer to the destination buffer. * * buffer = IO::Buffer.new(10) * buffer.set_string("0123456789") * buffer.copy(buffer, 3, 7) * # => 7 * buffer * # => * # # * # 0x00000000 30 31 32 30 31 32 33 34 35 36 0120123456 */ static VALUE io_buffer_copy(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 4); VALUE source = argv[0]; struct io_buffer_copy_arguments arguments = { .destination = self, .argc = argc-1, .argv = argv+1, }; // Lock the source first, then io_buffer_copy_from_readable nests the // destination lock. The scoped helpers use rb_ensure, so the destination // is unlocked before the source on both normal and exceptional returns. // If both buffers share an allocation, its reference-counted lock is // acquired and released twice. return rb_io_buffer_locked_for_reading(source, io_buffer_copy_from_readable, (VALUE)&arguments); } struct io_buffer_get_string_arguments { size_t offset; size_t length; rb_encoding *encoding; }; static VALUE io_buffer_get_string_locked(const void *base, size_t size, VALUE _arguments) { struct io_buffer_get_string_arguments *arguments = (void *)_arguments; if (size_sum_is_bigger_than(arguments->offset, arguments->length, size)) { rb_raise(rb_eArgError, "Specified offset+length is bigger than the buffer size!"); } const char *data = base ? (const char *)base + arguments->offset : NULL; return rb_enc_str_new(data, arguments->length, arguments->encoding); } /* * call-seq: get_string([offset, [length, [encoding]]]) -> string * * Read a chunk or all of the buffer into a string, in the specified * +encoding+. If no encoding is provided +Encoding::BINARY+ is used. * * buffer = IO::Buffer.for('test') * buffer.get_string * # => "test" * buffer.get_string(2) * # => "st" * buffer.get_string(2, 1) * # => "s" */ static VALUE io_buffer_get_string(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 3); struct io_buffer_get_string_arguments arguments; io_buffer_extract_offset_length(self, argc, argv, &arguments.offset, &arguments.length); // Encoding coercion may invoke Ruby and change the source. Do it before // locking and resolving the current view; retain no pointer across it. arguments.encoding = argc >= 3 ? rb_find_encoding(argv[2]) : rb_ascii8bit_encoding(); return rb_io_buffer_locked_for_reading(self, io_buffer_get_string_locked, (VALUE)&arguments); } /* * call-seq: set_string(string, [offset, [length, [source_offset]]]) -> size * * Efficiently copy from a source String into the buffer, at +offset+ using * +memmove+. * * buf = IO::Buffer.new(8) * # => * # # * # 0x00000000 00 00 00 00 00 00 00 00 ........ * * # set buffer starting from offset 1, take 2 bytes starting from string's * # second * buf.set_string('test', 1, 2, 1) * # => 2 * buf * # => * # # * # 0x00000000 00 65 73 00 00 00 00 00 .es..... * * See also #copy for examples of how buffer writing might be used for changing * associated strings and files. */ static VALUE io_buffer_set_string(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 4); struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self); VALUE string = rb_str_to_str(argv[0]); const void *source_base = RSTRING_PTR(string); size_t source_size = RSTRING_LEN(string); VALUE result = io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1); RB_GC_GUARD(string); return result; } void rb_io_buffer_clear(VALUE self, uint8_t value, size_t offset, size_t length) { struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self); void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); io_buffer_validate_range(buffer, offset, length); if (length == 0) return; RUBY_ASSERT(base != NULL); memset((char*)base + offset, value, length); } /* * call-seq: clear(value = 0, [offset, [length]]) -> self * * Fill buffer with +value+, starting with +offset+ and going for +length+ * bytes. * * buffer = IO::Buffer.for('test').dup * # => * # * # 0x00000000 74 65 73 74 test * * buffer.clear * # => * # * # 0x00000000 00 00 00 00 .... * * buf.clear(1) # fill with 1 * # => * # * # 0x00000000 01 01 01 01 .... * * buffer.clear(2, 1, 2) # fill with 2, starting from offset 1, for 2 bytes * # => * # * # 0x00000000 01 02 02 01 .... * * buffer.clear(2, 1) # fill with 2, starting from offset 1 * # => * # * # 0x00000000 01 02 02 02 .... */ static VALUE io_buffer_clear(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 3); uint8_t value = 0; if (argc >= 1) { value = NUM2UINT(argv[0]); } size_t offset, length; io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length); rb_io_buffer_clear(self, value, offset, length); return self; } static size_t io_buffer_default_size(size_t page_size) { // Platform agnostic default size, based on empirical performance observation: const size_t platform_agnostic_default_size = 64*1024; // Allow user to specify custom default buffer size: const char *default_size = getenv("RUBY_IO_BUFFER_DEFAULT_SIZE"); if (default_size) { // For the purpose of setting a default size, 2^31 is an acceptable maximum: int value = atoi(default_size); // assuming sizeof(int) <= sizeof(size_t) if (value > 0) { return value; } } if (platform_agnostic_default_size < page_size) { return page_size; } return platform_agnostic_default_size; } struct io_buffer_blocking_region_argument { struct rb_io *io; struct rb_io_buffer_view *buffer; rb_blocking_function_t *function; void *data; }; static VALUE io_buffer_blocking_region_begin(VALUE _argument) { struct io_buffer_blocking_region_argument *argument = (void*)_argument; return rb_io_blocking_region(argument->io, argument->function, argument->data); } 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(VALUE io, struct rb_io_buffer_view *buffer, rb_blocking_function_t *function, void *data) { struct rb_io *ioptr; RB_IO_POINTER(io, ioptr); struct io_buffer_blocking_region_argument argument = { .io = ioptr, .buffer = buffer, .function = function, .data = data, }; // The buffer should be locked for the duration of the blocking region. We // always acquire our own reference so another operation cannot release the // allocation while this operation is still using it: 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; // The base pointer to read into: char *base; // The maximum number of bytes to read: size_t length; }; static VALUE io_buffer_read_internal(void *_argument) { struct io_buffer_read_internal_argument *argument = _argument; ssize_t result = read(argument->descriptor, argument->base, argument->length); return rb_fiber_scheduler_io_result(result, errno); } VALUE rb_io_buffer_read(VALUE self, VALUE io, size_t offset, size_t length) { io = rb_io_get_io(io); struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self); io_buffer_validate_range(buffer, offset, length); if (length == 0) return SIZET2NUM(0); VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_read(scheduler, io, self, offset, length); if (!UNDEF_P(result)) { return result; } // The scheduler capability check can invoke Ruby and change the view. buffer = get_io_buffer_view_for_writing(self); io_buffer_validate_range(buffer, offset, length); } void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); RUBY_ASSERT(base != NULL); struct io_buffer_read_internal_argument argument = { .descriptor = rb_io_descriptor(io), .base = (char*)base + offset, .length = length, }; return io_buffer_blocking_region(io, buffer, io_buffer_read_internal, &argument); } /* * call-seq: read(io, [offset, [length]]) -> read length or -errno * * Perform one read operation of at most +length+ bytes from +io+ into the * buffer starting at +offset+. A short read is a normal result. If an error * occurs, return -errno. * * If +offset+ is not given, it defaults to zero, i.e. the beginning of the * buffer. If +length+ is not given, it defaults to the size of the buffer * minus the offset. A zero length is a no-op. * * IO::Buffer.for('test') do |buffer| * p buffer * # => * # * # 0x00000000 74 65 73 74 test * buffer.read(File.open('/dev/urandom', 'rb'), 0, 2) * p buffer * # => * # * # 0x00000000 05 35 73 74 .5st * end */ static VALUE io_buffer_read(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 3); VALUE io = argv[0]; size_t offset, length; io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length); return rb_io_buffer_read(self, io, offset, length); } struct io_buffer_pread_internal_argument { // The file descriptor to read from: int descriptor; // The base pointer to read into: char *base; // The maximum number of bytes to read: size_t length; // The position to read from: rb_off_t from; }; static VALUE io_buffer_pread_internal(void *_argument) { struct io_buffer_pread_internal_argument *argument = _argument; ssize_t result = pread(argument->descriptor, argument->base, argument->length, argument->from); return rb_fiber_scheduler_io_result(result, errno); } VALUE rb_io_buffer_pread(VALUE self, VALUE io, rb_off_t from, size_t offset, size_t length) { io = rb_io_get_io(io); struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self); io_buffer_validate_range(buffer, offset, length); if (length == 0) return SIZET2NUM(0); VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_pread(scheduler, io, from, self, offset, length); if (!UNDEF_P(result)) { return result; } buffer = get_io_buffer_view_for_writing(self); io_buffer_validate_range(buffer, offset, length); } void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); RUBY_ASSERT(base != NULL); struct io_buffer_pread_internal_argument argument = { .descriptor = rb_io_descriptor(io), .base = (char*)base + offset, .length = length, .from = from, }; return io_buffer_blocking_region(io, buffer, io_buffer_pread_internal, &argument); } /* * call-seq: pread(io, from, [offset, [length]]) -> read length or -errno * * Perform one read operation of at most +length+ bytes from +io+ at +from+ * into the buffer starting at +offset+. A short read is a normal result and * the IO's current position is not modified. If an error occurs, return * -errno. * * If +offset+ is not given, it defaults to zero, i.e. the beginning of the * buffer. If +length+ is not given, it defaults to the size of the buffer * minus the offset. A zero length is a no-op. * * IO::Buffer.for('test') do |buffer| * p buffer * # => * # * # 0x00000000 74 65 73 74 test * * # take 2 bytes from the beginning of urandom, * # put them in buffer starting from position 2 * buffer.pread(File.open('/dev/urandom', 'rb'), 0, 2, 2) * p buffer * # => * # * # 0x00000000 05 35 73 74 te.5 * end */ static VALUE io_buffer_pread(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 2, 4); VALUE io = argv[0]; rb_off_t from = NUM2OFFT(argv[1]); size_t offset, length; io_buffer_extract_offset_length(self, argc-2, argv+2, &offset, &length); return rb_io_buffer_pread(self, io, from, offset, length); } struct io_buffer_write_internal_argument { // The file descriptor to write to: int descriptor; // The base pointer to write from: const char *base; // The maximum number of bytes to write: size_t length; }; static VALUE io_buffer_write_internal(void *_argument) { struct io_buffer_write_internal_argument *argument = _argument; ssize_t result = write(argument->descriptor, argument->base, argument->length); return rb_fiber_scheduler_io_result(result, errno); } VALUE rb_io_buffer_write(VALUE self, VALUE io, size_t offset, size_t length) { io = rb_io_get_write_io(rb_io_get_io(io)); struct rb_io_buffer_view *buffer = get_io_buffer_view(self); io_buffer_validate_range(buffer, offset, length); if (length == 0) return SIZET2NUM(0); VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_write(scheduler, io, self, offset, length); if (!UNDEF_P(result)) { return result; } buffer = get_io_buffer_view(self); io_buffer_validate_range(buffer, offset, length); } const void *base; size_t size; io_buffer_get_bytes_for_reading(buffer, &base, &size); RUBY_ASSERT(base != NULL); struct io_buffer_write_internal_argument argument = { .descriptor = rb_io_descriptor(io), .base = (const char*)base + offset, .length = length, }; return io_buffer_blocking_region(io, buffer, io_buffer_write_internal, &argument); } /* * call-seq: write(io, [offset, [length]]) -> written length or -errno * * Perform one write operation of at most +length+ bytes to +io+ from the * buffer starting at +offset+. A short write is a normal result. If an error * occurs, return -errno. * * If +offset+ is not given, it defaults to zero, i.e. the beginning of the * buffer. If +length+ is not given, it defaults to the size of the buffer * minus the offset. A zero length is a no-op. * * out = File.open('output.txt', 'wb') * IO::Buffer.for('1234567').write(out, 0, 3) * * This leads to +123+ being written into output.txt */ static VALUE io_buffer_write(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 3); VALUE io = argv[0]; size_t offset, length; io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length); return rb_io_buffer_write(self, io, offset, length); } struct io_buffer_pwrite_internal_argument { // The file descriptor to write to: int descriptor; // The base pointer to write from: const char *base; // The maximum number of bytes to write: size_t length; // The position to write to: rb_off_t from; }; static VALUE io_buffer_pwrite_internal(void *_argument) { struct io_buffer_pwrite_internal_argument *argument = _argument; ssize_t result = pwrite(argument->descriptor, argument->base, argument->length, argument->from); return rb_fiber_scheduler_io_result(result, errno); } VALUE rb_io_buffer_pwrite(VALUE self, VALUE io, rb_off_t from, size_t offset, size_t length) { io = rb_io_get_write_io(rb_io_get_io(io)); struct rb_io_buffer_view *buffer = get_io_buffer_view(self); io_buffer_validate_range(buffer, offset, length); if (length == 0) return SIZET2NUM(0); VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_pwrite(scheduler, io, from, self, offset, length); if (!UNDEF_P(result)) { return result; } buffer = get_io_buffer_view(self); io_buffer_validate_range(buffer, offset, length); } const void *base; size_t size; io_buffer_get_bytes_for_reading(buffer, &base, &size); RUBY_ASSERT(base != NULL); struct io_buffer_pwrite_internal_argument argument = { .descriptor = rb_io_descriptor(io), .base = (const char*)base + offset, .length = length, .from = from, }; return io_buffer_blocking_region(io, buffer, io_buffer_pwrite_internal, &argument); } /* * call-seq: pwrite(io, from, [offset, [length]]) -> written length or -errno * * Perform one write operation of at most +length+ bytes to +io+ at +from+ * from the buffer starting at +offset+. A short write is a normal result and * the IO's current position is not modified. If an error occurs, return * -errno. * * If +offset+ is not given, it defaults to zero, i.e. the beginning of the * buffer. If +length+ is not given, it defaults to the size of the buffer * minus the offset. A zero length is a no-op. * * 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, 1, 3) * * This leads to +234+ (3 bytes, starting from position 1) being written into * output.txt, starting from file position 2. */ static VALUE io_buffer_pwrite(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 2, 4); VALUE io = argv[0]; rb_off_t from = NUM2OFFT(argv[1]); size_t offset, length; io_buffer_extract_offset_length(self, argc-2, argv+2, &offset, &length); return rb_io_buffer_pwrite(self, io, from, offset, length); } static inline void io_buffer_check_mask_size(size_t size) { if (size == 0) rb_raise(rb_eIOBufferMaskError, "Zero-length mask given!"); } static void memory_and(unsigned char * restrict output, const unsigned char * restrict base, size_t size, const unsigned char * restrict mask, size_t mask_size) { for (size_t offset = 0; offset < size; offset += 1) { output[offset] = base[offset] & mask[offset % mask_size]; } } /* * call-seq: * source & mask -> io_buffer * * Generate a new buffer the same size as the source by applying the binary AND * operation to the source, using the mask, repeating as necessary. * * IO::Buffer.for("1234567890") & IO::Buffer.for("\xFF\x00\x00\xFF") * # => * # # * # 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89. */ static VALUE io_buffer_and(VALUE self, VALUE mask) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask); const void *base; size_t size; io_buffer_get_bytes_for_reading(buffer, &base, &size); const void *mask_base; size_t mask_size; io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size); io_buffer_check_mask_size(mask_size); VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size)); struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output); memory_and(output_buffer->base, base, size, mask_base, mask_size); return output; } static void memory_or(unsigned char * restrict output, const unsigned char * restrict base, size_t size, const unsigned char * restrict mask, size_t mask_size) { for (size_t offset = 0; offset < size; offset += 1) { output[offset] = base[offset] | mask[offset % mask_size]; } } /* * call-seq: * source | mask -> io_buffer * * Generate a new buffer the same size as the source by applying the binary OR * operation to the source, using the mask, repeating as necessary. * * IO::Buffer.for("1234567890") | IO::Buffer.for("\xFF\x00\x00\xFF") * # => * # # * # 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0 */ static VALUE io_buffer_or(VALUE self, VALUE mask) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask); const void *base; size_t size; io_buffer_get_bytes_for_reading(buffer, &base, &size); const void *mask_base; size_t mask_size; io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size); io_buffer_check_mask_size(mask_size); VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size)); struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output); memory_or(output_buffer->base, base, size, mask_base, mask_size); return output; } static void memory_xor(unsigned char * restrict output, const unsigned char * restrict base, size_t size, const unsigned char * restrict mask, size_t mask_size) { for (size_t offset = 0; offset < size; offset += 1) { output[offset] = base[offset] ^ mask[offset % mask_size]; } } /* * call-seq: * source ^ mask -> io_buffer * * Generate a new buffer the same size as the source by applying the binary XOR * operation to the source, using the mask, repeating as necessary. * * IO::Buffer.for("1234567890") ^ IO::Buffer.for("\xFF\x00\x00\xFF") * # => * # # * # 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0 */ static VALUE io_buffer_xor(VALUE self, VALUE mask) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask); const void *base; size_t size; io_buffer_get_bytes_for_reading(buffer, &base, &size); const void *mask_base; size_t mask_size; io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size); io_buffer_check_mask_size(mask_size); VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size)); struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output); memory_xor(output_buffer->base, base, size, mask_base, mask_size); return output; } static void memory_not(unsigned char * restrict output, const unsigned char * restrict base, size_t size) { for (size_t offset = 0; offset < size; offset += 1) { output[offset] = ~base[offset]; } } /* * call-seq: * ~source -> io_buffer * * Generate a new buffer the same size as the source by applying the unary NOT * operation to the source. * * ~IO::Buffer.for("1234567890") * # => * # # * # 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf .......... */ static VALUE io_buffer_not(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); const void *base; size_t size; io_buffer_get_bytes_for_reading(buffer, &base, &size); VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size)); struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output); memory_not(output_buffer->base, base, size); return output; } static inline int io_buffer_overlaps(const struct rb_io_buffer_view *a, const struct rb_io_buffer_view *b) { // Resolve the current base pointers (following slice indirection): void *a_base = NULL, *b_base = NULL; size_t a_size = 0, b_size = 0; if (!io_buffer_try_get_bytes(a, &a_base, &a_size)) return 0; if (!io_buffer_try_get_bytes(b, &b_base, &b_size)) return 0; if (a_size == 0 || b_size == 0 || a_base == NULL || b_base == NULL) return 0; // Compare integer address differences, without ordering unrelated C // pointers or constructing end addresses that could overflow. uintptr_t a_start = (uintptr_t)a_base; uintptr_t b_start = (uintptr_t)b_base; if (a_start <= b_start) return b_start - a_start < a_size; return a_start - b_start < b_size; } static inline void io_buffer_check_overlaps(const struct rb_io_buffer_view *a, const struct rb_io_buffer_view *b) { if (io_buffer_overlaps(a, b)) rb_raise(rb_eIOBufferMaskError, "Mask overlaps source buffer!"); } static void memory_and_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size) { for (size_t offset = 0; offset < size; offset += 1) { base[offset] &= mask[offset % mask_size]; } } /* * call-seq: * source.and!(mask) -> io_buffer * * Modify the source buffer in place by applying the binary AND * operation to the source, using the mask, repeating as necessary. * * source = IO::Buffer.for("1234567890").dup # Make a read/write copy. * # => * # # * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 * * source.and!(IO::Buffer.for("\xFF\x00\x00\xFF")) * # => * # # * # 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89. */ static VALUE io_buffer_and_inplace(VALUE self, VALUE mask) { struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self); struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask); io_buffer_check_mask_size(mask_buffer->size); io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); const void *mask_base; size_t mask_size; io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size); memory_and_inplace(base, size, (unsigned char *)mask_base, mask_size); return self; } static void memory_or_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size) { for (size_t offset = 0; offset < size; offset += 1) { base[offset] |= mask[offset % mask_size]; } } /* * call-seq: * source.or!(mask) -> io_buffer * * Modify the source buffer in place by applying the binary OR * operation to the source, using the mask, repeating as necessary. * * source = IO::Buffer.for("1234567890").dup # Make a read/write copy. * # => * # # * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 * * source.or!(IO::Buffer.for("\xFF\x00\x00\xFF")) * # => * # # * # 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0 */ static VALUE io_buffer_or_inplace(VALUE self, VALUE mask) { struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self); struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask); io_buffer_check_mask_size(mask_buffer->size); io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); const void *mask_base; size_t mask_size; io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size); memory_or_inplace(base, size, (unsigned char *)mask_base, mask_size); return self; } static void memory_xor_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size) { for (size_t offset = 0; offset < size; offset += 1) { base[offset] ^= mask[offset % mask_size]; } } /* * call-seq: * source.xor!(mask) -> io_buffer * * Modify the source buffer in place by applying the binary XOR * operation to the source, using the mask, repeating as necessary. * * source = IO::Buffer.for("1234567890").dup # Make a read/write copy. * # => * # # * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 * * source.xor!(IO::Buffer.for("\xFF\x00\x00\xFF")) * # => * # # * # 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0 */ static VALUE io_buffer_xor_inplace(VALUE self, VALUE mask) { struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self); struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask); io_buffer_check_mask_size(mask_buffer->size); io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); const void *mask_base; size_t mask_size; io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size); memory_xor_inplace(base, size, (unsigned char *)mask_base, mask_size); return self; } static void memory_not_inplace(unsigned char * restrict base, size_t size) { for (size_t offset = 0; offset < size; offset += 1) { base[offset] = ~base[offset]; } } /* * call-seq: * source.not! -> io_buffer * * Modify the source buffer in place by applying the unary NOT * operation to the source. * * source = IO::Buffer.for("1234567890").dup # Make a read/write copy. * # => * # # * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 * * source.not! * # => * # # * # 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf .......... */ static VALUE io_buffer_not_inplace(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self); void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); memory_not_inplace(base, size); return self; } static size_t memory_bit_count(const unsigned char *base, size_t size) { size_t count = 0; // Process 8 bytes at a time for efficiency: const uint64_t *base64 = (const uint64_t *)base; size_t count64 = size / 8; for (size_t i = 0; i < count64; i += 1) { count += rb_popcount64(base64[i]); } // Process any remaining bytes: size_t remaining = size % 8; const unsigned char *tail = base + (count64 * 8); for (size_t i = 0; i < remaining; i += 1) { count += rb_popcount32(tail[i]); } return count; } /* * call-seq: bit_count([offset, [length]]) -> integer * * Returns the number of set bits (1s) in the buffer, also known as the * Hamming weight or population count. An optional +offset+ and +length+ * can be provided to count bits in a subrange of the buffer. * * IO::Buffer.for("\xFF\x00\x0F").bit_count * # => 12 * * IO::Buffer.for("\xFF\x00\x0F").bit_count(1, 2) * # => 4 */ static VALUE io_buffer_bit_count(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 2); size_t offset, length; struct rb_io_buffer_view *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length); io_buffer_validate_range(buffer, offset, length); const void *base; size_t size; io_buffer_get_bytes_for_reading(buffer, &base, &size); if (length == 0) return SIZET2NUM(0); RUBY_ASSERT(base != NULL); size_t count = memory_bit_count((const unsigned char *)base + offset, length); return SIZET2NUM(count); } static bool io_buffer_memory_view_get(VALUE self, rb_memory_view_t *view, int flags) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); void *base = NULL; size_t size = 0; if (!io_buffer_try_get_bytes(buffer, &base, &size) || base == NULL) { return false; } bool readonly = true; if (flags & RUBY_MEMORY_VIEW_WRITABLE) { if (io_buffer_readonly_p(buffer)) { return false; } else { readonly = false; } } rb_memory_view_init_as_byte_array(view, self, base, buffer->size, readonly); if (flags & RUBY_MEMORY_VIEW_FORMAT) { view->format = "C"; } bool request_multi_dimensional = flags & RUBY_MEMORY_VIEW_MULTI_DIMENSIONAL; bool request_strides = (flags & RUBY_MEMORY_VIEW_STRIDES) == RUBY_MEMORY_VIEW_STRIDES; if (request_multi_dimensional || request_strides) { size_t n_metadata = 0; if (request_multi_dimensional) n_metadata++; if (request_strides) n_metadata++; ssize_t *metadata_buffer = ALLOC_N(ssize_t, n_metadata); size_t i = 0; if (request_multi_dimensional) { ssize_t *shape = &metadata_buffer[i]; shape[0] = buffer->size; view->shape = shape; i++; } if (request_strides) { ssize_t *strides = &metadata_buffer[i]; strides[0] = 1; view->strides = strides; i++; } view->private_data = metadata_buffer; } io_buffer_lock(buffer); return true; } static bool io_buffer_memory_view_release(VALUE self, rb_memory_view_t *view) { rb_io_buffer_unlock(self); if (view->private_data) { xfree(view->private_data); } return true; } static bool io_buffer_memory_view_available_p(VALUE self) { struct rb_io_buffer_view *buffer = get_io_buffer_view(self); void *base = NULL; size_t size = 0; return io_buffer_try_get_bytes(buffer, &base, &size) && base != NULL; } static const rb_memory_view_entry_t io_buffer_memory_view_entry = { .get_func = io_buffer_memory_view_get, .release_func = io_buffer_memory_view_release, .available_p_func = io_buffer_memory_view_available_p, }; /* * Document-class: IO::Buffer * * IO::Buffer is the common byte-view interface. Its concrete subclasses are * IO::Buffer::Storage, which manages backing storage, and IO::Buffer::Slice, * which represents a parent-relative view. Both are IO::Buffer instances. * * IO::Buffer.new, IO::Buffer.for, and IO::Buffer.map create Storage objects. * The abstract base itself cannot be allocated directly. Each factory creates * one storage-bearing object, with no additional view needed for byte access. * * Storage#resize may reallocate; Storage#free and Storage#transfer manage the * backing storage. Slice#resize and Slice#advance only change a view, and * Slice never acquires its own storage. Duplicating Storage copies its bytes; * duplicating a Slice copies its range and source reference without copying * bytes. * * IO::Buffer is an efficient zero-copy buffer for input/output. There are * typical use cases: * * * 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). * * 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::Scheduler#io_read and Fiber::Scheduler#io_write and parsing binary * protocols. * * == MemoryView Support * * IO::Buffer supports the C-level MemoryView protocol, so C * extensions can use +rb_memory_view_get()+ to access the buffer's * memory directly (zero-copy) as a 1-dimensional contiguous array of * bytes. The memory view is writable if the buffer is not * #readonly? and +RUBY_MEMORY_VIEW_WRITABLE+ is specified. * * While a MemoryView is exported, the buffer is locked. * * == Examples of Usage * * Empty buffer: * * buffer = IO::Buffer.new(8) # create empty 8-byte buffer * # => * # # * # ... * buffer * # => * # * # 0x00000000 00 00 00 00 00 00 00 00 * buffer.set_string('test', 2) # put there bytes of the "test" string, starting from offset 2 * # => 4 * buffer.get_string # get the result * # => "\x00\x00test\x00\x00" * * \Buffer from string: * * string = 'data' * IO::Buffer.for(string) do |buffer| * buffer * # => * # # * # 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 * # => * # # * # 0x00000000 64 2d 2d 2d d--- * string # original string changed, too * # => "d---" * end * * \Buffer from file: * * File.write('test.txt', 'test data') * # => 9 * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) * # => * # # * # ... * buffer.get_string(5, 2) # read 2 bytes, starting from offset 5 * # => "da" * buffer.set_string('---', 1) # attempt to write * # in `set_string': Buffer is not writable! (IO::Buffer::AccessError) * * # To create writable file-mapped buffer * # Open file for read-write, pass size, offset, and flags=0 * buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 9, 0, 0) * buffer.set_string('---', 1) * # => 3 -- bytes written * File.read('test.txt') * # => "t--- data" * * 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. */ void Init_IO_Buffer(void) { rb_cIOBuffer = rb_define_class_under(rb_cIO, "Buffer", rb_cObject); rb_undef_alloc_func(rb_cIOBuffer); /* * Document-class: IO::Buffer::Storage * * A buffer that manages backing storage, including internally allocated, * mapped, String-backed, and borrowed memory. Storage provides Buffer's * byte-access interface together with allocation resizing, #free, #transfer, * and storage-specific predicates. It need not own the bytes it references. * * IO::Buffer.new, IO::Buffer.for, and IO::Buffer.map create Storage objects. * The Storage object is directly usable as a buffer; no separate view object * is needed unless a caller requests a slice. Duplicating Storage copies its * bytes into independent storage, unlike duplicating an IO::Buffer::Slice. */ rb_cIOBufferStorage = rb_define_class_under(rb_cIOBuffer, "Storage", rb_cIOBuffer); /* * Document-class: IO::Buffer::Slice * * A zero-copy, parent-relative view of an IO::Buffer or another Slice. * Create a view with IO::Buffer#slice or IO::Buffer::Slice.new: * * buffer = IO::Buffer.new(6) * buffer.set_string("abcdef") * parent = buffer.slice(1, 4) * child = parent.slice(1, 2) * child.source.equal?(parent) # => true * child.get_string # => "cd" * parent.advance(1) * child.get_string # => "de" * * A slice retains its immediate source and follows that source's current * view. Its range is valid only while every parent is valid and the range * fits within its source. Moving the backing allocation does not itself * invalidate a slice. Shrinking a parent can invalidate descendants; * growing it again can restore their validity. * * Slice inherits Buffer's byte-access interface but does not manage storage. * Resizing a slice only changes its extent within its parent's bounds, and * advancing it consumes bytes from the front of its view. Neither operation * allocates or releases storage. Slice has no +free+ or +transfer+ method, * nor Storage's predicates such as +mapped?+ or +internal?+. * * Writes modify the source's bytes and respect its current permissions. * Locking a slice protects the backing allocation; +locked?+ reports that * shared lock state. Locked or read-only views may still be resized or * advanced, but frozen views cannot change their range. Locking does not * serialize access to bytes or view metadata. * * Duplicating or cloning a Slice copies its view rather than its bytes. * Both Slice and Storage are subclasses of IO::Buffer. */ rb_cIOBufferSlice = rb_define_class_under(rb_cIOBuffer, "Slice", rb_cIOBuffer); rb_define_alloc_func(rb_cIOBufferSlice, io_buffer_slice_allocate); rb_define_method(rb_cIOBufferSlice, "initialize", io_buffer_slice_initialize, -1); rb_define_method(rb_cIOBufferSlice, "initialize_copy", io_buffer_slice_initialize_copy, 1); /* 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_cIOBufferStorage, rb_io_buffer_type_allocate); rb_define_singleton_method(rb_cIOBuffer, "new", io_buffer_s_new, -1); 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; GetSystemInfo(&info); RUBY_IO_BUFFER_PAGE_SIZE = info.dwPageSize; RUBY_IO_BUFFER_MAP_ALIGNMENT = info.dwAllocationGranularity; #else /* not WIN32 */ RUBY_IO_BUFFER_PAGE_SIZE = sysconf(_SC_PAGESIZE); RUBY_IO_BUFFER_MAP_ALIGNMENT = RUBY_IO_BUFFER_PAGE_SIZE; #endif RUBY_IO_BUFFER_DEFAULT_SIZE = io_buffer_default_size(RUBY_IO_BUFFER_PAGE_SIZE); /* The IO::Buffer interface version. */ rb_define_const(rb_cIOBuffer, "VERSION", INT2NUM(RUBY_IO_BUFFER_VERSION)); /* 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 alignment required for file mapping offsets. Mapping sizes do not need to be aligned. */ rb_define_const(rb_cIOBuffer, "MAP_ALIGNMENT", SIZET2NUM(RUBY_IO_BUFFER_MAP_ALIGNMENT)); /* 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); // Storage factories belong to Buffer/Storage, not to Slice subclasses. rb_undef_method(rb_singleton_class(rb_cIOBufferSlice), "for"); rb_undef_method(rb_singleton_class(rb_cIOBufferSlice), "string"); rb_undef_method(rb_singleton_class(rb_cIOBufferSlice), "map"); rb_define_method(rb_cIOBufferStorage, "initialize", rb_io_buffer_initialize, -1); rb_define_method(rb_cIOBufferStorage, "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, -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); rb_define_method(rb_cIOBufferStorage, "transfer", io_buffer_transfer, 0); /* 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 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)); /* 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); rb_define_method(rb_cIOBuffer, "empty?", rb_io_buffer_empty_p, 0); rb_define_method(rb_cIOBufferStorage, "external?", rb_io_buffer_external_p, 0); rb_define_method(rb_cIOBufferStorage, "internal?", rb_io_buffer_internal_p, 0); rb_define_method(rb_cIOBufferStorage, "mapped?", rb_io_buffer_mapped_p, 0); rb_define_method(rb_cIOBufferStorage, "shared?", rb_io_buffer_shared_p, 0); rb_define_method(rb_cIOBuffer, "locked?", rb_io_buffer_locked_p, 0); rb_define_method(rb_cIOBufferStorage, "private?", rb_io_buffer_private_p, 0); rb_define_method(rb_cIOBuffer, "readonly?", io_buffer_readonly, 0); // Locking to prevent changes while using pointer: // rb_define_method(rb_cIOBuffer, "lock", rb_io_buffer_lock, 0); // rb_define_method(rb_cIOBuffer, "unlock", rb_io_buffer_unlock, 0); rb_define_method(rb_cIOBuffer, "locked", rb_io_buffer_locked, 0); // Manipulation: rb_define_method(rb_cIOBuffer, "source", io_buffer_source, 0); rb_define_method(rb_cIOBuffer, "slice", io_buffer_slice, -1); rb_define_method(rb_cIOBuffer, "<=>", rb_io_buffer_compare, 1); rb_define_method(rb_cIOBufferStorage, "resize", io_buffer_resize, 1); rb_define_method(rb_cIOBufferSlice, "resize", io_buffer_resize, 1); rb_define_method(rb_cIOBuffer, "advance", io_buffer_advance, 1); rb_define_method(rb_cIOBuffer, "clear", io_buffer_clear, -1); rb_define_method(rb_cIOBufferStorage, "free", io_buffer_free, 0); rb_include_module(rb_cIOBuffer, rb_mComparable); #define IO_BUFFER_DEFINE_DATA_TYPE(name) RB_IO_BUFFER_DATA_TYPE_##name = rb_intern_const(#name) IO_BUFFER_DEFINE_DATA_TYPE(U8); IO_BUFFER_DEFINE_DATA_TYPE(S8); IO_BUFFER_DEFINE_DATA_TYPE(u16); IO_BUFFER_DEFINE_DATA_TYPE(U16); IO_BUFFER_DEFINE_DATA_TYPE(s16); IO_BUFFER_DEFINE_DATA_TYPE(S16); IO_BUFFER_DEFINE_DATA_TYPE(u32); IO_BUFFER_DEFINE_DATA_TYPE(U32); IO_BUFFER_DEFINE_DATA_TYPE(s32); IO_BUFFER_DEFINE_DATA_TYPE(S32); IO_BUFFER_DEFINE_DATA_TYPE(u64); IO_BUFFER_DEFINE_DATA_TYPE(U64); IO_BUFFER_DEFINE_DATA_TYPE(s64); IO_BUFFER_DEFINE_DATA_TYPE(S64); IO_BUFFER_DEFINE_DATA_TYPE(u128); IO_BUFFER_DEFINE_DATA_TYPE(U128); IO_BUFFER_DEFINE_DATA_TYPE(s128); IO_BUFFER_DEFINE_DATA_TYPE(S128); IO_BUFFER_DEFINE_DATA_TYPE(f32); IO_BUFFER_DEFINE_DATA_TYPE(F32); IO_BUFFER_DEFINE_DATA_TYPE(f64); IO_BUFFER_DEFINE_DATA_TYPE(F64); #undef IO_BUFFER_DEFINE_DATA_TYPE rb_define_singleton_method(rb_cIOBuffer, "size_of", io_buffer_size_of, 1); // Data access: rb_define_method(rb_cIOBuffer, "get_value", io_buffer_get_value, 2); rb_define_method(rb_cIOBuffer, "get_values", io_buffer_get_values, 2); rb_define_method(rb_cIOBuffer, "each", io_buffer_each, -1); rb_define_method(rb_cIOBuffer, "values", io_buffer_values, -1); rb_define_method(rb_cIOBuffer, "each_byte", io_buffer_each_byte, -1); rb_define_method(rb_cIOBuffer, "set_value", io_buffer_set_value, 3); rb_define_method(rb_cIOBuffer, "set_values", io_buffer_set_values, 3); rb_define_method(rb_cIOBuffer, "copy", io_buffer_copy, -1); 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 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); rb_define_method(rb_cIOBuffer, "~", io_buffer_not, 0); rb_define_method(rb_cIOBuffer, "and!", io_buffer_and_inplace, 1); rb_define_method(rb_cIOBuffer, "or!", io_buffer_or_inplace, 1); rb_define_method(rb_cIOBuffer, "xor!", io_buffer_xor_inplace, 1); rb_define_method(rb_cIOBuffer, "not!", io_buffer_not_inplace, 0); rb_define_method(rb_cIOBuffer, "bit_count", io_buffer_bit_count, -1); // IO operations: rb_define_method(rb_cIOBuffer, "read", io_buffer_read, -1); rb_define_method(rb_cIOBuffer, "pread", io_buffer_pread, -1); rb_define_method(rb_cIOBuffer, "write", io_buffer_write, -1); rb_define_method(rb_cIOBuffer, "pwrite", io_buffer_pwrite, -1); // MemoryView: rb_memory_view_register(rb_cIOBuffer, &io_buffer_memory_view_entry); }