Skip to content
Open
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
14 changes: 14 additions & 0 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1478,6 +1478,10 @@ link(2) documentation for more detail.
<!-- YAML
added: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63116
description: Accepts a `throwIfNoEntry` option to specify whether
an exception should be thrown if the entry does not exist.
- version: v10.5.0
pr-url: https://github.com/nodejs/node/pull/20220
description: Accepts an additional `options` object to specify whether
Expand All @@ -1488,6 +1492,9 @@ changes:
* `options` {Object}
* `bigint` {boolean} Whether the numeric values in the returned
{fs.Stats} object should be `bigint`. **Default:** `false`.
* `throwIfNoEntry` {boolean} Whether an exception will be thrown
if no file system entry exists, rather than returning `undefined`.
**Default:** `true`.
* Returns: {Promise} Fulfills with the {fs.Stats} object for the given
symbolic link `path`.

Expand Down Expand Up @@ -3673,6 +3680,10 @@ exception are given to the completion callback.
<!-- YAML
added: v0.1.30
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63116
description: Accepts a `throwIfNoEntry` option to specify whether
an exception should be thrown if the entry does not exist.
- version: v18.0.0
pr-url: https://github.com/nodejs/node/pull/41678
description: Passing an invalid callback to the `callback` argument
Expand Down Expand Up @@ -3700,6 +3711,9 @@ changes:
* `options` {Object}
* `bigint` {boolean} Whether the numeric values in the returned
{fs.Stats} object should be `bigint`. **Default:** `false`.
* `throwIfNoEntry` {boolean} Whether an exception will be thrown
if no file system entry exists, rather than returning `undefined`.
**Default:** `true`.
* `callback` {Function}
* `err` {Error}
* `stats` {fs.Stats}
Expand Down
17 changes: 11 additions & 6 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,11 @@ function makeStatsCallback(cb) {
validateFunction(cb, 'cb');

return (err, stats) => {
if (err) return cb(err);
if (stats === undefined && err === null) return cb(null, undefined);
cb(err, getStatsFromBinding(stats));
if (err) {
cb(err);
} else {
cb(null, stats !== undefined ? getStatsFromBinding(stats) : undefined);
}
};
}

Expand Down Expand Up @@ -1587,14 +1589,17 @@ function fstat(fd, options = { bigint: false }, callback) {
* Retrieves the `fs.Stats` for the symbolic link
* referred to by the `path`.
* @param {string | Buffer | URL} path
* @param {{ bigint?: boolean; }} [options]
* @param {{
bigint?: boolean;
throwIfNoEntry?: boolean;
* }} [options]
* @param {(
* err?: Error,
* stats?: Stats
* ) => any} callback
* @returns {void}
*/
function lstat(path, options = { bigint: false }, callback) {
function lstat(path, options = { bigint: false, throwIfNoEntry: true }, callback) {
if (typeof options === 'function') {
callback = options;
options = kEmptyObject;
Expand All @@ -1609,7 +1614,7 @@ function lstat(path, options = { bigint: false }, callback) {

const req = new FSReqCallback(options.bigint);
req.oncomplete = callback;
binding.lstat(path, options.bigint, req);
binding.lstat(path, options.bigint, req, options.throwIfNoEntry);
}

/**
Expand Down
12 changes: 4 additions & 8 deletions lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -1662,18 +1662,18 @@ async function fstat(handle, options = { bigint: false }) {
return getStatsFromBinding(result);
}

async function lstat(path, options = { bigint: false }) {
async function lstat(path, options = { bigint: false, throwIfNoEntry: true }) {
path = getValidatedPath(path);
if (permission.isEnabled() && !permission.has('fs.read', path)) {
const resource = pathModule.toNamespacedPath(BufferIsBuffer(path) ? BufferToString(path) : path);
throw new ERR_ACCESS_DENIED('Access to this API has been restricted', 'FileSystemRead', resource);
}
const result = await PromisePrototypeThen(
binding.lstat(path, options.bigint, kUsePromises),
binding.lstat(path, options.bigint, kUsePromises, options.throwIfNoEntry),
undefined,
handleErrorFromBinding,
);
return getStatsFromBinding(result);
return result !== undefined ? getStatsFromBinding(result) : undefined;
}

async function stat(path, options = { bigint: false, throwIfNoEntry: true }) {
Expand All @@ -1682,11 +1682,7 @@ async function stat(path, options = { bigint: false, throwIfNoEntry: true }) {
undefined,
handleErrorFromBinding,
);

// Binding will resolve undefined if UV_ENOENT or UV_ENOTDIR and throwIfNoEntry is false
if (!options.throwIfNoEntry && result === undefined) return undefined;

return getStatsFromBinding(result);
return result !== undefined ? getStatsFromBinding(result) : undefined;
}

async function statfs(path, options = { bigint: false }) {
Expand Down
35 changes: 13 additions & 22 deletions src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1139,7 +1139,7 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {

bool use_bigint = args[1]->IsTrue();
if (!args[2]->IsUndefined()) { // stat(path, use_bigint, req,
// do_not_throw_if_no_entry)
// throw_if_no_entry)
bool do_not_throw_if_no_entry = args[3]->IsFalse();
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
CHECK_NOT_NULL(req_wrap_async);
Expand All @@ -1150,25 +1150,14 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
path.ToStringView());
FS_ASYNC_TRACE_BEGIN1(
UV_FS_STAT, req_wrap_async, "path", TRACE_STR_COPY(*path))
if (do_not_throw_if_no_entry) {
AsyncCall(env,
req_wrap_async,
args,
"stat",
UTF8,
AfterStatNoThrowIfNoEntry,
uv_fs_stat,
*path);
} else {
AsyncCall(env,
req_wrap_async,
args,
"stat",
UTF8,
AfterStat,
uv_fs_stat,
*path);
}
AsyncCall(env,
req_wrap_async,
args,
"stat",
UTF8,
do_not_throw_if_no_entry ? AfterStatNoThrowIfNoEntry : AfterStat,
uv_fs_stat,
*path);
} else { // stat(path, use_bigint, undefined, do_not_throw_if_no_entry)
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
Expand Down Expand Up @@ -1210,7 +1199,9 @@ static void LStat(const FunctionCallbackInfo<Value>& args) {
ToNamespacedPath(env, &path);

bool use_bigint = args[1]->IsTrue();
if (!args[2]->IsUndefined()) { // lstat(path, use_bigint, req)
if (!args[2]->IsUndefined()) { // lstat(path, use_bigint, req,
// throw_if_no_entry)
bool do_not_throw_if_no_entry = args[3]->IsFalse();
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
CHECK_NOT_NULL(req_wrap_async);
FS_ASYNC_TRACE_BEGIN1(
Expand All @@ -1220,7 +1211,7 @@ static void LStat(const FunctionCallbackInfo<Value>& args) {
args,
"lstat",
UTF8,
AfterStat,
do_not_throw_if_no_entry ? AfterStatNoThrowIfNoEntry : AfterStat,
uv_fs_lstat,
*path);
} else { // lstat(path, use_bigint, undefined, throw_if_no_entry)
Expand Down
17 changes: 14 additions & 3 deletions test/parallel/test-fs-stat.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,20 @@ fs.lstat(__filename, undefined, common.mustCall());

{
// Test that the throwIfNoEntry option works and returns undefined
const path = 'does_not_exist';
const opts = { throwIfNoEntry: false };
assert.ok(!(fs.statSync('./wont_exists', opts)));
fs.stat('./wont_exists', opts, common.mustSucceed((err, stats) => {

const assertResult = (stats) => {
// eslint-disable-next-line node-core/must-call-assert
assert.strictEqual(stats, undefined);
}));
};

assertResult(fs.statSync(path, opts));
assertResult(fs.lstatSync(path, opts));

fs.stat(path, opts, common.mustSucceed(assertResult));
fs.lstat(path, opts, common.mustSucceed(assertResult));

fs.promises.stat(path, opts).then(assertResult).then(common.mustCall());
fs.promises.lstat(path, opts).then(assertResult).then(common.mustCall());
}
Loading