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:
parent
81fe82be4e
commit
11c311e36f
3 changed files with 114 additions and 3 deletions
30
benchmark/realpath.yml
Normal file
30
benchmark/realpath.yml
Normal 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"
|
|
@ -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
86
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)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue