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;
103 Ok(())
104 }
105
106 pub fn is_mounted(&self) -> bool {
108 self.mounted
109 }
110
111 pub fn merged_path(&self) -> &Path {
113 &self.config.merged
114 }
115
116 pub fn upper_path(&self) -> &Path {
118 &self.config.upper
119 }
120
121 pub fn lower_path(&self) -> &Path {
123 &self.config.lower
124 }
125
126 pub fn cleanup(&mut self) -> Result<()> {
128 if self.mounted {
129 self.mounted = false;
131 }
132
133 let _ = fs::remove_dir_all(&self.config.work);
135
136 Ok(())
137 }
138
139 pub fn get_changes_size(&self) -> Result<u64> {
141 let mut total = 0u64;
142
143 for entry in fs::read_dir(&self.config.upper)
144 .map_err(|e| SandboxError::Syscall(format!("Cannot read upper layer: {}", e)))?
145 {
146 let entry = entry
147 .map_err(|e| SandboxError::Syscall(format!("Directory entry error: {}", e)))?;
148 let path = entry.path();
149
150 if path.is_file() {
151 total += entry
152 .metadata()
153 .map_err(|e| SandboxError::Syscall(e.to_string()))?
154 .len();
155 }
156 }
157
158 Ok(total)
159 }
160}
161
162#[derive(Debug, Clone)]
164pub struct LayerInfo {
165 pub name: String,
167 pub size: u64,
169 pub file_count: usize,
171 pub writable: bool,
173}
174
175impl LayerInfo {
176 pub fn from_path(name: &str, path: &Path, writable: bool) -> Result<Self> {
178 let mut size = 0u64;
179 let mut file_count = 0;
180
181 if path.exists() {
182 for entry in fs::read_dir(path)
183 .map_err(|e| SandboxError::Syscall(format!("Cannot read layer: {}", e)))?
184 {
185 let entry = entry.map_err(|e| SandboxError::Syscall(e.to_string()))?;
186
187 if entry.path().is_file() {
188 file_count += 1;
189 size += entry
190 .metadata()
191 .map_err(|e| SandboxError::Syscall(e.to_string()))?
192 .len();
193 }
194 }
195 }
196
197 Ok(Self {
198 name: name.to_string(),
199 size,
200 file_count,
201 writable,
202 })
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_overlay_config_creation() {
212 let config = OverlayConfig::new("/base", "/upper");
213 assert_eq!(config.lower, PathBuf::from("/base"));
214 assert_eq!(config.upper, PathBuf::from("/upper"));
215 }
216
217 #[test]
218 fn test_overlay_config_mount_options() {
219 let config = OverlayConfig::new("/lower", "/upper");
220 let opts = config.get_mount_options();
221
222 assert!(opts.contains("lowerdir="));
223 assert!(opts.contains("upperdir="));
224 assert!(opts.contains("workdir="));
225 }
226
227 #[test]
228 fn test_overlay_fs_creation() {
229 let config = OverlayConfig::new("/base", "/upper");
230 let fs = OverlayFS::new(config);
231
232 assert!(!fs.is_mounted());
233 }
234
235 #[test]
236 fn test_layer_info_size_calculation() {
237 let info = LayerInfo {
238 name: "test".to_string(),
239 size: 1024,
240 file_count: 5,
241 writable: true,
242 };
243
244 assert_eq!(info.size, 1024);
245 assert_eq!(info.file_count, 5);
246 assert!(info.writable);
247 }
248
249 #[test]
250 fn test_overlay_paths() {
251 let config = OverlayConfig::new("/lower", "/upper");
252 let fs = OverlayFS::new(config);
253
254 assert_eq!(fs.lower_path(), Path::new("/lower"));
255 assert_eq!(fs.upper_path(), Path::new("/upper"));
256 }
257}