1use std::collections::HashSet;
10use std::os::unix::fs::PermissionsExt;
11use std::path::{Component, Path, PathBuf};
12use std::sync::Arc;
13
14use crate::platform::SystemTime;
15
16use parking_lot::RwLock;
17
18use super::{DirEntry, InMemoryFs, Metadata, NodeType, VirtualFs};
19use crate::error::VfsError;
20use crate::interpreter::pattern::glob_match;
21
22const MAX_SYMLINK_DEPTH: u32 = 40;
23
24pub struct OverlayFs {
47 lower: PathBuf,
48 upper: InMemoryFs,
49 whiteouts: Arc<RwLock<HashSet<PathBuf>>>,
50}
51
52enum LayerResult {
54 Whiteout,
55 Upper,
56 Lower,
57 NotFound,
58}
59
60impl OverlayFs {
61 pub fn new(lower: impl Into<PathBuf>) -> std::io::Result<Self> {
66 let lower = lower.into();
67 if !lower.is_dir() {
68 return Err(std::io::Error::new(
69 std::io::ErrorKind::NotADirectory,
70 format!("{} is not a directory", lower.display()),
71 ));
72 }
73 let lower = lower.canonicalize()?;
74 Ok(Self {
75 lower,
76 upper: InMemoryFs::new(),
77 whiteouts: Arc::new(RwLock::new(HashSet::new())),
78 })
79 }
80
81 fn is_whiteout(&self, path: &Path) -> bool {
87 let whiteouts = self.whiteouts.read();
88 let mut current = path.to_path_buf();
89 loop {
90 if whiteouts.contains(¤t) {
91 return true;
92 }
93 if !current.pop() {
94 return false;
95 }
96 }
97 }
98
99 fn add_whiteout(&self, path: &Path) {
101 self.whiteouts.write().insert(path.to_path_buf());
102 }
103
104 fn remove_whiteout(&self, path: &Path) {
106 self.whiteouts.write().remove(path);
107 }
108
109 fn upper_has_entry(&self, path: &Path) -> bool {
118 self.upper.lstat(path).is_ok()
119 }
120
121 fn resolve_layer(&self, path: &Path) -> LayerResult {
127 if self.is_whiteout(path) {
128 return LayerResult::Whiteout;
129 }
130 if self.upper_has_entry(path) {
131 return LayerResult::Upper;
132 }
133 if self.lower_exists(path) {
134 return LayerResult::Lower;
135 }
136 LayerResult::NotFound
137 }
138
139 fn lower_path(&self, vfs_path: &Path) -> PathBuf {
145 let rel = vfs_path.strip_prefix("/").unwrap_or(vfs_path.as_ref());
146 self.lower.join(rel)
147 }
148
149 fn read_lower_file(&self, path: &Path) -> Result<Vec<u8>, VfsError> {
151 let real = self.lower_path(path);
152 std::fs::read(&real).map_err(|e| map_io_error(e, path))
153 }
154
155 fn stat_lower(&self, path: &Path) -> Result<Metadata, VfsError> {
157 let real = self.lower_path(path);
158 let meta = std::fs::metadata(&real).map_err(|e| map_io_error(e, path))?;
159 Ok(map_std_metadata(&meta))
160 }
161
162 fn lstat_lower(&self, path: &Path) -> Result<Metadata, VfsError> {
164 let real = self.lower_path(path);
165 let meta = std::fs::symlink_metadata(&real).map_err(|e| map_io_error(e, path))?;
166 Ok(map_std_metadata(&meta))
167 }
168
169 fn readdir_lower(&self, path: &Path) -> Result<Vec<DirEntry>, VfsError> {
171 let real = self.lower_path(path);
172 let entries = std::fs::read_dir(&real).map_err(|e| map_io_error(e, path))?;
173 let mut result = Vec::new();
174 for entry in entries {
175 let entry = entry.map_err(|e| map_io_error(e, path))?;
176 let ft = entry.file_type().map_err(|e| map_io_error(e, path))?;
177 let node_type = if ft.is_dir() {
178 NodeType::Directory
179 } else if ft.is_symlink() {
180 NodeType::Symlink
181 } else {
182 NodeType::File
183 };
184 result.push(DirEntry {
185 name: entry.file_name().to_string_lossy().into_owned(),
186 node_type,
187 });
188 }
189 Ok(result)
190 }
191
192 fn readlink_lower(&self, path: &Path) -> Result<PathBuf, VfsError> {
194 let real = self.lower_path(path);
195 std::fs::read_link(&real).map_err(|e| map_io_error(e, path))
196 }
197
198 fn lower_exists(&self, path: &Path) -> bool {
200 let real = self.lower_path(path);
201 real.symlink_metadata().is_ok()
202 }
203
204 fn copy_up_if_needed(&self, path: &Path) -> Result<(), VfsError> {
211 if self.upper_has_entry(path) {
212 return Ok(());
213 }
214 debug_assert!(
215 !self.is_whiteout(path),
216 "copy_up_if_needed called on whiteout-ed path"
217 );
218 if let Some(parent) = path.parent()
220 && parent != Path::new("/")
221 {
222 self.ensure_upper_dir_path(parent)?;
223 }
224 let content = self.read_lower_file(path)?;
225 let meta = self.stat_lower(path)?;
226 self.upper.write_file(path, &content)?;
227 self.upper.chmod(path, meta.mode)?;
228 self.upper.utimes(path, meta.mtime)?;
229 Ok(())
230 }
231
232 fn ensure_upper_dir_path(&self, path: &Path) -> Result<(), VfsError> {
237 let norm = normalize(path)?;
238 let parts = path_components(&norm);
239 let mut built = PathBuf::from("/");
240 for name in parts {
241 built.push(name);
242 self.remove_whiteout(&built);
243 if self.upper_has_entry(&built) {
244 continue;
245 }
246 let mode = if let Ok(m) = self.stat_lower(&built) {
248 m.mode
249 } else {
250 0o755
251 };
252 self.upper.mkdir_p(&built)?;
253 self.upper.chmod(&built, mode)?;
254 }
255 Ok(())
256 }
257
258 fn whiteout_recursive(&self, dir: &Path) -> Result<(), VfsError> {
264 let entries = self.readdir_merged(dir)?;
266 for entry in &entries {
267 let child = dir.join(&entry.name);
268 if entry.node_type == NodeType::Directory {
269 self.whiteout_recursive(&child)?;
270 }
271 self.add_whiteout(&child);
272 }
273 Ok(())
274 }
275
276 fn readdir_merged(&self, path: &Path) -> Result<Vec<DirEntry>, VfsError> {
282 let mut entries: std::collections::BTreeMap<String, DirEntry> =
283 std::collections::BTreeMap::new();
284
285 if self.lower_exists(path)
287 && let Ok(lower_entries) = self.readdir_lower(path)
288 {
289 for e in lower_entries {
290 let child_path = path.join(&e.name);
291 if !self.is_whiteout(&child_path) {
292 entries.insert(e.name.clone(), e);
293 }
294 }
295 }
296
297 if self.upper_has_entry(path)
299 && let Ok(upper_entries) = self.upper.readdir(path)
300 {
301 for e in upper_entries {
302 entries.insert(e.name.clone(), e);
303 }
304 }
305
306 Ok(entries.into_values().collect())
307 }
308
309 fn resolve_path(&self, path: &Path, follow_final: bool) -> Result<PathBuf, VfsError> {
315 self.resolve_path_depth(path, follow_final, MAX_SYMLINK_DEPTH)
316 }
317
318 fn resolve_path_depth(
319 &self,
320 path: &Path,
321 follow_final: bool,
322 depth: u32,
323 ) -> Result<PathBuf, VfsError> {
324 if depth == 0 {
325 return Err(VfsError::SymlinkLoop(path.to_path_buf()));
326 }
327
328 let norm = normalize(path)?;
329 let parts = path_components(&norm);
330 let mut resolved = PathBuf::from("/");
331
332 for (i, name) in parts.iter().enumerate() {
333 let is_last = i == parts.len() - 1;
334 let candidate = resolved.join(name);
335
336 if self.is_whiteout(&candidate) {
337 return Err(VfsError::NotFound(path.to_path_buf()));
338 }
339
340 let is_symlink_in_upper = self
342 .upper
343 .lstat(&candidate)
344 .is_ok_and(|m| m.node_type == NodeType::Symlink);
345 let is_symlink_in_lower = !is_symlink_in_upper
346 && self
347 .lstat_lower(&candidate)
348 .is_ok_and(|m| m.node_type == NodeType::Symlink);
349
350 if is_symlink_in_upper || is_symlink_in_lower {
351 if is_last && !follow_final {
352 resolved = candidate;
353 } else {
354 let target = if is_symlink_in_upper {
356 self.upper.readlink(&candidate)?
357 } else {
358 self.readlink_lower(&candidate)?
359 };
360 let abs_target = if target.is_absolute() {
362 target
363 } else {
364 resolved.join(&target)
365 };
366 resolved = self.resolve_path_depth(&abs_target, true, depth - 1)?;
367 }
368 } else {
369 resolved = candidate;
370 }
371 }
372 Ok(resolved)
373 }
374
375 fn glob_walk(
381 &self,
382 dir: &Path,
383 components: &[&str],
384 current_path: PathBuf,
385 results: &mut Vec<PathBuf>,
386 max: usize,
387 ) {
388 if results.len() >= max || components.is_empty() {
389 if components.is_empty() {
390 results.push(current_path);
391 }
392 return;
393 }
394
395 let pattern = components[0];
396 let rest = &components[1..];
397
398 if pattern == "**" {
399 self.glob_walk(dir, rest, current_path.clone(), results, max);
401
402 if let Ok(entries) = self.readdir_merged(dir) {
404 for entry in entries {
405 if results.len() >= max {
406 return;
407 }
408 if entry.name.starts_with('.') {
409 continue;
410 }
411 let child_path = current_path.join(&entry.name);
412 let child_dir = dir.join(&entry.name);
413 if entry.node_type == NodeType::Directory
414 || entry.node_type == NodeType::Symlink
415 {
416 self.glob_walk(&child_dir, components, child_path, results, max);
417 }
418 }
419 }
420 } else if let Ok(entries) = self.readdir_merged(dir) {
421 for entry in entries {
422 if results.len() >= max {
423 return;
424 }
425 if entry.name.starts_with('.') && !pattern.starts_with('.') {
426 continue;
427 }
428 if glob_match(pattern, &entry.name) {
429 let child_path = current_path.join(&entry.name);
430 let child_dir = dir.join(&entry.name);
431 if rest.is_empty() {
432 results.push(child_path);
433 } else if entry.node_type == NodeType::Directory
434 || entry.node_type == NodeType::Symlink
435 {
436 self.glob_walk(&child_dir, rest, child_path, results, max);
437 }
438 }
439 }
440 }
441 }
442}
443
444impl VirtualFs for OverlayFs {
449 fn read_file(&self, path: &Path) -> Result<Vec<u8>, VfsError> {
450 let norm = normalize(path)?;
451 let resolved = self.resolve_path(&norm, true)?;
452 match self.resolve_layer(&resolved) {
453 LayerResult::Whiteout => Err(VfsError::NotFound(path.to_path_buf())),
454 LayerResult::Upper => self.upper.read_file(&resolved),
455 LayerResult::Lower => self.read_lower_file(&resolved),
456 LayerResult::NotFound => Err(VfsError::NotFound(path.to_path_buf())),
457 }
458 }
459
460 fn write_file(&self, path: &Path, content: &[u8]) -> Result<(), VfsError> {
461 let norm = normalize(path)?;
462 if let Some(parent) = norm.parent()
464 && parent != Path::new("/")
465 {
466 self.ensure_upper_dir_path(parent)?;
467 }
468 self.remove_whiteout(&norm);
470 self.upper.write_file(&norm, content)
471 }
472
473 fn append_file(&self, path: &Path, content: &[u8]) -> Result<(), VfsError> {
474 let norm = normalize(path)?;
475 let resolved = self.resolve_path(&norm, true)?;
476 match self.resolve_layer(&resolved) {
477 LayerResult::Whiteout => Err(VfsError::NotFound(path.to_path_buf())),
478 LayerResult::Upper => self.upper.append_file(&resolved, content),
479 LayerResult::Lower => {
480 self.copy_up_if_needed(&resolved)?;
481 self.upper.append_file(&resolved, content)
482 }
483 LayerResult::NotFound => Err(VfsError::NotFound(path.to_path_buf())),
484 }
485 }
486
487 fn remove_file(&self, path: &Path) -> Result<(), VfsError> {
488 let norm = normalize(path)?;
489 if self.is_whiteout(&norm) {
490 return Err(VfsError::NotFound(path.to_path_buf()));
491 }
492 let in_upper = self.upper_has_entry(&norm);
493 let in_lower = self.lower_exists(&norm);
494 if !in_upper && !in_lower {
495 return Err(VfsError::NotFound(path.to_path_buf()));
496 }
497 if in_upper {
499 if let Ok(m) = self.upper.lstat(&norm)
500 && m.node_type == NodeType::Directory
501 {
502 return Err(VfsError::IsADirectory(path.to_path_buf()));
503 }
504 } else if let Ok(m) = self.lstat_lower(&norm)
505 && m.node_type == NodeType::Directory
506 {
507 return Err(VfsError::IsADirectory(path.to_path_buf()));
508 }
509 if in_upper {
510 self.upper.remove_file(&norm)?;
511 }
512 self.add_whiteout(&norm);
513 Ok(())
514 }
515
516 fn mkdir(&self, path: &Path) -> Result<(), VfsError> {
517 let norm = normalize(path)?;
518 if self.is_whiteout(&norm) {
519 self.remove_whiteout(&norm);
521 } else {
522 let in_upper = self.upper_has_entry(&norm);
524 let in_lower = self.lower_exists(&norm);
525 if in_upper || in_lower {
526 return Err(VfsError::AlreadyExists(path.to_path_buf()));
527 }
528 }
529 if let Some(parent) = norm.parent()
531 && parent != Path::new("/")
532 {
533 self.ensure_upper_dir_path(parent)?;
534 }
535 if self.upper_has_entry(&norm) {
537 return Err(VfsError::AlreadyExists(path.to_path_buf()));
538 }
539 self.upper.mkdir(&norm)
540 }
541
542 fn mkdir_p(&self, path: &Path) -> Result<(), VfsError> {
543 let norm = normalize(path)?;
544 let parts = path_components(&norm);
545 if parts.is_empty() {
546 return Ok(());
547 }
548
549 let mut built = PathBuf::from("/");
550 for name in parts {
551 built.push(name);
552
553 if self.is_whiteout(&built) {
555 self.remove_whiteout(&built);
556 self.ensure_single_dir_in_upper(&built)?;
558 continue;
559 }
560
561 if self.upper_has_entry(&built) {
563 let m = self.upper.lstat(&built)?;
564 if m.node_type != NodeType::Directory {
565 return Err(VfsError::NotADirectory(path.to_path_buf()));
566 }
567 continue;
568 }
569
570 if self.lower_exists(&built) {
572 let m = self.lstat_lower(&built)?;
573 if m.node_type != NodeType::Directory {
574 return Err(VfsError::NotADirectory(path.to_path_buf()));
575 }
576 continue;
577 }
578
579 self.ensure_single_dir_in_upper(&built)?;
581 }
582 Ok(())
583 }
584
585 fn readdir(&self, path: &Path) -> Result<Vec<DirEntry>, VfsError> {
586 let norm = normalize(path)?;
587 if self.is_whiteout(&norm) {
588 return Err(VfsError::NotFound(path.to_path_buf()));
589 }
590
591 let in_upper = self.upper_has_entry(&norm);
592 let in_lower = self.lower_exists(&norm);
593
594 if !in_upper && !in_lower {
595 return Err(VfsError::NotFound(path.to_path_buf()));
596 }
597
598 let m = self.stat(&norm)?;
600 if m.node_type != NodeType::Directory {
601 return Err(VfsError::NotADirectory(path.to_path_buf()));
602 }
603
604 self.readdir_merged(&norm)
605 }
606
607 fn remove_dir(&self, path: &Path) -> Result<(), VfsError> {
608 let norm = normalize(path)?;
609 if self.is_whiteout(&norm) {
610 return Err(VfsError::NotFound(path.to_path_buf()));
611 }
612
613 let m = self.lstat_overlay(&norm, path)?;
615 if m.node_type != NodeType::Directory {
616 return Err(VfsError::NotADirectory(path.to_path_buf()));
617 }
618
619 let entries = self.readdir_merged(&norm)?;
621 if !entries.is_empty() {
622 return Err(VfsError::DirectoryNotEmpty(path.to_path_buf()));
623 }
624
625 if self.upper_has_entry(&norm) {
627 self.upper.remove_dir(&norm).ok();
628 }
629 self.add_whiteout(&norm);
630 Ok(())
631 }
632
633 fn remove_dir_all(&self, path: &Path) -> Result<(), VfsError> {
634 let norm = normalize(path)?;
635 if self.is_whiteout(&norm) {
636 return Err(VfsError::NotFound(path.to_path_buf()));
637 }
638
639 let m = self.lstat_overlay(&norm, path)?;
641 if m.node_type != NodeType::Directory {
642 return Err(VfsError::NotADirectory(path.to_path_buf()));
643 }
644
645 self.whiteout_recursive(&norm)?;
647
648 if self.upper_has_entry(&norm) {
650 self.upper.remove_dir_all(&norm).ok();
651 }
652
653 self.add_whiteout(&norm);
655 Ok(())
656 }
657
658 fn exists(&self, path: &Path) -> bool {
659 let norm = match normalize(path) {
660 Ok(p) => p,
661 Err(_) => return false,
662 };
663 if self.is_whiteout(&norm) {
664 return false;
665 }
666 if !self.upper_has_entry(&norm) && !self.lower_exists(&norm) {
668 return false;
669 }
670 let meta = match self.lstat_overlay(&norm, &norm) {
672 Ok(m) => m,
673 Err(_) => return false,
674 };
675 if meta.node_type == NodeType::Symlink {
676 return self.stat(&norm).is_ok();
677 }
678 true
679 }
680
681 fn stat(&self, path: &Path) -> Result<Metadata, VfsError> {
682 let norm = normalize(path)?;
683 let resolved = self.resolve_path(&norm, true)?;
685 match self.resolve_layer(&resolved) {
686 LayerResult::Whiteout => Err(VfsError::NotFound(path.to_path_buf())),
687 LayerResult::Upper => self.upper.stat(&resolved),
688 LayerResult::Lower => self.stat_lower(&resolved),
689 LayerResult::NotFound => Err(VfsError::NotFound(path.to_path_buf())),
690 }
691 }
692
693 fn lstat(&self, path: &Path) -> Result<Metadata, VfsError> {
694 let norm = normalize(path)?;
695 self.lstat_overlay(&norm, path)
696 }
697
698 fn chmod(&self, path: &Path, mode: u32) -> Result<(), VfsError> {
699 let norm = normalize(path)?;
700 let resolved = self.resolve_path(&norm, true)?;
701 match self.resolve_layer(&resolved) {
702 LayerResult::Whiteout => Err(VfsError::NotFound(path.to_path_buf())),
703 LayerResult::Upper => self.upper.chmod(&resolved, mode),
704 LayerResult::Lower => {
705 let meta = self.lstat_lower(&resolved)?;
707 match meta.node_type {
708 NodeType::File => {
709 self.copy_up_if_needed(&resolved)?;
710 self.upper.chmod(&resolved, mode)
711 }
712 NodeType::Directory => {
713 self.ensure_upper_dir_path(&resolved)?;
714 self.upper.chmod(&resolved, mode)
715 }
716 NodeType::Symlink => {
717 Err(VfsError::IoError("cannot chmod a symlink directly".into()))
718 }
719 }
720 }
721 LayerResult::NotFound => Err(VfsError::NotFound(path.to_path_buf())),
722 }
723 }
724
725 fn utimes(&self, path: &Path, mtime: SystemTime) -> Result<(), VfsError> {
726 let norm = normalize(path)?;
727 let resolved = self.resolve_path(&norm, true)?;
728 match self.resolve_layer(&resolved) {
729 LayerResult::Whiteout => Err(VfsError::NotFound(path.to_path_buf())),
730 LayerResult::Upper => self.upper.utimes(&resolved, mtime),
731 LayerResult::Lower => {
732 let meta = self.lstat_lower(&resolved)?;
733 match meta.node_type {
734 NodeType::File => {
735 self.copy_up_if_needed(&resolved)?;
736 self.upper.utimes(&resolved, mtime)
737 }
738 NodeType::Directory => {
739 self.ensure_upper_dir_path(&resolved)?;
740 self.upper.utimes(&resolved, mtime)
741 }
742 NodeType::Symlink => {
743 self.copy_up_symlink_if_needed(&resolved)?;
744 self.upper.utimes(&resolved, mtime)
745 }
746 }
747 }
748 LayerResult::NotFound => Err(VfsError::NotFound(path.to_path_buf())),
749 }
750 }
751
752 fn symlink(&self, target: &Path, link: &Path) -> Result<(), VfsError> {
753 let norm_link = normalize(link)?;
754 if let Some(parent) = norm_link.parent()
756 && parent != Path::new("/")
757 {
758 self.ensure_upper_dir_path(parent)?;
759 }
760 self.remove_whiteout(&norm_link);
762 self.upper.symlink(target, &norm_link)
763 }
764
765 fn hardlink(&self, src: &Path, dst: &Path) -> Result<(), VfsError> {
766 let norm_src = normalize(src)?;
767 let norm_dst = normalize(dst)?;
768 let content = self.read_file(&norm_src)?;
770 let meta = self.stat(&norm_src)?;
771 if let Some(parent) = norm_dst.parent()
773 && parent != Path::new("/")
774 {
775 self.ensure_upper_dir_path(parent)?;
776 }
777 self.remove_whiteout(&norm_dst);
778 self.upper.write_file(&norm_dst, &content)?;
779 self.upper.chmod(&norm_dst, meta.mode)?;
780 self.upper.utimes(&norm_dst, meta.mtime)?;
781 Ok(())
782 }
783
784 fn readlink(&self, path: &Path) -> Result<PathBuf, VfsError> {
785 let norm = normalize(path)?;
786 if self.is_whiteout(&norm) {
787 return Err(VfsError::NotFound(path.to_path_buf()));
788 }
789 if self.upper_has_entry(&norm) {
790 return self.upper.readlink(&norm);
791 }
792 if self.lower_exists(&norm) {
793 return self.readlink_lower(&norm);
794 }
795 Err(VfsError::NotFound(path.to_path_buf()))
796 }
797
798 fn canonicalize(&self, path: &Path) -> Result<PathBuf, VfsError> {
799 let norm = normalize(path)?;
800 let resolved = self.resolve_path(&norm, true)?;
801 if self.is_whiteout(&resolved) {
803 return Err(VfsError::NotFound(path.to_path_buf()));
804 }
805 if !self.upper_has_entry(&resolved) && !self.lower_exists(&resolved) {
806 return Err(VfsError::NotFound(path.to_path_buf()));
807 }
808 Ok(resolved)
809 }
810
811 fn copy(&self, src: &Path, dst: &Path) -> Result<(), VfsError> {
812 let norm_src = normalize(src)?;
813 let norm_dst = normalize(dst)?;
814 let content = self.read_file(&norm_src)?;
816 let meta = self.stat(&norm_src)?;
817 self.write_file(&norm_dst, &content)?;
819 self.chmod(&norm_dst, meta.mode)?;
820 Ok(())
821 }
822
823 fn rename(&self, src: &Path, dst: &Path) -> Result<(), VfsError> {
824 let norm_src = normalize(src)?;
825 let norm_dst = normalize(dst)?;
826
827 if self.is_whiteout(&norm_src) {
828 return Err(VfsError::NotFound(src.to_path_buf()));
829 }
830
831 let in_upper = self.upper_has_entry(&norm_src);
833 let in_lower = self.lower_exists(&norm_src);
834 if !in_upper && !in_lower {
835 return Err(VfsError::NotFound(src.to_path_buf()));
836 }
837
838 let meta = self.lstat_overlay(&norm_src, src)?;
840 match meta.node_type {
841 NodeType::File => {
842 let content = self.read_file(&norm_src)?;
843 if let Some(parent) = norm_dst.parent()
845 && parent != Path::new("/")
846 {
847 self.ensure_upper_dir_path(parent)?;
848 }
849 self.remove_whiteout(&norm_dst);
850 self.upper.write_file(&norm_dst, &content)?;
851 self.upper.chmod(&norm_dst, meta.mode)?;
852 }
853 NodeType::Symlink => {
854 let target = self.readlink(&norm_src)?;
855 if let Some(parent) = norm_dst.parent()
856 && parent != Path::new("/")
857 {
858 self.ensure_upper_dir_path(parent)?;
859 }
860 self.remove_whiteout(&norm_dst);
861 self.upper.symlink(&target, &norm_dst)?;
862 }
863 NodeType::Directory => {
864 if let Some(parent) = norm_dst.parent()
866 && parent != Path::new("/")
867 {
868 self.ensure_upper_dir_path(parent)?;
869 }
870 self.remove_whiteout(&norm_dst);
871 self.upper.mkdir_p(&norm_dst)?;
872 let entries = self.readdir_merged(&norm_src)?;
873 for entry in entries {
874 let child_src = norm_src.join(&entry.name);
875 let child_dst = norm_dst.join(&entry.name);
876 self.rename(&child_src, &child_dst)?;
877 }
878 }
879 }
880
881 if in_upper {
883 match meta.node_type {
884 NodeType::Directory => {
885 self.upper.remove_dir_all(&norm_src).ok();
886 }
887 _ => {
888 self.upper.remove_file(&norm_src).ok();
889 }
890 }
891 }
892 self.add_whiteout(&norm_src);
894 Ok(())
895 }
896
897 fn glob(&self, pattern: &str, cwd: &Path) -> Result<Vec<PathBuf>, VfsError> {
898 let is_absolute = pattern.starts_with('/');
899 let abs_pattern = if is_absolute {
900 pattern.to_string()
901 } else {
902 let cwd_str = cwd.to_str().unwrap_or("/").trim_end_matches('/');
903 format!("{cwd_str}/{pattern}")
904 };
905
906 let components: Vec<&str> = abs_pattern.split('/').filter(|s| !s.is_empty()).collect();
907 let mut results = Vec::new();
908 let max = 100_000;
909 self.glob_walk(
910 Path::new("/"),
911 &components,
912 PathBuf::from("/"),
913 &mut results,
914 max,
915 );
916
917 results.sort();
918 results.dedup();
919
920 if !is_absolute {
921 results = results
922 .into_iter()
923 .filter_map(|p| p.strip_prefix(cwd).ok().map(|r| r.to_path_buf()))
924 .collect();
925 }
926
927 Ok(results)
928 }
929
930 fn deep_clone(&self) -> Arc<dyn VirtualFs> {
931 Arc::new(OverlayFs {
932 lower: self.lower.clone(),
933 upper: self.upper.deep_clone(),
934 whiteouts: Arc::new(RwLock::new(self.whiteouts.read().clone())),
935 })
936 }
937}
938
939impl OverlayFs {
944 fn lstat_overlay(&self, norm: &Path, orig: &Path) -> Result<Metadata, VfsError> {
946 if self.is_whiteout(norm) {
947 return Err(VfsError::NotFound(orig.to_path_buf()));
948 }
949 if self.upper_has_entry(norm) {
950 return self.upper.lstat(norm);
951 }
952 if self.lower_exists(norm) {
953 return self.lstat_lower(norm);
954 }
955 Err(VfsError::NotFound(orig.to_path_buf()))
956 }
957
958 fn ensure_single_dir_in_upper(&self, path: &Path) -> Result<(), VfsError> {
960 if self.upper_has_entry(path) {
961 return Ok(());
962 }
963 if let Some(parent) = path.parent()
965 && parent != Path::new("/")
966 && !self.upper_has_entry(parent)
967 {
968 self.ensure_single_dir_in_upper(parent)?;
969 }
970 self.upper.mkdir(path)
971 }
972
973 fn copy_up_symlink_if_needed(&self, path: &Path) -> Result<(), VfsError> {
975 if self.upper_has_entry(path) {
976 return Ok(());
977 }
978 if let Some(parent) = path.parent()
979 && parent != Path::new("/")
980 {
981 self.ensure_upper_dir_path(parent)?;
982 }
983 let target = self.readlink_lower(path)?;
984 self.upper.symlink(&target, path)
985 }
986}
987
988fn normalize(path: &Path) -> Result<PathBuf, VfsError> {
994 let s = path.to_str().unwrap_or("");
995 if s.is_empty() {
996 return Err(VfsError::InvalidPath("empty path".into()));
997 }
998 if !super::vfs_path_is_absolute(path) {
999 return Err(VfsError::InvalidPath(format!(
1000 "path must be absolute: {}",
1001 path.display()
1002 )));
1003 }
1004 let mut parts: Vec<String> = Vec::new();
1005 for comp in path.components() {
1006 match comp {
1007 Component::RootDir | Component::Prefix(_) => {}
1008 Component::CurDir => {}
1009 Component::ParentDir => {
1010 parts.pop();
1011 }
1012 Component::Normal(seg) => {
1013 if let Some(s) = seg.to_str() {
1014 parts.push(s.to_owned());
1015 } else {
1016 return Err(VfsError::InvalidPath(format!(
1017 "non-UTF-8 component in: {}",
1018 path.display()
1019 )));
1020 }
1021 }
1022 }
1023 }
1024 let mut result = PathBuf::from("/");
1025 for p in &parts {
1026 result.push(p);
1027 }
1028 Ok(result)
1029}
1030
1031fn path_components(path: &Path) -> Vec<&str> {
1033 path.components()
1034 .filter_map(|c| match c {
1035 Component::Normal(s) => s.to_str(),
1036 _ => None,
1037 })
1038 .collect()
1039}
1040
1041fn map_io_error(err: std::io::Error, path: &Path) -> VfsError {
1043 let p = path.to_path_buf();
1044 match err.kind() {
1045 std::io::ErrorKind::NotFound => VfsError::NotFound(p),
1046 std::io::ErrorKind::AlreadyExists => VfsError::AlreadyExists(p),
1047 std::io::ErrorKind::PermissionDenied => VfsError::PermissionDenied(p),
1048 std::io::ErrorKind::DirectoryNotEmpty => VfsError::DirectoryNotEmpty(p),
1049 std::io::ErrorKind::NotADirectory => VfsError::NotADirectory(p),
1050 std::io::ErrorKind::IsADirectory => VfsError::IsADirectory(p),
1051 _ => VfsError::IoError(err.to_string()),
1052 }
1053}
1054
1055fn map_std_metadata(meta: &std::fs::Metadata) -> Metadata {
1057 let node_type = if meta.is_symlink() {
1058 NodeType::Symlink
1059 } else if meta.is_dir() {
1060 NodeType::Directory
1061 } else {
1062 NodeType::File
1063 };
1064 Metadata {
1065 node_type,
1066 size: meta.len(),
1067 mode: meta.permissions().mode(),
1068 mtime: meta.modified().unwrap_or(SystemTime::UNIX_EPOCH),
1069 }
1070}