1use super::vfs::{
2 normalize_path, MemoryFileSystem, VfsError, VfsResult, VirtualDirEntry, VirtualFileSystem,
3 VirtualStat, VirtualUtimeSpec,
4};
5use base64::Engine;
6use std::collections::BTreeSet;
7
8const MAX_SNAPSHOT_DEPTH: usize = 1024;
9const OVERLAY_METADATA_ROOT: &str = "/.secure-exec-overlay";
10const OVERLAY_WHITEOUT_DIR: &str = "/.secure-exec-overlay/whiteouts";
11const OVERLAY_OPAQUE_DIR: &str = "/.secure-exec-overlay/opaque";
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum OverlayMode {
15 Ephemeral,
16 ReadOnly,
17}
18
19#[derive(Debug)]
20pub struct OverlayFileSystem {
21 lowers: Vec<MemoryFileSystem>,
22 upper: Option<MemoryFileSystem>,
23 writes_locked: bool,
24}
25
26#[derive(Debug, Clone, Copy)]
27enum OverlayMarkerKind {
28 Whiteout,
29 Opaque,
30}
31
32#[derive(Debug)]
33enum OverlaySnapshotKind {
34 Directory,
35 File(Vec<u8>),
36 Symlink(String),
37}
38
39#[derive(Debug)]
40struct OverlaySnapshotEntry {
41 path: String,
42 stat: VirtualStat,
43 kind: OverlaySnapshotKind,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
47struct OverlayCopyUpUsage {
48 total_bytes: u64,
49 inode_count: usize,
50}
51
52impl OverlayFileSystem {
53 pub fn new(lowers: Vec<MemoryFileSystem>, mode: OverlayMode) -> Self {
54 let mut effective_lowers = lowers;
55 if effective_lowers.is_empty() {
56 effective_lowers.push(MemoryFileSystem::new());
57 }
58
59 let mut upper = match mode {
60 OverlayMode::Ephemeral => Some(MemoryFileSystem::new()),
61 OverlayMode::ReadOnly => None,
62 };
63 if let Some(upper_filesystem) = upper.as_mut() {
64 sync_upper_root_metadata(upper_filesystem, &effective_lowers);
65 }
66
67 Self {
68 lowers: effective_lowers,
69 upper,
70 writes_locked: matches!(mode, OverlayMode::ReadOnly),
71 }
72 }
73
74 pub fn with_upper(lowers: Vec<MemoryFileSystem>, upper: MemoryFileSystem) -> Self {
75 let mut effective_lowers = lowers;
76 if effective_lowers.is_empty() {
77 effective_lowers.push(MemoryFileSystem::new());
78 }
79
80 Self {
81 lowers: effective_lowers,
82 upper: Some(upper),
83 writes_locked: false,
84 }
85 }
86
87 pub fn lock_writes(&mut self) {
88 self.writes_locked = true;
89 }
90
91 fn normalized(path: &str) -> String {
92 normalize_path(path)
93 }
94
95 fn parent_path(path: &str) -> String {
96 let normalized = Self::normalized(path);
97 if normalized == "/" {
98 return String::from("/");
99 }
100
101 match normalized.rsplit_once('/') {
102 Some(("", _)) | None => String::from("/"),
103 Some((parent, _)) => String::from(parent),
104 }
105 }
106
107 fn basename(path: &str) -> String {
108 let normalized = Self::normalized(path);
109 if normalized == "/" {
110 return String::from("/");
111 }
112 normalized
113 .rsplit('/')
114 .find(|component| !component.is_empty())
115 .unwrap_or("")
116 .to_owned()
117 }
118
119 fn validate_destination_parent(&mut self, path: &str) -> VfsResult<()> {
120 let parent = Self::parent_path(path);
121 let resolved_parent = self.resolve_merged_path(&parent, true, 0)?;
122 let stat = self.merged_lstat(&resolved_parent)?;
123 if !stat.is_directory {
124 return Err(Self::not_directory(&parent));
125 }
126 Ok(())
127 }
128
129 fn resolved_destination_path(&self, path: &str) -> VfsResult<String> {
130 let parent = Self::parent_path(path);
131 let resolved_parent = self.resolve_merged_path(&parent, true, 0)?;
132 Ok(Self::join_path(&resolved_parent, &Self::basename(path)))
133 }
134
135 fn resolve_merged_path(
136 &self,
137 path: &str,
138 follow_final_symlink: bool,
139 depth: usize,
140 ) -> VfsResult<String> {
141 if depth > MAX_SNAPSHOT_DEPTH {
142 return Err(VfsError::new(
143 "ELOOP",
144 format!("too many symbolic links while resolving '{path}'"),
145 ));
146 }
147
148 let normalized = Self::normalized(path);
149 if normalized == "/" {
150 return Ok(normalized);
151 }
152
153 let components: Vec<&str> = normalized
154 .split('/')
155 .filter(|component| !component.is_empty())
156 .collect();
157 let mut current = String::from("/");
158
159 for (index, component) in components.iter().enumerate() {
160 let candidate = Self::join_path(¤t, component);
161 let is_final = index + 1 == components.len();
162 let should_follow = !is_final || follow_final_symlink;
163
164 if should_follow {
165 if let Ok(stat) = self.merged_lstat(&candidate) {
166 if stat.is_symbolic_link {
167 let target = self.read_link_inner(&candidate)?;
168 let target_path = if target.starts_with('/') {
169 Self::normalized(&target)
170 } else {
171 Self::normalized(&Self::join_path(
172 &Self::parent_path(&candidate),
173 &target,
174 ))
175 };
176 let remainder = components[index + 1..].join("/");
177 let next_path = if remainder.is_empty() {
178 target_path
179 } else {
180 Self::normalized(&Self::join_path(&target_path, &remainder))
181 };
182 return self.resolve_merged_path(
183 &next_path,
184 follow_final_symlink,
185 depth + 1,
186 );
187 }
188
189 if !is_final && !stat.is_directory {
190 return Err(Self::not_directory(&candidate));
191 }
192 }
193 } else if let Ok(stat) = self.merged_lstat(&candidate) {
194 if !is_final && !stat.is_directory {
195 return Err(Self::not_directory(&candidate));
196 }
197 }
198
199 current = candidate;
200 }
201
202 Ok(current)
203 }
204
205 fn destination_parent_copy_up_paths(&self, path: &str) -> VfsResult<Vec<String>> {
206 let parent = Self::parent_path(path);
207 let mut paths = Vec::new();
208 let mut seen = BTreeSet::new();
209 self.collect_destination_parent_copy_up_paths(&parent, &mut paths, &mut seen, 0)?;
210 Ok(paths)
211 }
212
213 fn collect_destination_parent_copy_up_paths(
214 &self,
215 parent: &str,
216 paths: &mut Vec<String>,
217 seen: &mut BTreeSet<String>,
218 depth: usize,
219 ) -> VfsResult<()> {
220 if depth > MAX_SNAPSHOT_DEPTH {
221 return Err(VfsError::new(
222 "ELOOP",
223 format!("too many symbolic links while resolving '{parent}'"),
224 ));
225 }
226
227 let normalized = Self::normalized(parent);
228 if normalized == "/" {
229 return Ok(());
230 }
231
232 let components: Vec<&str> = normalized
233 .split('/')
234 .filter(|component| !component.is_empty())
235 .collect();
236 let mut current = String::from("/");
237 for (index, component) in components.iter().enumerate() {
238 current = Self::join_path(¤t, component);
239 let stat = self.merged_lstat(¤t)?;
240
241 if stat.is_symbolic_link {
242 if !self.has_entry_in_upper(¤t) && seen.insert(current.clone()) {
243 paths.push(current.clone());
244 }
245
246 let target = self.read_link_inner(¤t)?;
247 let target_path = if target.starts_with('/') {
248 Self::normalized(&target)
249 } else {
250 Self::normalized(&Self::join_path(&Self::parent_path(¤t), &target))
251 };
252 let remainder = components[index + 1..].join("/");
253 let next_parent = if remainder.is_empty() {
254 target_path
255 } else {
256 Self::normalized(&Self::join_path(&target_path, &remainder))
257 };
258 return self.collect_destination_parent_copy_up_paths(
259 &next_parent,
260 paths,
261 seen,
262 depth + 1,
263 );
264 }
265
266 if self.find_lower_by_entry(¤t).is_some()
267 && !self.has_entry_in_upper(¤t)
268 && seen.insert(current.clone())
269 {
270 paths.push(current.clone());
271 }
272 }
273
274 Ok(())
275 }
276
277 fn encode_marker_path(path: &str) -> String {
278 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(path)
279 }
280
281 fn marker_directory(kind: OverlayMarkerKind) -> &'static str {
282 match kind {
283 OverlayMarkerKind::Whiteout => OVERLAY_WHITEOUT_DIR,
284 OverlayMarkerKind::Opaque => OVERLAY_OPAQUE_DIR,
285 }
286 }
287
288 fn marker_path(kind: OverlayMarkerKind, path: &str) -> String {
289 format!(
290 "{}/{}",
291 Self::marker_directory(kind),
292 Self::encode_marker_path(&Self::normalized(path))
293 )
294 }
295
296 fn is_internal_metadata_path(path: &str) -> bool {
297 let normalized = Self::normalized(path);
298 normalized == OVERLAY_METADATA_ROOT
299 || normalized.starts_with(&(String::from(OVERLAY_METADATA_ROOT) + "/"))
300 }
301
302 fn touches_internal_metadata(&self, path: &str) -> bool {
313 if Self::is_internal_metadata_path(path) {
314 return true;
315 }
316 if let Ok(resolved) = self.resolve_merged_path(path, true, 0) {
317 if Self::is_internal_metadata_path(&resolved) {
318 return true;
319 }
320 }
321 if let Ok(resolved) = self.resolved_destination_path(path) {
322 if Self::is_internal_metadata_path(&resolved) {
323 return true;
324 }
325 }
326 false
327 }
328
329 fn entry_touches_internal_metadata(&self, path: &str) -> bool {
330 Self::is_internal_metadata_path(path)
331 || self
332 .resolved_destination_path(path)
333 .is_ok_and(|resolved| Self::is_internal_metadata_path(&resolved))
334 }
335
336 fn hidden_root_entry_name() -> &'static str {
337 ".secure-exec-overlay"
338 }
339
340 fn should_hide_directory_entry(path: &str, entry: &str) -> bool {
341 let normalized = Self::normalized(path);
342 normalized == "/" && entry == Self::hidden_root_entry_name()
343 }
344
345 fn should_ignore_raw_directory_entry(
346 upper: Option<&MemoryFileSystem>,
347 path: &str,
348 entry: &str,
349 ) -> bool {
350 if entry == "." || entry == ".." || Self::should_hide_directory_entry(path, entry) {
351 return true;
352 }
353
354 let entry_path = Self::join_path(path, entry);
355 Self::marker_exists_in_upper(upper, OverlayMarkerKind::Whiteout, &entry_path)
356 }
357
358 fn check_copy_up_usage_limits(
359 usage: &OverlayCopyUpUsage,
360 max_bytes: Option<u64>,
361 max_inodes: Option<usize>,
362 ) -> VfsResult<()> {
363 if let Some(limit) = max_bytes {
364 if usage.total_bytes > limit {
365 return Err(VfsError::new(
366 "ENOSPC",
367 format!(
368 "overlay rename copy-up bytes {} exceed configured limit {}",
369 usage.total_bytes, limit
370 ),
371 ));
372 }
373 }
374
375 if let Some(limit) = max_inodes {
376 if usage.inode_count > limit {
377 return Err(VfsError::new(
378 "ENOSPC",
379 format!(
380 "overlay rename copy-up inodes {} exceed configured limit {}",
381 usage.inode_count, limit
382 ),
383 ));
384 }
385 }
386
387 Ok(())
388 }
389
390 fn add_copy_up_usage(
391 usage: &mut OverlayCopyUpUsage,
392 bytes: u64,
393 inodes: usize,
394 max_bytes: Option<u64>,
395 max_inodes: Option<usize>,
396 ) -> VfsResult<()> {
397 usage.total_bytes = usage.total_bytes.saturating_add(bytes);
398 usage.inode_count = usage.inode_count.saturating_add(inodes);
399 Self::check_copy_up_usage_limits(usage, max_bytes, max_inodes)
400 }
401
402 fn remaining_inode_budget(
403 usage: &OverlayCopyUpUsage,
404 max_inodes: Option<usize>,
405 ) -> Option<usize> {
406 max_inodes.map(|limit| limit.saturating_sub(usage.inode_count))
407 }
408
409 fn copy_up_directory_entries_limited(
410 &mut self,
411 path: &str,
412 max_entries: Option<usize>,
413 ) -> VfsResult<Vec<String>> {
414 let Some(max_entries) = max_entries else {
415 return self.read_dir(path);
416 };
417
418 match self.read_dir_limited(path, max_entries) {
419 Ok(entries) => Ok(entries),
420 Err(error) if error.code() == "ENOMEM" => Err(VfsError::new(
421 "ENOSPC",
422 format!("overlay rename copy-up directory '{path}' exceeds configured inode limit"),
423 )),
424 Err(error) => Err(error),
425 }
426 }
427
428 fn directory_has_visible_entries_limited(&mut self, path: &str) -> VfsResult<bool> {
429 match self.read_dir_limited(path, 1) {
430 Ok(entries) => Ok(!entries.is_empty()),
431 Err(error) if error.code() == "ENOMEM" => Ok(true),
432 Err(error) => Err(error),
433 }
434 }
435
436 fn memory_subtree_usage_limited(
437 filesystem: &mut MemoryFileSystem,
438 path: &str,
439 max_bytes: Option<u64>,
440 max_inodes: Option<usize>,
441 ) -> VfsResult<OverlayCopyUpUsage> {
442 let mut usage = OverlayCopyUpUsage::default();
443 let mut visited = BTreeSet::new();
444 let mut pending = vec![Self::normalized(path)];
445 while let Some(current_path) = pending.pop() {
446 let stat = filesystem.lstat(¤t_path)?;
447 if visited.insert((stat.dev, stat.ino)) {
448 let bytes = if stat.is_directory && !stat.is_symbolic_link {
449 0
450 } else {
451 stat.size
452 };
453 Self::add_copy_up_usage(&mut usage, bytes, 1, max_bytes, max_inodes)?;
454 }
455
456 if stat.is_directory && !stat.is_symbolic_link {
457 let remaining = Self::remaining_inode_budget(&usage, max_inodes);
458 let children = if let Some(max_entries) = remaining {
459 filesystem.read_dir_limited(¤t_path, max_entries)?
460 } else {
461 filesystem.read_dir(¤t_path)?
462 };
463 for entry in children.into_iter().rev() {
464 if matches!(entry.as_str(), "." | "..") {
465 continue;
466 }
467 if Self::should_hide_directory_entry(¤t_path, &entry) {
468 continue;
469 }
470 pending.push(Self::join_path(¤t_path, &entry));
471 }
472 }
473 }
474
475 Ok(usage)
476 }
477
478 fn memory_subtree_released_usage(
479 filesystem: &mut MemoryFileSystem,
480 path: &str,
481 ) -> VfsResult<OverlayCopyUpUsage> {
482 let mut usage = OverlayCopyUpUsage::default();
483 let mut visited = BTreeSet::new();
484 let mut pending = vec![Self::normalized(path)];
485 while let Some(current_path) = pending.pop() {
486 let stat = filesystem.lstat(¤t_path)?;
487 if visited.insert((stat.dev, stat.ino)) {
488 let subtree_links = filesystem.link_count_in_subtree(stat.ino, path) as u64;
489 if stat.is_directory || stat.nlink <= subtree_links {
490 let bytes = if stat.is_directory && !stat.is_symbolic_link {
491 0
492 } else {
493 stat.size
494 };
495 Self::add_copy_up_usage(&mut usage, bytes, 1, None, None)?;
496 }
497 }
498
499 if stat.is_directory && !stat.is_symbolic_link {
500 for entry in filesystem.read_dir(¤t_path)?.into_iter().rev() {
501 if matches!(entry.as_str(), "." | "..") {
502 continue;
503 }
504 if Self::should_hide_directory_entry(¤t_path, &entry) {
505 continue;
506 }
507 pending.push(Self::join_path(¤t_path, &entry));
508 }
509 }
510 }
511
512 Ok(usage)
513 }
514
515 fn upper_usage_limited(
516 &mut self,
517 max_bytes: Option<u64>,
518 max_inodes: Option<usize>,
519 ) -> VfsResult<OverlayCopyUpUsage> {
520 let Some(upper) = self.upper.as_mut() else {
521 return Ok(OverlayCopyUpUsage::default());
522 };
523
524 Self::memory_subtree_usage_limited(upper, "/", max_bytes, max_inodes)
525 }
526
527 fn upper_subtree_released_usage(&mut self, path: &str) -> VfsResult<OverlayCopyUpUsage> {
528 let Some(upper) = self.upper.as_mut() else {
529 return Ok(OverlayCopyUpUsage::default());
530 };
531
532 if !upper.exists(path) {
533 return Ok(OverlayCopyUpUsage::default());
534 }
535
536 Self::memory_subtree_released_usage(upper, path)
537 }
538
539 fn collect_copy_up_usage_limited(
540 &mut self,
541 path: &str,
542 usage: &mut OverlayCopyUpUsage,
543 max_bytes: Option<u64>,
544 max_inodes: Option<usize>,
545 ) -> VfsResult<()> {
546 let mut pending = vec![(Self::normalized(path), 0usize)];
547 while let Some((current_path, depth)) = pending.pop() {
548 if depth > MAX_SNAPSHOT_DEPTH {
549 return Err(VfsError::new(
550 "EINVAL",
551 format!("overlay snapshot depth limit exceeded at '{current_path}'"),
552 ));
553 }
554
555 let stat = self.merged_lstat(¤t_path)?;
556 if !self.has_entry_in_upper(¤t_path) {
557 let bytes = if stat.is_symbolic_link {
558 self.read_link_inner(¤t_path)?.len() as u64
559 } else if stat.is_directory {
560 0
561 } else {
562 stat.size
563 };
564 Self::add_copy_up_usage(usage, bytes, 1, max_bytes, max_inodes)?;
565 }
566
567 if stat.is_directory && !stat.is_symbolic_link {
568 let children = self.copy_up_directory_entries_limited(¤t_path, max_inodes)?;
569 for entry in children.into_iter().rev() {
570 pending.push((Self::join_path(¤t_path, &entry), depth + 1));
571 }
572 }
573 }
574
575 Ok(())
576 }
577
578 fn collect_single_copy_up_usage_limited(
579 &mut self,
580 path: &str,
581 usage: &mut OverlayCopyUpUsage,
582 max_bytes: Option<u64>,
583 max_inodes: Option<usize>,
584 ) -> VfsResult<()> {
585 if self.has_entry_in_upper(path) {
586 return Ok(());
587 }
588
589 let stat = self.merged_lstat(path)?;
590 let bytes = if stat.is_symbolic_link {
591 self.read_link_inner(path)?.len() as u64
592 } else if stat.is_directory {
593 0
594 } else {
595 stat.size
596 };
597 Self::add_copy_up_usage(usage, bytes, 1, max_bytes, max_inodes)
598 }
599
600 pub fn check_rename_copy_up_limits(
601 &mut self,
602 old_path: &str,
603 new_path: &str,
604 max_bytes: Option<u64>,
605 max_inodes: Option<usize>,
606 ) -> VfsResult<()> {
607 let old_normalized = Self::normalized(old_path);
608 let new_normalized = Self::normalized(new_path);
609 if Self::is_internal_metadata_path(&old_normalized)
610 || Self::is_internal_metadata_path(&new_normalized)
611 {
612 return Err(VfsError::permission_denied("rename", old_path));
613 }
614
615 if old_normalized == "/" {
616 return Err(VfsError::permission_denied("rename", old_path));
617 }
618
619 if old_normalized == new_normalized {
620 return Ok(());
621 }
622
623 let source_stat = self.merged_lstat(old_path)?;
624 if self.writes_locked {
625 self.writable_upper(&old_normalized)?;
626 }
627 self.validate_destination_parent(&new_normalized)?;
628 let resolved_new_normalized = self.resolved_destination_path(&new_normalized)?;
629
630 if old_normalized == resolved_new_normalized {
631 return Ok(());
632 }
633
634 if source_stat.is_directory
635 && resolved_new_normalized.starts_with(&(old_normalized.clone() + "/"))
636 {
637 return Err(VfsError::new(
638 "EINVAL",
639 format!(
640 "cannot move '{}' into its own descendant '{}'",
641 old_path, new_path
642 ),
643 ));
644 }
645
646 let destination_parent_copy_up_paths =
647 self.destination_parent_copy_up_paths(&new_normalized)?;
648
649 if let Ok(destination_stat) = self.merged_lstat(&resolved_new_normalized) {
650 if destination_stat.is_directory
651 && !destination_stat.is_symbolic_link
652 && self.directory_has_visible_entries_limited(&resolved_new_normalized)?
653 {
654 return Err(Self::not_empty(&resolved_new_normalized));
655 }
656 }
657
658 let mut usage = self.upper_usage_limited(None, None)?;
659 if self.has_entry_in_upper(&resolved_new_normalized) {
660 let destination_usage = self.upper_subtree_released_usage(&resolved_new_normalized)?;
661 usage.total_bytes = usage
662 .total_bytes
663 .saturating_sub(destination_usage.total_bytes);
664 usage.inode_count = usage
665 .inode_count
666 .saturating_sub(destination_usage.inode_count);
667 }
668 Self::check_copy_up_usage_limits(&usage, max_bytes, max_inodes)?;
669 for path in destination_parent_copy_up_paths {
670 self.collect_single_copy_up_usage_limited(&path, &mut usage, max_bytes, max_inodes)?;
671 }
672 self.collect_copy_up_usage_limited(&old_normalized, &mut usage, max_bytes, max_inodes)?;
673
674 Self::check_copy_up_usage_limits(&usage, max_bytes, max_inodes)
675 }
676
677 fn marker_exists(&self, kind: OverlayMarkerKind, path: &str) -> bool {
678 Self::marker_exists_in_upper(self.upper.as_ref(), kind, path)
679 }
680
681 fn marker_exists_in_upper(
682 upper: Option<&MemoryFileSystem>,
683 kind: OverlayMarkerKind,
684 path: &str,
685 ) -> bool {
686 upper.is_some_and(|filesystem| filesystem.exists(&Self::marker_path(kind, path)))
687 }
688
689 fn is_whited_out(&self, path: &str) -> bool {
690 self.marker_exists(OverlayMarkerKind::Whiteout, path)
691 }
692
693 fn ensure_metadata_directories_in_upper(&mut self, path: &str) -> VfsResult<()> {
694 let upper = self.writable_upper(path)?;
695 upper.mkdir(OVERLAY_METADATA_ROOT, true)?;
696 upper.mkdir(OVERLAY_WHITEOUT_DIR, true)?;
697 upper.mkdir(OVERLAY_OPAQUE_DIR, true)?;
698 Ok(())
699 }
700
701 fn set_marker(&mut self, kind: OverlayMarkerKind, path: &str, present: bool) -> VfsResult<()> {
702 let marker_path = Self::marker_path(kind, path);
703 if present {
704 self.ensure_metadata_directories_in_upper(path)?;
705 self.writable_upper(path)?
706 .write_file(&marker_path, Self::normalized(path).into_bytes())?;
707 return Ok(());
708 }
709
710 if self
711 .upper
712 .as_ref()
713 .is_some_and(|upper| upper.exists(&marker_path))
714 {
715 self.writable_upper(path)?.remove_file(&marker_path)?;
716 }
717 Ok(())
718 }
719
720 fn add_whiteout(&mut self, path: &str) -> VfsResult<()> {
721 self.set_marker(OverlayMarkerKind::Whiteout, path, true)
722 }
723
724 fn remove_whiteout(&mut self, path: &str) -> VfsResult<()> {
725 self.set_marker(OverlayMarkerKind::Whiteout, path, false)
726 }
727
728 fn mark_opaque_directory(&mut self, path: &str) -> VfsResult<()> {
729 self.set_marker(OverlayMarkerKind::Opaque, path, true)
730 }
731
732 fn clear_opaque_directory(&mut self, path: &str) -> VfsResult<()> {
733 self.set_marker(OverlayMarkerKind::Opaque, path, false)
734 }
735
736 fn clear_path_metadata(&mut self, path: &str) -> VfsResult<()> {
737 self.remove_whiteout(path)?;
738 self.clear_opaque_directory(path)
739 }
740
741 fn join_path(base: &str, name: &str) -> String {
742 if base == "/" {
743 format!("/{name}")
744 } else {
745 format!("{base}/{name}")
746 }
747 }
748
749 fn rebase_path(path: &str, old_root: &str, new_root: &str) -> String {
750 if path == old_root {
751 return String::from(new_root);
752 }
753
754 format!("{new_root}{}", &path[old_root.len()..])
755 }
756
757 fn read_only_error(path: &str) -> VfsError {
758 VfsError::new("EROFS", format!("read-only filesystem: {path}"))
759 }
760
761 fn entry_not_found(path: &str) -> VfsError {
762 VfsError::new("ENOENT", format!("no such file: {path}"))
763 }
764
765 fn directory_not_found(path: &str) -> VfsError {
766 VfsError::new("ENOENT", format!("no such directory: {path}"))
767 }
768
769 fn already_exists(path: &str) -> VfsError {
770 VfsError::new("EEXIST", format!("file exists: {path}"))
771 }
772
773 fn not_directory(path: &str) -> VfsError {
774 VfsError::new("ENOTDIR", format!("not a directory: {path}"))
775 }
776
777 fn writable_upper(&mut self, path: &str) -> VfsResult<&mut MemoryFileSystem> {
778 if self.writes_locked {
779 return Err(Self::read_only_error(path));
780 }
781 self.upper
782 .as_mut()
783 .ok_or_else(|| Self::read_only_error(path))
784 }
785
786 fn path_exists_in_filesystem(filesystem: &MemoryFileSystem, path: &str) -> bool {
787 filesystem.exists(path)
788 }
789
790 fn has_entry_in_filesystem(filesystem: &MemoryFileSystem, path: &str) -> bool {
791 filesystem.lstat(path).is_ok()
792 }
793
794 fn exists_in_upper(&self, path: &str) -> bool {
795 self.upper
796 .as_ref()
797 .is_some_and(|upper| Self::path_exists_in_filesystem(upper, path))
798 }
799
800 fn has_entry_in_upper(&self, path: &str) -> bool {
801 self.upper
802 .as_ref()
803 .is_some_and(|upper| Self::has_entry_in_filesystem(upper, path))
804 }
805
806 fn find_lower_by_exists(&self, path: &str) -> Option<usize> {
807 self.lowers
808 .iter()
809 .position(|lower| Self::path_exists_in_filesystem(lower, path))
810 }
811
812 fn find_lower_by_entry(&self, path: &str) -> Option<(usize, VirtualStat)> {
813 self.lowers
814 .iter()
815 .enumerate()
816 .find_map(|(index, lower)| lower.lstat(path).ok().map(|stat| (index, stat)))
817 }
818
819 fn merged_lstat(&self, path: &str) -> VfsResult<VirtualStat> {
820 if Self::is_internal_metadata_path(path) {
821 return Err(Self::entry_not_found(path));
822 }
823 if self.is_whited_out(path) {
824 return Err(Self::entry_not_found(path));
825 }
826 if self.has_entry_in_upper(path) {
827 return self
828 .upper
829 .as_ref()
830 .expect("upper must exist when entry exists")
831 .lstat(path);
832 }
833 self.find_lower_by_entry(path)
834 .map(|(_, stat)| stat)
835 .ok_or_else(|| Self::entry_not_found(path))
836 }
837
838 fn read_link_inner(&self, path: &str) -> VfsResult<String> {
844 if Self::is_internal_metadata_path(path) {
845 return Err(Self::entry_not_found(path));
846 }
847 if self.is_whited_out(path) {
848 return Err(Self::entry_not_found(path));
849 }
850 if self.has_entry_in_upper(path) {
851 return self
852 .upper
853 .as_ref()
854 .expect("upper must exist when path exists")
855 .read_link(path);
856 }
857 let Some((index, _)) = self.find_lower_by_entry(path) else {
858 return Err(Self::entry_not_found(path));
859 };
860 self.lowers[index].read_link(path)
861 }
862
863 fn ensure_ancestor_directories_in_upper(&mut self, path: &str) -> VfsResult<()> {
864 if Self::is_internal_metadata_path(path) {
865 return Err(VfsError::permission_denied("mkdir", path));
866 }
867 let normalized = Self::normalized(path);
868 let parts = normalized
869 .split('/')
870 .filter(|part| !part.is_empty())
871 .collect::<Vec<_>>();
872
873 let mut current = String::new();
874 for part in parts.iter().take(parts.len().saturating_sub(1)) {
875 current.push('/');
876 current.push_str(part);
877
878 if self.exists_in_upper(¤t) {
879 continue;
880 }
881
882 if let Some(index) = self.find_lower_by_exists(¤t) {
883 let stat = self.lowers[index].stat(¤t)?;
884 if !stat.is_directory {
885 return Err(Self::not_directory(¤t));
886 }
887
888 let upper = self.writable_upper(¤t)?;
889 upper.mkdir(¤t, false)?;
890 upper.chmod(¤t, stat.mode)?;
891 upper.chown(¤t, stat.uid, stat.gid)?;
892 continue;
893 }
894
895 let upper = self.writable_upper(¤t)?;
896 upper.mkdir(¤t, false)?;
897 }
898
899 Ok(())
900 }
901
902 fn copy_up_path(&mut self, path: &str) -> VfsResult<()> {
903 if self.has_entry_in_upper(path) {
904 return Ok(());
905 }
906
907 self.ensure_ancestor_directories_in_upper(path)?;
908
909 let (lower_index, stat) = self
910 .find_lower_by_entry(path)
911 .ok_or_else(|| Self::entry_not_found(path))?;
912 let xattrs = match self.lowers[lower_index].list_xattrs(path, false) {
913 Ok(names) => names
914 .into_iter()
915 .map(|name| {
916 self.lowers[lower_index]
917 .get_xattr(path, &name, false)
918 .map(|value| (name, value))
919 })
920 .collect::<VfsResult<Vec<_>>>()?,
921 Err(error) if error.code() == "EOPNOTSUPP" => Vec::new(),
922 Err(error) => return Err(error),
923 };
924
925 if stat.is_symbolic_link {
926 let target = self.lowers[lower_index].read_link(path)?;
927 let upper = self.writable_upper(path)?;
928 upper.symlink(&target, path)?;
929 for (name, value) in xattrs {
930 upper.set_xattr(path, &name, value, 0, false)?;
931 }
932 return Ok(());
933 }
934
935 if stat.is_directory {
936 let upper = self.writable_upper(path)?;
937 upper.mkdir(path, false)?;
938 upper.chmod(path, stat.mode)?;
939 upper.chown(path, stat.uid, stat.gid)?;
940 for (name, value) in xattrs {
941 upper.set_xattr(path, &name, value, 0, false)?;
942 }
943 self.mark_opaque_directory(path)?;
944 return Ok(());
945 }
946
947 let data = self.lowers[lower_index].read_file(path)?;
948 let upper = self.writable_upper(path)?;
949 upper.write_file(path, data)?;
950 upper.chmod(path, stat.mode)?;
951 upper.chown(path, stat.uid, stat.gid)?;
952 for (name, value) in xattrs {
953 upper.set_xattr(path, &name, value, 0, false)?;
954 }
955 Ok(())
956 }
957
958 fn materialize_destination_parent_in_upper(&mut self, path: &str) -> VfsResult<()> {
959 if self.has_entry_in_upper(path) {
960 return Ok(());
961 }
962
963 if self
964 .merged_lstat(path)
965 .is_ok_and(|stat| stat.is_symbolic_link)
966 {
967 return self.copy_up_path(path);
968 }
969
970 self.ensure_ancestor_directories_in_upper(path)?;
971 let stat = self.merged_lstat(path)?;
972 if !stat.is_directory || stat.is_symbolic_link {
973 return Err(Self::not_directory(path));
974 }
975
976 let upper = self.writable_upper(path)?;
977 upper.create_dir(path)?;
978 upper.chmod(path, stat.mode)?;
979 upper.chown(path, stat.uid, stat.gid)?;
980 Ok(())
981 }
982
983 fn path_exists_in_merged_view(&self, path: &str) -> bool {
984 if self.is_whited_out(path) {
985 return false;
986 }
987 if self.has_entry_in_upper(path) {
988 return true;
989 }
990 self.find_lower_by_entry(path).is_some()
991 }
992
993 fn not_empty(path: &str) -> VfsError {
994 VfsError::new("ENOTEMPTY", format!("directory not empty, rmdir '{path}'"))
995 }
996
997 fn collect_snapshot_entries(
998 &mut self,
999 path: &str,
1000 entries: &mut Vec<OverlaySnapshotEntry>,
1001 ) -> VfsResult<()> {
1002 let mut pending = vec![(Self::normalized(path), 0usize)];
1003 while let Some((current_path, depth)) = pending.pop() {
1004 if depth > MAX_SNAPSHOT_DEPTH {
1005 return Err(VfsError::new(
1006 "EINVAL",
1007 format!("overlay snapshot depth limit exceeded at '{current_path}'"),
1008 ));
1009 }
1010
1011 let stat = self.merged_lstat(¤t_path)?;
1012
1013 if stat.is_symbolic_link {
1014 entries.push(OverlaySnapshotEntry {
1015 path: current_path.clone(),
1016 stat,
1017 kind: OverlaySnapshotKind::Symlink(self.read_link_inner(¤t_path)?),
1018 });
1019 continue;
1020 }
1021
1022 if stat.is_directory {
1023 entries.push(OverlaySnapshotEntry {
1024 path: current_path.clone(),
1025 stat,
1026 kind: OverlaySnapshotKind::Directory,
1027 });
1028
1029 let children = self.read_dir_with_types_inner(¤t_path)?;
1030 for entry in children.into_iter().rev() {
1031 pending.push((Self::join_path(¤t_path, &entry.name), depth + 1));
1032 }
1033 continue;
1034 }
1035
1036 entries.push(OverlaySnapshotEntry {
1037 path: current_path.clone(),
1038 stat,
1039 kind: OverlaySnapshotKind::File(self.read_file(¤t_path)?),
1040 });
1041 }
1042 Ok(())
1043 }
1044
1045 fn remove_snapshot_entries(&mut self, entries: &[OverlaySnapshotEntry]) -> VfsResult<()> {
1046 for entry in entries.iter().rev() {
1047 if self.has_entry_in_upper(&entry.path) {
1048 match entry.kind {
1049 OverlaySnapshotKind::Directory => {
1050 self.writable_upper(&entry.path)?.remove_dir(&entry.path)?;
1051 }
1052 OverlaySnapshotKind::File(_) | OverlaySnapshotKind::Symlink(_) => {
1053 self.writable_upper(&entry.path)?.remove_file(&entry.path)?;
1054 }
1055 }
1056 }
1057
1058 if self.find_lower_by_entry(&entry.path).is_some() {
1059 self.clear_opaque_directory(&entry.path)?;
1060 self.add_whiteout(&entry.path)?;
1061 } else {
1062 self.clear_path_metadata(&entry.path)?;
1063 }
1064 }
1065
1066 Ok(())
1067 }
1068
1069 fn directory_has_raw_children(&mut self, path: &str) -> VfsResult<bool> {
1070 let normalized = Self::normalized(path);
1071 let mut directory_exists = false;
1072
1073 if let Some(upper) = self.upper.as_mut() {
1074 if let Ok(entries) = upper.read_dir(&normalized) {
1075 directory_exists = true;
1076 if entries.into_iter().any(|entry| {
1077 !Self::should_ignore_raw_directory_entry(Some(&*upper), &normalized, &entry)
1078 }) {
1079 return Ok(true);
1080 }
1081 }
1082 }
1083
1084 let upper = self.upper.as_ref();
1085 for lower in self.lowers.iter_mut().rev() {
1086 if let Ok(entries) = lower.read_dir(&normalized) {
1087 directory_exists = true;
1088 if entries.into_iter().any(|entry| {
1089 !Self::should_ignore_raw_directory_entry(upper, &normalized, &entry)
1090 }) {
1091 return Ok(true);
1092 }
1093 }
1094 }
1095
1096 if !directory_exists {
1097 return Err(Self::directory_not_found(path));
1098 }
1099
1100 Ok(false)
1101 }
1102
1103 fn read_dir_with_types_inner(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
1104 if self.is_whited_out(path) {
1105 return Err(Self::directory_not_found(path));
1106 }
1107
1108 let normalized = Self::normalized(path);
1109 let mut directory_exists = false;
1110 let mut entries = Vec::<VirtualDirEntry>::new();
1111 let mut seen = BTreeSet::<String>::new();
1112 let upper = self.upper.as_ref();
1113 let include_lowers = !Self::marker_exists_in_upper(upper, OverlayMarkerKind::Opaque, path);
1114
1115 if include_lowers {
1116 for lower in self.lowers.iter_mut().rev() {
1117 if let Ok(lower_entries) = lower.read_dir_with_types(path) {
1118 directory_exists = true;
1119 for entry in lower_entries {
1120 if entry.name == "."
1121 || entry.name == ".."
1122 || Self::should_hide_directory_entry(path, &entry.name)
1123 {
1124 continue;
1125 }
1126 let child_path = if normalized == "/" {
1127 format!("/{}", entry.name)
1128 } else {
1129 format!("{normalized}/{}", entry.name)
1130 };
1131 if Self::marker_exists_in_upper(
1132 upper,
1133 OverlayMarkerKind::Whiteout,
1134 &child_path,
1135 ) || seen.contains(&entry.name)
1136 {
1137 continue;
1138 }
1139 seen.insert(entry.name.clone());
1140 entries.push(entry);
1141 }
1142 }
1143 }
1144 }
1145
1146 if let Some(upper) = self.upper.as_mut() {
1147 if let Ok(upper_entries) = upper.read_dir_with_types(path) {
1148 directory_exists = true;
1149 for entry in upper_entries {
1150 if entry.name == "."
1151 || entry.name == ".."
1152 || Self::should_hide_directory_entry(path, &entry.name)
1153 {
1154 continue;
1155 }
1156 if let Some(index) = entries
1157 .iter()
1158 .position(|existing| existing.name == entry.name)
1159 {
1160 entries[index] = entry;
1161 } else {
1162 seen.insert(entry.name.clone());
1163 entries.push(entry);
1164 }
1165 }
1166 }
1167 }
1168
1169 if !directory_exists {
1170 return Err(Self::directory_not_found(path));
1171 }
1172
1173 Ok(entries)
1174 }
1175
1176 fn marker_paths_in_upper(&mut self, kind: OverlayMarkerKind) -> VfsResult<Vec<String>> {
1177 let Some(upper) = self.upper.as_mut() else {
1178 return Ok(Vec::new());
1179 };
1180
1181 let marker_dir = Self::marker_directory(kind);
1182 let entries = match upper.read_dir(marker_dir) {
1183 Ok(entries) => entries,
1184 Err(error) if error.code() == "ENOENT" => return Ok(Vec::new()),
1185 Err(error) => return Err(error),
1186 };
1187
1188 let mut marker_paths = Vec::new();
1189 for entry in entries {
1190 if entry == "." || entry == ".." {
1191 continue;
1192 }
1193
1194 let marker_file = Self::join_path(marker_dir, &entry);
1195 let marker_path =
1196 String::from_utf8(upper.read_file(&marker_file).map_err(|_| {
1197 VfsError::io(format!("invalid overlay marker '{marker_file}'"))
1198 })?)
1199 .map_err(|_| VfsError::io(format!("invalid overlay marker '{marker_file}'")))?;
1200 marker_paths.push(Self::normalized(&marker_path));
1201 }
1202
1203 Ok(marker_paths)
1204 }
1205
1206 fn path_in_subtree(path: &str, root: &str) -> bool {
1207 path == root || path.starts_with(&(String::from(root) + "/"))
1208 }
1209
1210 fn clear_subtree_metadata(&mut self, path: &str) -> VfsResult<()> {
1211 let normalized = Self::normalized(path);
1212 for kind in [OverlayMarkerKind::Whiteout, OverlayMarkerKind::Opaque] {
1213 for marker_path in self.marker_paths_in_upper(kind)? {
1214 if Self::path_in_subtree(&marker_path, &normalized) {
1215 self.set_marker(kind, &marker_path, false)?;
1216 }
1217 }
1218 }
1219 Ok(())
1220 }
1221
1222 fn copy_subtree_metadata(&mut self, old_root: &str, new_root: &str) -> VfsResult<()> {
1223 let old_normalized = Self::normalized(old_root);
1224 let new_normalized = Self::normalized(new_root);
1225
1226 for kind in [OverlayMarkerKind::Whiteout, OverlayMarkerKind::Opaque] {
1227 for marker_path in self.marker_paths_in_upper(kind)? {
1228 if Self::path_in_subtree(&marker_path, &old_normalized) {
1229 let destination =
1230 Self::rebase_path(&marker_path, &old_normalized, &new_normalized);
1231 self.set_marker(kind, &destination, true)?;
1232 }
1233 }
1234 }
1235
1236 Ok(())
1237 }
1238
1239 fn stage_snapshot_entries_in_upper(
1240 &mut self,
1241 entries: &[OverlaySnapshotEntry],
1242 ) -> VfsResult<()> {
1243 for entry in entries {
1244 match &entry.kind {
1245 OverlaySnapshotKind::Directory => {
1246 if !self.has_entry_in_upper(&entry.path) {
1247 self.ensure_ancestor_directories_in_upper(&entry.path)?;
1248 self.writable_upper(&entry.path)?.create_dir(&entry.path)?;
1249 }
1250 self.writable_upper(&entry.path)?
1251 .chmod(&entry.path, entry.stat.mode)?;
1252 self.writable_upper(&entry.path)?.chown(
1253 &entry.path,
1254 entry.stat.uid,
1255 entry.stat.gid,
1256 )?;
1257 self.mark_opaque_directory(&entry.path)?;
1258 }
1259 OverlaySnapshotKind::File(data) => {
1260 if self.has_entry_in_upper(&entry.path) {
1261 continue;
1262 }
1263 self.ensure_ancestor_directories_in_upper(&entry.path)?;
1264 self.writable_upper(&entry.path)?
1265 .write_file(&entry.path, data.clone())?;
1266 self.writable_upper(&entry.path)?
1267 .chmod(&entry.path, entry.stat.mode)?;
1268 self.writable_upper(&entry.path)?.chown(
1269 &entry.path,
1270 entry.stat.uid,
1271 entry.stat.gid,
1272 )?;
1273 }
1274 OverlaySnapshotKind::Symlink(target) => {
1275 if self.has_entry_in_upper(&entry.path) {
1276 continue;
1277 }
1278 self.ensure_ancestor_directories_in_upper(&entry.path)?;
1279 self.writable_upper(&entry.path)?
1280 .symlink(target, &entry.path)?;
1281 }
1282 }
1283 }
1284
1285 Ok(())
1286 }
1287}
1288
1289fn sync_upper_root_metadata(upper: &mut MemoryFileSystem, lowers: &[MemoryFileSystem]) {
1290 let Some(root_stat) = lowers.iter().find_map(|lower| lower.lstat("/").ok()) else {
1291 return;
1292 };
1293
1294 upper
1295 .chmod("/", root_stat.mode)
1296 .expect("overlay upper root should exist");
1297 upper
1298 .chown("/", root_stat.uid, root_stat.gid)
1299 .expect("overlay upper root should exist");
1300}
1301
1302impl VirtualFileSystem for OverlayFileSystem {
1303 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
1304 if self.touches_internal_metadata(path) {
1305 return Err(Self::entry_not_found(path));
1306 }
1307 if self.is_whited_out(path) {
1308 return Err(Self::entry_not_found(path));
1309 }
1310 if self.exists_in_upper(path) {
1311 return self
1312 .upper
1313 .as_mut()
1314 .expect("upper must exist when path exists")
1315 .read_file(path);
1316 }
1317 let Some(index) = self.find_lower_by_exists(path) else {
1318 return Err(Self::entry_not_found(path));
1319 };
1320 self.lowers[index].read_file(path)
1321 }
1322
1323 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
1324 if self.touches_internal_metadata(path) {
1325 return Err(Self::directory_not_found(path));
1326 }
1327 if self.is_whited_out(path) {
1328 return Err(Self::directory_not_found(path));
1329 }
1330
1331 let normalized = Self::normalized(path);
1332 let mut directory_exists = false;
1333 let mut entries = BTreeSet::new();
1334 let upper = self.upper.as_ref();
1335 let include_lowers = !Self::marker_exists_in_upper(upper, OverlayMarkerKind::Opaque, path);
1336
1337 if include_lowers {
1338 for lower in self.lowers.iter_mut().rev() {
1339 if let Ok(lower_entries) = lower.read_dir(path) {
1340 directory_exists = true;
1341 for entry in lower_entries {
1342 if entry == "."
1343 || entry == ".."
1344 || Self::should_hide_directory_entry(path, &entry)
1345 {
1346 continue;
1347 }
1348 let child_path = if normalized == "/" {
1349 format!("/{entry}")
1350 } else {
1351 format!("{normalized}/{entry}")
1352 };
1353 if !Self::marker_exists_in_upper(
1354 upper,
1355 OverlayMarkerKind::Whiteout,
1356 &child_path,
1357 ) {
1358 entries.insert(entry);
1359 }
1360 }
1361 }
1362 }
1363 }
1364
1365 if let Some(upper) = self.upper.as_mut() {
1366 if let Ok(upper_entries) = upper.read_dir(path) {
1367 directory_exists = true;
1368 for entry in upper_entries {
1369 if entry == "."
1370 || entry == ".."
1371 || Self::should_hide_directory_entry(path, &entry)
1372 {
1373 continue;
1374 }
1375 entries.insert(entry);
1376 }
1377 }
1378 }
1379
1380 if !directory_exists {
1381 return Err(Self::directory_not_found(path));
1382 }
1383
1384 Ok(entries.into_iter().collect())
1385 }
1386
1387 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
1388 if self.touches_internal_metadata(path) {
1389 return Err(Self::directory_not_found(path));
1390 }
1391 if self.is_whited_out(path) {
1392 return Err(Self::directory_not_found(path));
1393 }
1394
1395 let normalized = Self::normalized(path);
1396 let mut directory_exists = false;
1397 let mut entries = BTreeSet::new();
1398 let upper = self.upper.as_ref();
1399 let include_lowers = !Self::marker_exists_in_upper(upper, OverlayMarkerKind::Opaque, path);
1400
1401 if include_lowers {
1402 for lower in self.lowers.iter_mut().rev() {
1403 let lower_entries = match lower.read_dir_filtered_limited(
1404 path,
1405 max_entries.saturating_sub(entries.len()),
1406 |entry| {
1407 if entry == "."
1408 || entry == ".."
1409 || Self::should_hide_directory_entry(path, entry)
1410 {
1411 return false;
1412 }
1413 let child_path = if normalized == "/" {
1414 format!("/{entry}")
1415 } else {
1416 format!("{normalized}/{entry}")
1417 };
1418 !Self::marker_exists_in_upper(
1419 upper,
1420 OverlayMarkerKind::Whiteout,
1421 &child_path,
1422 ) && !entries.contains(entry)
1423 },
1424 ) {
1425 Ok(entries) => entries,
1426 Err(error) if error.code() == "ENOENT" || error.code() == "ENOTDIR" => {
1427 continue;
1428 }
1429 Err(error) => return Err(error),
1430 };
1431 directory_exists = true;
1432 for entry in lower_entries {
1433 entries.insert(entry);
1434 if entries.len() > max_entries {
1435 return Err(VfsError::new(
1436 "ENOMEM",
1437 format!(
1438 "directory listing for '{path}' exceeds configured limit of {max_entries} entries"
1439 ),
1440 ));
1441 }
1442 }
1443 }
1444 }
1445
1446 if let Some(upper) = self.upper.as_mut() {
1447 let upper_entries = match upper.read_dir_filtered_limited(
1448 path,
1449 max_entries.saturating_sub(entries.len()),
1450 |entry| {
1451 entry != "."
1452 && entry != ".."
1453 && !Self::should_hide_directory_entry(path, entry)
1454 && !entries.contains(entry)
1455 },
1456 ) {
1457 Ok(entries) => entries,
1458 Err(error) if error.code() == "ENOENT" => Vec::new(),
1459 Err(error) => return Err(error),
1460 };
1461 directory_exists = directory_exists || upper.exists(path);
1462 for entry in upper_entries {
1463 if entry == "." || entry == ".." || Self::should_hide_directory_entry(path, &entry)
1464 {
1465 continue;
1466 }
1467 entries.insert(entry);
1468 if entries.len() > max_entries {
1469 return Err(VfsError::new(
1470 "ENOMEM",
1471 format!(
1472 "directory listing for '{path}' exceeds configured limit of {max_entries} entries"
1473 ),
1474 ));
1475 }
1476 }
1477 }
1478
1479 if !directory_exists {
1480 return Err(Self::directory_not_found(path));
1481 }
1482
1483 Ok(entries.into_iter().collect())
1484 }
1485
1486 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
1487 if self.touches_internal_metadata(path) {
1488 return Err(Self::directory_not_found(path));
1489 }
1490 self.read_dir_with_types_inner(path)
1491 }
1492
1493 fn write_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
1494 if self.touches_internal_metadata(path) {
1495 return Err(VfsError::permission_denied("open", path));
1496 }
1497 self.clear_path_metadata(path)?;
1498 if self.find_lower_by_entry(path).is_some() {
1499 self.copy_up_path(path)?;
1500 } else {
1501 self.ensure_ancestor_directories_in_upper(path)?;
1502 }
1503 self.writable_upper(path)?.write_file(path, content.into())
1504 }
1505
1506 fn create_file_exclusive(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
1507 if self.touches_internal_metadata(path) {
1508 return Err(VfsError::permission_denied("open", path));
1509 }
1510 self.clear_path_metadata(path)?;
1511 if self.path_exists_in_merged_view(path) {
1512 return Err(Self::already_exists(path));
1513 }
1514 self.ensure_ancestor_directories_in_upper(path)?;
1515 self.writable_upper(path)?
1516 .create_file_exclusive(path, content.into())
1517 }
1518
1519 fn append_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<u64> {
1520 if self.touches_internal_metadata(path) {
1521 return Err(VfsError::permission_denied("open", path));
1522 }
1523 self.clear_path_metadata(path)?;
1524 if self.find_lower_by_entry(path).is_some() {
1525 self.copy_up_path(path)?;
1526 } else {
1527 self.ensure_ancestor_directories_in_upper(path)?;
1528 }
1529 self.writable_upper(path)?.append_file(path, content.into())
1530 }
1531
1532 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
1533 if self.touches_internal_metadata(path) {
1534 return Err(VfsError::permission_denied("mkdir", path));
1535 }
1536 self.clear_path_metadata(path)?;
1537 if self.path_exists_in_merged_view(path) {
1538 return Err(Self::already_exists(path));
1539 }
1540 self.ensure_ancestor_directories_in_upper(path)?;
1541 self.writable_upper(path)?.create_dir(path)
1542 }
1543
1544 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> {
1545 if self.touches_internal_metadata(path) {
1546 return Err(VfsError::permission_denied("mkdir", path));
1547 }
1548 self.clear_path_metadata(path)?;
1549 if self.path_exists_in_merged_view(path) {
1550 let stat = self.merged_lstat(path)?;
1551 if recursive && stat.is_directory && !stat.is_symbolic_link {
1552 return Ok(());
1553 }
1554 return Err(Self::already_exists(path));
1555 }
1556 self.ensure_ancestor_directories_in_upper(path)?;
1557 self.writable_upper(path)?.mkdir(path, recursive)
1558 }
1559
1560 fn mknod(&mut self, path: &str, mode: u32, rdev: u64) -> VfsResult<()> {
1561 if self.touches_internal_metadata(path) {
1562 return Err(VfsError::permission_denied("mknod", path));
1563 }
1564 self.clear_path_metadata(path)?;
1565 if self.path_exists_in_merged_view(path) {
1566 return Err(Self::already_exists(path));
1567 }
1568 self.ensure_ancestor_directories_in_upper(path)?;
1569 self.writable_upper(path)?.mknod(path, mode, rdev)
1570 }
1571
1572 fn exists(&self, path: &str) -> bool {
1573 if self.touches_internal_metadata(path) {
1574 return false;
1575 }
1576 self.path_exists_in_merged_view(path)
1577 }
1578
1579 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
1580 if self.touches_internal_metadata(path) {
1581 return Err(Self::entry_not_found(path));
1582 }
1583 if self.is_whited_out(path) {
1584 return Err(Self::entry_not_found(path));
1585 }
1586 if self.exists_in_upper(path) {
1587 return self
1588 .upper
1589 .as_mut()
1590 .expect("upper must exist when path exists")
1591 .stat(path);
1592 }
1593 let Some(index) = self.find_lower_by_exists(path) else {
1594 return Err(Self::entry_not_found(path));
1595 };
1596 self.lowers[index].stat(path)
1597 }
1598
1599 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
1600 if self.entry_touches_internal_metadata(path) {
1601 return Err(VfsError::permission_denied("unlink", path));
1602 }
1603 if self.is_whited_out(path) {
1604 return Err(Self::entry_not_found(path));
1605 }
1606 let lower_exists = self.find_lower_by_entry(path).is_some();
1612 let upper_exists = self.has_entry_in_upper(path);
1613 if !lower_exists && !upper_exists {
1614 return Err(Self::entry_not_found(path));
1615 }
1616 if upper_exists {
1617 self.writable_upper(path)?.remove_file(path)?;
1618 } else {
1619 self.writable_upper(path)?;
1620 }
1621 self.clear_opaque_directory(path)?;
1622 self.add_whiteout(path)?;
1623 Ok(())
1624 }
1625
1626 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
1627 let normalized = Self::normalized(path);
1628 if self.touches_internal_metadata(&normalized) {
1629 return Err(VfsError::permission_denied("rmdir", path));
1630 }
1631 if normalized == "/" {
1632 return Err(VfsError::permission_denied("rmdir", path));
1633 }
1634
1635 let stat = match self.merged_lstat(path) {
1636 Ok(stat) => stat,
1637 Err(error) if error.code() == "ENOENT" => return Err(Self::directory_not_found(path)),
1638 Err(error) => return Err(error),
1639 };
1640
1641 if !stat.is_directory || stat.is_symbolic_link {
1642 return Err(Self::not_directory(path));
1643 }
1644
1645 if self.directory_has_raw_children(path)? {
1646 return Err(Self::not_empty(path));
1647 }
1648
1649 let lower_exists = self.find_lower_by_entry(path).is_some();
1650 let upper_exists = self.has_entry_in_upper(path);
1651 if upper_exists {
1652 self.writable_upper(path)?.remove_dir(&normalized)?;
1653 } else {
1654 self.writable_upper(path)?;
1655 }
1656 if lower_exists {
1657 self.clear_opaque_directory(path)?;
1658 self.add_whiteout(path)?;
1659 } else {
1660 self.clear_path_metadata(path)?;
1661 }
1662 Ok(())
1663 }
1664
1665 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
1666 let old_normalized = Self::normalized(old_path);
1667 let new_normalized = Self::normalized(new_path);
1668 if self.touches_internal_metadata(&old_normalized)
1669 || self.touches_internal_metadata(&new_normalized)
1670 {
1671 return Err(VfsError::permission_denied("rename", old_path));
1672 }
1673
1674 if old_normalized == "/" {
1675 return Err(VfsError::permission_denied("rename", old_path));
1676 }
1677
1678 if old_normalized == new_normalized {
1679 return Ok(());
1680 }
1681
1682 let source_stat = self.merged_lstat(old_path)?;
1683 self.validate_destination_parent(&new_normalized)?;
1684 let resolved_new_normalized = self.resolved_destination_path(&new_normalized)?;
1685
1686 if old_normalized == resolved_new_normalized {
1687 return Ok(());
1688 }
1689
1690 if source_stat.is_directory
1691 && resolved_new_normalized.starts_with(&(old_normalized.clone() + "/"))
1692 {
1693 return Err(VfsError::new(
1694 "EINVAL",
1695 format!(
1696 "cannot move '{}' into its own descendant '{}'",
1697 old_path, new_path
1698 ),
1699 ));
1700 }
1701
1702 for path in self.destination_parent_copy_up_paths(&new_normalized)? {
1703 self.materialize_destination_parent_in_upper(&path)?;
1704 }
1705
1706 let mut snapshot_entries = Vec::new();
1707 self.collect_snapshot_entries(&old_normalized, &mut snapshot_entries)?;
1708
1709 self.clear_path_metadata(&resolved_new_normalized)?;
1710 self.clear_subtree_metadata(&resolved_new_normalized)?;
1711 if let Ok(destination_stat) = self.merged_lstat(&resolved_new_normalized) {
1712 if destination_stat.is_directory
1713 && !destination_stat.is_symbolic_link
1714 && self.directory_has_visible_entries_limited(&resolved_new_normalized)?
1715 {
1716 return Err(Self::not_empty(&resolved_new_normalized));
1717 }
1718
1719 if self.has_entry_in_upper(&resolved_new_normalized) {
1720 if destination_stat.is_directory && !destination_stat.is_symbolic_link {
1721 self.writable_upper(&resolved_new_normalized)?
1722 .remove_dir(&resolved_new_normalized)?;
1723 } else {
1724 self.writable_upper(&resolved_new_normalized)?
1725 .remove_file(&resolved_new_normalized)?;
1726 }
1727 }
1728 }
1729
1730 self.stage_snapshot_entries_in_upper(&snapshot_entries)?;
1731 self.copy_subtree_metadata(&old_normalized, &resolved_new_normalized)?;
1732 self.writable_upper(&old_normalized)?
1733 .rename(&old_normalized, &resolved_new_normalized)?;
1734 self.remove_snapshot_entries(&snapshot_entries)
1735 }
1736
1737 fn realpath(&self, path: &str) -> VfsResult<String> {
1738 if self.touches_internal_metadata(path) {
1739 return Err(Self::entry_not_found(path));
1740 }
1741 if self.is_whited_out(path) {
1742 return Err(Self::entry_not_found(path));
1743 }
1744 if self.exists_in_upper(path) {
1745 return self
1746 .upper
1747 .as_ref()
1748 .expect("upper must exist when path exists")
1749 .realpath(path);
1750 }
1751 let Some(index) = self.find_lower_by_exists(path) else {
1752 return Err(Self::entry_not_found(path));
1753 };
1754 self.lowers[index].realpath(path)
1755 }
1756
1757 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> {
1758 if self.touches_internal_metadata(link_path) {
1759 return Err(VfsError::permission_denied("symlink", link_path));
1760 }
1761 self.clear_path_metadata(link_path)?;
1762 self.ensure_ancestor_directories_in_upper(link_path)?;
1763 self.writable_upper(link_path)?.symlink(target, link_path)
1764 }
1765
1766 fn read_link(&self, path: &str) -> VfsResult<String> {
1767 if self.touches_internal_metadata(path) {
1768 return Err(Self::entry_not_found(path));
1769 }
1770 if self.is_whited_out(path) {
1771 return Err(Self::entry_not_found(path));
1772 }
1773 if self.has_entry_in_upper(path) {
1774 return self
1775 .upper
1776 .as_ref()
1777 .expect("upper must exist when path exists")
1778 .read_link(path);
1779 }
1780 let Some((index, _)) = self.find_lower_by_entry(path) else {
1781 return Err(Self::entry_not_found(path));
1782 };
1783 self.lowers[index].read_link(path)
1784 }
1785
1786 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
1787 if self.touches_internal_metadata(path) {
1788 return Err(Self::entry_not_found(path));
1789 }
1790 if self.is_whited_out(path) {
1791 return Err(Self::entry_not_found(path));
1792 }
1793 if self.has_entry_in_upper(path) {
1794 return self
1795 .upper
1796 .as_ref()
1797 .expect("upper must exist when path exists")
1798 .lstat(path);
1799 }
1800 self.find_lower_by_entry(path)
1801 .map(|(_, stat)| stat)
1802 .ok_or_else(|| Self::entry_not_found(path))
1803 }
1804
1805 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
1806 if self.touches_internal_metadata(old_path) || self.touches_internal_metadata(new_path) {
1807 return Err(VfsError::permission_denied("link", new_path));
1808 }
1809 self.clear_path_metadata(new_path)?;
1810 self.copy_up_path(old_path)?;
1811 self.ensure_ancestor_directories_in_upper(new_path)?;
1812 self.writable_upper(new_path)?.link(old_path, new_path)
1813 }
1814
1815 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> {
1816 if self.touches_internal_metadata(path) {
1817 return Err(VfsError::permission_denied("chmod", path));
1818 }
1819 if self.is_whited_out(path) {
1820 return Err(Self::entry_not_found(path));
1821 }
1822 if !self.exists_in_upper(path) {
1823 self.copy_up_path(path)?;
1824 }
1825 self.writable_upper(path)?.chmod(path, mode)
1826 }
1827
1828 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
1829 self.chown_spec(path, uid, gid, true)
1830 }
1831
1832 fn chown_spec(
1833 &mut self,
1834 path: &str,
1835 uid: u32,
1836 gid: u32,
1837 follow_symlinks: bool,
1838 ) -> VfsResult<()> {
1839 if self.touches_internal_metadata(path) {
1840 return Err(VfsError::permission_denied("chown", path));
1841 }
1842 if self.is_whited_out(path) {
1843 return Err(Self::entry_not_found(path));
1844 }
1845 if !self.exists_in_upper(path) {
1846 self.copy_up_path(path)?;
1847 }
1848 self.writable_upper(path)?
1849 .chown_spec(path, uid, gid, follow_symlinks)
1850 }
1851
1852 fn lchown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
1853 if self.touches_internal_metadata(path) {
1854 return Err(VfsError::permission_denied("lchown", path));
1855 }
1856 if self.is_whited_out(path) {
1857 return Err(Self::entry_not_found(path));
1858 }
1859 if !self.has_entry_in_upper(path) {
1860 self.copy_up_path(path)?;
1861 }
1862 self.writable_upper(path)?.lchown(path, uid, gid)
1863 }
1864
1865 fn get_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<Vec<u8>> {
1866 if self.touches_internal_metadata(path) || self.is_whited_out(path) {
1867 return Err(Self::entry_not_found(path));
1868 }
1869 let upper_exists = if follow_symlinks {
1870 self.exists_in_upper(path)
1871 } else {
1872 self.has_entry_in_upper(path)
1873 };
1874 if upper_exists {
1875 return self
1876 .upper
1877 .as_mut()
1878 .expect("upper must exist when path exists")
1879 .get_xattr(path, name, follow_symlinks);
1880 }
1881 let lower_index = if follow_symlinks {
1882 self.find_lower_by_exists(path)
1883 } else {
1884 self.find_lower_by_entry(path).map(|(index, _)| index)
1885 }
1886 .ok_or_else(|| Self::entry_not_found(path))?;
1887 self.lowers[lower_index].get_xattr(path, name, follow_symlinks)
1888 }
1889
1890 fn list_xattrs(&mut self, path: &str, follow_symlinks: bool) -> VfsResult<Vec<String>> {
1891 if self.touches_internal_metadata(path) || self.is_whited_out(path) {
1892 return Err(Self::entry_not_found(path));
1893 }
1894 let upper_exists = if follow_symlinks {
1895 self.exists_in_upper(path)
1896 } else {
1897 self.has_entry_in_upper(path)
1898 };
1899 if upper_exists {
1900 return self
1901 .upper
1902 .as_mut()
1903 .expect("upper must exist when path exists")
1904 .list_xattrs(path, follow_symlinks);
1905 }
1906 let lower_index = if follow_symlinks {
1907 self.find_lower_by_exists(path)
1908 } else {
1909 self.find_lower_by_entry(path).map(|(index, _)| index)
1910 }
1911 .ok_or_else(|| Self::entry_not_found(path))?;
1912 self.lowers[lower_index].list_xattrs(path, follow_symlinks)
1913 }
1914
1915 fn set_xattr(
1916 &mut self,
1917 path: &str,
1918 name: &str,
1919 value: Vec<u8>,
1920 flags: u32,
1921 follow_symlinks: bool,
1922 ) -> VfsResult<()> {
1923 if self.touches_internal_metadata(path) {
1924 return Err(VfsError::permission_denied("setxattr", path));
1925 }
1926 if self.is_whited_out(path) {
1927 return Err(Self::entry_not_found(path));
1928 }
1929 let upper_exists = if follow_symlinks {
1930 self.exists_in_upper(path)
1931 } else {
1932 self.has_entry_in_upper(path)
1933 };
1934 if !upper_exists {
1935 self.copy_up_path(path)?;
1936 }
1937 self.writable_upper(path)?
1938 .set_xattr(path, name, value, flags, follow_symlinks)
1939 }
1940
1941 fn remove_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<()> {
1942 if self.touches_internal_metadata(path) {
1943 return Err(VfsError::permission_denied("removexattr", path));
1944 }
1945 if self.is_whited_out(path) {
1946 return Err(Self::entry_not_found(path));
1947 }
1948 let upper_exists = if follow_symlinks {
1949 self.exists_in_upper(path)
1950 } else {
1951 self.has_entry_in_upper(path)
1952 };
1953 if !upper_exists {
1954 self.copy_up_path(path)?;
1955 }
1956 self.writable_upper(path)?
1957 .remove_xattr(path, name, follow_symlinks)
1958 }
1959
1960 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> {
1961 if self.touches_internal_metadata(path) {
1962 return Err(VfsError::permission_denied("utime", path));
1963 }
1964 if self.is_whited_out(path) {
1965 return Err(Self::entry_not_found(path));
1966 }
1967 if !self.exists_in_upper(path) {
1968 self.copy_up_path(path)?;
1969 }
1970 self.writable_upper(path)?.utimes(path, atime_ms, mtime_ms)
1971 }
1972
1973 fn utimes_spec(
1974 &mut self,
1975 path: &str,
1976 atime: VirtualUtimeSpec,
1977 mtime: VirtualUtimeSpec,
1978 follow_symlinks: bool,
1979 ) -> VfsResult<()> {
1980 if self.touches_internal_metadata(path) {
1981 return Err(VfsError::permission_denied("utime", path));
1982 }
1983 if self.is_whited_out(path) {
1984 return Err(Self::entry_not_found(path));
1985 }
1986 if !self.exists_in_upper(path) {
1987 self.copy_up_path(path)?;
1988 }
1989 self.writable_upper(path)?
1990 .utimes_spec(path, atime, mtime, follow_symlinks)
1991 }
1992
1993 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> {
1994 if self.touches_internal_metadata(path) {
1995 return Err(VfsError::permission_denied("truncate", path));
1996 }
1997 if self.is_whited_out(path) {
1998 return Err(Self::entry_not_found(path));
1999 }
2000 if !self.exists_in_upper(path) {
2001 self.copy_up_path(path)?;
2002 }
2003 self.writable_upper(path)?.truncate(path, length)
2004 }
2005
2006 fn allocate(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
2007 if self.touches_internal_metadata(path) {
2008 return Err(VfsError::permission_denied("fallocate", path));
2009 }
2010 if self.is_whited_out(path) {
2011 return Err(Self::entry_not_found(path));
2012 }
2013 if !self.exists_in_upper(path) {
2014 self.copy_up_path(path)?;
2015 }
2016 self.writable_upper(path)?.allocate(path, offset, length)
2017 }
2018
2019 fn insert_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
2020 if self.touches_internal_metadata(path) {
2021 return Err(VfsError::permission_denied("fallocate", path));
2022 }
2023 if self.is_whited_out(path) {
2024 return Err(Self::entry_not_found(path));
2025 }
2026 if !self.exists_in_upper(path) {
2027 self.copy_up_path(path)?;
2028 }
2029 self.writable_upper(path)?
2030 .insert_range(path, offset, length)
2031 }
2032
2033 fn collapse_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
2034 if self.touches_internal_metadata(path) {
2035 return Err(VfsError::permission_denied("fallocate", path));
2036 }
2037 if self.is_whited_out(path) {
2038 return Err(Self::entry_not_found(path));
2039 }
2040 if !self.exists_in_upper(path) {
2041 self.copy_up_path(path)?;
2042 }
2043 self.writable_upper(path)?
2044 .collapse_range(path, offset, length)
2045 }
2046
2047 fn zero_range(
2048 &mut self,
2049 path: &str,
2050 offset: u64,
2051 length: u64,
2052 keep_size: bool,
2053 ) -> VfsResult<()> {
2054 if self.touches_internal_metadata(path) {
2055 return Err(VfsError::permission_denied("fallocate", path));
2056 }
2057 if self.is_whited_out(path) {
2058 return Err(Self::entry_not_found(path));
2059 }
2060 if !self.exists_in_upper(path) {
2061 self.copy_up_path(path)?;
2062 }
2063 self.writable_upper(path)?
2064 .zero_range(path, offset, length, keep_size)
2065 }
2066
2067 fn punch_hole(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
2068 if self.touches_internal_metadata(path) {
2069 return Err(VfsError::permission_denied("fallocate", path));
2070 }
2071 if self.is_whited_out(path) {
2072 return Err(Self::entry_not_found(path));
2073 }
2074 if !self.exists_in_upper(path) {
2075 self.copy_up_path(path)?;
2076 }
2077 self.writable_upper(path)?.punch_hole(path, offset, length)
2078 }
2079
2080 fn allocated_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
2081 if self.touches_internal_metadata(path) || self.is_whited_out(path) {
2082 return Err(Self::entry_not_found(path));
2083 }
2084 if self.exists_in_upper(path) {
2085 self.writable_upper(path)?.allocated_ranges(path)
2086 } else {
2087 let Some(index) = self.find_lower_by_exists(path) else {
2088 return Err(Self::entry_not_found(path));
2089 };
2090 self.lowers[index].allocated_ranges(path)
2091 }
2092 }
2093
2094 fn unwritten_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
2095 if self.touches_internal_metadata(path) || self.is_whited_out(path) {
2096 return Err(Self::entry_not_found(path));
2097 }
2098 if self.exists_in_upper(path) {
2099 self.writable_upper(path)?.unwritten_ranges(path)
2100 } else {
2101 let Some(index) = self.find_lower_by_exists(path) else {
2102 return Err(Self::entry_not_found(path));
2103 };
2104 self.lowers[index].unwritten_ranges(path)
2105 }
2106 }
2107
2108 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
2109 if self.touches_internal_metadata(path) {
2110 return Err(Self::entry_not_found(path));
2111 }
2112 if self.is_whited_out(path) {
2113 return Err(Self::entry_not_found(path));
2114 }
2115 if self.exists_in_upper(path) {
2116 return self
2117 .upper
2118 .as_mut()
2119 .expect("upper must exist when path exists")
2120 .pread(path, offset, length);
2121 }
2122 let Some(index) = self.find_lower_by_exists(path) else {
2123 return Err(Self::entry_not_found(path));
2124 };
2125 self.lowers[index].pread(path, offset, length)
2126 }
2127
2128 fn pwrite(&mut self, path: &str, content: impl Into<Vec<u8>>, offset: u64) -> VfsResult<()> {
2129 if self.touches_internal_metadata(path) {
2130 return Err(VfsError::permission_denied("pwrite", path));
2131 }
2132 if self.is_whited_out(path) {
2133 return Err(Self::entry_not_found(path));
2134 }
2135 if !self.exists_in_upper(path) {
2136 self.copy_up_path(path)?;
2137 }
2138 self.writable_upper(path)?
2139 .pwrite(path, content.into(), offset)
2140 }
2141}
2142
2143#[cfg(test)]
2144mod tests {
2145 use super::{OverlayFileSystem, OverlayMode};
2146 use crate::posix::vfs::{MemoryFileSystem, VfsResult, VirtualFileSystem};
2147
2148 #[test]
2149 fn symlink_into_metadata_namespace_cannot_read_or_resurrect_whiteouts() {
2150 let mut lower = MemoryFileSystem::new();
2151 lower.mkdir("/data", true).expect("create lower directory");
2152 lower
2153 .write_file("/data/secret.txt", b"secret".to_vec())
2154 .expect("seed lower file");
2155
2156 let mut overlay = OverlayFileSystem::with_upper(vec![lower], MemoryFileSystem::new());
2157
2158 overlay
2161 .remove_file("/data/secret.txt")
2162 .expect("whiteout lower file");
2163 assert!(!overlay.exists("/data/secret.txt"));
2164
2165 overlay
2168 .symlink("/.secure-exec-overlay/whiteouts", "/escape")
2169 .expect("creating the symlink itself is allowed");
2170
2171 assert!(
2173 overlay.read_dir("/escape").is_err(),
2174 "listing the metadata namespace via a symlink must be denied"
2175 );
2176
2177 assert!(
2180 overlay.remove_file("/escape/anything").is_err(),
2181 "tampering with metadata via a symlink must be denied"
2182 );
2183 assert!(
2184 !overlay.exists("/data/secret.txt"),
2185 "deleted lower-layer file must stay deleted"
2186 );
2187
2188 overlay
2190 .symlink("/", "/rootlink")
2191 .expect("symlink to root is allowed");
2192 assert!(
2193 overlay
2194 .read_dir("/rootlink/.secure-exec-overlay/whiteouts")
2195 .is_err(),
2196 "metadata must be unreachable via an ancestor symlink too"
2197 );
2198 }
2199
2200 #[test]
2201 fn whiteouts_persist_when_overlay_reopens_with_same_upper() {
2202 let mut lower = MemoryFileSystem::new();
2203 lower.mkdir("/data", true).expect("create lower directory");
2204 lower
2205 .write_file("/data/base.txt", b"base".to_vec())
2206 .expect("seed lower file");
2207 let lower_snapshot = lower.snapshot();
2208
2209 let mut overlay = OverlayFileSystem::with_upper(
2210 vec![MemoryFileSystem::from_snapshot(lower_snapshot.clone())],
2211 MemoryFileSystem::new(),
2212 );
2213 overlay
2214 .remove_file("/data/base.txt")
2215 .expect("whiteout lower file");
2216
2217 let upper = overlay.upper.take().expect("overlay upper");
2218 let restored_lower = MemoryFileSystem::from_snapshot(lower_snapshot);
2219 let mut restored = OverlayFileSystem::with_upper(vec![restored_lower], upper);
2220
2221 assert!(!restored.exists("/data/base.txt"));
2222 assert_eq!(
2223 restored.read_dir("/data").expect("read merged directory"),
2224 Vec::<String>::new()
2225 );
2226 }
2227
2228 #[test]
2229 fn remove_file_does_not_follow_a_self_referential_symlink() {
2230 let mut overlay = OverlayFileSystem::new(Vec::new(), OverlayMode::Ephemeral);
2231 overlay
2232 .symlink("self", "/self")
2233 .expect("create self-referential symlink");
2234
2235 overlay
2236 .remove_file("/self")
2237 .expect("unlink the symlink entry without following it");
2238
2239 assert_error_code(overlay.lstat("/self"), "ENOENT");
2240 }
2241
2242 #[test]
2243 fn copied_up_directories_become_opaque_and_hide_overlay_metadata() {
2244 let mut lower = MemoryFileSystem::new();
2245 lower.mkdir("/data", true).expect("create lower directory");
2246 lower
2247 .write_file("/data/base.txt", b"base".to_vec())
2248 .expect("seed lower file");
2249
2250 let mut overlay = OverlayFileSystem::new(vec![lower], OverlayMode::Ephemeral);
2251 overlay
2252 .chmod("/data", 0o700)
2253 .expect("copy up lower directory");
2254
2255 assert_eq!(
2256 overlay.read_dir("/data").expect("read opaque directory"),
2257 Vec::<String>::new()
2258 );
2259 let root_entries = overlay.read_dir("/").expect("read root");
2260 assert!(!root_entries
2261 .iter()
2262 .any(|entry| entry == ".secure-exec-overlay"));
2263 }
2264
2265 #[test]
2266 fn remove_dir_succeeds_when_only_lower_children_are_whited_out() {
2267 let mut lower = MemoryFileSystem::new();
2268 lower.mkdir("/a", true).expect("create lower directory");
2269 lower
2270 .write_file("/a/c", b"child".to_vec())
2271 .expect("seed lower child");
2272
2273 let mut overlay = OverlayFileSystem::new(vec![lower], OverlayMode::Ephemeral);
2274 overlay.remove_file("/a/c").expect("whiteout lower child");
2275 overlay
2276 .remove_dir("/a")
2277 .expect("remove merged-empty directory");
2278
2279 assert!(!overlay.exists("/a"));
2280 assert_error_code(overlay.read_dir("/a"), "ENOENT");
2281 }
2282
2283 #[test]
2284 fn rename_clears_destination_whiteout() {
2285 let lower = MemoryFileSystem::new();
2286 let mut overlay = OverlayFileSystem::new(vec![lower], OverlayMode::Ephemeral);
2287 overlay
2288 .write_file("/archive.zip", b"old".to_vec())
2289 .expect("create old archive");
2290 overlay
2291 .remove_file("/archive.zip")
2292 .expect("whiteout old archive");
2293 overlay
2294 .write_file("/workspace-temp", b"new".to_vec())
2295 .expect("create replacement");
2296
2297 overlay
2298 .rename("/workspace-temp", "/archive.zip")
2299 .expect("rename replacement over whiteout");
2300
2301 assert_eq!(
2302 overlay
2303 .read_file("/archive.zip")
2304 .expect("read renamed file"),
2305 b"new"
2306 );
2307 assert!(!overlay.exists("/workspace-temp"));
2308 }
2309
2310 #[test]
2311 fn remove_file_unlinks_dangling_symlink() {
2312 let lower = MemoryFileSystem::new();
2316 let mut overlay = OverlayFileSystem::new(vec![lower], OverlayMode::Ephemeral);
2317 overlay.mkdir("/repo", true).expect("create directory");
2318 overlay
2319 .symlink("testing", "/repo/probe")
2320 .expect("create dangling symlink");
2321 assert!(
2322 overlay
2323 .lstat("/repo/probe")
2324 .expect("lstat dangling symlink")
2325 .is_symbolic_link
2326 );
2327
2328 overlay
2329 .remove_file("/repo/probe")
2330 .expect("unlink dangling symlink");
2331
2332 assert!(overlay.lstat("/repo/probe").is_err());
2333 assert_eq!(
2334 overlay.read_dir("/repo").expect("read emptied directory"),
2335 Vec::<String>::new()
2336 );
2337 overlay
2338 .remove_dir("/repo")
2339 .expect("rmdir emptied directory");
2340 }
2341
2342 #[test]
2343 fn remove_file_unlinks_dangling_symlink_from_lower_layer() {
2344 let mut lower = MemoryFileSystem::new();
2345 lower.mkdir("/repo", true).expect("create lower directory");
2346 lower
2347 .symlink("missing-target", "/repo/probe")
2348 .expect("seed lower dangling symlink");
2349
2350 let mut overlay = OverlayFileSystem::new(vec![lower], OverlayMode::Ephemeral);
2351 overlay
2352 .remove_file("/repo/probe")
2353 .expect("whiteout lower dangling symlink");
2354
2355 assert!(overlay.lstat("/repo/probe").is_err());
2356 overlay
2357 .remove_dir("/repo")
2358 .expect("rmdir merged-empty directory");
2359 }
2360
2361 #[test]
2362 fn remove_dir_still_rejects_visible_children() {
2363 let mut lower = MemoryFileSystem::new();
2364 lower.mkdir("/a", true).expect("create lower directory");
2365 lower
2366 .write_file("/a/c", b"child".to_vec())
2367 .expect("seed lower child");
2368
2369 let mut overlay = OverlayFileSystem::new(vec![lower], OverlayMode::Ephemeral);
2370 assert_error_code(overlay.remove_dir("/a"), "ENOTEMPTY");
2371 assert!(overlay.exists("/a/c"));
2372 }
2373
2374 fn assert_error_code<T: std::fmt::Debug>(result: VfsResult<T>, expected: &str) {
2375 let error = result.expect_err("expected operation to fail");
2376 assert_eq!(error.code(), expected);
2377 }
2378}