tokio_uring/fs/
create_dir_all.rs

1use futures_util::future::LocalBoxFuture;
2use std::io;
3use std::path::Path;
4
5/// Recursively create a directory and all of its parent components if they are missing.
6///
7/// # Examples
8///
9/// ```no_run
10/// tokio_uring::start(async {
11///     tokio_uring::fs::create_dir_all("/some/dir").await.unwrap();
12/// });
13/// ```
14pub async fn create_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
15    DirBuilder::new()
16        .recursive(true)
17        .create(path.as_ref())
18        .await
19}
20
21/// A builder used to create directories in various manners, based on uring async operations.
22///
23/// This builder supports the Linux specific option `mode` and may support `at` in the future.
24#[derive(Debug)]
25pub struct DirBuilder {
26    inner: fs_imp::DirBuilder,
27    recursive: bool,
28}
29
30impl Default for DirBuilder {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl DirBuilder {
37    /// Creates a new set of options with default mode/security settings for all
38    /// platforms and also non-recursive.
39    ///
40    /// # Examples
41    ///
42    /// ```
43    /// let builder = tokio_uring::fs::DirBuilder::new();
44    /// ```
45    #[must_use]
46    pub fn new() -> DirBuilder {
47        DirBuilder {
48            inner: fs_imp::DirBuilder::new(),
49            recursive: false,
50        }
51    }
52
53    /// Indicates that directories should be created recursively, creating all
54    /// parent directories. Parents that do not exist are created with the same
55    /// security and permissions settings.
56    ///
57    /// This option defaults to `false`.
58    ///
59    /// # Examples
60    ///
61    /// ```
62    /// let mut builder = tokio_uring::fs::DirBuilder::new();
63    /// builder.recursive(true);
64    /// ```
65    #[must_use]
66    pub fn recursive(&mut self, recursive: bool) -> &mut Self {
67        self.recursive = recursive;
68        self
69    }
70
71    /// Sets the mode to create new directories with. This option defaults to 0o777.
72    ///
73    /// This option defaults to 0o777.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// let mut builder = tokio_uring::fs::DirBuilder::new();
79    /// builder.mode(0o700);
80    /// ```
81    #[must_use]
82    pub fn mode(&mut self, mode: u32) -> &mut Self {
83        self.inner.set_mode(mode);
84        self
85    }
86
87    /// Creates the specified directory with the options configured in this
88    /// builder.
89    ///
90    /// It is considered an error if the directory already exists unless
91    /// recursive mode is enabled.
92    ///
93    /// # Examples
94    ///
95    /// ```no_run
96    /// tokio_uring::start(async {
97    ///     let path = "/tmp/foo/bar/baz";
98    ///     tokio_uring::fs::DirBuilder::new()
99    ///         .recursive(true)
100    ///         .mode(0o700) // user-only mode: drwx------
101    ///         .create(path).await.unwrap();
102    ///
103    ///     // TODO change with tokio_uring version
104    ///     assert!(std::fs::metadata(path).unwrap().is_dir());
105    /// })
106    /// ```
107    pub async fn create<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
108        self._create(path.as_ref()).await
109    }
110
111    async fn _create(&self, path: &Path) -> io::Result<()> {
112        if self.recursive {
113            self.recurse_create_dir_all(path).await
114        } else {
115            self.inner.mkdir(path).await
116        }
117    }
118
119    // This recursive function is very closely modeled after the std library version.
120    //
121    // A recursive async function requires a Boxed Future. TODO There may be an implementation that
122    // is less costly in terms of heap allocations. Maybe a non-recursive version is possible given
123    // we even know the path separator for Linux. Or maybe expand the first level to avoid
124    // recursion when only the first level of the directory needs to be built. For now, this serves
125    // its purpose.
126
127    fn recurse_create_dir_all<'a>(&'a self, path: &'a Path) -> LocalBoxFuture<io::Result<()>> {
128        Box::pin(async move {
129            if path == Path::new("") {
130                return Ok(());
131            }
132
133            match self.inner.mkdir(path).await {
134                Ok(()) => return Ok(()),
135                Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
136                Err(_) if is_dir(path).await => return Ok(()),
137                Err(e) => return Err(e),
138            }
139            match path.parent() {
140                Some(p) => self.recurse_create_dir_all(p).await?,
141                None => {
142                    return Err(std::io::Error::new(
143                        std::io::ErrorKind::Other,
144                        "failed to create whole tree",
145                    ));
146                    /* TODO build own allocation free error some day like the std library does.
147                    return Err(io::const_io_error!(
148                        io::ErrorKind::Uncategorized,
149                        "failed to create whole tree",
150                    ));
151                    */
152                }
153            }
154            match self.inner.mkdir(path).await {
155                Ok(()) => Ok(()),
156                Err(_) if is_dir(path).await => Ok(()),
157                Err(e) => Err(e),
158            }
159        })
160    }
161}
162
163// TODO this DirBuilder and this fs_imp module is modeled after the std library's. Here there is
164// only Linux supported so is it worth to continue this separation?
165
166mod fs_imp {
167    use crate::runtime::driver::op::Op;
168    use libc::mode_t;
169    use std::path::Path;
170
171    #[derive(Debug)]
172    pub struct DirBuilder {
173        mode: mode_t,
174    }
175
176    impl DirBuilder {
177        pub fn new() -> DirBuilder {
178            DirBuilder { mode: 0o777 }
179        }
180
181        pub async fn mkdir(&self, p: &Path) -> std::io::Result<()> {
182            Op::make_dir(p, self.mode)?.await
183        }
184
185        pub fn set_mode(&mut self, mode: u32) {
186            self.mode = mode as mode_t;
187        }
188    }
189}
190
191// Returns true if the path represents a directory.
192//
193// Uses one asynchronous uring call to determine this.
194async fn is_dir<P: AsRef<Path>>(path: P) -> bool {
195    let mut builder = crate::fs::StatxBuilder::new();
196    if builder.mask(libc::STATX_TYPE).pathname(path).is_err() {
197        return false;
198    }
199
200    let res = builder.statx().await;
201    match res {
202        Ok(statx) => (u32::from(statx.stx_mode) & libc::S_IFMT) == libc::S_IFDIR,
203        Err(_) => false,
204    }
205}