Skip to content

Commit 6143aef

Browse files
committed
cp: Preserve mode for directories when copying by default.
1 parent d946dce commit 6143aef

File tree

3 files changed

+88
-1
lines changed

3 files changed

+88
-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 explicitly 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: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7400,3 +7400,71 @@ 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+
}
7434+
7435+
#[test]
7436+
#[cfg(not(target_os = "windows"))]
7437+
fn test_cp_no_preserve_directory_permissions() {
7438+
let scene = TestScenario::new(util_name!());
7439+
let at = &scene.fixtures;
7440+
7441+
let dir = "a/b/c/d";
7442+
let file = "foo.txt";
7443+
7444+
at.mkdir_all(dir);
7445+
7446+
let file_path = format!("{dir}/{file}");
7447+
7448+
at.touch(file_path);
7449+
7450+
scene.cmd("chmod").arg("-R").arg("555").arg("a").succeeds();
7451+
scene.cmd("cp").arg("-r").arg("a").arg("b").succeeds();
7452+
7453+
scene
7454+
.ucmd()
7455+
.arg("-r")
7456+
.arg("--no-preserve=mode")
7457+
.arg("a")
7458+
.arg("c")
7459+
.succeeds();
7460+
7461+
assert_eq!(at.get_mode("b"), 0o40555);
7462+
assert_eq!(at.get_mode("b/b"), 0o40555);
7463+
assert_eq!(at.get_mode("b/b/c"), 0o40555);
7464+
assert_eq!(at.get_mode("b/b/c/d"), 0o40555);
7465+
7466+
assert_eq!(at.get_mode("c"), 0o40755);
7467+
assert_eq!(at.get_mode("c/b"), 0o40755);
7468+
assert_eq!(at.get_mode("c/b/c"), 0o40755);
7469+
assert_eq!(at.get_mode("c/b/c/d"), 0o40755);
7470+
}

tests/uutests/src/lib/util.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,18 @@ impl AtPath {
13241324
perms.set_mode(mode);
13251325
std::fs::set_permissions(&path, perms).unwrap();
13261326
}
1327+
1328+
/// Get the permissions of the specified file.
1329+
///
1330+
/// # Panics
1331+
///
1332+
/// This function panics if there is an error loading the metadata
1333+
#[cfg(not(windows))]
1334+
pub fn get_mode(&self, filename: &str) -> u32 {
1335+
let path = self.plus(filename);
1336+
let perms = std::fs::metadata(&path).unwrap().permissions();
1337+
perms.mode()
1338+
}
13271339
}
13281340

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

0 commit comments

Comments
 (0)