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}