Skip to content
Merged
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
8 changes: 7 additions & 1 deletion src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1808,7 +1808,13 @@ fn is_forbidden_to_copy_to_same_file(
if options.copy_mode == CopyMode::SymLink && dest_is_symlink {
return false;
}
if dest_is_symlink && source_is_symlink && !options.dereference {
// If source and dest are both the same symlink but with different names, then allow the copy.
// This can occur, for example, if source and dest are both hardlinks to the same symlink.
if dest_is_symlink
&& source_is_symlink
&& source.file_name() != dest.file_name()
&& !options.dereference
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, if source and dest are both the same symlink but with different names, then the existing behavior (cp succeeds) is correct. This can occur, for example, if source and dest are both hardlinks to the same symlink. There's actually an existing unit test for this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add this as a comment in the code too ? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"hardlinks to the same symlink" also sounds like something that we should include in the test suite, especially because this PR does NOT handle it correctly, despite your comment:

# Test setup
$ ln -s nonexistent bad
$ cp -P --link bad bad2
$ ls -1i bad bad2
20145169 bad
20145169 bad2

# GNU
$ cp -P bad bad2
$ ls -1i bad bad2
20145169 bad
20145169 bad2

# This PR (7677be2075a0401f05fdf9e138d821a5673f4404)
$ cargo run -q cp -P bad bad2
$ ls -1i bad bad2
20145169 bad
20178774 bad2

As you can see, the second file is no longer a hardlink of the first, even though it should still be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Interesting test case. I looked into it and it seems related but separate from this PR (it occurs on main too BTW), so I made a separate PR for it: #7753

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! I thought it's the same issue, sorry :D

{
return false;
}
true
Expand Down
25 changes: 25 additions & 0 deletions tests/by-util/test_cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5217,6 +5217,31 @@ mod same_file {
assert_eq!(symlink1, at.resolve_link(symlink2));
}

#[test]
fn test_same_symlink_to_itself_no_dereference() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&["-P", SYMLINK_NAME, SYMLINK_NAME])
.fails()
.stderr_contains("are the same file");
}

#[test]
fn test_same_dangling_symlink_to_itself_no_dereference() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.symlink_file("nonexistent_file", SYMLINK_NAME);
scene
.ucmd()
.args(&["-P", SYMLINK_NAME, SYMLINK_NAME])
.fails()
.stderr_contains("are the same file");
}

// the following tests tries to copy file to a hardlink of the same file with
// various options
#[test]
Expand Down
Loading