mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-05 17:02:11 +01:00
fix(fuse,core): remediate audit bugs B23, B26-B29, B33 and update BUGS tracking
- B23: Generate synthetic StagedWrite sidecars in rescue_scratch when sidecar is corrupted - B26: Add open_count and unlinked state fields to defer node removal and cache eviction on unlinked open files until release - B27: Discard orphaned write revisions for local UIDs unlinked prior to upload - B28: Add emergency_evict to ContentCache for LRU blob eviction upon ENOSPC - B29: Implement FUSE forget handler and forget_lookup in state to prune unreferenced inodes - B33: Quarantine failing drain ops on HTTP 40x / max retries to unblock offline sync queue - docs: Track audit items B18-B36 in docs/BUGS.md with fix details and verification results
This commit is contained in:
1 parent
fe75998cea
commit
1cd6a26be3
5 files changed
+427
-38
No files matched your search
+62
-18
@@ -613,7 +613,17 @@ impl Core {
|
||||
.and_then(|p| st.by_uid.get(p).copied())
|
||||
.unwrap_or(ORPHAN_INO);
|
||||
let uid = node.uid.clone();
|
||||
st.entries.insert(ino, Entry { uid, parent, node });
|
||||
st.entries.insert(
|
||||
ino,
|
||||
Entry {
|
||||
uid,
|
||||
parent,
|
||||
node,
|
||||
lookup_count: 1,
|
||||
open_count: 0,
|
||||
unlinked: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Pass 3: rebuild child listings for fully-enumerated folders. The root
|
||||
@@ -3323,6 +3333,9 @@ impl ProtonFs {
|
||||
uid: root.uid.clone(),
|
||||
parent: ROOT_INO,
|
||||
node: root,
|
||||
lookup_count: 1,
|
||||
open_count: 0,
|
||||
unlinked: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -3466,19 +3479,25 @@ impl ProtonFs {
|
||||
/// local cache. Backs both `unlink` and `rmdir` (Proton trashes whole
|
||||
/// subtrees, so an `rmdir` of a non-empty dir behaves the same).
|
||||
fn trash_child(&self, parent: u64, name: &str, reply: ReplyEmpty) {
|
||||
let (_ino, uid) = match self.core.lookup_child(parent, name) {
|
||||
let (ino, uid) = match self.core.lookup_child(parent, name) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
reply.error(e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let open_now = {
|
||||
let mut st = self.core.state.lock();
|
||||
st.forget_or_unlink(&uid);
|
||||
st.entries.get(&ino).map(|e| e.open_count).unwrap_or(0) > 0
|
||||
};
|
||||
// A node the server has never heard of cannot be trashed there; deleting
|
||||
// it just means its queued creation is no longer wanted. This works
|
||||
// offline, which the remote path below cannot (offline.md Phase 3b).
|
||||
if is_local_uid(&uid) {
|
||||
self.core.discard_queued_ops(&uid);
|
||||
self.core.state.lock().forget(&uid);
|
||||
if !open_now {
|
||||
self.core.discard_queued_ops(&uid);
|
||||
}
|
||||
debug!(%uid, name, "deleted a node that had not been created remotely yet");
|
||||
reply.ok();
|
||||
return;
|
||||
@@ -3504,10 +3523,11 @@ impl ProtonFs {
|
||||
reply.error(Errno::EIO);
|
||||
return;
|
||||
}
|
||||
self.core.discard_queued_ops(&uid);
|
||||
self.core.state.lock().forget(&uid);
|
||||
self.core.cache.evict(&uid);
|
||||
self.core.evict_reader(&uid);
|
||||
if !open_now {
|
||||
self.core.discard_queued_ops(&uid);
|
||||
self.core.cache.evict(&uid);
|
||||
self.core.evict_reader(&uid);
|
||||
}
|
||||
self.core.invalidate_trash();
|
||||
// Every other trash site records itself; this one did not, which made a
|
||||
// file found in the trash impossible to attribute after the fact — the
|
||||
@@ -3968,10 +3988,8 @@ impl Filesystem for ProtonFs {
|
||||
}
|
||||
|
||||
fn forget(&self, _req: &Request, ino: INodeNo, nlookup: u64) {
|
||||
let st = self.core.state.lock();
|
||||
if let Some(entry) = st.entries.get(&ino.0) {
|
||||
debug!(ino = ino.0, uid = %entry.uid, nlookup, "kernel forget message received");
|
||||
}
|
||||
let mut st = self.core.state.lock();
|
||||
st.forget_lookup(ino.0, nlookup);
|
||||
}
|
||||
|
||||
fn getattr(&self, _req: &Request, ino: INodeNo, _fh: Option<FileHandle>, reply: ReplyAttr) {
|
||||
@@ -4054,9 +4072,10 @@ impl Filesystem for ProtonFs {
|
||||
|
||||
fn open(&self, _req: &Request, ino: INodeNo, flags: OpenFlags, reply: ReplyOpen) {
|
||||
let (uid, base_mtime, base_size) = {
|
||||
let st = self.core.state.lock();
|
||||
match st.entries.get(&ino.0) {
|
||||
let mut st = self.core.state.lock();
|
||||
match st.entries.get_mut(&ino.0) {
|
||||
Some(e) if e.node.is_file() => {
|
||||
e.open_count = e.open_count.saturating_add(1);
|
||||
(e.uid.clone(), e.node.modification_time, node_size(&e.node))
|
||||
}
|
||||
Some(_) => {
|
||||
@@ -4167,10 +4186,17 @@ impl Filesystem for ProtonFs {
|
||||
};
|
||||
let is_video =
|
||||
PhotoKind::classify(Some(&e.node.name), media_type) == PhotoKind::Video;
|
||||
let fsize = self
|
||||
.core
|
||||
.pending
|
||||
.lock()
|
||||
.get(&e.uid)
|
||||
.map(|p| p.meta.len)
|
||||
.unwrap_or_else(|| node_size(&e.node));
|
||||
(
|
||||
e.uid.clone(),
|
||||
e.node.modification_time,
|
||||
node_size(&e.node),
|
||||
fsize,
|
||||
is_video,
|
||||
)
|
||||
}
|
||||
@@ -4609,10 +4635,22 @@ impl Filesystem for ProtonFs {
|
||||
_flush: bool,
|
||||
reply: ReplyEmpty,
|
||||
) {
|
||||
let handle = {
|
||||
let (handle, unlinked_uid) = {
|
||||
let mut st = self.core.state.lock();
|
||||
let unlinked_uid = if let Some(entry) = st.entries.get_mut(&_ino.0) {
|
||||
entry.open_count = entry.open_count.saturating_sub(1);
|
||||
if entry.open_count == 0 && entry.unlinked {
|
||||
let uid = entry.uid.clone();
|
||||
st.forget(&uid);
|
||||
Some(uid)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ino = st.handles.remove(&fh.0);
|
||||
ino.and_then(|i| {
|
||||
let h = ino.and_then(|i| {
|
||||
let aw = st.active_writes.get_mut(&i)?;
|
||||
aw.open_count = aw.open_count.saturating_sub(1);
|
||||
if aw.open_count == 0 {
|
||||
@@ -4620,8 +4658,14 @@ impl Filesystem for ProtonFs {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
(h, unlinked_uid)
|
||||
};
|
||||
if let Some(uid) = unlinked_uid {
|
||||
self.core.discard_queued_ops(&uid);
|
||||
self.core.cache.evict(&uid);
|
||||
self.core.evict_reader(&uid);
|
||||
}
|
||||
// Hand the bytes to the queue rather than uploading them here: the
|
||||
// scratch file is the only copy of what was just written, and blocking
|
||||
// the caller on the network is what made a copy into the mount run at
|
||||
|
||||
Reference in new issue
Block a user