Files
node/lib/internal/fs/utils.js
T
Marco 1a885caa6e fs: add mkstemp()
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>
2026-10-19 02:34:09 +00:00

1243 lines
35 KiB
JavaScript

'use strict';
const {
ArrayIsArray,
ArrayPrototypePush,
BigInt,
Date,
DateNow,
DatePrototypeGetTime,
ErrorCaptureStackTrace,
FunctionPrototypeCall,
MathFloor,
MathMin,
MathRound,
Number,
NumberIsFinite,
ObjectDefineProperties,
ObjectIs,
ObjectSetPrototypeOf,
ReflectOwnKeys,
RegExpPrototypeSymbolReplace,
StringPrototypeEndsWith,
StringPrototypeIncludes,
Symbol,
TypedArrayPrototypeAt,
TypedArrayPrototypeIncludes,
globalThis,
} = primordials;
const { Buffer } = require('buffer');
const { isBuffer: BufferIsBuffer } = Buffer;
const {
UVException,
codes: {
ERR_FS_EISDIR,
ERR_INCOMPATIBLE_OPTION_PAIR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_NO_TEMPORAL,
ERR_OUT_OF_RANGE,
},
hideStackFrames,
} = require('internal/errors');
const {
isArrayBufferView,
isBigInt64Array,
isDate,
isUint8Array,
} = require('internal/util/types');
const {
deprecate,
isWindows,
kEmptyObject,
once,
setOwnProperty,
} = require('internal/util');
const { toPathIfFileURL } = require('internal/url');
const {
validateAbortSignal,
validateBoolean,
validateBuffer,
validateFunction,
validateInt32,
validateInteger,
validateObject,
validateUint32,
} = require('internal/validators');
const pathModule = require('path');
const binding = internalBinding('fs');
const {
CHAR_BACKWARD_SLASH,
CHAR_FORWARD_SLASH,
} = require('internal/constants');
const kType = Symbol('type');
const kStats = Symbol('stats');
const kPartialAtimeNs = Symbol('partialAtimeNs');
const kPartialMtimeNs = Symbol('partialMtimeNs');
const kPartialCtimeNs = Symbol('partialCtimeNs');
const kPartialBirthtimeNs = Symbol('kPartialBirthtimeNs');
const assert = require('internal/assert');
const {
fs: {
F_OK = 0,
W_OK = 0,
R_OK = 0,
X_OK = 0,
COPYFILE_EXCL,
COPYFILE_FICLONE,
COPYFILE_FICLONE_FORCE,
O_APPEND,
O_CREAT,
O_EXCL,
O_RDONLY,
O_RDWR,
O_SYNC,
O_TRUNC,
O_WRONLY,
S_IFBLK,
S_IFCHR,
S_IFDIR,
S_IFIFO,
S_IFLNK,
S_IFMT,
S_IFREG,
S_IFSOCK,
UV_FS_SYMLINK_DIR,
UV_FS_SYMLINK_JUNCTION,
UV_DIRENT_UNKNOWN,
UV_DIRENT_FILE,
UV_DIRENT_DIR,
UV_DIRENT_LINK,
UV_DIRENT_FIFO,
UV_DIRENT_SOCKET,
UV_DIRENT_CHAR,
UV_DIRENT_BLOCK,
},
os: {
errno: {
EISDIR,
},
},
} = internalBinding('constants');
// The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be
// available on specific systems. They can be used in combination as well
// (F_OK | R_OK | W_OK | X_OK).
const kMinimumAccessMode = MathMin(F_OK, W_OK, R_OK, X_OK);
const kMaximumAccessMode = F_OK | W_OK | R_OK | X_OK;
const kDefaultCopyMode = 0;
// The copy modes can be any of COPYFILE_EXCL, COPYFILE_FICLONE or
// COPYFILE_FICLONE_FORCE. They can be used in combination as well
// (COPYFILE_EXCL | COPYFILE_FICLONE | COPYFILE_FICLONE_FORCE).
const kMinimumCopyMode = MathMin(
kDefaultCopyMode,
COPYFILE_EXCL,
COPYFILE_FICLONE,
COPYFILE_FICLONE_FORCE,
);
const kMaximumCopyMode = COPYFILE_EXCL |
COPYFILE_FICLONE |
COPYFILE_FICLONE_FORCE;
// Most platforms don't allow reads or writes >= 2 GiB.
// See https://github.com/libuv/libuv/pull/1501.
const kIoMaxLength = 2 ** 31 - 1;
// Use 64kb in case the file type is not a regular file and thus do not know the
// actual file size. Increasing the value further results in more frequent over
// allocation for small files and consumes CPU time and memory that should be
// used else wise.
// Use up to 512kb per read otherwise to partition reading big files to prevent
// blocking other threads in case the available threads are all in use.
const kReadFileUnknownBufferLength = 64 * 1024;
const kReadFileBufferLength = 512 * 1024;
const kWriteFileMaxChunkSize = 512 * 1024;
const kMaxUserId = 2 ** 32 - 1;
let fs;
function lazyLoadFs() {
return fs ??= require('fs');
}
function assertEncoding(encoding) {
if (encoding && !Buffer.isEncoding(encoding)) {
const reason = 'is invalid encoding';
throw new ERR_INVALID_ARG_VALUE('encoding', encoding, reason);
}
}
class Dirent {
constructor(name, type, path) {
this.name = name;
this.parentPath = path;
this[kType] = type;
}
isDirectory() {
return this[kType] === UV_DIRENT_DIR;
}
isFile() {
return this[kType] === UV_DIRENT_FILE;
}
isBlockDevice() {
return this[kType] === UV_DIRENT_BLOCK;
}
isCharacterDevice() {
return this[kType] === UV_DIRENT_CHAR;
}
isSymbolicLink() {
return this[kType] === UV_DIRENT_LINK;
}
isFIFO() {
return this[kType] === UV_DIRENT_FIFO;
}
isSocket() {
return this[kType] === UV_DIRENT_SOCKET;
}
}
class DirentFromStats extends Dirent {
constructor(name, stats, path) {
super(name, null, path);
this[kStats] = stats;
}
}
for (const name of ReflectOwnKeys(Dirent.prototype)) {
if (name === 'constructor') {
continue;
}
DirentFromStats.prototype[name] = function() {
return this[kStats][name]();
};
}
function copyObject(source) {
const target = {};
for (const key in source)
target[key] = source[key];
return target;
}
const bufferSep = Buffer.from(pathModule.sep);
function join(path, name) {
if ((typeof path === 'string' || isUint8Array(path)) &&
name === undefined) {
return path;
}
if (typeof path === 'string' && isUint8Array(name)) {
const pathBuffer = Buffer.from(pathModule.join(path, pathModule.sep));
return Buffer.concat([pathBuffer, name]);
}
if (typeof path === 'string' && typeof name === 'string') {
return pathModule.join(path, name);
}
if (isUint8Array(path) && isUint8Array(name)) {
return Buffer.concat([path, bufferSep, name]);
}
throw new ERR_INVALID_ARG_TYPE(
'path', ['string', 'Buffer'], path);
}
function getDirents(path, { 0: names, 1: types }, callback) {
let i;
if (typeof callback === 'function') {
const len = names.length;
let toFinish = 0;
callback = once(callback);
for (i = 0; i < len; i++) {
const type = types[i];
if (type === UV_DIRENT_UNKNOWN) {
const name = names[i];
const idx = i;
toFinish++;
let filepath;
try {
filepath = join(path, name);
} catch (err) {
callback(err);
return;
}
lazyLoadFs().lstat(filepath, (err, stats) => {
if (err) {
callback(err);
return;
}
names[idx] = new DirentFromStats(name, stats, path);
if (--toFinish === 0) {
callback(null, names);
}
});
} else {
names[i] = new Dirent(names[i], types[i], path);
}
}
if (toFinish === 0) {
callback(null, names);
}
} else {
const len = names.length;
for (i = 0; i < len; i++) {
names[i] = getDirent(path, names[i], types[i]);
}
return names;
}
}
function getDirent(path, name, type, callback) {
if (typeof callback === 'function') {
if (type === UV_DIRENT_UNKNOWN) {
let filepath;
try {
filepath = join(path, name);
} catch (err) {
callback(err);
return;
}
lazyLoadFs().lstat(filepath, (err, stats) => {
if (err) {
callback(err);
return;
}
callback(null, new DirentFromStats(name, stats, path));
});
} else {
callback(null, new Dirent(name, type, path));
}
} else if (type === UV_DIRENT_UNKNOWN) {
const filepath = join(path, name);
const stats = lazyLoadFs().lstatSync(filepath);
return new DirentFromStats(name, stats, path);
} else {
return new Dirent(name, type, path);
}
}
/**
* Builds the Dirent objects for a `binding.readdirRecursive()` result, whose
* entries are grouped by directory: `counts[i]` entries under `dirs[i]`.
* @param {string | Buffer} basePath
* @param {[string[] | Buffer[], Uint8Array, Uint32Array, string[] | Buffer[]]} result
* @returns {Dirent[]}
*/
function getRecursiveDirents(basePath, { 0: names, 1: types, 2: counts, 3: dirs }) {
let i = 0;
for (let d = 0; d < dirs.length; d++) {
// The root keeps the path as given, the directories below it get the
// relative directory joined onto it.
const parentPath = d === 0 ? basePath : joinParentPath(basePath, dirs[d]);
for (const end = i + counts[d]; i < end; i++) {
names[i] = new Dirent(names[i], types[i], parentPath);
}
}
return names;
}
/**
* Joins the relative path of a directory onto the path a recursive readdir
* was given.
* @param {string | Buffer} basePath
* @param {string | Buffer} relative
* @returns {string | Buffer}
*/
function joinParentPath(basePath, relative) {
if (typeof basePath === 'string' && typeof relative === 'string') {
return pathModule.join(basePath, relative);
}
const base = typeof basePath === 'string' ? Buffer.from(basePath) : basePath;
const parts = [base];
const last = base.length === 0 ? -1 : base[base.length - 1];
if (last !== CHAR_FORWARD_SLASH && (!isWindows || last !== CHAR_BACKWARD_SLASH)) {
ArrayPrototypePush(parts, Buffer.from(pathModule.sep));
}
ArrayPrototypePush(parts, typeof relative === 'string' ? Buffer.from(relative) : relative);
return Buffer.concat(parts);
}
/**
* Appends one directory's entries to `context.results` and the subdirectories
* still to visit to `context.dirs`.
* @param {string} dir
* @param {string} prefix
* @param {[string[], number[]]} result
* @param {{ withFileTypes: boolean, results: (string | Dirent)[], dirs: string[], prefixes: string[] }} context
*/
function collectRecursiveReaddirResult(dir, prefix, { 0: names, 1: types }, context) {
const { length } = names;
for (let i = 0; i < length; i++) {
const name = names[i];
const relative = prefix === '' ? name : `${prefix}${pathModule.sep}${name}`;
let isDirectory;
if (context.withFileTypes) {
const dirent = getDirent(dir, name, types[i]);
ArrayPrototypePush(context.results, dirent);
// https://github.com/nodejs/node/issues/52663
isDirectory = dirent.isDirectory() ||
(dirent.isSymbolicLink() && binding.internalModuleStat(pathModule.join(dir, name)) === 1);
} else {
ArrayPrototypePush(context.results, relative);
const type = types[i];
isDirectory = type === UV_DIRENT_DIR ||
((type === UV_DIRENT_LINK || type === UV_DIRENT_UNKNOWN) &&
binding.internalModuleStat(pathModule.join(dir, name)) === 1);
}
if (isDirectory) {
ArrayPrototypePush(context.dirs, pathModule.join(dir, name));
ArrayPrototypePush(context.prefixes, relative);
}
}
}
function getOptions(options, defaultOptions = kEmptyObject) {
if (options == null || typeof options === 'function') {
return defaultOptions;
}
if (typeof options === 'string') {
defaultOptions = { ...defaultOptions };
defaultOptions.encoding = options;
options = defaultOptions;
} else if (typeof options !== 'object') {
throw new ERR_INVALID_ARG_TYPE('options', ['string', 'Object'], options);
}
if (options.encoding !== 'buffer')
assertEncoding(options.encoding);
if (options.signal !== undefined) {
validateAbortSignal(options.signal, 'options.signal');
}
return options;
}
/**
* @param {InternalFSBinding.FSSyncContext} ctx
*/
function handleErrorFromBinding(ctx) {
if (ctx.errno !== undefined) { // libuv error numbers
const err = new UVException(ctx);
ErrorCaptureStackTrace(err, handleErrorFromBinding);
throw err;
}
if (ctx.error !== undefined) { // Errors created in C++ land.
// TODO(joyeecheung): currently, ctx.error are encoding errors
// usually caused by memory problems. We need to figure out proper error
// code(s) for this.
ErrorCaptureStackTrace(ctx.error, handleErrorFromBinding);
throw ctx.error;
}
}
function preprocessSymlinkDestination(path, type, linkPath) {
if (!isWindows) {
// No preprocessing is needed on Unix.
return path;
}
path = '' + path;
if (type === 'junction') {
// Junctions paths need to be absolute and \\?\-prefixed.
// A relative target is relative to the link's parent directory.
path = pathModule.resolve(linkPath, '..', path);
return pathModule.toNamespacedPath(path);
}
if (pathModule.isAbsolute(path)) {
// If the path is absolute, use the \\?\-prefix to enable long filenames
return pathModule.toNamespacedPath(path);
}
// Windows symlinks don't tolerate forward slashes.
return RegExpPrototypeSymbolReplace(/\//g, path, '\\');
}
// Constructor for file stats.
function StatsBase(dev, mode, nlink, uid, gid, rdev, blksize,
ino, size, blocks) {
this.dev = dev;
this.mode = mode;
this.nlink = nlink;
this.uid = uid;
this.gid = gid;
this.rdev = rdev;
this.blksize = blksize;
this.ino = ino;
this.size = size;
this.blocks = blocks;
}
StatsBase.prototype.isDirectory = function() {
return this._checkModeProperty(S_IFDIR);
};
StatsBase.prototype.isFile = function() {
return this._checkModeProperty(S_IFREG);
};
StatsBase.prototype.isBlockDevice = function() {
return this._checkModeProperty(S_IFBLK);
};
StatsBase.prototype.isCharacterDevice = function() {
return this._checkModeProperty(S_IFCHR);
};
StatsBase.prototype.isSymbolicLink = function() {
return this._checkModeProperty(S_IFLNK);
};
StatsBase.prototype.isFIFO = function() {
return this._checkModeProperty(S_IFIFO);
};
StatsBase.prototype.isSocket = function() {
return this._checkModeProperty(S_IFSOCK);
};
const kNsPerMsBigInt = 1_000_000n;
const kNsPerSecBigInt = 1_000_000_000n;
const kMsPerSec = 1_000;
const kNsPerMs = 1_000_000;
function msFromTimeSpec(sec, nsec) {
return sec * kMsPerSec + nsec / kNsPerMs;
}
function nsFromTimeSpecBigInt(sec, nsec) {
return sec * kNsPerSecBigInt + nsec;
}
// TODO(LiviaMedeiros): TemporalInstant primordial
let TemporalInstant;
function instantFromNs(nsec) {
TemporalInstant ??= globalThis.Temporal?.Instant;
if (TemporalInstant === undefined) {
throw new ERR_NO_TEMPORAL();
}
return new TemporalInstant(nsec);
}
function instantFromTimeSpecMs(msec, nsec) {
TemporalInstant ??= globalThis.Temporal?.Instant;
if (TemporalInstant === undefined) {
throw new ERR_NO_TEMPORAL();
}
return new TemporalInstant(BigInt(MathFloor(msec / kMsPerSec)) * kNsPerSecBigInt + BigInt(nsec));
}
// The Date constructor performs Math.floor() on the absolute value
// of the timestamp: https://tc39.es/ecma262/#sec-timeclip
// Since there may be a precision loss when the timestamp is
// converted to a floating point number, we manually round
// the timestamp here before passing it to Date().
// Refs: https://github.com/nodejs/node/pull/12607
// Refs: https://github.com/nodejs/node/pull/43714
function dateFromMs(ms) {
// Coercing to number, ms can be bigint
return new Date(MathRound(Number(ms)));
}
const lazyDateFields = {
__proto__: null,
atime: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'atime', dateFromMs(this.atimeMs));
},
set(value) {
setOwnProperty(this, 'atime', value);
},
},
mtime: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'mtime', dateFromMs(this.mtimeMs));
},
set(value) {
setOwnProperty(this, 'mtime', value);
},
},
ctime: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'ctime', dateFromMs(this.ctimeMs));
},
set(value) {
setOwnProperty(this, 'ctime', value);
},
},
birthtime: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'birthtime', dateFromMs(this.birthtimeMs));
},
set(value) {
setOwnProperty(this, 'birthtime', value);
},
},
};
const lazyTemporalFields = {
__proto__: null,
atimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'atimeInstant',
instantFromTimeSpecMs(this.atimeMs, this[kPartialAtimeNs]));
},
set(value) {
setOwnProperty(this, 'atimeInstant', value);
},
},
mtimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'mtimeInstant',
instantFromTimeSpecMs(this.mtimeMs, this[kPartialMtimeNs]));
},
set(value) {
setOwnProperty(this, 'mtimeInstant', value);
},
},
ctimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'ctimeInstant',
instantFromTimeSpecMs(this.ctimeMs, this[kPartialCtimeNs]));
},
set(value) {
setOwnProperty(this, 'ctimeInstant', value);
},
},
birthtimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'birthtimeInstant',
instantFromTimeSpecMs(this.birthtimeMs, this[kPartialBirthtimeNs]));
},
set(value) {
setOwnProperty(this, 'birthtimeInstant', value);
},
},
};
const lazyTemporalBigIntFields = {
__proto__: null,
atimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'atimeInstant', instantFromNs(this.atimeNs));
},
set(value) {
setOwnProperty(this, 'atimeInstant', value);
},
},
mtimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'mtimeInstant', instantFromNs(this.mtimeNs));
},
set(value) {
setOwnProperty(this, 'mtimeInstant', value);
},
},
ctimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'ctimeInstant', instantFromNs(this.ctimeNs));
},
set(value) {
setOwnProperty(this, 'ctimeInstant', value);
},
},
birthtimeInstant: {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return setOwnProperty(this, 'birthtimeInstant', instantFromNs(this.birthtimeNs));
},
set(value) {
setOwnProperty(this, 'birthtimeInstant', value);
},
},
};
function BigIntStats(dev, mode, nlink, uid, gid, rdev, blksize,
ino, size, blocks,
atimeNs, mtimeNs, ctimeNs, birthtimeNs) {
FunctionPrototypeCall(StatsBase, this, dev, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks);
this.atimeMs = atimeNs / kNsPerMsBigInt;
this.mtimeMs = mtimeNs / kNsPerMsBigInt;
this.ctimeMs = ctimeNs / kNsPerMsBigInt;
this.birthtimeMs = birthtimeNs / kNsPerMsBigInt;
this.atimeNs = atimeNs;
this.mtimeNs = mtimeNs;
this.ctimeNs = ctimeNs;
this.birthtimeNs = birthtimeNs;
}
ObjectSetPrototypeOf(BigIntStats.prototype, StatsBase.prototype);
ObjectSetPrototypeOf(BigIntStats, StatsBase);
ObjectDefineProperties(BigIntStats.prototype, lazyDateFields);
ObjectDefineProperties(BigIntStats.prototype, lazyTemporalBigIntFields);
BigIntStats.prototype._checkModeProperty = function(property) {
if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
property === S_IFSOCK)) {
return false; // Some types are not available on Windows
}
return (this.mode & BigInt(S_IFMT)) === BigInt(property);
};
function Stats(dev, mode, nlink, uid, gid, rdev, blksize,
ino, size, blocks,
atimeS, atimeNs, mtimeS, mtimeNs, ctimeS, ctimeNs, birthtimeS, birthtimeNs) {
FunctionPrototypeCall(StatsBase, this, dev, mode, nlink, uid, gid, rdev,
blksize, ino, size, blocks);
this.atimeMs = msFromTimeSpec(atimeS, atimeNs);
this.mtimeMs = msFromTimeSpec(mtimeS, mtimeNs);
this.ctimeMs = msFromTimeSpec(ctimeS, ctimeNs);
this.birthtimeMs = msFromTimeSpec(birthtimeS, birthtimeNs);
this[kPartialAtimeNs] = atimeNs;
this[kPartialMtimeNs] = mtimeNs;
this[kPartialCtimeNs] = ctimeNs;
this[kPartialBirthtimeNs] = birthtimeNs;
}
ObjectSetPrototypeOf(Stats.prototype, StatsBase.prototype);
ObjectSetPrototypeOf(Stats, StatsBase);
ObjectDefineProperties(Stats.prototype, lazyDateFields);
ObjectDefineProperties(Stats.prototype, lazyTemporalFields);
Stats.prototype._checkModeProperty = function(property) {
if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
property === S_IFSOCK)) {
return false; // Some types are not available on Windows
}
return (this.mode & S_IFMT) === property;
};
/**
* @param {Float64Array | BigInt64Array} stats
* @param {number} offset
* @returns {BigIntStats | Stats}
*/
function getStatsFromBinding(stats, offset = 0) {
if (isBigInt64Array(stats)) {
return new BigIntStats(
stats[0 + offset], stats[1 + offset], stats[2 + offset],
stats[3 + offset], stats[4 + offset], stats[5 + offset],
stats[6 + offset], stats[7 + offset], stats[8 + offset],
stats[9 + offset],
nsFromTimeSpecBigInt(stats[10 + offset], stats[11 + offset]),
nsFromTimeSpecBigInt(stats[12 + offset], stats[13 + offset]),
nsFromTimeSpecBigInt(stats[14 + offset], stats[15 + offset]),
nsFromTimeSpecBigInt(stats[16 + offset], stats[17 + offset]),
);
}
return new Stats(
stats[0 + offset], stats[1 + offset], stats[2 + offset],
stats[3 + offset], stats[4 + offset], stats[5 + offset],
stats[6 + offset], stats[7 + offset], stats[8 + offset],
stats[9 + offset],
stats[10 + offset], stats[11 + offset], // atime
stats[12 + offset], stats[13 + offset], // mtime
stats[14 + offset], stats[15 + offset], // ctime
stats[16 + offset], stats[17 + offset], // birthtime
);
}
class StatFs {
constructor(type, bsize, frsize, blocks, bfree, bavail, files, ffree) {
this.type = type;
this.bsize = bsize;
this.frsize = frsize;
this.blocks = blocks;
this.bfree = bfree;
this.bavail = bavail;
this.files = files;
this.ffree = ffree;
}
}
function getStatFsFromBinding(stats) {
return new StatFs(
stats[0], stats[1], stats[2], stats[3], stats[4], stats[5], stats[6], stats[7],
);
}
function stringToFlags(flags, name = 'flags') {
if (typeof flags === 'number') {
validateInt32(flags, name);
// Coerce -0 to +0.
return flags + 0;
}
if (flags == null) {
return O_RDONLY;
}
switch (flags) {
case 'r' : return O_RDONLY;
case 'rs' : // Fall through.
case 'sr' : return O_RDONLY | O_SYNC;
case 'r+' : return O_RDWR;
case 'rs+' : // Fall through.
case 'sr+' : return O_RDWR | O_SYNC;
case 'w' : return O_TRUNC | O_CREAT | O_WRONLY;
case 'wx' : // Fall through.
case 'xw' : return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;
case 'w+' : return O_TRUNC | O_CREAT | O_RDWR;
case 'wx+': // Fall through.
case 'xw+': return O_TRUNC | O_CREAT | O_RDWR | O_EXCL;
case 'a' : return O_APPEND | O_CREAT | O_WRONLY;
case 'ax' : // Fall through.
case 'xa' : return O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
case 'as' : // Fall through.
case 'sa' : return O_APPEND | O_CREAT | O_WRONLY | O_SYNC;
case 'a+' : return O_APPEND | O_CREAT | O_RDWR;
case 'ax+': // Fall through.
case 'xa+': return O_APPEND | O_CREAT | O_RDWR | O_EXCL;
case 'as+': // Fall through.
case 'sa+': return O_APPEND | O_CREAT | O_RDWR | O_SYNC;
}
throw new ERR_INVALID_ARG_VALUE('flags', flags);
}
const stringToSymlinkType = hideStackFrames((type) => {
switch (type) {
case undefined:
case null:
case 'file':
return 0;
case 'dir':
return UV_FS_SYMLINK_DIR;
case 'junction':
return UV_FS_SYMLINK_JUNCTION;
}
});
// converts Date or number to a fractional UNIX timestamp
function toUnixTimestamp(time, name = 'time') {
// eslint-disable-next-line eqeqeq
if (typeof time === 'string' && +time == time) {
return +time;
}
if (NumberIsFinite(time)) {
if (time < 0) {
return DateNow() / 1000;
}
return time;
}
if (isDate(time)) {
// Convert to 123.456 UNIX timestamp
return DatePrototypeGetTime(time) / 1000;
}
throw new ERR_INVALID_ARG_TYPE(name, ['Date', 'Time in seconds'], time);
}
const validateOffsetLengthRead = hideStackFrames(
(offset, length, bufferLength) => {
if (offset < 0) {
throw new ERR_OUT_OF_RANGE.HideStackFramesError('offset', '>= 0', offset);
}
if (length < 0) {
throw new ERR_OUT_OF_RANGE.HideStackFramesError('length', '>= 0', length);
}
if (offset + length > bufferLength) {
throw new ERR_OUT_OF_RANGE.HideStackFramesError('length',
`<= ${bufferLength - offset}`, length);
}
},
);
const validateOffsetLengthWrite = hideStackFrames(
(offset, length, byteLength) => {
if (offset > byteLength) {
throw new ERR_OUT_OF_RANGE.HideStackFramesError('offset', `<= ${byteLength}`, offset);
}
if (length > byteLength - offset) {
throw new ERR_OUT_OF_RANGE.HideStackFramesError('length', `<= ${byteLength - offset}`, length);
}
if (length < 0) {
throw new ERR_OUT_OF_RANGE.HideStackFramesError('length', '>= 0', length);
}
validateInt32.withoutStackTrace(length, 'length', 0);
},
);
const validatePath = hideStackFrames((path, propName = 'path') => {
if (typeof path !== 'string' && !isUint8Array(path)) {
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(propName, ['string', 'Buffer', 'URL'], path);
}
const pathIsString = typeof path === 'string';
const pathIsUint8Array = isUint8Array(path);
// We can only perform meaningful checks on strings and Uint8Arrays.
if ((!pathIsString && !pathIsUint8Array) ||
(pathIsString && !StringPrototypeIncludes(path, '\u0000')) ||
(pathIsUint8Array && !TypedArrayPrototypeIncludes(path, 0))) {
return;
}
throw new ERR_INVALID_ARG_VALUE.HideStackFramesError(
propName,
path,
'must be a string, Uint8Array, or URL without null bytes',
);
});
const getValidatedPath = hideStackFrames((fileURLOrPath, propName = 'path') => {
const path = toPathIfFileURL(fileURLOrPath);
validatePath(path, propName);
return path;
});
const getValidatedFd = hideStackFrames((fd, propName = 'fd') => {
if (ObjectIs(fd, -0)) {
return 0;
}
validateInt32(fd, propName, 0);
return fd;
});
const validateBufferArray = hideStackFrames((buffers, propName = 'buffers') => {
if (!ArrayIsArray(buffers))
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(propName, 'ArrayBufferView[]', buffers);
for (let i = 0; i < buffers.length; i++) {
if (!isArrayBufferView(buffers[i]))
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(propName, 'ArrayBufferView[]', buffers);
}
return buffers;
});
let nonPortableTemplateWarn = true;
function warnOnNonPortableTemplate(template) {
// Template strings passed to the mkdtemp() and mkstemp() families of
// functions should not end with 'X' because they are handled inconsistently
// across platforms.
if (nonPortableTemplateWarn &&
((typeof template === 'string' && StringPrototypeEndsWith(template, 'X')) ||
(typeof template !== 'string' && TypedArrayPrototypeAt(template, -1) === 0x58))) {
process.emitWarning('mkdtemp() and mkstemp() templates ending with X are ' +
'not portable. ' +
'For details see: https://nodejs.org/api/fs.html');
nonPortableTemplateWarn = false;
}
}
const defaultCpOptions = {
dereference: false,
errorOnExist: false,
filter: undefined,
force: true,
preserveTimestamps: false,
recursive: false,
verbatimSymlinks: false,
};
const defaultRmOptions = {
recursive: false,
force: false,
retryDelay: 100,
maxRetries: 0,
};
const validateCpOptions = hideStackFrames((options) => {
if (options === undefined)
return { ...defaultCpOptions };
validateObject(options, 'options');
options = { ...defaultCpOptions, ...options };
validateBoolean(options.dereference, 'options.dereference');
validateBoolean(options.errorOnExist, 'options.errorOnExist');
validateBoolean(options.force, 'options.force');
validateBoolean(options.preserveTimestamps, 'options.preserveTimestamps');
validateBoolean(options.recursive, 'options.recursive');
validateBoolean(options.verbatimSymlinks, 'options.verbatimSymlinks');
options.mode = getValidMode(options.mode, 'copyFile');
if (options.dereference === true && options.verbatimSymlinks === true) {
throw new ERR_INCOMPATIBLE_OPTION_PAIR.HideStackFramesError('dereference', 'verbatimSymlinks');
}
if (options.filter !== undefined) {
validateFunction(options.filter, 'options.filter');
}
return options;
});
const validateRmOptions = hideStackFrames((path, options, expectDir, cb) => {
options = validateRmdirOptions(options, defaultRmOptions);
validateBoolean.withoutStackTrace(options.force, 'options.force');
validateBoolean.withoutStackTrace(options.recursive, 'options.recursive');
validateInt32.withoutStackTrace(options.retryDelay, 'options.retryDelay', 0);
validateUint32.withoutStackTrace(options.maxRetries, 'options.maxRetries');
lazyLoadFs().lstat(path, (err, stats) => {
if (err) {
if (options.force && err.code === 'ENOENT') {
return cb(null, options);
}
return cb(err, options);
}
if (expectDir && !stats.isDirectory()) {
return cb(false);
}
if (stats.isDirectory() && !options.recursive) {
const err = new ERR_FS_EISDIR.HideStackFramesError({
code: 'EISDIR',
message: 'is a directory',
path,
syscall: 'rm',
errno: EISDIR,
});
return cb(err);
}
return cb(null, options);
});
});
const validateRmOptionsSync = hideStackFrames((path, options, expectDir) => {
options = validateRmdirOptions.withoutStackTrace(options, defaultRmOptions);
validateBoolean.withoutStackTrace(options.force, 'options.force');
validateBoolean.withoutStackTrace(options.recursive, 'options.recursive');
validateInt32.withoutStackTrace(options.retryDelay, 'options.retryDelay', 0);
validateUint32.withoutStackTrace(options.maxRetries, 'options.maxRetries');
if (!options.force || expectDir || !options.recursive) {
const isDirectory = lazyLoadFs()
.lstatSync(path, { throwIfNoEntry: !options.force })?.isDirectory();
if (expectDir && !isDirectory) {
return false;
}
if (isDirectory && !options.recursive) {
throw new ERR_FS_EISDIR.HideStackFramesError({
code: 'EISDIR',
message: 'is a directory',
path,
syscall: 'rm',
errno: EISDIR,
});
}
}
return options;
});
const validateRmdirOptions = hideStackFrames(
(options, defaults = { __proto__: null }) => {
if (options === undefined)
return defaults;
validateObject.withoutStackTrace(options, 'options');
options = { ...defaults, ...options };
return options;
});
const getValidMode = hideStackFrames((mode, type) => {
let min = kMinimumAccessMode;
let max = kMaximumAccessMode;
let def = F_OK;
if (type === 'copyFile') {
min = kMinimumCopyMode;
max = kMaximumCopyMode;
def = mode || kDefaultCopyMode;
} else {
assert(type === 'access');
}
if (mode == null) {
return def;
}
validateInteger.withoutStackTrace(mode, 'mode', min, max);
return mode;
});
const validateStringAfterArrayBufferView = hideStackFrames((buffer, name) => {
if (typeof buffer !== 'string') {
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(
name,
['string', 'Buffer', 'TypedArray', 'DataView'],
buffer,
);
}
});
const validateReadFileBufferOptions = hideStackFrames((options) => {
const { buffer } = options;
if (buffer !== undefined && typeof buffer !== 'function' &&
!isArrayBufferView(buffer)) {
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(
'options.buffer',
['Buffer', 'TypedArray', 'DataView', 'Function'],
buffer,
);
}
});
function getReadFileBufferByteLengthName(options) {
return typeof options.buffer === 'function' ?
'options.buffer().byteLength' :
'options.buffer.byteLength';
}
const getReadFileBuffer = hideStackFrames((options, size) => {
let { buffer } = options;
if (typeof buffer === 'function') {
buffer = options.buffer(size);
validateBuffer.withoutStackTrace(buffer, 'options.buffer()');
}
if (buffer === undefined) {
return undefined;
}
if (!BufferIsBuffer(buffer)) {
buffer = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
}
if (size > buffer.byteLength) {
throw new ERR_INVALID_ARG_VALUE.HideStackFramesError(
getReadFileBufferByteLengthName(options),
buffer.byteLength,
`is smaller than the file size of ${size} bytes`,
);
}
return buffer;
});
const createReadFileBufferTooSmallError = hideStackFrames((name, byteLength) => {
return new ERR_INVALID_ARG_VALUE.HideStackFramesError(
name,
byteLength,
'is too small to contain the entire file',
);
});
const validatePosition = hideStackFrames((position, name, length) => {
if (typeof position === 'number') {
validateInteger.withoutStackTrace(position, name, -1);
} else if (typeof position === 'bigint') {
const maxPosition = 2n ** 63n - 1n - BigInt(length);
if (!(position >= -1n && position <= maxPosition)) {
throw new ERR_OUT_OF_RANGE.HideStackFramesError(name,
`>= -1 && <= ${maxPosition}`,
position);
}
} else {
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(name, ['integer', 'bigint'], position);
}
});
// Shared VFS handler state for fs wrapping.
// When handlers is null, no VFS is active (zero overhead).
const vfsState = { __proto__: null, handlers: null };
function setVfsHandlers(handlers) { vfsState.handlers = handlers; }
module.exports = {
collectRecursiveReaddirResult,
constants: {
kIoMaxLength,
kMaxUserId,
kReadFileBufferLength,
kReadFileUnknownBufferLength,
kWriteFileMaxChunkSize,
},
assertEncoding,
setVfsHandlers,
vfsState,
BigIntStats, // for testing
copyObject,
Dirent,
DirentFromStats,
getDirent,
getDirents,
getOptions,
getRecursiveDirents,
getValidatedFd,
getValidatedPath,
handleErrorFromBinding,
preprocessSymlinkDestination,
realpathCacheKey: Symbol('realpathCacheKey'),
getStatFsFromBinding,
getStatsFromBinding,
stringToFlags,
stringToSymlinkType,
Stats: deprecate(Stats, 'fs.Stats constructor is deprecated.', 'DEP0180'),
toUnixTimestamp,
validateBufferArray,
validateCpOptions,
validateOffsetLengthRead,
validateOffsetLengthWrite,
validatePath,
validatePosition,
validateReadFileBufferOptions,
getReadFileBuffer,
getReadFileBufferByteLengthName,
createReadFileBufferTooSmallError,
validateRmOptions,
validateRmOptionsSync,
validateRmdirOptions,
validateStringAfterArrayBufferView,
warnOnNonPortableTemplate,
};