Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/uu/cp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ fluent = { workspace = true }
[target.'cfg(unix)'.dependencies]
exacl = { workspace = true, optional = true }
nix = { workspace = true, features = ["fs"] }
xattr = { workspace = true }

[[bin]]
name = "cp"
Expand Down
16 changes: 15 additions & 1 deletion src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,7 @@ impl Attributes {
ownership: Preserve::Yes { required: true },
mode: Preserve::Yes { required: true },
timestamps: Preserve::Yes { required: true },
xattr: Preserve::Yes { required: true },
xattr: Preserve::No { explicit: false },
..Self::NONE
};

Expand Down Expand Up @@ -1831,6 +1831,20 @@ pub(crate) fn copy_attributes(
exacl::getfacl(source, None)
.and_then(|acl| exacl::setfacl(&[dest], &acl, None))
.map_err(|err| CpError::Error(err.to_string()))?;
// POSIX ACLs live in system.posix_acl_{access,default} xattrs and are
// part of "mode" in GNU cp semantics, so copy them even when the
// general xattr preservation is off and feat_acl is not compiled in.
#[cfg(all(unix, not(feature = "feat_acl")))]
for name in ["system.posix_acl_access", "system.posix_acl_default"] {
let value = xattr::get(source, name)
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
if let Some(value) = value
&& let Err(e) = xattr::set(dest, name, &value)
&& e.raw_os_error() != Some(libc::ENOTSUP)
{
return Err(CpError::IoErrContext(e, context.to_owned()));
}
}
}

Ok(())
Expand Down
106 changes: 106 additions & 0 deletions tests/by-util/test_cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1802,6 +1802,112 @@ fn test_cp_preserve_xattr() {
}
}

// -p preserves mode, ownership, and timestamps, but not xattrs
// xattrs require explicit --preserve=xattr or -a
#[test]
#[cfg(all(
unix,
not(any(target_os = "android", target_os = "macos", target_os = "openbsd"))
))]
fn test_cp_preserve_default_no_xattr() {
use std::process::Command;
use uutests::util::compare_xattrs;

let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

let source_file = "source_with_xattr";
let dest_file_p = "dest_with_p_flag";
let dest_file_explicit = "dest_with_explicit_xattr";

at.touch(source_file);

// set xattr on source
let xattr_key = "user.test_preserve";
let xattr_value = "test_value";
match Command::new("setfattr")
.args([
"-n",
xattr_key,
"-v",
xattr_value,
&at.plus_as_string(source_file),
])
.status()
.map(|status| status.code())
{
Ok(Some(0)) => {}
Ok(_) => {
println!("test skipped: setfattr failed");
return;
}
Err(e) => {
println!("test skipped: setfattr failed with {e}");
return;
}
}

// verify xattr was set
let getfattr_output = Command::new("getfattr")
.args([&at.plus_as_string(source_file)])
.output()
.expect("failed to run getfattr");

assert!(
getfattr_output.status.success(),
"getfattr failed: {}",
String::from_utf8_lossy(&getfattr_output.stderr)
);

let stdout = String::from_utf8_lossy(&getfattr_output.stdout);
assert!(
stdout.contains(xattr_key),
"xattr not found on source: {xattr_key}"
);

// -p should not preserve xattrs
scene
.ucmd()
.args(&[
"-p",
&at.plus_as_string(source_file),
&at.plus_as_string(dest_file_p),
])
.succeeds()
.no_output();

// xattrs should not be copied
assert!(
!compare_xattrs(&at.plus(source_file), &at.plus(dest_file_p)),
"cp -p incorrectly preserved xattrs"
);

// mode, ownership, and timestamps should be preserved
#[cfg(not(any(target_os = "freebsd", target_os = "macos")))]
{
let metadata_src = at.metadata(source_file);
let metadata_dst = at.metadata(dest_file_p);
assert_metadata_eq!(metadata_src, metadata_dst);
}

// --preserve=xattr should explicitly preserve xattrs
scene
.ucmd()
.args(&[
"--preserve=xattr",
&at.plus_as_string(source_file),
&at.plus_as_string(dest_file_explicit),
])
.succeeds()
.no_output();

// xattrs should be copied
assert!(
compare_xattrs(&at.plus(source_file), &at.plus(dest_file_explicit)),
"cp --preserve=xattr did not preserve xattrs"
);
}

#[test]
#[cfg(all(target_os = "linux", not(feature = "feat_selinux")))]
fn test_cp_preserve_all_context_fails_on_non_selinux() {
Expand Down
Loading