diff --git a/configure.in b/configure.in index e30ae6c..a3d53dd 100644 --- a/configure.in +++ b/configure.in @@ -2449,6 +2449,7 @@ AC_CHECK_FUNCS(killpg) AC_CHECK_FUNCS(lchmod) AC_CHECK_FUNCS(lchown) AC_CHECK_FUNCS(link) +AC_CHECK_FUNCS(linkat) AC_CHECK_FUNCS(llabs) AC_CHECK_FUNCS(lockf) AC_CHECK_FUNCS(log2) diff --git a/file.c b/file.c index ce367fd..227d8e5 100644 --- a/file.c +++ b/file.c @@ -2717,6 +2717,26 @@ syserr_fail2_in(const char *func, int e, VALUE s1, VALUE s2) static VALUE rb_file_s_link(VALUE klass, VALUE from, VALUE to) { + +#if defined(HAVE_LINKAT) && defined(AT_SYMLINK_FOLLOW) + VALUE tmp = rb_io_check_io(from); + rb_io_t *from_fptr = RFILE(rb_io_taint_check(from))->fptr; + + if (!NIL_P(tmp) && from_fptr && from_fptr->fd >= 0) { /* when 'from' is an IO and opened */ + char from_path[PATH_MAX]; + + rb_io_flush(from); + FilePathValue(to); + to = rb_str_encode_ospath(to); + snprintf(from_path, PATH_MAX, "/proc/self/fd/%d", from_fptr->fd); + if (linkat(AT_FDCWD, from_path, AT_FDCWD, StringValueCStr(to), AT_SYMLINK_FOLLOW) < 0) { + FilePathValue(from); + from = rb_str_encode_ospath(from); + sys_fail2(from, to); + } + return INT2FIX(0); + } +#endif FilePathValue(from); FilePathValue(to); from = rb_str_encode_ospath(from); diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb index a3c2a40..7830b19 100644 --- a/test/ruby/test_file_exhaustive.rb +++ b/test/ruby/test_file_exhaustive.rb @@ -647,6 +647,29 @@ def test_hardlink assert_raise(Errno::EEXIST) { File.link(utf8_file, utf8_file) } end + def test_hardlink_file_instannce + regular_file = make_tmp_filename("hardlinkfile_file_instannce_from") + hardlinkfile = make_tmp_filename("hardlinkfile_file_instannce_to") + content = "foo" + File.open(regular_file, "w") do |file| + file.write(content) + File.link(file, hardlinkfile) + assert_equal(content, File.read(hardlinkfile)) + end + rescue NotImplementedError + end + + def test_hardlink_tmpfile + hardlinkfile = make_tmp_filename("hardlinkfile_tmpfile") + content = "foo" + File.open(@dir, IO::WRONLY|IO::TMPFILE) do |tmpfile| + tmpfile.write(content) + File.link(tmpfile, hardlinkfile) + assert_equal(content, File.read(hardlinkfile)) + end + rescue NotImplementedError + end if IO::TMPFILE + def test_readlink return unless symlinkfile assert_equal(regular_file, File.readlink(symlinkfile))