virtual_fs/
builder.rs

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