mfio_rt/virt/
mod.rs

1//! Virtual Runtime
2//!
3//! This module implements a virtual runtime (and in-memory filesystem) that can be used as basis
4//! for no_std implementations.
5//!
6//! Note that at the current moment this can be considered more as a toy example, rather than fully
7//! featured implementation.
8
9use alloc::{
10    collections::BTreeMap,
11    string::{String, ToString},
12    vec,
13    vec::Vec,
14};
15use core::future::{pending, ready, Future, Ready};
16use core::mem::drop;
17use core::ops::Bound;
18use core::pin::Pin;
19use core::task::{Context, Poll};
20use futures::Stream;
21
22use log::*;
23use slab::Slab;
24
25use mfio::backend::{BackendContainer, BackendHandle, DynBackend, IoBackend, PollingHandle};
26use mfio::error::Result;
27use mfio::io::*;
28use mfio::mferr;
29use mfio::stdeq::Seekable;
30
31use crate::{
32    util::path_filename_str, Component, DirEntry, DirHandle, DirOp, FileType, Fs, Metadata,
33    OpenOptions, Path, PathBuf, Permissions,
34};
35
36#[cfg(not(feature = "virt-sync"))]
37use alloc::rc::Rc;
38#[cfg(feature = "virt-sync")]
39use alloc::sync::Arc as Rc;
40
41#[cfg(feature = "virt-sync")]
42use mfio::locks::RwLock;
43
44#[cfg(not(feature = "virt-sync"))]
45use core::cell::{Ref, RefCell, RefMut};
46#[cfg(not(feature = "virt-sync"))]
47struct RwLock<T>(RefCell<T>);
48
49#[cfg(not(feature = "virt-sync"))]
50impl<T> RwLock<T> {
51    fn new(v: T) -> Self {
52        Self(RefCell::new(v))
53    }
54
55    fn read(&self) -> Ref<T> {
56        self.0.borrow()
57    }
58
59    fn write(&self) -> RefMut<T> {
60        self.0.borrow_mut()
61    }
62}
63
64type Shared<T> = Rc<RwLock<T>>;
65type InodeId = usize;
66
67pub struct VirtRt {
68    cwd: VirtDir,
69    backend: BackendContainer<DynBackend>,
70}
71
72impl Default for VirtRt {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl VirtRt {
79    pub fn new() -> Self {
80        Self {
81            cwd: VirtDir::default(),
82            backend: BackendContainer::new_dyn(pending()),
83        }
84    }
85
86    pub fn build<'a>(
87        dirs: impl Iterator<Item = &'a str> + 'a,
88        files: impl Iterator<Item = (&'a str, &'a [u8])> + 'a,
89    ) -> Self {
90        Self {
91            cwd: VirtDir::from_fs(VirtFs::build(dirs, files).into()),
92            backend: BackendContainer::new_dyn(pending()),
93        }
94    }
95}
96
97impl IoBackend for VirtRt {
98    type Backend = DynBackend;
99
100    fn polling_handle(&self) -> Option<PollingHandle> {
101        None
102    }
103
104    fn get_backend(&self) -> BackendHandle<Self::Backend> {
105        self.backend.acquire(None)
106    }
107}
108
109impl Fs for VirtRt {
110    type DirHandle<'a> = VirtDir;
111
112    fn current_dir(&self) -> &Self::DirHandle<'_> {
113        &self.cwd
114    }
115}
116
117pub struct VirtDir {
118    inode: Shared<Inode>,
119    fs: Rc<VirtFs>,
120}
121
122impl Default for VirtDir {
123    fn default() -> Self {
124        Self::from_fs(Rc::new(VirtFs::default()))
125    }
126}
127
128impl VirtDir {
129    fn from_fs(fs: Rc<VirtFs>) -> Self {
130        Self {
131            inode: fs.root_dir.clone(),
132            fs,
133        }
134    }
135
136    // Internal sync implementations of directory-relative fs operations
137
138    fn get_path(&self) -> Result<PathBuf> {
139        Inode::path(self.inode.clone(), &self.fs.inodes.read())
140            .ok_or_else(|| mferr!(Path, Invalid, Filesystem))
141    }
142
143    fn do_open_file(
144        &self,
145        path: &Path,
146        options: OpenOptions,
147    ) -> Result<<Self as DirHandle>::FileHandle> {
148        path.parent()
149            .and_then(|path| {
150                let inodes = self.fs.inodes.read();
151                Inode::walk_rel(self.inode.clone(), &self.fs.root_dir, path, &inodes)
152            })
153            // TODO: do we want to filter here, since we are returning None later anyways. The only
154            // difference here is that we are doing so with read-only lock.
155            .filter(|dir| matches!(dir.read().entry, InodeData::Dir(_)))
156            .ok_or(mferr!(Directory, NotFound, Filesystem))
157            .and_then(|dir| {
158                let mut dir_guard = dir.write();
159                let dir_guard = &mut *dir_guard;
160
161                let InodeData::Dir(dir_entry) = &mut dir_guard.entry else {
162                    unreachable!();
163                };
164
165                let Some(parent_id) = dir_guard.id else {
166                    return Err(mferr!(Path, Removed, Filesystem));
167                };
168
169                let filename = path_filename_str(path).ok_or(mferr!(Path, Invalid, Filesystem))?;
170
171                // creating new file may get triggered from 2 branches
172                let new_file = |entries: &mut BTreeMap<_, _>| {
173                    let mut inodes = self.fs.inodes.write();
174                    let entry = inodes.vacant_entry();
175                    let id = entry.key();
176
177                    let name: Rc<str> = filename.into();
178
179                    let inode = entry
180                        .insert(Rc::new(RwLock::new(Inode {
181                            id: Some(id),
182                            parent_link: Some(ParentLink {
183                                name: name.clone(),
184                                parent: parent_id,
185                            }),
186                            entry: InodeData::File(VirtFileInner { data: vec![] }),
187                            metadata: Metadata::empty_file(Permissions {}, None),
188                        })))
189                        .clone();
190
191                    entries.insert(name.clone(), id);
192
193                    Ok(VirtFile { inode, options })
194                };
195
196                // Verify open options
197                if options.create_new {
198                    if options.write && !dir_entry.entries.contains_key(filename) {
199                        new_file(&mut dir_entry.entries)
200                    } else {
201                        Err(mferr!(File, AlreadyExists, Filesystem))
202                    }
203                } else if let Some(&id) = dir_entry.entries.get(filename) {
204                    let inodes = self.fs.inodes.read();
205                    // Different from file not found, because it's the whole inode that's gone, and
206                    // this is not expected.
207                    let inode = inodes
208                        .get(id)
209                        .ok_or(mferr!(Entry, NotFound, Filesystem))?
210                        .clone();
211
212                    // Verify that we've got a file in both of the branches, except we are holding
213                    // different types of locks (read vs write).
214                    if options.truncate {
215                        if options.write {
216                            let mut inode = inode.write();
217                            let InodeData::File(file) = &mut inode.entry else {
218                                return Err(mferr!(Entry, Invalid, Filesystem));
219                            };
220                            file.data.clear();
221                            inode.metadata.len = 0;
222                        } else {
223                            return Err(mferr!(Argument, Unsupported, Filesystem));
224                        }
225                    } else if !matches!(inode.read().entry, InodeData::File(_)) {
226                        return Err(mferr!(Entry, Invalid, Filesystem));
227                    }
228
229                    Ok(VirtFile { inode, options })
230                } else if options.create && options.write {
231                    new_file(&mut dir_entry.entries)
232                } else {
233                    Err(mferr!(File, NotFound, Filesystem))
234                }
235            })
236            .map(Seekable::from)
237    }
238
239    fn set_permissions(&self, path: &Path, permissions: Permissions) -> Result<()> {
240        let inodes = self.fs.inodes.read();
241        let node = Inode::walk_rel(self.inode.clone(), &self.fs.root_dir, path, &inodes)
242            .ok_or(mferr!(Path, NotFound, Filesystem))?;
243        let mut node = node.write();
244        // TODO: check whether the caller is authorized
245        node.metadata.permissions = permissions;
246        // TODO: change mtime
247        Ok(())
248    }
249
250    /// Unlink from given parent
251    ///
252    /// # Panics
253    ///
254    /// This function panics if `parent` is not the parent of `node`.
255    fn unlink_with_parent(node: &mut Inode, parent_node: &mut Inode) {
256        if let Some(ParentLink { name, parent }) = node.parent_link.take() {
257            assert_eq!(parent, parent_node.id.unwrap());
258            let InodeData::Dir(parent) = &mut parent_node.entry else {
259                panic!("Parent changed from dir to file")
260            };
261            parent.entries.remove(&name);
262        }
263    }
264
265    fn unlink(node: &mut Inode, inodes: &mut Slab<Shared<Inode>>) {
266        if let Some(ParentLink { parent, .. }) = &node.parent_link {
267            let parent = inodes
268                .get(*parent)
269                .expect("No parent inode found. This indicates buggy unlinking")
270                .clone();
271            let mut parent = parent.write();
272            Self::unlink_with_parent(node, &mut parent)
273        }
274    }
275
276    fn remove_dir(&self, path: &Path) -> Result<()> {
277        let mut inodes = self.fs.inodes.write();
278        let node = Inode::walk_rel(self.inode.clone(), &self.fs.root_dir, path, &inodes)
279            .ok_or(mferr!(Path, NotFound, Filesystem))?;
280        let mut node = node.write();
281
282        let InodeData::Dir(dir) = &node.entry else {
283            return Err(mferr!(Entry, Invalid, Filesystem));
284        };
285
286        if !dir.entries.is_empty() {
287            return Err(mferr!(Directory, InUse, Filesystem));
288        }
289
290        if let Some(id) = node.id.take() {
291            inodes.remove(id);
292        }
293
294        Self::unlink(&mut node, &mut inodes);
295
296        Ok(())
297    }
298
299    fn remove_dir_all(&self, path: &Path) -> Result<()> {
300        let mut inodes = self.fs.inodes.write();
301        let node = Inode::walk_rel(self.inode.clone(), &self.fs.root_dir, path, &inodes)
302            .ok_or(mferr!(Path, NotFound, Filesystem))?;
303
304        let mut stack = vec![node];
305
306        while let Some(head) = stack.pop() {
307            let mut node = head.write();
308
309            let InodeData::Dir(dir) = &mut node.entry else {
310                return Err(mferr!(Entry, Invalid, Filesystem));
311            };
312
313            while let Some((_, id)) = dir.entries.pop_last() {
314                let inode = inodes.get(id).expect("entry exists, with no inode").clone();
315                let mut inode_guard = inode.write();
316                match inode_guard.entry {
317                    InodeData::File(_) => {
318                        inodes.remove(inode_guard.id.take().expect("parent does not exist"));
319                    }
320                    InodeData::Dir(_) => {
321                        drop(inode_guard);
322                        stack.push(head.clone());
323                        stack.push(inode);
324                        break;
325                    }
326                }
327            }
328
329            if dir.entries.is_empty() {
330                if let Some(id) = node.id.take() {
331                    inodes.remove(id);
332                }
333
334                Self::unlink(&mut node, &mut inodes);
335            }
336        }
337
338        Ok(())
339    }
340
341    fn create_dir(&self, path: &Path) -> Result<()> {
342        let filename = path_filename_str(path).ok_or(mferr!(Path, Invalid, Filesystem))?;
343
344        let mut inodes = self.fs.inodes.write();
345        let node = Inode::walk_rel(
346            self.inode.clone(),
347            &self.fs.root_dir,
348            path.parent().ok_or(mferr!(Path, NotFound, Filesystem))?,
349            &inodes,
350        )
351        .ok_or(mferr!(Path, NotFound, Filesystem))?;
352
353        let mut node = node.write();
354
355        let Some(parent) = node.id else {
356            return Err(mferr!(Path, Removed, Filesystem));
357        };
358
359        let InodeData::Dir(parent_dir) = &mut node.entry else {
360            return Err(mferr!(Path, NotFound, Filesystem));
361        };
362
363        if parent_dir.entries.contains_key(filename) {
364            Err(mferr!(Directory, AlreadyExists, Filesystem))
365        } else {
366            let filename: Rc<str> = Rc::from(filename);
367
368            let entry = inodes.vacant_entry();
369            let id = entry.key();
370            entry.insert(Rc::new(RwLock::new(Inode {
371                id: Some(id),
372                metadata: Metadata::empty_dir(Permissions {}, None),
373                entry: InodeData::Dir(VirtDirInner {
374                    entries: Default::default(),
375                }),
376                parent_link: Some(ParentLink {
377                    name: filename.clone(),
378                    parent,
379                }),
380            })));
381
382            parent_dir.entries.insert(filename, id);
383
384            Ok(())
385        }
386    }
387
388    fn create_dir_all(&self, path: &Path) -> Result<()> {
389        let mut inodes = self.fs.inodes.write();
390        let mut cur_node = self.inode.clone();
391
392        let components = path.components();
393
394        for component in components {
395            let mut cur_inode = cur_node.write();
396
397            let Some(parent) = cur_inode.id else {
398                return Err(mferr!(Path, Removed, Filesystem));
399            };
400
401            let next_node = match component {
402                Component::RootDir => self.fs.root_dir.clone(),
403                Component::ParentDir => {
404                    // If we are at the root, then going up means cycling back.
405                    if let Some(link) = &cur_inode.parent_link {
406                        inodes
407                            .get(link.parent)
408                            .ok_or(mferr!(Path, Invalid, Filesystem))?
409                            .clone()
410                    } else {
411                        continue;
412                    }
413                }
414                Component::Normal(n) => {
415                    #[cfg(feature = "std")]
416                    let n = n.to_str().ok_or(mferr!(Path, Invalid, Filesystem))?;
417                    #[cfg(not(feature = "std"))]
418                    let n =
419                        core::str::from_utf8(n).map_err(|_| mferr!(Path, Invalid, Filesystem))?;
420
421                    let InodeData::Dir(d) = &mut cur_inode.entry else {
422                        return Err(mferr!(Path, Invalid, Filesystem));
423                    };
424                    let inode = d.entries.get(n);
425
426                    if let Some(&inode) = inode {
427                        inodes
428                            .get(inode)
429                            .ok_or(mferr!(Path, Invalid, Filesystem))?
430                            .clone()
431                    } else {
432                        let entry = inodes.vacant_entry();
433                        let id = entry.key();
434                        let name: Rc<str> = Rc::from(n);
435                        d.entries.insert(name.clone(), id);
436                        let entry = entry
437                            .insert(Rc::new(RwLock::new(Inode {
438                                id: Some(id),
439                                parent_link: Some(ParentLink { parent, name }),
440                                entry: InodeData::Dir(VirtDirInner {
441                                    entries: Default::default(),
442                                }),
443                                metadata: Metadata::empty_dir(Permissions {}, None),
444                            })))
445                            .clone();
446                        entry
447                    }
448                }
449                _ => continue,
450            };
451
452            drop(cur_inode);
453            cur_node = next_node;
454        }
455
456        Ok(())
457    }
458
459    fn remove_file(&self, path: &Path) -> Result<()> {
460        let mut inodes = self.fs.inodes.write();
461        let node = Inode::walk_rel(self.inode.clone(), &self.fs.root_dir, path, &inodes)
462            .ok_or(mferr!(Path, NotFound, Filesystem))?;
463        let mut node = node.write();
464
465        let InodeData::File(_) = &node.entry else {
466            return Err(mferr!(Entry, Invalid, Filesystem));
467        };
468
469        if let Some(id) = node.id.take() {
470            inodes.remove(id);
471        }
472
473        Self::unlink(&mut node, &mut inodes);
474
475        Ok(())
476    }
477
478    fn rename(&self, from: &Path, to: &Path) -> Result<()> {
479        let mut inodes = self.fs.inodes.write();
480
481        let from = Inode::walk_rel(self.inode.clone(), &self.fs.root_dir, from, &inodes)
482            .ok_or(mferr!(Path, NotFound, Filesystem))?;
483
484        let to_parent = to.parent().ok_or(mferr!(Path, Unavailable, Filesystem))?;
485        let to_filename = to.file_name().ok_or(mferr!(Path, Invalid, Filesystem))?;
486        #[cfg(feature = "std")]
487        let to_filename = to_filename
488            .to_str()
489            .ok_or(mferr!(Path, Invalid, Filesystem))?;
490        #[cfg(not(feature = "std"))]
491        let to_filename =
492            core::str::from_utf8(to_filename).map_err(|_| mferr!(Path, Invalid, Filesystem))?;
493
494        let to_parent = Inode::walk_rel(self.inode.clone(), &self.fs.root_dir, to_parent, &inodes)
495            .ok_or(mferr!(Path, NotFound, Filesystem))?;
496
497        // We need to make sure that `to_parent` is not child of `from`.
498        // I wonder, what if we didn't check for this? We could create "hidden islands" of sorts.
499        {
500            let mut tmp = from.clone();
501            while let Some(node) =
502                Inode::walk_rel(tmp, &self.fs.root_dir, Path::new("../"), &inodes)
503            {
504                if Rc::as_ptr(&node) == Rc::as_ptr(&to_parent) {
505                    return Err(mferr!(Path, Unavailable, Filesystem));
506                } else if Rc::as_ptr(&node) == Rc::as_ptr(&self.fs.root_dir) {
507                    break;
508                } else {
509                    tmp = node;
510                }
511            }
512        }
513
514        let mut to_parent = to_parent.write();
515        let InodeData::Dir(to_parent_dir) = &mut to_parent.entry else {
516            return Err(mferr!(Path, Invalid, Filesystem));
517        };
518
519        let mut from = from.write();
520
521        // Remove old entry, if we can
522        // This way we can also reuse the old arc for the name, without performing a new allocation
523        let to_filename =
524            if let Some((name, entry)) = to_parent_dir.entries.get_key_value(to_filename) {
525                let entry = inodes
526                    .get(*entry)
527                    .ok_or(mferr!(Path, Unavailable, Filesystem))?
528                    .clone();
529                let mut entry = entry.write();
530                if let InodeData::Dir(v) = &entry.entry {
531                    if !v.entries.is_empty() || matches!(from.entry, InodeData::File(_)) {
532                        return Err(mferr!(Directory, InUse, Filesystem));
533                    }
534                }
535                let name = name.clone();
536                Self::unlink_with_parent(&mut entry, &mut to_parent);
537                name
538            } else {
539                Rc::from(to_filename)
540            };
541
542        let InodeData::Dir(to_parent_dir) = &mut to_parent.entry else {
543            unreachable!()
544        };
545
546        Self::unlink(&mut from, &mut inodes);
547
548        // Finally, relink to the parent
549        to_parent_dir.entries.insert(
550            to_filename.clone(),
551            from.id
552                .expect("moving a file with no inode, this shouldn't happen"),
553        );
554
555        from.parent_link = Some(ParentLink {
556            name: to_filename,
557            parent: to_parent
558                .id
559                .expect("parent dir with no inode, this shouldn't happen"),
560        });
561
562        Ok(())
563    }
564
565    fn copy(&self, from: &Path, to: &Path) -> Result<()> {
566        let mut inodes = self.fs.inodes.write();
567
568        let from = Inode::walk_rel(self.inode.clone(), &self.fs.root_dir, from, &inodes)
569            .ok_or(mferr!(Path, NotFound, Filesystem))?;
570
571        let to_parent = to.parent().ok_or(mferr!(Path, Unavailable, Filesystem))?;
572        let to_filename = to.file_name().ok_or(mferr!(Path, Invalid, Filesystem))?;
573        #[cfg(feature = "std")]
574        let to_filename = to_filename
575            .to_str()
576            .ok_or(mferr!(Path, Invalid, Filesystem))?;
577        #[cfg(not(feature = "std"))]
578        let to_filename =
579            core::str::from_utf8(to_filename).map_err(|_| mferr!(Path, Invalid, Filesystem))?;
580
581        let to_parent = Inode::walk_rel(self.inode.clone(), &self.fs.root_dir, to_parent, &inodes)
582            .ok_or(mferr!(Path, NotFound, Filesystem))?;
583
584        let mut to_parent = to_parent.write();
585        let InodeData::Dir(to_parent_dir) = &mut to_parent.entry else {
586            return Err(mferr!(Path, Invalid, Filesystem));
587        };
588
589        let mut from = from.write();
590        let InodeData::File(from_file) = &mut from.entry else {
591            return Err(mferr!(Path, Invalid, Filesystem));
592        };
593
594        // Remove old entry, if we can
595        // This way we can also reuse the old arc for the name, without performing a new allocation
596        let to_filename =
597            if let Some((name, entry)) = to_parent_dir.entries.get_key_value(to_filename) {
598                let entry = inodes
599                    .get(*entry)
600                    .ok_or(mferr!(Path, Unavailable, Filesystem))?
601                    .clone();
602                let mut entry = entry.write();
603                if let InodeData::Dir(v) = &entry.entry {
604                    if !v.entries.is_empty() {
605                        return Err(mferr!(Directory, InUse, Filesystem));
606                    }
607                }
608                let name = name.clone();
609                Self::unlink_with_parent(&mut entry, &mut to_parent);
610                name
611            } else {
612                Rc::from(to_filename)
613            };
614
615        let Some(parent) = to_parent.id else {
616            return Err(mferr!(Path, Removed, Filesystem));
617        };
618
619        let InodeData::Dir(to_parent_dir) = &mut to_parent.entry else {
620            unreachable!()
621        };
622
623        let new_entry = inodes.vacant_entry();
624        let id = new_entry.key();
625        new_entry.insert(Rc::new(RwLock::new(Inode {
626            entry: InodeData::File(VirtFileInner {
627                data: from_file.data.clone(),
628            }),
629            id: Some(id),
630            parent_link: Some(ParentLink {
631                name: to_filename.clone(),
632                parent,
633            }),
634            // TODO: we need to update time here (when we have access to time).
635            metadata: from.metadata.clone(),
636        })));
637
638        // Finally, link up to the parent
639        to_parent_dir.entries.insert(to_filename.clone(), id);
640
641        Ok(())
642    }
643}
644
645impl DirHandle for VirtDir {
646    type FileHandle = Seekable<VirtFile, u64>;
647    type OpenFileFuture<'a> = OpenFileFuture<'a>;
648    type PathFuture<'a> = Ready<Result<PathBuf>>;
649    type OpenDirFuture<'a> = Ready<Result<Self>>;
650    type ReadDir<'a> = ReadDir<'a>;
651    type ReadDirFuture<'a> = Ready<Result<ReadDir<'a>>>;
652    type MetadataFuture<'a> = Ready<Result<Metadata>>;
653    type OpFuture<'a> = OpFuture<'a>;
654
655    /// Gets the absolute path to this `DirHandle`.
656    fn path(&self) -> Self::PathFuture<'_> {
657        ready(self.get_path())
658    }
659
660    /// Reads the directory contents.
661    ///
662    /// Iterating directories has the complexity of `O(n log(n))`, because we are not maintaining
663    /// the cursor to the directory.
664    ///
665    /// TODO: do not go back for entries after every iteration, instead, cache up a few entries.
666    fn read_dir(&self) -> Self::ReadDirFuture<'_> {
667        ready(Ok(ReadDir {
668            dir: self,
669            cur: None,
670        }))
671    }
672
673    /// Opens a file.
674    ///
675    /// This function accepts an absolute or relative path to a file for reading. If the path is
676    /// relative, it is opened relative to this `DirHandle`.
677    fn open_file<'a, P: AsRef<Path> + ?Sized>(
678        &'a self,
679        path: &'a P,
680        options: OpenOptions,
681    ) -> Self::OpenFileFuture<'a> {
682        OpenFileFuture {
683            dir: self,
684            path: path.as_ref(),
685            options,
686        }
687    }
688
689    /// Opens a directory.
690    ///
691    /// This function accepts an absolute or relative path to a directory for reading. If the path
692    /// is relative, it is opened relative to this `DirHandle`.
693    fn open_dir<'a, P: AsRef<Path> + ?Sized>(&'a self, path: &'a P) -> Self::OpenDirFuture<'a> {
694        // We do not write anything here, therefore
695        let inode = {
696            let inodes = self.fs.inodes.read();
697            Inode::walk_rel(
698                self.inode.clone(),
699                &self.fs.root_dir,
700                path.as_ref(),
701                &inodes,
702            )
703        };
704
705        let ret = inode
706            // TODO: do we want to filter here, since we are returning None later anyways. The only
707            // difference here is that we are doing so with read-only lock.
708            .filter(|dir| matches!(dir.read().entry, InodeData::Dir(_)))
709            .ok_or(mferr!(Directory, NotFound, Filesystem))
710            .map(|inode| VirtDir {
711                inode,
712                fs: self.fs.clone(),
713            });
714
715        ready(ret)
716    }
717
718    fn metadata<'a, P: AsRef<Path> + ?Sized>(&'a self, path: &'a P) -> Self::MetadataFuture<'a> {
719        let inodes = self.fs.inodes.read();
720
721        let ret = Inode::walk_rel(
722            self.inode.clone(),
723            &self.fs.root_dir,
724            path.as_ref(),
725            &inodes,
726        )
727        .map(|v| v.read().metadata.clone())
728        .ok_or(mferr!(Entry, NotFound, Filesystem));
729
730        ready(ret)
731    }
732
733    /// Do an operation.
734    ///
735    /// This function performs an operation from the [`DirOp`] enum.
736    fn do_op<'a, P: AsRef<Path> + ?Sized>(&'a self, operation: DirOp<&'a P>) -> Self::OpFuture<'a> {
737        OpFuture {
738            dir: self,
739            operation: Some(operation.into_path()),
740        }
741    }
742}
743
744// VirtDir's DirHandle futures:
745// TODO: Once impl_trait_in_assoc_type is stabilized (https://github.com/rust-lang/rust/issues/63063),
746// we can remove these in favor of async fns in traits.
747
748pub struct OpFuture<'a> {
749    dir: &'a VirtDir,
750    operation: Option<DirOp<&'a Path>>,
751}
752
753impl Future for OpFuture<'_> {
754    type Output = Result<()>;
755
756    fn poll(mut self: Pin<&mut Self>, _: &mut Context) -> Poll<Self::Output> {
757        //let this = unsafe { self.get_unchecked_mut() }
758        //Poll::Ready(self.dir.do_open_file(self.path, self.options))
759        let ret = match self.operation.take().unwrap() {
760            DirOp::SetPermissions { path, permissions } => {
761                self.dir.set_permissions(path, permissions)
762            }
763            DirOp::RemoveDir { path } => self.dir.remove_dir(path),
764            DirOp::RemoveDirAll { path } => self.dir.remove_dir_all(path),
765            DirOp::CreateDir { path } => self.dir.create_dir(path),
766            DirOp::CreateDirAll { path } => self.dir.create_dir_all(path),
767            DirOp::RemoveFile { path } => self.dir.remove_file(path),
768            DirOp::Rename { from, to } => self.dir.rename(from, to),
769            DirOp::Copy { from, to } => self.dir.copy(from, to),
770            DirOp::HardLink { .. } => Err(mferr!(Operation, NotImplemented, Filesystem)),
771        };
772
773        Poll::Ready(ret)
774    }
775}
776
777pub struct OpenFileFuture<'a> {
778    dir: &'a VirtDir,
779    path: &'a Path,
780    options: OpenOptions,
781}
782
783impl Future for OpenFileFuture<'_> {
784    type Output = Result<<VirtDir as DirHandle>::FileHandle>;
785
786    fn poll(self: Pin<&mut Self>, _: &mut Context) -> Poll<Self::Output> {
787        Poll::Ready(self.dir.do_open_file(self.path, self.options))
788    }
789}
790
791pub struct ReadDir<'a> {
792    dir: &'a VirtDir,
793    cur: Option<Rc<str>>,
794}
795
796impl Stream for ReadDir<'_> {
797    type Item = Result<DirEntry>;
798
799    fn poll_next(self: Pin<&mut Self>, _: &mut Context) -> Poll<Option<Self::Item>> {
800        let this = unsafe { self.get_unchecked_mut() };
801
802        let dir = this.dir.inode.read();
803
804        let InodeData::Dir(dir) = &dir.entry else {
805            return Poll::Ready(None);
806        };
807
808        let next = if let Some(cur) = this.cur.clone() {
809            dir.entries
810                .range((Bound::Excluded(cur), Bound::Unbounded))
811                .next()
812        } else {
813            dir.entries.iter().next()
814        };
815
816        Poll::Ready(next.map(|(k, v)| {
817            this.cur = Some(k.clone());
818            this.dir
819                .fs
820                .inodes
821                .read()
822                .get(*v)
823                .map(|v| v.read())
824                .as_ref()
825                .map(|v| v.entry.to_dir_entry(k.clone()))
826                .ok_or(mferr!(Entry, Corrupted, Filesystem))
827        }))
828    }
829}
830
831// Core filesystem container:
832
833pub struct VirtFs {
834    inodes: RwLock<Slab<Shared<Inode>>>,
835    root_dir: Shared<Inode>,
836}
837
838impl VirtFs {
839    /// Build a filesystem from directory and file list.
840    ///
841    /// Builds a filesystem instance from given data. This is generally more efficient than
842    /// manually creating all entries (without caching), because the build step is able to directly
843    /// access all inodes.
844    pub fn build<'a>(
845        dirs: impl Iterator<Item = &'a str> + 'a,
846        files: impl Iterator<Item = (&'a str, &'a [u8])> + 'a,
847    ) -> Self {
848        let mut inode_cache = BTreeMap::new();
849
850        let mut inodes = Slab::new();
851
852        let entry = inodes.vacant_entry();
853        let id = entry.key();
854
855        let root_dir = Rc::new(RwLock::new(Inode {
856            id: Some(id),
857            parent_link: None,
858            entry: InodeData::Dir(VirtDirInner {
859                entries: Default::default(),
860            }),
861            metadata: Metadata::empty_dir(Permissions {}, None),
862        }));
863
864        entry.insert(root_dir.clone());
865
866        let insert_entry = |inodes: &mut Slab<_>, parent_id, p: &str, entry, metadata| {
867            let new_entry = inodes.vacant_entry();
868            let id = new_entry.key();
869
870            let name: Rc<str> = p.into();
871
872            new_entry.insert(Rc::new(RwLock::new(Inode {
873                id: Some(id),
874                parent_link: Some(ParentLink {
875                    parent: parent_id,
876                    name: name.clone(),
877                }),
878                entry,
879                metadata,
880            })));
881
882            let parent = inodes.get(parent_id).unwrap();
883            let mut parent = parent.write();
884            let InodeData::Dir(d) = &mut parent.entry else {
885                unreachable!()
886            };
887            d.entries.insert(name, id);
888
889            id
890        };
891
892        for dir in dirs {
893            let mut d = String::new();
894            // Root inode ID
895            let mut parent_id = id;
896            for p in dir.split('/') {
897                if !d.is_empty() {
898                    d.push('/');
899                }
900                d.push_str(p);
901
902                parent_id = *inode_cache.entry(d.clone()).or_insert_with(|| {
903                    insert_entry(
904                        &mut inodes,
905                        parent_id,
906                        p,
907                        InodeData::Dir(VirtDirInner::default()),
908                        Metadata::empty_dir(Permissions {}, None),
909                    )
910                });
911            }
912        }
913
914        for (path, data) in files {
915            let mut d = String::new();
916            // Root inode ID
917            let mut parent_id = id;
918            for p in path.split('/') {
919                if !d.is_empty() {
920                    d.push('/');
921                }
922                d.push_str(p);
923
924                parent_id = *inode_cache.entry(d.clone()).or_insert_with(|| {
925                    insert_entry(
926                        &mut inodes,
927                        parent_id,
928                        p,
929                        InodeData::Dir(VirtDirInner::default()),
930                        Metadata::empty_dir(Permissions {}, None),
931                    )
932                });
933            }
934
935            let inode = inode_cache.get(path).unwrap();
936            let inode = inodes.get(*inode).unwrap();
937            let mut inode = inode.write();
938
939            {
940                let InodeData::Dir(v) = &inode.entry else {
941                    unreachable!()
942                };
943                assert!(v.entries.is_empty());
944            }
945
946            inode.metadata.len = data.len() as _;
947            inode.entry = InodeData::File(VirtFileInner { data: data.into() });
948        }
949
950        let inodes = RwLock::new(inodes);
951
952        Self { inodes, root_dir }
953    }
954}
955
956impl Default for VirtFs {
957    fn default() -> Self {
958        let mut inodes = Slab::new();
959
960        let entry = inodes.vacant_entry();
961        let id = entry.key();
962
963        let root_dir = Rc::new(RwLock::new(Inode {
964            id: Some(id),
965            parent_link: None,
966            entry: InodeData::Dir(VirtDirInner {
967                entries: Default::default(),
968            }),
969            metadata: Metadata::empty_dir(Permissions {}, None),
970        }));
971
972        entry.insert(root_dir.clone());
973
974        let inodes = RwLock::new(inodes);
975
976        Self { inodes, root_dir }
977    }
978}
979
980// File implementation:
981
982pub struct VirtFile {
983    inode: Shared<Inode>,
984    options: OpenOptions,
985}
986
987impl PacketIo<Write, u64> for VirtFile {
988    fn send_io(&self, pos: u64, pkt: BoundPacketView<Write>) {
989        if self.options.read {
990            let node = self.inode.read();
991
992            let InodeData::File(file) = &node.entry else {
993                core::mem::drop(node);
994                pkt.error(mferr!(File, Unreadable, Filesystem));
995                return;
996            };
997
998            if pos < file.data.len() as u64 {
999                let split_pos = (file.data.len() as u64).saturating_sub(pos);
1000                if split_pos < pkt.len() {
1001                    let (a, b) = pkt.split_at(split_pos);
1002
1003                    let transferred =
1004                        unsafe { a.transfer_data(file.data[(pos as usize)..].as_ptr().cast()) };
1005
1006                    core::mem::drop(node);
1007                    core::mem::drop(transferred);
1008                    b.error(mferr!(Position, Outside, Filesystem));
1009                } else {
1010                    let transferred =
1011                        unsafe { pkt.transfer_data(file.data[(pos as usize)..].as_ptr().cast()) };
1012                    core::mem::drop(node);
1013                    core::mem::drop(transferred);
1014                }
1015            } else {
1016                core::mem::drop(node);
1017                pkt.error(mferr!(Position, Outside, Filesystem));
1018            }
1019        } else {
1020            pkt.error(mferr!(Io, PermissionDenied, Filesystem));
1021        }
1022    }
1023}
1024
1025impl PacketIo<Read, u64> for VirtFile {
1026    fn send_io(&self, pos: u64, pkt: BoundPacketView<Read>) {
1027        if self.options.write {
1028            let mut node = self.inode.write();
1029
1030            let InodeData::File(file) = &mut node.entry else {
1031                core::mem::drop(node);
1032                pkt.error(mferr!(File, Unreadable, Filesystem));
1033                return;
1034            };
1035
1036            // TODO: we may want to have sparse files
1037            let needed_len = (pos + pkt.len()) as usize;
1038            if needed_len > file.data.len() {
1039                file.data.resize(needed_len, 0);
1040            }
1041
1042            let transferred =
1043                unsafe { pkt.transfer_data(file.data[(pos as usize)..].as_mut_ptr().cast()) };
1044
1045            core::mem::drop(node);
1046            core::mem::drop(transferred);
1047        } else {
1048            pkt.error(mferr!(Io, PermissionDenied, Filesystem));
1049        }
1050    }
1051}
1052
1053// Internal data structures:
1054
1055struct Inode {
1056    // handles to this Inode's arc. Therefore, we need to signify somehow that this inode was
1057    // already deleted, and that it cannot be relinked anywhere.
1058    id: Option<InodeId>,
1059    parent_link: Option<ParentLink>,
1060    entry: InodeData,
1061    metadata: Metadata,
1062}
1063
1064impl Inode {
1065    /// Walks the filesystem to reach a specified directory.
1066    ///
1067    /// Returns `None` if the resulting path, or given inode are not directories.
1068    fn walk_rel(
1069        mut cur_node: Shared<Inode>,
1070        root_dir: &Shared<Inode>,
1071        path: &Path,
1072        inodes: &Slab<Shared<Inode>>,
1073    ) -> Option<Shared<Inode>> {
1074        trace!("Walk rel: {path:?}");
1075
1076        let components = path.components();
1077
1078        for component in components {
1079            let inode = cur_node.read();
1080
1081            let next_node = match component {
1082                Component::RootDir => root_dir.clone(),
1083                Component::ParentDir => {
1084                    // If we are at the root, then going up means cycling back.
1085                    if let Some(link) = &inode.parent_link {
1086                        inodes.get(link.parent)?.clone()
1087                    } else {
1088                        continue;
1089                    }
1090                }
1091                Component::Normal(n) => {
1092                    #[cfg(feature = "std")]
1093                    let n = n.to_str()?;
1094                    #[cfg(not(feature = "std"))]
1095                    let n = core::str::from_utf8(n).ok()?;
1096
1097                    let InodeData::Dir(d) = &inode.entry else {
1098                        return None;
1099                    };
1100
1101                    trace!("Entries: {:?}", d.entries);
1102
1103                    let inode = *d.entries.get(n)?;
1104
1105                    inodes.get(inode)?.clone()
1106                }
1107                _ => continue,
1108            };
1109
1110            drop(inode);
1111            cur_node = next_node;
1112        }
1113
1114        Some(cur_node)
1115    }
1116
1117    fn path(mut cur_node: Shared<Inode>, inodes: &Slab<Shared<Inode>>) -> Option<PathBuf> {
1118        let mut segments = vec![];
1119
1120        loop {
1121            let cn = cur_node.read();
1122
1123            if let Some(link) = &cn.parent_link {
1124                segments.push(link.name.clone());
1125                let parent = inodes.get(link.parent)?.clone();
1126                drop(cn);
1127                cur_node = parent;
1128            } else {
1129                break;
1130            }
1131        }
1132
1133        let mut res = PathBuf::new();
1134
1135        while let Some(seg) = segments.pop() {
1136            res.push(&*seg);
1137        }
1138
1139        Some(res)
1140    }
1141}
1142
1143#[derive(Clone)]
1144struct ParentLink {
1145    name: Rc<str>,
1146    parent: InodeId,
1147}
1148
1149enum InodeData {
1150    File(VirtFileInner),
1151    Dir(VirtDirInner),
1152}
1153
1154impl InodeData {
1155    fn to_dir_entry(&self, name: Rc<str>) -> DirEntry {
1156        DirEntry {
1157            name: name.to_string(),
1158            ty: match self {
1159                Self::File(_) => FileType::File,
1160                Self::Dir(_) => FileType::Directory,
1161            },
1162        }
1163    }
1164}
1165
1166#[derive(Default)]
1167struct VirtDirInner {
1168    entries: BTreeMap<Rc<str>, InodeId>,
1169}
1170
1171struct VirtFileInner {
1172    // TODO: support sparse files
1173    data: Vec<u8>,
1174}
1175#[cfg(not(miri))]
1176macro_rules! test_suite {
1177    ($($tt:tt)*) => {
1178        #[cfg(test)]
1179        $crate::test_suite!($($tt)*);
1180    }
1181}
1182
1183// Walking directories is currently extremely slow. So let's not do O(n^2) tests for now.
1184#[cfg(miri)]
1185macro_rules! test_suite {
1186    ($($tt:tt)*) => {
1187        #[cfg(test)]
1188        $crate::test_suite_base!(
1189            $($tt)*,
1190            files_equal,
1191            files_equal_rel,
1192            dirs_equal,
1193            writes_equal,
1194            writes_equal_rel
1195        );
1196    }
1197}
1198
1199test_suite!(tests_default, |_, closure| {
1200    use super::VirtRt;
1201    #[cfg(not(miri))]
1202    let _ = ::env_logger::builder().is_test(true).try_init();
1203    let mut rt = VirtRt::build(
1204        CTX.dirs().iter().map(|v| v.as_str()),
1205        CTX.files().iter().map(|(n, d)| (n.as_str(), &d[..])),
1206    );
1207    let rt = staticify(&mut rt);
1208
1209    pub fn run<'a, Func: FnOnce(&'a VirtRt) -> F, F: Future>(
1210        fs: &'a mut VirtRt,
1211        func: Func,
1212    ) -> F::Output {
1213        fs.block_on(func(fs))
1214    }
1215
1216    run(rt, |rt| async move {
1217        let run = TestRun::assume_built(rt, ());
1218        closure(run).await
1219    });
1220});