Ruby 4.1.0dev (2026-10-03 revision c6a042f7a4077d741d075cbdd6028fea1e0f9409)
io_buffer.c (c6a042f7a4077d741d075cbdd6028fea1e0f9409)
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 = 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_NUM2LONG(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
2633static void
2634io_buffer_validate_type(size_t size, size_t offset, size_t extend)
2635{
2636 if (size_sum_is_bigger_than(offset, extend, size)) {
2637 rb_raise(rb_eArgError, "Type extends beyond end of buffer! (offset=%"PRIdSIZE" > size=%"PRIdSIZE")", offset, size);
2638 }
2639}
2640
2641// Lower case: little endian.
2642// Upper case: big endian (network endian).
2643//
2644// :U8 | unsigned 8-bit integer.
2645// :S8 | signed 8-bit integer.
2646//
2647// :u16, :U16 | unsigned 16-bit integer.
2648// :s16, :S16 | signed 16-bit integer.
2649//
2650// :u32, :U32 | unsigned 32-bit integer.
2651// :s32, :S32 | signed 32-bit integer.
2652//
2653// :u64, :U64 | unsigned 64-bit integer.
2654// :s64, :S64 | signed 64-bit integer.
2655//
2656// :u128, :U128 | unsigned 128-bit integer.
2657// :s128, :S128 | signed 128-bit integer.
2658//
2659// :f32, :F32 | 32-bit floating point number.
2660// :f64, :F64 | 64-bit floating point number.
2661
2662#define ruby_swap8(value) value
2663
2664union swapf32 {
2665 uint32_t integral;
2666 float value;
2667};
2668
2669static float
2670ruby_swapf32(float value)
2671{
2672 union swapf32 swap = {.value = value};
2673 swap.integral = ruby_swap32(swap.integral);
2674 return swap.value;
2675}
2676
2677union swapf64 {
2678 uint64_t integral;
2679 double value;
2680};
2681
2682static double
2683ruby_swapf64(double value)
2684{
2685 union swapf64 swap = {.value = value};
2686 swap.integral = ruby_swap64(swap.integral);
2687 return swap.value;
2688}
2689
2690// Structures and conversion functions are now in numeric.h/numeric.c
2691// Unified swap function for 128-bit integers (works with both signed and unsigned)
2692// Since both rb_uint128_t and rb_int128_t have the same memory layout,
2693// we can use a union to make the swap function work with both types
2694static inline rb_uint128_t
2695ruby_swap128_uint(rb_uint128_t x)
2696{
2697 rb_uint128_t result;
2698#ifdef HAVE_UINT128_T
2699#if __has_builtin(__builtin_bswap128)
2700 result.value = __builtin_bswap128(x.value);
2701#else
2702 // Manual byte swap for 128-bit integers
2703 uint64_t low = (uint64_t)x.value;
2704 uint64_t high = (uint64_t)(x.value >> 64);
2705 low = ruby_swap64(low);
2706 high = ruby_swap64(high);
2707 result.value = ((uint128_t)low << 64) | high;
2708#endif
2709#else
2710 // Fallback swap function using two 64-bit integers
2711 // For big-endian data on little-endian host (or vice versa):
2712 // 1. Swap bytes within each 64-bit part
2713 // 2. Swap the order of the parts (since big-endian stores high first, little-endian stores low first)
2714 result.parts.low = ruby_swap64(x.parts.high);
2715 result.parts.high = ruby_swap64(x.parts.low);
2716#endif
2717 return result;
2718}
2719
2720static inline rb_int128_t
2721ruby_swap128_int(rb_int128_t x)
2722{
2723 union uint128_int128_conversion conversion = {
2724 .int128 = x
2725 };
2726 conversion.uint128 = ruby_swap128_uint(conversion.uint128);
2727 return conversion.int128;
2728}
2729
2730#define IO_BUFFER_VALIDATE_TYPE_FOR_WRITING(buffer, base, size, offset, type) \
2731 (io_buffer_get_bytes_for_writing(buffer, &(base), &(size)), \
2732 io_buffer_validate_type(size, offset, sizeof(type)))
2733
2734#define IO_BUFFER_DECLARE_TYPE(name, type, endian, wrap, unwrap, swap) \
2735static ID RB_IO_BUFFER_DATA_TYPE_##name; \
2736\
2737static VALUE \
2738io_buffer_read_##name(const void* base, size_t size, size_t *offset) \
2739{ \
2740 io_buffer_validate_type(size, *offset, sizeof(type)); \
2741 type value; \
2742 memcpy(&value, (char*)base + *offset, sizeof(type)); \
2743 if (endian != RB_IO_BUFFER_HOST_ENDIAN) value = swap(value); \
2744 *offset += sizeof(type); \
2745 return wrap(value); \
2746} \
2747\
2748static void \
2749io_buffer_write_##name(struct rb_io_buffer_view* buffer, size_t *offset, VALUE _value) \
2750{ \
2751 void* base; size_t size; \
2752 IO_BUFFER_VALIDATE_TYPE_FOR_WRITING(buffer, base, size, *offset, type); \
2753 type value = unwrap(_value); \
2754 IO_BUFFER_VALIDATE_TYPE_FOR_WRITING(buffer, base, size, *offset, type); \
2755 if (endian != RB_IO_BUFFER_HOST_ENDIAN) value = swap(value); \
2756 memcpy((char*)base + *offset, &value, sizeof(type)); \
2757 *offset += sizeof(type); \
2758} \
2759\
2760enum { \
2761 RB_IO_BUFFER_DATA_TYPE_##name##_SIZE = sizeof(type) \
2762};
2763
2764IO_BUFFER_DECLARE_TYPE(U8, uint8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap8)
2765IO_BUFFER_DECLARE_TYPE(S8, int8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap8)
2766
2767IO_BUFFER_DECLARE_TYPE(u16, uint16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16)
2768IO_BUFFER_DECLARE_TYPE(U16, uint16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16)
2769IO_BUFFER_DECLARE_TYPE(s16, int16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16)
2770IO_BUFFER_DECLARE_TYPE(S16, int16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16)
2771
2772IO_BUFFER_DECLARE_TYPE(u32, uint32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32)
2773IO_BUFFER_DECLARE_TYPE(U32, uint32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32)
2774IO_BUFFER_DECLARE_TYPE(s32, int32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32)
2775IO_BUFFER_DECLARE_TYPE(S32, int32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32)
2776
2777IO_BUFFER_DECLARE_TYPE(u64, uint64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_ULL2NUM, RB_NUM2ULL, ruby_swap64)
2778IO_BUFFER_DECLARE_TYPE(U64, uint64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_ULL2NUM, RB_NUM2ULL, ruby_swap64)
2779IO_BUFFER_DECLARE_TYPE(s64, int64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64)
2780IO_BUFFER_DECLARE_TYPE(S64, int64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64)
2781
2782IO_BUFFER_DECLARE_TYPE(u128, rb_uint128_t, RB_IO_BUFFER_LITTLE_ENDIAN, rb_uint128_to_numeric, rb_numeric_to_uint128, ruby_swap128_uint)
2783IO_BUFFER_DECLARE_TYPE(U128, rb_uint128_t, RB_IO_BUFFER_BIG_ENDIAN, rb_uint128_to_numeric, rb_numeric_to_uint128, ruby_swap128_uint)
2784IO_BUFFER_DECLARE_TYPE(s128, rb_int128_t, RB_IO_BUFFER_LITTLE_ENDIAN, rb_int128_to_numeric, rb_numeric_to_int128, ruby_swap128_int)
2785IO_BUFFER_DECLARE_TYPE(S128, rb_int128_t, RB_IO_BUFFER_BIG_ENDIAN, rb_int128_to_numeric, rb_numeric_to_int128, ruby_swap128_int)
2786
2787IO_BUFFER_DECLARE_TYPE(f32, float, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
2788IO_BUFFER_DECLARE_TYPE(F32, float, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
2789IO_BUFFER_DECLARE_TYPE(f64, double, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64)
2790IO_BUFFER_DECLARE_TYPE(F64, double, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64)
2791#undef IO_BUFFER_DECLARE_TYPE
2792
2793static inline size_t
2794io_buffer_buffer_type_size(ID buffer_type)
2795{
2796#define IO_BUFFER_DATA_TYPE_SIZE(name) if (buffer_type == RB_IO_BUFFER_DATA_TYPE_##name) return RB_IO_BUFFER_DATA_TYPE_##name##_SIZE;
2797 IO_BUFFER_DATA_TYPE_SIZE(U8)
2798 IO_BUFFER_DATA_TYPE_SIZE(S8)
2799 IO_BUFFER_DATA_TYPE_SIZE(u16)
2800 IO_BUFFER_DATA_TYPE_SIZE(U16)
2801 IO_BUFFER_DATA_TYPE_SIZE(s16)
2802 IO_BUFFER_DATA_TYPE_SIZE(S16)
2803 IO_BUFFER_DATA_TYPE_SIZE(u32)
2804 IO_BUFFER_DATA_TYPE_SIZE(U32)
2805 IO_BUFFER_DATA_TYPE_SIZE(s32)
2806 IO_BUFFER_DATA_TYPE_SIZE(S32)
2807 IO_BUFFER_DATA_TYPE_SIZE(u64)
2808 IO_BUFFER_DATA_TYPE_SIZE(U64)
2809 IO_BUFFER_DATA_TYPE_SIZE(s64)
2810 IO_BUFFER_DATA_TYPE_SIZE(S64)
2811 IO_BUFFER_DATA_TYPE_SIZE(u128)
2812 IO_BUFFER_DATA_TYPE_SIZE(U128)
2813 IO_BUFFER_DATA_TYPE_SIZE(s128)
2814 IO_BUFFER_DATA_TYPE_SIZE(S128)
2815 IO_BUFFER_DATA_TYPE_SIZE(f32)
2816 IO_BUFFER_DATA_TYPE_SIZE(F32)
2817 IO_BUFFER_DATA_TYPE_SIZE(f64)
2818 IO_BUFFER_DATA_TYPE_SIZE(F64)
2819#undef IO_BUFFER_DATA_TYPE_SIZE
2820
2821 rb_raise(rb_eArgError, "Invalid type name!");
2822}
2823
2824static inline ID
2825io_buffer_type_id(VALUE name)
2826{
2827 Check_Type(name, T_SYMBOL);
2828 if (!STATIC_SYM_P(name)) return 0;
2829 return rb_sym2id(name);
2830}
2831#define TYPE_ID(name) io_buffer_type_id(name)
2832
2833/*
2834 * call-seq:
2835 * size_of(buffer_type) -> byte size
2836 * size_of(array of buffer_type) -> byte size
2837 *
2838 * Returns the size of the given buffer type(s) in bytes.
2839 *
2840 * IO::Buffer.size_of(:u32) # => 4
2841 * IO::Buffer.size_of([:u32, :u32]) # => 8
2842 */
2843static VALUE
2844io_buffer_size_of(VALUE klass, VALUE buffer_type)
2845{
2846 if (RB_TYPE_P(buffer_type, T_ARRAY)) {
2847 size_t total = 0;
2848 for (long i = 0; i < RARRAY_LEN(buffer_type); i++) {
2849 total += io_buffer_buffer_type_size(TYPE_ID(RARRAY_AREF(buffer_type, i)));
2850 }
2851 return SIZET2NUM(total);
2852 }
2853 else {
2854 return SIZET2NUM(io_buffer_buffer_type_size(TYPE_ID(buffer_type)));
2855 }
2856}
2857
2858static inline VALUE
2859rb_io_buffer_get_value(const void* base, size_t size, ID buffer_type, size_t *offset)
2860{
2861#define IO_BUFFER_GET_VALUE(name) if (buffer_type == RB_IO_BUFFER_DATA_TYPE_##name) return io_buffer_read_##name(base, size, offset);
2862 IO_BUFFER_GET_VALUE(U8)
2863 IO_BUFFER_GET_VALUE(S8)
2864
2865 IO_BUFFER_GET_VALUE(u16)
2866 IO_BUFFER_GET_VALUE(U16)
2867 IO_BUFFER_GET_VALUE(s16)
2868 IO_BUFFER_GET_VALUE(S16)
2869
2870 IO_BUFFER_GET_VALUE(u32)
2871 IO_BUFFER_GET_VALUE(U32)
2872 IO_BUFFER_GET_VALUE(s32)
2873 IO_BUFFER_GET_VALUE(S32)
2874
2875 IO_BUFFER_GET_VALUE(u64)
2876 IO_BUFFER_GET_VALUE(U64)
2877 IO_BUFFER_GET_VALUE(s64)
2878 IO_BUFFER_GET_VALUE(S64)
2879
2880 IO_BUFFER_GET_VALUE(u128)
2881 IO_BUFFER_GET_VALUE(U128)
2882 IO_BUFFER_GET_VALUE(s128)
2883 IO_BUFFER_GET_VALUE(S128)
2884
2885 IO_BUFFER_GET_VALUE(f32)
2886 IO_BUFFER_GET_VALUE(F32)
2887 IO_BUFFER_GET_VALUE(f64)
2888 IO_BUFFER_GET_VALUE(F64)
2889#undef IO_BUFFER_GET_VALUE
2890
2891 rb_raise(rb_eArgError, "Invalid type name!");
2892}
2893
2894/*
2895 * call-seq: get_value(buffer_type, offset) -> numeric
2896 *
2897 * Read from buffer a value of +type+ at +offset+. +buffer_type+ should be one
2898 * of symbols:
2899 *
2900 * * +:U8+: unsigned integer, 1 byte
2901 * * +:S8+: signed integer, 1 byte
2902 * * +:u16+: unsigned integer, 2 bytes, little-endian
2903 * * +:U16+: unsigned integer, 2 bytes, big-endian
2904 * * +:s16+: signed integer, 2 bytes, little-endian
2905 * * +:S16+: signed integer, 2 bytes, big-endian
2906 * * +:u32+: unsigned integer, 4 bytes, little-endian
2907 * * +:U32+: unsigned integer, 4 bytes, big-endian
2908 * * +:s32+: signed integer, 4 bytes, little-endian
2909 * * +:S32+: signed integer, 4 bytes, big-endian
2910 * * +:u64+: unsigned integer, 8 bytes, little-endian
2911 * * +:U64+: unsigned integer, 8 bytes, big-endian
2912 * * +:s64+: signed integer, 8 bytes, little-endian
2913 * * +:S64+: signed integer, 8 bytes, big-endian
2914 * * +:u128+: unsigned integer, 16 bytes, little-endian
2915 * * +:U128+: unsigned integer, 16 bytes, big-endian
2916 * * +:s128+: signed integer, 16 bytes, little-endian
2917 * * +:S128+: signed integer, 16 bytes, big-endian
2918 * * +:f32+: float, 4 bytes, little-endian
2919 * * +:F32+: float, 4 bytes, big-endian
2920 * * +:f64+: double, 8 bytes, little-endian
2921 * * +:F64+: double, 8 bytes, big-endian
2922 *
2923 * A buffer type refers specifically to the type of binary buffer that is stored
2924 * in the buffer. For example, a +:u32+ buffer type is a 32-bit unsigned
2925 * integer in little-endian format.
2926 *
2927 * string = [1.5].pack('f')
2928 * # => "\x00\x00\xC0?"
2929 * IO::Buffer.for(string).get_value(:f32, 0)
2930 * # => 1.5
2931 */
2932static VALUE
2933io_buffer_get_value(VALUE self, VALUE type, VALUE _offset)
2934{
2935 const void *base;
2936 size_t size;
2937 size_t offset = io_buffer_extract_offset(_offset);
2938
2939 rb_io_buffer_get_bytes_for_reading(self, &base, &size);
2940
2941 return rb_io_buffer_get_value(base, size, TYPE_ID(type), &offset);
2942}
2943
2944/*
2945 * call-seq: get_values(buffer_types, offset) -> array
2946 *
2947 * Similar to #get_value, except that it can handle multiple buffer types and
2948 * returns an array of values.
2949 *
2950 * string = [1.5, 2.5].pack('ff')
2951 * IO::Buffer.for(string).get_values([:f32, :f32], 0)
2952 * # => [1.5, 2.5]
2953 */
2954static VALUE
2955io_buffer_get_values(VALUE self, VALUE buffer_types, VALUE _offset)
2956{
2957 size_t offset = io_buffer_extract_offset(_offset);
2958
2959 const void *base;
2960 size_t size;
2961 rb_io_buffer_get_bytes_for_reading(self, &base, &size);
2962
2963 if (!RB_TYPE_P(buffer_types, T_ARRAY)) {
2964 rb_raise(rb_eArgError, "Argument buffer_types should be an array!");
2965 }
2966
2967 VALUE array = rb_ary_new_capa(RARRAY_LEN(buffer_types));
2968
2969 for (long i = 0; i < RARRAY_LEN(buffer_types); i++) {
2970 VALUE type = rb_ary_entry(buffer_types, i);
2971 VALUE value = rb_io_buffer_get_value(base, size, TYPE_ID(type), &offset);
2972 rb_ary_push(array, value);
2973 }
2974
2975 return array;
2976}
2977
2978// Extract a count argument, which must be a positive integer.
2979// Count is generally considered relative to the number of things.
2980static inline size_t
2981io_buffer_extract_count(VALUE argument)
2982{
2983 if (rb_int_negative_p(argument)) {
2984 rb_raise(rb_eArgError, "Count can't be negative!");
2985 }
2986
2987 return NUM2SIZET(argument);
2988}
2989
2990static inline void
2991io_buffer_extract_offset_count(ID buffer_type, size_t size, int argc, VALUE *argv, size_t *offset, size_t *count)
2992{
2993 if (argc >= 1) {
2994 *offset = io_buffer_extract_offset(argv[0]);
2995 }
2996 else {
2997 *offset = 0;
2998 }
2999
3000 if (argc >= 2) {
3001 *count = io_buffer_extract_count(argv[1]);
3002 }
3003 else {
3004 if (*offset > size) {
3005 rb_raise(rb_eArgError, "The given offset is bigger than the buffer size!");
3006 }
3007
3008 *count = (size - *offset) / io_buffer_buffer_type_size(buffer_type);
3009 }
3010}
3011
3013 VALUE self;
3014 int argc;
3015 VALUE *argv;
3016};
3017
3018static VALUE
3019io_buffer_each_locked(VALUE _arguments)
3020{
3021 struct io_buffer_each_arguments *arguments = (void *)_arguments;
3022 VALUE self = arguments->self;
3023 int argc = arguments->argc;
3024 VALUE *argv = arguments->argv;
3025
3026 const void *base;
3027 size_t size;
3028
3029 rb_io_buffer_get_bytes_for_reading(self, &base, &size);
3030
3031 ID buffer_type;
3032 if (argc >= 1) {
3033 buffer_type = TYPE_ID(argv[0]);
3034 }
3035 else {
3036 buffer_type = RB_IO_BUFFER_DATA_TYPE_U8;
3037 }
3038
3039 size_t offset, count;
3040 io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count);
3041
3042 for (size_t i = 0; i < count; i++) {
3043 size_t current_offset = offset;
3044 VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset);
3045 rb_yield_values(2, SIZET2NUM(current_offset), value);
3046 }
3047
3048 return self;
3049}
3050
3051/*
3052 * call-seq:
3053 * each(buffer_type, [offset, [count]]) {|offset, value| ...} -> self
3054 * each(buffer_type, [offset, [count]]) -> enumerator
3055 *
3056 * Iterates over the buffer, yielding each +value+ of +buffer_type+ starting
3057 * from +offset+.
3058 *
3059 * If +count+ is given, only +count+ values will be yielded.
3060 *
3061 * IO::Buffer.for("Hello World").each(:U8, 2, 2) do |offset, value|
3062 * puts "#{offset}: #{value}"
3063 * end
3064 * # 2: 108
3065 * # 3: 108
3066 */
3067static VALUE
3068io_buffer_each(int argc, VALUE *argv, VALUE self)
3069{
3070 RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);
3071
3072 struct io_buffer_each_arguments arguments = {
3073 .self = self,
3074 .argc = argc,
3075 .argv = argv,
3076 };
3077
3078 rb_io_buffer_lock(self);
3079 return rb_ensure(io_buffer_each_locked, (VALUE)&arguments, rb_io_buffer_locked_ensure, self);
3080}
3081
3082/*
3083 * call-seq: values(buffer_type, [offset, [count]]) -> array
3084 *
3085 * Returns an array of values of +buffer_type+ starting from +offset+.
3086 *
3087 * If +count+ is given, only +count+ values will be returned.
3088 *
3089 * IO::Buffer.for("Hello World").values(:U8, 2, 2)
3090 * # => [108, 108]
3091 */
3092static VALUE
3093io_buffer_values(int argc, VALUE *argv, VALUE self)
3094{
3095 const void *base;
3096 size_t size;
3097
3098 rb_io_buffer_get_bytes_for_reading(self, &base, &size);
3099
3100 ID buffer_type;
3101 if (argc >= 1) {
3102 buffer_type = TYPE_ID(argv[0]);
3103 }
3104 else {
3105 buffer_type = RB_IO_BUFFER_DATA_TYPE_U8;
3106 }
3107
3108 size_t offset, count;
3109 io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count);
3110
3111 VALUE array = rb_ary_new_capa(count);
3112
3113 for (size_t i = 0; i < count; i++) {
3114 VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset);
3115 rb_ary_push(array, value);
3116 }
3117
3118 return array;
3119}
3120
3121static VALUE
3122io_buffer_each_byte_locked(VALUE _arguments)
3123{
3124 struct io_buffer_each_arguments *arguments = (void *)_arguments;
3125 VALUE self = arguments->self;
3126 int argc = arguments->argc;
3127 VALUE *argv = arguments->argv;
3128
3129 const void *base;
3130 size_t size;
3131
3132 rb_io_buffer_get_bytes_for_reading(self, &base, &size);
3133
3134 size_t offset, count;
3135 io_buffer_extract_offset_count(RB_IO_BUFFER_DATA_TYPE_U8, size, argc, argv, &offset, &count);
3136
3137 if (size_sum_is_bigger_than(offset, count, size)) {
3138 rb_raise(rb_eArgError, "Specified offset+count is bigger than the buffer size!");
3139 }
3140
3141 for (size_t i = 0; i < count; i++) {
3142 unsigned char *value = (unsigned char *)base + i + offset;
3143 rb_yield(RB_INT2FIX(*value));
3144 }
3145
3146 return self;
3147}
3148
3149/*
3150 * call-seq:
3151 * each_byte([offset, [count]]) {|byte| ...} -> self
3152 * each_byte([offset, [count]]) -> enumerator
3153 *
3154 * Iterates over the buffer, yielding each byte starting from +offset+.
3155 *
3156 * If +count+ is given, only +count+ bytes will be yielded.
3157 *
3158 * IO::Buffer.for("Hello World").each_byte(2, 2) do |offset, byte|
3159 * puts "#{offset}: #{byte}"
3160 * end
3161 * # 2: 108
3162 * # 3: 108
3163 */
3164static VALUE
3165io_buffer_each_byte(int argc, VALUE *argv, VALUE self)
3166{
3167 RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);
3168
3169 struct io_buffer_each_arguments arguments = {
3170 .self = self,
3171 .argc = argc,
3172 .argv = argv,
3173 };
3174
3175 rb_io_buffer_lock(self);
3176 return rb_ensure(io_buffer_each_byte_locked, (VALUE)&arguments, rb_io_buffer_locked_ensure, self);
3177}
3178
3179static inline void
3180rb_io_buffer_set_value(struct rb_io_buffer_view *buffer, VALUE buffer_type, size_t *offset, VALUE value)
3181{
3182 ID type = TYPE_ID(buffer_type);
3183#define IO_BUFFER_SET_VALUE(name) if (type == RB_IO_BUFFER_DATA_TYPE_##name) {io_buffer_write_##name(buffer, offset, value); return;}
3184 IO_BUFFER_SET_VALUE(U8);
3185 IO_BUFFER_SET_VALUE(S8);
3186
3187 IO_BUFFER_SET_VALUE(u16);
3188 IO_BUFFER_SET_VALUE(U16);
3189 IO_BUFFER_SET_VALUE(s16);
3190 IO_BUFFER_SET_VALUE(S16);
3191
3192 IO_BUFFER_SET_VALUE(u32);
3193 IO_BUFFER_SET_VALUE(U32);
3194 IO_BUFFER_SET_VALUE(s32);
3195 IO_BUFFER_SET_VALUE(S32);
3196
3197 IO_BUFFER_SET_VALUE(u64);
3198 IO_BUFFER_SET_VALUE(U64);
3199 IO_BUFFER_SET_VALUE(s64);
3200 IO_BUFFER_SET_VALUE(S64);
3201
3202 IO_BUFFER_SET_VALUE(u128);
3203 IO_BUFFER_SET_VALUE(U128);
3204 IO_BUFFER_SET_VALUE(s128);
3205 IO_BUFFER_SET_VALUE(S128);
3206
3207 IO_BUFFER_SET_VALUE(f32);
3208 IO_BUFFER_SET_VALUE(F32);
3209 IO_BUFFER_SET_VALUE(f64);
3210 IO_BUFFER_SET_VALUE(F64);
3211#undef IO_BUFFER_SET_VALUE
3212
3213 rb_raise(rb_eArgError, "Invalid type name!");
3214}
3215
3217 struct rb_io_buffer_view *buffer;
3218 size_t offset;
3219 VALUE type, value;
3220};
3221
3222/*
3223 * call-seq: set_value(type, offset, value) -> offset
3224 *
3225 * Write to a buffer a +value+ of +type+ at +offset+. +type+ should be one of
3226 * symbols described in #get_value. Returns the offset just after the written
3227 * value.
3228 *
3229 * buffer = IO::Buffer.new(8)
3230 * # =>
3231 * # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
3232 * # 0x00000000 00 00 00 00 00 00 00 00
3233 *
3234 * buffer.set_value(:U8, 1, 111)
3235 * # => 2
3236 *
3237 * buffer
3238 * # =>
3239 * # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
3240 * # 0x00000000 00 6f 00 00 00 00 00 00 .o......
3241 *
3242 * Note that if the +type+ is integer and +value+ is Float, the implicit truncation is performed:
3243 *
3244 * buffer = IO::Buffer.new(8)
3245 * buffer.set_value(:U32, 0, 2.5)
3246 *
3247 * buffer
3248 * # =>
3249 * # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
3250 * # 0x00000000 00 00 00 02 00 00 00 00
3251 * # ^^ the same as if we'd pass just integer 2
3252 */
3253static VALUE
3254io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value)
3255{
3256 struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
3257 size_t offset = io_buffer_extract_offset(_offset);
3258 rb_io_buffer_set_value(buffer, type, &offset, value);
3259 return SIZET2NUM(offset);
3260}
3261
3262/*
3263 * call-seq: set_values(buffer_types, offset, values) -> offset
3264 *
3265 * Write +values+ of +buffer_types+ at +offset+ to the buffer. +buffer_types+
3266 * should be an array of symbols as described in #get_value. +values+ should
3267 * be an array of values to write. Returns the offset just after the last
3268 * written value.
3269 *
3270 * buffer = IO::Buffer.new(8)
3271 * buffer.set_values([:U8, :U16], 0, [1, 2])
3272 * # => 3
3273 * buffer
3274 * # =>
3275 * # #<IO::Buffer 0x696f717561746978+8 INTERNAL>
3276 * # 0x00000000 01 00 02 00 00 00 00 00 ........
3277 */
3278static VALUE
3279io_buffer_set_values(VALUE self, VALUE buffer_types, VALUE _offset, VALUE values)
3280{
3281 struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
3282
3283 if (!RB_TYPE_P(buffer_types, T_ARRAY)) {
3284 rb_raise(rb_eArgError, "Argument buffer_types should be an array!");
3285 }
3286
3287 size_t offset = io_buffer_extract_offset(_offset);
3288
3289 if (!RB_TYPE_P(values, T_ARRAY)) {
3290 rb_raise(rb_eArgError, "Argument values should be an array!");
3291 }
3292
3293 if (RARRAY_LEN(buffer_types) != RARRAY_LEN(values)) {
3294 rb_raise(rb_eArgError, "Argument buffer_types and values should have the same length!");
3295 }
3296
3297 for (long i = 0; i < RARRAY_LEN(buffer_types); i++) {
3298 VALUE type = rb_ary_entry(buffer_types, i);
3299 VALUE value = rb_ary_entry(values, i);
3300 rb_io_buffer_set_value(buffer, type, &offset, value);
3301 }
3302
3303 return SIZET2NUM(offset);
3304}
3305
3306static size_t IO_BUFFER_BLOCKING_SIZE = 1024*1024;
3307
3309 unsigned char * destination;
3310 const unsigned char * source;
3311 size_t length;
3312};
3313
3314static void *
3315io_buffer_memmove_blocking(void *data)
3316{
3317 struct io_buffer_memmove_arguments *arguments = (struct io_buffer_memmove_arguments *)data;
3318
3319 memmove(arguments->destination, arguments->source, arguments->length);
3320
3321 return NULL;
3322}
3323
3324static void
3325io_buffer_memmove_unblock(void *data)
3326{
3327 // No safe way to interrupt.
3328}
3329
3330static void
3331io_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)
3332{
3333 if (size_sum_is_bigger_than(offset, length, size)) {
3334 rb_raise(rb_eArgError, "Specified offset+length is bigger than the buffer size!");
3335 }
3336
3337 if (size_sum_is_bigger_than(source_offset, length, source_size)) {
3338 rb_raise(rb_eArgError, "The computed source range exceeds the size of the source buffer!");
3339 }
3340
3341 if (length == 0) return;
3342
3343 RUBY_ASSERT(base != NULL);
3344 RUBY_ASSERT(source_base != NULL);
3345 struct io_buffer_memmove_arguments arguments = {
3346 .destination = (unsigned char*)base+offset,
3347 .source = (unsigned char*)source_base+source_offset,
3348 .length = length
3349 };
3350
3351 if (arguments.length >= IO_BUFFER_BLOCKING_SIZE) {
3352 rb_nogvl(io_buffer_memmove_blocking, &arguments, io_buffer_memmove_unblock, &arguments, RB_NOGVL_OFFLOAD_SAFE);
3353 } else if (arguments.length != 0) {
3354 memmove(arguments.destination, arguments.source, arguments.length);
3355 }
3356}
3357
3358static void
3359io_buffer_extract_copy_arguments(size_t source_size, int argc, VALUE *argv, size_t *offset, size_t *length, size_t *source_offset)
3360{
3361 // The offset we copy into the buffer:
3362 if (argc >= 1) {
3363 *offset = io_buffer_extract_offset(argv[0]);
3364 }
3365 else {
3366 *offset = 0;
3367 }
3368
3369 // The offset we start from within the string:
3370 if (argc >= 3) {
3371 *source_offset = io_buffer_extract_offset(argv[2]);
3372
3373 if (*source_offset > source_size) {
3374 rb_raise(rb_eArgError, "The given source offset is bigger than the source itself!");
3375 }
3376 }
3377 else {
3378 *source_offset = 0;
3379 }
3380
3381 // The length we are going to copy:
3382 if (argc >= 2 && !RB_NIL_P(argv[1])) {
3383 *length = io_buffer_extract_length(argv[1]);
3384 }
3385 else {
3386 // Default to the source offset -> source size:
3387 *length = source_size - *source_offset;
3388 }
3389}
3390
3391// (offset, length, source_offset) -> length
3392static VALUE
3393io_buffer_copy_from(struct rb_io_buffer_view *buffer, const void *source_base, size_t source_size, int argc, VALUE *argv)
3394{
3395 size_t offset, length, source_offset;
3396 io_buffer_extract_copy_arguments(source_size, argc, argv, &offset, &length, &source_offset);
3397
3398 void *base;
3399 size_t size;
3400 io_buffer_get_bytes_for_writing(buffer, &base, &size);
3401
3402 if (length >= IO_BUFFER_BLOCKING_SIZE) {
3403 io_buffer_lock(buffer);
3404 }
3405
3406 io_buffer_memmove(base, size, offset, source_base, source_offset, source_size, length);
3407
3408 if (length >= IO_BUFFER_BLOCKING_SIZE) {
3409 io_buffer_unlock(buffer);
3410 }
3411
3412 return SIZET2NUM(length);
3413}
3414
3416 VALUE destination;
3417 const void *source_base;
3418 size_t source_size;
3419 int argc;
3420 VALUE *argv;
3421};
3422
3423// This is the innermost callback for IO::Buffer#copy. At this point the source
3424// is locked for reading and the destination is locked for writing, so both
3425// pointers and sizes remain valid while arguments are extracted, ranges are
3426// validated, and memmove potentially releases the GVL.
3427static VALUE
3428io_buffer_copy_to(void *base, size_t size, VALUE _arguments)
3429{
3430 struct io_buffer_copy_arguments *arguments = (void *)_arguments;
3431
3432 size_t offset, length, source_offset;
3433 io_buffer_extract_copy_arguments(arguments->source_size, arguments->argc, arguments->argv, &offset, &length, &source_offset);
3434
3435 io_buffer_memmove(base, size, offset, arguments->source_base, source_offset, arguments->source_size, length);
3436
3437 return SIZET2NUM(length);
3438}
3439
3440// This callback runs while the source is locked for reading. Retain its bytes
3441// in the callback arguments, then enter the destination's writable scope. The
3442// source scope remains active until that nested scope returns.
3443static VALUE
3444io_buffer_copy_from_readable(const void *base, size_t size, VALUE _arguments)
3445{
3446 struct io_buffer_copy_arguments *arguments = (void *)_arguments;
3447
3448 arguments->source_base = base;
3449 arguments->source_size = size;
3450
3451 return rb_io_buffer_locked_for_writing(arguments->destination, io_buffer_copy_to, _arguments);
3452}
3453
3454static VALUE
3455io_buffer_initialize_copy_from(const void *base, size_t size, VALUE self)
3456{
3457 struct rb_io_buffer_storage *buffer = get_io_buffer_storage(self);
3458 if (buffer->lock_count) rb_raise(rb_eIOBufferLockedError, "Cannot initialize locked buffer!");
3459 io_buffer_storage_release(buffer);
3460 io_buffer_storage_initialize(self, buffer, NULL, size, io_flags_for_size(size), Qnil);
3461
3462 struct io_buffer_copy_arguments arguments = {
3463 .destination = self,
3464 .source_base = base,
3465 .source_size = size,
3466 .argc = 0,
3467 .argv = NULL,
3468 };
3469
3470 // The source remains locked by the outer readable scope while the newly
3471 // initialized destination is locked and populated by io_buffer_copy_to.
3472 return rb_io_buffer_locked_for_writing(self, io_buffer_copy_to, (VALUE)&arguments);
3473}
3474
3475/*
3476 * call-seq:
3477 * dup -> io_buffer
3478 * clone -> io_buffer
3479 *
3480 * Make an internal copy of the source buffer. Updates to the copy will not
3481 * affect the source buffer.
3482 *
3483 * source = IO::Buffer.for("Hello World")
3484 * # =>
3485 * # #<IO::Buffer 0x00007fd598466830+11 EXTERNAL READONLY SLICE>
3486 * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
3487 * buffer = source.dup
3488 * # =>
3489 * # #<IO::Buffer 0x0000558cbec03320+11 INTERNAL>
3490 * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
3491 */
3492static VALUE
3493rb_io_buffer_initialize_copy(VALUE self, VALUE source)
3494{
3495 if (self == source) return self;
3496 rb_check_frozen(self);
3497 return rb_io_buffer_locked_for_reading(source, io_buffer_initialize_copy_from, self);
3498}
3499
3500/*
3501 * call-seq:
3502 * copy(source, [offset, [length, [source_offset]]]) -> size
3503 *
3504 * Efficiently copy from a source IO::Buffer into the buffer, at +offset+
3505 * using +memmove+. For copying String instances, see #set_string.
3506 *
3507 * buffer = IO::Buffer.new(32)
3508 * # =>
3509 * # #<IO::Buffer 0x0000555f5ca22520+32 INTERNAL>
3510 * # 0x00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
3511 * # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ *
3512 *
3513 * buffer.copy(IO::Buffer.for("test"), 8)
3514 * # => 4 -- size of buffer copied
3515 * buffer
3516 * # =>
3517 * # #<IO::Buffer 0x0000555f5cf8fe40+32 INTERNAL>
3518 * # 0x00000000 00 00 00 00 00 00 00 00 74 65 73 74 00 00 00 00 ........test....
3519 * # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ *
3520 *
3521 * #copy can be used to put buffer into strings associated with buffer:
3522 *
3523 * string = "data: "
3524 * # => "data: "
3525 * buffer = IO::Buffer.for(string) do |buffer|
3526 * buffer.copy(IO::Buffer.for("test"), 5)
3527 * end
3528 * # => 4
3529 * string
3530 * # => "data:test"
3531 *
3532 * Attempt to copy into a read-only buffer will fail:
3533 *
3534 * File.write('test.txt', 'test')
3535 * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
3536 * buffer.copy(IO::Buffer.for("test"), 8)
3537 * # in `copy': Buffer is not writable! (IO::Buffer::AccessError)
3538 *
3539 * See ::map for details of creation of mutable file mappings, this will
3540 * work:
3541 *
3542 * buffer = IO::Buffer.map(File.open('test.txt', 'r+'))
3543 * buffer.copy(IO::Buffer.for("boom"), 0)
3544 * # => 4
3545 * File.read('test.txt')
3546 * # => "boom"
3547 *
3548 * Attempt to copy the buffer which will need place outside of buffer's
3549 * bounds will fail:
3550 *
3551 * buffer = IO::Buffer.new(2)
3552 * buffer.copy(IO::Buffer.for('test'), 0)
3553 * # in `copy': Specified offset+length is bigger than the buffer size! (ArgumentError)
3554 *
3555 * It is safe to copy between memory regions that overlaps each other.
3556 * In such case, the data is copied as if the data was first copied from the source buffer to
3557 * a temporary buffer, and then copied from the temporary buffer to the destination buffer.
3558 *
3559 * buffer = IO::Buffer.new(10)
3560 * buffer.set_string("0123456789")
3561 * buffer.copy(buffer, 3, 7)
3562 * # => 7
3563 * buffer
3564 * # =>
3565 * # #<IO::Buffer 0x000056494f8ce440+10 INTERNAL>
3566 * # 0x00000000 30 31 32 30 31 32 33 34 35 36 0120123456
3567 */
3568static VALUE
3569io_buffer_copy(int argc, VALUE *argv, VALUE self)
3570{
3571 rb_check_arity(argc, 1, 4);
3572
3573 VALUE source = argv[0];
3574 struct io_buffer_copy_arguments arguments = {
3575 .destination = self,
3576 .argc = argc-1,
3577 .argv = argv+1,
3578 };
3579
3580 // Lock the source first, then io_buffer_copy_from_readable nests the
3581 // destination lock. The scoped helpers use rb_ensure, so the destination
3582 // is unlocked before the source on both normal and exceptional returns.
3583 // If both buffers share an allocation, its reference-counted lock is
3584 // acquired and released twice.
3585 return rb_io_buffer_locked_for_reading(source, io_buffer_copy_from_readable, (VALUE)&arguments);
3586}
3587
3589 size_t offset;
3590 size_t length;
3591 rb_encoding *encoding;
3592};
3593
3594static VALUE
3595io_buffer_get_string_locked(const void *base, size_t size, VALUE _arguments)
3596{
3597 struct io_buffer_get_string_arguments *arguments = (void *)_arguments;
3598 if (size_sum_is_bigger_than(arguments->offset, arguments->length, size)) {
3599 rb_raise(rb_eArgError, "Specified offset+length is bigger than the buffer size!");
3600 }
3601 const char *data = base ? (const char *)base + arguments->offset : NULL;
3602 return rb_enc_str_new(data, arguments->length, arguments->encoding);
3603}
3604
3605/*
3606 * call-seq: get_string([offset, [length, [encoding]]]) -> string
3607 *
3608 * Read a chunk or all of the buffer into a string, in the specified
3609 * +encoding+. If no encoding is provided +Encoding::BINARY+ is used.
3610 *
3611 * buffer = IO::Buffer.for('test')
3612 * buffer.get_string
3613 * # => "test"
3614 * buffer.get_string(2)
3615 * # => "st"
3616 * buffer.get_string(2, 1)
3617 * # => "s"
3618 */
3619static VALUE
3620io_buffer_get_string(int argc, VALUE *argv, VALUE self)
3621{
3622 rb_check_arity(argc, 0, 3);
3623
3624 struct io_buffer_get_string_arguments arguments;
3625 io_buffer_extract_offset_length(self, argc, argv, &arguments.offset, &arguments.length);
3626
3627 // Encoding coercion may invoke Ruby and change the source. Do it before
3628 // locking and resolving the current view; retain no pointer across it.
3629 arguments.encoding = argc >= 3 ? rb_find_encoding(argv[2]) : rb_ascii8bit_encoding();
3630 return rb_io_buffer_locked_for_reading(self, io_buffer_get_string_locked, (VALUE)&arguments);
3631}
3632
3633/*
3634 * call-seq: set_string(string, [offset, [length, [source_offset]]]) -> size
3635 *
3636 * Efficiently copy from a source String into the buffer, at +offset+ using
3637 * +memmove+.
3638 *
3639 * buf = IO::Buffer.new(8)
3640 * # =>
3641 * # #<IO::Buffer 0x0000557412714a20+8 INTERNAL>
3642 * # 0x00000000 00 00 00 00 00 00 00 00 ........
3643 *
3644 * # set buffer starting from offset 1, take 2 bytes starting from string's
3645 * # second
3646 * buf.set_string('test', 1, 2, 1)
3647 * # => 2
3648 * buf
3649 * # =>
3650 * # #<IO::Buffer 0x0000557412714a20+8 INTERNAL>
3651 * # 0x00000000 00 65 73 00 00 00 00 00 .es.....
3652 *
3653 * See also #copy for examples of how buffer writing might be used for changing
3654 * associated strings and files.
3655 */
3656static VALUE
3657io_buffer_set_string(int argc, VALUE *argv, VALUE self)
3658{
3659 rb_check_arity(argc, 1, 4);
3660
3661 struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
3662
3663 VALUE string = rb_str_to_str(argv[0]);
3664
3665 const void *source_base = RSTRING_PTR(string);
3666 size_t source_size = RSTRING_LEN(string);
3667
3668 VALUE result = io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
3669 RB_GC_GUARD(string);
3670 return result;
3671}
3672
3673void
3674rb_io_buffer_clear(VALUE self, uint8_t value, size_t offset, size_t length)
3675{
3676 struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
3677
3678 void *base;
3679 size_t size;
3680 io_buffer_get_bytes_for_writing(buffer, &base, &size);
3681
3682 io_buffer_validate_range(buffer, offset, length);
3683
3684 if (length == 0) return;
3685
3686 RUBY_ASSERT(base != NULL);
3687 memset((char*)base + offset, value, length);
3688}
3689
3690/*
3691 * call-seq: clear(value = 0, [offset, [length]]) -> self
3692 *
3693 * Fill buffer with +value+, starting with +offset+ and going for +length+
3694 * bytes.
3695 *
3696 * buffer = IO::Buffer.for('test').dup
3697 * # =>
3698 * # <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
3699 * # 0x00000000 74 65 73 74 test
3700 *
3701 * buffer.clear
3702 * # =>
3703 * # <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
3704 * # 0x00000000 00 00 00 00 ....
3705 *
3706 * buf.clear(1) # fill with 1
3707 * # =>
3708 * # <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
3709 * # 0x00000000 01 01 01 01 ....
3710 *
3711 * buffer.clear(2, 1, 2) # fill with 2, starting from offset 1, for 2 bytes
3712 * # =>
3713 * # <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
3714 * # 0x00000000 01 02 02 01 ....
3715 *
3716 * buffer.clear(2, 1) # fill with 2, starting from offset 1
3717 * # =>
3718 * # <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
3719 * # 0x00000000 01 02 02 02 ....
3720 */
3721static VALUE
3722io_buffer_clear(int argc, VALUE *argv, VALUE self)
3723{
3724 rb_check_arity(argc, 0, 3);
3725
3726 uint8_t value = 0;
3727 if (argc >= 1) {
3728 value = NUM2UINT(argv[0]);
3729 }
3730
3731 size_t offset, length;
3732 io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);
3733
3734 rb_io_buffer_clear(self, value, offset, length);
3735
3736 return self;
3737}
3738
3739static size_t
3740io_buffer_default_size(size_t page_size)
3741{
3742 // Platform agnostic default size, based on empirical performance observation:
3743 const size_t platform_agnostic_default_size = 64*1024;
3744
3745 // Allow user to specify custom default buffer size:
3746 const char *default_size = getenv("RUBY_IO_BUFFER_DEFAULT_SIZE");
3747 if (default_size) {
3748 // For the purpose of setting a default size, 2^31 is an acceptable maximum:
3749 int value = atoi(default_size);
3750
3751 // assuming sizeof(int) <= sizeof(size_t)
3752 if (value > 0) {
3753 return value;
3754 }
3755 }
3756
3757 if (platform_agnostic_default_size < page_size) {
3758 return page_size;
3759 }
3760
3761 return platform_agnostic_default_size;
3762}
3763
3765 struct rb_io *io;
3766 struct rb_io_buffer_view *buffer;
3767 rb_blocking_function_t *function;
3768 void *data;
3769};
3770
3771static VALUE
3772io_buffer_blocking_region_begin(VALUE _argument)
3773{
3774 struct io_buffer_blocking_region_argument *argument = (void*)_argument;
3775
3776 return rb_io_blocking_region(argument->io, argument->function, argument->data);
3777}
3778
3779static VALUE
3780io_buffer_blocking_region_ensure(VALUE _argument)
3781{
3782 struct io_buffer_blocking_region_argument *argument = (void*)_argument;
3783
3784 io_buffer_unlock(argument->buffer);
3785
3786 return Qnil;
3787}
3788
3789static VALUE
3790io_buffer_blocking_region(VALUE io, struct rb_io_buffer_view *buffer, rb_blocking_function_t *function, void *data)
3791{
3792 struct rb_io *ioptr;
3793 RB_IO_POINTER(io, ioptr);
3794
3795 struct io_buffer_blocking_region_argument argument = {
3796 .io = ioptr,
3797 .buffer = buffer,
3798 .function = function,
3799 .data = data,
3800 };
3801
3802 // The buffer should be locked for the duration of the blocking region. We
3803 // always acquire our own reference so another operation cannot release the
3804 // allocation while this operation is still using it:
3805 io_buffer_lock(buffer);
3806
3807 return rb_ensure(io_buffer_blocking_region_begin, (VALUE)&argument, io_buffer_blocking_region_ensure, (VALUE)&argument);
3808}
3809
3811 // The file descriptor to read from:
3812 int descriptor;
3813 // The base pointer to read into:
3814 char *base;
3815 // The maximum number of bytes to read:
3816 size_t length;
3817};
3818
3819static VALUE
3820io_buffer_read_internal(void *_argument)
3821{
3822 struct io_buffer_read_internal_argument *argument = _argument;
3823 ssize_t result = read(argument->descriptor, argument->base, argument->length);
3824
3825 return rb_fiber_scheduler_io_result(result, errno);
3826}
3827
3828VALUE
3829rb_io_buffer_read(VALUE self, VALUE io, size_t offset, size_t length)
3830{
3831 io = rb_io_get_io(io);
3832 struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
3833
3834 io_buffer_validate_range(buffer, offset, length);
3835
3836 if (length == 0) return SIZET2NUM(0);
3837
3838 VALUE scheduler = rb_fiber_scheduler_current();
3839 if (scheduler != Qnil) {
3840 VALUE result = rb_fiber_scheduler_io_read(scheduler, io, self, offset, length);
3841
3842 if (!UNDEF_P(result)) {
3843 return result;
3844 }
3845
3846 // The scheduler capability check can invoke Ruby and change the view.
3847 buffer = get_io_buffer_view_for_writing(self);
3848 io_buffer_validate_range(buffer, offset, length);
3849 }
3850
3851 void *base;
3852 size_t size;
3853 io_buffer_get_bytes_for_writing(buffer, &base, &size);
3854
3855 RUBY_ASSERT(base != NULL);
3856
3857 struct io_buffer_read_internal_argument argument = {
3858 .descriptor = rb_io_descriptor(io),
3859 .base = (char*)base + offset,
3860 .length = length,
3861 };
3862
3863 return io_buffer_blocking_region(io, buffer, io_buffer_read_internal, &argument);
3864}
3865
3866/*
3867 * call-seq: read(io, [offset, [length]]) -> read length or -errno
3868 *
3869 * Perform one read operation of at most +length+ bytes from +io+ into the
3870 * buffer starting at +offset+. A short read is a normal result. If an error
3871 * occurs, return <tt>-errno</tt>.
3872 *
3873 * If +offset+ is not given, it defaults to zero, i.e. the beginning of the
3874 * buffer. If +length+ is not given, it defaults to the size of the buffer
3875 * minus the offset. A zero length is a no-op.
3876 *
3877 * IO::Buffer.for('test') do |buffer|
3878 * p buffer
3879 * # =>
3880 * # <IO::Buffer 0x00007fca40087c38+4 SLICE>
3881 * # 0x00000000 74 65 73 74 test
3882 * buffer.read(File.open('/dev/urandom', 'rb'), 0, 2)
3883 * p buffer
3884 * # =>
3885 * # <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE>
3886 * # 0x00000000 05 35 73 74 .5st
3887 * end
3888 */
3889static VALUE
3890io_buffer_read(int argc, VALUE *argv, VALUE self)
3891{
3892 rb_check_arity(argc, 1, 3);
3893
3894 VALUE io = argv[0];
3895
3896 size_t offset, length;
3897 io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);
3898
3899 return rb_io_buffer_read(self, io, offset, length);
3900}
3901
3903 // The file descriptor to read from:
3904 int descriptor;
3905 // The base pointer to read into:
3906 char *base;
3907 // The maximum number of bytes to read:
3908 size_t length;
3909 // The position to read from:
3910 off_t from;
3911};
3912
3913static VALUE
3914io_buffer_pread_internal(void *_argument)
3915{
3916 struct io_buffer_pread_internal_argument *argument = _argument;
3917 ssize_t result = pread(argument->descriptor, argument->base, argument->length, argument->from);
3918
3919 return rb_fiber_scheduler_io_result(result, errno);
3920}
3921
3922VALUE
3923rb_io_buffer_pread(VALUE self, VALUE io, rb_off_t from, size_t offset, size_t length)
3924{
3925 io = rb_io_get_io(io);
3926 struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
3927
3928 io_buffer_validate_range(buffer, offset, length);
3929
3930 if (length == 0) return SIZET2NUM(0);
3931
3932 VALUE scheduler = rb_fiber_scheduler_current();
3933 if (scheduler != Qnil) {
3934 VALUE result = rb_fiber_scheduler_io_pread(scheduler, io, from, self, offset, length);
3935
3936 if (!UNDEF_P(result)) {
3937 return result;
3938 }
3939
3940 buffer = get_io_buffer_view_for_writing(self);
3941 io_buffer_validate_range(buffer, offset, length);
3942 }
3943
3944 void *base;
3945 size_t size;
3946 io_buffer_get_bytes_for_writing(buffer, &base, &size);
3947
3948 RUBY_ASSERT(base != NULL);
3949
3950 struct io_buffer_pread_internal_argument argument = {
3951 .descriptor = rb_io_descriptor(io),
3952 .base = (char*)base + offset,
3953 .length = length,
3954 .from = from,
3955 };
3956
3957 return io_buffer_blocking_region(io, buffer, io_buffer_pread_internal, &argument);
3958}
3959
3960/*
3961 * call-seq: pread(io, from, [offset, [length]]) -> read length or -errno
3962 *
3963 * Perform one read operation of at most +length+ bytes from +io+ at +from+
3964 * into the buffer starting at +offset+. A short read is a normal result and
3965 * the IO's current position is not modified. If an error occurs, return
3966 * <tt>-errno</tt>.
3967 *
3968 * If +offset+ is not given, it defaults to zero, i.e. the beginning of the
3969 * buffer. If +length+ is not given, it defaults to the size of the buffer
3970 * minus the offset. A zero length is a no-op.
3971 *
3972 * IO::Buffer.for('test') do |buffer|
3973 * p buffer
3974 * # =>
3975 * # <IO::Buffer 0x00007fca40087c38+4 SLICE>
3976 * # 0x00000000 74 65 73 74 test
3977 *
3978 * # take 2 bytes from the beginning of urandom,
3979 * # put them in buffer starting from position 2
3980 * buffer.pread(File.open('/dev/urandom', 'rb'), 0, 2, 2)
3981 * p buffer
3982 * # =>
3983 * # <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE>
3984 * # 0x00000000 05 35 73 74 te.5
3985 * end
3986 */
3987static VALUE
3988io_buffer_pread(int argc, VALUE *argv, VALUE self)
3989{
3990 rb_check_arity(argc, 2, 4);
3991
3992 VALUE io = argv[0];
3993 rb_off_t from = NUM2OFFT(argv[1]);
3994
3995 size_t offset, length;
3996 io_buffer_extract_offset_length(self, argc-2, argv+2, &offset, &length);
3997
3998 return rb_io_buffer_pread(self, io, from, offset, length);
3999}
4000
4002 // The file descriptor to write to:
4003 int descriptor;
4004 // The base pointer to write from:
4005 const char *base;
4006 // The maximum number of bytes to write:
4007 size_t length;
4008};
4009
4010static VALUE
4011io_buffer_write_internal(void *_argument)
4012{
4013 struct io_buffer_write_internal_argument *argument = _argument;
4014 ssize_t result = write(argument->descriptor, argument->base, argument->length);
4015
4016 return rb_fiber_scheduler_io_result(result, errno);
4017}
4018
4019VALUE
4020rb_io_buffer_write(VALUE self, VALUE io, size_t offset, size_t length)
4021{
4023
4024 struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
4025 io_buffer_validate_range(buffer, offset, length);
4026
4027 if (length == 0) return SIZET2NUM(0);
4028
4029 VALUE scheduler = rb_fiber_scheduler_current();
4030 if (scheduler != Qnil) {
4031 VALUE result = rb_fiber_scheduler_io_write(scheduler, io, self, offset, length);
4032
4033 if (!UNDEF_P(result)) {
4034 return result;
4035 }
4036
4037 buffer = get_io_buffer_view(self);
4038 io_buffer_validate_range(buffer, offset, length);
4039 }
4040
4041 const void *base;
4042 size_t size;
4043 io_buffer_get_bytes_for_reading(buffer, &base, &size);
4044
4045 RUBY_ASSERT(base != NULL);
4046
4047 struct io_buffer_write_internal_argument argument = {
4048 .descriptor = rb_io_descriptor(io),
4049 .base = (const char*)base + offset,
4050 .length = length,
4051 };
4052
4053 return io_buffer_blocking_region(io, buffer, io_buffer_write_internal, &argument);
4054}
4055
4056/*
4057 * call-seq: write(io, [offset, [length]]) -> written length or -errno
4058 *
4059 * Perform one write operation of at most +length+ bytes to +io+ from the
4060 * buffer starting at +offset+. A short write is a normal result. If an error
4061 * occurs, return <tt>-errno</tt>.
4062 *
4063 * If +offset+ is not given, it defaults to zero, i.e. the beginning of the
4064 * buffer. If +length+ is not given, it defaults to the size of the buffer
4065 * minus the offset. A zero length is a no-op.
4066 *
4067 * out = File.open('output.txt', 'wb')
4068 * IO::Buffer.for('1234567').write(out, 0, 3)
4069 *
4070 * This leads to +123+ being written into <tt>output.txt</tt>
4071 */
4072static VALUE
4073io_buffer_write(int argc, VALUE *argv, VALUE self)
4074{
4075 rb_check_arity(argc, 1, 3);
4076
4077 VALUE io = argv[0];
4078
4079 size_t offset, length;
4080 io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);
4081
4082 return rb_io_buffer_write(self, io, offset, length);
4083}
4084
4086 // The file descriptor to write to:
4087 int descriptor;
4088 // The base pointer to write from:
4089 const char *base;
4090 // The maximum number of bytes to write:
4091 size_t length;
4092 // The position to write to:
4093 off_t from;
4094};
4095
4096static VALUE
4097io_buffer_pwrite_internal(void *_argument)
4098{
4099 struct io_buffer_pwrite_internal_argument *argument = _argument;
4100 ssize_t result = pwrite(argument->descriptor, argument->base, argument->length, argument->from);
4101
4102 return rb_fiber_scheduler_io_result(result, errno);
4103}
4104
4105VALUE
4106rb_io_buffer_pwrite(VALUE self, VALUE io, rb_off_t from, size_t offset, size_t length)
4107{
4109
4110 struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
4111 io_buffer_validate_range(buffer, offset, length);
4112
4113 if (length == 0) return SIZET2NUM(0);
4114
4115 VALUE scheduler = rb_fiber_scheduler_current();
4116 if (scheduler != Qnil) {
4117 VALUE result = rb_fiber_scheduler_io_pwrite(scheduler, io, from, self, offset, length);
4118
4119 if (!UNDEF_P(result)) {
4120 return result;
4121 }
4122
4123 buffer = get_io_buffer_view(self);
4124 io_buffer_validate_range(buffer, offset, length);
4125 }
4126
4127 const void *base;
4128 size_t size;
4129 io_buffer_get_bytes_for_reading(buffer, &base, &size);
4130
4131 RUBY_ASSERT(base != NULL);
4132
4133 struct io_buffer_pwrite_internal_argument argument = {
4134 .descriptor = rb_io_descriptor(io),
4135 .base = (const char*)base + offset,
4136 .length = length,
4137 .from = from,
4138 };
4139
4140 return io_buffer_blocking_region(io, buffer, io_buffer_pwrite_internal, &argument);
4141}
4142
4143/*
4144 * call-seq: pwrite(io, from, [offset, [length]]) -> written length or -errno
4145 *
4146 * Perform one write operation of at most +length+ bytes to +io+ at +from+
4147 * from the buffer starting at +offset+. A short write is a normal result and
4148 * the IO's current position is not modified. If an error occurs, return
4149 * <tt>-errno</tt>.
4150 *
4151 * If +offset+ is not given, it defaults to zero, i.e. the beginning of the
4152 * buffer. If +length+ is not given, it defaults to the size of the buffer
4153 * minus the offset. A zero length is a no-op.
4154 *
4155 * If the +from+ position is beyond the end of the file, the gap will be
4156 * filled with null (0 value) bytes.
4157 *
4158 * out = File.open('output.txt', File::RDWR) # open for read/write, no truncation
4159 * IO::Buffer.for('1234567').pwrite(out, 2, 1, 3)
4160 *
4161 * This leads to +234+ (3 bytes, starting from position 1) being written into
4162 * <tt>output.txt</tt>, starting from file position 2.
4163 */
4164static VALUE
4165io_buffer_pwrite(int argc, VALUE *argv, VALUE self)
4166{
4167 rb_check_arity(argc, 2, 4);
4168
4169 VALUE io = argv[0];
4170 rb_off_t from = NUM2OFFT(argv[1]);
4171
4172 size_t offset, length;
4173 io_buffer_extract_offset_length(self, argc-2, argv+2, &offset, &length);
4174
4175 return rb_io_buffer_pwrite(self, io, from, offset, length);
4176}
4177
4178static inline void
4179io_buffer_check_mask_size(size_t size)
4180{
4181 if (size == 0)
4182 rb_raise(rb_eIOBufferMaskError, "Zero-length mask given!");
4183}
4184
4185static void
4186memory_and(unsigned char * restrict output, const unsigned char * restrict base, size_t size, const unsigned char * restrict mask, size_t mask_size)
4187{
4188 for (size_t offset = 0; offset < size; offset += 1) {
4189 output[offset] = base[offset] & mask[offset % mask_size];
4190 }
4191}
4192
4193/*
4194 * call-seq:
4195 * source & mask -> io_buffer
4196 *
4197 * Generate a new buffer the same size as the source by applying the binary AND
4198 * operation to the source, using the mask, repeating as necessary.
4199 *
4200 * IO::Buffer.for("1234567890") & IO::Buffer.for("\xFF\x00\x00\xFF")
4201 * # =>
4202 * # #<IO::Buffer 0x00005589b2758480+10 INTERNAL>
4203 * # 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89.
4204 */
4205static VALUE
4206io_buffer_and(VALUE self, VALUE mask)
4207{
4208 struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
4209
4210 struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
4211
4212 const void *base;
4213 size_t size;
4214 io_buffer_get_bytes_for_reading(buffer, &base, &size);
4215
4216 const void *mask_base;
4217 size_t mask_size;
4218 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
4219
4220 io_buffer_check_mask_size(mask_size);
4221
4222 VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
4223 struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output);
4224
4225 memory_and(output_buffer->base, base, size, mask_base, mask_size);
4226
4227 return output;
4228}
4229
4230static void
4231memory_or(unsigned char * restrict output, const unsigned char * restrict base, size_t size, const unsigned char * restrict mask, size_t mask_size)
4232{
4233 for (size_t offset = 0; offset < size; offset += 1) {
4234 output[offset] = base[offset] | mask[offset % mask_size];
4235 }
4236}
4237
4238/*
4239 * call-seq:
4240 * source | mask -> io_buffer
4241 *
4242 * Generate a new buffer the same size as the source by applying the binary OR
4243 * operation to the source, using the mask, repeating as necessary.
4244 *
4245 * IO::Buffer.for("1234567890") | IO::Buffer.for("\xFF\x00\x00\xFF")
4246 * # =>
4247 * # #<IO::Buffer 0x0000561785ae3480+10 INTERNAL>
4248 * # 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0
4249 */
4250static VALUE
4251io_buffer_or(VALUE self, VALUE mask)
4252{
4253 struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
4254
4255 struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
4256
4257 const void *base;
4258 size_t size;
4259 io_buffer_get_bytes_for_reading(buffer, &base, &size);
4260
4261 const void *mask_base;
4262 size_t mask_size;
4263 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
4264
4265 io_buffer_check_mask_size(mask_size);
4266
4267 VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
4268 struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output);
4269
4270 memory_or(output_buffer->base, base, size, mask_base, mask_size);
4271
4272 return output;
4273}
4274
4275static void
4276memory_xor(unsigned char * restrict output, const unsigned char * restrict base, size_t size, const unsigned char * restrict mask, size_t mask_size)
4277{
4278 for (size_t offset = 0; offset < size; offset += 1) {
4279 output[offset] = base[offset] ^ mask[offset % mask_size];
4280 }
4281}
4282
4283/*
4284 * call-seq:
4285 * source ^ mask -> io_buffer
4286 *
4287 * Generate a new buffer the same size as the source by applying the binary XOR
4288 * operation to the source, using the mask, repeating as necessary.
4289 *
4290 * IO::Buffer.for("1234567890") ^ IO::Buffer.for("\xFF\x00\x00\xFF")
4291 * # =>
4292 * # #<IO::Buffer 0x000055a2d5d10480+10 INTERNAL>
4293 * # 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0
4294 */
4295static VALUE
4296io_buffer_xor(VALUE self, VALUE mask)
4297{
4298 struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
4299
4300 struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
4301
4302 const void *base;
4303 size_t size;
4304 io_buffer_get_bytes_for_reading(buffer, &base, &size);
4305
4306 const void *mask_base;
4307 size_t mask_size;
4308 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
4309
4310 io_buffer_check_mask_size(mask_size);
4311
4312 VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
4313 struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output);
4314
4315 memory_xor(output_buffer->base, base, size, mask_base, mask_size);
4316
4317 return output;
4318}
4319
4320static void
4321memory_not(unsigned char * restrict output, const unsigned char * restrict base, size_t size)
4322{
4323 for (size_t offset = 0; offset < size; offset += 1) {
4324 output[offset] = ~base[offset];
4325 }
4326}
4327
4328/*
4329 * call-seq:
4330 * ~source -> io_buffer
4331 *
4332 * Generate a new buffer the same size as the source by applying the unary NOT
4333 * operation to the source.
4334 *
4335 * ~IO::Buffer.for("1234567890")
4336 * # =>
4337 * # #<IO::Buffer 0x000055a5ac42f120+10 INTERNAL>
4338 * # 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf ..........
4339 */
4340static VALUE
4341io_buffer_not(VALUE self)
4342{
4343 struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
4344
4345 const void *base;
4346 size_t size;
4347 io_buffer_get_bytes_for_reading(buffer, &base, &size);
4348
4349 VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
4350 struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output);
4351
4352 memory_not(output_buffer->base, base, size);
4353
4354 return output;
4355}
4356
4357static inline int
4358io_buffer_overlaps(const struct rb_io_buffer_view *a, const struct rb_io_buffer_view *b)
4359{
4360 // Resolve the current base pointers (following slice indirection):
4361 void *a_base = NULL, *b_base = NULL;
4362 size_t a_size = 0, b_size = 0;
4363 if (!io_buffer_try_get_bytes(a, &a_base, &a_size)) return 0;
4364 if (!io_buffer_try_get_bytes(b, &b_base, &b_size)) return 0;
4365
4366 if (a_size == 0 || b_size == 0 || a_base == NULL || b_base == NULL) return 0;
4367
4368 // Compare integer address differences, without ordering unrelated C
4369 // pointers or constructing end addresses that could overflow.
4370 uintptr_t a_start = (uintptr_t)a_base;
4371 uintptr_t b_start = (uintptr_t)b_base;
4372 if (a_start <= b_start) return b_start - a_start < a_size;
4373 return a_start - b_start < b_size;
4374}
4375
4376static inline void
4377io_buffer_check_overlaps(const struct rb_io_buffer_view *a, const struct rb_io_buffer_view *b)
4378{
4379 if (io_buffer_overlaps(a, b))
4380 rb_raise(rb_eIOBufferMaskError, "Mask overlaps source buffer!");
4381}
4382
4383static void
4384memory_and_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size)
4385{
4386 for (size_t offset = 0; offset < size; offset += 1) {
4387 base[offset] &= mask[offset % mask_size];
4388 }
4389}
4390
4391/*
4392 * call-seq:
4393 * source.and!(mask) -> io_buffer
4394 *
4395 * Modify the source buffer in place by applying the binary AND
4396 * operation to the source, using the mask, repeating as necessary.
4397 *
4398 * source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
4399 * # =>
4400 * # #<IO::Buffer 0x000056307a0d0c20+10 INTERNAL>
4401 * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
4402 *
4403 * source.and!(IO::Buffer.for("\xFF\x00\x00\xFF"))
4404 * # =>
4405 * # #<IO::Buffer 0x000056307a0d0c20+10 INTERNAL>
4406 * # 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89.
4407 */
4408static VALUE
4409io_buffer_and_inplace(VALUE self, VALUE mask)
4410{
4411 struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
4412
4413 struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
4414
4415 io_buffer_check_mask_size(mask_buffer->size);
4416 io_buffer_check_overlaps(buffer, mask_buffer);
4417
4418 void *base;
4419 size_t size;
4420 io_buffer_get_bytes_for_writing(buffer, &base, &size);
4421
4422 const void *mask_base;
4423 size_t mask_size;
4424 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
4425
4426 memory_and_inplace(base, size, (unsigned char *)mask_base, mask_size);
4427
4428 return self;
4429}
4430
4431static void
4432memory_or_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size)
4433{
4434 for (size_t offset = 0; offset < size; offset += 1) {
4435 base[offset] |= mask[offset % mask_size];
4436 }
4437}
4438
4439/*
4440 * call-seq:
4441 * source.or!(mask) -> io_buffer
4442 *
4443 * Modify the source buffer in place by applying the binary OR
4444 * operation to the source, using the mask, repeating as necessary.
4445 *
4446 * source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
4447 * # =>
4448 * # #<IO::Buffer 0x000056307a272350+10 INTERNAL>
4449 * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
4450 *
4451 * source.or!(IO::Buffer.for("\xFF\x00\x00\xFF"))
4452 * # =>
4453 * # #<IO::Buffer 0x000056307a272350+10 INTERNAL>
4454 * # 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0
4455 */
4456static VALUE
4457io_buffer_or_inplace(VALUE self, VALUE mask)
4458{
4459 struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
4460
4461 struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
4462
4463 io_buffer_check_mask_size(mask_buffer->size);
4464 io_buffer_check_overlaps(buffer, mask_buffer);
4465
4466 void *base;
4467 size_t size;
4468 io_buffer_get_bytes_for_writing(buffer, &base, &size);
4469
4470 const void *mask_base;
4471 size_t mask_size;
4472 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
4473
4474 memory_or_inplace(base, size, (unsigned char *)mask_base, mask_size);
4475
4476 return self;
4477}
4478
4479static void
4480memory_xor_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size)
4481{
4482 for (size_t offset = 0; offset < size; offset += 1) {
4483 base[offset] ^= mask[offset % mask_size];
4484 }
4485}
4486
4487/*
4488 * call-seq:
4489 * source.xor!(mask) -> io_buffer
4490 *
4491 * Modify the source buffer in place by applying the binary XOR
4492 * operation to the source, using the mask, repeating as necessary.
4493 *
4494 * source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
4495 * # =>
4496 * # #<IO::Buffer 0x000056307a25b3e0+10 INTERNAL>
4497 * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
4498 *
4499 * source.xor!(IO::Buffer.for("\xFF\x00\x00\xFF"))
4500 * # =>
4501 * # #<IO::Buffer 0x000056307a25b3e0+10 INTERNAL>
4502 * # 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0
4503 */
4504static VALUE
4505io_buffer_xor_inplace(VALUE self, VALUE mask)
4506{
4507 struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
4508
4509 struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
4510
4511 io_buffer_check_mask_size(mask_buffer->size);
4512 io_buffer_check_overlaps(buffer, mask_buffer);
4513
4514 void *base;
4515 size_t size;
4516 io_buffer_get_bytes_for_writing(buffer, &base, &size);
4517
4518 const void *mask_base;
4519 size_t mask_size;
4520 io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
4521
4522 memory_xor_inplace(base, size, (unsigned char *)mask_base, mask_size);
4523
4524 return self;
4525}
4526
4527static void
4528memory_not_inplace(unsigned char * restrict base, size_t size)
4529{
4530 for (size_t offset = 0; offset < size; offset += 1) {
4531 base[offset] = ~base[offset];
4532 }
4533}
4534
4535/*
4536 * call-seq:
4537 * source.not! -> io_buffer
4538 *
4539 * Modify the source buffer in place by applying the unary NOT
4540 * operation to the source.
4541 *
4542 * source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
4543 * # =>
4544 * # #<IO::Buffer 0x000056307a33a450+10 INTERNAL>
4545 * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
4546 *
4547 * source.not!
4548 * # =>
4549 * # #<IO::Buffer 0x000056307a33a450+10 INTERNAL>
4550 * # 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf ..........
4551 */
4552static VALUE
4553io_buffer_not_inplace(VALUE self)
4554{
4555 struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
4556
4557 void *base;
4558 size_t size;
4559 io_buffer_get_bytes_for_writing(buffer, &base, &size);
4560
4561 memory_not_inplace(base, size);
4562
4563 return self;
4564}
4565
4566static size_t
4567memory_bit_count(const unsigned char *base, size_t size)
4568{
4569 size_t count = 0;
4570
4571 // Process 8 bytes at a time for efficiency:
4572 const uint64_t *base64 = (const uint64_t *)base;
4573 size_t count64 = size / 8;
4574 for (size_t i = 0; i < count64; i += 1) {
4575 count += rb_popcount64(base64[i]);
4576 }
4577
4578 // Process any remaining bytes:
4579 size_t remaining = size % 8;
4580 const unsigned char *tail = base + (count64 * 8);
4581 for (size_t i = 0; i < remaining; i += 1) {
4582 count += rb_popcount32(tail[i]);
4583 }
4584
4585 return count;
4586}
4587
4588/*
4589 * call-seq: bit_count([offset, [length]]) -> integer
4590 *
4591 * Returns the number of set bits (1s) in the buffer, also known as the
4592 * Hamming weight or population count. An optional +offset+ and +length+
4593 * can be provided to count bits in a subrange of the buffer.
4594 *
4595 * IO::Buffer.for("\xFF\x00\x0F").bit_count
4596 * # => 12
4597 *
4598 * IO::Buffer.for("\xFF\x00\x0F").bit_count(1, 2)
4599 * # => 4
4600 */
4601static VALUE
4602io_buffer_bit_count(int argc, VALUE *argv, VALUE self)
4603{
4604 rb_check_arity(argc, 0, 2);
4605
4606 size_t offset, length;
4607 struct rb_io_buffer_view *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);
4608
4609 io_buffer_validate_range(buffer, offset, length);
4610
4611 const void *base;
4612 size_t size;
4613 io_buffer_get_bytes_for_reading(buffer, &base, &size);
4614
4615 if (length == 0) return SIZET2NUM(0);
4616
4617 RUBY_ASSERT(base != NULL);
4618 size_t count = memory_bit_count((const unsigned char *)base + offset, length);
4619
4620 return SIZET2NUM(count);
4621}
4622
4623static bool
4624io_buffer_memory_view_get(VALUE self, rb_memory_view_t *view, int flags)
4625{
4626 struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
4627
4628 void *base = NULL;
4629 size_t size = 0;
4630 if (!io_buffer_try_get_bytes(buffer, &base, &size) || base == NULL) {
4631 return false;
4632 }
4633
4634 bool readonly = true;
4635 if (flags & RUBY_MEMORY_VIEW_WRITABLE) {
4636 if (io_buffer_readonly_p(buffer)) {
4637 return false;
4638 } else {
4639 readonly = false;
4640 }
4641 }
4642 rb_memory_view_init_as_byte_array(view, self, base, buffer->size, readonly);
4643 if (flags & RUBY_MEMORY_VIEW_FORMAT) {
4644 view->format = "C";
4645 }
4646 bool request_multi_dimensional = flags & RUBY_MEMORY_VIEW_MULTI_DIMENSIONAL;
4647 bool request_strides =
4648 (flags & RUBY_MEMORY_VIEW_STRIDES) == RUBY_MEMORY_VIEW_STRIDES;
4649 if (request_multi_dimensional || request_strides) {
4650 size_t n_metadata = 0;
4651 if (request_multi_dimensional)
4652 n_metadata++;
4653 if (request_strides)
4654 n_metadata++;
4655 ssize_t *metadata_buffer = ALLOC_N(ssize_t, n_metadata);
4656 size_t i = 0;
4657 if (request_multi_dimensional) {
4658 ssize_t *shape = &metadata_buffer[i];
4659 shape[0] = buffer->size;
4660 view->shape = shape;
4661 i++;
4662 }
4663 if (request_strides) {
4664 ssize_t *strides = &metadata_buffer[i];
4665 strides[0] = 1;
4666 view->strides = strides;
4667 i++;
4668 }
4669 view->private_data = metadata_buffer;
4670 }
4671 io_buffer_lock(buffer);
4672
4673 return true;
4674}
4675
4676static bool
4677io_buffer_memory_view_release(VALUE self, rb_memory_view_t *view)
4678{
4679 rb_io_buffer_unlock(self);
4680 if (view->private_data) {
4681 xfree(view->private_data);
4682 }
4683 return true;
4684}
4685
4686static bool
4687io_buffer_memory_view_available_p(VALUE self)
4688{
4689 struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
4690
4691 void *base = NULL;
4692 size_t size = 0;
4693 return io_buffer_try_get_bytes(buffer, &base, &size) && base != NULL;
4694}
4695
4696static const rb_memory_view_entry_t io_buffer_memory_view_entry = {
4697 .get_func = io_buffer_memory_view_get,
4698 .release_func = io_buffer_memory_view_release,
4699 .available_p_func = io_buffer_memory_view_available_p,
4700};
4701
4702/*
4703 * Document-class: IO::Buffer
4704 *
4705 * IO::Buffer is the common byte-view interface. Its concrete subclasses are
4706 * IO::Buffer::Storage, which manages backing storage, and IO::Buffer::Slice,
4707 * which represents a parent-relative view. Both are IO::Buffer instances.
4708 *
4709 * IO::Buffer.new, IO::Buffer.for, and IO::Buffer.map create Storage objects.
4710 * The abstract base itself cannot be allocated directly. Each factory creates
4711 * one storage-bearing object, with no additional view needed for byte access.
4712 *
4713 * Storage#resize may reallocate; Storage#free and Storage#transfer manage the
4714 * backing storage. Slice#resize and Slice#advance only change a view, and
4715 * Slice never acquires its own storage. Duplicating Storage copies its bytes;
4716 * duplicating a Slice copies its range and source reference without copying
4717 * bytes.
4718 *
4719 * IO::Buffer is an efficient zero-copy buffer for input/output. There are
4720 * typical use cases:
4721 *
4722 * * Create an empty buffer with ::new, fill it with buffer using #copy or
4723 * #set_value, #set_string, get buffer with #get_string or write it directly
4724 * to some file with #write.
4725 * * Create a buffer mapped to some string with ::for, then it could be used
4726 * both for reading with #get_string or #get_value, and writing (writing will
4727 * change the source string, too).
4728 * * Create a buffer mapped to some file with ::map, then it could be used for
4729 * reading and writing the underlying file.
4730 * * Create a string of a fixed size with ::string, then #read into it, or
4731 * modify it using #set_value.
4732 *
4733 * Interaction with string and file memory is performed by efficient low-level
4734 * C mechanisms like `memcpy`.
4735 *
4736 * The class is meant to be an utility for implementing more high-level mechanisms
4737 * like Fiber::Scheduler#io_read and Fiber::Scheduler#io_write and parsing binary
4738 * protocols.
4739 *
4740 * == MemoryView Support
4741 *
4742 * IO::Buffer supports the C-level MemoryView protocol, so C
4743 * extensions can use +rb_memory_view_get()+ to access the buffer's
4744 * memory directly (zero-copy) as a 1-dimensional contiguous array of
4745 * bytes. The memory view is writable if the buffer is not
4746 * #readonly? and +RUBY_MEMORY_VIEW_WRITABLE+ is specified.
4747 *
4748 * While a MemoryView is exported, the buffer is locked.
4749 *
4750 * == Examples of Usage
4751 *
4752 * Empty buffer:
4753 *
4754 * buffer = IO::Buffer.new(8) # create empty 8-byte buffer
4755 * # =>
4756 * # #<IO::Buffer 0x0000555f5d1a5c50+8 INTERNAL>
4757 * # ...
4758 * buffer
4759 * # =>
4760 * # <IO::Buffer 0x0000555f5d156ab0+8 INTERNAL>
4761 * # 0x00000000 00 00 00 00 00 00 00 00
4762 * buffer.set_string('test', 2) # put there bytes of the "test" string, starting from offset 2
4763 * # => 4
4764 * buffer.get_string # get the result
4765 * # => "\x00\x00test\x00\x00"
4766 *
4767 * \Buffer from string:
4768 *
4769 * string = 'data'
4770 * IO::Buffer.for(string) do |buffer|
4771 * buffer
4772 * # =>
4773 * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
4774 * # 0x00000000 64 61 74 61 data
4775 *
4776 * buffer.get_string(2) # read content starting from offset 2
4777 * # => "ta"
4778 * buffer.set_string('---', 1) # write content, starting from offset 1
4779 * # => 3
4780 * buffer
4781 * # =>
4782 * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
4783 * # 0x00000000 64 2d 2d 2d d---
4784 * string # original string changed, too
4785 * # => "d---"
4786 * end
4787 *
4788 * \Buffer from file:
4789 *
4790 * File.write('test.txt', 'test data')
4791 * # => 9
4792 * buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
4793 * # =>
4794 * # #<IO::Buffer 0x00007f3f0768c000+9 EXTERNAL MAPPED FILE SHARED READONLY>
4795 * # ...
4796 * buffer.get_string(5, 2) # read 2 bytes, starting from offset 5
4797 * # => "da"
4798 * buffer.set_string('---', 1) # attempt to write
4799 * # in `set_string': Buffer is not writable! (IO::Buffer::AccessError)
4800 *
4801 * # To create writable file-mapped buffer
4802 * # Open file for read-write, pass size, offset, and flags=0
4803 * buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 9, 0, 0)
4804 * buffer.set_string('---', 1)
4805 * # => 3 -- bytes written
4806 * File.read('test.txt')
4807 * # => "t--- data"
4808 *
4809 * <b>The class is experimental and the interface is subject to change, this
4810 * is especially true of file mappings which may be removed entirely in
4811 * the future.</b>
4812 */
4813void
4814Init_IO_Buffer(void)
4815{
4816 rb_cIOBuffer = rb_define_class_under(rb_cIO, "Buffer", rb_cObject);
4817 rb_undef_alloc_func(rb_cIOBuffer);
4818 /*
4819 * Document-class: IO::Buffer::Storage
4820 *
4821 * A buffer that manages backing storage, including internally allocated,
4822 * mapped, String-backed, and borrowed memory. Storage provides Buffer's
4823 * byte-access interface together with allocation resizing, #free, #transfer,
4824 * and storage-specific predicates. It need not own the bytes it references.
4825 *
4826 * IO::Buffer.new, IO::Buffer.for, and IO::Buffer.map create Storage objects.
4827 * The Storage object is directly usable as a buffer; no separate view object
4828 * is needed unless a caller requests a slice. Duplicating Storage copies its
4829 * bytes into independent storage, unlike duplicating an IO::Buffer::Slice.
4830 */
4831 rb_cIOBufferStorage = rb_define_class_under(rb_cIOBuffer, "Storage", rb_cIOBuffer);
4832 /*
4833 * Document-class: IO::Buffer::Slice
4834 *
4835 * A zero-copy, parent-relative view of an IO::Buffer or another Slice.
4836 * Create a view with IO::Buffer#slice or IO::Buffer::Slice.new:
4837 *
4838 * buffer = IO::Buffer.new(6)
4839 * buffer.set_string("abcdef")
4840 * parent = buffer.slice(1, 4)
4841 * child = parent.slice(1, 2)
4842 * child.source.equal?(parent) # => true
4843 * child.get_string # => "cd"
4844 * parent.advance(1)
4845 * child.get_string # => "de"
4846 *
4847 * A slice retains its immediate source and follows that source's current
4848 * view. Its range is valid only while every parent is valid and the range
4849 * fits within its source. Moving the backing allocation does not itself
4850 * invalidate a slice. Shrinking a parent can invalidate descendants;
4851 * growing it again can restore their validity.
4852 *
4853 * Slice inherits Buffer's byte-access interface but does not manage storage.
4854 * Resizing a slice only changes its extent within its parent's bounds, and
4855 * advancing it consumes bytes from the front of its view. Neither operation
4856 * allocates or releases storage. Slice has no +free+ or +transfer+ method,
4857 * nor Storage's predicates such as +mapped?+ or +internal?+.
4858 *
4859 * Writes modify the source's bytes and respect its current permissions.
4860 * Locking a slice protects the backing allocation; +locked?+ reports that
4861 * shared lock state. Locked or read-only views may still be resized or
4862 * advanced, but frozen views cannot change their range. Locking does not
4863 * serialize access to bytes or view metadata.
4864 *
4865 * Duplicating or cloning a Slice copies its view rather than its bytes.
4866 * Both Slice and Storage are subclasses of IO::Buffer.
4867 */
4868 rb_cIOBufferSlice = rb_define_class_under(rb_cIOBuffer, "Slice", rb_cIOBuffer);
4869 rb_define_alloc_func(rb_cIOBufferSlice, io_buffer_slice_allocate);
4870 rb_define_method(rb_cIOBufferSlice, "initialize", io_buffer_slice_initialize, -1);
4871 rb_define_method(rb_cIOBufferSlice, "initialize_copy", io_buffer_slice_initialize_copy, 1);
4872
4873 /* Raised when an operation would resize or re-allocate a locked buffer. */
4874 rb_eIOBufferLockedError = rb_define_class_under(rb_cIOBuffer, "LockedError", rb_eRuntimeError);
4875
4876 /* Raised when the buffer cannot be allocated for some reason, or you try to use a buffer that's not allocated. */
4877 rb_eIOBufferAllocationError = rb_define_class_under(rb_cIOBuffer, "AllocationError", rb_eRuntimeError);
4878
4879 /* Raised when you try to write to a read-only buffer, or resize an external buffer. */
4880 rb_eIOBufferAccessError = rb_define_class_under(rb_cIOBuffer, "AccessError", rb_eRuntimeError);
4881
4882 /* Raised if you try to access a buffer slice which no longer references a valid memory range of the underlying source. */
4883 rb_eIOBufferInvalidatedError = rb_define_class_under(rb_cIOBuffer, "InvalidatedError", rb_eRuntimeError);
4884
4885 /* Raised if the mask given to a binary operation is invalid, e.g. zero length or overlaps the target buffer. */
4886 rb_eIOBufferMaskError = rb_define_class_under(rb_cIOBuffer, "MaskError", rb_eArgError);
4887
4888 rb_define_alloc_func(rb_cIOBufferStorage, rb_io_buffer_type_allocate);
4889 rb_define_singleton_method(rb_cIOBuffer, "new", io_buffer_s_new, -1);
4890 rb_define_singleton_method(rb_cIOBuffer, "for", rb_io_buffer_type_for, 1);
4891 rb_define_singleton_method(rb_cIOBuffer, "string", rb_io_buffer_type_string, 1);
4892
4893#ifdef _WIN32
4894 SYSTEM_INFO info;
4895 GetSystemInfo(&info);
4896 RUBY_IO_BUFFER_PAGE_SIZE = info.dwPageSize;
4897 RUBY_IO_BUFFER_MAP_ALIGNMENT = info.dwAllocationGranularity;
4898#else /* not WIN32 */
4899 RUBY_IO_BUFFER_PAGE_SIZE = sysconf(_SC_PAGESIZE);
4900 RUBY_IO_BUFFER_MAP_ALIGNMENT = RUBY_IO_BUFFER_PAGE_SIZE;
4901#endif
4902
4903 RUBY_IO_BUFFER_DEFAULT_SIZE = io_buffer_default_size(RUBY_IO_BUFFER_PAGE_SIZE);
4904
4905 /* The IO::Buffer interface version. */
4906 rb_define_const(rb_cIOBuffer, "VERSION", INT2NUM(RUBY_IO_BUFFER_VERSION));
4907
4908 /* The operating system page size. Used for efficient page-aligned memory allocations. */
4909 rb_define_const(rb_cIOBuffer, "PAGE_SIZE", SIZET2NUM(RUBY_IO_BUFFER_PAGE_SIZE));
4910
4911 /* The alignment required for file mapping offsets. Mapping sizes do not need to be aligned. */
4912 rb_define_const(rb_cIOBuffer, "MAP_ALIGNMENT", SIZET2NUM(RUBY_IO_BUFFER_MAP_ALIGNMENT));
4913
4914 /* The default buffer size, typically a (small) multiple of the PAGE_SIZE.
4915 Can be explicitly specified by setting the RUBY_IO_BUFFER_DEFAULT_SIZE
4916 environment variable. */
4917 rb_define_const(rb_cIOBuffer, "DEFAULT_SIZE", SIZET2NUM(RUBY_IO_BUFFER_DEFAULT_SIZE));
4918
4919 rb_define_singleton_method(rb_cIOBuffer, "map", io_buffer_map, -1);
4920
4921 // Storage factories belong to Buffer/Storage, not to Slice subclasses.
4922 rb_undef_method(rb_singleton_class(rb_cIOBufferSlice), "for");
4923 rb_undef_method(rb_singleton_class(rb_cIOBufferSlice), "string");
4924 rb_undef_method(rb_singleton_class(rb_cIOBufferSlice), "map");
4925
4926 rb_define_method(rb_cIOBufferStorage, "initialize", rb_io_buffer_initialize, -1);
4927 rb_define_method(rb_cIOBufferStorage, "initialize_copy", rb_io_buffer_initialize_copy, 1);
4928 rb_define_method(rb_cIOBuffer, "inspect", rb_io_buffer_inspect, 0);
4929 rb_define_method(rb_cIOBuffer, "hexdump", rb_io_buffer_hexdump, -1);
4930 rb_define_method(rb_cIOBuffer, "to_s", rb_io_buffer_to_s, 0);
4931 rb_define_method(rb_cIOBuffer, "size", rb_io_buffer_size, 0);
4932 rb_define_method(rb_cIOBuffer, "valid?", rb_io_buffer_valid_p, 0);
4933
4934 rb_define_method(rb_cIOBufferStorage, "transfer", io_buffer_transfer, 0);
4935
4936 /* Indicates that the memory in the buffer is owned by someone else. See #external? for more details. */
4937 rb_define_const(rb_cIOBuffer, "EXTERNAL", RB_INT2NUM(RB_IO_BUFFER_EXTERNAL));
4938
4939 /* Indicates that the memory in the buffer is owned by the buffer. See #internal? for more details. */
4940 rb_define_const(rb_cIOBuffer, "INTERNAL", RB_INT2NUM(RB_IO_BUFFER_INTERNAL));
4941
4942 /* Indicates that the memory in the buffer is mapped by the operating system. See #mapped? for more details. */
4943 rb_define_const(rb_cIOBuffer, "MAPPED", RB_INT2NUM(RB_IO_BUFFER_MAPPED));
4944
4945 /* Indicates that the memory in the buffer is also mapped such that it can be shared with other processes. See #shared? for more details. */
4946 rb_define_const(rb_cIOBuffer, "SHARED", RB_INT2NUM(RB_IO_BUFFER_SHARED));
4947
4948 /* 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. */
4949 rb_define_const(rb_cIOBuffer, "PRIVATE", RB_INT2NUM(RB_IO_BUFFER_PRIVATE));
4950
4951 /* Indicates that the memory in the buffer is read only, and attempts to modify it will fail. See #readonly? for more details.*/
4952 rb_define_const(rb_cIOBuffer, "READONLY", RB_INT2NUM(RB_IO_BUFFER_READONLY));
4953
4954 /* Refers to little endian byte order, where the least significant byte is stored first. See #get_value for more details. */
4955 rb_define_const(rb_cIOBuffer, "LITTLE_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_LITTLE_ENDIAN));
4956
4957 /* Refers to big endian byte order, where the most significant byte is stored first. See #get_value for more details. */
4958 rb_define_const(rb_cIOBuffer, "BIG_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_BIG_ENDIAN));
4959
4960 /* Refers to the byte order of the host machine. See #get_value for more details. */
4961 rb_define_const(rb_cIOBuffer, "HOST_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_HOST_ENDIAN));
4962
4963 /* Refers to network byte order, which is the same as big endian. See #get_value for more details. */
4964 rb_define_const(rb_cIOBuffer, "NETWORK_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_NETWORK_ENDIAN));
4965
4966 rb_define_method(rb_cIOBuffer, "null?", rb_io_buffer_null_p, 0);
4967 rb_define_method(rb_cIOBuffer, "empty?", rb_io_buffer_empty_p, 0);
4968 rb_define_method(rb_cIOBufferStorage, "external?", rb_io_buffer_external_p, 0);
4969 rb_define_method(rb_cIOBufferStorage, "internal?", rb_io_buffer_internal_p, 0);
4970 rb_define_method(rb_cIOBufferStorage, "mapped?", rb_io_buffer_mapped_p, 0);
4971 rb_define_method(rb_cIOBufferStorage, "shared?", rb_io_buffer_shared_p, 0);
4972 rb_define_method(rb_cIOBuffer, "locked?", rb_io_buffer_locked_p, 0);
4973 rb_define_method(rb_cIOBufferStorage, "private?", rb_io_buffer_private_p, 0);
4974 rb_define_method(rb_cIOBuffer, "readonly?", io_buffer_readonly, 0);
4975
4976 // Locking to prevent changes while using pointer:
4977 // rb_define_method(rb_cIOBuffer, "lock", rb_io_buffer_lock, 0);
4978 // rb_define_method(rb_cIOBuffer, "unlock", rb_io_buffer_unlock, 0);
4979 rb_define_method(rb_cIOBuffer, "locked", rb_io_buffer_locked, 0);
4980
4981 // Manipulation:
4982 rb_define_method(rb_cIOBuffer, "source", io_buffer_source, 0);
4983 rb_define_method(rb_cIOBuffer, "slice", io_buffer_slice, -1);
4984 rb_define_method(rb_cIOBuffer, "<=>", rb_io_buffer_compare, 1);
4985 rb_define_method(rb_cIOBufferStorage, "resize", io_buffer_resize, 1);
4986 rb_define_method(rb_cIOBufferSlice, "resize", io_buffer_resize, 1);
4987 rb_define_method(rb_cIOBuffer, "advance", io_buffer_advance, 1);
4988 rb_define_method(rb_cIOBuffer, "clear", io_buffer_clear, -1);
4989 rb_define_method(rb_cIOBufferStorage, "free", io_buffer_free, 0);
4990
4991 rb_include_module(rb_cIOBuffer, rb_mComparable);
4992
4993#define IO_BUFFER_DEFINE_DATA_TYPE(name) RB_IO_BUFFER_DATA_TYPE_##name = rb_intern_const(#name)
4994 IO_BUFFER_DEFINE_DATA_TYPE(U8);
4995 IO_BUFFER_DEFINE_DATA_TYPE(S8);
4996
4997 IO_BUFFER_DEFINE_DATA_TYPE(u16);
4998 IO_BUFFER_DEFINE_DATA_TYPE(U16);
4999 IO_BUFFER_DEFINE_DATA_TYPE(s16);
5000 IO_BUFFER_DEFINE_DATA_TYPE(S16);
5001
5002 IO_BUFFER_DEFINE_DATA_TYPE(u32);
5003 IO_BUFFER_DEFINE_DATA_TYPE(U32);
5004 IO_BUFFER_DEFINE_DATA_TYPE(s32);
5005 IO_BUFFER_DEFINE_DATA_TYPE(S32);
5006
5007 IO_BUFFER_DEFINE_DATA_TYPE(u64);
5008 IO_BUFFER_DEFINE_DATA_TYPE(U64);
5009 IO_BUFFER_DEFINE_DATA_TYPE(s64);
5010 IO_BUFFER_DEFINE_DATA_TYPE(S64);
5011
5012 IO_BUFFER_DEFINE_DATA_TYPE(u128);
5013 IO_BUFFER_DEFINE_DATA_TYPE(U128);
5014 IO_BUFFER_DEFINE_DATA_TYPE(s128);
5015 IO_BUFFER_DEFINE_DATA_TYPE(S128);
5016
5017 IO_BUFFER_DEFINE_DATA_TYPE(f32);
5018 IO_BUFFER_DEFINE_DATA_TYPE(F32);
5019 IO_BUFFER_DEFINE_DATA_TYPE(f64);
5020 IO_BUFFER_DEFINE_DATA_TYPE(F64);
5021#undef IO_BUFFER_DEFINE_DATA_TYPE
5022
5023 rb_define_singleton_method(rb_cIOBuffer, "size_of", io_buffer_size_of, 1);
5024
5025 // Data access:
5026 rb_define_method(rb_cIOBuffer, "get_value", io_buffer_get_value, 2);
5027 rb_define_method(rb_cIOBuffer, "get_values", io_buffer_get_values, 2);
5028 rb_define_method(rb_cIOBuffer, "each", io_buffer_each, -1);
5029 rb_define_method(rb_cIOBuffer, "values", io_buffer_values, -1);
5030 rb_define_method(rb_cIOBuffer, "each_byte", io_buffer_each_byte, -1);
5031 rb_define_method(rb_cIOBuffer, "set_value", io_buffer_set_value, 3);
5032 rb_define_method(rb_cIOBuffer, "set_values", io_buffer_set_values, 3);
5033
5034 rb_define_method(rb_cIOBuffer, "copy", io_buffer_copy, -1);
5035
5036 rb_define_method(rb_cIOBuffer, "get_string", io_buffer_get_string, -1);
5037 rb_define_method(rb_cIOBuffer, "set_string", io_buffer_set_string, -1);
5038
5039 // Binary buffer manipulations:
5040 rb_define_method(rb_cIOBuffer, "&", io_buffer_and, 1);
5041 rb_define_method(rb_cIOBuffer, "|", io_buffer_or, 1);
5042 rb_define_method(rb_cIOBuffer, "^", io_buffer_xor, 1);
5043 rb_define_method(rb_cIOBuffer, "~", io_buffer_not, 0);
5044
5045 rb_define_method(rb_cIOBuffer, "and!", io_buffer_and_inplace, 1);
5046 rb_define_method(rb_cIOBuffer, "or!", io_buffer_or_inplace, 1);
5047 rb_define_method(rb_cIOBuffer, "xor!", io_buffer_xor_inplace, 1);
5048 rb_define_method(rb_cIOBuffer, "not!", io_buffer_not_inplace, 0);
5049
5050 rb_define_method(rb_cIOBuffer, "bit_count", io_buffer_bit_count, -1);
5051
5052 // IO operations:
5053 rb_define_method(rb_cIOBuffer, "read", io_buffer_read, -1);
5054 rb_define_method(rb_cIOBuffer, "pread", io_buffer_pread, -1);
5055 rb_define_method(rb_cIOBuffer, "write", io_buffer_write, -1);
5056 rb_define_method(rb_cIOBuffer, "pwrite", io_buffer_pwrite, -1);
5057
5058 // MemoryView:
5059 rb_memory_view_register(rb_cIOBuffer, &io_buffer_memory_view_entry);
5060}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
#define rb_define_singleton_method(klass, mid, func, arity)
Defines klass.mid.
static bool RB_OBJ_FROZEN(VALUE obj)
Checks if an object is frozen.
Definition fl_type.h:714
void rb_include_module(VALUE klass, VALUE module)
Includes a module to a class.
Definition class.c:1769
VALUE rb_singleton_class(VALUE obj)
Finds or creates the singleton class of the passed object.
Definition class.c:3051
void rb_undef_method(VALUE klass, const char *name)
Defines an undef of a method.
Definition class.c:2897
int rb_block_given_p(void)
Determines if the current method is given a block.
Definition eval.c:1035
#define T_STRING
Old name of RUBY_T_STRING.
Definition value_type.h:78
#define xfree
Old name of ruby_xfree.
Definition xmalloc.h:58
#define OBJ_FROZEN
Old name of RB_OBJ_FROZEN.
Definition fl_type.h:133
#define rb_str_cat2
Old name of rb_str_cat_cstr.
Definition string.h: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:399
#define NUM2DBL
Old name of rb_num2dbl.
Definition double.h:27
#define INT2NUM
Old name of RB_INT2NUM.
Definition int.h:43
#define Qnil
Old name of RUBY_Qnil.
#define T_ARRAY
Old name of RUBY_T_ARRAY.
Definition value_type.h:56
#define NIL_P
Old name of RB_NIL_P.
#define T_SYMBOL
Old name of RUBY_T_SYMBOL.
Definition value_type.h:80
#define DBL2NUM
Old name of rb_float_new.
Definition double.h:29
#define NUM2SIZET
Old name of RB_NUM2SIZE.
Definition size_t.h:61
void rb_category_warn(rb_warning_category_t category, const char *fmt,...)
Identical to rb_category_warning(), except it reports unless $VERBOSE is nil.
Definition error.c:478
VALUE rb_eTypeError
TypeError exception.
Definition error.c:1473
VALUE rb_eRuntimeError
RuntimeError exception.
Definition error.c:1471
@ RB_WARN_CATEGORY_EXPERIMENTAL
Warning is for experimental features.
Definition error.h:51
VALUE rb_cObject
Object class.
Definition object.c:60
VALUE rb_cIO
IO class.
Definition io.c:187
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:2281
static VALUE rb_class_of(VALUE obj)
Object to class mapping function.
Definition globals.h:174
VALUE rb_obj_class(VALUE obj)
Queries the class of an object.
Definition object.c:234
VALUE rb_class_inherited_p(VALUE scion, VALUE ascendant)
Determines if the given two modules are relatives.
Definition object.c:1843
VALUE rb_mComparable
Comparable module.
Definition compar.c:19
#define RB_OBJ_WRITE(old, slot, young)
Declaration of a "back" pointer.
Definition gc.h:492
Scheduler APIs.
VALUE rb_fiber_scheduler_current(void)
Identical to rb_fiber_scheduler_get(), except it also returns RUBY_Qnil in case of a blocking fiber.
Definition scheduler.c:581
VALUE rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length)
Non-blocking read from the passed IO.
Definition scheduler.c:935
VALUE rb_fiber_scheduler_io_pwrite(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t offset, size_t length)
Non-blocking write to the passed IO at the specified offset.
Definition scheduler.c:1046
static VALUE rb_fiber_scheduler_io_result(ssize_t result, int error)
Wrap a ssize_t and int errno into a single VALUE.
Definition scheduler.h:52
VALUE rb_fiber_scheduler_io_pread(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t offset, size_t length)
Non-blocking read from the passed IO at the specified offset.
Definition scheduler.c:969
VALUE rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length)
Non-blocking write to the passed IO.
Definition scheduler.c:1012
VALUE rb_ary_new_capa(long capa)
Identical to rb_ary_new(), except it additionally specifies how many rooms of objects it should alloc...
VALUE rb_ary_push(VALUE ary, VALUE elem)
Special case of rb_ary_cat() that it adds only one element.
VALUE rb_ary_entry(VALUE ary, long off)
Queries an element of an array.
#define RETURN_ENUMERATOR_KW(obj, argc, argv, kw_splat)
Identical to RETURN_SIZED_ENUMERATOR_KW(), except its size is unknown.
Definition enumerator.h:260
static int rb_check_arity(int argc, int min, int max)
Ensures that the passed integer is in the passed range.
Definition error.h:284
VALUE rb_str_append(VALUE dst, VALUE src)
Identical to rb_str_buf_append(), except it converts the right hand side before concatenating.
Definition string.c:3913
#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:1555
VALUE rb_str_locktmp(VALUE str)
Obtains a "temporary lock" of the string.
VALUE rb_str_unlocktmp(VALUE str)
Releases a lock formerly obtained by rb_str_locktmp().
Definition string.c:3482
VALUE rb_str_buf_new(long capa)
Allocates a "string buffer".
Definition string.c:1769
#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:518
void rb_undef_alloc_func(VALUE klass)
Deletes the allocator function of a class.
Definition vm_method.c:1846
void rb_define_alloc_func(VALUE klass, rb_alloc_func_t func)
Sets the allocator function of a class.
ID rb_sym2id(VALUE obj)
Converts an instance of rb_cSymbol into an ID.
Definition symbol.c:1091
VALUE rb_io_get_io(VALUE io)
Identical to rb_io_check_io(), except it raises exceptions on conversion failures.
Definition io.c:828
int rb_io_descriptor(VALUE io)
Returns an integer representing the numeric file descriptor for io.
Definition io.c:2979
#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:840
void * rb_nogvl(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void *data2, int flags)
Identical to rb_thread_call_without_gvl(), except it additionally takes "flags" that change the behav...
Definition thread.c:1773
#define RB_NOGVL_OFFLOAD_SAFE
Passing this flag to rb_nogvl() indicates that the passed function is safe to offload to a background...
Definition thread.h:84
#define RB_NUM2INT
Just another name of rb_num2int_inline.
Definition int.h:38
#define RB_UINT2NUM
Just another name of rb_uint2num_inline.
Definition int.h:39
#define RB_INT2NUM
Just another name of rb_int2num_inline.
Definition int.h:37
static unsigned int RB_NUM2UINT(VALUE x)
Converts an instance of rb_cNumeric into C's unsigned int.
Definition int.h:185
#define RB_LL2NUM
Just another name of rb_ll2num_inline.
Definition long_long.h:28
#define RB_ULL2NUM
Just another name of rb_ull2num_inline.
Definition long_long.h:29
#define RB_NUM2ULL
Just another name of rb_num2ull_inline.
Definition long_long.h:33
#define RB_NUM2LL
Just another name of rb_num2ll_inline.
Definition long_long.h:32
VALUE rb_yield_values(int n,...)
Identical to rb_yield(), except it takes variadic number of parameters and pass them to the block.
Definition vm_eval.c:1401
VALUE rb_yield(VALUE val)
Yields the block.
Definition vm_eval.c:1378
static VALUE RB_INT2FIX(long i)
Converts a C's long into an instance of rb_cInteger.
Definition long.h:111
#define RB_NUM2LONG
Just another name of rb_num2long_inline.
Definition long.h:57
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
Memory View.
bool rb_memory_view_register(VALUE klass, const rb_memory_view_entry_t *entry)
Associates the passed class with the passed memory view entry.
bool rb_memory_view_init_as_byte_array(rb_memory_view_t *view, VALUE obj, void *data, const ssize_t len, const bool readonly)
Fill the members of view as an 1-dimensional byte array.
VALUE type(ANYARGS)
ANYARGS-ed function type.
VALUE rb_ensure(type *q, VALUE w, type *e, VALUE r)
An equivalent of ensure clause.
#define OFFT2NUM
Converts a C's off_t into an instance of rb_cInteger.
Definition off_t.h:33
#define NUM2OFFT
Converts an instance of rb_cNumeric into C's off_t.
Definition off_t.h:44
#define RARRAY_LEN
Just another name of rb_array_len.
Definition rarray.h:50
#define RARRAY_AREF(a, i)
Definition rarray.h:402
#define StringValue(v)
Ensures that the parameter object is a String.
Definition rstring.h:66
#define RSTRING_GETMEM(str, ptrvar, lenvar)
Convenient macro to obtain the contents and length at once.
Definition rstring.h:450
VALUE rb_str_to_str(VALUE obj)
Identical to rb_check_string_type(), except it raises exceptions in case of conversion failures.
Definition string.c:1830
#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
const ssize_t * strides
ndim size array indicating the number of bytes to skip to go to the next element in each dimension.
const ssize_t * shape
ndim size array indicating the number of elements in each dimension.
void * private_data
The private data for managing this exported memory.
const char * format
A string to describe the format of an element, or NULL for unsigned bytes.
uintptr_t ID
Type that represents a Ruby identifier such as a variable name.
Definition value.h:52
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static void Check_Type(VALUE v, enum ruby_value_type t)
Identical to RB_TYPE_P(), except it raises exceptions on predication failure.
Definition value_type.h:425
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376