veridian_kernel/virt/containers/
overlay.rs1#[cfg(feature = "alloc")]
4use alloc::{collections::BTreeMap, format, string::String, vec::Vec};
5
6use crate::error::KernelError;
7
8const WHITEOUT_PREFIX: &str = ".wh.";
10
11const OPAQUE_WHITEOUT: &str = ".wh..wh..opq";
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum OverlayEntryKind {
17 File,
18 Directory,
19 Symlink,
20 Whiteout,
21 OpaqueDir,
22}
23
24#[cfg(feature = "alloc")]
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct OverlayEntry {
28 pub path: String,
30 pub kind: OverlayEntryKind,
32 pub content: Vec<u8>,
34 pub mode: u32,
36}
37
38#[cfg(feature = "alloc")]
40#[derive(Debug, Clone)]
41pub struct OverlayLayer {
42 pub(crate) entries: BTreeMap<String, OverlayEntry>,
44 pub readonly: bool,
46}
47
48#[cfg(feature = "alloc")]
49impl OverlayLayer {
50 pub fn new(readonly: bool) -> Self {
52 Self {
53 entries: BTreeMap::new(),
54 readonly,
55 }
56 }
57
58 pub fn add_entry(&mut self, entry: OverlayEntry) -> Result<(), KernelError> {
60 if self.readonly {
61 return Err(KernelError::PermissionDenied {
62 operation: "write to readonly layer",
63 });
64 }
65 self.entries.insert(entry.path.clone(), entry);
66 Ok(())
67 }
68
69 pub fn get_entry(&self, path: &str) -> Option<&OverlayEntry> {
71 self.entries.get(path)
72 }
73
74 pub fn is_whiteout(&self, path: &str) -> bool {
76 if let Some(entry) = self.entries.get(path) {
78 return entry.kind == OverlayEntryKind::Whiteout;
79 }
80 if let Some((_dir, name)) = path.rsplit_once('/') {
82 let wh_path = format!(
83 "{}/{}{}",
84 path.rsplit_once('/').map(|(d, _)| d).unwrap_or(""),
85 WHITEOUT_PREFIX,
86 name
87 );
88 self.entries.contains_key(&wh_path)
89 } else {
90 let wh_path = format!("{}{}", WHITEOUT_PREFIX, path);
91 self.entries.contains_key(&wh_path)
92 }
93 }
94
95 pub fn is_opaque_dir(&self, dir_path: &str) -> bool {
97 let opq_path = if dir_path.ends_with('/') {
98 format!("{}{}", dir_path, OPAQUE_WHITEOUT)
99 } else {
100 format!("{}/{}", dir_path, OPAQUE_WHITEOUT)
101 };
102 self.entries.contains_key(&opq_path)
103 }
104
105 pub fn list_dir(&self, dir_path: &str) -> Vec<&OverlayEntry> {
107 let prefix = if dir_path.ends_with('/') || dir_path.is_empty() {
108 String::from(dir_path)
109 } else {
110 format!("{}/", dir_path)
111 };
112 self.entries
113 .values()
114 .filter(|e| {
115 if e.path.starts_with(prefix.as_str()) {
116 let rest = &e.path[prefix.len()..];
117 !rest.is_empty() && !rest.contains('/')
118 } else {
119 false
120 }
121 })
122 .collect()
123 }
124
125 pub fn entry_count(&self) -> usize {
126 self.entries.len()
127 }
128}
129
130#[cfg(feature = "alloc")]
132#[derive(Debug)]
133pub struct OverlayFs {
134 lower_layers: Vec<OverlayLayer>,
136 upper_layer: OverlayLayer,
138 work_dir: String,
140}
141
142#[cfg(feature = "alloc")]
143impl OverlayFs {
144 pub fn new(work_dir: &str) -> Self {
146 Self {
147 lower_layers: Vec::new(),
148 upper_layer: OverlayLayer::new(false),
149 work_dir: String::from(work_dir),
150 }
151 }
152
153 pub fn add_lower_layer(&mut self, layer: OverlayLayer) {
155 self.lower_layers.push(layer);
156 }
157
158 pub fn lookup(&self, path: &str) -> Option<&OverlayEntry> {
161 if self.upper_layer.is_whiteout(path) {
163 return None; }
165 if let Some(entry) = self.upper_layer.get_entry(path) {
166 return Some(entry);
167 }
168
169 for layer in self.lower_layers.iter().rev() {
171 if layer.is_whiteout(path) {
172 return None;
173 }
174 if let Some((parent, _)) = path.rsplit_once('/') {
176 if layer.is_opaque_dir(parent) {
177 return layer.get_entry(path);
178 }
179 }
180 if let Some(entry) = layer.get_entry(path) {
181 return Some(entry);
182 }
183 }
184
185 None
186 }
187
188 pub fn write_file(
191 &mut self,
192 path: &str,
193 content: Vec<u8>,
194 mode: u32,
195 ) -> Result<(), KernelError> {
196 let entry = OverlayEntry {
197 path: String::from(path),
198 kind: OverlayEntryKind::File,
199 content,
200 mode,
201 };
202 self.upper_layer.entries.insert(String::from(path), entry);
203 Ok(())
204 }
205
206 pub fn delete_file(&mut self, path: &str) -> Result<(), KernelError> {
208 self.upper_layer.entries.remove(path);
210
211 let exists_in_lower = self
213 .lower_layers
214 .iter()
215 .any(|l| l.get_entry(path).is_some());
216
217 if exists_in_lower {
218 if let Some((dir, name)) = path.rsplit_once('/') {
220 let wh_path = format!("{}/{}{}", dir, WHITEOUT_PREFIX, name);
221 self.upper_layer.entries.insert(
222 wh_path.clone(),
223 OverlayEntry {
224 path: wh_path,
225 kind: OverlayEntryKind::Whiteout,
226 content: Vec::new(),
227 mode: 0,
228 },
229 );
230 } else {
231 let wh_path = format!("{}{}", WHITEOUT_PREFIX, path);
232 self.upper_layer.entries.insert(
233 wh_path.clone(),
234 OverlayEntry {
235 path: wh_path,
236 kind: OverlayEntryKind::Whiteout,
237 content: Vec::new(),
238 mode: 0,
239 },
240 );
241 }
242 }
243
244 Ok(())
245 }
246
247 pub fn make_opaque_dir(&mut self, dir_path: &str) -> Result<(), KernelError> {
249 let opq_path = format!("{}/{}", dir_path, OPAQUE_WHITEOUT);
250 self.upper_layer.entries.insert(
251 opq_path.clone(),
252 OverlayEntry {
253 path: opq_path,
254 kind: OverlayEntryKind::OpaqueDir,
255 content: Vec::new(),
256 mode: 0,
257 },
258 );
259 Ok(())
260 }
261
262 pub fn list_dir(&self, dir_path: &str) -> Vec<&OverlayEntry> {
265 let mut seen: BTreeMap<String, &OverlayEntry> = BTreeMap::new();
266 let mut whited_out: Vec<String> = Vec::new();
267
268 for entry in self.upper_layer.list_dir(dir_path) {
270 if entry.kind == OverlayEntryKind::Whiteout {
271 if let Some(name) = entry
273 .path
274 .rsplit('/')
275 .next()
276 .and_then(|n| n.strip_prefix(WHITEOUT_PREFIX))
277 {
278 let orig = if dir_path.is_empty() {
279 String::from(name)
280 } else {
281 format!("{}/{}", dir_path, name)
282 };
283 whited_out.push(orig);
284 }
285 } else if entry.kind != OverlayEntryKind::OpaqueDir {
286 seen.insert(entry.path.clone(), entry);
287 }
288 }
289
290 let is_opaque = self.upper_layer.is_opaque_dir(dir_path);
292
293 if !is_opaque {
294 for layer in self.lower_layers.iter().rev() {
296 if layer.is_opaque_dir(dir_path) {
297 for entry in layer.list_dir(dir_path) {
299 if !seen.contains_key(&entry.path) && !whited_out.contains(&entry.path) {
300 seen.insert(entry.path.clone(), entry);
301 }
302 }
303 break;
304 }
305 for entry in layer.list_dir(dir_path) {
306 if !seen.contains_key(&entry.path) && !whited_out.contains(&entry.path) {
307 seen.insert(entry.path.clone(), entry);
308 }
309 }
310 }
311 }
312
313 seen.into_values().collect()
314 }
315
316 pub fn work_dir(&self) -> &str {
317 &self.work_dir
318 }
319
320 pub fn lower_layer_count(&self) -> usize {
321 self.lower_layers.len()
322 }
323}