-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
mkdir: Fix stack overflow with deeply nested directories #8947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Replaced recursive parent directory creation with iterative approach to prevent stack overflow when creating directories with 200+ nesting levels. The previous recursive implementation consumed one stack frame per directory level, causing crashes on systems with limited stack size. The new implementation collects parent directories into a vector and creates them iteratively, using constant stack space regardless of depth. All existing functionality preserved: verbose output, permissions, SELinux context, and ACLs. Includes regression test for 350-level directory nesting.
GNU testsuite comparison:
|
…h .. components When creating directories with -p -v and paths containing .. components (like test_dir/../test_dir_a), the verbose output wasn't printing messages for logical parent directories that already existed. This caused test_recursive_reporting to fail on Windows. The fix ensures verbose messages are printed for existing parent directories in recursive mode, but only for logical directory names (not parent references ending in ..). This matches GNU mkdir behavior where all logical path components are reported even if they resolve to existing directories. Fixes Windows CI test failures for test_mkdir::test_recursive_reporting.
CodSpeed Performance ReportMerging #8947 will not alter performanceComparing Summary
Footnotes
|
GNU testsuite comparison:
|
- Format long method chains across multiple lines - Remove trailing whitespace - Use next_back() instead of last() to avoid needless iterator traversal
GNU testsuite comparison:
|
GNU testsuite comparison:
|
dev | ||
devs | ||
discoverability | ||
dotdot |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please ignore it on top of the file
don't need to be global
} | ||
|
||
#[test] | ||
fn test_mkdir_deep_nesting() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this test supposed to fail without the changes made to mkdir.rs
?
let mut path = String::new(); | ||
for i in 0..depth { | ||
if i > 0 { | ||
path.push('/'); | ||
} | ||
path.push_str(dir_name); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can omit the condition if you use a PathBuf
instead of a String
:
let mut path = String::new(); | |
for i in 0..depth { | |
if i > 0 { | |
path.push('/'); | |
} | |
path.push_str(dir_name); | |
} | |
let mut path = PathBuf::new(); | |
for _ in 0..depth { | |
path.push(dir_name); | |
} |
Or you could use a functional approach:
let path = Path::new(dir_name).join(std::iter::repeat(dir_name).take(depth).collect::<PathBuf>());
let has_dotdot = parent | ||
.as_os_str() | ||
.as_encoded_bytes() | ||
.windows(2) | ||
.any(|w| w == b".."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be clearer to use components()
:
let has_dotdot = parent | |
.as_os_str() | |
.as_encoded_bytes() | |
.windows(2) | |
.any(|w| w == b".."); | |
let has_dotdot = parent.components().any(|c| c.as_os_str() == ".."); |
// `is_parent` argument is not used on windows | ||
#[allow(unused_variables)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment should be moved to create_single_dir
as this function no longer contains any Windows-specific code.
// `is_parent` argument is not used on windows | |
#[allow(unused_variables)] |
let ends_with_dotdot = path | ||
.components() | ||
.next_back() | ||
.map(|c| matches!(c, std::path::Component::ParentDir)) | ||
.unwrap_or(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can re-arrange things a bit and get rid of the unwrap_or
:
let ends_with_dotdot = path | |
.components() | |
.next_back() | |
.map(|c| matches!(c, std::path::Component::ParentDir)) | |
.unwrap_or(false); | |
let ends_with_dotdot = matches!( | |
path.components().next_back(), | |
Some(std::path::Component::ParentDir) | |
); |
Fixes #8945
mkdir -p
with 200+ nested directories? Segfault. The recursive implementation ate stack frames like candy.Fix
Swapped recursion for iteration. Collect parent directories into a vector (heap), create them root-to-leaf. Constant stack space, infinite depth.
Proof It Works
Our changes touch code originally by 8ccc45c (2022)