summaryrefslogtreecommitdiff
path: root/io.c
diff options
context:
space:
mode:
Diffstat (limited to 'io.c')
-rw-r--r--io.c2581
1 files changed, 1601 insertions, 980 deletions
diff --git a/io.c b/io.c
index f2c64989df..feff445943 100644
--- a/io.c
+++ b/io.c
@@ -16,11 +16,6 @@
#include "ruby/fiber/scheduler.h"
#include "ruby/io/buffer.h"
-#ifdef _WIN32
-# include "ruby/ruby.h"
-# include "ruby/io.h"
-#endif
-
#include <ctype.h>
#include <errno.h>
#include <stddef.h>
@@ -75,10 +70,6 @@
#include <sys/fcntl.h>
#endif
-#if !HAVE_OFF_T && !defined(off_t)
-# define off_t long
-#endif
-
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
@@ -121,6 +112,7 @@
#include "encindex.h"
#include "id.h"
#include "internal.h"
+#include "internal/class.h"
#include "internal/encoding.h"
#include "internal/error.h"
#include "internal/inits.h"
@@ -149,10 +141,6 @@
#define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR)
#endif
-#if SIZEOF_OFF_T > SIZEOF_LONG && !defined(HAVE_LONG_LONG)
-# error off_t is bigger than long, but you have no long long...
-#endif
-
#ifndef PIPE_BUF
# ifdef _POSIX_PIPE_BUF
# define PIPE_BUF _POSIX_PIPE_BUF
@@ -175,17 +163,21 @@ off_t __syscall(quad_t number, ...);
#define IO_RBUF_CAPA_FOR(fptr) (NEED_READCONV(fptr) ? IO_CBUF_CAPA_MIN : IO_RBUF_CAPA_MIN)
#define IO_WBUF_CAPA_MIN 8192
+#define IO_MAX_BUFFER_GROWTH 8 * 1024 * 1024 // 8MB
+
/* define system APIs */
#ifdef _WIN32
#undef open
#define open rb_w32_uopen
#undef rename
#define rename(f, t) rb_w32_urename((f), (t))
+#include "win32/file.h"
#endif
VALUE rb_cIO;
VALUE rb_eEOFError;
VALUE rb_eIOError;
+VALUE rb_eIOTimeoutError;
VALUE rb_mWaitReadable;
VALUE rb_mWaitWritable;
@@ -226,7 +218,7 @@ struct argf {
long lineno;
VALUE argv;
VALUE inplace;
- struct rb_io_enc_t encs;
+ struct rb_io_encoding encs;
int8_t init_p, next_p, binmode;
};
@@ -501,7 +493,7 @@ rb_cloexec_fcntl_dupfd(int fd, int minfd)
#if defined(_WIN32)
#define WAIT_FD_IN_WIN32(fptr) \
- (rb_w32_io_cancelable_p((fptr)->fd) ? Qnil : rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), Qnil))
+ (rb_w32_io_cancelable_p((fptr)->fd) ? Qnil : rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), RUBY_IO_TIMEOUT_DEFAULT))
#else
#define WAIT_FD_IN_WIN32(fptr)
#endif
@@ -530,7 +522,6 @@ rb_cloexec_fcntl_dupfd(int fd, int minfd)
static int io_fflush(rb_io_t *);
static rb_io_t *flush_before_seek(rb_io_t *fptr);
-#define FMODE_PREP (1<<16)
#define FMODE_SIGNAL_ON_EPIPE (1<<17)
#define fptr_signal_on_epipe(fptr) \
@@ -543,10 +534,12 @@ static rb_io_t *flush_before_seek(rb_io_t *fptr);
extern ID ruby_static_id_signo;
-NORETURN(static void raise_on_write(rb_io_t *fptr, int e, VALUE errinfo));
+NORETURN(static void rb_sys_fail_on_write(rb_io_t *fptr));
static void
-raise_on_write(rb_io_t *fptr, int e, VALUE errinfo)
+rb_sys_fail_on_write(rb_io_t *fptr)
{
+ int e = errno;
+ VALUE errinfo = rb_syserr_new_path(e, (fptr)->pathv);
#if defined EPIPE
if (fptr_signal_on_epipe(fptr) && (e == EPIPE)) {
const VALUE sig =
@@ -560,12 +553,6 @@ raise_on_write(rb_io_t *fptr, int e, VALUE errinfo)
rb_exc_raise(errinfo);
}
-#define rb_sys_fail_on_write(fptr) \
- do { \
- int e = errno; \
- raise_on_write(fptr, e, rb_syserr_new_path(e, (fptr)->pathv)); \
- } while (0)
-
#define NEED_NEWLINE_DECORATOR_ON_READ(fptr) ((fptr)->mode & FMODE_TEXTMODE)
#define NEED_NEWLINE_DECORATOR_ON_WRITE(fptr) ((fptr)->mode & FMODE_TEXTMODE)
#if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32)
@@ -620,7 +607,7 @@ raise_on_write(rb_io_t *fptr, int e, VALUE errinfo)
static void
io_unread(rb_io_t *fptr)
{
- off_t r, pos;
+ rb_off_t r, pos;
ssize_t read_size;
long i;
long newlines = 0;
@@ -839,6 +826,57 @@ rb_io_set_write_io(VALUE io, VALUE w)
/*
* call-seq:
+ * timeout -> duration or nil
+ *
+ * Get the internal timeout duration or nil if it was not set.
+ *
+ */
+VALUE
+rb_io_timeout(VALUE self)
+{
+ rb_io_t *fptr = rb_io_get_fptr(self);
+
+ return fptr->timeout;
+}
+
+/*
+ * call-seq:
+ * timeout = duration -> duration
+ * timeout = nil -> nil
+ *
+ * Sets the internal timeout to the specified duration or nil. The timeout
+ * applies to all blocking operations where possible.
+ *
+ * When the operation performs longer than the timeout set, IO::TimeoutError
+ * is raised.
+ *
+ * This affects the following methods (but is not limited to): #gets, #puts,
+ * #read, #write, #wait_readable and #wait_writable. This also affects
+ * blocking socket operations like Socket#accept and Socket#connect.
+ *
+ * Some operations like File#open and IO#close are not affected by the
+ * timeout. A timeout during a write operation may leave the IO in an
+ * inconsistent state, e.g. data was partially written. Generally speaking, a
+ * timeout is a last ditch effort to prevent an application from hanging on
+ * slow I/O operations, such as those that occur during a slowloris attack.
+ */
+VALUE
+rb_io_set_timeout(VALUE self, VALUE timeout)
+{
+ // Validate it:
+ if (RTEST(timeout)) {
+ rb_time_interval(timeout);
+ }
+
+ rb_io_t *fptr = rb_io_get_fptr(self);
+
+ fptr->timeout = timeout;
+
+ return self;
+}
+
+/*
+ * call-seq:
* IO.try_convert(object) -> new_io or nil
*
* Attempts to convert +object+ into an \IO object via method +to_io+;
@@ -859,7 +897,7 @@ rb_io_s_try_convert(VALUE dummy, VALUE io)
static void
io_unread(rb_io_t *fptr)
{
- off_t r;
+ rb_off_t r;
rb_io_check_closed(fptr);
if (fptr->rbuf.len == 0 || fptr->mode & FMODE_DUPLEX)
return;
@@ -1008,7 +1046,7 @@ void
rb_io_read_check(rb_io_t *fptr)
{
if (!READ_DATA_PENDING(fptr)) {
- rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), Qnil);
+ rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), RUBY_IO_TIMEOUT_DEFAULT);
}
return;
}
@@ -1023,20 +1061,24 @@ rb_gc_for_fd(int err)
return 0;
}
+/* try `expr` upto twice while it returns false and `errno`
+ * is to GC. Each `errno`s are available as `first_errno` and
+ * `retried_errno` respectively */
+#define TRY_WITH_GC(expr) \
+ for (int first_errno, retried_errno = 0, retried = 0; \
+ (!retried && \
+ !(expr) && \
+ (!rb_gc_for_fd(first_errno = errno) || !(expr)) && \
+ (retried_errno = errno, 1)); \
+ (void)retried_errno, retried = 1)
+
static int
ruby_dup(int orig)
{
- int fd;
+ int fd = -1;
- fd = rb_cloexec_dup(orig);
- if (fd < 0) {
- int e = errno;
- if (rb_gc_for_fd(e)) {
- fd = rb_cloexec_dup(orig);
- }
- if (fd < 0) {
- rb_syserr_fail(e, 0);
- }
+ TRY_WITH_GC((fd = rb_cloexec_dup(orig)) >= 0) {
+ rb_syserr_fail(first_errno, 0);
}
rb_update_max_fd(fd);
return fd;
@@ -1045,7 +1087,7 @@ ruby_dup(int orig)
static VALUE
io_alloc(VALUE klass)
{
- NEWOBJ_OF(io, struct RFile, klass, T_FILE);
+ NEWOBJ_OF(io, struct RFile, klass, T_FILE, sizeof(struct RFile), 0);
io->fptr = 0;
@@ -1060,56 +1102,129 @@ struct io_internal_read_struct {
VALUE th;
rb_io_t *fptr;
int nonblock;
+ int fd;
+
void *buf;
size_t capa;
+ struct timeval *timeout;
};
struct io_internal_write_struct {
+ VALUE th;
+ rb_io_t *fptr;
+ int nonblock;
int fd;
+
const void *buf;
size_t capa;
+ struct timeval *timeout;
};
#ifdef HAVE_WRITEV
struct io_internal_writev_struct {
+ VALUE th;
+ rb_io_t *fptr;
+ int nonblock;
int fd;
+
int iovcnt;
const struct iovec *iov;
+ struct timeval *timeout;
};
#endif
-static int nogvl_wait_for(VALUE th, rb_io_t *fptr, short events);
+static int nogvl_wait_for(VALUE th, rb_io_t *fptr, short events, struct timeval *timeout);
+
+/**
+ * Wait for the given events on the given file descriptor.
+ * Returns -1 if an error or timeout occurred. +errno+ will be set.
+ * Returns the event mask if an event occurred.
+ */
+static inline int
+io_internal_wait(VALUE thread, rb_io_t *fptr, int error, int events, struct timeval *timeout)
+{
+ if (!timeout && rb_thread_mn_schedulable(thread)) {
+ RUBY_ASSERT(errno == EWOULDBLOCK || errno == EAGAIN);
+ return -1;
+ }
+
+ int ready = nogvl_wait_for(thread, fptr, events, timeout);
+
+ if (ready > 0) {
+ return ready;
+ }
+ else if (ready == 0) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+
+ errno = error;
+ return -1;
+}
+
static VALUE
internal_read_func(void *ptr)
{
struct io_internal_read_struct *iis = ptr;
- ssize_t r;
-retry:
- r = read(iis->fptr->fd, iis->buf, iis->capa);
- if (r < 0 && !iis->nonblock) {
- int e = errno;
- if (io_again_p(e)) {
- if (nogvl_wait_for(iis->th, iis->fptr, RB_WAITFD_IN) != -1) {
+ ssize_t result;
+
+ if (iis->timeout && !iis->nonblock) {
+ if (io_internal_wait(iis->th, iis->fptr, 0, RB_WAITFD_IN, iis->timeout) == -1) {
+ return -1;
+ }
+ }
+
+ retry:
+ result = read(iis->fd, iis->buf, iis->capa);
+
+ if (result < 0 && !iis->nonblock) {
+ if (io_again_p(errno)) {
+ if (io_internal_wait(iis->th, iis->fptr, errno, RB_WAITFD_IN, iis->timeout) == -1) {
+ return -1;
+ }
+ else {
goto retry;
}
- errno = e;
}
}
- return r;
+
+ return result;
}
#if defined __APPLE__
-# define do_write_retry(code) do {ret = code;} while (ret == -1 && errno == EPROTOTYPE)
+# define do_write_retry(code) do {result = code;} while (result == -1 && errno == EPROTOTYPE)
#else
-# define do_write_retry(code) ret = code
+# define do_write_retry(code) result = code
#endif
+
static VALUE
internal_write_func(void *ptr)
{
struct io_internal_write_struct *iis = ptr;
- ssize_t ret;
+ ssize_t result;
+
+ if (iis->timeout && !iis->nonblock) {
+ if (io_internal_wait(iis->th, iis->fptr, 0, RB_WAITFD_OUT, iis->timeout) == -1) {
+ return -1;
+ }
+ }
+
+ retry:
do_write_retry(write(iis->fd, iis->buf, iis->capa));
- return (VALUE)ret;
+
+ if (result < 0 && !iis->nonblock) {
+ int e = errno;
+ if (io_again_p(e)) {
+ if (io_internal_wait(iis->th, iis->fptr, errno, RB_WAITFD_OUT, iis->timeout) == -1) {
+ return -1;
+ }
+ else {
+ goto retry;
+ }
+ }
+ }
+
+ return result;
}
#ifdef HAVE_WRITEV
@@ -1117,20 +1232,40 @@ static VALUE
internal_writev_func(void *ptr)
{
struct io_internal_writev_struct *iis = ptr;
- ssize_t ret;
+ ssize_t result;
+
+ if (iis->timeout && !iis->nonblock) {
+ if (io_internal_wait(iis->th, iis->fptr, 0, RB_WAITFD_OUT, iis->timeout) == -1) {
+ return -1;
+ }
+ }
+
+ retry:
do_write_retry(writev(iis->fd, iis->iov, iis->iovcnt));
- return (VALUE)ret;
+
+ if (result < 0 && !iis->nonblock) {
+ if (io_again_p(errno)) {
+ if (io_internal_wait(iis->th, iis->fptr, errno, RB_WAITFD_OUT, iis->timeout) == -1) {
+ return -1;
+ }
+ else {
+ goto retry;
+ }
+ }
+ }
+
+ return result;
}
#endif
static ssize_t
-rb_read_internal(rb_io_t *fptr, void *buf, size_t count)
+rb_io_read_memory(rb_io_t *fptr, void *buf, size_t count)
{
VALUE scheduler = rb_fiber_scheduler_current();
if (scheduler != Qnil) {
VALUE result = rb_fiber_scheduler_io_read_memory(scheduler, fptr->self, buf, count, 0);
- if (result != Qundef) {
+ if (!UNDEF_P(result)) {
return rb_fiber_scheduler_io_result_apply(result);
}
}
@@ -1139,56 +1274,91 @@ rb_read_internal(rb_io_t *fptr, void *buf, size_t count)
.th = rb_thread_current(),
.fptr = fptr,
.nonblock = 0,
+ .fd = fptr->fd,
+
.buf = buf,
- .capa = count
+ .capa = count,
+ .timeout = NULL,
};
- return (ssize_t)rb_thread_io_blocking_region(internal_read_func, &iis, fptr->fd);
+ struct timeval timeout_storage;
+
+ if (fptr->timeout != Qnil) {
+ timeout_storage = rb_time_interval(fptr->timeout);
+ iis.timeout = &timeout_storage;
+ }
+
+ return (ssize_t)rb_thread_io_blocking_call(internal_read_func, &iis, fptr->fd, RB_WAITFD_IN);
}
static ssize_t
-rb_write_internal(rb_io_t *fptr, const void *buf, size_t count)
+rb_io_write_memory(rb_io_t *fptr, const void *buf, size_t count)
{
VALUE scheduler = rb_fiber_scheduler_current();
if (scheduler != Qnil) {
VALUE result = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, buf, count, 0);
- if (result != Qundef) {
+ if (!UNDEF_P(result)) {
return rb_fiber_scheduler_io_result_apply(result);
}
}
struct io_internal_write_struct iis = {
+ .th = rb_thread_current(),
+ .fptr = fptr,
+ .nonblock = 0,
.fd = fptr->fd,
+
.buf = buf,
- .capa = count
+ .capa = count,
+ .timeout = NULL
};
- return (ssize_t)rb_thread_io_blocking_region(internal_write_func, &iis, fptr->fd);
+ struct timeval timeout_storage;
+
+ if (fptr->timeout != Qnil) {
+ timeout_storage = rb_time_interval(fptr->timeout);
+ iis.timeout = &timeout_storage;
+ }
+
+ return (ssize_t)rb_thread_io_blocking_call(internal_write_func, &iis, fptr->fd, RB_WAITFD_OUT);
}
#ifdef HAVE_WRITEV
static ssize_t
rb_writev_internal(rb_io_t *fptr, const struct iovec *iov, int iovcnt)
{
+ if (!iovcnt) return 0;
+
VALUE scheduler = rb_fiber_scheduler_current();
if (scheduler != Qnil) {
- for (int i = 0; i < iovcnt; i += 1) {
- VALUE result = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, iov[i].iov_base, iov[i].iov_len, 0);
+ // This path assumes at least one `iov`:
+ VALUE result = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, iov[0].iov_base, iov[0].iov_len, 0);
- if (result != Qundef) {
- return rb_fiber_scheduler_io_result_apply(result);
- }
+ if (!UNDEF_P(result)) {
+ return rb_fiber_scheduler_io_result_apply(result);
}
}
struct io_internal_writev_struct iis = {
+ .th = rb_thread_current(),
+ .fptr = fptr,
+ .nonblock = 0,
.fd = fptr->fd,
+
.iov = iov,
.iovcnt = iovcnt,
+ .timeout = NULL
};
- return (ssize_t)rb_thread_io_blocking_region(internal_writev_func, &iis, fptr->fd);
+ struct timeval timeout_storage;
+
+ if (fptr->timeout != Qnil) {
+ timeout_storage = rb_time_interval(fptr->timeout);
+ iis.timeout = &timeout_storage;
+ }
+
+ return (ssize_t)rb_thread_io_blocking_call(internal_writev_func, &iis, fptr->fd, RB_WAITFD_OUT);
}
#endif
@@ -1204,11 +1374,13 @@ io_flush_buffer_sync(void *arg)
fptr->wbuf.len = 0;
return 0;
}
+
if (0 <= r) {
fptr->wbuf.off += (int)r;
fptr->wbuf.len -= (int)r;
errno = EAGAIN;
}
+
return (VALUE)-1;
}
@@ -1216,7 +1388,7 @@ static VALUE
io_flush_buffer_async(VALUE arg)
{
rb_io_t *fptr = (rb_io_t *)arg;
- return rb_thread_io_blocking_region(io_flush_buffer_sync, fptr, fptr->fd);
+ return rb_thread_io_blocking_call(io_flush_buffer_sync, fptr, fptr->fd, RB_WAITFD_OUT);
}
static inline int
@@ -1239,7 +1411,7 @@ io_fflush(rb_io_t *fptr)
return 0;
while (fptr->wbuf.len > 0 && io_flush_buffer(fptr) != 0) {
- if (!rb_io_maybe_wait_writable(errno, fptr->self, Qnil))
+ if (!rb_io_maybe_wait_writable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT))
return -1;
rb_io_check_closed(fptr);
@@ -1263,6 +1435,10 @@ rb_io_wait(VALUE io, VALUE events, VALUE timeout)
struct timeval tv_storage;
struct timeval *tv = NULL;
+ if (NIL_OR_UNDEF_P(timeout)) {
+ timeout = fptr->timeout;
+ }
+
if (timeout != Qnil) {
tv_storage = rb_time_interval(timeout);
tv = &tv_storage;
@@ -1288,7 +1464,7 @@ rb_io_wait(VALUE io, VALUE events, VALUE timeout)
static VALUE
io_from_fd(int fd)
{
- return prep_io(fd, FMODE_PREP, rb_cIO, NULL);
+ return prep_io(fd, FMODE_EXTERNAL, rb_cIO, NULL);
}
static int
@@ -1481,7 +1657,7 @@ make_writeconv(rb_io_t *fptr)
ecflags = fptr->encs.ecflags & ~ECONV_NEWLINE_DECORATOR_READ_MASK;
ecopts = fptr->encs.ecopts;
- if (!fptr->encs.enc || (fptr->encs.enc == rb_ascii8bit_encoding() && !fptr->encs.enc2)) {
+ if (!fptr->encs.enc || (rb_is_ascii8bit_enc(fptr->encs.enc) && !fptr->encs.enc2)) {
/* no encoding conversion */
fptr->writeconv_pre_ecflags = 0;
fptr->writeconv_pre_ecopts = Qnil;
@@ -1525,7 +1701,6 @@ make_writeconv(rb_io_t *fptr)
/* writing functions */
struct binwrite_arg {
rb_io_t *fptr;
- VALUE str;
const char *ptr;
long length;
};
@@ -1570,7 +1745,7 @@ io_binwrite_string_internal(rb_io_t *fptr, const char *ptr, long length)
return result;
}
else {
- return rb_write_internal(fptr, ptr, length);
+ return rb_io_write_memory(fptr, ptr, length);
}
}
#else
@@ -1605,7 +1780,7 @@ io_binwrite_string_internal(rb_io_t *fptr, const char *ptr, long length)
}
// Otherwise, we should write the data directly:
- return rb_write_internal(fptr, ptr, length);
+ return rb_io_write_memory(fptr, ptr, length);
}
#endif
@@ -1621,19 +1796,17 @@ io_binwrite_string(VALUE arg)
// Write as much as possible:
ssize_t result = io_binwrite_string_internal(p->fptr, ptr, remaining);
- // If only the internal buffer is written, result will be zero [bytes of given data written]. This means we
- // should try again.
if (result == 0) {
- errno = EWOULDBLOCK;
+ // If only the internal buffer is written, result will be zero [bytes of given data written]. This means we
+ // should try again immediately.
}
-
- if (result > 0) {
+ else if (result > 0) {
if ((size_t)result == remaining) break;
ptr += result;
remaining -= result;
}
// Wait for it to become writable:
- else if (rb_io_maybe_wait_writable(errno, p->fptr->self, Qnil)) {
+ else if (rb_io_maybe_wait_writable(errno, p->fptr->self, RUBY_IO_TIMEOUT_DEFAULT)) {
rb_io_check_closed(p->fptr);
}
else {
@@ -1664,7 +1837,7 @@ io_allocate_write_buffer(rb_io_t *fptr, int sync)
static inline int
io_binwrite_requires_flush_write(rb_io_t *fptr, long len, int nosync)
{
- // If the requested operation was synchronous and the output mode is synchronus or a TTY:
+ // If the requested operation was synchronous and the output mode is synchronous or a TTY:
if (!nosync && (fptr->mode & (FMODE_SYNC|FMODE_TTY)))
return 1;
@@ -1677,7 +1850,7 @@ io_binwrite_requires_flush_write(rb_io_t *fptr, long len, int nosync)
}
static long
-io_binwrite(VALUE str, const char *ptr, long len, rb_io_t *fptr, int nosync)
+io_binwrite(const char *ptr, long len, rb_io_t *fptr, int nosync)
{
if (len <= 0) return len;
@@ -1690,7 +1863,6 @@ io_binwrite(VALUE str, const char *ptr, long len, rb_io_t *fptr, int nosync)
struct binwrite_arg arg;
arg.fptr = fptr;
- arg.str = str;
arg.ptr = ptr;
arg.length = len;
@@ -1798,9 +1970,9 @@ io_fwrite(VALUE str, rb_io_t *fptr, int nosync)
if (converted)
OBJ_FREEZE(str);
- tmp = rb_str_tmp_frozen_acquire(str);
+ tmp = rb_str_tmp_frozen_no_embed_acquire(str);
RSTRING_GETMEM(tmp, ptr, len);
- n = io_binwrite(tmp, ptr, len, fptr, nosync);
+ n = io_binwrite(ptr, len, fptr, nosync);
rb_str_tmp_frozen_release(str, tmp);
return n;
@@ -1813,7 +1985,7 @@ rb_io_bufwrite(VALUE io, const void *buf, size_t size)
GetOpenFile(io, fptr);
rb_io_check_writable(fptr);
- return (ssize_t)io_binwrite(0, buf, (long)size, fptr, 0);
+ return (ssize_t)io_binwrite(buf, (long)size, fptr, 0);
}
static VALUE
@@ -1867,7 +2039,7 @@ io_binwritev_internal(VALUE arg)
while (remaining) {
long result = rb_writev_internal(fptr, iov, iovcnt);
- if (result > 0) {
+ if (result >= 0) {
offset += result;
if (fptr->wbuf.ptr && fptr->wbuf.len) {
if (offset < (size_t)fptr->wbuf.len) {
@@ -1900,7 +2072,7 @@ io_binwritev_internal(VALUE arg)
iov->iov_base = (char *)iov->iov_base + result;
iov->iov_len -= result;
}
- else if (rb_io_maybe_wait_writable(errno, fptr->self, Qnil)) {
+ else if (rb_io_maybe_wait_writable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT)) {
rb_io_check_closed(fptr);
}
else {
@@ -2065,7 +2237,8 @@ io_writev(int argc, const VALUE *argv, VALUE io)
* write(*objects) -> integer
*
* Writes each of the given +objects+ to +self+,
- * which must be opened for writing (see IO@Modes);
+ * which must be opened for writing
+ * (see {Access Modes}[rdoc-ref:File@Access+Modes]);
* returns the total number bytes written;
* each of +objects+ that is not a string is converted via method +to_s+:
*
@@ -2077,6 +2250,7 @@ io_writev(int argc, const VALUE *argv, VALUE io)
* Hello, World!
* foobar2
*
+ * Related: IO#read.
*/
static VALUE
@@ -2103,7 +2277,7 @@ rb_io_writev(VALUE io, int argc, const VALUE *argv)
if (argc > 1 && rb_obj_method_arity(io, id_write) == 1) {
if (io != rb_ractor_stderr() && RTEST(ruby_verbose)) {
VALUE klass = CLASS_OF(io);
- char sep = FL_TEST(klass, FL_SINGLETON) ? (klass = io, '.') : '#';
+ char sep = RCLASS_SINGLETON_P(klass) ? (klass = io, '.') : '#';
rb_category_warning(
RB_WARN_CATEGORY_DEPRECATED, "%+"PRIsVALUE"%c""write is outdated interface"
" which accepts just one argument",
@@ -2124,7 +2298,7 @@ rb_io_writev(VALUE io, int argc, const VALUE *argv)
* self << object -> self
*
* Writes the given +object+ to +self+,
- * which must be opened for writing (see IO@Modes);
+ * which must be opened for writing (see {Access Modes}[rdoc-ref:File@Access+Modes]);
* returns +self+;
* if +object+ is not a string, it is converted via method +to_s+:
*
@@ -2215,16 +2389,13 @@ rb_io_flush(VALUE io)
* f.close
*
* Related: IO#pos=, IO#seek.
- *
- * IO#pos is an alias for IO#tell.
- *
*/
static VALUE
rb_io_tell(VALUE io)
{
rb_io_t *fptr;
- off_t pos;
+ rb_off_t pos;
GetOpenFile(io, fptr);
pos = io_tell(fptr);
@@ -2237,7 +2408,7 @@ static VALUE
rb_io_seek(VALUE io, VALUE offset, int whence)
{
rb_io_t *fptr;
- off_t pos;
+ rb_off_t pos;
pos = NUM2OFFT(offset);
GetOpenFile(io, fptr);
@@ -2348,7 +2519,7 @@ static VALUE
rb_io_set_pos(VALUE io, VALUE offset)
{
rb_io_t *fptr;
- off_t pos;
+ rb_off_t pos;
pos = NUM2OFFT(offset);
GetOpenFile(io, fptr);
@@ -2405,12 +2576,12 @@ rb_io_rewind(VALUE io)
static int
fptr_wait_readable(rb_io_t *fptr)
{
- int ret = rb_io_maybe_wait_readable(errno, fptr->self, Qnil);
+ int result = rb_io_maybe_wait_readable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT);
- if (ret)
+ if (result)
rb_io_check_closed(fptr);
- return ret;
+ return result;
}
static int
@@ -2429,7 +2600,7 @@ io_fillbuf(rb_io_t *fptr)
}
if (fptr->rbuf.len == 0) {
retry:
- r = rb_read_internal(fptr, fptr->rbuf.ptr, fptr->rbuf.capa);
+ r = rb_io_read_memory(fptr, fptr->rbuf.ptr, fptr->rbuf.capa);
if (r < 0) {
if (fptr_wait_readable(fptr))
@@ -2466,7 +2637,7 @@ io_fillbuf(rb_io_t *fptr)
* f.close
*
* Raises an exception unless the stream is opened for reading;
- * see {Mode}[rdoc-ref:IO@Mode].
+ * see {Mode}[rdoc-ref:File@Access+Modes].
*
* If +self+ is a stream such as pipe or socket, this method
* blocks until the other end sends some data or closes it:
@@ -2485,9 +2656,6 @@ io_fillbuf(rb_io_t *fptr)
* Note that this method reads data to the input byte buffer. So
* IO#sysread may not behave as you intend with IO#eof?, unless you
* call IO#rewind first (which is not available for some streams).
- *
- * IO#eof? is an alias for IO#eof.
- *
*/
VALUE
@@ -2503,7 +2671,7 @@ rb_io_eof(VALUE io)
READ_CHECK(fptr);
#if RUBY_CRLF_ENVIRONMENT
if (!NEED_READCONV(fptr) && NEED_NEWLINE_DECORATOR_ON_READ(fptr)) {
- return RBOOL(eof(fptr->fd));;
+ return RBOOL(eof(fptr->fd));
}
#endif
return RBOOL(io_fillbuf(fptr) < 0);
@@ -2678,8 +2846,6 @@ rb_io_fdatasync(VALUE io)
* File.open('t.txt').fileno # => 10
* f.close
*
- * IO#to_i is an alias for IO#fileno.
- *
*/
static VALUE
@@ -2702,8 +2868,23 @@ rb_io_descriptor(VALUE io)
return fptr->fd;
}
else {
- return RB_NUM2INT(rb_funcall(io, id_fileno, 0));
+ VALUE fileno = rb_check_funcall(io, id_fileno, 0, NULL);
+ if (!UNDEF_P(fileno)) {
+ return RB_NUM2INT(fileno);
+ }
}
+
+ rb_raise(rb_eTypeError, "expected IO or #fileno, %"PRIsVALUE" given", rb_obj_class(io));
+
+ UNREACHABLE_RETURN(-1);
+}
+
+int
+rb_io_mode(VALUE io)
+{
+ rb_io_t *fptr;
+ GetOpenFile(io, fptr);
+ return fptr->mode;
}
/*
@@ -2739,6 +2920,29 @@ rb_io_pid(VALUE io)
return PIDT2NUM(fptr->pid);
}
+/*
+ * call-seq:
+ * path -> string or nil
+ *
+ * Returns the path associated with the IO, or +nil+ if there is no path
+ * associated with the IO. It is not guaranteed that the path exists on
+ * the filesystem.
+ *
+ * $stdin.path # => "<STDIN>"
+ *
+ * File.open("testfile") {|f| f.path} # => "testfile"
+ */
+
+VALUE
+rb_io_path(VALUE io)
+{
+ rb_io_t *fptr = RFILE(io)->fptr;
+
+ if (!fptr)
+ return Qnil;
+
+ return rb_obj_dup(fptr->pathv);
+}
/*
* call-seq:
@@ -2821,7 +3025,7 @@ io_bufread(char *ptr, long len, rb_io_t *fptr)
while (n > 0) {
again:
rb_io_check_closed(fptr);
- c = rb_read_internal(fptr, ptr+offset, n);
+ c = rb_io_read_memory(fptr, ptr+offset, n);
if (c == 0) break;
if (c < 0) {
if (fptr_wait_readable(fptr))
@@ -2884,8 +3088,8 @@ static long
remain_size(rb_io_t *fptr)
{
struct stat st;
- off_t siz = READ_DATA_PENDING_COUNT(fptr);
- off_t pos;
+ rb_off_t siz = READ_DATA_PENDING_COUNT(fptr);
+ rb_off_t pos;
if (fstat(fptr->fd, &st) == 0 && S_ISREG(st.st_mode)
#if defined(__HAIKU__)
@@ -3061,7 +3265,8 @@ static int
io_setstrbuf(VALUE *str, long len)
{
#ifdef _WIN32
- len = (len + 1) & ~1L; /* round up for wide char */
+ if (len > 0)
+ len = (len + 1) & ~1L; /* round up for wide char */
#endif
if (NIL_P(*str)) {
*str = rb_str_new(0, len);
@@ -3069,14 +3274,17 @@ io_setstrbuf(VALUE *str, long len)
}
else {
VALUE s = StringValue(*str);
+ rb_str_modify(s);
+
long clen = RSTRING_LEN(s);
if (clen >= len) {
- rb_str_modify(s);
return FALSE;
}
len -= clen;
}
- rb_str_modify_expand(*str, len);
+ if ((rb_str_capacity(*str) - (size_t)RSTRING_LEN(*str)) < (size_t)len) {
+ rb_str_modify_expand(*str, len);
+ }
return FALSE;
}
@@ -3159,7 +3367,17 @@ read_all(rb_io_t *fptr, long siz, VALUE str)
pos += rb_str_coderange_scan_restartable(RSTRING_PTR(str) + pos, RSTRING_PTR(str) + bytes, enc, &cr);
if (bytes < siz) break;
siz += BUFSIZ;
- rb_str_modify_expand(str, BUFSIZ);
+
+ size_t capa = rb_str_capacity(str);
+ if (capa < (size_t)RSTRING_LEN(str) + BUFSIZ) {
+ if (capa < BUFSIZ) {
+ capa = BUFSIZ;
+ }
+ else if (capa > IO_MAX_BUFFER_GROWTH) {
+ capa = IO_MAX_BUFFER_GROWTH;
+ }
+ rb_str_modify_expand(str, capa);
+ }
}
if (shrinkable) io_shrink_read_string(str, RSTRING_LEN(str));
str = io_enc_str(str, fptr);
@@ -3176,7 +3394,7 @@ rb_io_set_nonblock(rb_io_t *fptr)
}
static VALUE
-read_internal_call(VALUE arg)
+io_read_memory_call(VALUE arg)
{
struct io_internal_read_struct *iis = (struct io_internal_read_struct *)arg;
@@ -3184,19 +3402,24 @@ read_internal_call(VALUE arg)
if (scheduler != Qnil) {
VALUE result = rb_fiber_scheduler_io_read_memory(scheduler, iis->fptr->self, iis->buf, iis->capa, 0);
- if (result != Qundef) {
+ if (!UNDEF_P(result)) {
// This is actually returned as a pseudo-VALUE and later cast to a long:
return (VALUE)rb_fiber_scheduler_io_result_apply(result);
}
}
- return rb_thread_io_blocking_region(internal_read_func, iis, iis->fptr->fd);
+ if (iis->nonblock) {
+ return rb_thread_io_blocking_call(internal_read_func, iis, iis->fptr->fd, 0);
+ }
+ else {
+ return rb_thread_io_blocking_call(internal_read_func, iis, iis->fptr->fd, RB_WAITFD_IN);
+ }
}
static long
-read_internal_locktmp(VALUE str, struct io_internal_read_struct *iis)
+io_read_memory_locktmp(VALUE str, struct io_internal_read_struct *iis)
{
- return (long)rb_str_locktmp_ensure(str, read_internal_call, (VALUE)iis);
+ return (long)rb_str_locktmp_ensure(str, io_read_memory_call, (VALUE)iis);
}
#define no_exception_p(opts) !rb_opts_exception_p((opts), TRUE)
@@ -3238,9 +3461,11 @@ io_getpartial(int argc, VALUE *argv, VALUE io, int no_exception, int nonblock)
iis.th = rb_thread_current();
iis.fptr = fptr;
iis.nonblock = nonblock;
+ iis.fd = fptr->fd;
iis.buf = RSTRING_PTR(str);
iis.capa = len;
- n = read_internal_locktmp(str, &iis);
+ iis.timeout = NULL;
+ n = io_read_memory_locktmp(str, &iis);
if (n < 0) {
int e = errno;
if (!nonblock && fptr_wait_readable(fptr))
@@ -3401,13 +3626,15 @@ io_read_nonblock(rb_execution_context_t *ec, VALUE io, VALUE length, VALUE str,
n = read_buffered_data(RSTRING_PTR(str), len, fptr);
if (n <= 0) {
- rb_io_set_nonblock(fptr);
+ rb_fd_set_nonblock(fptr->fd);
shrinkable |= io_setstrbuf(&str, len);
iis.fptr = fptr;
iis.nonblock = 1;
+ iis.fd = fptr->fd;
iis.buf = RSTRING_PTR(str);
iis.capa = len;
- n = read_internal_locktmp(str, &iis);
+ iis.timeout = NULL;
+ n = io_read_memory_locktmp(str, &iis);
if (n < 0) {
int e = errno;
if (io_again_p(e)) {
@@ -3446,7 +3673,7 @@ io_write_nonblock(rb_execution_context_t *ec, VALUE io, VALUE str, VALUE ex)
if (io_fflush(fptr) < 0)
rb_sys_fail_on_write(fptr);
- rb_io_set_nonblock(fptr);
+ rb_fd_set_nonblock(fptr->fd);
n = write(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str));
RB_GC_GUARD(str);
@@ -3468,14 +3695,13 @@ io_write_nonblock(rb_execution_context_t *ec, VALUE io, VALUE str, VALUE ex)
/*
* call-seq:
- * read(maxlen = nil) -> string or nil
- * read(maxlen = nil, out_string) -> out_string or nil
+ * read(maxlen = nil, out_string = nil) -> new_string, out_string, or nil
*
- * Reads bytes from the stream (in binary mode):
+ * Reads bytes from the stream; the stream must be opened for reading
+ * (see {Access Modes}[rdoc-ref:File@Access+Modes]):
*
- * - If +maxlen+ is +nil+, reads all bytes.
- * - Otherwise reads +maxlen+ bytes, if available.
- * - Otherwise reads all bytes.
+ * - If +maxlen+ is +nil+, reads all bytes using the stream's data mode.
+ * - Otherwise reads up to +maxlen+ bytes in binary mode.
*
* Returns a string (either a new string or the given +out_string+)
* containing the bytes read.
@@ -3535,6 +3761,7 @@ io_write_nonblock(rb_execution_context_t *ec, VALUE io, VALUE str, VALUE ex)
* If you need the behavior like a single read(2) system call,
* consider #readpartial, #read_nonblock, and #sysread.
*
+ * Related: IO#write.
*/
static VALUE
@@ -3798,7 +4025,7 @@ extract_getline_opts(VALUE opts, struct getline_arg *args)
kwds[0] = rb_intern_const("chomp");
}
rb_get_kwargs(opts, kwds, 0, -2, &vchomp);
- chomp = (vchomp != Qundef) && RTEST(vchomp);
+ chomp = (!UNDEF_P(vchomp)) && RTEST(vchomp);
}
args->chomp = chomp;
}
@@ -3929,8 +4156,7 @@ rb_io_getline_0(VALUE rs, long limit, int chomp, rb_io_t *fptr)
s = RSTRING_PTR(str);
e = RSTRING_END(str);
p = e - rslen;
- pp = rb_enc_left_char_head(s, p, e, enc);
- if (pp != p) continue;
+ if (!at_char_boundary(s, p, e, enc)) continue;
if (!rspara) rscheck(rsptr, rslen, rs);
if (memcmp(p, rsptr, rslen) == 0) {
if (chomp) {
@@ -4019,13 +4245,13 @@ rb_io_gets_internal(VALUE io)
/*
* call-seq:
- * gets(sep = $/, **line_opts) -> string or nil
- * gets(limit, **line_opts) -> string or nil
- * gets(sep, limit, **line_opts) -> string or nil
+ * gets(sep = $/, chomp: false) -> string or nil
+ * gets(limit, chomp: false) -> string or nil
+ * gets(sep, limit, chomp: false) -> string or nil
*
- * Reads and returns a line from the stream
- * (see {Lines}[rdoc-ref:IO@Lines]);
+ * Reads and returns a line from the stream;
* assigns the return value to <tt>$_</tt>.
+ * See {Line IO}[rdoc-ref:IO@Line+IO].
*
* With no arguments given, returns the next line
* as determined by line separator <tt>$/</tt>, or +nil+ if none:
@@ -4063,7 +4289,7 @@ rb_io_gets_internal(VALUE io)
*
* With only integer argument +limit+ given,
* limits the number of bytes in the line;
- * see {Line Limit}}[rdoc-ref:IO@Line+Limit]:
+ * see {Line Limit}[rdoc-ref:IO@Line+Limit]:
*
* # No more than one line.
* File.open('t.txt') {|f| f.gets(10) } # => "First line"
@@ -4071,14 +4297,11 @@ rb_io_gets_internal(VALUE io)
* File.open('t.txt') {|f| f.gets(12) } # => "First line\n"
*
* With arguments +sep+ and +limit+ given,
- * combines the two behaviors:
- *
- * - Returns the next line as determined by line separator +sep+,
- * or +nil+ if none.
- * - But returns no more bytes than are allowed by the limit.
+ * combines the two behaviors
+ * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]).
*
- * For all forms above, optional keyword arguments +line_opts+ specify
- * {Line Options}[rdoc-ref:IO@Line+Options]:
+ * Optional keyword argument +chomp+ specifies whether line separators
+ * are to be omitted:
*
* f = File.open('t.txt')
* # Chomp the lines.
@@ -4107,8 +4330,8 @@ rb_io_gets_m(int argc, VALUE *argv, VALUE io)
* call-seq:
* lineno -> integer
*
- * Returns the current line number for the stream.
- * See {Line Number}[rdoc-ref:IO@Line+Number].
+ * Returns the current line number for the stream;
+ * see {Line Number}[rdoc-ref:IO@Line+Number].
*
*/
@@ -4126,8 +4349,8 @@ rb_io_lineno(VALUE io)
* call-seq:
* lineno = integer -> integer
*
- * Sets and returns the line number for the stream.
- * See {Line Number}[rdoc-ref:IO@Line+Number].
+ * Sets and returns the line number for the stream;
+ * see {Line Number}[rdoc-ref:IO@Line+Number].
*
*/
@@ -4142,20 +4365,28 @@ rb_io_set_lineno(VALUE io, VALUE lineno)
return lineno;
}
-/*
- * call-seq:
- * readline(sep = $/, **line_opts) -> string
- * readline(limit, **line_opts) -> string
- * readline(sep, limit, **line_opts) -> string
- *
- * Reads a line as with IO#gets, but raises EOFError if already at end-of-file.
- *
- */
-
+/* :nodoc: */
static VALUE
-rb_io_readline(int argc, VALUE *argv, VALUE io)
+io_readline(rb_execution_context_t *ec, VALUE io, VALUE sep, VALUE lim, VALUE chomp)
{
- VALUE line = rb_io_gets_m(argc, argv, io);
+ if (NIL_P(lim)) {
+ // If sep is specified, but it's not a string and not nil, then assume
+ // it's the limit (it should be an integer)
+ if (!NIL_P(sep) && NIL_P(rb_check_string_type(sep))) {
+ // If the user has specified a non-nil / non-string value
+ // for the separator, we assume it's the limit and set the
+ // separator to default: rb_rs.
+ lim = sep;
+ sep = rb_rs;
+ }
+ }
+
+ if (!NIL_P(sep)) {
+ StringValue(sep);
+ }
+
+ VALUE line = rb_io_getline_1(sep, NIL_P(lim) ? -1L : NUM2LONG(lim), RTEST(chomp), io);
+ rb_lastline_set_up(line, 1);
if (NIL_P(line)) {
rb_eof_error();
@@ -4167,13 +4398,13 @@ static VALUE io_readlines(const struct getline_arg *arg, VALUE io);
/*
* call-seq:
- * readlines(sep = $/, **line_opts) -> array
- * readlines(limit, **line_opts) -> array
- * readlines(sep, limit, **line_opts) -> array
+ * readlines(sep = $/, chomp: false) -> array
+ * readlines(limit, chomp: false) -> array
+ * readlines(sep, limit, chomp: false) -> array
*
- * Reads and returns all remaining line from the stream
- * (see {Lines}[rdoc-ref:IO@Lines]);
+ * Reads and returns all remaining line from the stream;
* does not modify <tt>$_</tt>.
+ * See {Line IO}[rdoc-ref:IO@Line+IO].
*
* With no arguments given, returns lines
* as determined by line separator <tt>$/</tt>, or +nil+ if none:
@@ -4216,13 +4447,11 @@ static VALUE io_readlines(const struct getline_arg *arg, VALUE io);
* f.close
*
* With arguments +sep+ and +limit+ given,
- * combines the two behaviors:
- *
- * - Returns lines as determined by line separator +sep+.
- * - But returns no more bytes in a line than are allowed by the limit.
+ * combines the two behaviors
+ * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]).
*
- * For all forms above, optional keyword arguments +line_opts+ specify
- * {Line Options}[rdoc-ref:IO@Line+Options]:
+ * Optional keyword argument +chomp+ specifies whether line separators
+ * are to be omitted:
*
* f = File.new('t.txt')
* f.readlines(chomp: true)
@@ -4256,15 +4485,15 @@ io_readlines(const struct getline_arg *arg, VALUE io)
/*
* call-seq:
- * each_line(sep = $/, **line_opts) {|line| ... } -> self
- * each_line(limit, **line_opts) {|line| ... } -> self
- * each_line(sep, limit, **line_opts) {|line| ... } -> self
+ * each_line(sep = $/, chomp: false) {|line| ... } -> self
+ * each_line(limit, chomp: false) {|line| ... } -> self
+ * each_line(sep, limit, chomp: false) {|line| ... } -> self
* each_line -> enumerator
*
- * Calls the block with each remaining line read from the stream
- * (see {Lines}[rdoc-ref:IO@Lines]);
- * does nothing if already at end-of-file;
+ * Calls the block with each remaining line read from the stream;
* returns +self+.
+ * Does nothing if already at end-of-stream;
+ * See {Line IO}[rdoc-ref:IO@Line+IO].
*
* With no arguments given, reads lines
* as determined by line separator <tt>$/</tt>:
@@ -4320,7 +4549,7 @@ io_readlines(const struct getline_arg *arg, VALUE io)
*
* With only integer argument +limit+ given,
* limits the number of bytes in each line;
- * see {Line Limit}}[rdoc-ref:IO@Line+Limit]:
+ * see {Line Limit}[rdoc-ref:IO@Line+Limit]:
*
* f = File.new('t.txt')
* f.each_line(8) {|line| p line }
@@ -4339,13 +4568,11 @@ io_readlines(const struct getline_arg *arg, VALUE io)
* "ne\n"
*
* With arguments +sep+ and +limit+ given,
- * combines the two behaviors:
- *
- * - Calls with the next line as determined by line separator +sep+.
- * - But returns no more bytes than are allowed by the limit.
+ * combines the two behaviors
+ * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]).
*
- * For all forms above, optional keyword arguments +line_opts+ specify
- * {Line Options}[rdoc-ref:IO@Line+Options]:
+ * Optional keyword argument +chomp+ specifies whether line separators
+ * are to be omitted:
*
* f = File.new('t.txt')
* f.each_line(chomp: true) {|line| p line }
@@ -4360,9 +4587,6 @@ io_readlines(const struct getline_arg *arg, VALUE io)
* "Fifth line"
*
* Returns an Enumerator if no block is given.
- *
- * IO#each is an alias for IO#each_line.
- *
*/
static VALUE
@@ -4386,7 +4610,8 @@ rb_io_each_line(int argc, VALUE *argv, VALUE io)
* each_byte {|byte| ... } -> self
* each_byte -> enumerator
*
- * Calls the given block with each byte (0..255) in the stream; returns +self+:
+ * Calls the given block with each byte (0..255) in the stream; returns +self+.
+ * See {Byte IO}[rdoc-ref:IO@Byte+IO].
*
* f = File.new('t.rus')
* a = []
@@ -4533,7 +4758,8 @@ io_getc(rb_io_t *fptr, rb_encoding *enc)
* each_char {|c| ... } -> self
* each_char -> enumerator
*
- * Calls the given block with each character in the stream; returns +self+:
+ * Calls the given block with each character in the stream; returns +self+.
+ * See {Character IO}[rdoc-ref:IO@Character+IO].
*
* f = File.new('t.rus')
* a = []
@@ -4694,7 +4920,8 @@ rb_io_each_codepoint(VALUE io)
* getc -> character or nil
*
* Reads and returns the next 1-character string from the stream;
- * returns +nil+ if already at end-of-file:
+ * returns +nil+ if already at end-of-stream.
+ * See {Character IO}[rdoc-ref:IO@Character+IO].
*
* f = File.open('t.txt')
* f.getc # => "F"
@@ -4726,7 +4953,8 @@ rb_io_getc(VALUE io)
* readchar -> string
*
* Reads and returns the next 1-character string from the stream;
- * raises EOFError if already at end-of-file:
+ * raises EOFError if already at end-of-stream.
+ * See {Character IO}[rdoc-ref:IO@Character+IO].
*
* f = File.open('t.txt')
* f.readchar # => "F"
@@ -4755,7 +4983,8 @@ rb_io_readchar(VALUE io)
* getbyte -> integer or nil
*
* Reads and returns the next byte (in range 0..255) from the stream;
- * returns +nil+ if already at end-of-file:
+ * returns +nil+ if already at end-of-stream.
+ * See {Byte IO}[rdoc-ref:IO@Byte+IO].
*
* f = File.open('t.txt')
* f.getbyte # => 70
@@ -4765,7 +4994,6 @@ rb_io_readchar(VALUE io)
* f.close
*
* Related: IO#readbyte (may raise EOFError).
- *
*/
VALUE
@@ -4799,7 +5027,8 @@ rb_io_getbyte(VALUE io)
* readbyte -> integer
*
* Reads and returns the next byte (in range 0..255) from the stream;
- * raises EOFError if already at end-of-file:
+ * raises EOFError if already at end-of-stream.
+ * See {Byte IO}[rdoc-ref:IO@Byte+IO].
*
* f = File.open('t.txt')
* f.readbyte # => 70
@@ -4830,10 +5059,11 @@ rb_io_readbyte(VALUE io)
*
* Pushes back ("unshifts") the given data onto the stream's buffer,
* placing the data so that it is next to be read; returns +nil+.
+ * See {Byte IO}[rdoc-ref:IO@Byte+IO].
*
* Note that:
*
- * - Calling the method hs no effect with unbuffered reads (such as IO#sysread).
+ * - Calling the method has no effect with unbuffered reads (such as IO#sysread).
* - Calling #rewind on the stream discards the pushed-back data.
*
* When argument +integer+ is given, uses only its low-order byte:
@@ -4877,7 +5107,7 @@ rb_io_ungetbyte(VALUE io, VALUE b)
b = rb_str_new((const char *)&c, 1);
break;
default:
- SafeStringValue(b);
+ StringValue(b);
}
io_ungetbyte(b, fptr);
return Qnil;
@@ -4890,10 +5120,11 @@ rb_io_ungetbyte(VALUE io, VALUE b)
*
* Pushes back ("unshifts") the given data onto the stream's buffer,
* placing the data so that it is next to be read; returns +nil+.
+ * See {Character IO}[rdoc-ref:IO@Character+IO].
*
* Note that:
*
- * - Calling the method hs no effect with unbuffered reads (such as IO#sysread).
+ * - Calling the method has no effect with unbuffered reads (such as IO#sysread).
* - Calling #rewind on the stream discards the pushed-back data.
*
* When argument +integer+ is given, interprets the integer as a character:
@@ -4938,7 +5169,7 @@ rb_io_ungetc(VALUE io, VALUE c)
c = rb_enc_uint_chr(NUM2UINT(c), io_read_encoding(fptr));
}
else {
- SafeStringValue(c);
+ StringValue(c);
}
if (NEED_READCONV(fptr)) {
SET_BINARY_MODE(fptr);
@@ -4974,10 +5205,10 @@ rb_io_ungetc(VALUE io, VALUE c)
* Returns +true+ if the stream is associated with a terminal device (tty),
* +false+ otherwise:
*
- * File.new('t.txt').isatty #=> false
- * File.new('/dev/tty').isatty #=> true
- *
- * IO#tty? is an alias for IO#isatty.
+ * f = File.new('t.txt').isatty #=> false
+ * f.close
+ * f = File.new('/dev/tty').isatty #=> true
+ * f.close
*
*/
@@ -5039,7 +5270,7 @@ rb_io_close_on_exec_p(VALUE io)
*
* Sets a close-on-exec flag.
*
- * f = open("/dev/null")
+ * f = File.open(File::NULL)
* f.close_on_exec = true
* system("cat", "/proc/self/fd/#{f.fileno}") # cat: /proc/self/fd/3: No such file or directory
* f.closed? #=> false
@@ -5090,7 +5321,7 @@ rb_io_set_close_on_exec(VALUE io, VALUE arg)
#define rb_io_set_close_on_exec rb_f_notimplement
#endif
-#define IS_PREP_STDIO(f) ((f)->mode & FMODE_PREP)
+#define RUBY_IO_EXTERNAL_P(f) ((f)->mode & FMODE_EXTERNAL)
#define PREP_STDIO_NAME(f) (RSTRING_PTR((f)->pathv))
static VALUE
@@ -5109,13 +5340,13 @@ finish_writeconv(rb_io_t *fptr, int noalloc)
res = rb_econv_convert(fptr->writeconv, NULL, NULL, &dp, de, 0);
while (dp-ds) {
size_t remaining = dp-ds;
- long result = rb_write_internal(fptr, ds, remaining);
+ long result = rb_io_write_memory(fptr, ds, remaining);
if (result > 0) {
ds += result;
if ((size_t)result == remaining) break;
}
- else if (rb_io_maybe_wait_writable(errno, fptr->self, Qnil)) {
+ else if (rb_io_maybe_wait_writable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT)) {
if (fptr->fd < 0)
return noalloc ? Qtrue : rb_exc_new3(rb_eIOError, rb_str_new_cstr(closed_stream));
}
@@ -5184,7 +5415,7 @@ maygvl_close(int fd, int keepgvl)
* close() may block for certain file types (NFS, SO_LINGER sockets,
* inotify), so let other threads run.
*/
- return (int)(intptr_t)rb_thread_call_without_gvl(nogvl_close, &fd, RUBY_UBF_IO, 0);
+ return IO_WITHOUT_GVL_INT(nogvl_close, &fd);
}
static void*
@@ -5201,7 +5432,7 @@ maygvl_fclose(FILE *file, int keepgvl)
if (keepgvl)
return fclose(file);
- return (int)(intptr_t)rb_thread_call_without_gvl(nogvl_fclose, file, RUBY_UBF_IO, 0);
+ return IO_WITHOUT_GVL_INT(nogvl_fclose, file);
}
static void free_io_buffer(rb_io_buffer_t *buf);
@@ -5209,7 +5440,7 @@ static void clear_codeconv(rb_io_t *fptr);
static void
fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl,
- struct ccan_list_head *busy)
+ struct rb_io_close_wait_list *busy)
{
VALUE error = Qnil;
int fd = fptr->fd;
@@ -5240,7 +5471,7 @@ fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl,
int done = 0;
- if (IS_PREP_STDIO(fptr) || fd <= 2) {
+ if (RUBY_IO_EXTERNAL_P(fptr) || fd <= 2) {
// Need to keep FILE objects of stdin, stdout and stderr, so we are done:
done = 1;
}
@@ -5252,7 +5483,7 @@ fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl,
// Ensure waiting_fd users do not hit EBADF.
if (busy) {
// Wait for them to exit before we call close().
- do rb_thread_schedule(); while (!ccan_list_empty(busy));
+ rb_notify_fd_close_wait(busy);
}
// Disable for now.
@@ -5260,7 +5491,7 @@ fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl,
// VALUE scheduler = rb_fiber_scheduler_current();
// if (scheduler != Qnil) {
// VALUE result = rb_fiber_scheduler_io_close(scheduler, fptr->self);
- // if (result != Qundef) done = 1;
+ // if (!UNDEF_P(result)) done = 1;
// }
// }
@@ -5354,12 +5585,9 @@ clear_codeconv(rb_io_t *fptr)
clear_writeconv(fptr);
}
-void
-rb_io_fptr_finalize_internal(void *ptr)
+static void
+rb_io_fptr_cleanup_all(rb_io_t *fptr)
{
- rb_io_t *fptr = ptr;
-
- if (!ptr) return;
fptr->pathv = Qnil;
if (0 <= fptr->fd)
rb_io_fptr_cleanup(fptr, TRUE);
@@ -5367,7 +5595,14 @@ rb_io_fptr_finalize_internal(void *ptr)
free_io_buffer(&fptr->rbuf);
free_io_buffer(&fptr->wbuf);
clear_codeconv(fptr);
- free(fptr);
+}
+
+void
+rb_io_fptr_finalize_internal(void *ptr)
+{
+ if (!ptr) return;
+ rb_io_fptr_cleanup_all(ptr);
+ free(ptr);
}
#undef rb_io_fptr_finalize
@@ -5384,7 +5619,7 @@ rb_io_fptr_finalize(rb_io_t *fptr)
}
#define rb_io_fptr_finalize(fptr) rb_io_fptr_finalize_internal(fptr)
-RUBY_FUNC_EXPORTED size_t
+size_t
rb_io_memsize(const rb_io_t *fptr)
{
size_t size = sizeof(rb_io_t);
@@ -5403,16 +5638,14 @@ rb_io_memsize(const rb_io_t *fptr)
# define KEEPGVL FALSE
#endif
-int rb_notify_fd_close(int fd, struct ccan_list_head *);
static rb_io_t *
io_close_fptr(VALUE io)
{
rb_io_t *fptr;
VALUE write_io;
rb_io_t *write_fptr;
- struct ccan_list_head busy;
+ struct rb_io_close_wait_list busy;
- ccan_list_head_init(&busy);
write_io = GetWriteIO(io);
if (io != write_io) {
write_fptr = RFILE(write_io)->fptr;
@@ -5456,12 +5689,32 @@ rb_io_close(VALUE io)
* call-seq:
* close -> nil
*
- * Closes the stream, if it is open, after flushing any buffered writes
- * to the operating system; does nothing if the stream is already closed.
- * A stream is automatically closed when claimed by the garbage collector.
+ * Closes the stream for both reading and writing
+ * if open for either or both; returns +nil+.
+ * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams].
*
- * If the stream was opened by IO.popen, #close sets global variable <tt>$?</tt>.
+ * If the stream is open for writing, flushes any buffered writes
+ * to the operating system before closing.
*
+ * If the stream was opened by IO.popen, sets global variable <tt>$?</tt>
+ * (child exit status).
+ *
+ * Example:
+ *
+ * IO.popen('ruby', 'r+') do |pipe|
+ * puts pipe.closed?
+ * pipe.close
+ * puts $?
+ * puts pipe.closed?
+ * end
+ *
+ * Output:
+ *
+ * false
+ * pid 13760 exit 0
+ * true
+ *
+ * Related: IO#close_read, IO#close_write, IO#closed?.
*/
static VALUE
@@ -5499,7 +5752,7 @@ static VALUE
io_close(VALUE io)
{
VALUE closed = rb_check_funcall(io, rb_intern("closed?"), 0, 0);
- if (closed != Qundef && RTEST(closed)) return io;
+ if (!UNDEF_P(closed) && RTEST(closed)) return io;
rb_rescue2(io_call_close, io, ignore_closed_stream, io,
rb_eIOError, (VALUE)0);
return io;
@@ -5510,22 +5763,27 @@ io_close(VALUE io)
* closed? -> true or false
*
* Returns +true+ if the stream is closed for both reading and writing,
- * +false+ otherwise:
+ * +false+ otherwise.
+ * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams].
*
- * f = File.new('t.txt')
- * 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
+ * IO.popen('ruby', 'r+') do |pipe|
+ * puts pipe.closed?
+ * pipe.close_read
+ * puts pipe.closed?
+ * pipe.close_write
+ * puts pipe.closed?
+ * end
+ *
+ * Output:
+ *
+ * false
+ * false
+ * true
*
+ * Related: IO#close_read, IO#close_write, IO#close.
*/
-
-
-static VALUE
-rb_io_closed(VALUE io)
+VALUE
+rb_io_closed_p(VALUE io)
{
rb_io_t *fptr;
VALUE write_io;
@@ -5547,15 +5805,32 @@ rb_io_closed(VALUE io)
* call-seq:
* close_read -> nil
*
- * Closes the read end of a duplexed stream (i.e., one that is both readable
- * and writable, such as a pipe); does nothing if already closed:
+ * Closes the stream for reading if open for reading;
+ * returns +nil+.
+ * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams].
*
- * f = IO.popen('/bin/sh','r+')
- * f.close_read
- * f.readlines # Raises IOError
+ * If the stream was opened by IO.popen and is also closed for writing,
+ * sets global variable <tt>$?</tt> (child exit status).
*
- * Raises an exception if the stream is not duplexed.
+ * Example:
*
+ * IO.popen('ruby', 'r+') do |pipe|
+ * puts pipe.closed?
+ * pipe.close_write
+ * puts pipe.closed?
+ * pipe.close_read
+ * puts $?
+ * puts pipe.closed?
+ * end
+ *
+ * Output:
+ *
+ * false
+ * false
+ * pid 14748 exit 0
+ * true
+ *
+ * Related: IO#close, IO#close_write, IO#closed?.
*/
static VALUE
@@ -5603,13 +5878,32 @@ rb_io_close_read(VALUE io)
* call-seq:
* close_write -> nil
*
- * Closes the write end of a duplexed stream (i.e., one that is both readable
- * and writable, such as a pipe); does nothing if already closed:
+ * Closes the stream for writing if open for writing;
+ * returns +nil+.
+ * See {Open and Closed Streams}[rdoc-ref:IO@Open+and+Closed+Streams].
+ *
+ * Flushes any buffered writes to the operating system before closing.
+ *
+ * If the stream was opened by IO.popen and is also closed for reading,
+ * sets global variable <tt>$?</tt> (child exit status).
+ *
+ * IO.popen('ruby', 'r+') do |pipe|
+ * puts pipe.closed?
+ * pipe.close_read
+ * puts pipe.closed?
+ * pipe.close_write
+ * puts $?
+ * puts pipe.closed?
+ * end
+ *
+ * Output:
*
- * f = IO.popen('/bin/sh', 'r+')
- * f.close_write
- * f.print 'nowhere' # Raises IOError.
+ * false
+ * false
+ * pid 15044 exit 0
+ * true
*
+ * Related: IO#close, IO#close_read, IO#closed?.
*/
static VALUE
@@ -5662,7 +5956,7 @@ rb_io_sysseek(int argc, VALUE *argv, VALUE io)
VALUE offset, ptrname;
int whence = SEEK_SET;
rb_io_t *fptr;
- off_t pos;
+ rb_off_t pos;
if (rb_scan_args(argc, argv, "11", &offset, &ptrname) == 2) {
whence = interpret_seek_whence(ptrname);
@@ -5722,7 +6016,7 @@ rb_io_syswrite(VALUE io, VALUE str)
tmp = rb_str_tmp_frozen_acquire(str);
RSTRING_GETMEM(tmp, ptr, len);
- n = rb_write_internal(fptr, ptr, len);
+ n = rb_io_write_memory(fptr, ptr, len);
if (n < 0) rb_sys_fail_path(fptr->pathv);
rb_str_tmp_frozen_release(str, tmp);
@@ -5768,9 +6062,11 @@ rb_io_sysread(int argc, VALUE *argv, VALUE io)
iis.th = rb_thread_current();
iis.fptr = fptr;
iis.nonblock = 0;
+ iis.fd = fptr->fd;
iis.buf = RSTRING_PTR(str);
iis.capa = ilen;
- n = read_internal_locktmp(str, &iis);
+ iis.timeout = NULL;
+ n = io_read_memory_locktmp(str, &iis);
if (n < 0) {
rb_sys_fail_path(fptr->pathv);
@@ -5785,28 +6081,37 @@ rb_io_sysread(int argc, VALUE *argv, VALUE io)
return str;
}
-#if defined(HAVE_PREAD) || defined(HAVE_PWRITE)
struct prdwr_internal_arg {
+ VALUE io;
int fd;
void *buf;
size_t count;
- off_t offset;
+ rb_off_t offset;
};
-#endif /* HAVE_PREAD || HAVE_PWRITE */
-#if defined(HAVE_PREAD)
static VALUE
-internal_pread_func(void *arg)
+internal_pread_func(void *_arg)
{
- struct prdwr_internal_arg *p = arg;
- return (VALUE)pread(p->fd, p->buf, p->count, p->offset);
+ struct prdwr_internal_arg *arg = _arg;
+
+ return (VALUE)pread(arg->fd, arg->buf, arg->count, arg->offset);
}
static VALUE
-pread_internal_call(VALUE arg)
+pread_internal_call(VALUE _arg)
{
- struct prdwr_internal_arg *p = (struct prdwr_internal_arg *)arg;
- return rb_thread_io_blocking_region(internal_pread_func, p, p->fd);
+ struct prdwr_internal_arg *arg = (struct prdwr_internal_arg *)_arg;
+
+ VALUE scheduler = rb_fiber_scheduler_current();
+ if (scheduler != Qnil) {
+ VALUE result = rb_fiber_scheduler_io_pread_memory(scheduler, arg->io, arg->offset, arg->buf, arg->count, 0);
+
+ if (!UNDEF_P(result)) {
+ return rb_fiber_scheduler_io_result_apply(result);
+ }
+ }
+
+ return rb_thread_io_blocking_call(internal_pread_func, arg, arg->fd, RB_WAITFD_IN);
}
/*
@@ -5843,7 +6148,7 @@ rb_io_pread(int argc, VALUE *argv, VALUE io)
VALUE len, offset, str;
rb_io_t *fptr;
ssize_t n;
- struct prdwr_internal_arg arg;
+ struct prdwr_internal_arg arg = {.io = io};
int shrinkable;
rb_scan_args(argc, argv, "21", &len, &offset, &str);
@@ -5873,15 +6178,21 @@ rb_io_pread(int argc, VALUE *argv, VALUE io)
return str;
}
-#else
-# define rb_io_pread rb_f_notimplement
-#endif /* HAVE_PREAD */
-#if defined(HAVE_PWRITE)
static VALUE
-internal_pwrite_func(void *ptr)
+internal_pwrite_func(void *_arg)
{
- struct prdwr_internal_arg *arg = ptr;
+ struct prdwr_internal_arg *arg = _arg;
+
+ VALUE scheduler = rb_fiber_scheduler_current();
+ if (scheduler != Qnil) {
+ VALUE result = rb_fiber_scheduler_io_pwrite_memory(scheduler, arg->io, arg->offset, arg->buf, arg->count, 0);
+
+ if (!UNDEF_P(result)) {
+ return rb_fiber_scheduler_io_result_apply(result);
+ }
+ }
+
return (VALUE)pwrite(arg->fd, arg->buf, arg->count, arg->offset);
}
@@ -5916,7 +6227,7 @@ rb_io_pwrite(VALUE io, VALUE str, VALUE offset)
{
rb_io_t *fptr;
ssize_t n;
- struct prdwr_internal_arg arg;
+ struct prdwr_internal_arg arg = {.io = io};
VALUE tmp;
if (!RB_TYPE_P(str, T_STRING))
@@ -5933,15 +6244,12 @@ rb_io_pwrite(VALUE io, VALUE str, VALUE offset)
arg.buf = RSTRING_PTR(tmp);
arg.count = (size_t)RSTRING_LEN(tmp);
- n = (ssize_t)rb_thread_io_blocking_region(internal_pwrite_func, &arg, fptr->fd);
+ n = (ssize_t)rb_thread_io_blocking_call(internal_pwrite_func, &arg, fptr->fd, RB_WAITFD_OUT);
if (n < 0) rb_sys_fail_path(fptr->pathv);
rb_str_tmp_frozen_release(str, tmp);
return SSIZET2NUM(n);
}
-#else
-# define rb_io_pwrite rb_f_notimplement
-#endif /* HAVE_PWRITE */
VALUE
rb_io_binmode(VALUE io)
@@ -6005,7 +6313,7 @@ rb_io_ascii8bit_binmode(VALUE io)
* binmode -> self
*
* Sets the stream's data mode as binary
- * (see {Data Mode}[rdoc-ref:IO@Data+Mode]).
+ * (see {Data Mode}[rdoc-ref:File@Data+Mode]).
*
* A stream's data mode may not be changed from binary to text.
*
@@ -6029,7 +6337,7 @@ rb_io_binmode_m(VALUE io)
* binmode? -> true or false
*
* Returns +true+ if the stream is on binary mode, +false+ otherwise.
- * See {Data Mode}[rdoc-ref:IO@Data+Mode].
+ * See {Data Mode}[rdoc-ref:File@Data+Mode].
*
*/
static VALUE
@@ -6264,7 +6572,7 @@ rb_io_ext_int_to_encs(rb_encoding *ext, rb_encoding *intern, rb_encoding **enc,
ext = rb_default_external_encoding();
default_ext = 1;
}
- if (ext == rb_ascii8bit_encoding()) {
+ if (rb_is_ascii8bit_enc(ext)) {
/* If external is ASCII-8BIT, no transcoding */
intern = NULL;
}
@@ -6372,21 +6680,21 @@ rb_io_extract_encoding_option(VALUE opt, rb_encoding **enc_p, rb_encoding **enc2
v = rb_hash_lookup2(opt, sym_extenc, Qundef);
if (v != Qnil) extenc = v;
v = rb_hash_lookup2(opt, sym_intenc, Qundef);
- if (v != Qundef) intenc = v;
+ if (!UNDEF_P(v)) intenc = v;
}
- if ((extenc != Qundef || intenc != Qundef) && !NIL_P(encoding)) {
+ if ((!UNDEF_P(extenc) || !UNDEF_P(intenc)) && !NIL_P(encoding)) {
if (!NIL_P(ruby_verbose)) {
int idx = rb_to_encoding_index(encoding);
if (idx >= 0) encoding = rb_enc_from_encoding(rb_enc_from_index(idx));
rb_warn("Ignoring encoding parameter '%"PRIsVALUE"': %s_encoding is used",
- encoding, extenc == Qundef ? "internal" : "external");
+ encoding, UNDEF_P(extenc) ? "internal" : "external");
}
encoding = Qnil;
}
- if (extenc != Qundef && !NIL_P(extenc)) {
+ if (!UNDEF_P(extenc) && !NIL_P(extenc)) {
extencoding = rb_to_encoding(extenc);
}
- if (intenc != Qundef) {
+ if (!UNDEF_P(intenc)) {
if (NIL_P(intenc)) {
/* internal_encoding: nil => no transcoding */
intencoding = (rb_encoding *)Qnil;
@@ -6419,15 +6727,13 @@ rb_io_extract_encoding_option(VALUE opt, rb_encoding **enc_p, rb_encoding **enc2
rb_io_ext_int_to_encs(rb_to_encoding(encoding), NULL, enc_p, enc2_p, 0);
}
}
- else if (extenc != Qundef || intenc != Qundef) {
+ else if (!UNDEF_P(extenc) || !UNDEF_P(intenc)) {
extracted = 1;
rb_io_ext_int_to_encs(extencoding, intencoding, enc_p, enc2_p, 0);
}
return extracted;
}
-typedef struct rb_io_enc_t convconfig_t;
-
static void
validate_enc_binmode(int *fmode_p, int ecflags, rb_encoding *enc, rb_encoding *enc2)
{
@@ -6486,7 +6792,7 @@ extract_binmode(VALUE opthash, int *fmode)
void
rb_io_extract_modeenc(VALUE *vmode_p, VALUE *vperm_p, VALUE opthash,
- int *oflags_p, int *fmode_p, convconfig_t *convconfig_p)
+ int *oflags_p, int *fmode_p, struct rb_io_encoding *convconfig_p)
{
VALUE vmode;
int oflags, fmode;
@@ -6514,7 +6820,7 @@ rb_io_extract_modeenc(VALUE *vmode_p, VALUE *vperm_p, VALUE opthash,
else {
const char *p;
- SafeStringValue(vmode);
+ StringValue(vmode);
p = StringValueCStr(vmode);
fmode = rb_io_modestr_fmode(p);
oflags = rb_io_fmode_oflags(fmode);
@@ -6647,8 +6953,7 @@ sysopen_func(void *ptr)
static inline int
rb_sysopen_internal(struct sysopen_struct *data)
{
- int fd;
- fd = (int)(VALUE)rb_thread_call_without_gvl(sysopen_func, data, RUBY_UBF_IO, 0);
+ int fd = IO_WITHOUT_GVL_INT(sysopen_func, data);
if (0 <= fd)
rb_update_max_fd(fd);
return fd;
@@ -6657,7 +6962,7 @@ rb_sysopen_internal(struct sysopen_struct *data)
static int
rb_sysopen(VALUE fname, int oflags, mode_t perm)
{
- int fd;
+ int fd = -1;
struct sysopen_struct data;
data.fname = rb_str_encode_ospath(fname);
@@ -6665,21 +6970,14 @@ rb_sysopen(VALUE fname, int oflags, mode_t perm)
data.oflags = oflags;
data.perm = perm;
- fd = rb_sysopen_internal(&data);
- if (fd < 0) {
- int e = errno;
- if (rb_gc_for_fd(e)) {
- fd = rb_sysopen_internal(&data);
- }
- if (fd < 0) {
- rb_syserr_fail_path(e, fname);
- }
+ TRY_WITH_GC((fd = rb_sysopen_internal(&data)) >= 0) {
+ rb_syserr_fail_path(first_errno, fname);
}
return fd;
}
-FILE *
-rb_fdopen(int fd, const char *modestr)
+static inline FILE *
+fdopen_internal(int fd, const char *modestr)
{
FILE *file;
@@ -6688,26 +6986,22 @@ rb_fdopen(int fd, const char *modestr)
#endif
file = fdopen(fd, modestr);
if (!file) {
- int e = errno;
-#if defined(__sun)
- if (e == 0) {
- rb_gc();
- errno = 0;
- file = fdopen(fd, modestr);
- }
- else
-#endif
- if (rb_gc_for_fd(e)) {
- file = fdopen(fd, modestr);
- }
- if (!file) {
#ifdef _WIN32
- if (e == 0) e = EINVAL;
+ if (errno == 0) errno = EINVAL;
#elif defined(__sun)
- if (e == 0) e = EMFILE;
+ if (errno == 0) errno = EMFILE;
#endif
- rb_syserr_fail(e, 0);
- }
+ }
+ return file;
+}
+
+FILE *
+rb_fdopen(int fd, const char *modestr)
+{
+ FILE *file = 0;
+
+ TRY_WITH_GC((file = fdopen_internal(fd, modestr)) != 0) {
+ rb_syserr_fail(first_errno, 0);
}
/* xxx: should be _IONBF? A buffer in FILE may have trouble. */
@@ -6814,11 +7108,11 @@ io_set_encoding_by_bom(VALUE io)
static VALUE
rb_file_open_generic(VALUE io, VALUE filename, int oflags, int fmode,
- const convconfig_t *convconfig, mode_t perm)
+ const struct rb_io_encoding *convconfig, mode_t perm)
{
VALUE pathv;
rb_io_t *fptr;
- convconfig_t cc;
+ struct rb_io_encoding cc;
if (!convconfig) {
/* Set to default encodings */
rb_io_ext_int_to_encs(NULL, NULL, &cc.enc, &cc.enc2, fmode);
@@ -6852,13 +7146,11 @@ rb_file_open_internal(VALUE io, VALUE filename, const char *modestr)
{
int fmode = rb_io_modestr_fmode(modestr);
const char *p = strchr(modestr, ':');
- convconfig_t convconfig;
+ struct rb_io_encoding convconfig;
if (p) {
parse_mode_enc(p+1, rb_usascii_encoding(),
&convconfig.enc, &convconfig.enc2, &fmode);
- convconfig.ecflags = 0;
- convconfig.ecopts = Qnil;
}
else {
rb_encoding *e;
@@ -6866,10 +7158,19 @@ rb_file_open_internal(VALUE io, VALUE filename, const char *modestr)
e = (fmode & FMODE_BINMODE) ? rb_ascii8bit_encoding() : NULL;
rb_io_ext_int_to_encs(e, NULL, &convconfig.enc, &convconfig.enc2, fmode);
- convconfig.ecflags = 0;
- convconfig.ecopts = Qnil;
}
+ convconfig.ecflags = (fmode & FMODE_READABLE) ?
+ MODE_BTMODE(ECONV_DEFAULT_NEWLINE_DECORATOR,
+ 0, ECONV_UNIVERSAL_NEWLINE_DECORATOR) : 0;
+#ifdef TEXTMODE_NEWLINE_DECORATOR_ON_WRITE
+ convconfig.ecflags |= (fmode & FMODE_WRITABLE) ?
+ MODE_BTMODE(TEXTMODE_NEWLINE_DECORATOR_ON_WRITE,
+ 0, TEXTMODE_NEWLINE_DECORATOR_ON_WRITE) : 0;
+#endif
+ SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(convconfig.enc2, convconfig.ecflags);
+ convconfig.ecopts = Qnil;
+
return rb_file_open_generic(io, filename,
rb_io_fmode_oflags(fmode),
fmode,
@@ -6960,7 +7261,7 @@ static void
fptr_copy_finalizer(rb_io_t *fptr, const rb_io_t *orig)
{
#if defined(__CYGWIN__) || !defined(HAVE_WORKING_FORK)
- void (*const old_finalize)(struct rb_io_t*,int) = fptr->finalize;
+ void (*const old_finalize)(struct rb_io*,int) = fptr->finalize;
if (old_finalize == orig->finalize) return;
#endif
@@ -6998,12 +7299,7 @@ int
rb_pipe(int *pipes)
{
int ret;
- ret = rb_cloexec_pipe(pipes);
- if (ret < 0) {
- if (rb_gc_for_fd(errno)) {
- ret = rb_cloexec_pipe(pipes);
- }
- }
+ TRY_WITH_GC((ret = rb_cloexec_pipe(pipes)) >= 0);
if (ret == 0) {
rb_update_max_fd(pipes[0]);
rb_update_max_fd(pipes[1]);
@@ -7165,7 +7461,7 @@ char *rb_execarg_commandline(const struct rb_execarg *eargp, VALUE *prog);
#ifndef __EMSCRIPTEN__
static VALUE
pipe_open(VALUE execarg_obj, const char *modestr, int fmode,
- const convconfig_t *convconfig)
+ const struct rb_io_encoding *convconfig)
{
struct rb_execarg *eargp = NIL_P(execarg_obj) ? NULL : rb_execarg_get(execarg_obj);
VALUE prog = eargp ? (eargp->use_shell ? eargp->invoke.sh.shell_script : eargp->invoke.cmd.command_name) : Qfalse ;
@@ -7394,7 +7690,7 @@ pipe_open(VALUE execarg_obj, const char *modestr, int fmode,
#else
static VALUE
pipe_open(VALUE execarg_obj, const char *modestr, int fmode,
- const convconfig_t *convconfig)
+ const struct rb_io_encoding *convconfig)
{
rb_raise(rb_eNotImpError, "popen() is not available");
}
@@ -7416,7 +7712,7 @@ is_popen_fork(VALUE prog)
static VALUE
pipe_open_s(VALUE prog, const char *modestr, int fmode,
- const convconfig_t *convconfig)
+ const struct rb_io_encoding *convconfig)
{
int argc = 1;
VALUE *argv = &prog;
@@ -7460,7 +7756,7 @@ static VALUE popen_finish(VALUE port, VALUE klass);
* and the block's value is assigned to global variable <tt>$?</tt> and returned.
*
* Optional argument +mode+ may be any valid \IO mode.
- * See IO@Modes.
+ * See {Access Modes}[rdoc-ref:File@Access+Modes].
*
* Required argument +cmd+ determines which of the following occurs:
*
@@ -7625,7 +7921,7 @@ rb_io_popen(VALUE pname, VALUE pmode, VALUE env, VALUE opt)
const char *modestr;
VALUE tmp, execarg_obj = Qnil;
int oflags, fmode;
- convconfig_t convconfig;
+ struct rb_io_encoding convconfig;
tmp = rb_check_array_type(pname);
if (!NIL_P(tmp)) {
@@ -7639,7 +7935,7 @@ rb_io_popen(VALUE pname, VALUE pmode, VALUE env, VALUE opt)
RB_GC_GUARD(tmp);
}
else {
- SafeStringValue(pname);
+ StringValue(pname);
execarg_obj = Qnil;
if (!is_popen_fork(pname))
execarg_obj = rb_execarg_new(1, &pname, TRUE, FALSE);
@@ -7676,10 +7972,64 @@ popen_finish(VALUE port, VALUE klass)
return port;
}
+#if defined(HAVE_WORKING_FORK) && !defined(__EMSCRIPTEN__)
+struct popen_writer_arg {
+ char *const *argv;
+ struct popen_arg popen;
+};
+
+static int
+exec_popen_writer(void *arg, char *errmsg, size_t buflen)
+{
+ struct popen_writer_arg *pw = arg;
+ pw->popen.modef = FMODE_WRITABLE;
+ popen_redirect(&pw->popen);
+ execv(pw->argv[0], pw->argv);
+ strlcpy(errmsg, strerror(errno), buflen);
+ return -1;
+}
+#endif
+
+FILE *
+ruby_popen_writer(char *const *argv, rb_pid_t *pid)
+{
+#if (defined(HAVE_WORKING_FORK) && !defined(__EMSCRIPTEN__)) || defined(_WIN32)
+# ifdef HAVE_WORKING_FORK
+ struct popen_writer_arg pw;
+ int *const write_pair = pw.popen.pair;
+# else
+ int write_pair[2];
+# endif
+
+ int result = rb_cloexec_pipe(write_pair);
+ *pid = -1;
+ if (result == 0) {
+# ifdef HAVE_WORKING_FORK
+ pw.argv = argv;
+ int status;
+ char errmsg[80] = {'\0'};
+ *pid = rb_fork_async_signal_safe(&status, exec_popen_writer, &pw, Qnil, errmsg, sizeof(errmsg));
+# else
+ *pid = rb_w32_uspawn_process(P_NOWAIT, argv[0], argv, write_pair[0], -1, -1, 0);
+ const char *errmsg = (*pid < 0) ? strerror(errno) : NULL;
+# endif
+ close(write_pair[0]);
+ if (*pid < 0) {
+ close(write_pair[1]);
+ fprintf(stderr, "ruby_popen_writer(%s): %s\n", argv[0], errmsg);
+ }
+ else {
+ return fdopen(write_pair[1], "w");
+ }
+ }
+#endif
+ return NULL;
+}
+
static void
rb_scan_open_args(int argc, const VALUE *argv,
VALUE *fname_p, int *oflags_p, int *fmode_p,
- convconfig_t *convconfig_p, mode_t *perm_p)
+ struct rb_io_encoding *convconfig_p, mode_t *perm_p)
{
VALUE opt, fname, vmode, vperm;
int oflags, fmode;
@@ -7703,7 +8053,7 @@ rb_open_file(int argc, const VALUE *argv, VALUE io)
{
VALUE fname;
int oflags, fmode;
- convconfig_t convconfig;
+ struct rb_io_encoding convconfig;
mode_t perm;
rb_scan_open_args(argc, argv, &fname, &oflags, &fmode, &convconfig, &perm);
@@ -7719,11 +8069,11 @@ rb_open_file(int argc, const VALUE *argv, VALUE io)
* File.open(path, mode = 'r', perm = 0666, **opts) -> file
* File.open(path, mode = 'r', perm = 0666, **opts) {|f| ... } -> object
*
- * Creates a new \File object, via File.new with the given arguments.
+ * Creates a new File object, via File.new with the given arguments.
*
- * With no block given, returns the \File object.
+ * With no block given, returns the File object.
*
- * With a block given, calls the block with the \File object
+ * With a block given, calls the block with the File object
* and returns the block's value.
*
*/
@@ -7790,7 +8140,7 @@ rb_io_s_sysopen(int argc, VALUE *argv, VALUE _)
else if (!NIL_P(intmode = rb_check_to_integer(vmode, "to_int")))
oflags = NUM2INT(intmode);
else {
- SafeStringValue(vmode);
+ StringValue(vmode);
oflags = rb_io_modestr_oflags(StringValueCStr(vmode));
}
if (NIL_P(vperm)) perm = 0666;
@@ -7821,20 +8171,10 @@ check_pipe_command(VALUE filename_or_command)
* open(path, mode = 'r', perm = 0666, **opts) -> io or nil
* open(path, mode = 'r', perm = 0666, **opts) {|io| ... } -> obj
*
- * Creates an IO object connected to the given stream, file, or subprocess.
- *
- * Required string argument +path+ determines which of the following occurs:
- *
- * - The file at the specified +path+ is opened.
- * - The process forks.
- * - A subprocess is created.
- *
- * Each of these is detailed below.
+ * Creates an IO object connected to the given file.
*
- * <b>File Opened</b>
-
- * If +path+ does _not_ start with a pipe character (<tt>'|'</tt>),
- * a file stream is opened with <tt>File.open(path, mode, perm, **opts)</tt>.
+ * This method has potential security vulnerabilities if called with untrusted input;
+ * see {Command Injection}[rdoc-ref:command_injection.rdoc].
*
* With no block given, file stream is returned:
*
@@ -7851,67 +8191,6 @@ check_pipe_command(VALUE filename_or_command)
*
* See File.open for details.
*
- * <b>Process Forked</b>
- *
- * If +path+ is the 2-character string <tt>'|-'</tt>, the process forks
- * and the child process is connected to the parent.
- *
- * With no block given:
- *
- * io = open('|-')
- * if io
- * $stderr.puts "In parent, child pid is #{io.pid}."
- * else
- * $stderr.puts "In child, pid is #{$$}."
- * end
- *
- * Output:
- *
- * In parent, child pid is 27903.
- * In child, pid is 27903.
- *
- * With a block given:
- *
- * open('|-') do |io|
- * if io
- * $stderr.puts "In parent, child pid is #{io.pid}."
- * else
- * $stderr.puts "In child, pid is #{$$}."
- * end
- * end
- *
- * Output:
- *
- * In parent, child pid is 28427.
- * In child, pid is 28427.
- *
- * <b>Subprocess Created</b>
- *
- * If +path+ is <tt>'|command'</tt> (<tt>'command' != '-'</tt>),
- * a new subprocess runs the command; its open stream is returned.
- * Note that the command may be processed by shell if it contains
- * shell metacharacters.
- *
- * With no block given:
- *
- * io = open('|echo "Hi!"') # => #<IO:fd 12>
- * print io.gets
- * io.close
- *
- * Output:
- *
- * "Hi!"
- *
- * With a block given, calls the block with the stream, then closes the stream:
- *
- * open('|echo "Hi!"') do |io|
- * print io.gets
- * end
- *
- * Output:
- *
- * "Hi!"
- *
*/
static VALUE
@@ -7934,6 +8213,8 @@ rb_f_open(int argc, VALUE *argv, VALUE _)
else {
VALUE cmd = check_pipe_command(tmp);
if (!NIL_P(cmd)) {
+ // TODO: when removed in 4.0, update command_injection.rdoc
+ rb_warn_deprecated_to_remove_at(4.0, "Calling Kernel#open with a leading '|'", "IO.popen");
argv[0] = cmd;
return rb_io_s_popen(argc, argv, rb_cIO);
}
@@ -7951,13 +8232,13 @@ rb_f_open(int argc, VALUE *argv, VALUE _)
return rb_io_s_open(argc, argv, rb_cFile);
}
-static VALUE rb_io_open_generic(VALUE, VALUE, int, int, const convconfig_t *, mode_t);
+static VALUE rb_io_open_generic(VALUE, VALUE, int, int, const struct rb_io_encoding *, mode_t);
static VALUE
rb_io_open(VALUE io, VALUE filename, VALUE vmode, VALUE vperm, VALUE opt)
{
int oflags, fmode;
- convconfig_t convconfig;
+ struct rb_io_encoding convconfig;
mode_t perm;
rb_io_extract_modeenc(&vmode, &vperm, opt, &oflags, &fmode, &convconfig);
@@ -7967,10 +8248,12 @@ rb_io_open(VALUE io, VALUE filename, VALUE vmode, VALUE vperm, VALUE opt)
static VALUE
rb_io_open_generic(VALUE klass, VALUE filename, int oflags, int fmode,
- const convconfig_t *convconfig, mode_t perm)
+ const struct rb_io_encoding *convconfig, mode_t perm)
{
VALUE cmd;
if (klass == rb_cIO && !NIL_P(cmd = check_pipe_command(filename))) {
+ // TODO: when removed in 4.0, update command_injection.rdoc
+ rb_warn_deprecated_to_remove_at(4.0, "IO process creation with a leading '|'", "IO.popen");
return pipe_open_s(cmd, rb_io_oflags_modestr(oflags), fmode, convconfig);
}
else {
@@ -7984,14 +8267,14 @@ io_reopen(VALUE io, VALUE nfile)
{
rb_io_t *fptr, *orig;
int fd, fd2;
- off_t pos = 0;
+ rb_off_t pos = 0;
nfile = rb_io_get_io(nfile);
GetOpenFile(io, fptr);
GetOpenFile(nfile, orig);
if (fptr == orig) return io;
- if (IS_PREP_STDIO(fptr)) {
+ if (RUBY_IO_EXTERNAL_P(fptr)) {
if ((fptr->stdio_file == stdin && !(orig->mode & FMODE_READABLE)) ||
(fptr->stdio_file == stdout && !(orig->mode & FMODE_WRITABLE)) ||
(fptr->stdio_file == stderr && !(orig->mode & FMODE_WRITABLE))) {
@@ -8017,17 +8300,17 @@ io_reopen(VALUE io, VALUE nfile)
}
/* copy rb_io_t structure */
- fptr->mode = orig->mode | (fptr->mode & FMODE_PREP);
+ fptr->mode = orig->mode | (fptr->mode & FMODE_EXTERNAL);
fptr->pid = orig->pid;
fptr->lineno = orig->lineno;
if (RTEST(orig->pathv)) fptr->pathv = orig->pathv;
- else if (!IS_PREP_STDIO(fptr)) fptr->pathv = Qnil;
+ else if (!RUBY_IO_EXTERNAL_P(fptr)) fptr->pathv = Qnil;
fptr_copy_finalizer(fptr, orig);
fd = fptr->fd;
fd2 = orig->fd;
if (fd != fd2) {
- if (IS_PREP_STDIO(fptr) || fd <= 2 || !fptr->stdio_file) {
+ if (RUBY_IO_EXTERNAL_P(fptr) || fd <= 2 || !fptr->stdio_file) {
/* need to keep FILE objects of stdin, stdout and stderr */
if (rb_cloexec_dup2(fd2, fd) < 0)
rb_sys_fail_path(orig->pathv);
@@ -8132,10 +8415,10 @@ rb_io_reopen(int argc, VALUE *argv, VALUE file)
if (!NIL_P(nmode) || !NIL_P(opt)) {
int fmode;
- convconfig_t convconfig;
+ struct rb_io_encoding convconfig;
rb_io_extract_modeenc(&nmode, 0, opt, &oflags, &fmode, &convconfig);
- if (IS_PREP_STDIO(fptr) &&
+ if (RUBY_IO_EXTERNAL_P(fptr) &&
((fptr->mode & FMODE_READWRITE) & (fmode & FMODE_READWRITE)) !=
(fptr->mode & FMODE_READWRITE)) {
rb_raise(rb_eArgError,
@@ -8204,7 +8487,7 @@ rb_io_init_copy(VALUE dest, VALUE io)
rb_io_t *fptr, *orig;
int fd;
VALUE write_io;
- off_t pos;
+ rb_off_t pos;
io = rb_io_get_io(io);
if (!OBJ_INIT_COPY(dest, io)) return dest;
@@ -8214,10 +8497,11 @@ rb_io_init_copy(VALUE dest, VALUE io)
rb_io_flush(io);
/* copy rb_io_t structure */
- fptr->mode = orig->mode & ~FMODE_PREP;
+ fptr->mode = orig->mode & ~FMODE_EXTERNAL;
fptr->encs = orig->encs;
fptr->pid = orig->pid;
fptr->lineno = orig->lineno;
+ fptr->timeout = orig->timeout;
if (!NIL_P(orig->pathv)) fptr->pathv = orig->pathv;
fptr_copy_finalizer(fptr, orig);
@@ -8316,7 +8600,7 @@ deprecated_str_setter(VALUE val, ID id, VALUE *var)
{
rb_str_setter(val, id, &val);
if (!NIL_P(val)) {
- rb_warn_deprecated("`%s'", NULL, rb_id2name(id));
+ rb_warn_deprecated("'%s'", NULL, rb_id2name(id));
}
*var = val;
}
@@ -8328,6 +8612,7 @@ deprecated_str_setter(VALUE val, ID id, VALUE *var)
* Writes the given objects to the stream; returns +nil+.
* Appends the output record separator <tt>$OUTPUT_RECORD_SEPARATOR</tt>
* (<tt>$\\</tt>), if it is not +nil+.
+ * See {Line IO}[rdoc-ref:IO@Line+IO].
*
* With argument +objects+ given, for each object:
*
@@ -8465,6 +8750,7 @@ rb_f_print(int argc, const VALUE *argv, VALUE _)
* putc(object) -> object
*
* Writes a character to the stream.
+ * See {Character IO}[rdoc-ref:IO@Character+IO].
*
* If +object+ is numeric, converts to integer if necessary,
* then writes the character whose code is the
@@ -8568,15 +8854,16 @@ io_puts_ary(VALUE ary, VALUE out, int recur)
* returns +nil+.\
* Writes a newline after each that does not already end with a newline sequence.
* If called without arguments, writes a newline.
+ * See {Line IO}[rdoc-ref:IO@Line+IO].
*
* Note that each added newline is the character <tt>"\n"<//tt>,
* not the output record separator (<tt>$\\</tt>).
*
* Treatment for each object:
*
- * - \String: writes the string.
+ * - String: writes the string.
* - Neither string nor array: writes <tt>object.to_s</tt>.
- * - \Array: writes each element of the array; arrays may be nested.
+ * - Array: writes each element of the array; arrays may be nested.
*
* To keep these examples brief, we define this helper method:
*
@@ -8609,7 +8896,6 @@ io_puts_ary(VALUE ary, VALUE out, int recur)
VALUE
rb_io_puts(int argc, const VALUE *argv, VALUE out)
{
- int i, n;
VALUE line, args[2];
/* if no argument given, print newline. */
@@ -8617,22 +8903,30 @@ rb_io_puts(int argc, const VALUE *argv, VALUE out)
rb_io_write(out, rb_default_rs);
return Qnil;
}
- for (i=0; i<argc; i++) {
+ for (int i = 0; i < argc; i++) {
+ // Convert the argument to a string:
if (RB_TYPE_P(argv[i], T_STRING)) {
line = argv[i];
- goto string;
}
- if (rb_exec_recursive(io_puts_ary, argv[i], out)) {
+ else if (rb_exec_recursive(io_puts_ary, argv[i], out)) {
continue;
}
- line = rb_obj_as_string(argv[i]);
- string:
- n = 0;
- args[n++] = line;
- if (RSTRING_LEN(line) == 0 ||
- !rb_str_end_with_asciichar(line, '\n')) {
+ else {
+ line = rb_obj_as_string(argv[i]);
+ }
+
+ // Write the line:
+ int n = 0;
+ if (RSTRING_LEN(line) == 0) {
args[n++] = rb_default_rs;
}
+ else {
+ args[n++] = line;
+ if (!rb_str_end_with_asciichar(line, '\n')) {
+ args[n++] = rb_default_rs;
+ }
+ }
+
rb_io_writev(out, n, args);
}
@@ -8725,6 +9019,10 @@ rb_p_result(int argc, const VALUE *argv)
* 0..4
* [0..4, 0..4, 0..4]
*
+ * Kernel#p is designed for debugging purposes.
+ * Ruby implementations may define Kernel#p to be uninterruptible
+ * in whole or in part.
+ * On CRuby, Kernel#p's writing of data is uninterruptible.
*/
static VALUE
@@ -8878,25 +9176,94 @@ stderr_getter(ID id, VALUE *ptr)
}
static VALUE
+allocate_and_open_new_file(VALUE klass)
+{
+ VALUE self = io_alloc(klass);
+ rb_io_make_open_file(self);
+ return self;
+}
+
+VALUE
+rb_io_open_descriptor(VALUE klass, int descriptor, int mode, VALUE path, VALUE timeout, struct rb_io_encoding *encoding)
+{
+ int state;
+ VALUE self = rb_protect(allocate_and_open_new_file, klass, &state);
+ if (state) {
+ /* if we raised an exception allocating an IO object, but the caller
+ intended to transfer ownership of this FD to us, close the fd before
+ raising the exception. Otherwise, we would leak a FD - the caller
+ expects GC to close the file, but we never got around to assigning
+ it to a rb_io. */
+ if (!(mode & FMODE_EXTERNAL)) {
+ maygvl_close(descriptor, 0);
+ }
+ rb_jump_tag(state);
+ }
+
+
+ rb_io_t *io = RFILE(self)->fptr;
+ io->self = self;
+ io->fd = descriptor;
+ io->mode = mode;
+
+ /* At this point, Ruby fully owns the descriptor, and will close it when
+ the IO gets GC'd (unless FMODE_EXTERNAL was set), no matter what happens
+ in the rest of this method. */
+
+ if (NIL_P(path)) {
+ io->pathv = Qnil;
+ }
+ else {
+ StringValue(path);
+ io->pathv = rb_str_new_frozen(path);
+ }
+
+ io->timeout = timeout;
+
+ if (encoding) {
+ io->encs = *encoding;
+ }
+
+ rb_update_max_fd(descriptor);
+
+ return self;
+}
+
+static VALUE
prep_io(int fd, int fmode, VALUE klass, const char *path)
{
- rb_io_t *fp;
- VALUE io = io_alloc(klass);
+ VALUE path_value = Qnil;
+ rb_encoding *e;
+ struct rb_io_encoding convconfig;
- MakeOpenFile(io, fp);
- fp->self = io;
- fp->fd = fd;
- fp->mode = fmode;
- if (!io_check_tty(fp)) {
+ if (path) {
+ path_value = rb_obj_freeze(rb_str_new_cstr(path));
+ }
+
+ e = (fmode & FMODE_BINMODE) ? rb_ascii8bit_encoding() : NULL;
+ rb_io_ext_int_to_encs(e, NULL, &convconfig.enc, &convconfig.enc2, fmode);
+ convconfig.ecflags = (fmode & FMODE_READABLE) ?
+ MODE_BTMODE(ECONV_DEFAULT_NEWLINE_DECORATOR,
+ 0, ECONV_UNIVERSAL_NEWLINE_DECORATOR) : 0;
+#ifdef TEXTMODE_NEWLINE_DECORATOR_ON_WRITE
+ convconfig.ecflags |= (fmode & FMODE_WRITABLE) ?
+ MODE_BTMODE(TEXTMODE_NEWLINE_DECORATOR_ON_WRITE,
+ 0, TEXTMODE_NEWLINE_DECORATOR_ON_WRITE) : 0;
+#endif
+ SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(convconfig.enc2, convconfig.ecflags);
+ convconfig.ecopts = Qnil;
+
+ VALUE self = rb_io_open_descriptor(klass, fd, fmode, path_value, Qnil, &convconfig);
+ rb_io_t*io = RFILE(self)->fptr;
+
+ if (!io_check_tty(io)) {
#ifdef __CYGWIN__
- fp->mode |= FMODE_BINMODE;
+ io->mode |= FMODE_BINMODE;
setmode(fd, O_BINARY);
#endif
}
- if (path) fp->pathv = rb_obj_freeze(rb_str_new_cstr(path));
- rb_update_max_fd(fd);
- return io;
+ return self;
}
VALUE
@@ -8912,7 +9279,7 @@ static VALUE
prep_stdio(FILE *f, int fmode, VALUE klass, const char *path)
{
rb_io_t *fptr;
- VALUE io = prep_io(fileno(f), fmode|FMODE_PREP|DEFAULT_TEXTMODE, klass, path);
+ VALUE io = prep_io(fileno(f), fmode|FMODE_EXTERNAL|DEFAULT_TEXTMODE, klass, path);
GetOpenFile(io, fptr);
fptr->encs.ecflags |= ECONV_DEFAULT_NEWLINE_DECORATOR;
@@ -8956,7 +9323,7 @@ rb_io_stdio_file(rb_io_t *fptr)
}
static inline void
-rb_io_buffer_init(rb_io_buffer_t *buf)
+rb_io_buffer_init(struct rb_io_internal_buffer *buf)
{
buf->ptr = NULL;
buf->off = 0;
@@ -8991,6 +9358,7 @@ rb_io_fptr_new(void)
fp->encs.ecflags = 0;
fp->encs.ecopts = Qnil;
fp->write_lock = Qnil;
+ fp->timeout = Qnil;
return fp;
}
@@ -9034,8 +9402,8 @@ rb_io_make_open_file(VALUE obj)
* io = IO.new(fd)
* io.external_encoding # => #<Encoding:UTF-8> # Not ASCII-8BIT.
*
- * Optional argument +mode+ (defaults to 'r') must specify a valid mode
- * see IO@Modes:
+ * Optional argument +mode+ (defaults to 'r') must specify a valid mode;
+ * see {Access Modes}[rdoc-ref:File@Access+Modes]:
*
* IO.new(fd, 'w') # => #<IO:fd 3>
* IO.new(fd, File::WRONLY) # => #<IO:fd 3>
@@ -9058,7 +9426,7 @@ rb_io_initialize(int argc, VALUE *argv, VALUE io)
VALUE fnum, vmode;
rb_io_t *fp;
int fd, fmode, oflags = O_RDONLY;
- convconfig_t convconfig;
+ struct rb_io_encoding convconfig;
VALUE opt;
#if defined(HAVE_FCNTL) && defined(F_GETFL)
int ofmode;
@@ -9091,14 +9459,27 @@ rb_io_initialize(int argc, VALUE *argv, VALUE io)
rb_exc_raise(rb_class_new_instance(1, &error, rb_eSystemCallError));
}
#endif
- if (!NIL_P(opt) && rb_hash_aref(opt, sym_autoclose) == Qfalse) {
- fmode |= FMODE_PREP;
+ VALUE path = Qnil;
+
+ if (!NIL_P(opt)) {
+ if (rb_hash_aref(opt, sym_autoclose) == Qfalse) {
+ fmode |= FMODE_EXTERNAL;
+ }
+
+ path = rb_hash_aref(opt, RB_ID2SYM(idPath));
+ if (!NIL_P(path)) {
+ StringValue(path);
+ path = rb_str_new_frozen(path);
+ }
}
+
MakeOpenFile(io, fp);
fp->self = io;
fp->fd = fd;
fp->mode = fmode;
fp->encs = convconfig;
+ fp->pathv = path;
+ fp->timeout = Qnil;
clear_codeconv(fp);
io_check_tty(fp);
if (fileno(stdin) == fd)
@@ -9161,39 +9542,40 @@ rb_io_set_encoding_by_bom(VALUE io)
* File.new(path, mode = 'r', perm = 0666, **opts) -> file
*
* Opens the file at the given +path+ according to the given +mode+;
- * creates and returns a new \File object for that file.
+ * creates and returns a new File object for that file.
*
- * The new \File object is buffered mode (or non-sync mode), unless
+ * The new File object is buffered mode (or non-sync mode), unless
* +filename+ is a tty.
* See IO#flush, IO#fsync, IO#fdatasync, and IO#sync=.
*
* Argument +path+ must be a valid file path:
*
- * File.new('/etc/fstab')
- * File.new('t.txt')
+ * f = File.new('/etc/fstab')
+ * f.close
+ * f = File.new('t.txt')
+ * f.close
*
- * Optional argument +mode+ (defaults to 'r') must specify a valid mode
- * see IO@Modes:
+ * Optional argument +mode+ (defaults to 'r') must specify a valid mode;
+ * see {Access Modes}[rdoc-ref:File@Access+Modes]:
*
- * File.new('t.tmp', 'w')
- * File.new('t.tmp', File::RDONLY)
+ * f = File.new('t.tmp', 'w')
+ * f.close
+ * f = File.new('t.tmp', File::RDONLY)
+ * f.close
*
* Optional argument +perm+ (defaults to 0666) must specify valid permissions
- * see {File Permissions}[rdoc-ref:IO@File+Permissions]:
+ * see {File Permissions}[rdoc-ref:File@File+Permissions]:
*
- * File.new('t.tmp', File::CREAT, 0644)
- * File.new('t.tmp', File::CREAT, 0444)
+ * f = File.new('t.tmp', File::CREAT, 0644)
+ * f.close
+ * f = File.new('t.tmp', File::CREAT, 0444)
+ * f.close
*
* Optional keyword arguments +opts+ specify:
*
* - {Open Options}[rdoc-ref:IO@Open+Options].
* - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
*
- * Examples:
- *
- * File.new('t.tmp', autoclose: true)
- * File.new('t.tmp', internal_encoding: nil)
- *
*/
static VALUE
@@ -9250,7 +9632,7 @@ rb_io_s_for_fd(int argc, VALUE *argv, VALUE klass)
* ios.autoclose? -> true or false
*
* Returns +true+ if the underlying file descriptor of _ios_ will be
- * closed automatically at its finalization, otherwise +false+.
+ * closed at its finalization or at calling #close, otherwise +false+.
*/
static VALUE
@@ -9258,7 +9640,7 @@ rb_io_autoclose_p(VALUE io)
{
rb_io_t *fptr = RFILE(io)->fptr;
rb_io_check_closed(fptr);
- return RBOOL(!(fptr->mode & FMODE_PREP));
+ return RBOOL(!(fptr->mode & FMODE_EXTERNAL));
}
/*
@@ -9267,14 +9649,14 @@ rb_io_autoclose_p(VALUE io)
*
* Sets auto-close flag.
*
- * f = open("/dev/null")
- * IO.for_fd(f.fileno)
- * # ...
- * f.gets # may cause Errno::EBADF
+ * f = File.open(File::NULL)
+ * IO.for_fd(f.fileno).close
+ * f.gets # raises Errno::EBADF
*
- * f = open("/dev/null")
- * IO.for_fd(f.fileno).autoclose = false
- * # ...
+ * f = File.open(File::NULL)
+ * g = IO.for_fd(f.fileno)
+ * g.autoclose = false
+ * g.close
* f.gets # won't cause Errno::EBADF
*/
@@ -9284,9 +9666,9 @@ rb_io_set_autoclose(VALUE io, VALUE autoclose)
rb_io_t *fptr;
GetOpenFile(io, fptr);
if (!RTEST(autoclose))
- fptr->mode |= FMODE_PREP;
+ fptr->mode |= FMODE_EXTERNAL;
else
- fptr->mode &= ~FMODE_PREP;
+ fptr->mode &= ~FMODE_EXTERNAL;
return autoclose;
}
@@ -9419,7 +9801,7 @@ wait_mode_sym(VALUE mode)
rb_raise(rb_eArgError, "unsupported mode: %"PRIsVALUE, mode);
}
-static inline rb_io_event_t
+static inline enum rb_io_event
io_event_from_value(VALUE value)
{
int events = RB_NUM2INT(value);
@@ -9450,7 +9832,7 @@ static VALUE
io_wait(int argc, VALUE *argv, VALUE io)
{
VALUE timeout = Qundef;
- rb_io_event_t events = 0;
+ enum rb_io_event events = 0;
int return_io = 0;
// The documented signature for this method is actually incorrect.
@@ -9466,7 +9848,7 @@ io_wait(int argc, VALUE *argv, VALUE io)
if (RB_SYMBOL_P(argv[i])) {
events |= wait_mode_sym(argv[i]);
}
- else if (timeout == Qundef) {
+ else if (UNDEF_P(timeout)) {
rb_time_interval(timeout = argv[i]);
}
else {
@@ -9474,7 +9856,7 @@ io_wait(int argc, VALUE *argv, VALUE io)
}
}
- if (timeout == Qundef) timeout = Qnil;
+ if (UNDEF_P(timeout)) timeout = Qnil;
if (events == 0) {
events = RUBY_IO_READABLE;
@@ -9976,9 +10358,9 @@ static VALUE argf_readline(int, VALUE *, VALUE);
/*
* call-seq:
- * readline(sep = $/, **line_opts) -> string
- * readline(limit, **line_opts) -> string
- * readline(sep, limit, **line_opts) -> string
+ * readline(sep = $/, chomp: false) -> string
+ * readline(limit, chomp: false) -> string
+ * readline(sep, limit, chomp: false) -> string
*
* Equivalent to method Kernel#gets, except that it raises an exception
* if called at end-of-stream:
@@ -9987,6 +10369,8 @@ static VALUE argf_readline(int, VALUE *, VALUE);
* ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"]
* in `readline': end of file reached (EOFError)
*
+ * Optional keyword argument +chomp+ specifies whether line separators
+ * are to be omitted.
*/
static VALUE
@@ -10035,13 +10419,13 @@ static VALUE argf_readlines(int, VALUE *, VALUE);
/*
* call-seq:
- * readlines(sep = $/, **line_opts) -> array
- * readlines(limit, **line_opts) -> array
- * readlines(sep, limit, **line_opts) -> array
+ * readlines(sep = $/, chomp: false, **enc_opts) -> array
+ * readlines(limit, chomp: false, **enc_opts) -> array
+ * readlines(sep, limit, chomp: false, **enc_opts) -> array
*
* Returns an array containing the lines returned by calling
- * Kernel#gets until the end-of-file is reached;
- * (see {Lines}[rdoc-ref:IO@Lines]).
+ * Kernel#gets until the end-of-stream is reached;
+ * (see {Line IO}[rdoc-ref:IO@Line+IO]).
*
* With only string argument +sep+ given,
* returns the remaining lines as determined by line separator +sep+,
@@ -10077,19 +10461,19 @@ static VALUE argf_readlines(int, VALUE *, VALUE);
* $cat t.txt | ruby -e "p readlines 12"
* ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"]
*
- * With arguments +sep+ and +limit+ given, combines the two behaviors;
- * see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit].
- *
- * For all forms above, optional keyword arguments specify:
- *
- * - {Line Options}[rdoc-ref:IO@Line+Options].
- * - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
+ * With arguments +sep+ and +limit+ given,
+ * combines the two behaviors
+ * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]).
*
- * Examples:
+ * Optional keyword argument +chomp+ specifies whether line separators
+ * are to be omitted:
*
* $ cat t.txt | ruby -e "p readlines(chomp: true)"
* ["First line", "Second line", "", "Fourth line", "Fifth line"]
*
+ * Optional keyword arguments +enc_opts+ specify encoding options;
+ * see {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
+ *
*/
static VALUE
@@ -10103,19 +10487,21 @@ rb_f_readlines(int argc, VALUE *argv, VALUE recv)
/*
* call-seq:
- * ARGF.readlines(sep = $/) -> array
- * ARGF.readlines(limit) -> array
- * ARGF.readlines(sep, limit) -> array
+ * ARGF.readlines(sep = $/, chomp: false) -> array
+ * ARGF.readlines(limit, chomp: false) -> array
+ * ARGF.readlines(sep, limit, chomp: false) -> array
*
- * ARGF.to_a(sep = $/) -> array
- * ARGF.to_a(limit) -> array
- * ARGF.to_a(sep, limit) -> array
+ * ARGF.to_a(sep = $/, chomp: false) -> array
+ * ARGF.to_a(limit, chomp: false) -> array
+ * ARGF.to_a(sep, limit, chomp: false) -> array
*
* Reads each file in ARGF in its entirety, returning an Array containing
* lines from the files. Lines are assumed to be separated by _sep_.
*
* lines = ARGF.readlines
* lines[0] #=> "This is line one\n"
+ *
+ * See +IO.readlines+ for a full description of all options.
*/
static VALUE
argf_readlines(int argc, VALUE *argv, VALUE argf)
@@ -10169,7 +10555,7 @@ rb_f_backquote(VALUE obj, VALUE str)
VALUE result;
rb_io_t *fptr;
- SafeStringValue(str);
+ StringValue(str);
rb_last_status_clear();
port = pipe_open_s(str, "r", FMODE_READABLE|DEFAULT_TEXTMODE, NULL);
if (NIL_P(port)) return rb_str_new(0,0);
@@ -10177,8 +10563,7 @@ rb_f_backquote(VALUE obj, VALUE str)
GetOpenFile(port, fptr);
result = read_all(fptr, remain_size(fptr), Qnil);
rb_io_close(port);
- RFILE(port)->fptr = NULL;
- rb_io_fptr_finalize(fptr);
+ rb_io_fptr_cleanup_all(fptr);
RB_GC_GUARD(port);
return result;
@@ -10345,8 +10730,8 @@ static VALUE sym_normal, sym_sequential, sym_random,
struct io_advise_struct {
int fd;
int advice;
- off_t offset;
- off_t len;
+ rb_off_t offset;
+ rb_off_t len;
};
static VALUE
@@ -10393,7 +10778,7 @@ io_advise_sym_to_const(VALUE sym)
}
static VALUE
-do_io_advise(rb_io_t *fptr, VALUE advice, off_t offset, off_t len)
+do_io_advise(rb_io_t *fptr, VALUE advice, rb_off_t offset, rb_off_t len)
{
int rv;
struct io_advise_struct ias;
@@ -10483,7 +10868,7 @@ static VALUE
rb_io_advise(int argc, VALUE *argv, VALUE io)
{
VALUE advice, offset, len;
- off_t off, l;
+ rb_off_t off, l;
rb_io_t *fptr;
rb_scan_args(argc, argv, "12", &advice, &offset, &len);
@@ -10655,6 +11040,13 @@ rb_io_advise(int argc, VALUE *argv, VALUE io)
static VALUE
rb_f_select(int argc, VALUE *argv, VALUE obj)
{
+ VALUE scheduler = rb_fiber_scheduler_current();
+ if (scheduler != Qnil) {
+ // It's optionally supported.
+ VALUE result = rb_fiber_scheduler_io_selectv(scheduler, argc, argv);
+ if (!UNDEF_P(result)) return result;
+ }
+
VALUE timeout;
struct select_args args;
struct timeval timerec;
@@ -11072,7 +11464,7 @@ rb_fcntl(VALUE io, VALUE req, VALUE arg)
* a file-oriented I/O stream. Arguments and results are platform
* dependent.
*
- * If +argument is a number, its value is passed directly;
+ * If +argument+ 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.)
*
@@ -11169,7 +11561,7 @@ rb_f_syscall(int argc, VALUE *argv, VALUE _)
VALUE v = rb_check_string_type(argv[i]);
if (!NIL_P(v)) {
- SafeStringValue(v);
+ StringValue(v);
rb_str_modify(v);
arg[i] = (VALUE)StringValueCStr(v);
}
@@ -11260,6 +11652,11 @@ io_encoding_set(rb_io_t *fptr, VALUE v1, VALUE v2, VALUE opt)
enc2 = NULL;
}
}
+ if (enc2 == rb_ascii8bit_encoding()) {
+ /* If external is ASCII-8BIT, no transcoding */
+ enc = enc2;
+ enc2 = NULL;
+ }
SET_UNIVERSAL_NEWLINE_DECORATOR_IF_ENC2(enc2, ecflags);
ecflags = rb_econv_prepare_options(opt, &ecopts, ecflags);
}
@@ -11320,9 +11717,9 @@ pipe_pair_close(VALUE rw)
* IO.pipe(**opts) -> [read_io, write_io]
* IO.pipe(enc, **opts) -> [read_io, write_io]
* IO.pipe(ext_enc, int_enc, **opts) -> [read_io, write_io]
- * IO.pipe(**opts) {|read_io, write_io] ...} -> object
- * IO.pipe(enc, **opts) {|read_io, write_io] ...} -> object
- * IO.pipe(ext_enc, int_enc, **opts) {|read_io, write_io] ...} -> object
+ * IO.pipe(**opts) {|read_io, write_io| ...} -> object
+ * IO.pipe(enc, **opts) {|read_io, write_io| ...} -> object
+ * IO.pipe(ext_enc, int_enc, **opts) {|read_io, write_io| ...} -> object
*
* Creates a pair of pipe endpoints, +read_io+ and +write_io+,
* connected to each other.
@@ -11518,6 +11915,8 @@ io_s_foreach(VALUE v)
struct getline_arg *arg = (void *)v;
VALUE str;
+ if (arg->limit == 0)
+ rb_raise(rb_eArgError, "invalid limit: 0 for foreach");
while (!NIL_P(str = rb_io_getline_1(arg->rs, arg->limit, arg->chomp, arg->io))) {
rb_lastline_set(str);
rb_yield(str);
@@ -11531,9 +11930,6 @@ io_s_foreach(VALUE v)
* IO.foreach(path, sep = $/, **opts) {|line| block } -> nil
* IO.foreach(path, limit, **opts) {|line| block } -> nil
* IO.foreach(path, sep, limit, **opts) {|line| block } -> nil
- * IO.foreach(command, sep = $/, **opts) {|line| block } -> nil
- * IO.foreach(command, limit, **opts) {|line| block } -> nil
- * IO.foreach(command, sep, limit, **opts) {|line| block } -> nil
* IO.foreach(...) -> an_enumerator
*
* Calls the block with each successive line read from the stream.
@@ -11542,16 +11938,7 @@ io_s_foreach(VALUE v)
* this method has potential security vulnerabilities if called with untrusted input;
* see {Command Injection}[rdoc-ref:command_injection.rdoc].
*
- * The first argument must be a string that is one of the following:
- *
- * - Path: if +self+ is a subclass of \IO (\File, for example),
- * or if the string _does_ _not_ start with the pipe character (<tt>'|'</tt>),
- * the string is the path to a file.
- * - Command: if +self+ is the class \IO,
- * and if the string starts with the pipe character,
- * the rest of the string is a command to be executed as a subprocess.
- * This usage has potential security vulnerabilities if called with untrusted input;
- * see {Command Injection}[rdoc-ref:command_injection.rdoc].
+ * The first argument must be a string that is the path to a file.
*
* With only argument +path+ given, parses lines from the file at the given +path+,
* as determined by the default line separator,
@@ -11587,7 +11974,7 @@ io_s_foreach(VALUE v)
*
* With argument +limit+ given, parses lines as determined by the default
* line separator and the given line-length limit
- * (see {Line Limit}[rdoc-ref:IO@Line+Limit]):
+ * (see {Line Separator}[rdoc-ref:IO@Line+Separator] and {Line Limit}[rdoc-ref:IO@Line+Limit]):
*
* File.foreach('t.txt', 7) {|line| p line }
*
@@ -11603,16 +11990,15 @@ io_s_foreach(VALUE v)
* "Fourth l"
* "line\n"
*
- * With arguments +sep+ and +limit+ given,
- * parses lines as determined by the given
- * line separator and the given line-length limit
- * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]):
+ * With arguments +sep+ and +limit+ given,
+ * combines the two behaviors
+ * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]).
*
* Optional keyword arguments +opts+ specify:
*
* - {Open Options}[rdoc-ref:IO@Open+Options].
* - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
- * - {Line Options}[rdoc-ref:IO@Line+Options].
+ * - {Line Options}[rdoc-ref:IO@Line+IO].
*
* Returns an Enumerator if no block is given.
*
@@ -11645,9 +12031,6 @@ io_s_readlines(VALUE v)
/*
* call-seq:
- * IO.readlines(command, sep = $/, **opts) -> array
- * IO.readlines(command, limit, **opts) -> array
- * IO.readlines(command, sep, limit, **opts) -> array
* IO.readlines(path, sep = $/, **opts) -> array
* IO.readlines(path, limit, **opts) -> array
* IO.readlines(path, sep, limit, **opts) -> array
@@ -11658,19 +12041,7 @@ io_s_readlines(VALUE v)
* this method has potential security vulnerabilities if called with untrusted input;
* see {Command Injection}[rdoc-ref:command_injection.rdoc].
*
- * The first argument must be a string;
- * its meaning depends on whether it starts with the pipe character (<tt>'|'</tt>):
- *
- * - If so (and if +self+ is \IO),
- * the rest of the string is a command to be executed as a subprocess.
- * - Otherwise, the string is the path to a file.
- *
- * With only argument +command+ given, executes the command in a shell,
- * parses its $stdout into lines, as determined by the default line separator,
- * and returns those lines in an array:
- *
- * IO.readlines('| cat t.txt')
- * # => ["First line\n", "Second line\n", "\n", "Third line\n", "Fourth line\n"]
+ * The first argument must be a string that is the path to a file.
*
* With only argument +path+ given, parses lines from the file at the given +path+,
* as determined by the default line separator,
@@ -11679,8 +12050,6 @@ io_s_readlines(VALUE v)
* IO.readlines('t.txt')
* # => ["First line\n", "Second line\n", "\n", "Third line\n", "Fourth line\n"]
*
- * For both forms, command and path, the remaining arguments are the same.
- *
* With argument +sep+ given, parses lines as determined by that line separator
* (see {Line Separator}[rdoc-ref:IO@Line+Separator]):
*
@@ -11696,21 +12065,20 @@ io_s_readlines(VALUE v)
*
* With argument +limit+ given, parses lines as determined by the default
* line separator and the given line-length limit
- * (see {Line Limit}[rdoc-ref:IO@Line+Limit]):
+ * (see {Line Separator}[rdoc-ref:IO@Line+Separator] and {Line Limit}[rdoc-ref:IO@Line+Limit]:
*
* IO.readlines('t.txt', 7)
* # => ["First l", "ine\n", "Second ", "line\n", "\n", "Third l", "ine\n", "Fourth ", "line\n"]
*
- * With arguments +sep+ and +limit+ given,
- * parses lines as determined by the given
- * line separator and the given line-length limit
- * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]):
+ * With arguments +sep+ and +limit+ given,
+ * combines the two behaviors
+ * (see {Line Separator and Line Limit}[rdoc-ref:IO@Line+Separator+and+Line+Limit]).
*
* Optional keyword arguments +opts+ specify:
*
* - {Open Options}[rdoc-ref:IO@Open+Options].
* - {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
- * - {Line Options}[rdoc-ref:IO@Line+Options].
+ * - {Line Options}[rdoc-ref:IO@Line+IO].
*
*/
@@ -11753,7 +12121,6 @@ seek_before_access(VALUE argp)
/*
* call-seq:
- * IO.read(command, length = nil, offset = 0, **opts) -> string or nil
* IO.read(path, length = nil, offset = 0, **opts) -> string or nil
*
* Opens the stream, reads and returns some or all of its content,
@@ -11763,18 +12130,7 @@ seek_before_access(VALUE argp)
* this method has potential security vulnerabilities if called with untrusted input;
* see {Command Injection}[rdoc-ref:command_injection.rdoc].
*
- * The first argument must be a string;
- * its meaning depends on whether it starts with the pipe character (<tt>'|'</tt>):
- *
- * - If so (and if +self+ is \IO),
- * the rest of the string is a command to be executed as a subprocess.
- * - Otherwise, the string is the path to a file.
- *
- * With only argument +command+ given, executes the command in a shell,
- * returns its entire $stdout:
- *
- * IO.read('| cat t.txt')
- * # => "First line\nSecond line\n\nThird line\nFourth line\n"
+ * The first argument must be a string that is the path to a file.
*
* With only argument +path+ given, reads in text mode and returns the entire content
* of the file at the given path:
@@ -11786,8 +12142,6 @@ seek_before_access(VALUE argp)
* unread when encountering certain special bytes. Consider using
* IO.binread if all bytes in the file should be read.
*
- * For both forms, command and path, the remaining arguments are the same.
- *
* With argument +length+, returns +length+ bytes if available:
*
* IO.read('t.txt', 7) # => "First l"
@@ -11811,9 +12165,13 @@ static VALUE
rb_io_s_read(int argc, VALUE *argv, VALUE io)
{
VALUE opt, offset;
+ long off;
struct foreach_arg arg;
argc = rb_scan_args(argc, argv, "13:", NULL, NULL, &offset, NULL, &opt);
+ if (!NIL_P(offset) && (off = NUM2LONG(offset)) < 0) {
+ rb_raise(rb_eArgError, "negative offset %ld given", off);
+ }
open_key_args(io, argc, argv, opt, &arg);
if (NIL_P(arg.io)) return Qnil;
if (!NIL_P(offset)) {
@@ -11834,7 +12192,6 @@ rb_io_s_read(int argc, VALUE *argv, VALUE io)
/*
* call-seq:
- * IO.binread(command, length = nil, offset = 0) -> string or nil
* IO.binread(path, length = nil, offset = 0) -> string or nil
*
* Behaves like IO.read, except that the stream is opened in binary mode
@@ -11858,7 +12215,7 @@ rb_io_s_binread(int argc, VALUE *argv, VALUE io)
|O_BINARY
#endif
};
- convconfig_t convconfig = {NULL, NULL, 0, Qnil};
+ struct rb_io_encoding convconfig = {NULL, NULL, 0, Qnil};
rb_scan_args(argc, argv, "12", NULL, NULL, &offset);
FilePathValue(argv[0]);
@@ -11939,7 +12296,6 @@ io_s_write(int argc, VALUE *argv, VALUE klass, int binary)
/*
* call-seq:
- * IO.write(command, data, **opts) -> integer
* IO.write(path, data, offset = 0, **opts) -> integer
*
* Opens the stream, writes the given +data+ to it,
@@ -11949,25 +12305,9 @@ io_s_write(int argc, VALUE *argv, VALUE klass, int binary)
* this method has potential security vulnerabilities if called with untrusted input;
* see {Command Injection}[rdoc-ref:command_injection.rdoc].
*
- * The first argument must be a string;
- * its meaning depends on whether it starts with the pipe character (<tt>'|'</tt>):
- *
- * - If so (and if +self+ is \IO),
- * the rest of the string is a command to be executed as a subprocess.
- * - Otherwise, the string is the path to a file.
+ * The first argument must be a string that is the path to a file.
*
- * With argument +command+ given, executes the command in a shell,
- * passes +data+ through standard input, writes its output to $stdout,
- * and returns the length of the given +data+:
- *
- * IO.write('| cat', 'Hello World!') # => 12
- *
- * Output:
- *
- * Hello World!
- *
- * With argument +path+ given, writes the given +data+ to the file
- * at that path:
+ * With only argument +path+ given, writes the given +data+ to the file at that path:
*
* IO.write('t.tmp', 'abc') # => 3
* File.read('t.tmp') # => "abc"
@@ -12006,7 +12346,6 @@ rb_io_s_write(int argc, VALUE *argv, VALUE io)
/*
* call-seq:
- * IO.binwrite(command, string, offset = 0) -> integer
* IO.binwrite(path, string, offset = 0) -> integer
*
* Behaves like IO.write, except that the stream is opened in binary mode
@@ -12027,15 +12366,15 @@ rb_io_s_binwrite(int argc, VALUE *argv, VALUE io)
struct copy_stream_struct {
VALUE src;
VALUE dst;
- off_t copy_length; /* (off_t)-1 if not specified */
- off_t src_offset; /* (off_t)-1 if not specified */
+ rb_off_t copy_length; /* (rb_off_t)-1 if not specified */
+ rb_off_t src_offset; /* (rb_off_t)-1 if not specified */
rb_io_t *src_fptr;
rb_io_t *dst_fptr;
unsigned close_src : 1;
unsigned close_dst : 1;
int error_no;
- off_t total;
+ rb_off_t total;
const char *syserr;
const char *notimp;
VALUE th;
@@ -12078,7 +12417,7 @@ maygvl_copy_stream_continue_p(int has_gvl, struct copy_stream_struct *stp)
return FALSE;
}
-struct wait_for_single_fd {
+struct fiber_scheduler_wait_for_arguments {
VALUE scheduler;
rb_io_t *fptr;
@@ -12088,11 +12427,11 @@ struct wait_for_single_fd {
};
static void *
-rb_thread_fiber_scheduler_wait_for(void * _args)
+fiber_scheduler_wait_for(void * _arguments)
{
- struct wait_for_single_fd *args = (struct wait_for_single_fd *)_args;
+ struct fiber_scheduler_wait_for_arguments *arguments = (struct fiber_scheduler_wait_for_arguments *)_arguments;
- args->result = rb_fiber_scheduler_io_wait(args->scheduler, args->fptr->self, INT2NUM(args->events), Qnil);
+ arguments->result = rb_fiber_scheduler_io_wait(arguments->scheduler, arguments->fptr->self, INT2NUM(arguments->events), RUBY_IO_TIMEOUT_DEFAULT);
return NULL;
}
@@ -12102,12 +12441,12 @@ rb_thread_fiber_scheduler_wait_for(void * _args)
STATIC_ASSERT(pollin_expected, POLLIN == RB_WAITFD_IN);
STATIC_ASSERT(pollout_expected, POLLOUT == RB_WAITFD_OUT);
static int
-nogvl_wait_for(VALUE th, rb_io_t *fptr, short events)
+nogvl_wait_for(VALUE th, rb_io_t *fptr, short events, struct timeval *timeout)
{
VALUE scheduler = rb_fiber_scheduler_current_for_thread(th);
if (scheduler != Qnil) {
- struct wait_for_single_fd args = {.scheduler = scheduler, .fptr = fptr, .events = events};
- rb_thread_call_with_gvl(rb_thread_fiber_scheduler_wait_for, &args);
+ struct fiber_scheduler_wait_for_arguments args = {.scheduler = scheduler, .fptr = fptr, .events = events};
+ rb_thread_call_with_gvl(fiber_scheduler_wait_for, &args);
return RTEST(args.result);
}
@@ -12119,22 +12458,32 @@ nogvl_wait_for(VALUE th, rb_io_t *fptr, short events)
fds.fd = fd;
fds.events = events;
- return poll(&fds, 1, -1);
+ int timeout_milliseconds = -1;
+
+ if (timeout) {
+ timeout_milliseconds = (int)(timeout->tv_sec * 1000) + (int)(timeout->tv_usec / 1000);
+ }
+
+ return poll(&fds, 1, timeout_milliseconds);
}
#else /* !USE_POLL */
# define IOWAIT_SYSCALL "select"
static int
-nogvl_wait_for(VALUE th, rb_io_t *fptr, short events)
+nogvl_wait_for(VALUE th, rb_io_t *fptr, short events, struct timeval *timeout)
{
VALUE scheduler = rb_fiber_scheduler_current_for_thread(th);
if (scheduler != Qnil) {
- struct wait_for_single_fd args = {.scheduler = scheduler, .fptr = fptr, .events = events};
- rb_thread_call_with_gvl(rb_thread_fiber_scheduler_wait_for, &args);
+ struct fiber_scheduler_wait_for_arguments args = {.scheduler = scheduler, .fptr = fptr, .events = events};
+ rb_thread_call_with_gvl(fiber_scheduler_wait_for, &args);
return RTEST(args.result);
}
int fd = fptr->fd;
- if (fd == -1) return 0;
+
+ if (fd == -1) {
+ errno = EBADF;
+ return -1;
+ }
rb_fdset_t fds;
int ret;
@@ -12144,16 +12493,18 @@ nogvl_wait_for(VALUE th, rb_io_t *fptr, short events)
switch (events) {
case RB_WAITFD_IN:
- ret = rb_fd_select(fd + 1, &fds, 0, 0, 0);
+ ret = rb_fd_select(fd + 1, &fds, 0, 0, timeout);
break;
case RB_WAITFD_OUT:
- ret = rb_fd_select(fd + 1, 0, &fds, 0, 0);
+ ret = rb_fd_select(fd + 1, 0, &fds, 0, timeout);
break;
default:
VM_UNREACHABLE(nogvl_wait_for);
}
rb_fd_term(&fds);
+
+ // On timeout, this returns 0.
return ret;
}
#endif /* !USE_POLL */
@@ -12168,7 +12519,7 @@ maygvl_copy_stream_wait_read(int has_gvl, struct copy_stream_struct *stp)
ret = RB_NUM2INT(rb_io_wait(stp->src, RB_INT2NUM(RUBY_IO_READABLE), Qnil));
}
else {
- ret = nogvl_wait_for(stp->th, stp->src_fptr, RB_WAITFD_IN);
+ ret = nogvl_wait_for(stp->th, stp->src_fptr, RB_WAITFD_IN, NULL);
}
} while (ret < 0 && maygvl_copy_stream_continue_p(has_gvl, stp));
@@ -12186,7 +12537,7 @@ nogvl_copy_stream_wait_write(struct copy_stream_struct *stp)
int ret;
do {
- ret = nogvl_wait_for(stp->th, stp->dst_fptr, RB_WAITFD_OUT);
+ ret = nogvl_wait_for(stp->th, stp->dst_fptr, RB_WAITFD_OUT, NULL);
} while (ret < 0 && maygvl_copy_stream_continue_p(0, stp));
if (ret < 0) {
@@ -12200,7 +12551,7 @@ nogvl_copy_stream_wait_write(struct copy_stream_struct *stp)
#ifdef USE_COPY_FILE_RANGE
static ssize_t
-simple_copy_file_range(int in_fd, off_t *in_offset, int out_fd, off_t *out_offset, size_t count, unsigned int flags)
+simple_copy_file_range(int in_fd, rb_off_t *in_offset, int out_fd, rb_off_t *out_offset, size_t count, unsigned int flags)
{
#ifdef HAVE_COPY_FILE_RANGE
return copy_file_range(in_fd, in_offset, out_fd, out_offset, count, flags);
@@ -12213,15 +12564,15 @@ static int
nogvl_copy_file_range(struct copy_stream_struct *stp)
{
ssize_t ss;
- off_t src_size;
- off_t copy_length, src_offset, *src_offset_ptr;
+ rb_off_t src_size;
+ rb_off_t copy_length, src_offset, *src_offset_ptr;
if (!S_ISREG(stp->src_stat.st_mode))
return 0;
src_size = stp->src_stat.st_size;
src_offset = stp->src_offset;
- if (src_offset >= (off_t)0) {
+ if (src_offset >= (rb_off_t)0) {
src_offset_ptr = &src_offset;
}
else {
@@ -12229,12 +12580,12 @@ nogvl_copy_file_range(struct copy_stream_struct *stp)
}
copy_length = stp->copy_length;
- if (copy_length < (off_t)0) {
- if (src_offset < (off_t)0) {
- off_t current_offset;
+ if (copy_length < (rb_off_t)0) {
+ if (src_offset < (rb_off_t)0) {
+ rb_off_t current_offset;
errno = 0;
current_offset = lseek(stp->src_fptr->fd, 0, SEEK_CUR);
- if (current_offset < (off_t)0 && errno) {
+ if (current_offset < (rb_off_t)0 && errno) {
stp->syserr = "lseek";
stp->error_no = errno;
return (int)current_offset;
@@ -12249,7 +12600,7 @@ nogvl_copy_file_range(struct copy_stream_struct *stp)
retry_copy_file_range:
# if SIZEOF_OFF_T > SIZEOF_SIZE_T
/* we are limited by the 32-bit ssize_t return value on 32-bit */
- ss = (copy_length > (off_t)SSIZE_MAX) ? SSIZE_MAX : (ssize_t)copy_length;
+ ss = (copy_length > (rb_off_t)SSIZE_MAX) ? SSIZE_MAX : (ssize_t)copy_length;
# else
ss = (ssize_t)copy_length;
# endif
@@ -12308,11 +12659,11 @@ nogvl_copy_file_range(struct copy_stream_struct *stp)
static int
nogvl_fcopyfile(struct copy_stream_struct *stp)
{
- off_t cur, ss = 0;
- const off_t src_offset = stp->src_offset;
+ rb_off_t cur, ss = 0;
+ const rb_off_t src_offset = stp->src_offset;
int ret;
- if (stp->copy_length >= (off_t)0) {
+ if (stp->copy_length >= (rb_off_t)0) {
/* copy_length can't be specified in fcopyfile(3) */
return 0;
}
@@ -12322,30 +12673,30 @@ nogvl_fcopyfile(struct copy_stream_struct *stp)
if (!S_ISREG(stp->dst_stat.st_mode))
return 0;
- if (lseek(stp->dst_fptr->fd, 0, SEEK_CUR) > (off_t)0) /* if dst IO was already written */
+ if (lseek(stp->dst_fptr->fd, 0, SEEK_CUR) > (rb_off_t)0) /* if dst IO was already written */
return 0;
if (fcntl(stp->dst_fptr->fd, F_GETFL) & O_APPEND) {
/* fcopyfile(3) appends src IO to dst IO and then truncates
* dst IO to src IO's original size. */
- off_t end = lseek(stp->dst_fptr->fd, 0, SEEK_END);
+ rb_off_t end = lseek(stp->dst_fptr->fd, 0, SEEK_END);
lseek(stp->dst_fptr->fd, 0, SEEK_SET);
- if (end > (off_t)0) return 0;
+ if (end > (rb_off_t)0) return 0;
}
- if (src_offset > (off_t)0) {
- off_t r;
+ if (src_offset > (rb_off_t)0) {
+ rb_off_t r;
/* get current offset */
errno = 0;
cur = lseek(stp->src_fptr->fd, 0, SEEK_CUR);
- if (cur < (off_t)0 && errno) {
+ if (cur < (rb_off_t)0 && errno) {
stp->error_no = errno;
return 1;
}
errno = 0;
r = lseek(stp->src_fptr->fd, src_offset, SEEK_SET);
- if (r < (off_t)0 && errno) {
+ if (r < (rb_off_t)0 && errno) {
stp->error_no = errno;
return 1;
}
@@ -12357,12 +12708,12 @@ nogvl_fcopyfile(struct copy_stream_struct *stp)
if (ret == 0) { /* success */
stp->total = ss;
- if (src_offset > (off_t)0) {
- off_t r;
+ if (src_offset > (rb_off_t)0) {
+ rb_off_t r;
errno = 0;
/* reset offset */
r = lseek(stp->src_fptr->fd, cur, SEEK_SET);
- if (r < (off_t)0 && errno) {
+ if (r < (rb_off_t)0 && errno) {
stp->error_no = errno;
return 1;
}
@@ -12393,7 +12744,7 @@ nogvl_fcopyfile(struct copy_stream_struct *stp)
# endif
static ssize_t
-simple_sendfile(int out_fd, int in_fd, off_t *offset, off_t count)
+simple_sendfile(int out_fd, int in_fd, rb_off_t *offset, rb_off_t count)
{
return sendfile(out_fd, in_fd, offset, (size_t)count);
}
@@ -12405,11 +12756,11 @@ simple_sendfile(int out_fd, int in_fd, off_t *offset, off_t count)
# define USE_SENDFILE
static ssize_t
-simple_sendfile(int out_fd, int in_fd, off_t *offset, off_t count)
+simple_sendfile(int out_fd, int in_fd, rb_off_t *offset, rb_off_t count)
{
int r;
- off_t pos = offset ? *offset : lseek(in_fd, 0, SEEK_CUR);
- off_t sbytes;
+ rb_off_t pos = offset ? *offset : lseek(in_fd, 0, SEEK_CUR);
+ rb_off_t sbytes;
# ifdef __APPLE__
r = sendfile(in_fd, out_fd, pos, &count, NULL, 0);
sbytes = count;
@@ -12435,9 +12786,9 @@ static int
nogvl_copy_stream_sendfile(struct copy_stream_struct *stp)
{
ssize_t ss;
- off_t src_size;
- off_t copy_length;
- off_t src_offset;
+ rb_off_t src_size;
+ rb_off_t copy_length;
+ rb_off_t src_offset;
int use_pread;
if (!S_ISREG(stp->src_stat.st_mode))
@@ -12450,17 +12801,17 @@ nogvl_copy_stream_sendfile(struct copy_stream_struct *stp)
#endif
src_offset = stp->src_offset;
- use_pread = src_offset >= (off_t)0;
+ use_pread = src_offset >= (rb_off_t)0;
copy_length = stp->copy_length;
- if (copy_length < (off_t)0) {
+ if (copy_length < (rb_off_t)0) {
if (use_pread)
copy_length = src_size - src_offset;
else {
- off_t cur;
+ rb_off_t cur;
errno = 0;
cur = lseek(stp->src_fptr->fd, 0, SEEK_CUR);
- if (cur < (off_t)0 && errno) {
+ if (cur < (rb_off_t)0 && errno) {
stp->syserr = "lseek";
stp->error_no = errno;
return (int)cur;
@@ -12472,7 +12823,7 @@ nogvl_copy_stream_sendfile(struct copy_stream_struct *stp)
retry_sendfile:
# if SIZEOF_OFF_T > SIZEOF_SIZE_T
/* we are limited by the 32-bit ssize_t return value on 32-bit */
- ss = (copy_length > (off_t)SSIZE_MAX) ? SSIZE_MAX : (ssize_t)copy_length;
+ ss = (copy_length > (rb_off_t)SSIZE_MAX) ? SSIZE_MAX : (ssize_t)copy_length;
# else
ss = (ssize_t)copy_length;
# endif
@@ -12537,26 +12888,21 @@ static ssize_t
maygvl_read(int has_gvl, rb_io_t *fptr, void *buf, size_t count)
{
if (has_gvl)
- return rb_read_internal(fptr, buf, count);
+ return rb_io_read_memory(fptr, buf, count);
else
return read(fptr->fd, buf, count);
}
static ssize_t
-maygvl_copy_stream_read(int has_gvl, struct copy_stream_struct *stp, char *buf, size_t len, off_t offset)
+maygvl_copy_stream_read(int has_gvl, struct copy_stream_struct *stp, char *buf, size_t len, rb_off_t offset)
{
ssize_t ss;
retry_read:
- if (offset < (off_t)0) {
+ if (offset < (rb_off_t)0) {
ss = maygvl_read(has_gvl, stp->src_fptr, buf, len);
}
else {
-#ifdef HAVE_PREAD
ss = pread(stp->src_fptr->fd, buf, len, offset);
-#else
- stp->notimp = "pread";
- return -1;
-#endif
}
if (ss == 0) {
return 0;
@@ -12580,7 +12926,7 @@ maygvl_copy_stream_read(int has_gvl, struct copy_stream_struct *stp, char *buf,
return ss;
#endif
}
- stp->syserr = offset < (off_t)0 ? "read" : "pread";
+ stp->syserr = offset < (rb_off_t)0 ? "read" : "pread";
stp->error_no = errno;
}
return ss;
@@ -12619,31 +12965,31 @@ nogvl_copy_stream_read_write(struct copy_stream_struct *stp)
size_t len;
ssize_t ss;
int ret;
- off_t copy_length;
+ rb_off_t copy_length;
+ rb_off_t src_offset;
int use_eof;
- off_t src_offset;
int use_pread;
copy_length = stp->copy_length;
- use_eof = copy_length < (off_t)0;
+ use_eof = copy_length < (rb_off_t)0;
src_offset = stp->src_offset;
- use_pread = src_offset >= (off_t)0;
+ use_pread = src_offset >= (rb_off_t)0;
if (use_pread && stp->close_src) {
- off_t r;
+ rb_off_t r;
errno = 0;
r = lseek(stp->src_fptr->fd, src_offset, SEEK_SET);
- if (r < (off_t)0 && errno) {
+ if (r < (rb_off_t)0 && errno) {
stp->syserr = "lseek";
stp->error_no = errno;
return;
}
- src_offset = (off_t)-1;
+ src_offset = (rb_off_t)-1;
use_pread = 0;
}
while (use_eof || 0 < copy_length) {
- if (!use_eof && copy_length < (off_t)sizeof(buf)) {
+ if (!use_eof && copy_length < (rb_off_t)sizeof(buf)) {
len = (size_t)copy_length;
}
else {
@@ -12655,7 +13001,7 @@ nogvl_copy_stream_read_write(struct copy_stream_struct *stp)
src_offset += ss;
}
else {
- ss = maygvl_copy_stream_read(0, stp, buf, len, (off_t)-1);
+ ss = maygvl_copy_stream_read(0, stp, buf, len, (rb_off_t)-1);
}
if (ss <= 0) /* EOF or error */
return;
@@ -12710,8 +13056,8 @@ copy_stream_fallback_body(VALUE arg)
const int buflen = 16*1024;
VALUE n;
VALUE buf = rb_str_buf_new(buflen);
- off_t rest = stp->copy_length;
- off_t off = stp->src_offset;
+ rb_off_t rest = stp->copy_length;
+ rb_off_t off = stp->src_offset;
ID read_method = id_readpartial;
if (!stp->src_fptr) {
@@ -12723,7 +13069,7 @@ copy_stream_fallback_body(VALUE arg)
while (1) {
long numwrote;
long l;
- if (stp->copy_length < (off_t)0) {
+ if (stp->copy_length < (rb_off_t)0) {
l = buflen;
}
else {
@@ -12748,7 +13094,7 @@ copy_stream_fallback_body(VALUE arg)
return Qnil;
if (ss == 0)
rb_eof_error();
- if (off >= (off_t)0)
+ if (off >= (rb_off_t)0)
off += ss;
}
n = rb_io_write(stp->dst, buf);
@@ -12766,7 +13112,7 @@ copy_stream_fallback_body(VALUE arg)
static VALUE
copy_stream_fallback(struct copy_stream_struct *stp)
{
- if (!stp->src_fptr && stp->src_offset >= (off_t)0) {
+ if (!stp->src_fptr && stp->src_offset >= (rb_off_t)0) {
rb_raise(rb_eArgError, "cannot specify src_offset for non-IO");
}
rb_rescue2(copy_stream_fallback_body, (VALUE)stp,
@@ -12866,24 +13212,24 @@ copy_stream_body(VALUE arg)
if (stp->dst_fptr)
io_ascii8bit_binmode(stp->dst_fptr);
- if (stp->src_offset < (off_t)0 && stp->src_fptr && stp->src_fptr->rbuf.len) {
+ if (stp->src_offset < (rb_off_t)0 && stp->src_fptr && stp->src_fptr->rbuf.len) {
size_t len = stp->src_fptr->rbuf.len;
VALUE str;
- if (stp->copy_length >= (off_t)0 && stp->copy_length < (off_t)len) {
+ if (stp->copy_length >= (rb_off_t)0 && stp->copy_length < (rb_off_t)len) {
len = (size_t)stp->copy_length;
}
str = rb_str_buf_new(len);
rb_str_resize(str,len);
read_buffered_data(RSTRING_PTR(str), len, stp->src_fptr);
if (stp->dst_fptr) { /* IO or filename */
- if (io_binwrite(str, RSTRING_PTR(str), RSTRING_LEN(str), stp->dst_fptr, 0) < 0)
+ if (io_binwrite(RSTRING_PTR(str), RSTRING_LEN(str), stp->dst_fptr, 0) < 0)
rb_sys_fail_on_write(stp->dst_fptr);
}
else /* others such as StringIO */
rb_io_write(dst_io, str);
rb_str_resize(str, 0);
stp->total += len;
- if (stp->copy_length >= (off_t)0)
+ if (stp->copy_length >= (rb_off_t)0)
stp->copy_length -= len;
}
@@ -12898,7 +13244,7 @@ copy_stream_body(VALUE arg)
return copy_stream_fallback(stp);
}
- rb_thread_call_without_gvl(nogvl_copy_stream_func, (void*)stp, RUBY_UBF_IO, 0);
+ IO_WITHOUT_GVL(nogvl_copy_stream_func, stp);
return Qnil;
}
@@ -12996,12 +13342,12 @@ rb_io_s_copy_stream(int argc, VALUE *argv, VALUE io)
st.dst_fptr = NULL;
if (NIL_P(length))
- st.copy_length = (off_t)-1;
+ st.copy_length = (rb_off_t)-1;
else
st.copy_length = NUM2OFFT(length);
if (NIL_P(src_offset))
- st.src_offset = (off_t)-1;
+ st.src_offset = (rb_off_t)-1;
else
st.src_offset = NUM2OFFT(src_offset);
@@ -13017,7 +13363,7 @@ rb_io_s_copy_stream(int argc, VALUE *argv, VALUE io)
* Returns the Encoding object that represents the encoding of the stream,
* or +nil+ if the stream is in write mode and no encoding is specified.
*
- * See {Encodings}[rdoc-ref:IO@Encodings].
+ * See {Encodings}[rdoc-ref:File@Encodings].
*
*/
@@ -13045,7 +13391,7 @@ rb_io_external_encoding(VALUE io)
* if conversion is specified,
* or +nil+ otherwise.
*
- * See {Encodings}[rdoc-ref:IO@Encodings].
+ * See {Encodings}[rdoc-ref:File@Encodings].
*
*/
@@ -13064,12 +13410,14 @@ rb_io_internal_encoding(VALUE io)
* set_encoding(ext_enc, int_enc, **enc_opts) -> self
* set_encoding('ext_enc:int_enc', **enc_opts) -> self
*
- * See {Encodings}[rdoc-ref:IO@Encodings].
+ * See {Encodings}[rdoc-ref:File@Encodings].
*
- * Argument +ext_enc+, if given, must be an Encoding object;
+ * Argument +ext_enc+, if given, must be an Encoding object
+ * or a String with the encoding name;
* it is assigned as the encoding for the stream.
*
- * Argument +int_enc+, if given, must be an Encoding object;
+ * Argument +int_enc+, if given, must be an Encoding object
+ * or a String with the encoding name;
* it is assigned as the encoding for the internal string.
*
* Argument <tt>'ext_enc:int_enc'</tt>, if given, is a string
@@ -13077,6 +13425,10 @@ rb_io_internal_encoding(VALUE io)
* corresponding Encoding objects are assigned as the external
* and internal encodings for the stream.
*
+ * If the external encoding of a string is binary/ASCII-8BIT,
+ * the internal encoding of the string is set to nil, since no
+ * transcoding is needed.
+ *
* Optional keyword arguments +enc_opts+ specify
* {Encoding options}[rdoc-ref:encodings.rdoc@Encoding+Options].
*
@@ -13126,6 +13478,17 @@ global_argf_p(VALUE arg)
return arg == argf;
}
+typedef VALUE (*argf_encoding_func)(VALUE io);
+
+static VALUE
+argf_encoding(VALUE argf, argf_encoding_func func)
+{
+ if (!RTEST(ARGF.current_file)) {
+ return rb_enc_default_external();
+ }
+ return func(rb_io_check_io(ARGF.current_file));
+}
+
/*
* call-seq:
* ARGF.external_encoding -> encoding
@@ -13145,10 +13508,7 @@ global_argf_p(VALUE arg)
static VALUE
argf_external_encoding(VALUE argf)
{
- if (!RTEST(ARGF.current_file)) {
- return rb_enc_from_encoding(rb_default_external_encoding());
- }
- return rb_io_external_encoding(rb_io_check_io(ARGF.current_file));
+ return argf_encoding(argf, rb_io_external_encoding);
}
/*
@@ -13167,10 +13527,7 @@ argf_external_encoding(VALUE argf)
static VALUE
argf_internal_encoding(VALUE argf)
{
- if (!RTEST(ARGF.current_file)) {
- return rb_enc_from_encoding(rb_default_external_encoding());
- }
- return rb_io_internal_encoding(rb_io_check_io(ARGF.current_file));
+ return argf_encoding(argf, rb_io_internal_encoding);
}
/*
@@ -13762,7 +14119,7 @@ static void
argf_block_call(ID mid, int argc, VALUE *argv, VALUE argf)
{
VALUE ret = ARGF_block_call(mid, argc, argv, argf_block_call_i, argf);
- if (ret != Qundef) ARGF.next_p = 1;
+ if (!UNDEF_P(ret)) ARGF.next_p = 1;
}
static VALUE
@@ -13778,7 +14135,7 @@ static void
argf_block_call_line(ID mid, int argc, VALUE *argv, VALUE argf)
{
VALUE ret = ARGF_block_call(mid, argc, argv, argf_block_call_line_i, argf);
- if (ret != Qundef) ARGF.next_p = 1;
+ if (!UNDEF_P(ret)) ARGF.next_p = 1;
}
/*
@@ -14079,7 +14436,7 @@ argf_closed(VALUE argf)
{
next_argv();
ARGF_FORWARD(0, 0);
- return rb_io_closed(ARGF.current_file);
+ return rb_io_closed_p(ARGF.current_file);
}
/*
@@ -14214,14 +14571,14 @@ argf_write_io(VALUE argf)
/*
* call-seq:
- * ARGF.write(string) -> integer
+ * ARGF.write(*objects) -> integer
*
- * Writes _string_ if inplace mode.
+ * Writes each of the given +objects+ if inplace mode.
*/
static VALUE
-argf_write(VALUE argf, VALUE str)
+argf_write(int argc, VALUE *argv, VALUE argf)
{
- return rb_io_write(argf_write_io(argf), str);
+ return rb_io_writev(argf_write_io(argf), argc, argv);
}
void
@@ -14327,42 +14684,253 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
/*
* Document-class: ARGF
*
- * ARGF is a stream designed for use in scripts that process files given as
- * command-line arguments or passed in via STDIN.
+ * == \ARGF and +ARGV+
+ *
+ * The \ARGF object works with the array at global variable +ARGV+
+ * to make <tt>$stdin</tt> and file streams available in the Ruby program:
+ *
+ * - **ARGV** may be thought of as the <b>argument vector</b> array.
+ *
+ * Initially, it contains the command-line arguments and options
+ * that are passed to the Ruby program;
+ * the program can modify that array as it likes.
+ *
+ * - **ARGF** may be thought of as the <b>argument files</b> object.
+ *
+ * It can access file streams and/or the <tt>$stdin</tt> stream,
+ * based on what it finds in +ARGV+.
+ * This provides a convenient way for the command line
+ * to specify streams for a Ruby program to read.
+ *
+ * == Reading
+ *
+ * \ARGF may read from _source_ streams,
+ * which at any particular time are determined by the content of +ARGV+.
+ *
+ * === Simplest Case
+ *
+ * When the <i>very first</i> \ARGF read occurs with an empty +ARGV+ (<tt>[]</tt>),
+ * the source is <tt>$stdin</tt>:
+ *
+ * - \File +t.rb+:
+ *
+ * p ['ARGV', ARGV]
+ * p ['ARGF.read', ARGF.read]
+ *
+ * - Commands and outputs
+ * (see below for the content of files +foo.txt+ and +bar.txt+):
+ *
+ * $ echo "Open the pod bay doors, Hal." | ruby t.rb
+ * ["ARGV", []]
+ * ["ARGF.read", "Open the pod bay doors, Hal.\n"]
+ *
+ * $ cat foo.txt bar.txt | ruby t.rb
+ * ["ARGV", []]
+ * ["ARGF.read", "Foo 0\nFoo 1\nBar 0\nBar 1\nBar 2\nBar 3\n"]
*
- * The arguments passed to your script are stored in the +ARGV+ Array, one
- * argument per element. ARGF assumes that any arguments that aren't
- * filenames have been removed from +ARGV+. For example:
+ * === About the Examples
*
- * $ ruby argf.rb --verbose file1 file2
+ * Many examples here assume the existence of files +foo.txt+ and +bar.txt+:
*
- * ARGV #=> ["--verbose", "file1", "file2"]
- * option = ARGV.shift #=> "--verbose"
- * ARGV #=> ["file1", "file2"]
+ * $ cat foo.txt
+ * Foo 0
+ * Foo 1
+ * $ cat bar.txt
+ * Bar 0
+ * Bar 1
+ * Bar 2
+ * Bar 3
*
- * You can now use ARGF to work with a concatenation of each of these named
- * files. For instance, ARGF.read will return the contents of _file1_
- * followed by the contents of _file2_.
+ * === Sources in +ARGV+
*
- * After a file in +ARGV+ has been read ARGF removes it from the Array.
- * Thus, after all files have been read +ARGV+ will be empty.
+ * For any \ARGF read _except_ the {simplest case}[rdoc-ref:ARGF@Simplest+Case]
+ * (that is, _except_ for the <i>very first</i> \ARGF read with an empty +ARGV+),
+ * the sources are found in +ARGV+.
*
- * You can manipulate +ARGV+ yourself to control what ARGF operates on. If
- * you remove a file from +ARGV+, it is ignored by ARGF; if you add files to
- * +ARGV+, they are treated as if they were named on the command line. For
- * example:
+ * \ARGF assumes that each element in array +ARGV+ is a potential source,
+ * and is one of:
*
- * ARGV.replace ["file1"]
- * ARGF.readlines # Returns the contents of file1 as an Array
- * ARGV #=> []
- * ARGV.replace ["file2", "file3"]
- * ARGF.read # Returns the contents of file2 and file3
+ * - The string path to a file that may be opened as a stream.
+ * - The character <tt>'-'</tt>, meaning stream <tt>$stdin</tt>.
*
- * If +ARGV+ is empty, ARGF acts as if it contained STDIN, i.e. the data
- * piped to your script. For example:
+ * Each element that is _not_ one of these
+ * should be removed from +ARGV+ before \ARGF accesses that source.
+ *
+ * In the following example:
+ *
+ * - Filepaths +foo.txt+ and +bar.txt+ may be retained as potential sources.
+ * - Options <tt>--xyzzy</tt> and <tt>--mojo</tt> should be removed.
+ *
+ * Example:
+ *
+ * - \File +t.rb+:
+ *
+ * # Print arguments (and options, if any) found on command line.
+ * p ['ARGV', ARGV]
+ *
+ * - Command and output:
+ *
+ * $ ruby t.rb --xyzzy --mojo foo.txt bar.txt
+ * ["ARGV", ["--xyzzy", "--mojo", "foo.txt", "bar.txt"]]
+ *
+ * \ARGF's stream access considers the elements of +ARGV+, left to right:
+ *
+ * - \File +t.rb+:
+ *
+ * p "ARGV: #{ARGV}"
+ * p "Line: #{ARGF.read}" # Read everything from all specified streams.
+ *
+ * - Command and output:
+ *
+ * $ ruby t.rb foo.txt bar.txt
+ * "ARGV: [\"foo.txt\", \"bar.txt\"]"
+ * "Read: Foo 0\nFoo 1\nBar 0\nBar 1\nBar 2\nBar 3\n"
+ *
+ * Because the value at +ARGV+ is an ordinary array,
+ * you can manipulate it to control which sources \ARGF considers:
+ *
+ * - If you remove an element from +ARGV+, \ARGF will not consider the corresponding source.
+ * - If you add an element to +ARGV+, \ARGF will consider the corresponding source.
+ *
+ * Each element in +ARGV+ is removed when its corresponding source is accessed;
+ * when all sources have been accessed, the array is empty:
+ *
+ * - \File +t.rb+:
+ *
+ * until ARGV.empty? && ARGF.eof?
+ * p "ARGV: #{ARGV}"
+ * p "Line: #{ARGF.readline}" # Read each line from each specified stream.
+ * end
+ *
+ * - Command and output:
+ *
+ * $ ruby t.rb foo.txt bar.txt
+ * "ARGV: [\"foo.txt\", \"bar.txt\"]"
+ * "Line: Foo 0\n"
+ * "ARGV: [\"bar.txt\"]"
+ * "Line: Foo 1\n"
+ * "ARGV: [\"bar.txt\"]"
+ * "Line: Bar 0\n"
+ * "ARGV: []"
+ * "Line: Bar 1\n"
+ * "ARGV: []"
+ * "Line: Bar 2\n"
+ * "ARGV: []"
+ * "Line: Bar 3\n"
+ *
+ * ==== Filepaths in +ARGV+
+ *
+ * The +ARGV+ array may contain filepaths the specify sources for \ARGF reading.
+ *
+ * This program prints what it reads from files at the paths specified
+ * on the command line:
+ *
+ * - \File +t.rb+:
+ *
+ * p ['ARGV', ARGV]
+ * # Read and print all content from the specified sources.
+ * p ['ARGF.read', ARGF.read]
+ *
+ * - Command and output:
+ *
+ * $ ruby t.rb foo.txt bar.txt
+ * ["ARGV", [foo.txt, bar.txt]
+ * ["ARGF.read", "Foo 0\nFoo 1\nBar 0\nBar 1\nBar 2\nBar 3\n"]
+ *
+ * ==== Specifying <tt>$stdin</tt> in +ARGV+
+ *
+ * To specify stream <tt>$stdin</tt> in +ARGV+, us the character <tt>'-'</tt>:
+ *
+ * - \File +t.rb+:
+ *
+ * p ['ARGV', ARGV]
+ * p ['ARGF.read', ARGF.read]
+ *
+ * - Command and output:
+ *
+ * $ echo "Open the pod bay doors, Hal." | ruby t.rb -
+ * ["ARGV", ["-"]]
+ * ["ARGF.read", "Open the pod bay doors, Hal.\n"]
+ *
+ * When no character <tt>'-'</tt> is given, stream <tt>$stdin</tt> is ignored
+ * (exception:
+ * see {Specifying $stdin in ARGV}[rdoc-ref:ARGF@Specifying+-24stdin+in+ARGV]):
+ *
+ * - Command and output:
+ *
+ * $ echo "Open the pod bay doors, Hal." | ruby t.rb foo.txt bar.txt
+ * "ARGV: [\"foo.txt\", \"bar.txt\"]"
+ * "Read: Foo 0\nFoo 1\nBar 0\nBar 1\nBar 2\nBar 3\n"
+ *
+ * ==== Mixtures and Repetitions in +ARGV+
+ *
+ * For an \ARGF reader, +ARGV+ may contain any mixture of filepaths
+ * and character <tt>'-'</tt>, including repetitions.
+ *
+ * ==== Modifications to +ARGV+
+ *
+ * The running Ruby program may make any modifications to the +ARGV+ array;
+ * the current value of +ARGV+ affects \ARGF reading.
+ *
+ * ==== Empty +ARGV+
+ *
+ * For an empty +ARGV+, an \ARGF read method either returns +nil+
+ * or raises an exception, depending on the specific method.
+ *
+ * === More Read Methods
+ *
+ * As seen above, method ARGF#read reads the content of all sources
+ * into a single string.
+ * Other \ARGF methods provide other ways to access that content;
+ * these include:
+ *
+ * - Byte access: #each_byte, #getbyte, #readbyte.
+ * - Character access: #each_char, #getc, #readchar.
+ * - Codepoint access: #each_codepoint.
+ * - Line access: #each_line, #gets, #readline, #readlines.
+ * - Source access: #read, #read_nonblock, #readpartial.
+ *
+ * === About \Enumerable
+ *
+ * \ARGF includes module Enumerable.
+ * Virtually all methods in \Enumerable call method <tt>#each</tt> in the including class.
+ *
+ * <b>Note well</b>: In \ARGF, method #each returns data from the _sources_,
+ * _not_ from +ARGV+;
+ * therefore, for example, <tt>ARGF#entries</tt> returns an array of lines from the sources,
+ * not an array of the strings from +ARGV+:
+ *
+ * - \File +t.rb+:
+ *
+ * p ['ARGV', ARGV]
+ * p ['ARGF.entries', ARGF.entries]
+ *
+ * - Command and output:
+ *
+ * $ ruby t.rb foo.txt bar.txt
+ * ["ARGV", ["foo.txt", "bar.txt"]]
+ * ["ARGF.entries", ["Foo 0\n", "Foo 1\n", "Bar 0\n", "Bar 1\n", "Bar 2\n", "Bar 3\n"]]
+ *
+ * == Writing
+ *
+ * If <i>inplace mode</i> is in effect,
+ * \ARGF may write to target streams,
+ * which at any particular time are determined by the content of ARGV.
+ *
+ * Methods about inplace mode:
+ *
+ * - #inplace_mode
+ * - #inplace_mode=
+ * - #to_write_io
+ *
+ * Methods for writing:
+ *
+ * - #print
+ * - #printf
+ * - #putc
+ * - #puts
+ * - #write
*
- * $ echo "glark" | ruby -e 'p ARGF.read'
- * "glark\n"
*/
/*
@@ -14377,7 +14945,10 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* The global constant ARGF (also accessible as <tt>$<</tt>)
* provides an IO-like stream that allows access to all file paths
* found in ARGV (or found in STDIN if ARGV is empty).
- * Note that ARGF is not itself a subclass of \IO.
+ * ARGF is not itself a subclass of \IO.
+ *
+ * \Class StringIO provides an IO-like stream that handles a String.
+ * StringIO is not itself a subclass of \IO.
*
* Important objects based on \IO include:
*
@@ -14395,20 +14966,23 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* - Kernel#open: Returns a new \IO object connected to a given source:
* stream, file, or subprocess.
*
- * An \IO stream has:
+ * Like a File stream, an \IO stream has:
*
* - A read/write mode, which may be read-only, write-only, or read/write;
- * see {Read/Write Mode}[rdoc-ref:IO@Read-2FWrite+Mode].
+ * see {Read/Write Mode}[rdoc-ref:File@Read-2FWrite+Mode].
* - A data mode, which may be text-only or binary;
- * see {Data Mode}[rdoc-ref:IO@Data+Mode].
+ * see {Data Mode}[rdoc-ref:File@Data+Mode].
+ * - Internal and external encodings;
+ * see {Encodings}[rdoc-ref:File@Encodings].
+ *
+ * And like other \IO streams, it has:
+ *
* - A position, which determines where in the stream the next
* read or write is to occur;
* see {Position}[rdoc-ref:IO@Position].
* - A line number, which is a special, line-oriented, "position"
* (different from the position mentioned above);
* see {Line Number}[rdoc-ref:IO@Line+Number].
- * - Internal and external encodings;
- * see {Encodings}[rdoc-ref:IO@Encodings].
*
* == Extension <tt>io/console</tt>
*
@@ -14418,221 +14992,154 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
*
* == Example Files
*
- * Many examples here use these filenames and their corresponding files:
+ * Many examples here use these variables:
*
- * - <tt>t.txt</tt>: A text-only file that is assumed to exist via:
+ * :include: doc/examples/files.rdoc
*
- * text = <<~EOT
- * First line
- * Second line
- *
- * Fourth line
- * Fifth line
- * EOT
- * File.write('t.txt', text)
- *
- * - <tt>t.dat</tt>: A data file that is assumed to exist via:
- *
- * data = "\u9990\u9991\u9992\u9993\u9994"
- * f = File.open('t.dat', 'wb:UTF-16')
- * f.write(data)
- * f.close
- *
- * - <tt>t.rus</tt>: A Russian-language text file that is assumed to exist via:
- *
- * File.write('t.rus', "\u{442 435 441 442}")
- *
- * - <tt>t.tmp</tt>: A file that is assumed _not_ to exist.
- *
- * == Modes
- *
- * A number of \IO method calls must or may specify a _mode_ for the stream;
- * the mode determines how stream is to be accessible, including:
- *
- * - Whether the stream is to be read-only, write-only, or read-write.
- * - Whether the stream is positioned at its beginning or its end.
- * - Whether the stream treats data as text-only or binary.
- * - The external and internal encodings.
- *
- * === Read/Write Mode
- *
- * ==== Read/Write Mode Specified as an \Integer
+ * == Open Options
*
- * When +mode+ is an integer it must be one or more (combined by bitwise OR (<tt>|</tt>)
- * of the following modes:
+ * A number of \IO methods accept optional keyword arguments
+ * that determine how a new stream is to be opened:
*
- * - +File::RDONLY+: Open for reading only.
- * - +File::WRONLY+: Open for writing only.
- * - +File::RDWR+: Open for reading and writing.
- * - +File::APPEND+: Open for appending only.
- * - +File::CREAT+: Create file if it does not exist.
- * - +File::EXCL+: Raise an exception if +File::CREAT+ is given and the file exists.
+ * - +:mode+: Stream mode.
+ * - +:flags+: Integer file open flags;
+ * If +mode+ is also given, the two are bitwise-ORed.
+ * - +:external_encoding+: External encoding for the stream.
+ * - +:internal_encoding+: Internal encoding for the stream.
+ * <tt>'-'</tt> is a synonym for the default internal encoding.
+ * If the value is +nil+ no conversion occurs.
+ * - +:encoding+: Specifies external and internal encodings as <tt>'extern:intern'</tt>.
+ * - +:textmode+: If a truthy value, specifies the mode as text-only, binary otherwise.
+ * - +:binmode+: If a truthy value, specifies the mode as binary, text-only otherwise.
+ * - +:autoclose+: If a truthy value, specifies that the +fd+ will close
+ * when the stream closes; otherwise it remains open.
+ * - +:path:+ If a string value is provided, it is used in #inspect and is available as
+ * #path method.
*
- * Examples:
+ * Also available are the options offered in String#encode,
+ * which may control conversion between external and internal encoding.
*
- * File.new('t.txt', File::RDONLY)
- * File.new('t.tmp', File::RDWR | File::CREAT | File::EXCL)
+ * == Basic \IO
*
- * Note: Method IO#set_encoding does not allow the mode to be specified as an integer.
+ * You can perform basic stream \IO with these methods,
+ * which typically operate on multi-byte strings:
*
- * ==== Read/Write Mode Specified As a \String
+ * - IO#read: Reads and returns some or all of the remaining bytes from the stream.
+ * - IO#write: Writes zero or more strings to the stream;
+ * each given object that is not already a string is converted via +to_s+.
*
- * When +mode+ is a string it must begin with one of the following:
+ * === Position
*
- * - <tt>'r'</tt>: Read-only stream, positioned at the beginning;
- * the stream cannot be changed to writable.
- * - <tt>'w'</tt>: Write-only stream, positioned at the beginning;
- * the stream cannot be changed to readable.
- * - <tt>'a'</tt>: Write-only stream, positioned at the end;
- * every write appends to the end;
- * the stream cannot be changed to readable.
- * - <tt>'r+'</tt>: Read-write stream, positioned at the beginning.
- * - <tt>'w+'</tt>: Read-write stream, positioned at the end.
- * - <tt>'a+'</tt>: Read-write stream, positioned at the end.
+ * An \IO stream has a nonnegative integer _position_,
+ * which is the byte offset at which the next read or write is to occur.
+ * A new stream has position zero (and line number zero);
+ * method +rewind+ resets the position (and line number) to zero.
*
- * For a writable file stream (that is, any except read-only),
- * the file is truncated to zero if it exists,
- * and is created if it does not exist.
+ * The relevant methods:
*
- * Examples:
+ * - IO#tell (aliased as +#pos+): Returns the current position (in bytes) in the stream.
+ * - IO#pos=: Sets the position of the stream to a given integer +new_position+ (in bytes).
+ * - IO#seek: Sets the position of the stream to a given integer +offset+ (in bytes),
+ * relative to a given position +whence+
+ * (indicating the beginning, end, or current position).
+ * - IO#rewind: Positions the stream at the beginning (also resetting the line number).
*
- * File.open('t.txt', 'r')
- * File.open('t.tmp', 'w')
+ * === Open and Closed Streams
*
- * === Data Mode
+ * A new \IO stream may be open for reading, open for writing, or both.
*
- * Either of the following may be suffixed to any of the string read/write modes above:
+ * A stream is automatically closed when claimed by the garbage collector.
*
- * - <tt>'t'</tt>: Text data; sets the default external encoding to +Encoding::UTF_8+;
- * on Windows, enables conversion between EOL and CRLF and enables interpreting +0x1A+
- * as an end-of-file marker.
- * - <tt>'b'</tt>: Binary data; sets the default external encoding to +Encoding::ASCII_8BIT+;
- * on Windows, suppresses conversion between EOL and CRLF and disables interpreting +0x1A+
- * as an end-of-file marker.
+ * Attempted reading or writing on a closed stream raises an exception.
*
- * If neither is given, the stream defaults to text data.
+ * The relevant methods:
*
- * Examples:
+ * - IO#close: Closes the stream for both reading and writing.
+ * - IO#close_read: Closes the stream for reading.
+ * - IO#close_write: Closes the stream for writing.
+ * - IO#closed?: Returns whether the stream is closed.
*
- * File.open('t.txt', 'rt')
- * File.open('t.dat', 'rb')
+ * === End-of-Stream
*
- * The following may be suffixed to any writable string mode above:
+ * You can query whether a stream is positioned at its end:
*
- * - <tt>'x'</tt>: Creates the file if it does not exist;
- * raises an exception if the file exists.
+ * - IO#eof? (also aliased as +#eof+): Returns whether the stream is at end-of-stream.
*
- * Example:
+ * You can reposition to end-of-stream by using method IO#seek:
*
- * File.open('t.tmp', 'wx')
- *
- * Note that when using integer flags to set the read/write mode, it's not
- * possible to also set the binary data mode by adding the File::BINARY flag
- * to the bitwise OR combination of integer flags. This is because, as
- * documented in File::Constants, the File::BINARY flag only disables line code
- * conversion, but does not change the external encoding at all.
- *
- * == Encodings
- *
- * Any of the string modes above may specify encodings --
- * either external encoding only or both external and internal encodings --
- * by appending one or both encoding names, separated by colons:
- *
- * f = File.new('t.dat', 'rb')
- * f.external_encoding # => #<Encoding:ASCII-8BIT>
- * f.internal_encoding # => nil
- * f = File.new('t.dat', 'rb:UTF-16')
- * f.external_encoding # => #<Encoding:UTF-16 (dummy)>
- * f.internal_encoding # => nil
- * f = File.new('t.dat', 'rb:UTF-16:UTF-16')
- * f.external_encoding # => #<Encoding:UTF-16 (dummy)>
- * f.internal_encoding # => #<Encoding:UTF-16>
+ * f = File.new('t.txt')
+ * f.eof? # => false
+ * f.seek(0, :END)
+ * f.eof? # => true
* f.close
*
- * The numerous encoding names are available in array Encoding.name_list:
+ * Or by reading all stream content (which is slower than using IO#seek):
*
- * Encoding.name_list.size # => 175
- * Encoding.name_list.take(3) # => ["ASCII-8BIT", "UTF-8", "US-ASCII"]
+ * f.rewind
+ * f.eof? # => false
+ * f.read # => "First line\nSecond line\n\nFourth line\nFifth line\n"
+ * f.eof? # => true
*
- * When the external encoding is set,
- * strings read are tagged by that encoding
- * when reading, and strings written are converted to that
- * encoding when writing.
+ * == Line \IO
*
- * When both external and internal encodings are set,
- * strings read are converted from external to internal encoding,
- * and strings written are converted from internal to external encoding.
- * For further details about transcoding input and output, see Encoding.
+ * \Class \IO supports line-oriented
+ * {input}[rdoc-ref:IO@Line+Input] and {output}[rdoc-ref:IO@Line+Output]
*
- * If the external encoding is <tt>'BOM|UTF-8'</tt>, <tt>'BOM|UTF-16LE'</tt>
- * or <tt>'BOM|UTF16-BE'</tt>, Ruby checks for
- * a Unicode BOM in the input document to help determine the encoding. For
- * UTF-16 encodings the file open mode must be binary.
- * If the BOM is found, it is stripped and the external encoding from the BOM is used.
+ * === Line Input
*
- * Note that the BOM-style encoding option is case insensitive,
- * so 'bom|utf-8' is also valid.)
+ * \Class \IO supports line-oriented input for
+ * {files}[rdoc-ref:IO@File+Line+Input] and {IO streams}[rdoc-ref:IO@Stream+Line+Input]
*
- * == Open Options
+ * ==== \File Line Input
*
- * A number of \IO methods accept optional keyword arguments
- * that determine how a new stream is to be opened:
+ * You can read lines from a file using these methods:
*
- * - +:mode+: Stream mode.
- * - +:flags+: \Integer file open flags;
- * If +mode+ is also given, the two are bitwise-ORed.
- * - +:external_encoding+: External encoding for the stream.
- * - +:internal_encoding+: Internal encoding for the stream.
- * <tt>'-'</tt> is a synonym for the default internal encoding.
- * If the value is +nil+ no conversion occurs.
- * - +:encoding+: Specifies external and internal encodings as <tt>'extern:intern'</tt>.
- * - +:textmode+: If a truthy value, specifies the mode as text-only, binary otherwise.
- * - +:binmode+: If a truthy value, specifies the mode as binary, text-only otherwise.
- * - +:autoclose+: If a truthy value, specifies that the +fd+ will close
- * when the stream closes; otherwise it remains open.
+ * - IO.foreach: Reads each line and passes it to the given block.
+ * - IO.readlines: Reads and returns all lines in an array.
*
- * Also available are the options offered in String#encode,
- * which may control conversion between external internal encoding.
+ * For each of these methods:
*
- * == Lines
+ * - You can specify {open options}[rdoc-ref:IO@Open+Options].
+ * - Line parsing depends on the effective <i>line separator</i>;
+ * see {Line Separator}[rdoc-ref:IO@Line+Separator].
+ * - The length of each returned line depends on the effective <i>line limit</i>;
+ * see {Line Limit}[rdoc-ref:IO@Line+Limit].
*
- * Some reader methods in \IO are line-oriented;
- * such a method reads one or more lines,
- * which are separated by an implicit or explicit line separator.
+ * ==== Stream Line Input
*
- * These methods include:
+ * You can read lines from an \IO stream using these methods:
*
- * - Kernel#gets
- * - Kernel#readline
- * - Kernel#readlines
- * - IO.foreach
- * - IO.readlines
- * - IO#each_line
- * - IO#gets
- * - IO#readline
- * - IO#readlines
- * - ARGF.each
- * - ARGF.gets
- * - ARGF.readline
- * - ARGF.readlines
+ * - IO#each_line: Reads each remaining line, passing it to the given block.
+ * - IO#gets: Returns the next line.
+ * - IO#readline: Like #gets, but raises an exception at end-of-stream.
+ * - IO#readlines: Returns all remaining lines in an array.
*
- * Each of these methods returns +nil+ if called when already at end-of-stream,
- * except for IO#readline, which raises an exception.
+ * For each of these methods:
*
- * Each of these methods may be called with:
+ * - Reading may begin mid-line,
+ * depending on the stream's _position_;
+ * see {Position}[rdoc-ref:IO@Position].
+ * - Line parsing depends on the effective <i>line separator</i>;
+ * see {Line Separator}[rdoc-ref:IO@Line+Separator].
+ * - The length of each returned line depends on the effective <i>line limit</i>;
+ * see {Line Limit}[rdoc-ref:IO@Line+Limit].
*
- * - An optional line separator, +sep+.
- * - An optional line-size limit, +limit+.
- * - Both +sep+ and +limit+.
+ * ===== Line Separator
*
- * === Line Separator
+ * Each of the {line input methods}[rdoc-ref:IO@Line+Input] uses a <i>line separator</i>:
+ * the string that determines what is considered a line;
+ * it is sometimes called the <i>input record separator</i>.
*
- * The default line separator is the given by the global variable <tt>$/</tt>,
- * whose value is often <tt>"\n"</tt>.
- * The line to be read next is all data from the current position
- * to the next line separator:
+ * The default line separator is taken from global variable <tt>$/</tt>,
+ * whose initial value is <tt>"\n"</tt>.
*
- * f = File.open('t.txt')
+ * Generally, the line to be read next is all data
+ * from the current {position}[rdoc-ref:IO@Position]
+ * to the next line separator
+ * (but see {Special Line Separator Values}[rdoc-ref:IO@Special+Line+Separator+Values]):
+ *
+ * f = File.new('t.txt')
+ * # Method gets with no sep argument returns the next line, according to $/.
* f.gets # => "First line\n"
* f.gets # => "Second line\n"
* f.gets # => "\n"
@@ -14640,7 +15147,7 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* f.gets # => "Fifth line\n"
* f.close
*
- * You can specify a different line separator:
+ * You can use a different line separator by passing argument +sep+:
*
* f = File.new('t.txt')
* f.gets('l') # => "First l"
@@ -14649,15 +15156,27 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* f.gets # => "e\n"
* f.close
*
- * There are two special line separators:
+ * Or by setting global variable <tt>$/</tt>:
+ *
+ * f = File.new('t.txt')
+ * $/ = 'l'
+ * f.gets # => "First l"
+ * f.gets # => "ine\nSecond l"
+ * f.gets # => "ine\n\nFourth l"
+ * f.close
+ *
+ * ===== Special Line Separator Values
*
- * - +nil+: The entire stream is read into a single string:
+ * Each of the {line input methods}[rdoc-ref:IO@Line+Input]
+ * accepts two special values for parameter +sep+:
+ *
+ * - +nil+: The entire stream is to be read ("slurped") into a single string:
*
* f = File.new('t.txt')
* f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n"
* f.close
*
- * - <tt>''</tt> (the empty string): The next "paragraph" is read
+ * - <tt>''</tt> (the empty string): The next "paragraph" is to be read
* (paragraphs being separated by two consecutive line separators):
*
* f = File.new('t.txt')
@@ -14665,14 +15184,18 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* f.gets('') # => "Fourth line\nFifth line\n"
* f.close
*
- * === Line Limit
+ * ===== Line Limit
+ *
+ * Each of the {line input methods}[rdoc-ref:IO@Line+Input]
+ * uses an integer <i>line limit</i>,
+ * which restricts the number of bytes that may be returned.
+ * (A multi-byte character will not be split, and so a returned line may be slightly longer
+ * than the limit).
*
- * The line to be read may be further defined by an optional argument +limit+,
- * which specifies that the line may not be (much) longer than the given limit;
- * a multi-byte character will not be split, and so a line may be slightly longer
- * than the given limit.
+ * The default limit value is <tt>-1</tt>;
+ * any negative limit value means that there is no limit.
*
- * If +limit+ is not given, the line is determined only by +sep+.
+ * If there is no limit, the line is determined only by +sep+.
*
* # Text with 1-byte characters.
* File.open('t.txt') {|f| f.gets(1) } # => "F"
@@ -14685,37 +15208,55 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* File.open('t.txt') {|f| f.gets(12) } # => "First line\n"
*
* # Text with 2-byte characters, which will not be split.
- * File.open('r.rus') {|f| f.gets(1).size } # => 1
- * File.open('r.rus') {|f| f.gets(2).size } # => 1
- * File.open('r.rus') {|f| f.gets(3).size } # => 2
- * File.open('r.rus') {|f| f.gets(4).size } # => 2
+ * File.open('t.rus') {|f| f.gets(1).size } # => 1
+ * File.open('t.rus') {|f| f.gets(2).size } # => 1
+ * File.open('t.rus') {|f| f.gets(3).size } # => 2
+ * File.open('t.rus') {|f| f.gets(4).size } # => 2
*
- * === Line Separator and Line Limit
+ * ===== Line Separator and Line Limit
*
- * With arguments +sep+ and +limit+ given,
- * combines the two behaviors:
+ * With arguments +sep+ and +limit+ given, combines the two behaviors:
*
* - Returns the next line as determined by line separator +sep+.
- * - But returns no more bytes than are allowed by the limit.
+ * - But returns no more bytes than are allowed by the limit +limit+.
*
* Example:
*
* File.open('t.txt') {|f| f.gets('li', 20) } # => "First li"
* File.open('t.txt') {|f| f.gets('li', 2) } # => "Fi"
*
- * === Line Number
+ * ===== Line Number
+ *
+ * A readable \IO stream has a non-negative integer <i>line number</i>:
+ *
+ * - IO#lineno: Returns the line number.
+ * - IO#lineno=: Resets and returns the line number.
*
- * A readable \IO stream has a _line_ _number_,
- * which is the non-negative integer line number
- * in the stream where the next read will occur.
+ * Unless modified by a call to method IO#lineno=,
+ * the line number is the number of lines read
+ * by certain line-oriented methods,
+ * according to the effective {line separator}[rdoc-ref:IO@Line+Separator]:
*
- * A new stream is initially has line number +0+.
+ * - IO.foreach: Increments the line number on each call to the block.
+ * - IO#each_line: Increments the line number on each call to the block.
+ * - IO#gets: Increments the line number.
+ * - IO#readline: Increments the line number.
+ * - IO#readlines: Increments the line number for each line read.
*
- * \Method IO#lineno returns the line number.
+ * A new stream is initially has line number zero (and position zero);
+ * method +rewind+ resets the line number (and position) to zero:
+ *
+ * f = File.new('t.txt')
+ * f.lineno # => 0
+ * f.gets # => "First line\n"
+ * f.lineno # => 1
+ * f.rewind
+ * f.lineno # => 0
+ * f.close
*
* Reading lines from a stream usually changes its line number:
*
- * f = File.open('t.txt', 'r')
+ * f = File.new('t.txt', 'r')
* f.lineno # => 0
* f.readline # => "This is line one.\n"
* f.lineno # => 1
@@ -14728,24 +15269,93 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
*
* Iterating over lines in a stream usually changes its line number:
*
- * f = File.open('t.txt')
- * f.each_line do |line|
- * p "position=#{f.pos} eof?=#{f.eof?} line=#{line}"
- * end
- * f.close
+ * File.open('t.txt') do |f|
+ * f.each_line do |line|
+ * p "position=#{f.pos} eof?=#{f.eof?} lineno=#{f.lineno}"
+ * end
+ * end
*
* Output:
*
- * "position=19 eof?=false line=This is line one.\n"
- * "position=45 eof?=false line=This is the second line.\n"
- * "position=70 eof?=true line=This is the third line.\n"
+ * "position=11 eof?=false lineno=1"
+ * "position=23 eof?=false lineno=2"
+ * "position=24 eof?=false lineno=3"
+ * "position=36 eof?=false lineno=4"
+ * "position=47 eof?=true lineno=5"
*
- * === Line Options
+ * Unlike the stream's {position}[rdoc-ref:IO@Position],
+ * the line number does not affect where the next read or write will occur:
*
- * A number of \IO methods accept optional keyword arguments
- * that determine how lines in a stream are to be treated:
+ * f = File.new('t.txt')
+ * f.lineno = 1000
+ * f.lineno # => 1000
+ * f.gets # => "First line\n"
+ * f.lineno # => 1001
+ * f.close
+ *
+ * Associated with the line number is the global variable <tt>$.</tt>:
+ *
+ * - When a stream is opened, <tt>$.</tt> is not set;
+ * its value is left over from previous activity in the process:
+ *
+ * $. = 41
+ * f = File.new('t.txt')
+ * $. = 41
+ * # => 41
+ * f.close
+ *
+ * - When a stream is read, <tt>$.</tt> is set to the line number for that stream:
+ *
+ * f0 = File.new('t.txt')
+ * f1 = File.new('t.dat')
+ * f0.readlines # => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"]
+ * $. # => 5
+ * f1.readlines # => ["\xFE\xFF\x99\x90\x99\x91\x99\x92\x99\x93\x99\x94"]
+ * $. # => 1
+ * f0.close
+ * f1.close
+ *
+ * - Methods IO#rewind and IO#seek do not affect <tt>$.</tt>:
+ *
+ * f = File.new('t.txt')
+ * f.readlines # => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"]
+ * $. # => 5
+ * f.rewind
+ * f.seek(0, :SET)
+ * $. # => 5
+ * f.close
*
- * - +:chomp+: If +true+, line separators are omitted; default is +false+.
+ * === Line Output
+ *
+ * You can write to an \IO stream line-by-line using this method:
+ *
+ * - IO#puts: Writes objects to the stream.
+ *
+ * == Character \IO
+ *
+ * You can process an \IO stream character-by-character using these methods:
+ *
+ * - IO#getc: Reads and returns the next character from the stream.
+ * - IO#readchar: Like #getc, but raises an exception at end-of-stream.
+ * - IO#ungetc: Pushes back ("unshifts") a character or integer onto the stream.
+ * - IO#putc: Writes a character to the stream.
+ * - IO#each_char: Reads each remaining character in the stream,
+ * passing the character to the given block.
+ * == Byte \IO
+ *
+ * You can process an \IO stream byte-by-byte using these methods:
+ *
+ * - IO#getbyte: Returns the next 8-bit byte as an integer in range 0..255.
+ * - IO#readbyte: Like #getbyte, but raises an exception if at end-of-stream.
+ * - IO#ungetbyte: Pushes back ("unshifts") a byte back onto the stream.
+ * - IO#each_byte: Reads each remaining byte in the stream,
+ * passing the byte to the given block.
+ *
+ * == Codepoint \IO
+ *
+ * You can process an \IO stream codepoint-by-codepoint:
+ *
+ * - IO#each_codepoint: Reads each remaining codepoint, passing it to the given block.
*
* == What's Here
*
@@ -14794,11 +15404,11 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* - #read_nonblock: the next _n_ bytes read from +self+ for a given _n_,
* in non-block mode.
* - #readbyte: Returns the next byte read from +self+;
- * same as #getbyte, but raises an exception on end-of-file.
+ * same as #getbyte, but raises an exception on end-of-stream.
* - #readchar: Returns the next character read from +self+;
- * same as #getc, but raises an exception on end-of-file.
+ * same as #getc, but raises an exception on end-of-stream.
* - #readline: Returns the next line read from +self+;
- * same as #getline, but raises an exception of end-of-file.
+ * same as #getline, but raises an exception of end-of-stream.
* - #readlines: Returns an array of all lines read read from +self+.
* - #readpartial: Returns up to the given number of bytes from +self+.
*
@@ -14858,7 +15468,7 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y)
* - #binmode?: Returns whether +self+ is in binary mode.
* - #close_on_exec?: Returns the close-on-exec flag for +self+.
* - #closed?: Returns whether +self+ is closed.
- * - #eof? (aliased as #eof): Returns whether +self+ is at end-of-file.
+ * - #eof? (aliased as #eof): Returns whether +self+ is at end-of-stream.
* - #external_encoding: Returns the external encoding object for +self+.
* - #fileno (aliased as #to_i): Returns the integer file descriptor for +self+
* - #internal_encoding: Returns the internal encoding object for +self+.
@@ -14949,8 +15559,14 @@ Init_IO(void)
rb_cIO = rb_define_class("IO", rb_cObject);
rb_include_module(rb_cIO, rb_mEnumerable);
+ /* Can be raised by IO operations when IO#timeout= is set. */
+ rb_eIOTimeoutError = rb_define_class_under(rb_cIO, "TimeoutError", rb_eIOError);
+
+ /* Readable event mask for IO#wait. */
rb_define_const(rb_cIO, "READABLE", INT2NUM(RUBY_IO_READABLE));
+ /* Writable event mask for IO#wait. */
rb_define_const(rb_cIO, "WRITABLE", INT2NUM(RUBY_IO_WRITABLE));
+ /* Priority event mask for IO#wait. */
rb_define_const(rb_cIO, "PRIORITY", INT2NUM(RUBY_IO_PRIORITY));
/* exception to wait for reading. see IO.select. */
@@ -15013,7 +15629,7 @@ Init_IO(void)
rb_define_hooked_variable("$,", &rb_output_fs, 0, deprecated_str_setter);
rb_default_rs = rb_fstring_lit("\n"); /* avoid modifying RS_default */
- rb_gc_register_mark_object(rb_default_rs);
+ rb_vm_register_global_object(rb_default_rs);
rb_rs = rb_default_rs;
rb_output_rs = Qnil;
rb_define_hooked_variable("$/", &rb_rs, 0, deprecated_str_setter);
@@ -15047,23 +15663,25 @@ Init_IO(void)
rb_define_alias(rb_cIO, "to_i", "fileno");
rb_define_method(rb_cIO, "to_io", rb_io_to_io, 0);
- rb_define_method(rb_cIO, "fsync", rb_io_fsync, 0);
- rb_define_method(rb_cIO, "fdatasync", rb_io_fdatasync, 0);
- rb_define_method(rb_cIO, "sync", rb_io_sync, 0);
- rb_define_method(rb_cIO, "sync=", rb_io_set_sync, 1);
+ rb_define_method(rb_cIO, "timeout", rb_io_timeout, 0);
+ rb_define_method(rb_cIO, "timeout=", rb_io_set_timeout, 1);
- rb_define_method(rb_cIO, "lineno", rb_io_lineno, 0);
- rb_define_method(rb_cIO, "lineno=", rb_io_set_lineno, 1);
+ rb_define_method(rb_cIO, "fsync", rb_io_fsync, 0);
+ rb_define_method(rb_cIO, "fdatasync", rb_io_fdatasync, 0);
+ rb_define_method(rb_cIO, "sync", rb_io_sync, 0);
+ rb_define_method(rb_cIO, "sync=", rb_io_set_sync, 1);
- rb_define_method(rb_cIO, "readlines", rb_io_readlines, -1);
+ rb_define_method(rb_cIO, "lineno", rb_io_lineno, 0);
+ rb_define_method(rb_cIO, "lineno=", rb_io_set_lineno, 1);
- rb_define_method(rb_cIO, "readpartial", io_readpartial, -1);
- rb_define_method(rb_cIO, "read", io_read, -1);
+ rb_define_method(rb_cIO, "readlines", rb_io_readlines, -1);
+
+ rb_define_method(rb_cIO, "readpartial", io_readpartial, -1);
+ rb_define_method(rb_cIO, "read", io_read, -1);
rb_define_method(rb_cIO, "write", io_write_m, -1);
- rb_define_method(rb_cIO, "gets", rb_io_gets_m, -1);
- rb_define_method(rb_cIO, "readline", rb_io_readline, -1);
- rb_define_method(rb_cIO, "getc", rb_io_getc, 0);
- rb_define_method(rb_cIO, "getbyte", rb_io_getbyte, 0);
+ rb_define_method(rb_cIO, "gets", rb_io_gets_m, -1);
+ rb_define_method(rb_cIO, "getc", rb_io_getc, 0);
+ rb_define_method(rb_cIO, "getbyte", rb_io_getbyte, 0);
rb_define_method(rb_cIO, "readchar", rb_io_readchar, 0);
rb_define_method(rb_cIO, "readbyte", rb_io_readbyte, 0);
rb_define_method(rb_cIO, "ungetbyte",rb_io_ungetbyte, 1);
@@ -15096,7 +15714,7 @@ Init_IO(void)
rb_define_method(rb_cIO, "close_on_exec=", rb_io_set_close_on_exec, 1);
rb_define_method(rb_cIO, "close", rb_io_close_m, 0);
- rb_define_method(rb_cIO, "closed?", rb_io_closed, 0);
+ rb_define_method(rb_cIO, "closed?", rb_io_closed_p, 0);
rb_define_method(rb_cIO, "close_read", rb_io_close_read, 0);
rb_define_method(rb_cIO, "close_write", rb_io_close_write, 0);
@@ -15110,6 +15728,10 @@ Init_IO(void)
rb_define_method(rb_cIO, "ioctl", rb_io_ioctl, -1);
rb_define_method(rb_cIO, "fcntl", rb_io_fcntl, -1);
rb_define_method(rb_cIO, "pid", rb_io_pid, 0);
+
+ rb_define_method(rb_cIO, "path", rb_io_path, 0);
+ rb_define_method(rb_cIO, "to_path", rb_io_path, 0);
+
rb_define_method(rb_cIO, "inspect", rb_io_inspect, 0);
rb_define_method(rb_cIO, "external_encoding", rb_io_external_encoding, 0);
@@ -15136,13 +15758,12 @@ Init_IO(void)
rb_gvar_ractor_local("$>");
rb_gvar_ractor_local("$stderr");
- rb_stdin = rb_io_prep_stdin();
- rb_stdout = rb_io_prep_stdout();
- rb_stderr = rb_io_prep_stderr();
-
rb_global_variable(&rb_stdin);
+ rb_stdin = rb_io_prep_stdin();
rb_global_variable(&rb_stdout);
+ rb_stdout = rb_io_prep_stdout();
rb_global_variable(&rb_stderr);
+ rb_stderr = rb_io_prep_stderr();
orig_stdout = rb_stdout;
orig_stderr = rb_stderr;
@@ -15202,7 +15823,7 @@ Init_IO(void)
rb_define_method(rb_cARGF, "binmode", argf_binmode_m, 0);
rb_define_method(rb_cARGF, "binmode?", argf_binmode_p, 0);
- rb_define_method(rb_cARGF, "write", argf_write, 1);
+ rb_define_method(rb_cARGF, "write", argf_write, -1);
rb_define_method(rb_cARGF, "print", rb_io_print, -1);
rb_define_method(rb_cARGF, "putc", rb_io_putc, 1);
rb_define_method(rb_cARGF, "puts", rb_io_puts, -1);