virtual_fs/
builder.rs

1use crate::random_file::RandomFile;
2use crate::{FileSystem, VirtualFile};
3use std::path::{Path, PathBuf};
4use tracing::*;
5
6use super::ZeroFile;
7use super::{DeviceFile, NullFile};
8use crate::tmp_fs::TmpFileSystem;
9
10pub struct RootFileSystemBuilder {
11    default_root_dirs: bool,
12    default_dev_files: bool,
13    add_wasmer_command: bool,
14    stdin: Option<Box<dyn VirtualFile + Send + Sync>>,
15    stdout: Option<Box<dyn VirtualFile + Send + Sync>>,
16    stderr: Option<Box<dyn VirtualFile + Send + Sync>>,
17    tty: Option<Box<dyn VirtualFile + Send + Sync>>,
18}
19
20impl Default for RootFileSystemBuilder {
21    fn default() -> Self {
22        Self {
23            default_root_dirs: true,
24            default_dev_files: true,
25            add_wasmer_command: true,
26            stdin: None,
27            stdout: None,
28            stderr: None,
29            tty: None,
30        }
31    }
32}
33
34impl RootFileSystemBuilder {
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    pub fn with_stdin(mut self, file: Box<dyn VirtualFile + Send + Sync>) -> Self {
40        self.stdin.replace(file);
41        self
42    }
43
44    pub fn with_stdout(mut self, file: Box<dyn VirtualFile + Send + Sync>) -> Self {
45        self.stdout.replace(file);
46        self
47    }
48
49    pub fn with_stderr(mut self, file: Box<dyn VirtualFile + Send + Sync>) -> Self {
50        self.stderr.replace(file);
51        self
52    }
53
54    pub fn with_tty(mut self, file: Box<dyn VirtualFile + Send + Sync>) -> Self {
55        self.tty.replace(file);
56        self
57    }
58
59    pub fn default_root_dirs(mut self, val: bool) -> Self {
60        self.default_root_dirs = val;
61        self
62    }
63
64    pub fn build(self) -> TmpFileSystem {
65        self.build_ext(&[])
66    }
67
68    pub fn build_ext(self, mapped_dirs: &[&str]) -> TmpFileSystem {
69        let tmp = TmpFileSystem::new();
70
71        if self.default_root_dirs {
72            let default_dirs = ["/.app", "/.private", "/bin", "/dev", "/etc", "/tmp"]
73                .into_iter()
74                .filter(|d| !mapped_dirs.contains(d))
75                .collect::<Vec<_>>();
76
77            for root_dir in &default_dirs {
78                if let Err(err) = tmp.create_dir(Path::new(root_dir)) {
79                    debug!("failed to create dir [{}] - {}", root_dir, err);
80                }
81            }
82        }
83        if self.add_wasmer_command {
84            let _ = tmp
85                .new_open_options_ext()
86                .insert_device_file(PathBuf::from("/bin/wasmer"), Box::<NullFile>::default());
87        }
88        if self.default_dev_files {
89            let _ = tmp
90                .new_open_options_ext()
91                .insert_device_file(PathBuf::from("/dev/null"), Box::<NullFile>::default());
92            let _ = tmp
93                .new_open_options_ext()
94                .insert_device_file(PathBuf::from("/dev/zero"), Box::<ZeroFile>::default());
95            let _ = tmp
96                .new_open_options_ext()
97                .insert_device_file(PathBuf::from("/dev/urandom"), Box::<RandomFile>::default());
98            let _ = tmp.new_open_options_ext().insert_device_file(
99                PathBuf::from("/dev/stdin"),
100                self.stdin
101                    .unwrap_or_else(|| Box::new(DeviceFile::new(DeviceFile::STDIN))),
102            );
103            let _ = tmp.new_open_options_ext().insert_device_file(
104                PathBuf::from("/dev/stdout"),
105                self.stdout
106                    .unwrap_or_else(|| Box::new(DeviceFile::new(DeviceFile::STDOUT))),
107            );
108            let _ = tmp.new_open_options_ext().insert_device_file(
109                PathBuf::from("/dev/stderr"),
110                self.stderr
111                    .unwrap_or_else(|| Box::new(DeviceFile::new(DeviceFile::STDERR))),
112            );
113            let _ = tmp.new_open_options_ext().insert_device_file(
114                PathBuf::from("/dev/tty"),
115                self.tty.unwrap_or_else(|| Box::<NullFile>::default()),
116            );
117
118            let _ = tmp.create_dir(Path::new("/dev/shm"));
119        }
120        tmp
121    }
122}
123
124#[cfg(test)]
125mod test_builder {
126    use crate::{FileSystem, RootFileSystemBuilder};
127    use std::path::Path;
128    use tokio::io::{AsyncReadExt, AsyncWriteExt};
129
130    #[tokio::test]
131    async fn test_root_file_system() {
132        let root_fs = RootFileSystemBuilder::new().build();
133        let mut dev_null = root_fs
134            .new_open_options()
135            .read(true)
136            .write(true)
137            .open("/dev/null")
138            .unwrap();
139        assert_eq!(dev_null.write(b"hello").await.unwrap(), 5);
140        let mut buf = Vec::new();
141        dev_null.read_to_end(&mut buf).await.unwrap();
142        assert!(buf.is_empty());
143        assert!(dev_null.get_special_fd().is_none());
144
145        let mut dev_zero = root_fs
146            .new_open_options()
147            .read(true)
148            .write(true)
149            .open("/dev/zero")
150            .unwrap();
151        assert_eq!(dev_zero.write(b"hello").await.unwrap(), 5);
152        let mut buf = vec![1; 10];
153        dev_zero.read_exact(&mut buf[..]).await.unwrap();
154        assert_eq!(buf, vec![0; 10]);
155        assert!(dev_zero.get_special_fd().is_none());
156
157        let mut dev_tty = root_fs
158            .new_open_options()
159            .read(true)
160            .write(true)
161            .open("/dev/tty")
162            .unwrap();
163        assert_eq!(dev_tty.write(b"hello").await.unwrap(), 5);
164        let mut buf = Vec::new();
165        dev_tty.read_to_end(&mut buf).await.unwrap();
166        assert!(buf.is_empty());
167        assert!(dev_tty.get_special_fd().is_none());
168
169        root_fs
170            .new_open_options()
171            .read(true)
172            .open("/bin/wasmer")
173            .unwrap();
174
175        let dev_stdin = root_fs
176            .new_open_options()
177            .read(true)
178            .write(true)
179            .open("/dev/stdin")
180            .unwrap();
181        assert_eq!(dev_stdin.get_special_fd().unwrap(), 0);
182        let dev_stdout = root_fs
183            .new_open_options()
184            .read(true)
185            .write(true)
186            .open("/dev/stdout")
187            .unwrap();
188        assert_eq!(dev_stdout.get_special_fd().unwrap(), 1);
189        let dev_stderr = root_fs
190            .new_open_options()
191            .read(true)
192            .write(true)
193            .open("/dev/stderr")
194            .unwrap();
195        assert_eq!(dev_stderr.get_special_fd().unwrap(), 2);
196
197        let dev_shm_metadata = root_fs.metadata(Path::new("/dev/shm")).unwrap();
198        assert!(dev_shm_metadata.is_dir());
199    }
200}