From f7b28f67f7a2835aa5d738cbfd9c54d0f77303b3 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 6 Oct 2026 23:53:28 +0100 Subject: [PATCH 1/6] fs: add diagnostics_channel tracing for operations Each node:fs operation publishes to its own TracingChannel, named fs.. The sync, callback, and promise forms of an operation share a channel, and the event context carries the form in `api`, the caller's arguments in `args`, and the leading arguments by name. The events are published from JavaScript with the TracingChannel helpers, so all events of a call share one context object, stores bound with bindStore() reach the callback and nested operations, and the error event is always followed by end or asyncEnd. Each traced function starts with a check that calls the function again inside the trace when the channel has subscribers. The whole body then runs inside the start scope, so requests capture the bound stores, and captured function references still publish. Assisted-by: claude:opus-5.5 Signed-off-by: Tim Fish --- doc/api/diagnostics_channel.md | 84 +++++ lib/fs.js | 112 +++++- lib/internal/fs/dir.js | 38 +- lib/internal/fs/promises.js | 80 ++++- lib/internal/fs/utils.js | 143 ++++++++ .../test-diagnostics-channel-fs-bind-store.js | 96 +++++ .../test-diagnostics-channel-fs-callback.js | 164 +++++++++ .../test-diagnostics-channel-fs-coverage.js | 330 ++++++++++++++++++ .../test-diagnostics-channel-fs-promises.js | 141 ++++++++ .../test-diagnostics-channel-fs-sync.js | 153 ++++++++ 10 files changed, 1329 insertions(+), 12 deletions(-) create mode 100644 test/parallel/test-diagnostics-channel-fs-bind-store.js create mode 100644 test/parallel/test-diagnostics-channel-fs-callback.js create mode 100644 test/parallel/test-diagnostics-channel-fs-coverage.js create mode 100644 test/parallel/test-diagnostics-channel-fs-promises.js create mode 100644 test/parallel/test-diagnostics-channel-fs-sync.js diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index 25e544ccf27..64fce6c9edd 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -1638,6 +1638,90 @@ diagnosticsChannel.subscribe('crypto.fips.indicator', (message) => { }); ``` +#### File system + + + +> Stability: 1 - Experimental + +Each `node:fs` operation publishes to its own [`TracingChannel`][] named +`fs.`, for example `tracing:fs.stat:start`. The synchronous, +callback, and promise forms of an operation share the same channel. + +* `event` {Object} containing the following properties + * `api` {string} The form of the function: `'sync'`, `'callback'`, or + `'promise'`. + * `args` {Array} The arguments as passed by the caller. + * The leading arguments of the operation, by name, as listed in the table + below. For `FileHandle` methods, `fd` is the descriptor of the handle and + `args` holds the arguments of the method. + +| Channel | Functions | Named properties | +| --------------- | ---------------------------------------------------------------- | ------------------------ | +| `fs.access` | `access` | `path` | +| `fs.appendFile` | `appendFile` | `path`, `data` | +| `fs.chmod` | `chmod` | `path`, `mode` | +| `fs.chown` | `chown` | `path`, `uid`, `gid` | +| `fs.close` | `close`, `filehandle.close()` | `fd` | +| `fs.closedir` | `dir.close()`, `dir.closeSync()` | `path` | +| `fs.copyFile` | `copyFile` | `path`, `dest` | +| `fs.cp` | `cp` | `path`, `dest` | +| `fs.exists` | `exists` | `path` | +| `fs.fchmod` | `fchmod`, `filehandle.chmod()` | `fd`, `mode` | +| `fs.fchown` | `fchown`, `filehandle.chown()` | `fd`, `uid`, `gid` | +| `fs.fdatasync` | `fdatasync`, `filehandle.datasync()` | `fd` | +| `fs.fstat` | `fstat`, `filehandle.stat()` | `fd` | +| `fs.fsync` | `fsync`, `filehandle.sync()` | `fd` | +| `fs.ftruncate` | `ftruncate`, `filehandle.truncate()` | `fd` | +| `fs.futimes` | `futimes`, `filehandle.utimes()` | `fd`, `atime`, `mtime` | +| `fs.glob` | `glob` | `pattern` | +| `fs.lchmod` | `lchmod` | `path`, `mode` | +| `fs.lchown` | `lchown` | `path`, `uid`, `gid` | +| `fs.link` | `link` | `path`, `dest` | +| `fs.lstat` | `lstat` | `path` | +| `fs.lutimes` | `lutimes` | `path`, `atime`, `mtime` | +| `fs.mkdir` | `mkdir` | `path` | +| `fs.mkdtemp` | `mkdtemp`, `mkdtempDisposable` | `prefix` | +| `fs.open` | `open` | `path` | +| `fs.openAsBlob` | `openAsBlob` | `path` | +| `fs.opendir` | `opendir` | `path` | +| `fs.read` | `read`, `filehandle.read()` | `fd` | +| `fs.readdir` | `readdir` | `path` | +| `fs.readFile` | `readFile`, `filehandle.readFile()` | `path` | +| `fs.readlink` | `readlink` | `path` | +| `fs.readv` | `readv`, `filehandle.readv()` | `fd`, `buffers` | +| `fs.realpath` | `realpath`, `realpath.native` | `path` | +| `fs.rename` | `rename` | `path`, `dest` | +| `fs.rm` | `rm` | `path` | +| `fs.rmdir` | `rmdir` | `path` | +| `fs.stat` | `stat` | `path` | +| `fs.statfs` | `statfs` | `path` | +| `fs.symlink` | `symlink` | `target`, `path` | +| `fs.truncate` | `truncate` | `path` | +| `fs.unlink` | `unlink` | `path` | +| `fs.utimes` | `utimes` | `path`, `atime`, `mtime` | +| `fs.write` | `write`, `filehandle.write()` | `fd`, `buffer` | +| `fs.writeFile` | `writeFile`, `filehandle.writeFile()`, `filehandle.appendFile()` | `path`, `data` | +| `fs.writev` | `writev`, `filehandle.writev()` | `fd`, `buffers` | + +A function name such as `stat` means `fs.stat()`, `fs.statSync()`, and +`fsPromises.stat()`, where these exist. For `fs.readFile`, `fs.writeFile`, and +`fs.appendFile`, `path` is the value as passed, so it can also be a file +descriptor or a {FileHandle}. For `fs.write` with a string, `buffer` is the +string. For callback forms, `result` is the first callback argument after the +error, so for `fs.read` and `fs.write` it is the number of bytes. + +An operation that uses other `fs` operations also publishes those, inside its +own events. For example, `fs.readFileSync()` without an encoding publishes to +`fs.open`, `fs.read`, and `fs.close`. Streams from `fs.createReadStream()` and +`fs.createWriteStream()` publish the `fs.open`, `fs.read`, `fs.write`, +`fs.writev`, and `fs.close` operations they use. + +`fsPromises.glob()`, `fs.watch()`, `fs.watchFile()`, and `dir.read()` do not +publish events. + #### HTTP > Stability: 1 - Experimental diff --git a/lib/fs.js b/lib/fs.js index c0f7e6f0adf..dcbc98887c5 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -115,9 +115,13 @@ const { getStatFsFromBinding, getStatsFromBinding, realpathCacheKey, + shouldTraceFs, stringToFlags, stringToSymlinkType, toUnixTimestamp, + traceFsCallback, + traceFsPromise, + traceFsSync, validateBufferArray, validateCpOptions, validateOffsetLengthRead, @@ -241,6 +245,7 @@ function isFileType(stats, fileType) { * @returns {void} */ function access(path, mode, callback) { + if (shouldTraceFs('access')) return traceFsCallback('access', access, this, arguments); if (typeof mode === 'function') { callback = mode; mode = F_OK; @@ -265,6 +270,7 @@ function access(path, mode, callback) { * @returns {void} */ function accessSync(path, mode) { + if (shouldTraceFs('access')) return traceFsSync('access', accessSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.accessSync(path, mode); @@ -281,6 +287,11 @@ function accessSync(path, mode) { */ function exists(path, callback) { validateFunction(callback, 'cb'); + if (shouldTraceFs('exists')) { + // The callback of fs.exists() takes only the result. + return traceFsCallback('exists', existsErrorFirst, this, + [path, (err, result) => callback(result)], arguments); + } const h = vfsState.handlers; if (h !== null) { @@ -302,6 +313,10 @@ function exists(path, callback) { } } +function existsErrorFirst(path, callback) { + exists(path, (result) => callback(null, result)); +} + ObjectDefineProperty(exists, kCustomPromisifiedSymbol, { __proto__: null, value: function exists(path) { @@ -316,6 +331,7 @@ let showExistsDeprecation = true; * @returns {boolean} */ function existsSync(path) { + if (shouldTraceFs('exists')) return traceFsSync('exists', existsSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.existsSync(path); @@ -419,6 +435,7 @@ function bindSignalToReq(req, signal, callback) { * @returns {void} */ function readFile(path, options, callback) { + if (shouldTraceFs('readFile')) return traceFsCallback('readFile', readFile, this, arguments); callback ||= options; validateFunction(callback, 'cb'); @@ -602,6 +619,7 @@ function tryReadSyncWithUserBuffer(fd, isUserFd, buffer, byteLengthName) { * @returns {string | Buffer} */ function readFileSync(path, options) { + if (shouldTraceFs('readFile')) return traceFsSync('readFile', readFileSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.readFileSync(path, options); @@ -701,6 +719,10 @@ function defaultCloseCallback(err) { * @returns {void} */ function close(fd, callback = defaultCloseCallback) { + if (shouldTraceFs('close')) { + // Passes the default callback, so that the call has one to trace. + return traceFsCallback('close', close, this, [fd, callback], arguments); + } if (callback !== defaultCloseCallback) callback = makeCallback(callback); @@ -718,6 +740,7 @@ function close(fd, callback = defaultCloseCallback) { * @returns {void} */ function closeSync(fd) { + if (shouldTraceFs('close')) return traceFsSync('close', closeSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.closeSync(fd); @@ -738,6 +761,7 @@ function closeSync(fd) { * @returns {void} */ function open(path, flags, mode, callback) { + if (shouldTraceFs('open')) return traceFsCallback('open', open, this, arguments); if (arguments.length < 3) { callback = flags; flags = 'r'; @@ -771,6 +795,7 @@ function open(path, flags, mode, callback) { * @returns {number} */ function openSync(path, flags, mode) { + if (shouldTraceFs('open')) return traceFsSync('open', openSync, this, arguments); flags = stringToFlags(flags); mode = parseFileMode(mode, 'mode', 0o666); const h = vfsState.handlers; @@ -793,6 +818,7 @@ function openSync(path, flags, mode) { * @returns {Promise} */ function openAsBlob(path, options = kEmptyObject) { + if (shouldTraceFs('openAsBlob')) return traceFsPromise('openAsBlob', openAsBlob, this, arguments); validateObject(options, 'options'); const { type = '' } = options; validateString(type, 'options.type'); @@ -819,6 +845,7 @@ function openAsBlob(path, options = kEmptyObject) { * @returns {Blob} */ function openAsBlobSync(path, options = kEmptyObject) { + if (shouldTraceFs('openAsBlob')) return traceFsSync('openAsBlob', openAsBlobSync, this, arguments); validateObject(options, 'options'); const { type = '' } = options; validateString(type, 'options.type'); @@ -853,6 +880,7 @@ function openAsBlobSync(path, options = kEmptyObject) { * @returns {void} */ function read(fd, buffer, offsetOrOptions, length, position, callback) { + if (shouldTraceFs('read')) return traceFsCallback('read', read, this, arguments); fd = getValidatedFd(fd); let offset = offsetOrOptions; let params = null; @@ -955,6 +983,7 @@ ObjectDefineProperty(read, kCustomPromisifyArgsSymbol, * @returns {number} */ function readSync(fd, buffer, offsetOrOptions, length, position) { + if (shouldTraceFs('read')) return traceFsSync('read', readSync, this, arguments); validateBuffer(buffer); let offset = offsetOrOptions; @@ -1018,6 +1047,7 @@ function readSync(fd, buffer, offsetOrOptions, length, position) { * @returns {void} */ function readv(fd, buffers, position, callback) { + if (shouldTraceFs('readv')) return traceFsCallback('readv', readv, this, arguments); function wrapper(err, read) { callback(err, read || 0, buffers); } @@ -1059,6 +1089,7 @@ ObjectDefineProperty(readv, kCustomPromisifyArgsSymbol, * @returns {number} */ function readvSync(fd, buffers, position) { + if (shouldTraceFs('readv')) return traceFsSync('readv', readvSync, this, arguments); validateBufferArray(buffers); if (typeof position !== 'number') @@ -1088,6 +1119,7 @@ function readvSync(fd, buffers, position) { * @returns {void} */ function write(fd, buffer, offsetOrOptions, length, position, callback) { + if (shouldTraceFs('write')) return traceFsCallback('write', write, this, arguments); function wrapper(err, written) { // Retain a reference to buffer so that it can't be GC'ed too soon. callback(err, written || 0, buffer); @@ -1185,6 +1217,7 @@ ObjectDefineProperty(write, kCustomPromisifyArgsSymbol, * @returns {number} */ function writeSync(fd, buffer, offsetOrOptions, length, position) { + if (shouldTraceFs('write')) return traceFsSync('write', writeSync, this, arguments); const ctx = {}; let result; @@ -1251,6 +1284,7 @@ function writeSync(fd, buffer, offsetOrOptions, length, position) { * @returns {void} */ function writev(fd, buffers, position, callback) { + if (shouldTraceFs('writev')) return traceFsCallback('writev', writev, this, arguments); function wrapper(err, written) { callback(err, written || 0, buffers); } @@ -1299,6 +1333,7 @@ ObjectDefineProperty(writev, kCustomPromisifyArgsSymbol, { * @returns {number} */ function writevSync(fd, buffers, position) { + if (shouldTraceFs('writev')) return traceFsSync('writev', writevSync, this, arguments); validateBufferArray(buffers); if (buffers.length === 0) { @@ -1326,6 +1361,7 @@ function writevSync(fd, buffers, position) { * @returns {void} */ function rename(oldPath, newPath, callback) { + if (shouldTraceFs('rename')) return traceFsCallback('rename', rename, this, arguments); const h = vfsState.handlers; if (h !== null && vfsVoid(h.rename(oldPath, newPath), callback)) return; @@ -1348,6 +1384,7 @@ function rename(oldPath, newPath, callback) { * @returns {void} */ function renameSync(oldPath, newPath) { + if (shouldTraceFs('rename')) return traceFsSync('rename', renameSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.renameSync(oldPath, newPath); @@ -1367,6 +1404,7 @@ function renameSync(oldPath, newPath) { * @returns {void} */ function truncate(path, len, callback) { + if (shouldTraceFs('truncate')) return traceFsCallback('truncate', truncate, this, arguments); if (typeof len === 'function') { callback = len; len = 0; @@ -1400,6 +1438,7 @@ function truncate(path, len, callback) { * @returns {void} */ function truncateSync(path, len) { + if (shouldTraceFs('truncate')) return traceFsSync('truncate', truncateSync, this, arguments); if (len === undefined) { len = 0; } @@ -1427,6 +1466,7 @@ function truncateSync(path, len) { * @returns {void} */ function ftruncate(fd, len = 0, callback) { + if (shouldTraceFs('ftruncate')) return traceFsCallback('ftruncate', ftruncate, this, arguments); if (typeof len === 'function') { callback = len; len = 0; @@ -1450,6 +1490,7 @@ function ftruncate(fd, len = 0, callback) { * @returns {void} */ function ftruncateSync(fd, len = 0) { + if (shouldTraceFs('ftruncate')) return traceFsSync('ftruncate', ftruncateSync, this, arguments); validateInteger(len, 'len'); const h = vfsState.handlers; @@ -1482,6 +1523,7 @@ function lazyLoadRimraf() { * @returns {void} */ function rmdir(path, options, callback) { + if (shouldTraceFs('rmdir')) return traceFsCallback('rmdir', rmdir, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -1518,6 +1560,7 @@ function rmdir(path, options, callback) { * @returns {void} */ function rmdirSync(path, options) { + if (shouldTraceFs('rmdir')) return traceFsSync('rmdir', rmdirSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.rmdirSync(path); @@ -1551,6 +1594,7 @@ function rmdirSync(path, options) { * @returns {void} */ function rm(path, options, callback) { + if (shouldTraceFs('rm')) return traceFsCallback('rm', rm, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -1583,6 +1627,7 @@ function rm(path, options, callback) { * @returns {void} */ function rmSync(path, options) { + if (shouldTraceFs('rm')) return traceFsSync('rm', rmSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.rmSync(path, options); @@ -1601,6 +1646,7 @@ function rmSync(path, options) { * @returns {void} */ function fdatasync(fd, callback) { + if (shouldTraceFs('fdatasync')) return traceFsCallback('fdatasync', fdatasync, this, arguments); callback = makeCallback(callback); const h = vfsState.handlers; @@ -1624,6 +1670,7 @@ function fdatasync(fd, callback) { * @returns {void} */ function fdatasyncSync(fd) { + if (shouldTraceFs('fdatasync')) return traceFsSync('fdatasync', fdatasyncSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.fdatasyncSync(fd); @@ -1644,6 +1691,7 @@ function fdatasyncSync(fd) { * @returns {void} */ function fsync(fd, callback) { + if (shouldTraceFs('fsync')) return traceFsCallback('fsync', fsync, this, arguments); callback = makeCallback(callback); const h = vfsState.handlers; @@ -1665,6 +1713,7 @@ function fsync(fd, callback) { * @returns {void} */ function fsyncSync(fd) { + if (shouldTraceFs('fsync')) return traceFsSync('fsync', fsyncSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.fsyncSync(fd); @@ -1688,6 +1737,7 @@ function fsyncSync(fd) { * @returns {void} */ function mkdir(path, options, callback) { + if (shouldTraceFs('mkdir')) return traceFsCallback('mkdir', mkdir, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -1737,6 +1787,7 @@ function mkdir(path, options, callback) { * @returns {string | void} */ function mkdirSync(path, options) { + if (shouldTraceFs('mkdir')) return traceFsSync('mkdir', mkdirSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const vfsResult = h.mkdirSync(path, options); @@ -1885,6 +1936,7 @@ function readdirSyncRecursive(basePath, options) { * @returns {void} */ function readdir(path, options, callback) { + if (shouldTraceFs('readdir')) return traceFsCallback('readdir', readdir, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -1936,6 +1988,7 @@ function readdir(path, options, callback) { * @returns {string | Buffer[] | Dirent[]} */ function readdirSync(path, options) { + if (shouldTraceFs('readdir')) return traceFsSync('readdir', readdirSync, this, arguments); options = getOptions(options); path = getValidatedPath(path); if (options.recursive != null) { @@ -1973,6 +2026,7 @@ function readdirSync(path, options) { * @returns {void} */ function fstat(fd, options = { __proto__: null, bigint: false }, callback) { + if (shouldTraceFs('fstat')) return traceFsCallback('fstat', fstat, this, arguments); if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2005,6 +2059,7 @@ function fstat(fd, options = { __proto__: null, bigint: false }, callback) { * @returns {void} */ function lstat(path, options = { __proto__: null, bigint: false }, callback) { + if (shouldTraceFs('lstat')) return traceFsCallback('lstat', lstat, this, arguments); if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2045,6 +2100,7 @@ function lstat(path, options = { __proto__: null, bigint: false }, callback) { * @returns {void} */ function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }, callback) { + if (shouldTraceFs('stat')) return traceFsCallback('stat', stat, this, arguments); if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2069,6 +2125,7 @@ function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: } function statfs(path, options = { __proto__: null, bigint: false }, callback) { + if (shouldTraceFs('statfs')) return traceFsCallback('statfs', statfs, this, arguments); if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2110,6 +2167,7 @@ function statfs(path, options = { __proto__: null, bigint: false }, callback) { * @returns {Stats | undefined} */ function fstatSync(fd, options = { __proto__: null, bigint: false }) { + if (shouldTraceFs('fstat')) return traceFsSync('fstat', fstatSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.fstatSync(fd); @@ -2133,6 +2191,7 @@ function fstatSync(fd, options = { __proto__: null, bigint: false }) { * @returns {Stats | undefined} */ function lstatSync(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { + if (shouldTraceFs('lstat')) return traceFsSync('lstat', lstatSync, this, arguments); const h = vfsState.handlers; if (h !== null) { try { @@ -2173,6 +2232,7 @@ function lstatSync(path, options = { __proto__: null, bigint: false, throwIfNoEn * @returns {Stats} */ function statSync(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { + if (shouldTraceFs('stat')) return traceFsSync('stat', statSync, this, arguments); const h = vfsState.handlers; if (h !== null) { try { @@ -2196,6 +2256,7 @@ function statSync(path, options = { __proto__: null, bigint: false, throwIfNoEnt } function statfsSync(path, options = { __proto__: null, bigint: false }) { + if (shouldTraceFs('statfs')) return traceFsSync('statfs', statfsSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.statfsSync(path, options); @@ -2218,6 +2279,7 @@ function statfsSync(path, options = { __proto__: null, bigint: false }) { * @returns {void} */ function readlink(path, options, callback) { + if (shouldTraceFs('readlink')) return traceFsCallback('readlink', readlink, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -2241,6 +2303,7 @@ function readlink(path, options, callback) { * @returns {string | Buffer} */ function readlinkSync(path, options) { + if (shouldTraceFs('readlink')) return traceFsSync('readlink', readlinkSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.readlinkSync(path, options); @@ -2259,6 +2322,7 @@ function readlinkSync(path, options) { * @returns {void} */ function symlink(target, path, type, callback) { + if (shouldTraceFs('symlink')) return traceFsCallback('symlink', symlink, this, arguments); if (callback === undefined) { callback = makeCallback(type); type = undefined; @@ -2331,6 +2395,7 @@ function symlink(target, path, type, callback) { * @returns {void} */ function symlinkSync(target, path, type) { + if (shouldTraceFs('symlink')) return traceFsSync('symlink', symlinkSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.symlinkSync(target, path, type); @@ -2371,6 +2436,7 @@ function symlinkSync(target, path, type) { * @returns {void} */ function link(existingPath, newPath, callback) { + if (shouldTraceFs('link')) return traceFsCallback('link', link, this, arguments); callback = makeCallback(callback); const h = vfsState.handlers; @@ -2393,6 +2459,7 @@ function link(existingPath, newPath, callback) { * @returns {void} */ function linkSync(existingPath, newPath) { + if (shouldTraceFs('link')) return traceFsSync('link', linkSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.linkSync(existingPath, newPath); @@ -2415,6 +2482,7 @@ function linkSync(existingPath, newPath) { * @returns {void} */ function unlink(path, callback) { + if (shouldTraceFs('unlink')) return traceFsCallback('unlink', unlink, this, arguments); const h = vfsState.handlers; if (h !== null && vfsVoid(h.unlink(path), callback)) return; @@ -2430,6 +2498,7 @@ function unlink(path, callback) { * @returns {void} */ function unlinkSync(path) { + if (shouldTraceFs('unlink')) return traceFsSync('unlink', unlinkSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.unlinkSync(path); @@ -2446,6 +2515,7 @@ function unlinkSync(path) { * @returns {void} */ function fchmod(fd, mode, callback) { + if (shouldTraceFs('fchmod')) return traceFsCallback('fchmod', fchmod, this, arguments); mode = parseFileMode(mode, 'mode'); callback = makeCallback(callback); @@ -2469,6 +2539,7 @@ function fchmod(fd, mode, callback) { * @returns {void} */ function fchmodSync(fd, mode) { + if (shouldTraceFs('fchmod')) return traceFsSync('fchmod', fchmodSync, this, arguments); mode = parseFileMode(mode, 'mode'); const h = vfsState.handlers; @@ -2491,6 +2562,7 @@ function fchmodSync(fd, mode) { * @returns {void} */ function lchmod(path, mode, callback) { + if (shouldTraceFs('lchmod')) return traceFsCallback('lchmod', lchmod, this, arguments); validateFunction(callback, 'cb'); mode = parseFileMode(mode, 'mode'); @@ -2519,6 +2591,7 @@ function lchmod(path, mode, callback) { * @returns {void} */ function lchmodSync(path, mode) { + if (shouldTraceFs('lchmod')) return traceFsSync('lchmod', lchmodSync, this, arguments); const fd = fs.openSync(path, O_WRONLY | O_SYMLINK); // Prefer to return the chmod error, if one occurs, @@ -2538,6 +2611,7 @@ function lchmodSync(path, mode) { * @returns {void} */ function chmod(path, mode, callback) { + if (shouldTraceFs('chmod')) return traceFsCallback('chmod', chmod, this, arguments); path = getValidatedPath(path); mode = parseFileMode(mode, 'mode'); callback = makeCallback(callback); @@ -2557,6 +2631,7 @@ function chmod(path, mode, callback) { * @returns {void} */ function chmodSync(path, mode) { + if (shouldTraceFs('chmod')) return traceFsSync('chmod', chmodSync, this, arguments); path = getValidatedPath(path); mode = parseFileMode(mode, 'mode'); @@ -2578,6 +2653,7 @@ function chmodSync(path, mode) { * @returns {void} */ function lchown(path, uid, gid, callback) { + if (shouldTraceFs('lchown')) return traceFsCallback('lchown', lchown, this, arguments); callback = makeCallback(callback); path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); @@ -2599,6 +2675,7 @@ function lchown(path, uid, gid, callback) { * @returns {void} */ function lchownSync(path, uid, gid) { + if (shouldTraceFs('lchown')) return traceFsSync('lchown', lchownSync, this, arguments); path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); @@ -2621,6 +2698,7 @@ function lchownSync(path, uid, gid) { * @returns {void} */ function fchown(fd, uid, gid, callback) { + if (shouldTraceFs('fchown')) return traceFsCallback('fchown', fchown, this, arguments); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); callback = makeCallback(callback); @@ -2646,6 +2724,7 @@ function fchown(fd, uid, gid, callback) { * @returns {void} */ function fchownSync(fd, uid, gid) { + if (shouldTraceFs('fchown')) return traceFsSync('fchown', fchownSync, this, arguments); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); @@ -2672,6 +2751,7 @@ function fchownSync(fd, uid, gid) { * @returns {void} */ function chown(path, uid, gid, callback) { + if (shouldTraceFs('chown')) return traceFsCallback('chown', chown, this, arguments); callback = makeCallback(callback); path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); @@ -2694,6 +2774,7 @@ function chown(path, uid, gid, callback) { * @returns {void} */ function chownSync(path, uid, gid) { + if (shouldTraceFs('chown')) return traceFsSync('chown', chownSync, this, arguments); path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); @@ -2717,6 +2798,7 @@ function chownSync(path, uid, gid) { * @returns {void} */ function utimes(path, atime, mtime, callback) { + if (shouldTraceFs('utimes')) return traceFsCallback('utimes', utimes, this, arguments); callback = makeCallback(callback); path = getValidatedPath(path); atime = toUnixTimestamp(atime); @@ -2739,6 +2821,7 @@ function utimes(path, atime, mtime, callback) { * @returns {void} */ function utimesSync(path, atime, mtime) { + if (shouldTraceFs('utimes')) return traceFsSync('utimes', utimesSync, this, arguments); path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -2762,6 +2845,7 @@ function utimesSync(path, atime, mtime) { * @returns {void} */ function futimes(fd, atime, mtime, callback) { + if (shouldTraceFs('futimes')) return traceFsCallback('futimes', futimes, this, arguments); atime = toUnixTimestamp(atime, 'atime'); mtime = toUnixTimestamp(mtime, 'mtime'); callback = makeCallback(callback); @@ -2789,6 +2873,7 @@ function futimes(fd, atime, mtime, callback) { * @returns {void} */ function futimesSync(fd, atime, mtime) { + if (shouldTraceFs('futimes')) return traceFsSync('futimes', futimesSync, this, arguments); atime = toUnixTimestamp(atime, 'atime'); mtime = toUnixTimestamp(mtime, 'mtime'); @@ -2815,6 +2900,7 @@ function futimesSync(fd, atime, mtime) { * @returns {void} */ function lutimes(path, atime, mtime, callback) { + if (shouldTraceFs('lutimes')) return traceFsCallback('lutimes', lutimes, this, arguments); callback = makeCallback(callback); path = getValidatedPath(path); atime = toUnixTimestamp(atime); @@ -2837,6 +2923,7 @@ function lutimes(path, atime, mtime, callback) { * @returns {void} */ function lutimesSync(path, atime, mtime) { + if (shouldTraceFs('lutimes')) return traceFsSync('lutimes', lutimesSync, this, arguments); path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -2919,6 +3006,7 @@ function writeAll(fd, isUserFd, buffer, offset, length, signal, flush, callback) * @returns {void} */ function writeFile(path, data, options, callback) { + if (shouldTraceFs('writeFile')) return traceFsCallback('writeFile', writeFile, this, arguments); callback ||= options; validateFunction(callback, 'cb'); @@ -2996,6 +3084,7 @@ function writeFile(path, data, options, callback) { * @returns {void} */ function writeFileSync(path, data, options) { + if (shouldTraceFs('writeFile')) return traceFsSync('writeFile', writeFileSync, this, arguments); options = getOptions(options, { encoding: 'utf8', mode: 0o666, @@ -3071,6 +3160,7 @@ function writeFileSync(path, data, options) { * @returns {void} */ function appendFile(path, data, options, callback) { + if (shouldTraceFs('appendFile')) return traceFsCallback('appendFile', appendFile, this, arguments); callback ||= options; validateFunction(callback, 'cb'); @@ -3107,6 +3197,7 @@ function appendFile(path, data, options, callback) { * @returns {void} */ function appendFileSync(path, data, options) { + if (shouldTraceFs('appendFile')) return traceFsSync('appendFile', appendFileSync, this, arguments); options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); parseFileMode(options.mode, 'mode', 0o666); @@ -3355,6 +3446,7 @@ if (isWindows) { * @returns {string | Buffer} */ function realpathSync(p, options) { + if (shouldTraceFs('realpath')) return traceFsSync('realpath', realpathSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.realpathSync(p, options); @@ -3503,7 +3595,10 @@ function realpathSync(p, options) { * @param {string | { encoding?: string; }} [options] * @returns {string | Buffer} */ -realpathSync.native = (path, options) => { +realpathSync.native = function(path, options) { + if (shouldTraceFs('realpath')) { + return traceFsSync('realpath', realpathSync.native, undefined, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.realpathSync(path, options); @@ -3528,6 +3623,7 @@ realpathSync.native = (path, options) => { * @returns {void} */ function realpath(p, options, callback) { + if (shouldTraceFs('realpath')) return traceFsCallback('realpath', realpath, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -3680,7 +3776,10 @@ function realpath(p, options, callback) { * ) => any} callback * @returns {void} */ -realpath.native = (path, options, callback) => { +realpath.native = function(path, options, callback) { + if (shouldTraceFs('realpath')) { + return traceFsCallback('realpath', realpath.native, undefined, arguments); + } callback = makeCallback(callback || options); const h = vfsState.handlers; if (h !== null && vfsResult(h.realpath(path, options), callback)) return; @@ -3702,6 +3801,7 @@ realpath.native = (path, options, callback) => { * @returns {void} */ function mkdtemp(prefix, options, callback) { + if (shouldTraceFs('mkdtemp')) return traceFsCallback('mkdtemp', mkdtemp, this, arguments); callback = makeCallback(typeof options === 'function' ? options : callback); const h = vfsState.handlers; @@ -3726,6 +3826,7 @@ function mkdtemp(prefix, options, callback) { * @returns {string} */ function mkdtempSync(prefix, options) { + if (shouldTraceFs('mkdtemp')) return traceFsSync('mkdtemp', mkdtempSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.mkdtempSync(prefix, options); @@ -3750,6 +3851,7 @@ function mkdtempSync(prefix, options) { * @returns {object} A disposable object with a "path" property. */ function mkdtempDisposableSync(prefix, options) { + if (shouldTraceFs('mkdtemp')) return traceFsSync('mkdtemp', mkdtempDisposableSync, this, arguments); options = getOptions(options); if (BufferIsBuffer(prefix)) { options = { ...options, encoding: 'buffer' }; @@ -3835,6 +3937,7 @@ function mkstempSync(prefix, options) { * @returns {void} */ function copyFile(src, dest, mode, callback) { + if (shouldTraceFs('copyFile')) return traceFsCallback('copyFile', copyFile, this, arguments); if (typeof mode === 'function') { callback = mode; mode = 0; @@ -3861,6 +3964,7 @@ function copyFile(src, dest, mode, callback) { * @returns {void} */ function copyFileSync(src, dest, mode) { + if (shouldTraceFs('copyFile')) return traceFsSync('copyFile', copyFileSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.copyFileSync(src, dest, mode); @@ -3883,6 +3987,7 @@ function copyFileSync(src, dest, mode) { * @returns {void} */ function cp(src, dest, options, callback) { + if (shouldTraceFs('cp')) return traceFsCallback('cp', cp, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -3904,6 +4009,7 @@ function cp(src, dest, options, callback) { * @returns {void} */ function cpSync(src, dest, options) { + if (shouldTraceFs('cp')) return traceFsSync('cp', cpSync, this, arguments); options = validateCpOptions(options); src = getValidatedPath(src, 'src'); dest = getValidatedPath(dest, 'dest'); @@ -3979,6 +4085,7 @@ function createWriteStream(path, options) { const lazyGlob = getLazy(() => require('internal/fs/glob').Glob); function glob(pattern, options, callback) { + if (shouldTraceFs('glob')) return traceFsCallback('glob', glob, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -3994,6 +4101,7 @@ function glob(pattern, options, callback) { } function globSync(pattern, options) { + if (shouldTraceFs('glob')) return traceFsSync('glob', globSync, this, arguments); const Glob = lazyGlob(); return new Glob(pattern, options).globSync(); } diff --git a/lib/internal/fs/dir.js b/lib/internal/fs/dir.js index 32050f31ae6..27677a3a521 100644 --- a/lib/internal/fs/dir.js +++ b/lib/internal/fs/dir.js @@ -29,7 +29,11 @@ const { } = require('internal/util'); const { getDirent, + getFsChannel, getOptions, + shouldTraceFs, + traceFsCallback, + traceFsSync, getValidatedPath, vfsState, } = require('internal/fs/utils'); @@ -71,7 +75,7 @@ class Dir { this.#readPromisified = FunctionPrototypeBind( promisify(this.#readImpl), this, false); this.#closePromisified = FunctionPrototypeBind( - promisify(this.close), this); + promisify(this.#closeImpl), this); } get path() { @@ -246,11 +250,28 @@ class Dir { if (this.#closed === true) { return PromiseReject(new ERR_DIR_CLOSED()); } - return this.#closePromisified(); + return this.#closePromise(); } validateFunction(callback, 'callback'); + if (getFsChannel('closedir').hasSubscribers) { + const context = { api: 'callback', args: [callback], path: this.#path }; + return getFsChannel('closedir').traceCallback(this.#closeImpl, -1, context, this, + callback); + } + this.#closeImpl(callback); + } + + #closePromise() { + if (getFsChannel('closedir').hasSubscribers) { + const context = { api: 'promise', args: [], path: this.#path }; + return getFsChannel('closedir').tracePromise(this.#closePromisified, context); + } + return this.#closePromisified(); + } + + #closeImpl(callback) { if (this.#closed === true) { process.nextTick(callback, new ERR_DIR_CLOSED()); return; @@ -258,7 +279,7 @@ class Dir { if (this.#operationQueue !== null) { ArrayPrototypePush(this.#operationQueue, () => { - this.close(callback); + this.#closeImpl(callback); }); return; } @@ -289,6 +310,11 @@ class Dir { } this.#closed = true; + if (getFsChannel('closedir').hasSubscribers) { + const context = { api: 'sync', args: [], path: this.#path }; + getFsChannel('closedir').traceSync(this.#handle.close, context, this.#handle); + return; + } this.#handle.close(); } @@ -302,7 +328,7 @@ class Dir { yield result; } } finally { - await this.#closePromisified(); + await this.#closePromise(); } } @@ -313,7 +339,7 @@ class Dir { async [SymbolAsyncDispose]() { if (this.#closed) return; - await this.#closePromisified(); + await this.#closePromise(); } } @@ -328,6 +354,7 @@ ObjectDefineProperties(Dir.prototype, { }); function opendir(path, options, callback) { + if (shouldTraceFs('opendir')) return traceFsCallback('opendir', opendir, this, arguments); callback = typeof options === 'function' ? options : callback; validateFunction(callback, 'callback'); @@ -369,6 +396,7 @@ function opendir(path, options, callback) { } function opendirSync(path, options) { + if (shouldTraceFs('opendir')) return traceFsSync('opendir', opendirSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.opendirSync(path, options); diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 47574a5a471..8804b66b53d 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -65,6 +65,7 @@ const { collectRecursiveReaddirResult, copyObject, getDirents, + getFsChannel, getRecursiveDirents, getOptions, getStatFsFromBinding, @@ -73,10 +74,13 @@ const { getReadFileBuffer, getReadFileBufferByteLengthName, preprocessSymlinkDestination, + shouldTraceFs, stringToFlags, stringToSymlinkType, toUnixTimestamp, handleErrorFromBinding: handleSyncErrorFromBinding, + callUntraced, + traceFsPromise, validateBufferArray, validateCpOptions, validateOffsetLengthRead, @@ -89,7 +93,7 @@ const { vfsState, warnOnNonPortableTemplate, } = require('internal/fs/utils'); -const { opendir } = require('internal/fs/dir'); +const { opendir: opendirCallback } = require('internal/fs/dir'); const { parseFileMode, validateAbortSignal, @@ -187,6 +191,14 @@ function handleErrorFromBinding(error) { return PromiseReject(error); } +function closeFileHandle(handle, fd) { + if (getFsChannel('close').hasSubscribers) { + const context = { api: 'promise', args: [], fd }; + return getFsChannel('close').tracePromise(handle.close, context, handle); + } + return handle.close(); +} + class FileHandle extends EventEmitter { #brandCheck = undefined; @@ -289,9 +301,10 @@ class FileHandle extends EventEmitter { this[kRefs]--; if (this[kRefs] === 0) { + const fd = this[kFd]; this[kFd] = -1; this[kClosePromise] = SafePromisePrototypeFinally( - this[kHandle].close(), + closeFileHandle(this[kHandle], fd), () => { this[kClosePromise] = undefined; }, ); } else { @@ -316,8 +329,14 @@ class FileHandle extends EventEmitter { if (this[kClosePromise]) { throw new ERR_INVALID_STATE('The FileHandle is closing'); } + const fd = this[kFd]; this[kFd] = -1; - this[kHandle].closeSync(); + if (getFsChannel('close').hasSubscribers) { + const context = { api: 'sync', args: [], fd }; + getFsChannel('close').traceSync(this[kHandle].closeSync, context, this[kHandle]); + } else { + this[kHandle].closeSync(); + } this.emit('close'); } @@ -466,9 +485,10 @@ class FileHandle extends EventEmitter { [kUnref]() { this[kRefs]--; if (this[kRefs] === 0) { + const fd = this[kFd]; this[kFd] = -1; PromisePrototypeThen( - this[kHandle].close(), + closeFileHandle(this[kHandle], fd), this[kCloseResolve], this[kCloseReject], ); @@ -1416,6 +1436,7 @@ async function readFileHandle(filehandle, options, knownRegularFileSize) { // All of the functions are defined as async in order to ensure that errors // thrown cause promise rejections rather than being thrown synchronously. async function access(path, mode = F_OK) { + if (shouldTraceFs('access')) return await traceFsPromise('access', access, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.access(path, mode); @@ -1429,6 +1450,7 @@ async function access(path, mode = F_OK) { } async function cp(src, dest, options) { + if (shouldTraceFs('cp')) return await traceFsPromise('cp', cp, this, arguments); options = validateCpOptions(options); src = getValidatedPath(src, 'src'); dest = getValidatedPath(dest, 'dest'); @@ -1436,6 +1458,7 @@ async function cp(src, dest, options) { } async function copyFile(src, dest, mode) { + if (shouldTraceFs('copyFile')) return await traceFsPromise('copyFile', copyFile, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.copyFile(src, dest, mode); @@ -1456,6 +1479,7 @@ async function copyFile(src, dest, mode) { // Note that unlike fs.open() which uses numeric file descriptors, // fsPromises.open() uses the fs.FileHandle class. async function open(path, flags, mode) { + if (shouldTraceFs('open')) return await traceFsPromise('open', open, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.promisesOpen(path, flags, mode); @@ -1472,6 +1496,7 @@ async function open(path, flags, mode) { } async function read(handle, bufferOrParams, offset, length, position) { + if (shouldTraceFs('read')) return await traceFsPromise('read', read, this, arguments); let buffer = bufferOrParams; if (!isArrayBufferView(buffer)) { // This is fh.read(params) @@ -1532,6 +1557,7 @@ async function read(handle, bufferOrParams, offset, length, position) { } async function readv(handle, buffers, position) { + if (shouldTraceFs('readv')) return await traceFsPromise('readv', readv, this, arguments); validateBufferArray(buffers); if (typeof position !== 'number') @@ -1546,6 +1572,7 @@ async function readv(handle, buffers, position) { } async function write(handle, buffer, offsetOrOptions, length, position) { + if (shouldTraceFs('write')) return await traceFsPromise('write', write, this, arguments); if (buffer?.byteLength === 0) return { __proto__: null, bytesWritten: 0, buffer }; @@ -1590,6 +1617,7 @@ async function write(handle, buffer, offsetOrOptions, length, position) { } async function writev(handle, buffers, position) { + if (shouldTraceFs('writev')) return await traceFsPromise('writev', writev, this, arguments); validateBufferArray(buffers); if (typeof position !== 'number') @@ -1608,6 +1636,7 @@ async function writev(handle, buffers, position) { } async function rename(oldPath, newPath) { + if (shouldTraceFs('rename')) return await traceFsPromise('rename', rename, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.rename(oldPath, newPath); @@ -1623,6 +1652,7 @@ async function rename(oldPath, newPath) { } async function truncate(path, len = 0) { + if (shouldTraceFs('truncate')) return await traceFsPromise('truncate', truncate, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.truncate(path, len); @@ -1633,6 +1663,7 @@ async function truncate(path, len = 0) { } async function ftruncate(handle, len = 0) { + if (shouldTraceFs('ftruncate')) return await traceFsPromise('ftruncate', ftruncate, this, arguments); validateInteger(len, 'len'); len = MathMax(0, len); return await PromisePrototypeThen( @@ -1643,6 +1674,7 @@ async function ftruncate(handle, len = 0) { } async function rm(path, options) { + if (shouldTraceFs('rm')) return await traceFsPromise('rm', rm, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.rm(path, options); @@ -1654,6 +1686,7 @@ async function rm(path, options) { } async function rmdir(path, options) { + if (shouldTraceFs('rmdir')) return await traceFsPromise('rmdir', rmdir, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.rmdir(path); @@ -1679,6 +1712,7 @@ async function rmdir(path, options) { } async function fdatasync(handle) { + if (shouldTraceFs('fdatasync')) return await traceFsPromise('fdatasync', fdatasync, this, arguments); if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.'); } @@ -1690,6 +1724,7 @@ async function fdatasync(handle) { } async function fsync(handle) { + if (shouldTraceFs('fsync')) return await traceFsPromise('fsync', fsync, this, arguments); if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.'); } @@ -1701,6 +1736,7 @@ async function fsync(handle) { } async function mkdir(path, options) { + if (shouldTraceFs('mkdir')) return await traceFsPromise('mkdir', mkdir, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.mkdir(path, options); @@ -1768,6 +1804,7 @@ async function readdirRecursiveWithPermissionModel(basePath, options) { } async function readdir(path, options) { + if (shouldTraceFs('readdir')) return await traceFsPromise('readdir', readdir, this, arguments); options = getOptions(options); // Make shallow copy to prevent mutating options from affecting results @@ -1799,6 +1836,7 @@ async function readdir(path, options) { } async function readlink(path, options) { + if (shouldTraceFs('readlink')) return await traceFsPromise('readlink', readlink, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.readlink(path, options); @@ -1814,6 +1852,7 @@ async function readlink(path, options) { } async function symlink(target, path, type) { + if (shouldTraceFs('symlink')) return await traceFsPromise('symlink', symlink, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.symlink(target, path, type); @@ -1853,6 +1892,7 @@ async function symlink(target, path, type) { } async function fstat(handle, options = { __proto__: null, bigint: false }) { + if (shouldTraceFs('fstat')) return await traceFsPromise('fstat', fstat, this, arguments); validateObject(options, 'options'); const { signal } = options; if (signal !== undefined) validateAbortSignal(signal, 'options.signal'); @@ -1869,6 +1909,7 @@ async function fstat(handle, options = { __proto__: null, bigint: false }) { } async function lstat(path, options = { __proto__: null, bigint: false }) { + if (shouldTraceFs('lstat')) return await traceFsPromise('lstat', lstat, this, arguments); validateObject(options, 'options'); const { signal } = options; if (signal !== undefined) validateAbortSignal(signal, 'options.signal'); @@ -1896,6 +1937,7 @@ async function lstat(path, options = { __proto__: null, bigint: false }) { } async function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { + if (shouldTraceFs('stat')) return await traceFsPromise('stat', stat, this, arguments); validateObject(options, 'options'); const { signal } = options; if (signal !== undefined) validateAbortSignal(signal, 'options.signal'); @@ -1921,6 +1963,7 @@ async function stat(path, options = { __proto__: null, bigint: false, throwIfNoE } async function statfs(path, options = { __proto__: null, bigint: false }) { + if (shouldTraceFs('statfs')) return await traceFsPromise('statfs', statfs, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.statfs(path, options); @@ -1936,6 +1979,7 @@ async function statfs(path, options = { __proto__: null, bigint: false }) { } async function link(existingPath, newPath) { + if (shouldTraceFs('link')) return await traceFsPromise('link', link, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.link(existingPath, newPath); @@ -1951,6 +1995,7 @@ async function link(existingPath, newPath) { } async function unlink(path) { + if (shouldTraceFs('unlink')) return await traceFsPromise('unlink', unlink, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.unlink(path); @@ -1964,6 +2009,7 @@ async function unlink(path) { } async function fchmod(handle, mode) { + if (shouldTraceFs('fchmod')) return await traceFsPromise('fchmod', fchmod, this, arguments); if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('fchmod API is disabled when Permission Model is enabled.'); } @@ -1976,6 +2022,7 @@ async function fchmod(handle, mode) { } async function chmod(path, mode) { + if (shouldTraceFs('chmod')) return await traceFsPromise('chmod', chmod, this, arguments); path = getValidatedPath(path); mode = parseFileMode(mode, 'mode'); @@ -1993,6 +2040,7 @@ async function chmod(path, mode) { } async function lchmod(path, mode) { + if (shouldTraceFs('lchmod')) return await traceFsPromise('lchmod', lchmod, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.lchmod(path, mode); @@ -2007,6 +2055,7 @@ async function lchmod(path, mode) { } async function lchown(path, uid, gid) { + if (shouldTraceFs('lchown')) return await traceFsPromise('lchown', lchown, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.lchown(path, uid, gid); @@ -2024,6 +2073,7 @@ async function lchown(path, uid, gid) { } async function fchown(handle, uid, gid) { + if (shouldTraceFs('fchown')) return await traceFsPromise('fchown', fchown, this, arguments); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); if (permission.isEnabled()) { @@ -2037,6 +2087,7 @@ async function fchown(handle, uid, gid) { } async function chown(path, uid, gid) { + if (shouldTraceFs('chown')) return await traceFsPromise('chown', chown, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.chown(path, uid, gid); @@ -2054,6 +2105,7 @@ async function chown(path, uid, gid) { } async function utimes(path, atime, mtime) { + if (shouldTraceFs('utimes')) return await traceFsPromise('utimes', utimes, this, arguments); path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -2072,6 +2124,7 @@ async function utimes(path, atime, mtime) { } async function futimes(handle, atime, mtime) { + if (shouldTraceFs('futimes')) return await traceFsPromise('futimes', futimes, this, arguments); if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.'); } @@ -2085,6 +2138,7 @@ async function futimes(handle, atime, mtime) { } async function lutimes(path, atime, mtime) { + if (shouldTraceFs('lutimes')) return await traceFsPromise('lutimes', lutimes, this, arguments); path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -2103,6 +2157,7 @@ async function lutimes(path, atime, mtime) { } async function realpath(path, options) { + if (shouldTraceFs('realpath')) return await traceFsPromise('realpath', realpath, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.realpath(path, options); @@ -2117,6 +2172,7 @@ async function realpath(path, options) { } async function mkdtemp(prefix, options) { + if (shouldTraceFs('mkdtemp')) return await traceFsPromise('mkdtemp', mkdtemp, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.mkdtemp(prefix, options); @@ -2159,6 +2215,9 @@ async function mkstemp(prefix, options) { } async function mkdtempDisposable(prefix, options) { + if (shouldTraceFs('mkdtemp')) { + return await traceFsPromise('mkdtemp', mkdtempDisposable, this, arguments); + } options = getOptions(options); if (BufferIsBuffer(prefix)) { options = { ...options, encoding: 'buffer' }; @@ -2194,6 +2253,7 @@ async function mkdtempDisposable(prefix, options) { } async function writeFile(path, data, options) { + if (shouldTraceFs('writeFile')) return await traceFsPromise('writeFile', writeFile, this, arguments); options = getOptions(options, { encoding: 'utf8', mode: 0o666, @@ -2274,6 +2334,7 @@ function isCustomIterable(obj) { } async function appendFile(path, data, options) { + if (shouldTraceFs('appendFile')) return await traceFsPromise('appendFile', appendFile, this, arguments); options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); parseFileMode(options.mode, 'mode', 0o666); @@ -2289,6 +2350,7 @@ async function appendFile(path, data, options) { } async function readFile(path, options) { + if (shouldTraceFs('readFile')) return await traceFsPromise('readFile', readFile, this, arguments); const h = vfsState.handlers; if (h !== null) { checkAborted(options?.signal); @@ -2347,6 +2409,14 @@ function readFileInOneRoundTrip(path, flagsNumber) { }); } +const opendirPromisified = promisify(opendirCallback); +// The unused `callback` keeps the length of promisify(fs.opendir). +function opendir(path, options, callback) { + if (shouldTraceFs('opendir')) return traceFsPromise('opendir', opendir, this, arguments); + // Skips the check in fs.opendir(), so the call is traced only once. + return callUntraced(opendirPromisified, undefined, [path, options]); +} + async function* _watch(filename, options = kEmptyObject) { const h = vfsState.handlers; if (h !== null) { @@ -2388,7 +2458,7 @@ module.exports = { cp, glob, open, - opendir: promisify(opendir), + opendir, rename, truncate, rm, diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index 29f254d9250..9789949cf45 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -3,6 +3,8 @@ const { ArrayIsArray, ArrayPrototypePush, + ArrayPrototypePushApply, + ArrayPrototypeSlice, BigInt, Date, DateNow, @@ -17,6 +19,7 @@ const { ObjectDefineProperties, ObjectIs, ObjectSetPrototypeOf, + ReflectApply, ReflectOwnKeys, RegExpPrototypeSymbolReplace, StringPrototypeEndsWith, @@ -1193,6 +1196,140 @@ const validatePosition = hideStackFrames((position, name, length) => { const vfsState = { __proto__: null, handlers: null }; function setVfsHandlers(handlers) { vfsState.handlers = handlers; } +// Each traced fs function starts with: +// +// if (shouldTraceFs('name')) return traceFsSync('name', fn, this, arguments); +// +// The trace helpers call the same function again inside the trace, so +// that its whole body runs inside the `start` scope. They set this flag +// just before that call, so that the call skips the check once. +let skipNextTrace = false; + +function shouldTraceFs(name) { + if (skipNextTrace) { + skipNextTrace = false; + return false; + } + return getFsChannel(name).hasSubscribers; +} + +function callUntraced(fn, thisArg, args) { + skipNextTrace = true; + try { + return ReflectApply(fn, thisArg, args); + } finally { + skipNextTrace = false; + } +} + +let tracingChannel; +const fsChannels = { __proto__: null }; +function getFsChannel(name) { + let channel = fsChannels[name]; + if (channel === undefined) { + tracingChannel ??= require('diagnostics_channel').tracingChannel; + channel = fsChannels[name] = tracingChannel(`fs.${name}`); + } + return channel; +} + +// Context property names for the leading arguments of each operation. +// All arguments are also in `context.args`. +const kFsContextFields = { + __proto__: null, + access: ['path'], + appendFile: ['path', 'data'], + chmod: ['path', 'mode'], + chown: ['path', 'uid', 'gid'], + close: ['fd'], + copyFile: ['path', 'dest'], + cp: ['path', 'dest'], + exists: ['path'], + fchmod: ['fd', 'mode'], + fchown: ['fd', 'uid', 'gid'], + fdatasync: ['fd'], + fstat: ['fd'], + fsync: ['fd'], + ftruncate: ['fd'], + futimes: ['fd', 'atime', 'mtime'], + glob: ['pattern'], + lchmod: ['path', 'mode'], + lchown: ['path', 'uid', 'gid'], + link: ['path', 'dest'], + lstat: ['path'], + lutimes: ['path', 'atime', 'mtime'], + mkdir: ['path'], + mkdtemp: ['prefix'], + open: ['path'], + openAsBlob: ['path'], + opendir: ['path'], + read: ['fd'], + readdir: ['path'], + readFile: ['path'], + readlink: ['path'], + readv: ['fd', 'buffers'], + realpath: ['path'], + rename: ['path', 'dest'], + rm: ['path'], + rmdir: ['path'], + stat: ['path'], + statfs: ['path'], + symlink: ['target', 'path'], + truncate: ['path'], + unlink: ['path'], + utimes: ['path', 'atime', 'mtime'], + write: ['fd', 'buffer'], + writeFile: ['path', 'data'], + writev: ['fd', 'buffers'], +}; + +// `args` are the caller's arguments. `context.args` is a copy, so that +// subscribers can not change the arguments of the call. +function createFsContext(name, api, args) { + const fields = kFsContextFields[name]; + let start = 0; + // FileHandle methods pass the handle in place of a descriptor. It is + // not one of the arguments of the method. + if (fields[0] === 'fd' && typeof args[0] === 'object' && args[0] !== null) { + start = 1; + } + const context = { api, args: ArrayPrototypeSlice(args, start) }; + for (let i = 0; i < fields.length && i < args.length; i++) { + context[fields[i]] = i === 0 && start === 1 ? args[0].fd : args[i]; + } + return context; +} + +function traceFsSync(name, fn, thisArg, args) { + const context = createFsContext(name, 'sync', args); + return getFsChannel(name).traceSync(callUntraced, context, undefined, + fn, thisArg, args); +} + +function traceFsPromise(name, fn, thisArg, args) { + const context = createFsContext(name, 'promise', args); + return getFsChannel(name).tracePromise(callUntraced, context, undefined, + fn, thisArg, args); +} + +function callUntracedSpread(fn, thisArg, ...args) { + return callUntraced(fn, thisArg, args); +} + +// The callback is the last function argument. Without one, the +// function throws when it is called again. `contextArgs` are the +// arguments to report, when they differ from the arguments of the call. +function traceFsCallback(name, fn, thisArg, args, contextArgs = args) { + let position = args.length - 1; + while (position >= 0 && typeof args[position] !== 'function') position--; + if (position < 0) return callUntraced(fn, thisArg, args); + const context = createFsContext(name, 'callback', contextArgs); + const callArgs = [callUntracedSpread, position + 2, context, undefined, fn, thisArg]; + ArrayPrototypePushApply(callArgs, args); + const channel = getFsChannel(name); + return ReflectApply(channel.traceCallback, channel, callArgs); +} + module.exports = { collectRecursiveReaddirResult, constants: { @@ -1211,11 +1348,17 @@ module.exports = { DirentFromStats, getDirent, getDirents, + getFsChannel, getOptions, getRecursiveDirents, getValidatedFd, getValidatedPath, handleErrorFromBinding, + callUntraced, + shouldTraceFs, + traceFsCallback, + traceFsPromise, + traceFsSync, preprocessSymlinkDestination, realpathCacheKey: Symbol('realpathCacheKey'), getStatFsFromBinding, diff --git a/test/parallel/test-diagnostics-channel-fs-bind-store.js b/test/parallel/test-diagnostics-channel-fs-bind-store.js new file mode 100644 index 00000000000..f272b69eded --- /dev/null +++ b/test/parallel/test-diagnostics-channel-fs-bind-store.js @@ -0,0 +1,96 @@ +'use strict'; + +// Checks that stores bound to the fs tracing channels reach the async +// work, the user callback and nested operations. + +const common = require('../common'); +const assert = require('node:assert'); +const { AsyncLocalStorage } = require('node:async_hooks'); +const dc = require('node:diagnostics_channel'); +const fs = require('node:fs'); +const fsp = require('node:fs/promises'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const file = tmpdir.resolve('file.txt'); +fs.writeFileSync(file, 'hello'); + +const startStore = new AsyncLocalStorage(); +const asyncStartStore = new AsyncLocalStorage(); + +for (const name of ['stat', 'readFile', 'truncate']) { + const channel = dc.tracingChannel(`fs.${name}`); + channel.start.bindStore(startStore, (context) => ({ name, context })); + channel.asyncStart.bindStore(asyncStartStore, (context) => ({ name, context })); +} + +// The store that is active when a nested `fs.open` starts. +const openStores = []; +dc.tracingChannel('fs.open').subscribe({ + start() { openStores.push(startStore.getStore()); }, +}); + +function testCallback() { + startStore.run('outer', common.mustCall(() => { + fs.stat(file, common.mustSucceed(() => { + assert.strictEqual(startStore.getStore().name, 'stat'); + assert.strictEqual(asyncStartStore.getStore().name, 'stat'); + assert.strictEqual(startStore.getStore().context, + asyncStartStore.getStore().context); + process.nextTick(testCallbackNested); + })); + assert.strictEqual(startStore.getStore(), 'outer'); + })); +} + +function testCallbackNested() { + openStores.length = 0; + startStore.run('outer', common.mustCall(() => { + fs.truncate(file, 5, common.mustSucceed(() => { + assert.strictEqual(openStores.length, 1); + assert.strictEqual(openStores[0].name, 'truncate'); + process.nextTick(testSync); + })); + })); +} + +function testSync() { + openStores.length = 0; + startStore.run('outer', common.mustCall(() => { + // Without an encoding, readFileSync() opens the file with + // fs.openSync(), which runs inside the readFile scope. + fs.readFileSync(file); + assert.strictEqual(openStores.length, 1); + assert.strictEqual(openStores[0].name, 'readFile'); + assert.strictEqual(startStore.getStore(), 'outer'); + })); + testPromise().then(common.mustCall()); +} + +async function testPromise() { + let asyncStartContext; + const onAsyncStart = common.mustCall(() => { + asyncStartContext = startStore.getStore(); + }); + const stat = dc.tracingChannel('fs.stat'); + stat.asyncStart.subscribe(onAsyncStart); + + await startStore.run('outer', common.mustCall(async () => { + await fsp.stat(file); + // The caller's continuation keeps its own store. + assert.strictEqual(startStore.getStore(), 'outer'); + })); + stat.asyncStart.unsubscribe(onAsyncStart); + assert.strictEqual(asyncStartContext.name, 'stat'); + + openStores.length = 0; + await startStore.run('outer', common.mustCall(async () => { + await fsp.truncate(file, 2); + assert.strictEqual(startStore.getStore(), 'outer'); + })); + assert.strictEqual(openStores.length, 1); + assert.strictEqual(openStores[0].name, 'truncate'); +} + +testCallback(); diff --git a/test/parallel/test-diagnostics-channel-fs-callback.js b/test/parallel/test-diagnostics-channel-fs-callback.js new file mode 100644 index 00000000000..376ea04e5d6 --- /dev/null +++ b/test/parallel/test-diagnostics-channel-fs-callback.js @@ -0,0 +1,164 @@ +'use strict'; + +// Checks the events that callback fs functions publish, and that the +// user callback runs between asyncStart and asyncEnd. + +const common = require('../common'); +const assert = require('node:assert'); +const dc = require('node:diagnostics_channel'); +const fs = require('node:fs'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const file = tmpdir.resolve('file.txt'); +const missing = tmpdir.resolve('missing.txt'); +fs.writeFileSync(file, 'hello'); + +function record(name, log) { + const channel = dc.tracingChannel(`fs.${name}`); + const handlers = {}; + for (const type of ['start', 'end', 'asyncStart', 'asyncEnd', 'error']) { + handlers[type] = (context) => log.push({ type, context }); + } + channel.subscribe(handlers); + return () => channel.unsubscribe(handlers); +} + +function types(log) { + return log.map((e) => e.type); +} + +function assertSameContext(log) { + const contexts = log.filter((e) => e.context).map((e) => e.context); + for (const context of contexts) { + assert.strictEqual(context, contexts[0]); + } +} + +function testStat() { + const log = []; + const stop = record('stat', log); + fs.stat(file, common.mustSucceed((stats) => { + log.push({ type: 'callback' }); + process.nextTick(() => { + stop(); + assert.deepStrictEqual(types(log), + ['start', 'end', 'asyncStart', 'callback', 'asyncEnd']); + assertSameContext(log); + const { context } = log[0]; + assert.strictEqual(context.api, 'callback'); + assert.strictEqual(context.path, file); + assert.strictEqual(context.result, stats); + assert.ok(stats instanceof fs.Stats); + testStatError(); + }); + })); + // The callback has not run yet. + assert.deepStrictEqual(types(log), ['start', 'end']); +} + +function testStatError() { + const log = []; + const stop = record('stat', log); + fs.stat(missing, common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOENT'); + log.push({ type: 'callback' }); + process.nextTick(() => { + stop(); + assert.deepStrictEqual( + types(log), + ['start', 'end', 'error', 'asyncStart', 'callback', 'asyncEnd']); + assertSameContext(log); + assert.strictEqual(log[0].context.error, err); + testRead(); + }); + })); +} + +function testRead() { + const log = []; + const stop = record('read', log); + const fd = fs.openSync(file, 'r'); + const buffer = Buffer.alloc(5); + fs.read(fd, buffer, 0, 5, 0, common.mustSucceed((bytesRead) => { + log.push({ type: 'callback' }); + process.nextTick(() => { + stop(); + fs.closeSync(fd); + assert.deepStrictEqual(types(log), + ['start', 'end', 'asyncStart', 'callback', 'asyncEnd']); + const { context } = log[0]; + assert.strictEqual(context.fd, fd); + assert.strictEqual(context.args[1], buffer); + assert.strictEqual(context.result, bytesRead); + assert.strictEqual(bytesRead, 5); + testRename(); + }); + })); +} + +function testRename() { + const log = []; + const stop = record('rename', log); + const dest = tmpdir.resolve('renamed.txt'); + fs.rename(file, dest, common.mustSucceed(() => { + process.nextTick(() => { + stop(); + assert.deepStrictEqual(types(log), ['start', 'end', 'asyncStart', 'asyncEnd']); + assert.strictEqual(log[0].context.path, file); + assert.strictEqual(log[0].context.dest, dest); + fs.renameSync(dest, file); + testExists(); + }); + })); +} + +function testExists() { + const log = []; + const stop = record('exists', log); + const callback = common.mustCall((exists) => { + assert.strictEqual(exists, true); + process.nextTick(() => { + stop(); + assert.deepStrictEqual(types(log), ['start', 'end', 'asyncStart', 'asyncEnd']); + assert.strictEqual(log[0].context.result, true); + assert.deepStrictEqual(log[0].context.args, [file, callback]); + testReadFile(); + }); + }); + fs.exists(file, callback); +} + +function testReadFile() { + const log = []; + const stop = record('readFile', log); + fs.readFile(file, 'utf8', common.mustSucceed((data) => { + process.nextTick(() => { + stop(); + assert.deepStrictEqual(types(log), ['start', 'end', 'asyncStart', 'asyncEnd']); + assert.strictEqual(log[0].context.path, file); + assert.strictEqual(log[0].context.result, data); + assert.strictEqual(data, 'hello'); + testCloseWithoutCallback(); + }); + })); +} + +function testCloseWithoutCallback() { + const fd = fs.openSync(file, 'r'); + const channel = dc.tracingChannel('fs.close'); + const handlers = { + start: common.mustCall((context) => { + assert.strictEqual(context.api, 'callback'); + assert.deepStrictEqual(context.args, [fd]); + }), + asyncEnd: common.mustCall(() => { + process.nextTick(() => channel.unsubscribe(handlers)); + }), + }; + channel.subscribe(handlers); + fs.close(fd); +} + +testStat(); diff --git a/test/parallel/test-diagnostics-channel-fs-coverage.js b/test/parallel/test-diagnostics-channel-fs-coverage.js new file mode 100644 index 00000000000..746d4391b5f --- /dev/null +++ b/test/parallel/test-diagnostics-channel-fs-coverage.js @@ -0,0 +1,330 @@ +'use strict'; + +// Checks that every public fs operation publishes to its tracing +// channel, in all of its forms. A new public function must be added to +// the tables below, or to the list of functions that do not publish. + +const common = require('../common'); +const assert = require('node:assert'); +const dc = require('node:diagnostics_channel'); +const fs = require('node:fs'); +const fsp = require('node:fs/promises'); +const path = require('node:path'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const file = tmpdir.resolve('file.txt'); +const now = new Date(); +const { uid, gid } = (() => { + fs.writeFileSync(file, 'hello world'); + return fs.statSync(file); +})(); + +let counter = 0; +function fresh(name) { + return tmpdir.resolve(`${name}-${counter++}`); +} + +function newFile() { + const target = fresh('file'); + fs.writeFileSync(target, 'hello world'); + return target; +} + +function newEmptyDir() { + const target = fresh('empty'); + fs.mkdirSync(target); + return target; +} + +function newDir() { + const target = fresh('dir'); + fs.mkdirSync(target); + fs.writeFileSync(path.join(target, 'a.txt'), 'a'); + return target; +} + +function newLink() { + const target = fresh('link'); + try { + fs.symlinkSync(file, target); + } catch { + // Symbolic links can need extra privileges on Windows. The + // operation then fails, which still publishes events. + } + return target; +} + +// Not file system operations, or covered by the operations they use. +const notTraced = new Set([ + 'Dir', 'Dirent', 'FileReadStream', 'FileWriteStream', 'ReadStream', + 'Stats', 'Utf8Stream', 'WriteStream', '_toUnixTimestamp', + 'createReadStream', 'createWriteStream', 'unwatchFile', 'watch', + 'watchFile', +]); +const notTracedPromises = new Set(['constants', 'glob', 'watch']); + +// [export name, channel name, call] +const syncOps = [ + ['accessSync', 'access', () => fs.accessSync(file)], + ['appendFileSync', 'appendFile', () => fs.appendFileSync(newFile(), 'x')], + ['chmodSync', 'chmod', () => fs.chmodSync(newFile(), 0o644)], + ['chownSync', 'chown', () => fs.chownSync(newFile(), uid, gid)], + ['closeSync', 'close', () => fs.closeSync(fs.openSync(file))], + ['copyFileSync', 'copyFile', () => fs.copyFileSync(file, fresh('copy'))], + ['cpSync', 'cp', () => fs.cpSync(file, fresh('cp'))], + ['existsSync', 'exists', () => fs.existsSync(file)], + ['fchmodSync', 'fchmod', (fd) => fs.fchmodSync(fd, 0o644)], + ['fchownSync', 'fchown', (fd) => fs.fchownSync(fd, uid, gid)], + ['fdatasyncSync', 'fdatasync', (fd) => fs.fdatasyncSync(fd)], + ['fstatSync', 'fstat', (fd) => fs.fstatSync(fd)], + ['fsyncSync', 'fsync', (fd) => fs.fsyncSync(fd)], + ['ftruncateSync', 'ftruncate', (fd) => fs.ftruncateSync(fd, 11)], + ['futimesSync', 'futimes', (fd) => fs.futimesSync(fd, now, now)], + ['globSync', 'glob', () => fs.globSync('*', { cwd: newDir() })], + ['lchmodSync', 'lchmod', () => fs.lchmodSync(newLink(), 0o644)], + ['lchownSync', 'lchown', () => fs.lchownSync(newLink(), uid, gid)], + ['linkSync', 'link', () => fs.linkSync(file, fresh('hardlink'))], + ['lstatSync', 'lstat', () => fs.lstatSync(file)], + ['lutimesSync', 'lutimes', () => fs.lutimesSync(newLink(), now, now)], + ['mkdirSync', 'mkdir', () => fs.mkdirSync(fresh('mkdir'))], + ['mkdtempSync', 'mkdtemp', () => fs.mkdtempSync(fresh('tmp'))], + ['mkdtempDisposableSync', 'mkdtemp', + () => fs.mkdtempDisposableSync(fresh('tmp')).remove()], + ['openAsBlobSync', 'openAsBlob', () => fs.openAsBlobSync(file)], + ['openSync', 'open', () => fs.closeSync(fs.openSync(file))], + ['opendirSync', 'opendir', () => fs.opendirSync(newDir()).closeSync()], + ['readFileSync', 'readFile', () => fs.readFileSync(file)], + ['readSync', 'read', (fd) => fs.readSync(fd, Buffer.alloc(4), 0, 4, 0)], + ['readdirSync', 'readdir', () => fs.readdirSync(newDir())], + ['readlinkSync', 'readlink', () => fs.readlinkSync(newLink())], + ['readvSync', 'readv', (fd) => fs.readvSync(fd, [Buffer.alloc(4)], 0)], + ['realpathSync', 'realpath', () => fs.realpathSync(file)], + ['realpathSync.native', 'realpath', () => fs.realpathSync.native(file)], + ['renameSync', 'rename', () => fs.renameSync(newFile(), fresh('renamed'))], + ['rmSync', 'rm', () => fs.rmSync(newDir(), { recursive: true })], + ['rmdirSync', 'rmdir', () => fs.rmdirSync(newEmptyDir())], + ['statSync', 'stat', () => fs.statSync(file)], + ['statfsSync', 'statfs', () => fs.statfsSync(file)], + ['symlinkSync', 'symlink', () => fs.symlinkSync(file, fresh('symlink'))], + ['truncateSync', 'truncate', () => fs.truncateSync(newFile(), 2)], + ['unlinkSync', 'unlink', () => fs.unlinkSync(newFile())], + ['utimesSync', 'utimes', () => fs.utimesSync(file, now, now)], + ['writeFileSync', 'writeFile', () => fs.writeFileSync(fresh('written'), 'x')], + ['writeSync', 'write', (fd) => fs.writeSync(fd, Buffer.from('x'))], + ['writevSync', 'writev', (fd) => fs.writevSync(fd, [Buffer.from('x')])], + ['Dir.closeSync', 'closedir', (fd, dir) => dir.closeSync()], +]; + +const callbackOps = [ + ['access', 'access', (cb) => fs.access(file, cb)], + ['appendFile', 'appendFile', (cb) => fs.appendFile(newFile(), 'x', cb)], + ['chmod', 'chmod', (cb) => fs.chmod(newFile(), 0o644, cb)], + ['chown', 'chown', (cb) => fs.chown(newFile(), uid, gid, cb)], + ['close', 'close', (cb) => fs.close(fs.openSync(file), cb)], + ['copyFile', 'copyFile', (cb) => fs.copyFile(file, fresh('copy'), cb)], + ['cp', 'cp', (cb) => fs.cp(file, fresh('cp'), cb)], + ['exists', 'exists', (cb) => fs.exists(file, () => cb())], + ['fchmod', 'fchmod', (cb, fd) => fs.fchmod(fd, 0o644, cb)], + ['fchown', 'fchown', (cb, fd) => fs.fchown(fd, uid, gid, cb)], + ['fdatasync', 'fdatasync', (cb, fd) => fs.fdatasync(fd, cb)], + ['fstat', 'fstat', (cb, fd) => fs.fstat(fd, cb)], + ['fsync', 'fsync', (cb, fd) => fs.fsync(fd, cb)], + ['ftruncate', 'ftruncate', (cb, fd) => fs.ftruncate(fd, 11, cb)], + ['futimes', 'futimes', (cb, fd) => fs.futimes(fd, now, now, cb)], + ['glob', 'glob', (cb) => fs.glob('*', { cwd: newDir() }, cb)], + ['lchmod', 'lchmod', (cb) => fs.lchmod(newLink(), 0o644, cb)], + ['lchown', 'lchown', (cb) => fs.lchown(newLink(), uid, gid, cb)], + ['link', 'link', (cb) => fs.link(file, fresh('hardlink'), cb)], + ['lstat', 'lstat', (cb) => fs.lstat(file, cb)], + ['lutimes', 'lutimes', (cb) => fs.lutimes(newLink(), now, now, cb)], + ['mkdir', 'mkdir', (cb) => fs.mkdir(fresh('mkdir'), cb)], + ['mkdtemp', 'mkdtemp', (cb) => fs.mkdtemp(fresh('tmp'), cb)], + ['open', 'open', (cb) => fs.open(file, (err, fd) => { + if (fd !== undefined) fs.closeSync(fd); + cb(err); + })], + ['opendir', 'opendir', (cb) => fs.opendir(newDir(), (err, dir) => { + dir?.closeSync(); + cb(err); + })], + ['read', 'read', (cb, fd) => fs.read(fd, Buffer.alloc(4), 0, 4, 0, cb)], + ['readFile', 'readFile', (cb) => fs.readFile(file, cb)], + ['readdir', 'readdir', (cb) => fs.readdir(newDir(), cb)], + ['readlink', 'readlink', (cb) => fs.readlink(newLink(), cb)], + ['readv', 'readv', (cb, fd) => fs.readv(fd, [Buffer.alloc(4)], 0, cb)], + ['realpath', 'realpath', (cb) => fs.realpath(file, cb)], + ['realpath.native', 'realpath', (cb) => fs.realpath.native(file, cb)], + ['rename', 'rename', (cb) => fs.rename(newFile(), fresh('renamed'), cb)], + ['rm', 'rm', (cb) => fs.rm(newDir(), { recursive: true }, cb)], + ['rmdir', 'rmdir', (cb) => fs.rmdir(newEmptyDir(), cb)], + ['stat', 'stat', (cb) => fs.stat(file, cb)], + ['statfs', 'statfs', (cb) => fs.statfs(file, cb)], + ['symlink', 'symlink', (cb) => fs.symlink(file, fresh('symlink'), cb)], + ['truncate', 'truncate', (cb) => fs.truncate(newFile(), 2, cb)], + ['unlink', 'unlink', (cb) => fs.unlink(newFile(), cb)], + ['utimes', 'utimes', (cb) => fs.utimes(file, now, now, cb)], + ['write', 'write', (cb, fd) => fs.write(fd, Buffer.from('x'), cb)], + ['writeFile', 'writeFile', (cb) => fs.writeFile(fresh('written'), 'x', cb)], + ['writev', 'writev', (cb, fd) => fs.writev(fd, [Buffer.from('x')], cb)], + ['Dir.close', 'closedir', (cb, fd, dir) => dir.close(cb)], +]; + +// FileHandle methods are listed with a `FileHandle.` prefix. +const promiseOps = [ + ['access', 'access', () => fsp.access(file)], + ['appendFile', 'appendFile', () => fsp.appendFile(newFile(), 'x')], + ['chmod', 'chmod', () => fsp.chmod(newFile(), 0o644)], + ['chown', 'chown', () => fsp.chown(newFile(), uid, gid)], + ['copyFile', 'copyFile', () => fsp.copyFile(file, fresh('copy'))], + ['cp', 'cp', () => fsp.cp(file, fresh('cp'))], + ['lchmod', 'lchmod', () => fsp.lchmod(newLink(), 0o644)], + ['lchown', 'lchown', () => fsp.lchown(newLink(), uid, gid)], + ['link', 'link', () => fsp.link(file, fresh('hardlink'))], + ['lstat', 'lstat', () => fsp.lstat(file)], + ['lutimes', 'lutimes', () => fsp.lutimes(newLink(), now, now)], + ['mkdir', 'mkdir', () => fsp.mkdir(fresh('mkdir'))], + ['mkdtemp', 'mkdtemp', () => fsp.mkdtemp(fresh('tmp'))], + ['mkdtempDisposable', 'mkdtemp', + async () => (await fsp.mkdtempDisposable(fresh('tmp'))).remove()], + ['open', 'open', async () => (await fsp.open(file)).close()], + ['opendir', 'opendir', async () => (await fsp.opendir(newDir())).close()], + ['openAsBlob', 'openAsBlob', () => fs.openAsBlob(file)], + ['readFile', 'readFile', () => fsp.readFile(file)], + ['readdir', 'readdir', () => fsp.readdir(newDir())], + ['readlink', 'readlink', () => fsp.readlink(newLink())], + ['realpath', 'realpath', () => fsp.realpath(file)], + ['rename', 'rename', () => fsp.rename(newFile(), fresh('renamed'))], + ['rm', 'rm', () => fsp.rm(newDir(), { recursive: true })], + ['rmdir', 'rmdir', () => fsp.rmdir(newEmptyDir())], + ['stat', 'stat', () => fsp.stat(file)], + ['statfs', 'statfs', () => fsp.statfs(file)], + ['symlink', 'symlink', () => fsp.symlink(file, fresh('symlink'))], + ['truncate', 'truncate', () => fsp.truncate(newFile(), 2)], + ['unlink', 'unlink', () => fsp.unlink(newFile())], + ['utimes', 'utimes', () => fsp.utimes(file, now, now)], + ['writeFile', 'writeFile', () => fsp.writeFile(fresh('written'), 'x')], + ['FileHandle.appendFile', 'writeFile', (fh) => fh.appendFile('x')], + ['FileHandle.chmod', 'fchmod', (fh) => fh.chmod(0o644)], + ['FileHandle.chown', 'fchown', (fh) => fh.chown(uid, gid)], + ['FileHandle.close', 'close', (fh) => fh.close()], + ['FileHandle.datasync', 'fdatasync', (fh) => fh.datasync()], + ['FileHandle.read', 'read', (fh) => fh.read(Buffer.alloc(4), 0, 4, 0)], + ['FileHandle.readFile', 'readFile', (fh) => fh.readFile()], + ['FileHandle.readv', 'readv', (fh) => fh.readv([Buffer.alloc(4)], 0)], + ['FileHandle.stat', 'fstat', (fh) => fh.stat()], + ['FileHandle.sync', 'fsync', (fh) => fh.sync()], + ['FileHandle.truncate', 'ftruncate', (fh) => fh.truncate(11)], + ['FileHandle.utimes', 'futimes', (fh) => fh.utimes(now, now)], + ['FileHandle.write', 'write', (fh) => fh.write(Buffer.from('x'))], + ['FileHandle.writeFile', 'writeFile', (fh) => fh.writeFile('x')], + ['FileHandle.writev', 'writev', (fh) => fh.writev([Buffer.from('x')])], + ['Dir.close', 'closedir', () => fs.opendirSync(newDir()).close()], +]; + +function supported(name) { + // The lchmod functions only exist where O_SYMLINK does. + return !name.includes('lchmod') || fs.lchmod !== undefined; +} + +function listed(ops) { + return new Set(ops.map(([name]) => name)); +} + +{ + // Every public function is listed. + const sync = listed(syncOps); + const callback = listed(callbackOps); + const promise = listed(promiseOps); + for (const [name, value] of Object.entries(fs)) { + if (typeof value !== 'function' || notTraced.has(name)) continue; + if (!supported(name)) continue; + // The fs.openAsBlob() function returns a promise. + assert.ok(sync.has(name) || callback.has(name) || promise.has(name), + `fs.${name} is not listed`); + } + for (const [name, value] of Object.entries(fsp)) { + if (typeof value !== 'function' || notTracedPromises.has(name)) continue; + if (!supported(name)) continue; + assert.ok(promise.has(name), `fs.promises.${name} is not listed`); + } +} + +function watch(channelName) { + const events = []; + const channel = dc.tracingChannel(`fs.${channelName}`); + const handlers = {}; + for (const type of ['start', 'end', 'asyncStart', 'asyncEnd', 'error']) { + handlers[type] = (context) => events.push({ type, context }); + } + channel.subscribe(handlers); + return { events, stop: () => channel.unsubscribe(handlers) }; +} + +// Symbolic links can need extra privileges on Windows, so operations on +// them may fail there. +const mayFail = new Set(common.isWindows ? + ['lchmod', 'lchown', 'lutimes', 'readlink', 'symlink'] : []); + +function check(label, channelName, events, api) { + const starts = events.filter((e) => e.type === 'start' && e.context.api === api); + assert.strictEqual(starts.length, 1, + `${label} published ${starts.length} times with api '${api}'`); + const { context } = starts[0]; + const types = events.filter((e) => e.context === context).map((e) => e.type); + const expected = api === 'sync' ? + ['start', 'end'] : + ['start', 'end', 'asyncStart', 'asyncEnd']; + assert.deepStrictEqual(types.filter((t) => t !== 'error'), expected, label); + if (!mayFail.has(channelName)) { + assert.strictEqual(context.error, undefined, + `${label} failed: ${context.error?.message}`); + } +} + +async function run() { + for (const [name, channelName, call] of syncOps) { + if (!supported(name)) continue; + const fd = fs.openSync(newFile(), 'r+'); + const dir = fs.opendirSync(newDir()); + const { events, stop } = watch(channelName); + try { + call(fd, dir); + } catch { + // check() looks at the error. + } + stop(); + fs.closeSync(fd); + try { dir.closeSync(); } catch { /* Closed by the operation. */ } + check(`fs.${name}`, channelName, events, 'sync'); + } + + for (const [name, channelName, call] of callbackOps) { + if (!supported(name)) continue; + const fd = fs.openSync(newFile(), 'r+'); + const dir = fs.opendirSync(newDir()); + const { events, stop } = watch(channelName); + await new Promise((resolve) => call(() => setImmediate(resolve), fd, dir)); + stop(); + fs.closeSync(fd); + try { dir.closeSync(); } catch { /* Closed by the operation. */ } + check(`fs.${name}`, channelName, events, 'callback'); + } + + for (const [name, channelName, call] of promiseOps) { + if (!supported(name)) continue; + const fh = await fsp.open(newFile(), 'r+'); + const { events, stop } = watch(channelName); + try { + await call(fh); + } catch { + // check() looks at the error. + } + stop(); + await fh.close(); + check(`fs.promises.${name}`, channelName, events, 'promise'); + } +} + +run().then(common.mustCall()); diff --git a/test/parallel/test-diagnostics-channel-fs-promises.js b/test/parallel/test-diagnostics-channel-fs-promises.js new file mode 100644 index 00000000000..28e538cfbe3 --- /dev/null +++ b/test/parallel/test-diagnostics-channel-fs-promises.js @@ -0,0 +1,141 @@ +'use strict'; + +// Checks the events that fs/promises functions and FileHandle methods +// publish. + +const common = require('../common'); +const assert = require('node:assert'); +const dc = require('node:diagnostics_channel'); +const fs = require('node:fs'); +const fsp = require('node:fs/promises'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const file = tmpdir.resolve('file.txt'); +const missing = tmpdir.resolve('missing.txt'); +fs.writeFileSync(file, 'hello'); + +function record(name) { + const events = []; + const channel = dc.tracingChannel(`fs.${name}`); + const handlers = {}; + for (const type of ['start', 'end', 'asyncStart', 'asyncEnd', 'error']) { + handlers[type] = (context) => events.push({ type, context }); + } + channel.subscribe(handlers); + return { + events, + types: () => events.map((e) => e.type), + stop: () => channel.unsubscribe(handlers), + }; +} + +function assertSameContext(events) { + for (const { context } of events) { + assert.strictEqual(context, events[0].context); + } +} + +(async () => { + { + const stat = record('stat'); + const promise = fsp.stat(file); + const stats = await promise; + stat.stop(); + assert.deepStrictEqual(stat.types(), ['start', 'end', 'asyncStart', 'asyncEnd']); + assertSameContext(stat.events); + const { context } = stat.events[0]; + assert.strictEqual(context.api, 'promise'); + assert.strictEqual(context.path, file); + assert.strictEqual(context.result, stats); + assert.ok(stats instanceof fs.Stats); + } + + { + const stat = record('stat'); + const err = await fsp.stat(missing).catch((err) => err); + stat.stop(); + assert.strictEqual(err.code, 'ENOENT'); + assert.deepStrictEqual(stat.types(), + ['start', 'end', 'error', 'asyncStart', 'asyncEnd']); + assertSameContext(stat.events); + assert.strictEqual(stat.events[0].context.error, err); + } + + { + // Invalid arguments are reported like other errors. + const stat = record('stat'); + await assert.rejects(fsp.stat(123), { code: 'ERR_INVALID_ARG_TYPE' }); + stat.stop(); + assert.deepStrictEqual(stat.types(), + ['start', 'end', 'error', 'asyncStart', 'asyncEnd']); + } + + { + const readFile = record('readFile'); + const data = await fsp.readFile(file, 'utf8'); + readFile.stop(); + assert.deepStrictEqual(readFile.types(), ['start', 'end', 'asyncStart', 'asyncEnd']); + assert.strictEqual(readFile.events[0].context.path, file); + assert.strictEqual(readFile.events[0].context.result, data); + } + + { + const writeFile = record('writeFile'); + const data = Buffer.from('written'); + await fsp.writeFile(file, data); + writeFile.stop(); + assert.deepStrictEqual(writeFile.types(), ['start', 'end', 'asyncStart', 'asyncEnd']); + assert.strictEqual(writeFile.events[0].context.path, file); + assert.strictEqual(writeFile.events[0].context.data, data); + } + + { + // FileHandle methods publish with the descriptor of the handle. + const open = record('open'); + const read = record('read'); + const fstat = record('fstat'); + const close = record('close'); + + const handle = await fsp.open(file, 'r'); + const { fd } = handle; + assert.deepStrictEqual(open.types(), ['start', 'end', 'asyncStart', 'asyncEnd']); + assert.strictEqual(open.events[0].context.path, file); + assert.strictEqual(open.events[0].context.result, handle); + + const buffer = Buffer.alloc(7); + const result = await handle.read(buffer, 0, 7, 0); + assert.deepStrictEqual(read.types(), ['start', 'end', 'asyncStart', 'asyncEnd']); + assert.strictEqual(read.events[0].context.api, 'promise'); + assert.strictEqual(read.events[0].context.fd, fd); + assert.deepStrictEqual(read.events[0].context.args, [buffer, 0, 7, 0]); + assert.strictEqual(read.events[0].context.result, result); + assert.strictEqual(result.bytesRead, 7); + + const stats = await handle.stat(); + assert.deepStrictEqual(fstat.types(), ['start', 'end', 'asyncStart', 'asyncEnd']); + assert.strictEqual(fstat.events[0].context.fd, fd); + assert.strictEqual(fstat.events[0].context.result, stats); + + await handle.close(); + assert.deepStrictEqual(close.types(), ['start', 'end', 'asyncStart', 'asyncEnd']); + assert.strictEqual(close.events[0].context.api, 'promise'); + assert.strictEqual(close.events[0].context.fd, fd); + + open.stop(); + read.stop(); + fstat.stop(); + close.stop(); + } + + { + const closedir = record('closedir'); + const dir = fs.opendirSync(tmpdir.path); + await dir.close(); + closedir.stop(); + assert.deepStrictEqual(closedir.types(), ['start', 'end', 'asyncStart', 'asyncEnd']); + assert.strictEqual(closedir.events[0].context.api, 'promise'); + assert.deepStrictEqual(closedir.events[0].context.args, []); + } +})().then(common.mustCall()); diff --git a/test/parallel/test-diagnostics-channel-fs-sync.js b/test/parallel/test-diagnostics-channel-fs-sync.js new file mode 100644 index 00000000000..72fc60157f6 --- /dev/null +++ b/test/parallel/test-diagnostics-channel-fs-sync.js @@ -0,0 +1,153 @@ +'use strict'; + +// Checks the events that synchronous fs functions publish. + +const common = require('../common'); +const assert = require('node:assert'); +const dc = require('node:diagnostics_channel'); +const fs = require('node:fs'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +// Captured before any subscriber exists. +const { statSync } = fs; + +function record(name) { + const events = []; + const channel = dc.tracingChannel(`fs.${name}`); + const handlers = {}; + for (const type of ['start', 'end', 'asyncStart', 'asyncEnd', 'error']) { + handlers[type] = (context) => events.push({ type, context }); + } + channel.subscribe(handlers); + return { + events, + types: () => events.map((e) => e.type), + stop: () => channel.unsubscribe(handlers), + }; +} + +function assertSameContext(events) { + for (const { context } of events) { + assert.strictEqual(context, events[0].context); + } +} + +const file = tmpdir.resolve('file.txt'); +const missing = tmpdir.resolve('missing.txt'); +fs.writeFileSync(file, 'hello'); + +{ + const stat = record('stat'); + const stats = statSync(file); + assert.deepStrictEqual(stat.types(), ['start', 'end']); + assertSameContext(stat.events); + const { context } = stat.events[0]; + assert.strictEqual(context.api, 'sync'); + assert.strictEqual(context.path, file); + assert.strictEqual(context.result, stats); + assert.ok(context.result instanceof fs.Stats); + stat.stop(); +} + +{ + // The error event comes before end. + const stat = record('stat'); + assert.throws(() => fs.statSync(missing), { code: 'ENOENT' }); + assert.deepStrictEqual(stat.types(), ['start', 'error', 'end']); + assertSameContext(stat.events); + assert.strictEqual(stat.events[0].context.error.code, 'ENOENT'); + assert.strictEqual(stat.events[0].context.path, missing); + stat.stop(); +} + +{ + // Invalid arguments are reported like other errors. + const stat = record('stat'); + assert.throws(() => fs.statSync(123), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.deepStrictEqual(stat.types(), ['start', 'error', 'end']); + assert.strictEqual(stat.events[0].context.error.code, 'ERR_INVALID_ARG_TYPE'); + stat.stop(); +} + +{ + const open = record('open'); + const read = record('read'); + const close = record('close'); + + const fd = fs.openSync(file, 'r'); + assert.deepStrictEqual(open.types(), ['start', 'end']); + assert.strictEqual(open.events[0].context.path, file); + assert.deepStrictEqual(open.events[0].context.args, [file, 'r']); + assert.strictEqual(open.events[0].context.result, fd); + + const buffer = Buffer.alloc(5); + const bytesRead = fs.readSync(fd, buffer, 0, 5, 0); + assert.deepStrictEqual(read.types(), ['start', 'end']); + const readContext = read.events[0].context; + assert.strictEqual(readContext.fd, fd); + assert.strictEqual(readContext.args[1], buffer); + assert.deepStrictEqual(readContext.args.slice(2), [0, 5, 0]); + assert.strictEqual(readContext.result, bytesRead); + + fs.closeSync(fd); + assert.deepStrictEqual(close.types(), ['start', 'end']); + assert.strictEqual(close.events[0].context.fd, fd); + + open.stop(); + read.stop(); + close.stop(); +} + +{ + const writeFile = record('writeFile'); + const readFile = record('readFile'); + const data = 'new contents'; + + fs.writeFileSync(file, data); + assert.deepStrictEqual(writeFile.types(), ['start', 'end']); + assert.strictEqual(writeFile.events[0].context.path, file); + assert.strictEqual(writeFile.events[0].context.data, data); + + // The utf8 fast path and the generic path both publish. + const text = fs.readFileSync(file, 'utf8'); + const buffer = fs.readFileSync(file); + assert.deepStrictEqual(readFile.types(), ['start', 'end', 'start', 'end']); + assert.strictEqual(readFile.events[1].context.result, text); + assert.strictEqual(readFile.events[3].context.result, buffer); + + writeFile.stop(); + readFile.stop(); +} + +{ + const exists = record('exists'); + assert.strictEqual(fs.existsSync(file), true); + assert.strictEqual(fs.existsSync(missing), false); + assert.deepStrictEqual(exists.types(), ['start', 'end', 'start', 'end']); + assert.strictEqual(exists.events[1].context.result, true); + assert.strictEqual(exists.events[3].context.result, false); + exists.stop(); +} + +{ + // A subscriber that changes `context.args` does not change the call. + const channel = dc.tracingChannel('fs.stat'); + const onStart = common.mustCall((context) => { + context.args[0] = tmpdir.resolve('does-not-exist'); + }); + channel.start.subscribe(onStart); + assert.ok(fs.statSync(file).isFile()); + channel.start.unsubscribe(onStart); +} + +{ + const rename = record('rename'); + const dest = tmpdir.resolve('renamed.txt'); + fs.renameSync(file, dest); + assert.deepStrictEqual(rename.types(), ['start', 'end']); + assert.strictEqual(rename.events[0].context.path, file); + assert.strictEqual(rename.events[0].context.dest, dest); + rename.stop(); +} From 536f9cc2c4ee0f438d96f770e40956c019bf8fa5 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 12 Oct 2026 14:45:43 +0100 Subject: [PATCH 2/6] fs: trace the realpath.native functions by name The tracing checks of realpath.native and realpathSync.native passed the functions to the trace helpers as property reads. When user code replaces the property, the trace helper called the replacement again, so it ran twice for each call. The functions now pass themselves by their own name. The new checks assert from setImmediate(), because an exception thrown in a realpath.native() callback or in a nextTick from it is lost. That is a separate bug. Assisted-by: claude:fable-5.1 Signed-off-by: Tim Fish --- lib/fs.js | 8 +++--- .../test-diagnostics-channel-fs-callback.js | 27 ++++++++++++++++++- .../test-diagnostics-channel-fs-sync.js | 18 +++++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index dcbc98887c5..42dc88b30fa 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -3595,9 +3595,9 @@ function realpathSync(p, options) { * @param {string | { encoding?: string; }} [options] * @returns {string | Buffer} */ -realpathSync.native = function(path, options) { +realpathSync.native = function native(path, options) { if (shouldTraceFs('realpath')) { - return traceFsSync('realpath', realpathSync.native, undefined, arguments); + return traceFsSync('realpath', native, undefined, arguments); } const h = vfsState.handlers; if (h !== null) { @@ -3776,9 +3776,9 @@ function realpath(p, options, callback) { * ) => any} callback * @returns {void} */ -realpath.native = function(path, options, callback) { +realpath.native = function native(path, options, callback) { if (shouldTraceFs('realpath')) { - return traceFsCallback('realpath', realpath.native, undefined, arguments); + return traceFsCallback('realpath', native, undefined, arguments); } callback = makeCallback(callback || options); const h = vfsState.handlers; diff --git a/test/parallel/test-diagnostics-channel-fs-callback.js b/test/parallel/test-diagnostics-channel-fs-callback.js index 376ea04e5d6..6ec5b592c93 100644 --- a/test/parallel/test-diagnostics-channel-fs-callback.js +++ b/test/parallel/test-diagnostics-channel-fs-callback.js @@ -140,11 +140,36 @@ function testReadFile() { assert.strictEqual(log[0].context.path, file); assert.strictEqual(log[0].context.result, data); assert.strictEqual(data, 'hello'); - testCloseWithoutCallback(); + testRealpathNativeReplaced(); }); })); } +function testRealpathNativeReplaced() { + // A replaced `realpath.native` runs once per traced call, because the + // trace calls the original function, not the property. + const original = fs.realpath.native; + let calls = 0; + fs.realpath.native = function(path, options, callback) { + calls++; + return original(path, options, callback); + }; + const log = []; + const stop = record('realpath', log); + fs.realpath.native(file, common.mustSucceed((resolved) => { + // An exception thrown in this callback, or in a nextTick from it, is + // lost (a bug in realpath.native), so the checks run from setImmediate. + setImmediate(common.mustCall(() => { + stop(); + fs.realpath.native = original; + assert.strictEqual(resolved, fs.realpathSync(file)); + assert.strictEqual(calls, 1); + assert.deepStrictEqual(types(log), ['start', 'end', 'asyncStart', 'asyncEnd']); + testCloseWithoutCallback(); + })); + })); +} + function testCloseWithoutCallback() { const fd = fs.openSync(file, 'r'); const channel = dc.tracingChannel('fs.close'); diff --git a/test/parallel/test-diagnostics-channel-fs-sync.js b/test/parallel/test-diagnostics-channel-fs-sync.js index 72fc60157f6..48293e15dc4 100644 --- a/test/parallel/test-diagnostics-channel-fs-sync.js +++ b/test/parallel/test-diagnostics-channel-fs-sync.js @@ -142,6 +142,24 @@ fs.writeFileSync(file, 'hello'); channel.start.unsubscribe(onStart); } +{ + // A replaced `realpathSync.native` runs once per traced call, because + // the trace calls the original function, not the property. + const original = fs.realpathSync.native; + const expected = fs.realpathSync(file); + let calls = 0; + fs.realpathSync.native = function(path, options) { + calls++; + return original(path, options); + }; + const realpath = record('realpath'); + assert.strictEqual(fs.realpathSync.native(file), expected); + realpath.stop(); + fs.realpathSync.native = original; + assert.strictEqual(calls, 1); + assert.deepStrictEqual(realpath.types(), ['start', 'end']); +} + { const rename = record('rename'); const dest = tmpdir.resolve('renamed.txt'); From a943e7f5767318323ff12efe8a58042bafbed472 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 19 Oct 2026 22:16:08 +0100 Subject: [PATCH 3/6] doc: explain the fs tracing channels Say which events each form of an fs operation publishes, and explain the function names in the channel table. Assisted-by: claude:fable-5.1 Signed-off-by: Tim Fish --- doc/api/diagnostics_channel.md | 63 ++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index 64fce6c9edd..ac50aa57612 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -1646,19 +1646,34 @@ added: REPLACEME > Stability: 1 - Experimental -Each `node:fs` operation publishes to its own [`TracingChannel`][] named -`fs.`, for example `tracing:fs.stat:start`. The synchronous, -callback, and promise forms of an operation share the same channel. +Each `node:fs` operation has a [`TracingChannel`][] named `fs.`. +The callback, synchronous, and promise forms of an operation all publish to +that channel. For example, `fs.stat()`, `fs.statSync()`, and `fsPromises.stat()` +all publish to `tracing:fs.stat:start`, `tracing:fs.stat:end`, and so on. + +The form decides which events a call publishes. The synchronous form publishes +`start` and `end`. The callback and promise forms publish `start` and `end` +when the call returns, then `asyncStart` and `asyncEnd` when the callback runs +or the promise settles. A call that fails also publishes `error`. See +[TracingChannel Channels][] for the order and contents of the events. * `event` {Object} containing the following properties - * `api` {string} The form of the function: `'sync'`, `'callback'`, or - `'promise'`. - * `args` {Array} The arguments as passed by the caller. - * The leading arguments of the operation, by name, as listed in the table - below. For `FileHandle` methods, `fd` is the descriptor of the handle and - `args` holds the arguments of the method. - -| Channel | Functions | Named properties | + * `api` {string} The form of the function that was called: `'callback'`, + `'sync'`, or `'promise'`. + * `args` {Array} The arguments the function was called with. For a + `FileHandle` method, the arguments of the method. + * `result` {any} The result of the operation. For the callback form, this is + the first callback argument after the error, so for `fs.read` and + `fs.write` it is the number of bytes. + * One property for each leading argument, named as in the table below. For + `FileHandle` methods, `fd` is the file descriptor of the handle. + +The table lists the functions that publish to each channel and the properties +for their leading arguments. A function name without a prefix stands for all +of its forms: `stat` means `fs.stat()`, `fs.statSync()`, and +`fsPromises.stat()`. + +| Channel | Functions | Properties | | --------------- | ---------------------------------------------------------------- | ------------------------ | | `fs.access` | `access` | `path` | | `fs.appendFile` | `appendFile` | `path`, `data` | @@ -1706,21 +1721,17 @@ callback, and promise forms of an operation share the same channel. | `fs.writeFile` | `writeFile`, `filehandle.writeFile()`, `filehandle.appendFile()` | `path`, `data` | | `fs.writev` | `writev`, `filehandle.writev()` | `fd`, `buffers` | -A function name such as `stat` means `fs.stat()`, `fs.statSync()`, and -`fsPromises.stat()`, where these exist. For `fs.readFile`, `fs.writeFile`, and -`fs.appendFile`, `path` is the value as passed, so it can also be a file -descriptor or a {FileHandle}. For `fs.write` with a string, `buffer` is the -string. For callback forms, `result` is the first callback argument after the -error, so for `fs.read` and `fs.write` it is the number of bytes. - -An operation that uses other `fs` operations also publishes those, inside its -own events. For example, `fs.readFileSync()` without an encoding publishes to -`fs.open`, `fs.read`, and `fs.close`. Streams from `fs.createReadStream()` and -`fs.createWriteStream()` publish the `fs.open`, `fs.read`, `fs.write`, -`fs.writev`, and `fs.close` operations they use. - -`fsPromises.glob()`, `fs.watch()`, `fs.watchFile()`, and `dir.read()` do not -publish events. +`fs.readFile`, `fs.writeFile`, and `fs.appendFile` accept a file descriptor or +a {FileHandle} in place of a path. `path` then holds that descriptor or handle. +When `fs.write` is called with a string, `buffer` holds that string. + +An operation that is implemented with other `fs` operations publishes those +too, between its own `start` and `end`. For example, `fs.readFileSync()` without +an encoding calls `fs.openSync()`, `fs.readSync()`, and `fs.closeSync()`, so it +publishes `fs.open`, `fs.read`, and `fs.close` events inside its `fs.readFile` +events. The streams from `fs.createReadStream()` and `fs.createWriteStream()` +call `fs.open()`, `fs.read()`, `fs.write()`, `fs.writev()`, and `fs.close()`, +which publish as usual. #### HTTP From bc2624af8e4ba809cec620b260788d7fab8b10d9 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 20 Oct 2026 10:32:31 +0100 Subject: [PATCH 4/6] fs: define the tracing channels at module level The checks looked each channel up by name on every call, through a dictionary and a helper that took the operation name. The channels and their context field names are now module-level constants, one tracer object per operation, so a check is a property read on a held object. Refs: https://github.com/nodejs/node/pull/66575#pullrequestreview-5580756826 Assisted-by: claude:fable-5.1 Signed-off-by: Tim Fish --- lib/fs.js | 235 +++++++++++++++++++------------- lib/internal/fs/dir.js | 24 ++-- lib/internal/fs/promises.js | 136 ++++++++++++------- lib/internal/fs/utils.js | 264 ++++++++++++++++++++---------------- 4 files changed, 385 insertions(+), 274 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index 42dc88b30fa..ec2f039561c 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -115,13 +115,9 @@ const { getStatFsFromBinding, getStatsFromBinding, realpathCacheKey, - shouldTraceFs, stringToFlags, stringToSymlinkType, toUnixTimestamp, - traceFsCallback, - traceFsPromise, - traceFsSync, validateBufferArray, validateCpOptions, validateOffsetLengthRead, @@ -135,6 +131,49 @@ const { validateStringAfterArrayBufferView, vfsState, warnOnNonPortableTemplate, + fsAccess, + fsAppendFile, + fsChmod, + fsChown, + fsClose, + fsCopyFile, + fsCp, + fsExists, + fsFchmod, + fsFchown, + fsFdatasync, + fsFstat, + fsFsync, + fsFtruncate, + fsFutimes, + fsGlob, + fsLchmod, + fsLchown, + fsLink, + fsLstat, + fsLutimes, + fsMkdir, + fsMkdtemp, + fsOpen, + fsOpenAsBlob, + fsRead, + fsReadFile, + fsReaddir, + fsReadlink, + fsReadv, + fsRealpath, + fsRename, + fsRm, + fsRmdir, + fsStat, + fsStatfs, + fsSymlink, + fsTruncate, + fsUnlink, + fsUtimes, + fsWrite, + fsWriteFile, + fsWritev, } = require('internal/fs/utils'); const { CHAR_FORWARD_SLASH, @@ -245,7 +284,7 @@ function isFileType(stats, fileType) { * @returns {void} */ function access(path, mode, callback) { - if (shouldTraceFs('access')) return traceFsCallback('access', access, this, arguments); + if (fsAccess.shouldTrace()) return fsAccess.traceCallback(access, this, arguments); if (typeof mode === 'function') { callback = mode; mode = F_OK; @@ -270,7 +309,7 @@ function access(path, mode, callback) { * @returns {void} */ function accessSync(path, mode) { - if (shouldTraceFs('access')) return traceFsSync('access', accessSync, this, arguments); + if (fsAccess.shouldTrace()) return fsAccess.traceSync(accessSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.accessSync(path, mode); @@ -287,10 +326,10 @@ function accessSync(path, mode) { */ function exists(path, callback) { validateFunction(callback, 'cb'); - if (shouldTraceFs('exists')) { + if (fsExists.shouldTrace()) { // The callback of fs.exists() takes only the result. - return traceFsCallback('exists', existsErrorFirst, this, - [path, (err, result) => callback(result)], arguments); + return fsExists.traceCallback(existsErrorFirst, this, + [path, (err, result) => callback(result)], arguments); } const h = vfsState.handlers; @@ -331,7 +370,7 @@ let showExistsDeprecation = true; * @returns {boolean} */ function existsSync(path) { - if (shouldTraceFs('exists')) return traceFsSync('exists', existsSync, this, arguments); + if (fsExists.shouldTrace()) return fsExists.traceSync(existsSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.existsSync(path); @@ -435,7 +474,7 @@ function bindSignalToReq(req, signal, callback) { * @returns {void} */ function readFile(path, options, callback) { - if (shouldTraceFs('readFile')) return traceFsCallback('readFile', readFile, this, arguments); + if (fsReadFile.shouldTrace()) return fsReadFile.traceCallback(readFile, this, arguments); callback ||= options; validateFunction(callback, 'cb'); @@ -619,7 +658,7 @@ function tryReadSyncWithUserBuffer(fd, isUserFd, buffer, byteLengthName) { * @returns {string | Buffer} */ function readFileSync(path, options) { - if (shouldTraceFs('readFile')) return traceFsSync('readFile', readFileSync, this, arguments); + if (fsReadFile.shouldTrace()) return fsReadFile.traceSync(readFileSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.readFileSync(path, options); @@ -719,9 +758,9 @@ function defaultCloseCallback(err) { * @returns {void} */ function close(fd, callback = defaultCloseCallback) { - if (shouldTraceFs('close')) { + if (fsClose.shouldTrace()) { // Passes the default callback, so that the call has one to trace. - return traceFsCallback('close', close, this, [fd, callback], arguments); + return fsClose.traceCallback(close, this, [fd, callback], arguments); } if (callback !== defaultCloseCallback) callback = makeCallback(callback); @@ -740,7 +779,7 @@ function close(fd, callback = defaultCloseCallback) { * @returns {void} */ function closeSync(fd) { - if (shouldTraceFs('close')) return traceFsSync('close', closeSync, this, arguments); + if (fsClose.shouldTrace()) return fsClose.traceSync(closeSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.closeSync(fd); @@ -761,7 +800,7 @@ function closeSync(fd) { * @returns {void} */ function open(path, flags, mode, callback) { - if (shouldTraceFs('open')) return traceFsCallback('open', open, this, arguments); + if (fsOpen.shouldTrace()) return fsOpen.traceCallback(open, this, arguments); if (arguments.length < 3) { callback = flags; flags = 'r'; @@ -795,7 +834,7 @@ function open(path, flags, mode, callback) { * @returns {number} */ function openSync(path, flags, mode) { - if (shouldTraceFs('open')) return traceFsSync('open', openSync, this, arguments); + if (fsOpen.shouldTrace()) return fsOpen.traceSync(openSync, this, arguments); flags = stringToFlags(flags); mode = parseFileMode(mode, 'mode', 0o666); const h = vfsState.handlers; @@ -818,7 +857,7 @@ function openSync(path, flags, mode) { * @returns {Promise} */ function openAsBlob(path, options = kEmptyObject) { - if (shouldTraceFs('openAsBlob')) return traceFsPromise('openAsBlob', openAsBlob, this, arguments); + if (fsOpenAsBlob.shouldTrace()) return fsOpenAsBlob.tracePromise(openAsBlob, this, arguments); validateObject(options, 'options'); const { type = '' } = options; validateString(type, 'options.type'); @@ -845,7 +884,7 @@ function openAsBlob(path, options = kEmptyObject) { * @returns {Blob} */ function openAsBlobSync(path, options = kEmptyObject) { - if (shouldTraceFs('openAsBlob')) return traceFsSync('openAsBlob', openAsBlobSync, this, arguments); + if (fsOpenAsBlob.shouldTrace()) return fsOpenAsBlob.traceSync(openAsBlobSync, this, arguments); validateObject(options, 'options'); const { type = '' } = options; validateString(type, 'options.type'); @@ -880,7 +919,7 @@ function openAsBlobSync(path, options = kEmptyObject) { * @returns {void} */ function read(fd, buffer, offsetOrOptions, length, position, callback) { - if (shouldTraceFs('read')) return traceFsCallback('read', read, this, arguments); + if (fsRead.shouldTrace()) return fsRead.traceCallback(read, this, arguments); fd = getValidatedFd(fd); let offset = offsetOrOptions; let params = null; @@ -983,7 +1022,7 @@ ObjectDefineProperty(read, kCustomPromisifyArgsSymbol, * @returns {number} */ function readSync(fd, buffer, offsetOrOptions, length, position) { - if (shouldTraceFs('read')) return traceFsSync('read', readSync, this, arguments); + if (fsRead.shouldTrace()) return fsRead.traceSync(readSync, this, arguments); validateBuffer(buffer); let offset = offsetOrOptions; @@ -1047,7 +1086,7 @@ function readSync(fd, buffer, offsetOrOptions, length, position) { * @returns {void} */ function readv(fd, buffers, position, callback) { - if (shouldTraceFs('readv')) return traceFsCallback('readv', readv, this, arguments); + if (fsReadv.shouldTrace()) return fsReadv.traceCallback(readv, this, arguments); function wrapper(err, read) { callback(err, read || 0, buffers); } @@ -1089,7 +1128,7 @@ ObjectDefineProperty(readv, kCustomPromisifyArgsSymbol, * @returns {number} */ function readvSync(fd, buffers, position) { - if (shouldTraceFs('readv')) return traceFsSync('readv', readvSync, this, arguments); + if (fsReadv.shouldTrace()) return fsReadv.traceSync(readvSync, this, arguments); validateBufferArray(buffers); if (typeof position !== 'number') @@ -1119,7 +1158,7 @@ function readvSync(fd, buffers, position) { * @returns {void} */ function write(fd, buffer, offsetOrOptions, length, position, callback) { - if (shouldTraceFs('write')) return traceFsCallback('write', write, this, arguments); + if (fsWrite.shouldTrace()) return fsWrite.traceCallback(write, this, arguments); function wrapper(err, written) { // Retain a reference to buffer so that it can't be GC'ed too soon. callback(err, written || 0, buffer); @@ -1217,7 +1256,7 @@ ObjectDefineProperty(write, kCustomPromisifyArgsSymbol, * @returns {number} */ function writeSync(fd, buffer, offsetOrOptions, length, position) { - if (shouldTraceFs('write')) return traceFsSync('write', writeSync, this, arguments); + if (fsWrite.shouldTrace()) return fsWrite.traceSync(writeSync, this, arguments); const ctx = {}; let result; @@ -1284,7 +1323,7 @@ function writeSync(fd, buffer, offsetOrOptions, length, position) { * @returns {void} */ function writev(fd, buffers, position, callback) { - if (shouldTraceFs('writev')) return traceFsCallback('writev', writev, this, arguments); + if (fsWritev.shouldTrace()) return fsWritev.traceCallback(writev, this, arguments); function wrapper(err, written) { callback(err, written || 0, buffers); } @@ -1333,7 +1372,7 @@ ObjectDefineProperty(writev, kCustomPromisifyArgsSymbol, { * @returns {number} */ function writevSync(fd, buffers, position) { - if (shouldTraceFs('writev')) return traceFsSync('writev', writevSync, this, arguments); + if (fsWritev.shouldTrace()) return fsWritev.traceSync(writevSync, this, arguments); validateBufferArray(buffers); if (buffers.length === 0) { @@ -1361,7 +1400,7 @@ function writevSync(fd, buffers, position) { * @returns {void} */ function rename(oldPath, newPath, callback) { - if (shouldTraceFs('rename')) return traceFsCallback('rename', rename, this, arguments); + if (fsRename.shouldTrace()) return fsRename.traceCallback(rename, this, arguments); const h = vfsState.handlers; if (h !== null && vfsVoid(h.rename(oldPath, newPath), callback)) return; @@ -1384,7 +1423,7 @@ function rename(oldPath, newPath, callback) { * @returns {void} */ function renameSync(oldPath, newPath) { - if (shouldTraceFs('rename')) return traceFsSync('rename', renameSync, this, arguments); + if (fsRename.shouldTrace()) return fsRename.traceSync(renameSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.renameSync(oldPath, newPath); @@ -1404,7 +1443,7 @@ function renameSync(oldPath, newPath) { * @returns {void} */ function truncate(path, len, callback) { - if (shouldTraceFs('truncate')) return traceFsCallback('truncate', truncate, this, arguments); + if (fsTruncate.shouldTrace()) return fsTruncate.traceCallback(truncate, this, arguments); if (typeof len === 'function') { callback = len; len = 0; @@ -1438,7 +1477,7 @@ function truncate(path, len, callback) { * @returns {void} */ function truncateSync(path, len) { - if (shouldTraceFs('truncate')) return traceFsSync('truncate', truncateSync, this, arguments); + if (fsTruncate.shouldTrace()) return fsTruncate.traceSync(truncateSync, this, arguments); if (len === undefined) { len = 0; } @@ -1466,7 +1505,7 @@ function truncateSync(path, len) { * @returns {void} */ function ftruncate(fd, len = 0, callback) { - if (shouldTraceFs('ftruncate')) return traceFsCallback('ftruncate', ftruncate, this, arguments); + if (fsFtruncate.shouldTrace()) return fsFtruncate.traceCallback(ftruncate, this, arguments); if (typeof len === 'function') { callback = len; len = 0; @@ -1490,7 +1529,7 @@ function ftruncate(fd, len = 0, callback) { * @returns {void} */ function ftruncateSync(fd, len = 0) { - if (shouldTraceFs('ftruncate')) return traceFsSync('ftruncate', ftruncateSync, this, arguments); + if (fsFtruncate.shouldTrace()) return fsFtruncate.traceSync(ftruncateSync, this, arguments); validateInteger(len, 'len'); const h = vfsState.handlers; @@ -1523,7 +1562,7 @@ function lazyLoadRimraf() { * @returns {void} */ function rmdir(path, options, callback) { - if (shouldTraceFs('rmdir')) return traceFsCallback('rmdir', rmdir, this, arguments); + if (fsRmdir.shouldTrace()) return fsRmdir.traceCallback(rmdir, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -1560,7 +1599,7 @@ function rmdir(path, options, callback) { * @returns {void} */ function rmdirSync(path, options) { - if (shouldTraceFs('rmdir')) return traceFsSync('rmdir', rmdirSync, this, arguments); + if (fsRmdir.shouldTrace()) return fsRmdir.traceSync(rmdirSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.rmdirSync(path); @@ -1594,7 +1633,7 @@ function rmdirSync(path, options) { * @returns {void} */ function rm(path, options, callback) { - if (shouldTraceFs('rm')) return traceFsCallback('rm', rm, this, arguments); + if (fsRm.shouldTrace()) return fsRm.traceCallback(rm, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -1627,7 +1666,7 @@ function rm(path, options, callback) { * @returns {void} */ function rmSync(path, options) { - if (shouldTraceFs('rm')) return traceFsSync('rm', rmSync, this, arguments); + if (fsRm.shouldTrace()) return fsRm.traceSync(rmSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.rmSync(path, options); @@ -1646,7 +1685,7 @@ function rmSync(path, options) { * @returns {void} */ function fdatasync(fd, callback) { - if (shouldTraceFs('fdatasync')) return traceFsCallback('fdatasync', fdatasync, this, arguments); + if (fsFdatasync.shouldTrace()) return fsFdatasync.traceCallback(fdatasync, this, arguments); callback = makeCallback(callback); const h = vfsState.handlers; @@ -1670,7 +1709,7 @@ function fdatasync(fd, callback) { * @returns {void} */ function fdatasyncSync(fd) { - if (shouldTraceFs('fdatasync')) return traceFsSync('fdatasync', fdatasyncSync, this, arguments); + if (fsFdatasync.shouldTrace()) return fsFdatasync.traceSync(fdatasyncSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.fdatasyncSync(fd); @@ -1691,7 +1730,7 @@ function fdatasyncSync(fd) { * @returns {void} */ function fsync(fd, callback) { - if (shouldTraceFs('fsync')) return traceFsCallback('fsync', fsync, this, arguments); + if (fsFsync.shouldTrace()) return fsFsync.traceCallback(fsync, this, arguments); callback = makeCallback(callback); const h = vfsState.handlers; @@ -1713,7 +1752,7 @@ function fsync(fd, callback) { * @returns {void} */ function fsyncSync(fd) { - if (shouldTraceFs('fsync')) return traceFsSync('fsync', fsyncSync, this, arguments); + if (fsFsync.shouldTrace()) return fsFsync.traceSync(fsyncSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.fsyncSync(fd); @@ -1737,7 +1776,7 @@ function fsyncSync(fd) { * @returns {void} */ function mkdir(path, options, callback) { - if (shouldTraceFs('mkdir')) return traceFsCallback('mkdir', mkdir, this, arguments); + if (fsMkdir.shouldTrace()) return fsMkdir.traceCallback(mkdir, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -1787,7 +1826,7 @@ function mkdir(path, options, callback) { * @returns {string | void} */ function mkdirSync(path, options) { - if (shouldTraceFs('mkdir')) return traceFsSync('mkdir', mkdirSync, this, arguments); + if (fsMkdir.shouldTrace()) return fsMkdir.traceSync(mkdirSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const vfsResult = h.mkdirSync(path, options); @@ -1936,7 +1975,7 @@ function readdirSyncRecursive(basePath, options) { * @returns {void} */ function readdir(path, options, callback) { - if (shouldTraceFs('readdir')) return traceFsCallback('readdir', readdir, this, arguments); + if (fsReaddir.shouldTrace()) return fsReaddir.traceCallback(readdir, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -1988,7 +2027,7 @@ function readdir(path, options, callback) { * @returns {string | Buffer[] | Dirent[]} */ function readdirSync(path, options) { - if (shouldTraceFs('readdir')) return traceFsSync('readdir', readdirSync, this, arguments); + if (fsReaddir.shouldTrace()) return fsReaddir.traceSync(readdirSync, this, arguments); options = getOptions(options); path = getValidatedPath(path); if (options.recursive != null) { @@ -2026,7 +2065,7 @@ function readdirSync(path, options) { * @returns {void} */ function fstat(fd, options = { __proto__: null, bigint: false }, callback) { - if (shouldTraceFs('fstat')) return traceFsCallback('fstat', fstat, this, arguments); + if (fsFstat.shouldTrace()) return fsFstat.traceCallback(fstat, this, arguments); if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2059,7 +2098,7 @@ function fstat(fd, options = { __proto__: null, bigint: false }, callback) { * @returns {void} */ function lstat(path, options = { __proto__: null, bigint: false }, callback) { - if (shouldTraceFs('lstat')) return traceFsCallback('lstat', lstat, this, arguments); + if (fsLstat.shouldTrace()) return fsLstat.traceCallback(lstat, this, arguments); if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2100,7 +2139,7 @@ function lstat(path, options = { __proto__: null, bigint: false }, callback) { * @returns {void} */ function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }, callback) { - if (shouldTraceFs('stat')) return traceFsCallback('stat', stat, this, arguments); + if (fsStat.shouldTrace()) return fsStat.traceCallback(stat, this, arguments); if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2125,7 +2164,7 @@ function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: } function statfs(path, options = { __proto__: null, bigint: false }, callback) { - if (shouldTraceFs('statfs')) return traceFsCallback('statfs', statfs, this, arguments); + if (fsStatfs.shouldTrace()) return fsStatfs.traceCallback(statfs, this, arguments); if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2167,7 +2206,7 @@ function statfs(path, options = { __proto__: null, bigint: false }, callback) { * @returns {Stats | undefined} */ function fstatSync(fd, options = { __proto__: null, bigint: false }) { - if (shouldTraceFs('fstat')) return traceFsSync('fstat', fstatSync, this, arguments); + if (fsFstat.shouldTrace()) return fsFstat.traceSync(fstatSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.fstatSync(fd); @@ -2191,7 +2230,7 @@ function fstatSync(fd, options = { __proto__: null, bigint: false }) { * @returns {Stats | undefined} */ function lstatSync(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { - if (shouldTraceFs('lstat')) return traceFsSync('lstat', lstatSync, this, arguments); + if (fsLstat.shouldTrace()) return fsLstat.traceSync(lstatSync, this, arguments); const h = vfsState.handlers; if (h !== null) { try { @@ -2232,7 +2271,7 @@ function lstatSync(path, options = { __proto__: null, bigint: false, throwIfNoEn * @returns {Stats} */ function statSync(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { - if (shouldTraceFs('stat')) return traceFsSync('stat', statSync, this, arguments); + if (fsStat.shouldTrace()) return fsStat.traceSync(statSync, this, arguments); const h = vfsState.handlers; if (h !== null) { try { @@ -2256,7 +2295,7 @@ function statSync(path, options = { __proto__: null, bigint: false, throwIfNoEnt } function statfsSync(path, options = { __proto__: null, bigint: false }) { - if (shouldTraceFs('statfs')) return traceFsSync('statfs', statfsSync, this, arguments); + if (fsStatfs.shouldTrace()) return fsStatfs.traceSync(statfsSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.statfsSync(path, options); @@ -2279,7 +2318,7 @@ function statfsSync(path, options = { __proto__: null, bigint: false }) { * @returns {void} */ function readlink(path, options, callback) { - if (shouldTraceFs('readlink')) return traceFsCallback('readlink', readlink, this, arguments); + if (fsReadlink.shouldTrace()) return fsReadlink.traceCallback(readlink, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -2303,7 +2342,7 @@ function readlink(path, options, callback) { * @returns {string | Buffer} */ function readlinkSync(path, options) { - if (shouldTraceFs('readlink')) return traceFsSync('readlink', readlinkSync, this, arguments); + if (fsReadlink.shouldTrace()) return fsReadlink.traceSync(readlinkSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.readlinkSync(path, options); @@ -2322,7 +2361,7 @@ function readlinkSync(path, options) { * @returns {void} */ function symlink(target, path, type, callback) { - if (shouldTraceFs('symlink')) return traceFsCallback('symlink', symlink, this, arguments); + if (fsSymlink.shouldTrace()) return fsSymlink.traceCallback(symlink, this, arguments); if (callback === undefined) { callback = makeCallback(type); type = undefined; @@ -2395,7 +2434,7 @@ function symlink(target, path, type, callback) { * @returns {void} */ function symlinkSync(target, path, type) { - if (shouldTraceFs('symlink')) return traceFsSync('symlink', symlinkSync, this, arguments); + if (fsSymlink.shouldTrace()) return fsSymlink.traceSync(symlinkSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.symlinkSync(target, path, type); @@ -2436,7 +2475,7 @@ function symlinkSync(target, path, type) { * @returns {void} */ function link(existingPath, newPath, callback) { - if (shouldTraceFs('link')) return traceFsCallback('link', link, this, arguments); + if (fsLink.shouldTrace()) return fsLink.traceCallback(link, this, arguments); callback = makeCallback(callback); const h = vfsState.handlers; @@ -2459,7 +2498,7 @@ function link(existingPath, newPath, callback) { * @returns {void} */ function linkSync(existingPath, newPath) { - if (shouldTraceFs('link')) return traceFsSync('link', linkSync, this, arguments); + if (fsLink.shouldTrace()) return fsLink.traceSync(linkSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.linkSync(existingPath, newPath); @@ -2482,7 +2521,7 @@ function linkSync(existingPath, newPath) { * @returns {void} */ function unlink(path, callback) { - if (shouldTraceFs('unlink')) return traceFsCallback('unlink', unlink, this, arguments); + if (fsUnlink.shouldTrace()) return fsUnlink.traceCallback(unlink, this, arguments); const h = vfsState.handlers; if (h !== null && vfsVoid(h.unlink(path), callback)) return; @@ -2498,7 +2537,7 @@ function unlink(path, callback) { * @returns {void} */ function unlinkSync(path) { - if (shouldTraceFs('unlink')) return traceFsSync('unlink', unlinkSync, this, arguments); + if (fsUnlink.shouldTrace()) return fsUnlink.traceSync(unlinkSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.unlinkSync(path); @@ -2515,7 +2554,7 @@ function unlinkSync(path) { * @returns {void} */ function fchmod(fd, mode, callback) { - if (shouldTraceFs('fchmod')) return traceFsCallback('fchmod', fchmod, this, arguments); + if (fsFchmod.shouldTrace()) return fsFchmod.traceCallback(fchmod, this, arguments); mode = parseFileMode(mode, 'mode'); callback = makeCallback(callback); @@ -2539,7 +2578,7 @@ function fchmod(fd, mode, callback) { * @returns {void} */ function fchmodSync(fd, mode) { - if (shouldTraceFs('fchmod')) return traceFsSync('fchmod', fchmodSync, this, arguments); + if (fsFchmod.shouldTrace()) return fsFchmod.traceSync(fchmodSync, this, arguments); mode = parseFileMode(mode, 'mode'); const h = vfsState.handlers; @@ -2562,7 +2601,7 @@ function fchmodSync(fd, mode) { * @returns {void} */ function lchmod(path, mode, callback) { - if (shouldTraceFs('lchmod')) return traceFsCallback('lchmod', lchmod, this, arguments); + if (fsLchmod.shouldTrace()) return fsLchmod.traceCallback(lchmod, this, arguments); validateFunction(callback, 'cb'); mode = parseFileMode(mode, 'mode'); @@ -2591,7 +2630,7 @@ function lchmod(path, mode, callback) { * @returns {void} */ function lchmodSync(path, mode) { - if (shouldTraceFs('lchmod')) return traceFsSync('lchmod', lchmodSync, this, arguments); + if (fsLchmod.shouldTrace()) return fsLchmod.traceSync(lchmodSync, this, arguments); const fd = fs.openSync(path, O_WRONLY | O_SYMLINK); // Prefer to return the chmod error, if one occurs, @@ -2611,7 +2650,7 @@ function lchmodSync(path, mode) { * @returns {void} */ function chmod(path, mode, callback) { - if (shouldTraceFs('chmod')) return traceFsCallback('chmod', chmod, this, arguments); + if (fsChmod.shouldTrace()) return fsChmod.traceCallback(chmod, this, arguments); path = getValidatedPath(path); mode = parseFileMode(mode, 'mode'); callback = makeCallback(callback); @@ -2631,7 +2670,7 @@ function chmod(path, mode, callback) { * @returns {void} */ function chmodSync(path, mode) { - if (shouldTraceFs('chmod')) return traceFsSync('chmod', chmodSync, this, arguments); + if (fsChmod.shouldTrace()) return fsChmod.traceSync(chmodSync, this, arguments); path = getValidatedPath(path); mode = parseFileMode(mode, 'mode'); @@ -2653,7 +2692,7 @@ function chmodSync(path, mode) { * @returns {void} */ function lchown(path, uid, gid, callback) { - if (shouldTraceFs('lchown')) return traceFsCallback('lchown', lchown, this, arguments); + if (fsLchown.shouldTrace()) return fsLchown.traceCallback(lchown, this, arguments); callback = makeCallback(callback); path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); @@ -2675,7 +2714,7 @@ function lchown(path, uid, gid, callback) { * @returns {void} */ function lchownSync(path, uid, gid) { - if (shouldTraceFs('lchown')) return traceFsSync('lchown', lchownSync, this, arguments); + if (fsLchown.shouldTrace()) return fsLchown.traceSync(lchownSync, this, arguments); path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); @@ -2698,7 +2737,7 @@ function lchownSync(path, uid, gid) { * @returns {void} */ function fchown(fd, uid, gid, callback) { - if (shouldTraceFs('fchown')) return traceFsCallback('fchown', fchown, this, arguments); + if (fsFchown.shouldTrace()) return fsFchown.traceCallback(fchown, this, arguments); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); callback = makeCallback(callback); @@ -2724,7 +2763,7 @@ function fchown(fd, uid, gid, callback) { * @returns {void} */ function fchownSync(fd, uid, gid) { - if (shouldTraceFs('fchown')) return traceFsSync('fchown', fchownSync, this, arguments); + if (fsFchown.shouldTrace()) return fsFchown.traceSync(fchownSync, this, arguments); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); @@ -2751,7 +2790,7 @@ function fchownSync(fd, uid, gid) { * @returns {void} */ function chown(path, uid, gid, callback) { - if (shouldTraceFs('chown')) return traceFsCallback('chown', chown, this, arguments); + if (fsChown.shouldTrace()) return fsChown.traceCallback(chown, this, arguments); callback = makeCallback(callback); path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); @@ -2774,7 +2813,7 @@ function chown(path, uid, gid, callback) { * @returns {void} */ function chownSync(path, uid, gid) { - if (shouldTraceFs('chown')) return traceFsSync('chown', chownSync, this, arguments); + if (fsChown.shouldTrace()) return fsChown.traceSync(chownSync, this, arguments); path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); @@ -2798,7 +2837,7 @@ function chownSync(path, uid, gid) { * @returns {void} */ function utimes(path, atime, mtime, callback) { - if (shouldTraceFs('utimes')) return traceFsCallback('utimes', utimes, this, arguments); + if (fsUtimes.shouldTrace()) return fsUtimes.traceCallback(utimes, this, arguments); callback = makeCallback(callback); path = getValidatedPath(path); atime = toUnixTimestamp(atime); @@ -2821,7 +2860,7 @@ function utimes(path, atime, mtime, callback) { * @returns {void} */ function utimesSync(path, atime, mtime) { - if (shouldTraceFs('utimes')) return traceFsSync('utimes', utimesSync, this, arguments); + if (fsUtimes.shouldTrace()) return fsUtimes.traceSync(utimesSync, this, arguments); path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -2845,7 +2884,7 @@ function utimesSync(path, atime, mtime) { * @returns {void} */ function futimes(fd, atime, mtime, callback) { - if (shouldTraceFs('futimes')) return traceFsCallback('futimes', futimes, this, arguments); + if (fsFutimes.shouldTrace()) return fsFutimes.traceCallback(futimes, this, arguments); atime = toUnixTimestamp(atime, 'atime'); mtime = toUnixTimestamp(mtime, 'mtime'); callback = makeCallback(callback); @@ -2873,7 +2912,7 @@ function futimes(fd, atime, mtime, callback) { * @returns {void} */ function futimesSync(fd, atime, mtime) { - if (shouldTraceFs('futimes')) return traceFsSync('futimes', futimesSync, this, arguments); + if (fsFutimes.shouldTrace()) return fsFutimes.traceSync(futimesSync, this, arguments); atime = toUnixTimestamp(atime, 'atime'); mtime = toUnixTimestamp(mtime, 'mtime'); @@ -2900,7 +2939,7 @@ function futimesSync(fd, atime, mtime) { * @returns {void} */ function lutimes(path, atime, mtime, callback) { - if (shouldTraceFs('lutimes')) return traceFsCallback('lutimes', lutimes, this, arguments); + if (fsLutimes.shouldTrace()) return fsLutimes.traceCallback(lutimes, this, arguments); callback = makeCallback(callback); path = getValidatedPath(path); atime = toUnixTimestamp(atime); @@ -2923,7 +2962,7 @@ function lutimes(path, atime, mtime, callback) { * @returns {void} */ function lutimesSync(path, atime, mtime) { - if (shouldTraceFs('lutimes')) return traceFsSync('lutimes', lutimesSync, this, arguments); + if (fsLutimes.shouldTrace()) return fsLutimes.traceSync(lutimesSync, this, arguments); path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -3006,7 +3045,7 @@ function writeAll(fd, isUserFd, buffer, offset, length, signal, flush, callback) * @returns {void} */ function writeFile(path, data, options, callback) { - if (shouldTraceFs('writeFile')) return traceFsCallback('writeFile', writeFile, this, arguments); + if (fsWriteFile.shouldTrace()) return fsWriteFile.traceCallback(writeFile, this, arguments); callback ||= options; validateFunction(callback, 'cb'); @@ -3084,7 +3123,7 @@ function writeFile(path, data, options, callback) { * @returns {void} */ function writeFileSync(path, data, options) { - if (shouldTraceFs('writeFile')) return traceFsSync('writeFile', writeFileSync, this, arguments); + if (fsWriteFile.shouldTrace()) return fsWriteFile.traceSync(writeFileSync, this, arguments); options = getOptions(options, { encoding: 'utf8', mode: 0o666, @@ -3160,7 +3199,7 @@ function writeFileSync(path, data, options) { * @returns {void} */ function appendFile(path, data, options, callback) { - if (shouldTraceFs('appendFile')) return traceFsCallback('appendFile', appendFile, this, arguments); + if (fsAppendFile.shouldTrace()) return fsAppendFile.traceCallback(appendFile, this, arguments); callback ||= options; validateFunction(callback, 'cb'); @@ -3197,7 +3236,7 @@ function appendFile(path, data, options, callback) { * @returns {void} */ function appendFileSync(path, data, options) { - if (shouldTraceFs('appendFile')) return traceFsSync('appendFile', appendFileSync, this, arguments); + if (fsAppendFile.shouldTrace()) return fsAppendFile.traceSync(appendFileSync, this, arguments); options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); parseFileMode(options.mode, 'mode', 0o666); @@ -3446,7 +3485,7 @@ if (isWindows) { * @returns {string | Buffer} */ function realpathSync(p, options) { - if (shouldTraceFs('realpath')) return traceFsSync('realpath', realpathSync, this, arguments); + if (fsRealpath.shouldTrace()) return fsRealpath.traceSync(realpathSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.realpathSync(p, options); @@ -3596,8 +3635,8 @@ function realpathSync(p, options) { * @returns {string | Buffer} */ realpathSync.native = function native(path, options) { - if (shouldTraceFs('realpath')) { - return traceFsSync('realpath', native, undefined, arguments); + if (fsRealpath.shouldTrace()) { + return fsRealpath.traceSync(native, undefined, arguments); } const h = vfsState.handlers; if (h !== null) { @@ -3623,7 +3662,7 @@ realpathSync.native = function native(path, options) { * @returns {void} */ function realpath(p, options, callback) { - if (shouldTraceFs('realpath')) return traceFsCallback('realpath', realpath, this, arguments); + if (fsRealpath.shouldTrace()) return fsRealpath.traceCallback(realpath, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -3777,8 +3816,8 @@ function realpath(p, options, callback) { * @returns {void} */ realpath.native = function native(path, options, callback) { - if (shouldTraceFs('realpath')) { - return traceFsCallback('realpath', native, undefined, arguments); + if (fsRealpath.shouldTrace()) { + return fsRealpath.traceCallback(native, undefined, arguments); } callback = makeCallback(callback || options); const h = vfsState.handlers; @@ -3801,7 +3840,7 @@ realpath.native = function native(path, options, callback) { * @returns {void} */ function mkdtemp(prefix, options, callback) { - if (shouldTraceFs('mkdtemp')) return traceFsCallback('mkdtemp', mkdtemp, this, arguments); + if (fsMkdtemp.shouldTrace()) return fsMkdtemp.traceCallback(mkdtemp, this, arguments); callback = makeCallback(typeof options === 'function' ? options : callback); const h = vfsState.handlers; @@ -3826,7 +3865,7 @@ function mkdtemp(prefix, options, callback) { * @returns {string} */ function mkdtempSync(prefix, options) { - if (shouldTraceFs('mkdtemp')) return traceFsSync('mkdtemp', mkdtempSync, this, arguments); + if (fsMkdtemp.shouldTrace()) return fsMkdtemp.traceSync(mkdtempSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.mkdtempSync(prefix, options); @@ -3851,7 +3890,7 @@ function mkdtempSync(prefix, options) { * @returns {object} A disposable object with a "path" property. */ function mkdtempDisposableSync(prefix, options) { - if (shouldTraceFs('mkdtemp')) return traceFsSync('mkdtemp', mkdtempDisposableSync, this, arguments); + if (fsMkdtemp.shouldTrace()) return fsMkdtemp.traceSync(mkdtempDisposableSync, this, arguments); options = getOptions(options); if (BufferIsBuffer(prefix)) { options = { ...options, encoding: 'buffer' }; @@ -3937,7 +3976,7 @@ function mkstempSync(prefix, options) { * @returns {void} */ function copyFile(src, dest, mode, callback) { - if (shouldTraceFs('copyFile')) return traceFsCallback('copyFile', copyFile, this, arguments); + if (fsCopyFile.shouldTrace()) return fsCopyFile.traceCallback(copyFile, this, arguments); if (typeof mode === 'function') { callback = mode; mode = 0; @@ -3964,7 +4003,7 @@ function copyFile(src, dest, mode, callback) { * @returns {void} */ function copyFileSync(src, dest, mode) { - if (shouldTraceFs('copyFile')) return traceFsSync('copyFile', copyFileSync, this, arguments); + if (fsCopyFile.shouldTrace()) return fsCopyFile.traceSync(copyFileSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.copyFileSync(src, dest, mode); @@ -3987,7 +4026,7 @@ function copyFileSync(src, dest, mode) { * @returns {void} */ function cp(src, dest, options, callback) { - if (shouldTraceFs('cp')) return traceFsCallback('cp', cp, this, arguments); + if (fsCp.shouldTrace()) return fsCp.traceCallback(cp, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -4009,7 +4048,7 @@ function cp(src, dest, options, callback) { * @returns {void} */ function cpSync(src, dest, options) { - if (shouldTraceFs('cp')) return traceFsSync('cp', cpSync, this, arguments); + if (fsCp.shouldTrace()) return fsCp.traceSync(cpSync, this, arguments); options = validateCpOptions(options); src = getValidatedPath(src, 'src'); dest = getValidatedPath(dest, 'dest'); @@ -4085,7 +4124,7 @@ function createWriteStream(path, options) { const lazyGlob = getLazy(() => require('internal/fs/glob').Glob); function glob(pattern, options, callback) { - if (shouldTraceFs('glob')) return traceFsCallback('glob', glob, this, arguments); + if (fsGlob.shouldTrace()) return fsGlob.traceCallback(glob, this, arguments); if (typeof options === 'function') { callback = options; options = undefined; @@ -4101,7 +4140,7 @@ function glob(pattern, options, callback) { } function globSync(pattern, options) { - if (shouldTraceFs('glob')) return traceFsSync('glob', globSync, this, arguments); + if (fsGlob.shouldTrace()) return fsGlob.traceSync(globSync, this, arguments); const Glob = lazyGlob(); return new Glob(pattern, options).globSync(); } diff --git a/lib/internal/fs/dir.js b/lib/internal/fs/dir.js index 27677a3a521..ebbaed25e97 100644 --- a/lib/internal/fs/dir.js +++ b/lib/internal/fs/dir.js @@ -29,13 +29,11 @@ const { } = require('internal/util'); const { getDirent, - getFsChannel, getOptions, - shouldTraceFs, - traceFsCallback, - traceFsSync, getValidatedPath, vfsState, + fsClosedir, + fsOpendir, } = require('internal/fs/utils'); const { validateFunction, @@ -255,18 +253,18 @@ class Dir { validateFunction(callback, 'callback'); - if (getFsChannel('closedir').hasSubscribers) { + if (fsClosedir.channel.hasSubscribers) { const context = { api: 'callback', args: [callback], path: this.#path }; - return getFsChannel('closedir').traceCallback(this.#closeImpl, -1, context, this, - callback); + return fsClosedir.channel.traceCallback(this.#closeImpl, -1, context, this, + callback); } this.#closeImpl(callback); } #closePromise() { - if (getFsChannel('closedir').hasSubscribers) { + if (fsClosedir.channel.hasSubscribers) { const context = { api: 'promise', args: [], path: this.#path }; - return getFsChannel('closedir').tracePromise(this.#closePromisified, context); + return fsClosedir.channel.tracePromise(this.#closePromisified, context); } return this.#closePromisified(); } @@ -310,9 +308,9 @@ class Dir { } this.#closed = true; - if (getFsChannel('closedir').hasSubscribers) { + if (fsClosedir.channel.hasSubscribers) { const context = { api: 'sync', args: [], path: this.#path }; - getFsChannel('closedir').traceSync(this.#handle.close, context, this.#handle); + fsClosedir.channel.traceSync(this.#handle.close, context, this.#handle); return; } this.#handle.close(); @@ -354,7 +352,7 @@ ObjectDefineProperties(Dir.prototype, { }); function opendir(path, options, callback) { - if (shouldTraceFs('opendir')) return traceFsCallback('opendir', opendir, this, arguments); + if (fsOpendir.shouldTrace()) return fsOpendir.traceCallback(opendir, this, arguments); callback = typeof options === 'function' ? options : callback; validateFunction(callback, 'callback'); @@ -396,7 +394,7 @@ function opendir(path, options, callback) { } function opendirSync(path, options) { - if (shouldTraceFs('opendir')) return traceFsSync('opendir', opendirSync, this, arguments); + if (fsOpendir.shouldTrace()) return fsOpendir.traceSync(opendirSync, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.opendirSync(path, options); diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 8804b66b53d..29f8c01aad8 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -65,7 +65,6 @@ const { collectRecursiveReaddirResult, copyObject, getDirents, - getFsChannel, getRecursiveDirents, getOptions, getStatFsFromBinding, @@ -74,13 +73,11 @@ const { getReadFileBuffer, getReadFileBufferByteLengthName, preprocessSymlinkDestination, - shouldTraceFs, stringToFlags, stringToSymlinkType, toUnixTimestamp, handleErrorFromBinding: handleSyncErrorFromBinding, callUntraced, - traceFsPromise, validateBufferArray, validateCpOptions, validateOffsetLengthRead, @@ -92,6 +89,47 @@ const { validateStringAfterArrayBufferView, vfsState, warnOnNonPortableTemplate, + fsAccess, + fsAppendFile, + fsChmod, + fsChown, + fsClose, + fsCopyFile, + fsCp, + fsFchmod, + fsFchown, + fsFdatasync, + fsFstat, + fsFsync, + fsFtruncate, + fsFutimes, + fsLchmod, + fsLchown, + fsLink, + fsLstat, + fsLutimes, + fsMkdir, + fsMkdtemp, + fsOpen, + fsOpendir, + fsRead, + fsReadFile, + fsReaddir, + fsReadlink, + fsReadv, + fsRealpath, + fsRename, + fsRm, + fsRmdir, + fsStat, + fsStatfs, + fsSymlink, + fsTruncate, + fsUnlink, + fsUtimes, + fsWrite, + fsWriteFile, + fsWritev, } = require('internal/fs/utils'); const { opendir: opendirCallback } = require('internal/fs/dir'); const { @@ -192,9 +230,9 @@ function handleErrorFromBinding(error) { } function closeFileHandle(handle, fd) { - if (getFsChannel('close').hasSubscribers) { + if (fsClose.channel.hasSubscribers) { const context = { api: 'promise', args: [], fd }; - return getFsChannel('close').tracePromise(handle.close, context, handle); + return fsClose.channel.tracePromise(handle.close, context, handle); } return handle.close(); } @@ -331,9 +369,9 @@ class FileHandle extends EventEmitter { } const fd = this[kFd]; this[kFd] = -1; - if (getFsChannel('close').hasSubscribers) { + if (fsClose.channel.hasSubscribers) { const context = { api: 'sync', args: [], fd }; - getFsChannel('close').traceSync(this[kHandle].closeSync, context, this[kHandle]); + fsClose.channel.traceSync(this[kHandle].closeSync, context, this[kHandle]); } else { this[kHandle].closeSync(); } @@ -1436,7 +1474,7 @@ async function readFileHandle(filehandle, options, knownRegularFileSize) { // All of the functions are defined as async in order to ensure that errors // thrown cause promise rejections rather than being thrown synchronously. async function access(path, mode = F_OK) { - if (shouldTraceFs('access')) return await traceFsPromise('access', access, this, arguments); + if (fsAccess.shouldTrace()) return await fsAccess.tracePromise(access, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.access(path, mode); @@ -1450,7 +1488,7 @@ async function access(path, mode = F_OK) { } async function cp(src, dest, options) { - if (shouldTraceFs('cp')) return await traceFsPromise('cp', cp, this, arguments); + if (fsCp.shouldTrace()) return await fsCp.tracePromise(cp, this, arguments); options = validateCpOptions(options); src = getValidatedPath(src, 'src'); dest = getValidatedPath(dest, 'dest'); @@ -1458,7 +1496,7 @@ async function cp(src, dest, options) { } async function copyFile(src, dest, mode) { - if (shouldTraceFs('copyFile')) return await traceFsPromise('copyFile', copyFile, this, arguments); + if (fsCopyFile.shouldTrace()) return await fsCopyFile.tracePromise(copyFile, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.copyFile(src, dest, mode); @@ -1479,7 +1517,7 @@ async function copyFile(src, dest, mode) { // Note that unlike fs.open() which uses numeric file descriptors, // fsPromises.open() uses the fs.FileHandle class. async function open(path, flags, mode) { - if (shouldTraceFs('open')) return await traceFsPromise('open', open, this, arguments); + if (fsOpen.shouldTrace()) return await fsOpen.tracePromise(open, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.promisesOpen(path, flags, mode); @@ -1496,7 +1534,7 @@ async function open(path, flags, mode) { } async function read(handle, bufferOrParams, offset, length, position) { - if (shouldTraceFs('read')) return await traceFsPromise('read', read, this, arguments); + if (fsRead.shouldTrace()) return await fsRead.tracePromise(read, this, arguments); let buffer = bufferOrParams; if (!isArrayBufferView(buffer)) { // This is fh.read(params) @@ -1557,7 +1595,7 @@ async function read(handle, bufferOrParams, offset, length, position) { } async function readv(handle, buffers, position) { - if (shouldTraceFs('readv')) return await traceFsPromise('readv', readv, this, arguments); + if (fsReadv.shouldTrace()) return await fsReadv.tracePromise(readv, this, arguments); validateBufferArray(buffers); if (typeof position !== 'number') @@ -1572,7 +1610,7 @@ async function readv(handle, buffers, position) { } async function write(handle, buffer, offsetOrOptions, length, position) { - if (shouldTraceFs('write')) return await traceFsPromise('write', write, this, arguments); + if (fsWrite.shouldTrace()) return await fsWrite.tracePromise(write, this, arguments); if (buffer?.byteLength === 0) return { __proto__: null, bytesWritten: 0, buffer }; @@ -1617,7 +1655,7 @@ async function write(handle, buffer, offsetOrOptions, length, position) { } async function writev(handle, buffers, position) { - if (shouldTraceFs('writev')) return await traceFsPromise('writev', writev, this, arguments); + if (fsWritev.shouldTrace()) return await fsWritev.tracePromise(writev, this, arguments); validateBufferArray(buffers); if (typeof position !== 'number') @@ -1636,7 +1674,7 @@ async function writev(handle, buffers, position) { } async function rename(oldPath, newPath) { - if (shouldTraceFs('rename')) return await traceFsPromise('rename', rename, this, arguments); + if (fsRename.shouldTrace()) return await fsRename.tracePromise(rename, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.rename(oldPath, newPath); @@ -1652,7 +1690,7 @@ async function rename(oldPath, newPath) { } async function truncate(path, len = 0) { - if (shouldTraceFs('truncate')) return await traceFsPromise('truncate', truncate, this, arguments); + if (fsTruncate.shouldTrace()) return await fsTruncate.tracePromise(truncate, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.truncate(path, len); @@ -1663,7 +1701,7 @@ async function truncate(path, len = 0) { } async function ftruncate(handle, len = 0) { - if (shouldTraceFs('ftruncate')) return await traceFsPromise('ftruncate', ftruncate, this, arguments); + if (fsFtruncate.shouldTrace()) return await fsFtruncate.tracePromise(ftruncate, this, arguments); validateInteger(len, 'len'); len = MathMax(0, len); return await PromisePrototypeThen( @@ -1674,7 +1712,7 @@ async function ftruncate(handle, len = 0) { } async function rm(path, options) { - if (shouldTraceFs('rm')) return await traceFsPromise('rm', rm, this, arguments); + if (fsRm.shouldTrace()) return await fsRm.tracePromise(rm, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.rm(path, options); @@ -1686,7 +1724,7 @@ async function rm(path, options) { } async function rmdir(path, options) { - if (shouldTraceFs('rmdir')) return await traceFsPromise('rmdir', rmdir, this, arguments); + if (fsRmdir.shouldTrace()) return await fsRmdir.tracePromise(rmdir, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.rmdir(path); @@ -1712,7 +1750,7 @@ async function rmdir(path, options) { } async function fdatasync(handle) { - if (shouldTraceFs('fdatasync')) return await traceFsPromise('fdatasync', fdatasync, this, arguments); + if (fsFdatasync.shouldTrace()) return await fsFdatasync.tracePromise(fdatasync, this, arguments); if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.'); } @@ -1724,7 +1762,7 @@ async function fdatasync(handle) { } async function fsync(handle) { - if (shouldTraceFs('fsync')) return await traceFsPromise('fsync', fsync, this, arguments); + if (fsFsync.shouldTrace()) return await fsFsync.tracePromise(fsync, this, arguments); if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.'); } @@ -1736,7 +1774,7 @@ async function fsync(handle) { } async function mkdir(path, options) { - if (shouldTraceFs('mkdir')) return await traceFsPromise('mkdir', mkdir, this, arguments); + if (fsMkdir.shouldTrace()) return await fsMkdir.tracePromise(mkdir, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.mkdir(path, options); @@ -1804,7 +1842,7 @@ async function readdirRecursiveWithPermissionModel(basePath, options) { } async function readdir(path, options) { - if (shouldTraceFs('readdir')) return await traceFsPromise('readdir', readdir, this, arguments); + if (fsReaddir.shouldTrace()) return await fsReaddir.tracePromise(readdir, this, arguments); options = getOptions(options); // Make shallow copy to prevent mutating options from affecting results @@ -1836,7 +1874,7 @@ async function readdir(path, options) { } async function readlink(path, options) { - if (shouldTraceFs('readlink')) return await traceFsPromise('readlink', readlink, this, arguments); + if (fsReadlink.shouldTrace()) return await fsReadlink.tracePromise(readlink, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.readlink(path, options); @@ -1852,7 +1890,7 @@ async function readlink(path, options) { } async function symlink(target, path, type) { - if (shouldTraceFs('symlink')) return await traceFsPromise('symlink', symlink, this, arguments); + if (fsSymlink.shouldTrace()) return await fsSymlink.tracePromise(symlink, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.symlink(target, path, type); @@ -1892,7 +1930,7 @@ async function symlink(target, path, type) { } async function fstat(handle, options = { __proto__: null, bigint: false }) { - if (shouldTraceFs('fstat')) return await traceFsPromise('fstat', fstat, this, arguments); + if (fsFstat.shouldTrace()) return await fsFstat.tracePromise(fstat, this, arguments); validateObject(options, 'options'); const { signal } = options; if (signal !== undefined) validateAbortSignal(signal, 'options.signal'); @@ -1909,7 +1947,7 @@ async function fstat(handle, options = { __proto__: null, bigint: false }) { } async function lstat(path, options = { __proto__: null, bigint: false }) { - if (shouldTraceFs('lstat')) return await traceFsPromise('lstat', lstat, this, arguments); + if (fsLstat.shouldTrace()) return await fsLstat.tracePromise(lstat, this, arguments); validateObject(options, 'options'); const { signal } = options; if (signal !== undefined) validateAbortSignal(signal, 'options.signal'); @@ -1937,7 +1975,7 @@ async function lstat(path, options = { __proto__: null, bigint: false }) { } async function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { - if (shouldTraceFs('stat')) return await traceFsPromise('stat', stat, this, arguments); + if (fsStat.shouldTrace()) return await fsStat.tracePromise(stat, this, arguments); validateObject(options, 'options'); const { signal } = options; if (signal !== undefined) validateAbortSignal(signal, 'options.signal'); @@ -1963,7 +2001,7 @@ async function stat(path, options = { __proto__: null, bigint: false, throwIfNoE } async function statfs(path, options = { __proto__: null, bigint: false }) { - if (shouldTraceFs('statfs')) return await traceFsPromise('statfs', statfs, this, arguments); + if (fsStatfs.shouldTrace()) return await fsStatfs.tracePromise(statfs, this, arguments); const h = vfsState.handlers; if (h !== null) { const result = h.statfs(path, options); @@ -1979,7 +2017,7 @@ async function statfs(path, options = { __proto__: null, bigint: false }) { } async function link(existingPath, newPath) { - if (shouldTraceFs('link')) return await traceFsPromise('link', link, this, arguments); + if (fsLink.shouldTrace()) return await fsLink.tracePromise(link, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.link(existingPath, newPath); @@ -1995,7 +2033,7 @@ async function link(existingPath, newPath) { } async function unlink(path) { - if (shouldTraceFs('unlink')) return await traceFsPromise('unlink', unlink, this, arguments); + if (fsUnlink.shouldTrace()) return await fsUnlink.tracePromise(unlink, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.unlink(path); @@ -2009,7 +2047,7 @@ async function unlink(path) { } async function fchmod(handle, mode) { - if (shouldTraceFs('fchmod')) return await traceFsPromise('fchmod', fchmod, this, arguments); + if (fsFchmod.shouldTrace()) return await fsFchmod.tracePromise(fchmod, this, arguments); if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('fchmod API is disabled when Permission Model is enabled.'); } @@ -2022,7 +2060,7 @@ async function fchmod(handle, mode) { } async function chmod(path, mode) { - if (shouldTraceFs('chmod')) return await traceFsPromise('chmod', chmod, this, arguments); + if (fsChmod.shouldTrace()) return await fsChmod.tracePromise(chmod, this, arguments); path = getValidatedPath(path); mode = parseFileMode(mode, 'mode'); @@ -2040,7 +2078,7 @@ async function chmod(path, mode) { } async function lchmod(path, mode) { - if (shouldTraceFs('lchmod')) return await traceFsPromise('lchmod', lchmod, this, arguments); + if (fsLchmod.shouldTrace()) return await fsLchmod.tracePromise(lchmod, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.lchmod(path, mode); @@ -2055,7 +2093,7 @@ async function lchmod(path, mode) { } async function lchown(path, uid, gid) { - if (shouldTraceFs('lchown')) return await traceFsPromise('lchown', lchown, this, arguments); + if (fsLchown.shouldTrace()) return await fsLchown.tracePromise(lchown, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.lchown(path, uid, gid); @@ -2073,7 +2111,7 @@ async function lchown(path, uid, gid) { } async function fchown(handle, uid, gid) { - if (shouldTraceFs('fchown')) return await traceFsPromise('fchown', fchown, this, arguments); + if (fsFchown.shouldTrace()) return await fsFchown.tracePromise(fchown, this, arguments); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); if (permission.isEnabled()) { @@ -2087,7 +2125,7 @@ async function fchown(handle, uid, gid) { } async function chown(path, uid, gid) { - if (shouldTraceFs('chown')) return await traceFsPromise('chown', chown, this, arguments); + if (fsChown.shouldTrace()) return await fsChown.tracePromise(chown, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.chown(path, uid, gid); @@ -2105,7 +2143,7 @@ async function chown(path, uid, gid) { } async function utimes(path, atime, mtime) { - if (shouldTraceFs('utimes')) return await traceFsPromise('utimes', utimes, this, arguments); + if (fsUtimes.shouldTrace()) return await fsUtimes.tracePromise(utimes, this, arguments); path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -2124,7 +2162,7 @@ async function utimes(path, atime, mtime) { } async function futimes(handle, atime, mtime) { - if (shouldTraceFs('futimes')) return await traceFsPromise('futimes', futimes, this, arguments); + if (fsFutimes.shouldTrace()) return await fsFutimes.tracePromise(futimes, this, arguments); if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.'); } @@ -2138,7 +2176,7 @@ async function futimes(handle, atime, mtime) { } async function lutimes(path, atime, mtime) { - if (shouldTraceFs('lutimes')) return await traceFsPromise('lutimes', lutimes, this, arguments); + if (fsLutimes.shouldTrace()) return await fsLutimes.tracePromise(lutimes, this, arguments); path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -2157,7 +2195,7 @@ async function lutimes(path, atime, mtime) { } async function realpath(path, options) { - if (shouldTraceFs('realpath')) return await traceFsPromise('realpath', realpath, this, arguments); + if (fsRealpath.shouldTrace()) return await fsRealpath.tracePromise(realpath, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.realpath(path, options); @@ -2172,7 +2210,7 @@ async function realpath(path, options) { } async function mkdtemp(prefix, options) { - if (shouldTraceFs('mkdtemp')) return await traceFsPromise('mkdtemp', mkdtemp, this, arguments); + if (fsMkdtemp.shouldTrace()) return await fsMkdtemp.tracePromise(mkdtemp, this, arguments); const h = vfsState.handlers; if (h !== null) { const promise = h.mkdtemp(prefix, options); @@ -2215,8 +2253,8 @@ async function mkstemp(prefix, options) { } async function mkdtempDisposable(prefix, options) { - if (shouldTraceFs('mkdtemp')) { - return await traceFsPromise('mkdtemp', mkdtempDisposable, this, arguments); + if (fsMkdtemp.shouldTrace()) { + return await fsMkdtemp.tracePromise(mkdtempDisposable, this, arguments); } options = getOptions(options); if (BufferIsBuffer(prefix)) { @@ -2253,7 +2291,7 @@ async function mkdtempDisposable(prefix, options) { } async function writeFile(path, data, options) { - if (shouldTraceFs('writeFile')) return await traceFsPromise('writeFile', writeFile, this, arguments); + if (fsWriteFile.shouldTrace()) return await fsWriteFile.tracePromise(writeFile, this, arguments); options = getOptions(options, { encoding: 'utf8', mode: 0o666, @@ -2334,7 +2372,7 @@ function isCustomIterable(obj) { } async function appendFile(path, data, options) { - if (shouldTraceFs('appendFile')) return await traceFsPromise('appendFile', appendFile, this, arguments); + if (fsAppendFile.shouldTrace()) return await fsAppendFile.tracePromise(appendFile, this, arguments); options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); parseFileMode(options.mode, 'mode', 0o666); @@ -2350,7 +2388,7 @@ async function appendFile(path, data, options) { } async function readFile(path, options) { - if (shouldTraceFs('readFile')) return await traceFsPromise('readFile', readFile, this, arguments); + if (fsReadFile.shouldTrace()) return await fsReadFile.tracePromise(readFile, this, arguments); const h = vfsState.handlers; if (h !== null) { checkAborted(options?.signal); @@ -2412,7 +2450,7 @@ function readFileInOneRoundTrip(path, flagsNumber) { const opendirPromisified = promisify(opendirCallback); // The unused `callback` keeps the length of promisify(fs.opendir). function opendir(path, options, callback) { - if (shouldTraceFs('opendir')) return traceFsPromise('opendir', opendir, this, arguments); + if (fsOpendir.shouldTrace()) return fsOpendir.tracePromise(opendir, this, arguments); // Skips the check in fs.opendir(), so the call is traced only once. return callUntraced(opendirPromisified, undefined, [path, options]); } diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index 9789949cf45..75a0a1b20aa 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -81,6 +81,7 @@ const kPartialMtimeNs = Symbol('partialMtimeNs'); const kPartialCtimeNs = Symbol('partialCtimeNs'); const kPartialBirthtimeNs = Symbol('kPartialBirthtimeNs'); const assert = require('internal/assert'); +const { tracingChannel } = require('diagnostics_channel'); const { fs: { @@ -1198,21 +1199,13 @@ function setVfsHandlers(handlers) { vfsState.handlers = handlers; } // Each traced fs function starts with: // -// if (shouldTraceFs('name')) return traceFsSync('name', fn, this, arguments); +// if (fsStat.shouldTrace()) return fsStat.traceCallback(stat, this, arguments); // -// The trace helpers call the same function again inside the trace, so +// The trace methods call the same function again inside the trace, so // that its whole body runs inside the `start` scope. They set this flag // just before that call, so that the call skips the check once. let skipNextTrace = false; -function shouldTraceFs(name) { - if (skipNextTrace) { - skipNextTrace = false; - return false; - } - return getFsChannel(name).hasSubscribers; -} - function callUntraced(fn, thisArg, args) { skipNextTrace = true; try { @@ -1222,115 +1215,163 @@ function callUntraced(fn, thisArg, args) { } } -let tracingChannel; -const fsChannels = { __proto__: null }; -function getFsChannel(name) { - let channel = fsChannels[name]; - if (channel === undefined) { - tracingChannel ??= require('diagnostics_channel').tracingChannel; - channel = fsChannels[name] = tracingChannel(`fs.${name}`); - } - return channel; +function callUntracedSpread(fn, thisArg, ...args) { + return callUntraced(fn, thisArg, args); } -// Context property names for the leading arguments of each operation. -// All arguments are also in `context.args`. -const kFsContextFields = { - __proto__: null, - access: ['path'], - appendFile: ['path', 'data'], - chmod: ['path', 'mode'], - chown: ['path', 'uid', 'gid'], - close: ['fd'], - copyFile: ['path', 'dest'], - cp: ['path', 'dest'], - exists: ['path'], - fchmod: ['fd', 'mode'], - fchown: ['fd', 'uid', 'gid'], - fdatasync: ['fd'], - fstat: ['fd'], - fsync: ['fd'], - ftruncate: ['fd'], - futimes: ['fd', 'atime', 'mtime'], - glob: ['pattern'], - lchmod: ['path', 'mode'], - lchown: ['path', 'uid', 'gid'], - link: ['path', 'dest'], - lstat: ['path'], - lutimes: ['path', 'atime', 'mtime'], - mkdir: ['path'], - mkdtemp: ['prefix'], - open: ['path'], - openAsBlob: ['path'], - opendir: ['path'], - read: ['fd'], - readdir: ['path'], - readFile: ['path'], - readlink: ['path'], - readv: ['fd', 'buffers'], - realpath: ['path'], - rename: ['path', 'dest'], - rm: ['path'], - rmdir: ['path'], - stat: ['path'], - statfs: ['path'], - symlink: ['target', 'path'], - truncate: ['path'], - unlink: ['path'], - utimes: ['path', 'atime', 'mtime'], - write: ['fd', 'buffer'], - writeFile: ['path', 'data'], - writev: ['fd', 'buffers'], -}; +// The tracing channel of one fs operation. `fields` are the context +// property names of the leading arguments. All arguments are also in +// `context.args`. +class FsTracer { + constructor(name, fields) { + this.channel = tracingChannel(`fs.${name}`); + this.fields = fields; + } -// `args` are the caller's arguments. `context.args` is a copy, so that -// subscribers can not change the arguments of the call. -function createFsContext(name, api, args) { - const fields = kFsContextFields[name]; - let start = 0; - // FileHandle methods pass the handle in place of a descriptor. It is - // not one of the arguments of the method. - if (fields[0] === 'fd' && typeof args[0] === 'object' && args[0] !== null) { - start = 1; - } - const context = { api, args: ArrayPrototypeSlice(args, start) }; - for (let i = 0; i < fields.length && i < args.length; i++) { - context[fields[i]] = i === 0 && start === 1 ? args[0].fd : args[i]; - } - return context; -} + shouldTrace() { + if (skipNextTrace) { + skipNextTrace = false; + return false; + } + return this.channel.hasSubscribers; + } + + // `context.args` is a copy, so that subscribers can not change the + // arguments of the call. + #createContext(api, args) { + const { fields } = this; + let start = 0; + // FileHandle methods pass the handle in place of a descriptor. It is + // not one of the arguments of the method. + if (fields[0] === 'fd' && typeof args[0] === 'object' && args[0] !== null) { + start = 1; + } + const context = { api, args: ArrayPrototypeSlice(args, start) }; + for (let i = 0; i < fields.length && i < args.length; i++) { + context[fields[i]] = i === 0 && start === 1 ? args[0].fd : args[i]; + } + return context; + } -function traceFsSync(name, fn, thisArg, args) { - const context = createFsContext(name, 'sync', args); - return getFsChannel(name).traceSync(callUntraced, context, undefined, - fn, thisArg, args); -} + traceSync(fn, thisArg, args) { + const context = this.#createContext('sync', args); + return this.channel.traceSync(callUntraced, context, undefined, + fn, thisArg, args); + } -function traceFsPromise(name, fn, thisArg, args) { - const context = createFsContext(name, 'promise', args); - return getFsChannel(name).tracePromise(callUntraced, context, undefined, - fn, thisArg, args); -} + tracePromise(fn, thisArg, args) { + const context = this.#createContext('promise', args); + return this.channel.tracePromise(callUntraced, context, undefined, + fn, thisArg, args); + } -function callUntracedSpread(fn, thisArg, ...args) { - return callUntraced(fn, thisArg, args); + // The callback is the last function argument. Without one, the + // function throws when it is called again. `contextArgs` are the + // arguments to report, when they differ from the arguments of the call. + traceCallback(fn, thisArg, args, contextArgs = args) { + let position = args.length - 1; + while (position >= 0 && typeof args[position] !== 'function') position--; + if (position < 0) return callUntraced(fn, thisArg, args); + const context = this.#createContext('callback', contextArgs); + const callArgs = [callUntracedSpread, position + 2, context, undefined, fn, thisArg]; + ArrayPrototypePushApply(callArgs, args); + const { channel } = this; + return ReflectApply(channel.traceCallback, channel, callArgs); + } } -// The callback is the last function argument. Without one, the -// function throws when it is called again. `contextArgs` are the -// arguments to report, when they differ from the arguments of the call. -function traceFsCallback(name, fn, thisArg, args, contextArgs = args) { - let position = args.length - 1; - while (position >= 0 && typeof args[position] !== 'function') position--; - if (position < 0) return callUntraced(fn, thisArg, args); - const context = createFsContext(name, 'callback', contextArgs); - const callArgs = [callUntracedSpread, position + 2, context, undefined, fn, thisArg]; - ArrayPrototypePushApply(callArgs, args); - const channel = getFsChannel(name); - return ReflectApply(channel.traceCallback, channel, callArgs); -} +const fsAccess = new FsTracer('access', ['path']); +const fsAppendFile = new FsTracer('appendFile', ['path', 'data']); +const fsChmod = new FsTracer('chmod', ['path', 'mode']); +const fsChown = new FsTracer('chown', ['path', 'uid', 'gid']); +const fsClose = new FsTracer('close', ['fd']); +const fsClosedir = new FsTracer('closedir', ['path']); +const fsCopyFile = new FsTracer('copyFile', ['path', 'dest']); +const fsCp = new FsTracer('cp', ['path', 'dest']); +const fsExists = new FsTracer('exists', ['path']); +const fsFchmod = new FsTracer('fchmod', ['fd', 'mode']); +const fsFchown = new FsTracer('fchown', ['fd', 'uid', 'gid']); +const fsFdatasync = new FsTracer('fdatasync', ['fd']); +const fsFstat = new FsTracer('fstat', ['fd']); +const fsFsync = new FsTracer('fsync', ['fd']); +const fsFtruncate = new FsTracer('ftruncate', ['fd']); +const fsFutimes = new FsTracer('futimes', ['fd', 'atime', 'mtime']); +const fsGlob = new FsTracer('glob', ['pattern']); +const fsLchmod = new FsTracer('lchmod', ['path', 'mode']); +const fsLchown = new FsTracer('lchown', ['path', 'uid', 'gid']); +const fsLink = new FsTracer('link', ['path', 'dest']); +const fsLstat = new FsTracer('lstat', ['path']); +const fsLutimes = new FsTracer('lutimes', ['path', 'atime', 'mtime']); +const fsMkdir = new FsTracer('mkdir', ['path']); +const fsMkdtemp = new FsTracer('mkdtemp', ['prefix']); +const fsOpen = new FsTracer('open', ['path']); +const fsOpenAsBlob = new FsTracer('openAsBlob', ['path']); +const fsOpendir = new FsTracer('opendir', ['path']); +const fsRead = new FsTracer('read', ['fd']); +const fsReadFile = new FsTracer('readFile', ['path']); +const fsReaddir = new FsTracer('readdir', ['path']); +const fsReadlink = new FsTracer('readlink', ['path']); +const fsReadv = new FsTracer('readv', ['fd', 'buffers']); +const fsRealpath = new FsTracer('realpath', ['path']); +const fsRename = new FsTracer('rename', ['path', 'dest']); +const fsRm = new FsTracer('rm', ['path']); +const fsRmdir = new FsTracer('rmdir', ['path']); +const fsStat = new FsTracer('stat', ['path']); +const fsStatfs = new FsTracer('statfs', ['path']); +const fsSymlink = new FsTracer('symlink', ['target', 'path']); +const fsTruncate = new FsTracer('truncate', ['path']); +const fsUnlink = new FsTracer('unlink', ['path']); +const fsUtimes = new FsTracer('utimes', ['path', 'atime', 'mtime']); +const fsWrite = new FsTracer('write', ['fd', 'buffer']); +const fsWriteFile = new FsTracer('writeFile', ['path', 'data']); +const fsWritev = new FsTracer('writev', ['fd', 'buffers']); module.exports = { + fsAccess, + fsAppendFile, + fsChmod, + fsChown, + fsClose, + fsClosedir, + fsCopyFile, + fsCp, + fsExists, + fsFchmod, + fsFchown, + fsFdatasync, + fsFstat, + fsFsync, + fsFtruncate, + fsFutimes, + fsGlob, + fsLchmod, + fsLchown, + fsLink, + fsLstat, + fsLutimes, + fsMkdir, + fsMkdtemp, + fsOpen, + fsOpenAsBlob, + fsOpendir, + fsRead, + fsReadFile, + fsReaddir, + fsReadlink, + fsReadv, + fsRealpath, + fsRename, + fsRm, + fsRmdir, + fsStat, + fsStatfs, + fsSymlink, + fsTruncate, + fsUnlink, + fsUtimes, + fsWrite, + fsWriteFile, + fsWritev, collectRecursiveReaddirResult, constants: { kIoMaxLength, @@ -1348,17 +1389,12 @@ module.exports = { DirentFromStats, getDirent, getDirents, - getFsChannel, getOptions, getRecursiveDirents, getValidatedFd, getValidatedPath, handleErrorFromBinding, callUntraced, - shouldTraceFs, - traceFsCallback, - traceFsPromise, - traceFsSync, preprocessSymlinkDestination, realpathCacheKey: Symbol('realpathCacheKey'), getStatFsFromBinding, From 907cbcb0cf96c54af41dfb11df10652eaf117728 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 20 Oct 2026 12:55:21 +0100 Subject: [PATCH 5/6] fs: use the tracing channels directly The fs modules now hold the TracingChannel instances themselves, and each publish site builds its own context literal, so the shape of the context is fixed per site instead of computed from a field table on every publish. The check at the start of each function is `hasSubscribers` on the held channel. The trace helpers call the traced function again with a private `this`, so that the second entry skips the check. This replaces the re-entry flag and the wrapper that consumed it. No traced function uses `this` for anything else. Refs: https://github.com/nodejs/node/pull/66575#pullrequestreview-5584290101 Assisted-by: claude:fable-5.1 Signed-off-by: Tim Fish --- lib/fs.js | 454 ++++++++++++++++++++++++++++-------- lib/internal/fs/dir.js | 28 ++- lib/internal/fs/promises.js | 220 +++++++++++++---- lib/internal/fs/utils.js | 197 +++++++--------- 4 files changed, 628 insertions(+), 271 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index ec2f039561c..d73bcdda4ec 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -26,6 +26,7 @@ const { ArrayPrototypePush, + ArrayPrototypeSlice, BigIntPrototypeToString, FunctionPrototypeCall, MathMax, @@ -174,6 +175,10 @@ const { fsWrite, fsWriteFile, fsWritev, + kTraced, + traceSync, + traceCallback, + tracePromise, } = require('internal/fs/utils'); const { CHAR_FORWARD_SLASH, @@ -284,7 +289,10 @@ function isFileType(stats, fileType) { * @returns {void} */ function access(path, mode, callback) { - if (fsAccess.shouldTrace()) return fsAccess.traceCallback(access, this, arguments); + if (this !== kTraced && fsAccess.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsAccess, context, access, arguments); + } if (typeof mode === 'function') { callback = mode; mode = F_OK; @@ -309,7 +317,10 @@ function access(path, mode, callback) { * @returns {void} */ function accessSync(path, mode) { - if (fsAccess.shouldTrace()) return fsAccess.traceSync(accessSync, this, arguments); + if (this !== kTraced && fsAccess.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsAccess, context, accessSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.accessSync(path, mode); @@ -326,10 +337,11 @@ function accessSync(path, mode) { */ function exists(path, callback) { validateFunction(callback, 'cb'); - if (fsExists.shouldTrace()) { + if (this !== kTraced && fsExists.hasSubscribers) { // The callback of fs.exists() takes only the result. - return fsExists.traceCallback(existsErrorFirst, this, - [path, (err, result) => callback(result)], arguments); + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsExists, context, existsErrorFirst, + [path, (err, result) => callback(result)]); } const h = vfsState.handlers; @@ -353,7 +365,7 @@ function exists(path, callback) { } function existsErrorFirst(path, callback) { - exists(path, (result) => callback(null, result)); + FunctionPrototypeCall(exists, this, path, (result) => callback(null, result)); } ObjectDefineProperty(exists, kCustomPromisifiedSymbol, { @@ -370,7 +382,10 @@ let showExistsDeprecation = true; * @returns {boolean} */ function existsSync(path) { - if (fsExists.shouldTrace()) return fsExists.traceSync(existsSync, this, arguments); + if (this !== kTraced && fsExists.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsExists, context, existsSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.existsSync(path); @@ -474,7 +489,10 @@ function bindSignalToReq(req, signal, callback) { * @returns {void} */ function readFile(path, options, callback) { - if (fsReadFile.shouldTrace()) return fsReadFile.traceCallback(readFile, this, arguments); + if (this !== kTraced && fsReadFile.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsReadFile, context, readFile, arguments); + } callback ||= options; validateFunction(callback, 'cb'); @@ -658,7 +676,10 @@ function tryReadSyncWithUserBuffer(fd, isUserFd, buffer, byteLengthName) { * @returns {string | Buffer} */ function readFileSync(path, options) { - if (fsReadFile.shouldTrace()) return fsReadFile.traceSync(readFileSync, this, arguments); + if (this !== kTraced && fsReadFile.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsReadFile, context, readFileSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.readFileSync(path, options); @@ -758,9 +779,10 @@ function defaultCloseCallback(err) { * @returns {void} */ function close(fd, callback = defaultCloseCallback) { - if (fsClose.shouldTrace()) { + if (this !== kTraced && fsClose.hasSubscribers) { // Passes the default callback, so that the call has one to trace. - return fsClose.traceCallback(close, this, [fd, callback], arguments); + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd }; + return traceCallback(fsClose, context, close, [fd, callback]); } if (callback !== defaultCloseCallback) callback = makeCallback(callback); @@ -779,7 +801,10 @@ function close(fd, callback = defaultCloseCallback) { * @returns {void} */ function closeSync(fd) { - if (fsClose.shouldTrace()) return fsClose.traceSync(closeSync, this, arguments); + if (this !== kTraced && fsClose.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd }; + return traceSync(fsClose, context, closeSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.closeSync(fd); @@ -800,7 +825,10 @@ function closeSync(fd) { * @returns {void} */ function open(path, flags, mode, callback) { - if (fsOpen.shouldTrace()) return fsOpen.traceCallback(open, this, arguments); + if (this !== kTraced && fsOpen.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsOpen, context, open, arguments); + } if (arguments.length < 3) { callback = flags; flags = 'r'; @@ -834,7 +862,10 @@ function open(path, flags, mode, callback) { * @returns {number} */ function openSync(path, flags, mode) { - if (fsOpen.shouldTrace()) return fsOpen.traceSync(openSync, this, arguments); + if (this !== kTraced && fsOpen.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsOpen, context, openSync, arguments); + } flags = stringToFlags(flags); mode = parseFileMode(mode, 'mode', 0o666); const h = vfsState.handlers; @@ -857,7 +888,10 @@ function openSync(path, flags, mode) { * @returns {Promise} */ function openAsBlob(path, options = kEmptyObject) { - if (fsOpenAsBlob.shouldTrace()) return fsOpenAsBlob.tracePromise(openAsBlob, this, arguments); + if (this !== kTraced && fsOpenAsBlob.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return tracePromise(fsOpenAsBlob, context, openAsBlob, arguments); + } validateObject(options, 'options'); const { type = '' } = options; validateString(type, 'options.type'); @@ -884,7 +918,10 @@ function openAsBlob(path, options = kEmptyObject) { * @returns {Blob} */ function openAsBlobSync(path, options = kEmptyObject) { - if (fsOpenAsBlob.shouldTrace()) return fsOpenAsBlob.traceSync(openAsBlobSync, this, arguments); + if (this !== kTraced && fsOpenAsBlob.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsOpenAsBlob, context, openAsBlobSync, arguments); + } validateObject(options, 'options'); const { type = '' } = options; validateString(type, 'options.type'); @@ -919,7 +956,10 @@ function openAsBlobSync(path, options = kEmptyObject) { * @returns {void} */ function read(fd, buffer, offsetOrOptions, length, position, callback) { - if (fsRead.shouldTrace()) return fsRead.traceCallback(read, this, arguments); + if (this !== kTraced && fsRead.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd }; + return traceCallback(fsRead, context, read, arguments); + } fd = getValidatedFd(fd); let offset = offsetOrOptions; let params = null; @@ -1022,7 +1062,10 @@ ObjectDefineProperty(read, kCustomPromisifyArgsSymbol, * @returns {number} */ function readSync(fd, buffer, offsetOrOptions, length, position) { - if (fsRead.shouldTrace()) return fsRead.traceSync(readSync, this, arguments); + if (this !== kTraced && fsRead.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd }; + return traceSync(fsRead, context, readSync, arguments); + } validateBuffer(buffer); let offset = offsetOrOptions; @@ -1086,7 +1129,10 @@ function readSync(fd, buffer, offsetOrOptions, length, position) { * @returns {void} */ function readv(fd, buffers, position, callback) { - if (fsReadv.shouldTrace()) return fsReadv.traceCallback(readv, this, arguments); + if (this !== kTraced && fsReadv.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd, buffers }; + return traceCallback(fsReadv, context, readv, arguments); + } function wrapper(err, read) { callback(err, read || 0, buffers); } @@ -1128,7 +1174,10 @@ ObjectDefineProperty(readv, kCustomPromisifyArgsSymbol, * @returns {number} */ function readvSync(fd, buffers, position) { - if (fsReadv.shouldTrace()) return fsReadv.traceSync(readvSync, this, arguments); + if (this !== kTraced && fsReadv.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd, buffers }; + return traceSync(fsReadv, context, readvSync, arguments); + } validateBufferArray(buffers); if (typeof position !== 'number') @@ -1158,7 +1207,10 @@ function readvSync(fd, buffers, position) { * @returns {void} */ function write(fd, buffer, offsetOrOptions, length, position, callback) { - if (fsWrite.shouldTrace()) return fsWrite.traceCallback(write, this, arguments); + if (this !== kTraced && fsWrite.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd, buffer }; + return traceCallback(fsWrite, context, write, arguments); + } function wrapper(err, written) { // Retain a reference to buffer so that it can't be GC'ed too soon. callback(err, written || 0, buffer); @@ -1256,7 +1308,10 @@ ObjectDefineProperty(write, kCustomPromisifyArgsSymbol, * @returns {number} */ function writeSync(fd, buffer, offsetOrOptions, length, position) { - if (fsWrite.shouldTrace()) return fsWrite.traceSync(writeSync, this, arguments); + if (this !== kTraced && fsWrite.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd, buffer }; + return traceSync(fsWrite, context, writeSync, arguments); + } const ctx = {}; let result; @@ -1323,7 +1378,10 @@ function writeSync(fd, buffer, offsetOrOptions, length, position) { * @returns {void} */ function writev(fd, buffers, position, callback) { - if (fsWritev.shouldTrace()) return fsWritev.traceCallback(writev, this, arguments); + if (this !== kTraced && fsWritev.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd, buffers }; + return traceCallback(fsWritev, context, writev, arguments); + } function wrapper(err, written) { callback(err, written || 0, buffers); } @@ -1372,7 +1430,10 @@ ObjectDefineProperty(writev, kCustomPromisifyArgsSymbol, { * @returns {number} */ function writevSync(fd, buffers, position) { - if (fsWritev.shouldTrace()) return fsWritev.traceSync(writevSync, this, arguments); + if (this !== kTraced && fsWritev.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd, buffers }; + return traceSync(fsWritev, context, writevSync, arguments); + } validateBufferArray(buffers); if (buffers.length === 0) { @@ -1400,7 +1461,10 @@ function writevSync(fd, buffers, position) { * @returns {void} */ function rename(oldPath, newPath, callback) { - if (fsRename.shouldTrace()) return fsRename.traceCallback(rename, this, arguments); + if (this !== kTraced && fsRename.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path: oldPath, dest: newPath }; + return traceCallback(fsRename, context, rename, arguments); + } const h = vfsState.handlers; if (h !== null && vfsVoid(h.rename(oldPath, newPath), callback)) return; @@ -1423,7 +1487,10 @@ function rename(oldPath, newPath, callback) { * @returns {void} */ function renameSync(oldPath, newPath) { - if (fsRename.shouldTrace()) return fsRename.traceSync(renameSync, this, arguments); + if (this !== kTraced && fsRename.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path: oldPath, dest: newPath }; + return traceSync(fsRename, context, renameSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.renameSync(oldPath, newPath); @@ -1443,7 +1510,10 @@ function renameSync(oldPath, newPath) { * @returns {void} */ function truncate(path, len, callback) { - if (fsTruncate.shouldTrace()) return fsTruncate.traceCallback(truncate, this, arguments); + if (this !== kTraced && fsTruncate.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsTruncate, context, truncate, arguments); + } if (typeof len === 'function') { callback = len; len = 0; @@ -1477,7 +1547,10 @@ function truncate(path, len, callback) { * @returns {void} */ function truncateSync(path, len) { - if (fsTruncate.shouldTrace()) return fsTruncate.traceSync(truncateSync, this, arguments); + if (this !== kTraced && fsTruncate.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsTruncate, context, truncateSync, arguments); + } if (len === undefined) { len = 0; } @@ -1505,7 +1578,10 @@ function truncateSync(path, len) { * @returns {void} */ function ftruncate(fd, len = 0, callback) { - if (fsFtruncate.shouldTrace()) return fsFtruncate.traceCallback(ftruncate, this, arguments); + if (this !== kTraced && fsFtruncate.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd }; + return traceCallback(fsFtruncate, context, ftruncate, arguments); + } if (typeof len === 'function') { callback = len; len = 0; @@ -1529,7 +1605,10 @@ function ftruncate(fd, len = 0, callback) { * @returns {void} */ function ftruncateSync(fd, len = 0) { - if (fsFtruncate.shouldTrace()) return fsFtruncate.traceSync(ftruncateSync, this, arguments); + if (this !== kTraced && fsFtruncate.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd }; + return traceSync(fsFtruncate, context, ftruncateSync, arguments); + } validateInteger(len, 'len'); const h = vfsState.handlers; @@ -1562,7 +1641,10 @@ function lazyLoadRimraf() { * @returns {void} */ function rmdir(path, options, callback) { - if (fsRmdir.shouldTrace()) return fsRmdir.traceCallback(rmdir, this, arguments); + if (this !== kTraced && fsRmdir.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsRmdir, context, rmdir, arguments); + } if (typeof options === 'function') { callback = options; options = undefined; @@ -1599,7 +1681,10 @@ function rmdir(path, options, callback) { * @returns {void} */ function rmdirSync(path, options) { - if (fsRmdir.shouldTrace()) return fsRmdir.traceSync(rmdirSync, this, arguments); + if (this !== kTraced && fsRmdir.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsRmdir, context, rmdirSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.rmdirSync(path); @@ -1633,7 +1718,10 @@ function rmdirSync(path, options) { * @returns {void} */ function rm(path, options, callback) { - if (fsRm.shouldTrace()) return fsRm.traceCallback(rm, this, arguments); + if (this !== kTraced && fsRm.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsRm, context, rm, arguments); + } if (typeof options === 'function') { callback = options; options = undefined; @@ -1666,7 +1754,10 @@ function rm(path, options, callback) { * @returns {void} */ function rmSync(path, options) { - if (fsRm.shouldTrace()) return fsRm.traceSync(rmSync, this, arguments); + if (this !== kTraced && fsRm.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsRm, context, rmSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.rmSync(path, options); @@ -1685,7 +1776,10 @@ function rmSync(path, options) { * @returns {void} */ function fdatasync(fd, callback) { - if (fsFdatasync.shouldTrace()) return fsFdatasync.traceCallback(fdatasync, this, arguments); + if (this !== kTraced && fsFdatasync.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd }; + return traceCallback(fsFdatasync, context, fdatasync, arguments); + } callback = makeCallback(callback); const h = vfsState.handlers; @@ -1709,7 +1803,10 @@ function fdatasync(fd, callback) { * @returns {void} */ function fdatasyncSync(fd) { - if (fsFdatasync.shouldTrace()) return fsFdatasync.traceSync(fdatasyncSync, this, arguments); + if (this !== kTraced && fsFdatasync.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd }; + return traceSync(fsFdatasync, context, fdatasyncSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.fdatasyncSync(fd); @@ -1730,7 +1827,10 @@ function fdatasyncSync(fd) { * @returns {void} */ function fsync(fd, callback) { - if (fsFsync.shouldTrace()) return fsFsync.traceCallback(fsync, this, arguments); + if (this !== kTraced && fsFsync.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd }; + return traceCallback(fsFsync, context, fsync, arguments); + } callback = makeCallback(callback); const h = vfsState.handlers; @@ -1752,7 +1852,10 @@ function fsync(fd, callback) { * @returns {void} */ function fsyncSync(fd) { - if (fsFsync.shouldTrace()) return fsFsync.traceSync(fsyncSync, this, arguments); + if (this !== kTraced && fsFsync.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd }; + return traceSync(fsFsync, context, fsyncSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.fsyncSync(fd); @@ -1776,7 +1879,10 @@ function fsyncSync(fd) { * @returns {void} */ function mkdir(path, options, callback) { - if (fsMkdir.shouldTrace()) return fsMkdir.traceCallback(mkdir, this, arguments); + if (this !== kTraced && fsMkdir.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsMkdir, context, mkdir, arguments); + } if (typeof options === 'function') { callback = options; options = undefined; @@ -1826,7 +1932,10 @@ function mkdir(path, options, callback) { * @returns {string | void} */ function mkdirSync(path, options) { - if (fsMkdir.shouldTrace()) return fsMkdir.traceSync(mkdirSync, this, arguments); + if (this !== kTraced && fsMkdir.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsMkdir, context, mkdirSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const vfsResult = h.mkdirSync(path, options); @@ -1975,7 +2084,10 @@ function readdirSyncRecursive(basePath, options) { * @returns {void} */ function readdir(path, options, callback) { - if (fsReaddir.shouldTrace()) return fsReaddir.traceCallback(readdir, this, arguments); + if (this !== kTraced && fsReaddir.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsReaddir, context, readdir, arguments); + } if (typeof options === 'function') { callback = options; options = undefined; @@ -2027,7 +2139,10 @@ function readdir(path, options, callback) { * @returns {string | Buffer[] | Dirent[]} */ function readdirSync(path, options) { - if (fsReaddir.shouldTrace()) return fsReaddir.traceSync(readdirSync, this, arguments); + if (this !== kTraced && fsReaddir.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsReaddir, context, readdirSync, arguments); + } options = getOptions(options); path = getValidatedPath(path); if (options.recursive != null) { @@ -2065,7 +2180,10 @@ function readdirSync(path, options) { * @returns {void} */ function fstat(fd, options = { __proto__: null, bigint: false }, callback) { - if (fsFstat.shouldTrace()) return fsFstat.traceCallback(fstat, this, arguments); + if (this !== kTraced && fsFstat.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd }; + return traceCallback(fsFstat, context, fstat, arguments); + } if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2098,7 +2216,10 @@ function fstat(fd, options = { __proto__: null, bigint: false }, callback) { * @returns {void} */ function lstat(path, options = { __proto__: null, bigint: false }, callback) { - if (fsLstat.shouldTrace()) return fsLstat.traceCallback(lstat, this, arguments); + if (this !== kTraced && fsLstat.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsLstat, context, lstat, arguments); + } if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2139,7 +2260,10 @@ function lstat(path, options = { __proto__: null, bigint: false }, callback) { * @returns {void} */ function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }, callback) { - if (fsStat.shouldTrace()) return fsStat.traceCallback(stat, this, arguments); + if (this !== kTraced && fsStat.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsStat, context, stat, arguments); + } if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2164,7 +2288,10 @@ function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: } function statfs(path, options = { __proto__: null, bigint: false }, callback) { - if (fsStatfs.shouldTrace()) return fsStatfs.traceCallback(statfs, this, arguments); + if (this !== kTraced && fsStatfs.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsStatfs, context, statfs, arguments); + } if (typeof options === 'function') { callback = options; options = kEmptyObject; @@ -2206,7 +2333,10 @@ function statfs(path, options = { __proto__: null, bigint: false }, callback) { * @returns {Stats | undefined} */ function fstatSync(fd, options = { __proto__: null, bigint: false }) { - if (fsFstat.shouldTrace()) return fsFstat.traceSync(fstatSync, this, arguments); + if (this !== kTraced && fsFstat.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd }; + return traceSync(fsFstat, context, fstatSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.fstatSync(fd); @@ -2230,7 +2360,10 @@ function fstatSync(fd, options = { __proto__: null, bigint: false }) { * @returns {Stats | undefined} */ function lstatSync(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { - if (fsLstat.shouldTrace()) return fsLstat.traceSync(lstatSync, this, arguments); + if (this !== kTraced && fsLstat.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsLstat, context, lstatSync, arguments); + } const h = vfsState.handlers; if (h !== null) { try { @@ -2271,7 +2404,10 @@ function lstatSync(path, options = { __proto__: null, bigint: false, throwIfNoEn * @returns {Stats} */ function statSync(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { - if (fsStat.shouldTrace()) return fsStat.traceSync(statSync, this, arguments); + if (this !== kTraced && fsStat.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsStat, context, statSync, arguments); + } const h = vfsState.handlers; if (h !== null) { try { @@ -2295,7 +2431,10 @@ function statSync(path, options = { __proto__: null, bigint: false, throwIfNoEnt } function statfsSync(path, options = { __proto__: null, bigint: false }) { - if (fsStatfs.shouldTrace()) return fsStatfs.traceSync(statfsSync, this, arguments); + if (this !== kTraced && fsStatfs.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsStatfs, context, statfsSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.statfsSync(path, options); @@ -2318,7 +2457,10 @@ function statfsSync(path, options = { __proto__: null, bigint: false }) { * @returns {void} */ function readlink(path, options, callback) { - if (fsReadlink.shouldTrace()) return fsReadlink.traceCallback(readlink, this, arguments); + if (this !== kTraced && fsReadlink.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsReadlink, context, readlink, arguments); + } if (typeof options === 'function') { callback = options; options = undefined; @@ -2342,7 +2484,10 @@ function readlink(path, options, callback) { * @returns {string | Buffer} */ function readlinkSync(path, options) { - if (fsReadlink.shouldTrace()) return fsReadlink.traceSync(readlinkSync, this, arguments); + if (this !== kTraced && fsReadlink.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsReadlink, context, readlinkSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.readlinkSync(path, options); @@ -2361,7 +2506,10 @@ function readlinkSync(path, options) { * @returns {void} */ function symlink(target, path, type, callback) { - if (fsSymlink.shouldTrace()) return fsSymlink.traceCallback(symlink, this, arguments); + if (this !== kTraced && fsSymlink.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), target, path }; + return traceCallback(fsSymlink, context, symlink, arguments); + } if (callback === undefined) { callback = makeCallback(type); type = undefined; @@ -2434,7 +2582,10 @@ function symlink(target, path, type, callback) { * @returns {void} */ function symlinkSync(target, path, type) { - if (fsSymlink.shouldTrace()) return fsSymlink.traceSync(symlinkSync, this, arguments); + if (this !== kTraced && fsSymlink.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), target, path }; + return traceSync(fsSymlink, context, symlinkSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.symlinkSync(target, path, type); @@ -2475,7 +2626,10 @@ function symlinkSync(target, path, type) { * @returns {void} */ function link(existingPath, newPath, callback) { - if (fsLink.shouldTrace()) return fsLink.traceCallback(link, this, arguments); + if (this !== kTraced && fsLink.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path: existingPath, dest: newPath }; + return traceCallback(fsLink, context, link, arguments); + } callback = makeCallback(callback); const h = vfsState.handlers; @@ -2498,7 +2652,10 @@ function link(existingPath, newPath, callback) { * @returns {void} */ function linkSync(existingPath, newPath) { - if (fsLink.shouldTrace()) return fsLink.traceSync(linkSync, this, arguments); + if (this !== kTraced && fsLink.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path: existingPath, dest: newPath }; + return traceSync(fsLink, context, linkSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.linkSync(existingPath, newPath); @@ -2521,7 +2678,10 @@ function linkSync(existingPath, newPath) { * @returns {void} */ function unlink(path, callback) { - if (fsUnlink.shouldTrace()) return fsUnlink.traceCallback(unlink, this, arguments); + if (this !== kTraced && fsUnlink.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsUnlink, context, unlink, arguments); + } const h = vfsState.handlers; if (h !== null && vfsVoid(h.unlink(path), callback)) return; @@ -2537,7 +2697,10 @@ function unlink(path, callback) { * @returns {void} */ function unlinkSync(path) { - if (fsUnlink.shouldTrace()) return fsUnlink.traceSync(unlinkSync, this, arguments); + if (this !== kTraced && fsUnlink.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsUnlink, context, unlinkSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.unlinkSync(path); @@ -2554,7 +2717,10 @@ function unlinkSync(path) { * @returns {void} */ function fchmod(fd, mode, callback) { - if (fsFchmod.shouldTrace()) return fsFchmod.traceCallback(fchmod, this, arguments); + if (this !== kTraced && fsFchmod.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd, mode }; + return traceCallback(fsFchmod, context, fchmod, arguments); + } mode = parseFileMode(mode, 'mode'); callback = makeCallback(callback); @@ -2578,7 +2744,10 @@ function fchmod(fd, mode, callback) { * @returns {void} */ function fchmodSync(fd, mode) { - if (fsFchmod.shouldTrace()) return fsFchmod.traceSync(fchmodSync, this, arguments); + if (this !== kTraced && fsFchmod.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd, mode }; + return traceSync(fsFchmod, context, fchmodSync, arguments); + } mode = parseFileMode(mode, 'mode'); const h = vfsState.handlers; @@ -2601,7 +2770,10 @@ function fchmodSync(fd, mode) { * @returns {void} */ function lchmod(path, mode, callback) { - if (fsLchmod.shouldTrace()) return fsLchmod.traceCallback(lchmod, this, arguments); + if (this !== kTraced && fsLchmod.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path, mode }; + return traceCallback(fsLchmod, context, lchmod, arguments); + } validateFunction(callback, 'cb'); mode = parseFileMode(mode, 'mode'); @@ -2630,7 +2802,10 @@ function lchmod(path, mode, callback) { * @returns {void} */ function lchmodSync(path, mode) { - if (fsLchmod.shouldTrace()) return fsLchmod.traceSync(lchmodSync, this, arguments); + if (this !== kTraced && fsLchmod.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path, mode }; + return traceSync(fsLchmod, context, lchmodSync, arguments); + } const fd = fs.openSync(path, O_WRONLY | O_SYMLINK); // Prefer to return the chmod error, if one occurs, @@ -2650,7 +2825,10 @@ function lchmodSync(path, mode) { * @returns {void} */ function chmod(path, mode, callback) { - if (fsChmod.shouldTrace()) return fsChmod.traceCallback(chmod, this, arguments); + if (this !== kTraced && fsChmod.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path, mode }; + return traceCallback(fsChmod, context, chmod, arguments); + } path = getValidatedPath(path); mode = parseFileMode(mode, 'mode'); callback = makeCallback(callback); @@ -2670,7 +2848,10 @@ function chmod(path, mode, callback) { * @returns {void} */ function chmodSync(path, mode) { - if (fsChmod.shouldTrace()) return fsChmod.traceSync(chmodSync, this, arguments); + if (this !== kTraced && fsChmod.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path, mode }; + return traceSync(fsChmod, context, chmodSync, arguments); + } path = getValidatedPath(path); mode = parseFileMode(mode, 'mode'); @@ -2692,7 +2873,10 @@ function chmodSync(path, mode) { * @returns {void} */ function lchown(path, uid, gid, callback) { - if (fsLchown.shouldTrace()) return fsLchown.traceCallback(lchown, this, arguments); + if (this !== kTraced && fsLchown.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path, uid, gid }; + return traceCallback(fsLchown, context, lchown, arguments); + } callback = makeCallback(callback); path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); @@ -2714,7 +2898,10 @@ function lchown(path, uid, gid, callback) { * @returns {void} */ function lchownSync(path, uid, gid) { - if (fsLchown.shouldTrace()) return fsLchown.traceSync(lchownSync, this, arguments); + if (this !== kTraced && fsLchown.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path, uid, gid }; + return traceSync(fsLchown, context, lchownSync, arguments); + } path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); @@ -2737,7 +2924,10 @@ function lchownSync(path, uid, gid) { * @returns {void} */ function fchown(fd, uid, gid, callback) { - if (fsFchown.shouldTrace()) return fsFchown.traceCallback(fchown, this, arguments); + if (this !== kTraced && fsFchown.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd, uid, gid }; + return traceCallback(fsFchown, context, fchown, arguments); + } validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); callback = makeCallback(callback); @@ -2763,7 +2953,10 @@ function fchown(fd, uid, gid, callback) { * @returns {void} */ function fchownSync(fd, uid, gid) { - if (fsFchown.shouldTrace()) return fsFchown.traceSync(fchownSync, this, arguments); + if (this !== kTraced && fsFchown.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd, uid, gid }; + return traceSync(fsFchown, context, fchownSync, arguments); + } validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); @@ -2790,7 +2983,10 @@ function fchownSync(fd, uid, gid) { * @returns {void} */ function chown(path, uid, gid, callback) { - if (fsChown.shouldTrace()) return fsChown.traceCallback(chown, this, arguments); + if (this !== kTraced && fsChown.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path, uid, gid }; + return traceCallback(fsChown, context, chown, arguments); + } callback = makeCallback(callback); path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); @@ -2813,7 +3009,10 @@ function chown(path, uid, gid, callback) { * @returns {void} */ function chownSync(path, uid, gid) { - if (fsChown.shouldTrace()) return fsChown.traceSync(chownSync, this, arguments); + if (this !== kTraced && fsChown.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path, uid, gid }; + return traceSync(fsChown, context, chownSync, arguments); + } path = getValidatedPath(path); validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); @@ -2837,7 +3036,10 @@ function chownSync(path, uid, gid) { * @returns {void} */ function utimes(path, atime, mtime, callback) { - if (fsUtimes.shouldTrace()) return fsUtimes.traceCallback(utimes, this, arguments); + if (this !== kTraced && fsUtimes.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path, atime, mtime }; + return traceCallback(fsUtimes, context, utimes, arguments); + } callback = makeCallback(callback); path = getValidatedPath(path); atime = toUnixTimestamp(atime); @@ -2860,7 +3062,10 @@ function utimes(path, atime, mtime, callback) { * @returns {void} */ function utimesSync(path, atime, mtime) { - if (fsUtimes.shouldTrace()) return fsUtimes.traceSync(utimesSync, this, arguments); + if (this !== kTraced && fsUtimes.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path, atime, mtime }; + return traceSync(fsUtimes, context, utimesSync, arguments); + } path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -2884,7 +3089,10 @@ function utimesSync(path, atime, mtime) { * @returns {void} */ function futimes(fd, atime, mtime, callback) { - if (fsFutimes.shouldTrace()) return fsFutimes.traceCallback(futimes, this, arguments); + if (this !== kTraced && fsFutimes.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), fd, atime, mtime }; + return traceCallback(fsFutimes, context, futimes, arguments); + } atime = toUnixTimestamp(atime, 'atime'); mtime = toUnixTimestamp(mtime, 'mtime'); callback = makeCallback(callback); @@ -2912,7 +3120,10 @@ function futimes(fd, atime, mtime, callback) { * @returns {void} */ function futimesSync(fd, atime, mtime) { - if (fsFutimes.shouldTrace()) return fsFutimes.traceSync(futimesSync, this, arguments); + if (this !== kTraced && fsFutimes.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), fd, atime, mtime }; + return traceSync(fsFutimes, context, futimesSync, arguments); + } atime = toUnixTimestamp(atime, 'atime'); mtime = toUnixTimestamp(mtime, 'mtime'); @@ -2939,7 +3150,10 @@ function futimesSync(fd, atime, mtime) { * @returns {void} */ function lutimes(path, atime, mtime, callback) { - if (fsLutimes.shouldTrace()) return fsLutimes.traceCallback(lutimes, this, arguments); + if (this !== kTraced && fsLutimes.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path, atime, mtime }; + return traceCallback(fsLutimes, context, lutimes, arguments); + } callback = makeCallback(callback); path = getValidatedPath(path); atime = toUnixTimestamp(atime); @@ -2962,7 +3176,10 @@ function lutimes(path, atime, mtime, callback) { * @returns {void} */ function lutimesSync(path, atime, mtime) { - if (fsLutimes.shouldTrace()) return fsLutimes.traceSync(lutimesSync, this, arguments); + if (this !== kTraced && fsLutimes.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path, atime, mtime }; + return traceSync(fsLutimes, context, lutimesSync, arguments); + } path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -3045,7 +3262,10 @@ function writeAll(fd, isUserFd, buffer, offset, length, signal, flush, callback) * @returns {void} */ function writeFile(path, data, options, callback) { - if (fsWriteFile.shouldTrace()) return fsWriteFile.traceCallback(writeFile, this, arguments); + if (this !== kTraced && fsWriteFile.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path, data }; + return traceCallback(fsWriteFile, context, writeFile, arguments); + } callback ||= options; validateFunction(callback, 'cb'); @@ -3123,7 +3343,10 @@ function writeFile(path, data, options, callback) { * @returns {void} */ function writeFileSync(path, data, options) { - if (fsWriteFile.shouldTrace()) return fsWriteFile.traceSync(writeFileSync, this, arguments); + if (this !== kTraced && fsWriteFile.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path, data }; + return traceSync(fsWriteFile, context, writeFileSync, arguments); + } options = getOptions(options, { encoding: 'utf8', mode: 0o666, @@ -3199,7 +3422,10 @@ function writeFileSync(path, data, options) { * @returns {void} */ function appendFile(path, data, options, callback) { - if (fsAppendFile.shouldTrace()) return fsAppendFile.traceCallback(appendFile, this, arguments); + if (this !== kTraced && fsAppendFile.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path, data }; + return traceCallback(fsAppendFile, context, appendFile, arguments); + } callback ||= options; validateFunction(callback, 'cb'); @@ -3236,7 +3462,10 @@ function appendFile(path, data, options, callback) { * @returns {void} */ function appendFileSync(path, data, options) { - if (fsAppendFile.shouldTrace()) return fsAppendFile.traceSync(appendFileSync, this, arguments); + if (this !== kTraced && fsAppendFile.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path, data }; + return traceSync(fsAppendFile, context, appendFileSync, arguments); + } options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); parseFileMode(options.mode, 'mode', 0o666); @@ -3485,7 +3714,10 @@ if (isWindows) { * @returns {string | Buffer} */ function realpathSync(p, options) { - if (fsRealpath.shouldTrace()) return fsRealpath.traceSync(realpathSync, this, arguments); + if (this !== kTraced && fsRealpath.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path: p }; + return traceSync(fsRealpath, context, realpathSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.realpathSync(p, options); @@ -3635,8 +3867,9 @@ function realpathSync(p, options) { * @returns {string | Buffer} */ realpathSync.native = function native(path, options) { - if (fsRealpath.shouldTrace()) { - return fsRealpath.traceSync(native, undefined, arguments); + if (this !== kTraced && fsRealpath.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsRealpath, context, native, arguments); } const h = vfsState.handlers; if (h !== null) { @@ -3662,7 +3895,10 @@ realpathSync.native = function native(path, options) { * @returns {void} */ function realpath(p, options, callback) { - if (fsRealpath.shouldTrace()) return fsRealpath.traceCallback(realpath, this, arguments); + if (this !== kTraced && fsRealpath.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path: p }; + return traceCallback(fsRealpath, context, realpath, arguments); + } if (typeof options === 'function') { callback = options; options = undefined; @@ -3816,8 +4052,9 @@ function realpath(p, options, callback) { * @returns {void} */ realpath.native = function native(path, options, callback) { - if (fsRealpath.shouldTrace()) { - return fsRealpath.traceCallback(native, undefined, arguments); + if (this !== kTraced && fsRealpath.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsRealpath, context, native, arguments); } callback = makeCallback(callback || options); const h = vfsState.handlers; @@ -3840,7 +4077,10 @@ realpath.native = function native(path, options, callback) { * @returns {void} */ function mkdtemp(prefix, options, callback) { - if (fsMkdtemp.shouldTrace()) return fsMkdtemp.traceCallback(mkdtemp, this, arguments); + if (this !== kTraced && fsMkdtemp.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), prefix }; + return traceCallback(fsMkdtemp, context, mkdtemp, arguments); + } callback = makeCallback(typeof options === 'function' ? options : callback); const h = vfsState.handlers; @@ -3865,7 +4105,10 @@ function mkdtemp(prefix, options, callback) { * @returns {string} */ function mkdtempSync(prefix, options) { - if (fsMkdtemp.shouldTrace()) return fsMkdtemp.traceSync(mkdtempSync, this, arguments); + if (this !== kTraced && fsMkdtemp.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), prefix }; + return traceSync(fsMkdtemp, context, mkdtempSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.mkdtempSync(prefix, options); @@ -3890,7 +4133,10 @@ function mkdtempSync(prefix, options) { * @returns {object} A disposable object with a "path" property. */ function mkdtempDisposableSync(prefix, options) { - if (fsMkdtemp.shouldTrace()) return fsMkdtemp.traceSync(mkdtempDisposableSync, this, arguments); + if (this !== kTraced && fsMkdtemp.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), prefix }; + return traceSync(fsMkdtemp, context, mkdtempDisposableSync, arguments); + } options = getOptions(options); if (BufferIsBuffer(prefix)) { options = { ...options, encoding: 'buffer' }; @@ -3976,7 +4222,10 @@ function mkstempSync(prefix, options) { * @returns {void} */ function copyFile(src, dest, mode, callback) { - if (fsCopyFile.shouldTrace()) return fsCopyFile.traceCallback(copyFile, this, arguments); + if (this !== kTraced && fsCopyFile.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path: src, dest }; + return traceCallback(fsCopyFile, context, copyFile, arguments); + } if (typeof mode === 'function') { callback = mode; mode = 0; @@ -4003,7 +4252,10 @@ function copyFile(src, dest, mode, callback) { * @returns {void} */ function copyFileSync(src, dest, mode) { - if (fsCopyFile.shouldTrace()) return fsCopyFile.traceSync(copyFileSync, this, arguments); + if (this !== kTraced && fsCopyFile.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path: src, dest }; + return traceSync(fsCopyFile, context, copyFileSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.copyFileSync(src, dest, mode); @@ -4026,7 +4278,10 @@ function copyFileSync(src, dest, mode) { * @returns {void} */ function cp(src, dest, options, callback) { - if (fsCp.shouldTrace()) return fsCp.traceCallback(cp, this, arguments); + if (this !== kTraced && fsCp.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path: src, dest }; + return traceCallback(fsCp, context, cp, arguments); + } if (typeof options === 'function') { callback = options; options = undefined; @@ -4048,7 +4303,10 @@ function cp(src, dest, options, callback) { * @returns {void} */ function cpSync(src, dest, options) { - if (fsCp.shouldTrace()) return fsCp.traceSync(cpSync, this, arguments); + if (this !== kTraced && fsCp.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path: src, dest }; + return traceSync(fsCp, context, cpSync, arguments); + } options = validateCpOptions(options); src = getValidatedPath(src, 'src'); dest = getValidatedPath(dest, 'dest'); @@ -4124,7 +4382,10 @@ function createWriteStream(path, options) { const lazyGlob = getLazy(() => require('internal/fs/glob').Glob); function glob(pattern, options, callback) { - if (fsGlob.shouldTrace()) return fsGlob.traceCallback(glob, this, arguments); + if (this !== kTraced && fsGlob.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), pattern }; + return traceCallback(fsGlob, context, glob, arguments); + } if (typeof options === 'function') { callback = options; options = undefined; @@ -4140,7 +4401,10 @@ function glob(pattern, options, callback) { } function globSync(pattern, options) { - if (fsGlob.shouldTrace()) return fsGlob.traceSync(globSync, this, arguments); + if (this !== kTraced && fsGlob.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), pattern }; + return traceSync(fsGlob, context, globSync, arguments); + } const Glob = lazyGlob(); return new Glob(pattern, options).globSync(); } diff --git a/lib/internal/fs/dir.js b/lib/internal/fs/dir.js index ebbaed25e97..e96f013f86e 100644 --- a/lib/internal/fs/dir.js +++ b/lib/internal/fs/dir.js @@ -3,6 +3,7 @@ const { ArrayPrototypePush, ArrayPrototypeShift, + ArrayPrototypeSlice, FunctionPrototypeBind, ObjectDefineProperties, PromiseReject, @@ -34,6 +35,9 @@ const { vfsState, fsClosedir, fsOpendir, + kTraced, + traceSync, + traceCallback, } = require('internal/fs/utils'); const { validateFunction, @@ -253,18 +257,18 @@ class Dir { validateFunction(callback, 'callback'); - if (fsClosedir.channel.hasSubscribers) { + if (fsClosedir.hasSubscribers) { const context = { api: 'callback', args: [callback], path: this.#path }; - return fsClosedir.channel.traceCallback(this.#closeImpl, -1, context, this, - callback); + return fsClosedir.traceCallback(this.#closeImpl, -1, context, this, + callback); } this.#closeImpl(callback); } #closePromise() { - if (fsClosedir.channel.hasSubscribers) { + if (fsClosedir.hasSubscribers) { const context = { api: 'promise', args: [], path: this.#path }; - return fsClosedir.channel.tracePromise(this.#closePromisified, context); + return fsClosedir.tracePromise(this.#closePromisified, context); } return this.#closePromisified(); } @@ -308,9 +312,9 @@ class Dir { } this.#closed = true; - if (fsClosedir.channel.hasSubscribers) { + if (fsClosedir.hasSubscribers) { const context = { api: 'sync', args: [], path: this.#path }; - fsClosedir.channel.traceSync(this.#handle.close, context, this.#handle); + fsClosedir.traceSync(this.#handle.close, context, this.#handle); return; } this.#handle.close(); @@ -352,7 +356,10 @@ ObjectDefineProperties(Dir.prototype, { }); function opendir(path, options, callback) { - if (fsOpendir.shouldTrace()) return fsOpendir.traceCallback(opendir, this, arguments); + if (this !== kTraced && fsOpendir.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; + return traceCallback(fsOpendir, context, opendir, arguments); + } callback = typeof options === 'function' ? options : callback; validateFunction(callback, 'callback'); @@ -394,7 +401,10 @@ function opendir(path, options, callback) { } function opendirSync(path, options) { - if (fsOpendir.shouldTrace()) return fsOpendir.traceSync(opendirSync, this, arguments); + if (this !== kTraced && fsOpendir.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), path }; + return traceSync(fsOpendir, context, opendirSync, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.opendirSync(path, options); diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 29f8c01aad8..c8fcbc0a055 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -2,9 +2,11 @@ const { ArrayPrototypePush, + ArrayPrototypeSlice, Error, ErrorCaptureStackTrace, FunctionPrototypeBind, + FunctionPrototypeCall, MathMax, MathMin, Promise, @@ -77,7 +79,6 @@ const { stringToSymlinkType, toUnixTimestamp, handleErrorFromBinding: handleSyncErrorFromBinding, - callUntraced, validateBufferArray, validateCpOptions, validateOffsetLengthRead, @@ -130,6 +131,8 @@ const { fsWrite, fsWriteFile, fsWritev, + kTraced, + tracePromise, } = require('internal/fs/utils'); const { opendir: opendirCallback } = require('internal/fs/dir'); const { @@ -230,9 +233,9 @@ function handleErrorFromBinding(error) { } function closeFileHandle(handle, fd) { - if (fsClose.channel.hasSubscribers) { + if (fsClose.hasSubscribers) { const context = { api: 'promise', args: [], fd }; - return fsClose.channel.tracePromise(handle.close, context, handle); + return fsClose.tracePromise(handle.close, context, handle); } return handle.close(); } @@ -369,9 +372,9 @@ class FileHandle extends EventEmitter { } const fd = this[kFd]; this[kFd] = -1; - if (fsClose.channel.hasSubscribers) { + if (fsClose.hasSubscribers) { const context = { api: 'sync', args: [], fd }; - fsClose.channel.traceSync(this[kHandle].closeSync, context, this[kHandle]); + fsClose.traceSync(this[kHandle].closeSync, context, this[kHandle]); } else { this[kHandle].closeSync(); } @@ -1474,7 +1477,10 @@ async function readFileHandle(filehandle, options, knownRegularFileSize) { // All of the functions are defined as async in order to ensure that errors // thrown cause promise rejections rather than being thrown synchronously. async function access(path, mode = F_OK) { - if (fsAccess.shouldTrace()) return await fsAccess.tracePromise(access, this, arguments); + if (this !== kTraced && fsAccess.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsAccess, context, access, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.access(path, mode); @@ -1488,7 +1494,10 @@ async function access(path, mode = F_OK) { } async function cp(src, dest, options) { - if (fsCp.shouldTrace()) return await fsCp.tracePromise(cp, this, arguments); + if (this !== kTraced && fsCp.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path: src, dest }; + return await tracePromise(fsCp, context, cp, arguments); + } options = validateCpOptions(options); src = getValidatedPath(src, 'src'); dest = getValidatedPath(dest, 'dest'); @@ -1496,7 +1505,10 @@ async function cp(src, dest, options) { } async function copyFile(src, dest, mode) { - if (fsCopyFile.shouldTrace()) return await fsCopyFile.tracePromise(copyFile, this, arguments); + if (this !== kTraced && fsCopyFile.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path: src, dest }; + return await tracePromise(fsCopyFile, context, copyFile, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.copyFile(src, dest, mode); @@ -1517,7 +1529,10 @@ async function copyFile(src, dest, mode) { // Note that unlike fs.open() which uses numeric file descriptors, // fsPromises.open() uses the fs.FileHandle class. async function open(path, flags, mode) { - if (fsOpen.shouldTrace()) return await fsOpen.tracePromise(open, this, arguments); + if (this !== kTraced && fsOpen.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsOpen, context, open, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.promisesOpen(path, flags, mode); @@ -1534,7 +1549,10 @@ async function open(path, flags, mode) { } async function read(handle, bufferOrParams, offset, length, position) { - if (fsRead.shouldTrace()) return await fsRead.tracePromise(read, this, arguments); + if (this !== kTraced && fsRead.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments, 1), fd: handle.fd }; + return await tracePromise(fsRead, context, read, arguments); + } let buffer = bufferOrParams; if (!isArrayBufferView(buffer)) { // This is fh.read(params) @@ -1595,7 +1613,10 @@ async function read(handle, bufferOrParams, offset, length, position) { } async function readv(handle, buffers, position) { - if (fsReadv.shouldTrace()) return await fsReadv.tracePromise(readv, this, arguments); + if (this !== kTraced && fsReadv.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments, 1), fd: handle.fd, buffers }; + return await tracePromise(fsReadv, context, readv, arguments); + } validateBufferArray(buffers); if (typeof position !== 'number') @@ -1610,7 +1631,10 @@ async function readv(handle, buffers, position) { } async function write(handle, buffer, offsetOrOptions, length, position) { - if (fsWrite.shouldTrace()) return await fsWrite.tracePromise(write, this, arguments); + if (this !== kTraced && fsWrite.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments, 1), fd: handle.fd, buffer }; + return await tracePromise(fsWrite, context, write, arguments); + } if (buffer?.byteLength === 0) return { __proto__: null, bytesWritten: 0, buffer }; @@ -1655,7 +1679,10 @@ async function write(handle, buffer, offsetOrOptions, length, position) { } async function writev(handle, buffers, position) { - if (fsWritev.shouldTrace()) return await fsWritev.tracePromise(writev, this, arguments); + if (this !== kTraced && fsWritev.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments, 1), fd: handle.fd, buffers }; + return await tracePromise(fsWritev, context, writev, arguments); + } validateBufferArray(buffers); if (typeof position !== 'number') @@ -1674,7 +1701,10 @@ async function writev(handle, buffers, position) { } async function rename(oldPath, newPath) { - if (fsRename.shouldTrace()) return await fsRename.tracePromise(rename, this, arguments); + if (this !== kTraced && fsRename.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path: oldPath, dest: newPath }; + return await tracePromise(fsRename, context, rename, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.rename(oldPath, newPath); @@ -1690,7 +1720,10 @@ async function rename(oldPath, newPath) { } async function truncate(path, len = 0) { - if (fsTruncate.shouldTrace()) return await fsTruncate.tracePromise(truncate, this, arguments); + if (this !== kTraced && fsTruncate.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsTruncate, context, truncate, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.truncate(path, len); @@ -1701,7 +1734,10 @@ async function truncate(path, len = 0) { } async function ftruncate(handle, len = 0) { - if (fsFtruncate.shouldTrace()) return await fsFtruncate.tracePromise(ftruncate, this, arguments); + if (this !== kTraced && fsFtruncate.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments, 1), fd: handle.fd }; + return await tracePromise(fsFtruncate, context, ftruncate, arguments); + } validateInteger(len, 'len'); len = MathMax(0, len); return await PromisePrototypeThen( @@ -1712,7 +1748,10 @@ async function ftruncate(handle, len = 0) { } async function rm(path, options) { - if (fsRm.shouldTrace()) return await fsRm.tracePromise(rm, this, arguments); + if (this !== kTraced && fsRm.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsRm, context, rm, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.rm(path, options); @@ -1724,7 +1763,10 @@ async function rm(path, options) { } async function rmdir(path, options) { - if (fsRmdir.shouldTrace()) return await fsRmdir.tracePromise(rmdir, this, arguments); + if (this !== kTraced && fsRmdir.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsRmdir, context, rmdir, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.rmdir(path); @@ -1750,7 +1792,10 @@ async function rmdir(path, options) { } async function fdatasync(handle) { - if (fsFdatasync.shouldTrace()) return await fsFdatasync.tracePromise(fdatasync, this, arguments); + if (this !== kTraced && fsFdatasync.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments, 1), fd: handle.fd }; + return await tracePromise(fsFdatasync, context, fdatasync, arguments); + } if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.'); } @@ -1762,7 +1807,10 @@ async function fdatasync(handle) { } async function fsync(handle) { - if (fsFsync.shouldTrace()) return await fsFsync.tracePromise(fsync, this, arguments); + if (this !== kTraced && fsFsync.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments, 1), fd: handle.fd }; + return await tracePromise(fsFsync, context, fsync, arguments); + } if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.'); } @@ -1774,7 +1822,10 @@ async function fsync(handle) { } async function mkdir(path, options) { - if (fsMkdir.shouldTrace()) return await fsMkdir.tracePromise(mkdir, this, arguments); + if (this !== kTraced && fsMkdir.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsMkdir, context, mkdir, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.mkdir(path, options); @@ -1842,7 +1893,10 @@ async function readdirRecursiveWithPermissionModel(basePath, options) { } async function readdir(path, options) { - if (fsReaddir.shouldTrace()) return await fsReaddir.tracePromise(readdir, this, arguments); + if (this !== kTraced && fsReaddir.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsReaddir, context, readdir, arguments); + } options = getOptions(options); // Make shallow copy to prevent mutating options from affecting results @@ -1874,7 +1928,10 @@ async function readdir(path, options) { } async function readlink(path, options) { - if (fsReadlink.shouldTrace()) return await fsReadlink.tracePromise(readlink, this, arguments); + if (this !== kTraced && fsReadlink.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsReadlink, context, readlink, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.readlink(path, options); @@ -1890,7 +1947,10 @@ async function readlink(path, options) { } async function symlink(target, path, type) { - if (fsSymlink.shouldTrace()) return await fsSymlink.tracePromise(symlink, this, arguments); + if (this !== kTraced && fsSymlink.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), target, path }; + return await tracePromise(fsSymlink, context, symlink, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.symlink(target, path, type); @@ -1930,7 +1990,10 @@ async function symlink(target, path, type) { } async function fstat(handle, options = { __proto__: null, bigint: false }) { - if (fsFstat.shouldTrace()) return await fsFstat.tracePromise(fstat, this, arguments); + if (this !== kTraced && fsFstat.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments, 1), fd: handle.fd }; + return await tracePromise(fsFstat, context, fstat, arguments); + } validateObject(options, 'options'); const { signal } = options; if (signal !== undefined) validateAbortSignal(signal, 'options.signal'); @@ -1947,7 +2010,10 @@ async function fstat(handle, options = { __proto__: null, bigint: false }) { } async function lstat(path, options = { __proto__: null, bigint: false }) { - if (fsLstat.shouldTrace()) return await fsLstat.tracePromise(lstat, this, arguments); + if (this !== kTraced && fsLstat.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsLstat, context, lstat, arguments); + } validateObject(options, 'options'); const { signal } = options; if (signal !== undefined) validateAbortSignal(signal, 'options.signal'); @@ -1975,7 +2041,10 @@ async function lstat(path, options = { __proto__: null, bigint: false }) { } async function stat(path, options = { __proto__: null, bigint: false, throwIfNoEntry: true }) { - if (fsStat.shouldTrace()) return await fsStat.tracePromise(stat, this, arguments); + if (this !== kTraced && fsStat.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsStat, context, stat, arguments); + } validateObject(options, 'options'); const { signal } = options; if (signal !== undefined) validateAbortSignal(signal, 'options.signal'); @@ -2001,7 +2070,10 @@ async function stat(path, options = { __proto__: null, bigint: false, throwIfNoE } async function statfs(path, options = { __proto__: null, bigint: false }) { - if (fsStatfs.shouldTrace()) return await fsStatfs.tracePromise(statfs, this, arguments); + if (this !== kTraced && fsStatfs.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsStatfs, context, statfs, arguments); + } const h = vfsState.handlers; if (h !== null) { const result = h.statfs(path, options); @@ -2017,7 +2089,10 @@ async function statfs(path, options = { __proto__: null, bigint: false }) { } async function link(existingPath, newPath) { - if (fsLink.shouldTrace()) return await fsLink.tracePromise(link, this, arguments); + if (this !== kTraced && fsLink.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path: existingPath, dest: newPath }; + return await tracePromise(fsLink, context, link, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.link(existingPath, newPath); @@ -2033,7 +2108,10 @@ async function link(existingPath, newPath) { } async function unlink(path) { - if (fsUnlink.shouldTrace()) return await fsUnlink.tracePromise(unlink, this, arguments); + if (this !== kTraced && fsUnlink.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsUnlink, context, unlink, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.unlink(path); @@ -2047,7 +2125,10 @@ async function unlink(path) { } async function fchmod(handle, mode) { - if (fsFchmod.shouldTrace()) return await fsFchmod.tracePromise(fchmod, this, arguments); + if (this !== kTraced && fsFchmod.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments, 1), fd: handle.fd, mode }; + return await tracePromise(fsFchmod, context, fchmod, arguments); + } if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('fchmod API is disabled when Permission Model is enabled.'); } @@ -2060,7 +2141,10 @@ async function fchmod(handle, mode) { } async function chmod(path, mode) { - if (fsChmod.shouldTrace()) return await fsChmod.tracePromise(chmod, this, arguments); + if (this !== kTraced && fsChmod.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path, mode }; + return await tracePromise(fsChmod, context, chmod, arguments); + } path = getValidatedPath(path); mode = parseFileMode(mode, 'mode'); @@ -2078,7 +2162,10 @@ async function chmod(path, mode) { } async function lchmod(path, mode) { - if (fsLchmod.shouldTrace()) return await fsLchmod.tracePromise(lchmod, this, arguments); + if (this !== kTraced && fsLchmod.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path, mode }; + return await tracePromise(fsLchmod, context, lchmod, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.lchmod(path, mode); @@ -2093,7 +2180,10 @@ async function lchmod(path, mode) { } async function lchown(path, uid, gid) { - if (fsLchown.shouldTrace()) return await fsLchown.tracePromise(lchown, this, arguments); + if (this !== kTraced && fsLchown.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path, uid, gid }; + return await tracePromise(fsLchown, context, lchown, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.lchown(path, uid, gid); @@ -2111,7 +2201,10 @@ async function lchown(path, uid, gid) { } async function fchown(handle, uid, gid) { - if (fsFchown.shouldTrace()) return await fsFchown.tracePromise(fchown, this, arguments); + if (this !== kTraced && fsFchown.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments, 1), fd: handle.fd, uid, gid }; + return await tracePromise(fsFchown, context, fchown, arguments); + } validateInteger(uid, 'uid', -1, kMaxUserId); validateInteger(gid, 'gid', -1, kMaxUserId); if (permission.isEnabled()) { @@ -2125,7 +2218,10 @@ async function fchown(handle, uid, gid) { } async function chown(path, uid, gid) { - if (fsChown.shouldTrace()) return await fsChown.tracePromise(chown, this, arguments); + if (this !== kTraced && fsChown.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path, uid, gid }; + return await tracePromise(fsChown, context, chown, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.chown(path, uid, gid); @@ -2143,7 +2239,10 @@ async function chown(path, uid, gid) { } async function utimes(path, atime, mtime) { - if (fsUtimes.shouldTrace()) return await fsUtimes.tracePromise(utimes, this, arguments); + if (this !== kTraced && fsUtimes.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path, atime, mtime }; + return await tracePromise(fsUtimes, context, utimes, arguments); + } path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -2162,7 +2261,10 @@ async function utimes(path, atime, mtime) { } async function futimes(handle, atime, mtime) { - if (fsFutimes.shouldTrace()) return await fsFutimes.tracePromise(futimes, this, arguments); + if (this !== kTraced && fsFutimes.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments, 1), fd: handle.fd, atime, mtime }; + return await tracePromise(fsFutimes, context, futimes, arguments); + } if (permission.isEnabled()) { throw new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.'); } @@ -2176,7 +2278,10 @@ async function futimes(handle, atime, mtime) { } async function lutimes(path, atime, mtime) { - if (fsLutimes.shouldTrace()) return await fsLutimes.tracePromise(lutimes, this, arguments); + if (this !== kTraced && fsLutimes.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path, atime, mtime }; + return await tracePromise(fsLutimes, context, lutimes, arguments); + } path = getValidatedPath(path); atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); @@ -2195,7 +2300,10 @@ async function lutimes(path, atime, mtime) { } async function realpath(path, options) { - if (fsRealpath.shouldTrace()) return await fsRealpath.tracePromise(realpath, this, arguments); + if (this !== kTraced && fsRealpath.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsRealpath, context, realpath, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.realpath(path, options); @@ -2210,7 +2318,10 @@ async function realpath(path, options) { } async function mkdtemp(prefix, options) { - if (fsMkdtemp.shouldTrace()) return await fsMkdtemp.tracePromise(mkdtemp, this, arguments); + if (this !== kTraced && fsMkdtemp.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), prefix }; + return await tracePromise(fsMkdtemp, context, mkdtemp, arguments); + } const h = vfsState.handlers; if (h !== null) { const promise = h.mkdtemp(prefix, options); @@ -2253,8 +2364,9 @@ async function mkstemp(prefix, options) { } async function mkdtempDisposable(prefix, options) { - if (fsMkdtemp.shouldTrace()) { - return await fsMkdtemp.tracePromise(mkdtempDisposable, this, arguments); + if (this !== kTraced && fsMkdtemp.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), prefix }; + return await tracePromise(fsMkdtemp, context, mkdtempDisposable, arguments); } options = getOptions(options); if (BufferIsBuffer(prefix)) { @@ -2291,7 +2403,10 @@ async function mkdtempDisposable(prefix, options) { } async function writeFile(path, data, options) { - if (fsWriteFile.shouldTrace()) return await fsWriteFile.tracePromise(writeFile, this, arguments); + if (this !== kTraced && fsWriteFile.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path, data }; + return await tracePromise(fsWriteFile, context, writeFile, arguments); + } options = getOptions(options, { encoding: 'utf8', mode: 0o666, @@ -2372,7 +2487,10 @@ function isCustomIterable(obj) { } async function appendFile(path, data, options) { - if (fsAppendFile.shouldTrace()) return await fsAppendFile.tracePromise(appendFile, this, arguments); + if (this !== kTraced && fsAppendFile.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path, data }; + return await tracePromise(fsAppendFile, context, appendFile, arguments); + } options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); parseFileMode(options.mode, 'mode', 0o666); @@ -2388,7 +2506,10 @@ async function appendFile(path, data, options) { } async function readFile(path, options) { - if (fsReadFile.shouldTrace()) return await fsReadFile.tracePromise(readFile, this, arguments); + if (this !== kTraced && fsReadFile.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return await tracePromise(fsReadFile, context, readFile, arguments); + } const h = vfsState.handlers; if (h !== null) { checkAborted(options?.signal); @@ -2450,9 +2571,12 @@ function readFileInOneRoundTrip(path, flagsNumber) { const opendirPromisified = promisify(opendirCallback); // The unused `callback` keeps the length of promisify(fs.opendir). function opendir(path, options, callback) { - if (fsOpendir.shouldTrace()) return fsOpendir.tracePromise(opendir, this, arguments); + if (this !== kTraced && fsOpendir.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), path }; + return tracePromise(fsOpendir, context, opendir, arguments); + } // Skips the check in fs.opendir(), so the call is traced only once. - return callUntraced(opendirPromisified, undefined, [path, options]); + return FunctionPrototypeCall(opendirPromisified, kTraced, path, options); } async function* _watch(filename, options = kEmptyObject) { diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index 75a0a1b20aa..ee28ab9dcda 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -4,7 +4,6 @@ const { ArrayIsArray, ArrayPrototypePush, ArrayPrototypePushApply, - ArrayPrototypeSlice, BigInt, Date, DateNow, @@ -1199,132 +1198,89 @@ function setVfsHandlers(handlers) { vfsState.handlers = handlers; } // Each traced fs function starts with: // -// if (fsStat.shouldTrace()) return fsStat.traceCallback(stat, this, arguments); +// if (this !== kTraced && fsStat.hasSubscribers) { +// const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), path }; +// return traceCallback(fsStat, context, stat, arguments); +// } // -// The trace methods call the same function again inside the trace, so -// that its whole body runs inside the `start` scope. They set this flag -// just before that call, so that the call skips the check once. -let skipNextTrace = false; - -function callUntraced(fn, thisArg, args) { - skipNextTrace = true; - try { - return ReflectApply(fn, thisArg, args); - } finally { - skipNextTrace = false; - } +// The trace helpers call the same function again inside the trace, so +// that its whole body runs inside the `start` scope. They pass kTraced +// as `this`, so that the second entry skips the check. No traced +// function uses `this` for anything else. +const kTraced = Symbol('kTraced'); + +function applyTraced(fn, args) { + return ReflectApply(fn, kTraced, args); } -function callUntracedSpread(fn, thisArg, ...args) { - return callUntraced(fn, thisArg, args); +function applyTracedSpread(fn, ...args) { + return ReflectApply(fn, kTraced, args); } -// The tracing channel of one fs operation. `fields` are the context -// property names of the leading arguments. All arguments are also in -// `context.args`. -class FsTracer { - constructor(name, fields) { - this.channel = tracingChannel(`fs.${name}`); - this.fields = fields; - } - - shouldTrace() { - if (skipNextTrace) { - skipNextTrace = false; - return false; - } - return this.channel.hasSubscribers; - } - - // `context.args` is a copy, so that subscribers can not change the - // arguments of the call. - #createContext(api, args) { - const { fields } = this; - let start = 0; - // FileHandle methods pass the handle in place of a descriptor. It is - // not one of the arguments of the method. - if (fields[0] === 'fd' && typeof args[0] === 'object' && args[0] !== null) { - start = 1; - } - const context = { api, args: ArrayPrototypeSlice(args, start) }; - for (let i = 0; i < fields.length && i < args.length; i++) { - context[fields[i]] = i === 0 && start === 1 ? args[0].fd : args[i]; - } - return context; - } - - traceSync(fn, thisArg, args) { - const context = this.#createContext('sync', args); - return this.channel.traceSync(callUntraced, context, undefined, - fn, thisArg, args); - } +function traceSync(channel, context, fn, args) { + return channel.traceSync(applyTraced, context, undefined, fn, args); +} - tracePromise(fn, thisArg, args) { - const context = this.#createContext('promise', args); - return this.channel.tracePromise(callUntraced, context, undefined, - fn, thisArg, args); - } +function tracePromise(channel, context, fn, args) { + return channel.tracePromise(applyTraced, context, undefined, fn, args); +} - // The callback is the last function argument. Without one, the - // function throws when it is called again. `contextArgs` are the - // arguments to report, when they differ from the arguments of the call. - traceCallback(fn, thisArg, args, contextArgs = args) { - let position = args.length - 1; - while (position >= 0 && typeof args[position] !== 'function') position--; - if (position < 0) return callUntraced(fn, thisArg, args); - const context = this.#createContext('callback', contextArgs); - const callArgs = [callUntracedSpread, position + 2, context, undefined, fn, thisArg]; - ArrayPrototypePushApply(callArgs, args); - const { channel } = this; - return ReflectApply(channel.traceCallback, channel, callArgs); - } +// The callback is the last function argument. Without one, the function +// is called again untraced, because it throws for a missing callback. +function traceCallback(channel, context, fn, args) { + let position = args.length - 1; + while (position >= 0 && typeof args[position] !== 'function') position--; + if (position < 0) return applyTraced(fn, args); + const callArgs = [applyTracedSpread, position + 1, context, undefined, fn]; + ArrayPrototypePushApply(callArgs, args); + return ReflectApply(channel.traceCallback, channel, callArgs); } -const fsAccess = new FsTracer('access', ['path']); -const fsAppendFile = new FsTracer('appendFile', ['path', 'data']); -const fsChmod = new FsTracer('chmod', ['path', 'mode']); -const fsChown = new FsTracer('chown', ['path', 'uid', 'gid']); -const fsClose = new FsTracer('close', ['fd']); -const fsClosedir = new FsTracer('closedir', ['path']); -const fsCopyFile = new FsTracer('copyFile', ['path', 'dest']); -const fsCp = new FsTracer('cp', ['path', 'dest']); -const fsExists = new FsTracer('exists', ['path']); -const fsFchmod = new FsTracer('fchmod', ['fd', 'mode']); -const fsFchown = new FsTracer('fchown', ['fd', 'uid', 'gid']); -const fsFdatasync = new FsTracer('fdatasync', ['fd']); -const fsFstat = new FsTracer('fstat', ['fd']); -const fsFsync = new FsTracer('fsync', ['fd']); -const fsFtruncate = new FsTracer('ftruncate', ['fd']); -const fsFutimes = new FsTracer('futimes', ['fd', 'atime', 'mtime']); -const fsGlob = new FsTracer('glob', ['pattern']); -const fsLchmod = new FsTracer('lchmod', ['path', 'mode']); -const fsLchown = new FsTracer('lchown', ['path', 'uid', 'gid']); -const fsLink = new FsTracer('link', ['path', 'dest']); -const fsLstat = new FsTracer('lstat', ['path']); -const fsLutimes = new FsTracer('lutimes', ['path', 'atime', 'mtime']); -const fsMkdir = new FsTracer('mkdir', ['path']); -const fsMkdtemp = new FsTracer('mkdtemp', ['prefix']); -const fsOpen = new FsTracer('open', ['path']); -const fsOpenAsBlob = new FsTracer('openAsBlob', ['path']); -const fsOpendir = new FsTracer('opendir', ['path']); -const fsRead = new FsTracer('read', ['fd']); -const fsReadFile = new FsTracer('readFile', ['path']); -const fsReaddir = new FsTracer('readdir', ['path']); -const fsReadlink = new FsTracer('readlink', ['path']); -const fsReadv = new FsTracer('readv', ['fd', 'buffers']); -const fsRealpath = new FsTracer('realpath', ['path']); -const fsRename = new FsTracer('rename', ['path', 'dest']); -const fsRm = new FsTracer('rm', ['path']); -const fsRmdir = new FsTracer('rmdir', ['path']); -const fsStat = new FsTracer('stat', ['path']); -const fsStatfs = new FsTracer('statfs', ['path']); -const fsSymlink = new FsTracer('symlink', ['target', 'path']); -const fsTruncate = new FsTracer('truncate', ['path']); -const fsUnlink = new FsTracer('unlink', ['path']); -const fsUtimes = new FsTracer('utimes', ['path', 'atime', 'mtime']); -const fsWrite = new FsTracer('write', ['fd', 'buffer']); -const fsWriteFile = new FsTracer('writeFile', ['path', 'data']); -const fsWritev = new FsTracer('writev', ['fd', 'buffers']); +const fsAccess = tracingChannel('fs.access'); +const fsAppendFile = tracingChannel('fs.appendFile'); +const fsChmod = tracingChannel('fs.chmod'); +const fsChown = tracingChannel('fs.chown'); +const fsClose = tracingChannel('fs.close'); +const fsClosedir = tracingChannel('fs.closedir'); +const fsCopyFile = tracingChannel('fs.copyFile'); +const fsCp = tracingChannel('fs.cp'); +const fsExists = tracingChannel('fs.exists'); +const fsFchmod = tracingChannel('fs.fchmod'); +const fsFchown = tracingChannel('fs.fchown'); +const fsFdatasync = tracingChannel('fs.fdatasync'); +const fsFstat = tracingChannel('fs.fstat'); +const fsFsync = tracingChannel('fs.fsync'); +const fsFtruncate = tracingChannel('fs.ftruncate'); +const fsFutimes = tracingChannel('fs.futimes'); +const fsGlob = tracingChannel('fs.glob'); +const fsLchmod = tracingChannel('fs.lchmod'); +const fsLchown = tracingChannel('fs.lchown'); +const fsLink = tracingChannel('fs.link'); +const fsLstat = tracingChannel('fs.lstat'); +const fsLutimes = tracingChannel('fs.lutimes'); +const fsMkdir = tracingChannel('fs.mkdir'); +const fsMkdtemp = tracingChannel('fs.mkdtemp'); +const fsOpen = tracingChannel('fs.open'); +const fsOpenAsBlob = tracingChannel('fs.openAsBlob'); +const fsOpendir = tracingChannel('fs.opendir'); +const fsRead = tracingChannel('fs.read'); +const fsReadFile = tracingChannel('fs.readFile'); +const fsReaddir = tracingChannel('fs.readdir'); +const fsReadlink = tracingChannel('fs.readlink'); +const fsReadv = tracingChannel('fs.readv'); +const fsRealpath = tracingChannel('fs.realpath'); +const fsRename = tracingChannel('fs.rename'); +const fsRm = tracingChannel('fs.rm'); +const fsRmdir = tracingChannel('fs.rmdir'); +const fsStat = tracingChannel('fs.stat'); +const fsStatfs = tracingChannel('fs.statfs'); +const fsSymlink = tracingChannel('fs.symlink'); +const fsTruncate = tracingChannel('fs.truncate'); +const fsUnlink = tracingChannel('fs.unlink'); +const fsUtimes = tracingChannel('fs.utimes'); +const fsWrite = tracingChannel('fs.write'); +const fsWriteFile = tracingChannel('fs.writeFile'); +const fsWritev = tracingChannel('fs.writev'); module.exports = { fsAccess, @@ -1394,7 +1350,10 @@ module.exports = { getValidatedFd, getValidatedPath, handleErrorFromBinding, - callUntraced, + kTraced, + traceCallback, + tracePromise, + traceSync, preprocessSymlinkDestination, realpathCacheKey: Symbol('realpathCacheKey'), getStatFsFromBinding, From 87bf499cc9011fada889a6e84a8ffbbd57f91072 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 20 Oct 2026 14:51:42 +0100 Subject: [PATCH 6/6] fs: trace the mkstemp functions fs.mkstemp(), fs.mkstempSync() and fsPromises.mkstemp() landed after this branch, so the coverage test failed on them. Add the fs.mkstemp channel with the same context as fs.mkdtemp. Assisted-by: claude:fable-5.1 Signed-off-by: Tim Fish --- doc/api/diagnostics_channel.md | 1 + lib/fs.js | 9 +++++++++ lib/internal/fs/promises.js | 5 +++++ lib/internal/fs/utils.js | 2 ++ test/parallel/test-diagnostics-channel-fs-coverage.js | 6 ++++++ 5 files changed, 23 insertions(+) diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index ac50aa57612..a1427475626 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -1699,6 +1699,7 @@ of its forms: `stat` means `fs.stat()`, `fs.statSync()`, and | `fs.lutimes` | `lutimes` | `path`, `atime`, `mtime` | | `fs.mkdir` | `mkdir` | `path` | | `fs.mkdtemp` | `mkdtemp`, `mkdtempDisposable` | `prefix` | +| `fs.mkstemp` | `mkstemp` | `prefix` | | `fs.open` | `open` | `path` | | `fs.openAsBlob` | `openAsBlob` | `path` | | `fs.opendir` | `opendir` | `path` | diff --git a/lib/fs.js b/lib/fs.js index d73bcdda4ec..b5ad44d0674 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -155,6 +155,7 @@ const { fsLutimes, fsMkdir, fsMkdtemp, + fsMkstemp, fsOpen, fsOpenAsBlob, fsRead, @@ -4168,6 +4169,10 @@ function mkdtempDisposableSync(prefix, options) { * @returns {void} */ function mkstemp(prefix, options, callback) { + if (this !== kTraced && fsMkstemp.hasSubscribers) { + const context = { api: 'callback', args: ArrayPrototypeSlice(arguments), prefix }; + return traceCallback(fsMkstemp, context, mkstemp, arguments); + } callback = makeCallback(typeof options === 'function' ? options : callback); options = getOptions(options); @@ -4195,6 +4200,10 @@ function mkstemp(prefix, options, callback) { * @returns {{ path: string | Buffer, fd: number }} */ function mkstempSync(prefix, options) { + if (this !== kTraced && fsMkstemp.hasSubscribers) { + const context = { api: 'sync', args: ArrayPrototypeSlice(arguments), prefix }; + return traceSync(fsMkstemp, context, mkstempSync, arguments); + } options = getOptions(options); if (BufferIsBuffer(prefix)) { options = { ...options, encoding: 'buffer' }; diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index c8fcbc0a055..66847cb9ef8 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -111,6 +111,7 @@ const { fsLutimes, fsMkdir, fsMkdtemp, + fsMkstemp, fsOpen, fsOpendir, fsRead, @@ -2342,6 +2343,10 @@ async function mkdtemp(prefix, options) { } async function mkstemp(prefix, options) { + if (this !== kTraced && fsMkstemp.hasSubscribers) { + const context = { api: 'promise', args: ArrayPrototypeSlice(arguments), prefix }; + return await tracePromise(fsMkstemp, context, mkstemp, arguments); + } options = getOptions(options); if (BufferIsBuffer(prefix)) { options = { ...options, encoding: 'buffer' }; diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index ee28ab9dcda..272d3ff9d93 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -1260,6 +1260,7 @@ const fsLstat = tracingChannel('fs.lstat'); const fsLutimes = tracingChannel('fs.lutimes'); const fsMkdir = tracingChannel('fs.mkdir'); const fsMkdtemp = tracingChannel('fs.mkdtemp'); +const fsMkstemp = tracingChannel('fs.mkstemp'); const fsOpen = tracingChannel('fs.open'); const fsOpenAsBlob = tracingChannel('fs.openAsBlob'); const fsOpendir = tracingChannel('fs.opendir'); @@ -1307,6 +1308,7 @@ module.exports = { fsLutimes, fsMkdir, fsMkdtemp, + fsMkstemp, fsOpen, fsOpenAsBlob, fsOpendir, diff --git a/test/parallel/test-diagnostics-channel-fs-coverage.js b/test/parallel/test-diagnostics-channel-fs-coverage.js index 746d4391b5f..1e38f245b49 100644 --- a/test/parallel/test-diagnostics-channel-fs-coverage.js +++ b/test/parallel/test-diagnostics-channel-fs-coverage.js @@ -92,6 +92,7 @@ const syncOps = [ ['mkdtempSync', 'mkdtemp', () => fs.mkdtempSync(fresh('tmp'))], ['mkdtempDisposableSync', 'mkdtemp', () => fs.mkdtempDisposableSync(fresh('tmp')).remove()], + ['mkstempSync', 'mkstemp', () => fs.closeSync(fs.mkstempSync(fresh('tmp')).fd)], ['openAsBlobSync', 'openAsBlob', () => fs.openAsBlobSync(file)], ['openSync', 'open', () => fs.closeSync(fs.openSync(file))], ['opendirSync', 'opendir', () => fs.opendirSync(newDir()).closeSync()], @@ -141,6 +142,10 @@ const callbackOps = [ ['lutimes', 'lutimes', (cb) => fs.lutimes(newLink(), now, now, cb)], ['mkdir', 'mkdir', (cb) => fs.mkdir(fresh('mkdir'), cb)], ['mkdtemp', 'mkdtemp', (cb) => fs.mkdtemp(fresh('tmp'), cb)], + ['mkstemp', 'mkstemp', (cb) => fs.mkstemp(fresh('tmp'), (err, file) => { + if (file !== undefined) fs.closeSync(file.fd); + cb(err); + })], ['open', 'open', (cb) => fs.open(file, (err, fd) => { if (fd !== undefined) fs.closeSync(fd); cb(err); @@ -188,6 +193,7 @@ const promiseOps = [ ['mkdtemp', 'mkdtemp', () => fsp.mkdtemp(fresh('tmp'))], ['mkdtempDisposable', 'mkdtemp', async () => (await fsp.mkdtempDisposable(fresh('tmp'))).remove()], + ['mkstemp', 'mkstemp', async () => (await fsp.mkstemp(fresh('tmp'))).handle.close()], ['open', 'open', async () => (await fsp.open(file)).close()], ['opendir', 'opendir', async () => (await fsp.opendir(newDir())).close()], ['openAsBlob', 'openAsBlob', () => fs.openAsBlob(file)],