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