From 11dc3abd331e41edc18a0ed62916de3f9264a8e4 Mon Sep 17 00:00:00 2001 From: yohannesjoya Date: Fri, 30 Oct 2026 16:57:55 +0300 Subject: [PATCH] fs: fix cpSync() abort when dest is a dangling symlink CpSyncCheckPaths() used the throwing overload of std::filesystem::equivalent(), which fails when dest is a dangling symlink. The uncaught C++ exception aborted the process. Use the std::error_code overload instead: a dangling dest is not the same file as src, so the copy goes ahead and replaces the link. Fixes: https://github.com/nodejs/node/issues/67141 Signed-off-by: yohannesjoya --- src/node_file.cc | 6 ++-- ...-fs-cp-sync-file-over-dangling-symlink.mjs | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-fs-cp-sync-file-over-dangling-symlink.mjs diff --git a/src/node_file.cc b/src/node_file.cc index c2cd5998e6f..e9f44171556 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -4643,8 +4643,10 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo& args) { auto dest_path_str = ConvertPathToUTF8(dest_path); if (!error_code) { - // Check if src and dest are identical. - if (std::filesystem::equivalent(src_path, dest_path)) { + // Check if src and dest are identical. A dangling dest symlink makes + // equivalent() fail, which means they are not the same file. + std::error_code equivalent_error; + if (std::filesystem::equivalent(src_path, dest_path, equivalent_error)) { static constexpr const char* message = "src and dest cannot be the same %s"; return THROW_ERR_FS_CP_EINVAL(env, message, dest_path_str); diff --git a/test/parallel/test-fs-cp-sync-file-over-dangling-symlink.mjs b/test/parallel/test-fs-cp-sync-file-over-dangling-symlink.mjs new file mode 100644 index 00000000000..51ab2d6def4 --- /dev/null +++ b/test/parallel/test-fs-cp-sync-file-over-dangling-symlink.mjs @@ -0,0 +1,30 @@ +// This tests that cpSync() replaces a dangling symlink in dest with a file +// instead of aborting the process. +import { mustNotMutateObjectDeep } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { + cpSync, + existsSync, + lstatSync, + mkdirSync, + readFileSync, + symlinkSync, + writeFileSync, +} from 'node:fs'; +import { join } from 'node:path'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const dir = nextdir(); +mkdirSync(dir); +const src = join(dir, 'src'); +const dest = join(dir, 'dest'); +writeFileSync(src, 'data'); +symlinkSync('missing', dest); + +cpSync(src, dest, mustNotMutateObjectDeep({ force: true })); +assert(lstatSync(dest).isFile()); +assert.strictEqual(readFileSync(dest, 'utf8'), 'data'); +assert(!existsSync(join(dir, 'missing')));