mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
fix(fuse): a file replaced just as its create lands is trashed on Drive (B175)
This commit is contained in:
1 parent
1612a9d07a
commit
ce60e006ac
6 files changed
+97
-9
No files matched your search
@@ -1946,7 +1946,7 @@ impl ProtonFs {
|
||||
reply.error(error);
|
||||
return;
|
||||
}
|
||||
if let Err(e) = self.core.remove_replaced(victim_uid, newname) {
|
||||
if let Err(e) = self.core.remove_replaced(*victim_ino, victim_uid, newname) {
|
||||
reply.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5007,15 +5007,18 @@ impl Core {
|
||||
///
|
||||
/// A node whose own creation is still queued has never reached the server, so
|
||||
/// dropping its queued ops is the whole removal; nothing goes to the wire and
|
||||
/// it works offline.
|
||||
/// it works offline. A create that landed before its ops were dropped left
|
||||
/// none: Drive has the node, so it is trashed there like any other
|
||||
/// (`docs/BUGS.md` B175).
|
||||
///
|
||||
/// A handle still open on the replaced node keeps reading its bytes, as
|
||||
/// `rename(2)` promises: the inode stays as an unlinked entry and its
|
||||
/// staged bytes, cache and reader go at the final close, exactly as for an
|
||||
/// `unlink` under an open handle. Its queued ops go now, or the drain would
|
||||
/// upload to the node after it was trashed (`docs/BUGS.md` B106).
|
||||
fn remove_replaced(&self, uid: &NodeUid, name: &str) -> Result<(), Errno> {
|
||||
if is_local_uid(uid) {
|
||||
fn remove_replaced(&self, ino: u64, uid: &NodeUid, name: &str) -> Result<(), Errno> {
|
||||
let landed;
|
||||
let uid = if is_local_uid(uid) {
|
||||
if self.is_open_anywhere(uid) {
|
||||
self.withdraw_queued_ops(uid)?;
|
||||
} else {
|
||||
@@ -5024,9 +5027,20 @@ impl Core {
|
||||
self.for_each_state(|st| {
|
||||
st.forget_or_unlink(uid);
|
||||
});
|
||||
debug!(%uid, name, "replaced a node whose create was still queued");
|
||||
return Ok(());
|
||||
}
|
||||
match self.landed_row_uid(ino) {
|
||||
Some(real) => {
|
||||
debug!(local = %uid, %real, name, "create landed under a replace");
|
||||
landed = real;
|
||||
&landed
|
||||
}
|
||||
None => {
|
||||
debug!(%uid, name, "replaced a node whose create was still queued");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uid
|
||||
};
|
||||
// Local-first or offline, or the network goes away under the call:
|
||||
// queue the trash. The rename that follows queues as well, behind it.
|
||||
if !self.sends_inline() {
|
||||
|
||||
@@ -560,6 +560,55 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "mounts FUSE: run with `cargo test -p pdfs-fuse sim:: -- --ignored`"]
|
||||
fn a_file_replaced_as_its_create_lands_is_trashed_on_drive() {
|
||||
// The same race as B173, through a rename onto the file: the replace
|
||||
// found no op left to drop and took the file for one Drive had never
|
||||
// seen. Drive kept it under the name, and the next rename of that name
|
||||
// moved it instead of the file that replaced it (B175).
|
||||
let drive = FakeDrive::new();
|
||||
let dir = scratch("replaced-as-create-lands");
|
||||
let daemon = Daemon::start(&dir, drive.client(1, Faults::lan())).unwrap();
|
||||
let mnt = daemon.mountpoint.clone();
|
||||
std::fs::write(mnt.join("b.txt"), b"new").unwrap();
|
||||
assert!(
|
||||
wait_until(Duration::from_secs(30), || {
|
||||
daemon.pending().is_ok_and(|items| items.is_empty())
|
||||
&& drive.lookup("b.txt").is_some()
|
||||
}),
|
||||
"{:?} {:?}",
|
||||
daemon.pending(),
|
||||
drive.tree()
|
||||
);
|
||||
|
||||
let create = daemon.client.hold_reply_to_next_create();
|
||||
std::fs::write(mnt.join("a.txt"), b"old").unwrap();
|
||||
assert!(wait_until(Duration::from_secs(30), || create.reached()));
|
||||
let uid = drive.lookup("a.txt").unwrap();
|
||||
let landing = daemon.client.hold_next_landing(&uid);
|
||||
drop(create);
|
||||
assert!(wait_until(Duration::from_secs(30), || landing.reached()));
|
||||
std::fs::rename(mnt.join("b.txt"), mnt.join("a.txt")).unwrap();
|
||||
drop(landing);
|
||||
std::fs::rename(mnt.join("a.txt"), mnt.join("d")).unwrap();
|
||||
assert!(
|
||||
wait_until(Duration::from_secs(30), || {
|
||||
daemon.pending().is_ok_and(|items| items.is_empty())
|
||||
&& drive.lookup("a.txt").is_none()
|
||||
&& drive.tree().get("d") == Some(&Entry::File(Arc::new(b"new".to_vec())))
|
||||
}),
|
||||
"{:?} {:?}",
|
||||
daemon.pending(),
|
||||
drive.tree()
|
||||
);
|
||||
assert_eq!(std::fs::read(mnt.join("d")).unwrap(), b"new");
|
||||
assert!(!mnt.join("a.txt").exists());
|
||||
|
||||
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_moved_while_its_create_was_on_the_wire_keeps_its_new_name() {
|
||||
|
||||
Reference in new issue
Block a user