From 2fa143fe8899c344e8c7debe7a592f824e23a014 Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@jeremyevans.net>
Date: Sat, 27 Apr 2019 20:18:55 -0700
Subject: [PATCH] Use realpath(3) instead of custom realpath implementation if
 available

One reason to do this is simplicity, as this approach is ~30 lines of
code instead of ~200.

Performance wise, this performs 25%-115% better, using the following
benchmark on OpenBSD 6.5:

```ruby
require 'benchmark'

f = File
pwd = Dir.pwd
Dir.mkdir('b') unless f.directory?('b')
f.write('b/a', '') unless f.file?('b/a')

args = [
  ["b/a", nil],
  ["#{pwd}/b/a", nil],
  ['a', 'b'],
  ["#{pwd}/b/a", 'b'],
  ["b/a", pwd]
]

args.each do |path, base|
  print "File.realpath(#{path.inspect}, #{base.inspect}): ".ljust(50)
  puts Benchmark.measure{100000.times{f.realpath(path, base)}}
end
```

Before:

```
File.realpath("b/a", nil):                          4.330000   2.990000   7.320000 (  7.316244)
File.realpath("/home/testr/ruby/b/a", nil):         3.560000   2.680000   6.240000 (  6.240951)
File.realpath("a", "b"):                            4.370000   3.080000   7.450000 (  7.452511)
File.realpath("/home/testr/ruby/b/a", "b"):         3.730000   2.640000   6.370000 (  6.371979)
File.realpath("b/a", "/home/testr/ruby"):           3.590000   2.630000   6.220000 (  6.226824)
```

After:

```
File.realpath("b/a", nil):                          1.370000   2.030000   3.400000 (  3.400775)
File.realpath("/home/testr/ruby/b/a", nil):         1.260000   2.770000   4.030000 (  4.024957)
File.realpath("a", "b"):                            2.090000   1.990000   4.080000 (  4.080284)
File.realpath("/home/testr/ruby/b/a", "b"):         1.400000   2.620000   4.020000 (  4.015505)
File.realpath("b/a", "/home/testr/ruby"):           2.150000   2.760000   4.910000 (  4.910634)
```

By using realpath(3), we can better integrate with system security
features such as OpenBSD's unveil(2) system call.

Do not use realpath(3) on Windows even if it is defined, as the
approach for checkign for absolute paths does not work for drive
letters.

When using RB_REALPATH_DIR, only use realpath(3) on OpenBSD and FreeBSD,
as those operating systems have a realpath(3) that does not require the
last component of the path to exist. On other operating systems,
fallback to the previous approach when RB_REALPATH_DIR is used.
---
 configure.ac |  1 +
 file.c       | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 74 insertions(+), 2 deletions(-)

diff --git a/configure.ac b/configure.ac
index d068efddd4..b23656c181 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1845,6 +1845,7 @@ AC_CHECK_FUNCS(pwrite)
 AC_CHECK_FUNCS(qsort_r)
 AC_CHECK_FUNCS(qsort_s)
 AC_CHECK_FUNCS(readlink)
+AC_CHECK_FUNCS(realpath)
 AC_CHECK_FUNCS(round)
 AC_CHECK_FUNCS(sched_getaffinity)
 AC_CHECK_FUNCS(seekdir)
diff --git a/file.c b/file.c
index a04fe538e5..728eba71ff 100644
--- a/file.c
+++ b/file.c
@@ -115,6 +115,12 @@ int flock(int, int);
 #define rename(f, t)	rb_w32_urename((f), (t))
 #undef symlink
 #define symlink(s, l)	rb_w32_usymlink((s), (l))
+
+#ifdef HAVE_REALPATH
+/* Don't use native realpath(3) on Windows, as the check for
+   absolute paths does not work for drive letters. */
+#undef HAVE_REALPATH
+#endif
 #else
 #define STAT(p, s)	stat((p), (s))
 #endif
@@ -132,6 +138,17 @@ int flock(int, int);
 #  define UTIME_EINVAL
 #endif
 
+#ifdef HAVE_REALPATH
+#if defined __OpenBSD__ || defined __FreeBSD__
+#include <limits.h>
+#include <stdlib.h>
+/* Only use native realpath(3) for RB_REALPATH_DIR on FreeBSD and OpenBSD,
+   as these do not require that the last element of the path exists.
+   NetBSD started requiring the last element to exist in 2005. */
+#define ALLOW_NATIVE_REALPATH_DIR 1
+#endif
+#endif
+
 VALUE rb_cFile;
 VALUE rb_mFileTest;
 VALUE rb_cStat;
@@ -4066,6 +4083,7 @@ enum rb_realpath_mode {
     RB_REALPATH_MODE_MAX
 };
 
+#if !defined(HAVE_REALPATH) || !defined(ALLOW_NATIVE_REALPATH_DIR)
 static int
 realpath_rec(long *prefixlenp, VALUE *resolvedp, const char *unresolved, VALUE fallback,
 	     VALUE loopcheck, enum rb_realpath_mode mode, int last)
@@ -4180,10 +4198,61 @@ realpath_rec(long *prefixlenp, VALUE *resolvedp, const char *unresolved, VALUE f
     }
     return 0;
 }
+#endif
+
+static VALUE rb_file_join(VALUE ary);
 
 static VALUE
 rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode)
 {
+#if defined(HAVE_REALPATH) && !defined(ALLOW_NATIVE_REALPATH_DIR)
+    if (mode != RB_REALPATH_DIR) {
+#endif
+#ifdef HAVE_REALPATH
+    VALUE unresolved_path;
+    rb_encoding *origenc;
+    char *resolved_ptr = NULL;
+    VALUE resolved;
+
+    unresolved_path = rb_str_dup_frozen(path);
+    origenc = rb_enc_get(unresolved_path);
+    if (*RSTRING_PTR(unresolved_path) != '/' && !NIL_P(basedir)) {
+	unresolved_path = rb_file_join(rb_ary_new_from_args(2, basedir, unresolved_path));
+    }
+    unresolved_path = TO_OSPATH(unresolved_path);
+
+    if((resolved_ptr = realpath(RSTRING_PTR(unresolved_path), NULL)) == NULL) {
+	if (mode == RB_REALPATH_CHECK) {
+	    return Qnil;
+	}
+	rb_sys_fail_path(unresolved_path);
+    }
+    resolved = rb_enc_str_new_cstr(resolved_ptr, origenc);
+    free(resolved_ptr);
+
+    if (mode == RB_REALPATH_STRICT || mode == RB_REALPATH_CHECK) {
+	struct stat st;
+
+	if (rb_stat(resolved, &st) < 0) {
+	    if (mode == RB_REALPATH_STRICT) {
+		rb_sys_fail_path(unresolved_path);
+	    }
+	    return Qnil;
+	}
+    }
+
+    if(rb_enc_str_coderange(resolved) == ENC_CODERANGE_BROKEN) {
+	rb_enc_associate(resolved, rb_ascii8bit_encoding());
+    }
+
+    rb_obj_taint(resolved);
+    RB_GC_GUARD(unresolved_path);
+    return resolved;
+#endif
+#if defined(HAVE_REALPATH) && !defined(ALLOW_NATIVE_REALPATH_DIR)
+    } else {
+#endif
+#if !defined(HAVE_REALPATH) || !defined(ALLOW_NATIVE_REALPATH_DIR)
     long prefixlen;
     VALUE resolved;
     VALUE unresolved_path;
@@ -4274,6 +4343,10 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode
     RB_GC_GUARD(unresolved_path);
     RB_GC_GUARD(curdir);
     return resolved;
+#endif
+#if defined(HAVE_REALPATH) && !defined(ALLOW_NATIVE_REALPATH_DIR)
+    }
+#endif
 }
 
 VALUE
@@ -4697,8 +4770,6 @@ rb_file_s_split(VALUE klass, VALUE path)
     return rb_assoc_new(rb_file_dirname(path), rb_file_s_basename(1,&path));
 }
 
-static VALUE rb_file_join(VALUE ary);
-
 static VALUE
 file_inspect_join(VALUE ary, VALUE arg, int recur)
 {
-- 
2.21.0

