Skip to main content

agent_os_kernel/
mount_table.rs

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