From e92e419a93dd42dd44749b2d8e3d607f29231e2f Mon Sep 17 00:00:00 2001 From: Dan Hipschman <48698358+dan-hipschman@users.noreply.github.com> Date: Fri, 11 Apr 2025 12:24:52 -0700 Subject: [PATCH] cp: refuse to copy symlink over itself --- src/uu/cp/src/cp.rs | 8 +++++++- tests/by-util/test_cp.rs | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 190bbde3c..60d9c98fe 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1802,7 +1802,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 + { return false; } true diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 2361201e6..683691504 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -5218,6 +5218,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]