Skip to content

Commit e96a0d6

Browse files
committed
cp: Preserve mode for directories when copying by default.
1 parent 0f8eb45 commit e96a0d6

File tree

3 files changed

+45
-1
lines changed

3 files changed

+45
-1
lines changed

src/uu/cp/src/cp.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1664,6 +1664,13 @@ pub(crate) fn copy_attributes(
16641664
let source_metadata =
16651665
fs::symlink_metadata(source).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
16661666

1667+
// if --no-preserve wasn't explicitely passed and we're copying a directory by default we should preserve mode
1668+
let mode = if dest.is_dir() && attributes.mode == (Preserve::No { explicit: false }) {
1669+
Preserve::Yes { required : false }
1670+
} else {
1671+
attributes.mode
1672+
};
1673+
16671674
// Ownership must be changed first to avoid interfering with mode change.
16681675
#[cfg(unix)]
16691676
handle_preserve(&attributes.ownership, || -> CopyResult<()> {
@@ -1701,7 +1708,7 @@ pub(crate) fn copy_attributes(
17011708
Ok(())
17021709
})?;
17031710

1704-
handle_preserve(&attributes.mode, || -> CopyResult<()> {
1711+
handle_preserve(&mode, || -> CopyResult<()> {
17051712
// The `chmod()` system call that underlies the
17061713
// `fs::set_permissions()` call is unable to change the
17071714
// permissions of a symbolic link. In that case, we just

tests/by-util/test_cp.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7400,3 +7400,34 @@ fn test_cp_recurse_verbose_output_with_symlink_already_exists() {
74007400
.no_stderr()
74017401
.stdout_is(output);
74027402
}
7403+
7404+
#[test]
7405+
#[cfg(not(target_os = "windows"))]
7406+
fn test_cp_preserve_directory_permissions_by_default() {
7407+
let scene = TestScenario::new(util_name!());
7408+
let at = &scene.fixtures;
7409+
7410+
let dir = "a/b/c/d";
7411+
let file = "foo.txt";
7412+
7413+
at.mkdir_all(dir);
7414+
7415+
let file_path = format!("{dir}/{file}");
7416+
7417+
at.touch(file_path);
7418+
7419+
scene.cmd("chmod").arg("-R").arg("555").arg("a").succeeds();
7420+
scene.cmd("cp").arg("-r").arg("a").arg("b").succeeds();
7421+
7422+
scene.ucmd().arg("-r").arg("a").arg("c").succeeds();
7423+
7424+
assert_eq!(at.get_mode("b"), 0o40555);
7425+
assert_eq!(at.get_mode("b/b"), 0o40555);
7426+
assert_eq!(at.get_mode("b/b/c"), 0o40555);
7427+
assert_eq!(at.get_mode("b/b/c/d"), 0o40555);
7428+
7429+
assert_eq!(at.get_mode("c"), 0o40555);
7430+
assert_eq!(at.get_mode("c/b"), 0o40555);
7431+
assert_eq!(at.get_mode("c/b/c"), 0o40555);
7432+
assert_eq!(at.get_mode("c/b/c/d"), 0o40555);
7433+
}

tests/uutests/src/lib/util.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,12 @@ impl AtPath {
13241324
perms.set_mode(mode);
13251325
std::fs::set_permissions(&path, perms).unwrap();
13261326
}
1327+
1328+
pub fn get_mode(&self, filename: &str) -> u32 {
1329+
let path = self.plus(filename);
1330+
let perms = std::fs::metadata(&path).unwrap().permissions();
1331+
perms.mode()
1332+
}
13271333
}
13281334

13291335
/// An environment for running a single uutils test case, serves three functions:

0 commit comments

Comments
 (0)