diff --git a/benchmark/fs/bench-cpSync.js b/benchmark/fs/bench-cpSync.js index 822036726d198d..fa6350384c69dd 100644 --- a/benchmark/fs/bench-cpSync.js +++ b/benchmark/fs/bench-cpSync.js @@ -8,10 +8,7 @@ const tmpdir = require('../../test/common/tmpdir'); const bench = common.createBenchmark(main, { n: [1, 100, 10_000], dereference: ['true', 'false'], - // When `force` is `true` the `cpSync` function is called twice the second - // time however an `ERR_FS_CP_EINVAL` is thrown, so skip `true` for the time being - // TODO: allow `force` to also be `true` once https://github.com/nodejs/node/issues/58468 is addressed - force: ['false'], + force: ['true', 'false'], }); function prepareTestDirectory() { diff --git a/lib/internal/fs/cp/cp-sync.js b/lib/internal/fs/cp/cp-sync.js index 9e67ae6335ec46..1b562b7d239079 100644 --- a/lib/internal/fs/cp/cp-sync.js +++ b/lib/internal/fs/cp/cp-sync.js @@ -196,7 +196,9 @@ function onLink(destStat, src, dest, verbatimSymlinks) { if (!isAbsolute(resolvedDest)) { resolvedDest = resolve(dirname(dest), resolvedDest); } - if (isSrcSubdir(resolvedSrc, resolvedDest)) { + const srcIsDir = fsBinding.internalModuleStat(src) === 1; + + if (srcIsDir && isSrcSubdir(resolvedSrc, resolvedDest)) { throw new ERR_FS_CP_EINVAL({ message: `cannot copy ${resolvedSrc} to a subdirectory of self ` + `${resolvedDest}`, diff --git a/lib/internal/fs/cp/cp.js b/lib/internal/fs/cp/cp.js index 0724eb730db058..b632c650681e77 100644 --- a/lib/internal/fs/cp/cp.js +++ b/lib/internal/fs/cp/cp.js @@ -54,6 +54,7 @@ const { resolve, sep, } = require('path'); +const fsBinding = internalBinding('fs'); async function cpFn(src, dest, opts) { // Warn about using preserveTimestamps on 32-bit node @@ -344,7 +345,10 @@ async function onLink(destStat, src, dest, opts) { if (!isAbsolute(resolvedDest)) { resolvedDest = resolve(dirname(dest), resolvedDest); } - if (isSrcSubdir(resolvedSrc, resolvedDest)) { + + const srcIsDir = fsBinding.internalModuleStat(src) === 1; + + if (srcIsDir && isSrcSubdir(resolvedSrc, resolvedDest)) { throw new ERR_FS_CP_EINVAL({ message: `cannot copy ${resolvedSrc} to a subdirectory of self ` + `${resolvedDest}`, diff --git a/test/parallel/test-fs-cp.mjs b/test/parallel/test-fs-cp.mjs index 260a1449d1a953..7a89d0d859bc6c 100644 --- a/test/parallel/test-fs-cp.mjs +++ b/test/parallel/test-fs-cp.mjs @@ -15,7 +15,7 @@ const { writeFileSync, } = fs; import net from 'net'; -import { join } from 'path'; +import { join, resolve } from 'path'; import { pathToFileURL } from 'url'; import { setTimeout } from 'timers/promises'; @@ -248,6 +248,34 @@ function nextdir(dirname) { ); } +// It allows cpSync copying symlinks in src to locations in dest with existing synlinks not pointing to a directory. +{ + const src = nextdir(); + const dest = nextdir(); + mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); + writeFileSync(`${src}/test.txt`, 'test'); + symlinkSync(resolve(`${src}/test.txt`), join(src, 'link.txt')); + cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); + cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true })); +} + +// It allows cp copying symlinks in src to locations in dest with existing synlinks not pointing to a directory. +{ + const src = nextdir(); + const dest = nextdir(); + mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); + writeFileSync(`${src}/test.txt`, 'test'); + symlinkSync(resolve(`${src}/test.txt`), join(src, 'link.txt')); + cp(src, dest, { recursive: true }, + mustCall((err) => { + assert.strictEqual(err, null); + + cp(src, dest, { recursive: true }, mustCall((err) => { + assert.strictEqual(err, null); + })); + })); +} + // It throws error if symlink in dest points to location in src. { const src = nextdir();