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
24 changes: 24 additions & 0 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,10 @@ link(2) documentation for more detail.
<!-- YAML
added: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63143
description: Accepts an additional `signal` option to allow aborting the
operation.
- version: v10.5.0
pr-url: https://github.com/nodejs/node/pull/20220
description: Accepts an additional `options` object to specify whether
Expand All @@ -1491,6 +1495,8 @@ changes:
* `options` {Object}
* `bigint` {boolean} Whether the numeric values in the returned
{fs.Stats} object should be `bigint`. **Default:** `false`.
* `signal` {AbortSignal} An AbortSignal to cancel the operation.
**Default:** `undefined`.
* Returns: {Promise} Fulfills with the {fs.Stats} object for the given
symbolic link `path`.

Expand Down Expand Up @@ -1985,6 +1991,10 @@ Removes files and directories (modeled on the standard POSIX `rm` utility).
<!-- YAML
added: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63143
description: Accepts an additional `signal` option to allow aborting the
operation.
- version: v25.7.0
pr-url: https://github.com/nodejs/node/pull/61178
description: Accepts a `throwIfNoEntry` option to specify whether
Expand All @@ -2002,6 +2012,8 @@ changes:
* `throwIfNoEntry` {boolean} Whether an exception will be thrown
if no file system entry exists, rather than returning `undefined`.
**Default:** `true`.
* `signal` {AbortSignal} An AbortSignal to cancel the operation.
**Default:** `undefined`.
* Returns: {Promise} Fulfills with the {fs.Stats} object for the
given `path`.

Expand Down Expand Up @@ -3312,6 +3324,10 @@ exception are given to the completion callback.
<!-- YAML
added: v0.1.95
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63143
description: Accepts an additional `signal` option to allow aborting the
operation.
- version: v18.0.0
pr-url: https://github.com/nodejs/node/pull/41678
description: Passing an invalid callback to the `callback` argument
Expand All @@ -3335,6 +3351,8 @@ changes:
* `options` {Object}
* `bigint` {boolean} Whether the numeric values in the returned
{fs.Stats} object should be `bigint`. **Default:** `false`.
* `signal` {AbortSignal} An AbortSignal to cancel the operation.
**Default:** `undefined`.
* `callback` {Function}
* `err` {Error}
* `stats` {fs.Stats}
Expand Down Expand Up @@ -3676,6 +3694,10 @@ exception are given to the completion callback.
<!-- YAML
added: v0.1.30
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63143
description: Accepts an additional `signal` option to allow aborting the
operation.
- 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 @@ -3703,6 +3725,8 @@ changes:
* `options` {Object}
* `bigint` {boolean} Whether the numeric values in the returned
{fs.Stats} object should be `bigint`. **Default:** `false`.
* `signal` {AbortSignal} An AbortSignal to cancel the operation.
**Default:** `undefined`.
* `callback` {Function}
* `err` {Error}
* `stats` {fs.Stats}
Expand Down
45 changes: 39 additions & 6 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ const {
const {
isInt32,
parseFileMode,
validateAbortSignal,
validateBoolean,
validateBuffer,
validateEncoding,
Expand Down Expand Up @@ -340,6 +341,25 @@ function checkAborted(signal, callback) {
return false;
}

function bindSignalToReq(req, signal, callback) {
if (!signal) {
req.oncomplete = callback;
return;
}
let aborted = false;
const onAbort = () => {
aborted = true;
callback(new AbortError(undefined, { cause: signal.reason }));
};
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
signal.addEventListener('abort', onAbort, { __proto__: null, [kResistStopPropagation]: true });
req.oncomplete = function(err, result) {
signal.removeEventListener('abort', onAbort);
if (aborted) return;
callback(err, result);
};
}

/**
* Asynchronously reads the entire contents of a file.
* @param {string | Buffer | URL | number} path
Expand Down Expand Up @@ -1564,7 +1584,7 @@ function readdirSync(path, options) {
* Invokes the callback with the `fs.Stats`
* for the file descriptor.
* @param {number} fd
* @param {{ bigint?: boolean; }} [options]
* @param {{ bigint?: boolean, signal?: AbortSignal }} [options]
* @param {(
* err?: Error,
* stats?: Stats
Expand All @@ -1575,19 +1595,24 @@ function fstat(fd, options = { bigint: false }, callback) {
if (typeof options === 'function') {
callback = options;
options = kEmptyObject;
} else if (options === null || typeof options !== 'object') {
options = kEmptyObject;
}
callback = makeStatsCallback(callback);

if (options.signal !== undefined) validateAbortSignal(options.signal, 'options.signal');
if (checkAborted(options.signal, callback)) return;

const req = new FSReqCallback(options.bigint);
req.oncomplete = callback;
bindSignalToReq(req, options.signal, callback);
binding.fstat(fd, options.bigint, req);
}

/**
* 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, signal?: AbortSignal }} [options]
* @param {(
* err?: Error,
* stats?: Stats
Expand All @@ -1598,6 +1623,10 @@ function lstat(path, options = { bigint: false }, callback) {
if (typeof options === 'function') {
callback = options;
options = kEmptyObject;
} else if (options === null || typeof options !== 'object') {
options = kEmptyObject;
} else {
options = getOptions(options, { bigint: false });
}
callback = makeStatsCallback(callback);
path = getValidatedPath(path);
Expand All @@ -1607,8 +1636,11 @@ function lstat(path, options = { bigint: false }, callback) {
return;
}

if (options.signal !== undefined) validateAbortSignal(options.signal, 'options.signal');
if (checkAborted(options.signal, callback)) return;

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

Expand All @@ -1635,11 +1667,12 @@ function stat(path, options = { bigint: false, throwIfNoEntry: true }, callback)
callback = makeStatsCallback(callback);
path = getValidatedPath(path);

if (options.signal !== undefined) validateAbortSignal(options.signal, 'options.signal');
if (checkAborted(options.signal, callback)) return;

const req = new FSReqCallback(options.bigint);
req.oncomplete = callback;
binding.stat(getValidatedPath(path), options.bigint, req, options.throwIfNoEntry);
bindSignalToReq(req, options.signal, callback);
binding.stat(path, options.bigint, req, options.throwIfNoEntry);
}

function statfs(path, options = { bigint: false }, callback) {
Expand Down
55 changes: 43 additions & 12 deletions lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ const {
PromisePrototypeThen,
PromiseReject,
PromiseResolve,
PromiseWithResolvers,
SafeArrayIterator,
SafePromisePrototypeFinally,
SafePromiseRace,
Symbol,
SymbolAsyncDispose,
SymbolAsyncIterator,
Expand Down Expand Up @@ -1116,6 +1118,14 @@ function checkAborted(signal) {
throw new AbortError(undefined, { cause: signal.reason });
}

async function raceWithSignal(opPromise, signal) {
if (!signal) return opPromise;
const { promise: abortPromise, reject } = PromiseWithResolvers();
// eslint-disable-next-line no-unused-vars
using _ = EventEmitter.addAbortListener(signal, () => reject(new AbortError(undefined, { cause: signal.reason })));
return await SafePromiseRace([opPromise, abortPromise]);
}
Comment thread
mertcanaltin marked this conversation as resolved.

async function writeFileHandle(filehandle, data, signal, encoding) {
checkAborted(signal);
if (isCustomIterable(data)) {
Expand Down Expand Up @@ -1654,33 +1664,54 @@ async function symlink(target, path, type) {
}

async function fstat(handle, options = { bigint: false }) {
const result = await PromisePrototypeThen(
binding.fstat(handle.fd, options.bigint, kUsePromises),
undefined,
handleErrorFromBinding,
validateObject(options, 'options');
const { signal } = options;
if (signal !== undefined) validateAbortSignal(signal, 'options.signal');
Comment thread
mertcanaltin marked this conversation as resolved.
checkAborted(signal);
const result = await raceWithSignal(
PromisePrototypeThen(
binding.fstat(handle.fd, options.bigint, kUsePromises),
undefined,
handleErrorFromBinding,
),
signal,
);
return getStatsFromBinding(result);
}

async function lstat(path, options = { bigint: false }) {
validateObject(options, 'options');
const { signal } = options;
if (signal !== undefined) validateAbortSignal(signal, 'options.signal');
Comment thread
mertcanaltin marked this conversation as resolved.
checkAborted(signal);
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),
undefined,
handleErrorFromBinding,
const result = await raceWithSignal(
PromisePrototypeThen(
binding.lstat(path, options.bigint, kUsePromises),
undefined,
handleErrorFromBinding,
),
signal,
);
return getStatsFromBinding(result);
}

async function stat(path, options = { bigint: false, throwIfNoEntry: true }) {
const result = await PromisePrototypeThen(
binding.stat(getValidatedPath(path), options.bigint, kUsePromises, options.throwIfNoEntry),
undefined,
handleErrorFromBinding,
validateObject(options, 'options');
const { signal } = options;
if (signal !== undefined) validateAbortSignal(signal, 'options.signal');
Comment thread
mertcanaltin marked this conversation as resolved.
checkAborted(signal);
const result = await raceWithSignal(
PromisePrototypeThen(
binding.stat(getValidatedPath(path), options.bigint, kUsePromises, options.throwIfNoEntry),
undefined,
handleErrorFromBinding,
),
signal,
);

// Binding will resolve undefined if UV_ENOENT or UV_ENOTDIR and throwIfNoEntry is false
Expand Down
Loading
Loading