pub struct OverlayFs { /* private fields */ }Expand description
Copy-on-write overlay filesystem.
OverlayFs layers a writable upper filesystem on top of a read-only base
(lower) filesystem, similar to Docker’s overlay storage driver or Linux
overlayfs.
§Behavior
- Reads: Check upper layer first, fall back to lower layer
- Writes: Always go to the upper layer (copy-on-write)
- Deletes: Tracked via whiteouts - deleted files are hidden but the lower layer is unchanged
§Use Cases
- Template systems: Start from a read-only template, allow modifications
- Immutable infrastructure: Keep base images unchanged while allowing runtime modifications
- Testing: Run tests against a base state without modifying it
- Undo support: Discard the upper layer to “reset” to the base state
§Example
use bashkit::{Bash, FileSystem, InMemoryFs, OverlayFs};
use std::path::Path;
use std::sync::Arc;
// Create a base filesystem with template files
let base = Arc::new(InMemoryFs::new());
base.mkdir(Path::new("/config"), false).await?;
base.write_file(Path::new("/config/app.conf"), b"debug=false").await?;
// Create overlay - base is read-only, changes go to overlay
let overlay = Arc::new(OverlayFs::new(base.clone()));
// Use with Bash
let mut bash = Bash::builder().fs(overlay.clone()).build();
// Read from base layer
let result = bash.exec("cat /config/app.conf").await?;
assert_eq!(result.stdout, "debug=false");
// Modify - changes go to overlay only
bash.exec("echo 'debug=true' > /config/app.conf").await?;
// Overlay shows modified content
let result = bash.exec("cat /config/app.conf").await?;
assert_eq!(result.stdout, "debug=true\n");
// Base is unchanged!
let original = base.read_file(Path::new("/config/app.conf")).await?;
assert_eq!(original, b"debug=false");§Whiteouts (Deletion Handling)
When you delete a file that exists in the base layer, OverlayFs creates
a “whiteout” marker that hides the file without modifying the base:
use bashkit::{Bash, FileSystem, InMemoryFs, OverlayFs};
use std::path::Path;
use std::sync::Arc;
let base = Arc::new(InMemoryFs::new());
base.write_file(Path::new("/tmp/secret.txt"), b"sensitive").await?;
let overlay = Arc::new(OverlayFs::new(base.clone()));
let mut bash = Bash::builder().fs(overlay.clone()).build();
// File exists initially
assert!(overlay.exists(Path::new("/tmp/secret.txt")).await?);
// Delete it
bash.exec("rm /tmp/secret.txt").await?;
// Gone from overlay's view
assert!(!overlay.exists(Path::new("/tmp/secret.txt")).await?);
// But base is unchanged
assert!(base.exists(Path::new("/tmp/secret.txt")).await?);§Directory Listing
When listing directories, entries from both layers are merged, with the upper layer taking precedence for files that exist in both:
use bashkit::{FileSystem, InMemoryFs, OverlayFs};
use std::path::Path;
use std::sync::Arc;
let base = Arc::new(InMemoryFs::new());
base.write_file(Path::new("/tmp/base.txt"), b"from base").await?;
let overlay = OverlayFs::new(base);
overlay.write_file(Path::new("/tmp/upper.txt"), b"from upper").await?;
// Both files visible
let entries = overlay.read_dir(Path::new("/tmp")).await?;
let names: Vec<_> = entries.iter().map(|e| e.name.as_str()).collect();
assert!(names.contains(&"base.txt"));
assert!(names.contains(&"upper.txt"));Implementations§
Source§impl OverlayFs
impl OverlayFs
Sourcepub fn new(lower: Arc<dyn FileSystem>) -> Self
pub fn new(lower: Arc<dyn FileSystem>) -> Self
Create a new overlay filesystem with the given base layer and default limits.
The lower filesystem is treated as read-only - all reads will first
check the upper layer, then fall back to the lower layer. All writes
go to a new InMemoryFs upper layer.
§Arguments
lower- The base (read-only) filesystem
§Example
use bashkit::{FileSystem, InMemoryFs, OverlayFs};
use std::path::Path;
use std::sync::Arc;
// Create base with some files
let base = Arc::new(InMemoryFs::new());
base.mkdir(Path::new("/data"), false).await?;
base.write_file(Path::new("/data/readme.txt"), b"Read me!").await?;
// Create overlay
let overlay = OverlayFs::new(base);
// Can read from base
let content = overlay.read_file(Path::new("/data/readme.txt")).await?;
assert_eq!(content, b"Read me!");
// Writes go to upper layer
overlay.write_file(Path::new("/data/new.txt"), b"New file").await?;Sourcepub fn with_limits(lower: Arc<dyn FileSystem>, limits: FsLimits) -> Self
pub fn with_limits(lower: Arc<dyn FileSystem>, limits: FsLimits) -> Self
Create a new overlay filesystem with custom limits.
Limits apply to the combined view (upper layer writes + lower layer content).
§Example
use bashkit::{FileSystem, InMemoryFs, OverlayFs, FsLimits};
use std::path::Path;
use std::sync::Arc;
let base = Arc::new(InMemoryFs::new());
let limits = FsLimits::new().max_total_bytes(10_000_000); // 10MB
let overlay = OverlayFs::with_limits(base, limits);Sourcepub fn upper(&self) -> &InMemoryFs
pub fn upper(&self) -> &InMemoryFs
Access the upper (writable) filesystem layer.
This provides direct access to the InMemoryFs that stores all writes.
Useful for pre-populating files during construction.
§Example
use bashkit::{InMemoryFs, OverlayFs};
use std::sync::Arc;
let base = Arc::new(InMemoryFs::new());
let overlay = OverlayFs::new(base);
// Add files directly to upper layer
overlay.upper().add_file("/config/app.conf", "debug=true\n", 0o644);