Create directory if not exists, and return `Dir`
Is there a simple way to create a directory if it does not yet exist and to return the Dir?
Something along the lines of:
async fn create_dir(parent: &Dir, path: &str) -> anyhow::Result<Dir> {
match parent.create_dir(path) {
Ok(_) => {}
Err(e) => match e.kind() {
std::io::ErrorKind::AlreadyExists => {}
_ => {
Err(e)?;
}
},
}
Ok(parent.open_dir(path).await?) // could maybe be a TOCTOU?
}
I couldn't find a create_dir_with that would allow specifying e.g. OpenOptions, and all the create_dir functions don't return the Dir itself.
If not, would the project be open to contributions on this?
The reason for this is that the underying platform APIs don't have such a function. There is no way to atomically obtain a directory handle to a newly-created directory on Unix-family platforms, and possibly not in Windows either. If we added such a function in cap-std, it would have the same TOCTOU hazard.
There is no way to atomically obtain a directory handle to a newly-created directory on Unix-family platforms, and possibly not in Windows either
On Windows, NtCreateFile with FILE_DIRECTORY_FILE can create and open in one op. CreateFile can do this too with FILE_FLAG_POSIX_SEMANTICS and FILE_ATTRIBUTE_DIRECTORY.
Thanks. We still can't do it on Unix though. It's also worth mentioning that cap-std is following std here, where create_dir similarly does not return a handle, presumably for the same reason.
If we added such a function in cap-std, it would have the same TOCTOU hazard.
There can only be a TOCTOU if there's already a way for a potentially hostile other process to swap the directory after mkdir, which is only possible if it has write access to the parent, which is only possible if the two processes share a uid or we're talking about a directory like /tmp to simplify.
Let's discard the "same UID" case...anyone trying to isolate while having concurrent write access to the same directory starts to get arbitrarily hard.
For /tmp the sticky bit exists for this reason really...otherwise using it would be impossible.
I can't think of a real world situation where software would be at risk. Certainly it'd make sense to add to the documentation of such a function that it's not guaranteed that "the same" directory that was created was opened...but in practice the people who want to do this are just going to call the two functions anyways since that's all they can do, and I don't see why it makes sense to deny them the "sugar" for such a theoretical problem.
where create_dir similarly does not return a handle,
What "handle" would it return?
It's not necessarily one Unix user attacking another. It can also be a user typing make and then doing other stuff while they wait. Or a cron job that scanning directories on a system that happens to run at the same time as the admin doing sudo apt purge chromium-browser. These things don't happen frequently, but we can't guarantee that they don't happen.
where create_dir similarly does not return a handle,
What "handle" would it return?
Unix today doesn't give you a handle (or "file descriptor", which I'm using interchangeably in this thread) from mkdir. I speculate that if it did, Rust would have had a Dir type in its standard library by now, and create_dir would return it.
Sorry for resurrecting an old thread, but on Unix, cap-std is using openat2() where it is available. Wouldn't opening a directory with RESOLVE_NO_SYMLINKS | RESOLVE_BENEATH and O_CREAT | O_DIRECTORY | O_NOFOLLOW negate the TOCTOU if you wanted to create the directory and get the file descriptor?
I'm trying to wrap my head around how that is less safe against TOCTOU than: create_dir -> open_dir
or worse
check_dir_exists -> create_dir -> open_dir
Wouldn't opening a directory with
RESOLVE_NO_SYMLINKS | RESOLVE_BENEATHandO_CREAT | O_DIRECTORY | O_NOFOLLOWnegate the TOCTOU if you wanted to create the directory and get the file descriptor?
That would be ideal, but unfortunately open syscalls can't create directories and return a handle to them.
See man 2 open:
When both O_CREAT and O_DIRECTORY are specified in flags and thefile specified by pathname does not exist, open() will create a regular file (i.e., O_DIRECTORY is ignored).
openat2 just fails - see:
https://github.com/torvalds/linux/blob/48a5eed9ad584315c30ed35204510536235ce402/fs/open.c#L1275
It would be nice if Linux could support this atomic create directory and return a handle to it but I don't know if that's possible without cooperation from the filesystem driver. But that's out of scope for this project in any case.