Skip to content

Commit a8cb80f

Browse files
committed
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 6781132 commit a8cb80f

6 files changed

Lines changed: 64 additions & 8 deletions

File tree

doc/api/fs.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,6 +1683,9 @@ changes:
16831683
* `options` {Object}
16841684
* `bigint` {boolean} Whether the numeric values in the returned
16851685
{fs.Stats} object should be `bigint`. **Default:** `false`.
1686+
* `throwIfNoEntry` {boolean} Whether an exception will be thrown
1687+
if no file system entry exists, rather than returning `undefined`.
1688+
**Default:** `true`.
16861689
* Returns: {Promise} Fulfills with the {fs.Stats} object for the
16871690
given `path`.
16881691
@@ -4393,6 +4396,9 @@ changes:
43934396
* `options` {Object}
43944397
* `bigint` {boolean} Whether the numeric values in the returned
43954398
{fs.Stats} object should be `bigint`. **Default:** `false`.
4399+
* `throwIfNoEntry` {boolean} Whether an exception will be thrown
4400+
if no file system entry exists, rather than returning `undefined`.
4401+
**Default:** `true`.
43964402
* `callback` {Function}
43974403
* `err` {Error}
43984404
* `stats` {fs.Stats}

lib/fs.js

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

195195
return (err, stats) => {
196196
if (err) return cb(err);
197+
if (stats === undefined && err === null) return cb(null, undefined);
197198
cb(err, getStatsFromBinding(stats));
198199
};
199200
}
@@ -1658,7 +1659,7 @@ function lstat(path, options = { bigint: false }, callback) {
16581659
* ) => any} callback
16591660
* @returns {void}
16601661
*/
1661-
function stat(path, options = { bigint: false }, callback) {
1662+
function stat(path, options = { bigint: false, throwIfNoEntry: true }, callback) {
16621663
if (typeof options === 'function') {
16631664
callback = options;
16641665
options = kEmptyObject;
@@ -1667,7 +1668,7 @@ function stat(path, options = { bigint: false }, callback) {
16671668

16681669
const req = new FSReqCallback(options.bigint);
16691670
req.oncomplete = callback;
1670-
binding.stat(getValidatedPath(path), options.bigint, req);
1671+
binding.stat(getValidatedPath(path), options.bigint, req, options.throwIfNoEntry);
16711672
}
16721673

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

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

src/node_file.cc

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,22 @@ void AfterStat(uv_fs_t* req) {
794794
}
795795
}
796796

797+
void AfterStatNoThrowIfNoEntry(uv_fs_t* req) {
798+
FSReqBase* req_wrap = FSReqBase::from_req(req);
799+
FSReqAfterScope after(req_wrap, req);
800+
801+
FS_ASYNC_TRACE_END1(
802+
req->fs_type, req_wrap, "result", static_cast<int>(req->result))
803+
if (req->result == UV_ENOENT || req->result == UV_ENOTDIR) {
804+
req_wrap->Resolve(Undefined(req_wrap->env()->isolate()));
805+
return;
806+
}
807+
808+
if (after.Proceed()) {
809+
req_wrap->ResolveStat(&req->statbuf);
810+
}
811+
}
812+
797813
void AfterStatFs(uv_fs_t* req) {
798814
FSReqBase* req_wrap = FSReqBase::from_req(req);
799815
FSReqAfterScope after(req_wrap, req);
@@ -1087,7 +1103,9 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
10871103
ToNamespacedPath(env, &path);
10881104

10891105
bool use_bigint = args[1]->IsTrue();
1090-
if (!args[2]->IsUndefined()) { // stat(path, use_bigint, req)
1106+
if (!args[2]->IsUndefined()) { // stat(path, use_bigint, req,
1107+
// do_not_throw_if_no_entry)
1108+
bool do_not_throw_if_no_entry = args[3]->IsFalse();
10911109
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
10921110
CHECK_NOT_NULL(req_wrap_async);
10931111
ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(
@@ -1097,8 +1115,25 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
10971115
path.ToStringView());
10981116
FS_ASYNC_TRACE_BEGIN1(
10991117
UV_FS_STAT, req_wrap_async, "path", TRACE_STR_COPY(*path))
1100-
AsyncCall(env, req_wrap_async, args, "stat", UTF8, AfterStat,
1101-
uv_fs_stat, *path);
1118+
if (do_not_throw_if_no_entry) {
1119+
AsyncCall(env,
1120+
req_wrap_async,
1121+
args,
1122+
"stat",
1123+
UTF8,
1124+
AfterStatNoThrowIfNoEntry,
1125+
uv_fs_stat,
1126+
*path);
1127+
} else {
1128+
AsyncCall(env,
1129+
req_wrap_async,
1130+
args,
1131+
"stat",
1132+
UTF8,
1133+
AfterStat,
1134+
uv_fs_stat,
1135+
*path);
1136+
}
11021137
} else { // stat(path, use_bigint, undefined, do_not_throw_if_no_entry)
11031138
THROW_IF_INSUFFICIENT_PERMISSIONS(
11041139
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)