Skip to content

Commit 5da89dc

Browse files
Rollup merge of rust-lang#148196 - std-fs-iterative-create-dir-all, r=Mark-Simulacrum,jhpratt
Implement create_dir_all() to operate iteratively instead of recursively The current implementation of `create_dir_all(...)` in std::fs operates recursively. As mentioned in rust-lang#124309, this could run into a stack overflow with big paths. To avoid this stack overflow issue, this PR implements the method in an iterative manner, preserving the documented behavior of: ``` Recursively create a directory and all of its parent components if they are missing. This function is not atomic. If it returns an error, any parent components it was able to create will remain. If the empty path is passed to this function, it always succeeds without creating any directories. ```
2 parents 49e8a6c + 167ad11 commit 5da89dc

1 file changed

Lines changed: 36 additions & 18 deletions

File tree

library/std/src/fs.rs

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3446,30 +3446,48 @@ impl DirBuilder {
34463446
}
34473447

34483448
fn create_dir_all(&self, path: &Path) -> io::Result<()> {
3449-
if path == Path::new("") {
3449+
// if path's parent is None, it is "/" path, which should
3450+
// return Ok immediately
3451+
if path == Path::new("") || path.parent() == None {
34503452
return Ok(());
34513453
}
34523454

3453-
match self.inner.mkdir(path) {
3454-
Ok(()) => return Ok(()),
3455-
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
3456-
Err(_) if path.is_dir() => return Ok(()),
3457-
Err(e) => return Err(e),
3458-
}
3459-
match path.parent() {
3460-
Some(p) => self.create_dir_all(p)?,
3461-
None => {
3462-
return Err(io::const_error!(
3463-
io::ErrorKind::Uncategorized,
3464-
"failed to create whole tree",
3465-
));
3455+
let ancestors = path.ancestors();
3456+
let mut uncreated_dirs = 0;
3457+
3458+
for ancestor in ancestors {
3459+
// for relative paths like "foo/bar", the parent of
3460+
// "foo" will be "" which there's no need to invoke
3461+
// a mkdir syscall on
3462+
if ancestor == Path::new("") || ancestor.parent() == None {
3463+
break;
3464+
}
3465+
3466+
match self.inner.mkdir(ancestor) {
3467+
Ok(()) => break,
3468+
Err(e) if e.kind() == io::ErrorKind::NotFound => uncreated_dirs += 1,
3469+
// we check if the err is AlreadyExists for two reasons
3470+
// - in case the path exists as a *file*
3471+
// - and to avoid calls to .is_dir() in case of other errs
3472+
// (i.e. PermissionDenied)
3473+
Err(e) if e.kind() == io::ErrorKind::AlreadyExists && ancestor.is_dir() => break,
3474+
Err(e) => return Err(e),
34663475
}
34673476
}
3468-
match self.inner.mkdir(path) {
3469-
Ok(()) => Ok(()),
3470-
Err(_) if path.is_dir() => Ok(()),
3471-
Err(e) => Err(e),
3477+
3478+
// collect only the uncreated directories w/o letting the vec resize
3479+
let mut uncreated_dirs_vec = Vec::with_capacity(uncreated_dirs);
3480+
uncreated_dirs_vec.extend(ancestors.take(uncreated_dirs));
3481+
3482+
for uncreated_dir in uncreated_dirs_vec.iter().rev() {
3483+
if let Err(e) = self.inner.mkdir(uncreated_dir) {
3484+
if e.kind() != io::ErrorKind::AlreadyExists || !uncreated_dir.is_dir() {
3485+
return Err(e);
3486+
}
3487+
}
34723488
}
3489+
3490+
Ok(())
34733491
}
34743492
}
34753493

0 commit comments

Comments
 (0)