From 853dd6cabe692251376271421be433d68bea2865 Mon Sep 17 00:00:00 2001 From: akr Date: Tue, 12 Jan 2010 00:32:22 +0000 Subject: * prelude.rb (require_relative): use File.realpath. [ruby-dev:40040] * include/ruby/intern.h: declare rb_dir_getwd. * dir.c (rb_dir_getwd): copied from dir_s_getwd to export. (dir_s_getwd): use rb_dir_getwd. * file.c (rb_file_s_realpath): new method File.realpath. (rb_file_s_realdirpath): new method File.realdirpath. * lib/pathname.rb (Pathname#realpath): use File.realpath. (Pathname#realdirpath): use File.realdirpath. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@26290 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 15 +++++ NEWS | 5 ++ dir.c | 26 +++++---- file.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++ include/ruby/intern.h | 2 + lib/pathname.rb | 59 +------------------ prelude.rb | 2 +- test/ruby/test_require.rb | 17 +++++- 8 files changed, 198 insertions(+), 69 deletions(-) diff --git a/ChangeLog b/ChangeLog index 444f132123..8fbecd6ba4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +Tue Jan 12 09:22:43 2010 Tanaka Akira + + * prelude.rb (require_relative): use File.realpath. [ruby-dev:40040] + + * include/ruby/intern.h: declare rb_dir_getwd. + + * dir.c (rb_dir_getwd): copied from dir_s_getwd to export. + (dir_s_getwd): use rb_dir_getwd. + + * file.c (rb_file_s_realpath): new method File.realpath. + (rb_file_s_realdirpath): new method File.realdirpath. + + * lib/pathname.rb (Pathname#realpath): use File.realpath. + (Pathname#realdirpath): use File.realdirpath. + Mon Jan 11 22:45:08 2010 Akinori MUSHA * hash.c (ruby_setenv): Improve the emulatation of setenv(3) on diff --git a/NEWS b/NEWS index c152cf58a9..b4e00eb987 100644 --- a/NEWS +++ b/NEWS @@ -52,6 +52,11 @@ with all sufficient information, see the ChangeLog file. * Float::INFINITY * Float::NAN + * File + * new methods: + * File.realpath + * File.realdirpath + * IO * new method: * IO#fdatasync diff --git a/dir.c b/dir.c index e49e9bdc75..c25b33b19d 100644 --- a/dir.c +++ b/dir.c @@ -851,6 +851,21 @@ dir_s_chdir(int argc, VALUE *argv, VALUE obj) return INT2FIX(0); } +VALUE +rb_dir_getwd(void) +{ + char *path; + VALUE cwd; + + rb_secure(4); + path = my_getcwd(); + cwd = rb_tainted_str_new2(path); + rb_enc_associate(cwd, rb_filesystem_encoding()); + + xfree(path); + return cwd; +} + /* * call-seq: * Dir.getwd => string @@ -865,16 +880,7 @@ dir_s_chdir(int argc, VALUE *argv, VALUE obj) static VALUE dir_s_getwd(VALUE dir) { - char *path; - VALUE cwd; - - rb_secure(4); - path = my_getcwd(); - cwd = rb_tainted_str_new2(path); - rb_enc_associate(cwd, rb_filesystem_encoding()); - - xfree(path); - return cwd; + return rb_dir_getwd(); } static void diff --git a/file.c b/file.c index 331bb2e244..969c75eb16 100644 --- a/file.c +++ b/file.c @@ -3082,6 +3082,145 @@ rb_file_s_absolute_path(int argc, VALUE *argv) return rb_file_absolute_path(fname, dname); } +static void +realpath_rec(long *prefixlenp, VALUE *resolvedp, char *unresolved, VALUE loopcheck, int strict, int last) +{ + while (*unresolved) { + char *testname = unresolved; + char *unresolved_firstsep = rb_path_next(unresolved); + long testnamelen = unresolved_firstsep - unresolved; + char *unresolved_nextname = unresolved_firstsep; + while (isdirsep(*unresolved_nextname)) unresolved_nextname++; + unresolved = unresolved_nextname; + if (testnamelen == 1 && testname[0] == '.') { + } + else if (testnamelen == 2 && testname[0] == '.' && testname[1] == '.') { + if (*prefixlenp < RSTRING_LEN(*resolvedp)) { + char *resolved_names = RSTRING_PTR(*resolvedp) + *prefixlenp; + long len = rb_path_last_separator(resolved_names) - resolved_names; + rb_str_modify(*resolvedp); + rb_str_set_len(*resolvedp, *prefixlenp + len); + } + } + else { + VALUE checkval; + VALUE testpath = rb_str_dup(*resolvedp); + if (*prefixlenp < RSTRING_LEN(testpath)) + rb_str_cat2(testpath, "/"); + rb_str_cat(testpath, testname, testnamelen); + checkval = rb_hash_aref(loopcheck, testpath); + if (!NIL_P(checkval)) { + if (checkval == ID2SYM(rb_intern("resolving"))) { + errno = ELOOP; + rb_sys_fail(RSTRING_PTR(testpath)); + } + else { + *resolvedp = rb_str_dup(checkval); + } + } + else { + struct stat sbuf; + int ret; + ret = lstat(RSTRING_PTR(testpath), &sbuf); + if (ret == -1) { + if (errno == ENOENT) { + if (strict || !last || *unresolved_firstsep) + rb_sys_fail(RSTRING_PTR(testpath)); + *resolvedp = testpath; + break; + } + else { + rb_sys_fail(RSTRING_PTR(testpath)); + } + } + if (S_ISLNK(sbuf.st_mode)) { + volatile VALUE link; + char *link_prefix, *link_names; + long link_prefixlen; + rb_hash_aset(loopcheck, testpath, ID2SYM(rb_intern("resolving"))); + link = rb_file_s_readlink(rb_cFile, testpath); + link_prefix = RSTRING_PTR(link); + link_names = skiproot(link_prefix); + link_prefixlen = link_names - link_prefix; + if (link_prefixlen == 0) { + realpath_rec(prefixlenp, resolvedp, link_names, loopcheck, strict, *unresolved_firstsep == '\0'); + } + else { + *resolvedp = rb_str_new(link_prefix, link_prefixlen); + *prefixlenp = link_prefixlen; + realpath_rec(prefixlenp, resolvedp, link_names, loopcheck, strict, *unresolved_firstsep == '\0'); + } + rb_hash_aset(loopcheck, testpath, rb_str_dup_frozen(*resolvedp)); + } + else { + VALUE s = rb_str_dup_frozen(testpath); + rb_hash_aset(loopcheck, s, s); + *resolvedp = testpath; + } + } + } + } +} + +static VALUE +realpath_internal(VALUE path, int strict) +{ + long prefixlen; + VALUE resolved; + volatile VALUE unresolved_path; + char *unresolved_names; + VALUE loopcheck; + FilePathValue(path); + unresolved_path = rb_str_dup_frozen(path); + unresolved_names = skiproot(RSTRING_PTR(unresolved_path)); + prefixlen = unresolved_names - RSTRING_PTR(unresolved_path); + loopcheck = rb_hash_new(); + if (prefixlen == 0) { + volatile VALUE curdir = rb_dir_getwd(); + char *unresolved_curdir_names = skiproot(RSTRING_PTR(curdir)); + prefixlen = unresolved_curdir_names - RSTRING_PTR(curdir); + resolved = rb_str_new(RSTRING_PTR(curdir), prefixlen); + realpath_rec(&prefixlen, &resolved, unresolved_curdir_names, loopcheck, 1, 0); + } + else { + resolved = rb_str_new(RSTRING_PTR(unresolved_path), prefixlen); + } + realpath_rec(&prefixlen, &resolved, unresolved_names, loopcheck, strict, 1); + OBJ_TAINT(resolved); + return resolved; +} + +/* + * call-seq: + * File.realpath(pathname) -> real_pathname + * + * Returns the real (absolute) pathname of +pathname+ in the actual + * filesystem not containing symlinks or useless dots. + * + * All components of the pathname must exist when this method is + * called. + */ +static VALUE +rb_file_s_realpath(VALUE klass, VALUE path) +{ + return realpath_internal(path, 1); +} + +/* + * call-seq: + * File.realdirpath(pathname) -> real_pathname + * + * Returns the real (absolute) pathname of +pathname+ in the actual filesystem. + * The real pathname doesn't contain symlinks or useless dots. + * + * The last component of the real pathname can be nonexistent. + */ +static VALUE +rb_file_s_realdirpath(VALUE klass, VALUE path) +{ + return realpath_internal(path, 0); +} + static size_t rmext(const char *p, long l1, const char *e) { @@ -4896,6 +5035,8 @@ Init_File(void) rb_define_singleton_method(rb_cFile, "truncate", rb_file_s_truncate, 2); rb_define_singleton_method(rb_cFile, "expand_path", rb_file_s_expand_path, -1); rb_define_singleton_method(rb_cFile, "absolute_path", rb_file_s_absolute_path, -1); + rb_define_singleton_method(rb_cFile, "realpath", rb_file_s_realpath, 1); + rb_define_singleton_method(rb_cFile, "realdirpath", rb_file_s_realdirpath, 1); rb_define_singleton_method(rb_cFile, "basename", rb_file_s_basename, -1); rb_define_singleton_method(rb_cFile, "dirname", rb_file_s_dirname, 1); rb_define_singleton_method(rb_cFile, "extname", rb_file_s_extname, 1); diff --git a/include/ruby/intern.h b/include/ruby/intern.h index 116afa520b..66791a0bca 100644 --- a/include/ruby/intern.h +++ b/include/ruby/intern.h @@ -346,6 +346,8 @@ void rb_thread_atfork_before_exec(void); VALUE rb_exec_recursive(VALUE(*)(VALUE, VALUE, int),VALUE,VALUE); VALUE rb_exec_recursive_paired(VALUE(*)(VALUE, VALUE, int),VALUE,VALUE,VALUE); VALUE rb_exec_recursive_outer(VALUE(*)(VALUE, VALUE, int),VALUE,VALUE); +/* dir.c */ +VALUE rb_dir_getwd(void); /* file.c */ VALUE rb_file_s_expand_path(int, VALUE *); VALUE rb_file_expand_path(VALUE, VALUE); diff --git a/lib/pathname.rb b/lib/pathname.rb index 226b0da229..bfd3771c28 100644 --- a/lib/pathname.rb +++ b/lib/pathname.rb @@ -435,61 +435,6 @@ class Pathname end private :cleanpath_conservative - def realpath_rec(prefix, unresolved, h, strict, last = true) - resolved = [] - until unresolved.empty? - n = unresolved.shift - if n == '.' - next - elsif n == '..' - resolved.pop - else - path = prepend_prefix(prefix, File.join(*(resolved + [n]))) - if h.include? path - if h[path] == :resolving - raise Errno::ELOOP.new(path) - else - prefix, *resolved = h[path] - end - else - begin - s = File.lstat(path) - rescue Errno::ENOENT => e - raise e if strict || !last || !unresolved.empty? - resolved << n - break - end - if s.symlink? - h[path] = :resolving - link_prefix, link_names = split_names(File.readlink(path)) - if link_prefix == '' - prefix, *resolved = h[path] = realpath_rec(prefix, resolved + link_names, h, strict, unresolved.empty?) - else - prefix, *resolved = h[path] = realpath_rec(link_prefix, link_names, h, strict, unresolved.empty?) - end - else - resolved << n - h[path] = [prefix, *resolved] - end - end - end - end - return prefix, *resolved - end - private :realpath_rec - - def real_path_internal(strict = false) - path = @path - prefix, names = split_names(path) - if prefix == '' - prefix, names2 = split_names(Dir.pwd) - names = names2 + names - end - prefix, *names = realpath_rec(prefix, names, {}, strict) - self.class.new(prepend_prefix(prefix, File.join(*names))) - end - private :real_path_internal - # # Returns the real (absolute) pathname of +self+ in the actual # filesystem not containing symlinks or useless dots. @@ -498,7 +443,7 @@ class Pathname # called. # def realpath - real_path_internal(true) + self.class.new(File.realpath(@path)) end # @@ -508,7 +453,7 @@ class Pathname # The last component of the real pathname can be nonexistent. # def realdirpath - real_path_internal(false) + self.class.new(File.realdirpath(@path)) end # #parent returns the parent directory. diff --git a/prelude.rb b/prelude.rb index a9e9e3c23e..679e831ca4 100644 --- a/prelude.rb +++ b/prelude.rb @@ -32,7 +32,7 @@ module Kernel if /\A\((.*)\)/ =~ file # eval, etc. raise LoadError, "require_relative is called in #{$1}" end - absolute_feature = File.expand_path(File.join(File.dirname(file), relative_feature)) + absolute_feature = File.join(File.dirname(File.realpath(file)), relative_feature) require absolute_feature end end diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb index 510da8e272..5d46c9a1a8 100644 --- a/test/ruby/test_require.rb +++ b/test/ruby/test_require.rb @@ -2,6 +2,7 @@ require 'test/unit' require 'tempfile' require_relative 'envutil' +require 'tmpdir' class TestRequire < Test::Unit::TestCase def test_require_invalid_shared_object @@ -246,7 +247,6 @@ class TestRequire < Test::Unit::TestCase end def test_relative - require 'tmpdir' load_path = $:.dup $:.delete(".") Dir.mktmpdir do |tmp| @@ -268,4 +268,19 @@ class TestRequire < Test::Unit::TestCase ensure $:.replace(load_path) if load_path end + + def test_relative_symlink + Dir.mktmpdir {|tmp| + Dir.chdir(tmp) { + Dir.mkdir "a" + Dir.mkdir "b" + File.open("a/lib.rb", "w") {|f| f.puts 'puts "a/lib.rb"' } + File.open("b/lib.rb", "w") {|f| f.puts 'puts "b/lib.rb"' } + File.open("a/tst.rb", "w") {|f| f.puts 'require_relative "lib"' } + File.symlink("../a/tst.rb", "b/tst.rb") + result = IO.popen([EnvUtil.rubybin, "b/tst.rb"]).read + assert_equal("a/lib.rb\n", result) + } + } + end end -- cgit v1.2.3