From 5b2602e3a33691b0bac890042debb356d2bd8203 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 26 Jun 2026 23:47:16 -0700 Subject: [PATCH 1/2] vfs: support writeFileSync with virtual fds Route fs.writeFileSync() and fs.appendFileSync() calls with VFS-owned file descriptors through the virtual file handle instead of falling back to the native fs binding. This preserves descriptor semantics for both memory-backed VFS files and RealFSProvider handles. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: openai:gpt-5.5 --- lib/internal/vfs/setup.js | 38 ++++++++++++-- test/parallel/test-vfs-fs-writeFileSync.js | 59 ++++++++++++++++++++++ 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/lib/internal/vfs/setup.js b/lib/internal/vfs/setup.js index fe06f578b2f6ca..c857009bf25d4f 100644 --- a/lib/internal/vfs/setup.js +++ b/lib/internal/vfs/setup.js @@ -9,6 +9,7 @@ const { } = primordials; const { Buffer } = require('buffer'); +const { isArrayBufferView } = require('internal/util/types'); const { resolve, sep } = require('path'); const { fileURLToPath, URL } = require('internal/url'); const { kEmptyObject } = require('internal/util'); @@ -46,6 +47,31 @@ function noopFd(fd) { return undefined; } +function toWriteBuffer(data, options) { + if (Buffer.isBuffer(data)) return data; + if (isArrayBufferView(data)) { + return Buffer.from(data.buffer, data.byteOffset, data.byteLength); + } + const encoding = typeof options === 'string' ? options : options?.encoding; + return Buffer.from(data, encoding || 'utf8'); +} + +function writeFileSyncFd(fd, data, options) { + const vfd = getVirtualFd(fd); + if (!vfd) return undefined; + + const buffer = toWriteBuffer(data, options); + let offset = 0; + let length = buffer.byteLength; + while (length > 0) { + const written = vfd.entry.writeSync(buffer, offset, length, null); + offset += written; + length -= written; + } + + return true; +} + // Registry of active VFS instances. const activeVFSList = []; @@ -288,10 +314,14 @@ function createVfsHandlers() { // ==================== Sync path-based write ops ==================== - writeFileSync: (path, data, options) => - vfsOpVoid(path, (vfs, n) => vfs.writeFileSync(n, data, options)), - appendFileSync: (path, data, options) => - vfsOpVoid(path, (vfs, n) => vfs.appendFileSync(n, data, options)), + writeFileSync(path, data, options) { + if (typeof path === 'number') return writeFileSyncFd(path, data, options); + return vfsOpVoid(path, (vfs, n) => vfs.writeFileSync(n, data, options)); + }, + appendFileSync(path, data, options) { + if (typeof path === 'number') return writeFileSyncFd(path, data, options); + return vfsOpVoid(path, (vfs, n) => vfs.appendFileSync(n, data, options)); + }, mkdirSync: (path, options) => vfsOp(path, (vfs, n) => ({ result: vfs.mkdirSync(n, options) })), rmdirSync: (path) => vfsOpVoid(path, (vfs, n) => vfs.rmdirSync(n)), diff --git a/test/parallel/test-vfs-fs-writeFileSync.js b/test/parallel/test-vfs-fs-writeFileSync.js index 7469127bb1fde3..eeed2a685f08e3 100644 --- a/test/parallel/test-vfs-fs-writeFileSync.js +++ b/test/parallel/test-vfs-fs-writeFileSync.js @@ -26,4 +26,63 @@ assert.strictEqual(fs.readFileSync(target, 'utf8'), 'fresh more'); fs.writeFileSync(target, Buffer.from('binary')); assert.strictEqual(fs.readFileSync(target, 'utf8'), 'binary'); +// writeFileSync via a VFS fd writes through the open descriptor. +{ + const fdTarget = path.join(mountPoint, 'src/fd.txt'); + const fd = fs.openSync(fdTarget, 'w+'); + try { + fs.writeFileSync(fd, 'hello'); + fs.writeFileSync(fd, new Uint8Array(Buffer.from(' world'))); + assert.strictEqual(fs.readFileSync(fdTarget, 'utf8'), 'hello world'); + } finally { + fs.closeSync(fd); + } +} + +// appendFileSync via a VFS fd follows normal fd write semantics. +{ + const fdTarget = path.join(mountPoint, 'src/append-fd.txt'); + fs.writeFileSync(fdTarget, 'start'); + const fd = fs.openSync(fdTarget, 'a'); + try { + fs.appendFileSync(fd, ' end'); + assert.strictEqual(fs.readFileSync(fdTarget, 'utf8'), 'start end'); + } finally { + fs.closeSync(fd); + } +} + myVfs.unmount(); + +// writeFileSync via a RealFSProvider fd remains tied to the open descriptor +// after the backing path is renamed. +{ + const root = path.join('/tmp', 'vfs-real-writeFileSync-' + process.pid); + const realMountPoint = path.join('/tmp', 'vfs-real-writeFileSync-mount-' + process.pid); + fs.rmSync(root, { recursive: true, force: true }); + fs.rmSync(realMountPoint, { recursive: true, force: true }); + fs.mkdirSync(root, { recursive: true }); + fs.mkdirSync(realMountPoint, { recursive: true }); + + const realVfs = vfs + .create(new vfs.RealFSProvider(root), { emitExperimentalWarning: false }) + .mount(realMountPoint); + try { + const mountedFile = path.join(realMountPoint, 'a.txt'); + fs.writeFileSync(path.join(root, 'a.txt'), 'old'); + const fd = fs.openSync(mountedFile, 'r+'); + try { + fs.renameSync(path.join(root, 'a.txt'), path.join(root, 'b.txt')); + fs.writeFileSync(path.join(root, 'a.txt'), 'new'); + fs.writeFileSync(fd, 'updated'); + assert.strictEqual(fs.readFileSync(path.join(root, 'b.txt'), 'utf8'), 'updated'); + assert.strictEqual(fs.readFileSync(path.join(root, 'a.txt'), 'utf8'), 'new'); + } finally { + fs.closeSync(fd); + } + } finally { + realVfs.unmount(); + fs.rmSync(root, { recursive: true, force: true }); + fs.rmSync(realMountPoint, { recursive: true, force: true }); + } +} From e21d9a84004c7f182a7cbf656c4b78bee1203c94 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Sat, 27 Jun 2026 10:24:27 -0700 Subject: [PATCH 2/2] fixup! vfs: make writeFileSync fd handling explicit --- lib/internal/vfs/setup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/vfs/setup.js b/lib/internal/vfs/setup.js index c857009bf25d4f..e2e641ad841561 100644 --- a/lib/internal/vfs/setup.js +++ b/lib/internal/vfs/setup.js @@ -58,7 +58,7 @@ function toWriteBuffer(data, options) { function writeFileSyncFd(fd, data, options) { const vfd = getVirtualFd(fd); - if (!vfd) return undefined; + if (vfd === undefined) return undefined; const buffer = toWriteBuffer(data, options); let offset = 0;