Skip to content

Commit b8bd531

Browse files
authored
Rollup merge of rust-lang#148196 - asder8215:std-fs-iterative-create-dir-all, r=Mark-Simulacrum
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 54d729a + 4081c14 commit b8bd531

File tree

1 file changed

+33
-17
lines changed

1 file changed

+33
-17
lines changed

library/std/src/fs.rs

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3333,26 +3333,42 @@ impl DirBuilder {
33333333
return Ok(());
33343334
}
33353335

3336-
match self.inner.mkdir(path) {
3337-
Ok(()) => return Ok(()),
3338-
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
3339-
Err(_) if path.is_dir() => return Ok(()),
3340-
Err(e) => return Err(e),
3341-
}
3342-
match path.parent() {
3343-
Some(p) => self.create_dir_all(p)?,
3344-
None => {
3345-
return Err(io::const_error!(
3346-
io::ErrorKind::Uncategorized,
3347-
"failed to create whole tree",
3348-
));
3336+
let ancestors = path.ancestors();
3337+
let mut uncreated_dirs = 0;
3338+
3339+
for ancestor in ancestors {
3340+
// for relative paths like "foo/bar", the parent of
3341+
// "foo" will be "" which there's no need to invoke
3342+
// a mkdir syscall on
3343+
if ancestor == Path::new("") {
3344+
break;
3345+
}
3346+
3347+
match self.inner.mkdir(ancestor) {
3348+
Ok(()) => break,
3349+
Err(e) if e.kind() == io::ErrorKind::NotFound => uncreated_dirs += 1,
3350+
// we check if the err is AlreadyExists for two reasons
3351+
// - in case the path exists as a *file*
3352+
// - and to avoid calls to .is_dir() in case of other errs
3353+
// (i.e. PermissionDenied)
3354+
Err(e) if e.kind() == io::ErrorKind::AlreadyExists && ancestor.is_dir() => break,
3355+
Err(e) => return Err(e),
33493356
}
33503357
}
3351-
match self.inner.mkdir(path) {
3352-
Ok(()) => Ok(()),
3353-
Err(_) if path.is_dir() => Ok(()),
3354-
Err(e) => Err(e),
3358+
3359+
// collect only the uncreated directories w/o letting the vec resize
3360+
let mut uncreated_dirs_vec = Vec::with_capacity(uncreated_dirs);
3361+
uncreated_dirs_vec.extend(ancestors.take(uncreated_dirs));
3362+
3363+
for uncreated_dir in uncreated_dirs_vec.iter().rev() {
3364+
if let Err(e) = self.inner.mkdir(uncreated_dir) {
3365+
if e.kind() != io::ErrorKind::AlreadyExists || !uncreated_dir.is_dir() {
3366+
return Err(e);
3367+
}
3368+
}
33553369
}
3370+
3371+
Ok(())
33563372
}
33573373
}
33583374

0 commit comments

Comments
 (0)