Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1494 lines (1269 sloc) 38.141 kB
# -*- encoding: us-ascii -*-
class IO
include Enumerable
# Import platform constants
SEEK_SET = Rubinius::Config['rbx.platform.io.SEEK_SET']
SEEK_CUR = Rubinius::Config['rbx.platform.io.SEEK_CUR']
SEEK_END = Rubinius::Config['rbx.platform.io.SEEK_END']
# InternalBuffer provides a sliding window into a region of bytes.
# The buffer is filled to the +used+ indicator, which is
# always less than or equal to +total+. As bytes are taken
# from the buffer, the +start+ indicator is incremented by
# the number of bytes taken. Once +start+ == +used+, the
# buffer is +empty?+ and needs to be refilled.
#
# This description should be independent of the "direction"
# in which the buffer is used. As a read buffer, +fill_from+
# appends at +used+, but not exceeding +total+. When +used+
# equals total, no additional bytes will be filled until the
# buffer is emptied.
#
# As a write buffer, +empty_to+ removes bytes from +start+ up
# to +used+. When +start+ equals +used+, no additional bytes
# will be emptied until the buffer is filled.
#
# IO presents a stream of input. Buffer presents buckets of
# input. IO's task is to chain the buckets so the user sees
# a stream. IO explicitly requests that the buffer be filled
# (on input) and then determines how much of the input to take
# (e.g. by looking for a separator or collecting a certain
# number of bytes). Buffer decides whether or not to go to the
# source for more data or just present what is already in the
# buffer.
class InternalBuffer
attr_reader :total
attr_reader :start
attr_reader :used
attr_reader :channel
##
# Returns +true+ if the buffer can be filled.
def empty?
@start == @used
end
##
# Returns +true+ if the buffer is empty and cannot be filled further.
def exhausted?
@eof and empty?
end
##
# A request to the buffer to have data. The buffer decides whether
# existing data is sufficient, or whether to read more data from the
# +IO+ instance. Any new data causes this method to return.
#
# Returns the number of bytes in the buffer.
def fill_from(io, skip = nil)
Rubinius.synchronize(self) do
empty_to io
discard skip if skip
return size unless empty?
reset!
if fill(io) < 0
raise IOError, "error occurred while filling buffer (#{obj})"
end
if @used == 0
io.eof!
@eof = true
end
return size
end
end
def empty_to(io)
return 0 if @write_synced or empty?
@write_synced = true
io.prim_write(String.from_bytearray(@storage, @start, size))
reset!
return size
end
##
# Advances the beginning-of-buffer marker past any number
# of contiguous characters == +skip+. For example, if +skip+
# is ?\n and the buffer contents are "\n\n\nAbc...", the
# start marker will be positioned on 'A'.
def discard(skip)
while @start < @used
break unless @storage[@start] == skip
@start += 1
end
end
##
# Returns the number of bytes to fetch from the buffer up-to-
# and-including +pattern+. Returns +nil+ if pattern is not found.
def find(pattern, discard = nil)
if count = @storage.locate(pattern, @start, @used)
count - @start
end
end
##
# Returns +true+ if the buffer is filled to capacity.
def full?
@total == @used
end
def inspect # :nodoc:
"#<IO::InternalBuffer:0x%x total=%p start=%p used=%p data=%p>" % [
object_id, @total, @start, @used, @storage
]
end
##
# Resets the buffer state so the buffer can be filled again.
def reset!
@start = @used = 0
@eof = false
@write_synced = true
end
def write_synced?
@write_synced
end
def unseek!(io)
Rubinius.synchronize(self) do
# Unseek the still buffered amount
return unless write_synced?
io.prim_seek @start - @used, IO::SEEK_CUR unless empty?
reset!
end
end
##
# Returns +count+ bytes from the +start+ of the buffer as a new String.
# If +count+ is +nil+, returns all available bytes in the buffer.
def shift(count=nil)
Rubinius.synchronize(self) do
total = size
total = count if count and count < total
str = String.from_bytearray @storage, @start, total
@start += total
str
end
end
##
# Returns one Fixnum as the start byte, used for #getc
def get_first
Rubinius.synchronize(self) do
byte = @storage[@start]
@start += 1
byte
end
end
##
# Prepends the byte +chr+ to the internal buffer, so that future
# reads will return it.
def put_back(chr)
# A simple case, which is common and can be done efficiently
if @start > 0
@start -= 1
@storage[@start] = chr
else
@storage = @storage.prepend(chr.chr)
@start = 0
@total = @storage.size
@used += 1
end
end
##
# Returns the number of bytes available in the buffer.
def size
@used - @start
end
##
# Returns the number of bytes of capacity remaining in the buffer.
# This is the number of additional bytes that can be added to the
# buffer before it is full.
def unused
@total - @used
end
end
attr_accessor :descriptor
attr_accessor :mode
def self.foreach(name, sep_string = $/)
return to_enum(:foreach, name, sep_string) unless block_given?
name = StringValue(name)
if name[0] == ?|
io = IO.popen(name[1..-1], "r")
return nil unless io
else
io = File.open(name, 'r')
end
if sep_string.nil?
sep = sep_string
else
sep = StringValue(sep_string)
end
begin
while line = io.gets(sep)
yield line
end
ensure
io.close
end
return nil
end
##
# Creates a new IO object to access the existing stream referenced by the
# descriptor given. The stream is not copied in any way so anything done on
# one IO will affect any other IOs accessing the same descriptor.
#
# The mode string given must be compatible with the original one so going
# 'r' from 'w' cannot be done but it is possible to go from 'w+' to 'r', for
# example (since the stream is not being "widened".)
#
# The initialization will verify that the descriptor given is a valid one.
# Errno::EBADF will be raised if that is not the case. If the mode is
# incompatible, it will raise Errno::EINVAL instead.
def self.open(*args)
io = new(*args)
return io unless block_given?
begin
yield io
ensure
begin
io.close unless io.closed?
rescue StandardError
# nothing, just swallow them.
end
end
end
def self.parse_mode(mode)
return mode if Rubinius::Type.object_kind_of? mode, Integer
mode = StringValue(mode)
ret = 0
case mode[0]
when ?r
ret |= RDONLY
when ?w
ret |= WRONLY | CREAT | TRUNC
when ?a
ret |= WRONLY | CREAT | APPEND
else
raise ArgumentError, "invalid mode -- #{mode}"
end
return ret if mode.length == 1
case mode[1]
when ?+
ret &= ~(RDONLY | WRONLY)
ret |= RDWR
when ?b
ret |= BINARY
when ?:
warn("encoding options not supported in 1.8")
return ret
else
raise ArgumentError, "invalid mode -- #{mode}"
end
return ret if mode.length == 2
case mode[2]
when ?+
ret &= ~(RDONLY | WRONLY)
ret |= RDWR
when ?b
ret |= BINARY
when ?:
warn("encoding options not supported in 1.8")
return ret
else
raise ArgumentError, "invalid mode -- #{mode}"
end
ret
end
##
# Reads the entire file specified by name as individual
# lines, and returns those lines in an array. Lines are
# separated by sep_string.
#
# a = IO.readlines("testfile")
# a[0] #=> "This is line one\n"
def self.readlines(name, sep_string=$/)
name = StringValue name
if name[0] == ?|
io = IO.popen(name[1..-1], "r")
return nil unless io
else
io = File.open(name, 'r')
end
begin
io.readlines sep_string
ensure
io.close
end
end
#
# +select+ examines the IO object Arrays that are passed in
# as +readables+, +writables+, and +errorables+ to see if any
# of their descriptors are ready for reading, are ready for
# writing, or have an exceptions pending respectively. An IO
# may appear in more than one of the sets. Any of the three
# sets may be +nil+ if you are not interested in those events.
#
# If +timeout+ is not nil, it specifies the number of seconds
# to wait for events (maximum.) The number may be fractional,
# conceptually up to a microsecond resolution.
#
# A +timeout+ of 0 indicates that each descriptor should be
# checked once only, effectively polling the sets.
#
# Leaving the +timeout+ to +nil+ causes +select+ to block
# infinitely until an event transpires.
#
# If the timeout expires without events, +nil+ is returned.
# Otherwise, an [readable, writable, errors] Array of Arrays
# is returned, only, with the IO objects that have events.
#
# @compatibility MRI 1.8 and 1.9 require the +readables+ Array,
# Rubinius does not.
#
def self.select(readables=nil, writables=nil, errorables=nil, timeout=nil)
if timeout
unless Rubinius::Type.object_kind_of? timeout, Numeric
raise TypeError, "Timeout must be numeric"
end
raise ArgumentError, 'timeout must be positive' if timeout < 0
# Microseconds, rounded down
timeout = Integer(timeout * 1_000_000)
end
if readables
readables =
Rubinius::Type.coerce_to(readables, Array, :to_ary).map do |obj|
if obj.kind_of? IO
raise IOError, "closed stream" if obj.closed?
return [[obj],[],[]] unless obj.buffer_empty?
obj
else
io = Rubinius::Type.coerce_to(obj, IO, :to_io)
raise IOError, "closed stream" if io.closed?
[obj, io]
end
end
end
if writables
writables =
Rubinius::Type.coerce_to(writables, Array, :to_ary).map do |obj|
if obj.kind_of? IO
raise IOError, "closed stream" if obj.closed?
obj
else
io = Rubinius::Type.coerce_to(obj, IO, :to_io)
raise IOError, "closed stream" if io.closed?
[obj, io]
end
end
end
if errorables
errorables =
Rubinius::Type.coerce_to(errorables, Array, :to_ary).map do |obj|
if obj.kind_of? IO
raise IOError, "closed stream" if obj.closed?
obj
else
io = Rubinius::Type.coerce_to(obj, IO, :to_io)
raise IOError, "closed stream" if io.closed?
[obj, io]
end
end
end
IO.select_primitive(readables, writables, errorables, timeout)
end
##
# Opens the given path, returning the underlying file descriptor as a Fixnum.
# IO.sysopen("testfile") #=> 3
def self.sysopen(path, mode = nil, perm = nil)
path = Rubinius::Type.coerce_to_path path
mode = parse_mode(mode || "r")
perm ||= 0666
open_with_mode path, mode, perm
end
#
# Internally associate +io+ with the given descriptor.
#
# The +mode+ will be checked and set as the current mode if
# the underlying descriptor allows it.
#
# The +sync+ attribute will also be set.
#
def self.setup(io, fd, mode=nil, sync=false)
cur_mode = FFI::Platform::POSIX.fcntl(fd, F_GETFL, 0)
Errno.handle if cur_mode < 0
cur_mode &= ACCMODE
if mode
mode = parse_mode(mode)
mode &= ACCMODE
if (cur_mode == RDONLY or cur_mode == WRONLY) and mode != cur_mode
raise Errno::EINVAL, "Invalid new mode for existing descriptor #{fd}"
end
end
update_max_open_fd(fd)
io.descriptor = fd
io.mode = mode || cur_mode
io.sync = !!sync
io.sync ||= STDOUT.fileno == fd if STDOUT.respond_to?(:fileno)
io.sync ||= STDERR.fileno == fd if STDERR.respond_to?(:fileno)
end
@max_open_fd = 2
def self.update_max_open_fd(*fds)
fds.each do |fd|
@max_open_fd = fd if fd > @max_open_fd
end
end
def self.max_open_fd
@max_open_fd
end
##
# Obtains a new duplicate descriptor for the current one.
def initialize_copy(original) # :nodoc:
@descriptor = FFI::Platform::POSIX.dup(@descriptor)
end
private :initialize_copy
# Used to find out if there is buffered data available.
def buffer_empty?
@ibuffer.empty?
end
def <<(obj)
write(obj.to_s)
return self
end
def bytes
to_enum :each_byte
end
def chars
to_enum :each_char
end
##
# Closes the read end of a duplex I/O stream (i.e., one
# that contains both a read and a write stream, such as
# a pipe). Will raise an IOError if the stream is not duplexed.
#
# f = IO.popen("/bin/sh","r+")
# f.close_read
# f.readlines
# produces:
#
# prog.rb:3:in `readlines': not opened for reading (IOError)
# from prog.rb:3
def close_read
if @mode == WRONLY || @mode == RDWR
raise IOError, 'closing non-duplex IO for reading'
end
close
end
##
# Closes the write end of a duplex I/O stream (i.e., one
# that contains both a read and a write stream, such as
# a pipe). Will raise an IOError if the stream is not duplexed.
#
# f = IO.popen("/bin/sh","r+")
# f.close_write
# f.print "nowhere"
# produces:
#
# prog.rb:3:in `write': not opened for writing (IOError)
# from prog.rb:3:in `print'
# from prog.rb:3
def close_write
if @mode == RDONLY || @mode == RDWR
raise IOError, 'closing non-duplex IO for writing'
end
close
end
##
# Returns true if ios is completely closed (for duplex
# streams, both reader and writer), false otherwise.
#
# f = File.new("testfile")
# f.close #=> nil
# f.closed? #=> true
# f = IO.popen("/bin/sh","r+")
# f.close_write #=> nil
# f.closed? #=> false
# f.close_read #=> nil
# f.closed? #=> true
def closed?
@descriptor == -1
end
def dup
ensure_open
super
end
def each_byte
return to_enum(:each_byte) unless block_given?
yield getbyte until eof?
self
end
def each_char
return to_enum(:each_char) unless block_given?
ensure_open_and_readable
if Rubinius.kcode == :UTF8
# TODO zoinks. This is the slowest way possible to do this.
# We'll have to rewrite it.
lookup = 7.downto(4)
while c = read(1) do
n = c[0]
leftmost_zero_bit = lookup.find { |i| n[i] == 0 }
case leftmost_zero_bit
when 7 # ASCII
yield c
when 6 # UTF 8 complementary characters
next # Encoding error, ignore
else
more = read(6 - leftmost_zero_bit)
break unless more
yield c + more
end
end
else
while s = read(1)
yield s
end
end
self
end
##
# Set the pipe so it is at the end of the file
def eof!
@eof = true
end
##
# Returns true if ios is at end of file that means
# there are no more data to read. The stream must be
# opened for reading or an IOError will be raised.
#
# f = File.new("testfile")
# dummy = f.readlines
# f.eof #=> true
# If ios is a stream such as pipe or socket, IO#eof?
# blocks until the other end sends some data or closes it.
#
# r, w = IO.pipe
# Thread.new { sleep 1; w.close }
# r.eof? #=> true after 1 second blocking
#
# r, w = IO.pipe
# Thread.new { sleep 1; w.puts "a" }
# r.eof? #=> false after 1 second blocking
#
# r, w = IO.pipe
# r.eof? # blocks forever
#
# Note that IO#eof? reads data to a input buffer.
# So IO#sysread doesn't work with IO#eof?.
def eof?
ensure_open_and_readable
@ibuffer.fill_from self unless @ibuffer.exhausted?
@eof and @ibuffer.exhausted?
end
alias_method :eof, :eof?
def ensure_open_and_readable
ensure_open
write_only = @mode & ACCMODE == WRONLY
raise IOError, "not opened for reading" if write_only
end
def ensure_open_and_writable
ensure_open
read_only = @mode & ACCMODE == RDONLY
raise IOError, "not opened for writing" if read_only
end
##
# Provides a mechanism for issuing low-level commands to
# control or query file-oriented I/O streams. Arguments
# and results are platform dependent. If arg is a number,
# its value is passed directly. If it is a string, it is
# interpreted as a binary sequence of bytes (Array#pack
# might be a useful way to build this string). On Unix
# platforms, see fcntl(2) for details. Not implemented on all platforms.
def fcntl(command, arg=0)
ensure_open
if !arg
arg = 0
elsif arg == true
arg = 1
elsif arg.kind_of? String
raise NotImplementedError, "cannot handle String"
else
arg = Rubinius::Type.coerce_to arg, Fixnum, :to_int
end
command = Rubinius::Type.coerce_to command, Fixnum, :to_int
FFI::Platform::POSIX.fcntl descriptor, command, arg
end
##
# Provides a mechanism for issuing low-level commands to
# control or query file-oriented I/O streams. Arguments
# and results are platform dependent. If arg is a number,
# its value is passed directly. If it is a string, it is
# interpreted as a binary sequence of bytes (Array#pack
# might be a useful way to build this string). On Unix
# platforms, see fcntl(2) for details. Not implemented on all platforms.
def ioctl(command, arg=0)
ensure_open
if !arg
real_arg = 0
elsif arg == true
real_arg = 1
elsif arg.kind_of? String
# This could be faster.
buffer_size = arg.length
# On BSD and Linux, we could read the buffer size out of the ioctl value.
# Most Linux ioctl codes predate the convention, so a fallback like this
# is still necessary.
buffer_size = 4096 if buffer_size < 4096
buffer = FFI::MemoryPointer.new buffer_size
buffer.write_string arg, arg.length
real_arg = buffer.address
else
real_arg = Rubinius::Type.coerce_to arg, Fixnum, :to_int
end
command = Rubinius::Type.coerce_to command, Fixnum, :to_int
ret = FFI::Platform::POSIX.ioctl descriptor, command, real_arg
Errno.handle if ret < 0
if arg.kind_of?(String)
arg.replace buffer.read_string(buffer_size)
buffer.free
end
ret
end
##
# Returns an integer representing the numeric file descriptor for ios.
#
# $stdin.fileno #=> 0
# $stdout.fileno #=> 1
def fileno
ensure_open
@descriptor
end
alias_method :to_i, :fileno
##
# Flushes any buffered data within ios to the underlying
# operating system (note that this is Ruby internal
# buffering only; the OS may buffer the data as well).
#
# $stdout.print "no newline"
# $stdout.flush
# produces:
#
# no newline
def flush
ensure_open
@ibuffer.empty_to self
self
end
##
# Immediately writes all buffered data in ios to disk. Returns
# nil if the underlying operating system does not support fsync(2).
# Note that fsync differs from using IO#sync=. The latter ensures
# that data is flushed from Ruby's buffers, but does not guarantee
# that the underlying operating system actually writes it to disk.
def fsync
flush
err = FFI::Platform::POSIX.fsync @descriptor
Errno.handle 'fsync(2)' if err < 0
err
end
##
# Gets the next 8-bit byte (0..255) from ios.
# Returns nil if called at end of file.
#
# f = File.new("testfile")
# f.getc #=> 84
# f.getc #=> 104
def getc
ensure_open
if @ibuffer.size == 0
if @ibuffer.fill_from(self) == 0
return nil
end
end
return @ibuffer.get_first
end
def getbyte
char = read 1
return nil if char.nil?
char.bytes.to_a[0]
end
##
# Returns the current line number in ios. The
# stream must be opened for reading. lineno
# counts the number of times gets is called,
# rather than the number of newlines encountered.
# The two values will differ if gets is called with
# a separator other than newline. See also the $. variable.
#
# f = File.new("testfile")
# f.lineno #=> 0
# f.gets #=> "This is line one\n"
# f.lineno #=> 1
# f.gets #=> "This is line two\n"
# f.lineno #=> 2
def lineno
ensure_open
@lineno
end
##
# Manually sets the current line number to the
# given value. $. is updated only on the next read.
#
# f = File.new("testfile")
# f.gets #=> "This is line one\n"
# $. #=> 1
# f.lineno = 1000
# f.lineno #=> 1000
# $. # lineno of last read #=> 1
# f.gets #=> "This is line two\n"
# $. # lineno of last read #=> 1001
def lineno=(line_number)
ensure_open
raise TypeError if line_number.nil?
@lineno = Integer(line_number)
end
##
# FIXME
# Returns the process ID of a child process
# associated with ios. This will be set by IO::popen.
#
# pipe = IO.popen("-")
# if pipe
# $stderr.puts "In parent, child pid is #{pipe.pid}"
# else
# $stderr.puts "In child, pid is #{$$}"
# end
# produces:
#
# In child, pid is 26209
# In parent, child pid is 26209
def pid
raise IOError, 'closed stream' if closed?
@pid
end
attr_writer :pid
##
#
def pos
flush
@ibuffer.unseek! self
prim_seek 0, SEEK_CUR
end
alias_method :tell, :pos
##
# Seeks to the given position (in bytes) in ios.
#
# f = File.new("testfile")
# f.pos = 17
# f.gets #=> "This is line two\n"
def pos=(offset)
seek offset, SEEK_SET
end
##
# Writes each given argument.to_s to the stream or $_ (the result of last
# IO#gets) if called without arguments. Appends $\.to_s to output. Returns
# nil.
def print(*args)
if args.empty?
write $_.to_s
else
args.each { |o| write o.to_s }
end
write $\.to_s
nil
end
##
# Formats and writes to ios, converting parameters under
# control of the format string. See Kernel#sprintf for details.
def printf(fmt, *args)
fmt = StringValue(fmt)
write ::Rubinius::Sprinter.get(fmt).call(*args)
end
##
# If obj is Numeric, write the character whose code is obj,
# otherwise write the first character of the string
# representation of obj to ios.
#
# $stdout.putc "A"
# $stdout.putc 65
# produces:
#
# AA
def putc(obj)
if Rubinius::Type.object_kind_of? obj, String
write obj.substring(0, 1)
else
byte = Rubinius::Type.coerce_to(obj, Integer, :to_int) & 0xff
write byte.chr
end
return obj
end
##
# Reads at most _length_ bytes from the I/O stream, or to the
# end of file if _length_ is omitted or is +nil+. _length_
# must be a non-negative integer or +nil+. If the optional
# _buffer_ argument is present, it must reference a String,
# which will receive the data.
#
# At end of file, it returns +nil+ or +""+ depending on
# _length_. +_ios_.read()+ and +_ios_.read(nil)+ returns +""+.
# +_ios_.read(_positive-integer_)+ returns +nil+.
#
# f = File.new("testfile")
# f.read(16) #=> "This is line one"
def read(length=nil, buffer=nil)
ensure_open_and_readable
buffer = StringValue(buffer) if buffer
unless length
str = read_all
return str unless buffer
buffer.replace str
return buffer
end
return nil if @ibuffer.exhausted?
str = ""
needed = length
while needed > 0 and not @ibuffer.exhausted?
available = @ibuffer.fill_from self
count = available > needed ? needed : available
str << @ibuffer.shift(count)
str = nil if str.empty?
needed -= count
end
return str unless buffer
if str
buffer.replace str
buffer
else
buffer.replace ''
nil
end
end
##
# Reads all input until +#eof?+ is true. Returns the input read.
# If the buffer is already exhausted, returns +""+.
def read_all
str = ""
until @ibuffer.exhausted?
@ibuffer.fill_from self
str << @ibuffer.shift
end
str
end
private :read_all
# defined in bootstrap, used here.
private :read_if_available
##
# Reads at most maxlen bytes from ios using read(2) system
# call after O_NONBLOCK is set for the underlying file descriptor.
#
# If the optional outbuf argument is present, it must reference
# a String, which will receive the data.
#
# read_nonblock just calls read(2). It causes all errors read(2)
# causes: EAGAIN, EINTR, etc. The caller should care such errors.
#
# read_nonblock causes EOFError on EOF.
#
# If the read buffer is not empty, read_nonblock reads from the
# buffer like readpartial. In this case, read(2) is not called.
def read_nonblock(size, buffer=nil)
raise ArgumentError, "illegal read size" if size < 0
ensure_open
buffer = StringValue buffer if buffer
if @ibuffer.size > 0
return @ibuffer.shift(size)
end
if str = read_if_available(size)
buffer.replace(str) if buffer
return str
else
raise EOFError, "stream closed"
end
end
##
# Reads a character as with IO#getc, but raises an EOFError on end of file.
def readchar
char = getc
raise EOFError, 'end of file reached' unless char
char
end
def readbyte
byte = getbyte
raise EOFError, "end of file reached" unless byte
raise EOFError, "end of file" unless bytes
byte
end
##
# Reads a line as with IO#gets, but raises an EOFError on end of file.
def readline(sep=$/)
out = gets(sep)
raise EOFError, "end of file" unless out
return out
end
##
# Reads all of the lines in ios, and returns them in an array.
# Lines are separated by the optional sep_string. If sep_string
# is nil, the rest of the stream is returned as a single record.
# The stream must be opened for reading or an IOError will be raised.
#
# f = File.new("testfile")
# f.readlines[0] #=> "This is line one\n"
def readlines(sep=$/)
sep = StringValue sep if sep
old_line = $_
ary = Array.new
while line = gets(sep)
ary << line
end
$_ = old_line
ary
end
##
# Reads at most maxlen bytes from the I/O stream. It blocks
# only if ios has no data immediately available. It doesn‘t
# block if some data available. If the optional outbuf argument
# is present, it must reference a String, which will receive the
# data. It raises EOFError on end of file.
#
# readpartial is designed for streams such as pipe, socket, tty,
# etc. It blocks only when no data immediately available. This
# means that it blocks only when following all conditions hold.
#
# the buffer in the IO object is empty.
# the content of the stream is empty.
# the stream is not reached to EOF.
# When readpartial blocks, it waits data or EOF on the stream.
# If some data is reached, readpartial returns with the data.
# If EOF is reached, readpartial raises EOFError.
#
# When readpartial doesn‘t blocks, it returns or raises immediately.
# If the buffer is not empty, it returns the data in the buffer.
# Otherwise if the stream has some content, it returns the data in
# the stream. Otherwise if the stream is reached to EOF, it raises EOFError.
#
# r, w = IO.pipe # buffer pipe content
# w << "abc" # "" "abc".
# r.readpartial(4096) #=> "abc" "" ""
# r.readpartial(4096) # blocks because buffer and pipe is empty.
#
# r, w = IO.pipe # buffer pipe content
# w << "abc" # "" "abc"
# w.close # "" "abc" EOF
# r.readpartial(4096) #=> "abc" "" EOF
# r.readpartial(4096) # raises EOFError
#
# r, w = IO.pipe # buffer pipe content
# w << "abc\ndef\n" # "" "abc\ndef\n"
# r.gets #=> "abc\n" "def\n" ""
# w << "ghi\n" # "def\n" "ghi\n"
# r.readpartial(4096) #=> "def\n" "" "ghi\n"
# r.readpartial(4096) #=> "ghi\n" "" ""
# Note that readpartial behaves similar to sysread. The differences are:
#
# If the buffer is not empty, read from the buffer instead
# of "sysread for buffered IO (IOError)".
# It doesn‘t cause Errno::EAGAIN and Errno::EINTR. When readpartial
# meets EAGAIN and EINTR by read system call, readpartial retry the system call.
# The later means that readpartial is nonblocking-flag insensitive. It
# blocks on the situation IO#sysread causes Errno::EAGAIN as if the fd is blocking mode.
def readpartial(size, buffer=nil)
raise ArgumentError, 'negative string size' unless size >= 0
ensure_open
if buffer
buffer = StringValue(buffer)
buffer.shorten! buffer.size
return buffer if size == 0
if @ibuffer.size > 0
data = @ibuffer.shift(size)
else
data = sysread(size)
end
buffer.replace(data)
return buffer
else
return "" if size == 0
if @ibuffer.size > 0
return @ibuffer.shift(size)
end
return sysread(size)
end
end
##
# Reassociates ios with the I/O stream given in other_IO or to
# a new stream opened on path. This may dynamically change the
# actual class of this stream.
#
# f1 = File.new("testfile")
# f2 = File.new("testfile")
# f2.readlines[0] #=> "This is line one\n"
# f2.reopen(f1) #=> #<File:testfile>
# f2.readlines[0] #=> "This is line one\n"
def reopen(other, mode=undefined)
if other.respond_to?(:to_io)
flush
if other.kind_of? IO
io = other
else
io = other.to_io
unless io.kind_of? IO
raise TypeError, "#to_io must return an instance of IO"
end
end
io.ensure_open
io.reset_buffering
reopen_io io
Rubinius::Unsafe.set_class self, io.class
if io.respond_to?(:path)
@path = io.path
end
else
flush unless closed?
# If a mode isn't passed in, use the mode that the IO is already in.
if mode.equal? undefined
mode = @mode
# If this IO was already opened for writing, we should
# create the target file if it doesn't already exist.
if (mode & RDWR == RDWR) || (mode & WRONLY == WRONLY)
mode |= CREAT
end
else
mode = IO.parse_mode(mode)
end
reopen_path Rubinius::Type.coerce_to_path(other), mode
seek 0, SEEK_SET
end
self
end
##
# Internal method used to reset the state of the buffer, including the
# physical position in the stream.
def reset_buffering
@ibuffer.unseek! self
end
##
# Positions ios to the beginning of input, resetting lineno to zero.
#
# f = File.new("testfile")
# f.readline #=> "This is line one\n"
# f.rewind #=> 0
# f.lineno #=> 0
# f.readline #=> "This is line one\n"
def rewind
seek 0
@lineno = 0
return 0
end
##
# Seeks to a given offset +amount+ in the stream according to the value of whence:
#
# IO::SEEK_CUR | Seeks to _amount_ plus current position
# --------------+----------------------------------------------------
# IO::SEEK_END | Seeks to _amount_ plus end of stream (you probably
# | want a negative value for _amount_)
# --------------+----------------------------------------------------
# IO::SEEK_SET | Seeks to the absolute location given by _amount_
# Example:
#
# f = File.new("testfile")
# f.seek(-13, IO::SEEK_END) #=> 0
# f.readline #=> "And so on...\n"
def seek(amount, whence=SEEK_SET)
flush
@ibuffer.unseek! self
@eof = false
prim_seek Integer(amount), whence
return 0
end
##
# Returns status information for ios as an object of type File::Stat.
#
# f = File.new("testfile")
# s = f.stat
# "%o" % s.mode #=> "100644"
# s.blksize #=> 4096
# s.atime #=> Wed Apr 09 08:53:54 CDT 2003
def stat
ensure_open
File::Stat.from_fd @descriptor
end
##
# Returns the current "sync mode" of ios. When sync mode is true,
# all output is immediately flushed to the underlying operating
# system and is not buffered by Ruby internally. See also IO#fsync.
#
# f = File.new("testfile")
# f.sync #=> false
def sync
ensure_open
@sync == true
end
##
# Sets the "sync mode" to true or false. When sync mode is true,
# all output is immediately flushed to the underlying operating
# system and is not buffered internally. Returns the new state.
# See also IO#fsync.
def sync=(v)
ensure_open
@sync = !!v
end
##
# Reads integer bytes from ios using a low-level read and returns
# them as a string. Do not mix with other methods that read from
# ios or you may get unpredictable results. Raises SystemCallError
# on error and EOFError at end of file.
#
# f = File.new("testfile")
# f.sysread(16) #=> "This is line one"
#
# @todo Improve reading into provided buffer.
#
def sysread(number_of_bytes, buffer=undefined)
flush
raise IOError unless @ibuffer.empty?
str = read_primitive number_of_bytes
raise EOFError if str.nil?
unless buffer.equal? undefined
StringValue(buffer).replace str
end
str
end
##
# Seeks to a given offset in the stream according to the value
# of whence (see IO#seek for values of whence). Returns the new offset into the file.
#
# f = File.new("testfile")
# f.sysseek(-13, IO::SEEK_END) #=> 53
# f.sysread(10) #=> "And so on."
def sysseek(amount, whence=SEEK_SET)
ensure_open
if @ibuffer.write_synced?
raise IOError unless @ibuffer.empty?
else
warn 'sysseek for buffered IO'
end
amount = Integer(amount)
prim_seek amount, whence
end
def to_io
self
end
##
# Returns true if ios is associated with a terminal device (tty), false otherwise.
#
# File.new("testfile").isatty #=> false
# File.new("/dev/tty").isatty #=> true
def tty?
ensure_open
FFI::Platform::POSIX.isatty(@descriptor) == 1
end
alias_method :isatty, :tty?
alias_method :prim_write, :write
alias_method :prim_close, :close
##
# Pushes back one character (passed as a parameter) onto ios,
# such that a subsequent buffered read will return it. Only one
# character may be pushed back before a subsequent read operation
# (that is, you will be able to read only the last of several
# characters that have been pushed back). Has no effect with
# unbuffered reads (such as IO#sysread).
#
# f = File.new("testfile") #=> #<File:testfile>
# c = f.getc #=> 84
# f.ungetc(c) #=> nil
# f.getc #=> 84
def ungetc(chr)
ensure_open
@ibuffer.put_back chr
nil
end
def write(data)
data = String data
return 0 if data.length == 0
ensure_open_and_writable
if @sync
prim_write(data)
else
@ibuffer.unseek! self
bytes_to_write = data.size
while bytes_to_write > 0
bytes_to_write -= @ibuffer.unshift(data, data.size - bytes_to_write)
@ibuffer.empty_to self if @ibuffer.full? or sync
end
end
data.size
end
def syswrite(data)
data = String data
return 0 if data.length == 0
ensure_open_and_writable
@ibuffer.unseek!(self) unless @sync
prim_write(data)
end
def write_nonblock(data)
ensure_open_and_writable
data = String data
return 0 if data.length == 0
@ibuffer.unseek!(self) unless @sync
raw_write(data)
end
def close
begin
flush
ensure
prim_close
end
if @pid and @pid != 0
Process.wait @pid
@pid = nil
end
return nil
end
end
##
# Implements the pipe returned by IO::pipe.
class IO::BidirectionalPipe < IO
def set_pipe_info(write)
@write = write
@sync = true
end
##
# Closes ios and flushes any pending writes to the
# operating system. The stream is unavailable for
# any further data operations; an IOError is raised
# if such an attempt is made. I/O streams are
# automatically closed when they are claimed by
# the garbage collector.
#
# If ios is opened by IO.popen, close sets $?.
def close
@write.close unless @write.closed?
super unless closed?
nil
end
def closed?
super and @write.closed?
end
def close_read
raise IOError, 'closed stream' if closed?
close
end
def close_write
raise IOError, 'closed stream' if @write.closed?
@write.close
end
# Expand these out rather than using some metaprogramming because it's a fixed
# set and it's faster to have them as normal methods because then InlineCaches
# work right.
#
def <<(obj)
@write << obj
end
def print(*args)
@write.print(*args)
end
def printf(fmt, *args)
@write.printf(fmt, *args)
end
def putc(obj)
@write.putc(obj)
end
def puts(*args)
@write.puts(*args)
end
def syswrite(data)
@write.syswrite(data)
end
def write(data)
@write.write(data)
end
def write_nonblock(data)
@write.write_nonblock(data)
end
end
Jump to Line
Something went wrong with that request. Please try again.