1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
use futures_util::future::LocalBoxFuture;
use std::io;
use std::path::Path;

/// Recursively create a directory and all of its parent components if they are missing.
///
/// # Examples
///
/// ```no_run
/// tokio_uring::start(async {
///     tokio_uring::fs::create_dir_all("/some/dir").await.unwrap();
/// });
/// ```
pub async fn create_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
    DirBuilder::new()
        .recursive(true)
        .create(path.as_ref())
        .await
}

/// A builder used to create directories in various manners, based on uring async operations.
///
/// This builder supports the Linux specific option `mode` and may support `at` in the future.
#[derive(Debug)]
pub struct DirBuilder {
    inner: fs_imp::DirBuilder,
    recursive: bool,
}

impl Default for DirBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl DirBuilder {
    /// Creates a new set of options with default mode/security settings for all
    /// platforms and also non-recursive.
    ///
    /// # Examples
    ///
    /// ```
    /// let builder = tokio_uring::fs::DirBuilder::new();
    /// ```
    #[must_use]
    pub fn new() -> DirBuilder {
        DirBuilder {
            inner: fs_imp::DirBuilder::new(),
            recursive: false,
        }
    }

    /// Indicates that directories should be created recursively, creating all
    /// parent directories. Parents that do not exist are created with the same
    /// security and permissions settings.
    ///
    /// This option defaults to `false`.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut builder = tokio_uring::fs::DirBuilder::new();
    /// builder.recursive(true);
    /// ```
    #[must_use]
    pub fn recursive(&mut self, recursive: bool) -> &mut Self {
        self.recursive = recursive;
        self
    }

    /// Sets the mode to create new directories with. This option defaults to 0o777.
    ///
    /// This option defaults to 0o777.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut builder = tokio_uring::fs::DirBuilder::new();
    /// builder.mode(0o700);
    /// ```
    #[must_use]
    pub fn mode(&mut self, mode: u32) -> &mut Self {
        self.inner.set_mode(mode);
        self
    }

    /// Creates the specified directory with the options configured in this
    /// builder.
    ///
    /// It is considered an error if the directory already exists unless
    /// recursive mode is enabled.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// tokio_uring::start(async {
    ///     let path = "/tmp/foo/bar/baz";
    ///     tokio_uring::fs::DirBuilder::new()
    ///         .recursive(true)
    ///         .mode(0o700) // user-only mode: drwx------
    ///         .create(path).await.unwrap();
    ///
    ///     // TODO change with tokio_uring version
    ///     assert!(std::fs::metadata(path).unwrap().is_dir());
    /// })
    /// ```
    pub async fn create<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
        self._create(path.as_ref()).await
    }

    async fn _create(&self, path: &Path) -> io::Result<()> {
        if self.recursive {
            self.recurse_create_dir_all(path).await
        } else {
            self.inner.mkdir(path).await
        }
    }

    // This recursive function is very closely modeled after the std library version.
    //
    // A recursive async function requires a Boxed Future. TODO There may be an implementation that
    // is less costly in terms of heap allocations. Maybe a non-recursive version is possible given
    // we even know the path separator for Linux. Or maybe expand the first level to avoid
    // recursion when only the first level of the directory needs to be built. For now, this serves
    // its purpose.

    fn recurse_create_dir_all<'a>(&'a self, path: &'a Path) -> LocalBoxFuture<io::Result<()>> {
        Box::pin(async move {
            if path == Path::new("") {
                return Ok(());
            }

            match self.inner.mkdir(path).await {
                Ok(()) => return Ok(()),
                Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
                Err(_) if is_dir(path).await => return Ok(()),
                Err(e) => return Err(e),
            }
            match path.parent() {
                Some(p) => self.recurse_create_dir_all(p).await?,
                None => {
                    return Err(std::io::Error::new(
                        std::io::ErrorKind::Other,
                        "failed to create whole tree",
                    ));
                    /* TODO build own allocation free error some day like the std library does.
                    return Err(io::const_io_error!(
                        io::ErrorKind::Uncategorized,
                        "failed to create whole tree",
                    ));
                    */
                }
            }
            match self.inner.mkdir(path).await {
                Ok(()) => Ok(()),
                Err(_) if is_dir(path).await => Ok(()),
                Err(e) => Err(e),
            }
        })
    }
}

// TODO this DirBuilder and this fs_imp module is modeled after the std library's. Here there is
// only Linux supported so is it worth to continue this separation?

mod fs_imp {
    use crate::runtime::driver::op::Op;
    use libc::mode_t;
    use std::path::Path;

    #[derive(Debug)]
    pub struct DirBuilder {
        mode: mode_t,
    }

    impl DirBuilder {
        pub fn new() -> DirBuilder {
            DirBuilder { mode: 0o777 }
        }

        pub async fn mkdir(&self, p: &Path) -> std::io::Result<()> {
            Op::make_dir(p, self.mode)?.await
        }

        pub fn set_mode(&mut self, mode: u32) {
            self.mode = mode as mode_t;
        }
    }
}

// Returns true if the path represents a directory.
//
// Uses one asynchronous uring call to determine this.
async fn is_dir<P: AsRef<Path>>(path: P) -> bool {
    let mut builder = crate::fs::StatxBuilder::new();
    if builder.mask(libc::STATX_TYPE).pathname(path).is_err() {
        return false;
    }

    let res = builder.statx().await;
    match res {
        Ok(statx) => (u32::from(statx.stx_mode) & libc::S_IFMT) == libc::S_IFDIR,
        Err(_) => false,
    }
}