From 4e4ed7b39b68d6b5e931bc07a4490ace430decc0 Mon Sep 17 00:00:00 2001 From: Alex Chen Date: Mon, 17 Aug 2026 11:59:00 +0000 Subject: [PATCH] mkdir: do not ignore EEXIST on non-recursive creation Fixes #13970 Signed-off-by: Alex Chen --- src/uu/mkdir/src/mkdir.rs | 2 +- tests/by-util/test_mkdir.rs | 52 +++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 1d34d62bb7..72bed387c4 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -353,7 +353,7 @@ fn create_single_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<( Ok(()) } - Err(_) if path.is_dir() => { + Err(_) if (config.recursive || is_parent) && path.is_dir() => { // Directory already exists - check if this is a logical directory creation // (i.e., not just a parent reference like "test_dir/..") let ends_with_parent_dir = matches!( diff --git a/tests/by-util/test_mkdir.rs b/tests/by-util/test_mkdir.rs index e335a3f056..4650d7fc9c 100644 --- a/tests/by-util/test_mkdir.rs +++ b/tests/by-util/test_mkdir.rs @@ -1134,3 +1134,55 @@ mod diagnostics { assert!(!stderr.contains(":1:"), "{stderr}"); } } + +#[test] +fn test_mkdir_concurrent_non_recursive() { + // Test concurrent mkdir operations without -p: exactly one process must succeed per round + use std::sync::Arc; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::thread; + + for round in 0..10 { + let scene = TestScenario::new(util_name!()); + let target_dir = scene.fixtures.plus(format!("concurrent_target_{round}")); + let path_str = target_dir.to_string_lossy().to_string(); + let bin_path = scene.bin_path.clone(); + + let winners = Arc::new(AtomicUsize::new(0)); + let mut handles = vec![]; + + for _ in 0..16 { + let path_clone = path_str.clone(); + let bin_path_clone = bin_path.clone(); + let winners_clone = Arc::clone(&winners); + + let handle = thread::spawn(move || { + let result = std::process::Command::new(&bin_path_clone) + .arg("mkdir") + .arg(&path_clone) + .current_dir(std::env::current_dir().unwrap()) + .output() + .expect("failed to run binary"); + if result.status.success() { + winners_clone.fetch_add(1, Ordering::SeqCst); + } + }); + handles.push(handle); + } + + for h in handles { + h.join().unwrap(); + } + + assert_eq!( + winners.load(Ordering::SeqCst), + 1, + "round {round}: expected exactly 1 winner for concurrent non-recursive mkdir" + ); + assert!( + scene + .fixtures + .dir_exists(format!("concurrent_target_{round}")) + ); + } +}