'use strict';
const {
ArrayPrototypePush,
Error,
ErrorCaptureStackTrace,
FunctionPrototypeBind,
MathMax,
MathMin,
Promise,
PromisePrototypeThen,
PromiseReject,
PromiseResolve,
PromiseWithResolvers,
SafeArrayIterator,
SafePromisePrototypeFinally,
SafePromiseRace,
SafeSet,
Symbol,
SymbolAsyncDispose,
SymbolAsyncIterator,
SymbolDispose,
SymbolIterator,
Uint8Array,
uncurryThis,
} = primordials;
const { fs: constants } = internalBinding('constants');
const {
F_OK,
O_SYMLINK,
O_WRONLY,
S_IFMT,
S_IFREG,
} = constants;
const binding = internalBinding('fs');
const { Buffer } = require('buffer');
const { isBuffer: BufferIsBuffer } = Buffer;
const BufferToString = uncurryThis(Buffer.prototype.toString);
const {
AbortError,
aggregateTwoErrors,
codes: {
ERR_ACCESS_DENIED,
ERR_FS_FILE_TOO_LARGE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_STATE,
ERR_METHOD_NOT_IMPLEMENTED,
ERR_OPERATION_FAILED,
ERR_OUT_OF_RANGE,
},
} = require('internal/errors');
const { isArrayBufferView } = require('internal/util/types');
const {
constants: {
kIoMaxLength,
kMaxUserId,
kReadFileBufferLength,
kReadFileUnknownBufferLength,
kWriteFileMaxChunkSize,
},
collectRecursiveReaddirResult,
copyObject,
getDirents,
getRecursiveDirents,
getOptions,
getStatFsFromBinding,
getStatsFromBinding,
getValidatedPath,
getReadFileBuffer,
getReadFileBufferByteLengthName,
preprocessSymlinkDestination,
stringToFlags,
stringToSymlinkType,
toUnixTimestamp,
handleErrorFromBinding: handleSyncErrorFromBinding,
validateBufferArray,
validateCpOptions,
validateOffsetLengthRead,
validateOffsetLengthWrite,
validatePosition,
validateReadFileBufferOptions,
validateRmOptions,
validateRmdirOptions,
validateStringAfterArrayBufferView,
vfsState,
warnOnNonPortableTemplate,
} = require('internal/fs/utils');
const { opendir } = require('internal/fs/dir');
const {
parseFileMode,
validateAbortSignal,
validateBoolean,
validateBuffer,
validateEncoding,
validateInteger,
validateObject,
validateOneOf,
kValidateObjectAllowNullable,
} = require('internal/validators');
const pathModule = require('path');
const {
getLazy,
kEmptyObject,
lazyDOMException,
promisify,
isWindows,
isMacOS,
} = require('internal/util');
const { getOptionValue } = require('internal/options');
const EventEmitter = require('events');
const { StringDecoder } = require('string_decoder');
const { kFSWatchStart, watch } = require('internal/fs/watchers');
const nonNativeWatcher = require('internal/fs/recursive_watch');
const { isIterable } = require('internal/streams/utils');
const assert = require('internal/assert');
const permission = require('internal/process/permission');
const kHandle = Symbol('kHandle');
const kFd = Symbol('kFd');
const kRefs = Symbol('kRefs');
const kClosePromise = Symbol('kClosePromise');
const kCloseReason = Symbol('kCloseReason');
const kCloseResolve = Symbol('kCloseResolve');
const kCloseReject = Symbol('kCloseReject');
const kRef = Symbol('kRef');
const kUnref = Symbol('kUnref');
const kLocked = Symbol('kLocked');
const kCloseSync = Symbol('kCloseSync');
const { kUsePromises } = binding;
const { Interface } = require('internal/readline/interface');
const {
kDeserialize, kTransfer, kTransferList, markTransferMode,
} = require('internal/worker/js_transferable');
const getDirectoryEntriesPromise = promisify(getDirents);
const validateRmOptionsPromise = promisify(validateRmOptions);
let cpPromises;
function lazyLoadCpPromises() {
return cpPromises ??= require('internal/fs/cp/cp').cpFn;
}
// Lazy loaded to avoid circular dependency.
let fsStreams;
function lazyFsStreams() {
return fsStreams ??= require('internal/fs/streams');
}
const lazyRimRaf = getLazy(() => require('internal/fs/rimraf').rimrafPromises);
const lazyReadableStream = getLazy(() =>
require('internal/webstreams/readablestream').ReadableStream,
);
// Lazy loaded to avoid circular dependency with new streams.
let newStreamsPull;
let newStreamsPullSync;
let newStreamsParsePullArgs;
let newStreamsToWriterUint8Array;
let newStreamsConvertChunks;
let newStreamsGetWriterSignal;
function lazyNewStreams() {
if (newStreamsPull === undefined) {
const pullModule = require('internal/streams/iter/pull');
newStreamsPull = pullModule.pull;
newStreamsPullSync = pullModule.pullSync;
const utils = require('internal/streams/iter/utils');
newStreamsParsePullArgs = utils.parsePullArgs;
newStreamsToWriterUint8Array = utils.toWriterUint8Array;
newStreamsConvertChunks = utils.convertChunks;
newStreamsGetWriterSignal = utils.getWriterSignal;
}
}
// By the time the C++ land creates an error for a promise rejection (likely from a
// libuv callback), there is already no JS frames on the stack. So we need to
// wait until V8 resumes execution back to JS land before we have enough information
// to re-capture the stack trace.
function handleErrorFromBinding(error) {
ErrorCaptureStackTrace(error, handleErrorFromBinding);
return PromiseReject(error);
}
class FileHandle extends EventEmitter {
#brandCheck = undefined;
static isFileHandle(value) {
return (value != null && typeof value === 'object' && #brandCheck in value);
}
/**
* @param {InternalFSBinding.FileHandle | undefined} filehandle
*/
constructor(filehandle) {
super();
markTransferMode(this, false, true);
this[kHandle] = filehandle;
this[kFd] = filehandle ? filehandle.fd : -1;
this[kRefs] = 1;
this[kClosePromise] = null;
}
getAsyncId() {
return this[kHandle].getAsyncId();
}
get fd() {
return this[kFd];
}
appendFile(data, options) {
return fsCall(writeFile, this, data, options);
}
chmod(mode) {
return fsCall(fchmod, this, mode);
}
chown(uid, gid) {
return fsCall(fchown, this, uid, gid);
}
datasync() {
return fsCall(fdatasync, this);
}
sync() {
return fsCall(fsync, this);
}
read(buffer, offset, length, position) {
return fsCall(read, this, buffer, offset, length, position);
}
readv(buffers, position) {
return fsCall(readv, this, buffers, position);
}
readFile(options) {
return fsCall(readFile, this, options);
}
readLines(options = undefined) {
return new Interface({
input: this.createReadStream(options),
crlfDelay: Infinity,
});
}
stat(options) {
return fsCall(fstat, this, options);
}
truncate(len = 0) {
return fsCall(ftruncate, this, len);
}
utimes(atime, mtime) {
return fsCall(futimes, this, atime, mtime);
}
write(buffer, offset, length, position) {
return fsCall(write, this, buffer, offset, length, position);
}
writev(buffers, position) {
return fsCall(writev, this, buffers, position);
}
writeFile(data, options) {
return fsCall(writeFile, this, data, options);
}
close = () => {
if (this[kFd] === -1) {
return PromiseResolve();
}
if (this[kClosePromise]) {
return this[kClosePromise];
}
this[kRefs]--;
if (this[kRefs] === 0) {
this[kFd] = -1;
this[kClosePromise] = SafePromisePrototypeFinally(
this[kHandle].close(),
() => { this[kClosePromise] = undefined; },
);
} else {
this[kClosePromise] = SafePromisePrototypeFinally(
new Promise((resolve, reject) => {
this[kCloseResolve] = resolve;
this[kCloseReject] = reject;
}), () => {
this[kClosePromise] = undefined;
this[kCloseReject] = undefined;
this[kCloseResolve] = undefined;
},
);
}
this.emit('close');
return this[kClosePromise];
};
[kCloseSync]() {
if (this[kFd] === -1) return;
if (this[kClosePromise]) {
throw new ERR_INVALID_STATE('The FileHandle is closing');
}
this[kFd] = -1;
this[kHandle].closeSync();
this.emit('close');
}
async [SymbolAsyncDispose]() {
await this.close();
}
/**
* @typedef {import('../webstreams/readablestream').ReadableStream
* } ReadableStream
* @param {{ type?: 'bytes', autoClose?: boolean }} [options]
* @returns {ReadableStream}
*/
readableWebStream(options = kEmptyObject) {
if (this[kFd] === -1)
throw new ERR_INVALID_STATE('The FileHandle is closed');
if (this[kClosePromise])
throw new ERR_INVALID_STATE('The FileHandle is closing');
if (this[kLocked])
throw new ERR_INVALID_STATE('The FileHandle is locked');
this[kLocked] = true;
validateObject(options, 'options');
const {
type = 'bytes',
autoClose = false,
} = options;
validateBoolean(autoClose, 'options.autoClose');
if (type !== 'bytes') {
process.emitWarning(
'A non-"bytes" options.type has no effect. A byte-oriented steam is ' +
'always created.',
'ExperimentalWarning',
);
}
const readFn = FunctionPrototypeBind(this.read, this);
const ondone = async () => {
this[kUnref]();
if (autoClose) await this.close();
};
const ReadableStream = lazyReadableStream();
const readable = new ReadableStream({
type: 'bytes',
autoAllocateChunkSize: 16384,
async pull(controller) {
const view = controller.byobRequest.view;
const { bytesRead } = await readFn(view, 0, view.byteLength);
if (bytesRead === 0) {
controller.close();
await ondone();
}
controller.byobRequest.respond(bytesRead);
},
async cancel() {
await ondone();
},
});
const {
readableStreamCancel,
} = require('internal/webstreams/readablestream');
this[kRef]();
this.once('close', () => {
readableStreamCancel(readable);
});
return readable;
}
/**
* @typedef {import('./streams').ReadStream
* } ReadStream
* @param {{
* encoding?: string;
* autoClose?: boolean;
* emitClose?: boolean;
* start: number;
* end?: number;
* highWaterMark?: number;
* }} [options]
* @returns {ReadStream}
*/
createReadStream(options = undefined) {
const { ReadStream } = lazyFsStreams();
return new ReadStream(undefined, { ...options, fd: this });
}
/**
* @typedef {import('./streams').WriteStream
* } WriteStream
* @param {{
* encoding?: string;
* autoClose?: boolean;
* emitClose?: boolean;
* start: number;
* highWaterMark?: number;
* flush?: boolean;
* }} [options]
* @returns {WriteStream}
*/
createWriteStream(options = undefined) {
const { WriteStream } = lazyFsStreams();
return new WriteStream(undefined, { ...options, fd: this });
}
[kTransfer]() {
if (this[kClosePromise] || this[kRefs] > 1) {
throw lazyDOMException('Cannot transfer FileHandle while in use',
'DataCloneError');
}
const handle = this[kHandle];
this[kFd] = -1;
this[kCloseReason] = 'The FileHandle has been transferred';
this[kHandle] = null;
this[kRefs] = 0;
return {
data: { handle },
deserializeInfo: 'internal/fs/promises:FileHandle',
};
}
[kTransferList]() {
return [ this[kHandle] ];
}
[kDeserialize]({ handle }) {
this[kHandle] = handle;
this[kFd] = handle.fd;
}
[kRef]() {
this[kRefs]++;
}
[kUnref]() {
this[kRefs]--;
if (this[kRefs] === 0) {
this[kFd] = -1;
PromisePrototypeThen(
this[kHandle].close(),
this[kCloseResolve],
this[kCloseReject],
);
}
}
}
if (getOptionValue('--experimental-stream-iter')) {
const kNullPrototo = { __proto__: null };
const kDefaultChunkSize = 131072;
const kNone = -1;
// Called when iteration of a pull()/pullSync() iterable actually starts.
function lockForIteration(handle, fd) {
if (handle[kFd] === kNone || handle[kFd] !== fd)
throw new ERR_INVALID_STATE('The FileHandle is closed');
if (handle[kClosePromise])
throw new ERR_INVALID_STATE('The FileHandle is closing');
if (handle[kLocked])
throw new ERR_INVALID_STATE('The FileHandle is locked');
handle[kLocked] = true;
}
/**
* Return the file contents as an AsyncIterable<Uint8Array[]> using the
* new streams pull model. Optional transforms and options (including
* AbortSignal) may be provided as trailing arguments, mirroring the
* Stream.pull() signature.
* @param {...(Function|object)} args - Optional transforms and/or options
* @returns {AsyncIterable<Uint8Array[]>}
*/
FileHandle.prototype.pull = function pull(...args) {
if (this[kFd] === kNone)
throw new ERR_INVALID_STATE('The FileHandle is closed');
if (this[kClosePromise])
throw new ERR_INVALID_STATE('The FileHandle is closing');
if (this[kLocked])
throw new ERR_INVALID_STATE('The FileHandle is locked');
lazyNewStreams();
const { transforms, options = kNullPrototo } = newStreamsParsePullArgs(args);
const {
autoClose = false,
chunkSize: readSize = kDefaultChunkSize,
signal,
} = options;
let {
start: pos = kNone,
limit: remaining = kNone,
} = options;
const handle = this;
const fd = this[kFd];
validateBoolean(autoClose, 'options.autoClose');
if (pos !== kNone) {
validateInteger(pos, 'options.start', 0);
}
if (remaining !== kNone) {
validateInteger(remaining, 'options.limit', 1);
}
if (readSize !== undefined) {
validateInteger(readSize, 'options.chunkSize', 1);
}
if (signal !== undefined) {
validateAbortSignal(signal, 'options.signal');
}
const source = {
__proto__: null,
async *[SymbolAsyncIterator]() {
// The handle is locked only while the iterable is actually being
// consumed. Locking eagerly in pull() would leave the handle locked
// forever if the returned iterable were never iterated.
lockForIteration(handle, fd);
handle[kRef]();
try {
if (signal) {
// Signal-aware path
while (remaining !== 0) {
if (signal.aborted) {
throw signal.reason;
}
const toRead = remaining > 0 ?
MathMin(readSize, remaining) : readSize;
const buf = Buffer.allocUnsafe(toRead);
let bytesRead;
try {
bytesRead =
(await binding.read(fd, buf, 0,
toRead, pos, kUsePromises)) || 0;
} catch (err) {
ErrorCaptureStackTrace(err, handleErrorFromBinding);
throw err;
}
if (bytesRead === 0) break;
if (pos >= 0) pos += bytesRead;
if (remaining > 0) remaining -= bytesRead;
yield [bytesRead < toRead ? buf.subarray(0, bytesRead) : buf];
}
} else {
// Fast path - no signal check per iteration
while (remaining !== 0) {
const toRead = remaining > 0 ?
MathMin(readSize, remaining) : readSize;
const buf = Buffer.allocUnsafe(toRead);
let bytesRead;
try {
bytesRead =
(await binding.read(fd, buf, 0,
toRead, pos, kUsePromises)) || 0;
} catch (err) {
ErrorCaptureStackTrace(err, handleErrorFromBinding);
throw err;
}
if (bytesRead === 0) break;
if (pos >= 0) pos += bytesRead;
if (remaining > 0) remaining -= bytesRead;
yield [bytesRead < toRead ? buf.subarray(0, bytesRead) : buf];
}
}
} finally {
handle[kLocked] = false;
handle[kUnref]();
if (autoClose) {
await handle.close();
}
}
},
};
// If transforms provided, wrap with pull pipeline
if (transforms.length > 0) {
const pullArgs = [...transforms];
if (options) {
ArrayPrototypePush(pullArgs, options);
}
return newStreamsPull(source, ...pullArgs);
}
return source;
};
/**
* Return the file contents as an Iterable<Uint8Array[]> using synchronous
* reads. Optional transforms and options may be provided as trailing
* arguments, mirroring the Stream.pullSync() signature.
* @param {...(Function|object)} args - Optional transforms and/or options
* @returns {Iterable<Uint8Array[]>}
*/
FileHandle.prototype.pullSync = function pullSync(...args) {
if (this[kFd] === kNone)
throw new ERR_INVALID_STATE('The FileHandle is closed');
if (this[kClosePromise])
throw new ERR_INVALID_STATE('The FileHandle is closing');
if (this[kLocked])
throw new ERR_INVALID_STATE('The FileHandle is locked');
lazyNewStreams();
const { transforms, options = kNullPrototo } = newStreamsParsePullArgs(args);
const {
autoClose = false,
chunkSize: readSize = kDefaultChunkSize,
} = options;
let {
start: pos = kNone,
limit: remaining = kNone,
} = options;
const handle = this;
const fd = this[kFd];
validateBoolean(autoClose, 'options.autoClose');
if (pos !== kNone) {
validateInteger(pos, 'options.start', 0);
}
if (remaining !== kNone) {
validateInteger(remaining, 'options.limit', 1);
}
if (readSize !== undefined) {
validateInteger(readSize, 'options.chunkSize', 1);
}
function cleanup() {
handle[kLocked] = false;
handle[kUnref]();
if (autoClose) {
handle[kCloseSync]();
}
}
const source = {
__proto__: null,
[SymbolIterator]() {
let started = false;
let done = false;
return {
__proto__: null,
next() {
if (done) {
return { value: undefined, done: true };
}
if (!started) {
// Lock lazily, on the first read, so that an iterable that is
// never consumed does not leave the handle locked forever.
lockForIteration(handle, fd);
handle[kRef]();
started = true;
}
if (remaining === 0) {
done = true;
cleanup();
return { value: undefined, done: true };
}
const toRead = remaining > 0 ?
MathMin(readSize, remaining) : readSize;
const buf = Buffer.allocUnsafe(toRead);
let bytesRead;
try {
bytesRead = binding.read(fd, buf, 0, toRead, pos) || 0;
} catch (err) {
done = true;
cleanup();
throw err;
}
if (bytesRead === 0) {
done = true;
cleanup();
return { value: undefined, done: true };
}
if (pos >= 0) pos += bytesRead;
if (remaining > 0) remaining -= bytesRead;
const chunk = bytesRead < toRead ?
buf.subarray(0, bytesRead) : buf;
return { value: [chunk], done: false };
},
return() {
if (!done) {
done = true;
if (started) cleanup();
}
return { value: undefined, done: true };
},
};
},
};
if (transforms.length > 0) {
return newStreamsPullSync(source, ...transforms);
}
return source;
};
/**
* Return a new-streams Writer backed by this file handle.
* The writer uses direct binding.writeBuffer / binding.writeBuffers
* calls, bypassing the FileHandle.write() validation chain.
*
* Supports writev() for batch writes (single syscall per batch).
* Handles EAGAIN with retry (up to 5 attempts), matching WriteStream.
* @param {{
* autoClose?: boolean;
* start?: number;
* }} [options]
* @returns {{ write, writev, end, fail }}
*/
FileHandle.prototype.writer = function writer(options = kNullPrototo) {
if (this[kFd] === kNone)
throw new ERR_INVALID_STATE('The FileHandle is closed');
if (this[kClosePromise])
throw new ERR_INVALID_STATE('The FileHandle is closing');
if (this[kLocked])
throw new ERR_INVALID_STATE('The FileHandle is locked');
lazyNewStreams();
validateObject(options, 'options');
const {
autoClose = false,
chunkSize: syncWriteThreshold = kDefaultChunkSize,
} = options;
let {
start: pos = kNone,
limit: bytesRemaining = kNone,
} = options;
const handle = this;
const fd = this[kFd];
let totalBytesWritten = 0;
let closed = false;
let closing = false;
let pendingEnd = null;
let endingCleanup = false;
let errored = false;
let error;
let asyncPending = 0;
let released = false;
const pendingWrites = new SafeSet();
// Async writes are issued one at a time, in call order. Without this,
// overlapping write() calls would race on the file position: when no
// `start` is given every write targets the fd's current (shared)
// position, and partial writes are completed in follow-up syscalls.
let writeQueue = PromiseResolve();
validateBoolean(autoClose, 'options.autoClose');
if (pos !== kNone) {
validateInteger(pos, 'options.start', 0);
}
if (bytesRemaining !== kNone) {
validateInteger(bytesRemaining, 'options.limit', 1);
}
if (syncWriteThreshold !== undefined) {
validateInteger(syncWriteThreshold, 'options.chunkSize', 1);
}
this[kLocked] = true;
handle[kRef]();
// Write a single buffer with EAGAIN retry (up to 5 retries).
async function writeAll(buf, offset, length, position, signal) {
let retries = 0;
while (length > 0) {
const bytesWritten = (await PromisePrototypeThen(
binding.writeBuffer(fd, buf, offset, length, position,
kUsePromises),
undefined,
handleErrorFromBinding,
)) || 0;
signal?.throwIfAborted();
if (bytesWritten === 0) {
if (++retries > 5) {
throw new ERR_OPERATION_FAILED('write failed after retries');
}
} else {
retries = 0;
}
totalBytesWritten += bytesWritten;
offset += bytesWritten;
length -= bytesWritten;
if (position >= 0) position += bytesWritten;
}
}
// Writev with EAGAIN retry. On partial write, concatenates remaining
// buffers and falls back to writeAll (same approach as WriteStream).
async function writevAll(buffers, position, signal) {
let totalSize = 0;
for (let i = 0; i < buffers.length; i++) {
totalSize += buffers[i].byteLength;
}
let retries = 0;
while (totalSize > 0) {
const bytesWritten = (await PromisePrototypeThen(
binding.writeBuffers(fd, buffers, position, kUsePromises),
undefined,
handleErrorFromBinding,
)) || 0;
signal?.throwIfAborted();
if (bytesWritten === 0) {
if (++retries > 5) {
throw new ERR_OPERATION_FAILED('writev failed after retries');
}
} else {
retries = 0;
}
totalBytesWritten += bytesWritten;
totalSize -= bytesWritten;
if (position >= 0) position += bytesWritten;
if (totalSize > 0) {
// Partial write - concatenate remaining and use writeAll.
const remaining = Buffer.concat(buffers);
const wrote = bytesWritten;
await writeAll(remaining, wrote, remaining.length - wrote,
position, signal);
return;
}
}
}
function releaseAfterFailure() {
if (!errored || asyncPending !== 0 || released) return;
released = true;
handle[kLocked] = false;
handle[kUnref]();
if (autoClose) {
handle[kCloseSync]();
}
}
function finishEnd() {
if (!closing || errored || asyncPending !== 0 || endingCleanup) return;
endingCleanup = true;
PromisePrototypeThen(cleanup(), () => {
endingCleanup = false;
if (errored) return;
closing = false;
closed = true;
pendingEnd.resolve(totalBytesWritten);
pendingEnd = null;
}, (error) => {
endingCleanup = false;
if (errored) return;
closing = false;
closed = true;
pendingEnd.reject(error);
pendingEnd = null;
});
}
function ignoreWriteQueueResult() {}
// Run `start` once every previously queued write has settled.
function enqueueWrite(start, signal) {
const operation = PromisePrototypeThen(writeQueue, () => {
// A failed writer must not touch the file again, and a write whose
// signal aborted while it was queued must not be started.
if (errored) throw error;
signal?.throwIfAborted();
return start();
});
writeQueue = PromisePrototypeThen(operation, ignoreWriteQueueResult,
ignoreWriteQueueResult);
return operation;
}
function trackOperation(operation) {
const { promise, resolve, reject } = PromiseWithResolvers();
const pending = { __proto__: null, reject };
pendingWrites.add(pending);
asyncPending++;
PromisePrototypeThen(operation, (value) => {
const active = pendingWrites.delete(pending);
asyncPending--;
releaseAfterFailure();
if (active) resolve(value);
finishEnd();
}, (error) => {
const active = pendingWrites.delete(pending);
asyncPending--;
releaseAfterFailure();
if (active) reject(error);
finishEnd();
});
return promise;
}
// Synchronous write with EAGAIN retry. Throws on I/O error.
// Used by writeSync for the full write, and by writevSync for
// completing a partial writev.
function writeSyncAll(buf, offset, length, position) {
let retries = 0;
while (length > 0) {
const ctx = {};
const bytesWritten = binding.writeBuffer(
fd, buf, offset, length, position, undefined, ctx) || 0;
if (ctx.errno !== undefined) {
handleSyncErrorFromBinding(ctx);
}
if (bytesWritten === 0) {
if (++retries > 5) {
throw new ERR_OPERATION_FAILED('write failed after retries');
}
} else {
retries = 0;
}
totalBytesWritten += bytesWritten;
offset += bytesWritten;
length -= bytesWritten;
if (position >= 0) position += bytesWritten;
}
}
async function cleanup() {
if (released) return;
released = true;
handle[kLocked] = false;
handle[kUnref]();
if (autoClose) {
await handle.close();
}
}
return {
__proto__: null,
write(chunk, options = kNullPrototo) {
chunk = newStreamsToWriterUint8Array(chunk);
const signal = newStreamsGetWriterSignal(options);
if (errored) {
return PromiseReject(error);
}
if (closed) {
return PromiseReject(
new ERR_INVALID_STATE.TypeError('The writer is closed'));
}
if (closing) {
return PromiseReject(
new ERR_INVALID_STATE.TypeError('The writer is closing'));
}
if (signal?.aborted) {
return PromiseReject(signal.reason);
}
if (bytesRemaining >= 0 && chunk.byteLength > bytesRemaining) {
return PromiseReject(
new ERR_OUT_OF_RANGE('write', `<= ${bytesRemaining} bytes`,
chunk.byteLength));
}
if (bytesRemaining > 0) bytesRemaining -= chunk.byteLength;
const position = pos;
if (pos >= 0) pos += chunk.byteLength;
return trackOperation(enqueueWrite(
() => writeAll(chunk, 0, chunk.byteLength, position, signal),
signal));
},
writev(chunks, options = kNullPrototo) {
chunks = newStreamsConvertChunks(chunks);
const signal = newStreamsGetWriterSignal(options);
if (errored) {
return PromiseReject(error);
}
if (closed) {
return PromiseReject(
new ERR_INVALID_STATE.TypeError('The writer is closed'));
}
if (closing) {
return PromiseReject(
new ERR_INVALID_STATE.TypeError('The writer is closing'));
}
if (signal?.aborted) {
return PromiseReject(signal.reason);
}
let totalSize = 0;
for (let i = 0; i < chunks.length; i++) {
totalSize += chunks[i].byteLength;
}
if (bytesRemaining >= 0 && totalSize > bytesRemaining) {
return PromiseReject(
new ERR_OUT_OF_RANGE('writev', `<= ${bytesRemaining} bytes`,
totalSize));
}
if (bytesRemaining > 0) bytesRemaining -= totalSize;
const position = pos;
if (pos >= 0) pos += totalSize;
return trackOperation(enqueueWrite(
() => writevAll(chunks, position, signal), signal));
},
writeSync(chunk) {
chunk = newStreamsToWriterUint8Array(chunk);
if (errored || closed || closing || asyncPending) return false;
const length = chunk.byteLength;
if (length > syncWriteThreshold) return false;
if (length === 0) return true;
if (bytesRemaining >= 0 && length > bytesRemaining) return false;
const position = pos;
// First attempt - if this fails with zero bytes written,
// return false so pipeTo can fall back to async write().
const ctx = {};
const bytesWritten = binding.writeBuffer(
fd, chunk, 0, length, position, undefined, ctx) || 0;
if (ctx.errno !== undefined) return false;
totalBytesWritten += bytesWritten;
if (position >= 0) {
pos = position + bytesWritten;
}
if (bytesWritten === length) {
if (bytesRemaining > 0) bytesRemaining -= length;
return true;
}
// Partial write - bytes are on disk. Must complete or throw.
// Cannot return false here because pipeTo would re-send the
// full chunk, causing duplicate data on disk.
writeSyncAll(chunk, bytesWritten, length - bytesWritten,
position >= 0 ? position + bytesWritten : -1);
if (bytesRemaining > 0) bytesRemaining -= length;
return true;
},
writevSync(chunks) {
chunks = newStreamsConvertChunks(chunks);
if (errored || closed || closing || asyncPending) return false;
let totalSize = 0;
for (let i = 0; i < chunks.length; i++) {
totalSize += chunks[i].byteLength;
}
if (totalSize > syncWriteThreshold) return false;
if (totalSize === 0) return true;
if (bytesRemaining >= 0 && totalSize > bytesRemaining) return false;
const position = pos;
// writeBuffers throws on error (zero bytes written) - safe
// to catch and return false for async fallback.
let bytesWritten;
try {
bytesWritten = binding.writeBuffers(fd, chunks, position) || 0;
} catch {
return false;
}
totalBytesWritten += bytesWritten;
if (position >= 0) {
pos = position + bytesWritten;
}
if (bytesWritten === totalSize) {
if (bytesRemaining > 0) bytesRemaining -= totalSize;
return true;
}
// Partial writev - bytes are on disk. Must complete or throw.
const rest = Buffer.concat(chunks);
writeSyncAll(rest, bytesWritten,
rest.byteLength - bytesWritten,
position >= 0 ? position + bytesWritten : -1);
if (bytesRemaining > 0) bytesRemaining -= totalSize;
return true;
},
end(options = kNullPrototo) {
const signal = newStreamsGetWriterSignal(options);
if (errored) {
return PromiseReject(error);
}
if (closed) {
return PromiseResolve(totalBytesWritten);
}
if (closing) {
return pendingEnd.promise;
}
if (signal?.aborted) {
return PromiseReject(signal.reason);
}
closing = true;
pendingEnd = PromiseWithResolvers();
finishEnd();
return pendingEnd.promise;
},
endSync() {
if (errored) return -1;
if (closed) return totalBytesWritten;
if (closing) return -1;
if (asyncPending) return -1;
closed = true;
released = true;
handle[kLocked] = false;
handle[kUnref]();
if (autoClose) {
handle[kCloseSync]();
}
return totalBytesWritten;
},
fail(reason) {
if (closed || errored) return;
errored = true;
error = reason;
closing = false;
closed = true;
pendingEnd?.reject(reason);
pendingEnd = null;
for (const pending of pendingWrites) {
pending.reject(reason);
}
pendingWrites.clear();
releaseAfterFailure();
},
[SymbolAsyncDispose]() {
if (closing) {
return pendingEnd?.promise ?? PromiseResolve();
}
if (!closed && !errored) {
this.fail();
}
return PromiseResolve();
},
[SymbolDispose]() {
this.fail();
},
};
};
}
async function handleFdClose(fileOpPromise, closeFunc) {
return PromisePrototypeThen(
fileOpPromise,
(result) => PromisePrototypeThen(closeFunc(), () => result),
(opError) =>
PromisePrototypeThen(
closeFunc(),
() => PromiseReject(opError),
(closeError) => PromiseReject(aggregateTwoErrors(closeError, opError)),
),
);
}
async function handleFdSync(fileOpPromise, handle) {
return PromisePrototypeThen(
fileOpPromise,
(result) => PromisePrototypeThen(
handle.sync(),
() => result,
(syncError) => PromiseReject(syncError),
),
(opError) => PromiseReject(opError),
);
}
async function fsCall(fn, handle, ...args) {
assert(handle[kRefs] !== undefined,
'handle must be an instance of FileHandle');
if (handle.fd === -1) {
// eslint-disable-next-line no-restricted-syntax
const err = new Error(handle[kCloseReason] ?? 'file closed');
err.code = 'EBADF';
err.syscall = fn.name;
throw err;
}
try {
handle[kRef]();
return await fn(handle, ...new SafeArrayIterator(args));
} finally {
handle[kUnref]();
}
}
function checkAborted(signal) {
if (signal?.aborted)
throw new AbortError(undefined, { cause: signal.reason });
}
function rejectWithReason(signal, reject) {
return () => {
reject(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,
rejectWithReason(signal, reject));
return await SafePromiseRace([opPromise, abortPromise]);
}
async function writeFileHandle(filehandle, data, signal, encoding) {
checkAborted(signal);
if (isCustomIterable(data)) {
for await (const buf of data) {
checkAborted(signal);
const toWrite =
isArrayBufferView(buf) ? buf : Buffer.from(buf, encoding || 'utf8');
let remaining = toWrite.byteLength;
while (remaining > 0) {
const writeSize = MathMin(kWriteFileMaxChunkSize, remaining);
const { bytesWritten } = await write(
filehandle, toWrite, toWrite.byteLength - remaining, writeSize);
remaining -= bytesWritten;
checkAborted(signal);
}
}
return;
}
data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
let remaining = data.byteLength;
if (remaining === 0) return;
do {
checkAborted(signal);
const { bytesWritten } =
await write(filehandle, data, 0,
MathMin(kWriteFileMaxChunkSize, data.byteLength));
remaining -= bytesWritten;
data = new Uint8Array(
data.buffer,
data.byteOffset + bytesWritten,
data.byteLength - bytesWritten,
);
} while (remaining > 0);
}
async function readFileHandleWithUserBuffer(filehandle, options, size) {
const signal = options?.signal;
const encoding = options?.encoding;
const buffer = getReadFileBuffer(options, size);
const byteLengthName = getReadFileBufferByteLengthName(options);
let totalRead = 0;
while (totalRead < buffer.byteLength) {
checkAborted(signal);
const length = size === 0 ?
buffer.byteLength - totalRead :
MathMin(size - totalRead, kReadFileBufferLength);
const bytesRead = (await PromisePrototypeThen(
binding.read(filehandle.fd, buffer, totalRead, length, -1, kUsePromises),
undefined,
handleErrorFromBinding,
)) ?? 0;
totalRead += bytesRead;
if (bytesRead === 0 || totalRead === size) {
const result = buffer.subarray(0, totalRead);
return encoding ? result.toString(encoding) : result;
}
}
if (size === 0) {
checkAborted(signal);
const extraBuffer = Buffer.allocUnsafeSlow(1);
const bytesRead = (await PromisePrototypeThen(
binding.read(filehandle.fd, extraBuffer, 0, 1, -1, kUsePromises),
undefined,
handleErrorFromBinding,
)) ?? 0;
if (bytesRead !== 0) {
throw new ERR_INVALID_ARG_VALUE(
byteLengthName,
buffer.byteLength,
'is too small to contain the entire file',
);
}
}
return encoding ? buffer.toString(encoding) : buffer.subarray(0, totalRead);
}
async function readFileHandle(filehandle, options, knownRegularFileSize) {
const signal = options?.signal;
const encoding = options?.encoding;
const decoder = encoding && new StringDecoder(encoding);
checkAborted(signal);
let size = 0;
let length = 0;
if (knownRegularFileSize !== undefined) {
// Handed over by readFile() together with an already open fd.
size = knownRegularFileSize;
length = encoding ? MathMin(size, kReadFileBufferLength) : size;
} else {
const statFields = await PromisePrototypeThen(
binding.fstat(filehandle.fd, false, kUsePromises),
undefined,
handleErrorFromBinding,
);
checkAborted(signal);
if ((statFields[1/* mode */] & S_IFMT) === S_IFREG) {
size = statFields[8/* size */];
length = encoding ? MathMin(size, kReadFileBufferLength) : size;
}
}
if (length === 0) {
length = kReadFileUnknownBufferLength;
}
if (size > kIoMaxLength)
throw new ERR_FS_FILE_TOO_LARGE(size);
if (options.buffer !== undefined) {
return readFileHandleWithUserBuffer(filehandle, options, size);
}
let totalRead = 0;
const noSize = size === 0;
let buffer = Buffer.allocUnsafeSlow(length);
let result = '';
let offset = 0;
let buffers;
const chunkedRead = length > kReadFileBufferLength;
while (true) {
checkAborted(signal);
if (chunkedRead) {
length = MathMin(size - totalRead, kReadFileBufferLength);
}
const bytesRead = (await PromisePrototypeThen(
binding.read(filehandle.fd, buffer, offset, length, -1, kUsePromises),
undefined,
handleErrorFromBinding,
)) ?? 0;
totalRead += bytesRead;
if (bytesRead === 0 ||
totalRead === size ||
(bytesRead !== buffer.length && !chunkedRead && !noSize)) {
const singleRead = bytesRead === totalRead;
const bytesToCheck = chunkedRead ? totalRead : bytesRead;
if (bytesToCheck !== buffer.length) {
buffer = buffer.subarray(0, bytesToCheck);
}
if (!encoding) {
if (noSize && !singleRead) {
ArrayPrototypePush(buffers, buffer);
return Buffer.concat(buffers, totalRead);
}
return buffer;
}
if (singleRead) {
return buffer.toString(encoding);
}
result += decoder.end(buffer);
return result;
}
const readBuffer = bytesRead !== buffer.length ?
buffer.subarray(0, bytesRead) :
buffer;
if (encoding) {
result += decoder.write(readBuffer);
} else if (size !== 0) {
offset = totalRead;
} else {
buffers ??= [];
// Unknown file size requires chunks.
ArrayPrototypePush(buffers, readBuffer);
buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength);
}
}
}
// All of the functions are defined as async in order to ensure that errors
// thrown cause promise rejections rather than being thrown synchronously.
async function access(path, mode = F_OK) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.access(path, mode);
if (promise !== undefined) { await promise; return; }
}
return await PromisePrototypeThen(
binding.access(getValidatedPath(path), mode, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function cp(src, dest, options) {
options = validateCpOptions(options);
src = getValidatedPath(src, 'src');
dest = getValidatedPath(dest, 'dest');
return lazyLoadCpPromises()(src, dest, options);
}
async function copyFile(src, dest, mode) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.copyFile(src, dest, mode);
if (promise !== undefined) { await promise; return; }
}
return await PromisePrototypeThen(
binding.copyFile(
getValidatedPath(src, 'src'),
getValidatedPath(dest, 'dest'),
mode,
kUsePromises,
),
undefined,
handleErrorFromBinding,
);
}
// Note that unlike fs.open() which uses numeric file descriptors,
// fsPromises.open() uses the fs.FileHandle class.
async function open(path, flags, mode) {
const h = vfsState.handlers;
if (h !== null) {
const result = h.promisesOpen(path, flags, mode);
if (result !== undefined) return result;
}
path = getValidatedPath(path);
const flagsNumber = stringToFlags(flags);
mode = parseFileMode(mode, 'mode', 0o666);
return new FileHandle(await PromisePrototypeThen(
binding.openFileHandle(path, flagsNumber, mode, kUsePromises),
undefined,
handleErrorFromBinding,
));
}
async function read(handle, bufferOrParams, offset, length, position) {
let buffer = bufferOrParams;
if (!isArrayBufferView(buffer)) {
// This is fh.read(params)
if (bufferOrParams !== undefined) {
validateObject(bufferOrParams, 'options', kValidateObjectAllowNullable);
}
({
buffer = Buffer.alloc(16384),
offset = 0,
length = buffer.byteLength - offset,
position = null,
} = bufferOrParams ?? kEmptyObject);
validateBuffer(buffer);
}
if (offset !== null && typeof offset === 'object') {
// This is fh.read(buffer, options)
({
offset = 0,
length = buffer.byteLength - offset,
position = null,
} = offset);
}
if (offset == null) {
offset = 0;
} else {
validateInteger(offset, 'offset', 0);
}
length ??= buffer.byteLength - offset;
length |= 0;
if (position == null) {
position = -1;
} else {
validatePosition(position, 'position', length);
}
if (length === 0)
return { __proto__: null, bytesRead: length, buffer };
if (buffer.byteLength === 0) {
throw new ERR_INVALID_ARG_VALUE('buffer', buffer,
'is empty and cannot be written');
}
validateOffsetLengthRead(offset, length, buffer.byteLength);
const bytesRead = (await PromisePrototypeThen(
binding.read(handle.fd, buffer, offset, length, position, kUsePromises),
undefined,
handleErrorFromBinding,
)) || 0;
return { __proto__: null, bytesRead, buffer };
}
async function readv(handle, buffers, position) {
validateBufferArray(buffers);
if (typeof position !== 'number')
position = null;
const bytesRead = (await PromisePrototypeThen(
binding.readBuffers(handle.fd, buffers, position, kUsePromises),
undefined,
handleErrorFromBinding,
)) || 0;
return { __proto__: null, bytesRead, buffers };
}
async function write(handle, buffer, offsetOrOptions, length, position) {
if (buffer?.byteLength === 0)
return { __proto__: null, bytesWritten: 0, buffer };
let offset = offsetOrOptions;
if (isArrayBufferView(buffer)) {
if (typeof offset === 'object') {
({
offset = 0,
length = buffer.byteLength - offset,
position = null,
} = offsetOrOptions ?? kEmptyObject);
}
if (offset == null) {
offset = 0;
} else {
validateInteger(offset, 'offset', 0);
}
if (typeof length !== 'number')
length = buffer.byteLength - offset;
if (typeof position !== 'number')
position = null;
validateOffsetLengthWrite(offset, length, buffer.byteLength);
const bytesWritten =
(await PromisePrototypeThen(
binding.writeBuffer(handle.fd, buffer, offset,
length, position, kUsePromises),
undefined,
handleErrorFromBinding,
)) || 0;
return { __proto__: null, bytesWritten, buffer };
}
validateStringAfterArrayBufferView(buffer, 'buffer');
validateEncoding(buffer, length);
const bytesWritten = (await PromisePrototypeThen(
binding.writeString(handle.fd, buffer, offset, length, kUsePromises),
undefined,
handleErrorFromBinding,
)) || 0;
return { __proto__: null, bytesWritten, buffer };
}
async function writev(handle, buffers, position) {
validateBufferArray(buffers);
if (typeof position !== 'number')
position = null;
if (buffers.length === 0) {
return { __proto__: null, bytesWritten: 0, buffers };
}
const bytesWritten = (await PromisePrototypeThen(
binding.writeBuffers(handle.fd, buffers, position, kUsePromises),
undefined,
handleErrorFromBinding,
)) || 0;
return { __proto__: null, bytesWritten, buffers };
}
async function rename(oldPath, newPath) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.rename(oldPath, newPath);
if (promise !== undefined) { await promise; return; }
}
oldPath = getValidatedPath(oldPath, 'oldPath');
newPath = getValidatedPath(newPath, 'newPath');
return await PromisePrototypeThen(
binding.rename(oldPath, newPath, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function truncate(path, len = 0) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.truncate(path, len);
if (promise !== undefined) { await promise; return; }
}
const fd = await open(path, 'r+');
return handleFdClose(ftruncate(fd, len), fd.close);
}
async function ftruncate(handle, len = 0) {
validateInteger(len, 'len');
len = MathMax(0, len);
return await PromisePrototypeThen(
binding.ftruncate(handle.fd, len, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function rm(path, options) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.rm(path, options);
if (promise !== undefined) { await promise; return; }
}
path = getValidatedPath(path);
options = await validateRmOptionsPromise(path, options, false);
return lazyRimRaf()(path, options);
}
async function rmdir(path, options) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.rmdir(path);
if (promise !== undefined) { await promise; return; }
}
path = getValidatedPath(path);
if (options?.recursive !== undefined) {
throw new ERR_INVALID_ARG_VALUE(
'options.recursive',
options.recursive,
'is no longer supported',
);
}
options = validateRmdirOptions(options);
return await PromisePrototypeThen(
binding.rmdir(path, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function fdatasync(handle) {
if (permission.isEnabled()) {
throw new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.');
}
return await PromisePrototypeThen(
binding.fdatasync(handle.fd, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function fsync(handle) {
if (permission.isEnabled()) {
throw new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.');
}
return await PromisePrototypeThen(
binding.fsync(handle.fd, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function mkdir(path, options) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.mkdir(path, options);
if (promise !== undefined) return (await promise).result;
}
if (typeof options === 'number' || typeof options === 'string') {
options = { mode: options };
}
const {
recursive = false,
mode = 0o777,
} = options || kEmptyObject;
path = getValidatedPath(path);
validateBoolean(recursive, 'options.recursive');
return await PromisePrototypeThen(
binding.mkdir(
path,
parseFileMode(mode, 'mode', 0o777),
recursive,
kUsePromises,
),
undefined,
handleErrorFromBinding,
);
}
async function readdirRecursive(originalPath, options) {
if (permission.isEnabled()) {
return readdirRecursiveWithPermissionModel(originalPath, options);
}
const withFileTypes = !!options.withFileTypes;
const result = await PromisePrototypeThen(
binding.readdirRecursive(
originalPath,
options.encoding,
withFileTypes,
kUsePromises,
),
undefined,
handleErrorFromBinding,
);
return withFileTypes ? getRecursiveDirents(originalPath, result) : result;
}
// TODO: native?
async function readdirRecursiveWithPermissionModel(basePath, options) {
const { encoding } = options;
const context = {
withFileTypes: !!options.withFileTypes,
results: [],
dirs: [basePath],
prefixes: [''],
};
for (let i = 0; i < context.dirs.length; i++) {
const dir = context.dirs[i];
const result = await PromisePrototypeThen(
binding.readdir(dir, encoding, true, kUsePromises),
undefined,
handleErrorFromBinding,
);
collectRecursiveReaddirResult(dir, context.prefixes[i], result, context);
}
return context.results;
}
async function readdir(path, options) {
options = getOptions(options);
// Make shallow copy to prevent mutating options from affecting results
options = copyObject(options);
path = getValidatedPath(path);
const h = vfsState.handlers;
if (h !== null) {
const promise = h.readdir(path, options);
if (promise !== undefined) return await promise;
}
if (options.recursive) {
return readdirRecursive(path, options);
}
const result = await PromisePrototypeThen(
binding.readdir(
path,
options.encoding,
!!options.withFileTypes,
kUsePromises,
),
undefined,
handleErrorFromBinding,
);
return options.withFileTypes ?
getDirectoryEntriesPromise(path, result) :
result;
}
async function readlink(path, options) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.readlink(path, options);
if (promise !== undefined) return await promise;
}
options = getOptions(options);
path = getValidatedPath(path, 'oldPath');
return await PromisePrototypeThen(
binding.readlink(path, options.encoding, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function symlink(target, path, type) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.symlink(target, path, type);
if (promise !== undefined) { await promise; return; }
}
validateOneOf(type, 'type', ['dir', 'file', 'junction', null, undefined]);
if (isWindows && type == null) {
try {
const absoluteTarget = pathModule.resolve(`${path}`, '..', `${target}`);
type = (await stat(absoluteTarget)).isDirectory() ? 'dir' : 'file';
} catch {
// Default to 'file' if path is invalid or file does not exist
type = 'file';
}
}
// Due to the nature of Node.js runtime, symlinks has different edge cases that can bypass
// the permission model security guarantees. Thus, this API is disabled unless fs.read
// and fs.write permission has been given.
if (permission.isEnabled() && !permission.has('fs') &&
!permission.isAuditMode()) {
throw new ERR_ACCESS_DENIED('fs.symlink API requires full fs.read and fs.write permissions.');
}
target = getValidatedPath(target, 'target');
path = getValidatedPath(path);
return await PromisePrototypeThen(
binding.symlink(
preprocessSymlinkDestination(target, type, path),
path,
stringToSymlinkType(type),
kUsePromises,
),
undefined,
handleErrorFromBinding,
);
}
async function fstat(handle, options = { __proto__: null, bigint: false }) {
validateObject(options, 'options');
const { signal } = options;
if (signal !== undefined) validateAbortSignal(signal, 'options.signal');
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 = { __proto__: null, bigint: false }) {
validateObject(options, 'options');
const { signal } = options;
if (signal !== undefined) validateAbortSignal(signal, 'options.signal');
checkAborted(signal);
const h = vfsState.handlers;
if (h !== null) {
const promise = h.lstat(path, options);
if (promise !== undefined) return await promise;
}
path = getValidatedPath(path);
if (permission.isEnabled() && !permission.has('fs.read', path) &&
!permission.isAuditMode()) {
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 raceWithSignal(
PromisePrototypeThen(
binding.lstat(path, options.bigint, kUsePromises),
undefined,
handleErrorFromBinding,
),
signal,
);
return getStatsFromBinding(result);
}
async function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) {
validateObject(options, 'options');
const { signal } = options;
if (signal !== undefined) validateAbortSignal(signal, 'options.signal');
checkAborted(signal);
const h = vfsState.handlers;
if (h !== null) {
const promise = h.stat(path, options);
if (promise !== undefined) return await promise;
}
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
if (!options.throwIfNoEntry && result === undefined) return undefined;
return getStatsFromBinding(result);
}
async function statfs(path, options = { __proto__: null, bigint: false }) {
const h = vfsState.handlers;
if (h !== null) {
const result = h.statfs(path, options);
if (result !== undefined) return result;
}
const result = await PromisePrototypeThen(
binding.statfs(getValidatedPath(path), options.bigint, kUsePromises),
undefined,
handleErrorFromBinding,
);
return getStatFsFromBinding(result);
}
async function link(existingPath, newPath) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.link(existingPath, newPath);
if (promise !== undefined) { await promise; return; }
}
existingPath = getValidatedPath(existingPath, 'existingPath');
newPath = getValidatedPath(newPath, 'newPath');
return await PromisePrototypeThen(
binding.link(existingPath, newPath, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function unlink(path) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.unlink(path);
if (promise !== undefined) { await promise; return; }
}
return await PromisePrototypeThen(
binding.unlink(getValidatedPath(path), kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function fchmod(handle, mode) {
if (permission.isEnabled()) {
throw new ERR_ACCESS_DENIED('fchmod API is disabled when Permission Model is enabled.');
}
mode = parseFileMode(mode, 'mode');
return await PromisePrototypeThen(
binding.fchmod(handle.fd, mode, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function chmod(path, mode) {
path = getValidatedPath(path);
mode = parseFileMode(mode, 'mode');
const h = vfsState.handlers;
if (h !== null) {
const promise = h.chmod(path, mode);
if (promise !== undefined) { await promise; return; }
}
return await PromisePrototypeThen(
binding.chmod(path, mode, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function lchmod(path, mode) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.lchmod(path, mode);
if (promise !== undefined) { await promise; return; }
}
if (O_SYMLINK === undefined)
throw new ERR_METHOD_NOT_IMPLEMENTED('lchmod()');
const fd = await open(path, O_WRONLY | O_SYMLINK);
return handleFdClose(fchmod(fd, mode), fd.close);
}
async function lchown(path, uid, gid) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.lchown(path, uid, gid);
if (promise !== undefined) { await promise; return; }
}
path = getValidatedPath(path);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
return await PromisePrototypeThen(
binding.lchown(path, uid, gid, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function fchown(handle, uid, gid) {
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
if (permission.isEnabled()) {
throw new ERR_ACCESS_DENIED('fchown API is disabled when Permission Model is enabled.');
}
return await PromisePrototypeThen(
binding.fchown(handle.fd, uid, gid, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function chown(path, uid, gid) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.chown(path, uid, gid);
if (promise !== undefined) { await promise; return; }
}
path = getValidatedPath(path);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
return await PromisePrototypeThen(
binding.chown(path, uid, gid, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function utimes(path, atime, mtime) {
path = getValidatedPath(path);
atime = toUnixTimestamp(atime);
mtime = toUnixTimestamp(mtime);
const h = vfsState.handlers;
if (h !== null) {
const promise = h.utimes(path, atime, mtime);
if (promise !== undefined) { await promise; return; }
}
return await PromisePrototypeThen(
binding.utimes(path, atime, mtime, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function futimes(handle, atime, mtime) {
if (permission.isEnabled()) {
throw new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.');
}
atime = toUnixTimestamp(atime, 'atime');
mtime = toUnixTimestamp(mtime, 'mtime');
return await PromisePrototypeThen(
binding.futimes(handle.fd, atime, mtime, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function lutimes(path, atime, mtime) {
path = getValidatedPath(path);
atime = toUnixTimestamp(atime);
mtime = toUnixTimestamp(mtime);
const h = vfsState.handlers;
if (h !== null) {
const promise = h.lutimes(path, atime, mtime);
if (promise !== undefined) { await promise; return; }
}
return await PromisePrototypeThen(
binding.lutimes(path, atime, mtime, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function realpath(path, options) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.realpath(path, options);
if (promise !== undefined) return await promise;
}
options = getOptions(options);
return await PromisePrototypeThen(
binding.realpath(getValidatedPath(path), options.encoding, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function mkdtemp(prefix, options) {
const h = vfsState.handlers;
if (h !== null) {
const promise = h.mkdtemp(prefix, options);
if (promise !== undefined) return await promise;
}
options = getOptions(options);
if (BufferIsBuffer(prefix)) {
options = { ...options, encoding: 'buffer' };
}
prefix = getValidatedPath(prefix, 'prefix');
warnOnNonPortableTemplate(prefix);
return await PromisePrototypeThen(
binding.mkdtemp(prefix, options.encoding, kUsePromises),
undefined,
handleErrorFromBinding,
);
}
async function mkstemp(prefix, options) {
options = getOptions(options);
if (BufferIsBuffer(prefix)) {
options = { ...options, encoding: 'buffer' };
}
prefix = getValidatedPath(prefix, 'prefix');
warnOnNonPortableTemplate(prefix);
const h = vfsState.handlers;
if (h !== null) {
const promise = h.promisesMkstemp(prefix, options);
if (promise !== undefined) return await promise;
}
const result = await PromisePrototypeThen(
binding.mkstemp(prefix, options.encoding, kUsePromises),
undefined,
handleErrorFromBinding,
);
return { __proto__: null, path: result[0], handle: new FileHandle(result[1]) };
}
async function mkdtempDisposable(prefix, options) {
options = getOptions(options);
if (BufferIsBuffer(prefix)) {
options = { ...options, encoding: 'buffer' };
}
prefix = getValidatedPath(prefix, 'prefix');
warnOnNonPortableTemplate(prefix);
const cwd = process.cwd();
const path = await PromisePrototypeThen(
binding.mkdtemp(prefix, options.encoding, kUsePromises),
undefined,
handleErrorFromBinding,
);
// Stash the full path in case of process.chdir()
const fullPath = pathModule.resolve(cwd, path);
const remove = async () => {
const rmrf = lazyRimRaf();
await rmrf(fullPath, {
maxRetries: 0,
recursive: true,
retryDelay: 0,
});
};
return {
__proto__: null,
path,
remove,
async [SymbolAsyncDispose]() {
await remove();
},
};
}
async function writeFile(path, data, options) {
options = getOptions(options, {
encoding: 'utf8',
mode: 0o666,
flag: 'w',
flush: false,
});
const flush = options.flush ?? false;
validateBoolean(flush, 'options.flush');
parseFileMode(options.mode, 'mode', 0o666);
const h = vfsState.handlers;
if (h !== null) {
checkAborted(options.signal);
const promise = h.writeFile(path, data, options);
if (promise !== undefined) { await promise; return; }
}
const flag = options.flag || 'w';
if (!isArrayBufferView(data) && !isCustomIterable(data)) {
validateStringAfterArrayBufferView(data, 'data');
data = Buffer.from(data, options.encoding || 'utf8');
}
validateAbortSignal(options.signal);
if (path instanceof FileHandle)
return writeFileHandle(path, data, options.signal, options.encoding);
checkAborted(options.signal);
if (!flush && !isCustomIterable(data) && data.byteLength <= kWriteFileMaxChunkSize) {
path = getValidatedPath(path);
await writeFileInOneRoundTrip(path, stringToFlags(flag, 'options.flag'),
parseFileMode(options.mode, 'mode', 0o666), data);
checkAborted(options.signal); // An abort during the write still wins.
return;
}
const fd = await open(path, flag, options.mode);
let writeOp = writeFileHandle(fd, data, options.signal, options.encoding);
if (flush) {
writeOp = handleFdSync(writeOp, fd);
}
return handleFdClose(writeOp, fd.close);
}
/**
* Open + write + close as one thread pool round trip.
* @param {string|Buffer} path Validated path
* @param {number} flagsNumber
* @param {number} mode
* @param {ArrayBufferView} data
* @returns {Promise<void>}
*/
function writeFileInOneRoundTrip(path, flagsNumber, mode, data) {
return new Promise((resolve, reject) => {
const job = new binding.WriteFileJob(path, flagsNumber, mode, data);
job.ondone = (err) => {
if (err != null) {
ErrorCaptureStackTrace(err, writeFileInOneRoundTrip);
reject(err);
} else {
resolve();
}
};
const accessError = job.run(path);
if (accessError !== undefined) {
ErrorCaptureStackTrace(accessError, writeFileInOneRoundTrip);
reject(accessError);
}
});
}
function isCustomIterable(obj) {
return isIterable(obj) && !isArrayBufferView(obj) && typeof obj !== 'string';
}
async function appendFile(path, data, options) {
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
parseFileMode(options.mode, 'mode', 0o666);
const h = vfsState.handlers;
if (h !== null) {
checkAborted(options.signal);
const promise = h.appendFile(path, data, options);
if (promise !== undefined) { await promise; return; }
}
options = copyObject(options);
options.flag ||= 'a';
return writeFile(path, data, options);
}
async function readFile(path, options) {
const h = vfsState.handlers;
if (h !== null) {
checkAborted(options?.signal);
const result = h.readFile(path, options);
if (result !== undefined) return result;
}
options = getOptions(options, { flag: 'r' });
validateReadFileBufferOptions(options);
const flag = options.flag || 'r';
if (path instanceof FileHandle)
return readFileHandle(path, options);
checkAborted(options.signal);
if (options.buffer === undefined && vfsState.handlers === null) {
// Open + fstat + read + close in one thread pool round trip for files of
// up to one chunk; larger files come back as an open fd + size and are
// read by readFileHandle() as before.
path = getValidatedPath(path);
const { 0: buffer, 1: fd, 2: size } = await readFileInOneRoundTrip(path, stringToFlags(flag));
if (fd === -1) {
checkAborted(options.signal); // An abort during the read still wins.
return options.encoding ? buffer.toString(options.encoding) : buffer;
}
const filehandle = new FileHandle(new binding.FileHandle(fd));
return handleFdClose(readFileHandle(filehandle, options, size), filehandle.close);
}
const fd = await open(path, flag, 0o666);
return handleFdClose(readFileHandle(fd, options), fd.close);
}
/**
* @param {string|Buffer} path Validated path
* @param {number} flagsNumber
* @returns {Promise<[Buffer|undefined, number, number|undefined]>} [buffer, -1] or [undefined, fd, size]
*/
function readFileInOneRoundTrip(path, flagsNumber) {
return new Promise((resolve, reject) => {
const job = new binding.ReadFileJob(path, flagsNumber, kReadFileBufferLength);
job.ondone = (err, buffer, fd, size, closeErr) => {
const error = err ?? closeErr;
if (error != null) {
ErrorCaptureStackTrace(error, readFileInOneRoundTrip);
reject(error);
} else {
resolve([buffer, fd, size]);
}
};
const accessError = job.run(path);
if (accessError !== undefined) {
ErrorCaptureStackTrace(accessError, readFileInOneRoundTrip);
reject(accessError);
}
});
}
async function* _watch(filename, options = kEmptyObject) {
const h = vfsState.handlers;
if (h !== null) {
const result = h.promisesWatch(filename, options);
if (result !== undefined) {
yield* result;
return;
}
}
validateObject(options, 'options');
if (options.recursive != null) {
validateBoolean(options.recursive, 'options.recursive');
// TODO(anonrig): Remove non-native watcher when/if libuv supports recursive.
// As of November 2022, libuv does not support recursive file watch on all platforms,
// e.g. Linux due to the limitations of inotify.
if (options.recursive && !isMacOS && !isWindows) {
const watcher = new nonNativeWatcher.FSWatcher(options);
watcher[kFSWatchStart](filename);
yield* watcher;
return;
}
}
yield* watch(filename, options);
}
const lazyGlob = getLazy(() => require('internal/fs/glob').Glob);
async function* glob(pattern, options) {
const Glob = lazyGlob();
yield* new Glob(pattern, options).glob();
}
module.exports = {
exports: {
access,
copyFile,
cp,
glob,
open,
opendir: promisify(opendir),
rename,
truncate,
rm,
rmdir,
mkdir,
readdir,
readlink,
symlink,
lstat,
stat,
statfs,
link,
unlink,
chmod,
lchmod,
lchown,
chown,
utimes,
lutimes,
realpath,
mkdtemp,
mkdtempDisposable,
mkstemp,
writeFile,
appendFile,
readFile,
watch: _watch,
constants,
},
FileHandle,
kHandle,
kLocked,
kRef,
kUnref,
};