Add Dir#chdir

This uses Dir.fchdir if supported, or Dir.chdir otherwise.

Implements [Feature #19347]
This commit is contained in:
Jeremy Evans 2023-02-09 12:45:52 -08:00
parent 466ca7ae20
commit 3be65f63c7
Notes: git 2023-03-24 18:19:19 +00:00
3 changed files with 70 additions and 4 deletions

View File

@ -19,6 +19,8 @@ Note: We're only listing outstanding class updates.
* `Dir.fchdir` added for changing the directory to the directory specified
by the provided directory file descriptor. [[Feature #19347]]
* `Dir#chdir` added for changing the directory to the directory specified
by the provided `Dir` object. [[Feature #19347]]
* String

31
dir.c
View File

@ -972,7 +972,7 @@ nogvl_chdir(void *ptr)
}
static void
dir_chdir(VALUE path)
dir_chdir0(VALUE path)
{
if (chdir(RSTRING_PTR(path)) < 0)
rb_sys_fail_path(path);
@ -990,7 +990,7 @@ static VALUE
chdir_yield(VALUE v)
{
struct chdir_data *args = (void *)v;
dir_chdir(args->new_path);
dir_chdir0(args->new_path);
args->done = TRUE;
chdir_blocking++;
if (NIL_P(chdir_thread))
@ -1006,7 +1006,7 @@ chdir_restore(VALUE v)
chdir_blocking--;
if (chdir_blocking == 0)
chdir_thread = Qnil;
dir_chdir(args->old_path);
dir_chdir0(args->old_path);
}
return Qnil;
}
@ -1223,6 +1223,30 @@ dir_s_fchdir(VALUE klass, VALUE fd_value)
#define dir_s_fchdir rb_f_notimplement
#endif
/*
* call-seq:
* dir.chdir -> nil
*
* Changes the current working directory to the receiver.
*
* # Assume current directory is /path
* Dir.new("testdir").chdir
* Dir.pwd # => '/path/testdir'
*/
static VALUE
dir_chdir(VALUE dir)
{
#if defined(HAVE_FCHDIR) && defined(HAVE_DIRFD) && HAVE_FCHDIR && HAVE_DIRFD
dir_s_fchdir(rb_cDir, dir_fileno(dir));
#else
struct dir_data *dirp;
dirp = dir_get(dir);
dir_s_chdir(1, &dirp->path, rb_cDir);
#endif
return Qnil;
}
#ifndef _WIN32
VALUE
rb_dir_getwd_ospath(void)
@ -3502,6 +3526,7 @@ Init_Dir(void)
rb_define_method(rb_cDir,"pos", dir_tell, 0);
rb_define_method(rb_cDir,"pos=", dir_set_pos, 1);
rb_define_method(rb_cDir,"close", dir_close, 0);
rb_define_method(rb_cDir,"chdir", dir_chdir, 0);
rb_define_singleton_method(rb_cDir,"fchdir", dir_s_fchdir, 1);
rb_define_singleton_method(rb_cDir,"chdir", dir_s_chdir, -1);

View File

@ -96,7 +96,7 @@ class TestDir < Test::Unit::TestCase
d.close
end
def test_chdir
def test_class_chdir
pwd = Dir.pwd
setup_envs
@ -134,6 +134,45 @@ class TestDir < Test::Unit::TestCase
end
end
def test_instance_chdir
pwd = Dir.pwd
dir = Dir.new(pwd)
root_dir = Dir.new(@root)
setup_envs
ENV["HOME"] = pwd
root_dir.chdir do
assert_warning(/conflicting chdir during another chdir block/) { dir.chdir }
assert_warning(/conflicting chdir during another chdir block/) { root_dir.chdir }
assert_equal(@root, Dir.pwd)
assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; dir.chdir }.join }
assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; dir.chdir{} }.join }
assert_warning(/conflicting chdir during another chdir block/) { dir.chdir }
assert_equal(pwd, Dir.pwd)
assert_warning(/conflicting chdir during another chdir block/) { root_dir.chdir }
assert_equal(@root, Dir.pwd)
assert_warning(/conflicting chdir during another chdir block/) { dir.chdir }
root_dir.chdir do
assert_equal(@root, Dir.pwd)
end
assert_equal(pwd, Dir.pwd)
end
ensure
begin
dir.chdir
rescue
abort("cannot return the original directory: #{ pwd }")
end
dir.close
root_dir.close
end
def test_chdir_conflict
pwd = Dir.pwd
q = Thread::Queue.new