overlay_filesystem/
overlay_filesystem.rs

1//! Overlay Filesystem Example
2//!
3//! This example demonstrates how to use overlay filesystems with sandboxes
4//! for persistent storage with copy-on-write semantics.
5//!
6//! ## Overview
7//!
8//! An overlay filesystem combines:
9//! - **Lower layer**: Read-only base filesystem (immutable)
10//! - **Upper layer**: Read-write changes (modifications)
11//! - **Merged view**: Combined view of both layers
12//! - **Work directory**: Kernel internal state for overlayfs
13//!
14//! ## Use Cases
15//!
16//! 1. **Snapshots**: Run programs with same base, but isolate changes
17//! 2. **Recovery**: Revert to base state by deleting upper layer
18//! 3. **Efficiency**: Share common base across multiple sandboxes
19//! 4. **Auditing**: Track exactly what files changed during execution
20//!
21//! ## Running this example
22//!
23//! ```bash
24//! cargo run --example overlay_filesystem
25//! ```
26
27use sandbox_rs::{OverlayConfig, OverlayFS};
28use std::fs;
29use tempfile::TempDir;
30
31fn main() -> Result<(), Box<dyn std::error::Error>> {
32    println!("=== Sandbox-rs: Overlay Filesystem Example ===\n");
33
34    // Create temporary directories for demonstration
35    let temp_base = TempDir::new()?;
36    let base_dir = temp_base.path().join("base");
37    let upper_dir = temp_base.path().join("upper");
38
39    println!("[1] Setting up overlay filesystem layers\n");
40
41    // Create base directory with initial content
42    fs::create_dir_all(&base_dir)?;
43    fs::write(
44        base_dir.join("original.txt"),
45        "This is the original immutable file\n",
46    )?;
47    fs::write(base_dir.join("readme.txt"), "Base layer - read-only\n")?;
48
49    println!("  Created base layer at: {}", base_dir.display());
50    println!("    - original.txt");
51    println!("    - readme.txt\n");
52
53    // Create overlay configuration
54    println!("[2] Creating overlay configuration\n");
55    let overlay_config = OverlayConfig::new(&base_dir, &upper_dir);
56
57    println!(
58        "  Lower layer (read-only): {}",
59        overlay_config.lower.display()
60    );
61    println!(
62        "  Upper layer (read-write): {}",
63        overlay_config.upper.display()
64    );
65    println!("  Work directory: {}", overlay_config.work.display());
66    println!("  Merged view: {}\n", overlay_config.merged.display());
67
68    // Initialize overlay filesystem
69    println!("[3] Initializing overlay filesystem\n");
70    let mut overlay = OverlayFS::new(overlay_config);
71    overlay.setup()?;
72
73    println!("  Overlay filesystem initialized");
74    println!("  Mounted: {}\n", overlay.is_mounted());
75
76    // Simulate operations in the sandbox
77    println!("[4] Simulating sandbox operations\n");
78
79    // Create new file in upper layer (simulating sandbox write)
80    let upper_path = overlay.upper_path();
81    fs::create_dir_all(upper_path)?;
82
83    let new_file_path = upper_path.join("sandbox-output.txt");
84    fs::write(
85        &new_file_path,
86        "This file was created in the sandbox\nModified during execution\n",
87    )?;
88    println!("  Created new file: {}", new_file_path.display());
89
90    let modified_file_path = upper_path.join("modified.txt");
91    fs::write(
92        &modified_file_path,
93        "This original file was modified in the sandbox\n",
94    )?;
95    println!(
96        "  Created modified file: {}\n",
97        modified_file_path.display()
98    );
99
100    // Query layer information
101    println!("[5] Layer Information\n");
102
103    let lower_info =
104        sandbox_rs::storage::LayerInfo::from_path("lower", overlay.lower_path(), false)?;
105    println!("  Lower Layer (Read-only):");
106    println!("    Files: {}", lower_info.file_count);
107    println!("    Total size: {} bytes", lower_info.size);
108    println!("    Writable: {}\n", lower_info.writable);
109
110    let upper_info =
111        sandbox_rs::storage::LayerInfo::from_path("upper", overlay.upper_path(), true)?;
112    println!("  Upper Layer (Read-write changes):");
113    println!("    Files: {}", upper_info.file_count);
114    println!("    Total size: {} bytes", upper_info.size);
115    println!("    Writable: {}\n", upper_info.writable);
116
117    // Get total changes size
118    println!("[6] Sandbox Modifications Summary\n");
119    let changes_size = overlay.get_changes_size()?;
120    println!("  Total changes in upper layer: {} bytes", changes_size);
121    println!("  Files modified/created: {}\n", upper_info.file_count);
122
123    // Show how to use the merged view
124    println!("[7] Accessing Merged View\n");
125    println!("  In a real mount, you would access both layers transparently:");
126    println!("    - {} (combined view)", overlay.merged_path().display());
127    println!("    - Files from lower layer are visible");
128    println!("    - Files from upper layer override lower layer");
129    println!("    - New files only appear in upper layer\n");
130
131    // Demonstrate cleanup and recovery
132    println!("[8] Cleanup and Recovery Options\n");
133    println!("  Option A: Keep upper layer for audit trail");
134    println!(
135        "    - Preserve {} for reviewing changes",
136        upper_path.display()
137    );
138    println!("    - Original base layer remains untouched\n");
139
140    println!("  Option B: Discard changes (reset to base)");
141    println!("    - Delete upper layer: {}", upper_path.display());
142    println!("    - Next execution gets fresh base\n");
143
144    println!("  Option C: Commit changes to new base");
145    println!("    - Copy merged view to new base layer");
146    println!("    - Create fresh upper layer for next sandbox\n");
147
148    // Cleanup
149    println!("[9] Cleaning up\n");
150    overlay.cleanup()?;
151    println!("  Overlay filesystem cleaned up\n");
152
153    // Practical use case demonstration
154    println!("=== Practical Use Case ===\n");
155    println!("Scenario: Run Python data processing pipeline with multiple stages\n");
156
157    println!("Stage 1: Initial execution");
158    println!("  Base layer:  /data/pipeline-v1.0 (read-only)");
159    println!("  Upper layer: /sandbox-run-1/changes");
160    println!("  Output:      preprocessing complete, 1.2GB changes\n");
161
162    println!("Stage 2: Different parameters");
163    println!("  Base layer:  /data/pipeline-v1.0 (same - shared!)");
164    println!("  Upper layer: /sandbox-run-2/changes (fresh)");
165    println!("  Output:      processing complete, 2.1GB changes\n");
166
167    println!("Benefits:");
168    println!("  - Disk efficient: Base shared across runs");
169    println!("  - Isolation: Each run has independent changes");
170    println!("  - Auditability: See exactly what each run produced");
171    println!("  - Recoverability: Can revert to base state\n");
172
173    // Volume mount for persistent storage
174    println!("=== Combined with Volume Mounts ===\n");
175    println!("For persistent storage across sandbox runs:");
176    println!("  - Overlay FS: For temporary isolation per execution");
177    println!("  - Volume mounts: For data that needs to survive");
178    println!("  - Example setup:");
179    println!("    - Mount /home/user/data as /data (read-write)");
180    println!("    - Overlay base /usr/lib as /lib (read-only with changes)");
181    println!("    - Any writes to /data persist beyond sandbox");
182    println!("    - Any writes to /lib are isolated per run\n");
183
184    println!("=== Example completed successfully ===");
185    Ok(())
186}