Skip to content

Commit 6989430

Browse files
committed
fs: add throwIfNoEntry option for fs.stat and fs.promises.stat
Fixes: #61116 Signed-off-by: Juan José Arboleda <[email protected]>
1 parent 42d0e13 commit 6989430

File tree

6 files changed

+64
-8
lines changed

6 files changed

+64
-8
lines changed

doc/api/fs.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,6 +1719,9 @@ changes:
17191719
* `options` {Object}
17201720
* `bigint` {boolean} Whether the numeric values in the returned
17211721
{fs.Stats} object should be `bigint`. **Default:** `false`.
1722+
* `throwIfNoEntry` {boolean} Whether an exception will be thrown
1723+
if no file system entry exists, rather than returning `undefined`.
1724+
**Default:** `true`.
17221725
* Returns: {Promise} Fulfills with the {fs.Stats} object for the
17231726
given `path`.
17241727
@@ -4432,6 +4435,9 @@ changes:
44324435
* `options` {Object}
44334436
* `bigint` {boolean} Whether the numeric values in the returned
44344437
{fs.Stats} object should be `bigint`. **Default:** `false`.
4438+
* `throwIfNoEntry` {boolean} Whether an exception will be thrown
4439+
if no file system entry exists, rather than returning `undefined`.
4440+
**Default:** `true`.
44354441
* `callback` {Function}
44364442
* `err` {Error}
44374443
* `stats` {fs.Stats}

lib/fs.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ function makeStatsCallback(cb) {
189189

190190
return (err, stats) => {
191191
if (err) return cb(err);
192+
if (stats === undefined && err === null) return cb(null, undefined);
192193
cb(err, getStatsFromBinding(stats));
193194
};
194195
}
@@ -1614,7 +1615,7 @@ function lstat(path, options = { bigint: false }, callback) {
16141615
* ) => any} callback
16151616
* @returns {void}
16161617
*/
1617-
function stat(path, options = { bigint: false }, callback) {
1618+
function stat(path, options = { bigint: false, throwIfNoEntry: true }, callback) {
16181619
if (typeof options === 'function') {
16191620
callback = options;
16201621
options = kEmptyObject;
@@ -1623,7 +1624,7 @@ function stat(path, options = { bigint: false }, callback) {
16231624

16241625
const req = new FSReqCallback(options.bigint);
16251626
req.oncomplete = callback;
1626-
binding.stat(getValidatedPath(path), options.bigint, req);
1627+
binding.stat(getValidatedPath(path), options.bigint, req, options.throwIfNoEntry);
16271628
}
16281629

16291630
function statfs(path, options = { bigint: false }, callback) {

lib/internal/fs/promises.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,12 +1036,16 @@ async function lstat(path, options = { bigint: false }) {
10361036
return getStatsFromBinding(result);
10371037
}
10381038

1039-
async function stat(path, options = { bigint: false }) {
1039+
async function stat(path, options = { bigint: false, throwIfNoEntry: true }) {
10401040
const result = await PromisePrototypeThen(
1041-
binding.stat(getValidatedPath(path), options.bigint, kUsePromises),
1041+
binding.stat(getValidatedPath(path), options.bigint, kUsePromises, options.throwIfNoEntry),
10421042
undefined,
10431043
handleErrorFromBinding,
10441044
);
1045+
1046+
// Binding will resolve undefined if UV_ENOENT or UV_ENOTDIR and throwIfNoEntry is false
1047+
if (!options.throwIfNoEntry && result === undefined) return undefined;
1048+
10451049
return getStatsFromBinding(result);
10461050
}
10471051

src/node_file.cc

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,22 @@ void AfterStat(uv_fs_t* req) {
810810
}
811811
}
812812

813+
void AfterStatNoThrowIfNoEntry(uv_fs_t* req) {
814+
FSReqBase* req_wrap = FSReqBase::from_req(req);
815+
FSReqAfterScope after(req_wrap, req);
816+
817+
FS_ASYNC_TRACE_END1(
818+
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
819+
if (req->result == UV_ENOENT || req->result == UV_ENOTDIR) {
820+
req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
821+
return;
822+
}
823+
824+
if (after.Proceed()) {
825+
req_wrap->ResolveStat(&req->statbuf);
826+
}
827+
}
828+
813829
void AfterStatFs(uv_fs_t* req) {
814830
FSReqBase* req_wrap = FSReqBase::from_req(req);
815831
FSReqAfterScope after(req_wrap, req);
@@ -1105,7 +1121,9 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
11051121
ToNamespacedPath(env, &path);
11061122

11071123
bool use_bigint = args[1]->IsTrue();
1108-
if (!args[2]->IsUndefined()) { // stat(path, use_bigint, req)
1124+
if (!args[2]->IsUndefined()) { // stat(path, use_bigint, req,
1125+
// do_not_throw_if_no_entry)
1126+
bool do_not_throw_if_no_entry = args[3]->IsFalse();
11091127
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
11101128
CHECK_NOT_NULL(req_wrap_async);
11111129
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
@@ -1115,8 +1133,25 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
11151133
path.ToStringView());
11161134
FS_ASYNC_TRACE_BEGIN1(
11171135
UV_FS_STAT, req_wrap_async, "path", TRACE_STR_COPY(*path))
1118-
AsyncCall(env, req_wrap_async, args, "stat", UTF8, AfterStat,
1119-
uv_fs_stat, *path);
1136+
if (do_not_throw_if_no_entry) {
1137+
AsyncCall(env,
1138+
req_wrap_async,
1139+
args,
1140+
"stat",
1141+
UTF8,
1142+
AfterStatNoThrowIfNoEntry,
1143+
uv_fs_stat,
1144+
*path);
1145+
} else {
1146+
AsyncCall(env,
1147+
req_wrap_async,
1148+
args,
1149+
"stat",
1150+
UTF8,
1151+
AfterStat,
1152+
uv_fs_stat,
1153+
*path);
1154+
}
11201155
} else { // stat(path, use_bigint, undefined, do_not_throw_if_no_entry)
11211156
THROW_IF_INSUFFICIENT_PERMISSIONS(
11221157
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());

test/parallel/test-fs-promises.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ async function executeOnHandle(dest, func) {
152152
}));
153153
}
154154

155+
// File stats throwIfNoEntry: false
156+
{
157+
const stats = await stat('meow.js', { throwIfNoEntry: false });
158+
assert.strictEqual(stats, undefined);
159+
}
160+
155161
// File system stats
156162
{
157163
const statFs = await statfs(dest);

test/parallel/test-fs-stat.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,5 +224,9 @@ fs.lstat(__filename, undefined, common.mustCall());
224224

225225
{
226226
// Test that the throwIfNoEntry option works and returns undefined
227-
assert.ok(!(fs.statSync('./wont_exists', { throwIfNoEntry: false })));
227+
const opts = { throwIfNoEntry: false };
228+
assert.ok(!(fs.statSync('./wont_exists', opts)));
229+
fs.stat('./wont_exists', opts, common.mustSucceed((err, stats) => {
230+
assert.strictEqual(stats, undefined);
231+
}));
228232
}

0 commit comments

Comments
 (0)