1mod linked_storage;
4pub use linked_storage::LinkedStorage;
5
6use crate::Vfd;
7use std::{collections::HashMap, path::Path};
8
9pub(crate) trait NodeIdTrait {
10 fn ino(&self) -> u64;
11}
12
13pub(crate) trait NodeFileBody {
14 fn content(&self) -> &[u8];
15}
16
17pub(crate) struct DirEntry<S: Storage + ?Sized> {
18 pub(crate) name: String,
19 pub(crate) link_id: S::LinkId,
20}
21
22impl<S: Storage> Clone for DirEntry<S> {
23 fn clone(&self) -> Self {
24 DirEntry {
25 name: self.name.clone(),
26 link_id: self.link_id,
27 }
28 }
29}
30
31pub(crate) trait NodeDirBody<S: Storage + ?Sized> {
32 type Iter: Iterator<Item = DirEntry<S>>;
33 fn entries(&self) -> Self::Iter;
34}
35
36pub(crate) trait Storage {
38 type NodeId: NodeIdTrait + Clone + Copy;
39 type LinkId: Clone + Copy;
40 type NodeFileBody: NodeFileBody;
41 type NodeDirBody: NodeDirBody<Self>;
42
43 fn new_root_dir(&mut self) -> (Self::NodeId, Self::LinkId);
45
46 fn new_dir(
48 &mut self,
49 parent: (Self::NodeId, Self::LinkId),
50 name: String,
51 ) -> (Self::NodeId, Self::LinkId);
52
53 fn new_file(
55 &mut self,
56 parent: (Self::NodeId, Self::LinkId),
57 name: String,
58 content: Vec<u8>,
59 ) -> (Self::NodeId, Self::LinkId);
60
61 fn get_inode(&self, node_id: &Self::NodeId) -> Node<Self>;
63
64 fn get_link(&self, link_id: &Self::LinkId) -> Link<Self>;
66
67 fn resolve_node(
69 &self,
70 base: Self::NodeId,
71 base_link: Self::LinkId,
72 path: &Path,
73 ) -> Result<(Self::NodeId, Self::LinkId), wasi::Errno>;
74}
75
76pub(crate) enum Node<'a, S: Storage + ?Sized> {
77 File(&'a S::NodeFileBody),
78 Dir(&'a S::NodeDirBody),
79}
80
81pub(crate) struct Link<S: Storage + ?Sized> {
83 pub(crate) parent: Option<S::LinkId>,
84 pub(crate) node: S::NodeId,
85}
86
87impl<S: Storage> Clone for Link<S> {
88 fn clone(&self) -> Self {
89 Link {
90 parent: self.parent,
91 node: self.node,
92 }
93 }
94}
95
96impl<S: Storage> Copy for Link<S> {}
97
98pub(crate) struct FdEntry<S: Storage + ?Sized> {
99 pub(crate) offset: usize,
100 pub(crate) link_id: S::LinkId,
101 pub(crate) node_id: S::NodeId,
102 pub(crate) flags: wasi::Fdflags,
103}
104
105pub(crate) struct PreopenedDir {
106 pub(crate) path: String,
107}
108
109pub(crate) struct EmbeddedFs<S: Storage> {
110 preopened_dirs: Vec<PreopenedDir>,
111 storage: S,
112
113 opens: HashMap<Vfd, FdEntry<S>>,
114 fd_issuer: IdIssuer<Vfd>,
115}
116
117#[derive(Default)]
118struct IdIssuer<Id> {
119 next_id: Id,
120}
121
122impl<Id> IdIssuer<Id> {
123 fn new(base: Id) -> Self {
124 Self { next_id: base }
125 }
126}
127
128impl<Id: std::ops::AddAssign<u32> + Clone> IdIssuer<Id> {
129 fn issue(&mut self) -> Id {
130 let id = self.next_id.clone();
131 self.next_id += 1;
132 id
133 }
134}
135
136impl<S: Storage> Default for EmbeddedFs<S>
137where
138 S: Default,
139{
140 fn default() -> Self {
141 Self::new(S::default())
142 }
143}
144
145impl<S: Storage> EmbeddedFs<S> {
146 pub(crate) fn new(storage: S) -> Self {
147 Self {
148 preopened_dirs: vec![],
149 storage,
150 opens: HashMap::new(),
151 fd_issuer: IdIssuer::new(0_u32),
152 }
153 }
154
155 pub(crate) fn preopen_dir(&mut self, path: String) -> (Vfd, S::NodeId, S::LinkId) {
156 assert!(self.preopened_dirs.len() == self.opens.len());
157 let fd = self.fd_issuer.issue();
158 self.preopened_dirs.push(PreopenedDir { path });
159 let (node_id, link_id) = self.storage.new_root_dir();
160 self.opens.insert(
161 fd,
162 FdEntry {
163 offset: 0,
164 node_id,
165 link_id,
166 flags: 0,
167 },
168 );
169 (fd, node_id, link_id)
170 }
171
172 pub(crate) fn get_preopened_dir_path(&self, vfd: Vfd) -> Option<&str> {
173 let vfd = vfd as usize;
174 if vfd >= self.preopened_dirs.len() {
175 return None;
176 }
177 Some(&self.preopened_dirs[vfd].path)
178 }
179
180 pub(crate) fn create_dir(
181 &mut self,
182 dir_node: S::NodeId,
183 dir_link: S::LinkId,
184 relpath: &str,
185 ) -> Result<(), u16> {
186 let (cursor, filename) = self.create_intermediate_dirs(dir_node, dir_link, relpath)?;
187 self.storage.new_dir(cursor, filename.to_string());
188 Ok(())
189 }
190
191 pub(crate) fn create_file(
192 &mut self,
193 dir_node: S::NodeId,
194 dir_link: S::LinkId,
195 relpath: &str,
196 content: Vec<u8>,
197 ) -> Result<(), u16> {
198 let (cursor, filename) = self.create_intermediate_dirs(dir_node, dir_link, relpath)?;
199 self.storage.new_file(cursor, filename.to_string(), content);
200 Ok(())
201 }
202
203 fn create_intermediate_dirs<'path>(
204 &mut self,
205 base_node: S::NodeId,
206 base_link: S::LinkId,
207 mut relpath: &'path str,
208 ) -> Result<((S::NodeId, S::LinkId), &'path str), u16> {
209 let mut cursor = match self.storage.get_inode(&base_node) {
210 Node::Dir { .. } => (base_node, base_link),
211 _ => return Err(wasi::ERRNO_BADF.raw()),
212 };
213 if relpath.starts_with('/') {
214 relpath = &relpath[1..];
215 }
216 let components = relpath.split('/').collect::<Vec<_>>();
217 let filename = match components.last() {
218 Some(filename) => *filename,
219 None => return Err(wasi::ERRNO_NOENT.raw()),
220 };
221
222 let components_len = components.len();
223
224 'find_parent_node: for component in components.into_iter().take(components_len - 1) {
225 if component == "." {
226 continue;
227 }
228 let entries = match self.storage.get_inode(&cursor.0) {
229 Node::Dir(body) => body.entries(),
230 _ => return Err(wasi::ERRNO_BADF.raw()),
231 };
232 for entry in entries {
233 if component == entry.name {
234 cursor = (self.storage.get_link(&entry.link_id).node, entry.link_id);
235 continue 'find_parent_node;
236 }
237 }
238 {
240 let (new_dir_id, new_link_id) = self.storage.new_dir(cursor, component.to_string());
241 cursor = (new_dir_id, new_link_id);
242 }
243 }
244 Ok(((cursor.0, cursor.1), filename))
245 }
246
247 pub(crate) fn get_node_id_by_link(&self, id: S::LinkId) -> S::NodeId {
248 self.storage.get_link(&id).node
249 }
250
251 pub(crate) fn get_node(&self, fd: Vfd) -> Result<Node<S>, wasi::Errno> {
252 match self.opens.get(&fd) {
253 Some(entry) => Ok(self.storage.get_inode(&entry.node_id)),
254 None => Err(wasi::ERRNO_BADF),
255 }
256 }
257
258 pub(crate) fn get_fd_stat(&self, fd: Vfd) -> Result<wasi::Fdstat, wasi::Errno> {
259 const READ_ONLY_RIGHTS: wasi::Rights = wasi::RIGHTS_FD_READ
260 | wasi::RIGHTS_FD_ADVISE
261 | wasi::RIGHTS_PATH_OPEN
262 | wasi::RIGHTS_FD_READDIR
263 | wasi::RIGHTS_FD_FILESTAT_GET;
264 let entry = match self.opens.get(&fd) {
265 Some(entry) => entry,
266 None => return Err(wasi::ERRNO_BADF),
267 };
268 Ok(match self.storage.get_inode(&entry.node_id) {
269 Node::File { .. } => wasi::Fdstat {
270 fs_filetype: wasi::FILETYPE_REGULAR_FILE,
271 fs_flags: entry.flags,
272 fs_rights_base: READ_ONLY_RIGHTS,
273 fs_rights_inheriting: READ_ONLY_RIGHTS,
274 },
275 Node::Dir { .. } => wasi::Fdstat {
276 fs_filetype: wasi::FILETYPE_DIRECTORY,
277 fs_flags: entry.flags,
278 fs_rights_base: READ_ONLY_RIGHTS,
279 fs_rights_inheriting: READ_ONLY_RIGHTS,
280 },
281 })
282 }
283
284 pub(crate) fn get_filestat_from_node_id(&self, node_id: S::NodeId) -> wasi::Filestat {
285 let mut stat = wasi::Filestat {
286 dev: Default::default(),
287 ino: Default::default(),
288 filetype: wasi::FILETYPE_UNKNOWN,
289 nlink: Default::default(),
290 size: Default::default(),
291 atim: Default::default(),
292 mtim: Default::default(),
293 ctim: Default::default(),
294 };
295 stat.ino = node_id.ino();
296 match self.storage.get_inode(&node_id) {
297 Node::File(body) => {
298 stat.filetype = wasi::FILETYPE_REGULAR_FILE;
299 stat.size = body.content().len() as u64;
300 stat
301 }
302 Node::Dir { .. } => {
303 stat.filetype = wasi::FILETYPE_DIRECTORY;
304 stat
305 }
306 }
307 }
308
309 pub(crate) fn get_fd_entry_mut(&mut self, fd: Vfd) -> Result<&mut FdEntry<S>, wasi::Errno> {
310 match self.opens.get_mut(&fd) {
311 Some(open_file) => Ok(open_file),
312 None => Err(wasi::ERRNO_BADF),
313 }
314 }
315
316 pub(crate) fn get_fd_entry(&self, fd: Vfd) -> Result<&FdEntry<S>, wasi::Errno> {
317 match self.opens.get(&fd) {
318 Some(open_file) => Ok(open_file),
319 None => Err(wasi::ERRNO_BADF),
320 }
321 }
322
323 pub(crate) fn close_file(&mut self, fd: Vfd) -> Result<(), wasi::Errno> {
324 match self.opens.remove(&fd) {
325 Some(_) => Ok(()),
326 None => Err(wasi::ERRNO_BADF),
327 }
328 }
329
330 pub(crate) fn open_file(
331 &mut self,
332 base: Vfd,
333 path: &Path,
334 fdflags: wasi::Fdflags,
335 ) -> Result<Vfd, wasi::Errno> {
336 let base = &self.opens[&base];
337 let (node_id, link_id) = self
338 .storage
339 .resolve_node(base.node_id, base.link_id, path)?;
340 let new_fd = self.fd_issuer.issue();
341 self.opens.insert(
342 new_fd,
343 FdEntry {
344 offset: 0,
345 node_id,
346 link_id,
347 flags: fdflags,
348 },
349 );
350 Ok(new_fd)
351 }
352
353 pub(crate) fn get_filestat_at_path(
354 &self,
355 base: Vfd,
356 path: &Path,
357 ) -> Result<wasi::Filestat, wasi::Errno> {
358 let base = &self.opens[&base];
359 let (node_id, _) = self
360 .storage
361 .resolve_node(base.node_id, base.link_id, path)?;
362 let res = self.get_filestat_from_node_id(node_id);
363 Ok(res)
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use std::path::Path;
370
371 use super::{EmbeddedFs, LinkedStorage};
372
373 #[test]
374 fn test_embedded_node_create_file() {
375 let content = "Hello".as_bytes().to_vec();
376 let mut fs = EmbeddedFs::<LinkedStorage>::default();
377 let (_, node_id, link_id) = fs.preopen_dir("/".to_string());
378 fs.create_file(node_id, link_id, "hello.txt", content)
379 .unwrap();
380 }
381
382 #[test]
383 fn test_get_filestat_at_path_for_non_existing() {
384 let mut fs = EmbeddedFs::<LinkedStorage>::default();
385 let (vfd, _, _) = fs.preopen_dir("/".to_string());
386 let result = fs.get_filestat_at_path(vfd, Path::new("/not-exist"));
387 assert!(result.is_err());
388 }
389}