diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index fe8b4ad449c..0c0b10af60e 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -667,7 +667,9 @@ fn build_dir( }; excluded_perms |= umask; - let mode = !excluded_perms & 0o777; //use only the last three octet bits + // Always keep the owner write bit so we can copy files into the directory. + // The correct final permissions are applied afterward by dirs_needing_permissions. + let mode = (!excluded_perms & 0o777) | 0o200; // mask to permission bits, always keep owner write std::os::unix::fs::DirBuilderExt::mode(&mut builder, mode); } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 76c6fcfc37f..28af3075eef 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3795,6 +3795,27 @@ fn test_copy_dir_preserve_subdir_permissions() { assert_metadata_eq!(at.metadata("a1/a2"), at.metadata("b1/a2")); } +/// cp should successfully copy a read-only source directory containing files. +/// Regression test: build_dir previously created the destination with the source's +/// read-only mode, causing EPERM when copying files into it. +#[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "openbsd")))] +#[test] +fn test_copy_dir_preserve_readonly_source_with_files() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("src"); + at.write("src/file.txt", "hello"); + at.set_mode("src", 0o0555); + + ucmd.args(&["-p", "-r", "src", "dest"]) + .succeeds() + .no_stderr() + .no_stdout(); + + assert!(at.dir_exists("dest")); + assert_eq!(at.read("dest/file.txt"), "hello"); + assert_metadata_eq!(at.metadata("src"), at.metadata("dest")); +} + /// Test for preserving permissions when copying a directory, even in /// the face of an inaccessible file in that directory. #[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "openbsd")))]