diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 3e69e5791..642bbcb14 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1704,6 +1704,11 @@ fn copy_file( } copy_attributes(source, dest, &options.attributes)?; + if options.parents && should_preserve_attribute(options) { + for (x, y) in aligned_ancestors(source, dest) { + copy_attributes(x, y, &options.attributes)?; + } + } if let Some(progress_bar) = progress_bar { progress_bar.inc(fs::metadata(source)?.len()); @@ -1753,6 +1758,27 @@ fn copy_helper( Ok(()) } +fn should_preserve_attribute(options: &Options) -> bool { + let checks = [ + &options.attributes.mode, + &options.attributes.timestamps, + &options.attributes.links, + &options.attributes.context, + &options.attributes.xattr, + ]; + + #[cfg(unix)] + let checks = [ + checks.as_slice(), + [&options.attributes.ownership].as_slice(), + ] + .concat(); + + checks + .iter() + .any(|attr| matches!(attr, Preserve::Yes { .. })) +} + // "Copies" a FIFO by creating a new one. This workaround is because Rust's // built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390). #[cfg(unix)] diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 0a6e58049..27c996f1b 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1075,6 +1075,46 @@ fn test_cp_parents_dest_not_directory() { .stderr_contains("with --parents, the destination must be a directory"); } +#[test] +fn test_cp_parents_with_permissions_copy_file() { + let (at, mut ucmd) = at_and_ucmd!(); + + let dir = "dir"; + let file = "p1/p2/file"; + + at.mkdir(dir); + at.mkdir_all("p1/p2"); + at.touch(file); + + let p1_mode = 0o0777; + let p2_mode = 0o0711; + let file_mode = 0o0702; + + #[cfg(unix)] + { + at.set_mode("p1", p1_mode); + at.set_mode("p1/p2", p2_mode); + at.set_mode(file, file_mode); + } + + ucmd.arg("-p") + .arg("--parents") + .arg(file) + .arg(dir) + .succeeds(); + + #[cfg(all(unix, not(target_os = "freebsd")))] + { + let p1_metadata = at.metadata("p1"); + let p2_metadata = at.metadata("p1/p2"); + let file_metadata = at.metadata(file); + + assert_metadata_eq!(p1_metadata, at.metadata("dir/p1")); + assert_metadata_eq!(p2_metadata, at.metadata("dir/p1/p2")); + assert_metadata_eq!(file_metadata, at.metadata("dir/p1/p2/file")); + } +} + #[test] #[cfg(unix)] fn test_cp_writable_special_file_permissions() {