//! A representative Asterhold service opening a block extent or an OS file capability. use ratdb_core::{Config, Database, Error, MemoryStorage, Result, Storage}; const SECTOR: usize = 512; type Sector = [u8; SECTOR]; // The volume manager owns allocation and logical length metadata. Newly allocated // sectors read as zero. The device must preserve neighboring bytes on failed writes. trait Volume { fn length(&self) -> Result; fn allocate_zeroed(&mut self, bytes: u64) -> Result<()>; fn load(&mut self, sector: u64, data: &mut Sector) -> Result<()>; fn store(&mut self, sector: u64, data: &Sector) -> Result<()>; fn flush_data_and_length(&mut self) -> Result<()>; } struct BlockExtent(D); fn check_range(offset: u64, length: usize, end: u64) -> Result<()> { offset .checked_add(length as u64) .filter(|&n| n <= end) .map(|_| ()) .ok_or(Error::Io) } impl Storage for BlockExtent { fn len(&mut self) -> Result { self.0.length() } fn resize(&mut self, n: u64) -> Result<()> { if self.0.length()? != 0 || !n.is_multiple_of(SECTOR as u64) { return Err(Error::Invalid); } self.0.allocate_zeroed(n) } fn read(&mut self, mut offset: u64, mut dst: &mut [u8]) -> Result<()> { check_range(offset, dst.len(), self.len()?)?; let mut sector = [0; SECTOR]; while !dst.is_empty() { self.0.load(offset / SECTOR as u64, &mut sector)?; let start = (offset % SECTOR as u64) as usize; let n = dst.len().min(SECTOR - start); dst[..n].copy_from_slice(§or[start..start + n]); dst = &mut dst[n..]; offset += n as u64; } Ok(()) } fn write(&mut self, mut offset: u64, mut src: &[u8]) -> Result<()> { check_range(offset, src.len(), self.len()?)?; let mut sector = [0; SECTOR]; while !src.is_empty() { let lba = offset / SECTOR as u64; let start = (offset % SECTOR as u64) as usize; let n = src.len().min(SECTOR - start); if n != SECTOR { self.0.load(lba, &mut sector)?; } sector[start..start + n].copy_from_slice(&src[..n]); self.0.store(lba, §or)?; src = &src[n..]; offset += n as u64; } Ok(()) } fn sync(&mut self) -> Result<()> { self.0.flush_data_and_length() } } // Positioned I/O on an exclusively owned filesystem capability may complete short. trait Inode { fn size(&self) -> Result; fn set_size(&mut self, n: u64) -> Result<()>; fn pread(&mut self, offset: u64, data: &mut [u8]) -> Result; fn pwrite(&mut self, offset: u64, data: &[u8]) -> Result; fn fsync(&mut self) -> Result<()>; } struct OsFile(F); impl Storage for OsFile { fn len(&mut self) -> Result { self.0.size() } fn resize(&mut self, n: u64) -> Result<()> { self.0.set_size(n) } fn read(&mut self, mut offset: u64, mut buf: &mut [u8]) -> Result<()> { check_range(offset, buf.len(), self.len()?)?; while !buf.is_empty() { let n = self.0.pread(offset, buf)?; if n == 0 || n > buf.len() { return Err(Error::Io); } buf = &mut buf[n..]; offset += n as u64; } Ok(()) } fn write(&mut self, mut offset: u64, mut buf: &[u8]) -> Result<()> { check_range(offset, buf.len(), self.len()?)?; while !buf.is_empty() { let n = self.0.pwrite(offset, buf)?; if n == 0 || n > buf.len() { return Err(Error::Io); } buf = &buf[n..]; offset += n as u64; } Ok(()) } fn sync(&mut self) -> Result<()> { self.0.fsync() } } // Only these demonstration backends use RAM. Dropping pending state simulates restart. #[derive(Default)] struct MockVolume { pending: Vec, durable: Vec, fail_flush: bool, } impl Volume for MockVolume { fn length(&self) -> Result { Ok((self.pending.len() * SECTOR) as u64) } fn allocate_zeroed(&mut self, bytes: u64) -> Result<()> { if bytes > 8192 { return Err(Error::Full); } self.pending.resize(bytes as usize / SECTOR, [0; SECTOR]); Ok(()) } fn load(&mut self, sector: u64, data: &mut Sector) -> Result<()> { *data = *self.pending.get(sector as usize).ok_or(Error::Io)?; Ok(()) } fn store(&mut self, sector: u64, data: &Sector) -> Result<()> { *self.pending.get_mut(sector as usize).ok_or(Error::Io)? = *data; Ok(()) } fn flush_data_and_length(&mut self) -> Result<()> { if self.fail_flush { return Err(Error::Io); } self.durable.clone_from(&self.pending); Ok(()) } } struct MockInode { data: MemoryStorage, max_transfer: usize, } impl Inode for MockInode { fn size(&self) -> Result { Ok(self.data.0.len() as u64) } fn set_size(&mut self, n: u64) -> Result<()> { if n > 8192 { return Err(Error::Full); } self.data.resize(n) } fn pread(&mut self, offset: u64, buf: &mut [u8]) -> Result { let n = buf.len().min(self.max_transfer); self.data.read(offset, &mut buf[..n])?; Ok(n) } fn pwrite(&mut self, offset: u64, buf: &[u8]) -> Result { let n = buf.len().min(self.max_transfer); self.data.write(offset, &buf[..n])?; Ok(n) } fn fsync(&mut self) -> Result<()> { self.data.sync() } } const CONFIG: Config = Config { bank_size: 4096, max_key: 48, max_value: 640, }; const STATE: [(&[u8], &[u8]); 4] = [ (b"os/locale", b"en-US"), (b"app/display/brightness", b"60"), (b"service/logger", b"enabled"), (b"events/00001", b"boot complete"), ]; fn store_state(storage: S) -> Result { let mut db = Database::open(storage, CONFIG)?; for (key, value) in STATE { db.put(key, value)?; } Ok(db.into_storage()) } fn verify_state(storage: S) -> Result<()> { let mut db = Database::open(storage, CONFIG)?; for (key, value) in STATE { assert_eq!(db.get(key)?.as_deref(), Some(value)); } Ok(()) } fn main() -> Result<()> { let mut extent = store_state(BlockExtent(MockVolume::default()))?; extent.0.pending = extent.0.durable.clone(); verify_state(extent)?; verify_state(store_state(OsFile(MockInode { data: MemoryStorage::default(), max_transfer: 7, }))?) } #[cfg(test)] mod tests { use super::*; #[test] fn reopen_each_adapter() { main().unwrap(); } #[test] fn partial_sector_preserves_both_neighbors_and_enforces_bounds() { let mut extent = BlockExtent(MockVolume::default()); assert_eq!(extent.resize(16384), Err(Error::Full)); extent.resize(1024).unwrap(); extent.write(0, &[0xaf; 1024]).unwrap(); extent.write(510, &[0x10; 4]).unwrap(); let mut bytes = [0; 1024]; extent.read(0, &mut bytes).unwrap(); assert_eq!(bytes[..510], [0xaf; 510]); assert_eq!(bytes[510..514], [0x10; 4]); assert_eq!(bytes[514..], [0xaf; 510]); assert_eq!(extent.write(1023, &[0; 2]), Err(Error::Io)); assert_eq!(extent.read(u64::MAX, &mut bytes), Err(Error::Io)); } #[test] fn failed_barrier_then_power_cycle_preserves_acknowledged_state() { let extent = store_state(BlockExtent(MockVolume::default())).unwrap(); let mut device = extent.0; device.fail_flush = true; let mut db = Database::open(BlockExtent(device), CONFIG).unwrap(); assert_eq!(db.put(b"os/locale", b"changed"), Err(Error::Io)); assert_eq!(db.get(b"os/locale"), Err(Error::Poisoned)); let mut extent = db.into_storage(); extent.0.pending.clone_from(&extent.0.durable); verify_state(extent).unwrap(); } #[test] fn stalled_filesystem_io_is_an_error() { let mut file = OsFile(MockInode { data: MemoryStorage(vec![0; 9]), max_transfer: 0, }); assert_eq!(file.read(0, &mut [0; 1]), Err(Error::Io)); assert_eq!(file.write(0, &[1]), Err(Error::Io)); } }