-
Notifications
You must be signed in to change notification settings - Fork 29.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fs: fix long symlinks in realpath #7548
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const resolved_path = path.resolve(__dirname, '../../lib/'); | ||
const relative_path = path.relative(__dirname, '../../lib/'); | ||
|
||
const bench = common.createBenchmark(main, { | ||
n: [1e4], | ||
type: ['relative', 'resolved'], | ||
}); | ||
|
||
|
||
function main(conf) { | ||
const n = conf.n >>> 0; | ||
const type = conf.type; | ||
|
||
bench.start(); | ||
if (type === 'relative') | ||
relativePath(n); | ||
else if (type === 'resolved') | ||
resolvedPath(n); | ||
else | ||
throw new Error('unknown "type": ' + type); | ||
} | ||
|
||
function relativePath(n) { | ||
(function r(cntr) { | ||
if (--cntr <= 0) | ||
return bench.end(n); | ||
fs.realpath(relative_path, function() { | ||
r(cntr); | ||
}); | ||
}(n)); | ||
} | ||
|
||
function resolvedPath(n) { | ||
(function r(cntr) { | ||
if (--cntr <= 0) | ||
return bench.end(n); | ||
fs.realpath(resolved_path, function() { | ||
r(cntr); | ||
}); | ||
}(n)); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const resolved_path = path.resolve(__dirname, '../../lib/'); | ||
const relative_path = path.relative(__dirname, '../../lib/'); | ||
|
||
const bench = common.createBenchmark(main, { | ||
n: [1e4], | ||
type: ['relative', 'resolved'], | ||
}); | ||
|
||
|
||
function main(conf) { | ||
const n = conf.n >>> 0; | ||
const type = conf.type; | ||
|
||
bench.start(); | ||
if (type === 'relative') | ||
relativePath(n); | ||
else if (type === 'resolved') | ||
resolvedPath(n); | ||
else | ||
throw new Error('unknown "type": ' + type); | ||
bench.end(n); | ||
} | ||
|
||
function relativePath(n) { | ||
for (var i = 0; i < n; i++) { | ||
fs.realpathSync(relative_path); | ||
} | ||
} | ||
|
||
function resolvedPath(n) { | ||
for (var i = 0; i < n; i++) { | ||
fs.realpathSync(resolved_path); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -361,16 +361,19 @@ class fs_req_wrap { | |
#define ASYNC_CALL(func, req, encoding, ...) \ | ||
ASYNC_DEST_CALL(func, req, nullptr, encoding, __VA_ARGS__) \ | ||
|
||
#define SYNC_DEST_CALL(func, path, dest, ...) \ | ||
fs_req_wrap req_wrap; \ | ||
#define SYNC_CALL_NO_THROW(req_wrap, func, dest, ...) \ | ||
env->PrintSyncTrace(); \ | ||
int err = uv_fs_ ## func(env->event_loop(), \ | ||
&req_wrap.req, \ | ||
&(req_wrap).req, \ | ||
__VA_ARGS__, \ | ||
nullptr); \ | ||
if (err < 0) { \ | ||
return env->ThrowUVException(err, #func, nullptr, path, dest); \ | ||
} \ | ||
nullptr); | ||
|
||
#define SYNC_DEST_CALL(func, path, dest, ...) \ | ||
fs_req_wrap req_wrap; \ | ||
SYNC_CALL_NO_THROW(req_wrap, func, dest, __VA_ARGS__) \ | ||
if (SYNC_RESULT < 0) { \ | ||
return env->ThrowUVException(SYNC_RESULT, #func, nullptr, path, dest); \ | ||
} | ||
|
||
#define SYNC_CALL(func, path, ...) \ | ||
SYNC_DEST_CALL(func, path, nullptr, __VA_ARGS__) \ | ||
|
@@ -882,6 +885,77 @@ static void MKDir(const FunctionCallbackInfo<Value>& args) { | |
} | ||
} | ||
|
||
|
||
static size_t CountSlashes(const char* str, size_t len) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not simply pass the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since I'm using a simple |
||
size_t cntr = 0; | ||
for (size_t i = 0; i < len; i++) { | ||
if (str[i] == '/') cntr++; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. won't this be a problem on Windows? (I this code ever gets run there, that is) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't see in any of the win calls of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @saghul So, seems I can get |
||
} | ||
return cntr; | ||
} | ||
|
||
|
||
static int ResolveRealPathSync(Environment* env, | ||
std::string* ret_str, | ||
const char* path, | ||
size_t call_depth) { | ||
fs_req_wrap req_wrap; | ||
SYNC_CALL_NO_THROW(req_wrap, realpath, path, path); | ||
|
||
call_depth++; | ||
if (SYNC_RESULT != UV_ELOOP) { | ||
if (SYNC_RESULT) return SYNC_RESULT; | ||
*ret_str = std::string(static_cast<const char*>(SYNC_REQ.ptr)); | ||
return SYNC_RESULT; | ||
// TODO(trevnorris): Instead of simply not allowing too many recursive | ||
// calls, would it instead be a viable solution to attempt detection of | ||
// recursive symlinks? Thus preventing false negatives. | ||
} else if (SYNC_RESULT == UV_ELOOP && call_depth > 100) { | ||
return UV_ELOOP; | ||
} | ||
|
||
#ifdef _WIN32 | ||
const char separator = '\\'; | ||
#else | ||
const char separator = '/'; | ||
#endif | ||
std::string str_path(path); | ||
size_t offset = 0; | ||
size_t current = 0; | ||
size_t slash_count = 0; | ||
|
||
// Can assume '/' because uv_fs_realpath() cannot return UV_ELOOP on win. | ||
while ((offset = str_path.find(separator, offset + 1)) != std::string::npos) { | ||
// OSX libc bails with ELOOP when encountering more than MAXSYMLINKS, | ||
// which is hard coded to in the kernel header to 32. | ||
if (++slash_count < 32) { | ||
continue; | ||
} | ||
|
||
std::string partial = *ret_str + str_path.substr(current, offset - current); | ||
int err2 = ResolveRealPathSync(env, ret_str, partial.c_str(), call_depth); | ||
// No need to handle an error that was returned by a recursive call. | ||
if (err2) { | ||
*ret_str = std::string(); | ||
return err2; | ||
} | ||
|
||
current = offset; | ||
slash_count = CountSlashes(ret_str->c_str(), ret_str->length()); | ||
} | ||
|
||
if (offset == std::string::npos) { | ||
offset = str_path.length(); | ||
} | ||
if (current >= offset) { | ||
return 0; | ||
} | ||
|
||
std::string pass(*ret_str + str_path.substr(current, offset - current)); | ||
return ResolveRealPathSync(env, ret_str, pass.c_str(), call_depth); | ||
} | ||
|
||
|
||
static void RealPath(const FunctionCallbackInfo<Value>& args) { | ||
Environment* env = Environment::GetCurrent(args); | ||
|
||
|
@@ -902,10 +976,14 @@ static void RealPath(const FunctionCallbackInfo<Value>& args) { | |
if (callback->IsObject()) { | ||
ASYNC_CALL(realpath, callback, encoding, *path); | ||
} else { | ||
SYNC_CALL(realpath, *path, *path); | ||
const char* link_path = static_cast<const char*>(SYNC_REQ.ptr); | ||
std::string rc_string; | ||
// Resolve the symlink attempting simple amount of deep path resolution. | ||
int err = ResolveRealPathSync(env, &rc_string, *path, 0); | ||
if (err) { | ||
return env->ThrowUVException(err, "realpath", nullptr, *path, *path); | ||
} | ||
Local<Value> rc = StringBytes::Encode(env->isolate(), | ||
link_path, | ||
rc_string.c_str(), | ||
encoding); | ||
if (rc.IsEmpty()) { | ||
return env->ThrowUVException(UV_EINVAL, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make more sense to do a single async call to
ResolveRealPath
instead of mirroring the C++ code in JS?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. Reason I did it this way was because handling/passing the async resources was being a thorn at the time. I'll see about moving this into C++.