Skip to content
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: return stats to JS in sync methods #20167

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 32 additions & 31 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ const {
preprocessSymlinkDestination,
Stats,
getStatsFromBinding,
getStatsFromGlobalBinding,
stringToFlags,
stringToSymlinkType,
toUnixTimestamp,
Expand Down Expand Up @@ -158,10 +157,10 @@ function isFd(path) {

fs.Stats = Stats;

function isFileType(fileType) {
function isFileType(stats, fileType) {
// Use stats array directly to avoid creating an fs.Stats instance just for
// our internal use.
return (statValues[1/* mode */] & S_IFMT) === fileType;
return (stats[1/* mode */] & S_IFMT) === fileType;
}

// Don't allow mode to accidentally be overwritten.
Expand Down Expand Up @@ -330,15 +329,15 @@ function readFileAfterOpen(err, fd) {
binding.fstat(fd, req);
}

function readFileAfterStat(err) {
function readFileAfterStat(err, stats) {
var context = this.context;

if (err)
return context.close(err);

var size;
if (isFileType(S_IFREG))
size = context.size = statValues[8];
if (isFileType(stats, S_IFREG))
size = context.size = stats[8];
else
size = context.size = 0;

Expand Down Expand Up @@ -411,11 +410,12 @@ function readFileAfterClose(err) {

function tryStatSync(fd, isUserFd) {
const ctx = {};
binding.fstat(fd, undefined, ctx);
const stats = binding.fstat(fd, undefined, ctx);
if (ctx.errno !== undefined && !isUserFd) {
fs.closeSync(fd);
throw errors.uvException(ctx);
}
return stats;
}

function tryCreateBuffer(size, fd, isUserFd) {
Expand Down Expand Up @@ -450,10 +450,10 @@ fs.readFileSync = function(path, options) {
var isUserFd = isFd(path); // file descriptor ownership
var fd = isUserFd ? path : fs.openSync(path, options.flag || 'r', 0o666);

tryStatSync(fd, isUserFd);
const stats = tryStatSync(fd, isUserFd);
var size;
if (isFileType(S_IFREG))
size = statValues[8];
if (isFileType(stats, S_IFREG))
size = stats[8];
else
size = 0;
var pos = 0;
Expand Down Expand Up @@ -890,27 +890,29 @@ fs.stat = function(path, callback) {
fs.fstatSync = function(fd) {
validateUint32(fd, 'fd');
const ctx = { fd };
binding.fstat(fd, undefined, ctx);
const stats = binding.fstat(fd, undefined, ctx);
handleErrorFromBinding(ctx);
return getStatsFromGlobalBinding();
return getStatsFromBinding(stats);
};

fs.lstatSync = function(path) {
path = getPathFromURL(path);
validatePath(path);
const ctx = { path };
binding.lstat(pathModule.toNamespacedPath(path), undefined, ctx);
const stats = binding.lstat(pathModule.toNamespacedPath(path),
undefined, ctx);
handleErrorFromBinding(ctx);
return getStatsFromGlobalBinding();
return getStatsFromBinding(stats);
};

fs.statSync = function(path) {
path = getPathFromURL(path);
validatePath(path);
const ctx = { path };
binding.stat(pathModule.toNamespacedPath(path), undefined, ctx);
const stats = binding.stat(pathModule.toNamespacedPath(path),
undefined, ctx);
handleErrorFromBinding(ctx);
return getStatsFromGlobalBinding();
return getStatsFromBinding(stats);
};

fs.readlink = function(path, options, callback) {
Expand Down Expand Up @@ -1439,7 +1441,7 @@ function StatWatcher() {
this._handle.onchange = function(newStatus, stats) {
if (oldStatus === -1 &&
newStatus === -1 &&
statValues[2/* new nlink */] === statValues[16/* old nlink */]) return;
stats[2/* new nlink */] === stats[16/* old nlink */]) return;

oldStatus = newStatus;
self.emit('change', getStatsFromBinding(stats),
Expand Down Expand Up @@ -1666,7 +1668,8 @@ fs.realpathSync = function realpathSync(p, options) {

// continue if not a symlink, break if a pipe/socket
if (knownHard[base] || (cache && cache.get(base) === base)) {
if (isFileType(S_IFIFO) || isFileType(S_IFSOCK)) {
if (isFileType(statValues, S_IFIFO) ||
isFileType(statValues, S_IFSOCK)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

statValues is filled up by the binding.lstat() call ~20 lines up? Is the logic here still correct if the entry comes from the cache?

Apropos that lstat() call, maybe do this?

let stats;  // and use this in isFileType() calls
if (...) {
  const ctx = { path: base };
  stats = binding.lstat(pathModule.toNamespacedPath(base), undefined, ctx);
  // ...
}

(Also applies to fs.realpath().)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current code statValues always equals to stats since neither side allocate any new stats arrays. I could try this but from the code it's very hard to tell if stats will be undefinedor not when you hit the use statement below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, never mind then.

break;
}
continue;
Expand All @@ -1682,10 +1685,10 @@ fs.realpathSync = function realpathSync(p, options) {

var baseLong = pathModule.toNamespacedPath(base);
const ctx = { path: base };
binding.lstat(baseLong, undefined, ctx);
const stats = binding.lstat(baseLong, undefined, ctx);
handleErrorFromBinding(ctx);

if (!isFileType(S_IFLNK)) {
if (!isFileType(stats, S_IFLNK)) {
knownHard[base] = true;
if (cache) cache.set(base, base);
continue;
Expand All @@ -1696,8 +1699,8 @@ fs.realpathSync = function realpathSync(p, options) {
var linkTarget = null;
var id;
if (!isWindows) {
var dev = statValues[0].toString(32);
var ino = statValues[7].toString(32);
var dev = stats[0].toString(32);
var ino = stats[7].toString(32);
id = `${dev}:${ino}`;
if (seenLinks[id]) {
linkTarget = seenLinks[id];
Expand Down Expand Up @@ -1778,7 +1781,7 @@ fs.realpath = function realpath(p, options, callback) {

// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard[base]) {
fs.lstat(base, function(err) {
fs.lstat(base, function(err, stats) {
if (err) return callback(err);
knownHard[base] = true;
LOOP();
Expand Down Expand Up @@ -1811,7 +1814,8 @@ fs.realpath = function realpath(p, options, callback) {

// continue if not a symlink, break if a pipe/socket
if (knownHard[base]) {
if (isFileType(S_IFIFO) || isFileType(S_IFSOCK)) {
if (isFileType(statValues, S_IFIFO) ||
isFileType(statValues, S_IFSOCK)) {
return callback(null, encodeRealpathResult(p, options));
}
return process.nextTick(LOOP);
Expand All @@ -1820,14 +1824,11 @@ fs.realpath = function realpath(p, options, callback) {
return fs.lstat(base, gotStat);
}

function gotStat(err) {
function gotStat(err, stats) {
if (err) return callback(err);

// Use stats array directly to avoid creating an fs.Stats instance just for
// our internal use.

// if not a symlink, skip to the next path part
if (!isFileType(S_IFLNK)) {
if (!stats.isSymbolicLink()) {
knownHard[base] = true;
return process.nextTick(LOOP);
}
Expand All @@ -1837,8 +1838,8 @@ fs.realpath = function realpath(p, options, callback) {
// dev/ino always return 0 on windows, so skip the check.
let id;
if (!isWindows) {
var dev = statValues[0].toString(32);
var ino = statValues[7].toString(32);
var dev = stats.dev.toString(32);
var ino = stats.ino.toString(32);
id = `${dev}:${ino}`;
if (seenLinks[id]) {
return gotTarget(null, seenLinks[id], base);
Expand Down
6 changes: 0 additions & 6 deletions lib/internal/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const {
UV_FS_SYMLINK_DIR,
UV_FS_SYMLINK_JUNCTION
} = process.binding('constants').fs;
const { statValues } = process.binding('fs');

const isWindows = process.platform === 'win32';

Expand Down Expand Up @@ -217,10 +216,6 @@ function getStatsFromBinding(stats, offset = 0) {
stats[12 + offset], stats[13 + offset]);
}

function getStatsFromGlobalBinding(offset = 0) {
return getStatsFromBinding(statValues, offset);
}

function stringToFlags(flags) {
if (typeof flags === 'number') {
return flags;
Expand Down Expand Up @@ -442,7 +437,6 @@ module.exports = {
preprocessSymlinkDestination,
realpathCacheKey: Symbol('realpathCacheKey'),
getStatsFromBinding,
getStatsFromGlobalBinding,
stringToFlags,
stringToSymlinkType,
Stats,
Expand Down
30 changes: 19 additions & 11 deletions src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,7 @@ void FSReqWrap::Reject(Local<Value> reject) {
}

void FSReqWrap::ResolveStat(const uv_stat_t* stat) {
node::FillGlobalStatsArray(env(), stat);
Resolve(env()->fs_stats_field_array()->GetJSArray());
Resolve(node::FillGlobalStatsArray(env(), stat));
}

void FSReqWrap::Resolve(Local<Value> value) {
Expand Down Expand Up @@ -832,10 +831,13 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
FS_SYNC_TRACE_BEGIN(stat);
int err = SyncCall(env, args[2], &req_wrap_sync, "stat", uv_fs_stat, *path);
FS_SYNC_TRACE_END(stat);
if (err == 0) {
node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
if (err != 0) {
return; // error info is in ctx
}

Local<Value> arr = node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
args.GetReturnValue().Set(arr);
}
}

Expand All @@ -859,10 +861,13 @@ static void LStat(const FunctionCallbackInfo<Value>& args) {
int err = SyncCall(env, args[2], &req_wrap_sync, "lstat", uv_fs_lstat,
*path);
FS_SYNC_TRACE_END(lstat);
if (err == 0) {
node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
if (err != 0) {
return; // error info is in ctx
}

Local<Value> arr = node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
args.GetReturnValue().Set(arr);
}
}

Expand All @@ -885,10 +890,13 @@ static void FStat(const FunctionCallbackInfo<Value>& args) {
FS_SYNC_TRACE_BEGIN(fstat);
int err = SyncCall(env, args[2], &req_wrap_sync, "fstat", uv_fs_fstat, fd);
FS_SYNC_TRACE_END(fstat);
if (err == 0) {
node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
if (err != 0) {
return; // error info is in ctx
}

Local<Value> arr = node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
args.GetReturnValue().Set(arr);
}
}

Expand Down
12 changes: 7 additions & 5 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ v8::Maybe<bool> ProcessEmitDeprecationWarning(Environment* env,
const char* deprecation_code);

template <typename NativeT, typename V8T>
void FillStatsArray(AliasedBuffer<NativeT, V8T>* fields_ptr,
v8::Local<v8::Value> FillStatsArray(AliasedBuffer<NativeT, V8T>* fields_ptr,
const uv_stat_t* s, int offset = 0) {
AliasedBuffer<NativeT, V8T>& fields = *fields_ptr;
fields[offset + 0] = s->st_dev;
Expand Down Expand Up @@ -347,12 +347,14 @@ void FillStatsArray(AliasedBuffer<NativeT, V8T>* fields_ptr,
X(12, ctim)
X(13, birthtim)
#undef X

return fields_ptr->GetJSArray();
}

inline void FillGlobalStatsArray(Environment* env,
const uv_stat_t* s,
int offset = 0) {
node::FillStatsArray(env->fs_stats_field_array(), s, offset);
inline v8::Local<v8::Value> FillGlobalStatsArray(Environment* env,
const uv_stat_t* s,
int offset = 0) {
return node::FillStatsArray(env->fs_stats_field_array(), s, offset);
}

void SetupProcessObject(Environment* env,
Expand Down
4 changes: 2 additions & 2 deletions src/node_stat_watcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ void StatWatcher::Callback(uv_fs_poll_t* handle,
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());

node::FillGlobalStatsArray(env, curr);
Local<Value> arr = node::FillGlobalStatsArray(env, curr);
node::FillGlobalStatsArray(env, prev, env->kFsStatsFieldsLength);

Local<Value> argv[2] {
Integer::New(env->isolate(), status),
env->fs_stats_field_array()->GetJSArray()
arr
};
wrap->MakeCallback(env->onchange_string(), arraysize(argv), argv);
}
Expand Down