Skip to main content

secure_exec_kernel/
mount_table.rs

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            // Materializing the mountpoint directory on the parent is
683            // cosmetic: child mounts resolve by path prefix before the parent
684            // is consulted. A read-only parent (for example a read-only
685            // module-access mount hosting nested package mounts) cannot
686            // materialize the entry, but the mount must still succeed.
687            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}