Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions lib/internal/vfs/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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 === undefined) 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 = [];

Expand Down Expand Up @@ -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)),
Expand Down
59 changes: 59 additions & 0 deletions test/parallel/test-vfs-fs-writeFileSync.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
}
Loading