From c990867cc29a1120540380ebed8e86750bf40a91 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 1 Jun 2023 13:50:23 +0900 Subject: [PATCH] Hide most of `IO` internals where possible. --- common.mk | 1 + debug.c | 2 +- ext/pty/pty.c | 54 +++++------ file.c | 1 - include/ruby/io.h | 35 ++++++- io.c | 131 ++++++++++++++++++-------- process.c | 1 + spec/ruby/optional/capi/ext/io_spec.c | 10 +- win32/win32.c | 5 +- 9 files changed, 161 insertions(+), 79 deletions(-) diff --git a/common.mk b/common.mk index ec13d25ca22a56..71ae5eb6e6485a 100644 --- a/common.mk +++ b/common.mk @@ -10997,6 +10997,7 @@ process.$(OBJEXT): $(top_srcdir)/internal/fixnum.h process.$(OBJEXT): $(top_srcdir)/internal/gc.h process.$(OBJEXT): $(top_srcdir)/internal/hash.h process.$(OBJEXT): $(top_srcdir)/internal/imemo.h +process.$(OBJEXT): $(top_srcdir)/internal/io.h process.$(OBJEXT): $(top_srcdir)/internal/numeric.h process.$(OBJEXT): $(top_srcdir)/internal/object.h process.$(OBJEXT): $(top_srcdir)/internal/process.h diff --git a/debug.c b/debug.c index d3f41f1a3a1ebe..b5cba590bab5d3 100644 --- a/debug.c +++ b/debug.c @@ -70,7 +70,7 @@ const union { RUBY_FMODE_NOREVLOOKUP = 0x00000100, RUBY_FMODE_TRUNC = FMODE_TRUNC, RUBY_FMODE_TEXTMODE = FMODE_TEXTMODE, - RUBY_FMODE_PREP = 0x00010000, + RUBY_FMODE_EXTERNAL = 0x00010000, RUBY_FMODE_SETENC_BY_BOM = FMODE_SETENC_BY_BOM, RUBY_FMODE_UNIX = 0x00200000, RUBY_FMODE_INET = 0x00400000, diff --git a/ext/pty/pty.c b/ext/pty/pty.c index acec33f9bf5342..0aca10bfa070b5 100644 --- a/ext/pty/pty.c +++ b/ext/pty/pty.c @@ -448,8 +448,10 @@ pty_close_pty(VALUE assoc) for (i = 0; i < 2; i++) { io = rb_ary_entry(assoc, i); - if (RB_TYPE_P(io, T_FILE) && 0 <= RFILE(io)->fptr->fd) + if (RB_TYPE_P(io, T_FILE)) { + /* it's OK to call rb_io_close again even if it's already closed */ rb_io_close(io); + } } return Qnil; } @@ -499,28 +501,21 @@ pty_open(VALUE klass) { int master_fd, slave_fd; char slavename[DEVICELEN]; - VALUE master_io, slave_file; - rb_io_t *master_fptr, *slave_fptr; - VALUE assoc; getDevice(&master_fd, &slave_fd, slavename, 1); - master_io = rb_obj_alloc(rb_cIO); - MakeOpenFile(master_io, master_fptr); - master_fptr->mode = FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX; - master_fptr->fd = master_fd; - master_fptr->pathv = rb_obj_freeze(rb_sprintf("masterpty:%s", slavename)); + VALUE master_path = rb_obj_freeze(rb_sprintf("masterpty:%s", slavename)); + VALUE master_io = rb_io_open_descriptor(rb_cIO, master_fd, FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX, master_path, RUBY_IO_TIMEOUT_DEFAULT, NULL); + + VALUE slave_path = rb_obj_freeze(rb_str_new_cstr(slavename)); + VALUE slave_file = rb_io_open_descriptor(rb_cFile, slave_fd, FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX | FMODE_TTY, slave_path, RUBY_IO_TIMEOUT_DEFAULT, NULL); - slave_file = rb_obj_alloc(rb_cFile); - MakeOpenFile(slave_file, slave_fptr); - slave_fptr->mode = FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX | FMODE_TTY; - slave_fptr->fd = slave_fd; - slave_fptr->pathv = rb_obj_freeze(rb_str_new_cstr(slavename)); + VALUE assoc = rb_assoc_new(master_io, slave_file); - assoc = rb_assoc_new(master_io, slave_file); if (rb_block_given_p()) { return rb_ensure(rb_yield, assoc, pty_close_pty, assoc); } + return assoc; } @@ -577,30 +572,27 @@ pty_getpty(int argc, VALUE *argv, VALUE self) { VALUE res; struct pty_info info; - rb_io_t *wfptr,*rfptr; - VALUE rport = rb_obj_alloc(rb_cFile); - VALUE wport = rb_obj_alloc(rb_cFile); char SlaveName[DEVICELEN]; - MakeOpenFile(rport, rfptr); - MakeOpenFile(wport, wfptr); - establishShell(argc, argv, &info, SlaveName); - rfptr->mode = rb_io_modestr_fmode("r"); - rfptr->fd = info.fd; - rfptr->pathv = rb_obj_freeze(rb_str_new_cstr(SlaveName)); + VALUE pty_path = rb_obj_freeze(rb_str_new_cstr(SlaveName)); + VALUE rport = rb_io_open_descriptor( + rb_cFile, info.fd, FMODE_READABLE, pty_path, RUBY_IO_TIMEOUT_DEFAULT, NULL + ); - wfptr->mode = rb_io_modestr_fmode("w") | FMODE_SYNC; - wfptr->fd = rb_cloexec_dup(info.fd); - if (wfptr->fd == -1) + int wpty_fd = rb_cloexec_dup(info.fd); + if (wpty_fd == -1) { rb_sys_fail("dup()"); - rb_update_max_fd(wfptr->fd); - wfptr->pathv = rfptr->pathv; + } + VALUE wport = rb_io_open_descriptor( + rb_cFile, wpty_fd, FMODE_WRITABLE | FMODE_TRUNC | FMODE_CREATE | FMODE_SYNC, + pty_path, RUBY_IO_TIMEOUT_DEFAULT, NULL + ); res = rb_ary_new2(3); - rb_ary_store(res,0,(VALUE)rport); - rb_ary_store(res,1,(VALUE)wport); + rb_ary_store(res, 0, rport); + rb_ary_store(res, 1, wport); rb_ary_store(res,2,PIDT2NUM(info.child_pid)); if (rb_block_given_p()) { diff --git a/file.c b/file.c index 2c74f3eff5ef0c..5f59373a451439 100644 --- a/file.c +++ b/file.c @@ -169,7 +169,6 @@ int flock(int, int); #include "internal/thread.h" #include "internal/vm.h" #include "ruby/encoding.h" -#include "ruby/io.h" #include "ruby/thread.h" #include "ruby/util.h" diff --git a/include/ruby/io.h b/include/ruby/io.h index 8be83a215ca4b8..9b05c7b16411a2 100644 --- a/include/ruby/io.h +++ b/include/ruby/io.h @@ -331,7 +331,16 @@ typedef struct rb_io_enc_t rb_io_enc_t; * Setting this one and #FMODE_BINMODE at the same time is a contradiction. */ #define FMODE_TEXTMODE 0x00001000 -/* #define FMODE_PREP 0x00010000 */ +/** + * This flag means that an IO object is wrapping an "external" file descriptor, + * which is owned by something outside the Ruby interpreter (usually a C extension). + * Ruby will not close this file when the IO object is garbage collected. + * If this flag is set, then IO#autoclose? is false, and vice-versa. + * + * This flag was previously called FMODE_PREP internally. + */ +#define FMODE_EXTERNAL 0x00010000 + /* #define FMODE_SIGNAL_ON_EPIPE 0x00020000 */ /** @@ -345,6 +354,18 @@ typedef struct rb_io_enc_t rb_io_enc_t; /** @} */ +/** + * Allocate a new IO object, with the given file descriptor. + */ +VALUE rb_io_open_descriptor(VALUE klass, int descriptor, int mode, VALUE path, VALUE timeout, struct rb_io_enc_t *encoding); + +/** + * Returns whether or not the underlying IO is closed. + * + * @return Whether the underlying IO is closed. + */ +VALUE rb_io_closed_p(VALUE io); + /** * Queries the underlying IO pointer. * @@ -703,6 +724,12 @@ VALUE rb_io_set_write_io(VALUE io, VALUE w); */ void rb_io_set_nonblock(rb_io_t *fptr); +/** + * Returns the path for the given IO. + * + */ +VALUE rb_io_path(VALUE io); + /** * Returns an integer representing the numeric file descriptor for * io. @@ -712,6 +739,12 @@ void rb_io_set_nonblock(rb_io_t *fptr); */ int rb_io_descriptor(VALUE io); +/** + * Get the mode of the IO. + * + */ +int rb_io_mode(VALUE io); + /** * This function breaks down the option hash that `IO#initialize` takes into * components. This is an implementation detail of rb_io_extract_modeenc() diff --git a/io.c b/io.c index 88cb2667f5b579..b99041fbd1caaa 100644 --- a/io.c +++ b/io.c @@ -16,11 +16,6 @@ #include "ruby/fiber/scheduler.h" #include "ruby/io/buffer.h" -#ifdef _WIN32 -# include "ruby/ruby.h" -# include "ruby/io.h" -#endif - #include #include #include @@ -525,7 +520,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) \ @@ -1463,7 +1457,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 @@ -2875,6 +2869,13 @@ rb_io_descriptor(VALUE io) } } +int rb_io_mode(VALUE io) +{ + rb_io_t *fptr; + GetOpenFile(io, fptr); + return fptr->mode; +} + /* * call-seq: * pid -> integer or nil @@ -2921,7 +2922,7 @@ rb_io_pid(VALUE io) * File.open("testfile") {|f| f.path} # => "testfile" */ -static VALUE +VALUE rb_io_path(VALUE io) { rb_io_t *fptr = RFILE(io)->fptr; @@ -5305,7 +5306,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 @@ -5463,7 +5464,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; } @@ -5770,10 +5771,8 @@ io_close(VALUE io) * * 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; @@ -8289,7 +8288,7 @@ io_reopen(VALUE io, VALUE nfile) 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))) { @@ -8315,17 +8314,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); @@ -8433,7 +8432,7 @@ rb_io_reopen(int argc, VALUE *argv, VALUE file) convconfig_t 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, @@ -8512,7 +8511,7 @@ 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; @@ -9186,27 +9185,79 @@ stderr_getter(ID id, VALUE *ptr) return rb_ractor_stderr(); } +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_enc_t *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; + if (path) { + path_value = rb_obj_freeze(rb_str_new_cstr(path)); + } - MakeOpenFile(io, fp); - fp->self = io; - fp->fd = fd; - fp->mode = fmode; - fp->timeout = Qnil; - if (!io_check_tty(fp)) { + VALUE self = rb_io_open_descriptor(klass, fd, fmode, path_value, Qnil, NULL); + 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 @@ -9222,7 +9273,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; @@ -9266,7 +9317,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_buffer_t *buf) { buf->ptr = NULL; buf->off = 0; @@ -9406,7 +9457,7 @@ rb_io_initialize(int argc, VALUE *argv, VALUE io) if (!NIL_P(opt)) { if (rb_hash_aref(opt, sym_autoclose) == Qfalse) { - fmode |= FMODE_PREP; + fmode |= FMODE_EXTERNAL; } path = rb_hash_aref(opt, RB_ID2SYM(idPath)); @@ -9583,7 +9634,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)); } /* @@ -9609,9 +9660,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; } @@ -14442,7 +14493,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); } /* @@ -15489,7 +15540,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); diff --git a/process.c b/process.c index c54e11860e6248..b4435446f0c4b7 100644 --- a/process.c +++ b/process.c @@ -103,6 +103,7 @@ int initgroups(const char *, rb_gid_t); #include "internal/error.h" #include "internal/eval.h" #include "internal/hash.h" +#include "internal/io.h" #include "internal/numeric.h" #include "internal/object.h" #include "internal/process.h" diff --git a/spec/ruby/optional/capi/ext/io_spec.c b/spec/ruby/optional/capi/ext/io_spec.c index cd4bc80229c9b8..c60cd6e2226b07 100644 --- a/spec/ruby/optional/capi/ext/io_spec.c +++ b/spec/ruby/optional/capi/ext/io_spec.c @@ -28,9 +28,13 @@ static int set_non_blocking(int fd) { } static int io_spec_get_fd(VALUE io) { +#ifdef RUBY_VERSION_IS_3_3 + return rb_io_descriptor(io); +#else rb_io_t* fp; GetOpenFile(io, fp); return fp->fd; +#endif } VALUE io_spec_GetOpenFile_fd(VALUE self, VALUE io) { @@ -303,7 +307,7 @@ VALUE io_spec_rb_io_set_nonblock(VALUE self, VALUE io) { GetOpenFile(io, fp); rb_io_set_nonblock(fp); #ifdef F_GETFL - flags = fcntl(fp->fd, F_GETFL, 0); + flags = fcntl(io_spec_get_fd(io), F_GETFL, 0); return flags & O_NONBLOCK ? Qtrue : Qfalse; #else return Qfalse; @@ -322,9 +326,13 @@ static VALUE io_spec_errno_set(VALUE self, VALUE val) { } VALUE io_spec_mode_sync_flag(VALUE self, VALUE io) { +#ifdef RUBY_VERSION_IS_3_3 + if (rb_io_mode(io) & FMODE_SYNC) { +#else rb_io_t *fp; GetOpenFile(io, fp); if (fp->mode & FMODE_SYNC) { +#endif return Qtrue; } else { return Qfalse; diff --git a/win32/win32.c b/win32/win32.c index ec65f5383b6619..c2d12a79f4edbc 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -8269,10 +8269,7 @@ w32_io_info(VALUE *file, w32_io_info_t *st) tmp = rb_check_convert_type_with_id(*file, T_FILE, "IO", idTo_io); if (!NIL_P(tmp)) { - rb_io_t *fptr; - - GetOpenFile(tmp, fptr); - f = (HANDLE)rb_w32_get_osfhandle(fptr->fd); + f = (HANDLE)rb_w32_get_osfhandle(rb_io_descriptor(tmp)); if (f == (HANDLE)-1) return INVALID_HANDLE_VALUE; } else {