summaryrefslogtreecommitdiff
path: root/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'dir.c')
-rw-r--r--dir.c130
1 files changed, 130 insertions, 0 deletions
diff --git a/dir.c b/dir.c
index 20429e1516..c32e9de415 100644
--- a/dir.c
+++ b/dir.c
@@ -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);