node/lib/internal/vfs/stats.js
Antoine du Hamel 1778eeb6e4
lib: add lint rule to enforce use of kEmptyObject
Signed-off-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/63790
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Reviewed-By: Jake Yuesong Li <jake.yuesong@gmail.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
2026-06-10 10:56:56 +00:00

301 lines
10 KiB
JavaScript

'use strict';
const {
BigInt,
BigInt64Array,
DateNow,
Float64Array,
MathCeil,
MathFloor,
} = primordials;
const {
fs: {
S_IFDIR,
S_IFREG,
S_IFLNK,
},
} = internalBinding('constants');
const { getStatsFromBinding } = require('internal/fs/utils');
const { kEmptyObject } = require('internal/util');
// Default block size for virtual files (4KB)
const kDefaultBlockSize = 4096;
// Distinctive device number for VFS files (0xVF5 = 4085)
const kVfsDev = 4085;
// Incrementing inode counter for unique ino values
let inoCounter = 1;
// Reusable arrays for creating Stats objects.
// IMPORTANT: Safe only because getStatsFromBinding copies synchronously.
// Do not use in async paths.
// Format: dev, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks,
// atime_sec, atime_nsec, mtime_sec, mtime_nsec, ctime_sec, ctime_nsec,
// birthtime_sec, birthtime_nsec
const statsArray = new Float64Array(18);
const bigintStatsArray = new BigInt64Array(18);
/**
* Converts milliseconds to seconds and nanoseconds.
* @param {number} ms Milliseconds
* @returns {{ sec: number, nsec: number }}
*/
function msToTimeSpec(ms) {
const sec = MathFloor(ms / 1000);
const nsec = (ms % 1000) * 1_000_000;
return { sec, nsec };
}
/**
* Fills the bigint stats array with the given values.
* @returns {Stats}
*/
function fillBigIntStatsArray(
dev, mode, nlink, uid, gid, rdev, blksize, ino,
size, blocks, atime, mtime, ctime, birthtime,
) {
bigintStatsArray[0] = BigInt(dev);
bigintStatsArray[1] = BigInt(mode);
bigintStatsArray[2] = BigInt(nlink);
bigintStatsArray[3] = BigInt(uid);
bigintStatsArray[4] = BigInt(gid);
bigintStatsArray[5] = BigInt(rdev);
bigintStatsArray[6] = BigInt(blksize);
bigintStatsArray[7] = BigInt(ino);
bigintStatsArray[8] = BigInt(size);
bigintStatsArray[9] = BigInt(blocks);
bigintStatsArray[10] = BigInt(atime.sec);
bigintStatsArray[11] = BigInt(atime.nsec);
bigintStatsArray[12] = BigInt(mtime.sec);
bigintStatsArray[13] = BigInt(mtime.nsec);
bigintStatsArray[14] = BigInt(ctime.sec);
bigintStatsArray[15] = BigInt(ctime.nsec);
bigintStatsArray[16] = BigInt(birthtime.sec);
bigintStatsArray[17] = BigInt(birthtime.nsec);
return getStatsFromBinding(bigintStatsArray);
}
/**
* Creates a Stats object for a virtual file.
* @param {number} size The file size in bytes
* @param {object} [options] Optional stat properties
* @param {number} [options.mode] File mode (default: 0o644)
* @param {number} [options.uid] User ID (default: process.getuid() or 0)
* @param {number} [options.gid] Group ID (default: process.getgid() or 0)
* @param {number} [options.atimeMs] Access time in ms
* @param {number} [options.mtimeMs] Modification time in ms
* @param {number} [options.ctimeMs] Change time in ms
* @param {number} [options.birthtimeMs] Birth time in ms
* @param {boolean} [options.bigint] Return BigIntStats
* @returns {Stats}
*/
function createFileStats(size, options = kEmptyObject) {
const now = DateNow();
const mode = (options.mode ?? 0o644) | S_IFREG;
const nlink = options.nlink ?? 1;
const uid = options.uid ?? (process.getuid?.() ?? 0);
const gid = options.gid ?? (process.getgid?.() ?? 0);
const atimeMs = options.atimeMs ?? now;
const mtimeMs = options.mtimeMs ?? now;
const ctimeMs = options.ctimeMs ?? now;
const birthtimeMs = options.birthtimeMs ?? now;
const blocks = MathCeil(size / 512);
const ino = inoCounter++;
const atime = msToTimeSpec(atimeMs);
const mtime = msToTimeSpec(mtimeMs);
const ctime = msToTimeSpec(ctimeMs);
const birthtime = msToTimeSpec(birthtimeMs);
if (options.bigint) {
return fillBigIntStatsArray(
kVfsDev, mode, nlink, uid, gid, 0, kDefaultBlockSize, ino,
size, blocks, atime, mtime, ctime, birthtime,
);
}
statsArray[0] = kVfsDev; // dev
statsArray[1] = mode; // mode
statsArray[2] = nlink; // nlink
statsArray[3] = uid; // uid
statsArray[4] = gid; // gid
statsArray[5] = 0; // rdev
statsArray[6] = kDefaultBlockSize; // blksize
statsArray[7] = ino; // ino
statsArray[8] = size; // size
statsArray[9] = blocks; // blocks
statsArray[10] = atime.sec; // atime_sec
statsArray[11] = atime.nsec; // atime_nsec
statsArray[12] = mtime.sec; // mtime_sec
statsArray[13] = mtime.nsec; // mtime_nsec
statsArray[14] = ctime.sec; // ctime_sec
statsArray[15] = ctime.nsec; // ctime_nsec
statsArray[16] = birthtime.sec; // birthtime_sec
statsArray[17] = birthtime.nsec; // birthtime_nsec
return getStatsFromBinding(statsArray);
}
/**
* Creates a Stats object for a virtual directory.
* @param {object} [options] Optional stat properties
* @param {number} [options.mode] Directory mode (default: 0o755)
* @param {number} [options.uid] User ID (default: process.getuid() or 0)
* @param {number} [options.gid] Group ID (default: process.getgid() or 0)
* @param {number} [options.atimeMs] Access time in ms
* @param {number} [options.mtimeMs] Modification time in ms
* @param {number} [options.ctimeMs] Change time in ms
* @param {number} [options.birthtimeMs] Birth time in ms
* @returns {Stats}
*/
function createDirectoryStats(options = kEmptyObject) {
const now = DateNow();
const mode = (options.mode ?? 0o755) | S_IFDIR;
const uid = options.uid ?? (process.getuid?.() ?? 0);
const gid = options.gid ?? (process.getgid?.() ?? 0);
const atimeMs = options.atimeMs ?? now;
const mtimeMs = options.mtimeMs ?? now;
const ctimeMs = options.ctimeMs ?? now;
const birthtimeMs = options.birthtimeMs ?? now;
const ino = inoCounter++;
const atime = msToTimeSpec(atimeMs);
const mtime = msToTimeSpec(mtimeMs);
const ctime = msToTimeSpec(ctimeMs);
const birthtime = msToTimeSpec(birthtimeMs);
if (options.bigint) {
return fillBigIntStatsArray(
kVfsDev, mode, 1, uid, gid, 0, kDefaultBlockSize, ino,
kDefaultBlockSize, 8, atime, mtime, ctime, birthtime,
);
}
statsArray[0] = kVfsDev; // dev
statsArray[1] = mode; // mode
statsArray[2] = 1; // nlink
statsArray[3] = uid; // uid
statsArray[4] = gid; // gid
statsArray[5] = 0; // rdev
statsArray[6] = kDefaultBlockSize; // blksize
statsArray[7] = ino; // ino
statsArray[8] = kDefaultBlockSize; // size (directory size)
statsArray[9] = 8; // blocks
statsArray[10] = atime.sec; // atime_sec
statsArray[11] = atime.nsec; // atime_nsec
statsArray[12] = mtime.sec; // mtime_sec
statsArray[13] = mtime.nsec; // mtime_nsec
statsArray[14] = ctime.sec; // ctime_sec
statsArray[15] = ctime.nsec; // ctime_nsec
statsArray[16] = birthtime.sec; // birthtime_sec
statsArray[17] = birthtime.nsec; // birthtime_nsec
return getStatsFromBinding(statsArray);
}
/**
* Creates a Stats object for a virtual symbolic link.
* @param {number} size The symlink size (length of target path)
* @param {object} [options] Optional stat properties
* @param {number} [options.mode] Symlink mode (default: 0o777)
* @param {number} [options.uid] User ID (default: process.getuid() or 0)
* @param {number} [options.gid] Group ID (default: process.getgid() or 0)
* @param {number} [options.atimeMs] Access time in ms
* @param {number} [options.mtimeMs] Modification time in ms
* @param {number} [options.ctimeMs] Change time in ms
* @param {number} [options.birthtimeMs] Birth time in ms
* @returns {Stats}
*/
function createSymlinkStats(size, options = kEmptyObject) {
const now = DateNow();
const mode = (options.mode ?? 0o777) | S_IFLNK;
const uid = options.uid ?? (process.getuid?.() ?? 0);
const gid = options.gid ?? (process.getgid?.() ?? 0);
const atimeMs = options.atimeMs ?? now;
const mtimeMs = options.mtimeMs ?? now;
const ctimeMs = options.ctimeMs ?? now;
const birthtimeMs = options.birthtimeMs ?? now;
const blocks = MathCeil(size / 512);
const ino = inoCounter++;
const atime = msToTimeSpec(atimeMs);
const mtime = msToTimeSpec(mtimeMs);
const ctime = msToTimeSpec(ctimeMs);
const birthtime = msToTimeSpec(birthtimeMs);
if (options.bigint) {
return fillBigIntStatsArray(
kVfsDev, mode, 1, uid, gid, 0, kDefaultBlockSize, ino,
size, blocks, atime, mtime, ctime, birthtime,
);
}
statsArray[0] = kVfsDev; // dev
statsArray[1] = mode; // mode
statsArray[2] = 1; // nlink
statsArray[3] = uid; // uid
statsArray[4] = gid; // gid
statsArray[5] = 0; // rdev
statsArray[6] = kDefaultBlockSize; // blksize
statsArray[7] = ino; // ino
statsArray[8] = size; // size
statsArray[9] = blocks; // blocks
statsArray[10] = atime.sec; // atime_sec
statsArray[11] = atime.nsec; // atime_nsec
statsArray[12] = mtime.sec; // mtime_sec
statsArray[13] = mtime.nsec; // mtime_nsec
statsArray[14] = ctime.sec; // ctime_sec
statsArray[15] = ctime.nsec; // ctime_nsec
statsArray[16] = birthtime.sec; // birthtime_sec
statsArray[17] = birthtime.nsec; // birthtime_nsec
return getStatsFromBinding(statsArray);
}
/**
* Creates a zeroed Stats object for non-existent files.
* All fields are zero, including mode (no S_IFREG bit set).
* This matches Node.js fs.watchFile() behavior for missing files.
* @returns {Stats}
*/
function createZeroStats(options) {
const zero = { sec: 0, nsec: 0 };
if (options?.bigint) {
return fillBigIntStatsArray(
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, zero, zero, zero, zero,
);
}
statsArray[0] = 0; // dev
statsArray[1] = 0; // mode (no file type bits)
statsArray[2] = 0; // nlink
statsArray[3] = 0; // uid
statsArray[4] = 0; // gid
statsArray[5] = 0; // rdev
statsArray[6] = 0; // blksize
statsArray[7] = 0; // ino
statsArray[8] = 0; // size
statsArray[9] = 0; // blocks
statsArray[10] = 0; // atime_sec
statsArray[11] = 0; // atime_nsec
statsArray[12] = 0; // mtime_sec
statsArray[13] = 0; // mtime_nsec
statsArray[14] = 0; // ctime_sec
statsArray[15] = 0; // ctime_nsec
statsArray[16] = 0; // birthtime_sec
statsArray[17] = 0; // birthtime_nsec
return getStatsFromBinding(statsArray);
}
module.exports = {
createFileStats,
createDirectoryStats,
createSymlinkStats,
createZeroStats,
};