summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog15
-rw-r--r--NEWS5
-rw-r--r--dir.c26
-rw-r--r--file.c141
-rw-r--r--include/ruby/intern.h2
-rw-r--r--lib/pathname.rb59
-rw-r--r--prelude.rb2
-rw-r--r--test/ruby/test_require.rb17
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 <akr@fsij.org>
+
+ * 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 <knu@iDaemons.org>
* 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