Skip to content

Commit 46eebb6

Browse files
committed
fs: add throwIfNoEntry option to fs.lstat/fs.promises.lstat
Signed-off-by: Renegade334 <contact.9a5d6388@renegade334.me.uk>
1 parent b2f6aa3 commit 46eebb6

5 files changed

Lines changed: 59 additions & 39 deletions

File tree

doc/api/fs.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,10 @@ link(2) documentation for more detail.
14781478
<!-- YAML
14791479
added: v10.0.0
14801480
changes:
1481+
- version: REPLACEME
1482+
pr-url: https://github.com/nodejs/node/pull/63116
1483+
description: Accepts a `throwIfNoEntry` option to specify whether
1484+
an exception should be thrown if the entry does not exist.
14811485
- version: v10.5.0
14821486
pr-url: https://github.com/nodejs/node/pull/20220
14831487
description: Accepts an additional `options` object to specify whether
@@ -1488,6 +1492,9 @@ changes:
14881492
* `options` {Object}
14891493
* `bigint` {boolean} Whether the numeric values in the returned
14901494
{fs.Stats} object should be `bigint`. **Default:** `false`.
1495+
* `throwIfNoEntry` {boolean} Whether an exception will be thrown
1496+
if no file system entry exists, rather than returning `undefined`.
1497+
**Default:** `true`.
14911498
* Returns: {Promise} Fulfills with the {fs.Stats} object for the given
14921499
symbolic link `path`.
14931500
@@ -3673,6 +3680,10 @@ exception are given to the completion callback.
36733680
<!-- YAML
36743681
added: v0.1.30
36753682
changes:
3683+
- version: REPLACEME
3684+
pr-url: https://github.com/nodejs/node/pull/63116
3685+
description: Accepts a `throwIfNoEntry` option to specify whether
3686+
an exception should be thrown if the entry does not exist.
36763687
- version: v18.0.0
36773688
pr-url: https://github.com/nodejs/node/pull/41678
36783689
description: Passing an invalid callback to the `callback` argument
@@ -3700,6 +3711,9 @@ changes:
37003711
* `options` {Object}
37013712
* `bigint` {boolean} Whether the numeric values in the returned
37023713
{fs.Stats} object should be `bigint`. **Default:** `false`.
3714+
* `throwIfNoEntry` {boolean} Whether an exception will be thrown
3715+
if no file system entry exists, rather than returning `undefined`.
3716+
**Default:** `true`.
37033717
* `callback` {Function}
37043718
* `err` {Error}
37053719
* `stats` {fs.Stats}

lib/fs.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,11 @@ function makeStatsCallback(cb) {
187187
validateFunction(cb, 'cb');
188188

189189
return (err, stats) => {
190-
if (err) return cb(err);
191-
if (stats === undefined && err === null) return cb(null, undefined);
192-
cb(err, getStatsFromBinding(stats));
190+
if (err) {
191+
cb(err);
192+
} else {
193+
cb(null, stats !== undefined ? getStatsFromBinding(stats) : undefined);
194+
}
193195
};
194196
}
195197

@@ -1587,14 +1589,17 @@ function fstat(fd, options = { bigint: false }, callback) {
15871589
* Retrieves the `fs.Stats` for the symbolic link
15881590
* referred to by the `path`.
15891591
* @param {string | Buffer | URL} path
1590-
* @param {{ bigint?: boolean; }} [options]
1592+
* @param {{
1593+
bigint?: boolean;
1594+
throwIfNoEntry?: boolean;
1595+
* }} [options]
15911596
* @param {(
15921597
* err?: Error,
15931598
* stats?: Stats
15941599
* ) => any} callback
15951600
* @returns {void}
15961601
*/
1597-
function lstat(path, options = { bigint: false }, callback) {
1602+
function lstat(path, options = { bigint: false, throwIfNoEntry: true }, callback) {
15981603
if (typeof options === 'function') {
15991604
callback = options;
16001605
options = kEmptyObject;
@@ -1609,7 +1614,7 @@ function lstat(path, options = { bigint: false }, callback) {
16091614

16101615
const req = new FSReqCallback(options.bigint);
16111616
req.oncomplete = callback;
1612-
binding.lstat(path, options.bigint, req);
1617+
binding.lstat(path, options.bigint, req, options.throwIfNoEntry);
16131618
}
16141619

16151620
/**

lib/internal/fs/promises.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,18 +1662,18 @@ async function fstat(handle, options = { bigint: false }) {
16621662
return getStatsFromBinding(result);
16631663
}
16641664

1665-
async function lstat(path, options = { bigint: false }) {
1665+
async function lstat(path, options = { bigint: false, throwIfNoEntry: true }) {
16661666
path = getValidatedPath(path);
16671667
if (permission.isEnabled() && !permission.has('fs.read', path)) {
16681668
const resource = pathModule.toNamespacedPath(BufferIsBuffer(path) ? BufferToString(path) : path);
16691669
throw new ERR_ACCESS_DENIED('Access to this API has been restricted', 'FileSystemRead', resource);
16701670
}
16711671
const result = await PromisePrototypeThen(
1672-
binding.lstat(path, options.bigint, kUsePromises),
1672+
binding.lstat(path, options.bigint, kUsePromises, options.throwIfNoEntry),
16731673
undefined,
16741674
handleErrorFromBinding,
16751675
);
1676-
return getStatsFromBinding(result);
1676+
return result !== undefined ? getStatsFromBinding(result) : undefined;
16771677
}
16781678

16791679
async function stat(path, options = { bigint: false, throwIfNoEntry: true }) {
@@ -1682,11 +1682,7 @@ async function stat(path, options = { bigint: false, throwIfNoEntry: true }) {
16821682
undefined,
16831683
handleErrorFromBinding,
16841684
);
1685-
1686-
// Binding will resolve undefined if UV_ENOENT or UV_ENOTDIR and throwIfNoEntry is false
1687-
if (!options.throwIfNoEntry && result === undefined) return undefined;
1688-
1689-
return getStatsFromBinding(result);
1685+
return result !== undefined ? getStatsFromBinding(result) : undefined;
16901686
}
16911687

16921688
async function statfs(path, options = { bigint: false }) {

src/node_file.cc

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
11391139

11401140
bool use_bigint = args[1]->IsTrue();
11411141
if (!args[2]->IsUndefined()) { // stat(path, use_bigint, req,
1142-
// do_not_throw_if_no_entry)
1142+
// throw_if_no_entry)
11431143
bool do_not_throw_if_no_entry = args[3]->IsFalse();
11441144
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
11451145
CHECK_NOT_NULL(req_wrap_async);
@@ -1150,25 +1150,14 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
11501150
path.ToStringView());
11511151
FS_ASYNC_TRACE_BEGIN1(
11521152
UV_FS_STAT, req_wrap_async, "path", TRACE_STR_COPY(*path))
1153-
if (do_not_throw_if_no_entry) {
1154-
AsyncCall(env,
1155-
req_wrap_async,
1156-
args,
1157-
"stat",
1158-
UTF8,
1159-
AfterStatNoThrowIfNoEntry,
1160-
uv_fs_stat,
1161-
*path);
1162-
} else {
1163-
AsyncCall(env,
1164-
req_wrap_async,
1165-
args,
1166-
"stat",
1167-
UTF8,
1168-
AfterStat,
1169-
uv_fs_stat,
1170-
*path);
1171-
}
1153+
AsyncCall(env,
1154+
req_wrap_async,
1155+
args,
1156+
"stat",
1157+
UTF8,
1158+
do_not_throw_if_no_entry ? AfterStatNoThrowIfNoEntry : AfterStat,
1159+
uv_fs_stat,
1160+
*path);
11721161
} else { // stat(path, use_bigint, undefined, do_not_throw_if_no_entry)
11731162
THROW_IF_INSUFFICIENT_PERMISSIONS(
11741163
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
@@ -1210,7 +1199,9 @@ static void LStat(const FunctionCallbackInfo<Value>& args) {
12101199
ToNamespacedPath(env, &path);
12111200

12121201
bool use_bigint = args[1]->IsTrue();
1213-
if (!args[2]->IsUndefined()) { // lstat(path, use_bigint, req)
1202+
if (!args[2]->IsUndefined()) { // lstat(path, use_bigint, req,
1203+
// throw_if_no_entry)
1204+
bool do_not_throw_if_no_entry = args[3]->IsFalse();
12141205
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);
12151206
CHECK_NOT_NULL(req_wrap_async);
12161207
FS_ASYNC_TRACE_BEGIN1(
@@ -1220,7 +1211,7 @@ static void LStat(const FunctionCallbackInfo<Value>& args) {
12201211
args,
12211212
"lstat",
12221213
UTF8,
1223-
AfterStat,
1214+
do_not_throw_if_no_entry ? AfterStatNoThrowIfNoEntry : AfterStat,
12241215
uv_fs_lstat,
12251216
*path);
12261217
} else { // lstat(path, use_bigint, undefined, throw_if_no_entry)

test/parallel/test-fs-stat.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,23 @@ fs.lstat(__filename, undefined, common.mustCall());
224224

225225
{
226226
// Test that the throwIfNoEntry option works and returns undefined
227+
const path = 'does_not_exist';
227228
const opts = { throwIfNoEntry: false };
228-
assert.ok(!(fs.statSync('./wont_exists', opts)));
229-
fs.stat('./wont_exists', opts, common.mustSucceed((err, stats) => {
229+
230+
assert.strictEqual(fs.statSync(path, opts), undefined);
231+
assert.strictEqual(fs.lstatSync(path, opts), undefined);
232+
233+
/* eslint-disable node-core/must-call-assert */
234+
235+
const assertResult = (stats) => {
230236
assert.strictEqual(stats, undefined);
231-
}));
237+
};
238+
239+
fs.stat(path, opts, common.mustSucceed(assertResult));
240+
fs.lstat(path, opts, common.mustSucceed(assertResult));
241+
242+
fs.promises.stat(path, opts).then(common.mustCall(assertResult));
243+
fs.promises.lstat(path, opts).then(common.mustCall(assertResult));
244+
245+
/* eslint-enable node-core/must-call-assert */
232246
}

0 commit comments

Comments
 (0)