nodejs/lib/fs.js

3234 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,
ReflectApply,
SafeMap,
SafeSet,
String,
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');
// We need to get the statValues from the binding at the callsite since
// it's re-initialized after deserialization.
const binding = internalBinding('fs');
const { Buffer } = require('buffer');
const {
aggregateTwoErrors,
codes: {
ERR_FS_FILE_TOO_LARGE,
ERR_INVALID_ARG_VALUE,
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
},
AbortError,
uvErrmapGet,
uvException,
} = require('internal/errors');
const { FSReqCallback } = binding;
const { toPathIfFileURL } = require('internal/url');
const {
customPromisifyArgs: kCustomPromisifyArgsSymbol,
deprecate,
kEmptyObject,
promisify: {
custom: kCustomPromisifiedSymbol,
},
SideEffectFreeRegExpPrototypeExec,
defineLazyProperties,
} = require('internal/util');
const {
constants: {
kIoMaxLength,
kMaxUserId,
},
copyObject,
Dirent,
emitRecursiveRmdirWarning,
getDirent,
getDirents,
getOptions,
getValidatedFd,
getValidatedPath,
getValidMode,
handleErrorFromBinding,
preprocessSymlinkDestination,
Stats,
getStatFsFromBinding,
getStatsFromBinding,
realpathCacheKey,
stringToFlags,
stringToSymlinkType,
toUnixTimestamp,
validateBufferArray,
validateCpOptions,
validateOffsetLengthRead,
validateOffsetLengthWrite,
validatePath,
validatePosition,
validateRmOptions,
validateRmOptionsSync,
validateRmdirOptions,
validateStringAfterArrayBufferView,
validatePrimitiveStringAfterArrayBufferView,
warnOnNonPortableTemplate,
} = require('internal/fs/utils');
const {
CHAR_FORWARD_SLASH,
CHAR_BACKWARD_SLASH,
} = require('internal/constants');
const {
isUint32,
parseFileMode,
validateBoolean,
validateBuffer,
validateEncoding,
validateFunction,
validateInteger,
validateObject,
} = 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';
const showStringCoercionDeprecation = deprecate(
() => {},
'Implicit coercion of objects with own toString property is deprecated.',
'DEP0162',
);
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 = isUint32;
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');
const ctx = { path };
binding.access(pathModule.toNamespacedPath(path), mode, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* 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;
}
const ctx = { path };
const nPath = pathModule.toNamespacedPath(path);
binding.access(nPath, F_OK, undefined, ctx);
// In case of an invalid symlink, `binding.access()` on win32
// will **not** return an error and is therefore not enough.
// Double check with `binding.stat()`.
if (isWindows && ctx.errno === undefined) {
binding.stat(nPath, false, undefined, ctx);
}
return ctx.errno === undefined;
}
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_file_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' });
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);
const ctx = {};
binding.close(fd, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* 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);
const flagsNumber = stringToFlags(flags);
mode = parseFileMode(mode, 'mode', 0o666);
const ctx = { path };
const result = binding.open(pathModule.toNamespacedPath(path),
flagsNumber, mode,
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
/**
* 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);
}
({
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;
validatePosition(position, 'position');
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;
* }} [offset]
* @returns {number}
*/
function readSync(fd, buffer, offset, length, position) {
fd = getValidatedFd(fd);
validateBuffer(buffer);
if (arguments.length <= 3) {
// Assume fs.readSync(fd, buffer, options)
const options = offset || kEmptyObject;
({
offset = 0,
length = buffer.byteLength - offset,
position = null,
} = options);
}
if (offset == null) {
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;
validatePosition(position, 'position');
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 | object} 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 buffer !== 'string') {
showStringCoercionDeprecation();
}
if (typeof position !== 'function') {
if (typeof offset === 'function') {
position = offset;
offset = null;
} else {
position = length;
}
length = 'utf8';
}
const str = String(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 {
validatePrimitiveStringAfterArrayBufferView(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 ctx = { path };
const stats = binding.stat(pathModule.toNamespacedPath(path),
options.bigint, undefined, ctx);
if (options.throwIfNoEntry === false && hasNoEntryError(ctx)) {
return undefined;
}
handleErrorFromBinding(ctx);
return getStatsFromBinding(stats);
}
function statfsSync(path, options = { bigint: false }) {
path = getValidatedPath(path);
const ctx = { path };
const stats = binding.statfs(pathModule.toNamespacedPath(path),
options.bigint, undefined, ctx);
handleErrorFromBinding(ctx);
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 = getValidatedPath(path);
const ctx = { path };
binding.unlink(pathModule.toNamespacedPath(path), undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* 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 | object} 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');
if (typeof data !== 'string') {
showStringCoercionDeprecation();
}
data = Buffer.from(String(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 | object} 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');
if (typeof data !== 'string') {
showStringCoercionDeprecation();
}
data = Buffer.from(String(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;
if (options.recursive && !(isOSX || isWindows))
throw new ERR_FEATURE_UNAVAILABLE_ON_PLATFORM('watch recursively');
const watchers = require('internal/fs/watchers');
const watcher = new watchers.FSWatcher();
watcher[watchers.kFSWatchStart](filename,
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(binding.statValues, S_IFIFO) ||
isFileType(binding.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, ctx);
handleErrorFromBinding(ctx);
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(binding.statValues, S_IFIFO) ||
isFileType(binding.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');
const ctx = { path: src, dest }; // non-prefixed
src = pathModule._makeLong(src);
dest = pathModule._makeLong(dest);
mode = getValidMode(mode, 'copyFile');
binding.copyFile(src, dest, mode, undefined, ctx);
handleErrorFromBinding(ctx);
}
/**
* 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,
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;
},
},
});