From 397f5ab3a56007bd30e90c0760fb75f3fbb875bf Mon Sep 17 00:00:00 2001 From: Nikita Snetkov Date: Tue, 18 Aug 2026 11:01:38 +0500 Subject: [PATCH] vfs: reject rename onto a non-empty directory MemoryProvider.renameSync() rejected directory/non-directory mismatches but never checked whether an existing directory destination was empty, so renaming a directory onto a non-empty one silently discarded the destination's contents. POSIX rename(2) requires the destination directory to be empty, and RealFSProvider already reports ENOTEMPTY, so the two providers Node ships disagreed with each other. Signed-off-by: Nikita Snetkov --- lib/internal/vfs/providers/memory.js | 4 ++++ test/parallel/test-vfs-rename.js | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lib/internal/vfs/providers/memory.js b/lib/internal/vfs/providers/memory.js index acec5b4166b2..dcdae87255d0 100644 --- a/lib/internal/vfs/providers/memory.js +++ b/lib/internal/vfs/providers/memory.js @@ -849,6 +849,10 @@ class MemoryProvider extends VirtualProvider { if (!existingDest.isDirectory() && entry.isDirectory()) { throw createENOTDIR('rename', newPath); } + // Cannot overwrite a non-empty directory + if (existingDest.isDirectory() && existingDest.children.size > 0) { + throw createENOTEMPTY('rename', newPath); + } } // Remove from old location (after destination validation) diff --git a/test/parallel/test-vfs-rename.js b/test/parallel/test-vfs-rename.js index 69daba43c11d..009494495794 100644 --- a/test/parallel/test-vfs-rename.js +++ b/test/parallel/test-vfs-rename.js @@ -58,3 +58,28 @@ const vfs = require('node:vfs'); assert.strictEqual(myVfs.existsSync('/a/b/c'), false); assert.strictEqual(myVfs.readFileSync('/a/file.txt', 'utf8'), 'data'); } + +// Renaming a directory onto a non-empty directory throws ENOTEMPTY +{ + const myVfs = vfs.create(); + myVfs.mkdirSync('/src'); + myVfs.writeFileSync('/src/a.txt', 'a'); + myVfs.mkdirSync('/dst'); + myVfs.writeFileSync('/dst/keep.txt', 'keep'); + + assert.throws(() => myVfs.renameSync('/src', '/dst'), { code: 'ENOTEMPTY' }); + assert.strictEqual(myVfs.readFileSync('/dst/keep.txt', 'utf8'), 'keep'); + assert.strictEqual(myVfs.readFileSync('/src/a.txt', 'utf8'), 'a'); +} + +// Renaming a directory onto an empty directory succeeds +{ + const myVfs = vfs.create(); + myVfs.mkdirSync('/src'); + myVfs.writeFileSync('/src/a.txt', 'a'); + myVfs.mkdirSync('/dst'); + + myVfs.renameSync('/src', '/dst'); + assert.strictEqual(myVfs.existsSync('/src'), false); + assert.strictEqual(myVfs.readFileSync('/dst/a.txt', 'utf8'), 'a'); +}