fix(fuse): a deleted file's trash leaves a file of the user's under the name it was sent as (B193)

This commit is contained in:
nvrl committed 2026-10-05 04:50:17 +02:00
1 parent 31863a1da1
commit 812eb3d0bc
4 files changed
+87 -3

No files matched your search

+9 -1
View File
@@ -1081,12 +1081,20 @@ impl Core {
Err(e) => return Err(e.into()),
};
let children = self.block_on_bounded(self.drive.enumerate_nodes_light(&uids))?;
// As for a create's own twin (`Core::adoptable_twin`): a node this
// mount trashed is not one, and a node in the tree was answered for.
// A file of the user's renamed to the name the create was sent under
// was taken for it, and trashed (`docs/BUGS.md` B193).
let hidden = self.hidden.lock().clone();
let Some(twin) = children
.into_iter()
.find(|node| node.name == name && !node.trashed)
.find(|node| node.name == name && !node.trashed && !hidden.contains(&node.uid))
else {
return Ok(None);
};
if self.state().by_uid.contains_key(&twin.uid) {
return Ok(None);
}
let queued = twin.uid.to_string();
for kind in [OP_RENAME, OP_REVISION, OP_TRASH] {
if self.db.has_pending_op(&queued, kind)? {
+52
View File
@@ -1736,6 +1736,58 @@ mod tests {
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
#[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"]
fn a_withdrawn_create_leaves_the_file_renamed_to_the_name_it_was_sent_under() {
// A create was sent as d and refused, the file renamed to q, and an
// empty file of the user's renamed to d. Deleting q left a trash that
// looked for what the create may have made under d, took the user's
// empty file for it and trashed it (B193).
let drive = FakeDrive::new();
let dir = scratch("withdrawn-create-renamed-over");
let daemon = Daemon::start(&dir, drive.client(1, Faults::lan())).unwrap();
let mnt = daemon.mountpoint.clone();
let empty = Some(Entry::File(Arc::new(Vec::new())));
std::fs::write(mnt.join("e.md"), b"").unwrap();
assert!(
wait_until(Duration::from_secs(30), || {
daemon.pending().is_ok_and(|items| items.is_empty())
&& drive.tree().get("e.md").cloned() == empty
}),
"{:?} {:?}",
daemon.pending(),
drive.tree()
);
drive.set_quota(0);
std::fs::write(mnt.join("d"), b"refused").unwrap();
assert!(wait_until(Duration::from_secs(30), || {
daemon
.pending()
.is_ok_and(|items| items.iter().any(|item| item.attempts > 0))
}));
std::fs::rename(mnt.join("d"), mnt.join("q")).unwrap();
std::fs::rename(mnt.join("e.md"), mnt.join("d")).unwrap();
assert!(wait_until(Duration::from_secs(30), || {
drive.tree().get("d").cloned() == empty
}));
std::fs::remove_file(mnt.join("q")).unwrap();
assert!(
wait_until(Duration::from_secs(30), || {
daemon.pending().is_ok_and(|items| items.is_empty())
}),
"{:?}",
daemon.pending()
);
let tree = drive.tree();
assert_eq!(tree.get("d").cloned(), empty, "{tree:?}");
assert_eq!(tree.len(), 1, "{tree:?}");
assert_eq!(std::fs::read(mnt.join("d")).unwrap(), b"");
assert!(matches!(daemon.stop(), Ok(MountOutcome::Shutdown)));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
#[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"]
fn a_file_made_under_the_name_of_a_create_on_the_wire_waits_for_it() {