//! Fixed three-record checkpoint ordering on a disposable disk. //! Remount validates the saved journal and all home sectors before recycling. #[path = "lib.rs"] mod format; pub type Block = [u8; 512]; pub const LOG: u64 = 112; pub const COMMIT: u64 = 113; pub const CHECKPOINT: u64 = 114; 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, Order, Io, HomeMismatch } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Mount { Committed, Checkpointed, Clean } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum State { Pending, Applied, Checkpointed, Reusable, Failed } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Checkpoint { state: State, log: Block, homes: [Block; 3], checkpoint: Block, } fn crc(bytes: &[u8]) -> u32 { let mut value = !0u32; for (i, byte) in bytes.iter().enumerate() { value ^= if (20..24).contains(&i) { 0 } else { u32::from(*byte) }; for _ in 0..8 { value = (value >> 1) ^ (0xedb88320 & 0u32.wrapping_sub(value & 1)); } } !value } impl Checkpoint { 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(|v| *v != 0) || &commit[..8] != b"TKCOMMIT" || commit[8..16] != 7u64.to_le_bytes() || commit[16..20] != log[28..32] { return Err(Error::Format); } let mut homes = [[0; 512]; 3]; for (i, record) in parsed.records.iter().enumerate() { if record.block != HOMES[i] { return Err(Error::Format); } homes[i][..16].copy_from_slice(&record.payload); } let mut checkpoint = [0; 512]; checkpoint[..8].copy_from_slice(b"TKCP0001"); checkpoint[8..16].copy_from_slice(&7u64.to_le_bytes()); checkpoint[16..20].copy_from_slice(&log[28..32]); checkpoint[32..160].copy_from_slice(&log[..128]); let checksum = crc(&checkpoint); checkpoint[20..24].copy_from_slice(&checksum.to_le_bytes()); Ok(Self {state: State::Pending, log: *log, homes, checkpoint}) } pub const fn state(&self) -> State { self.state } pub const fn homes(&self) -> &[Block; 3] { &self.homes } pub const fn image(&self) -> &Block { &self.checkpoint } /// This fixed fixture accepts the expected committed log, or its own /// checkpoint plus matching home sectors. An untrusted checkpoint cannot /// authorize reuse solely from a generation number. pub fn mount(&self, log: &Block, commit: &Block, checkpoint: &Block, homes: &[Block; 3]) -> Result { if *checkpoint == [0; 512] { let pending = Self::new(log, commit)?; if pending.log != self.log { return Err(Error::Format); } return Ok(Mount::Committed); } if checkpoint != &self.checkpoint { return Err(Error::Format); } if homes != &self.homes { return Err(Error::HomeMismatch); } // Commit may already be cleared while the log awaits retirement. // The flushed checkpoint retains enough content to validate that case. if *commit == [0; 512] && (*log == [0; 512] || log == &self.log) { return Ok(if *log == [0; 512] { Mount::Clean } else { Mount::Checkpointed }); } let pending = Self::new(log, commit)?; if pending.log != self.log { return Err(Error::Format); } Ok(Mount::Checkpointed) } fn io(&mut self, result: Result<(), ()>) -> Result<(), Error> { result.map_err(|_| { self.state = State::Failed; Error::Io }) } pub fn apply(&mut self, disk: &mut impl Device) -> Result<(), Error> { if self.state != State::Pending { return Err(Error::Order); } for (i, lba) in HOMES.iter().enumerate() { let result = disk.write(*lba, &self.homes[i]); self.io(result)?; } self.io(disk.flush())?; self.state = State::Applied; Ok(()) } pub fn persist(&mut self, disk: &mut impl Device) -> Result<(), Error> { if self.state != State::Applied { return Err(Error::Order); } let result = disk.write(CHECKPOINT, &self.checkpoint); self.io(result)?; self.io(disk.flush())?; self.state = State::Checkpointed; Ok(()) } pub fn recycle(&mut self, disk: &mut impl Device) -> Result<(), Error> { if self.state != State::Checkpointed { return Err(Error::Order); } self.io(disk.write(COMMIT, &[0; 512]))?; self.io(disk.write(LOG, &[0; 512]))?; self.io(disk.flush())?; self.state = State::Reusable; Ok(()) } }