diff options
Diffstat (limited to 'io.c')
-rw-r--r-- | io.c | 2581 |
1 files changed, 1601 insertions, 980 deletions
@@ -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); |