From b7d7aba2547724572fd6555a968b98f6f7e9049f Mon Sep 17 00:00:00 2001 From: Michael Yang Date: Wed, 11 Feb 2026 01:16:11 +1100 Subject: [PATCH] fix(cp): add temp write/exec perm to built dirs --- src/uu/cp/src/copydir.rs | 12 +++++++----- tests/by-util/test_cp.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index 7f63b5e7e05..177d5ebcb34 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -618,12 +618,11 @@ fn build_dir( #[cfg(unix)] { use crate::Preserve; - use std::os::unix::fs::PermissionsExt; // we need to allow trivial casts here because some systems like linux have u32 constants in // in libc while others don't. #[allow(clippy::unnecessary_cast)] - let mut excluded_perms = if matches!(options.attributes.ownership, Preserve::Yes { .. }) { + let excluded_perms = if matches!(options.attributes.ownership, Preserve::Yes { .. }) { libc::S_IRWXG | libc::S_IRWXO // exclude rwx for group and other } else if matches!(options.attributes.mode, Preserve::Yes { .. }) { libc::S_IWGRP | libc::S_IWOTH //exclude w for group and other @@ -634,13 +633,16 @@ fn build_dir( let umask = if let (Some(from), Preserve::Yes { .. }) = (copy_attributes_from, options.attributes.mode) { - !fs::symlink_metadata(from)?.permissions().mode() + // temporary u+wx permission before true permission is set by + // `dirs_needing_permissions`. + !0o300 } else { uucore::mode::get_umask() }; - excluded_perms |= umask; - let mode = !excluded_perms & 0o777; //use only the last three octet bits + let mode = !(excluded_perms | umask); + // use only the last three octet bits + let mode = mode & 0o777; 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 b49f4606c76..507f4405153 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -6470,6 +6470,36 @@ fn test_cp_preserve_xattr_readonly_source() { ); } +#[test] +#[cfg(unix)] +fn test_cp_archive_dir_no_write_permission() { + // Test for issue #10787 + let (at, mut ucmd) = at_and_ucmd!(); + + // test-dir/inner/test: all without write permission (500) + at.mkdir("test-dir"); + at.mkdir("test-dir/inner"); + at.touch("test-dir/inner/test"); + at.set_mode("test-dir/inner/test", 0o500); + at.set_mode("test-dir/inner", 0o500); + at.set_mode("test-dir", 0o500); + + ucmd.arg("-a") + .arg("test-dir") + .arg("test-dir-copy") + .succeeds(); + + assert_eq!(at.metadata("test-dir").permissions().mode() & 0o777, 0o500); + assert_eq!( + at.metadata("test-dir/inner").permissions().mode() & 0o777, + 0o500 + ); + assert_eq!( + at.metadata("test-dir/inner/test").permissions().mode() & 0o777, + 0o500 + ); +} + #[test] #[cfg(unix)] fn test_cp_archive_preserves_directory_permissions() {