sandbox_rs/storage/
filesystem.rs1use crate::errors::{Result, SandboxError};
4use std::fs;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone)]
9pub struct OverlayConfig {
10 pub lower: PathBuf,
12 pub upper: PathBuf,
14 pub work: PathBuf,
16 pub merged: PathBuf,
18}
19
20impl OverlayConfig {
21 pub fn new(lower: impl AsRef<Path>, upper: impl AsRef<Path>) -> Self {
23 let lower_path = lower.as_ref().to_path_buf();
24 let upper_path = upper.as_ref().to_path_buf();
25 let work_path = upper_path
26 .parent()
27 .unwrap_or_else(|| Path::new("/tmp"))
28 .join("overlayfs-work");
29 let merged_path = upper_path
30 .parent()
31 .unwrap_or_else(|| Path::new("/tmp"))
32 .join("overlayfs-merged");
33
34 Self {
35 lower: lower_path,
36 upper: upper_path,
37 work: work_path,
38 merged: merged_path,
39 }
40 }
41
42 pub fn validate(&self) -> Result<()> {
44 if !self.lower.exists() {
45 return Err(SandboxError::Syscall(format!(
46 "Lower layer does not exist: {}",
47 self.lower.display()
48 )));
49 }
50
51 Ok(())
52 }
53
54 pub fn setup_directories(&self) -> Result<()> {
56 fs::create_dir_all(&self.upper)
57 .map_err(|e| SandboxError::Syscall(format!("Failed to create upper layer: {}", e)))?;
58
59 fs::create_dir_all(&self.work).map_err(|e| {
60 SandboxError::Syscall(format!("Failed to create work directory: {}", e))
61 })?;
62
63 fs::create_dir_all(&self.merged).map_err(|e| {
64 SandboxError::Syscall(format!("Failed to create merged directory: {}", e))
65 })?;
66
67 Ok(())
68 }
69
70 pub fn get_mount_options(&self) -> String {
72 format!(
73 "lowerdir={},upperdir={},workdir={}",
74 self.lower.display(),
75 self.upper.display(),
76 self.work.display()
77 )
78 }
79}
80
81pub struct OverlayFS {
83 config: OverlayConfig,
84 mounted: bool,
85}
86
87impl OverlayFS {
88 pub fn new(config: OverlayConfig) -> Self {
90 Self {
91 config,
92 mounted: false,
93 }
94 }
95
96 pub fn setup(&mut self) -> Result<()> {
98 self.config.validate()?;
99 self.config.setup_directories()?;
100
101 self.mounted = true;
104 Ok(())
105 }
106
107 pub fn is_mounted(&self) -> bool {
109 self.mounted
110 }
111
112 pub fn merged_path(&self) -> &Path {
114 &self.config.merged
115 }
116
117 pub fn upper_path(&self) -> &Path {
119 &self.config.upper
120 }
121
122 pub fn lower_path(&self) -> &Path {
124 &self.config.lower
125 }
126
127 pub fn cleanup(&mut self) -> Result<()> {
129 if self.mounted {
130 self.mounted = false;
132 }
133
134 let _ = fs::remove_dir_all(&self.config.work);
136
137 Ok(())
138 }
139
140 pub fn get_changes_size(&self) -> Result<u64> {
142 let mut total = 0u64;
143
144 for entry in fs::read_dir(&self.config.upper)
145 .map_err(|e| SandboxError::Syscall(format!("Cannot read upper layer: {}", e)))?
146 {
147 let entry = entry
148 .map_err(|e| SandboxError::Syscall(format!("Directory entry error: {}", e)))?;
149 let path = entry.path();
150
151 if path.is_file() {
152 total += entry
153 .metadata()
154 .map_err(|e| SandboxError::Syscall(e.to_string()))?
155 .len();
156 }
157 }
158
159 Ok(total)
160 }
161}
162
163#[derive(Debug, Clone)]
165pub struct LayerInfo {
166 pub name: String,
168 pub size: u64,
170 pub file_count: usize,
172 pub writable: bool,
174}
175
176impl LayerInfo {
177 pub fn from_path(name: &str, path: &Path, writable: bool) -> Result<Self> {
179 let mut size = 0u64;
180 let mut file_count = 0;
181
182 if path.exists() {
183 for entry in fs::read_dir(path)
184 .map_err(|e| SandboxError::Syscall(format!("Cannot read layer: {}", e)))?
185 {
186 let entry = entry.map_err(|e| SandboxError::Syscall(e.to_string()))?;
187
188 if entry.path().is_file() {
189 file_count += 1;
190 size += entry
191 .metadata()
192 .map_err(|e| SandboxError::Syscall(e.to_string()))?
193 .len();
194 }
195 }
196 }
197
198 Ok(Self {
199 name: name.to_string(),
200 size,
201 file_count,
202 writable,
203 })
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_overlay_config_creation() {
213 let config = OverlayConfig::new("/base", "/upper");
214 assert_eq!(config.lower, PathBuf::from("/base"));
215 assert_eq!(config.upper, PathBuf::from("/upper"));
216 }
217
218 #[test]
219 fn test_overlay_config_mount_options() {
220 let config = OverlayConfig::new("/lower", "/upper");
221 let opts = config.get_mount_options();
222
223 assert!(opts.contains("lowerdir="));
224 assert!(opts.contains("upperdir="));
225 assert!(opts.contains("workdir="));
226 }
227
228 #[test]
229 fn test_overlay_fs_creation() {
230 let config = OverlayConfig::new("/base", "/upper");
231 let fs = OverlayFS::new(config);
232
233 assert!(!fs.is_mounted());
234 }
235
236 #[test]
237 fn test_layer_info_size_calculation() {
238 let info = LayerInfo {
239 name: "test".to_string(),
240 size: 1024,
241 file_count: 5,
242 writable: true,
243 };
244
245 assert_eq!(info.size, 1024);
246 assert_eq!(info.file_count, 5);
247 assert!(info.writable);
248 }
249
250 #[test]
251 fn test_overlay_paths() {
252 let config = OverlayConfig::new("/lower", "/upper");
253 let fs = OverlayFS::new(config);
254
255 assert_eq!(fs.lower_path(), Path::new("/lower"));
256 assert_eq!(fs.upper_path(), Path::new("/upper"));
257 }
258}