%a{annotate:rdoc:skip} class IO # # IO::Buffer is a low-level efficient buffer for input/output. There are three # ways of using buffer: # # * Create an empty buffer with ::new, fill it with buffer using #copy or # #set_value, #set_string, get buffer with #get_string; # * Create a buffer mapped to some string with ::for, then it could be used # both for reading with #get_string or #get_value, and writing (writing will # change the source string, too); # * Create a buffer mapped to some file with ::map, then it could be used for # reading and writing the underlying file. # # # Interaction with string and file memory is performed by efficient low-level C # mechanisms like `memcpy`. # # The class is meant to be an utility for implementing more high-level # mechanisms like Fiber::SchedulerInterface#io_read and # Fiber::SchedulerInterface#io_write. # # **Examples of usage:** # # Empty buffer: # # buffer = IO::Buffer.new(8) # create empty 8-byte buffer # # => # # # # # ... # buffer # # => # # # # 0x00000000 00 00 00 00 00 00 00 00 # buffer.set_string('test', 2) # put there bytes of the "test" string, starting from offset 2 # # => 4 # buffer.get_string # get the result # # => "\x00\x00test\x00\x00" # # Buffer from string: # # string = 'buffer' # buffer = IO::Buffer.for(string) # # => # # # # # ... # buffer # # => # # # # # 0x00000000 64 61 74 61 buffer # # buffer.get_string(2) # read content starting from offset 2 # # => "ta" # buffer.set_string('---', 1) # write content, starting from offset 1 # # => 3 # buffer # # => # # # # # 0x00000000 64 2d 2d 2d d--- # string # original string changed, too # # => "d---" # # Buffer from file: # # File.write('test.txt', 'test buffer') # # => 9 # buffer = IO::Buffer.map(File.open('test.txt')) # # => # # # # # ... # buffer.get_string(5, 2) # read 2 bytes, starting from offset 5 # # => "da" # buffer.set_string('---', 1) # attempt to write # # in `set_string': Buffer is not writable! (IO::Buffer::AccessError) # # # To create writable file-mapped buffer # # Open file for read-write, pass size, offset, and flags=0 # buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 9, 0, 0) # buffer.set_string('---', 1) # # => 3 -- bytes written # File.read('test.txt') # # => "t--- buffer" # # **The class is experimental and the interface is subject to change.** # class Buffer include Comparable # # Creates a IO::Buffer from the given string's memory. Without a block a frozen # internal copy of the string is created efficiently and used as the buffer # source. When a block is provided, the buffer is associated directly with the # string's internal buffer and updating the buffer will update the string. # # Until #free is invoked on the buffer, either explicitly or via the garbage # collector, the source string will be locked and cannot be modified. # # If the string is frozen, it will create a read-only buffer which cannot be # modified. If the string is shared, it may trigger a copy-on-write when using # the block form. # # string = 'test' # buffer = IO::Buffer.for(string) # buffer.external? #=> true # # buffer.get_string(0, 1) # # => "t" # string # # => "best" # # buffer.resize(100) # # in `resize': Cannot resize external buffer! (IO::Buffer::AccessError) # # IO::Buffer.for(string) do |buffer| # buffer.set_string("T") # string # # => "Test" # end # def self.for: (String) -> Buffer # # Create an IO::Buffer for reading from `file` by memory-mapping the file. # `file_io` should be a `File` instance, opened for reading. # # Optional `size` and `offset` of mapping can be specified. # # By default, the buffer would be immutable (read only); to create a writable # mapping, you need to open a file in read-write mode, and explicitly pass # `flags` argument without IO::Buffer::IMMUTABLE. # # Example: # # File.write('test.txt', 'test') # # buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) # # => # # # buffer.readonly? # => true # # buffer.get_string # # => "test" # # buffer.set_string('b', 0) # # `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. # def self.map: (File file, ?Integer? size, ?Integer offset, ?Integer flags) -> Buffer # # Creates a new string of the given length and yields a IO::Buffer instance to # the block which uses the string as a source. The block is expected to write to # the buffer and the string will be returned. # # IO::Buffer.string(4) do |buffer| # buffer.set_string("Ruby") # end # # => "Ruby" # def self.string: (int) { (Buffer) -> void } -> String public # # Buffers are compared by size and exact contents of the memory they are # referencing using `memcmp`. # def <=>: (Buffer) -> Integer # # Fill buffer with `value`, starting with `offset` and going for `length` bytes. # # buffer = IO::Buffer.for('test') # # => # # # # 0x00000000 74 65 73 74 test # # buffer.clear # # => # # # # 0x00000000 00 00 00 00 .... # # buf.clear(1) # fill with 1 # # => # # # # 0x00000000 01 01 01 01 .... # # buffer.clear(2, 1, 2) # fill with 2, starting from offset 1, for 2 bytes # # => # # # # 0x00000000 01 02 02 01 .... # # buffer.clear(2, 1) # fill with 2, starting from offset 1 # # => # # # # 0x00000000 01 02 02 02 .... # def clear: (?Integer value, ?Integer offset, ?Integer length) -> self # # Efficiently copy from a source IO::Buffer into the buffer, at `offset` using # `memcpy`. For copying String instances, see #set_string. # # buffer = IO::Buffer.new(32) # # => # # # # # 0x00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ # # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * # # buffer.copy(IO::Buffer.for("test"), 8) # # => 4 -- size of buffer copied # buffer # # => # # # # # 0x00000000 00 00 00 00 00 00 00 00 74 65 73 74 00 00 00 00 ........test.... # # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * # # #copy can be used to put buffer into strings associated with buffer: # # string= "buffer: " # # => "buffer: " # buffer = IO::Buffer.for(string) # buffer.copy(IO::Buffer.for("test"), 5) # # => 4 # string # # => "buffer:test" # # Attempt to copy into a read-only buffer will fail: # # File.write('test.txt', 'test') # buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) # buffer.copy(IO::Buffer.for("test"), 8) # # in `copy': Buffer is not writable! (IO::Buffer::AccessError) # # See ::map for details of creation of mutable file mappings, this will work: # # buffer = IO::Buffer.map(File.open('test.txt', 'r+')) # buffer.copy(IO::Buffer.for("boom"), 0) # # => 4 # File.read('test.txt') # # => "boom" # # Attempt to copy the buffer which will need place outside of buffer's bounds # will fail: # # buffer = IO::Buffer.new(2) # buffer.copy(IO::Buffer.for('test'), 0) # # in `copy': Specified offset+length is bigger than the buffer size! (ArgumentError) # def copy: (Buffer source, ?Integer offset, ?Integer length, ?Integer source_offset) -> Integer # # If the buffer has 0 size: it is created by ::new with size 0, or with ::for # from an empty string. (Note that empty files can't be mapped, so the buffer # created with ::map will never be empty.) # def empty?: () -> bool # # The buffer is *external* if it references the memory which is not allocated or # mapped by the buffer itself. # # A buffer created using ::for has an external reference to the string's memory. # # External buffer can't be resized. # def external?: () -> bool # # If the buffer references memory, release it back to the operating system. # * for a *mapped* buffer (e.g. from file): unmap. # * for a buffer created from scratch: free memory. # * for a buffer created from string: undo the association. # # # After the buffer is freed, no further operations can't be performed on it. # # You can resize a freed buffer to re-allocate it. # # Example: # # buffer = IO::Buffer.for('test') # buffer.free # # => # # # buffer.get_value(:U8, 0) # # in `get_value': The buffer is not allocated! (IO::Buffer::AllocationError) # # buffer.get_string # # in `get_string': The buffer is not allocated! (IO::Buffer::AllocationError) # # buffer.null? # # => true # def free: () -> self # # Read a chunk or all of the buffer into a string, in the specified `encoding`. # If no encoding is provided `Encoding::BINARY` is used. # # buffer = IO::Buffer.for('test') # buffer.get_string # # => "test" # buffer.get_string(2) # # => "st" # buffer.get_string(2, 1) # # => "s" # def get_string: (?Integer offset, ?Integer length, ?Encoding encoding) -> String # # Read from buffer a value of `type` at `offset`. `buffer_type` should be one of # symbols: # # * `:U8`: unsigned integer, 1 byte # * `:S8`: signed integer, 1 byte # * `:u16`: unsigned integer, 2 bytes, little-endian # * `:U16`: unsigned integer, 2 bytes, big-endian # * `:s16`: signed integer, 2 bytes, little-endian # * `:S16`: signed integer, 2 bytes, big-endian # * `:u32`: unsigned integer, 4 bytes, little-endian # * `:U32`: unsigned integer, 4 bytes, big-endian # * `:s32`: signed integer, 4 bytes, little-endian # * `:S32`: signed integer, 4 bytes, big-endian # * `:u64`: unsigned integer, 8 bytes, little-endian # * `:U64`: unsigned integer, 8 bytes, big-endian # * `:s64`: signed integer, 8 bytes, little-endian # * `:S64`: signed integer, 8 bytes, big-endian # * `:f32`: float, 4 bytes, little-endian # * `:F32`: float, 4 bytes, big-endian # * `:f64`: double, 8 bytes, little-endian # * `:F64`: double, 8 bytes, big-endian # # # A buffer type refers specifically to the type of binary buffer that is stored # in the buffer. For example, a `:u32` buffer type is a 32-bit unsigned integer # in little-endian format. # # Example: # # string = [1.5].pack('f') # # => "\x00\x00\xC0?" # IO::Buffer.for(string).get_value(:f32, 0) # # => 1.5 # def get_value: (int_get_type, Integer offset) -> Integer | (float_get_type, Integer offset) -> Float type int_get_type = :U8 | :S8 | :u16 | :U16 | :s16 | :S16 | :u32 | :U32 | :s32 | :S32 | :u64 | :U64 | :s64 | :S64 type float_get_type = :f32 | :F32 | :f64 | :F64 # # def hexdump: () -> String # # def inspect: () -> String # # If the buffer is *internal*, meaning it references memory allocated by the # buffer itself. # # An internal buffer is not associated with any external memory (e.g. string) or # file mapping. # # Internal buffers are created using ::new and is the default when the requested # size is less than the IO::Buffer::PAGE_SIZE and it was not requested to be # mapped on creation. # # Internal buffers can be resized, and such an operation will typically # invalidate all slices, but not always. # def internal?: () -> bool # # Allows to process a buffer in exclusive way, for concurrency-safety. While the # block is performed, the buffer is considered locked, and no other code can # enter the lock. Also, locked buffer can't be changed with #resize or #free. # # The following operations acquire a lock: #resize, #free. # # Locking is not thread safe. It is designed as a safety net around non-blocking # system calls. You can only share a buffer between threads with appropriate # synchronisation techniques. # # Example: # # buffer = IO::Buffer.new(4) # buffer.locked? #=> false # # Fiber.schedule do # buffer.locked do # buffer.write(io) # theoretical system call interface # end # end # # Fiber.schedule do # # in `locked': Buffer already locked! (IO::Buffer::LockedError) # buffer.locked do # buffer.set_string("test", 0) # end # end # def locked: [A] () { (IO::Buffer) -> A } -> A # # If the buffer is *locked*, meaning it is inside #locked block execution. # Locked buffer can't be resized or freed, and another lock can't be acquired on # it. # # Locking is not thread safe, but is a semantic used to ensure buffers don't # move while being used by a system call. # # Example: # # buffer.locked do # buffer.write(io) # theoretical system call interface # end # def locked?: () -> bool # # If the buffer is *mapped*, meaning it references memory mapped by the buffer. # # Mapped buffers are either anonymous, if created by ::new with the # IO::Buffer::MAPPED flag or if the size was at least IO::Buffer::PAGE_SIZE, or # backed by a file if created with ::map. # # Mapped buffers can usually be resized, and such an operation will typically # invalidate all slices, but not always. # def mapped?: () -> bool # # If the buffer was freed with #free or was never allocated in the first place. # def null?: () -> bool # # Read at least `length` bytes from the `io` starting at the specified `from` # position, into the buffer starting at `offset`. If an error occurs, return # `-errno`. # # If `length` is not given or `nil`, it defaults to the size of the buffer minus # the offset, i.e. the entire buffer. # # If `length` is zero, exactly one `pread` operation will occur. # # If `offset` is not given, it defaults to zero, i.e. the beginning of the # buffer. # # Example: # # IO::Buffer.for('test') do |buffer| # p buffer # # => # # # # 0x00000000 74 65 73 74 test # # # take 2 bytes from the beginning of urandom, # # put them in buffer starting from position 2 # buffer.pread(File.open('/dev/urandom', 'rb'), 0, 2, 2) # p buffer # # => # # # # 0x00000000 05 35 73 74 te.5 # end # def pread: (untyped, untyped, untyped) -> untyped # # Write at least `length` bytes from the buffer starting at `offset`, into the # `io` starting at the specified `from` position. If an error occurs, return # `-errno`. # # If `length` is not given or `nil`, it defaults to the size of the buffer minus # the offset, i.e. the entire buffer. # # If `length` is zero, exactly one `pwrite` operation will occur. # # If `offset` is not given, it defaults to zero, i.e. the beginning of the # buffer. # # If the `from` position is beyond the end of the file, the gap will be filled # with null (0 value) bytes. # # Example: # # out = File.open('output.txt', File::RDWR) # open for read/write, no truncation # IO::Buffer.for('1234567').pwrite(out, 2, 3, 1) # # This leads to `234` (3 bytes, starting from position 1) being written into # `output.txt`, starting from file position 2. # def pwrite: (untyped, untyped, untyped) -> untyped # # Read at least `length` bytes from the `io`, into the buffer starting at # `offset`. If an error occurs, return `-errno`. # # If `length` is not given or `nil`, it defaults to the size of the buffer minus # the offset, i.e. the entire buffer. # # If `length` is zero, exactly one `read` operation will occur. # # If `offset` is not given, it defaults to zero, i.e. the beginning of the # buffer. # # Example: # # IO::Buffer.for('test') do |buffer| # p buffer # # => # # # # 0x00000000 74 65 73 74 test # buffer.read(File.open('/dev/urandom', 'rb'), 2) # p buffer # # => # # # # 0x00000000 05 35 73 74 .5st # end # def read: (untyped, untyped) -> untyped # # If the buffer is *read only*, meaning the buffer cannot be modified using # #set_value, #set_string or #copy and similar. # # Frozen strings and read-only files create read-only buffers. # def readonly?: () -> bool # # Resizes a buffer to a `new_size` bytes, preserving its content. Depending on # the old and new size, the memory area associated with the buffer might be # either extended, or rellocated at different address with content being copied. # # buffer = IO::Buffer.new(4) # buffer.set_string("test", 0) # buffer.resize(8) # resize to 8 bytes # # => # # # # # 0x00000000 74 65 73 74 00 00 00 00 test.... # # External buffer (created with ::for), and locked buffer can not be resized. # def resize: (Integer) -> self # # Efficiently copy buffer from a source String into the buffer, at `offset` # using `memcpy`. # # buf = IO::Buffer.new(8) # # => # # # # # 0x00000000 00 00 00 00 00 00 00 00 ........ # # # set buffer starting from offset 1, take 2 bytes starting from string's # # second # buf.set_string('test', 1, 2, 1) # # => 2 # buf # # => # # # # # 0x00000000 00 65 73 00 00 00 00 00 .es..... # # See also #copy for examples of how buffer writing might be used for changing # associated strings and files. # def set_string: (*untyped) -> untyped # # Write to a buffer a `value` of `type` at `offset`. `type` should be one of # symbols described in #get_value. # # buffer = IO::Buffer.new(8) # # => # # # # # 0x00000000 00 00 00 00 00 00 00 00 # # buffer.set_value(:U8, 1, 111) # # => 1 # # buffer # # => # # # # # 0x00000000 00 6f 00 00 00 00 00 00 .o...... # # Note that if the `type` is integer and `value` is Float, the implicit # truncation is performed: # # buffer = IO::Buffer.new(8) # buffer.set_value(:U32, 0, 2.5) # # buffer # # => # # # # # 0x00000000 00 00 00 02 00 00 00 00 # # ^^ the same as if we'd pass just integer 2 # def set_value: (int_get_type | float_get_type, Integer offset, Float | Integer value) -> Integer # # Returns the size of the buffer that was explicitly set (on creation with ::new # or on #resize), or deduced on buffer's creation from string or file. # def size: () -> Integer # # Produce another IO::Buffer which is a slice (or view into) the current one # starting at `offset` bytes and going for `length` bytes. # # The slicing happens without copying of memory, and the slice keeps being # associated with the original buffer's source (string, or file), if any. # # If the offset is not given, it will be zero. If the offset is negative, it # will raise an ArgumentError. # # If the length is not given, the slice will be as long as the original buffer # minus the specified offset. If the length is negative, it will raise an # ArgumentError. # # Raises RuntimeError if the `offset+length` is out of the current buffer's # bounds. # # Example: # # string = 'test' # buffer = IO::Buffer.for(string) # # slice = buffer.slice # # => # # # # # 0x00000000 74 65 73 74 test # # buffer.slice(2) # # => # # # # # 0x00000000 73 74 st # # slice = buffer.slice(1, 2) # # => # # # # # 0x00000000 65 73 es # # # Put "o" into 0s position of the slice # slice.set_string('o', 0) # slice # # => # # # # # 0x00000000 6f 73 os # # # it is also visible at position 1 of the original buffer # buffer # # => # # # # # 0x00000000 74 6f 73 74 tost # # # ...and original string # string # # => tost # def slice: (Integer offset, Integer length) -> Buffer # # Short representation of the buffer. It includes the address, size and symbolic # flags. This format is subject to change. # # puts IO::Buffer.new(4) # uses to_s internally # # # # def to_s: () -> String # # Transfers ownership to a new buffer, deallocating the current one. # # Example: # # buffer = IO::Buffer.new('test') # other = buffer.transfer # other # # => # # # # # 0x00000000 74 65 73 74 test # buffer # # => # # # # buffer.null? # # => true # def transfer: () -> Buffer # # Returns whether the buffer buffer is accessible. # # A buffer becomes invalid if it is a slice of another buffer which has been # freed. # def valid?: () -> bool # # Write at least `length` bytes from the buffer starting at `offset`, into the # `io`. If an error occurs, return `-errno`. # # If `length` is not given or `nil`, it defaults to the size of the buffer minus # the offset, i.e. the entire buffer. # # If `length` is zero, exactly one `write` operation will occur. # # If `offset` is not given, it defaults to zero, i.e. the beginning of the # buffer. # # Example: # # out = File.open('output.txt', 'wb') # IO::Buffer.for('1234567').write(out, 3) # # This leads to `123` being written into `output.txt` # def write: (untyped, untyped) -> untyped private # # Create a new zero-filled IO::Buffer of `size` bytes. By default, the buffer # will be *internal*: directly allocated chunk of the memory. But if the # requested `size` is more than OS-specific IO::Buffer::PAGE_SIZE, the buffer # would be allocated using the virtual memory mechanism (anonymous `mmap` on # Unix, `VirtualAlloc` on Windows). The behavior can be forced by passing # IO::Buffer::MAPPED as a second parameter. # # Examples # # buffer = IO::Buffer.new(4) # # => # # # # # 0x00000000 00 00 00 00 .... # # buffer.get_string(0, 1) # => "\x00" # # buffer.set_string("test") # buffer # # => # # # # # 0x00000000 74 65 73 74 test # def initialize: (?Integer size, ?Integer flags) -> void BIG_ENDIAN: Integer DEFAULT_SIZE: Integer EXTERNAL: Integer HOST_ENDIAN: Integer INTERNAL: Integer LITTLE_ENDIAN: Integer LOCKED: Integer MAPPED: Integer NETWORK_ENDIAN: Integer PAGE_SIZE: Integer PRIVATE: Integer READONLY: Integer class LockedError < RuntimeError end class AllocationError < RuntimeError end class AccessError < RuntimeError end class InvalidatedError < RuntimeError end class MaskError < ArgumentError end end end