Skip to content
Merged
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
23 changes: 9 additions & 14 deletions src/uu/mkdir/src/mkdir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,10 @@ impl Drop for UmaskGuard {

/// Create a directory with the exact mode specified, bypassing umask.
///
/// GNU mkdir temporarily sets umask to shaped mask before calling mkdir(2), ensuring the
/// directory is created atomically with the correct permissions. This avoids a
/// race condition where the directory briefly exists with umask-based permissions.
/// GNU mkdir temporarily sets umask to a shaped umask before calling mkdir(2),
/// ensuring the directory is created atomically with the correct permissions.
/// This avoids a race condition where the directory briefly exists with
/// umask-based permissions.
#[cfg(unix)]
fn create_dir_with_mode(
path: &Path,
Expand All @@ -282,30 +283,24 @@ fn create_dir_with_mode(path: &Path, _mode: u32, _shaped_umask: u32) -> std::io:

// Helper function to create a single directory with appropriate permissions
// `is_parent` argument is not used on windows
#[allow(unused_variables)]
#[cfg_attr(not(unix), allow(unused_variables))]
fn create_single_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<()> {
#[cfg(unix)]
let (mkdir_mode, shaped_umask) = {
let umask = mode::get_umask();
let umask_bits = rustix::fs::Mode::from_bits_truncate(umask as rustix::fs::RawMode);
let mode_bits = |x: u32| rustix::fs::Mode::from_bits_truncate(x as rustix::fs::RawMode);
let umask_bits = mode_bits(mode::get_umask());
if is_parent {
// Parent directories are never affected by -m (matches GNU behavior).
// We pass 0o777 as the mode and shape the umask so it cannot block
// owner write or execute (u+wx), ensuring the owner can traverse and
// write into the parent to create children. All other umask bits are
// preserved so the kernel applies them — and any default ACL on the
// grandparent — through the normal mkdir(2) path.
(
DEFAULT_PERM,
umask_bits & !rustix::fs::Mode::from_bits_truncate(0o300 as rustix::fs::RawMode),
)
(DEFAULT_PERM, umask_bits & !mode_bits(0o300))
} else {
match config.mode {
// Explicit -m: shape umask so it cannot block explicitly requested bits.
Some(m) => (
m,
umask_bits & !rustix::fs::Mode::from_bits_truncate(m as rustix::fs::RawMode),
),
Some(m) => (m, umask_bits & !mode_bits(m)),
// No -m: leave umask fully intact; kernel applies umask + ACL naturally.
None => (DEFAULT_PERM, umask_bits),
}
Expand Down
32 changes: 32 additions & 0 deletions tests/by-util/test_mkdir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,38 @@ fn test_mkdir_acl_inheritance_with_restrictive_mask() {
);
}

#[test]
#[cfg(unix)]
fn test_mkdir_p_respects_umask_without_acl() {
use std::os::unix::fs::PermissionsExt;
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-p").arg("a/b/c").umask(0o022).succeeds();
let perms = at.metadata("a/b/c").permissions().mode();
assert_eq!(perms & 0o777, 0o755);
}

#[test]
#[cfg(unix)]
fn test_mkdir_explicit_mode_zero() {
use std::os::unix::fs::PermissionsExt;
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-m").arg("0").arg("d").umask(0o022).succeeds();
let perms = at.metadata("d").permissions().mode();
assert_eq!(perms & 0o777, 0o000);
}

#[test]
#[cfg(unix)]
fn test_mkdir_explicit_mode_with_umask() {
// -m must win over umask: requesting 0o777 with a restrictive umask must
// still yield 0o777, since the umask is shaped to not block requested bits.
use std::os::unix::fs::PermissionsExt;
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-m").arg("777").arg("d").umask(0o077).succeeds();
let perms = at.metadata("d").permissions().mode();
assert_eq!(perms & 0o777, 0o777);
}

#[test]
fn test_mkdir_trailing_dot() {
new_ucmd!().arg("-p").arg("-v").arg("test_dir").succeeds();
Expand Down
Loading