diff --git a/Cargo.lock b/Cargo.lock index bf83d9accd..f8c68ecb49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,6 +507,7 @@ dependencies = [ "uucore", "uuhelp_parser", "walkdir", + "xattr", "zip", ] diff --git a/Cargo.toml b/Cargo.toml index a01fcdd2d3..ee1db36728 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -500,6 +500,7 @@ rlimit = "0.10.1" [target.'cfg(unix)'.dev-dependencies] nix = { workspace = true, features = ["process", "signal", "user"] } rand_pcg = "0.3" +xattr = { workspace = true } [build-dependencies] phf_codegen = { workspace = true } diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 8a4c5623ad..85fdf3f6b1 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -825,6 +825,7 @@ impl Attributes { ownership: Preserve::Yes { required: true }, mode: Preserve::Yes { required: true }, timestamps: Preserve::Yes { required: true }, + xattr: Preserve::Yes { required: true }, ..Self::NONE }; @@ -1440,7 +1441,7 @@ pub(crate) fn copy_attributes( })?; handle_preserve(&attributes.xattr, || -> CopyResult<()> { - #[cfg(unix)] + #[cfg(all(unix, not(target_os = "android")))] { let xattrs = xattr::list(source)?; for attr in xattrs { @@ -1449,7 +1450,7 @@ pub(crate) fn copy_attributes( } } } - #[cfg(not(unix))] + #[cfg(not(all(unix, not(target_os = "android"))))] { // The documentation for GNU cp states: // diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index cb6b7a8dce..a73b3e1130 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs ROOTDIR USERDIR procfs outfile uufs +// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs ROOTDIR USERDIR procfs outfile uufs xattrs use crate::common::util::TestScenario; #[cfg(not(windows))] @@ -56,6 +56,8 @@ static TEST_MOUNT_MOUNTPOINT: &str = "mount"; static TEST_MOUNT_OTHER_FILESYSTEM_FILE: &str = "mount/DO_NOT_copy_me.txt"; #[cfg(unix)] static TEST_NONEXISTENT_FILE: &str = "nonexistent_file.txt"; +#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] +use xattr; /// Assert that mode, ownership, and permissions of two metadata objects match. #[cfg(all(not(windows), not(target_os = "freebsd")))] @@ -3701,3 +3703,57 @@ fn test_cp_no_such() { .fails() .stderr_is("cp: 'no-such/' is not a directory\n"); } + +#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] +fn compare_xattrs>(path1: P, path2: P) -> bool { + let get_sorted_xattrs = |path: P| { + xattr::list(path) + .map(|attrs| { + let mut attrs = attrs.collect::>(); + attrs.sort(); + attrs + }) + .unwrap_or_else(|_| Vec::new()) + }; + + get_sorted_xattrs(path1) == get_sorted_xattrs(path2) +} + +#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] +#[test] +fn test_acl_preserve() { + use std::process::Command; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let path1 = "a"; + let path2 = "b"; + let file = "a/file"; + let file_target = "b/file"; + at.mkdir(path1); + at.mkdir(path2); + at.touch(file); + + let path = at.plus_as_string(file); + // calling the command directly. xattr requires some dev packages to be installed + // and it adds a complex dependency just for a test + match Command::new("setfacl") + .args(["-m", "group::rwx", &path1]) + .status() + .map(|status| status.code()) + { + Ok(Some(0)) => {} + Ok(_) => { + println!("test skipped: setfacl failed"); + return; + } + Err(e) => { + println!("test skipped: setfacl failed with {}", e); + return; + } + } + + scene.ucmd().args(&["-p", &path, path2]).succeeds(); + + assert!(compare_xattrs(&file, &file_target)); +}