aboutsummaryrefslogtreecommitdiffstats
path: root/file.c
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2019-04-27 20:18:55 -0700
committerJeremy Evans <code@jeremyevans.net>2019-07-01 11:46:30 -0700
commit11c311e36fa6f27a9144b0aefe16bdffea651251 (patch)
tree17c8f43e31c90d357680a00285601d4278091e70 /file.c
parent81fe82be4e5040b314717e8d6f9ba12de222b6e6 (diff)
downloadruby-11c311e36fa6f27a9144b0aefe16bdffea651251.tar.gz
Use realpath(3) instead of custom realpath implementation if available
This approach is simpler than the previous approach which tries to emulate realpath(3). It also performs much better on both Linux and OpenBSD on the included benchmarks. By using realpath(3), we can better integrate with system security features such as OpenBSD's unveil(2) system call. This does not use realpath(3) on Windows even if it exists, as the approach for checking for absolute paths does not work for drive letters. This can be fixed without too much difficultly, though until Windows defines realpath(3), there is no need to do so. For File.realdirpath, where the last element of the path is not required to exist, fallback to the previous approach, as realpath(3) on most operating systems requires the whole path be valid (per POSIX), and the operating systems where this isn't true either plan to conform to POSIX or may change to conform to POSIX in the future. glibc realpath(3) does not handle /path/to/file.rb/../other_file.rb paths, returning ENOTDIR in that case. Fallback to the previous code if realpath(3) returns ENOTDIR. glibc doesn't like realpath(3) usage for paths like /dev/fd/5, returning ENOENT even though the path may appear to exist in the filesystem. If ENOENT is returned and the path exists, then fall back to the default approach.
Diffstat (limited to 'file.c')
-rw-r--r--file.c86
1 files changed, 83 insertions, 3 deletions
diff --git a/file.c b/file.c
index 0dd35161be..7574ede636 100644
--- a/file.c
+++ b/file.c
@@ -123,6 +123,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
@@ -140,6 +146,11 @@ int flock(int, int);
# define UTIME_EINVAL
#endif
+#ifdef HAVE_REALPATH
+#include <limits.h>
+#include <stdlib.h>
+#endif
+
VALUE rb_cFile;
VALUE rb_mFileTest;
VALUE rb_cStat;
@@ -4198,7 +4209,7 @@ realpath_rec(long *prefixlenp, VALUE *resolvedp, const char *unresolved, VALUE f
}
static VALUE
-rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode)
+rb_check_realpath_emulate(VALUE basedir, VALUE path, enum rb_realpath_mode mode)
{
long prefixlen;
VALUE resolved;
@@ -4292,6 +4303,77 @@ rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode
return resolved;
}
+static VALUE rb_file_join(VALUE ary);
+
+static VALUE
+rb_check_realpath_internal(VALUE basedir, VALUE path, enum rb_realpath_mode mode)
+{
+#ifdef HAVE_REALPATH
+ VALUE unresolved_path;
+ rb_encoding *origenc;
+ char *resolved_ptr = NULL;
+ VALUE resolved;
+ struct stat st;
+
+ if (mode == RB_REALPATH_DIR) {
+ return rb_check_realpath_emulate(basedir, path, mode);
+ }
+
+ 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_assoc_new(basedir, unresolved_path));
+ }
+ unresolved_path = TO_OSPATH(unresolved_path);
+
+ if((resolved_ptr = realpath(RSTRING_PTR(unresolved_path), NULL)) == NULL) {
+ /* glibc realpath(3) does not allow /path/to/file.rb/../other_file.rb,
+ returning ENOTDIR in that case.
+ glibc realpath(3) can also return ENOENT for paths that exist,
+ such as /dev/fd/5.
+ Fallback to the emulated approach in either of those cases. */
+ if (errno == ENOTDIR ||
+ (errno == ENOENT && rb_file_exist_p(0, unresolved_path))) {
+ return rb_check_realpath_emulate(basedir, path, mode);
+
+ }
+ if (mode == RB_REALPATH_CHECK) {
+ return Qnil;
+ }
+ rb_sys_fail_path(unresolved_path);
+ }
+ resolved = ospath_new(resolved_ptr, strlen(resolved_ptr), rb_filesystem_encoding());
+ free(resolved_ptr);
+
+ if (rb_stat(resolved, &st) < 0) {
+ if (mode == RB_REALPATH_CHECK) {
+ return Qnil;
+ }
+ rb_sys_fail_path(unresolved_path);
+ }
+
+ if (origenc != rb_enc_get(resolved)) {
+ if (!rb_enc_str_asciionly_p(resolved)) {
+ resolved = rb_str_conv_enc(resolved, NULL, origenc);
+ }
+ rb_enc_associate(resolved, origenc);
+ }
+
+ if(rb_enc_str_coderange(resolved) == ENC_CODERANGE_BROKEN) {
+ rb_enc_associate(resolved, rb_filesystem_encoding());
+ 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;
+#else
+ return rb_check_realpath_emulate(basedir, path, mode);
+#endif /* HAVE_REALPATH */
+}
+
VALUE
rb_realpath_internal(VALUE basedir, VALUE path, int strict)
{
@@ -4713,8 +4795,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)
{