Skip to content

Commit d90f3a4

Browse files
authored
Merge pull request #8416 from drinkcat/cp-attr
`cp`: Properly preserves fifos and symlink attributes
2 parents a051ce0 + f4bf898 commit d90f3a4

File tree

4 files changed

+93
-6
lines changed

4 files changed

+93
-6
lines changed

src/uu/cp/src/copydir.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ fn copy_direntry(
227227
// If the source is a symbolic link and the options tell us not to
228228
// dereference the link, then copy the link object itself.
229229
if source_absolute.is_symlink() && !options.dereference {
230-
return copy_link(&source_absolute, &local_to_target, symlinked_files);
230+
return copy_link(&source_absolute, &local_to_target, symlinked_files, options);
231231
}
232232

233233
// If the source is a directory and the destination does not

src/uu/cp/src/cp.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2446,7 +2446,7 @@ fn copy_file(
24462446
copy_attributes(&src, dest, &options.attributes)?;
24472447
}
24482448
}
2449-
} else if source_is_stream && source.exists() {
2449+
} else if source_is_stream && !source.exists() {
24502450
// Some stream files may not exist after we have copied it,
24512451
// like anonymous pipes. Thus, we can't really copy its
24522452
// attributes. However, this is already handled in the stream
@@ -2563,7 +2563,7 @@ fn copy_helper(
25632563
#[cfg(unix)]
25642564
copy_fifo(dest, options.overwrite, options.debug)?;
25652565
} else if source_is_symlink {
2566-
copy_link(source, dest, symlinked_files)?;
2566+
copy_link(source, dest, symlinked_files, options)?;
25672567
} else {
25682568
let copy_debug = copy_on_write(
25692569
source,
@@ -2600,6 +2600,7 @@ fn copy_link(
26002600
source: &Path,
26012601
dest: &Path,
26022602
symlinked_files: &mut HashSet<FileInformation>,
2603+
options: &Options,
26032604
) -> CopyResult<()> {
26042605
// Here, we will copy the symlink itself (actually, just recreate it)
26052606
let link = fs::read_link(source)?;
@@ -2608,7 +2609,8 @@ fn copy_link(
26082609
if dest.is_symlink() || dest.is_file() {
26092610
fs::remove_file(dest)?;
26102611
}
2611-
symlink_file(&link, dest, symlinked_files)
2612+
symlink_file(&link, dest, symlinked_files)?;
2613+
copy_attributes(source, dest, &options.attributes)
26122614
}
26132615

26142616
/// Generate an error message if `target` is not the correct `target_type`

src/uucore/src/lib/features/perms.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ pub struct Verbosity {
4141
pub level: VerbosityLevel,
4242
}
4343

44+
impl Default for Verbosity {
45+
fn default() -> Self {
46+
Self {
47+
groups_only: false,
48+
level: VerbosityLevel::Normal,
49+
}
50+
}
51+
}
52+
4453
/// Actually perform the change of owner on a path
4554
fn chown<P: AsRef<Path>>(path: P, uid: uid_t, gid: gid_t, follow: bool) -> IOResult<()> {
4655
let path = path.as_ref();

tests/by-util/test_cp.rs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// file that was distributed with this source code.
55

66
// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR outfile uufs xattrs
7-
// spell-checker:ignore bdfl hlsl IRWXO IRWXG nconfined matchpathcon libselinux-devel
7+
// spell-checker:ignore bdfl hlsl IRWXO IRWXG nconfined matchpathcon libselinux-devel prwx doesnotexist
88
use uucore::display::Quotable;
99
use uutests::util::TestScenario;
1010
use uutests::{at_and_ucmd, new_ucmd, path_concat, util_name};
@@ -3087,13 +3087,89 @@ fn test_cp_link_backup() {
30873087
fn test_cp_fifo() {
30883088
let (at, mut ucmd) = at_and_ucmd!();
30893089
at.mkfifo("fifo");
3090-
ucmd.arg("-r")
3090+
// Also test that permissions are preserved
3091+
at.set_mode("fifo", 0o731);
3092+
ucmd.arg("--preserve=mode")
3093+
.arg("-r")
30913094
.arg("fifo")
30923095
.arg("fifo2")
30933096
.succeeds()
30943097
.no_stderr()
30953098
.no_stdout();
30963099
assert!(at.is_fifo("fifo2"));
3100+
3101+
let metadata = std::fs::metadata(at.subdir.join("fifo2")).unwrap();
3102+
let permission = uucore::fs::display_permissions(&metadata, true);
3103+
assert_eq!(permission, "prwx-wx--x".to_string());
3104+
}
3105+
3106+
#[cfg(all(unix, not(target_vendor = "apple")))]
3107+
fn find_other_group(current: u32) -> Option<u32> {
3108+
// Get the first group that doesn't match current
3109+
nix::unistd::getgroups().ok()?.iter().find_map(|group| {
3110+
let gid = group.as_raw();
3111+
(gid != current).then_some(gid)
3112+
})
3113+
}
3114+
3115+
#[cfg(target_vendor = "apple")]
3116+
fn find_other_group(_current: u32) -> Option<u32> {
3117+
None
3118+
}
3119+
3120+
#[test]
3121+
#[cfg(unix)]
3122+
fn test_cp_r_symlink() {
3123+
let (at, mut ucmd) = at_and_ucmd!();
3124+
// Specifically test copying a link in a subdirectory, as the internal path
3125+
// is slightly different.
3126+
at.mkdir("tmp");
3127+
// Create a symlink to a non-existent file to make sure
3128+
// we don't try to resolve it.
3129+
at.symlink_file("doesnotexist", "tmp/symlink");
3130+
let symlink = at.subdir.join("tmp").join("symlink");
3131+
3132+
// If we can find such a group, change the owner to a non-default to test
3133+
// that (group) ownership is preserved.
3134+
let metadata = std::fs::symlink_metadata(&symlink).unwrap();
3135+
let other_gid = find_other_group(metadata.gid());
3136+
if let Some(gid) = other_gid {
3137+
uucore::perms::wrap_chown(
3138+
&symlink,
3139+
&metadata,
3140+
None,
3141+
Some(gid),
3142+
false,
3143+
uucore::perms::Verbosity::default(),
3144+
)
3145+
.expect("Cannot chgrp symlink.");
3146+
} else {
3147+
println!("Cannot find a second group to chgrp to.");
3148+
}
3149+
3150+
// Use -r to make sure we copy the symlink itself
3151+
// --preserve will include ownership
3152+
ucmd.arg("--preserve")
3153+
.arg("-r")
3154+
.arg("tmp")
3155+
.arg("tmp2")
3156+
.succeeds()
3157+
.no_stderr()
3158+
.no_stdout();
3159+
3160+
// Is symlink2 still a symlink, and does it point at the same place?
3161+
assert!(at.is_symlink("tmp2/symlink"));
3162+
let symlink2 = at.subdir.join("tmp2/symlink");
3163+
assert_eq!(
3164+
std::fs::read_link(&symlink).unwrap(),
3165+
std::fs::read_link(&symlink2).unwrap(),
3166+
);
3167+
3168+
// If we found a suitable group, is the group correct after the copy.
3169+
if let Some(gid) = other_gid {
3170+
let metadata2 = std::fs::symlink_metadata(&symlink2).unwrap();
3171+
assert_eq!(metadata2.gid(), gid);
3172+
}
30973173
}
30983174

30993175
#[test]

0 commit comments

Comments
 (0)