1use super::root_fs::RootFileSystem;
2use super::usage::FileSystemUsage;
3use super::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 let mount_prefix = format!("{}/", mount.path);
824 if let Some(suffix) = normalized.strip_prefix(&mount_prefix) {
825 return Ok((index, format!("/{suffix}")));
830 }
831 }
832
833 Err(VfsError::new(
834 "ENOENT",
835 format!("no such file or directory, resolve '{full_path}'"),
836 ))
837 }
838
839 fn child_mount_basenames(&self, path: &str) -> Vec<String> {
840 let normalized = normalize_path(path);
841 let mut basenames = BTreeSet::new();
842 for mount in &self.mounts {
843 if mount.path == "/" || mount.path == normalized {
844 continue;
845 }
846
847 if parent_path(&mount.path) == normalized {
848 basenames.insert(basename(&mount.path));
849 }
850 }
851 basenames.into_iter().collect()
852 }
853}
854
855fn measure_mounted_filesystem_usage(
856 filesystem: &mut dyn MountedFileSystem,
857 path: &str,
858 visited: &mut BTreeSet<u64>,
859) -> VfsResult<FileSystemUsage> {
860 let stat = filesystem.lstat(path)?;
861 let mut usage = FileSystemUsage::default();
862
863 if visited.insert(stat.ino) {
864 usage.inode_count += 1;
865 if !stat.is_directory {
866 usage.total_bytes = usage.total_bytes.saturating_add(stat.size);
867 }
868 }
869
870 if !stat.is_directory || stat.is_symbolic_link {
871 return Ok(usage);
872 }
873
874 for entry in filesystem.read_dir_with_types(path)? {
875 if matches!(entry.name.as_str(), "." | "..") {
876 continue;
877 }
878
879 let child_path = if path == "/" {
880 format!("/{}", entry.name)
881 } else {
882 format!("{path}/{}", entry.name)
883 };
884 let child_usage = measure_mounted_filesystem_usage(filesystem, &child_path, visited)?;
885 usage.total_bytes = usage.total_bytes.saturating_add(child_usage.total_bytes);
886 usage.inode_count = usage.inode_count.saturating_add(child_usage.inode_count);
887 }
888
889 Ok(usage)
890}
891
892impl Drop for MountTable {
893 fn drop(&mut self) {
894 for mount in self.mounts.iter_mut().rev() {
895 let _ = mount.filesystem.shutdown();
896 }
897 }
898}
899
900impl VirtualFileSystem for MountTable {
901 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
902 let (index, relative_path) = self.resolve_index(path)?;
903 self.mounts[index].filesystem.read_file(&relative_path)
904 }
905
906 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
907 let normalized = normalize_path(path);
908 let (index, relative_path) = self.resolve_index(&normalized)?;
909 let mut entries = self.mounts[index].filesystem.read_dir(&relative_path)?;
910 let child_mounts = self.child_mount_basenames(&normalized);
911 if child_mounts.is_empty() {
912 return Ok(entries);
913 }
914
915 let mut merged = BTreeSet::new();
916 merged.extend(entries.drain(..));
917 merged.extend(child_mounts);
918 Ok(merged.into_iter().collect())
919 }
920
921 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
922 let normalized = normalize_path(path);
923 let (index, relative_path) = self.resolve_index(&normalized)?;
924 let mut entries = self.mounts[index]
925 .filesystem
926 .read_dir_limited(&relative_path, max_entries)?;
927 let child_mounts = self.child_mount_basenames(&normalized);
928 if child_mounts.is_empty() {
929 return Ok(entries);
930 }
931
932 let mut merged = BTreeSet::new();
933 merged.extend(entries.drain(..));
934 merged.extend(child_mounts);
935 if merged.len() > max_entries {
936 return Err(VfsError::new(
937 "ENOMEM",
938 format!(
939 "directory listing for '{path}' exceeds configured limit of {max_entries} entries"
940 ),
941 ));
942 }
943 Ok(merged.into_iter().collect())
944 }
945
946 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
947 let normalized = normalize_path(path);
948 let (index, relative_path) = self.resolve_index(&normalized)?;
949 let mut entries = self.mounts[index]
950 .filesystem
951 .read_dir_with_types(&relative_path)?;
952 let child_mounts = self.child_mount_basenames(&normalized);
953 if child_mounts.is_empty() {
954 return Ok(entries);
955 }
956
957 let existing = entries
958 .iter()
959 .map(|entry| entry.name.clone())
960 .collect::<BTreeSet<_>>();
961 for mount_name in child_mounts {
962 if existing.contains(&mount_name) {
963 continue;
964 }
965 entries.push(VirtualDirEntry {
966 name: mount_name,
967 is_directory: true,
968 is_symbolic_link: false,
969 });
970 }
971 Ok(entries)
972 }
973
974 fn write_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
975 let (index, relative_path) = self.resolve_index(path)?;
976 self.mounts[index]
977 .filesystem
978 .write_file(&relative_path, content.into())
979 }
980
981 fn write_file_with_mode(
982 &mut self,
983 path: &str,
984 content: impl Into<Vec<u8>>,
985 mode: Option<u32>,
986 ) -> VfsResult<()> {
987 let (index, relative_path) = self.resolve_index(path)?;
988 self.mounts[index]
989 .filesystem
990 .write_file_with_mode(&relative_path, content.into(), mode)
991 }
992
993 fn create_file_exclusive(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
994 let (index, relative_path) = self.resolve_index(path)?;
995 self.mounts[index]
996 .filesystem
997 .create_file_exclusive(&relative_path, content.into())
998 }
999
1000 fn create_file_exclusive_with_mode(
1001 &mut self,
1002 path: &str,
1003 content: impl Into<Vec<u8>>,
1004 mode: Option<u32>,
1005 ) -> VfsResult<()> {
1006 let (index, relative_path) = self.resolve_index(path)?;
1007 self.mounts[index]
1008 .filesystem
1009 .create_file_exclusive_with_mode(&relative_path, content.into(), mode)
1010 }
1011
1012 fn append_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<u64> {
1013 let (index, relative_path) = self.resolve_index(path)?;
1014 self.mounts[index]
1015 .filesystem
1016 .append_file(&relative_path, content.into())
1017 }
1018
1019 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
1020 let (index, relative_path) = self.resolve_index(path)?;
1021 self.mounts[index].filesystem.create_dir(&relative_path)
1022 }
1023
1024 fn create_dir_with_mode(&mut self, path: &str, mode: Option<u32>) -> VfsResult<()> {
1025 let (index, relative_path) = self.resolve_index(path)?;
1026 self.mounts[index]
1027 .filesystem
1028 .create_dir_with_mode(&relative_path, mode)
1029 }
1030
1031 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> {
1032 let (index, relative_path) = self.resolve_index(path)?;
1033 self.mounts[index]
1034 .filesystem
1035 .mkdir(&relative_path, recursive)
1036 }
1037
1038 fn mkdir_with_mode(&mut self, path: &str, recursive: bool, mode: Option<u32>) -> VfsResult<()> {
1039 let (index, relative_path) = self.resolve_index(path)?;
1040 self.mounts[index]
1041 .filesystem
1042 .mkdir_with_mode(&relative_path, recursive, mode)
1043 }
1044
1045 fn exists(&self, path: &str) -> bool {
1046 self.resolve_index(path)
1047 .map(|(index, relative_path)| self.mounts[index].filesystem.exists(&relative_path))
1048 .unwrap_or(false)
1049 }
1050
1051 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
1052 let (index, relative_path) = self.resolve_index(path)?;
1053 self.mounts[index].filesystem.stat(&relative_path)
1054 }
1055
1056 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
1057 let (index, relative_path) = self.resolve_index(path)?;
1058 self.mounts[index].filesystem.remove_file(&relative_path)
1059 }
1060
1061 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
1062 let (index, relative_path) = self.resolve_index(path)?;
1063 self.mounts[index].filesystem.remove_dir(&relative_path)
1064 }
1065
1066 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
1067 let (old_index, old_relative_path) = self.resolve_index(old_path)?;
1068 let (new_index, new_relative_path) = self.resolve_index(new_path)?;
1069 if old_index != new_index {
1070 return Err(VfsError::new(
1071 "EXDEV",
1072 format!("rename across mounts: {old_path} -> {new_path}"),
1073 ));
1074 }
1075
1076 self.mounts[old_index]
1077 .filesystem
1078 .rename(&old_relative_path, &new_relative_path)
1079 }
1080
1081 fn realpath(&self, path: &str) -> VfsResult<String> {
1082 let (index, relative_path) = self.resolve_index(path)?;
1083 let mount = &self.mounts[index];
1084 let resolved = mount.filesystem.realpath(&relative_path)?;
1085 if mount.path == "/" {
1086 return Ok(resolved);
1087 }
1088 if resolved == "/" {
1089 return Ok(mount.path.clone());
1090 }
1091 Ok(format!(
1092 "{}/{}",
1093 mount.path.trim_end_matches('/'),
1094 resolved.trim_start_matches('/')
1095 ))
1096 }
1097
1098 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> {
1099 let normalized_link_path = normalize_path(link_path);
1100 let link_parent = parent_path(&normalized_link_path);
1101 let absolute_target = if target.starts_with('/') {
1102 normalize_path(target)
1103 } else {
1104 normalize_path(&format!("{link_parent}/{target}"))
1105 };
1106
1107 let (index, relative_path) = self.resolve_index(&normalized_link_path)?;
1108 let (target_index, _) = self.resolve_index(&absolute_target)?;
1109 if index != target_index {
1110 return Err(VfsError::new(
1111 "EXDEV",
1112 format!("symlink across mounts: {link_path} -> {target}"),
1113 ));
1114 }
1115
1116 self.mounts[index]
1117 .filesystem
1118 .symlink(target, &relative_path)
1119 }
1120
1121 fn read_link(&self, path: &str) -> VfsResult<String> {
1122 let (index, relative_path) = self.resolve_index(path)?;
1123 self.mounts[index].filesystem.read_link(&relative_path)
1124 }
1125
1126 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
1127 let (index, relative_path) = self.resolve_index(path)?;
1128 self.mounts[index].filesystem.lstat(&relative_path)
1129 }
1130
1131 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
1132 let (old_index, old_relative_path) = self.resolve_index(old_path)?;
1133 let (new_index, new_relative_path) = self.resolve_index(new_path)?;
1134 if old_index != new_index {
1135 return Err(VfsError::new(
1136 "EXDEV",
1137 format!("link across mounts: {old_path} -> {new_path}"),
1138 ));
1139 }
1140
1141 self.mounts[old_index]
1142 .filesystem
1143 .link(&old_relative_path, &new_relative_path)
1144 }
1145
1146 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> {
1147 let (index, relative_path) = self.resolve_index(path)?;
1148 self.mounts[index].filesystem.chmod(&relative_path, mode)
1149 }
1150
1151 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
1152 let (index, relative_path) = self.resolve_index(path)?;
1153 self.mounts[index]
1154 .filesystem
1155 .chown(&relative_path, uid, gid)
1156 }
1157
1158 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> {
1159 let (index, relative_path) = self.resolve_index(path)?;
1160 self.mounts[index]
1161 .filesystem
1162 .utimes(&relative_path, atime_ms, mtime_ms)
1163 }
1164
1165 fn utimes_spec(
1166 &mut self,
1167 path: &str,
1168 atime: VirtualUtimeSpec,
1169 mtime: VirtualUtimeSpec,
1170 follow_symlinks: bool,
1171 ) -> VfsResult<()> {
1172 let (index, relative_path) = self.resolve_index(path)?;
1173 self.mounts[index]
1174 .filesystem
1175 .utimes_spec(&relative_path, atime, mtime, follow_symlinks)
1176 }
1177
1178 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> {
1179 let (index, relative_path) = self.resolve_index(path)?;
1180 self.mounts[index]
1181 .filesystem
1182 .truncate(&relative_path, length)
1183 }
1184
1185 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
1186 let (index, relative_path) = self.resolve_index(path)?;
1187 self.mounts[index]
1188 .filesystem
1189 .pread(&relative_path, offset, length)
1190 }
1191}
1192
1193fn normalize_path(path: &str) -> String {
1194 let mut segments = Vec::new();
1195 for component in Path::new(path).components() {
1196 match component {
1197 Component::RootDir => segments.clear(),
1198 Component::ParentDir => {
1199 segments.pop();
1200 }
1201 Component::CurDir => {}
1202 Component::Normal(value) => segments.push(value.to_string_lossy().into_owned()),
1203 Component::Prefix(prefix) => {
1204 segments.push(prefix.as_os_str().to_string_lossy().into_owned());
1205 }
1206 }
1207 }
1208
1209 if segments.is_empty() {
1210 String::from("/")
1211 } else {
1212 format!("/{}", segments.join("/"))
1213 }
1214}
1215
1216fn parent_path(path: &str) -> String {
1217 let normalized = normalize_path(path);
1218 let parent = Path::new(&normalized)
1219 .parent()
1220 .unwrap_or_else(|| Path::new("/"));
1221 let value = parent.to_string_lossy();
1222 if value.is_empty() {
1223 String::from("/")
1224 } else {
1225 value.into_owned()
1226 }
1227}
1228
1229fn basename(path: &str) -> String {
1230 let normalized = normalize_path(path);
1231 Path::new(&normalized)
1232 .file_name()
1233 .map(|name| name.to_string_lossy().into_owned())
1234 .unwrap_or_else(|| String::from("/"))
1235}