mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
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:
1 parent
31863a1da1
commit
812eb3d0bc
4 files changed
+87
-3
No files matched your search
@@ -1081,12 +1081,20 @@ impl Core {
|
|||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
};
|
};
|
||||||
let children = self.block_on_bounded(self.drive.enumerate_nodes_light(&uids))?;
|
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
|
let Some(twin) = children
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|node| node.name == name && !node.trashed)
|
.find(|node| node.name == name && !node.trashed && !hidden.contains(&node.uid))
|
||||||
else {
|
else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
if self.state().by_uid.contains_key(&twin.uid) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
let queued = twin.uid.to_string();
|
let queued = twin.uid.to_string();
|
||||||
for kind in [OP_RENAME, OP_REVISION, OP_TRASH] {
|
for kind in [OP_RENAME, OP_REVISION, OP_TRASH] {
|
||||||
if self.db.has_pending_op(&queued, kind)? {
|
if self.db.has_pending_op(&queued, kind)? {
|
||||||
|
|||||||
@@ -1736,6 +1736,58 @@ mod tests {
|
|||||||
let _ = std::fs::remove_dir_all(&dir);
|
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]
|
#[test]
|
||||||
#[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"]
|
#[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() {
|
fn a_file_made_under_the_name_of_a_create_on_the_wire_waits_for_it() {
|
||||||
|
|||||||
@@ -12,6 +12,29 @@ Conventions:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## B193 — Deleting a file whose create failed can trash a file of the user's on Drive
|
||||||
|
|
||||||
|
**Status:** Fixed (unverified).
|
||||||
|
**Found:** 2026-10-05, in the confirmation run on 1436c5f (`one_client_on_a_flaky_link` seed 6;
|
||||||
|
no replay reproduced it). A file renamed over `y/y/d`, then emptied, was gone from Drive and
|
||||||
|
from the local database, with nothing queued. The run kept no log. The path below explains it,
|
||||||
|
and the test reproduces that path every time.
|
||||||
|
|
||||||
|
**Where:** `Core::withdrawn_twin_in` in `crates/pdfs-fuse/src/drain.rs`.
|
||||||
|
|
||||||
|
**Cause.** A delete turns a create that was sent into a trash of what it may have made, under a
|
||||||
|
name it was sent with (B151, B184). The drain looks there for a node made in the last two
|
||||||
|
minutes that holds bytes the create sent, or none. It did not leave out a node in the tree, so
|
||||||
|
an empty file of the user's renamed to that name matched, and was trashed. A create's own search
|
||||||
|
for its twin (`Core::adoptable_twin`) already left such a node out, and nodes this mount trashed
|
||||||
|
(B162).
|
||||||
|
|
||||||
|
**Fix.** The trash's search leaves out the same nodes. A file a lost answer made that a listing
|
||||||
|
has since put in the tree stays on Drive, where the user can see and delete it.
|
||||||
|
|
||||||
|
**Test:** simulation test `a_withdrawn_create_leaves_the_file_renamed_to_the_name_it_was_sent_under`
|
||||||
|
(failed before the fix: Drive was empty).
|
||||||
|
|
||||||
## B192 — A new file closed empty reaches Drive only half a minute later
|
## B192 — A new file closed empty reaches Drive only half a minute later
|
||||||
|
|
||||||
**Status:** Fixed.
|
**Status:** Fixed.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
|
|||||||
- **Phase 2**: local ids, inode = local id, one tree for every mount (2.10.0).
|
- **Phase 2**: local ids, inode = local id, one tree for every mount (2.10.0).
|
||||||
- **Phase 3, first part**: mkdir, create, rename, unlink and rmdir on the mount are recorded
|
- **Phase 3, first part**: mkdir, create, rename, unlink and rmdir on the mount are recorded
|
||||||
locally and sent from the queue; `"local_first": false` brings back the old path (5e0f30c).
|
locally and sent from the queue; `"local_first": false` brings back the old path (5e0f30c).
|
||||||
- Fixes from the simulation and account runs since then: B125, B127, B130 and B141 to B192.
|
- Fixes from the simulation and account runs since then: B125, B127, B130 and B141 to B193.
|
||||||
- **Sync issues, first cut** (§6.2): refusals sorted into issues, shown at once in the app,
|
- **Sync issues, first cut** (§6.2): refusals sorted into issues, shown at once in the app,
|
||||||
tray and CLI; Export button and `pdfs sync export`; `pdfs sync issues`; Discard button and
|
tray and CLI; Export button and `pdfs sync export`; `pdfs sync issues`; Discard button and
|
||||||
`pdfs sync discard`; a warning badge on the node in the file browser.
|
`pdfs sync discard`; a warning badge on the node in the file browser.
|
||||||
@@ -119,7 +119,8 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
|
|||||||
saved just before a stop whose release the stop dropped (B190, fixed; older too). The run
|
saved just before a stop whose release the stop dropped (B190, fixed; older too). The run
|
||||||
on 1436c5f found a file deleted after its failed create was written again left on Drive
|
on 1436c5f found a file deleted after its failed create was written again left on Drive
|
||||||
(B191, fixed). Its test for B193 found a new empty file waiting out the idle poll (B192,
|
(B191, fixed). Its test for B193 found a new empty file waiting out the idle poll (B192,
|
||||||
fixed).
|
fixed), and seed 6 a deleted file's trash taking a file of the user's renamed to the name
|
||||||
|
its create was sent under (B193, fixed).
|
||||||
- [ ] A clean account run. The one on a6f36d5 (2026-10-04, LAN: 427 passed, 12 failed)
|
- [ ] A clean account run. The one on a6f36d5 (2026-10-04, LAN: 427 passed, 12 failed)
|
||||||
failed B113 and B121 on every on-demand mount, two mirror-to-mirror move cases, and
|
failed B113 and B121 on every on-demand mount, two mirror-to-mirror move cases, and
|
||||||
the journal check. B121 was a bug (B149, fixed); B113's case still expected creates
|
the journal check. B121 was a bug (B149, fixed); B113's case still expected creates
|
||||||
|
|||||||
Reference in new issue
Block a user