1use 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 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 .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 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 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 let inode = inodes
208 .get(id)
209 .ok_or(mferr!(Entry, NotFound, Filesystem))?
210 .clone();
211
212 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 node.metadata.permissions = permissions;
246 Ok(())
248 }
249
250 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 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 {
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 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 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 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 metadata: from.metadata.clone(),
636 })));
637
638 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 fn path(&self) -> Self::PathFuture<'_> {
657 ready(self.get_path())
658 }
659
660 fn read_dir(&self) -> Self::ReadDirFuture<'_> {
667 ready(Ok(ReadDir {
668 dir: self,
669 cur: None,
670 }))
671 }
672
673 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 fn open_dir<'a, P: AsRef<Path> + ?Sized>(&'a self, path: &'a P) -> Self::OpenDirFuture<'a> {
694 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 .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 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
744pub 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 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
831pub struct VirtFs {
834 inodes: RwLock<Slab<Shared<Inode>>>,
835 root_dir: Shared<Inode>,
836}
837
838impl VirtFs {
839 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 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 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
980pub 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 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
1053struct Inode {
1056 id: Option<InodeId>,
1059 parent_link: Option<ParentLink>,
1060 entry: InodeData,
1061 metadata: Metadata,
1062}
1063
1064impl Inode {
1065 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 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 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#[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});