Skip to content

Commit 30a0ad2

Browse files
juanarboladuh95
authored andcommitted
fs: add throwIfNoEntry option for fs.stat and fs.promises.stat
Fixes: #61116 Signed-off-by: Juan José Arboleda <soyjuanarbol@gmail.com> PR-URL: #61178 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 2a26509 commit 30a0ad2

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
@@ -1720,6 +1720,9 @@ changes:
17201720
* `options` {Object}
17211721
* `bigint` {boolean} Whether the numeric values in the returned
17221722
{fs.Stats} object should be `bigint`. **Default:** `false`.
1723+
* `throwIfNoEntry` {boolean} Whether an exception will be thrown
1724+
if no file system entry exists, rather than returning `undefined`.
1725+
**Default:** `true`.
17231726
* Returns: {Promise} Fulfills with the {fs.Stats} object for the
17241727
given `path`.
17251728
@@ -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
@@ -193,6 +193,7 @@ function makeStatsCallback(cb) {
193193

194194
return (err, stats) => {
195195
if (err) return cb(err);
196+
if (stats === undefined && err === null) return cb(null, undefined);
196197
cb(err, getStatsFromBinding(stats));
197198
};
198199
}
@@ -1637,7 +1638,7 @@ function lstat(path, options = { bigint: false }, callback) {
16371638
* ) => any} callback
16381639
* @returns {void}
16391640
*/
1640-
function stat(path, options = { bigint: false }, callback) {
1641+
function stat(path, options = { bigint: false, throwIfNoEntry: true }, callback) {
16411642
if (typeof options === 'function') {
16421643
callback = options;
16431644
options = kEmptyObject;
@@ -1646,7 +1647,7 @@ function stat(path, options = { bigint: false }, callback) {
16461647

16471648
const req = new FSReqCallback(options.bigint);
16481649
req.oncomplete = callback;
1649-
binding.stat(getValidatedPath(path), options.bigint, req);
1650+
binding.stat(getValidatedPath(path), options.bigint, req, options.throwIfNoEntry);
16501651
}
16511652

16521653
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
@@ -1034,12 +1034,16 @@ async function lstat(path, options = { bigint: false }) {
10341034
return getStatsFromBinding(result);
10351035
}
10361036

1037-
async function stat(path, options = { bigint: false }) {
1037+
async function stat(path, options = { bigint: false, throwIfNoEntry: true }) {
10381038
const result = await PromisePrototypeThen(
1039-
binding.stat(getValidatedPath(path), options.bigint, kUsePromises),
1039+
binding.stat(getValidatedPath(path), options.bigint, kUsePromises, options.throwIfNoEntry),
10401040
undefined,
10411041
handleErrorFromBinding,
10421042
);
1043+
1044+
// Binding will resolve undefined if UV_ENOENT or UV_ENOTDIR and throwIfNoEntry is false
1045+
if (!options.throwIfNoEntry && result === undefined) return undefined;
1046+
10431047
return getStatsFromBinding(result);
10441048
}
10451049

src/node_file.cc

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,22 @@ void AfterStat(uv_fs_t* req) {
801801
}
802802
}
803803

804+
void AfterStatNoThrowIfNoEntry(uv_fs_t* req) {
805+
FSReqBase* req_wrap = FSReqBase::from_req(req);
806+
FSReqAfterScope after(req_wrap, req);
807+
808+
FS_ASYNC_TRACE_END1(
809+
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
810+
if (req->result == UV_ENOENT || req->result == UV_ENOTDIR) {
811+
req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
812+
return;
813+
}
814+
815+
if (after.Proceed()) {
816+
req_wrap->ResolveStat(&req->statbuf);
817+
}
818+
}
819+
804820
void AfterStatFs(uv_fs_t* req) {
805821
FSReqBase* req_wrap = FSReqBase::from_req(req);
806822
FSReqAfterScope after(req_wrap, req);
@@ -1096,7 +1112,9 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
10961112
ToNamespacedPath(env, &path);
10971113

10981114
bool use_bigint = args[1]->IsTrue();
1099-
if (!args[2]->IsUndefined()) { // stat(path, use_bigint, req)
1115+
if (!args[2]->IsUndefined()) { // stat(path, use_bigint, req,
1116+
// do_not_throw_if_no_entry)
1117+
bool do_not_throw_if_no_entry = args[3]->IsFalse();
11001118
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
11011119
CHECK_NOT_NULL(req_wrap_async);
11021120
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
@@ -1106,8 +1124,25 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
11061124
path.ToStringView());
11071125
FS_ASYNC_TRACE_BEGIN1(
11081126
UV_FS_STAT, req_wrap_async, "path", TRACE_STR_COPY(*path))
1109-
AsyncCall(env, req_wrap_async, args, "stat", UTF8, AfterStat,
1110-
uv_fs_stat, *path);
1127+
if (do_not_throw_if_no_entry) {
1128+
AsyncCall(env,
1129+
req_wrap_async,
1130+
args,
1131+
"stat",
1132+
UTF8,
1133+
AfterStatNoThrowIfNoEntry,
1134+
uv_fs_stat,
1135+
*path);
1136+
} else {
1137+
AsyncCall(env,
1138+
req_wrap_async,
1139+
args,
1140+
"stat",
1141+
UTF8,
1142+
AfterStat,
1143+
uv_fs_stat,
1144+
*path);
1145+
}
11111146
} else { // stat(path, use_bigint, undefined, do_not_throw_if_no_entry)
11121147
THROW_IF_INSUFFICIENT_PERMISSIONS(
11131148
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)