1use crate::resource_accounting::FileSystemUsage;
2use crate::vfs::{
3 VfsError, VfsResult, VirtualDirEntry, VirtualFileSystem, VirtualStat, VirtualUtimeSpec,
4};
5use std::any::Any;
6use std::collections::BTreeSet;
7use std::path::{Component, Path};
8use std::time::{SystemTime, UNIX_EPOCH};
9
10pub trait MountedFileSystem: Any {
11 fn as_any(&self) -> &dyn Any;
12 fn as_any_mut(&mut self) -> &mut dyn Any;
13 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>>;
14 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>>;
15 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
16 let entries = self.read_dir(path)?;
17 if entries.len() > max_entries {
18 return Err(VfsError::new(
19 "ENOMEM",
20 format!(
21 "directory listing for '{path}' exceeds configured limit of {max_entries} entries"
22 ),
23 ));
24 }
25 Ok(entries)
26 }
27 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>>;
28 fn write_file(&mut self, path: &str, content: Vec<u8>) -> VfsResult<()>;
29 fn write_file_with_mode(
30 &mut self,
31 path: &str,
32 content: Vec<u8>,
33 mode: Option<u32>,
34 ) -> VfsResult<()> {
35 let _ = mode;
36 self.write_file(path, content)
37 }
38 fn create_file_exclusive(&mut self, path: &str, content: Vec<u8>) -> VfsResult<()> {
39 if self.exists(path) {
40 return Err(VfsError::new(
41 "EEXIST",
42 format!("file already exists, open '{path}'"),
43 ));
44 }
45 self.write_file(path, content)
46 }
47 fn create_file_exclusive_with_mode(
48 &mut self,
49 path: &str,
50 content: Vec<u8>,
51 mode: Option<u32>,
52 ) -> VfsResult<()> {
53 let _ = mode;
54 self.create_file_exclusive(path, content)
55 }
56 fn append_file(&mut self, path: &str, content: Vec<u8>) -> VfsResult<u64> {
57 let mut existing = self.read_file(path)?;
58 existing.extend_from_slice(&content);
59 let new_len = existing.len() as u64;
60 self.write_file(path, existing)?;
61 Ok(new_len)
62 }
63 fn create_dir(&mut self, path: &str) -> VfsResult<()>;
64 fn create_dir_with_mode(&mut self, path: &str, mode: Option<u32>) -> VfsResult<()> {
65 let _ = mode;
66 self.create_dir(path)
67 }
68 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()>;
69 fn mkdir_with_mode(&mut self, path: &str, recursive: bool, mode: Option<u32>) -> VfsResult<()> {
70 let _ = mode;
71 self.mkdir(path, recursive)
72 }
73 fn exists(&self, path: &str) -> bool;
74 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat>;
75 fn remove_file(&mut self, path: &str) -> VfsResult<()>;
76 fn remove_dir(&mut self, path: &str) -> VfsResult<()>;
77 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()>;
78 fn realpath(&self, path: &str) -> VfsResult<String>;
79 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()>;
80 fn read_link(&self, path: &str) -> VfsResult<String>;
81 fn lstat(&self, path: &str) -> VfsResult<VirtualStat>;
82 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()>;
83 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()>;
84 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()>;
85 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()>;
86 fn utimes_spec(
87 &mut self,
88 path: &str,
89 atime: VirtualUtimeSpec,
90 mtime: VirtualUtimeSpec,
91 follow_symlinks: bool,
92 ) -> VfsResult<()> {
93 if !follow_symlinks {
94 return Err(VfsError::unsupported(format!(
95 "lutimes is not supported for mount path '{path}'"
96 )));
97 }
98 let existing = match (atime, mtime) {
99 (VirtualUtimeSpec::Omit, _) | (_, VirtualUtimeSpec::Omit) => Some(self.stat(path)?),
100 _ => None,
101 };
102 let now_ms = SystemTime::now()
103 .duration_since(UNIX_EPOCH)
104 .unwrap_or_default()
105 .as_millis() as u64;
106 let atime_ms = match atime {
107 VirtualUtimeSpec::Set(spec) => spec.to_truncated_millis()?,
108 VirtualUtimeSpec::Now => now_ms,
109 VirtualUtimeSpec::Omit => {
110 existing
111 .as_ref()
112 .ok_or_else(|| {
113 VfsError::new("EINVAL", "UTIME_OMIT requires existing metadata")
114 })?
115 .atime_ms
116 }
117 };
118 let mtime_ms = match mtime {
119 VirtualUtimeSpec::Set(spec) => spec.to_truncated_millis()?,
120 VirtualUtimeSpec::Now => now_ms,
121 VirtualUtimeSpec::Omit => {
122 existing
123 .as_ref()
124 .ok_or_else(|| {
125 VfsError::new("EINVAL", "UTIME_OMIT requires existing metadata")
126 })?
127 .mtime_ms
128 }
129 };
130 self.utimes(path, atime_ms, mtime_ms)
131 }
132 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()>;
133 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>>;
134 fn shutdown(&mut self) -> VfsResult<()> {
135 Ok(())
136 }
137}
138
139pub struct MountedVirtualFileSystem<F> {
140 inner: F,
141}
142
143impl<F> MountedVirtualFileSystem<F> {
144 pub fn new(inner: F) -> Self {
145 Self { inner }
146 }
147
148 pub fn inner(&self) -> &F {
149 &self.inner
150 }
151
152 pub fn inner_mut(&mut self) -> &mut F {
153 &mut self.inner
154 }
155}
156
157impl<F> MountedFileSystem for MountedVirtualFileSystem<F>
158where
159 F: VirtualFileSystem + 'static,
160{
161 fn as_any(&self) -> &dyn Any {
162 self
163 }
164
165 fn as_any_mut(&mut self) -> &mut dyn Any {
166 self
167 }
168
169 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
170 VirtualFileSystem::read_file(&mut self.inner, path)
171 }
172
173 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
174 VirtualFileSystem::read_dir(&mut self.inner, path)
175 }
176
177 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
178 VirtualFileSystem::read_dir_limited(&mut self.inner, path, max_entries)
179 }
180
181 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
182 VirtualFileSystem::read_dir_with_types(&mut self.inner, path)
183 }
184
185 fn write_file(&mut self, path: &str, content: Vec<u8>) -> VfsResult<()> {
186 VirtualFileSystem::write_file(&mut self.inner, path, content)
187 }
188
189 fn write_file_with_mode(
190 &mut self,
191 path: &str,
192 content: Vec<u8>,
193 mode: Option<u32>,
194 ) -> VfsResult<()> {
195 VirtualFileSystem::write_file_with_mode(&mut self.inner, path, content, mode)
196 }
197
198 fn create_file_exclusive(&mut self, path: &str, content: Vec<u8>) -> VfsResult<()> {
199 VirtualFileSystem::create_file_exclusive(&mut self.inner, path, content)
200 }
201
202 fn create_file_exclusive_with_mode(
203 &mut self,
204 path: &str,
205 content: Vec<u8>,
206 mode: Option<u32>,
207 ) -> VfsResult<()> {
208 VirtualFileSystem::create_file_exclusive_with_mode(&mut self.inner, path, content, mode)
209 }
210
211 fn append_file(&mut self, path: &str, content: Vec<u8>) -> VfsResult<u64> {
212 VirtualFileSystem::append_file(&mut self.inner, path, content)
213 }
214
215 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
216 VirtualFileSystem::create_dir(&mut self.inner, path)
217 }
218
219 fn create_dir_with_mode(&mut self, path: &str, mode: Option<u32>) -> VfsResult<()> {
220 VirtualFileSystem::create_dir_with_mode(&mut self.inner, path, mode)
221 }
222
223 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> {
224 VirtualFileSystem::mkdir(&mut self.inner, path, recursive)
225 }
226
227 fn mkdir_with_mode(&mut self, path: &str, recursive: bool, mode: Option<u32>) -> VfsResult<()> {
228 VirtualFileSystem::mkdir_with_mode(&mut self.inner, path, recursive, mode)
229 }
230
231 fn exists(&self, path: &str) -> bool {
232 VirtualFileSystem::exists(&self.inner, path)
233 }
234
235 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
236 VirtualFileSystem::stat(&mut self.inner, path)
237 }
238
239 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
240 VirtualFileSystem::remove_file(&mut self.inner, path)
241 }
242
243 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
244 VirtualFileSystem::remove_dir(&mut self.inner, path)
245 }
246
247 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
248 VirtualFileSystem::rename(&mut self.inner, old_path, new_path)
249 }
250
251 fn realpath(&self, path: &str) -> VfsResult<String> {
252 VirtualFileSystem::realpath(&self.inner, path)
253 }
254
255 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> {
256 VirtualFileSystem::symlink(&mut self.inner, target, link_path)
257 }
258
259 fn read_link(&self, path: &str) -> VfsResult<String> {
260 VirtualFileSystem::read_link(&self.inner, path)
261 }
262
263 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
264 VirtualFileSystem::lstat(&self.inner, path)
265 }
266
267 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
268 VirtualFileSystem::link(&mut self.inner, old_path, new_path)
269 }
270
271 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> {
272 VirtualFileSystem::chmod(&mut self.inner, path, mode)
273 }
274
275 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
276 VirtualFileSystem::chown(&mut self.inner, path, uid, gid)
277 }
278
279 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> {
280 VirtualFileSystem::utimes(&mut self.inner, path, atime_ms, mtime_ms)
281 }
282
283 fn utimes_spec(
284 &mut self,
285 path: &str,
286 atime: VirtualUtimeSpec,
287 mtime: VirtualUtimeSpec,
288 follow_symlinks: bool,
289 ) -> VfsResult<()> {
290 VirtualFileSystem::utimes_spec(&mut self.inner, path, atime, mtime, follow_symlinks)
291 }
292
293 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> {
294 VirtualFileSystem::truncate(&mut self.inner, path, length)
295 }
296
297 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
298 VirtualFileSystem::pread(&mut self.inner, path, offset, length)
299 }
300}
301
302impl<T> MountedFileSystem for Box<T>
303where
304 T: MountedFileSystem + ?Sized + 'static,
305{
306 fn as_any(&self) -> &dyn Any {
307 (**self).as_any()
308 }
309
310 fn as_any_mut(&mut self) -> &mut dyn Any {
311 (**self).as_any_mut()
312 }
313
314 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
315 (**self).read_file(path)
316 }
317
318 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
319 (**self).read_dir(path)
320 }
321
322 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
323 (**self).read_dir_limited(path, max_entries)
324 }
325
326 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
327 (**self).read_dir_with_types(path)
328 }
329
330 fn write_file(&mut self, path: &str, content: Vec<u8>) -> VfsResult<()> {
331 (**self).write_file(path, content)
332 }
333
334 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
335 (**self).create_dir(path)
336 }
337
338 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> {
339 (**self).mkdir(path, recursive)
340 }
341
342 fn exists(&self, path: &str) -> bool {
343 (**self).exists(path)
344 }
345
346 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
347 (**self).stat(path)
348 }
349
350 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
351 (**self).remove_file(path)
352 }
353
354 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
355 (**self).remove_dir(path)
356 }
357
358 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
359 (**self).rename(old_path, new_path)
360 }
361
362 fn realpath(&self, path: &str) -> VfsResult<String> {
363 (**self).realpath(path)
364 }
365
366 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> {
367 (**self).symlink(target, link_path)
368 }
369
370 fn read_link(&self, path: &str) -> VfsResult<String> {
371 (**self).read_link(path)
372 }
373
374 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
375 (**self).lstat(path)
376 }
377
378 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
379 (**self).link(old_path, new_path)
380 }
381
382 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> {
383 (**self).chmod(path, mode)
384 }
385
386 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
387 (**self).chown(path, uid, gid)
388 }
389
390 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> {
391 (**self).utimes(path, atime_ms, mtime_ms)
392 }
393
394 fn utimes_spec(
395 &mut self,
396 path: &str,
397 atime: VirtualUtimeSpec,
398 mtime: VirtualUtimeSpec,
399 follow_symlinks: bool,
400 ) -> VfsResult<()> {
401 (**self).utimes_spec(path, atime, mtime, follow_symlinks)
402 }
403
404 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> {
405 (**self).truncate(path, length)
406 }
407
408 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
409 (**self).pread(path, offset, length)
410 }
411
412 fn shutdown(&mut self) -> VfsResult<()> {
413 (**self).shutdown()
414 }
415}
416
417pub struct ReadOnlyFileSystem<F> {
418 inner: F,
419}
420
421impl<F> ReadOnlyFileSystem<F> {
422 pub fn new(inner: F) -> Self {
423 Self { inner }
424 }
425}
426
427impl<F> MountedFileSystem for ReadOnlyFileSystem<F>
428where
429 F: MountedFileSystem + 'static,
430{
431 fn as_any(&self) -> &dyn Any {
432 self
433 }
434
435 fn as_any_mut(&mut self) -> &mut dyn Any {
436 self
437 }
438
439 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
440 self.inner.read_file(path)
441 }
442
443 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
444 self.inner.read_dir(path)
445 }
446
447 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
448 self.inner.read_dir_limited(path, max_entries)
449 }
450
451 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
452 self.inner.read_dir_with_types(path)
453 }
454
455 fn write_file(&mut self, path: &str, _content: Vec<u8>) -> VfsResult<()> {
456 Err(VfsError::new(
457 "EROFS",
458 format!("read-only filesystem: {path}"),
459 ))
460 }
461
462 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
463 Err(VfsError::new(
464 "EROFS",
465 format!("read-only filesystem: {path}"),
466 ))
467 }
468
469 fn mkdir(&mut self, path: &str, _recursive: bool) -> VfsResult<()> {
470 Err(VfsError::new(
471 "EROFS",
472 format!("read-only filesystem: {path}"),
473 ))
474 }
475
476 fn exists(&self, path: &str) -> bool {
477 self.inner.exists(path)
478 }
479
480 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
481 self.inner.stat(path)
482 }
483
484 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
485 Err(VfsError::new(
486 "EROFS",
487 format!("read-only filesystem: {path}"),
488 ))
489 }
490
491 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
492 Err(VfsError::new(
493 "EROFS",
494 format!("read-only filesystem: {path}"),
495 ))
496 }
497
498 fn rename(&mut self, old_path: &str, _new_path: &str) -> VfsResult<()> {
499 Err(VfsError::new(
500 "EROFS",
501 format!("read-only filesystem: {old_path}"),
502 ))
503 }
504
505 fn realpath(&self, path: &str) -> VfsResult<String> {
506 self.inner.realpath(path)
507 }
508
509 fn symlink(&mut self, _target: &str, link_path: &str) -> VfsResult<()> {
510 Err(VfsError::new(
511 "EROFS",
512 format!("read-only filesystem: {link_path}"),
513 ))
514 }
515
516 fn read_link(&self, path: &str) -> VfsResult<String> {
517 self.inner.read_link(path)
518 }
519
520 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
521 self.inner.lstat(path)
522 }
523
524 fn link(&mut self, _old_path: &str, new_path: &str) -> VfsResult<()> {
525 Err(VfsError::new(
526 "EROFS",
527 format!("read-only filesystem: {new_path}"),
528 ))
529 }
530
531 fn chmod(&mut self, path: &str, _mode: u32) -> VfsResult<()> {
532 Err(VfsError::new(
533 "EROFS",
534 format!("read-only filesystem: {path}"),
535 ))
536 }
537
538 fn chown(&mut self, path: &str, _uid: u32, _gid: u32) -> VfsResult<()> {
539 Err(VfsError::new(
540 "EROFS",
541 format!("read-only filesystem: {path}"),
542 ))
543 }
544
545 fn utimes(&mut self, path: &str, _atime_ms: u64, _mtime_ms: u64) -> VfsResult<()> {
546 Err(VfsError::new(
547 "EROFS",
548 format!("read-only filesystem: {path}"),
549 ))
550 }
551
552 fn utimes_spec(
553 &mut self,
554 path: &str,
555 _atime: VirtualUtimeSpec,
556 _mtime: VirtualUtimeSpec,
557 _follow_symlinks: bool,
558 ) -> VfsResult<()> {
559 Err(VfsError::new(
560 "EROFS",
561 format!("read-only filesystem: {path}"),
562 ))
563 }
564
565 fn truncate(&mut self, path: &str, _length: u64) -> VfsResult<()> {
566 Err(VfsError::new(
567 "EROFS",
568 format!("read-only filesystem: {path}"),
569 ))
570 }
571
572 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
573 self.inner.pread(path, offset, length)
574 }
575
576 fn shutdown(&mut self) -> VfsResult<()> {
577 self.inner.shutdown()
578 }
579}
580
581#[derive(Debug, Clone, PartialEq, Eq)]
582pub struct MountEntry {
583 pub path: String,
584 pub plugin_id: String,
585 pub read_only: bool,
586}
587
588#[derive(Debug, Clone, PartialEq, Eq)]
589pub struct MountOptions {
590 pub plugin_id: String,
591 pub read_only: bool,
592}
593
594impl MountOptions {
595 pub fn new(plugin_id: impl Into<String>) -> Self {
596 Self {
597 plugin_id: plugin_id.into(),
598 read_only: false,
599 }
600 }
601
602 pub fn read_only(mut self, read_only: bool) -> Self {
603 self.read_only = read_only;
604 self
605 }
606}
607
608struct MountRegistration {
609 path: String,
610 plugin_id: String,
611 read_only: bool,
612 filesystem: Box<dyn MountedFileSystem>,
613}
614
615pub struct MountTable {
616 mounts: Vec<MountRegistration>,
617}
618
619impl MountTable {
620 pub fn new(root_fs: impl VirtualFileSystem + 'static) -> Self {
621 Self {
622 mounts: vec![MountRegistration {
623 path: String::from("/"),
624 plugin_id: String::from("root"),
625 read_only: false,
626 filesystem: Box::new(MountedVirtualFileSystem::new(root_fs)),
627 }],
628 }
629 }
630
631 pub fn mount(
632 &mut self,
633 path: &str,
634 filesystem: impl VirtualFileSystem + 'static,
635 options: MountOptions,
636 ) -> VfsResult<()> {
637 self.mount_boxed(
638 path,
639 Box::new(MountedVirtualFileSystem::new(filesystem)),
640 options,
641 )
642 }
643
644 pub fn mount_boxed(
645 &mut self,
646 path: &str,
647 filesystem: Box<dyn MountedFileSystem>,
648 options: MountOptions,
649 ) -> VfsResult<()> {
650 let normalized = normalize_path(path);
651 if normalized == "/" {
652 return Err(VfsError::new("EINVAL", "cannot mount over root"));
653 }
654 if self.mounts.iter().any(|mount| mount.path == normalized) {
655 return Err(VfsError::new(
656 "EEXIST",
657 format!("already mounted at {normalized}"),
658 ));
659 }
660
661 let (parent_index, relative_path) = self.resolve_index(&normalized)?;
662 let parent_mount = &mut self.mounts[parent_index];
663 if !parent_mount.filesystem.exists(&relative_path) {
664 let _ = parent_mount.filesystem.mkdir(&relative_path, true);
665 }
666
667 let filesystem = if options.read_only {
668 Box::new(ReadOnlyFileSystem::new(filesystem)) as Box<dyn MountedFileSystem>
669 } else {
670 filesystem
671 };
672
673 self.mounts.push(MountRegistration {
674 path: normalized,
675 plugin_id: options.plugin_id,
676 read_only: options.read_only,
677 filesystem,
678 });
679 self.mounts
680 .sort_by(|left, right| right.path.len().cmp(&left.path.len()));
681 Ok(())
682 }
683
684 pub fn unmount(&mut self, path: &str) -> VfsResult<()> {
685 let normalized = normalize_path(path);
686 if normalized == "/" {
687 return Err(VfsError::new("EINVAL", "cannot unmount root"));
688 }
689
690 let child_mount_prefix = format!("{normalized}/");
691 if self
692 .mounts
693 .iter()
694 .any(|mount| mount.path.starts_with(&child_mount_prefix))
695 {
696 return Err(VfsError::new(
697 "EBUSY",
698 format!("mount point has child mounts: {normalized}"),
699 ));
700 }
701
702 let Some(index) = self
703 .mounts
704 .iter()
705 .position(|mount| mount.path == normalized)
706 else {
707 return Err(VfsError::new(
708 "EINVAL",
709 format!("not a mount point: {normalized}"),
710 ));
711 };
712
713 let mut mount = self.mounts.remove(index);
714 mount.filesystem.shutdown()?;
715 Ok(())
716 }
717
718 pub fn get_mounts(&self) -> Vec<MountEntry> {
719 self.mounts
720 .iter()
721 .map(|mount| MountEntry {
722 path: mount.path.clone(),
723 plugin_id: mount.plugin_id.clone(),
724 read_only: mount.read_only,
725 })
726 .collect()
727 }
728
729 pub fn root_virtual_filesystem_mut<T: VirtualFileSystem + 'static>(
730 &mut self,
731 ) -> Option<&mut T> {
732 let root = self.mounts.iter_mut().find(|mount| mount.path == "/")?;
733 root.filesystem
734 .as_any_mut()
735 .downcast_mut::<MountedVirtualFileSystem<T>>()
736 .map(MountedVirtualFileSystem::inner_mut)
737 }
738
739 pub fn root_usage(&mut self) -> VfsResult<FileSystemUsage> {
740 let root = self
741 .mounts
742 .iter_mut()
743 .find(|mount| mount.path == "/")
744 .ok_or_else(|| VfsError::new("ENOENT", "missing root mount"))?;
745 measure_mounted_filesystem_usage(root.filesystem.as_mut(), "/", &mut BTreeSet::new())
746 }
747
748 fn resolve_index(&self, full_path: &str) -> VfsResult<(usize, String)> {
749 let normalized = normalize_path(full_path);
750 for (index, mount) in self.mounts.iter().enumerate() {
751 if mount.path == "/" {
752 return Ok((index, normalized));
753 }
754 if normalized == mount.path {
755 return Ok((index, String::from("/")));
756 }
757 if normalized.starts_with(&format!("{}/", mount.path)) {
758 let suffix = normalized
759 .trim_start_matches(&mount.path)
760 .trim_start_matches('/');
761 return Ok((index, format!("/{suffix}")));
762 }
763 }
764
765 Err(VfsError::new(
766 "ENOENT",
767 format!("no such file or directory, resolve '{full_path}'"),
768 ))
769 }
770
771 fn child_mount_basenames(&self, path: &str) -> Vec<String> {
772 let normalized = normalize_path(path);
773 let mut basenames = BTreeSet::new();
774 for mount in &self.mounts {
775 if mount.path == "/" || mount.path == normalized {
776 continue;
777 }
778
779 if parent_path(&mount.path) == normalized {
780 basenames.insert(basename(&mount.path));
781 }
782 }
783 basenames.into_iter().collect()
784 }
785}
786
787fn measure_mounted_filesystem_usage(
788 filesystem: &mut dyn MountedFileSystem,
789 path: &str,
790 visited: &mut BTreeSet<u64>,
791) -> VfsResult<FileSystemUsage> {
792 let stat = filesystem.lstat(path)?;
793 let mut usage = FileSystemUsage::default();
794
795 if visited.insert(stat.ino) {
796 usage.inode_count += 1;
797 if !stat.is_directory {
798 usage.total_bytes = usage.total_bytes.saturating_add(stat.size);
799 }
800 }
801
802 if !stat.is_directory || stat.is_symbolic_link {
803 return Ok(usage);
804 }
805
806 for entry in filesystem.read_dir_with_types(path)? {
807 if matches!(entry.name.as_str(), "." | "..") {
808 continue;
809 }
810
811 let child_path = if path == "/" {
812 format!("/{}", entry.name)
813 } else {
814 format!("{path}/{}", entry.name)
815 };
816 let child_usage = measure_mounted_filesystem_usage(filesystem, &child_path, visited)?;
817 usage.total_bytes = usage.total_bytes.saturating_add(child_usage.total_bytes);
818 usage.inode_count = usage.inode_count.saturating_add(child_usage.inode_count);
819 }
820
821 Ok(usage)
822}
823
824impl Drop for MountTable {
825 fn drop(&mut self) {
826 for mount in self.mounts.iter_mut().rev() {
827 let _ = mount.filesystem.shutdown();
828 }
829 }
830}
831
832impl VirtualFileSystem for MountTable {
833 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
834 let (index, relative_path) = self.resolve_index(path)?;
835 self.mounts[index].filesystem.read_file(&relative_path)
836 }
837
838 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
839 let normalized = normalize_path(path);
840 let (index, relative_path) = self.resolve_index(&normalized)?;
841 let mut entries = self.mounts[index].filesystem.read_dir(&relative_path)?;
842 let child_mounts = self.child_mount_basenames(&normalized);
843 if child_mounts.is_empty() {
844 return Ok(entries);
845 }
846
847 let mut merged = BTreeSet::new();
848 merged.extend(entries.drain(..));
849 merged.extend(child_mounts);
850 Ok(merged.into_iter().collect())
851 }
852
853 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
854 let normalized = normalize_path(path);
855 let (index, relative_path) = self.resolve_index(&normalized)?;
856 let mut entries = self.mounts[index]
857 .filesystem
858 .read_dir_limited(&relative_path, max_entries)?;
859 let child_mounts = self.child_mount_basenames(&normalized);
860 if child_mounts.is_empty() {
861 return Ok(entries);
862 }
863
864 let mut merged = BTreeSet::new();
865 merged.extend(entries.drain(..));
866 merged.extend(child_mounts);
867 if merged.len() > max_entries {
868 return Err(VfsError::new(
869 "ENOMEM",
870 format!(
871 "directory listing for '{path}' exceeds configured limit of {max_entries} entries"
872 ),
873 ));
874 }
875 Ok(merged.into_iter().collect())
876 }
877
878 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
879 let normalized = normalize_path(path);
880 let (index, relative_path) = self.resolve_index(&normalized)?;
881 let mut entries = self.mounts[index]
882 .filesystem
883 .read_dir_with_types(&relative_path)?;
884 let child_mounts = self.child_mount_basenames(&normalized);
885 if child_mounts.is_empty() {
886 return Ok(entries);
887 }
888
889 let existing = entries
890 .iter()
891 .map(|entry| entry.name.clone())
892 .collect::<BTreeSet<_>>();
893 for mount_name in child_mounts {
894 if existing.contains(&mount_name) {
895 continue;
896 }
897 entries.push(VirtualDirEntry {
898 name: mount_name,
899 is_directory: true,
900 is_symbolic_link: false,
901 });
902 }
903 Ok(entries)
904 }
905
906 fn write_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
907 let (index, relative_path) = self.resolve_index(path)?;
908 self.mounts[index]
909 .filesystem
910 .write_file(&relative_path, content.into())
911 }
912
913 fn write_file_with_mode(
914 &mut self,
915 path: &str,
916 content: impl Into<Vec<u8>>,
917 mode: Option<u32>,
918 ) -> VfsResult<()> {
919 let (index, relative_path) = self.resolve_index(path)?;
920 self.mounts[index]
921 .filesystem
922 .write_file_with_mode(&relative_path, content.into(), mode)
923 }
924
925 fn create_file_exclusive(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
926 let (index, relative_path) = self.resolve_index(path)?;
927 self.mounts[index]
928 .filesystem
929 .create_file_exclusive(&relative_path, content.into())
930 }
931
932 fn create_file_exclusive_with_mode(
933 &mut self,
934 path: &str,
935 content: impl Into<Vec<u8>>,
936 mode: Option<u32>,
937 ) -> VfsResult<()> {
938 let (index, relative_path) = self.resolve_index(path)?;
939 self.mounts[index]
940 .filesystem
941 .create_file_exclusive_with_mode(&relative_path, content.into(), mode)
942 }
943
944 fn append_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<u64> {
945 let (index, relative_path) = self.resolve_index(path)?;
946 self.mounts[index]
947 .filesystem
948 .append_file(&relative_path, content.into())
949 }
950
951 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
952 let (index, relative_path) = self.resolve_index(path)?;
953 self.mounts[index].filesystem.create_dir(&relative_path)
954 }
955
956 fn create_dir_with_mode(&mut self, path: &str, mode: Option<u32>) -> VfsResult<()> {
957 let (index, relative_path) = self.resolve_index(path)?;
958 self.mounts[index]
959 .filesystem
960 .create_dir_with_mode(&relative_path, mode)
961 }
962
963 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> {
964 let (index, relative_path) = self.resolve_index(path)?;
965 self.mounts[index]
966 .filesystem
967 .mkdir(&relative_path, recursive)
968 }
969
970 fn mkdir_with_mode(&mut self, path: &str, recursive: bool, mode: Option<u32>) -> VfsResult<()> {
971 let (index, relative_path) = self.resolve_index(path)?;
972 self.mounts[index]
973 .filesystem
974 .mkdir_with_mode(&relative_path, recursive, mode)
975 }
976
977 fn exists(&self, path: &str) -> bool {
978 self.resolve_index(path)
979 .map(|(index, relative_path)| self.mounts[index].filesystem.exists(&relative_path))
980 .unwrap_or(false)
981 }
982
983 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
984 let (index, relative_path) = self.resolve_index(path)?;
985 self.mounts[index].filesystem.stat(&relative_path)
986 }
987
988 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
989 let (index, relative_path) = self.resolve_index(path)?;
990 self.mounts[index].filesystem.remove_file(&relative_path)
991 }
992
993 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
994 let (index, relative_path) = self.resolve_index(path)?;
995 self.mounts[index].filesystem.remove_dir(&relative_path)
996 }
997
998 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
999 let (old_index, old_relative_path) = self.resolve_index(old_path)?;
1000 let (new_index, new_relative_path) = self.resolve_index(new_path)?;
1001 if old_index != new_index {
1002 return Err(VfsError::new(
1003 "EXDEV",
1004 format!("rename across mounts: {old_path} -> {new_path}"),
1005 ));
1006 }
1007
1008 self.mounts[old_index]
1009 .filesystem
1010 .rename(&old_relative_path, &new_relative_path)
1011 }
1012
1013 fn realpath(&self, path: &str) -> VfsResult<String> {
1014 let (index, relative_path) = self.resolve_index(path)?;
1015 let mount = &self.mounts[index];
1016 let resolved = mount.filesystem.realpath(&relative_path)?;
1017 if mount.path == "/" {
1018 return Ok(resolved);
1019 }
1020 if resolved == "/" {
1021 return Ok(mount.path.clone());
1022 }
1023 Ok(format!(
1024 "{}/{}",
1025 mount.path.trim_end_matches('/'),
1026 resolved.trim_start_matches('/')
1027 ))
1028 }
1029
1030 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> {
1031 let normalized_link_path = normalize_path(link_path);
1032 let link_parent = parent_path(&normalized_link_path);
1033 let absolute_target = if target.starts_with('/') {
1034 normalize_path(target)
1035 } else {
1036 normalize_path(&format!("{link_parent}/{target}"))
1037 };
1038
1039 let (index, relative_path) = self.resolve_index(&normalized_link_path)?;
1040 let (target_index, _) = self.resolve_index(&absolute_target)?;
1041 if index != target_index {
1042 return Err(VfsError::new(
1043 "EXDEV",
1044 format!("symlink across mounts: {link_path} -> {target}"),
1045 ));
1046 }
1047
1048 self.mounts[index]
1049 .filesystem
1050 .symlink(target, &relative_path)
1051 }
1052
1053 fn read_link(&self, path: &str) -> VfsResult<String> {
1054 let (index, relative_path) = self.resolve_index(path)?;
1055 self.mounts[index].filesystem.read_link(&relative_path)
1056 }
1057
1058 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
1059 let (index, relative_path) = self.resolve_index(path)?;
1060 self.mounts[index].filesystem.lstat(&relative_path)
1061 }
1062
1063 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
1064 let (old_index, old_relative_path) = self.resolve_index(old_path)?;
1065 let (new_index, new_relative_path) = self.resolve_index(new_path)?;
1066 if old_index != new_index {
1067 return Err(VfsError::new(
1068 "EXDEV",
1069 format!("link across mounts: {old_path} -> {new_path}"),
1070 ));
1071 }
1072
1073 self.mounts[old_index]
1074 .filesystem
1075 .link(&old_relative_path, &new_relative_path)
1076 }
1077
1078 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> {
1079 let (index, relative_path) = self.resolve_index(path)?;
1080 self.mounts[index].filesystem.chmod(&relative_path, mode)
1081 }
1082
1083 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
1084 let (index, relative_path) = self.resolve_index(path)?;
1085 self.mounts[index]
1086 .filesystem
1087 .chown(&relative_path, uid, gid)
1088 }
1089
1090 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> {
1091 let (index, relative_path) = self.resolve_index(path)?;
1092 self.mounts[index]
1093 .filesystem
1094 .utimes(&relative_path, atime_ms, mtime_ms)
1095 }
1096
1097 fn utimes_spec(
1098 &mut self,
1099 path: &str,
1100 atime: VirtualUtimeSpec,
1101 mtime: VirtualUtimeSpec,
1102 follow_symlinks: bool,
1103 ) -> VfsResult<()> {
1104 let (index, relative_path) = self.resolve_index(path)?;
1105 self.mounts[index]
1106 .filesystem
1107 .utimes_spec(&relative_path, atime, mtime, follow_symlinks)
1108 }
1109
1110 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> {
1111 let (index, relative_path) = self.resolve_index(path)?;
1112 self.mounts[index]
1113 .filesystem
1114 .truncate(&relative_path, length)
1115 }
1116
1117 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
1118 let (index, relative_path) = self.resolve_index(path)?;
1119 self.mounts[index]
1120 .filesystem
1121 .pread(&relative_path, offset, length)
1122 }
1123}
1124
1125fn normalize_path(path: &str) -> String {
1126 let mut segments = Vec::new();
1127 for component in Path::new(path).components() {
1128 match component {
1129 Component::RootDir => segments.clear(),
1130 Component::ParentDir => {
1131 segments.pop();
1132 }
1133 Component::CurDir => {}
1134 Component::Normal(value) => segments.push(value.to_string_lossy().into_owned()),
1135 Component::Prefix(prefix) => {
1136 segments.push(prefix.as_os_str().to_string_lossy().into_owned());
1137 }
1138 }
1139 }
1140
1141 if segments.is_empty() {
1142 String::from("/")
1143 } else {
1144 format!("/{}", segments.join("/"))
1145 }
1146}
1147
1148fn parent_path(path: &str) -> String {
1149 let normalized = normalize_path(path);
1150 let parent = Path::new(&normalized)
1151 .parent()
1152 .unwrap_or_else(|| Path::new("/"));
1153 let value = parent.to_string_lossy();
1154 if value.is_empty() {
1155 String::from("/")
1156 } else {
1157 value.into_owned()
1158 }
1159}
1160
1161fn basename(path: &str) -> String {
1162 let normalized = normalize_path(path);
1163 Path::new(&normalized)
1164 .file_name()
1165 .map(|name| name.to_string_lossy().into_owned())
1166 .unwrap_or_else(|| String::from("/"))
1167}