pub struct MountableFs { /* private fields */ }Expand description
Filesystem with Unix-style mount points.
MountableFs allows mounting different filesystem implementations at
specific paths, similar to how Unix systems mount devices at directories.
This enables complex multi-source filesystem setups.
§Features
- Multiple mount points: Mount different filesystems at different paths
- Nested mounts: Mount filesystems within other mounts (longest-prefix matching)
- Dynamic mounting: Add/remove mounts at runtime
- Cross-mount operations: Copy/move files between different mounted filesystems
§Use Cases
- Hybrid storage: Combine in-memory temp storage with persistent data stores
- Multi-tenant isolation: Mount separate filesystems for different tenants
- Plugin systems: Each plugin gets its own mounted filesystem
- Testing: Mount mock filesystems for specific paths
§Example: Basic Mounting
use bashkit::{Bash, FileSystem, InMemoryFs, MountableFs};
use std::path::Path;
use std::sync::Arc;
// Create root and separate data filesystem
let root = Arc::new(InMemoryFs::new());
let data_fs = Arc::new(InMemoryFs::new());
// Pre-populate data filesystem
data_fs.write_file(Path::new("/users.json"), br#"["alice", "bob"]"#).await?;
// Create mountable filesystem
let mountable = MountableFs::new(root.clone());
// Mount data_fs at /mnt/data
mountable.mount("/mnt/data", data_fs.clone())?;
// Use with Bash
let mut bash = Bash::builder().fs(Arc::new(mountable)).build();
// Access mounted filesystem
let result = bash.exec("cat /mnt/data/users.json").await?;
assert!(result.stdout.contains("alice"));
// Access root filesystem
bash.exec("echo hello > /root.txt").await?;§Example: Nested Mounts
use bashkit::{FileSystem, InMemoryFs, MountableFs};
use std::path::Path;
use std::sync::Arc;
let root = Arc::new(InMemoryFs::new());
let outer = Arc::new(InMemoryFs::new());
let inner = Arc::new(InMemoryFs::new());
outer.write_file(Path::new("/outer.txt"), b"outer").await?;
inner.write_file(Path::new("/inner.txt"), b"inner").await?;
let mountable = MountableFs::new(root);
mountable.mount("/mnt", outer)?;
mountable.mount("/mnt/nested", inner)?;
// Access outer mount
let content = mountable.read_file(Path::new("/mnt/outer.txt")).await?;
assert_eq!(content, b"outer");
// Access nested mount (longest-prefix matching)
let content = mountable.read_file(Path::new("/mnt/nested/inner.txt")).await?;
assert_eq!(content, b"inner");§Example: Dynamic Mount/Unmount
use bashkit::{FileSystem, InMemoryFs, MountableFs};
use std::path::Path;
use std::sync::Arc;
let root = Arc::new(InMemoryFs::new());
let plugin_fs = Arc::new(InMemoryFs::new());
plugin_fs.write_file(Path::new("/plugin.so"), b"binary").await?;
let mountable = MountableFs::new(root);
// Mount plugin filesystem
mountable.mount("/plugins", plugin_fs)?;
assert!(mountable.exists(Path::new("/plugins/plugin.so")).await?);
// Unmount when done
mountable.unmount("/plugins")?;
assert!(!mountable.exists(Path::new("/plugins/plugin.so")).await?);§Path Resolution
When resolving a path, MountableFs uses longest-prefix matching to find
the appropriate filesystem. For example, with mounts at /mnt and /mnt/data:
/mnt/file.txt→ resolves to/mntmount/mnt/data/file.txt→ resolves to/mnt/datamount (longer prefix wins)/other/file.txt→ resolves to root filesystem
Implementations§
Source§impl MountableFs
impl MountableFs
Sourcepub fn new(root: Arc<dyn FileSystem>) -> Self
pub fn new(root: Arc<dyn FileSystem>) -> Self
Create a new MountableFs with the given root filesystem.
The root filesystem is used for all paths that don’t match any mount point.
§Example
use bashkit::{FileSystem, InMemoryFs, MountableFs};
use std::path::Path;
use std::sync::Arc;
let root = Arc::new(InMemoryFs::new());
let mountable = MountableFs::new(root);
// Paths not covered by mounts go to root
mountable.write_file(Path::new("/tmp/test.txt"), b"hello").await?;Sourcepub fn mount(
&self,
path: impl AsRef<Path>,
fs: Arc<dyn FileSystem>,
) -> Result<()>
pub fn mount( &self, path: impl AsRef<Path>, fs: Arc<dyn FileSystem>, ) -> Result<()>
Mount a filesystem at the given path.
After mounting, all operations on paths under the mount point will be directed to the mounted filesystem.
§Arguments
path- The mount point (must be an absolute path)fs- The filesystem to mount
§Errors
Returns an error if the path is not absolute.
§Example
use bashkit::{FileSystem, InMemoryFs, MountableFs};
use std::path::Path;
use std::sync::Arc;
let root = Arc::new(InMemoryFs::new());
let data_fs = Arc::new(InMemoryFs::new());
data_fs.write_file(Path::new("/data.txt"), b"data").await?;
let mountable = MountableFs::new(root);
mountable.mount("/data", data_fs)?;
// Access via mount point
let content = mountable.read_file(Path::new("/data/data.txt")).await?;
assert_eq!(content, b"data");Sourcepub fn unmount(&self, path: impl AsRef<Path>) -> Result<()>
pub fn unmount(&self, path: impl AsRef<Path>) -> Result<()>
Unmount a filesystem at the given path.
After unmounting, paths that previously resolved to the mounted filesystem will fall back to the root filesystem or a shorter mount prefix.
§Errors
Returns an error if no filesystem is mounted at the given path.
§Example
use bashkit::{FileSystem, InMemoryFs, MountableFs};
use std::path::Path;
use std::sync::Arc;
let root = Arc::new(InMemoryFs::new());
let plugin = Arc::new(InMemoryFs::new());
plugin.write_file(Path::new("/lib.so"), b"binary").await?;
let mountable = MountableFs::new(root);
mountable.mount("/plugin", plugin)?;
// File is accessible
assert!(mountable.exists(Path::new("/plugin/lib.so")).await?);
// Unmount
mountable.unmount("/plugin")?;
// No longer accessible
assert!(!mountable.exists(Path::new("/plugin/lib.so")).await?);