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}