Preview Ruby 4.1 is a preview. It can change before the final release. Switch to Ruby 4.0 →
IO::Buffer.map
static VALUE
io_buffer_map(int argc, VALUE *argv, VALUE klass)
{
klass = io_buffer_storage_class(klass);
rb_check_arity(argc, 1, 4);
// We might like to handle a string path?
VALUE io = argv[0];
rb_off_t file_size = rb_file_size(io);
// Compiler can confirm that we handled file_size <= 0 case:
if (UNLIKELY(file_size <= 0)) {
rb_raise(rb_eArgError, "Invalid negative or zero file size!");
}
// Here, we assume that file_size is positive:
else if (UNLIKELY((uintmax_t)file_size > SIZE_MAX)) {
rb_raise(rb_eArgError, "File larger than address space!");
}
size_t size;
if (argc >= 2 && !RB_NIL_P(argv[1])) {
size = io_buffer_extract_size(argv[1]);
if (UNLIKELY(size == 0)) {
rb_raise(rb_eArgError, "Size can't be zero!");
}
if (UNLIKELY(size > (size_t)file_size)) {
rb_raise(rb_eArgError,
"Size (%" PRIuSIZE ") can't be larger than "
"file size (%" PRIuSIZE ")",
size,
(size_t)file_size);
}
}
else {
// This conversion should be safe:
size = (size_t)file_size;
}
// This is the file offset, not the buffer offset:
rb_off_t offset = 0;
if (argc >= 3) {
offset = NUM2OFFT(argv[2]);
if (UNLIKELY(offset < 0)) {
rb_raise(rb_eArgError,
"Offset (%" PRIsVALUE ") can't be negative!",
argv[2]);
}
if (UNLIKELY(offset >= file_size)) {
rb_raise(rb_eArgError,
"Offset (%" PRIsVALUE ") can't be larger than "
"file size (%" PRIuSIZE ")",
argv[2],
(size_t)file_size);
}
if (RB_NIL_P(argv[1])) {
// Decrease size if it's set from the actual file size:
size = (size_t)(file_size - offset);
}
else if (UNLIKELY((size_t)(file_size - offset) < size)) {
size_t maximum_offset =
((size_t)file_size - size) / RUBY_IO_BUFFER_MAP_ALIGNMENT *
RUBY_IO_BUFFER_MAP_ALIGNMENT;
rb_raise(rb_eArgError,
"Offset (%" PRIsVALUE ") can't be larger than "
"%" PRIuSIZE " for requested size (%" PRIuSIZE ")",
argv[2],
maximum_offset,
size);
}
}
enum rb_io_buffer_flags flags = 0;
if (argc >= 4) {
flags = io_buffer_extract_flags(argv[3]);
}
flags = io_buffer_flags_for_map(flags);
return io_buffer_storage_map(klass, io, size, offset, flags);
}
Create an IO::Buffer for reading from file by memory-mapping the file. file should be a File instance, opened for reading or reading and writing.
Optional size and offset of mapping can be specified. The offset must be a multiple of IO::Buffer::MAP_ALIGNMENT. The size does not need to be aligned. Trying to map an empty file or specify size of 0 will raise an error.
By default, the buffer is writable and expects the file to be writable. It is also shared, so several processes can use the same mapping.
The mapping mode may be explicitly selected with IO::Buffer::SHARED or IO::Buffer::PRIVATE, but the two flags are mutually exclusive. IO::Buffer::MAPPED is accepted but redundant because this method always creates a mapped buffer. IO::Buffer::INTERNAL and IO::Buffer::EXTERNAL cannot be specified.
You can pass IO::Buffer::READONLY in flags argument to make a read-only buffer; this allows to work with files opened only for reading. Specifying IO::Buffer::PRIVATE in flags creates a private mapping, which will not impact other processes or the underlying file. It also allows updating a buffer created from a read-only file.
File.write('test.txt', 'test') buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) # => #<IO::Buffer 0x00000001014a0000+4 EXTERNAL MAPPED FILE SHARED READONLY> buffer.readonly? # => true buffer.get_string # => "test" buffer.set_string('b', 0) # 'IO::Buffer#set_string': Buffer is not writable! (IO::Buffer::AccessError) # create read/write mapping: length 4 bytes, offset 0, flags 0 buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 4, 0) buffer.set_string('b', 0) # => 1 # Check it File.read('test.txt') # => "best"
Note that some operating systems may not have cache coherency between mapped buffers and file reads.