mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 16:56:59 +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
@@ -748,14 +748,40 @@ impl ContentCache {
|
||||
continue;
|
||||
}
|
||||
let side = Self::scratch_sidecar(&blob);
|
||||
// Parsed, not merely present: an unreadable sidecar cannot drive a
|
||||
// recovery, so the blob is rubbish like any other leftover.
|
||||
if std::fs::read(&side)
|
||||
if !side.exists() {
|
||||
// Not marked durable, or durability marker was explicitly cleared on release
|
||||
continue;
|
||||
}
|
||||
// Parsed if valid; if sidecar exists but is corrupted, build synthetic StagedWrite
|
||||
let has_valid_sidecar = std::fs::read(&side)
|
||||
.ok()
|
||||
.and_then(|b| serde_json::from_slice::<StagedWrite>(&b).ok())
|
||||
.is_none()
|
||||
{
|
||||
continue;
|
||||
.is_some();
|
||||
if !has_valid_sidecar {
|
||||
if let Ok(meta) = std::fs::metadata(&blob)
|
||||
&& meta.len() > 0
|
||||
{
|
||||
let blob_name = blob
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
let synthetic = StagedWrite {
|
||||
uid: format!("recovered~{blob_name}"),
|
||||
len: meta.len(),
|
||||
base_size: meta.len(),
|
||||
base_mtime: 0,
|
||||
authored: vec![(0, meta.len())],
|
||||
complete: true,
|
||||
based_on: None,
|
||||
};
|
||||
if let Ok(data) = serde_json::to_vec(&synthetic) {
|
||||
let _ = std::fs::write(&side, data);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let Some(name) = blob.file_name() else {
|
||||
continue;
|
||||
@@ -770,6 +796,30 @@ impl ContentCache {
|
||||
}
|
||||
}
|
||||
|
||||
/// Emergency eviction of unpinned blobs when local disk runs out of space (ENOSPC).
|
||||
pub fn emergency_evict(&self) {
|
||||
let pinned: HashSet<String> = self
|
||||
.db
|
||||
.pinned_uids()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|uid| Self::key_str(uid))
|
||||
.collect();
|
||||
let content_dir = self.content_dir.clone();
|
||||
// Force pool enforcement ignoring current cap check to free disk space immediately
|
||||
let Ok(list) = self.db.cache_eviction_candidates(KIND_BLOB, 10) else { return; };
|
||||
for (key, bytes) in list {
|
||||
if pinned.contains(&key) { continue; }
|
||||
let p = content_dir.join(&key);
|
||||
let m = content_dir.join(format!("{key}.meta"));
|
||||
let _ = std::fs::remove_file(&p);
|
||||
let _ = std::fs::remove_file(&m);
|
||||
let _ = self.db.cache_remove(&key);
|
||||
self.blob_bytes.fetch_sub(bytes, Ordering::Relaxed);
|
||||
break; // Evict one item at a time until space is freed
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes rescued from an unclean shutdown, as `(blob, meta)` pairs for the
|
||||
/// daemon to hand to the upload queue. A blob whose sidecar did not survive
|
||||
/// the rescue is deleted here — nothing can address it.
|
||||
|
||||
Reference in new issue
Block a user