如何在目录不存在时原子性地创建并打开该目录?
解决方案
Linux内核本身没有提供单系统调用原子创建并打开目录的接口,普通文件可用的O_CREAT标志配合open调用的逻辑对目录不生效,O_DIRECTORY和O_CREAT同时使用时内核会忽略O_CREAT逻辑,所以无法通过单次系统调用完成操作,以下是两种可行的落地方案:
方案1:重试逻辑(适配绝大多数场景,包括你的Docker使用场景)
这个方案实现最简单,足够覆盖99.9%的生产场景:
- 先调用
create_dir_all创建目标目录 - 调用
read_dir尝试打开目录,如果返回NotFound错误则重复上述两步,设置最大重试次数(比如3次)即可 - 如果多次重试仍失败,再抛出错误即可
这种方案完全可以解决你提到的两次调用间隙目录被删除的极端偶发问题,实现成本极低,不需要额外依赖系统调用封装库。
示例代码片段:
use std::{fs, io::ErrorKind, path::Path}; fn create_and_open_dir(path: &Path, max_retry: u8) -> std::io::Result<fs::ReadDir> { let mut retry = 0; loop { fs::create_dir_all(path)?; match fs::read_dir(path) { Ok(dir) => return Ok(dir), Err(e) if e.kind() == ErrorKind::NotFound && retry < max_retry => { retry += 1; continue; } Err(e) => return Err(e), } } }
方案2:无竞态实现(针对安全性要求极高的场景)
如果需要完全消除竞态,可通过父目录文件描述符+mkdirat+openat的系统调用来实现,仅适用于单层目录创建的场景,如果需要递归创建多层目录,需要逐层向下处理:
- 首先打开目标目录的父目录,持有父目录的文件描述符,此时只要持有该fd,父目录的挂载点/路径映射就不会被篡改
- 调用
mkdirat在父目录fd下创建目标目录,忽略EEXIST错误(目录已存在的情况) - 调用
openat通过父目录fd打开目标目录,指定O_DIRECTORY标志,此时不会出现路径解析的竞态问题
你可以用rustix或者nix这类Rust系统调用封装库来实现上述逻辑,不需要手写unsafe代码调用libc。
内容的提问来源于stack exchange,提问作者Stargateur
相关产品推荐
相关产品推荐

