node/lib/fs.js
Joyee Cheung 813713f211
fs: throw errors from sync branches instead of separate implementations
Previously to throw errors from C++ land, sync versions of the fs
were created by copying C++ code from the original implementation
and moving JS code to a separate file. This can lead to several
problems:

1. By moving code to a new file for the sake of moving, it would
  be harder to use git blame to trace changes and harder to backport
  changes to older branches.
2. Scattering the async and sync versions of fs methods in
  different files makes it harder to keep them in sync and
  share code in the prologues and epilogues.
3. Having two copies of code doing almost the same thing results
  in duplication and can be prone to out-of-sync problems when the
  prologue and epilogue get updated.
4. There is a minor cost to startup in adding an additional file.
  This can add up even with the help of snapshots.

This patch moves the JS code back to lib/fs.js to stop 1, 2 & 4
and introduces C++ helpers SyncCallAndThrowIf() and
SyncCallAndThrowOnError() so that the original implementations
can be easily tweaked to allow throwing from C++ and stop 3.

PR-URL: https://github.com/nodejs/node/pull/49913
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Darshan Sen <raisinten@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
2023-09-30 01:17:00 +00:00

3242 lines
84 KiB
JavaScript

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// Maintainers, keep in mind that ES1-style octal literals (`0666`) are not
// allowed in strict mode. Use ES6-style octal literals instead (`0o666`).
'use strict';
const {
ArrayPrototypePush,
BigIntPrototypeToString,
Boolean,
MathMax,
Number,
ObjectDefineProperties,
ObjectDefineProperty,
Promise,
PromiseResolve,
ReflectApply,
SafeMap,
SafeSet,
StringPrototypeCharCodeAt,
StringPrototypeIndexOf,
StringPrototypeSlice,
} = primordials;
const { fs: constants } = internalBinding('constants');
const {
S_IFIFO,
S_IFLNK,
S_IFMT,
S_IFREG,
S_IFSOCK,
F_OK,
R_OK,
W_OK,
X_OK,
O_WRONLY,
O_SYMLINK,
} = constants;
const pathModule = require('path');
const { isArrayBufferView } = require('internal/util/types');
const binding = internalBinding('fs');
const { createBlobFromFilePath } = require('internal/blob');
const { Buffer } = require('buffer');
const {
aggregateTwoErrors,
codes: {
ERR_FS_FILE_TOO_LARGE,
ERR_INVALID_ARG_VALUE,
},
AbortError,
uvErrmapGet,
uvException,
} = require('internal/errors');
const {
FSReqCallback,
statValues,
} = binding;
const { toPathIfFileURL } = require('internal/url');
const {
customPromisifyArgs: kCustomPromisifyArgsSymbol,
kEmptyObject,
promisify: {
custom: kCustomPromisifiedSymbol,
},
SideEffectFreeRegExpPrototypeExec,
defineLazyProperties,
} = require('internal/util');
const {
constants: {
kIoMaxLength,
kMaxUserId,
},
copyObject,
Dirent,
emitRecursiveRmdirWarning,
getDirent,
getDirents,
getOptions,
getValidatedFd,
getValidatedPath,
getValidMode,
handleErrorFromBinding,
possiblyTransformPath,
preprocessSymlinkDestination,
Stats,
getStatFsFromBinding,
getStatsFromBinding,
realpathCacheKey,
stringToFlags,
stringToSymlinkType,
toUnixTimestamp,
validateBufferArray,
validateCpOptions,
validateOffsetLengthRead,
validateOffsetLengthWrite,
validatePath,
validatePosition,
validateRmOptions,
validateRmOptionsSync,
validateRmdirOptions,
validateStringAfterArrayBufferView,
warnOnNonPortableTemplate,
} = require('internal/fs/utils');
const {
CHAR_FORWARD_SLASH,
CHAR_BACKWARD_SLASH,
} = require('internal/constants');
const {
isInt32,
parseFileMode,
validateBoolean,
validateBuffer,
validateEncoding,
validateFunction,
validateInteger,
validateObject,
validateString,
} = require('internal/validators');
let truncateWarn = true;
let fs;
// Lazy loaded
let cpFn;
let cpSyncFn;
let promises = null;
let ReadStream;
let WriteStream;
let rimraf;
let rimrafSync;
let kResistStopPropagation;
// These have to be separate because of how graceful-fs happens to do it's
// monkeypatching.
let FileReadStream;
let FileWriteStream;
const isWindows = process.platform === 'win32';
const isOSX = process.platform === 'darwin';
function showTruncateDeprecation() {
if (truncateWarn) {
process.emitWarning(
'Using fs.truncate with a file descriptor is deprecated. Please use ' +
'fs.ftruncate with a file descriptor instead.',
'DeprecationWarning', 'DEP0081');
truncateWarn = false;
}
}
function maybeCallback(cb) {
validateFunction(cb, 'cb');
return cb;
}
// Ensure that callbacks run in the global context. Only use this function
// for callbacks that are passed to the binding layer, callbacks that are
// invoked from JS already run in the proper scope.
function makeCallback(cb) {
validateFunction(cb, 'cb');
return (...args) => ReflectApply(cb, this, args);
}
// Special case of `makeCallback()` that is specific to async `*stat()` calls as
// an optimization, since the data passed back to the callback needs to be
// transformed anyway.
function makeStatsCallback(cb) {
validateFunction(cb, 'cb');
return (err, stats) => {
if (err) return cb(err);
cb(err, getStatsFromBinding(stats));
};
}
const isFd = isInt32;
function isFileType(stats, fileType) {
// Use stats array directly to avoid creating an fs.Stats instance just for
// our internal use.
let mode = stats[1];
if (typeof mode === 'bigint')
mode = Number(mode);
return (mode & S_IFMT) === fileType;
}
/**
* Tests a user's permissions for the file or directory
* specified by `path`.
* @param {string | Buffer | URL} path
* @param {number} [mode]
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function access(path, mode, callback) {
if (typeof mode === 'function') {
callback = mode;
mode = F_OK;
}
path = getValidatedPath(path);
mode = getValidMode(mode, 'access');
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.access(pathModule.toNamespacedPath(path), mode, req);
}
/**
* Synchronously tests a user's permissions for the file or
* directory specified by `path`.
* @param {string | Buffer | URL} path
* @param {number} [mode]
* @returns {void}
*/
function accessSync(path, mode) {
path = getValidatedPath(path);
mode = getValidMode(mode, 'access');
binding.access(pathModule.toNamespacedPath(path), mode);
}
/**
* Tests whether or not the given path exists.
* @param {string | Buffer | URL} path
* @param {(exists?: boolean) => any} callback
* @returns {void}
*/
function exists(path, callback) {
maybeCallback(callback);
function suppressedCallback(err) {
callback(err ? false : true);
}
try {
fs.access(path, F_OK, suppressedCallback);
} catch {
return callback(false);
}
}
ObjectDefineProperty(exists, kCustomPromisifiedSymbol, {
__proto__: null,
value: function exists(path) { // eslint-disable-line func-name-matching
return new Promise((resolve) => fs.exists(path, resolve));
},
});
// fs.existsSync never throws, it only returns true or false.
// Since fs.existsSync never throws, users have established
// the expectation that passing invalid arguments to it, even like
// fs.existsSync(), would only get a false in return, so we cannot signal
// validation errors to users properly out of compatibility concerns.
// TODO(joyeecheung): deprecate the never-throw-on-invalid-arguments behavior
/**
* Synchronously tests whether or not the given path exists.
* @param {string | Buffer | URL} path
* @returns {boolean}
*/
function existsSync(path) {
try {
path = getValidatedPath(path);
} catch {
return false;
}
return binding.existsSync(pathModule.toNamespacedPath(path));
}
function readFileAfterOpen(err, fd) {
const context = this.context;
if (err) {
context.callback(err);
return;
}
context.fd = fd;
const req = new FSReqCallback();
req.oncomplete = readFileAfterStat;
req.context = context;
binding.fstat(fd, false, req);
}
function readFileAfterStat(err, stats) {
const context = this.context;
if (err)
return context.close(err);
// TODO(BridgeAR): Check if allocating a smaller chunk is better performance
// wise, similar to the promise based version (less peak memory and chunked
// stringify operations vs multiple C++/JS boundary crossings).
const size = context.size = isFileType(stats, S_IFREG) ? stats[8] : 0;
if (size > kIoMaxLength) {
err = new ERR_FS_FILE_TOO_LARGE(size);
return context.close(err);
}
try {
if (size === 0) {
// TODO(BridgeAR): If an encoding is set, use the StringDecoder to concat
// the result and reuse the buffer instead of allocating a new one.
context.buffers = [];
} else {
context.buffer = Buffer.allocUnsafeSlow(size);
}
} catch (err) {
return context.close(err);
}
context.read();
}
function checkAborted(signal, callback) {
if (signal?.aborted) {
callback(new AbortError(undefined, { cause: signal?.reason }));
return true;
}
return false;
}
/**
* Asynchronously reads the entire contents of a file.
* @param {string | Buffer | URL | number} path
* @param {{
* encoding?: string | null;
* flag?: string;
* signal?: AbortSignal;
* } | string} [options]
* @param {(
* err?: Error,
* data?: string | Buffer
* ) => any} callback
* @returns {void}
*/
function readFile(path, options, callback) {
callback = maybeCallback(callback || options);
options = getOptions(options, { flag: 'r' });
const ReadFileContext = require('internal/fs/read/context');
const context = new ReadFileContext(callback, options.encoding);
context.isUserFd = isFd(path); // File descriptor ownership
if (options.signal) {
context.signal = options.signal;
}
if (context.isUserFd) {
process.nextTick(function tick(context) {
ReflectApply(readFileAfterOpen, { context }, [null, path]);
}, context);
return;
}
if (checkAborted(options.signal, callback))
return;
const flagsNumber = stringToFlags(options.flag, 'options.flag');
path = getValidatedPath(path);
const req = new FSReqCallback();
req.context = context;
req.oncomplete = readFileAfterOpen;
binding.open(pathModule.toNamespacedPath(path),
flagsNumber,
0o666,
req);
}
function tryStatSync(fd, isUserFd) {
const ctx = {};
const stats = binding.fstat(fd, false, undefined, ctx);
if (ctx.errno !== undefined && !isUserFd) {
fs.closeSync(fd);
throw uvException(ctx);
}
return stats;
}
function tryCreateBuffer(size, fd, isUserFd) {
let threw = true;
let buffer;
try {
if (size > kIoMaxLength) {
throw new ERR_FS_FILE_TOO_LARGE(size);
}
buffer = Buffer.allocUnsafe(size);
threw = false;
} finally {
if (threw && !isUserFd) fs.closeSync(fd);
}
return buffer;
}
function tryReadSync(fd, isUserFd, buffer, pos, len) {
let threw = true;
let bytesRead;
try {
bytesRead = fs.readSync(fd, buffer, pos, len);
threw = false;
} finally {
if (threw && !isUserFd) fs.closeSync(fd);
}
return bytesRead;
}
/**
* Synchronously reads the entire contents of a file.
* @param {string | Buffer | URL | number} path
* @param {{
* encoding?: string | null;
* flag?: string;
* }} [options]
* @returns {string | Buffer}
*/
function readFileSync(path, options) {
options = getOptions(options, { flag: 'r' });
if (options.encoding === 'utf8' || options.encoding === 'utf-8') {
if (!isInt32(path)) {
path = pathModule.toNamespacedPath(getValidatedPath(path));
}
return binding.readFileUtf8(path, stringToFlags(options.flag));
}
const isUserFd = isFd(path); // File descriptor ownership
const fd = isUserFd ? path : fs.openSync(path, options.flag, 0o666);
const stats = tryStatSync(fd, isUserFd);
const size = isFileType(stats, S_IFREG) ? stats[8] : 0;
let pos = 0;
let buffer; // Single buffer with file data
let buffers; // List for when size is unknown
if (size === 0) {
buffers = [];
} else {
buffer = tryCreateBuffer(size, fd, isUserFd);
}
let bytesRead;
if (size !== 0) {
do {
bytesRead = tryReadSync(fd, isUserFd, buffer, pos, size - pos);
pos += bytesRead;
} while (bytesRead !== 0 && pos < size);
} else {
do {
// The kernel lies about many files.
// Go ahead and try to read some bytes.
buffer = Buffer.allocUnsafe(8192);
bytesRead = tryReadSync(fd, isUserFd, buffer, 0, 8192);
if (bytesRead !== 0) {
ArrayPrototypePush(buffers, buffer.slice(0, bytesRead));
}
pos += bytesRead;
} while (bytesRead !== 0);
}
if (!isUserFd)
fs.closeSync(fd);
if (size === 0) {
// Data was collected into the buffers list.
buffer = Buffer.concat(buffers, pos);
} else if (pos < size) {
buffer = buffer.slice(0, pos);
}
if (options.encoding) buffer = buffer.toString(options.encoding);
return buffer;
}
function defaultCloseCallback(err) {
if (err != null) throw err;
}
/**
* Closes the file descriptor.
* @param {number} fd
* @param {(err?: Error) => any} [callback]
* @returns {void}
*/
function close(fd, callback = defaultCloseCallback) {
fd = getValidatedFd(fd);
if (callback !== defaultCloseCallback)
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.close(fd, req);
}
/**
* Synchronously closes the file descriptor.
* @param {number} fd
* @returns {void}
*/
function closeSync(fd) {
fd = getValidatedFd(fd);
return binding.close(fd);
}
/**
* Asynchronously opens a file.
* @param {string | Buffer | URL} path
* @param {string | number} [flags]
* @param {string | number} [mode]
* @param {(
* err?: Error,
* fd?: number
* ) => any} callback
* @returns {void}
*/
function open(path, flags, mode, callback) {
path = getValidatedPath(path);
if (arguments.length < 3) {
callback = flags;
flags = 'r';
mode = 0o666;
} else if (typeof mode === 'function') {
callback = mode;
mode = 0o666;
} else {
mode = parseFileMode(mode, 'mode', 0o666);
}
const flagsNumber = stringToFlags(flags);
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.open(pathModule.toNamespacedPath(path),
flagsNumber,
mode,
req);
}
/**
* Synchronously opens a file.
* @param {string | Buffer | URL} path
* @param {string | number} [flags]
* @param {string | number} [mode]
* @returns {number}
*/
function openSync(path, flags, mode) {
path = getValidatedPath(path);
return binding.open(
pathModule.toNamespacedPath(path),
stringToFlags(flags),
parseFileMode(mode, 'mode', 0o666),
);
}
/**
* @param {string | Buffer | URL } path
* @param {{
* type?: string;
* }} [options]
* @returns {Promise<Blob>}
*/
function openAsBlob(path, options = kEmptyObject) {
validateObject(options, 'options');
const type = options.type || '';
validateString(type, 'options.type');
// The underlying implementation here returns the Blob synchronously for now.
// To give ourselves flexibility to maybe return the Blob asynchronously,
// this API returns a Promise.
path = getValidatedPath(path);
return PromiseResolve(createBlobFromFilePath(pathModule.toNamespacedPath(path), { type }));
}
/**
* Reads file from the specified `fd` (file descriptor).
* @param {number} fd
* @param {Buffer | TypedArray | DataView} buffer
* @param {number} offsetOrOptions
* @param {number} length
* @param {number | bigint | null} position
* @param {(
* err?: Error,
* bytesRead?: number,
* buffer?: Buffer
* ) => any} callback
* @returns {void}
*/
function read(fd, buffer, offsetOrOptions, length, position, callback) {
fd = getValidatedFd(fd);
let offset = offsetOrOptions;
let params = null;
if (arguments.length <= 4) {
if (arguments.length === 4) {
// This is fs.read(fd, buffer, options, callback)
validateObject(offsetOrOptions, 'options', { nullable: true });
callback = length;
params = offsetOrOptions;
} else if (arguments.length === 3) {
// This is fs.read(fd, bufferOrParams, callback)
if (!isArrayBufferView(buffer)) {
// This is fs.read(fd, params, callback)
params = buffer;
({ buffer = Buffer.alloc(16384) } = params ?? kEmptyObject);
}
callback = offsetOrOptions;
} else {
// This is fs.read(fd, callback)
callback = buffer;
buffer = Buffer.alloc(16384);
}
if (params !== undefined) {
validateObject(params, 'options', { nullable: true });
}
({
offset = 0,
length = buffer?.byteLength - offset,
position = null,
} = params ?? kEmptyObject);
}
validateBuffer(buffer);
callback = maybeCallback(callback);
if (offset == null) {
offset = 0;
} else {
validateInteger(offset, 'offset', 0);
}
length |= 0;
if (length === 0) {
return process.nextTick(function tick() {
callback(null, 0, buffer);
});
}
if (buffer.byteLength === 0) {
throw new ERR_INVALID_ARG_VALUE('buffer', buffer,
'is empty and cannot be written');
}
validateOffsetLengthRead(offset, length, buffer.byteLength);
if (position == null) {
position = -1;
} else {
validatePosition(position, 'position', length);
}
function wrapper(err, bytesRead) {
// Retain a reference to buffer so that it can't be GC'ed too soon.
callback(err, bytesRead || 0, buffer);
}
const req = new FSReqCallback();
req.oncomplete = wrapper;
binding.read(fd, buffer, offset, length, position, req);
}
ObjectDefineProperty(read, kCustomPromisifyArgsSymbol,
{ __proto__: null, value: ['bytesRead', 'buffer'], enumerable: false });
/**
* Synchronously reads the file from the
* specified `fd` (file descriptor).
* @param {number} fd
* @param {Buffer | TypedArray | DataView} buffer
* @param {{
* offset?: number;
* length?: number;
* position?: number | bigint | null;
* }} [offsetOrOptions]
* @returns {number}
*/
function readSync(fd, buffer, offsetOrOptions, length, position) {
fd = getValidatedFd(fd);
validateBuffer(buffer);
let offset = offsetOrOptions;
if (arguments.length <= 3 || typeof offsetOrOptions === 'object') {
if (offsetOrOptions !== undefined) {
validateObject(offsetOrOptions, 'options', { nullable: true });
}
({
offset = 0,
length = buffer.byteLength - offset,
position = null,
} = offsetOrOptions ?? kEmptyObject);
}
if (offset === undefined) {
offset = 0;
} else {
validateInteger(offset, 'offset', 0);
}
length |= 0;
if (length === 0) {
return 0;
}
if (buffer.byteLength === 0) {
throw new ERR_INVALID_ARG_VALUE('buffer', buffer,
'is empty and cannot be written');
}
validateOffsetLengthRead(offset, length, buffer.byteLength);
if (position == null) {
position = -1;
} else {
validatePosition(position, 'position', length);
}
const ctx = {};
const result = binding.read(fd, buffer, offset, length, position,
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
/**
* Reads file from the specified `fd` (file descriptor)
* and writes to an array of `ArrayBufferView`s.
* @param {number} fd
* @param {ArrayBufferView[]} buffers
* @param {number | null} [position]
* @param {(
* err?: Error,
* bytesRead?: number,
* buffers?: ArrayBufferView[];
* ) => any} callback
* @returns {void}
*/
function readv(fd, buffers, position, callback) {
function wrapper(err, read) {
callback(err, read || 0, buffers);
}
fd = getValidatedFd(fd);
validateBufferArray(buffers);
callback = maybeCallback(callback || position);
const req = new FSReqCallback();
req.oncomplete = wrapper;
if (typeof position !== 'number')
position = null;
return binding.readBuffers(fd, buffers, position, req);
}
ObjectDefineProperty(readv, kCustomPromisifyArgsSymbol,
{ __proto__: null, value: ['bytesRead', 'buffers'], enumerable: false });
/**
* Synchronously reads file from the
* specified `fd` (file descriptor) and writes to an array
* of `ArrayBufferView`s.
* @param {number} fd
* @param {ArrayBufferView[]} buffers
* @param {number | null} [position]
* @returns {number}
*/
function readvSync(fd, buffers, position) {
fd = getValidatedFd(fd);
validateBufferArray(buffers);
const ctx = {};
if (typeof position !== 'number')
position = null;
const result = binding.readBuffers(fd, buffers, position, undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
/**
* Writes `buffer` to the specified `fd` (file descriptor).
* @param {number} fd
* @param {Buffer | TypedArray | DataView | string} buffer
* @param {number | object} [offsetOrOptions]
* @param {number} [length]
* @param {number | null} [position]
* @param {(
* err?: Error,
* bytesWritten?: number;
* buffer?: Buffer | TypedArray | DataView
* ) => any} callback
* @returns {void}
*/
function write(fd, buffer, offsetOrOptions, length, position, callback) {
function wrapper(err, written) {
// Retain a reference to buffer so that it can't be GC'ed too soon.
callback(err, written || 0, buffer);
}
fd = getValidatedFd(fd);
let offset = offsetOrOptions;
if (isArrayBufferView(buffer)) {
callback = maybeCallback(callback || position || length || offset);
if (typeof offset === 'object') {
({
offset = 0,
length = buffer.byteLength - offset,
position = null,
} = offsetOrOptions ?? kEmptyObject);
}
if (offset == null || typeof offset === 'function') {
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 req = new FSReqCallback();
req.oncomplete = wrapper;
return binding.writeBuffer(fd, buffer, offset, length, position, req);
}
validateStringAfterArrayBufferView(buffer, 'buffer');
if (typeof position !== 'function') {
if (typeof offset === 'function') {
position = offset;
offset = null;
} else {
position = length;
}
length = 'utf8';
}
const str = buffer;
validateEncoding(str, length);
callback = maybeCallback(position);
const req = new FSReqCallback();
req.oncomplete = wrapper;
return binding.writeString(fd, str, offset, length, req);
}
ObjectDefineProperty(write, kCustomPromisifyArgsSymbol,
{ __proto__: null, value: ['bytesWritten', 'buffer'], enumerable: false });
/**
* Synchronously writes `buffer` to the
* specified `fd` (file descriptor).
* @param {number} fd
* @param {Buffer | TypedArray | DataView | string} buffer
* @param {{
* offset?: number;
* length?: number;
* position?: number | null;
* }} [offsetOrOptions]
* @returns {number}
*/
function writeSync(fd, buffer, offsetOrOptions, length, position) {
fd = getValidatedFd(fd);
const ctx = {};
let result;
let offset = offsetOrOptions;
if (isArrayBufferView(buffer)) {
if (typeof offset === 'object') {
({
offset = 0,
length = buffer.byteLength - offset,
position = null,
} = offsetOrOptions ?? kEmptyObject);
}
if (position === undefined)
position = null;
if (offset == null) {
offset = 0;
} else {
validateInteger(offset, 'offset', 0);
}
if (typeof length !== 'number')
length = buffer.byteLength - offset;
validateOffsetLengthWrite(offset, length, buffer.byteLength);
result = binding.writeBuffer(fd, buffer, offset, length, position,
undefined, ctx);
} else {
validateStringAfterArrayBufferView(buffer, 'buffer');
validateEncoding(buffer, length);
if (offset === undefined)
offset = null;
result = binding.writeString(fd, buffer, offset, length,
undefined, ctx);
}
handleErrorFromBinding(ctx);
return result;
}
/**
* Writes an array of `ArrayBufferView`s to the
* specified `fd` (file descriptor).
* @param {number} fd
* @param {ArrayBufferView[]} buffers
* @param {number | null} [position]
* @param {(
* err?: Error,
* bytesWritten?: number,
* buffers?: ArrayBufferView[]
* ) => any} callback
* @returns {void}
*/
function writev(fd, buffers, position, callback) {
function wrapper(err, written) {
callback(err, written || 0, buffers);
}
fd = getValidatedFd(fd);
validateBufferArray(buffers);
callback = maybeCallback(callback || position);
if (buffers.length === 0) {
process.nextTick(callback, null, 0, buffers);
return;
}
const req = new FSReqCallback();
req.oncomplete = wrapper;
if (typeof position !== 'number')
position = null;
return binding.writeBuffers(fd, buffers, position, req);
}
ObjectDefineProperty(writev, kCustomPromisifyArgsSymbol, {
__proto__: null,
value: ['bytesWritten', 'buffer'],
enumerable: false,
});
/**
* Synchronously writes an array of `ArrayBufferView`s
* to the specified `fd` (file descriptor).
* @param {number} fd
* @param {ArrayBufferView[]} buffers
* @param {number | null} [position]
* @returns {number}
*/
function writevSync(fd, buffers, position) {
fd = getValidatedFd(fd);
validateBufferArray(buffers);
if (buffers.length === 0) {
return 0;
}
const ctx = {};
if (typeof position !== 'number')
position = null;
const result = binding.writeBuffers(fd, buffers, position, undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
/**
* Asynchronously renames file at `oldPath` to
* the pathname provided as `newPath`.
* @param {string | Buffer | URL} oldPath
* @param {string | Buffer | URL} newPath
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function rename(oldPath, newPath, callback) {
callback = makeCallback(callback);
oldPath = getValidatedPath(oldPath, 'oldPath');
newPath = getValidatedPath(newPath, 'newPath');
const req = new FSReqCallback();
req.oncomplete = callback;
binding.rename(pathModule.toNamespacedPath(oldPath),
pathModule.toNamespacedPath(newPath),
req);
}
/**
* Synchronously renames file at `oldPath` to
* the pathname provided as `newPath`.
* @param {string | Buffer | URL} oldPath
* @param {string | Buffer | URL} newPath
* @returns {void}
*/
function renameSync(oldPath, newPath) {
oldPath = getValidatedPath(oldPath, 'oldPath');
newPath = getValidatedPath(newPath, 'newPath');
const ctx = { path: oldPath, dest: newPath };
binding.rename(pathModule.toNamespacedPath(oldPath),
pathModule.toNamespacedPath(newPath), undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* Truncates the file.
* @param {string | Buffer | URL} path
* @param {number} [len]
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function truncate(path, len, callback) {
if (typeof path === 'number') {
showTruncateDeprecation();
return fs.ftruncate(path, len, callback);
}
if (typeof len === 'function') {
callback = len;
len = 0;
} else if (len === undefined) {
len = 0;
}
validateInteger(len, 'len');
len = MathMax(0, len);
callback = maybeCallback(callback);
fs.open(path, 'r+', (er, fd) => {
if (er) return callback(er);
const req = new FSReqCallback();
req.oncomplete = function oncomplete(er) {
fs.close(fd, (er2) => {
callback(aggregateTwoErrors(er2, er));
});
};
binding.ftruncate(fd, len, req);
});
}
/**
* Synchronously truncates the file.
* @param {string | Buffer | URL} path
* @param {number} [len]
* @returns {void}
*/
function truncateSync(path, len) {
if (typeof path === 'number') {
// legacy
showTruncateDeprecation();
return fs.ftruncateSync(path, len);
}
if (len === undefined) {
len = 0;
}
// Allow error to be thrown, but still close fd.
const fd = fs.openSync(path, 'r+');
let ret;
try {
ret = fs.ftruncateSync(fd, len);
} finally {
fs.closeSync(fd);
}
return ret;
}
/**
* Truncates the file descriptor.
* @param {number} fd
* @param {number} [len]
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function ftruncate(fd, len = 0, callback) {
if (typeof len === 'function') {
callback = len;
len = 0;
}
fd = getValidatedFd(fd);
validateInteger(len, 'len');
len = MathMax(0, len);
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.ftruncate(fd, len, req);
}
/**
* Synchronously truncates the file descriptor.
* @param {number} fd
* @param {number} [len]
* @returns {void}
*/
function ftruncateSync(fd, len = 0) {
fd = getValidatedFd(fd);
validateInteger(len, 'len');
len = MathMax(0, len);
const ctx = {};
binding.ftruncate(fd, len, undefined, ctx);
handleErrorFromBinding(ctx);
}
function lazyLoadCp() {
if (cpFn === undefined) {
({ cpFn } = require('internal/fs/cp/cp'));
cpFn = require('util').callbackify(cpFn);
({ cpSyncFn } = require('internal/fs/cp/cp-sync'));
}
}
function lazyLoadRimraf() {
if (rimraf === undefined)
({ rimraf, rimrafSync } = require('internal/fs/rimraf'));
}
/**
* Asynchronously removes a directory.
* @param {string | Buffer | URL} path
* @param {{
* maxRetries?: number;
* recursive?: boolean;
* retryDelay?: number;
* }} [options]
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function rmdir(path, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
callback = makeCallback(callback);
path = pathModule.toNamespacedPath(getValidatedPath(path));
if (options?.recursive) {
emitRecursiveRmdirWarning();
validateRmOptions(
path,
{ ...options, force: false },
true,
(err, options) => {
if (err === false) {
const req = new FSReqCallback();
req.oncomplete = callback;
return binding.rmdir(path, req);
}
if (err) {
return callback(err);
}
lazyLoadRimraf();
rimraf(path, options, callback);
});
} else {
validateRmdirOptions(options);
const req = new FSReqCallback();
req.oncomplete = callback;
return binding.rmdir(path, req);
}
}
/**
* Synchronously removes a directory.
* @param {string | Buffer | URL} path
* @param {{
* maxRetries?: number;
* recursive?: boolean;
* retryDelay?: number;
* }} [options]
* @returns {void}
*/
function rmdirSync(path, options) {
path = getValidatedPath(path);
if (options?.recursive) {
emitRecursiveRmdirWarning();
options = validateRmOptionsSync(path, { ...options, force: false }, true);
if (options !== false) {
lazyLoadRimraf();
return rimrafSync(pathModule.toNamespacedPath(path), options);
}
} else {
validateRmdirOptions(options);
}
const ctx = { path };
binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx);
return handleErrorFromBinding(ctx);
}
/**
* Asynchronously removes files and
* directories (modeled on the standard POSIX `rm` utility).
* @param {string | Buffer | URL} path
* @param {{
* force?: boolean;
* maxRetries?: number;
* recursive?: boolean;
* retryDelay?: number;
* }} [options]
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function rm(path, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
path = getValidatedPath(path);
validateRmOptions(path, options, false, (err, options) => {
if (err) {
return callback(err);
}
lazyLoadRimraf();
return rimraf(pathModule.toNamespacedPath(path), options, callback);
});
}
/**
* Synchronously removes files and
* directories (modeled on the standard POSIX `rm` utility).
* @param {string | Buffer | URL} path
* @param {{
* force?: boolean;
* maxRetries?: number;
* recursive?: boolean;
* retryDelay?: number;
* }} [options]
* @returns {void}
*/
function rmSync(path, options) {
path = getValidatedPath(path);
options = validateRmOptionsSync(path, options, false);
lazyLoadRimraf();
return rimrafSync(pathModule.toNamespacedPath(path), options);
}
/**
* Forces all currently queued I/O operations associated
* with the file to the operating system's synchronized
* I/O completion state.
* @param {number} fd
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function fdatasync(fd, callback) {
fd = getValidatedFd(fd);
const req = new FSReqCallback();
req.oncomplete = makeCallback(callback);
binding.fdatasync(fd, req);
}
/**
* Synchronously forces all currently queued I/O operations
* associated with the file to the operating
* system's synchronized I/O completion state.
* @param {number} fd
* @returns {void}
*/
function fdatasyncSync(fd) {
fd = getValidatedFd(fd);
const ctx = {};
binding.fdatasync(fd, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* Requests for all data for the open file descriptor
* to be flushed to the storage device.
* @param {number} fd
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function fsync(fd, callback) {
fd = getValidatedFd(fd);
const req = new FSReqCallback();
req.oncomplete = makeCallback(callback);
binding.fsync(fd, req);
}
/**
* Synchronously requests for all data for the open
* file descriptor to be flushed to the storage device.
* @param {number} fd
* @returns {void}
*/
function fsyncSync(fd) {
fd = getValidatedFd(fd);
const ctx = {};
binding.fsync(fd, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* Asynchronously creates a directory.
* @param {string | Buffer | URL} path
* @param {{
* recursive?: boolean;
* mode?: string | number;
* } | number} [options]
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function mkdir(path, options, callback) {
let mode = 0o777;
let recursive = false;
if (typeof options === 'function') {
callback = options;
} else if (typeof options === 'number' || typeof options === 'string') {
mode = options;
} else if (options) {
if (options.recursive !== undefined)
recursive = options.recursive;
if (options.mode !== undefined)
mode = options.mode;
}
callback = makeCallback(callback);
path = getValidatedPath(path);
validateBoolean(recursive, 'options.recursive');
const req = new FSReqCallback();
req.oncomplete = callback;
binding.mkdir(pathModule.toNamespacedPath(path),
parseFileMode(mode, 'mode'), recursive, req);
}
/**
* Synchronously creates a directory.
* @param {string | Buffer | URL} path
* @param {{
* recursive?: boolean;
* mode?: string | number;
* } | number} [options]
* @returns {string | void}
*/
function mkdirSync(path, options) {
let mode = 0o777;
let recursive = false;
if (typeof options === 'number' || typeof options === 'string') {
mode = options;
} else if (options) {
if (options.recursive !== undefined)
recursive = options.recursive;
if (options.mode !== undefined)
mode = options.mode;
}
path = getValidatedPath(path);
validateBoolean(recursive, 'options.recursive');
const ctx = { path };
const result = binding.mkdir(pathModule.toNamespacedPath(path),
parseFileMode(mode, 'mode'), recursive,
undefined, ctx);
handleErrorFromBinding(ctx);
if (recursive) {
return result;
}
}
/**
* An iterative algorithm for reading the entire contents of the `basePath` directory.
* This function does not validate `basePath` as a directory. It is passed directly to
* `binding.readdir`.
* @param {string} basePath
* @param {{ encoding: string, withFileTypes: boolean }} options
* @returns {string[] | Dirent[]}
*/
function readdirSyncRecursive(basePath, options) {
const withFileTypes = Boolean(options.withFileTypes);
const encoding = options.encoding;
const readdirResults = [];
const pathsQueue = [basePath];
const ctx = { path: basePath };
function read(path) {
ctx.path = path;
const readdirResult = binding.readdir(
pathModule.toNamespacedPath(path),
encoding,
withFileTypes,
undefined,
ctx,
);
handleErrorFromBinding(ctx);
if (withFileTypes) {
// Calling `readdir` with `withFileTypes=true`, the result is an array of arrays.
// The first array is the names, and the second array is the types.
// They are guaranteed to be the same length; hence, setting `length` to the length
// of the first array within the result.
const length = readdirResult[0].length;
for (let i = 0; i < length; i++) {
const dirent = getDirent(path, readdirResult[0][i], readdirResult[1][i]);
ArrayPrototypePush(readdirResults, dirent);
if (dirent.isDirectory()) {
ArrayPrototypePush(pathsQueue, pathModule.join(dirent.path, dirent.name));
}
}
} else {
for (let i = 0; i < readdirResult.length; i++) {
const resultPath = pathModule.join(path, readdirResult[i]);
const relativeResultPath = pathModule.relative(basePath, resultPath);
const stat = binding.internalModuleStat(resultPath);
ArrayPrototypePush(readdirResults, relativeResultPath);
// 1 indicates directory
if (stat === 1) {
ArrayPrototypePush(pathsQueue, resultPath);
}
}
}
}
for (let i = 0; i < pathsQueue.length; i++) {
read(pathsQueue[i]);
}
return readdirResults;
}
/**
* Reads the contents of a directory.
* @param {string | Buffer | URL} path
* @param {string | {
* encoding?: string;
* withFileTypes?: boolean;
* }} [options]
* @param {(
* err?: Error,
* files?: string[] | Buffer[] | Direct[];
* ) => any} callback
* @returns {void}
*/
function readdir(path, options, callback) {
callback = makeCallback(typeof options === 'function' ? options : callback);
options = getOptions(options);
path = getValidatedPath(path);
if (options.recursive != null) {
validateBoolean(options.recursive, 'options.recursive');
}
if (options.recursive) {
callback(null, readdirSyncRecursive(path, options));
return;
}
const req = new FSReqCallback();
if (!options.withFileTypes) {
req.oncomplete = callback;
} else {
req.oncomplete = (err, result) => {
if (err) {
callback(err);
return;
}
getDirents(path, result, callback);
};
}
binding.readdir(pathModule.toNamespacedPath(path), options.encoding,
!!options.withFileTypes, req);
}
/**
* Synchronously reads the contents of a directory.
* @param {string | Buffer | URL} path
* @param {string | {
* encoding?: string;
* withFileTypes?: boolean;
* recursive?: boolean;
* }} [options]
* @returns {string | Buffer[] | Dirent[]}
*/
function readdirSync(path, options) {
options = getOptions(options);
path = getValidatedPath(path);
if (options.recursive != null) {
validateBoolean(options.recursive, 'options.recursive');
}
if (options.recursive) {
return readdirSyncRecursive(path, options);
}
const ctx = { path };
const result = binding.readdir(pathModule.toNamespacedPath(path),
options.encoding, !!options.withFileTypes,
undefined, ctx);
handleErrorFromBinding(ctx);
return options.withFileTypes ? getDirents(path, result) : result;
}
/**
* Invokes the callback with the `fs.Stats`
* for the file descriptor.
* @param {number} fd
* @param {{ bigint?: boolean; }} [options]
* @param {(
* err?: Error,
* stats?: Stats
* ) => any} callback
* @returns {void}
*/
function fstat(fd, options = { bigint: false }, callback) {
if (typeof options === 'function') {
callback = options;
options = kEmptyObject;
}
fd = getValidatedFd(fd);
callback = makeStatsCallback(callback);
const req = new FSReqCallback(options.bigint);
req.oncomplete = 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 {(
* err?: Error,
* stats?: Stats
* ) => any} callback
* @returns {void}
*/
function lstat(path, options = { bigint: false }, callback) {
if (typeof options === 'function') {
callback = options;
options = kEmptyObject;
}
callback = makeStatsCallback(callback);
path = getValidatedPath(path);
const req = new FSReqCallback(options.bigint);
req.oncomplete = callback;
binding.lstat(pathModule.toNamespacedPath(path), options.bigint, req);
}
/**
* Asynchronously gets the stats of a file.
* @param {string | Buffer | URL} path
* @param {{ bigint?: boolean; }} [options]
* @param {(
* err?: Error,
* stats?: Stats
* ) => any} callback
* @returns {void}
*/
function stat(path, options = { bigint: false }, callback) {
if (typeof options === 'function') {
callback = options;
options = kEmptyObject;
}
callback = makeStatsCallback(callback);
path = getValidatedPath(path);
const req = new FSReqCallback(options.bigint);
req.oncomplete = callback;
binding.stat(pathModule.toNamespacedPath(path), options.bigint, req);
}
function statfs(path, options = { bigint: false }, callback) {
if (typeof options === 'function') {
callback = options;
options = kEmptyObject;
}
callback = maybeCallback(callback);
path = getValidatedPath(path);
const req = new FSReqCallback(options.bigint);
req.oncomplete = (err, stats) => {
if (err) {
return callback(err);
}
callback(err, getStatFsFromBinding(stats));
};
binding.statfs(pathModule.toNamespacedPath(path), options.bigint, req);
}
function hasNoEntryError(ctx) {
if (ctx.errno) {
const uvErr = uvErrmapGet(ctx.errno);
return uvErr?.[0] === 'ENOENT';
}
if (ctx.error) {
return ctx.error.code === 'ENOENT';
}
return false;
}
/**
* Synchronously retrieves the `fs.Stats` for
* the file descriptor.
* @param {number} fd
* @param {{
* bigint?: boolean;
* }} [options]
* @returns {Stats}
*/
function fstatSync(fd, options = { bigint: false }) {
fd = getValidatedFd(fd);
const ctx = { fd };
const stats = binding.fstat(fd, options.bigint, undefined, ctx);
handleErrorFromBinding(ctx);
return getStatsFromBinding(stats);
}
/**
* Synchronously retrieves the `fs.Stats` for
* the symbolic link referred to by the `path`.
* @param {string | Buffer | URL} path
* @param {{
* bigint?: boolean;
* throwIfNoEntry?: boolean;
* }} [options]
* @returns {Stats}
*/
function lstatSync(path, options = { bigint: false, throwIfNoEntry: true }) {
path = getValidatedPath(path);
const ctx = { path };
const stats = binding.lstat(pathModule.toNamespacedPath(path),
options.bigint, undefined, ctx);
if (options.throwIfNoEntry === false && hasNoEntryError(ctx)) {
return undefined;
}
handleErrorFromBinding(ctx);
return getStatsFromBinding(stats);
}
/**
* Synchronously retrieves the `fs.Stats`
* for the `path`.
* @param {string | Buffer | URL} path
* @param {{
* bigint?: boolean;
* throwIfNoEntry?: boolean;
* }} [options]
* @returns {Stats}
*/
function statSync(path, options = { bigint: false, throwIfNoEntry: true }) {
path = getValidatedPath(path);
const stats = binding.stat(
pathModule.toNamespacedPath(path),
options.bigint,
undefined,
options.throwIfNoEntry,
);
if (stats === undefined) {
return undefined;
}
return getStatsFromBinding(stats);
}
function statfsSync(path, options = { bigint: false }) {
path = getValidatedPath(path);
const stats = binding.statfs(pathModule.toNamespacedPath(path), options.bigint);
return getStatFsFromBinding(stats);
}
/**
* Reads the contents of a symbolic link
* referred to by `path`.
* @param {string | Buffer | URL} path
* @param {{ encoding?: string; } | string} [options]
* @param {(
* err?: Error,
* linkString?: string | Buffer
* ) => any} callback
* @returns {void}
*/
function readlink(path, options, callback) {
callback = makeCallback(typeof options === 'function' ? options : callback);
options = getOptions(options);
path = getValidatedPath(path, 'oldPath');
const req = new FSReqCallback();
req.oncomplete = callback;
binding.readlink(pathModule.toNamespacedPath(path), options.encoding, req);
}
/**
* Synchronously reads the contents of a symbolic link
* referred to by `path`.
* @param {string | Buffer | URL} path
* @param {{ encoding?: string; } | string} [options]
* @returns {string | Buffer}
*/
function readlinkSync(path, options) {
options = getOptions(options);
path = getValidatedPath(path, 'oldPath');
const ctx = { path };
const result = binding.readlink(pathModule.toNamespacedPath(path),
options.encoding, undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
/**
* Creates the link called `path` pointing to `target`.
* @param {string | Buffer | URL} target
* @param {string | Buffer | URL} path
* @param {string | null} [type_]
* @param {(err?: Error) => any} callback_
* @returns {void}
*/
function symlink(target, path, type_, callback_) {
const type = (typeof type_ === 'string' ? type_ : null);
const callback = makeCallback(arguments[arguments.length - 1]);
target = getValidatedPath(target, 'target');
path = getValidatedPath(path);
if (isWindows && type === null) {
let absoluteTarget;
try {
// Symlinks targets can be relative to the newly created path.
// Calculate absolute file name of the symlink target, and check
// if it is a directory. Ignore resolve error to keep symlink
// errors consistent between platforms if invalid path is
// provided.
absoluteTarget = pathModule.resolve(path, '..', target);
} catch {
// Continue regardless of error.
}
if (absoluteTarget !== undefined) {
stat(absoluteTarget, (err, stat) => {
const resolvedType = !err && stat.isDirectory() ? 'dir' : 'file';
const resolvedFlags = stringToSymlinkType(resolvedType);
const destination = preprocessSymlinkDestination(target,
resolvedType,
path);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.symlink(destination,
pathModule.toNamespacedPath(path), resolvedFlags, req);
});
return;
}
}
const destination = preprocessSymlinkDestination(target, type, path);
const flags = stringToSymlinkType(type);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.symlink(destination, pathModule.toNamespacedPath(path), flags, req);
}
/**
* Synchronously creates the link called `path`
* pointing to `target`.
* @param {string | Buffer | URL} target
* @param {string | Buffer | URL} path
* @param {string | null} [type]
* @returns {void}
*/
function symlinkSync(target, path, type) {
type = (typeof type === 'string' ? type : null);
if (isWindows && type === null) {
const absoluteTarget = pathModule.resolve(`${path}`, '..', `${target}`);
if (statSync(absoluteTarget, { throwIfNoEntry: false })?.isDirectory()) {
type = 'dir';
}
}
target = getValidatedPath(target, 'target');
path = getValidatedPath(path);
const flags = stringToSymlinkType(type);
const ctx = { path: target, dest: path };
binding.symlink(preprocessSymlinkDestination(target, type, path),
pathModule.toNamespacedPath(path), flags, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* Creates a new link from the `existingPath`
* to the `newPath`.
* @param {string | Buffer | URL} existingPath
* @param {string | Buffer | URL} newPath
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function link(existingPath, newPath, callback) {
callback = makeCallback(callback);
existingPath = getValidatedPath(existingPath, 'existingPath');
newPath = getValidatedPath(newPath, 'newPath');
const req = new FSReqCallback();
req.oncomplete = callback;
binding.link(pathModule.toNamespacedPath(existingPath),
pathModule.toNamespacedPath(newPath),
req);
}
/**
* Synchronously creates a new link from the `existingPath`
* to the `newPath`.
* @param {string | Buffer | URL} existingPath
* @param {string | Buffer | URL} newPath
* @returns {void}
*/
function linkSync(existingPath, newPath) {
existingPath = getValidatedPath(existingPath, 'existingPath');
newPath = getValidatedPath(newPath, 'newPath');
const ctx = { path: existingPath, dest: newPath };
const result = binding.link(pathModule.toNamespacedPath(existingPath),
pathModule.toNamespacedPath(newPath),
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
/**
* Asynchronously removes a file or symbolic link.
* @param {string | Buffer | URL} path
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function unlink(path, callback) {
callback = makeCallback(callback);
path = getValidatedPath(path);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.unlink(pathModule.toNamespacedPath(path), req);
}
/**
* Synchronously removes a file or symbolic link.
* @param {string | Buffer | URL} path
* @returns {void}
*/
function unlinkSync(path) {
path = pathModule.toNamespacedPath(getValidatedPath(path));
return binding.unlink(path);
}
/**
* Sets the permissions on the file.
* @param {number} fd
* @param {string | number} mode
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function fchmod(fd, mode, callback) {
fd = getValidatedFd(fd);
mode = parseFileMode(mode, 'mode');
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.fchmod(fd, mode, req);
}
/**
* Synchronously sets the permissions on the file.
* @param {number} fd
* @param {string | number} mode
* @returns {void}
*/
function fchmodSync(fd, mode) {
fd = getValidatedFd(fd);
mode = parseFileMode(mode, 'mode');
const ctx = {};
binding.fchmod(fd, mode, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* Changes the permissions on a symbolic link.
* @param {string | Buffer | URL} path
* @param {number} mode
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function lchmod(path, mode, callback) {
callback = maybeCallback(callback);
mode = parseFileMode(mode, 'mode');
fs.open(path, O_WRONLY | O_SYMLINK, (err, fd) => {
if (err) {
callback(err);
return;
}
// Prefer to return the chmod error, if one occurs,
// but still try to close, and report closing errors if they occur.
fs.fchmod(fd, mode, (err) => {
fs.close(fd, (err2) => {
callback(aggregateTwoErrors(err2, err));
});
});
});
}
/**
* Synchronously changes the permissions on a symbolic link.
* @param {string | Buffer | URL} path
* @param {number} mode
* @returns {void}
*/
function lchmodSync(path, mode) {
const fd = fs.openSync(path, O_WRONLY | O_SYMLINK);
// Prefer to return the chmod error, if one occurs,
// but still try to close, and report closing errors if they occur.
let ret;
try {
ret = fs.fchmodSync(fd, mode);
} finally {
fs.closeSync(fd);
}
return ret;
}
/**
* Asynchronously changes the permissions of a file.
* @param {string | Buffer | URL} path
* @param {string | number} mode
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function chmod(path, mode, callback) {
path = getValidatedPath(path);
mode = parseFileMode(mode, 'mode');
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.chmod(pathModule.toNamespacedPath(path), mode, req);
}
/**
* Synchronously changes the permissions of a file.
* @param {string | Buffer | URL} path
* @param {string | number} mode
* @returns {void}
*/
function chmodSync(path, mode) {
path = getValidatedPath(path);
mode = parseFileMode(mode, 'mode');
const ctx = { path };
binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* Sets the owner of the symbolic link.
* @param {string | Buffer | URL} path
* @param {number} uid
* @param {number} gid
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function lchown(path, uid, gid, callback) {
callback = makeCallback(callback);
path = getValidatedPath(path);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.lchown(pathModule.toNamespacedPath(path), uid, gid, req);
}
/**
* Synchronously sets the owner of the symbolic link.
* @param {string | Buffer | URL} path
* @param {number} uid
* @param {number} gid
* @returns {void}
*/
function lchownSync(path, uid, gid) {
path = getValidatedPath(path);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
const ctx = { path };
binding.lchown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* Sets the owner of the file.
* @param {number} fd
* @param {number} uid
* @param {number} gid
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function fchown(fd, uid, gid, callback) {
fd = getValidatedFd(fd);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.fchown(fd, uid, gid, req);
}
/**
* Synchronously sets the owner of the file.
* @param {number} fd
* @param {number} uid
* @param {number} gid
* @returns {void}
*/
function fchownSync(fd, uid, gid) {
fd = getValidatedFd(fd);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
const ctx = {};
binding.fchown(fd, uid, gid, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* Asynchronously changes the owner and group
* of a file.
* @param {string | Buffer | URL} path
* @param {number} uid
* @param {number} gid
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function chown(path, uid, gid, callback) {
callback = makeCallback(callback);
path = getValidatedPath(path);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.chown(pathModule.toNamespacedPath(path), uid, gid, req);
}
/**
* Synchronously changes the owner and group
* of a file.
* @param {string | Buffer | URL} path
* @param {number} uid
* @param {number} gid
* @returns {void}
*/
function chownSync(path, uid, gid) {
path = getValidatedPath(path);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
const ctx = { path };
binding.chown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* Changes the file system timestamps of the object
* referenced by `path`.
* @param {string | Buffer | URL} path
* @param {number | string | Date} atime
* @param {number | string | Date} mtime
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function utimes(path, atime, mtime, callback) {
callback = makeCallback(callback);
path = getValidatedPath(path);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.utimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime),
toUnixTimestamp(mtime),
req);
}
/**
* Synchronously changes the file system timestamps
* of the object referenced by `path`.
* @param {string | Buffer | URL} path
* @param {number | string | Date} atime
* @param {number | string | Date} mtime
* @returns {void}
*/
function utimesSync(path, atime, mtime) {
path = getValidatedPath(path);
const ctx = { path };
binding.utimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime), toUnixTimestamp(mtime),
undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* Changes the file system timestamps of the object
* referenced by the supplied `fd` (file descriptor).
* @param {number} fd
* @param {number | string | Date} atime
* @param {number | string | Date} mtime
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function futimes(fd, atime, mtime, callback) {
fd = getValidatedFd(fd);
atime = toUnixTimestamp(atime, 'atime');
mtime = toUnixTimestamp(mtime, 'mtime');
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.futimes(fd, atime, mtime, req);
}
/**
* Synchronously changes the file system timestamps
* of the object referenced by the
* supplied `fd` (file descriptor).
* @param {number} fd
* @param {number | string | Date} atime
* @param {number | string | Date} mtime
* @returns {void}
*/
function futimesSync(fd, atime, mtime) {
fd = getValidatedFd(fd);
atime = toUnixTimestamp(atime, 'atime');
mtime = toUnixTimestamp(mtime, 'mtime');
const ctx = {};
binding.futimes(fd, atime, mtime, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* Changes the access and modification times of
* a file in the same way as `fs.utimes()`.
* @param {string | Buffer | URL} path
* @param {number | string | Date} atime
* @param {number | string | Date} mtime
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function lutimes(path, atime, mtime, callback) {
callback = makeCallback(callback);
path = getValidatedPath(path);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.lutimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime),
toUnixTimestamp(mtime),
req);
}
/**
* Synchronously changes the access and modification
* times of a file in the same way as `fs.utimesSync()`.
* @param {string | Buffer | URL} path
* @param {number | string | Date} atime
* @param {number | string | Date} mtime
* @returns {void}
*/
function lutimesSync(path, atime, mtime) {
path = getValidatedPath(path);
const ctx = { path };
binding.lutimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime),
toUnixTimestamp(mtime),
undefined, ctx);
handleErrorFromBinding(ctx);
}
function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) {
if (signal?.aborted) {
const abortError = new AbortError(undefined, { cause: signal?.reason });
if (isUserFd) {
callback(abortError);
} else {
fs.close(fd, (err) => {
callback(aggregateTwoErrors(err, abortError));
});
}
return;
}
// write(fd, buffer, offset, length, position, callback)
fs.write(fd, buffer, offset, length, null, (writeErr, written) => {
if (writeErr) {
if (isUserFd) {
callback(writeErr);
} else {
fs.close(fd, (err) => {
callback(aggregateTwoErrors(err, writeErr));
});
}
} else if (written === length) {
if (isUserFd) {
callback(null);
} else {
fs.close(fd, callback);
}
} else {
offset += written;
length -= written;
writeAll(fd, isUserFd, buffer, offset, length, signal, callback);
}
});
}
/**
* Asynchronously writes data to the file.
* @param {string | Buffer | URL | number} path
* @param {string | Buffer | TypedArray | DataView} data
* @param {{
* encoding?: string | null;
* mode?: number;
* flag?: string;
* signal?: AbortSignal;
* } | string} [options]
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function writeFile(path, data, options, callback) {
callback = maybeCallback(callback || options);
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
const flag = options.flag || 'w';
if (!isArrayBufferView(data)) {
validateStringAfterArrayBufferView(data, 'data');
data = Buffer.from(data, options.encoding || 'utf8');
}
if (isFd(path)) {
const isUserFd = true;
const signal = options.signal;
writeAll(path, isUserFd, data, 0, data.byteLength, signal, callback);
return;
}
if (checkAborted(options.signal, callback))
return;
fs.open(path, flag, options.mode, (openErr, fd) => {
if (openErr) {
callback(openErr);
} else {
const isUserFd = false;
const signal = options.signal;
writeAll(fd, isUserFd, data, 0, data.byteLength, signal, callback);
}
});
}
/**
* Synchronously writes data to the file.
* @param {string | Buffer | URL | number} path
* @param {string | Buffer | TypedArray | DataView} data
* @param {{
* encoding?: string | null;
* mode?: number;
* flag?: string;
* } | string} [options]
* @returns {void}
*/
function writeFileSync(path, data, options) {
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
if (!isArrayBufferView(data)) {
validateStringAfterArrayBufferView(data, 'data');
data = Buffer.from(data, options.encoding || 'utf8');
}
const flag = options.flag || 'w';
const isUserFd = isFd(path); // File descriptor ownership
const fd = isUserFd ? path : fs.openSync(path, flag, options.mode);
let offset = 0;
let length = data.byteLength;
try {
while (length > 0) {
const written = fs.writeSync(fd, data, offset, length);
offset += written;
length -= written;
}
} finally {
if (!isUserFd) fs.closeSync(fd);
}
}
/**
* Asynchronously appends data to a file.
* @param {string | Buffer | URL | number} path
* @param {string | Buffer} data
* @param {{
* encoding?: string | null;
* mode?: number;
* flag?: string;
* } | string} [options]
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function appendFile(path, data, options, callback) {
callback = maybeCallback(callback || options);
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
// Don't make changes directly on options object
options = copyObject(options);
// Force append behavior when using a supplied file descriptor
if (!options.flag || isFd(path))
options.flag = 'a';
fs.writeFile(path, data, options, callback);
}
/**
* Synchronously appends data to a file.
* @param {string | Buffer | URL | number} path
* @param {string | Buffer} data
* @param {{
* encoding?: string | null;
* mode?: number;
* flag?: string;
* } | string} [options]
* @returns {void}
*/
function appendFileSync(path, data, options) {
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
// Don't make changes directly on options object
options = copyObject(options);
// Force append behavior when using a supplied file descriptor
if (!options.flag || isFd(path))
options.flag = 'a';
fs.writeFileSync(path, data, options);
}
/**
* Watches for the changes on `filename`.
* @param {string | Buffer | URL} filename
* @param {string | {
* persistent?: boolean;
* recursive?: boolean;
* encoding?: string;
* signal?: AbortSignal;
* }} [options]
* @param {(
* eventType?: string,
* filename?: string | Buffer
* ) => any} [listener]
* @returns {watchers.FSWatcher}
*/
function watch(filename, options, listener) {
if (typeof options === 'function') {
listener = options;
}
options = getOptions(options);
// Don't make changes directly on options object
options = copyObject(options);
if (options.persistent === undefined) options.persistent = true;
if (options.recursive === undefined) options.recursive = false;
let watcher;
const watchers = require('internal/fs/watchers');
const path = possiblyTransformPath(filename);
// 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 && !isOSX && !isWindows) {
const nonNativeWatcher = require('internal/fs/recursive_watch');
watcher = new nonNativeWatcher.FSWatcher(options);
watcher[watchers.kFSWatchStart](path);
} else {
watcher = new watchers.FSWatcher();
watcher[watchers.kFSWatchStart](path,
options.persistent,
options.recursive,
options.encoding);
}
if (listener) {
watcher.addListener('change', listener);
}
if (options.signal) {
if (options.signal.aborted) {
process.nextTick(() => watcher.close());
} else {
const listener = () => watcher.close();
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
options.signal.addEventListener('abort', listener, { __proto__: null, [kResistStopPropagation]: true });
watcher.once('close', () => {
options.signal.removeEventListener('abort', listener);
});
}
}
return watcher;
}
const statWatchers = new SafeMap();
/**
* Watches for changes on `filename`.
* @param {string | Buffer | URL} filename
* @param {{
* bigint?: boolean;
* persistent?: boolean;
* interval?: number;
* }} [options]
* @param {(
* current?: Stats,
* previous?: Stats
* ) => any} listener
* @returns {watchers.StatWatcher}
*/
function watchFile(filename, options, listener) {
filename = getValidatedPath(filename);
filename = pathModule.resolve(filename);
let stat;
if (options === null || typeof options !== 'object') {
listener = options;
options = null;
}
options = {
// Poll interval in milliseconds. 5007 is what libev used to use. It's
// a little on the slow side but let's stick with it for now to keep
// behavioral changes to a minimum.
interval: 5007,
persistent: true,
...options,
};
validateFunction(listener, 'listener');
stat = statWatchers.get(filename);
const watchers = require('internal/fs/watchers');
if (stat === undefined) {
stat = new watchers.StatWatcher(options.bigint);
stat[watchers.kFSStatWatcherStart](filename,
options.persistent, options.interval);
statWatchers.set(filename, stat);
} else {
stat[watchers.kFSStatWatcherAddOrCleanRef]('add');
}
stat.addListener('change', listener);
return stat;
}
/**
* Stops watching for changes on `filename`.
* @param {string | Buffer | URL} filename
* @param {() => any} [listener]
* @returns {void}
*/
function unwatchFile(filename, listener) {
filename = getValidatedPath(filename);
filename = pathModule.resolve(filename);
const stat = statWatchers.get(filename);
if (stat === undefined) return;
const watchers = require('internal/fs/watchers');
if (typeof listener === 'function') {
const beforeListenerCount = stat.listenerCount('change');
stat.removeListener('change', listener);
if (stat.listenerCount('change') < beforeListenerCount)
stat[watchers.kFSStatWatcherAddOrCleanRef]('clean');
} else {
stat.removeAllListeners('change');
stat[watchers.kFSStatWatcherAddOrCleanRef]('cleanAll');
}
if (stat.listenerCount('change') === 0) {
stat.stop();
statWatchers.delete(filename);
}
}
let splitRoot;
if (isWindows) {
// Regex to find the device root on Windows (e.g. 'c:\\'), including trailing
// slash.
const splitRootRe = /^(?:[a-zA-Z]:|[\\/]{2}[^\\/]+[\\/][^\\/]+)?[\\/]*/;
splitRoot = function splitRoot(str) {
return SideEffectFreeRegExpPrototypeExec(splitRootRe, str)[0];
};
} else {
splitRoot = function splitRoot(str) {
for (let i = 0; i < str.length; ++i) {
if (StringPrototypeCharCodeAt(str, i) !== CHAR_FORWARD_SLASH)
return StringPrototypeSlice(str, 0, i);
}
return str;
};
}
function encodeRealpathResult(result, options) {
if (!options || !options.encoding || options.encoding === 'utf8')
return result;
const asBuffer = Buffer.from(result);
if (options.encoding === 'buffer') {
return asBuffer;
}
return asBuffer.toString(options.encoding);
}
// Finds the next portion of a (partial) path, up to the next path delimiter
let nextPart;
if (isWindows) {
nextPart = function nextPart(p, i) {
for (; i < p.length; ++i) {
const ch = StringPrototypeCharCodeAt(p, i);
// Check for a separator character
if (ch === CHAR_BACKWARD_SLASH || ch === CHAR_FORWARD_SLASH)
return i;
}
return -1;
};
} else {
nextPart = function nextPart(p, i) {
return StringPrototypeIndexOf(p, '/', i);
};
}
/**
* Returns the resolved pathname.
* @param {string | Buffer | URL} p
* @param {string | { encoding?: string | null; }} [options]
* @returns {string | Buffer}
*/
function realpathSync(p, options) {
options = getOptions(options);
p = toPathIfFileURL(p);
if (typeof p !== 'string') {
p += '';
}
validatePath(p);
p = pathModule.resolve(p);
const cache = options[realpathCacheKey];
const maybeCachedResult = cache?.get(p);
if (maybeCachedResult) {
return maybeCachedResult;
}
const seenLinks = new SafeMap();
const knownHard = new SafeSet();
const original = p;
// Current character position in p
let pos;
// The partial path so far, including a trailing slash if any
let current;
// The partial path without a trailing slash (except when pointing at a root)
let base;
// The partial path scanned in the previous round, with slash
let previous;
// Skip over roots
current = base = splitRoot(p);
pos = current.length;
// On windows, check that the root exists. On unix there is no need.
if (isWindows) {
const ctx = { path: base };
binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx);
handleErrorFromBinding(ctx);
knownHard.add(base);
}
// Walk down the path, swapping out linked path parts for their real
// values
// NB: p.length changes.
while (pos < p.length) {
// find the next part
const result = nextPart(p, pos);
previous = current;
if (result === -1) {
const last = StringPrototypeSlice(p, pos);
current += last;
base = previous + last;
pos = p.length;
} else {
current += StringPrototypeSlice(p, pos, result + 1);
base = previous + StringPrototypeSlice(p, pos, result);
pos = result + 1;
}
// Continue if not a symlink, break if a pipe/socket
if (knownHard.has(base) || cache?.get(base) === base) {
if (isFileType(statValues, S_IFIFO) ||
isFileType(statValues, S_IFSOCK)) {
break;
}
continue;
}
let resolvedLink;
const maybeCachedResolved = cache?.get(base);
if (maybeCachedResolved) {
resolvedLink = maybeCachedResolved;
} else {
// Use stats array directly to avoid creating an fs.Stats instance just
// for our internal use.
const baseLong = pathModule.toNamespacedPath(base);
const ctx = { path: base };
const stats = binding.lstat(baseLong, true, undefined, ctx);
handleErrorFromBinding(ctx);
if (!isFileType(stats, S_IFLNK)) {
knownHard.add(base);
cache?.set(base, base);
continue;
}
// Read the link if it wasn't read before
// dev/ino always return 0 on windows, so skip the check.
let linkTarget = null;
let id;
if (!isWindows) {
const dev = BigIntPrototypeToString(stats[0], 32);
const ino = BigIntPrototypeToString(stats[7], 32);
id = `${dev}:${ino}`;
if (seenLinks.has(id)) {
linkTarget = seenLinks.get(id);
}
}
if (linkTarget === null) {
const ctx = { path: base };
binding.stat(baseLong, false, undefined, true);
linkTarget = binding.readlink(baseLong, undefined, undefined, ctx);
handleErrorFromBinding(ctx);
}
resolvedLink = pathModule.resolve(previous, linkTarget);
cache?.set(base, resolvedLink);
if (!isWindows) seenLinks.set(id, linkTarget);
}
// Resolve the link, then start over
p = pathModule.resolve(resolvedLink, StringPrototypeSlice(p, pos));
// Skip over roots
current = base = splitRoot(p);
pos = current.length;
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard.has(base)) {
const ctx = { path: base };
binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx);
handleErrorFromBinding(ctx);
knownHard.add(base);
}
}
cache?.set(original, p);
return encodeRealpathResult(p, options);
}
/**
* Returns the resolved pathname.
* @param {string | Buffer | URL} path
* @param {string | { encoding?: string; }} [options]
* @returns {string | Buffer}
*/
realpathSync.native = (path, options) => {
options = getOptions(options);
path = getValidatedPath(path);
const ctx = { path };
const result = binding.realpath(pathModule.toNamespacedPath(path), options.encoding, undefined, ctx);
handleErrorFromBinding(ctx);
return result;
};
/**
* Asynchronously computes the canonical pathname by
* resolving `.`, `..` and symbolic links.
* @param {string | Buffer | URL} p
* @param {string | { encoding?: string; }} [options]
* @param {(
* err?: Error,
* resolvedPath?: string | Buffer
* ) => any} callback
* @returns {void}
*/
function realpath(p, options, callback) {
callback = typeof options === 'function' ? options : maybeCallback(callback);
options = getOptions(options);
p = toPathIfFileURL(p);
if (typeof p !== 'string') {
p += '';
}
validatePath(p);
p = pathModule.resolve(p);
const seenLinks = new SafeMap();
const knownHard = new SafeSet();
// Current character position in p
let pos;
// The partial path so far, including a trailing slash if any
let current;
// The partial path without a trailing slash (except when pointing at a root)
let base;
// The partial path scanned in the previous round, with slash
let previous;
current = base = splitRoot(p);
pos = current.length;
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard.has(base)) {
fs.lstat(base, (err, stats) => {
if (err) return callback(err);
knownHard.add(base);
LOOP();
});
} else {
process.nextTick(LOOP);
}
// Walk down the path, swapping out linked path parts for their real
// values
function LOOP() {
// Stop if scanned past end of path
if (pos >= p.length) {
return callback(null, encodeRealpathResult(p, options));
}
// find the next part
const result = nextPart(p, pos);
previous = current;
if (result === -1) {
const last = StringPrototypeSlice(p, pos);
current += last;
base = previous + last;
pos = p.length;
} else {
current += StringPrototypeSlice(p, pos, result + 1);
base = previous + StringPrototypeSlice(p, pos, result);
pos = result + 1;
}
// Continue if not a symlink, break if a pipe/socket
if (knownHard.has(base)) {
if (isFileType(statValues, S_IFIFO) ||
isFileType(statValues, S_IFSOCK)) {
return callback(null, encodeRealpathResult(p, options));
}
return process.nextTick(LOOP);
}
return fs.lstat(base, { bigint: true }, gotStat);
}
function gotStat(err, stats) {
if (err) return callback(err);
// If not a symlink, skip to the next path part
if (!stats.isSymbolicLink()) {
knownHard.add(base);
return process.nextTick(LOOP);
}
// Stat & read the link if not read before.
// Call `gotTarget()` as soon as the link target is known.
// `dev`/`ino` always return 0 on windows, so skip the check.
let id;
if (!isWindows) {
const dev = BigIntPrototypeToString(stats.dev, 32);
const ino = BigIntPrototypeToString(stats.ino, 32);
id = `${dev}:${ino}`;
if (seenLinks.has(id)) {
return gotTarget(null, seenLinks.get(id));
}
}
fs.stat(base, (err) => {
if (err) return callback(err);
fs.readlink(base, (err, target) => {
if (!isWindows) seenLinks.set(id, target);
gotTarget(err, target);
});
});
}
function gotTarget(err, target) {
if (err) return callback(err);
gotResolvedLink(pathModule.resolve(previous, target));
}
function gotResolvedLink(resolvedLink) {
// Resolve the link, then start over
p = pathModule.resolve(resolvedLink, StringPrototypeSlice(p, pos));
current = base = splitRoot(p);
pos = current.length;
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard.has(base)) {
fs.lstat(base, (err) => {
if (err) return callback(err);
knownHard.add(base);
LOOP();
});
} else {
process.nextTick(LOOP);
}
}
}
/**
* Asynchronously computes the canonical pathname by
* resolving `.`, `..` and symbolic links.
* @param {string | Buffer | URL} path
* @param {string | { encoding?: string; }} [options]
* @param {(
* err?: Error,
* resolvedPath?: string | Buffer
* ) => any} callback
* @returns {void}
*/
realpath.native = (path, options, callback) => {
callback = makeCallback(callback || options);
options = getOptions(options);
path = getValidatedPath(path);
const req = new FSReqCallback();
req.oncomplete = callback;
return binding.realpath(pathModule.toNamespacedPath(path), options.encoding, req);
};
/**
* Creates a unique temporary directory.
* @param {string | Buffer | URL} prefix
* @param {string | { encoding?: string; }} [options]
* @param {(
* err?: Error,
* directory?: string
* ) => any} callback
* @returns {void}
*/
function mkdtemp(prefix, options, callback) {
callback = makeCallback(typeof options === 'function' ? options : callback);
options = getOptions(options);
prefix = getValidatedPath(prefix, 'prefix');
warnOnNonPortableTemplate(prefix);
let path;
if (typeof prefix === 'string') {
path = `${prefix}XXXXXX`;
} else {
path = Buffer.concat([prefix, Buffer.from('XXXXXX')]);
}
const req = new FSReqCallback();
req.oncomplete = callback;
binding.mkdtemp(path, options.encoding, req);
}
/**
* Synchronously creates a unique temporary directory.
* @param {string | Buffer | URL} prefix
* @param {string | { encoding?: string; }} [options]
* @returns {string}
*/
function mkdtempSync(prefix, options) {
options = getOptions(options);
prefix = getValidatedPath(prefix, 'prefix');
warnOnNonPortableTemplate(prefix);
let path;
if (typeof prefix === 'string') {
path = `${prefix}XXXXXX`;
} else {
path = Buffer.concat([prefix, Buffer.from('XXXXXX')]);
}
const ctx = { path };
const result = binding.mkdtemp(path, options.encoding,
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
/**
* Asynchronously copies `src` to `dest`. By
* default, `dest` is overwritten if it already exists.
* @param {string | Buffer | URL} src
* @param {string | Buffer | URL} dest
* @param {number} [mode]
* @param {() => any} callback
* @returns {void}
*/
function copyFile(src, dest, mode, callback) {
if (typeof mode === 'function') {
callback = mode;
mode = 0;
}
src = getValidatedPath(src, 'src');
dest = getValidatedPath(dest, 'dest');
src = pathModule._makeLong(src);
dest = pathModule._makeLong(dest);
mode = getValidMode(mode, 'copyFile');
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.copyFile(src, dest, mode, req);
}
/**
* Synchronously copies `src` to `dest`. By
* default, `dest` is overwritten if it already exists.
* @param {string | Buffer | URL} src
* @param {string | Buffer | URL} dest
* @param {number} [mode]
* @returns {void}
*/
function copyFileSync(src, dest, mode) {
src = getValidatedPath(src, 'src');
dest = getValidatedPath(dest, 'dest');
binding.copyFile(
pathModule.toNamespacedPath(src),
pathModule.toNamespacedPath(dest),
getValidMode(mode, 'copyFile'),
);
}
/**
* Asynchronously copies `src` to `dest`. `src` can be a file, directory, or
* symlink. The contents of directories will be copied recursively.
* @param {string | URL} src
* @param {string | URL} dest
* @param {object} [options]
* @param {() => any} callback
* @returns {void}
*/
function cp(src, dest, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
callback = makeCallback(callback);
options = validateCpOptions(options);
src = pathModule.toNamespacedPath(getValidatedPath(src, 'src'));
dest = pathModule.toNamespacedPath(getValidatedPath(dest, 'dest'));
lazyLoadCp();
cpFn(src, dest, options, callback);
}
/**
* Synchronously copies `src` to `dest`. `src` can be a file, directory, or
* symlink. The contents of directories will be copied recursively.
* @param {string | URL} src
* @param {string | URL} dest
* @param {object} [options]
* @returns {void}
*/
function cpSync(src, dest, options) {
options = validateCpOptions(options);
src = pathModule.toNamespacedPath(getValidatedPath(src, 'src'));
dest = pathModule.toNamespacedPath(getValidatedPath(dest, 'dest'));
lazyLoadCp();
cpSyncFn(src, dest, options);
}
function lazyLoadStreams() {
if (!ReadStream) {
({ ReadStream, WriteStream } = require('internal/fs/streams'));
FileReadStream = ReadStream;
FileWriteStream = WriteStream;
}
}
/**
* Creates a readable stream with a default `highWaterMark`
* of 64 KiB.
* @param {string | Buffer | URL} path
* @param {string | {
* flags?: string;
* encoding?: string;
* fd?: number | FileHandle;
* mode?: number;
* autoClose?: boolean;
* emitClose?: boolean;
* start: number;
* end?: number;
* highWaterMark?: number;
* fs?: object | null;
* }} [options]
* @returns {ReadStream}
*/
function createReadStream(path, options) {
lazyLoadStreams();
return new ReadStream(path, options);
}
/**
* Creates a write stream.
* @param {string | Buffer | URL} path
* @param {string | {
* flags?: string;
* encoding?: string;
* fd?: number | FileHandle;
* mode?: number;
* autoClose?: boolean;
* emitClose?: boolean;
* start: number;
* fs?: object | null;
* }} [options]
* @returns {WriteStream}
*/
function createWriteStream(path, options) {
lazyLoadStreams();
return new WriteStream(path, options);
}
module.exports = fs = {
appendFile,
appendFileSync,
access,
accessSync,
chown,
chownSync,
chmod,
chmodSync,
close,
closeSync,
copyFile,
copyFileSync,
cp,
cpSync,
createReadStream,
createWriteStream,
exists,
existsSync,
fchown,
fchownSync,
fchmod,
fchmodSync,
fdatasync,
fdatasyncSync,
fstat,
fstatSync,
fsync,
fsyncSync,
ftruncate,
ftruncateSync,
futimes,
futimesSync,
lchown,
lchownSync,
lchmod: constants.O_SYMLINK !== undefined ? lchmod : undefined,
lchmodSync: constants.O_SYMLINK !== undefined ? lchmodSync : undefined,
link,
linkSync,
lstat,
lstatSync,
lutimes,
lutimesSync,
mkdir,
mkdirSync,
mkdtemp,
mkdtempSync,
open,
openSync,
openAsBlob,
readdir,
readdirSync,
read,
readSync,
readv,
readvSync,
readFile,
readFileSync,
readlink,
readlinkSync,
realpath,
realpathSync,
rename,
renameSync,
rm,
rmSync,
rmdir,
rmdirSync,
stat,
statfs,
statSync,
statfsSync,
symlink,
symlinkSync,
truncate,
truncateSync,
unwatchFile,
unlink,
unlinkSync,
utimes,
utimesSync,
watch,
watchFile,
writeFile,
writeFileSync,
write,
writeSync,
writev,
writevSync,
Dirent,
Stats,
get ReadStream() {
lazyLoadStreams();
return ReadStream;
},
set ReadStream(val) {
ReadStream = val;
},
get WriteStream() {
lazyLoadStreams();
return WriteStream;
},
set WriteStream(val) {
WriteStream = val;
},
// Legacy names... these have to be separate because of how graceful-fs
// (and possibly other) modules monkey patch the values.
get FileReadStream() {
lazyLoadStreams();
return FileReadStream;
},
set FileReadStream(val) {
FileReadStream = val;
},
get FileWriteStream() {
lazyLoadStreams();
return FileWriteStream;
},
set FileWriteStream(val) {
FileWriteStream = val;
},
// For tests
_toUnixTimestamp: toUnixTimestamp,
};
defineLazyProperties(
fs,
'internal/fs/dir',
['Dir', 'opendir', 'opendirSync'],
);
ObjectDefineProperties(fs, {
F_OK: { __proto__: null, enumerable: true, value: F_OK || 0 },
R_OK: { __proto__: null, enumerable: true, value: R_OK || 0 },
W_OK: { __proto__: null, enumerable: true, value: W_OK || 0 },
X_OK: { __proto__: null, enumerable: true, value: X_OK || 0 },
constants: {
__proto__: null,
configurable: false,
enumerable: true,
value: constants,
},
promises: {
__proto__: null,
configurable: true,
enumerable: true,
get() {
promises ??= require('internal/fs/promises').exports;
return promises;
},
},
});