wasi_common/tokio/
dir.rs

1use crate::tokio::{block_on_dummy_executor, file::File};
2use crate::{
3    dir::{ReaddirCursor, ReaddirEntity, WasiDir},
4    file::{FdFlags, Filestat, OFlags},
5    Error, ErrorExt,
6};
7use std::any::Any;
8use std::path::PathBuf;
9
10pub struct Dir(crate::sync::dir::Dir);
11
12impl Dir {
13    pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self {
14        Dir(crate::sync::dir::Dir::from_cap_std(dir))
15    }
16}
17
18#[wiggle::async_trait]
19impl WasiDir for Dir {
20    fn as_any(&self) -> &dyn Any {
21        self
22    }
23    async fn open_file(
24        &self,
25        symlink_follow: bool,
26        path: &str,
27        oflags: OFlags,
28        read: bool,
29        write: bool,
30        fdflags: FdFlags,
31    ) -> Result<crate::dir::OpenResult, Error> {
32        let f = block_on_dummy_executor(move || async move {
33            self.0
34                .open_file_(symlink_follow, path, oflags, read, write, fdflags)
35        })?;
36        match f {
37            crate::sync::dir::OpenResult::File(f) => {
38                Ok(crate::dir::OpenResult::File(Box::new(File::from_inner(f))))
39            }
40            crate::sync::dir::OpenResult::Dir(d) => {
41                Ok(crate::dir::OpenResult::Dir(Box::new(Dir(d))))
42            }
43        }
44    }
45
46    async fn create_dir(&self, path: &str) -> Result<(), Error> {
47        block_on_dummy_executor(|| self.0.create_dir(path))
48    }
49    async fn readdir(
50        &self,
51        cursor: ReaddirCursor,
52    ) -> Result<Box<dyn Iterator<Item = Result<ReaddirEntity, Error>> + Send>, Error> {
53        struct I(Box<dyn Iterator<Item = Result<ReaddirEntity, Error>> + Send>);
54        impl Iterator for I {
55            type Item = Result<ReaddirEntity, Error>;
56            fn next(&mut self) -> Option<Self::Item> {
57                tokio::task::block_in_place(move || self.0.next())
58            }
59        }
60
61        let inner = block_on_dummy_executor(move || self.0.readdir(cursor))?;
62        Ok(Box::new(I(inner)))
63    }
64
65    async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> {
66        block_on_dummy_executor(move || self.0.symlink(src_path, dest_path))
67    }
68    async fn remove_dir(&self, path: &str) -> Result<(), Error> {
69        block_on_dummy_executor(move || self.0.remove_dir(path))
70    }
71
72    async fn unlink_file(&self, path: &str) -> Result<(), Error> {
73        block_on_dummy_executor(move || self.0.unlink_file(path))
74    }
75    async fn read_link(&self, path: &str) -> Result<PathBuf, Error> {
76        block_on_dummy_executor(move || self.0.read_link(path))
77    }
78    async fn get_filestat(&self) -> Result<Filestat, Error> {
79        block_on_dummy_executor(|| self.0.get_filestat())
80    }
81    async fn get_path_filestat(
82        &self,
83        path: &str,
84        follow_symlinks: bool,
85    ) -> Result<Filestat, Error> {
86        block_on_dummy_executor(move || self.0.get_path_filestat(path, follow_symlinks))
87    }
88    async fn rename(
89        &self,
90        src_path: &str,
91        dest_dir: &dyn WasiDir,
92        dest_path: &str,
93    ) -> Result<(), Error> {
94        let dest_dir = dest_dir
95            .as_any()
96            .downcast_ref::<Self>()
97            .ok_or(Error::badf().context("failed downcast to tokio Dir"))?;
98        block_on_dummy_executor(
99            move || async move { self.0.rename_(src_path, &dest_dir.0, dest_path) },
100        )
101    }
102    async fn hard_link(
103        &self,
104        src_path: &str,
105        target_dir: &dyn WasiDir,
106        target_path: &str,
107    ) -> Result<(), Error> {
108        let target_dir = target_dir
109            .as_any()
110            .downcast_ref::<Self>()
111            .ok_or(Error::badf().context("failed downcast to tokio Dir"))?;
112        block_on_dummy_executor(move || async move {
113            self.0.hard_link_(src_path, &target_dir.0, target_path)
114        })
115    }
116    async fn set_times(
117        &self,
118        path: &str,
119        atime: Option<crate::SystemTimeSpec>,
120        mtime: Option<crate::SystemTimeSpec>,
121        follow_symlinks: bool,
122    ) -> Result<(), Error> {
123        block_on_dummy_executor(move || self.0.set_times(path, atime, mtime, follow_symlinks))
124    }
125}
126
127#[cfg(test)]
128mod test {
129    use super::Dir;
130    use crate::file::{FdFlags, OFlags};
131    use cap_std::ambient_authority;
132
133    #[tokio::test(flavor = "multi_thread")]
134    async fn scratch_dir() {
135        let tempdir = tempfile::Builder::new()
136            .prefix("cap-std-sync")
137            .tempdir()
138            .expect("create temporary dir");
139        let preopen_dir = cap_std::fs::Dir::open_ambient_dir(tempdir.path(), ambient_authority())
140            .expect("open ambient temporary dir");
141        let preopen_dir = Dir::from_cap_std(preopen_dir);
142        crate::WasiDir::open_file(
143            &preopen_dir,
144            false,
145            ".",
146            OFlags::empty(),
147            false,
148            false,
149            FdFlags::empty(),
150        )
151        .await
152        .expect("open the same directory via WasiDir abstraction");
153    }
154
155    // Readdir does not work on windows, so we won't test it there.
156    #[cfg(not(windows))]
157    #[tokio::test(flavor = "multi_thread")]
158    async fn readdir() {
159        use crate::dir::{ReaddirCursor, ReaddirEntity, WasiDir};
160        use crate::file::{FdFlags, FileType, OFlags};
161        use std::collections::HashMap;
162
163        async fn readdir_into_map(dir: &dyn WasiDir) -> HashMap<String, ReaddirEntity> {
164            let mut out = HashMap::new();
165            for readdir_result in dir
166                .readdir(ReaddirCursor::from(0))
167                .await
168                .expect("readdir succeeds")
169            {
170                let entity = readdir_result.expect("readdir entry is valid");
171                out.insert(entity.name.clone(), entity);
172            }
173            out
174        }
175
176        let tempdir = tempfile::Builder::new()
177            .prefix("cap-std-sync")
178            .tempdir()
179            .expect("create temporary dir");
180        let preopen_dir = cap_std::fs::Dir::open_ambient_dir(tempdir.path(), ambient_authority())
181            .expect("open ambient temporary dir");
182        let preopen_dir = Dir::from_cap_std(preopen_dir);
183
184        let entities = readdir_into_map(&preopen_dir).await;
185        assert_eq!(
186            entities.len(),
187            2,
188            "should just be . and .. in empty dir: {entities:?}"
189        );
190        assert!(entities.get(".").is_some());
191        assert!(entities.get("..").is_some());
192
193        preopen_dir
194            .open_file(
195                false,
196                "file1",
197                OFlags::CREATE,
198                true,
199                false,
200                FdFlags::empty(),
201            )
202            .await
203            .expect("create file1");
204
205        let entities = readdir_into_map(&preopen_dir).await;
206        assert_eq!(entities.len(), 3, "should be ., .., file1 {entities:?}");
207        assert_eq!(
208            entities.get(".").expect(". entry").filetype,
209            FileType::Directory
210        );
211        assert_eq!(
212            entities.get("..").expect(".. entry").filetype,
213            FileType::Directory
214        );
215        assert_eq!(
216            entities.get("file1").expect("file1 entry").filetype,
217            FileType::RegularFile
218        );
219    }
220}