diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index afccf2fef..62509b756 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1271,7 +1271,12 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult let dest = construct_dest_path(source, target, target_type, options) .unwrap_or_else(|_| target.to_path_buf()); - if fs::metadata(&dest).is_ok() && !fs::symlink_metadata(&dest)?.file_type().is_symlink() + if fs::metadata(&dest).is_ok() + && !fs::symlink_metadata(&dest)?.file_type().is_symlink() + // if both `source` and `dest` are symlinks, it should be considered as an overwrite. + || fs::metadata(source).is_ok() + && fs::symlink_metadata(source)?.file_type().is_symlink() + || matches!(options.copy_mode, CopyMode::SymLink) { // There is already a file and it isn't a symlink (managed in a different place) if copied_destinations.contains(&dest) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 6f141db97..0fba59672 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -5776,6 +5776,59 @@ fn test_cp_parents_absolute_path() { at.file_exists(res); } +#[test] +fn test_copy_symlink_overwrite() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("a"); + at.mkdir("b"); + at.mkdir("c"); + + at.write("t", "hello"); + at.relative_symlink_file("../t", "a/1"); + at.relative_symlink_file("../t", "b/1"); + + ucmd.arg("--no-dereference") + .arg("a/1") + .arg("b/1") + .arg("c") + .fails() + .stderr_only(if cfg!(not(target_os = "windows")) { + "cp: will not overwrite just-created 'c/1' with 'b/1'\n" + } else { + "cp: will not overwrite just-created 'c\\1' with 'b/1'\n" + }); +} + +#[test] +fn test_symlink_mode_overwrite() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("a"); + at.mkdir("b"); + + at.write("a/t", "hello"); + at.write("b/t", "hello"); + + if cfg!(not(target_os = "windows")) { + ucmd.arg("-s") + .arg("a/t") + .arg("b/t") + .arg(".") + .fails() + .stderr_only("cp: will not overwrite just-created './t' with 'b/t'\n"); + + assert_eq!(at.read("./t"), "hello"); + } else { + ucmd.arg("-s") + .arg("a\\t") + .arg("b\\t") + .arg(".") + .fails() + .stderr_only("cp: will not overwrite just-created '.\\t' with 'b\\t'\n"); + + assert_eq!(at.read(".\\t"), "hello"); + } +} + // make sure that cp backup dest symlink before removing it. #[test] fn test_cp_with_options_backup_and_rem_when_dest_is_symlink() {