Conversation
f3190d6 to
b91d82d
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #65411 +/- ##
==========================================
- Coverage 90.36% 90.35% -0.02%
==========================================
Files 792 792
Lines 275398 275441 +43
Branches 52776 52798 +22
==========================================
+ Hits 248877 248887 +10
- Misses 16937 16963 +26
- Partials 9584 9591 +7
🚀 New features to boost your workflow:
|
b91d82d to
7820495
Compare
|
Hi @RafaelGSS @jasnell @Renegade334, I've updated the PR to also cover the JavaScript (filter) path in the repeated-copy regression tests — Would you mind taking a look when you have a moment? Thanks! |
7820495 to
9ff389d
Compare
9ff389d to
375eaa8
Compare
375eaa8 to
25cbae4
Compare
| bool same_target = | ||
| symlink_target_absolute == | ||
| current_dest_symlink_target_absolute && | ||
| !isInsideDir(symlink_target_absolute, dest_path); |
There was a problem hiding this comment.
symlink_target_absolute has gone through weakly_canonical, but dest_path is the non-canonical path passed from JS.
If dest is reached through a symlinked directory (e.g. os.tmpdir() on macOS, /var -> /private/var), isInsideDir() returns false for a link pointing at dest itself, so same_target becomes true and the copy succeeds here
| if (error) { | ||
| return CpError::Std(error, dest_str); | ||
| } | ||
| #ifdef _WIN32 |
There was a problem hiding this comment.
(Non-blocking) This duplicates the \\?\ stripping just above and in normalizePathToArray(). A small helper would keep the three in sync.
| mkdirSync(src, mustNotMutateObjectDeep({ recursive: true })); | ||
| mkdirSync(target); | ||
| symlinkSync(target, join(src, 'link')); | ||
| cp(src, dest, mustNotMutateObjectDeep({ recursive: true }), mustCall((err) => { |
There was a problem hiding this comment.
(Non-blocking) mustSucceed() instead of mustCall((err) => { assert.ifError(err); ... }). Since this is an .mjs file, using fs/promises cp with top-level await would also avoid the four levels of nesting.
| // A symlink that resolves to the same target as the destination symlink | ||
| // is not a self-copy, unless the target is the destination directory | ||
| // itself or one of its ancestors. | ||
| const sameTarget = resolvedSrc === resolvedDest && |
There was a problem hiding this comment.
This also changes behavior for an identical target that lives inside dest (e.g. dest/sub): it used to throw ERR_FS_CP_EINVAL and is now allowed. That seems correct, since replacing a link with an identical one is a no-op, but could you add a test for it and for a target that is an ancestor of dest (not only dest itself)?
Repeatedly copying a directory that contains a symlink to an unrelated directory fails on the second copy with ERR_FS_CP_EINVAL because the symlink target is mistaken for a self-referential copy. Compare symlink targets using canonicalized paths, treating identical targets as self-copies only when the target is the destination root or one of its ancestors. Fixes: nodejs#65097 Signed-off-by: haramjeong <04harams77@gmail.com> Assisted-by: Codex
25cbae4 to
d5ef584
Compare
Canonicalize the destination root before allowing identical symlink replacements, and use real paths for the corresponding JavaScript guard. Share Windows namespace normalization and simplify the async tests. Cover descendant, equal, and ancestor targets through direct and aliased destinations with absolute and relative links, with and without filters. Signed-off-by: haramjeong <04harams77@gmail.com> Assisted-by: Codex
d5ef584 to
239d414
Compare
|
Thanks for the review, @RafaelGSS! I've addressed your feedback and added the requested tests. All 30 CI checks are now passing. Could you please take another look when you have a chance? |
Fixes: #65097
Repeatedly copying a directory that contains a symlink to an unrelated
directory fails on the second copy with
ERR_FS_CP_EINVALbecause thesymlink target is mistaken for a self-referential copy.
Root cause
Both the JS (
isSrcSubdirinlib/internal/fs/cp/cp.js) and the C++(
isInsideDirinsrc/node_file.cc) guards are prefix-inclusive, so whenthe source symlink and the already-copied destination symlink resolve to
the same directory, the "copy to a subdirectory of self" check fires on
the identical target.
Fix
the destination root or one of its ancestors; identical targets that are
unrelated to the destination root are allowed to be copied again.
weakly_canonical) in C++so relative-vs-absolute representations and macOS
/varvs/private/varare handled correctly.the internal options object.
Unlike the earlier attempt in #65099, the existing self-referential
protections are kept: a symlink pointing to the destination root or an
ancestor of it still fails with
ERR_FS_CP_EINVAL.Tests
New regression tests (
test-fs-cp-sync-symlink-repeated-copyandtest-fs-cp-async-symlink-repeated-copy) cover both the C++ (no filter)and JS (filter) paths with absolute and relative targets, each copied
twice, and verify the copied symlink still resolves to the original
target. Existing
*-points-to-desttests are unchanged and keep passing.Validation
makebuilds successfully.test/parallel/test-fs-*.{mjs,js}(351 tests) pass, including thenew regression tests and all
*-points-to-desttests.git-clang-formatreports no formatting changes.