Ruby 4.1.0dev (2026-09-30 revision 696ebaad23e46382f04235e9663767ab644795b4)
io_buffer.c (696ebaad23e46382f04235e9663767ab644795b4)
1/**********************************************************************
2
3 io_buffer.c
4
5 Copyright (C) 2021 Samuel Grant Dawson Williams
6
7**********************************************************************/
8
9#include "ruby/io/buffer.h"
11#include "ruby/memory_view.h"
12
13// For `rb_nogvl`.
14#include "ruby/thread.h"
15
16#include "internal.h"
17#include "internal/array.h"
18#include "internal/bits.h"
19#include "internal/error.h"
20#include "internal/gc.h"
21#include "internal/numeric.h"
22#include "internal/string.h"
23#include "internal/io.h"
24#include "internal/io_buffer.h"
25
26VALUE rb_cIOBuffer;
27VALUE rb_eIOBufferLockedError;
28VALUE rb_eIOBufferAllocationError;
29VALUE rb_eIOBufferAccessError;
30VALUE rb_eIOBufferInvalidatedError;
31VALUE rb_eIOBufferMaskError;
32
33size_t RUBY_IO_BUFFER_PAGE_SIZE;
34size_t RUBY_IO_BUFFER_MAP_ALIGNMENT;
35size_t RUBY_IO_BUFFER_DEFAULT_SIZE;
36
37#ifdef _WIN32
38#else
39#include <unistd.h>
40#include <sys/mman.h>
41#endif
42
43enum {
44 RB_IO_BUFFER_HEXDUMP_DEFAULT_WIDTH = 16,
45 RB_IO_BUFFER_HEXDUMP_MAXIMUM_WIDTH = 1024,
46
47 RB_IO_BUFFER_INSPECT_HEXDUMP_MAXIMUM_SIZE = 256,
48 RB_IO_BUFFER_INSPECT_HEXDUMP_WIDTH = 16,
49
50 // This is used to validate the flags given by the user.
51 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,
52
53 RB_IO_BUFFER_ALLOCATION_FLAGS = RB_IO_BUFFER_INTERNAL | RB_IO_BUFFER_MAPPED,
54 RB_IO_BUFFER_MAPPING_FLAGS = RB_IO_BUFFER_SHARED | RB_IO_BUFFER_PRIVATE,
55
56 RB_IO_BUFFER_DEBUG = 0,
57};
58
60 // Without a source (source == Qnil), this is an absolute pointer to owned
61 // or borrowed memory. Ownership is determined by the flags, not by the
62 // presence of a source.
63 //
64 // With a source (String or IO::Buffer), this is a byte offset into it.
65 // The absolute pointer is resolved as `source_base + offset` on demand
66 // (see io_buffer_try_get_bytes), so source relocation preserves the
67 // logical range. Use io_buffer_slice_offset() to read the offset.
68 void *base;
69 size_t size;
70
71 enum rb_io_buffer_flags flags;
72 // Locking and unlocking are performed with the GVL held.
73 size_t lock_count;
74
75#if defined(_WIN32)
76 HANDLE mapping;
77#endif
78
79 VALUE source;
80};
81
82static inline void *
83io_buffer_map_memory(size_t size, int flags)
84{
85#if defined(_WIN32)
86 void * base = VirtualAlloc(0, size, MEM_COMMIT, PAGE_READWRITE);
87
88 if (!base) {
89 rb_sys_fail("io_buffer_map_memory:VirtualAlloc");
90 }
91#else
92 int mmap_flags = MAP_ANONYMOUS;
93 if (flags & RB_IO_BUFFER_SHARED) {
94 mmap_flags |= MAP_SHARED;
95 }
96 else {
97 mmap_flags |= MAP_PRIVATE;
98 }
99
100 void * base = mmap(NULL, size, PROT_READ | PROT_WRITE, mmap_flags, -1, 0);
101
102 if (base == MAP_FAILED) {
103 rb_sys_fail("io_buffer_map_memory:mmap");
104 }
105
106 ruby_annotate_mmap(base, size, "Ruby:io_buffer_map_memory");
107#endif
108
109 return base;
110}
111
112static void
113io_buffer_map_file(struct rb_io_buffer *buffer, int descriptor, size_t size, rb_off_t offset, enum rb_io_buffer_flags flags)
114{
115#if defined(_WIN32)
116 HANDLE file = (HANDLE)_get_osfhandle(descriptor);
117 if (!file) rb_sys_fail("io_buffer_map_descriptor:_get_osfhandle");
118
119 DWORD protect = PAGE_READONLY, access = FILE_MAP_READ;
120
121 if (flags & RB_IO_BUFFER_READONLY) {
122 buffer->flags |= RB_IO_BUFFER_READONLY;
123 }
124 else {
125 protect = PAGE_READWRITE;
126 access = FILE_MAP_WRITE;
127 }
128
129 if (flags & RB_IO_BUFFER_PRIVATE) {
130 protect = PAGE_WRITECOPY;
131 access = FILE_MAP_COPY;
132 buffer->flags |= RB_IO_BUFFER_PRIVATE;
133 }
134 else {
135 // This buffer refers to external buffer.
136 buffer->flags |= RB_IO_BUFFER_EXTERNAL;
137 buffer->flags |= RB_IO_BUFFER_SHARED;
138 }
139
140 HANDLE mapping = CreateFileMapping(file, NULL, protect, 0, 0, NULL);
141 if (RB_IO_BUFFER_DEBUG) fprintf(stderr, "io_buffer_map_file:CreateFileMapping -> %p\n", mapping);
142 if (!mapping) rb_sys_fail("io_buffer_map_descriptor:CreateFileMapping");
143
144 void *base = MapViewOfFile(mapping, access, (DWORD)(offset >> 32), (DWORD)(offset & 0xFFFFFFFF), size);
145
146 if (!base) {
147 CloseHandle(mapping);
148 rb_sys_fail("io_buffer_map_file:MapViewOfFile");
149 }
150
151 buffer->mapping = mapping;
152#else
153 int protect = PROT_READ, access = 0;
154
155 if (flags & RB_IO_BUFFER_READONLY) {
156 buffer->flags |= RB_IO_BUFFER_READONLY;
157 }
158 else {
159 protect |= PROT_WRITE;
160 }
161
162 if (flags & RB_IO_BUFFER_PRIVATE) {
163 buffer->flags |= RB_IO_BUFFER_PRIVATE;
164 access |= MAP_PRIVATE;
165 }
166 else {
167 // This buffer refers to external buffer.
168 buffer->flags |= RB_IO_BUFFER_EXTERNAL;
169 buffer->flags |= RB_IO_BUFFER_SHARED;
170 access |= MAP_SHARED;
171 }
172
173 void *base = mmap(NULL, size, protect, access, descriptor, offset);
174
175 if (base == MAP_FAILED) {
176 rb_sys_fail("io_buffer_map_file:mmap");
177 }
178#endif
179
180 buffer->base = base;
181 buffer->size = size;
182
183 buffer->flags |= RB_IO_BUFFER_MAPPED;
184 buffer->flags |= RB_IO_BUFFER_FILE;
185}
186
187static void
188io_buffer_experimental(void)
189{
190 static int warned = 0;
191
192 if (warned) return;
193
194 warned = 1;
195
196 if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL)) {
198 "IO::Buffer is experimental and both the Ruby and C interface may change in the future!"
199 );
200 }
201}
202
203static void
204io_buffer_zero(struct rb_io_buffer *buffer)
205{
206 buffer->base = NULL;
207 buffer->size = 0;
208 buffer->flags = 0;
209 buffer->lock_count = 0;
210#if defined(_WIN32)
211 buffer->mapping = NULL;
212#endif
213 buffer->source = Qnil;
214}
215
216static void
217io_buffer_initialize(VALUE self, struct rb_io_buffer *buffer, void *base, size_t size, enum rb_io_buffer_flags flags, VALUE source)
218{
219 if (source != Qnil) {
220 // The buffer is backed by another object (e.g. a String). Here `base`
221 // is a byte *offset* into that source rather than an absolute pointer,
222 // and the memory is not owned by this buffer. The absolute base is
223 // resolved on demand as `source_base + offset` (see
224 // io_buffer_try_get_bytes), so it stays valid if the source moves.
225 }
226 else if (base) {
227 // If we are provided a pointer, we use it.
228 }
229 else if (size) {
230 // If we are provided a non-zero size, we allocate it:
231 if (flags & RB_IO_BUFFER_INTERNAL) {
232 base = calloc(size, 1);
233 }
234 else if (flags & RB_IO_BUFFER_MAPPED) {
235 base = io_buffer_map_memory(size, flags);
236 }
237
238 if (!base) {
239 rb_raise(rb_eIOBufferAllocationError, "Could not allocate buffer!");
240 }
241 }
242 else {
243 // Otherwise we don't do anything.
244 return;
245 }
246
247 buffer->base = base;
248 buffer->size = size;
249 buffer->flags = flags;
250 buffer->lock_count = 0;
251 RB_OBJ_WRITE(self, &buffer->source, source);
252
253#if defined(_WIN32)
254 buffer->mapping = NULL;
255#endif
256}
257
258static void
259io_buffer_release(struct rb_io_buffer *buffer)
260{
261 if (buffer->base) {
262 if (buffer->flags & RB_IO_BUFFER_INTERNAL) {
263 free(buffer->base);
264 }
265
266 if (buffer->flags & RB_IO_BUFFER_MAPPED) {
267#ifdef _WIN32
268 if (buffer->flags & RB_IO_BUFFER_FILE) {
269 UnmapViewOfFile(buffer->base);
270 }
271 else {
272 VirtualFree(buffer->base, 0, MEM_RELEASE);
273 }
274#else
275 munmap(buffer->base, buffer->size);
276#endif
277 }
278
279 // Previously we had this, but we found out due to the way GC works, we
280 // can't refer to any other Ruby objects here.
281 // if (RB_TYPE_P(buffer->source, T_STRING)) {
282 // rb_str_unlocktmp(buffer->source);
283 // }
284 }
285
286#if defined(_WIN32)
287 if (buffer->mapping) {
288 if (RB_IO_BUFFER_DEBUG) fprintf(stderr, "io_buffer_release:CloseHandle -> %p\n", buffer->mapping);
289 if (!CloseHandle(buffer->mapping)) {
290 fprintf(stderr, "io_buffer_release:GetLastError -> %lu\n", GetLastError());
291 }
292 buffer->mapping = NULL;
293 }
294#endif
295
296 io_buffer_zero(buffer);
297}
298
299static void
300rb_io_buffer_type_mark(void *_buffer)
301{
302 struct rb_io_buffer *buffer = _buffer;
303 if (buffer->source != Qnil) {
304 if (RB_TYPE_P(buffer->source, T_STRING)) {
305 // The `source` String has to be pinned, because the `base` may point to the embedded String content,
306 // which can be otherwise moved by GC compaction.
307 rb_gc_mark(buffer->source);
308 } else {
309 rb_gc_mark_movable(buffer->source);
310 }
311 }
312}
313
314static void
315rb_io_buffer_type_compact(void *_buffer)
316{
317 struct rb_io_buffer *buffer = _buffer;
318 if (buffer->source != Qnil) {
319 if (RB_TYPE_P(buffer->source, T_STRING)) {
320 // The `source` String has to be pinned, because the `base` may point to the embedded String content,
321 // which can be otherwise moved by GC compaction.
322 } else {
323 rb_gc_update_moved(&buffer->source);
324 }
325 }
326}
327
328static void
329rb_io_buffer_type_free(void *_buffer)
330{
331 struct rb_io_buffer *buffer = _buffer;
332
333 io_buffer_release(buffer);
334}
335
336static size_t
337rb_io_buffer_type_size(const void *_buffer)
338{
339 const struct rb_io_buffer *buffer = _buffer;
340 size_t total = sizeof(struct rb_io_buffer);
341
342 if (buffer->flags) {
343 total += buffer->size;
344 }
345
346 return total;
347}
348
349static const rb_data_type_t rb_io_buffer_type = {
350 .wrap_struct_name = "IO::Buffer",
351 .function = {
352 .dmark = rb_io_buffer_type_mark,
353 .dfree = rb_io_buffer_type_free,
354 .dsize = rb_io_buffer_type_size,
355 .dcompact = rb_io_buffer_type_compact,
356 },
357 .data = NULL,
358 .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE,
359};
360
361static struct rb_io_buffer *
362get_io_buffer(VALUE self)
363{
364 struct rb_io_buffer *buffer;
365 TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
366 return buffer;
367}
368
369static bool
370io_buffer_slice_p(struct rb_io_buffer *buffer)
371{
372 return rb_typeddata_is_kind_of(buffer->source, &rb_io_buffer_type);
373}
374
375// For a slice (io_buffer_slice_p), the `base` field stores the byte offset of
376// the slice within its (root) source rather than an absolute pointer.
377static inline size_t
378io_buffer_slice_offset(const struct rb_io_buffer *buffer)
379{
380 return (uintptr_t)buffer->base;
381}
382
383// Return the buffer which owns the lock count. A slice backed by another
384// buffer shares that source buffer's lock count. Other external sources, such
385// as strings, manage their own lifetime and do not share buffer lock state.
386static struct rb_io_buffer *
387io_buffer_lock_owner(struct rb_io_buffer *buffer)
388{
389 if (io_buffer_slice_p(buffer)) {
390 return get_io_buffer(buffer->source);
391 }
392
393 return buffer;
394}
395
396static bool
397io_buffer_locked(struct rb_io_buffer *buffer)
398{
399 return io_buffer_lock_owner(buffer)->lock_count > 0;
400}
401
402static inline enum rb_io_buffer_flags
403io_buffer_extract_flags(VALUE argument)
404{
405 if (rb_int_negative_p(argument)) {
406 rb_raise(rb_eArgError, "Flags can't be negative!");
407 }
408
409 enum rb_io_buffer_flags flags = RB_NUM2UINT(argument);
410
411 // We deliberately ignore unknown flags. Any future flags which are exposed this way should be safe to ignore.
412 return flags & RB_IO_BUFFER_FLAGS_MASK;
413}
414
415static inline enum rb_io_buffer_flags
416io_buffer_flags_for_map(enum rb_io_buffer_flags flags)
417{
418 if (flags & RB_IO_BUFFER_INTERNAL) {
419 rb_raise(rb_eArgError, "IO::Buffer::INTERNAL can't be used with IO::Buffer.map!");
420 }
421
422 if (flags & RB_IO_BUFFER_EXTERNAL) {
423 rb_raise(rb_eArgError, "IO::Buffer::EXTERNAL can't be used with IO::Buffer.map!");
424 }
425
426 if ((flags & RB_IO_BUFFER_MAPPING_FLAGS) == RB_IO_BUFFER_MAPPING_FLAGS) {
427 rb_raise(rb_eArgError, "Flags can't include both IO::Buffer::SHARED and IO::Buffer::PRIVATE!");
428 }
429
430 return flags;
431}
432
433// Extract an offset argument, which must be a non-negative integer.
434static inline size_t
435io_buffer_extract_offset(VALUE argument)
436{
437 if (rb_int_negative_p(argument)) {
438 rb_raise(rb_eArgError, "Offset can't be negative!");
439 }
440
441 return NUM2SIZET(argument);
442}
443
444// Extract a length argument, which must be a non-negative integer.
445// Length is generally considered a mutable property of an object and
446// semantically should be considered a subset of "size" as a concept.
447static inline size_t
448io_buffer_extract_length(VALUE argument)
449{
450 if (rb_int_negative_p(argument)) {
451 rb_raise(rb_eArgError, "Length can't be negative!");
452 }
453
454 return NUM2SIZET(argument);
455}
456
457// Extract a size argument, which must be a non-negative integer.
458// Size is generally considered an immutable property of an object.
459static inline size_t
460io_buffer_extract_size(VALUE argument)
461{
462 if (rb_int_negative_p(argument)) {
463 rb_raise(rb_eArgError, "Size can't be negative!");
464 }
465
466 return NUM2SIZET(argument);
467}
468
469// Extract a width argument, which must be a non-negative integer, and must be
470// at least the given minimum and at most RB_IO_BUFFER_HEXDUMP_MAXIMUM_WIDTH.
471static inline size_t
472io_buffer_extract_width(VALUE argument, size_t minimum)
473{
474 if (rb_int_negative_p(argument)) {
475 rb_raise(rb_eArgError, "Width can't be negative!");
476 }
477
478 size_t width = NUM2SIZET(argument);
479
480 if (width < minimum) {
481 rb_raise(rb_eArgError, "Width must be at least %" PRIuSIZE "!", minimum);
482 }
483
484 if (width > RB_IO_BUFFER_HEXDUMP_MAXIMUM_WIDTH) {
485 rb_raise(rb_eArgError, "Width must be at most %" PRIuSIZE "!", (size_t)RB_IO_BUFFER_HEXDUMP_MAXIMUM_WIDTH);
486 }
487
488 return width;
489}
490
491// Compute the default length for a buffer, given an offset into that buffer.
492// The default length is the size of the buffer minus the offset. The offset
493// must be less than the size of the buffer otherwise the length will be
494// invalid; in that case, an ArgumentError exception will be raised.
495static inline size_t
496io_buffer_default_length(const struct rb_io_buffer *buffer, size_t offset)
497{
498 if (offset > buffer->size) {
499 rb_raise(rb_eArgError, "The given offset is bigger than the buffer size!");
500 }
501
502 // Note that the "length" is computed by the size the offset.
503 return buffer->size - offset;
504}
505
506// Extract the optional offset and length arguments, returning the buffer.
507// The offset and length are optional, but if they are provided, they must be
508// positive integers. If the offset is not provided, it defaults to zero. If
509// the length is not provided, it defaults to the buffer size minus the offset.
510static inline struct rb_io_buffer *
511io_buffer_extract_offset_length(VALUE self, int argc, VALUE argv[], size_t *offset, size_t *length)
512{
513 struct rb_io_buffer *buffer = get_io_buffer(self);
514
515 if (argc >= 1 && !NIL_P(argv[0])) {
516 *offset = io_buffer_extract_offset(argv[0]);
517 }
518 else {
519 *offset = 0;
520 }
521
522 if (argc >= 2 && !NIL_P(argv[1])) {
523 *length = io_buffer_extract_length(argv[1]);
524 }
525 else {
526 *length = io_buffer_default_length(buffer, *offset);
527 }
528
529 return buffer;
530}
531
532VALUE
533rb_io_buffer_type_allocate(VALUE self)
534{
535 io_buffer_experimental();
536
537 struct rb_io_buffer *buffer = NULL;
538 VALUE instance = TypedData_Make_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
539
540 io_buffer_zero(buffer);
541
542 return instance;
543}
544
545static VALUE io_buffer_for_make_instance(VALUE klass, VALUE string, enum rb_io_buffer_flags flags)
546{
547 VALUE instance = rb_io_buffer_type_allocate(klass);
548
549 struct rb_io_buffer *buffer = get_io_buffer(instance);
550
551 flags |= RB_IO_BUFFER_EXTERNAL;
552
553 if (RB_OBJ_FROZEN(string))
554 flags |= RB_IO_BUFFER_READONLY;
555
556 if (!(flags & RB_IO_BUFFER_READONLY))
557 rb_str_modify(string);
558
559 // String-backed buffers are offset-based: pass offset 0 (the whole string),
560 // resolved as `RSTRING_PTR(string) + offset` on demand.
561 io_buffer_initialize(instance, buffer, (void *)0, RSTRING_LEN(string), flags, string);
562
563 return instance;
564}
565
567 VALUE klass;
568 VALUE string;
569 VALUE instance;
570 enum rb_io_buffer_flags flags;
571};
572
573static VALUE
574io_buffer_for_yield_instance(VALUE _arguments)
575{
577
578 arguments->instance = io_buffer_for_make_instance(arguments->klass, arguments->string, arguments->flags);
579
580 if (!RB_OBJ_FROZEN(arguments->string)) {
581 rb_str_locktmp(arguments->string);
582 }
583
584 return rb_yield(arguments->instance);
585}
586
587static VALUE
588io_buffer_for_yield_instance_ensure(VALUE _arguments)
589{
591
592 if (arguments->instance != Qnil) {
593 rb_io_buffer_free(arguments->instance);
594 }
595
596 if (!RB_OBJ_FROZEN(arguments->string)) {
597 rb_str_unlocktmp(arguments->string);
598 }
599
600 return Qnil;
601}
602
604 VALUE klass;
605 VALUE string;
606 VALUE instance;
607 enum rb_io_buffer_flags flags;
608 int locked;
609 VALUE (*callback)(VALUE, VALUE);
610 VALUE argument;
611};
612
613static VALUE rb_io_buffer_locked_ensure(VALUE self);
614
616 VALUE buffer;
617 VALUE (*callback)(VALUE, VALUE);
618 VALUE argument;
619};
620
621static VALUE
622io_buffer_for_locked_callback_call(VALUE _arguments)
623{
624 struct io_buffer_for_locked_callback_arguments *arguments = (void *)_arguments;
625
626 return arguments->callback(arguments->buffer, arguments->argument);
627}
628
629static VALUE
630io_buffer_for_locked_callback(VALUE buffer, VALUE (*callback)(VALUE, VALUE), VALUE argument)
631{
632 struct io_buffer_for_locked_callback_arguments arguments = {
633 .buffer = buffer,
634 .callback = callback,
635 .argument = argument,
636 };
637
638 rb_io_buffer_lock(buffer);
639 return rb_ensure(io_buffer_for_locked_callback_call, (VALUE)&arguments, rb_io_buffer_locked_ensure, buffer);
640}
641
642static VALUE
643io_buffer_for_callback_call(VALUE _arguments)
644{
645 struct io_buffer_for_callback_arguments *arguments = (struct io_buffer_for_callback_arguments *)_arguments;
646
647 arguments->instance = io_buffer_for_make_instance(arguments->klass, arguments->string, arguments->flags);
648
649 if (!RB_OBJ_FROZEN(arguments->string)) {
650 rb_str_locktmp(arguments->string);
651 arguments->locked = 1;
652 }
653
654 return io_buffer_for_locked_callback(arguments->instance, arguments->callback, arguments->argument);
655}
656
657static VALUE
658io_buffer_for_callback_ensure(VALUE _arguments)
659{
660 struct io_buffer_for_callback_arguments *arguments = (struct io_buffer_for_callback_arguments *)_arguments;
661
662 if (arguments->instance != Qnil) {
663 rb_io_buffer_free(arguments->instance);
664 }
665
666 if (arguments->locked) {
667 rb_str_unlocktmp(arguments->string);
668 }
669
670 return Qnil;
671}
672
673VALUE
674rb_io_buffer_for_reading(VALUE string_or_buffer, VALUE (*callback)(VALUE, VALUE), VALUE argument)
675{
676 if (rb_obj_is_kind_of(string_or_buffer, rb_cIOBuffer)) {
677 return io_buffer_for_locked_callback(string_or_buffer, callback, argument);
678 }
679 else if (RB_TYPE_P(string_or_buffer, T_STRING)) {
680 StringValue(string_or_buffer);
681 struct io_buffer_for_callback_arguments arguments = {
682 .klass = rb_cIOBuffer,
683 .string = string_or_buffer,
684 .instance = Qnil,
685 .flags = RB_IO_BUFFER_READONLY,
686 .locked = 0,
687 .callback = callback,
688 .argument = argument,
689 };
690 return rb_ensure(io_buffer_for_callback_call, (VALUE)&arguments,
691 io_buffer_for_callback_ensure, (VALUE)&arguments);
692 }
693 else {
694 rb_raise(rb_eTypeError, "expected String or IO::Buffer, not %"PRIsVALUE,
695 rb_obj_class(string_or_buffer));
696 }
697}
698
699/* Forward declaration: io_buffer_readonly_p is defined later in this file. */
700static int io_buffer_readonly_p(struct rb_io_buffer *buffer);
701
702VALUE
703rb_io_buffer_for_writing(VALUE string_or_buffer, VALUE (*callback)(VALUE, VALUE), VALUE argument)
704{
705 if (rb_obj_is_kind_of(string_or_buffer, rb_cIOBuffer)) {
706 struct rb_io_buffer *buffer = get_io_buffer(string_or_buffer);
707 if (io_buffer_readonly_p(buffer)) {
708 rb_raise(rb_eArgError, "buffer is read-only");
709 }
710 return io_buffer_for_locked_callback(string_or_buffer, callback, argument);
711 }
712 else if (RB_TYPE_P(string_or_buffer, T_STRING)) {
713 StringValue(string_or_buffer);
714 struct io_buffer_for_callback_arguments arguments = {
715 .klass = rb_cIOBuffer,
716 .string = string_or_buffer,
717 .instance = Qnil,
718 .flags = 0,
719 .locked = 0,
720 .callback = callback,
721 .argument = argument,
722 };
723 return rb_ensure(io_buffer_for_callback_call, (VALUE)&arguments,
724 io_buffer_for_callback_ensure, (VALUE)&arguments);
725 }
726 else {
727 rb_raise(rb_eTypeError, "expected String or IO::Buffer, not %"PRIsVALUE,
728 rb_obj_class(string_or_buffer));
729 }
730}
731
732/*
733 * call-seq:
734 * IO::Buffer.for(string) -> readonly io_buffer
735 * IO::Buffer.for(string) {|io_buffer| ... read/write io_buffer ...}
736 *
737 * Creates a zero-copy IO::Buffer from the given string's memory. Without a
738 * block a frozen internal copy of the string is created efficiently and used
739 * as the buffer source. When a block is provided, the buffer is associated
740 * directly with the string's internal buffer and updating the buffer will
741 * update the string.
742 *
743 * Until #free is invoked on the buffer, either explicitly or via the garbage
744 * collector, the source string will be locked and cannot be modified.
745 *
746 * If the string is frozen, it will create a read-only buffer which cannot be
747 * modified. If the string is shared, it may trigger a copy-on-write when
748 * using the block form.
749 *
750 * string = 'test'
751 * buffer = IO::Buffer.for(string)
752 * buffer.external? #=> true
753 *
754 * buffer.get_string(0, 1)
755 * # => "t"
756 * string
757 * # => "test"
758 *
759 * buffer.resize(100)
760 * # in `resize': Cannot resize external buffer! (IO::Buffer::AccessError)
761 *
762 * IO::Buffer.for(string) do |buffer|
763 * buffer.set_string("T")
764 * string
765 * # => "Test"
766 * end
767 */
768VALUE
769rb_io_buffer_type_for(VALUE klass, VALUE string)
770{
771 StringValue(string);
772
773 // If the string is frozen, both code paths are okay.
774 // If the string is not frozen, if a block is not given, it must be frozen.
775 if (rb_block_given_p()) {
776 struct io_buffer_for_yield_instance_arguments arguments = {
777 .klass = klass,
778 .string = string,
779 .instance = Qnil,
780 .flags = 0,
781 };
782
783 return rb_ensure(io_buffer_for_yield_instance, (VALUE)&arguments, io_buffer_for_yield_instance_ensure, (VALUE)&arguments);
784 }
785 else {
786 // This internally returns the source string if it's already frozen.
787 string = rb_str_tmp_frozen_acquire(string);
788 return io_buffer_for_make_instance(klass, string, RB_IO_BUFFER_READONLY);
789 }
790}
791
792/*
793 * call-seq:
794 * IO::Buffer.string(length) {|io_buffer| ... read/write io_buffer ...} -> string
795 *
796 * Creates a new string of the given length and yields a zero-copy IO::Buffer
797 * instance to the block which uses the string as a source. The block is
798 * expected to write to the buffer and the string will be returned.
799 *
800 * IO::Buffer.string(4) do |buffer|
801 * buffer.set_string("Ruby")
802 * end
803 * # => "Ruby"
804 */
805VALUE
806rb_io_buffer_type_string(VALUE klass, VALUE length)
807{
808 VALUE string = rb_str_new(NULL, RB_NUM2LONG(length));
809
810 struct io_buffer_for_yield_instance_arguments arguments = {
811 .klass = klass,
812 .string = string,
813 .instance = Qnil,
814 };
815
816 rb_ensure(io_buffer_for_yield_instance, (VALUE)&arguments, io_buffer_for_yield_instance_ensure, (VALUE)&arguments);
817
818 return string;
819}
820
821VALUE
822rb_io_buffer_new(void *base, size_t size, enum rb_io_buffer_flags flags)
823{
824 VALUE instance = rb_io_buffer_type_allocate(rb_cIOBuffer);
825
826 struct rb_io_buffer *buffer = get_io_buffer(instance);
827
828 io_buffer_initialize(instance, buffer, base, size, flags, Qnil);
829
830 return instance;
831}
832
833VALUE
834rb_io_buffer_new_locked(void *base, size_t size, enum rb_io_buffer_flags flags)
835{
836 VALUE instance = rb_io_buffer_new(base, size, flags);
837
838 rb_io_buffer_lock(instance);
839
840 return instance;
841}
842
843VALUE
844rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags flags)
845{
846 if (UNLIKELY(offset < 0)) {
847 rb_raise(rb_eArgError,
848 "Offset (%" PRIsVALUE ") can't be negative!",
849 OFFT2NUM(offset));
850 }
851
852 if (UNLIKELY((uintmax_t)offset % RUBY_IO_BUFFER_MAP_ALIGNMENT != 0)) {
853 rb_raise(rb_eArgError,
854 "Offset (%" PRIsVALUE ") must be a multiple of IO::Buffer::MAP_ALIGNMENT (%" PRIuSIZE ")!",
855 OFFT2NUM(offset),
856 RUBY_IO_BUFFER_MAP_ALIGNMENT);
857 }
858
859 VALUE instance = rb_io_buffer_type_allocate(rb_cIOBuffer);
860
861 struct rb_io_buffer *buffer = get_io_buffer(instance);
862
863 int descriptor = rb_io_descriptor(io);
864
865 io_buffer_map_file(buffer, descriptor, size, offset, flags);
866
867 return instance;
868}
869
870/*
871 * call-seq: IO::Buffer.map(file, [size, [offset, [flags]]]) -> io_buffer
872 *
873 * Create an IO::Buffer for reading from +file+ by memory-mapping the file.
874 * +file+ should be a +File+ instance, opened for reading or reading and writing.
875 *
876 * Optional +size+ and +offset+ of mapping can be specified. The +offset+ must
877 * be a multiple of IO::Buffer::MAP_ALIGNMENT. The +size+ does not need to be
878 * aligned. Trying to map an empty file or specify +size+ of 0 will raise an
879 * error.
880 *
881 * By default, the buffer is writable and expects the file to be writable.
882 * It is also shared, so several processes can use the same mapping.
883 *
884 * The mapping mode may be explicitly selected with IO::Buffer::SHARED or
885 * IO::Buffer::PRIVATE, but the two flags are mutually exclusive.
886 * IO::Buffer::MAPPED is accepted but redundant because this method always
887 * creates a mapped buffer. IO::Buffer::INTERNAL and IO::Buffer::EXTERNAL
888 * cannot be specified.
889 *
890 * You can pass IO::Buffer::READONLY in +flags+ argument to make a read-only buffer;
891 * this allows to work with files opened only for reading.
892 * Specifying IO::Buffer::PRIVATE in +flags+ creates a private mapping,
893 * which will not impact other processes or the underlying file.
894 * It also allows updating a buffer created from a read-only file.
895 *
896 * File.write('test.txt', 'test')
897 *
898 * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
899 * # => #<IO::Buffer 0x00000001014a0000+4 EXTERNAL MAPPED FILE SHARED READONLY>
900 *
901 * buffer.readonly? # => true
902 *
903 * buffer.get_string
904 * # => "test"
905 *
906 * buffer.set_string('b', 0)
907 * # 'IO::Buffer#set_string': Buffer is not writable! (IO::Buffer::AccessError)
908 *
909 * # create read/write mapping: length 4 bytes, offset 0, flags 0
910 * buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 4, 0)
911 * buffer.set_string('b', 0)
912 * # => 1
913 *
914 * # Check it
915 * File.read('test.txt')
916 * # => "best"
917 *
918 * Note that some operating systems may not have cache coherency between mapped
919 * buffers and file reads.
920 */
921static VALUE
922io_buffer_map(int argc, VALUE *argv, VALUE klass)
923{
924 rb_check_arity(argc, 1, 4);
925
926 // We might like to handle a string path?
927 VALUE io = argv[0];
928
929 rb_off_t file_size = rb_file_size(io);
930 // Compiler can confirm that we handled file_size <= 0 case:
931 if (UNLIKELY(file_size <= 0)) {
932 rb_raise(rb_eArgError, "Invalid negative or zero file size!");
933 }
934 // Here, we assume that file_size is positive:
935 else if (UNLIKELY((uintmax_t)file_size > SIZE_MAX)) {
936 rb_raise(rb_eArgError, "File larger than address space!");
937 }
938
939 size_t size;
940 if (argc >= 2 && !RB_NIL_P(argv[1])) {
941 size = io_buffer_extract_size(argv[1]);
942 if (UNLIKELY(size == 0)) {
943 rb_raise(rb_eArgError, "Size can't be zero!");
944 }
945 if (UNLIKELY(size > (size_t)file_size)) {
946 rb_raise(rb_eArgError,
947 "Size (%" PRIuSIZE ") can't be larger than "
948 "file size (%" PRIuSIZE ")",
949 size,
950 (size_t)file_size);
951 }
952 }
953 else {
954 // This conversion should be safe:
955 size = (size_t)file_size;
956 }
957
958 // This is the file offset, not the buffer offset:
959 rb_off_t offset = 0;
960 if (argc >= 3) {
961 offset = NUM2OFFT(argv[2]);
962 if (UNLIKELY(offset < 0)) {
963 rb_raise(rb_eArgError,
964 "Offset (%" PRIsVALUE ") can't be negative!",
965 argv[2]);
966 }
967 if (UNLIKELY(offset >= file_size)) {
968 rb_raise(rb_eArgError,
969 "Offset (%" PRIsVALUE ") can't be larger than "
970 "file size (%" PRIuSIZE ")",
971 argv[2],
972 (size_t)file_size);
973 }
974 if (RB_NIL_P(argv[1])) {
975 // Decrease size if it's set from the actual file size:
976 size = (size_t)(file_size - offset);
977 }
978 else if (UNLIKELY((size_t)(file_size - offset) < size)) {
979 size_t maximum_offset =
980 ((size_t)file_size - size) / RUBY_IO_BUFFER_MAP_ALIGNMENT *
981 RUBY_IO_BUFFER_MAP_ALIGNMENT;
982 rb_raise(rb_eArgError,
983 "Offset (%" PRIsVALUE ") can't be larger than "
984 "%" PRIuSIZE " for requested size (%" PRIuSIZE ")",
985 argv[2],
986 maximum_offset,
987 size);
988 }
989 }
990
991 enum rb_io_buffer_flags flags = 0;
992 if (argc >= 4) {
993 flags = io_buffer_extract_flags(argv[3]);
994 }
995 flags = io_buffer_flags_for_map(flags);
996
997 return rb_io_buffer_map(io, size, offset, flags);
998}
999
1000// Compute the optimal allocation flags for a buffer of the given size.
1001static inline enum rb_io_buffer_flags
1002io_flags_for_size(size_t size)
1003{
1004 if (size >= RUBY_IO_BUFFER_PAGE_SIZE) {
1005 return RB_IO_BUFFER_MAPPED;
1006 }
1007
1008 return RB_IO_BUFFER_INTERNAL;
1009}
1010
1011static inline enum rb_io_buffer_flags
1012io_buffer_flags_for_new(enum rb_io_buffer_flags flags, size_t size)
1013{
1014 if (size == 0) {
1015 // A null buffer has no allocation and therefore no allocation flags:
1016 return 0;
1017 }
1018
1019 if (!(flags & RB_IO_BUFFER_ALLOCATION_FLAGS)) {
1020 if (flags & RB_IO_BUFFER_MAPPING_FLAGS) {
1021 // Mapping properties imply a mapped allocation:
1022 flags |= RB_IO_BUFFER_MAPPED;
1023 }
1024 else {
1025 // No explicit allocation mode was given, so infer one from size:
1026 flags |= io_flags_for_size(size);
1027 }
1028 }
1029
1030 enum rb_io_buffer_flags allocation = flags & RB_IO_BUFFER_ALLOCATION_FLAGS;
1031 RUBY_ASSERT(allocation != 0);
1032
1033 if ((unsigned int)allocation == RB_IO_BUFFER_ALLOCATION_FLAGS) {
1034 rb_raise(rb_eArgError, "Flags can't include both IO::Buffer::INTERNAL and IO::Buffer::MAPPED!");
1035 }
1036
1037 if (flags & RB_IO_BUFFER_EXTERNAL) {
1038 rb_raise(rb_eArgError, "IO::Buffer::EXTERNAL can't be used with IO::Buffer.new!");
1039 }
1040
1041 if ((flags & RB_IO_BUFFER_MAPPING_FLAGS) && allocation != RB_IO_BUFFER_MAPPED) {
1042 rb_raise(rb_eArgError, "IO::Buffer::SHARED and IO::Buffer::PRIVATE require IO::Buffer::MAPPED!");
1043 }
1044
1045 if ((flags & RB_IO_BUFFER_MAPPING_FLAGS) == RB_IO_BUFFER_MAPPING_FLAGS) {
1046 rb_raise(rb_eArgError, "Flags can't include both IO::Buffer::SHARED and IO::Buffer::PRIVATE!");
1047 }
1048
1049 return flags;
1050}
1051
1052/*
1053 * call-seq: IO::Buffer.new([size = DEFAULT_SIZE, [flags]]) -> io_buffer
1054 *
1055 * Create a new zero-filled IO::Buffer of +size+ bytes.
1056 * By default, the buffer will be _internal_: directly allocated chunk
1057 * of the memory. But if the requested +size+ is more than OS-specific
1058 * IO::Buffer::PAGE_SIZE, the buffer would be allocated using the
1059 * virtual memory mechanism (anonymous +mmap+ on Unix, +VirtualAlloc+
1060 * on Windows). The behavior can be forced by passing IO::Buffer::MAPPED
1061 * as a second parameter.
1062 *
1063 * IO::Buffer::SHARED and IO::Buffer::PRIVATE imply IO::Buffer::MAPPED and are
1064 * mutually exclusive. Otherwise, if +flags+ do not include an allocation
1065 * mode, IO::Buffer::INTERNAL or IO::Buffer::MAPPED is inferred from the
1066 * requested size. The two allocation modes are mutually exclusive.
1067 *
1068 * buffer = IO::Buffer.new(4)
1069 * # =>
1070 * # #<IO::Buffer 0x000055b34497ea10+4 INTERNAL>
1071 * # 0x00000000 00 00 00 00 ....
1072 *
1073 * buffer.get_string(0, 1) # => "\x00"
1074 *
1075 * buffer.set_string("test")
1076 * buffer
1077 * # =>
1078 * # #<IO::Buffer 0x000055b34497ea10+4 INTERNAL>
1079 * # 0x00000000 74 65 73 74 test
1080 */
1081VALUE
1082rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self)
1083{
1084 rb_check_arity(argc, 0, 2);
1085
1086 struct rb_io_buffer *buffer = get_io_buffer(self);
1087
1088 size_t size;
1089 if (argc > 0) {
1090 size = io_buffer_extract_size(argv[0]);
1091 }
1092 else {
1093 size = RUBY_IO_BUFFER_DEFAULT_SIZE;
1094 }
1095
1096 enum rb_io_buffer_flags flags = 0;
1097 if (argc >= 2) {
1098 flags = io_buffer_extract_flags(argv[1]);
1099 }
1100 flags = io_buffer_flags_for_new(flags, size);
1101
1102 io_buffer_initialize(self, buffer, NULL, size, flags, Qnil);
1103
1104 return self;
1105}
1106
1107// Resolve the current base pointer and size of a buffer, following slice
1108// indirection. A slice of another IO::Buffer stores a logical `offset` into
1109// its (root) source and resolves its base as `source_base + offset` here, so
1110// it stays valid even if the source's allocation is moved by a resize. A
1111// String-backed buffer stores an offset into its pinned String and is
1112// range-validated. Returns non-zero if the buffer is valid, and sets
1113// `*base`/`*size` accordingly (NULL/0 when invalid).
1114static int
1115io_buffer_try_get_bytes(struct rb_io_buffer *buffer, void **base, size_t *size)
1116{
1117 // Symmetry: the resolved base is `source_base + buffer->base`, where
1118 // `buffer->base` is a byte offset into the source (and is the absolute
1119 // pointer for an owning buffer, whose source contributes 0):
1120 //
1121 // owning (source == Qnil): source_base = 0, base = buffer->base
1122 // String-backed: source_base = RSTRING_PTR, base = 0 + offset
1123 // slice (IO::Buffer): source_base = resolved(source), base = + offset
1124 //
1125 // Note: a valid buffer may still resolve to a NULL base (e.g. an empty
1126 // buffer, or an empty slice of an empty source). Validity means "the range
1127 // exists in the source"; whether the resolved base is NULL is a separate
1128 // property (see IO::Buffer#null?).
1129 if (buffer->source == Qnil) {
1130 // Owning (root) buffer: `base` is absolute (source contributes 0).
1131 *base = buffer->base;
1132 *size = buffer->size;
1133 return 1;
1134 }
1135
1136 // Source-backed buffer: `base` is an offset into the source.
1137 void *source_base = NULL;
1138 size_t source_size = 0;
1139
1140 if (io_buffer_slice_p(buffer)) {
1141 // Slice of another IO::Buffer: resolve the (root) source recursively.
1142 if (!io_buffer_try_get_bytes(get_io_buffer(buffer->source), &source_base, &source_size)) {
1143 *base = NULL;
1144 *size = 0;
1145 return 0;
1146 }
1147 }
1148 else {
1149 // String-backed buffer: the (pinned) String content is the source.
1150 RSTRING_GETMEM(buffer->source, source_base, source_size);
1151 }
1152
1153 size_t offset = io_buffer_slice_offset(buffer);
1154 if (offset <= source_size && buffer->size <= source_size - offset) {
1155 *base = source_base ? (char *)source_base + offset : NULL;
1156 *size = buffer->size;
1157 return 1;
1158 }
1159
1160 *base = NULL;
1161 *size = 0;
1162 return 0;
1163}
1164
1165static int
1166io_buffer_validate(struct rb_io_buffer *buffer)
1167{
1168 if (buffer->source != Qnil) {
1169 // Only slices incur this overhead, unfortunately... better safe than sorry!
1170 void *base = NULL;
1171 size_t size = 0;
1172 return io_buffer_try_get_bytes(buffer, &base, &size);
1173 }
1174 else {
1175 return 1;
1176 }
1177}
1178
1179enum rb_io_buffer_flags
1180rb_io_buffer_get_bytes(VALUE self, void **base, size_t *size)
1181{
1182 struct rb_io_buffer *buffer = get_io_buffer(self);
1183
1184 if (io_buffer_try_get_bytes(buffer, base, size)) {
1185 enum rb_io_buffer_flags flags = buffer->flags;
1186 if (io_buffer_readonly_p(buffer)) flags |= RB_IO_BUFFER_READONLY;
1187 return flags;
1188 }
1189
1190 return 0;
1191}
1192
1193// Internal function for accessing bytes for writing, wil
1194static void
1195io_buffer_validate_for_writing(struct rb_io_buffer *buffer)
1196{
1197 if (io_buffer_readonly_p(buffer)) {
1198 rb_raise(rb_eIOBufferAccessError, "Buffer is not writable!");
1199 }
1200
1201 if (!io_buffer_validate(buffer)) {
1202 rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!");
1203 }
1204}
1205
1206static struct rb_io_buffer *
1207get_io_buffer_for_writing(VALUE self)
1208{
1209 rb_check_frozen(self);
1210
1211 struct rb_io_buffer *buffer = get_io_buffer(self);
1212 io_buffer_validate_for_writing(buffer);
1213 return buffer;
1214}
1215
1216static inline void
1217io_buffer_get_bytes_for_writing(struct rb_io_buffer *buffer, void **base, size_t *size)
1218{
1219 io_buffer_validate_for_writing(buffer);
1220
1221 io_buffer_try_get_bytes(buffer, base, size);
1222}
1223
1224void
1225rb_io_buffer_get_bytes_for_writing(VALUE self, void **base, size_t *size)
1226{
1227 struct rb_io_buffer *buffer = get_io_buffer(self);
1228
1229 io_buffer_get_bytes_for_writing(buffer, base, size);
1230}
1231
1232static void
1233io_buffer_validate_for_reading(struct rb_io_buffer *buffer)
1234{
1235 if (!io_buffer_validate(buffer)) {
1236 rb_raise(rb_eIOBufferInvalidatedError, "Buffer has been invalidated!");
1237 }
1238}
1239
1240static void
1241io_buffer_get_bytes_for_reading(struct rb_io_buffer *buffer, const void **base, size_t *size)
1242{
1243 io_buffer_validate_for_reading(buffer);
1244
1245 void *writable_base = NULL;
1246 io_buffer_try_get_bytes(buffer, &writable_base, size);
1247 *base = writable_base;
1248}
1249
1250void
1251rb_io_buffer_get_bytes_for_reading(VALUE self, const void **base, size_t *size)
1252{
1253 struct rb_io_buffer *buffer = get_io_buffer(self);
1254
1255 io_buffer_get_bytes_for_reading(buffer, base, size);
1256}
1257
1258/*
1259 * call-seq: to_s -> string
1260 *
1261 * Short representation of the buffer. It includes the address, size and
1262 * symbolic flags. This format is subject to change.
1263 *
1264 * puts IO::Buffer.new(4) # uses to_s internally
1265 * # #<IO::Buffer 0x000055769f41b1a0+4 INTERNAL>
1266 */
1267VALUE
1268rb_io_buffer_to_s(VALUE self)
1269{
1270 struct rb_io_buffer *buffer = get_io_buffer(self);
1271
1272 VALUE result = rb_str_new_cstr("#<");
1273
1274 rb_str_append(result, rb_class_name(CLASS_OF(self)));
1275
1276 // Resolve the current base (following slice indirection) for display:
1277 void *base = NULL;
1278 size_t size = 0;
1279 io_buffer_try_get_bytes(buffer, &base, &size);
1280 rb_str_catf(result, " %p+%"PRIdSIZE, base, buffer->size);
1281
1282 if (base == NULL) {
1283 rb_str_cat2(result, " NULL");
1284 }
1285
1286 if (buffer->flags & RB_IO_BUFFER_EXTERNAL) {
1287 rb_str_cat2(result, " EXTERNAL");
1288 }
1289
1290 if (buffer->flags & RB_IO_BUFFER_INTERNAL) {
1291 rb_str_cat2(result, " INTERNAL");
1292 }
1293
1294 if (buffer->flags & RB_IO_BUFFER_MAPPED) {
1295 rb_str_cat2(result, " MAPPED");
1296 }
1297
1298 if (buffer->flags & RB_IO_BUFFER_FILE) {
1299 rb_str_cat2(result, " FILE");
1300 }
1301
1302 if (buffer->flags & RB_IO_BUFFER_SHARED) {
1303 rb_str_cat2(result, " SHARED");
1304 }
1305
1306 if (io_buffer_locked(buffer)) {
1307 rb_str_cat2(result, " LOCKED");
1308 }
1309
1310 if (buffer->flags & RB_IO_BUFFER_PRIVATE) {
1311 rb_str_cat2(result, " PRIVATE");
1312 }
1313
1314 if (io_buffer_readonly_p(buffer)) {
1315 rb_str_cat2(result, " READONLY");
1316 }
1317
1318 if (buffer->source != Qnil) {
1319 rb_str_cat2(result, " SLICE");
1320 }
1321
1322 if (!io_buffer_validate(buffer)) {
1323 rb_str_cat2(result, " INVALID");
1324 }
1325
1326 return rb_str_cat2(result, ">");
1327}
1328
1329// 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.
1330// This is used to preallocate the output string.
1331inline static size_t
1332io_buffer_hexdump_output_size(size_t width, size_t size, int first)
1333{
1334 // The preview on the right hand side is 1:1:
1335 size_t total = size;
1336
1337 size_t whole_lines = (size / width);
1338 size_t partial_line = (size % width) ? 1 : 0;
1339
1340 // For each line:
1341 // 1 byte 10 bytes 1 byte width*3 bytes 1 byte size bytes
1342 // (newline) (address) (space) (hexdump ) (space) (preview)
1343 total += (whole_lines + partial_line) * (1 + 10 + width*3 + 1 + 1);
1344
1345 // If the hexdump is the first line, one less newline will be emitted:
1346 if (size && first) total -= 1;
1347
1348 return total;
1349}
1350
1351// Append a hexdump of the given width (bytes per line), base address, size, and whether it is the first line in the output.
1352// If the hexdump is not the first line, it will prepend a newline if there is any output at all.
1353// If formatting here is adjusted, please update io_buffer_hexdump_output_size accordingly.
1354static VALUE
1355io_buffer_hexdump(VALUE string, size_t width, const char *base, size_t length, size_t offset, int first)
1356{
1357 char *text = alloca(width+1);
1358 text[width] = '\0';
1359
1360 for (; offset < length; offset += width) {
1361 memset(text, '\0', width);
1362 if (first) {
1363 rb_str_catf(string, "0x%08" PRIxSIZE " ", offset);
1364 first = 0;
1365 }
1366 else {
1367 rb_str_catf(string, "\n0x%08" PRIxSIZE " ", offset);
1368 }
1369
1370 for (size_t i = 0; i < width; i += 1) {
1371 if (offset+i < length) {
1372 unsigned char value = ((unsigned char*)base)[offset+i];
1373
1374 if (value < 127 && isprint(value)) {
1375 text[i] = (char)value;
1376 }
1377 else {
1378 text[i] = '.';
1379 }
1380
1381 rb_str_catf(string, " %02x", value);
1382 }
1383 else {
1384 rb_str_cat2(string, " ");
1385 }
1386 }
1387
1388 rb_str_catf(string, " %s", text);
1389 }
1390
1391 return string;
1392}
1393
1394/*
1395 * call-seq: inspect -> string
1396 *
1397 * Inspect the buffer and report useful information about it's internal state.
1398 * Only a limited portion of the buffer will be displayed in a hexdump style
1399 * format.
1400 *
1401 * buffer = IO::Buffer.for("Hello World")
1402 * puts buffer.inspect
1403 * # #<IO::Buffer 0x000000010198ccd8+11 EXTERNAL READONLY SLICE>
1404 * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
1405 */
1406VALUE
1407rb_io_buffer_inspect(VALUE self)
1408{
1409 struct rb_io_buffer *buffer = get_io_buffer(self);
1410
1411 VALUE result = rb_io_buffer_to_s(self);
1412
1413 void *base = NULL;
1414 size_t total = 0;
1415 if (io_buffer_try_get_bytes(buffer, &base, &total) && base) {
1416 // Limit the maximum size generated by inspect:
1417 size_t size = buffer->size;
1418 int clamped = 0;
1419
1420 if (size > RB_IO_BUFFER_INSPECT_HEXDUMP_MAXIMUM_SIZE) {
1421 size = RB_IO_BUFFER_INSPECT_HEXDUMP_MAXIMUM_SIZE;
1422 clamped = 1;
1423 }
1424
1425 io_buffer_hexdump(result, RB_IO_BUFFER_INSPECT_HEXDUMP_WIDTH, base, size, 0, 0);
1426
1427 if (clamped) {
1428 rb_str_catf(result, "\n(and %" PRIuSIZE " more bytes not printed)", buffer->size - size);
1429 }
1430 }
1431
1432 return result;
1433}
1434
1435/*
1436 * call-seq: size -> integer
1437 *
1438 * Returns the size of the buffer that was explicitly set (on creation with ::new
1439 * or on #resize), or deduced on buffer's creation from string or file.
1440 */
1441VALUE
1442rb_io_buffer_size(VALUE self)
1443{
1444 struct rb_io_buffer *buffer = get_io_buffer(self);
1445
1446 return SIZET2NUM(buffer->size);
1447}
1448
1449/*
1450 * call-seq: valid? -> true or false
1451 *
1452 * A buffer without a source is always valid, including a null buffer. A
1453 * source-backed buffer is valid when its offset and length fit within its
1454 * source's current size.
1455 *
1456 * Relocating a source does not invalidate a source-backed buffer. Freeing,
1457 * transferring, or shrinking the source can make it invalid; if the same
1458 * source later grows to include the range again, the buffer becomes valid
1459 * and refers to the current contents at its original offset.
1460 *
1461 * An empty source-backed range at offset zero can be valid even when its
1462 * source has no storage. #valid?, #null? and #empty? describe distinct
1463 * properties: a buffer can be valid, null, and empty at the same time.
1464 */
1465static VALUE
1466rb_io_buffer_valid_p(VALUE self)
1467{
1468 struct rb_io_buffer *buffer = get_io_buffer(self);
1469
1470 return RBOOL(io_buffer_validate(buffer));
1471}
1472
1473/*
1474 * call-seq: null? -> true or false
1475 *
1476 * Returns whether the buffer has no recorded base address.
1477 *
1478 * A buffer is null if it was freed with #free, transferred with #transfer, or
1479 * was never allocated in the first place. A zero-sized buffer or slice may
1480 * have a non-null address, so #null? and #empty? are distinct properties.
1481 *
1482 * buffer = IO::Buffer.new(0)
1483 * buffer.null? #=> true
1484 *
1485 * buffer = IO::Buffer.new(4)
1486 * buffer.null? #=> false
1487 * buffer.free
1488 * buffer.null? #=> true
1489 */
1490static VALUE
1491rb_io_buffer_null_p(VALUE self)
1492{
1493 struct rb_io_buffer *buffer = get_io_buffer(self);
1494
1495 void *base = NULL;
1496 size_t size = 0;
1497 io_buffer_try_get_bytes(buffer, &base, &size);
1498
1499 return RBOOL(base == NULL);
1500}
1501
1502/*
1503 * call-seq: empty? -> true or false
1504 *
1505 * Returns whether the buffer has zero size.
1506 *
1507 * A buffer can be empty but have a non-null address, for example a zero-sized
1508 * slice or a buffer created with ::for from an empty string. Therefore
1509 * #empty? does not imply #null?.
1510 */
1511static VALUE
1512rb_io_buffer_empty_p(VALUE self)
1513{
1514 struct rb_io_buffer *buffer = get_io_buffer(self);
1515
1516 return RBOOL(buffer->size == 0);
1517}
1518
1519/*
1520 * call-seq: external? -> true or false
1521 *
1522 * The buffer is _external_ if it references the memory which is not
1523 * allocated or mapped by the buffer itself.
1524 *
1525 * A buffer created using ::for has an external reference to the string's
1526 * memory.
1527 *
1528 * External buffer can't be resized.
1529 */
1530static VALUE
1531rb_io_buffer_external_p(VALUE self)
1532{
1533 struct rb_io_buffer *buffer = get_io_buffer(self);
1534
1535 return RBOOL(buffer->flags & RB_IO_BUFFER_EXTERNAL);
1536}
1537
1538/*
1539 * call-seq: internal? -> true or false
1540 *
1541 * If the buffer is _internal_, meaning it references memory allocated by the
1542 * buffer itself.
1543 *
1544 * An internal buffer is not associated with any external memory (e.g. string)
1545 * or file mapping.
1546 *
1547 * Internal buffers are created using ::new and is the default when the
1548 * requested size is less than the IO::Buffer::PAGE_SIZE and it was not
1549 * requested to be mapped on creation.
1550 *
1551 * Internal buffers can be resized. Slices remain valid if their ranges still
1552 * fit within the resized buffer, including when its storage is relocated.
1553 */
1554static VALUE
1555rb_io_buffer_internal_p(VALUE self)
1556{
1557 struct rb_io_buffer *buffer = get_io_buffer(self);
1558
1559 return RBOOL(buffer->flags & RB_IO_BUFFER_INTERNAL);
1560}
1561
1562/*
1563 * call-seq: mapped? -> true or false
1564 *
1565 * If the buffer is _mapped_, meaning it references memory mapped by the
1566 * buffer.
1567 *
1568 * Mapped buffers are either anonymous, if created by ::new with the
1569 * IO::Buffer::MAPPED flag or if the size was at least IO::Buffer::PAGE_SIZE,
1570 * or backed by a file if created with ::map.
1571 *
1572 * Mapped buffers can usually be resized. Slices remain valid if their ranges
1573 * still fit within the resized buffer, including when its mapping is moved.
1574 */
1575static VALUE
1576rb_io_buffer_mapped_p(VALUE self)
1577{
1578 struct rb_io_buffer *buffer = get_io_buffer(self);
1579
1580 return RBOOL(buffer->flags & RB_IO_BUFFER_MAPPED);
1581}
1582
1583/*
1584 * call-seq: shared? -> true or false
1585 *
1586 * If the buffer is _shared_, meaning it references memory that can be shared
1587 * with other processes (and thus might change without being modified
1588 * locally).
1589 *
1590 * # Create a test file:
1591 * File.write('test.txt', 'test')
1592 *
1593 * # Create a shared mapping from the given file, the file must be opened in
1594 * # read-write mode unless we also specify IO::Buffer::READONLY:
1595 * buffer = IO::Buffer.map(File.open('test.txt', 'r+'), nil, 0)
1596 * # => #<IO::Buffer 0x00007f1bffd5e000+4 EXTERNAL MAPPED SHARED>
1597 *
1598 * # Write to the buffer, which will modify the mapped file:
1599 * buffer.set_string('b', 0)
1600 * # => 1
1601 *
1602 * # The file itself is modified:
1603 * File.read('test.txt')
1604 * # => "best"
1605 */
1606static VALUE
1607rb_io_buffer_shared_p(VALUE self)
1608{
1609 struct rb_io_buffer *buffer = get_io_buffer(self);
1610
1611 return RBOOL(buffer->flags & RB_IO_BUFFER_SHARED);
1612}
1613
1614/*
1615 * call-seq: locked? -> true or false
1616 *
1617 * If the buffer is _locked_, its underlying allocation cannot be resized,
1618 * freed or transferred. Locks are shared with slices and may be nested.
1619 *
1620 * Locking is a lifetime mechanism used to ensure buffers don't move while
1621 * being used by a system call or other native operation.
1622 *
1623 * buffer.locked do
1624 * buffer.write(io) # theoretical system call interface
1625 * end
1626 */
1627static VALUE
1628rb_io_buffer_locked_p(VALUE self)
1629{
1630 struct rb_io_buffer *buffer = get_io_buffer(self);
1631
1632 return RBOOL(io_buffer_locked(buffer));
1633}
1634
1635/* call-seq: private? -> true or false
1636 *
1637 * If the buffer is _private_, meaning modifications to the buffer will not
1638 * be replicated to the underlying file mapping.
1639 *
1640 * # Create a test file:
1641 * File.write('test.txt', 'test')
1642 *
1643 * # Create a private mapping from the given file. Note that the file here
1644 * # is opened in read-only mode, but it doesn't matter due to the private
1645 * # mapping:
1646 * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::PRIVATE)
1647 * # => #<IO::Buffer 0x00007fce63f11000+4 MAPPED PRIVATE>
1648 *
1649 * # Write to the buffer (invoking CoW of the underlying file buffer):
1650 * buffer.set_string('b', 0)
1651 * # => 1
1652 *
1653 * # The file itself is not modified:
1654 * File.read('test.txt')
1655 * # => "test"
1656 */
1657static VALUE
1658rb_io_buffer_private_p(VALUE self)
1659{
1660 struct rb_io_buffer *buffer = get_io_buffer(self);
1661
1662 return RBOOL(buffer->flags & RB_IO_BUFFER_PRIVATE);
1663}
1664
1665static int
1666io_buffer_readonly_p(struct rb_io_buffer *buffer)
1667{
1668 if (buffer->flags & RB_IO_BUFFER_READONLY)
1669 return 1;
1670
1671 VALUE source = buffer->source;
1672 if (NIL_P(source))
1673 return 0;
1674
1675 if (OBJ_FROZEN(source))
1676 return 1;
1677
1678 if (RB_TYPE_P(source, T_STRING))
1679 return 0;
1680
1681 return io_buffer_readonly_p(get_io_buffer(source));
1682}
1683
1684/*
1685 * call-seq: readonly? -> true or false
1686 *
1687 * If the buffer is <i>read only</i>, meaning the buffer cannot be modified using
1688 * #set_value, #set_string or #copy and similar.
1689 *
1690 * A buffer created by IO::Buffer.for without a block is read-only, as is one
1691 * backed by a frozen string or a read-only file.
1692 *
1693 * A slice derives read-only access from its current source. Replacing the
1694 * source's storage can therefore change the slice's read-only status.
1695 */
1696static VALUE
1697rb_io_buffer_readonly_p(VALUE self)
1698{
1699 struct rb_io_buffer *buffer = get_io_buffer(self);
1700
1701 return RBOOL(io_buffer_readonly_p(buffer));
1702}
1703
1704static void
1705io_buffer_lock(struct rb_io_buffer *buffer)
1706{
1707 struct rb_io_buffer *owner = io_buffer_lock_owner(buffer);
1708
1709 if (owner->lock_count == SIZE_MAX) {
1710 rb_raise(rb_eIOBufferLockedError, "It's locks all the way down!");
1711 }
1712
1713 owner->lock_count += 1;
1714}
1715
1716VALUE
1717rb_io_buffer_lock(VALUE self)
1718{
1719 struct rb_io_buffer *buffer = get_io_buffer(self);
1720
1721 io_buffer_lock(buffer);
1722
1723 return self;
1724}
1725
1726static void
1727io_buffer_unlock(struct rb_io_buffer *buffer)
1728{
1729 struct rb_io_buffer *owner = io_buffer_lock_owner(buffer);
1730
1731 if (owner->lock_count == 0) {
1732 rb_raise(rb_eIOBufferLockedError, "Buffer not locked!");
1733 }
1734
1735 owner->lock_count -= 1;
1736}
1737
1738VALUE
1739rb_io_buffer_unlock(VALUE self)
1740{
1741 struct rb_io_buffer *buffer = get_io_buffer(self);
1742
1743 io_buffer_unlock(buffer);
1744
1745 return self;
1746}
1747
1748int
1749rb_io_buffer_try_unlock(VALUE self)
1750{
1751 struct rb_io_buffer *buffer = get_io_buffer(self);
1752 struct rb_io_buffer *owner = io_buffer_lock_owner(buffer);
1753
1754 if (owner->lock_count > 0) {
1755 owner->lock_count -= 1;
1756
1757 return 1;
1758 }
1759
1760 return 0;
1761}
1762
1763static VALUE
1764rb_io_buffer_locked_ensure(VALUE self)
1765{
1766 struct rb_io_buffer *buffer = get_io_buffer(self);
1767
1768 io_buffer_unlock(buffer);
1769
1770 return Qnil;
1771}
1772
1774 VALUE self;
1775 VALUE (*callback)(const void *base, size_t size, VALUE argument);
1776 VALUE argument;
1777};
1778
1779static VALUE
1780io_buffer_readable_bytes_call(VALUE _arguments)
1781{
1782 struct io_buffer_readable_bytes_arguments *arguments = (void *)_arguments;
1783
1784 const void *base;
1785 size_t size;
1786 rb_io_buffer_get_bytes_for_reading(arguments->self, &base, &size);
1787
1788 return arguments->callback(base, size, arguments->argument);
1789}
1790
1791VALUE
1792rb_io_buffer_locked_for_reading(VALUE self, VALUE (*callback)(const void *base, size_t size, VALUE argument), VALUE argument)
1793{
1794 struct rb_io_buffer *buffer = get_io_buffer(self);
1795 io_buffer_validate_for_reading(buffer);
1796
1797 struct io_buffer_readable_bytes_arguments arguments = {
1798 .self = self,
1799 .callback = callback,
1800 .argument = argument,
1801 };
1802
1803 rb_io_buffer_lock(self);
1804 return rb_ensure(io_buffer_readable_bytes_call, (VALUE)&arguments, rb_io_buffer_locked_ensure, self);
1805}
1806
1808 VALUE self;
1809 VALUE (*callback)(void *base, size_t size, VALUE argument);
1810 VALUE argument;
1811};
1812
1813static VALUE
1814io_buffer_writable_bytes_call(VALUE _arguments)
1815{
1816 struct io_buffer_writable_bytes_arguments *arguments = (void *)_arguments;
1817
1818 void *base;
1819 size_t size;
1820 rb_io_buffer_get_bytes_for_writing(arguments->self, &base, &size);
1821
1822 return arguments->callback(base, size, arguments->argument);
1823}
1824
1825VALUE
1826rb_io_buffer_locked_for_writing(VALUE self, VALUE (*callback)(void *base, size_t size, VALUE argument), VALUE argument)
1827{
1828 get_io_buffer_for_writing(self);
1829
1830 struct io_buffer_writable_bytes_arguments arguments = {
1831 .self = self,
1832 .callback = callback,
1833 .argument = argument,
1834 };
1835
1836 rb_io_buffer_lock(self);
1837 return rb_ensure(io_buffer_writable_bytes_call, (VALUE)&arguments, rb_io_buffer_locked_ensure, self);
1838}
1839
1840/*
1841 * call-seq: locked { ... }
1842 *
1843 * Prevents the buffer or its buffer source from being moved or freed while
1844 * the block is executing. Locks are nested and shared with slices backed by
1845 * the same buffer source. The source remains locked until every nested lock
1846 * has been released.
1847 *
1848 * Locking protects allocation lifetime; it does not serialize access to the
1849 * bytes. Code that shares mutable buffer contents between threads must still
1850 * use appropriate synchronization.
1851 *
1852 * buffer = IO::Buffer.new(4)
1853 * buffer.locked? #=> false
1854 *
1855 * Fiber.schedule do
1856 * buffer.locked do
1857 * buffer.write(io) # theoretical system call interface
1858 * end
1859 * end
1860 *
1861 * Fiber.schedule do
1862 * buffer.locked do
1863 * buffer.set_string("test", 0) # Nested locking is allowed.
1864 * end
1865 * end
1866 */
1867VALUE
1868rb_io_buffer_locked(VALUE self)
1869{
1870 struct rb_io_buffer *buffer = get_io_buffer(self);
1871
1872 // Only yield the block for a currently valid view. In particular, an
1873 // invalid slice should not lock its source.
1874 io_buffer_validate_for_reading(buffer);
1875
1876 io_buffer_lock(buffer);
1877
1878 return rb_ensure(rb_yield, self, rb_io_buffer_locked_ensure, self);
1879}
1880
1881VALUE
1882rb_io_buffer_free(VALUE self)
1883{
1884 struct rb_io_buffer *buffer = get_io_buffer(self);
1885
1886 if (io_buffer_locked(buffer)) {
1887 rb_raise(rb_eIOBufferLockedError, "Buffer is locked!");
1888 }
1889
1890 io_buffer_release(buffer);
1891
1892 return self;
1893}
1894
1895/*
1896 * call-seq: free -> self
1897 *
1898 * If the buffer references memory, release it back to the operating system.
1899 * * for a _mapped_ buffer (e.g. from file): unmap.
1900 * * for a buffer created from scratch: free memory.
1901 * * for a buffer created from string: undo the association.
1902 *
1903 * After releasing any referenced memory, the buffer is reset to a valid,
1904 * empty, null state. It has no backing storage and its size is zero.
1905 * Zero-length operations remain valid, while operations requiring bytes fail
1906 * normal bounds checking.
1907 *
1908 * You can resize the buffer to allocate new storage.
1909 *
1910 * buffer = IO::Buffer.for('test')
1911 * buffer.free
1912 * # => #<IO::Buffer 0x0000000000000000+0 NULL>
1913 *
1914 * buffer.null? # => true
1915 * buffer.empty? # => true
1916 * buffer.valid? # => true
1917 * buffer.get_string # => ""
1918 *
1919 * buffer.get_value(:U8, 0) # raises ArgumentError
1920 *
1921 * A frozen buffer cannot be freed, as that would release the memory its
1922 * contents live in:
1923 *
1924 * buffer = IO::Buffer.for('test').freeze
1925 * buffer.free
1926 * # in `free': can't modify frozen IO::Buffer (FrozenError)
1927 */
1928static VALUE
1929io_buffer_free(VALUE self)
1930{
1931 rb_check_frozen(self);
1932
1933 return rb_io_buffer_free(self);
1934}
1935
1936VALUE rb_io_buffer_free_locked(VALUE self)
1937{
1938 struct rb_io_buffer *buffer = get_io_buffer(self);
1939 struct rb_io_buffer *owner = io_buffer_lock_owner(buffer);
1940
1941 // This function is used to invalidate temporary wrappers around borrowed
1942 // memory. If another lock remains, the owner cannot safely end the
1943 // lifetime of that memory while another operation still retains it.
1944 if (owner->lock_count != 1) {
1945 rb_bug("rb_io_buffer_free_locked: expected lock count 1, got %" PRIuSIZE, owner->lock_count);
1946 }
1947
1948 io_buffer_unlock(buffer);
1949 io_buffer_release(buffer);
1950
1951 return self;
1952}
1953
1954static bool
1955size_sum_is_bigger_than(size_t a, size_t b, size_t x)
1956{
1957 struct rbimpl_size_overflow_tag size = rbimpl_size_add_overflow(a, b);
1958 return size.overflowed || size.result > x;
1959}
1960
1961// Validate that access to the buffer is within bounds, assuming you want to
1962// access length bytes from the specified offset.
1963static inline void
1964io_buffer_validate_range(struct rb_io_buffer *buffer, size_t offset, size_t length)
1965{
1966 io_buffer_validate_for_reading(buffer);
1967
1968 if (size_sum_is_bigger_than(offset, length, buffer->size)) {
1969 rb_raise(rb_eArgError, "Specified offset+length is bigger than the buffer size!");
1970 }
1971}
1972
1973/*
1974 * call-seq: hexdump([offset, [length, [width]]]) -> string or nil
1975 *
1976 * Returns a human-readable string representation of the buffer. The exact
1977 * format is subject to change.
1978 *
1979 * Returns +nil+ if the buffer does not reference any memory, that is, if
1980 * #null? returns +true+ (for example after #free or #transfer).
1981 *
1982 * buffer = IO::Buffer.for("Hello World")
1983 * puts buffer.hexdump
1984 * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
1985 *
1986 * As buffers are usually fairly big, you may want to limit the output by
1987 * specifying the offset and length:
1988 *
1989 * puts buffer.hexdump(6, 5)
1990 * # 0x00000006 57 6f 72 6c 64 World
1991 */
1992static VALUE
1993rb_io_buffer_hexdump(int argc, VALUE *argv, VALUE self)
1994{
1995 rb_check_arity(argc, 0, 3);
1996
1997 size_t offset, length;
1998 struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);
1999
2000 size_t width = RB_IO_BUFFER_HEXDUMP_DEFAULT_WIDTH;
2001 if (argc >= 3) {
2002 width = io_buffer_extract_width(argv[2], 1);
2003 }
2004
2005 // This may raise an exception if the offset/length is invalid:
2006 io_buffer_validate_range(buffer, offset, length);
2007
2008 VALUE result = Qnil;
2009
2010 void *base = NULL;
2011 size_t size = 0;
2012 if (io_buffer_try_get_bytes(buffer, &base, &size) && base) {
2013 result = rb_str_buf_new(io_buffer_hexdump_output_size(width, length, 1));
2014
2015 io_buffer_hexdump(result, width, base, offset+length, offset, 1);
2016 }
2017
2018 return result;
2019}
2020
2021static VALUE
2022rb_io_buffer_slice(struct rb_io_buffer *buffer, VALUE self, size_t offset, size_t length)
2023{
2024 io_buffer_validate_range(buffer, offset, length);
2025
2026 VALUE instance = rb_io_buffer_type_allocate(rb_class_of(self));
2027 struct rb_io_buffer *slice = get_io_buffer(instance);
2028
2029 slice->size = length;
2030
2031 // Slices retain their root buffer and store a logical offset into it,
2032 // rather than an absolute base pointer, so they remain valid across a
2033 // resize that relocates the source. The base is resolved on demand as
2034 // `source_base + offset`. If this buffer is already a slice, retain its
2035 // root directly rather than building a chain of slices, folding this
2036 // slice's offset into the existing one:
2037 if (io_buffer_slice_p(buffer)) {
2038 // Fold this slice's offset into the parent slice's offset (relative to
2039 // the shared root), stored in `base`:
2040 slice->base = (void *)(uintptr_t)(io_buffer_slice_offset(buffer) + offset);
2041 RB_OBJ_WRITE(instance, &slice->source, buffer->source);
2042 }
2043 else {
2044 slice->base = (void *)(uintptr_t)offset;
2045 RB_OBJ_WRITE(instance, &slice->source, self);
2046 }
2047
2048 return instance;
2049}
2050
2051/*
2052 * call-seq: slice([offset, [length]]) -> io_buffer
2053 *
2054 * Produce another IO::Buffer which is a slice (or view into) the current one
2055 * starting at +offset+ bytes and going for +length+ bytes.
2056 *
2057 * The slicing happens without copying memory. The slice retains its root
2058 * buffer and becomes invalid if that root is freed, transferred, resized so
2059 * that the slice is outside its bounds, or otherwise invalidated.
2060 * Reallocating the root's storage does not invalidate the slice; it keeps the
2061 * same logical offset into the root.
2062 *
2063 * If the offset is not given, it will be zero. If the offset is negative, it
2064 * will raise an ArgumentError.
2065 *
2066 * If the length is not given, the slice will be as long as the original
2067 * buffer minus the specified offset. If the length is negative, it will raise
2068 * an ArgumentError.
2069 *
2070 * Raises RuntimeError if the <tt>offset+length</tt> is out of the current
2071 * buffer's bounds.
2072 *
2073 * string = 'test'
2074 * buffer = IO::Buffer.for(string).dup
2075 *
2076 * slice = buffer.slice
2077 * # =>
2078 * # #<IO::Buffer 0x0000000108338e68+4 SLICE>
2079 * # 0x00000000 74 65 73 74 test
2080 *
2081 * buffer.slice(2)
2082 * # =>
2083 * # #<IO::Buffer 0x0000000108338e6a+2 SLICE>
2084 * # 0x00000000 73 74 st
2085 *
2086 * slice = buffer.slice(1, 2)
2087 * # =>
2088 * # #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE>
2089 * # 0x00000000 65 73 es
2090 *
2091 * # Put "o" into 0s position of the slice
2092 * slice.set_string('o', 0)
2093 * slice
2094 * # =>
2095 * # #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE>
2096 * # 0x00000000 6f 73 os
2097 *
2098 * # it is also visible at position 1 of the original buffer
2099 * buffer
2100 * # =>
2101 * # #<IO::Buffer 0x00007fc3d31e2d80+4 INTERNAL>
2102 * # 0x00000000 74 6f 73 74 tost
2103 */
2104static VALUE
2105io_buffer_slice(int argc, VALUE *argv, VALUE self)
2106{
2107 rb_check_arity(argc, 0, 2);
2108
2109 size_t offset, length;
2110 struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);
2111
2112 return rb_io_buffer_slice(buffer, self, offset, length);
2113}
2114
2115VALUE
2116rb_io_buffer_transfer(VALUE self)
2117{
2118 struct rb_io_buffer *buffer = get_io_buffer(self);
2119
2120 if (io_buffer_locked(buffer)) {
2121 rb_raise(rb_eIOBufferLockedError, "Cannot transfer ownership of locked buffer!");
2122 }
2123
2124 VALUE instance = rb_io_buffer_type_allocate(rb_class_of(self));
2125 struct rb_io_buffer *transferred;
2126 TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, transferred);
2127
2128 *transferred = *buffer;
2129 io_buffer_zero(buffer);
2130
2131 return instance;
2132}
2133
2134/*
2135 * call-seq: transfer -> new_io_buffer
2136 *
2137 * Transfers ownership of the underlying memory to a new buffer, causing the
2138 * current buffer to become uninitialized.
2139 *
2140 * buffer = IO::Buffer.for('test')
2141 * other = buffer.transfer
2142 * other
2143 * # =>
2144 * # #<IO::Buffer 0x00007f136a15f7b0+4 EXTERNAL READONLY SLICE>
2145 * # 0x00000000 74 65 73 74 test
2146 * buffer
2147 * # =>
2148 * # #<IO::Buffer 0x0000000000000000+0 NULL EXTERNAL READONLY>
2149 * buffer.null?
2150 * # => true
2151 *
2152 * A frozen buffer cannot transfer ownership, as that would leave it
2153 * uninitialized:
2154 *
2155 * buffer = IO::Buffer.for('test').freeze
2156 * buffer.transfer
2157 * # in `transfer': can't modify frozen IO::Buffer (FrozenError)
2158 */
2159static VALUE
2160io_buffer_transfer(VALUE self)
2161{
2162 rb_check_frozen(self);
2163
2164 return rb_io_buffer_transfer(self);
2165}
2166
2167static void
2168io_buffer_resize_clear(struct rb_io_buffer *buffer, void* base, size_t size)
2169{
2170 if (size > buffer->size) {
2171 memset((unsigned char*)base+buffer->size, 0, size - buffer->size);
2172 }
2173}
2174
2175static void
2176io_buffer_resize_copy(VALUE self, struct rb_io_buffer *buffer, size_t size)
2177{
2178 // Slow path:
2179 struct rb_io_buffer resized;
2180 enum rb_io_buffer_flags flags = io_flags_for_size(size) | (buffer->flags & RB_IO_BUFFER_READONLY);
2181 io_buffer_initialize(self, &resized, NULL, size, flags, Qnil);
2182
2183 if (buffer->base) {
2184 size_t preserve = buffer->size;
2185 if (preserve > size) preserve = size;
2186 memcpy(resized.base, buffer->base, preserve);
2187
2188 io_buffer_resize_clear(buffer, resized.base, size);
2189 }
2190
2191 io_buffer_release(buffer);
2192 *buffer = resized;
2193}
2194
2195static void
2196io_buffer_resize_slice(struct rb_io_buffer *slice, size_t size)
2197{
2198 struct rb_io_buffer *source = get_io_buffer(slice->source);
2199
2200 void *source_base = NULL;
2201 size_t source_size = 0;
2202
2203 if (!io_buffer_try_get_bytes(source, &source_base, &source_size)) {
2204 rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!");
2205 }
2206
2207 size_t offset = io_buffer_slice_offset(slice);
2208
2209 if (offset > source_size) {
2210 rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!");
2211 }
2212
2213 if (size > source_size - offset) {
2214 rb_raise(rb_eArgError, "Resized slice exceeds its source buffer!");
2215 }
2216
2217 // Validate the requested range rather than the current range so that
2218 // shrinking a slice can restore its validity after the source shrinks.
2219 slice->size = size;
2220}
2221
2222void
2223rb_io_buffer_resize(VALUE self, size_t size)
2224{
2225 struct rb_io_buffer *buffer = get_io_buffer(self);
2226
2227 if (io_buffer_slice_p(buffer)) {
2228 // Resizing a slice only changes the view, not the locked allocation.
2229 io_buffer_resize_slice(buffer, size);
2230 return;
2231 }
2232
2233 io_buffer_validate_for_reading(buffer);
2234
2235 if (io_buffer_locked(buffer)) {
2236 rb_raise(rb_eIOBufferLockedError, "Cannot resize locked buffer!");
2237 }
2238
2239 // An external buffer (including a String-backed buffer, whose base is an
2240 // offset into the source) does not own its memory and cannot be resized.
2241 // This must be checked before the empty-buffer case below, since a
2242 // String-backed buffer at offset 0 has a NULL `base`.
2243 if (buffer->flags & RB_IO_BUFFER_EXTERNAL) {
2244 rb_raise(rb_eIOBufferAccessError, "Cannot resize external buffer!");
2245 }
2246
2247 if (buffer->base == NULL) {
2248 io_buffer_initialize(self, buffer, NULL, size, io_flags_for_size(size), Qnil);
2249 return;
2250 }
2251
2252 if (size == 0) {
2253 io_buffer_release(buffer);
2254 return;
2255 }
2256
2257#if defined(HAVE_MREMAP) && defined(MREMAP_MAYMOVE)
2258 if (buffer->flags & RB_IO_BUFFER_MAPPED) {
2259 void *base = mremap(buffer->base, buffer->size, size, MREMAP_MAYMOVE);
2260
2261 if (base == MAP_FAILED) {
2262 rb_sys_fail("rb_io_buffer_resize:mremap");
2263 }
2264
2265 io_buffer_resize_clear(buffer, base, size);
2266
2267 buffer->base = base;
2268 buffer->size = size;
2269
2270 return;
2271 }
2272#endif
2273
2274 if (buffer->flags & RB_IO_BUFFER_INTERNAL) {
2275 void *base = realloc(buffer->base, size);
2276
2277 if (!base) {
2278 rb_sys_fail("rb_io_buffer_resize:realloc");
2279 }
2280
2281 io_buffer_resize_clear(buffer, base, size);
2282
2283 buffer->base = base;
2284 buffer->size = size;
2285
2286 return;
2287 }
2288
2289 io_buffer_resize_copy(self, buffer, size);
2290}
2291
2292/*
2293 * call-seq: resize(new_size) -> self
2294 *
2295 * Resizes a buffer to a +new_size+ bytes, preserving its content.
2296 * Depending on the old and new size, the memory area associated with
2297 * the buffer might be either extended, or rellocated at different
2298 * address with content being copied.
2299 *
2300 * buffer = IO::Buffer.new(4)
2301 * buffer.set_string("test", 0)
2302 * buffer.resize(8) # resize to 8 bytes
2303 * # =>
2304 * # #<IO::Buffer 0x0000555f5d1a1630+8 INTERNAL>
2305 * # 0x00000000 74 65 73 74 00 00 00 00 test....
2306 *
2307 * When the buffer is a slice, resizing changes the size of the view without
2308 * modifying the source buffer or allocating new storage. The resized view
2309 * must remain within the source buffer. Growing the view exposes the existing
2310 * bytes in the source; they are not cleared. Because the source allocation
2311 * does not change, a slice can be resized while its source is locked.
2312 *
2313 * External owning buffers (created with ::for), and locked owning buffers
2314 * cannot be resized. Frozen buffers cannot be resized.
2315 */
2316static VALUE
2317io_buffer_resize(VALUE self, VALUE size)
2318{
2319 rb_check_frozen(self);
2320
2321 rb_io_buffer_resize(self, io_buffer_extract_size(size));
2322
2323 return self;
2324}
2325
2326/*
2327 * call-seq: <=>(other) -> integer
2328 *
2329 * Returns a negative integer, zero, or a positive integer if the receiver is
2330 * less than, equal to, or greater than +other+, respectively.
2331 *
2332 * Buffers are compared by size first, and if the sizes are equal, by the exact
2333 * contents of the memory they are referencing using +memcmp+. Only the sign of
2334 * the returned integer is meaningful; the result of +memcmp+ is returned as is.
2335 *
2336 * IO::Buffer.for("abc") <=> IO::Buffer.for("abc") # => 0
2337 * IO::Buffer.for("abc") <=> IO::Buffer.for("ab") # => 1
2338 * IO::Buffer.for("abc") <=> IO::Buffer.for("abd") # => -1
2339 */
2340static VALUE
2341rb_io_buffer_compare(VALUE self, VALUE other)
2342{
2343 const void *ptr1, *ptr2;
2344 size_t size1, size2;
2345
2346 rb_io_buffer_get_bytes_for_reading(self, &ptr1, &size1);
2347 rb_io_buffer_get_bytes_for_reading(other, &ptr2, &size2);
2348
2349 if (size1 < size2) {
2350 return RB_INT2NUM(-1);
2351 }
2352
2353 if (size1 > size2) {
2354 return RB_INT2NUM(1);
2355 }
2356
2357 if (size1 == 0) {
2358 return RB_INT2NUM(0);
2359 }
2360
2361 RUBY_ASSERT(ptr1 != NULL);
2362 RUBY_ASSERT(ptr2 != NULL);
2363 return RB_INT2NUM(memcmp(ptr1, ptr2, size1));
2364}
2365
2366static void
2367io_buffer_validate_type(size_t size, size_t offset, size_t extend)
2368{
2369 if (size_sum_is_bigger_than(offset, extend, size)) {
2370 rb_raise(rb_eArgError, "Type extends beyond end of buffer! (offset=%"PRIdSIZE" > size=%"PRIdSIZE")", offset, size);
2371 }
2372}
2373
2374// Lower case: little endian.
2375// Upper case: big endian (network endian).
2376//
2377// :U8 | unsigned 8-bit integer.
2378// :S8 | signed 8-bit integer.
2379//
2380// :u16, :U16 | unsigned 16-bit integer.
2381// :s16, :S16 | signed 16-bit integer.
2382//
2383// :u32, :U32 | unsigned 32-bit integer.
2384// :s32, :S32 | signed 32-bit integer.
2385//
2386// :u64, :U64 | unsigned 64-bit integer.
2387// :s64, :S64 | signed 64-bit integer.
2388//
2389// :u128, :U128 | unsigned 128-bit integer.
2390// :s128, :S128 | signed 128-bit integer.
2391//
2392// :f32, :F32 | 32-bit floating point number.
2393// :f64, :F64 | 64-bit floating point number.
2394
2395#define ruby_swap8(value) value
2396
2397union swapf32 {
2398 uint32_t integral;
2399 float value;
2400};
2401
2402static float
2403ruby_swapf32(float value)
2404{
2405 union swapf32 swap = {.value = value};
2406 swap.integral = ruby_swap32(swap.integral);
2407 return swap.value;
2408}
2409
2410union swapf64 {
2411 uint64_t integral;
2412 double value;
2413};
2414
2415static double
2416ruby_swapf64(double value)
2417{
2418 union swapf64 swap = {.value = value};
2419 swap.integral = ruby_swap64(swap.integral);
2420 return swap.value;
2421}
2422
2423// Structures and conversion functions are now in numeric.h/numeric.c
2424// Unified swap function for 128-bit integers (works with both signed and unsigned)
2425// Since both rb_uint128_t and rb_int128_t have the same memory layout,
2426// we can use a union to make the swap function work with both types
2427static inline rb_uint128_t
2428ruby_swap128_uint(rb_uint128_t x)
2429{
2430 rb_uint128_t result;
2431#ifdef HAVE_UINT128_T
2432#if __has_builtin(__builtin_bswap128)
2433 result.value = __builtin_bswap128(x.value);
2434#else
2435 // Manual byte swap for 128-bit integers
2436 uint64_t low = (uint64_t)x.value;
2437 uint64_t high = (uint64_t)(x.value >> 64);
2438 low = ruby_swap64(low);
2439 high = ruby_swap64(high);
2440 result.value = ((uint128_t)low << 64) | high;
2441#endif
2442#else
2443 // Fallback swap function using two 64-bit integers
2444 // For big-endian data on little-endian host (or vice versa):
2445 // 1. Swap bytes within each 64-bit part
2446 // 2. Swap the order of the parts (since big-endian stores high first, little-endian stores low first)
2447 result.parts.low = ruby_swap64(x.parts.high);
2448 result.parts.high = ruby_swap64(x.parts.low);
2449#endif
2450 return result;
2451}
2452
2453static inline rb_int128_t
2454ruby_swap128_int(rb_int128_t x)
2455{
2456 union uint128_int128_conversion conversion = {
2457 .int128 = x
2458 };
2459 conversion.uint128 = ruby_swap128_uint(conversion.uint128);
2460 return conversion.int128;
2461}
2462
2463#define IO_BUFFER_VALIDATE_TYPE_FOR_WRITING(buffer, base, size, offset, type) \
2464 (io_buffer_get_bytes_for_writing(buffer, &(base), &(size)), \
2465 io_buffer_validate_type(size, offset, sizeof(type)))
2466
2467#define IO_BUFFER_DECLARE_TYPE(name, type, endian, wrap, unwrap, swap) \
2468static ID RB_IO_BUFFER_DATA_TYPE_##name; \
2469\
2470static VALUE \
2471io_buffer_read_##name(const void* base, size_t size, size_t *offset) \
2472{ \
2473 io_buffer_validate_type(size, *offset, sizeof(type)); \
2474 type value; \
2475 memcpy(&value, (char*)base + *offset, sizeof(type)); \
2476 if (endian != RB_IO_BUFFER_HOST_ENDIAN) value = swap(value); \
2477 *offset += sizeof(type); \
2478 return wrap(value); \
2479} \
2480\
2481static void \
2482io_buffer_write_##name(struct rb_io_buffer* buffer, size_t *offset, VALUE _value) \
2483{ \
2484 void* base; size_t size; \
2485 IO_BUFFER_VALIDATE_TYPE_FOR_WRITING(buffer, base, size, *offset, type); \
2486 type value = unwrap(_value); \
2487 IO_BUFFER_VALIDATE_TYPE_FOR_WRITING(buffer, base, size, *offset, type); \
2488 if (endian != RB_IO_BUFFER_HOST_ENDIAN) value = swap(value); \
2489 memcpy((char*)base + *offset, &value, sizeof(type)); \
2490 *offset += sizeof(type); \
2491} \
2492\
2493enum { \
2494 RB_IO_BUFFER_DATA_TYPE_##name##_SIZE = sizeof(type) \
2495};
2496
2497IO_BUFFER_DECLARE_TYPE(U8, uint8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap8)
2498IO_BUFFER_DECLARE_TYPE(S8, int8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap8)
2499
2500IO_BUFFER_DECLARE_TYPE(u16, uint16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16)
2501IO_BUFFER_DECLARE_TYPE(U16, uint16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16)
2502IO_BUFFER_DECLARE_TYPE(s16, int16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16)
2503IO_BUFFER_DECLARE_TYPE(S16, int16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16)
2504
2505IO_BUFFER_DECLARE_TYPE(u32, uint32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32)
2506IO_BUFFER_DECLARE_TYPE(U32, uint32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32)
2507IO_BUFFER_DECLARE_TYPE(s32, int32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32)
2508IO_BUFFER_DECLARE_TYPE(S32, int32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32)
2509
2510IO_BUFFER_DECLARE_TYPE(u64, uint64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_ULL2NUM, RB_NUM2ULL, ruby_swap64)
2511IO_BUFFER_DECLARE_TYPE(U64, uint64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_ULL2NUM, RB_NUM2ULL, ruby_swap64)
2512IO_BUFFER_DECLARE_TYPE(s64, int64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64)
2513IO_BUFFER_DECLARE_TYPE(S64, int64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64)
2514
2515IO_BUFFER_DECLARE_TYPE(u128, rb_uint128_t, RB_IO_BUFFER_LITTLE_ENDIAN, rb_uint128_to_numeric, rb_numeric_to_uint128, ruby_swap128_uint)
2516IO_BUFFER_DECLARE_TYPE(U128, rb_uint128_t, RB_IO_BUFFER_BIG_ENDIAN, rb_uint128_to_numeric, rb_numeric_to_uint128, ruby_swap128_uint)
2517IO_BUFFER_DECLARE_TYPE(s128, rb_int128_t, RB_IO_BUFFER_LITTLE_ENDIAN, rb_int128_to_numeric, rb_numeric_to_int128, ruby_swap128_int)
2518IO_BUFFER_DECLARE_TYPE(S128, rb_int128_t, RB_IO_BUFFER_BIG_ENDIAN, rb_int128_to_numeric, rb_numeric_to_int128, ruby_swap128_int)
2519
2520IO_BUFFER_DECLARE_TYPE(f32, float, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
2521IO_BUFFER_DECLARE_TYPE(F32, float, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
2522IO_BUFFER_DECLARE_TYPE(f64, double, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64)
2523IO_BUFFER_DECLARE_TYPE(F64, double, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64)
2524#undef IO_BUFFER_DECLARE_TYPE
2525
2526static inline size_t
2527io_buffer_buffer_type_size(ID buffer_type)
2528{
2529#define IO_BUFFER_DATA_TYPE_SIZE(name) if (buffer_type == RB_IO_BUFFER_DATA_TYPE_##name) return RB_IO_BUFFER_DATA_TYPE_##name##_SIZE;
2530 IO_BUFFER_DATA_TYPE_SIZE(U8)
2531 IO_BUFFER_DATA_TYPE_SIZE(S8)
2532 IO_BUFFER_DATA_TYPE_SIZE(u16)
2533 IO_BUFFER_DATA_TYPE_SIZE(U16)
2534 IO_BUFFER_DATA_TYPE_SIZE(s16)
2535 IO_BUFFER_DATA_TYPE_SIZE(S16)
2536 IO_BUFFER_DATA_TYPE_SIZE(u32)
2537 IO_BUFFER_DATA_TYPE_SIZE(U32)
2538 IO_BUFFER_DATA_TYPE_SIZE(s32)
2539 IO_BUFFER_DATA_TYPE_SIZE(S32)
2540 IO_BUFFER_DATA_TYPE_SIZE(u64)
2541 IO_BUFFER_DATA_TYPE_SIZE(U64)
2542 IO_BUFFER_DATA_TYPE_SIZE(s64)
2543 IO_BUFFER_DATA_TYPE_SIZE(S64)
2544 IO_BUFFER_DATA_TYPE_SIZE(u128)
2545 IO_BUFFER_DATA_TYPE_SIZE(U128)
2546 IO_BUFFER_DATA_TYPE_SIZE(s128)
2547 IO_BUFFER_DATA_TYPE_SIZE(S128)
2548 IO_BUFFER_DATA_TYPE_SIZE(f32)
2549 IO_BUFFER_DATA_TYPE_SIZE(F32)
2550 IO_BUFFER_DATA_TYPE_SIZE(f64)
2551 IO_BUFFER_DATA_TYPE_SIZE(F64)
2552#undef IO_BUFFER_DATA_TYPE_SIZE
2553
2554 rb_raise(rb_eArgError, "Invalid type name!");
2555}
2556
2557static inline ID
2558io_buffer_type_id(VALUE name)
2559{
2560 Check_Type(name, T_SYMBOL);
2561 if (!STATIC_SYM_P(name)) return 0;
2562 return rb_sym2id(name);
2563}
2564#define TYPE_ID(name) io_buffer_type_id(name)
2565
2566/*
2567 * call-seq:
2568 * size_of(buffer_type) -> byte size
2569 * size_of(array of buffer_type) -> byte size
2570 *
2571 * Returns the size of the given buffer type(s) in bytes.
2572 *
2573 * IO::Buffer.size_of(:u32) # => 4
2574 * IO::Buffer.size_of([:u32, :u32]) # => 8
2575 */
2576static VALUE
2577io_buffer_size_of(VALUE klass, VALUE buffer_type)
2578{
2579 if (RB_TYPE_P(buffer_type, T_ARRAY)) {
2580 size_t total = 0;
2581 for (long i = 0; i < RARRAY_LEN(buffer_type); i++) {
2582 total += io_buffer_buffer_type_size(TYPE_ID(RARRAY_AREF(buffer_type, i)));
2583 }
2584 return SIZET2NUM(total);
2585 }
2586 else {
2587 return SIZET2NUM(io_buffer_buffer_type_size(TYPE_ID(buffer_type)));
2588 }
2589}
2590
2591static inline VALUE
2592rb_io_buffer_get_value(const void* base, size_t size, ID buffer_type, size_t *offset)
2593{
2594#define IO_BUFFER_GET_VALUE(name) if (buffer_type == RB_IO_BUFFER_DATA_TYPE_##name) return io_buffer_read_##name(base, size, offset);
2595 IO_BUFFER_GET_VALUE(U8)
2596 IO_BUFFER_GET_VALUE(S8)
2597
2598 IO_BUFFER_GET_VALUE(u16)
2599 IO_BUFFER_GET_VALUE(U16)
2600 IO_BUFFER_GET_VALUE(s16)
2601 IO_BUFFER_GET_VALUE(S16)
2602
2603 IO_BUFFER_GET_VALUE(u32)
2604 IO_BUFFER_GET_VALUE(U32)
2605 IO_BUFFER_GET_VALUE(s32)
2606 IO_BUFFER_GET_VALUE(S32)
2607
2608 IO_BUFFER_GET_VALUE(u64)
2609 IO_BUFFER_GET_VALUE(U64)
2610 IO_BUFFER_GET_VALUE(s64)
2611 IO_BUFFER_GET_VALUE(S64)
2612
2613 IO_BUFFER_GET_VALUE(u128)
2614 IO_BUFFER_GET_VALUE(U128)
2615 IO_BUFFER_GET_VALUE(s128)
2616 IO_BUFFER_GET_VALUE(S128)
2617
2618 IO_BUFFER_GET_VALUE(f32)
2619 IO_BUFFER_GET_VALUE(F32)
2620 IO_BUFFER_GET_VALUE(f64)
2621 IO_BUFFER_GET_VALUE(F64)
2622#undef IO_BUFFER_GET_VALUE
2623
2624 rb_raise(rb_eArgError, "Invalid type name!");
2625}
2626
2627/*
2628 * call-seq: get_value(buffer_type, offset) -> numeric
2629 *
2630 * Read from buffer a value of +type+ at +offset+. +buffer_type+ should be one
2631 * of symbols:
2632 *
2633 * * +:U8+: unsigned integer, 1 byte
2634 * * +:S8+: signed integer, 1 byte
2635 * * +:u16+: unsigned integer, 2 bytes, little-endian
2636 * * +:U16+: unsigned integer, 2 bytes, big-endian
2637 * * +:s16+: signed integer, 2 bytes, little-endian
2638 * * +:S16+: signed integer, 2 bytes, big-endian
2639 * * +:u32+: unsigned integer, 4 bytes, little-endian
2640 * * +:U32+: unsigned integer, 4 bytes, big-endian
2641 * * +:s32+: signed integer, 4 bytes, little-endian
2642 * * +:S32+: signed integer, 4 bytes, big-endian
2643 * * +:u64+: unsigned integer, 8 bytes, little-endian
2644 * * +:U64+: unsigned integer, 8 bytes, big-endian
2645 * * +:s64+: signed integer, 8 bytes, little-endian
2646 * * +:S64+: signed integer, 8 bytes, big-endian
2647 * * +:u128+: unsigned integer, 16 bytes, little-endian
2648 * * +:U128+: unsigned integer, 16 bytes, big-endian
2649 * * +:s128+: signed integer, 16 bytes, little-endian
2650 * * +:S128+: signed integer, 16 bytes, big-endian
2651 * * +:f32+: float, 4 bytes, little-endian
2652 * * +:F32+: float, 4 bytes, big-endian
2653 * * +:f64+: double, 8 bytes, little-endian
2654 * * +:F64+: double, 8 bytes, big-endian
2655 *
2656 * A buffer type refers specifically to the type of binary buffer that is stored
2657 * in the buffer. For example, a +:u32+ buffer type is a 32-bit unsigned
2658 * integer in little-endian format.
2659 *
2660 * string = [1.5].pack('f')
2661 * # => "\x00\x00\xC0?"
2662 * IO::Buffer.for(string).get_value(:f32, 0)
2663 * # => 1.5
2664 */
2665static VALUE
2666io_buffer_get_value(VALUE self, VALUE type, VALUE _offset)
2667{
2668 const void *base;
2669 size_t size;
2670 size_t offset = io_buffer_extract_offset(_offset);
2671
2672 rb_io_buffer_get_bytes_for_reading(self, &base, &size);
2673
2674 return rb_io_buffer_get_value(base, size, TYPE_ID(type), &offset);
2675}
2676
2677/*
2678 * call-seq: get_values(buffer_types, offset) -> array
2679 *
2680 * Similar to #get_value, except that it can handle multiple buffer types and
2681 * returns an array of values.
2682 *
2683 * string = [1.5, 2.5].pack('ff')
2684 * IO::Buffer.for(string).get_values([:f32, :f32], 0)
2685 * # => [1.5, 2.5]
2686 */
2687static VALUE
2688io_buffer_get_values(VALUE self, VALUE buffer_types, VALUE _offset)
2689{
2690 size_t offset = io_buffer_extract_offset(_offset);
2691
2692 const void *base;
2693 size_t size;
2694 rb_io_buffer_get_bytes_for_reading(self, &base, &size);
2695
2696 if (!RB_TYPE_P(buffer_types, T_ARRAY)) {
2697 rb_raise(rb_eArgError, "Argument buffer_types should be an array!");
2698 }
2699
2700 VALUE array = rb_ary_new_capa(RARRAY_LEN(buffer_types));
2701
2702 for (long i = 0; i < RARRAY_LEN(buffer_types); i++) {
2703 VALUE type = rb_ary_entry(buffer_types, i);
2704 VALUE value = rb_io_buffer_get_value(base, size, TYPE_ID(type), &offset);
2705 rb_ary_push(array, value);
2706 }
2707
2708 return array;
2709}
2710
2711// Extract a count argument, which must be a positive integer.
2712// Count is generally considered relative to the number of things.
2713static inline size_t
2714io_buffer_extract_count(VALUE argument)
2715{
2716 if (rb_int_negative_p(argument)) {
2717 rb_raise(rb_eArgError, "Count can't be negative!");
2718 }
2719
2720 return NUM2SIZET(argument);
2721}
2722
2723static inline void
2724io_buffer_extract_offset_count(ID buffer_type, size_t size, int argc, VALUE *argv, size_t *offset, size_t *count)
2725{
2726 if (argc >= 1) {
2727 *offset = io_buffer_extract_offset(argv[0]);
2728 }
2729 else {
2730 *offset = 0;
2731 }
2732
2733 if (argc >= 2) {
2734 *count = io_buffer_extract_count(argv[1]);
2735 }
2736 else {
2737 if (*offset > size) {
2738 rb_raise(rb_eArgError, "The given offset is bigger than the buffer size!");
2739 }
2740
2741 *count = (size - *offset) / io_buffer_buffer_type_size(buffer_type);
2742 }
2743}
2744
2746 VALUE self;
2747 int argc;
2748 VALUE *argv;
2749};
2750
2751static VALUE
2752io_buffer_each_locked(VALUE _arguments)
2753{
2754 struct io_buffer_each_arguments *arguments = (void *)_arguments;
2755 VALUE self = arguments->self;
2756 int argc = arguments->argc;
2757 VALUE *argv = arguments->argv;
2758
2759 const void *base;
2760 size_t size;
2761
2762 rb_io_buffer_get_bytes_for_reading(self, &base, &size);
2763
2764 ID buffer_type;
2765 if (argc >= 1) {
2766 buffer_type = TYPE_ID(argv[0]);
2767 }
2768 else {
2769 buffer_type = RB_IO_BUFFER_DATA_TYPE_U8;
2770 }
2771
2772 size_t offset, count;
2773 io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count);
2774
2775 for (size_t i = 0; i < count; i++) {
2776 size_t current_offset = offset;
2777 VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset);
2778 rb_yield_values(2, SIZET2NUM(current_offset), value);
2779 }
2780
2781 return self;
2782}
2783
2784/*
2785 * call-seq:
2786 * each(buffer_type, [offset, [count]]) {|offset, value| ...} -> self
2787 * each(buffer_type, [offset, [count]]) -> enumerator
2788 *
2789 * Iterates over the buffer, yielding each +value+ of +buffer_type+ starting
2790 * from +offset+.
2791 *
2792 * If +count+ is given, only +count+ values will be yielded.
2793 *
2794 * IO::Buffer.for("Hello World").each(:U8, 2, 2) do |offset, value|
2795 * puts "#{offset}: #{value}"
2796 * end
2797 * # 2: 108
2798 * # 3: 108
2799 */
2800static VALUE
2801io_buffer_each(int argc, VALUE *argv, VALUE self)
2802{
2803 RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);
2804
2805 struct io_buffer_each_arguments arguments = {
2806 .self = self,
2807 .argc = argc,
2808 .argv = argv,
2809 };
2810
2811 rb_io_buffer_lock(self);
2812 return rb_ensure(io_buffer_each_locked, (VALUE)&arguments, rb_io_buffer_locked_ensure, self);
2813}
2814
2815/*
2816 * call-seq: values(buffer_type, [offset, [count]]) -> array
2817 *
2818 * Returns an array of values of +buffer_type+ starting from +offset+.
2819 *
2820 * If +count+ is given, only +count+ values will be returned.
2821 *
2822 * IO::Buffer.for("Hello World").values(:U8, 2, 2)
2823 * # => [108, 108]
2824 */
2825static VALUE
2826io_buffer_values(int argc, VALUE *argv, VALUE self)
2827{
2828 const void *base;
2829 size_t size;
2830
2831 rb_io_buffer_get_bytes_for_reading(self, &base, &size);
2832
2833 ID buffer_type;
2834 if (argc >= 1) {
2835 buffer_type = TYPE_ID(argv[0]);
2836 }
2837 else {
2838 buffer_type = RB_IO_BUFFER_DATA_TYPE_U8;
2839 }
2840
2841 size_t offset, count;
2842 io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count);
2843
2844 VALUE array = rb_ary_new_capa(count);
2845
2846 for (size_t i = 0; i < count; i++) {
2847 VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset);
2848 rb_ary_push(array, value);
2849 }
2850
2851 return array;
2852}
2853
2854static VALUE
2855io_buffer_each_byte_locked(VALUE _arguments)
2856{
2857 struct io_buffer_each_arguments *arguments = (void *)_arguments;
2858 VALUE self = arguments->self;
2859 int argc = arguments->argc;
2860 VALUE *argv = arguments->argv;
2861
2862 const void *base;
2863 size_t size;
2864
2865 rb_io_buffer_get_bytes_for_reading(self, &base, &size);
2866
2867 size_t offset, count;
2868 io_buffer_extract_offset_count(RB_IO_BUFFER_DATA_TYPE_U8, size, argc, argv, &offset, &count);
2869
2870 if (size_sum_is_bigger_than(offset, count, size)) {
2871 rb_raise(rb_eArgError, "Specified offset+count is bigger than the buffer size!");
2872 }
2873
2874 for (size_t i = 0; i < count; i++) {
2875 unsigned char *value = (unsigned char *)base + i + offset;
2876 rb_yield(RB_INT2FIX(*value));
2877 }
2878
2879 return self;
2880}
2881
2882/*
2883 * call-seq:
2884 * each_byte([offset, [count]]) {|byte| ...} -> self
2885 * each_byte([offset, [count]]) -> enumerator
2886 *
2887 * Iterates over the buffer, yielding each byte starting from +offset+.
2888 *
2889 * If +count+ is given, only +count+ bytes will be yielded.
2890 *
2891 * IO::Buffer.for("Hello World").each_byte(2, 2) do |offset, byte|
2892 * puts "#{offset}: #{byte}"
2893 * end
2894 * # 2: 108
2895 * # 3: 108
2896 */
2897static VALUE
2898io_buffer_each_byte(int argc, VALUE *argv, VALUE self)
2899{
2900 RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);
2901
2902 struct io_buffer_each_arguments arguments = {
2903 .self = self,
2904 .argc = argc,
2905 .argv = argv,
2906 };
2907
2908 rb_io_buffer_lock(self);
2909 return rb_ensure(io_buffer_each_byte_locked, (VALUE)&arguments, rb_io_buffer_locked_ensure, self);
2910}
2911
2912static inline void
2913rb_io_buffer_set_value(struct rb_io_buffer *buffer, VALUE buffer_type, size_t *offset, VALUE value)
2914{
2915 ID type = TYPE_ID(buffer_type);
2916#define IO_BUFFER_SET_VALUE(name) if (type == RB_IO_BUFFER_DATA_TYPE_##name) {io_buffer_write_##name(buffer, offset, value); return;}
2917 IO_BUFFER_SET_VALUE(U8);
2918 IO_BUFFER_SET_VALUE(S8);
2919
2920 IO_BUFFER_SET_VALUE(u16);
2921 IO_BUFFER_SET_VALUE(U16);
2922 IO_BUFFER_SET_VALUE(s16);
2923 IO_BUFFER_SET_VALUE(S16);
2924
2925 IO_BUFFER_SET_VALUE(u32);
2926 IO_BUFFER_SET_VALUE(U32);
2927 IO_BUFFER_SET_VALUE(s32);
2928 IO_BUFFER_SET_VALUE(S32);
2929
2930 IO_BUFFER_SET_VALUE(u64);
2931 IO_BUFFER_SET_VALUE(U64);
2932 IO_BUFFER_SET_VALUE(s64);
2933 IO_BUFFER_SET_VALUE(S64);
2934
2935 IO_BUFFER_SET_VALUE(u128);
2936 IO_BUFFER_SET_VALUE(U128);
2937 IO_BUFFER_SET_VALUE(s128);
2938 IO_BUFFER_SET_VALUE(S128);
2939
2940 IO_BUFFER_SET_VALUE(f32);
2941 IO_BUFFER_SET_VALUE(F32);
2942 IO_BUFFER_SET_VALUE(f64);
2943 IO_BUFFER_SET_VALUE(F64);
2944#undef IO_BUFFER_SET_VALUE
2945
2946 rb_raise(rb_eArgError, "Invalid type name!");
2947}
2948
2950 struct rb_io_buffer *buffer;
2951 size_t offset;
2952 VALUE type, value;
2953};
2954
2955/*
2956 * call-seq: set_value(type, offset, value) -> offset
2957 *
2958 * Write to a buffer a +value+ of +type+ at +offset+. +type+ should be one of
2959 * symbols described in #get_value. Returns the offset just after the written
2960 * value.
2961 *
2962 * buffer = IO::Buffer.new(8)
2963 * # =>
2964 * # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
2965 * # 0x00000000 00 00 00 00 00 00 00 00
2966 *
2967 * buffer.set_value(:U8, 1, 111)
2968 * # => 2
2969 *
2970 * buffer
2971 * # =>
2972 * # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
2973 * # 0x00000000 00 6f 00 00 00 00 00 00 .o......
2974 *
2975 * Note that if the +type+ is integer and +value+ is Float, the implicit truncation is performed:
2976 *
2977 * buffer = IO::Buffer.new(8)
2978 * buffer.set_value(:U32, 0, 2.5)
2979 *
2980 * buffer
2981 * # =>
2982 * # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
2983 * # 0x00000000 00 00 00 02 00 00 00 00
2984 * # ^^ the same as if we'd pass just integer 2
2985 */
2986static VALUE
2987io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value)
2988{
2989 struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
2990 size_t offset = io_buffer_extract_offset(_offset);
2991 rb_io_buffer_set_value(buffer, type, &offset, value);
2992 return SIZET2NUM(offset);
2993}
2994
2995/*
2996 * call-seq: set_values(buffer_types, offset, values) -> offset
2997 *
2998 * Write +values+ of +buffer_types+ at +offset+ to the buffer. +buffer_types+
2999 * should be an array of symbols as described in #get_value. +values+ should
3000 * be an array of values to write. Returns the offset just after the last
3001 * written value.
3002 *
3003 * buffer = IO::Buffer.new(8)
3004 * buffer.set_values([:U8, :U16], 0, [1, 2])
3005 * # => 3
3006 * buffer
3007 * # =>
3008 * # #<IO::Buffer 0x696f717561746978+8 INTERNAL>
3009 * # 0x00000000 01 00 02 00 00 00 00 00 ........
3010 */
3011static VALUE
3012io_buffer_set_values(VALUE self, VALUE buffer_types, VALUE _offset, VALUE values)
3013{
3014 struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
3015
3016 if (!RB_TYPE_P(buffer_types, T_ARRAY)) {
3017 rb_raise(rb_eArgError, "Argument buffer_types should be an array!");
3018 }
3019
3020 size_t offset = io_buffer_extract_offset(_offset);
3021
3022 if (!RB_TYPE_P(values, T_ARRAY)) {
3023 rb_raise(rb_eArgError, "Argument values should be an array!");
3024 }
3025
3026 if (RARRAY_LEN(buffer_types) != RARRAY_LEN(values)) {
3027 rb_raise(rb_eArgError, "Argument buffer_types and values should have the same length!");
3028 }
3029
3030 for (long i = 0; i < RARRAY_LEN(buffer_types); i++) {
3031 VALUE type = rb_ary_entry(buffer_types, i);
3032 VALUE value = rb_ary_entry(values, i);
3033 rb_io_buffer_set_value(buffer, type, &offset, value);
3034 }
3035
3036 return SIZET2NUM(offset);
3037}
3038
3039static size_t IO_BUFFER_BLOCKING_SIZE = 1024*1024;
3040
3042 unsigned char * destination;
3043 const unsigned char * source;
3044 size_t length;
3045};
3046
3047static void *
3048io_buffer_memmove_blocking(void *data)
3049{
3050 struct io_buffer_memmove_arguments *arguments = (struct io_buffer_memmove_arguments *)data;
3051
3052 memmove(arguments->destination, arguments->source, arguments->length);
3053
3054 return NULL;
3055}
3056
3057static void
3058io_buffer_memmove_unblock(void *data)
3059{
3060 // No safe way to interrupt.
3061}
3062
3063static void
3064io_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)
3065{
3066 if (size_sum_is_bigger_than(offset, length, size)) {
3067 rb_raise(rb_eArgError, "Specified offset+length is bigger than the buffer size!");
3068 }
3069
3070 if (size_sum_is_bigger_than(source_offset, length, source_size)) {
3071 rb_raise(rb_eArgError, "The computed source range exceeds the size of the source buffer!");
3072 }
3073
3074 if (length == 0) return;
3075
3076 RUBY_ASSERT(base != NULL);
3077 RUBY_ASSERT(source_base != NULL);
3078 struct io_buffer_memmove_arguments arguments = {
3079 .destination = (unsigned char*)base+offset,
3080 .source = (unsigned char*)source_base+source_offset,
3081 .length = length
3082 };
3083
3084 if (arguments.length >= IO_BUFFER_BLOCKING_SIZE) {
3085 rb_nogvl(io_buffer_memmove_blocking, &arguments, io_buffer_memmove_unblock, &arguments, RB_NOGVL_OFFLOAD_SAFE);
3086 } else if (arguments.length != 0) {
3087 memmove(arguments.destination, arguments.source, arguments.length);
3088 }
3089}
3090
3091static void
3092io_buffer_extract_copy_arguments(size_t source_size, int argc, VALUE *argv, size_t *offset, size_t *length, size_t *source_offset)
3093{
3094 // The offset we copy into the buffer:
3095 if (argc >= 1) {
3096 *offset = io_buffer_extract_offset(argv[0]);
3097 }
3098 else {
3099 *offset = 0;
3100 }
3101
3102 // The offset we start from within the string:
3103 if (argc >= 3) {
3104 *source_offset = io_buffer_extract_offset(argv[2]);
3105
3106 if (*source_offset > source_size) {
3107 rb_raise(rb_eArgError, "The given source offset is bigger than the source itself!");
3108 }
3109 }
3110 else {
3111 *source_offset = 0;
3112 }
3113
3114 // The length we are going to copy:
3115 if (argc >= 2 && !RB_NIL_P(argv[1])) {
3116 *length = io_buffer_extract_length(argv[1]);
3117 }
3118 else {
3119 // Default to the source offset -> source size:
3120 *length = source_size - *source_offset;
3121 }
3122}
3123
3124// (offset, length, source_offset) -> length
3125static VALUE
3126io_buffer_copy_from(struct rb_io_buffer *buffer, const void *source_base, size_t source_size, int argc, VALUE *argv)
3127{
3128 size_t offset, length, source_offset;
3129 io_buffer_extract_copy_arguments(source_size, argc, argv, &offset, &length, &source_offset);
3130
3131 void *base;
3132 size_t size;
3133 io_buffer_get_bytes_for_writing(buffer, &base, &size);
3134
3135 if (length >= IO_BUFFER_BLOCKING_SIZE) {
3136 io_buffer_lock(buffer);
3137 }
3138
3139 io_buffer_memmove(base, size, offset, source_base, source_offset, source_size, length);
3140
3141 if (length >= IO_BUFFER_BLOCKING_SIZE) {
3142 io_buffer_unlock(buffer);
3143 }
3144
3145 return SIZET2NUM(length);
3146}
3147
3149 VALUE destination;
3150 const void *source_base;
3151 size_t source_size;
3152 int argc;
3153 VALUE *argv;
3154};
3155
3156// This is the innermost callback for IO::Buffer#copy. At this point the source
3157// is locked for reading and the destination is locked for writing, so both
3158// pointers and sizes remain valid while arguments are extracted, ranges are
3159// validated, and memmove potentially releases the GVL.
3160static VALUE
3161io_buffer_copy_to(void *base, size_t size, VALUE _arguments)
3162{
3163 struct io_buffer_copy_arguments *arguments = (void *)_arguments;
3164
3165 size_t offset, length, source_offset;
3166 io_buffer_extract_copy_arguments(arguments->source_size, arguments->argc, arguments->argv, &offset, &length, &source_offset);
3167
3168 io_buffer_memmove(base, size, offset, arguments->source_base, source_offset, arguments->source_size, length);
3169
3170 return SIZET2NUM(length);
3171}
3172
3173// This callback runs while the source is locked for reading. Retain its bytes
3174// in the callback arguments, then enter the destination's writable scope. The
3175// source scope remains active until that nested scope returns.
3176static VALUE
3177io_buffer_copy_from_readable(const void *base, size_t size, VALUE _arguments)
3178{
3179 struct io_buffer_copy_arguments *arguments = (void *)_arguments;
3180
3181 arguments->source_base = base;
3182 arguments->source_size = size;
3183
3184 return rb_io_buffer_locked_for_writing(arguments->destination, io_buffer_copy_to, _arguments);
3185}
3186
3187static VALUE
3188io_buffer_initialize_copy_from(const void *base, size_t size, VALUE self)
3189{
3190 struct rb_io_buffer *buffer = get_io_buffer(self);
3191
3192 io_buffer_initialize(self, buffer, NULL, size, io_flags_for_size(size), Qnil);
3193
3194 struct io_buffer_copy_arguments arguments = {
3195 .destination = self,
3196 .source_base = base,
3197 .source_size = size,
3198 .argc = 0,
3199 .argv = NULL,
3200 };
3201
3202 // The source remains locked by the outer readable scope while the newly
3203 // initialized destination is locked and populated by io_buffer_copy_to.
3204 return rb_io_buffer_locked_for_writing(self, io_buffer_copy_to, (VALUE)&arguments);
3205}
3206
3207/*
3208 * call-seq:
3209 * dup -> io_buffer
3210 * clone -> io_buffer
3211 *
3212 * Make an internal copy of the source buffer. Updates to the copy will not
3213 * affect the source buffer.
3214 *
3215 * source = IO::Buffer.for("Hello World")
3216 * # =>
3217 * # #<IO::Buffer 0x00007fd598466830+11 EXTERNAL READONLY SLICE>
3218 * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
3219 * buffer = source.dup
3220 * # =>
3221 * # #<IO::Buffer 0x0000558cbec03320+11 INTERNAL>
3222 * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
3223 */
3224static VALUE
3225rb_io_buffer_initialize_copy(VALUE self, VALUE source)
3226{
3227 return rb_io_buffer_locked_for_reading(source, io_buffer_initialize_copy_from, self);
3228}
3229
3230/*
3231 * call-seq:
3232 * copy(source, [offset, [length, [source_offset]]]) -> size
3233 *
3234 * Efficiently copy from a source IO::Buffer into the buffer, at +offset+
3235 * using +memmove+. For copying String instances, see #set_string.
3236 *
3237 * buffer = IO::Buffer.new(32)
3238 * # =>
3239 * # #<IO::Buffer 0x0000555f5ca22520+32 INTERNAL>
3240 * # 0x00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
3241 * # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ *
3242 *
3243 * buffer.copy(IO::Buffer.for("test"), 8)
3244 * # => 4 -- size of buffer copied
3245 * buffer
3246 * # =>
3247 * # #<IO::Buffer 0x0000555f5cf8fe40+32 INTERNAL>
3248 * # 0x00000000 00 00 00 00 00 00 00 00 74 65 73 74 00 00 00 00 ........test....
3249 * # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ *
3250 *
3251 * #copy can be used to put buffer into strings associated with buffer:
3252 *
3253 * string = "data: "
3254 * # => "data: "
3255 * buffer = IO::Buffer.for(string) do |buffer|
3256 * buffer.copy(IO::Buffer.for("test"), 5)
3257 * end
3258 * # => 4
3259 * string
3260 * # => "data:test"
3261 *
3262 * Attempt to copy into a read-only buffer will fail:
3263 *
3264 * File.write('test.txt', 'test')
3265 * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
3266 * buffer.copy(IO::Buffer.for("test"), 8)
3267 * # in `copy': Buffer is not writable! (IO::Buffer::AccessError)
3268 *
3269 * See ::map for details of creation of mutable file mappings, this will
3270 * work:
3271 *
3272 * buffer = IO::Buffer.map(File.open('test.txt', 'r+'))
3273 * buffer.copy(IO::Buffer.for("boom"), 0)
3274 * # => 4
3275 * File.read('test.txt')
3276 * # => "boom"
3277 *
3278 * Attempt to copy the buffer which will need place outside of buffer's
3279 * bounds will fail:
3280 *
3281 * buffer = IO::Buffer.new(2)
3282 * buffer.copy(IO::Buffer.for('test'), 0)
3283 * # in `copy': Specified offset+length is bigger than the buffer size! (ArgumentError)
3284 *
3285 * It is safe to copy between memory regions that overlaps each other.
3286 * In such case, the data is copied as if the data was first copied from the source buffer to
3287 * a temporary buffer, and then copied from the temporary buffer to the destination buffer.
3288 *
3289 * buffer = IO::Buffer.new(10)
3290 * buffer.set_string("0123456789")
3291 * buffer.copy(buffer, 3, 7)
3292 * # => 7
3293 * buffer
3294 * # =>
3295 * # #<IO::Buffer 0x000056494f8ce440+10 INTERNAL>
3296 * # 0x00000000 30 31 32 30 31 32 33 34 35 36 0120123456
3297 */
3298static VALUE
3299io_buffer_copy(int argc, VALUE *argv, VALUE self)
3300{
3301 rb_check_arity(argc, 1, 4);
3302
3303 VALUE source = argv[0];
3304 struct io_buffer_copy_arguments arguments = {
3305 .destination = self,
3306 .argc = argc-1,
3307 .argv = argv+1,
3308 };
3309
3310 // Lock the source first, then io_buffer_copy_from_readable nests the
3311 // destination lock. The scoped helpers use rb_ensure, so the destination
3312 // is unlocked before the source on both normal and exceptional returns.
3313 // If both buffers share an allocation, its reference-counted lock is
3314 // acquired and released twice.
3315 return rb_io_buffer_locked_for_reading(source, io_buffer_copy_from_readable, (VALUE)&arguments);
3316}
3317
3319 size_t offset;
3320 size_t length;
3321 rb_encoding *encoding;
3322};
3323
3324static VALUE
3325io_buffer_get_string_locked(const void *base, size_t size, VALUE _arguments)
3326{
3327 struct io_buffer_get_string_arguments *arguments = (void *)_arguments;
3328 if (size_sum_is_bigger_than(arguments->offset, arguments->length, size)) {
3329 rb_raise(rb_eArgError, "Specified offset+length is bigger than the buffer size!");
3330 }
3331 const char *data = base ? (const char *)base + arguments->offset : NULL;
3332 return rb_enc_str_new(data, arguments->length, arguments->encoding);
3333}
3334
3335/*
3336 * call-seq: get_string([offset, [length, [encoding]]]) -> string
3337 *
3338 * Read a chunk or all of the buffer into a string, in the specified
3339 * +encoding+. If no encoding is provided +Encoding::BINARY+ is used.
3340 *
3341 * buffer = IO::Buffer.for('test')
3342 * buffer.get_string
3343 * # => "test"
3344 * buffer.get_string(2)
3345 * # => "st"
3346 * buffer.get_string(2, 1)
3347 * # => "s"
3348 */
3349static VALUE
3350io_buffer_get_string(int argc, VALUE *argv, VALUE self)
3351{
3352 rb_check_arity(argc, 0, 3);
3353
3354 struct io_buffer_get_string_arguments arguments;
3355 io_buffer_extract_offset_length(self, argc, argv, &arguments.offset, &arguments.length);
3356
3357 // Encoding coercion may invoke Ruby and change the buffer. Retain no
3358 // metadata or byte pointer across it; resolve under the subsequent lock.
3359 arguments.encoding = argc >= 3 ? rb_find_encoding(argv[2]) : rb_ascii8bit_encoding();
3360 return rb_io_buffer_locked_for_reading(self, io_buffer_get_string_locked, (VALUE)&arguments);
3361}
3362
3363/*
3364 * call-seq: set_string(string, [offset, [length, [source_offset]]]) -> size
3365 *
3366 * Efficiently copy from a source String into the buffer, at +offset+ using
3367 * +memmove+.
3368 *
3369 * buf = IO::Buffer.new(8)
3370 * # =>
3371 * # #<IO::Buffer 0x0000557412714a20+8 INTERNAL>
3372 * # 0x00000000 00 00 00 00 00 00 00 00 ........
3373 *
3374 * # set buffer starting from offset 1, take 2 bytes starting from string's
3375 * # second
3376 * buf.set_string('test', 1, 2, 1)
3377 * # => 2
3378 * buf
3379 * # =>
3380 * # #<IO::Buffer 0x0000557412714a20+8 INTERNAL>
3381 * # 0x00000000 00 65 73 00 00 00 00 00 .es.....
3382 *
3383 * See also #copy for examples of how buffer writing might be used for changing
3384 * associated strings and files.
3385 */
3386static VALUE
3387io_buffer_set_string(int argc, VALUE *argv, VALUE self)
3388{
3389 rb_check_arity(argc, 1, 4);
3390
3391 struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
3392
3393 VALUE string = rb_str_to_str(argv[0]);
3394
3395 const void *source_base = RSTRING_PTR(string);
3396 size_t source_size = RSTRING_LEN(string);
3397
3398 VALUE result = io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
3399 RB_GC_GUARD(string);
3400 return result;
3401}
3402
3403void
3404rb_io_buffer_clear(VALUE self, uint8_t value, size_t offset, size_t length)
3405{
3406 struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
3407
3408 void *base;
3409 size_t size;
3410 io_buffer_get_bytes_for_writing(buffer, &base, &size);
3411
3412 io_buffer_validate_range(buffer, offset, length);
3413
3414 if (length == 0) return;
3415
3416 RUBY_ASSERT(base != NULL);
3417 memset((char*)base + offset, value, length);
3418}
3419
3420/*
3421 * call-seq: clear(value = 0, [offset, [length]]) -> self
3422 *
3423 * Fill buffer with +value+, starting with +offset+ and going for +length+
3424 * bytes.
3425 *
3426 * buffer = IO::Buffer.for('test').dup
3427 * # =>
3428 * # <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
3429 * # 0x00000000 74 65 73 74 test
3430 *
3431 * buffer.clear
3432 * # =>
3433 * # <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
3434 * # 0x00000000 00 00 00 00 ....
3435 *
3436 * buf.clear(1) # fill with 1
3437 * # =>
3438 * # <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
3439 * # 0x00000000 01 01 01 01 ....
3440 *
3441 * buffer.clear(2, 1, 2) # fill with 2, starting from offset 1, for 2 bytes
3442 * # =>
3443 * # <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
3444 * # 0x00000000 01 02 02 01 ....
3445 *
3446 * buffer.clear(2, 1) # fill with 2, starting from offset 1
3447 * # =>
3448 * # <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
3449 * # 0x00000000 01 02 02 02 ....
3450 */
3451static VALUE
3452io_buffer_clear(int argc, VALUE *argv, VALUE self)
3453{
3454 rb_check_arity(argc, 0, 3);
3455
3456 uint8_t value = 0;
3457 if (argc >= 1) {
3458 value = NUM2UINT(argv[0]);
3459 }
3460
3461 size_t offset, length;
3462 io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);
3463
3464 rb_io_buffer_clear(self, value, offset, length);
3465
3466 return self;
3467}
3468
3469static size_t
3470io_buffer_default_size(size_t page_size)
3471{
3472 // Platform agnostic default size, based on empirical performance observation:
3473 const size_t platform_agnostic_default_size = 64*1024;
3474
3475 // Allow user to specify custom default buffer size:
3476 const char *default_size = getenv("RUBY_IO_BUFFER_DEFAULT_SIZE");
3477 if (default_size) {
3478 // For the purpose of setting a default size, 2^31 is an acceptable maximum:
3479 int value = atoi(default_size);
3480
3481 // assuming sizeof(int) <= sizeof(size_t)
3482 if (value > 0) {
3483 return value;
3484 }
3485 }
3486
3487 if (platform_agnostic_default_size < page_size) {
3488 return page_size;
3489 }
3490
3491 return platform_agnostic_default_size;
3492}
3493
3495 struct rb_io *io;
3496 struct rb_io_buffer *buffer;
3497 rb_blocking_function_t *function;
3498 void *data;
3499};
3500
3501static VALUE
3502io_buffer_blocking_region_begin(VALUE _argument)
3503{
3504 struct io_buffer_blocking_region_argument *argument = (void*)_argument;
3505
3506 return rb_io_blocking_region(argument->io, argument->function, argument->data);
3507}
3508
3509static VALUE
3510io_buffer_blocking_region_ensure(VALUE _argument)
3511{
3512 struct io_buffer_blocking_region_argument *argument = (void*)_argument;
3513
3514 io_buffer_unlock(argument->buffer);
3515
3516 return Qnil;
3517}
3518
3519static VALUE
3520io_buffer_blocking_region(VALUE io, struct rb_io_buffer *buffer, rb_blocking_function_t *function, void *data)
3521{
3522 struct rb_io *ioptr;
3523 RB_IO_POINTER(io, ioptr);
3524
3525 struct io_buffer_blocking_region_argument argument = {
3526 .io = ioptr,
3527 .buffer = buffer,
3528 .function = function,
3529 .data = data,
3530 };
3531
3532 // The buffer should be locked for the duration of the blocking region. We
3533 // always acquire our own reference so another operation cannot release the
3534 // allocation while this operation is still using it:
3535 io_buffer_lock(buffer);
3536
3537 return rb_ensure(io_buffer_blocking_region_begin, (VALUE)&argument, io_buffer_blocking_region_ensure, (VALUE)&argument);
3538}
3539
3541 // The file descriptor to read from:
3542 int descriptor;
3543 // The base pointer to read into:
3544 char *base;
3545 // The maximum number of bytes to read:
3546 size_t length;
3547};
3548
3549static VALUE
3550io_buffer_read_internal(void *_argument)
3551{
3552 struct io_buffer_read_internal_argument *argument = _argument;
3553 ssize_t result = read(argument->descriptor, argument->base, argument->length);
3554
3555 return rb_fiber_scheduler_io_result(result, errno);
3556}
3557
3558VALUE
3559rb_io_buffer_read(VALUE self, VALUE io, size_t offset, size_t length)
3560{
3561 io = rb_io_get_io(io);
3562 struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
3563
3564 io_buffer_validate_range(buffer, offset, length);
3565
3566 if (length == 0) return SIZET2NUM(0);
3567
3568 VALUE scheduler = rb_fiber_scheduler_current();
3569 if (scheduler != Qnil) {
3570 VALUE result = rb_fiber_scheduler_io_read(scheduler, io, self, offset, length);
3571
3572 if (!UNDEF_P(result)) {
3573 return result;
3574 }
3575
3576 // The scheduler capability check can invoke Ruby and change the buffer.
3577 buffer = get_io_buffer_for_writing(self);
3578 io_buffer_validate_range(buffer, offset, length);
3579 }
3580
3581 void *base;
3582 size_t size;
3583 io_buffer_get_bytes_for_writing(buffer, &base, &size);
3584
3585 RUBY_ASSERT(base != NULL);
3586
3587 struct io_buffer_read_internal_argument argument = {
3588 .descriptor = rb_io_descriptor(io),
3589 .base = (char*)base + offset,
3590 .length = length,
3591 };
3592
3593 return io_buffer_blocking_region(io, buffer, io_buffer_read_internal, &argument);
3594}
3595
3596/*
3597 * call-seq: read(io, [offset, [length]]) -> read length or -errno
3598 *
3599 * Perform one read operation of at most +length+ bytes from +io+ into the
3600 * buffer starting at +offset+. A short read is a normal result. If an error
3601 * occurs, return <tt>-errno</tt>.
3602 *
3603 * If +offset+ is not given, it defaults to zero, i.e. the beginning of the
3604 * buffer. If +length+ is not given, it defaults to the size of the buffer
3605 * minus the offset. A zero length is a no-op.
3606 *
3607 * IO::Buffer.for('test') do |buffer|
3608 * p buffer
3609 * # =>
3610 * # <IO::Buffer 0x00007fca40087c38+4 SLICE>
3611 * # 0x00000000 74 65 73 74 test
3612 * buffer.read(File.open('/dev/urandom', 'rb'), 0, 2)
3613 * p buffer
3614 * # =>
3615 * # <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE>
3616 * # 0x00000000 05 35 73 74 .5st
3617 * end
3618 */
3619static VALUE
3620io_buffer_read(int argc, VALUE *argv, VALUE self)
3621{
3622 rb_check_arity(argc, 1, 3);
3623
3624 VALUE io = argv[0];
3625
3626 size_t offset, length;
3627 io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);
3628
3629 return rb_io_buffer_read(self, io, offset, length);
3630}
3631
3633 // The file descriptor to read from:
3634 int descriptor;
3635 // The base pointer to read into:
3636 char *base;
3637 // The maximum number of bytes to read:
3638 size_t length;
3639 // The position to read from:
3640 off_t from;
3641};
3642
3643static VALUE
3644io_buffer_pread_internal(void *_argument)
3645{
3646 struct io_buffer_pread_internal_argument *argument = _argument;
3647 ssize_t result = pread(argument->descriptor, argument->base, argument->length, argument->from);
3648
3649 return rb_fiber_scheduler_io_result(result, errno);
3650}
3651
3652VALUE
3653rb_io_buffer_pread(VALUE self, VALUE io, rb_off_t from, size_t offset, size_t length)
3654{
3655 io = rb_io_get_io(io);
3656 struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
3657
3658 io_buffer_validate_range(buffer, offset, length);
3659
3660 if (length == 0) return SIZET2NUM(0);
3661
3662 VALUE scheduler = rb_fiber_scheduler_current();
3663 if (scheduler != Qnil) {
3664 VALUE result = rb_fiber_scheduler_io_pread(scheduler, io, from, self, offset, length);
3665
3666 if (!UNDEF_P(result)) {
3667 return result;
3668 }
3669
3670 buffer = get_io_buffer_for_writing(self);
3671 io_buffer_validate_range(buffer, offset, length);
3672 }
3673
3674 void *base;
3675 size_t size;
3676 io_buffer_get_bytes_for_writing(buffer, &base, &size);
3677
3678 RUBY_ASSERT(base != NULL);
3679
3680 struct io_buffer_pread_internal_argument argument = {
3681 .descriptor = rb_io_descriptor(io),
3682 .base = (char*)base + offset,
3683 .length = length,
3684 .from = from,
3685 };
3686
3687 return io_buffer_blocking_region(io, buffer, io_buffer_pread_internal, &argument);
3688}
3689
3690/*
3691 * call-seq: pread(io, from, [offset, [length]]) -> read length or -errno
3692 *
3693 * Perform one read operation of at most +length+ bytes from +io+ at +from+
3694 * into the buffer starting at +offset+. A short read is a normal result and
3695 * the IO's current position is not modified. If an error occurs, return
3696 * <tt>-errno</tt>.
3697 *
3698 * If +offset+ is not given, it defaults to zero, i.e. the beginning of the
3699 * buffer. If +length+ is not given, it defaults to the size of the buffer
3700 * minus the offset. A zero length is a no-op.
3701 *
3702 * IO::Buffer.for('test') do |buffer|
3703 * p buffer
3704 * # =>
3705 * # <IO::Buffer 0x00007fca40087c38+4 SLICE>
3706 * # 0x00000000 74 65 73 74 test
3707 *
3708 * # take 2 bytes from the beginning of urandom,
3709 * # put them in buffer starting from position 2
3710 * buffer.pread(File.open('/dev/urandom', 'rb'), 0, 2, 2)
3711 * p buffer
3712 * # =>
3713 * # <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE>
3714 * # 0x00000000 05 35 73 74 te.5
3715 * end
3716 */
3717static VALUE
3718io_buffer_pread(int argc, VALUE *argv, VALUE self)
3719{
3720 rb_check_arity(argc, 2, 4);
3721
3722 VALUE io = argv[0];
3723 rb_off_t from = NUM2OFFT(argv[1]);
3724
3725 size_t offset, length;
3726 io_buffer_extract_offset_length(self, argc-2, argv+2, &offset, &length);
3727
3728 return rb_io_buffer_pread(self, io, from, offset, length);
3729}
3730
3732 // The file descriptor to write to:
3733 int descriptor;
3734 // The base pointer to write from:
3735 const char *base;
3736 // The maximum number of bytes to write:
3737 size_t length;
3738};
3739
3740static VALUE
3741io_buffer_write_internal(void *_argument)
3742{
3743 struct io_buffer_write_internal_argument *argument = _argument;
3744 ssize_t result = write(argument->descriptor, argument->base, argument->length);
3745
3746 return rb_fiber_scheduler_io_result(result, errno);
3747}
3748
3749VALUE
3750rb_io_buffer_write(VALUE self, VALUE io, size_t offset, size_t length)
3751{
3753
3754 struct rb_io_buffer *buffer = get_io_buffer(self);
3755 io_buffer_validate_range(buffer, offset, length);
3756
3757 if (length == 0) return SIZET2NUM(0);
3758
3759 VALUE scheduler = rb_fiber_scheduler_current();
3760 if (scheduler != Qnil) {
3761 VALUE result = rb_fiber_scheduler_io_write(scheduler, io, self, offset, length);
3762
3763 if (!UNDEF_P(result)) {
3764 return result;
3765 }
3766
3767 buffer = get_io_buffer(self);
3768 io_buffer_validate_range(buffer, offset, length);
3769 }
3770
3771 const void *base;
3772 size_t size;
3773 io_buffer_get_bytes_for_reading(buffer, &base, &size);
3774
3775 RUBY_ASSERT(base != NULL);
3776
3777 struct io_buffer_write_internal_argument argument = {
3778 .descriptor = rb_io_descriptor(io),
3779 .base = (const char*)base + offset,
3780 .length = length,
3781 };
3782
3783 return io_buffer_blocking_region(io, buffer, io_buffer_write_internal, &argument);
3784}
3785
3786/*
3787 * call-seq: write(io, [offset, [length]]) -> written length or -errno
3788 *
3789 * Perform one write operation of at most +length+ bytes to +io+ from the
3790 * buffer starting at +offset+. A short write is a normal result. If an error
3791 * occurs, return <tt>-errno</tt>.
3792 *
3793 * If +offset+ is not given, it defaults to zero, i.e. the beginning of the
3794 * buffer. If +length+ is not given, it defaults to the size of the buffer
3795 * minus the offset. A zero length is a no-op.
3796 *
3797 * out = File.open('output.txt', 'wb')
3798 * IO::Buffer.for('1234567').write(out, 0, 3)
3799 *
3800 * This leads to +123+ being written into <tt>output.txt</tt>
3801 */
3802static VALUE
3803io_buffer_write(int argc, VALUE *argv, VALUE self)
3804{
3805 rb_check_arity(argc, 1, 3);
3806
3807 VALUE io = argv[0];
3808
3809 size_t offset, length;
3810 io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);
3811
3812 return rb_io_buffer_write(self, io, offset, length);
3813}
3814
3816 // The file descriptor to write to:
3817 int descriptor;
3818 // The base pointer to write from:
3819 const char *base;
3820 // The maximum number of bytes to write:
3821 size_t length;
3822 // The position to write to:
3823 off_t from;
3824};
3825
3826static VALUE
3827io_buffer_pwrite_internal(void *_argument)
3828{
3829 struct io_buffer_pwrite_internal_argument *argument = _argument;
3830 ssize_t result = pwrite(argument->descriptor, argument->base, argument->length, argument->from);
3831
3832 return rb_fiber_scheduler_io_result(result, errno);
3833}
3834
3835VALUE
3836rb_io_buffer_pwrite(VALUE self, VALUE io, rb_off_t from, size_t offset, size_t length)
3837{
3839
3840 struct rb_io_buffer *buffer = get_io_buffer(self);
3841 io_buffer_validate_range(buffer, offset, length);
3842
3843 if (length == 0) return SIZET2NUM(0);
3844
3845 VALUE scheduler = rb_fiber_scheduler_current();
3846 if (scheduler != Qnil) {
3847 VALUE result = rb_fiber_scheduler_io_pwrite(scheduler, io, from, self, offset, length);
3848
3849 if (!UNDEF_P(result)) {
3850 return result;
3851 }
3852
3853 buffer = get_io_buffer(self);
3854 io_buffer_validate_range(buffer, offset, length);
3855 }
3856
3857 const void *base;
3858 size_t size;
3859 io_buffer_get_bytes_for_reading(buffer, &base, &size);
3860
3861 RUBY_ASSERT(base != NULL);
3862
3863 struct io_buffer_pwrite_internal_argument argument = {
3864 .descriptor = rb_io_descriptor(io),
3865 .base = (const char*)base + offset,
3866 .length = length,
3867 .from = from,
3868 };
3869
3870 return io_buffer_blocking_region(io, buffer, io_buffer_pwrite_internal, &argument);
3871}
3872
3873/*
3874 * call-seq: pwrite(io, from, [offset, [length]]) -> written length or -errno
3875 *
3876 * Perform one write operation of at most +length+ bytes to +io+ at +from+
3877 * from the buffer starting at +offset+. A short write is a normal result and
3878 * the IO's current position is not modified. If an error occurs, return
3879 * <tt>-errno</tt>.
3880 *
3881 * If +offset+ is not given, it defaults to zero, i.e. the beginning of the
3882 * buffer. If +length+ is not given, it defaults to the size of the buffer
3883 * minus the offset. A zero length is a no-op.
3884 *
3885 * If the +from+ position is beyond the end of the file, the gap will be
3886 * filled with null (0 value) bytes.
3887 *
3888 * out = File.open('output.txt', File::RDWR) # open for read/write, no truncation
3889 * IO::Buffer.for('1234567').pwrite(out, 2, 1, 3)
3890 *
3891 * This leads to +234+ (3 bytes, starting from position 1) being written into
3892 * <tt>output.txt</tt>, starting from file position 2.
3893 */
3894static VALUE
3895io_buffer_pwrite(int argc, VALUE *argv, VALUE self)
3896{
3897 rb_check_arity(argc, 2, 4);
3898
3899 VALUE io = argv[0];
3900 rb_off_t from = NUM2OFFT(argv[1]);
3901
3902 size_t offset, length;
3903 io_buffer_extract_offset_length(self, argc-2, argv+2, &offset, &length);
3904
3905 return rb_io_buffer_pwrite(self, io, from, offset, length);
3906}
3907
3908static inline void
3909io_buffer_check_mask_size(size_t size)
3910{
3911 if (size == 0)
3912 rb_raise(rb_eIOBufferMaskError, "Zero-length mask given!");
3913}
3914
3915static void
3916memory_and(unsigned char * restrict output, const unsigned char * restrict base, size_t size, const unsigned char * restrict mask, size_t mask_size)
3917{
3918 for (size_t offset = 0; offset < size; offset += 1) {
3919 output[offset] = base[offset] & mask[offset % mask_size];
3920 }
3921}
3922
3923/*
3924 * call-seq:
3925 * source & mask -> io_buffer
3926 *
3927 * Generate a new buffer the same size as the source by applying the binary AND
3928 * operation to the source, using the mask, repeating as necessary.
3929 *
3930 * IO::Buffer.for("1234567890") & IO::Buffer.for("\xFF\x00\x00\xFF")
3931 * # =>
3932 * # #<IO::Buffer 0x00005589b2758480+10 INTERNAL>
3933 * # 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89.
3934 */
3935static VALUE
3936io_buffer_and(VALUE self, VALUE mask)
3937{
3938 struct rb_io_buffer *buffer = get_io_buffer(self);
3939
3940 struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
3941
3942 const void *base;
3943 size_t size;
3944 io_buffer_get_bytes_for_reading(buffer, &base, &size);
3945
3946 const void *mask_base;
3947 size_t mask_size;
3948 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
3949
3950 io_buffer_check_mask_size(mask_size);
3951
3952 VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
3953 struct rb_io_buffer *output_buffer = get_io_buffer(output);
3954
3955 memory_and(output_buffer->base, base, size, mask_base, mask_size);
3956
3957 return output;
3958}
3959
3960static void
3961memory_or(unsigned char * restrict output, const unsigned char * restrict base, size_t size, const unsigned char * restrict mask, size_t mask_size)
3962{
3963 for (size_t offset = 0; offset < size; offset += 1) {
3964 output[offset] = base[offset] | mask[offset % mask_size];
3965 }
3966}
3967
3968/*
3969 * call-seq:
3970 * source | mask -> io_buffer
3971 *
3972 * Generate a new buffer the same size as the source by applying the binary OR
3973 * operation to the source, using the mask, repeating as necessary.
3974 *
3975 * IO::Buffer.for("1234567890") | IO::Buffer.for("\xFF\x00\x00\xFF")
3976 * # =>
3977 * # #<IO::Buffer 0x0000561785ae3480+10 INTERNAL>
3978 * # 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0
3979 */
3980static VALUE
3981io_buffer_or(VALUE self, VALUE mask)
3982{
3983 struct rb_io_buffer *buffer = get_io_buffer(self);
3984
3985 struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
3986
3987 const void *base;
3988 size_t size;
3989 io_buffer_get_bytes_for_reading(buffer, &base, &size);
3990
3991 const void *mask_base;
3992 size_t mask_size;
3993 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
3994
3995 io_buffer_check_mask_size(mask_size);
3996
3997 VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
3998 struct rb_io_buffer *output_buffer = get_io_buffer(output);
3999
4000 memory_or(output_buffer->base, base, size, mask_base, mask_size);
4001
4002 return output;
4003}
4004
4005static void
4006memory_xor(unsigned char * restrict output, const unsigned char * restrict base, size_t size, const unsigned char * restrict mask, size_t mask_size)
4007{
4008 for (size_t offset = 0; offset < size; offset += 1) {
4009 output[offset] = base[offset] ^ mask[offset % mask_size];
4010 }
4011}
4012
4013/*
4014 * call-seq:
4015 * source ^ mask -> io_buffer
4016 *
4017 * Generate a new buffer the same size as the source by applying the binary XOR
4018 * operation to the source, using the mask, repeating as necessary.
4019 *
4020 * IO::Buffer.for("1234567890") ^ IO::Buffer.for("\xFF\x00\x00\xFF")
4021 * # =>
4022 * # #<IO::Buffer 0x000055a2d5d10480+10 INTERNAL>
4023 * # 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0
4024 */
4025static VALUE
4026io_buffer_xor(VALUE self, VALUE mask)
4027{
4028 struct rb_io_buffer *buffer = get_io_buffer(self);
4029
4030 struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
4031
4032 const void *base;
4033 size_t size;
4034 io_buffer_get_bytes_for_reading(buffer, &base, &size);
4035
4036 const void *mask_base;
4037 size_t mask_size;
4038 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
4039
4040 io_buffer_check_mask_size(mask_size);
4041
4042 VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
4043 struct rb_io_buffer *output_buffer = get_io_buffer(output);
4044
4045 memory_xor(output_buffer->base, base, size, mask_base, mask_size);
4046
4047 return output;
4048}
4049
4050static void
4051memory_not(unsigned char * restrict output, const unsigned char * restrict base, size_t size)
4052{
4053 for (size_t offset = 0; offset < size; offset += 1) {
4054 output[offset] = ~base[offset];
4055 }
4056}
4057
4058/*
4059 * call-seq:
4060 * ~source -> io_buffer
4061 *
4062 * Generate a new buffer the same size as the source by applying the unary NOT
4063 * operation to the source.
4064 *
4065 * ~IO::Buffer.for("1234567890")
4066 * # =>
4067 * # #<IO::Buffer 0x000055a5ac42f120+10 INTERNAL>
4068 * # 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf ..........
4069 */
4070static VALUE
4071io_buffer_not(VALUE self)
4072{
4073 struct rb_io_buffer *buffer = get_io_buffer(self);
4074
4075 const void *base;
4076 size_t size;
4077 io_buffer_get_bytes_for_reading(buffer, &base, &size);
4078
4079 VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
4080 struct rb_io_buffer *output_buffer = get_io_buffer(output);
4081
4082 memory_not(output_buffer->base, base, size);
4083
4084 return output;
4085}
4086
4087static inline int
4088io_buffer_overlaps(struct rb_io_buffer *a, struct rb_io_buffer *b)
4089{
4090 // Resolve the current base pointers (following slice indirection):
4091 void *a_base = NULL, *b_base = NULL;
4092 size_t a_size = 0, b_size = 0;
4093 if (!io_buffer_try_get_bytes(a, &a_base, &a_size)) return 0;
4094 if (!io_buffer_try_get_bytes(b, &b_base, &b_size)) return 0;
4095
4096 if (a_size == 0 || b_size == 0 || a_base == NULL || b_base == NULL) return 0;
4097
4098 // Compare integer address differences, without ordering unrelated C
4099 // pointers or constructing end addresses that could overflow.
4100 uintptr_t a_start = (uintptr_t)a_base;
4101 uintptr_t b_start = (uintptr_t)b_base;
4102 if (a_start <= b_start) return b_start - a_start < a_size;
4103 return a_start - b_start < b_size;
4104}
4105
4106static inline void
4107io_buffer_check_overlaps(struct rb_io_buffer *a, struct rb_io_buffer *b)
4108{
4109 if (io_buffer_overlaps(a, b))
4110 rb_raise(rb_eIOBufferMaskError, "Mask overlaps source buffer!");
4111}
4112
4113static void
4114memory_and_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size)
4115{
4116 for (size_t offset = 0; offset < size; offset += 1) {
4117 base[offset] &= mask[offset % mask_size];
4118 }
4119}
4120
4121/*
4122 * call-seq:
4123 * source.and!(mask) -> io_buffer
4124 *
4125 * Modify the source buffer in place by applying the binary AND
4126 * operation to the source, using the mask, repeating as necessary.
4127 *
4128 * source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
4129 * # =>
4130 * # #<IO::Buffer 0x000056307a0d0c20+10 INTERNAL>
4131 * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
4132 *
4133 * source.and!(IO::Buffer.for("\xFF\x00\x00\xFF"))
4134 * # =>
4135 * # #<IO::Buffer 0x000056307a0d0c20+10 INTERNAL>
4136 * # 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89.
4137 */
4138static VALUE
4139io_buffer_and_inplace(VALUE self, VALUE mask)
4140{
4141 struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
4142
4143 struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
4144
4145 io_buffer_check_mask_size(mask_buffer->size);
4146 io_buffer_check_overlaps(buffer, mask_buffer);
4147
4148 void *base;
4149 size_t size;
4150 io_buffer_get_bytes_for_writing(buffer, &base, &size);
4151
4152 const void *mask_base;
4153 size_t mask_size;
4154 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
4155
4156 memory_and_inplace(base, size, (unsigned char *)mask_base, mask_size);
4157
4158 return self;
4159}
4160
4161static void
4162memory_or_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size)
4163{
4164 for (size_t offset = 0; offset < size; offset += 1) {
4165 base[offset] |= mask[offset % mask_size];
4166 }
4167}
4168
4169/*
4170 * call-seq:
4171 * source.or!(mask) -> io_buffer
4172 *
4173 * Modify the source buffer in place by applying the binary OR
4174 * operation to the source, using the mask, repeating as necessary.
4175 *
4176 * source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
4177 * # =>
4178 * # #<IO::Buffer 0x000056307a272350+10 INTERNAL>
4179 * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
4180 *
4181 * source.or!(IO::Buffer.for("\xFF\x00\x00\xFF"))
4182 * # =>
4183 * # #<IO::Buffer 0x000056307a272350+10 INTERNAL>
4184 * # 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0
4185 */
4186static VALUE
4187io_buffer_or_inplace(VALUE self, VALUE mask)
4188{
4189 struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
4190
4191 struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
4192
4193 io_buffer_check_mask_size(mask_buffer->size);
4194 io_buffer_check_overlaps(buffer, mask_buffer);
4195
4196 void *base;
4197 size_t size;
4198 io_buffer_get_bytes_for_writing(buffer, &base, &size);
4199
4200 const void *mask_base;
4201 size_t mask_size;
4202 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
4203
4204 memory_or_inplace(base, size, (unsigned char *)mask_base, mask_size);
4205
4206 return self;
4207}
4208
4209static void
4210memory_xor_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size)
4211{
4212 for (size_t offset = 0; offset < size; offset += 1) {
4213 base[offset] ^= mask[offset % mask_size];
4214 }
4215}
4216
4217/*
4218 * call-seq:
4219 * source.xor!(mask) -> io_buffer
4220 *
4221 * Modify the source buffer in place by applying the binary XOR
4222 * operation to the source, using the mask, repeating as necessary.
4223 *
4224 * source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
4225 * # =>
4226 * # #<IO::Buffer 0x000056307a25b3e0+10 INTERNAL>
4227 * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
4228 *
4229 * source.xor!(IO::Buffer.for("\xFF\x00\x00\xFF"))
4230 * # =>
4231 * # #<IO::Buffer 0x000056307a25b3e0+10 INTERNAL>
4232 * # 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0
4233 */
4234static VALUE
4235io_buffer_xor_inplace(VALUE self, VALUE mask)
4236{
4237 struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
4238
4239 struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
4240
4241 io_buffer_check_mask_size(mask_buffer->size);
4242 io_buffer_check_overlaps(buffer, mask_buffer);
4243
4244 void *base;
4245 size_t size;
4246 io_buffer_get_bytes_for_writing(buffer, &base, &size);
4247
4248 const void *mask_base;
4249 size_t mask_size;
4250 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
4251
4252 memory_xor_inplace(base, size, (unsigned char *)mask_base, mask_size);
4253
4254 return self;
4255}
4256
4257static void
4258memory_not_inplace(unsigned char * restrict base, size_t size)
4259{
4260 for (size_t offset = 0; offset < size; offset += 1) {
4261 base[offset] = ~base[offset];
4262 }
4263}
4264
4265/*
4266 * call-seq:
4267 * source.not! -> io_buffer
4268 *
4269 * Modify the source buffer in place by applying the unary NOT
4270 * operation to the source.
4271 *
4272 * source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
4273 * # =>
4274 * # #<IO::Buffer 0x000056307a33a450+10 INTERNAL>
4275 * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
4276 *
4277 * source.not!
4278 * # =>
4279 * # #<IO::Buffer 0x000056307a33a450+10 INTERNAL>
4280 * # 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf ..........
4281 */
4282static VALUE
4283io_buffer_not_inplace(VALUE self)
4284{
4285 struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
4286
4287 void *base;
4288 size_t size;
4289 io_buffer_get_bytes_for_writing(buffer, &base, &size);
4290
4291 memory_not_inplace(base, size);
4292
4293 return self;
4294}
4295
4296static size_t
4297memory_bit_count(const unsigned char *base, size_t size)
4298{
4299 size_t count = 0;
4300
4301 // Process 8 bytes at a time for efficiency:
4302 const uint64_t *base64 = (const uint64_t *)base;
4303 size_t count64 = size / 8;
4304 for (size_t i = 0; i < count64; i += 1) {
4305 count += rb_popcount64(base64[i]);
4306 }
4307
4308 // Process any remaining bytes:
4309 size_t remaining = size % 8;
4310 const unsigned char *tail = base + (count64 * 8);
4311 for (size_t i = 0; i < remaining; i += 1) {
4312 count += rb_popcount32(tail[i]);
4313 }
4314
4315 return count;
4316}
4317
4318/*
4319 * call-seq: bit_count([offset, [length]]) -> integer
4320 *
4321 * Returns the number of set bits (1s) in the buffer, also known as the
4322 * Hamming weight or population count. An optional +offset+ and +length+
4323 * can be provided to count bits in a subrange of the buffer.
4324 *
4325 * IO::Buffer.for("\xFF\x00\x0F").bit_count
4326 * # => 12
4327 *
4328 * IO::Buffer.for("\xFF\x00\x0F").bit_count(1, 2)
4329 * # => 4
4330 */
4331static VALUE
4332io_buffer_bit_count(int argc, VALUE *argv, VALUE self)
4333{
4334 rb_check_arity(argc, 0, 2);
4335
4336 size_t offset, length;
4337 struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);
4338
4339 io_buffer_validate_range(buffer, offset, length);
4340
4341 const void *base;
4342 size_t size;
4343 io_buffer_get_bytes_for_reading(buffer, &base, &size);
4344
4345 if (length == 0) return SIZET2NUM(0);
4346
4347 RUBY_ASSERT(base != NULL);
4348 size_t count = memory_bit_count((const unsigned char *)base + offset, length);
4349
4350 return SIZET2NUM(count);
4351}
4352
4353static bool
4354io_buffer_memory_view_get(VALUE self, rb_memory_view_t *view, int flags)
4355{
4356 struct rb_io_buffer *buffer = get_io_buffer(self);
4357
4358 void *base = NULL;
4359 size_t size = 0;
4360 if (!io_buffer_try_get_bytes(buffer, &base, &size) || base == NULL) {
4361 return false;
4362 }
4363
4364 bool readonly = true;
4365 if (flags & RUBY_MEMORY_VIEW_WRITABLE) {
4366 if (io_buffer_readonly_p(buffer)) {
4367 return false;
4368 } else {
4369 readonly = false;
4370 }
4371 }
4372 rb_memory_view_init_as_byte_array(view, self, base, buffer->size, readonly);
4373 if (flags & RUBY_MEMORY_VIEW_FORMAT) {
4374 view->format = "C";
4375 }
4376 bool request_multi_dimensional = flags & RUBY_MEMORY_VIEW_MULTI_DIMENSIONAL;
4377 bool request_strides =
4378 (flags & RUBY_MEMORY_VIEW_STRIDES) == RUBY_MEMORY_VIEW_STRIDES;
4379 if (request_multi_dimensional || request_strides) {
4380 size_t n_metadata = 0;
4381 if (request_multi_dimensional)
4382 n_metadata++;
4383 if (request_strides)
4384 n_metadata++;
4385 ssize_t *metadata_buffer = ALLOC_N(ssize_t, n_metadata);
4386 size_t i = 0;
4387 if (request_multi_dimensional) {
4388 ssize_t *shape = &metadata_buffer[i];
4389 shape[0] = buffer->size;
4390 view->shape = shape;
4391 i++;
4392 }
4393 if (request_strides) {
4394 ssize_t *strides = &metadata_buffer[i];
4395 strides[0] = 1;
4396 view->strides = strides;
4397 i++;
4398 }
4399 view->private_data = metadata_buffer;
4400 }
4401 io_buffer_lock(buffer);
4402
4403 return true;
4404}
4405
4406static bool
4407io_buffer_memory_view_release(VALUE self, rb_memory_view_t *view)
4408{
4409 rb_io_buffer_unlock(self);
4410 if (view->private_data) {
4411 xfree(view->private_data);
4412 }
4413 return true;
4414}
4415
4416static bool
4417io_buffer_memory_view_available_p(VALUE self)
4418{
4419 struct rb_io_buffer *buffer = get_io_buffer(self);
4420
4421 void *base = NULL;
4422 size_t size = 0;
4423 return io_buffer_try_get_bytes(buffer, &base, &size) && base != NULL;
4424}
4425
4426static const rb_memory_view_entry_t io_buffer_memory_view_entry = {
4427 .get_func = io_buffer_memory_view_get,
4428 .release_func = io_buffer_memory_view_release,
4429 .available_p_func = io_buffer_memory_view_available_p,
4430};
4431
4432/*
4433 * Document-class: IO::Buffer
4434 *
4435 * IO::Buffer is a efficient zero-copy buffer for input/output. There are
4436 * typical use cases:
4437 *
4438 * * Create an empty buffer with ::new, fill it with buffer using #copy or
4439 * #set_value, #set_string, get buffer with #get_string or write it directly
4440 * to some file with #write.
4441 * * Create a buffer mapped to some string with ::for, then it could be used
4442 * both for reading with #get_string or #get_value, and writing (writing will
4443 * change the source string, too).
4444 * * Create a buffer mapped to some file with ::map, then it could be used for
4445 * reading and writing the underlying file.
4446 * * Create a string of a fixed size with ::string, then #read into it, or
4447 * modify it using #set_value.
4448 *
4449 * Interaction with string and file memory is performed by efficient low-level
4450 * C mechanisms like `memcpy`.
4451 *
4452 * The class is meant to be an utility for implementing more high-level mechanisms
4453 * like Fiber::Scheduler#io_read and Fiber::Scheduler#io_write and parsing binary
4454 * protocols.
4455 *
4456 * == MemoryView Support
4457 *
4458 * IO::Buffer supports the C-level MemoryView protocol, so C
4459 * extensions can use +rb_memory_view_get()+ to access the buffer's
4460 * memory directly (zero-copy) as a 1-dimensional contiguous array of
4461 * bytes. The memory view is writable if the buffer is not
4462 * #readonly? and +RUBY_MEMORY_VIEW_WRITABLE+ is specified.
4463 *
4464 * While a MemoryView is exported, the buffer is locked.
4465 *
4466 * == Examples of Usage
4467 *
4468 * Empty buffer:
4469 *
4470 * buffer = IO::Buffer.new(8) # create empty 8-byte buffer
4471 * # =>
4472 * # #<IO::Buffer 0x0000555f5d1a5c50+8 INTERNAL>
4473 * # ...
4474 * buffer
4475 * # =>
4476 * # <IO::Buffer 0x0000555f5d156ab0+8 INTERNAL>
4477 * # 0x00000000 00 00 00 00 00 00 00 00
4478 * buffer.set_string('test', 2) # put there bytes of the "test" string, starting from offset 2
4479 * # => 4
4480 * buffer.get_string # get the result
4481 * # => "\x00\x00test\x00\x00"
4482 *
4483 * \Buffer from string:
4484 *
4485 * string = 'data'
4486 * IO::Buffer.for(string) do |buffer|
4487 * buffer
4488 * # =>
4489 * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
4490 * # 0x00000000 64 61 74 61 data
4491 *
4492 * buffer.get_string(2) # read content starting from offset 2
4493 * # => "ta"
4494 * buffer.set_string('---', 1) # write content, starting from offset 1
4495 * # => 3
4496 * buffer
4497 * # =>
4498 * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
4499 * # 0x00000000 64 2d 2d 2d d---
4500 * string # original string changed, too
4501 * # => "d---"
4502 * end
4503 *
4504 * \Buffer from file:
4505 *
4506 * File.write('test.txt', 'test data')
4507 * # => 9
4508 * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
4509 * # =>
4510 * # #<IO::Buffer 0x00007f3f0768c000+9 EXTERNAL MAPPED FILE SHARED READONLY>
4511 * # ...
4512 * buffer.get_string(5, 2) # read 2 bytes, starting from offset 5
4513 * # => "da"
4514 * buffer.set_string('---', 1) # attempt to write
4515 * # in `set_string': Buffer is not writable! (IO::Buffer::AccessError)
4516 *
4517 * # To create writable file-mapped buffer
4518 * # Open file for read-write, pass size, offset, and flags=0
4519 * buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 9, 0, 0)
4520 * buffer.set_string('---', 1)
4521 * # => 3 -- bytes written
4522 * File.read('test.txt')
4523 * # => "t--- data"
4524 *
4525 * <b>The class is experimental and the interface is subject to change, this
4526 * is especially true of file mappings which may be removed entirely in
4527 * the future.</b>
4528 */
4529void
4530Init_IO_Buffer(void)
4531{
4532 rb_cIOBuffer = rb_define_class_under(rb_cIO, "Buffer", rb_cObject);
4533
4534 /* Raised when an operation would resize or re-allocate a locked buffer. */
4535 rb_eIOBufferLockedError = rb_define_class_under(rb_cIOBuffer, "LockedError", rb_eRuntimeError);
4536
4537 /* Raised when the buffer cannot be allocated for some reason, or you try to use a buffer that's not allocated. */
4538 rb_eIOBufferAllocationError = rb_define_class_under(rb_cIOBuffer, "AllocationError", rb_eRuntimeError);
4539
4540 /* Raised when you try to write to a read-only buffer, or resize an external buffer. */
4541 rb_eIOBufferAccessError = rb_define_class_under(rb_cIOBuffer, "AccessError", rb_eRuntimeError);
4542
4543 /* Raised if you try to access a buffer slice which no longer references a valid memory range of the underlying source. */
4544 rb_eIOBufferInvalidatedError = rb_define_class_under(rb_cIOBuffer, "InvalidatedError", rb_eRuntimeError);
4545
4546 /* Raised if the mask given to a binary operation is invalid, e.g. zero length or overlaps the target buffer. */
4547 rb_eIOBufferMaskError = rb_define_class_under(rb_cIOBuffer, "MaskError", rb_eArgError);
4548
4549 rb_define_alloc_func(rb_cIOBuffer, rb_io_buffer_type_allocate);
4550 rb_define_singleton_method(rb_cIOBuffer, "for", rb_io_buffer_type_for, 1);
4551 rb_define_singleton_method(rb_cIOBuffer, "string", rb_io_buffer_type_string, 1);
4552
4553#ifdef _WIN32
4554 SYSTEM_INFO info;
4555 GetSystemInfo(&info);
4556 RUBY_IO_BUFFER_PAGE_SIZE = info.dwPageSize;
4557 RUBY_IO_BUFFER_MAP_ALIGNMENT = info.dwAllocationGranularity;
4558#else /* not WIN32 */
4559 RUBY_IO_BUFFER_PAGE_SIZE = sysconf(_SC_PAGESIZE);
4560 RUBY_IO_BUFFER_MAP_ALIGNMENT = RUBY_IO_BUFFER_PAGE_SIZE;
4561#endif
4562
4563 RUBY_IO_BUFFER_DEFAULT_SIZE = io_buffer_default_size(RUBY_IO_BUFFER_PAGE_SIZE);
4564
4565 /* The IO::Buffer interface version. */
4566 rb_define_const(rb_cIOBuffer, "VERSION", INT2NUM(RUBY_IO_BUFFER_VERSION));
4567
4568 /* The operating system page size. Used for efficient page-aligned memory allocations. */
4569 rb_define_const(rb_cIOBuffer, "PAGE_SIZE", SIZET2NUM(RUBY_IO_BUFFER_PAGE_SIZE));
4570
4571 /* The alignment required for file mapping offsets. Mapping sizes do not need to be aligned. */
4572 rb_define_const(rb_cIOBuffer, "MAP_ALIGNMENT", SIZET2NUM(RUBY_IO_BUFFER_MAP_ALIGNMENT));
4573
4574 /* The default buffer size, typically a (small) multiple of the PAGE_SIZE.
4575 Can be explicitly specified by setting the RUBY_IO_BUFFER_DEFAULT_SIZE
4576 environment variable. */
4577 rb_define_const(rb_cIOBuffer, "DEFAULT_SIZE", SIZET2NUM(RUBY_IO_BUFFER_DEFAULT_SIZE));
4578
4579 rb_define_singleton_method(rb_cIOBuffer, "map", io_buffer_map, -1);
4580
4581 rb_define_method(rb_cIOBuffer, "initialize", rb_io_buffer_initialize, -1);
4582 rb_define_method(rb_cIOBuffer, "initialize_copy", rb_io_buffer_initialize_copy, 1);
4583 rb_define_method(rb_cIOBuffer, "inspect", rb_io_buffer_inspect, 0);
4584 rb_define_method(rb_cIOBuffer, "hexdump", rb_io_buffer_hexdump, -1);
4585 rb_define_method(rb_cIOBuffer, "to_s", rb_io_buffer_to_s, 0);
4586 rb_define_method(rb_cIOBuffer, "size", rb_io_buffer_size, 0);
4587 rb_define_method(rb_cIOBuffer, "valid?", rb_io_buffer_valid_p, 0);
4588
4589 rb_define_method(rb_cIOBuffer, "transfer", io_buffer_transfer, 0);
4590
4591 /* Indicates that the memory in the buffer is owned by someone else. See #external? for more details. */
4592 rb_define_const(rb_cIOBuffer, "EXTERNAL", RB_INT2NUM(RB_IO_BUFFER_EXTERNAL));
4593
4594 /* Indicates that the memory in the buffer is owned by the buffer. See #internal? for more details. */
4595 rb_define_const(rb_cIOBuffer, "INTERNAL", RB_INT2NUM(RB_IO_BUFFER_INTERNAL));
4596
4597 /* Indicates that the memory in the buffer is mapped by the operating system. See #mapped? for more details. */
4598 rb_define_const(rb_cIOBuffer, "MAPPED", RB_INT2NUM(RB_IO_BUFFER_MAPPED));
4599
4600 /* Indicates that the memory in the buffer is also mapped such that it can be shared with other processes. See #shared? for more details. */
4601 rb_define_const(rb_cIOBuffer, "SHARED", RB_INT2NUM(RB_IO_BUFFER_SHARED));
4602
4603 /* 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. */
4604 rb_define_const(rb_cIOBuffer, "PRIVATE", RB_INT2NUM(RB_IO_BUFFER_PRIVATE));
4605
4606 /* Indicates that the memory in the buffer is read only, and attempts to modify it will fail. See #readonly? for more details.*/
4607 rb_define_const(rb_cIOBuffer, "READONLY", RB_INT2NUM(RB_IO_BUFFER_READONLY));
4608
4609 /* Refers to little endian byte order, where the least significant byte is stored first. See #get_value for more details. */
4610 rb_define_const(rb_cIOBuffer, "LITTLE_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_LITTLE_ENDIAN));
4611
4612 /* Refers to big endian byte order, where the most significant byte is stored first. See #get_value for more details. */
4613 rb_define_const(rb_cIOBuffer, "BIG_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_BIG_ENDIAN));
4614
4615 /* Refers to the byte order of the host machine. See #get_value for more details. */
4616 rb_define_const(rb_cIOBuffer, "HOST_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_HOST_ENDIAN));
4617
4618 /* Refers to network byte order, which is the same as big endian. See #get_value for more details. */
4619 rb_define_const(rb_cIOBuffer, "NETWORK_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_NETWORK_ENDIAN));
4620
4621 rb_define_method(rb_cIOBuffer, "null?", rb_io_buffer_null_p, 0);
4622 rb_define_method(rb_cIOBuffer, "empty?", rb_io_buffer_empty_p, 0);
4623 rb_define_method(rb_cIOBuffer, "external?", rb_io_buffer_external_p, 0);
4624 rb_define_method(rb_cIOBuffer, "internal?", rb_io_buffer_internal_p, 0);
4625 rb_define_method(rb_cIOBuffer, "mapped?", rb_io_buffer_mapped_p, 0);
4626 rb_define_method(rb_cIOBuffer, "shared?", rb_io_buffer_shared_p, 0);
4627 rb_define_method(rb_cIOBuffer, "locked?", rb_io_buffer_locked_p, 0);
4628 rb_define_method(rb_cIOBuffer, "private?", rb_io_buffer_private_p, 0);
4629 rb_define_method(rb_cIOBuffer, "readonly?", rb_io_buffer_readonly_p, 0);
4630
4631 // Locking to prevent changes while using pointer:
4632 // rb_define_method(rb_cIOBuffer, "lock", rb_io_buffer_lock, 0);
4633 // rb_define_method(rb_cIOBuffer, "unlock", rb_io_buffer_unlock, 0);
4634 rb_define_method(rb_cIOBuffer, "locked", rb_io_buffer_locked, 0);
4635
4636 // Manipulation:
4637 rb_define_method(rb_cIOBuffer, "slice", io_buffer_slice, -1);
4638 rb_define_method(rb_cIOBuffer, "<=>", rb_io_buffer_compare, 1);
4639 rb_define_method(rb_cIOBuffer, "resize", io_buffer_resize, 1);
4640 rb_define_method(rb_cIOBuffer, "clear", io_buffer_clear, -1);
4641 rb_define_method(rb_cIOBuffer, "free", io_buffer_free, 0);
4642
4643 rb_include_module(rb_cIOBuffer, rb_mComparable);
4644
4645#define IO_BUFFER_DEFINE_DATA_TYPE(name) RB_IO_BUFFER_DATA_TYPE_##name = rb_intern_const(#name)
4646 IO_BUFFER_DEFINE_DATA_TYPE(U8);
4647 IO_BUFFER_DEFINE_DATA_TYPE(S8);
4648
4649 IO_BUFFER_DEFINE_DATA_TYPE(u16);
4650 IO_BUFFER_DEFINE_DATA_TYPE(U16);
4651 IO_BUFFER_DEFINE_DATA_TYPE(s16);
4652 IO_BUFFER_DEFINE_DATA_TYPE(S16);
4653
4654 IO_BUFFER_DEFINE_DATA_TYPE(u32);
4655 IO_BUFFER_DEFINE_DATA_TYPE(U32);
4656 IO_BUFFER_DEFINE_DATA_TYPE(s32);
4657 IO_BUFFER_DEFINE_DATA_TYPE(S32);
4658
4659 IO_BUFFER_DEFINE_DATA_TYPE(u64);
4660 IO_BUFFER_DEFINE_DATA_TYPE(U64);
4661 IO_BUFFER_DEFINE_DATA_TYPE(s64);
4662 IO_BUFFER_DEFINE_DATA_TYPE(S64);
4663
4664 IO_BUFFER_DEFINE_DATA_TYPE(u128);
4665 IO_BUFFER_DEFINE_DATA_TYPE(U128);
4666 IO_BUFFER_DEFINE_DATA_TYPE(s128);
4667 IO_BUFFER_DEFINE_DATA_TYPE(S128);
4668
4669 IO_BUFFER_DEFINE_DATA_TYPE(f32);
4670 IO_BUFFER_DEFINE_DATA_TYPE(F32);
4671 IO_BUFFER_DEFINE_DATA_TYPE(f64);
4672 IO_BUFFER_DEFINE_DATA_TYPE(F64);
4673#undef IO_BUFFER_DEFINE_DATA_TYPE
4674
4675 rb_define_singleton_method(rb_cIOBuffer, "size_of", io_buffer_size_of, 1);
4676
4677 // Data access:
4678 rb_define_method(rb_cIOBuffer, "get_value", io_buffer_get_value, 2);
4679 rb_define_method(rb_cIOBuffer, "get_values", io_buffer_get_values, 2);
4680 rb_define_method(rb_cIOBuffer, "each", io_buffer_each, -1);
4681 rb_define_method(rb_cIOBuffer, "values", io_buffer_values, -1);
4682 rb_define_method(rb_cIOBuffer, "each_byte", io_buffer_each_byte, -1);
4683 rb_define_method(rb_cIOBuffer, "set_value", io_buffer_set_value, 3);
4684 rb_define_method(rb_cIOBuffer, "set_values", io_buffer_set_values, 3);
4685
4686 rb_define_method(rb_cIOBuffer, "copy", io_buffer_copy, -1);
4687
4688 rb_define_method(rb_cIOBuffer, "get_string", io_buffer_get_string, -1);
4689 rb_define_method(rb_cIOBuffer, "set_string", io_buffer_set_string, -1);
4690
4691 // Binary buffer manipulations:
4692 rb_define_method(rb_cIOBuffer, "&", io_buffer_and, 1);
4693 rb_define_method(rb_cIOBuffer, "|", io_buffer_or, 1);
4694 rb_define_method(rb_cIOBuffer, "^", io_buffer_xor, 1);
4695 rb_define_method(rb_cIOBuffer, "~", io_buffer_not, 0);
4696
4697 rb_define_method(rb_cIOBuffer, "and!", io_buffer_and_inplace, 1);
4698 rb_define_method(rb_cIOBuffer, "or!", io_buffer_or_inplace, 1);
4699 rb_define_method(rb_cIOBuffer, "xor!", io_buffer_xor_inplace, 1);
4700 rb_define_method(rb_cIOBuffer, "not!", io_buffer_not_inplace, 0);
4701
4702 rb_define_method(rb_cIOBuffer, "bit_count", io_buffer_bit_count, -1);
4703
4704 // IO operations:
4705 rb_define_method(rb_cIOBuffer, "read", io_buffer_read, -1);
4706 rb_define_method(rb_cIOBuffer, "pread", io_buffer_pread, -1);
4707 rb_define_method(rb_cIOBuffer, "write", io_buffer_write, -1);
4708 rb_define_method(rb_cIOBuffer, "pwrite", io_buffer_pwrite, -1);
4709
4710 // MemoryView:
4711 rb_memory_view_register(rb_cIOBuffer, &io_buffer_memory_view_entry);
4712}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
#define rb_define_singleton_method(klass, mid, func, arity)
Defines klass.mid.
static bool RB_OBJ_FROZEN(VALUE obj)
Checks if an object is frozen.
Definition fl_type.h:714
void rb_include_module(VALUE klass, VALUE module)
Includes a module to a class.
Definition class.c:1769
int rb_block_given_p(void)
Determines if the current method is given a block.
Definition eval.c:1035
#define T_STRING
Old name of RUBY_T_STRING.
Definition value_type.h:78
#define xfree
Old name of ruby_xfree.
Definition xmalloc.h:58
#define OBJ_FROZEN
Old name of RB_OBJ_FROZEN.
Definition fl_type.h:133
#define rb_str_cat2
Old name of rb_str_cat_cstr.
Definition string.h:1684
#define CLASS_OF
Old name of rb_class_of.
Definition globals.h:205
#define SIZET2NUM
Old name of RB_SIZE2NUM.
Definition size_t.h:62
#define NUM2UINT
Old name of RB_NUM2UINT.
Definition int.h:45
#define STATIC_SYM_P
Old name of RB_STATIC_SYM_P.
#define ALLOC_N
Old name of RB_ALLOC_N.
Definition memory.h:399
#define NUM2DBL
Old name of rb_num2dbl.
Definition double.h:27
#define INT2NUM
Old name of RB_INT2NUM.
Definition int.h:43
#define Qnil
Old name of RUBY_Qnil.
#define T_ARRAY
Old name of RUBY_T_ARRAY.
Definition value_type.h:56
#define NIL_P
Old name of RB_NIL_P.
#define T_SYMBOL
Old name of RUBY_T_SYMBOL.
Definition value_type.h:80
#define DBL2NUM
Old name of rb_float_new.
Definition double.h:29
#define NUM2SIZET
Old name of RB_NUM2SIZE.
Definition size_t.h:61
void rb_category_warn(rb_warning_category_t category, const char *fmt,...)
Identical to rb_category_warning(), except it reports unless $VERBOSE is nil.
Definition error.c:478
VALUE rb_eTypeError
TypeError exception.
Definition error.c:1473
VALUE rb_eRuntimeError
RuntimeError exception.
Definition error.c:1471
@ RB_WARN_CATEGORY_EXPERIMENTAL
Warning is for experimental features.
Definition error.h:51
VALUE rb_cObject
Object class.
Definition object.c:60
VALUE rb_cIO
IO class.
Definition io.c:187
static VALUE rb_class_of(VALUE obj)
Object to class mapping function.
Definition globals.h:174
VALUE rb_obj_class(VALUE obj)
Queries the class of an object.
Definition object.c:234
VALUE rb_obj_is_kind_of(VALUE obj, VALUE klass)
Queries if the given object is an instance (of possibly descendants) of the given class.
Definition object.c:906
VALUE rb_mComparable
Comparable module.
Definition compar.c:19
#define RB_OBJ_WRITE(old, slot, young)
Declaration of a "back" pointer.
Definition gc.h:492
Scheduler APIs.
VALUE rb_fiber_scheduler_current(void)
Identical to rb_fiber_scheduler_get(), except it also returns RUBY_Qnil in case of a blocking fiber.
Definition scheduler.c:581
VALUE rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length)
Non-blocking read from the passed IO.
Definition scheduler.c:935
VALUE rb_fiber_scheduler_io_pwrite(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t offset, size_t length)
Non-blocking write to the passed IO at the specified offset.
Definition scheduler.c:1046
static VALUE rb_fiber_scheduler_io_result(ssize_t result, int error)
Wrap a ssize_t and int errno into a single VALUE.
Definition scheduler.h:52
VALUE rb_fiber_scheduler_io_pread(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t offset, size_t length)
Non-blocking read from the passed IO at the specified offset.
Definition scheduler.c:969
VALUE rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length)
Non-blocking write to the passed IO.
Definition scheduler.c:1012
VALUE rb_ary_new_capa(long capa)
Identical to rb_ary_new(), except it additionally specifies how many rooms of objects it should alloc...
VALUE rb_ary_push(VALUE ary, VALUE elem)
Special case of rb_ary_cat() that it adds only one element.
VALUE rb_ary_entry(VALUE ary, long off)
Queries an element of an array.
#define RETURN_ENUMERATOR_KW(obj, argc, argv, kw_splat)
Identical to RETURN_SIZED_ENUMERATOR_KW(), except its size is unknown.
Definition enumerator.h:260
static int rb_check_arity(int argc, int min, int max)
Ensures that the passed integer is in the passed range.
Definition error.h:284
VALUE rb_str_append(VALUE dst, VALUE src)
Identical to rb_str_buf_append(), except it converts the right hand side before concatenating.
Definition string.c:3906
#define rb_str_new(str, len)
Allocates an instance of rb_cString.
Definition string.h:1499
VALUE rb_str_locktmp(VALUE str)
Obtains a "temporary lock" of the string.
VALUE rb_str_unlocktmp(VALUE str)
Releases a lock formerly obtained by rb_str_locktmp().
Definition string.c:3475
VALUE rb_str_buf_new(long capa)
Allocates a "string buffer".
Definition string.c:1763
#define rb_str_new_cstr(str)
Identical to rb_str_new, except it assumes the passed pointer is a pointer to a C string.
Definition string.h:1515
VALUE rb_class_name(VALUE obj)
Queries the name of the given object's class.
Definition variable.c:518
void rb_define_alloc_func(VALUE klass, rb_alloc_func_t func)
Sets the allocator function of a class.
ID rb_sym2id(VALUE obj)
Converts an instance of rb_cSymbol into an ID.
Definition symbol.c:1091
VALUE rb_io_get_io(VALUE io)
Identical to rb_io_check_io(), except it raises exceptions on conversion failures.
Definition io.c:869
int rb_io_descriptor(VALUE io)
Returns an integer representing the numeric file descriptor for io.
Definition io.c:3014
#define RB_IO_POINTER(obj, fp)
Queries the underlying IO pointer.
Definition io.h:436
VALUE rb_io_get_write_io(VALUE io)
Queries the tied IO for writing.
Definition io.c:881
void * rb_nogvl(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void *data2, int flags)
Identical to rb_thread_call_without_gvl(), except it additionally takes "flags" that change the behav...
Definition thread.c:1773
#define RB_NOGVL_OFFLOAD_SAFE
Passing this flag to rb_nogvl() indicates that the passed function is safe to offload to a background...
Definition thread.h:84
#define RB_NUM2INT
Just another name of rb_num2int_inline.
Definition int.h:38
#define RB_UINT2NUM
Just another name of rb_uint2num_inline.
Definition int.h:39
#define RB_INT2NUM
Just another name of rb_int2num_inline.
Definition int.h:37
static unsigned int RB_NUM2UINT(VALUE x)
Converts an instance of rb_cNumeric into C's unsigned int.
Definition int.h:185
#define RB_LL2NUM
Just another name of rb_ll2num_inline.
Definition long_long.h:28
#define RB_ULL2NUM
Just another name of rb_ull2num_inline.
Definition long_long.h:29
#define RB_NUM2ULL
Just another name of rb_num2ull_inline.
Definition long_long.h:33
#define RB_NUM2LL
Just another name of rb_num2ll_inline.
Definition long_long.h:32
VALUE rb_yield_values(int n,...)
Identical to rb_yield(), except it takes variadic number of parameters and pass them to the block.
Definition vm_eval.c:1401
VALUE rb_yield(VALUE val)
Yields the block.
Definition vm_eval.c:1378
static VALUE RB_INT2FIX(long i)
Converts a C's long into an instance of rb_cInteger.
Definition long.h:111
#define RB_NUM2LONG
Just another name of rb_num2long_inline.
Definition long.h:57
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
Memory View.
bool rb_memory_view_register(VALUE klass, const rb_memory_view_entry_t *entry)
Associates the passed class with the passed memory view entry.
bool rb_memory_view_init_as_byte_array(rb_memory_view_t *view, VALUE obj, void *data, const ssize_t len, const bool readonly)
Fill the members of view as an 1-dimensional byte array.
VALUE type(ANYARGS)
ANYARGS-ed function type.
VALUE rb_ensure(type *q, VALUE w, type *e, VALUE r)
An equivalent of ensure clause.
#define OFFT2NUM
Converts a C's off_t into an instance of rb_cInteger.
Definition off_t.h:33
#define NUM2OFFT
Converts an instance of rb_cNumeric into C's off_t.
Definition off_t.h:44
#define RARRAY_LEN
Just another name of rb_array_len.
Definition rarray.h:50
#define RARRAY_AREF(a, i)
Definition rarray.h:402
#define StringValue(v)
Ensures that the parameter object is a String.
Definition rstring.h:66
#define RSTRING_GETMEM(str, ptrvar, lenvar)
Convenient macro to obtain the contents and length at once.
Definition rstring.h:450
VALUE rb_str_to_str(VALUE obj)
Identical to rb_check_string_type(), except it raises exceptions in case of conversion failures.
Definition string.c:1824
#define TypedData_Get_Struct(obj, type, data_type, sval)
Obtains a C struct from inside of a wrapper Ruby object.
Definition rtypeddata.h:773
#define TypedData_Make_Struct(klass, type, data_type, sval)
Identical to TypedData_Wrap_Struct, except it allocates a new data region internally instead of takin...
Definition rtypeddata.h:604
#define errno
Ractor-aware version of errno.
Definition ruby.h:388
#define RB_NO_KEYWORDS
Do not pass keywords.
Definition scan_args.h:69
static bool RB_NIL_P(VALUE obj)
Checks if the given object is nil.
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:242
const char * wrap_struct_name
Name of structs of this kind.
Definition rtypeddata.h:249
Ruby's IO, metadata and buffers.
Definition io.h:295
Operations applied to a specific kind of a memory view.
rb_memory_view_get_func_t get_func
Exports a memory view from a Ruby object.
A MemoryView structure, rb_memory_view_t, is used for exporting objects' MemoryView.
Definition memory_view.h:77
const ssize_t * strides
ndim size array indicating the number of bytes to skip to go to the next element in each dimension.
const ssize_t * shape
ndim size array indicating the number of elements in each dimension.
void * private_data
The private data for managing this exported memory.
const char * format
A string to describe the format of an element, or NULL for unsigned bytes.
uintptr_t ID
Type that represents a Ruby identifier such as a variable name.
Definition value.h:52
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static void Check_Type(VALUE v, enum ruby_value_type t)
Identical to RB_TYPE_P(), except it raises exceptions on predication failure.
Definition value_type.h:425
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376