diff options
Diffstat (limited to 'dir.c')
-rw-r--r-- | dir.c | 130 |
1 files changed, 130 insertions, 0 deletions
@@ -1094,6 +1094,135 @@ dir_s_chdir(int argc, VALUE *argv, VALUE obj) return INT2FIX(0); } +#if defined(HAVE_FCHDIR) && defined(HAVE_DIRFD) && HAVE_FCHDIR && HAVE_DIRFD +static void * +nogvl_fchdir(void *ptr) +{ + const int *fd = ptr; + + return (void *)(VALUE)fchdir(*fd); +} + +static void +dir_fchdir(int fd) +{ + if (fchdir(fd) < 0) + rb_sys_fail("fchdir"); +} + +struct fchdir_data { + VALUE old_dir; + int fd; + int done; +}; + +static VALUE +fchdir_yield(VALUE v) +{ + struct fchdir_data *args = (void *)v; + dir_fchdir(args->fd); + args->done = TRUE; + chdir_blocking++; + if (NIL_P(chdir_thread)) + chdir_thread = rb_thread_current(); + return rb_yield_values(0); +} + +static VALUE +fchdir_restore(VALUE v) +{ + struct fchdir_data *args = (void *)v; + if (args->done) { + chdir_blocking--; + if (chdir_blocking == 0) + chdir_thread = Qnil; + dir_fchdir(RB_NUM2INT(dir_fileno(args->old_dir))); + } + dir_close(args->old_dir); + return Qnil; +} + +/* + * call-seq: + * Dir.fchdir( integer ) -> 0 + * Dir.fchdir( integer ) { block } -> anObject + * + * Changes the current working directory of the process to the directory + * specified by the given file descriptor integer. If the file descriptor + * is not valid, raises SystemCallError. One reason to use + * <code>fchdir</code> instead of <code>chdir</code> is when passing + * directory file descriptors over a UNIX socket or to child processes, + * to avoid TOCTOU (time-of-check to time-of-use) vulnerabilities. + * + * If a block is given, the current working directory is changed for the + * duration of the block, and the original working directory is restored + * when the block exits. The return value of <code>fchdir</code> is the + * value of the block. <code>fchdir</code> and <code>chdir</code> blocks + * can be nested, but in a multi-threaded program an error will be raised + * if a thread attempts to open a <code>fchdir</code> or <code>chdir</code> + * block while another thread has one open or a call to <code>fchdir</code> + * or <code>chdir</code> without a block occurs inside a block passed to + * <code>fchdir</code> or <code>chdir</code> (even in the same thread). + * + * When generating directory file descriptors from a +Dir+ instance, + * make sure the +Dir+ instance is not garbage collected before the + * directory file descriptor is passed to another process. Otherwise, + * the directory file descriptor will be closed before it is passed. + * + * dir = Dir.new("/var/spool/mail") + * dir2 = Dir.new("/usr") + * fd = dir.fileno + * fd2 = dir2.fileno + * Dir.fchdir(fd) do + * puts Dir.pwd + * Dir.fchdir(fd2) do + * puts Dir.pwd + * end + * puts Dir.pwd + * end + * puts Dir.pwd + * + * <em>produces:</em> + * + * /var/spool/mail + * /tmp + * /usr + * /tmp + * /var/spool/mail + */ +static VALUE +dir_s_fchdir(VALUE klass, VALUE fd_value) +{ + int fd = RB_NUM2INT(fd_value); + + if (chdir_blocking > 0) { + if (rb_thread_current() != chdir_thread) + rb_raise(rb_eRuntimeError, "conflicting chdir during another chdir block"); + if (!rb_block_given_p()) + rb_warn("conflicting chdir during another chdir block"); + } + + if (rb_block_given_p()) { + struct fchdir_data args; + args.old_dir = dir_s_alloc(klass); + dir_initialize(NULL, args.old_dir, rb_fstring_cstr("."), Qnil); + args.fd = fd; + args.done = FALSE; + return rb_ensure(fchdir_yield, (VALUE)&args, fchdir_restore, (VALUE)&args); + } + else { + int r = (int)(VALUE)rb_thread_call_without_gvl(nogvl_fchdir, &fd, + RUBY_UBF_IO, 0); + if (r < 0) + rb_sys_fail("fchdir"); + } + + return INT2FIX(0); +} +#else +#define dir_s_fchdir rb_f_notimplement +#endif + #ifndef _WIN32 VALUE rb_dir_getwd_ospath(void) @@ -3374,6 +3503,7 @@ Init_Dir(void) rb_define_method(rb_cDir,"pos=", dir_set_pos, 1); rb_define_method(rb_cDir,"close", dir_close, 0); + rb_define_singleton_method(rb_cDir,"fchdir", dir_s_fchdir, 1); rb_define_singleton_method(rb_cDir,"chdir", dir_s_chdir, -1); rb_define_singleton_method(rb_cDir,"getwd", dir_s_getwd, 0); rb_define_singleton_method(rb_cDir,"pwd", dir_s_getwd, 0); |