diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index cdec02667fe..97b2b309442 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -2606,6 +2606,23 @@ fn copy_file( } } + // `--attributes-only` skips `handle_existing_dest` above, so its + // same-file check never runs; GNU cp refuses self-copies in this + // mode too. + if initial_dest_metadata.is_some() + && options.attributes_only + && !matches!( + options.overwrite, + OverwriteMode::Clobber(ClobberMode::RemoveDestination) + ) + && is_forbidden_to_copy_to_same_file(source, dest, options, source_in_command_line) + { + return Err(translate!("cp-error-same-file", + "source" => source.quote(), + "dest" => dest.quote()) + .into()); + } + if options.attributes_only && source_is_symlink && !matches!( diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 85dd3adfbba..ddd268673d3 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -4806,6 +4806,34 @@ fn test_cp_cannot_create_regular_file_attributes_only() { .stderr_only("cp: cannot create regular file '/dev/null/n.txt': Not a directory\n"); } +#[test] +fn test_cp_attributes_only_same_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "a"; + + at.touch(file); + + ucmd.arg("--attributes-only") + .arg(file) + .arg(file) + .fails_with_code(1) + .stderr_contains(format!("'{file}' and '{file}' are the same file")); +} + +#[test] +fn test_cp_attributes_only_same_file_dot_path() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "a"; + + at.touch(file); + + ucmd.arg("--attributes-only") + .arg(file) + .arg("./a") + .fails_with_code(1) + .stderr_contains(format!("'{file}' and './{file}' are the same file")); +} + #[test] fn test_cp_seen_file() { let ts = TestScenario::new(util_name!());