From b00e09b09101fb7526a0263948fdf4408f9d608e Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Sun, 8 Mar 2026 19:45:18 -0400 Subject: [PATCH] fsck: detect duplicate keys (different UUIDs, same key) replay_nodes now tracks all UUIDs per key using a temporary multimap. Warns on duplicates so they can be manually resolved. Co-Authored-By: ProofOfConcept --- src/store/persist.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/store/persist.rs b/src/store/persist.rs index 6497f7f..4984cad 100644 --- a/src/store/persist.rs +++ b/src/store/persist.rs @@ -91,12 +91,16 @@ impl Store { Ok(store) } - /// Replay node log, keeping latest version per UUID + /// Replay node log, keeping latest version per UUID. + /// Tracks all UUIDs seen per key to detect duplicates. fn replay_nodes(&mut self, path: &Path) -> Result<(), String> { let file = fs::File::open(path) .map_err(|e| format!("open {}: {}", path.display(), e))?; let mut reader = BufReader::new(file); + // Track all non-deleted UUIDs per key to detect duplicates + let mut key_uuids: HashMap> = HashMap::new(); + while let Ok(msg) = serialize::read_message(&mut reader, message::ReaderOptions::new()) { let log = msg.get_root::() .map_err(|e| format!("read node log: {}", e))?; @@ -110,13 +114,28 @@ impl Store { if node.deleted { self.nodes.remove(&node.key); self.uuid_to_key.remove(&node.uuid); + if let Some(uuids) = key_uuids.get_mut(&node.key) { + uuids.retain(|u| *u != node.uuid); + } } else { self.uuid_to_key.insert(node.uuid, node.key.clone()); - self.nodes.insert(node.key.clone(), node); + self.nodes.insert(node.key.clone(), node.clone()); + let uuids = key_uuids.entry(node.key).or_default(); + if !uuids.contains(&node.uuid) { + uuids.push(node.uuid); + } } } } } + + // Report duplicate keys + for (key, uuids) in &key_uuids { + if uuids.len() > 1 { + eprintln!("WARNING: key '{}' has {} UUIDs (duplicate nodes)", key, uuids.len()); + } + } + Ok(()) }