//! Bounded block bitmap ownership with permanently reserved metadata blocks. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { BadCapacity, BadReserved, BadBitmap, ReservedFree, NoSpace, OutOfBounds, ReservedBlock, AlreadyFree, BadFileState, UnownedFirstBlock, Busy, Stale, BadOwner, GenerationOverflow, InvalidSlot, ReaderOverflow, InvalidLength, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Counts { pub reserved: u32, pub user: u32, pub free: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Bitmap { capacity: u8, reserved: u64, allocated: u64, } impl Bitmap { pub fn validate(capacity: u8, reserved: u64, allocated: u64) -> Result { if capacity == 0 || capacity > 64 { return Err(Error::BadCapacity); } let valid = if capacity == 64 { u64::MAX } else { (1u64 << capacity) - 1 }; if reserved & !valid != 0 { return Err(Error::BadReserved); } if allocated & !valid != 0 { return Err(Error::BadBitmap); } if allocated & reserved != reserved { return Err(Error::ReservedFree); } Ok(Self {capacity, reserved, allocated}) } pub const fn allocated_bits(&self) -> u64 { self.allocated } pub fn counts(&self) -> Counts { let reserved = self.reserved.count_ones(); let user = (self.allocated & !self.reserved).count_ones(); Counts {reserved, user, free: u32::from(self.capacity) - reserved - user} } pub fn allocate(&mut self) -> Result { for block in 0..self.capacity { let bit = 1u64 << block; if self.allocated & bit == 0 { self.allocated |= bit; return Ok(block); } } Err(Error::NoSpace) } pub fn release(&mut self, block: u8) -> Result<(), Error> { if block >= self.capacity { return Err(Error::OutOfBounds); } let bit = 1u64 << block; if self.reserved & bit != 0 { return Err(Error::ReservedBlock); } if self.allocated & bit == 0 { return Err(Error::AlreadyFree); } self.allocated &= !bit; Ok(()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ExtentFile { blocks: [u8; 3], count: u8, } impl ExtentFile { pub fn one_block(first: u8, bitmap: &Bitmap) -> Result { if first >= bitmap.capacity { return Err(Error::OutOfBounds); } let bit = 1u64 << first; if bitmap.reserved & bit != 0 || bitmap.allocated & bit == 0 { return Err(Error::UnownedFirstBlock); } Ok(Self {blocks: [first, 0, 0], count: 1}) } pub const fn blocks(&self) -> [u8; 3] { self.blocks } pub const fn count(&self) -> u8 { self.count } /// Stage both allocations in a copy. Publish the file and bitmap only /// after all three block owners are known. pub fn grow_to_three(&mut self, bitmap: &mut Bitmap) -> Result<(), Error> { if self.count != 1 { return Err(Error::BadFileState); } let mut staged = *bitmap; let second = staged.allocate()?; let third = staged.allocate()?; self.blocks[1] = second; self.blocks[2] = third; self.count = 3; *bitmap = staged; Ok(()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct InodeHandle { pub slot: u16, pub generation: u64, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct InodeSlot { slot: u16, generation: u64, owner: u64, live: bool, } impl InodeSlot { pub fn new(slot: u16, generation: u64) -> Result { if slot == 0 || generation == 0 { return Err(Error::InvalidSlot); } Ok(Self {slot, generation, owner: 0, live: false}) } pub fn allocate(&mut self, owner: u64) -> Result { if owner == 0 { return Err(Error::BadOwner); } if self.live { return Err(Error::Busy); } self.owner = owner; self.live = true; Ok(InodeHandle {slot: self.slot, generation: self.generation}) } pub fn owner(&self, handle: InodeHandle) -> Result { if handle.slot != self.slot || handle.generation != self.generation || !self.live { return Err(Error::Stale); } Ok(self.owner) } pub fn release(&mut self, handle: InodeHandle) -> Result<(), Error> { self.owner(handle)?; let next = self.generation.checked_add(1) .ok_or(Error::GenerationOverflow)?; self.owner = 0; self.live = false; self.generation = next; Ok(()) } pub const fn generation(&self) -> u64 { self.generation } pub const fn live(&self) -> bool { self.live } } /// In-memory three-block file. Reader guards pin its backing until dropped. /// This model does not perform persistent bitmap or inode writes. pub struct TruncateFile { blocks: [u8; 3], length: core::cell::Cell, block_count: core::cell::Cell, readers: core::cell::Cell, bytes: core::cell::RefCell<[u8; 1536]>, } pub struct TruncateReader<'a> { file: &'a TruncateFile, } impl TruncateFile { pub fn new(blocks: [u8; 3], length: usize, bytes: [u8; 1536], bitmap: &Bitmap) -> Result { if !(1025..=1536).contains(&length) { return Err(Error::InvalidLength); } for (i, block) in blocks.iter().copied().enumerate() { if blocks[..i].contains(&block) { return Err(Error::BadFileState); } Self::check_owner(block, bitmap)?; } Ok(Self {blocks, length: core::cell::Cell::new(length), block_count: core::cell::Cell::new(3), readers: core::cell::Cell::new(0), bytes: core::cell::RefCell::new(bytes)}) } fn check_owner(block: u8, bitmap: &Bitmap) -> Result<(), Error> { if block >= bitmap.capacity { return Err(Error::OutOfBounds); } let bit = 1u64 << block; if bitmap.reserved & bit != 0 || bitmap.allocated & bit == 0 { return Err(Error::UnownedFirstBlock); } Ok(()) } pub fn length(&self) -> usize { self.length.get() } pub fn block_count(&self) -> usize { self.block_count.get() } pub fn active_readers(&self) -> u16 { self.readers.get() } pub fn reader(&self) -> Result, Error> { let next = self.readers.get().checked_add(1).ok_or(Error::ReaderOverflow)?; self.readers.set(next); Ok(TruncateReader {file: self}) } pub fn truncate(&self, new_length: usize, bitmap: &mut Bitmap) -> Result<(), Error> { if new_length > self.length.get() { return Err(Error::InvalidLength); } if false { return Err(Error::Busy); } let old_count = self.block_count.get(); let new_count = new_length.div_ceil(512); let mut staged = *bitmap; for block in &self.blocks[..old_count] { Self::check_owner(*block, &staged)?; } for block in &self.blocks[new_count..old_count] { staged.release(*block)?; } // All fallible checks finish before publishing any mutation. self.bytes.borrow_mut()[new_length..].fill(0); self.length.set(new_length); self.block_count.set(new_count); *bitmap = staged; Ok(()) } } impl TruncateReader<'_> { pub fn byte(&self, offset: usize) -> Option { if offset >= self.file.length.get() { return None; } Some(self.file.bytes.borrow()[offset]) } } impl Drop for TruncateReader<'_> { fn drop(&mut self) { self.file.readers.set(self.file.readers.get() - 1); } }