Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 40 additions & 8 deletions crates/bashkit/src/fs/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,14 @@ impl OverlayFs {
match meta.file_type {
FileType::File if !self.upper.exists(&child).await.unwrap_or(false) => {
self.hide_lower_file(meta.size);
self.add_whiteout(&child);
}
FileType::Directory => {
self.hide_lower_dir();
// Recurse into subdirectories
// THREAT[TM-DOS-038]: Materialize child whiteouts so
// recreating the parent does not reveal accounted children.
Box::pin(self.hide_lower_children_recursive(&child)).await;
self.add_whiteout(&child);
Comment on lines 328 to +333
}
_ => {}
}
Expand Down Expand Up @@ -610,16 +613,17 @@ impl FileSystem for OverlayFs {
}

// If was in lower, add whiteout and track hiding.
// If in_upper was also true, the lower was already hidden (by the upper
// override). The whiteout replaces the override as the hiding mechanism,
// so no additional deduction needed.
// File upper overrides already hide the lower file. Recursive directory
// deletes must still materialize child whiteouts because upper directory
// overlays merge lower children instead of replacing the subtree.
if in_lower {
// Newly hiding the lower entry only if there was no upper override
if !in_upper && let Ok(meta) = self.lower.stat(&path).await {
if let Ok(meta) = self.lower.stat(&path).await {
match meta.file_type {
FileType::File => self.hide_lower_file(meta.size),
FileType::File if !in_upper => self.hide_lower_file(meta.size),
FileType::Directory => {
Comment on lines +620 to 623
self.hide_lower_dir();
if !in_upper {
self.hide_lower_dir();
}
// THREAT[TM-DOS-038]: Recursive delete must track all
// lower children for accurate usage deduction.
if recursive {
Expand Down Expand Up @@ -1496,6 +1500,34 @@ mod tests {
);
}

#[tokio::test]
async fn test_recursive_delete_recreate_keeps_lower_children_hidden() {
let lower = Arc::new(InMemoryFs::new());
lower.mkdir(Path::new("/dir"), true).await.unwrap();
lower
.write_file(Path::new("/dir/a"), &[b'a'; 80])
.await
.unwrap();

let overlay = OverlayFs::new(lower);

overlay.remove(Path::new("/dir"), true).await.unwrap();
overlay.mkdir(Path::new("/dir"), false).await.unwrap();

assert!(
overlay.exists(Path::new("/dir")).await.unwrap(),
"recreated directory should exist"
);
assert!(
!overlay.exists(Path::new("/dir/a")).await.unwrap(),
"recursive delete child whiteout must survive parent recreation"
);
assert!(
overlay.read_file(Path::new("/dir/a")).await.is_err(),
"lower child must not reappear after mkdir removes parent whiteout"
);
}

#[tokio::test]
async fn test_recursive_delete_skips_already_hidden_children() {
let lower = Arc::new(InMemoryFs::new());
Expand Down
Loading