mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
Add fs.mkstemp(), fs.mkstempSync() and fsPromises.mkstemp(), which create and open a unique temporary file in one operation. They expose uv_fs_mkstemp(), the file counterpart of the function behind fs.mkdtemp(). The callback and sync versions return the path and a file descriptor, the promise version returns the path and a FileHandle. libuv clears the path of the request when mkstemp() fails, so the template is saved in the request and used for the path of the error. The functions are also supported on mounted virtual file systems. Refs: https://github.com/nodejs/node/pull/33549 Refs: https://github.com/nodejs/node/issues/33890 Refs: https://github.com/nodejs/node/issues/5332 Signed-off-by: marcopiraccini <marco.piraccini@gmail.com> PR-URL: https://github.com/nodejs/node/pull/66484 Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
4170 lines
109 KiB
JavaScript
4170 lines
109 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,
|
|
FunctionPrototypeCall,
|
|
MathMax,
|
|
Number,
|
|
ObjectDefineProperties,
|
|
ObjectDefineProperty,
|
|
Promise,
|
|
PromisePrototypeThen,
|
|
PromiseResolve,
|
|
ReflectApply,
|
|
SafeMap,
|
|
SafeSet,
|
|
StringPrototypeCharCodeAt,
|
|
StringPrototypeIndexOf,
|
|
StringPrototypeSlice,
|
|
SymbolDispose,
|
|
uncurryThis,
|
|
} = primordials;
|
|
|
|
const { fs: constants } = internalBinding('constants');
|
|
const {
|
|
S_IFIFO,
|
|
S_IFLNK,
|
|
S_IFMT,
|
|
S_IFREG,
|
|
S_IFSOCK,
|
|
F_OK,
|
|
O_WRONLY,
|
|
O_SYMLINK,
|
|
} = constants;
|
|
|
|
const pathModule = require('path');
|
|
const { isArrayBufferView } = require('internal/util/types');
|
|
|
|
const binding = internalBinding('fs');
|
|
|
|
const { Buffer } = require('buffer');
|
|
const { isBuffer: BufferIsBuffer } = Buffer;
|
|
const BufferToString = uncurryThis(Buffer.prototype.toString);
|
|
const {
|
|
AbortError,
|
|
aggregateTwoErrors,
|
|
codes: {
|
|
ERR_ACCESS_DENIED,
|
|
ERR_FS_FILE_TOO_LARGE,
|
|
ERR_INVALID_ARG_VALUE,
|
|
},
|
|
} = require('internal/errors');
|
|
|
|
const {
|
|
FSReqCallback,
|
|
ReadFileJob,
|
|
WriteFileJob,
|
|
} = binding;
|
|
const { toPathIfFileURL } = require('internal/url');
|
|
const {
|
|
customPromisifyArgs: kCustomPromisifyArgsSymbol,
|
|
getLazy,
|
|
kEmptyObject,
|
|
promisify: {
|
|
custom: kCustomPromisifiedSymbol,
|
|
},
|
|
SideEffectFreeRegExpPrototypeExec,
|
|
defineLazyProperties,
|
|
isWindows,
|
|
isMacOS,
|
|
} = require('internal/util');
|
|
const {
|
|
constants: {
|
|
kIoMaxLength,
|
|
kReadFileBufferLength,
|
|
kMaxUserId,
|
|
},
|
|
collectRecursiveReaddirResult,
|
|
copyObject,
|
|
Dirent,
|
|
getDirents,
|
|
getRecursiveDirents,
|
|
getOptions,
|
|
getValidatedFd,
|
|
getValidatedPath,
|
|
handleErrorFromBinding,
|
|
preprocessSymlinkDestination,
|
|
Stats,
|
|
getReadFileBuffer,
|
|
getReadFileBufferByteLengthName,
|
|
getStatFsFromBinding,
|
|
getStatsFromBinding,
|
|
realpathCacheKey,
|
|
stringToFlags,
|
|
stringToSymlinkType,
|
|
toUnixTimestamp,
|
|
validateBufferArray,
|
|
validateCpOptions,
|
|
validateOffsetLengthRead,
|
|
validateOffsetLengthWrite,
|
|
validatePath,
|
|
validatePosition,
|
|
validateReadFileBufferOptions,
|
|
validateRmOptions,
|
|
validateRmOptionsSync,
|
|
validateRmdirOptions,
|
|
validateStringAfterArrayBufferView,
|
|
vfsState,
|
|
warnOnNonPortableTemplate,
|
|
} = require('internal/fs/utils');
|
|
const {
|
|
CHAR_FORWARD_SLASH,
|
|
CHAR_BACKWARD_SLASH,
|
|
} = require('internal/constants');
|
|
const {
|
|
isInt32,
|
|
parseFileMode,
|
|
validateAbortSignal,
|
|
validateBoolean,
|
|
validateBuffer,
|
|
validateEncoding,
|
|
validateFunction,
|
|
validateInteger,
|
|
validateObject,
|
|
validateOneOf,
|
|
validateString,
|
|
kValidateObjectAllowNullable,
|
|
} = require('internal/validators');
|
|
|
|
const permission = require('internal/process/permission');
|
|
|
|
let fs;
|
|
|
|
// Lazy loaded
|
|
let cpFn;
|
|
let cpSyncFn;
|
|
let promises = null;
|
|
let ReadStream;
|
|
let WriteStream;
|
|
let rimraf;
|
|
let kResistStopPropagation;
|
|
let ReadFileContext;
|
|
|
|
// These have to be separate because of how graceful-fs happens to do it's
|
|
// monkeypatching.
|
|
let FileReadStream;
|
|
let FileWriteStream;
|
|
let Utf8Stream;
|
|
|
|
function lazyLoadUtf8Stream() {
|
|
Utf8Stream ??= require('internal/streams/fast-utf8-stream');
|
|
}
|
|
|
|
// 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);
|
|
if (stats === undefined && err === null) return cb(null, undefined);
|
|
cb(err, getStatsFromBinding(stats));
|
|
};
|
|
}
|
|
|
|
const isFd = isInt32;
|
|
|
|
/**
|
|
* Route VFS async result (Promise) to error-first callback.
|
|
* @param {Promise|undefined} promise The VFS handler result
|
|
* @param {Function} callback The error-first callback
|
|
* @returns {boolean} true if VFS handled, false otherwise
|
|
*/
|
|
function vfsResult(promise, callback) {
|
|
if (promise === undefined) return false;
|
|
PromisePrototypeThen(promise, (result) => callback(null, result), callback);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Route VFS async void result to error-first callback.
|
|
* @param {Promise|undefined} promise The VFS handler result
|
|
* @param {Function} callback The error-first callback
|
|
* @returns {boolean} true if VFS handled, false otherwise
|
|
*/
|
|
function vfsVoid(promise, callback) {
|
|
if (promise === undefined) return false;
|
|
PromisePrototypeThen(promise, () => callback(null), callback);
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.access(path, mode), callback)) return;
|
|
|
|
path = getValidatedPath(path);
|
|
callback = makeCallback(callback);
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.access(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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.accessSync(path, mode);
|
|
if (result !== undefined) return;
|
|
}
|
|
binding.access(getValidatedPath(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) {
|
|
validateFunction(callback, 'cb');
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.existsSync(path);
|
|
if (result !== undefined) {
|
|
process.nextTick(callback, result);
|
|
return;
|
|
}
|
|
}
|
|
|
|
function suppressedCallback(err) {
|
|
callback(!err);
|
|
}
|
|
|
|
try {
|
|
fs.access(path, F_OK, suppressedCallback);
|
|
} catch {
|
|
return callback(false);
|
|
}
|
|
}
|
|
|
|
ObjectDefineProperty(exists, kCustomPromisifiedSymbol, {
|
|
__proto__: null,
|
|
value: function exists(path) {
|
|
return new Promise((resolve) => fs.exists(path, resolve));
|
|
},
|
|
});
|
|
|
|
let showExistsDeprecation = true;
|
|
/**
|
|
* Synchronously tests whether or not the given path exists.
|
|
* @param {string | Buffer | URL} path
|
|
* @returns {boolean}
|
|
*/
|
|
function existsSync(path) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.existsSync(path);
|
|
if (result !== undefined) return result;
|
|
}
|
|
try {
|
|
path = getValidatedPath(path);
|
|
} catch (err) {
|
|
if (showExistsDeprecation && err?.code === 'ERR_INVALID_ARG_TYPE') {
|
|
process.emitWarning(
|
|
'Passing invalid argument types to fs.existsSync is deprecated', 'DeprecationWarning', 'DEP0187',
|
|
);
|
|
showExistsDeprecation = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return binding.existsSync(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 {
|
|
context.prepare();
|
|
} 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;
|
|
}
|
|
|
|
function bindSignalToReq(req, signal, callback) {
|
|
if (!signal) {
|
|
req.oncomplete = callback;
|
|
return;
|
|
}
|
|
let aborted = false;
|
|
const onAbort = () => {
|
|
aborted = true;
|
|
req.cancel();
|
|
callback(new AbortError(undefined, { cause: signal.reason }));
|
|
};
|
|
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
|
|
signal.addEventListener('abort', onAbort, { __proto__: null, [kResistStopPropagation]: true });
|
|
req.oncomplete = function(err, result) {
|
|
signal.removeEventListener('abort', onAbort);
|
|
if (aborted) return;
|
|
callback(err, result);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Asynchronously reads the entire contents of a file.
|
|
* @param {string | Buffer | URL | number} path
|
|
* @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 ||= options;
|
|
validateFunction(callback, 'cb');
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const opts = typeof options === 'function' ? undefined : options;
|
|
if (checkAborted(opts?.signal, callback)) return;
|
|
if (vfsResult(h.readFile(path, opts), callback)) return;
|
|
}
|
|
|
|
options = getOptions(options, { flag: 'r' });
|
|
validateReadFileBufferOptions(options);
|
|
ReadFileContext ??= require('internal/fs/read/context');
|
|
const context = new ReadFileContext(callback, options);
|
|
context.isUserFd = isFd(path); // File descriptor ownership
|
|
|
|
if (options.signal) {
|
|
context.signal = options.signal;
|
|
}
|
|
if (context.isUserFd) {
|
|
process.nextTick(function tick(context) {
|
|
FunctionPrototypeCall(readFileAfterOpen, { context }, null, path);
|
|
}, context);
|
|
return;
|
|
}
|
|
|
|
if (checkAborted(options.signal, callback))
|
|
return;
|
|
|
|
const flagsNumber = stringToFlags(options.flag, 'options.flag');
|
|
path = getValidatedPath(path);
|
|
if (options.buffer === undefined) {
|
|
// Open + fstat + read + close in one thread pool round trip for files of
|
|
// up to one chunk; larger files come back as an open fd + size and take
|
|
// the chunked reader below (readFileAfterOneShot). `true`: a handed-back
|
|
// fd will be closed through fs.close(), so track it as unmanaged.
|
|
const job = new ReadFileJob(path, flagsNumber, kReadFileBufferLength, true);
|
|
job.context = context;
|
|
job.ondone = readFileAfterOneShot;
|
|
const accessError = job.run(path);
|
|
if (accessError !== undefined) {
|
|
// Not scheduled: report it the way the request-based open() did.
|
|
callback(accessError);
|
|
}
|
|
return;
|
|
}
|
|
const req = new FSReqCallback();
|
|
req.context = context;
|
|
req.oncomplete = readFileAfterOpen;
|
|
binding.open(path, flagsNumber, 0o666, req);
|
|
}
|
|
|
|
function readFileAfterOneShot(err, buffer, fd, size, closeErr) {
|
|
const context = this.context;
|
|
if (err) {
|
|
context.callback(err);
|
|
return;
|
|
}
|
|
if (fd !== -1) {
|
|
// (context.read() below performs the abort check for this case.)
|
|
// Larger than one chunk: continue exactly like after open + fstat.
|
|
context.fd = fd;
|
|
context.size = size;
|
|
if (size > kIoMaxLength) {
|
|
return context.close(new ERR_FS_FILE_TOO_LARGE(size));
|
|
}
|
|
try {
|
|
context.prepare();
|
|
} catch (err) {
|
|
return context.close(err);
|
|
}
|
|
context.read();
|
|
return;
|
|
}
|
|
if (closeErr) {
|
|
context.callback(closeErr);
|
|
return;
|
|
}
|
|
if (context.signal?.aborted) {
|
|
// An abort that arrived while the read was in flight wins, as it did when
|
|
// it was noticed between the open/fstat/read steps.
|
|
context.callback(new AbortError(undefined, { cause: context.signal.reason }));
|
|
return;
|
|
}
|
|
let result = buffer;
|
|
if (context.encoding) {
|
|
try {
|
|
result = buffer.toString(context.encoding);
|
|
} catch (err) {
|
|
context.callback(err);
|
|
return;
|
|
}
|
|
}
|
|
context.callback(null, result);
|
|
}
|
|
|
|
function tryStatSync(fd, isUserFd) {
|
|
const stats = binding.fstat(fd, false, undefined, true /* shouldNotThrow */);
|
|
if (stats === undefined && !isUserFd) {
|
|
fs.closeSync(fd);
|
|
}
|
|
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 tryGetReadFileBuffer(options, size, fd, isUserFd) {
|
|
let threw = true;
|
|
let buffer;
|
|
try {
|
|
buffer = getReadFileBuffer(options, 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;
|
|
}
|
|
|
|
function tryReadSyncWithUserBuffer(fd, isUserFd, buffer, byteLengthName) {
|
|
let pos = 0;
|
|
let bytesRead = 0;
|
|
|
|
while (pos < buffer.byteLength) {
|
|
bytesRead = tryReadSync(fd, isUserFd, buffer, pos, buffer.byteLength - pos);
|
|
pos += bytesRead;
|
|
|
|
if (bytesRead === 0) {
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
const extraBuffer = tryCreateBuffer(1, fd, isUserFd);
|
|
bytesRead = tryReadSync(fd, isUserFd, extraBuffer, 0, 1);
|
|
|
|
if (bytesRead !== 0) {
|
|
if (!isUserFd) {
|
|
fs.closeSync(fd);
|
|
}
|
|
throw new ERR_INVALID_ARG_VALUE(
|
|
byteLengthName,
|
|
buffer.byteLength,
|
|
'is too small to contain the entire file',
|
|
);
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.readFileSync(path, options);
|
|
if (result !== undefined) return result;
|
|
}
|
|
options = getOptions(options, { flag: 'r' });
|
|
validateReadFileBufferOptions(options);
|
|
const hasUserBuffer = options.buffer !== undefined;
|
|
|
|
if ((options.encoding === 'utf8' || options.encoding === 'utf-8') &&
|
|
!hasUserBuffer) {
|
|
if (isInt32(path)) {
|
|
// V8 does not report -0 as an int32, so it would reach the binding as a
|
|
// path instead of a file descriptor.
|
|
path |= 0;
|
|
} else {
|
|
path = 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 (hasUserBuffer) {
|
|
buffer = tryGetReadFileBuffer(options, size, fd, isUserFd);
|
|
} else if (size === 0) {
|
|
buffers = [];
|
|
} else {
|
|
buffer = tryCreateBuffer(size, fd, isUserFd);
|
|
}
|
|
|
|
let bytesRead;
|
|
|
|
if (hasUserBuffer) {
|
|
if (size !== 0) {
|
|
do {
|
|
bytesRead = tryReadSync(fd, isUserFd, buffer, pos, size - pos);
|
|
pos += bytesRead;
|
|
} while (bytesRead !== 0 && pos < size);
|
|
} else {
|
|
pos = tryReadSyncWithUserBuffer(
|
|
fd,
|
|
isUserFd,
|
|
buffer,
|
|
getReadFileBufferByteLengthName(options),
|
|
);
|
|
}
|
|
} else 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 (hasUserBuffer) {
|
|
buffer = buffer.subarray(0, pos);
|
|
} else 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) {
|
|
if (callback !== defaultCloseCallback)
|
|
callback = makeCallback(callback);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.close(fd), callback)) return;
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.close(fd, req);
|
|
}
|
|
|
|
/**
|
|
* Synchronously closes the file descriptor.
|
|
* @param {number} fd
|
|
* @returns {void}
|
|
*/
|
|
function closeSync(fd) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.closeSync(fd);
|
|
if (result !== undefined) 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) {
|
|
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);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsResult(h.open(path, flagsNumber, mode), callback)) return;
|
|
|
|
path = getValidatedPath(path);
|
|
callback = makeCallback(callback);
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
|
|
binding.open(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) {
|
|
flags = stringToFlags(flags);
|
|
mode = parseFileMode(mode, 'mode', 0o666);
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.openSync(path, flags, mode);
|
|
if (result !== undefined) return result;
|
|
}
|
|
return binding.open(
|
|
getValidatedPath(path),
|
|
flags,
|
|
mode,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param {string | Buffer | URL } path
|
|
* @param {{
|
|
* type?: string;
|
|
* }} [options]
|
|
* @returns {Promise<Blob>}
|
|
*/
|
|
function openAsBlob(path, options = kEmptyObject) {
|
|
validateObject(options, 'options');
|
|
const { type = '' } = options;
|
|
validateString(type, 'options.type');
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.openAsBlob(path, options);
|
|
if (result !== undefined) return PromiseResolve(result);
|
|
}
|
|
|
|
// 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);
|
|
const { createBlobFromFilePath } = require('internal/blob');
|
|
return PromiseResolve(createBlobFromFilePath(path, { type }));
|
|
}
|
|
|
|
/**
|
|
* @param {string | Buffer | URL} path
|
|
* @param {{
|
|
* type?: string;
|
|
* }} [options]
|
|
* @returns {Blob}
|
|
*/
|
|
function openAsBlobSync(path, options = kEmptyObject) {
|
|
validateObject(options, 'options');
|
|
const { type = '' } = options;
|
|
validateString(type, 'options.type');
|
|
path = getValidatedPath(path);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.openAsBlobSync(path, options);
|
|
if (result !== undefined) return result;
|
|
}
|
|
|
|
const { createBlobFromFilePath } = require('internal/blob');
|
|
return createBlobFromFilePath(path, { type });
|
|
}
|
|
|
|
/**
|
|
* Reads file from the specified `fd` (file descriptor).
|
|
* @param {number} fd
|
|
* @param {Buffer | TypedArray | DataView} buffer
|
|
* @param {number | {
|
|
* offset?: number;
|
|
* length?: number;
|
|
* position?: number | bigint | null;
|
|
* }} [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', kValidateObjectAllowNullable);
|
|
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', kValidateObjectAllowNullable);
|
|
}
|
|
({
|
|
offset = 0,
|
|
length = buffer?.byteLength - offset,
|
|
position = null,
|
|
} = params ?? kEmptyObject);
|
|
}
|
|
|
|
validateBuffer(buffer);
|
|
validateFunction(callback, 'cb');
|
|
|
|
if (offset == null) {
|
|
offset = 0;
|
|
} else {
|
|
validateInteger(offset, 'offset', 0);
|
|
}
|
|
|
|
length |= 0;
|
|
|
|
if (position == null) {
|
|
position = -1;
|
|
} else {
|
|
validatePosition(position, 'position', length);
|
|
}
|
|
|
|
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);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const promise = h.read(fd, buffer, offset, length, position);
|
|
if (promise !== undefined) {
|
|
PromisePrototypeThen(promise,
|
|
(bytesRead) => callback(null, bytesRead, buffer), callback);
|
|
return;
|
|
}
|
|
}
|
|
|
|
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 {number | {
|
|
* offset?: number;
|
|
* length?: number;
|
|
* position?: number | bigint | null;
|
|
* }} [offsetOrOptions]
|
|
* @param {number} [length]
|
|
* @param {number} [position]
|
|
* @returns {number}
|
|
*/
|
|
function readSync(fd, buffer, offsetOrOptions, length, position) {
|
|
validateBuffer(buffer);
|
|
|
|
let offset = offsetOrOptions;
|
|
if (arguments.length <= 3 || typeof offsetOrOptions === 'object') {
|
|
if (offsetOrOptions !== undefined) {
|
|
validateObject(offsetOrOptions, 'options', kValidateObjectAllowNullable);
|
|
}
|
|
|
|
({
|
|
offset = 0,
|
|
length = buffer.byteLength - offset,
|
|
position = null,
|
|
} = offsetOrOptions ?? kEmptyObject);
|
|
}
|
|
|
|
if (offset === undefined) {
|
|
offset = 0;
|
|
} else {
|
|
validateInteger(offset, 'offset', 0);
|
|
}
|
|
|
|
length |= 0;
|
|
|
|
if (position == null) {
|
|
position = -1;
|
|
} else {
|
|
validatePosition(position, 'position', length);
|
|
}
|
|
|
|
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);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.readSync(fd, buffer, offset, length, position);
|
|
if (result !== undefined) return result;
|
|
}
|
|
|
|
return binding.read(fd, buffer, offset, length, position);
|
|
}
|
|
|
|
/**
|
|
* 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 ||= position;
|
|
validateFunction(callback, 'cb');
|
|
|
|
if (typeof position !== 'number')
|
|
position = null;
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const promise = h.readv(fd, buffers, position);
|
|
if (promise !== undefined) {
|
|
PromisePrototypeThen(promise,
|
|
(read) => callback(null, read, buffers), callback);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = wrapper;
|
|
|
|
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) {
|
|
validateBufferArray(buffers);
|
|
|
|
if (typeof position !== 'number')
|
|
position = null;
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.readvSync(fd, buffers, position);
|
|
if (result !== undefined) return result;
|
|
}
|
|
|
|
return binding.readBuffers(fd, buffers, position);
|
|
}
|
|
|
|
/**
|
|
* 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 ||= position || length || offset;
|
|
validateFunction(callback, 'cb');
|
|
|
|
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 h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const promise = h.write(fd, buffer, offset, length, position);
|
|
if (promise !== undefined) {
|
|
PromisePrototypeThen(promise,
|
|
(bytesWritten) => callback(null, bytesWritten, buffer), callback);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = wrapper;
|
|
binding.writeBuffer(fd, buffer, offset, length, position, req);
|
|
return;
|
|
}
|
|
|
|
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 = position;
|
|
validateFunction(callback, 'cb');
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const bufAsBuffer = Buffer.from(str, length);
|
|
const promise = h.write(fd, bufAsBuffer, 0, bufAsBuffer.length, offset);
|
|
if (promise !== undefined) {
|
|
PromisePrototypeThen(promise,
|
|
(bytesWritten) => callback(null, bytesWritten, buffer), callback);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = wrapper;
|
|
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]
|
|
* @param {number} [length]
|
|
* @param {number} [position]
|
|
* @returns {number}
|
|
*/
|
|
function writeSync(fd, buffer, offsetOrOptions, length, position) {
|
|
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);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const vfsResult = h.writeSync(fd, buffer, offset, length, position);
|
|
if (vfsResult !== undefined) return vfsResult;
|
|
}
|
|
|
|
result = binding.writeBuffer(fd, buffer, offset, length, position,
|
|
undefined, ctx);
|
|
} else {
|
|
validateStringAfterArrayBufferView(buffer, 'buffer');
|
|
validateEncoding(buffer, length);
|
|
|
|
if (offset === undefined)
|
|
offset = null;
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const bufAsBuffer = Buffer.from(buffer, length);
|
|
const vfsResult = h.writeSync(fd, bufAsBuffer, 0, bufAsBuffer.length, offset);
|
|
if (vfsResult !== undefined) return vfsResult;
|
|
}
|
|
|
|
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 ||= position;
|
|
validateFunction(callback, 'cb');
|
|
|
|
if (buffers.length === 0) {
|
|
process.nextTick(callback, null, 0, buffers);
|
|
return;
|
|
}
|
|
|
|
if (typeof position !== 'number')
|
|
position = null;
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const promise = h.writev(fd, buffers, position);
|
|
if (promise !== undefined) {
|
|
PromisePrototypeThen(promise,
|
|
(written) => callback(null, written, buffers), callback);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = wrapper;
|
|
|
|
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) {
|
|
validateBufferArray(buffers);
|
|
|
|
if (buffers.length === 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (typeof position !== 'number')
|
|
position = null;
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.writevSync(fd, buffers, position);
|
|
if (result !== undefined) return result;
|
|
}
|
|
|
|
return binding.writeBuffers(fd, buffers, position);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.rename(oldPath, newPath), callback)) return;
|
|
|
|
callback = makeCallback(callback);
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.rename(
|
|
getValidatedPath(oldPath, 'oldPath'),
|
|
getValidatedPath(newPath, '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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.renameSync(oldPath, newPath);
|
|
if (result !== undefined) return;
|
|
}
|
|
binding.rename(
|
|
getValidatedPath(oldPath, 'oldPath'),
|
|
getValidatedPath(newPath, 'newPath'),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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 len === 'function') {
|
|
callback = len;
|
|
len = 0;
|
|
} else if (len === undefined) {
|
|
len = 0;
|
|
}
|
|
|
|
validateInteger(len, 'len');
|
|
len = MathMax(0, len);
|
|
validateFunction(callback, 'cb');
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.truncate(path, len), callback)) return;
|
|
|
|
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 (len === undefined) {
|
|
len = 0;
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.truncateSync(path, len);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
// Allow error to be thrown, but still close fd.
|
|
const fd = fs.openSync(path, 'r+');
|
|
try {
|
|
fs.ftruncateSync(fd, len);
|
|
} finally {
|
|
fs.closeSync(fd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
validateInteger(len, 'len');
|
|
len = MathMax(0, len);
|
|
callback = makeCallback(callback);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.ftruncate(fd, len), callback)) return;
|
|
|
|
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) {
|
|
validateInteger(len, 'len');
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.ftruncateSync(fd, len < 0 ? 0 : len);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
binding.ftruncate(fd, len < 0 ? 0 : len);
|
|
}
|
|
|
|
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 } = require('internal/fs/rimraf'));
|
|
}
|
|
|
|
/**
|
|
* Asynchronously removes a directory.
|
|
* @param {string | Buffer | URL} path
|
|
* @param {object} [options]
|
|
* @param {(err?: Error) => any} callback
|
|
* @returns {void}
|
|
*/
|
|
function rmdir(path, options, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = undefined;
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.rmdir(path), callback)) return;
|
|
|
|
if (options?.recursive !== undefined) {
|
|
// This API previously accepted a `recursive` option that was deprecated
|
|
// and removed. However, in order to make the change more visible, we
|
|
// opted to throw an error if recursive is specified rather than removing it
|
|
// entirely.
|
|
throw new ERR_INVALID_ARG_VALUE(
|
|
'options.recursive',
|
|
options.recursive,
|
|
'is no longer supported',
|
|
);
|
|
}
|
|
|
|
callback = makeCallback(callback);
|
|
path = getValidatedPath(path);
|
|
|
|
validateRmdirOptions(options);
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.rmdir(path, req);
|
|
}
|
|
|
|
/**
|
|
* Synchronously removes a directory.
|
|
* @param {string | Buffer | URL} path
|
|
* @param {object} [options]
|
|
* @returns {void}
|
|
*/
|
|
function rmdirSync(path, options) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.rmdirSync(path);
|
|
if (result !== undefined) return;
|
|
}
|
|
path = getValidatedPath(path);
|
|
|
|
if (options?.recursive !== undefined) {
|
|
throw new ERR_INVALID_ARG_VALUE(
|
|
'options.recursive',
|
|
options.recursive,
|
|
'is no longer supported',
|
|
);
|
|
}
|
|
|
|
validateRmdirOptions(options);
|
|
binding.rmdir(path);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.rm(path, options), callback)) return;
|
|
|
|
path = getValidatedPath(path);
|
|
|
|
validateRmOptions(path, options, false, (err, options) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
lazyLoadRimraf();
|
|
return rimraf(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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.rmSync(path, options);
|
|
if (result !== undefined) return;
|
|
}
|
|
const opts = validateRmOptionsSync(path, options, false);
|
|
return binding.rmSync(getValidatedPath(path), opts.maxRetries, opts.recursive, opts.retryDelay);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
callback = makeCallback(callback);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.fdatasync(fd), callback)) return;
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
|
|
if (permission.isEnabled()) {
|
|
callback(new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.'));
|
|
return;
|
|
}
|
|
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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.fdatasyncSync(fd);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
if (permission.isEnabled()) {
|
|
throw new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.');
|
|
}
|
|
binding.fdatasync(fd);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
callback = makeCallback(callback);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.fsync(fd), callback)) return;
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
if (permission.isEnabled()) {
|
|
callback(new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.'));
|
|
return;
|
|
}
|
|
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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.fsyncSync(fd);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
if (permission.isEnabled()) {
|
|
throw new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.');
|
|
}
|
|
binding.fsync(fd);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = undefined;
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const promise = h.mkdir(path, options);
|
|
if (promise !== undefined) {
|
|
PromisePrototypeThen(promise, (r) => callback(null, r.result), callback);
|
|
return;
|
|
}
|
|
}
|
|
|
|
let mode = 0o777;
|
|
let recursive = false;
|
|
if (typeof options === 'number' || typeof options === 'string') {
|
|
mode = parseFileMode(options, 'mode');
|
|
} else if (options) {
|
|
if (options.recursive !== undefined) {
|
|
recursive = options.recursive;
|
|
validateBoolean(recursive, 'options.recursive');
|
|
}
|
|
if (options.mode !== undefined) {
|
|
mode = parseFileMode(options.mode, 'options.mode');
|
|
}
|
|
}
|
|
callback = makeCallback(callback);
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.mkdir(
|
|
getValidatedPath(path),
|
|
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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const vfsResult = h.mkdirSync(path, options);
|
|
if (vfsResult !== undefined) return vfsResult.result;
|
|
}
|
|
let mode = 0o777;
|
|
let recursive = false;
|
|
if (typeof options === 'number' || typeof options === 'string') {
|
|
mode = parseFileMode(options, 'mode');
|
|
} else if (options) {
|
|
if (options.recursive !== undefined) {
|
|
recursive = options.recursive;
|
|
validateBoolean(recursive, 'options.recursive');
|
|
}
|
|
if (options.mode !== undefined) {
|
|
mode = parseFileMode(options.mode, 'options.mode');
|
|
}
|
|
}
|
|
|
|
const result = binding.mkdir(
|
|
getValidatedPath(path),
|
|
mode,
|
|
recursive,
|
|
);
|
|
|
|
if (recursive) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads the entire contents of the `basePath` directory. This function does
|
|
* not validate `basePath` as a directory. It is passed directly to
|
|
* `binding.readdirRecursive`, or to `binding.readdir` for each directory when
|
|
* the permission model is enabled, since that checks every directory on the
|
|
* main thread where the native walk can not.
|
|
* @param {string | Buffer} basePath
|
|
* @param {{ encoding: string, withFileTypes: boolean }} options
|
|
* @param {(
|
|
* err?: Error,
|
|
* files?: string[] | Buffer[] | Dirent[]
|
|
* ) => any} callback
|
|
* @returns {void}
|
|
*/
|
|
function readdirRecursive(basePath, options, callback) {
|
|
if (permission.isEnabled()) {
|
|
readdirRecursiveWithPermissionModel(basePath, options, callback);
|
|
return;
|
|
}
|
|
const withFileTypes = !!options.withFileTypes;
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = (err, result) => {
|
|
if (err) {
|
|
callback(err);
|
|
return;
|
|
}
|
|
callback(null, withFileTypes ? getRecursiveDirents(basePath, result) : result);
|
|
};
|
|
binding.readdirRecursive(basePath, options.encoding, withFileTypes, req);
|
|
}
|
|
|
|
function readdirRecursiveWithPermissionModel(basePath, options, callback) {
|
|
const { encoding } = options;
|
|
const context = {
|
|
withFileTypes: !!options.withFileTypes,
|
|
results: [],
|
|
dirs: [basePath],
|
|
prefixes: [''],
|
|
};
|
|
|
|
let i = 0;
|
|
|
|
/**
|
|
* Reads one directory from `context.dirs` and then moves on to the next
|
|
* one, or calls back once none are left.
|
|
* @param {string} path
|
|
* @param {string} prefix path of this directory relative to `basePath`
|
|
*/
|
|
function read(path, prefix) {
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = (err, result) => {
|
|
if (err) {
|
|
callback(err);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
collectRecursiveReaddirResult(path, prefix, result, context);
|
|
} catch (err) {
|
|
callback(err);
|
|
return;
|
|
}
|
|
|
|
if (i < context.dirs.length) {
|
|
read(context.dirs[i], context.prefixes[i++]);
|
|
} else {
|
|
callback(null, context.results);
|
|
}
|
|
};
|
|
|
|
binding.readdir(path, encoding, true, req);
|
|
}
|
|
|
|
read(context.dirs[i], context.prefixes[i++]);
|
|
}
|
|
|
|
/**
|
|
* Synchronously reads the entire contents of the `basePath` directory, see
|
|
* `readdirRecursive`.
|
|
* @param {string | Buffer} basePath
|
|
* @param {{ encoding: string, withFileTypes: boolean }} options
|
|
* @returns {string[] | Buffer[] | Dirent[]}
|
|
*/
|
|
function readdirSyncRecursive(basePath, options) {
|
|
const withFileTypes = !!options.withFileTypes;
|
|
if (permission.isEnabled()) {
|
|
const context = {
|
|
withFileTypes,
|
|
results: [],
|
|
dirs: [basePath],
|
|
prefixes: [''],
|
|
};
|
|
for (let i = 0; i < context.dirs.length; i++) {
|
|
const dir = context.dirs[i];
|
|
const result = binding.readdir(dir, options.encoding, true);
|
|
collectRecursiveReaddirResult(dir, context.prefixes[i], result, context);
|
|
}
|
|
return context.results;
|
|
}
|
|
const result = binding.readdirRecursive(basePath, options.encoding, withFileTypes);
|
|
return result !== undefined && withFileTypes ? getRecursiveDirents(basePath, result) : result;
|
|
}
|
|
|
|
/**
|
|
* Reads the contents of a directory.
|
|
* @param {string | Buffer | URL} path
|
|
* @param {string | {
|
|
* encoding?: string;
|
|
* withFileTypes?: boolean;
|
|
* recursive?: boolean;
|
|
* }} [options]
|
|
* @param {(
|
|
* err?: Error,
|
|
* files?: string[] | Buffer[] | Dirent[]
|
|
* ) => any} callback
|
|
* @returns {void}
|
|
*/
|
|
function readdir(path, options, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = undefined;
|
|
}
|
|
|
|
callback = makeCallback(callback);
|
|
options = getOptions(options);
|
|
path = getValidatedPath(path);
|
|
if (options.recursive != null) {
|
|
validateBoolean(options.recursive, 'options.recursive');
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsResult(h.readdir(path, options), callback)) return;
|
|
|
|
if (options.recursive) {
|
|
readdirRecursive(path, options, callback);
|
|
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(
|
|
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');
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.readdirSync(path, options);
|
|
if (result !== undefined) return result;
|
|
}
|
|
|
|
if (options.recursive) {
|
|
return readdirSyncRecursive(path, options);
|
|
}
|
|
|
|
const result = binding.readdir(
|
|
path,
|
|
options.encoding,
|
|
!!options.withFileTypes,
|
|
);
|
|
|
|
return result !== undefined && options.withFileTypes ? getDirents(path, result) : result;
|
|
}
|
|
|
|
/**
|
|
* Invokes the callback with the `fs.Stats`
|
|
* for the file descriptor.
|
|
* @param {number} fd
|
|
* @param {{ bigint?: boolean, signal?: AbortSignal }} [options]
|
|
* @param {(
|
|
* err?: Error,
|
|
* stats?: Stats
|
|
* ) => any} [callback]
|
|
* @returns {void}
|
|
*/
|
|
function fstat(fd, options = { __proto__: null, bigint: false }, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = kEmptyObject;
|
|
} else if (options === null || typeof options !== 'object') {
|
|
options = kEmptyObject;
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsResult(h.fstat(fd, options), callback)) return;
|
|
|
|
callback = makeStatsCallback(callback);
|
|
|
|
if (options.signal !== undefined) validateAbortSignal(options.signal, 'options.signal');
|
|
if (checkAborted(options.signal, callback)) return;
|
|
|
|
const req = new FSReqCallback(options.bigint);
|
|
bindSignalToReq(req, options.signal, callback);
|
|
binding.fstat(fd, options.bigint, req);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the `fs.Stats` for the symbolic link
|
|
* referred to by the `path`.
|
|
* @param {string | Buffer | URL} path
|
|
* @param {{ bigint?: boolean, signal?: AbortSignal }} [options]
|
|
* @param {(
|
|
* err?: Error,
|
|
* stats?: Stats
|
|
* ) => any} callback
|
|
* @returns {void}
|
|
*/
|
|
function lstat(path, options = { __proto__: null, bigint: false }, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = kEmptyObject;
|
|
} else if (options === null || typeof options !== 'object') {
|
|
options = kEmptyObject;
|
|
} else {
|
|
options = getOptions(options, { bigint: false });
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsResult(h.lstat(path, options), callback)) return;
|
|
|
|
callback = makeStatsCallback(callback);
|
|
path = getValidatedPath(path);
|
|
if (permission.isEnabled() && !permission.has('fs.read', path) &&
|
|
!permission.isAuditMode()) {
|
|
const resource = BufferIsBuffer(path) ? BufferToString(path) : path;
|
|
callback(new ERR_ACCESS_DENIED('Access to this API has been restricted', 'FileSystemRead', resource));
|
|
return;
|
|
}
|
|
|
|
if (options.signal !== undefined) validateAbortSignal(options.signal, 'options.signal');
|
|
if (checkAborted(options.signal, callback)) return;
|
|
|
|
const req = new FSReqCallback(options.bigint);
|
|
bindSignalToReq(req, options.signal, callback);
|
|
binding.lstat(path, options.bigint, req);
|
|
}
|
|
|
|
/**
|
|
* Asynchronously gets the stats of a file.
|
|
* @param {string | Buffer | URL} path
|
|
* @param {{ bigint?: boolean, signal?: AbortSignal }} [options]
|
|
* @param {(
|
|
* err?: Error,
|
|
* stats?: Stats
|
|
* ) => any} callback
|
|
* @returns {void}
|
|
*/
|
|
function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = kEmptyObject;
|
|
} else if (options === null || typeof options !== 'object') {
|
|
options = kEmptyObject;
|
|
} else {
|
|
options = getOptions(options, { bigint: false });
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsResult(h.stat(path, options), callback)) return;
|
|
|
|
callback = makeStatsCallback(callback);
|
|
path = getValidatedPath(path);
|
|
|
|
if (options.signal !== undefined) validateAbortSignal(options.signal, 'options.signal');
|
|
if (checkAborted(options.signal, callback)) return;
|
|
|
|
const req = new FSReqCallback(options.bigint);
|
|
bindSignalToReq(req, options.signal, callback);
|
|
binding.stat(path, options.bigint, req, options.throwIfNoEntry);
|
|
}
|
|
|
|
function statfs(path, options = { __proto__: null, bigint: false }, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = kEmptyObject;
|
|
}
|
|
validateFunction(callback, 'cb');
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
let result;
|
|
try {
|
|
result = h.statfsSync(path, options);
|
|
} catch (err) {
|
|
process.nextTick(callback, err);
|
|
return;
|
|
}
|
|
if (result !== undefined) {
|
|
process.nextTick(callback, null, result);
|
|
return;
|
|
}
|
|
}
|
|
|
|
path = getValidatedPath(path);
|
|
const req = new FSReqCallback(options.bigint);
|
|
req.oncomplete = (err, stats) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
callback(err, getStatFsFromBinding(stats));
|
|
};
|
|
binding.statfs(path, options.bigint, req);
|
|
}
|
|
|
|
/**
|
|
* Synchronously retrieves the `fs.Stats` for
|
|
* the file descriptor.
|
|
* @param {number} fd
|
|
* @param {{ bigint?: boolean; }} [options]
|
|
* @returns {Stats | undefined}
|
|
*/
|
|
function fstatSync(fd, options = { __proto__: null, bigint: false }) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.fstatSync(fd);
|
|
if (result !== undefined) return result;
|
|
}
|
|
const stats = binding.fstat(fd, options.bigint, undefined, false);
|
|
if (stats === undefined) {
|
|
return;
|
|
}
|
|
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 | undefined}
|
|
*/
|
|
function lstatSync(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
try {
|
|
const result = h.lstatSync(path, options);
|
|
if (result !== undefined) return result;
|
|
} catch (err) {
|
|
if (err?.code === 'ENOENT' && options?.throwIfNoEntry === false) return;
|
|
throw err;
|
|
}
|
|
}
|
|
path = getValidatedPath(path);
|
|
if (permission.isEnabled() && !permission.has('fs.read', path) &&
|
|
!permission.isAuditMode()) {
|
|
const resource = BufferIsBuffer(path) ? BufferToString(path) : path;
|
|
throw new ERR_ACCESS_DENIED('Access to this API has been restricted', 'FileSystemRead', resource);
|
|
}
|
|
const stats = binding.lstat(
|
|
path,
|
|
options.bigint,
|
|
undefined,
|
|
options.throwIfNoEntry,
|
|
);
|
|
|
|
if (stats === undefined) {
|
|
return;
|
|
}
|
|
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 = { __proto__: null, bigint: false, throwIfNoEntry: true }) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
try {
|
|
const result = h.statSync(path, options);
|
|
if (result !== undefined) return result;
|
|
} catch (err) {
|
|
if (err?.code === 'ENOENT' && options?.throwIfNoEntry === false) return undefined;
|
|
throw err;
|
|
}
|
|
}
|
|
const stats = binding.stat(
|
|
getValidatedPath(path),
|
|
options.bigint,
|
|
undefined,
|
|
options.throwIfNoEntry,
|
|
);
|
|
if (stats === undefined) {
|
|
return undefined;
|
|
}
|
|
return getStatsFromBinding(stats);
|
|
}
|
|
|
|
function statfsSync(path, options = { __proto__: null, bigint: false }) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.statfsSync(path, options);
|
|
if (result !== undefined) return result;
|
|
}
|
|
|
|
const stats = binding.statfs(getValidatedPath(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) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = undefined;
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsResult(h.readlink(path, options), callback)) return;
|
|
|
|
callback = makeCallback(callback);
|
|
options = getOptions(options);
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.readlink(getValidatedPath(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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.readlinkSync(path, options);
|
|
if (result !== undefined) return result;
|
|
}
|
|
options = getOptions(options);
|
|
return binding.readlink(getValidatedPath(path), options.encoding);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
if (callback === undefined) {
|
|
callback = makeCallback(type);
|
|
type = undefined;
|
|
} else {
|
|
validateOneOf(type, 'type', ['dir', 'file', 'junction', null, undefined]);
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.symlink(target, path, type), callback)) return;
|
|
|
|
// Due to the nature of Node.js runtime, symlinks has different edge cases that can bypass
|
|
// the permission model security guarantees. Thus, this API is disabled unless fs.read
|
|
// and fs.write permission has been given.
|
|
if (permission.isEnabled() && !permission.has('fs') &&
|
|
!permission.isAuditMode()) {
|
|
callback(new ERR_ACCESS_DENIED('fs.symlink API requires full fs.read and fs.write permissions.'));
|
|
return;
|
|
}
|
|
|
|
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,
|
|
path,
|
|
resolvedFlags,
|
|
req,
|
|
);
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
const destination = preprocessSymlinkDestination(target, type, path);
|
|
|
|
const flags = stringToSymlinkType(type);
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.symlink(destination, 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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.symlinkSync(target, path, type);
|
|
if (result !== undefined) return;
|
|
}
|
|
validateOneOf(type, 'type', ['dir', 'file', 'junction', null, undefined]);
|
|
if (isWindows && type == null) {
|
|
const absoluteTarget = pathModule.resolve(`${path}`, '..', `${target}`);
|
|
if (statSync(absoluteTarget, { throwIfNoEntry: false })?.isDirectory()) {
|
|
type = 'dir';
|
|
}
|
|
}
|
|
|
|
// Due to the nature of Node.js runtime, symlinks has different edge cases that can bypass
|
|
// the permission model security guarantees. Thus, this API is disabled unless fs.read
|
|
// and fs.write permission has been given.
|
|
if (permission.isEnabled() && !permission.has('fs') &&
|
|
!permission.isAuditMode()) {
|
|
throw new ERR_ACCESS_DENIED('fs.symlink API requires full fs.read and fs.write permissions.');
|
|
}
|
|
|
|
target = getValidatedPath(target, 'target');
|
|
path = getValidatedPath(path);
|
|
|
|
binding.symlink(
|
|
preprocessSymlinkDestination(target, type, path),
|
|
path,
|
|
stringToSymlinkType(type),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.link(existingPath, newPath), callback)) return;
|
|
|
|
existingPath = getValidatedPath(existingPath, 'existingPath');
|
|
newPath = getValidatedPath(newPath, 'newPath');
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
|
|
binding.link(existingPath, 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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.linkSync(existingPath, newPath);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
existingPath = getValidatedPath(existingPath, 'existingPath');
|
|
newPath = getValidatedPath(newPath, 'newPath');
|
|
|
|
binding.link(
|
|
existingPath,
|
|
newPath,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Asynchronously removes a file or symbolic link.
|
|
* @param {string | Buffer | URL} path
|
|
* @param {(err?: Error) => any} callback
|
|
* @returns {void}
|
|
*/
|
|
function unlink(path, callback) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.unlink(path), callback)) return;
|
|
|
|
callback = makeCallback(callback);
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.unlink(getValidatedPath(path), req);
|
|
}
|
|
|
|
/**
|
|
* Synchronously removes a file or symbolic link.
|
|
* @param {string | Buffer | URL} path
|
|
* @returns {void}
|
|
*/
|
|
function unlinkSync(path) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.unlinkSync(path);
|
|
if (result !== undefined) return;
|
|
}
|
|
binding.unlink(getValidatedPath(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) {
|
|
mode = parseFileMode(mode, 'mode');
|
|
callback = makeCallback(callback);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.fchmod(fd, mode), callback)) return;
|
|
|
|
if (permission.isEnabled()) {
|
|
callback(new ERR_ACCESS_DENIED('fchmod API is disabled when Permission Model is enabled.'));
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
mode = parseFileMode(mode, 'mode');
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.fchmodSync(fd, mode);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
if (permission.isEnabled()) {
|
|
throw new ERR_ACCESS_DENIED('fchmod API is disabled when Permission Model is enabled.');
|
|
}
|
|
binding.fchmod(fd, mode);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
validateFunction(callback, 'cb');
|
|
mode = parseFileMode(mode, 'mode');
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.lchmod(path, mode), callback)) return;
|
|
|
|
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.
|
|
try {
|
|
fs.fchmodSync(fd, mode);
|
|
} finally {
|
|
fs.closeSync(fd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.chmod(path, mode), callback)) return;
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.chmod(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 h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.chmodSync(path, mode);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
binding.chmod(path, mode);
|
|
}
|
|
|
|
/**
|
|
* 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 h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.lchown(path, uid, gid), callback)) return;
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.lchown(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 h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.lchownSync(path, uid, gid);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
binding.lchown(path, uid, gid);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
validateInteger(uid, 'uid', -1, kMaxUserId);
|
|
validateInteger(gid, 'gid', -1, kMaxUserId);
|
|
callback = makeCallback(callback);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.fchown(fd), callback)) return;
|
|
|
|
if (permission.isEnabled()) {
|
|
callback(new ERR_ACCESS_DENIED('fchown API is disabled when Permission Model is enabled.'));
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
validateInteger(uid, 'uid', -1, kMaxUserId);
|
|
validateInteger(gid, 'gid', -1, kMaxUserId);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.fchownSync(fd);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
if (permission.isEnabled()) {
|
|
throw new ERR_ACCESS_DENIED('fchown API is disabled when Permission Model is enabled.');
|
|
}
|
|
|
|
binding.fchown(fd, uid, gid);
|
|
}
|
|
|
|
/**
|
|
* 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 h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.chown(path, uid, gid), callback)) return;
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.chown(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 h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.chownSync(path, uid, gid);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
binding.chown(path, uid, gid);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
atime = toUnixTimestamp(atime);
|
|
mtime = toUnixTimestamp(mtime);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.utimes(path, atime, mtime), callback)) return;
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.utimes(path, atime, 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);
|
|
atime = toUnixTimestamp(atime);
|
|
mtime = toUnixTimestamp(mtime);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.utimesSync(path, atime, mtime);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
binding.utimes(path, atime, mtime);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
atime = toUnixTimestamp(atime, 'atime');
|
|
mtime = toUnixTimestamp(mtime, 'mtime');
|
|
callback = makeCallback(callback);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.futimes(fd, atime, mtime), callback)) return;
|
|
|
|
if (permission.isEnabled()) {
|
|
callback(new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.'));
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
atime = toUnixTimestamp(atime, 'atime');
|
|
mtime = toUnixTimestamp(mtime, 'mtime');
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.futimesSync(fd, atime, mtime);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
if (permission.isEnabled()) {
|
|
throw new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.');
|
|
}
|
|
|
|
binding.futimes(fd, atime, mtime);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
atime = toUnixTimestamp(atime);
|
|
mtime = toUnixTimestamp(mtime);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.lutimes(path, atime, mtime), callback)) return;
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.lutimes(path, atime, 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);
|
|
atime = toUnixTimestamp(atime);
|
|
mtime = toUnixTimestamp(mtime);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.lutimesSync(path, atime, mtime);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
binding.lutimes(path, atime, mtime);
|
|
}
|
|
|
|
function writeAll(fd, isUserFd, buffer, offset, length, signal, flush, 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 (!flush) {
|
|
if (isUserFd) {
|
|
callback(null);
|
|
} else {
|
|
fs.close(fd, callback);
|
|
}
|
|
} else {
|
|
fs.fsync(fd, (syncErr) => {
|
|
if (syncErr) {
|
|
if (isUserFd) {
|
|
callback(syncErr);
|
|
} else {
|
|
fs.close(fd, (err) => {
|
|
callback(aggregateTwoErrors(err, syncErr));
|
|
});
|
|
}
|
|
} else if (isUserFd) {
|
|
callback(null);
|
|
} else {
|
|
fs.close(fd, callback);
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
offset += written;
|
|
length -= written;
|
|
writeAll(fd, isUserFd, buffer, offset, length, signal, flush, 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;
|
|
* flush?: boolean;
|
|
* } | string} [options]
|
|
* @param {(err?: Error) => any} callback
|
|
* @returns {void}
|
|
*/
|
|
function writeFile(path, data, options, callback) {
|
|
callback ||= options;
|
|
validateFunction(callback, 'cb');
|
|
|
|
options = getOptions(typeof options === 'function' ? null : options, {
|
|
encoding: 'utf8',
|
|
mode: 0o666,
|
|
flag: 'w',
|
|
flush: false,
|
|
});
|
|
const flush = options.flush ?? false;
|
|
validateBoolean(flush, 'options.flush');
|
|
parseFileMode(options.mode, 'mode', 0o666);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
if (checkAborted(options.signal, callback)) return;
|
|
if (vfsVoid(h.writeFile(path, data, options), callback)) return;
|
|
}
|
|
|
|
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, flush, callback);
|
|
return;
|
|
}
|
|
|
|
if (checkAborted(options.signal, callback))
|
|
return;
|
|
|
|
if (!flush) {
|
|
// Open + write + close in one thread pool round trip.
|
|
const signal = options.signal;
|
|
path = getValidatedPath(path);
|
|
const job = new WriteFileJob(path, stringToFlags(flag, 'options.flag'),
|
|
parseFileMode(options.mode, 'mode', 0o666), data);
|
|
job.ondone = signal == null ? callback : (err) => {
|
|
// An abort that arrived while the write was in flight still wins.
|
|
callback(signal.aborted && !err ? new AbortError(undefined, { cause: signal.reason }) : err);
|
|
};
|
|
const accessError = job.run(path);
|
|
if (accessError !== undefined) {
|
|
callback(accessError);
|
|
}
|
|
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, flush, 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;
|
|
* flush?: boolean;
|
|
* } | string} [options]
|
|
* @returns {void}
|
|
*/
|
|
function writeFileSync(path, data, options) {
|
|
options = getOptions(options, {
|
|
encoding: 'utf8',
|
|
mode: 0o666,
|
|
flag: 'w',
|
|
flush: false,
|
|
});
|
|
const flush = options.flush ?? false;
|
|
validateBoolean(flush, 'options.flush');
|
|
parseFileMode(options.mode, 'mode', 0o666);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.writeFileSync(path, data, options);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
const flag = options.flag || 'w';
|
|
|
|
// C++ fast path for string data and UTF8 encoding
|
|
if (typeof data === 'string' && (options.encoding === 'utf8' || options.encoding === 'utf-8')) {
|
|
if (isInt32(path)) {
|
|
// V8 does not report -0 as an int32, so it would reach the binding as a
|
|
// path instead of a file descriptor.
|
|
path |= 0;
|
|
} else {
|
|
path = getValidatedPath(path);
|
|
}
|
|
|
|
return binding.writeFileUtf8(
|
|
path,
|
|
data,
|
|
stringToFlags(flag),
|
|
parseFileMode(options.mode, 'mode', 0o666),
|
|
);
|
|
}
|
|
|
|
if (!isArrayBufferView(data)) {
|
|
validateStringAfterArrayBufferView(data, 'data');
|
|
data = Buffer.from(data, options.encoding || 'utf8');
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (flush) {
|
|
fs.fsyncSync(fd);
|
|
}
|
|
} 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;
|
|
* flush?: boolean;
|
|
* } | string} [options]
|
|
* @param {(err?: Error) => any} callback
|
|
* @returns {void}
|
|
*/
|
|
function appendFile(path, data, options, callback) {
|
|
callback ||= options;
|
|
validateFunction(callback, 'cb');
|
|
|
|
options = getOptions(typeof options === 'function' ? null : options, {
|
|
encoding: 'utf8', mode: 0o666, flag: 'a',
|
|
});
|
|
parseFileMode(options.mode, 'mode', 0o666);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
if (checkAborted(options.signal, callback)) return;
|
|
if (vfsVoid(h.appendFile(path, data, options), callback)) return;
|
|
}
|
|
|
|
// 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' });
|
|
parseFileMode(options.mode, 'mode', 0o666);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.appendFileSync(path, data, options);
|
|
if (result !== undefined) return;
|
|
}
|
|
|
|
// 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;
|
|
* throwIfNoEntry?: boolean;
|
|
* }} [options]
|
|
* @param {(
|
|
* eventType?: string,
|
|
* filename?: string | Buffer
|
|
* ) => any} [listener]
|
|
* @returns {watchers.FSWatcher}
|
|
*/
|
|
function watch(filename, options, listener) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.watch(filename, options, listener);
|
|
if (result !== undefined) return result;
|
|
}
|
|
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.throwIfNoEntry === undefined) options.throwIfNoEntry = true;
|
|
|
|
let watcher;
|
|
const watchers = require('internal/fs/watchers');
|
|
const path = getValidatedPath(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 && !isMacOS && !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,
|
|
options.ignore,
|
|
options.throwIfNoEntry);
|
|
}
|
|
|
|
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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.watchFile(filename, options, listener);
|
|
if (result !== undefined) return result;
|
|
}
|
|
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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
if (h.unwatchFile(filename, listener)) return;
|
|
}
|
|
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;
|
|
let getRealpathRootLstatPath;
|
|
if (isWindows) {
|
|
// Regex to find the device root on Windows (e.g. 'c:\\'), including trailing
|
|
// slash.
|
|
const splitRootRe = /^(?:[a-zA-Z]:|[\\/]{2}[^\\/]+[\\/][^\\/]+)?[\\/]*/;
|
|
const namespacedDriveRootRe = /^\\\\\?\\([a-zA-Z]:\\)$/;
|
|
splitRoot = function splitRoot(str) {
|
|
return SideEffectFreeRegExpPrototypeExec(splitRootRe, str)[0];
|
|
};
|
|
|
|
// The root probe is the only use of this path. Passing a namespaced drive
|
|
// root to the binding would lose its trailing separator during resolution.
|
|
getRealpathRootLstatPath = function getRealpathRootLstatPath(path) {
|
|
const match = SideEffectFreeRegExpPrototypeExec(namespacedDriveRootRe, path);
|
|
return match === null ? path : match[1];
|
|
};
|
|
} 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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.realpathSync(p, options);
|
|
if (result !== undefined) return result;
|
|
}
|
|
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;
|
|
// Whether the symlink this walk resolved last pointed at a pipe or a
|
|
// socket, which is where the walk stops. It cannot be read back from the
|
|
// shared stat buffer, which holds the last stat made anywhere in the
|
|
// process rather than the last one made here.
|
|
let reachedPipeOrSocket = false;
|
|
|
|
// 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 out = binding.lstat(
|
|
getRealpathRootLstatPath(base), false, undefined, true /* throwIfNoEntry */);
|
|
if (out === undefined) {
|
|
return;
|
|
}
|
|
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 (reachedPipeOrSocket) {
|
|
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 stats = binding.lstat(base, true, undefined, true /* throwIfNoEntry */);
|
|
if (stats === undefined) {
|
|
return;
|
|
}
|
|
|
|
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 targetStats = binding.stat(base, false, undefined, true);
|
|
reachedPipeOrSocket = isFileType(targetStats, S_IFIFO) ||
|
|
isFileType(targetStats, S_IFSOCK);
|
|
linkTarget = binding.readlink(base, undefined);
|
|
}
|
|
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 out = binding.lstat(
|
|
getRealpathRootLstatPath(base), false, undefined, true /* throwIfNoEntry */);
|
|
if (out === undefined) {
|
|
return;
|
|
}
|
|
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) => {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.realpathSync(path, options);
|
|
if (result !== undefined) return result;
|
|
}
|
|
options = getOptions(options);
|
|
return binding.realpath(
|
|
getValidatedPath(path),
|
|
options.encoding,
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = undefined;
|
|
} else {
|
|
validateFunction(callback, 'cb');
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsResult(h.realpath(p, options), callback)) return;
|
|
|
|
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();
|
|
// Whether the symlink this walk resolved last pointed at a pipe or a
|
|
// socket, which is where the walk stops. It cannot be read back from the
|
|
// shared stat buffer, which holds the last stat made anywhere in the
|
|
// process rather than the last one made here.
|
|
let reachedPipeOrSocket = false;
|
|
|
|
// 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(getRealpathRootLstatPath(base), (err) => {
|
|
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 (reachedPipeOrSocket) {
|
|
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, targetStats) => {
|
|
if (err) return callback(err);
|
|
|
|
reachedPipeOrSocket = targetStats.isFIFO() || targetStats.isSocket();
|
|
|
|
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(getRealpathRootLstatPath(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);
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsResult(h.realpath(path, options), callback)) return;
|
|
options = getOptions(options);
|
|
path = getValidatedPath(path);
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.realpath(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);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsResult(h.mkdtemp(prefix, typeof options === 'function' ? undefined : options), callback)) return;
|
|
|
|
options = getOptions(options);
|
|
if (BufferIsBuffer(prefix)) {
|
|
options = { ...options, encoding: 'buffer' };
|
|
}
|
|
prefix = getValidatedPath(prefix, 'prefix');
|
|
warnOnNonPortableTemplate(prefix);
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = callback;
|
|
binding.mkdtemp(prefix, 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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.mkdtempSync(prefix, options);
|
|
if (result !== undefined) return result;
|
|
}
|
|
|
|
options = getOptions(options);
|
|
if (BufferIsBuffer(prefix)) {
|
|
options = { ...options, encoding: 'buffer' };
|
|
}
|
|
prefix = getValidatedPath(prefix, 'prefix');
|
|
warnOnNonPortableTemplate(prefix);
|
|
return binding.mkdtemp(prefix, options.encoding);
|
|
}
|
|
|
|
/**
|
|
* Synchronously creates a unique temporary directory.
|
|
* The returned value is a disposable object which removes the
|
|
* directory and its contents when disposed.
|
|
* @param {string | Buffer | URL} prefix
|
|
* @param {string | { encoding?: string; }} [options]
|
|
* @returns {object} A disposable object with a "path" property.
|
|
*/
|
|
function mkdtempDisposableSync(prefix, options) {
|
|
options = getOptions(options);
|
|
if (BufferIsBuffer(prefix)) {
|
|
options = { ...options, encoding: 'buffer' };
|
|
}
|
|
prefix = getValidatedPath(prefix, 'prefix');
|
|
warnOnNonPortableTemplate(prefix);
|
|
|
|
const path = binding.mkdtemp(prefix, options.encoding);
|
|
// Stash the full path in case of process.chdir()
|
|
const fullPath = pathModule.resolve(process.cwd(), path);
|
|
|
|
const remove = () => {
|
|
binding.rmSync(fullPath, 0 /* maxRetries */, true /* recursive */, 100 /* retryDelay */);
|
|
};
|
|
return {
|
|
path,
|
|
remove,
|
|
[SymbolDispose]() {
|
|
remove();
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates and opens a unique temporary file.
|
|
* @param {string | Buffer | URL} prefix
|
|
* @param {string | { encoding?: string; }} [options]
|
|
* @param {(err?: Error, file?: { path: string | Buffer, fd: number }) => any} callback
|
|
* @returns {void}
|
|
*/
|
|
function mkstemp(prefix, options, callback) {
|
|
callback = makeCallback(typeof options === 'function' ? options : callback);
|
|
|
|
options = getOptions(options);
|
|
if (BufferIsBuffer(prefix)) {
|
|
options = { ...options, encoding: 'buffer' };
|
|
}
|
|
prefix = getValidatedPath(prefix, 'prefix');
|
|
warnOnNonPortableTemplate(prefix);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsResult(h.mkstemp(prefix, options), callback)) return;
|
|
|
|
const req = new FSReqCallback();
|
|
req.oncomplete = (err, result) => {
|
|
if (err) return callback(err);
|
|
callback(null, { path: result[0], fd: result[1] });
|
|
};
|
|
binding.mkstemp(prefix, options.encoding, req);
|
|
}
|
|
|
|
/**
|
|
* Synchronously creates and opens a unique temporary file.
|
|
* @param {string | Buffer | URL} prefix
|
|
* @param {string | { encoding?: string; }} [options]
|
|
* @returns {{ path: string | Buffer, fd: number }}
|
|
*/
|
|
function mkstempSync(prefix, options) {
|
|
options = getOptions(options);
|
|
if (BufferIsBuffer(prefix)) {
|
|
options = { ...options, encoding: 'buffer' };
|
|
}
|
|
prefix = getValidatedPath(prefix, 'prefix');
|
|
warnOnNonPortableTemplate(prefix);
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.mkstempSync(prefix, options);
|
|
if (result !== undefined) return result;
|
|
}
|
|
|
|
const result = binding.mkstemp(prefix, options.encoding);
|
|
return { path: result[0], fd: result[1] };
|
|
}
|
|
|
|
/**
|
|
* 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 {(err?: Error) => any} callback
|
|
* @returns {void}
|
|
*/
|
|
function copyFile(src, dest, mode, callback) {
|
|
if (typeof mode === 'function') {
|
|
callback = mode;
|
|
mode = 0;
|
|
}
|
|
|
|
const h = vfsState.handlers;
|
|
if (h !== null && vfsVoid(h.copyFile(src, dest, mode), callback)) return;
|
|
|
|
src = getValidatedPath(src, 'src');
|
|
dest = getValidatedPath(dest, 'dest');
|
|
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) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.copyFileSync(src, dest, mode);
|
|
if (result !== undefined) return;
|
|
}
|
|
binding.copyFile(
|
|
getValidatedPath(src, 'src'),
|
|
getValidatedPath(dest, 'dest'),
|
|
mode,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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 {(err?: Error) => 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 = getValidatedPath(src, 'src');
|
|
dest = 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 = getValidatedPath(src, 'src');
|
|
dest = 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;
|
|
* signal?: AbortSignal | null;
|
|
* }} [options]
|
|
* @returns {ReadStream}
|
|
*/
|
|
function createReadStream(path, options) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.createReadStream(path, options);
|
|
if (result !== undefined) return result;
|
|
}
|
|
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;
|
|
* signal?: AbortSignal | null;
|
|
* highWaterMark?: number;
|
|
* flush?: boolean;
|
|
* }} [options]
|
|
* @returns {WriteStream}
|
|
*/
|
|
function createWriteStream(path, options) {
|
|
const h = vfsState.handlers;
|
|
if (h !== null) {
|
|
const result = h.createWriteStream(path, options);
|
|
if (result !== undefined) return result;
|
|
}
|
|
lazyLoadStreams();
|
|
return new WriteStream(path, options);
|
|
}
|
|
|
|
const lazyGlob = getLazy(() => require('internal/fs/glob').Glob);
|
|
|
|
function glob(pattern, options, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = undefined;
|
|
}
|
|
callback = makeCallback(callback);
|
|
|
|
const Glob = lazyGlob();
|
|
PromisePrototypeThen(
|
|
new Glob(pattern, options).globAll(),
|
|
(res) => callback(null, res),
|
|
callback,
|
|
);
|
|
}
|
|
|
|
function globSync(pattern, options) {
|
|
const Glob = lazyGlob();
|
|
return new Glob(pattern, options).globSync();
|
|
}
|
|
|
|
|
|
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,
|
|
glob,
|
|
globSync,
|
|
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,
|
|
mkdtempDisposableSync,
|
|
mkstemp,
|
|
mkstempSync,
|
|
open,
|
|
openSync,
|
|
openAsBlob,
|
|
openAsBlobSync,
|
|
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;
|
|
},
|
|
|
|
get Utf8Stream() {
|
|
lazyLoadUtf8Stream();
|
|
return Utf8Stream;
|
|
},
|
|
|
|
// For tests
|
|
_toUnixTimestamp: toUnixTimestamp,
|
|
};
|
|
|
|
defineLazyProperties(
|
|
fs,
|
|
'internal/fs/dir',
|
|
['Dir', 'opendir', 'opendirSync'],
|
|
);
|
|
|
|
ObjectDefineProperties(fs, {
|
|
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;
|
|
},
|
|
},
|
|
});
|