//! Idempotent replay of the fixed disposable three-record journal fixture. #[path = "lib.rs"] mod format; pub type Block = [u8; 512]; pub const HOMES: [u64; 3] = [120, 128, 136]; pub trait Device { fn write(&mut self, lba: u64, bytes: &Block) -> Result<(), ()>; fn flush(&mut self) -> Result<(), ()>; } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Format, Commit, UnexpectedHome, UncommittedHome, Io, Terminal } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Outcome { NoCommit, AlreadyApplied, Replayed { writes: u8 } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Recovery { old: [Block; 3], new: [Block; 3], committed: bool, terminal: bool, } impl Recovery { pub fn new(log: &Block, commit: &Block) -> Result { let parsed = format::parse(&log[..128], 2048, 7).map_err(|_| Error::Format)?; if log[128..].iter().any(|b| *b != 0) { return Err(Error::Format); } let committed = *commit != [0; 512]; if committed && (&commit[..8] != b"TKCOMMIT" || commit[8..16] != 7u64.to_le_bytes() || commit[16..20] != log[28..32]) { return Err(Error::Commit); } let mut new = [[0; 512]; 3]; for (i, record) in parsed.records.iter().enumerate() { if record.block != HOMES[i] { return Err(Error::Format); } new[i][..16].copy_from_slice(&record.payload); } Ok(Self {old: [[0x44;512],[0x66;512],[0x77;512]], new, committed, terminal:false}) } pub const fn expected(&self) -> &[Block;3] { &self.new } pub fn replay(&mut self, disk: &mut impl Device, observed: &[Block;3]) -> Result { if self.terminal { return Err(Error::Terminal); } if !self.committed { if observed != &self.old { return Err(Error::UncommittedHome); } self.terminal = true; return Ok(Outcome::NoCommit); } // Validate every home before issuing the first write. for (i, block) in observed.iter().enumerate() { if block != &self.old[i] && block != &self.new[i] { return Err(Error::UnexpectedHome); } } let mut writes = 0; self.terminal = true; // Any I/O error requires a fresh mount and fresh reads. for (i, block) in observed.iter().enumerate() { disk.write(HOMES[i], &self.new[i]).map_err(|_| Error::Io)?; writes += 1; } if writes == 0 { return Ok(Outcome::AlreadyApplied); } disk.flush().map_err(|_| Error::Io)?; Ok(Outcome::Replayed {writes}) } }