1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

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.
This commit is contained in:
Jeremy Evans 2019-04-27 20:18:55 -07:00
parent 81fe82be4e
commit 11c311e36f
3 changed files with 114 additions and 3 deletions

30
benchmark/realpath.yml Normal file
View file

@ -0,0 +1,30 @@
prelude: |
f = File
pwd = Dir.pwd
Dir.mkdir('b') unless f.directory?('b')
f.write('b/a', '') unless f.file?('b/a')
relative = 'b/a'
absolute = File.join(pwd, relative)
dir = 'b'
file = 'a'
relative_dir = 'b/c'
absolute_dir = File.join(pwd, relative_dir)
file_dir = 'c'
benchmark:
relative_nil: "f.realpath(relative, nil)"
absolute_nil: "f.realpath(absolute, nil)"
relative_relative: "f.realpath(file, dir)"
absolute_relative: "f.realpath(absolute, dir)"
relative_absolute: "f.realpath(relative, pwd)"
relative_nil_dir: "f.realdirpath(relative_dir, nil)"
absolute_nil_dir: "f.realdirpath(absolute_dir, nil)"
relative_relative_dir: "f.realdirpath(file_dir, dir)"
absolute_relative_dir: "f.realdirpath(absolute_dir, dir)"
relative_absolute_dir: "f.realdirpath(relative_dir, pwd)"
relative_nil_notexist: "f.realpath(relative_dir, nil) rescue nil"
absolute_nil_notexist: "f.realpath(absolute_dir, nil) rescue nil"
relative_relative_notexist: "f.realpath(file_dir, dir) rescue nil"
absolute_relative_notexist: "f.realpath(absolute_dir, dir) rescue nil"
relative_absolute_notexist: "f.realpath(relative_dir, pwd) rescue nil"

View file

@ -1827,6 +1827,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)

86
file.c
View file

@ -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)
{