Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1877,6 +1877,11 @@ pub(crate) fn copy_attributes(
attributes.mode
};

// A created directory only defaults to copying the source mode; unlike an
// explicit preserve (-p/-a), GNU applies the umask to it.
let apply_umask_to_mode =
dest_is_freshly_created_dir && !matches!(attributes.mode, Preserve::Yes { .. });

// Track whether `chown` to the source's uid succeeded. If it did not
// (typical case: non-root user copying a root-owned setuid file), the
// mode preservation below must strip setuid/setgid so the destination
Expand Down Expand Up @@ -1941,6 +1946,9 @@ pub(crate) fn copy_attributes(
let mode = perms.mode() & !0o6000;
perms.set_mode(mode);
}
if apply_umask_to_mode {
perms.set_mode(perms.mode() & !uucore::mode::get_umask());
}
perms
};
#[cfg(not(unix))]
Expand Down
16 changes: 16 additions & 0 deletions tests/by-util/test_cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,22 @@ fn test_cp_umask_stripping_owner_write_bit_reflink_never() {
}
}

// Regression for #14549: `cp -r` (without preserve) must apply the umask to
// directories it creates, matching GNU, instead of copying the source's mode.
#[test]
#[cfg(unix)]
fn test_cp_recursive_dir_applies_umask() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("src");
at.mkdir("src/dir");
at.set_mode("src/dir", 0o777);

ucmd.umask(0o077).args(&["-r", "src", "d"]).succeeds();

// 0o777 & ~0o077 = 0o700, not the source's raw 0o777.
assert_eq!(at.metadata("d/dir").permissions().mode() & 0o777, 0o700);
}

// When --reflink=always fails, GNU cp removes a destination it created
// itself but keeps a pre-existing (truncated) one. Only observable on
// filesystems without clone support; when the clone succeeds there is
Expand Down
Loading