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