From a1c94311db19a41c413d7402f886a5d5fa42de95 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 1 Aug 2025 11:51:37 -0600 Subject: [PATCH] Store number of blocks actually used --- src/allocator.rs | 22 +++++----- src/block.rs | 4 +- src/mount/fuse.rs | 2 +- src/mount/redox/resource.rs | 6 ++- src/node.rs | 34 ++++++++++++--- src/tests.rs | 4 +- src/transaction.rs | 86 ++++++++++++++++++++++++++----------- src/tree.rs | 4 +- 8 files changed, 110 insertions(+), 52 deletions(-) diff --git a/src/allocator.rs b/src/allocator.rs index ff35b370c5..6447159882 100644 --- a/src/allocator.rs +++ b/src/allocator.rs @@ -191,11 +191,11 @@ impl AllocEntry { } pub fn allocate(addr: BlockAddr) -> Self { - Self::new(addr.index(), -addr.level().blocks()) + Self::new(addr.index(), -addr.level().blocks::()) } pub fn deallocate(addr: BlockAddr) -> Self { - Self::new(addr.index(), addr.level().blocks()) + Self::new(addr.index(), addr.level().blocks::()) } pub fn index(&self) -> u64 { @@ -282,17 +282,17 @@ fn alloc_node_size_test() { fn allocator_test() { let mut alloc = Allocator::default(); - assert_eq!(alloc.allocate(BlockLevel::default()), None); + assert_eq!(alloc.allocate(BlockMeta::default()), None); - alloc.deallocate(unsafe { BlockAddr::new(1, BlockLevel::default()) }); + alloc.deallocate(unsafe { BlockAddr::new(1, BlockMeta::default()) }); assert_eq!( - alloc.allocate(BlockLevel::default()), - Some(unsafe { BlockAddr::new(1, BlockLevel::default()) }) + alloc.allocate(BlockMeta::default()), + Some(unsafe { BlockAddr::new(1, BlockMeta::default()) }) ); - assert_eq!(alloc.allocate(BlockLevel::default()), None); + assert_eq!(alloc.allocate(BlockMeta::default()), None); for addr in 1023..2048 { - alloc.deallocate(unsafe { BlockAddr::new(addr, BlockLevel::default()) }); + alloc.deallocate(unsafe { BlockAddr::new(addr, BlockMeta::default()) }); } assert_eq!(alloc.levels.len(), 11); @@ -308,11 +308,11 @@ fn allocator_test() { for addr in 1023..2048 { assert_eq!( - alloc.allocate(BlockLevel::default()), - Some(unsafe { BlockAddr::new(addr, BlockLevel::default()) }) + alloc.allocate(BlockMeta::default()), + Some(unsafe { BlockAddr::new(addr, BlockMeta::default()) }) ); } - assert_eq!(alloc.allocate(BlockLevel::default()), None); + assert_eq!(alloc.allocate(BlockMeta::default()), None); assert_eq!(alloc.levels.len(), 11); for level in 0..alloc.levels.len() { diff --git a/src/block.rs b/src/block.rs index 3b6c95b9fb..c6e5833363 100644 --- a/src/block.rs +++ b/src/block.rs @@ -125,8 +125,8 @@ impl BlockLevel { /// The number of [`BLOCK_SIZE`] blocks (i.e, level 0 blocks) /// in a block of this level - pub fn blocks(self) -> i64 { - 1 << self.0 + pub fn blocks>(self) -> T { + T::from(1u32 << self.0) } /// The number of bytes in a block of this level diff --git a/src/mount/fuse.rs b/src/mount/fuse.rs index ff363dea33..5d07de3768 100644 --- a/src/mount/fuse.rs +++ b/src/mount/fuse.rs @@ -77,7 +77,7 @@ fn node_attr(node: &TreeData) -> FileAttr { ino: node.id() as u64, size: node.data().size(), // Blocks is in 512 byte blocks, not in our block size - blocks: node.data().size().div_ceil(BLOCK_SIZE) * (BLOCK_SIZE / 512), + blocks: node.data().blocks() * (BLOCK_SIZE / 512), blksize: 512, atime: SystemTime::UNIX_EPOCH + Duration::new(node.data().atime().0, node.data().atime().1), mtime: SystemTime::UNIX_EPOCH + Duration::new(node.data().mtime().0, node.data().mtime().1), diff --git a/src/mount/redox/resource.rs b/src/mount/redox/resource.rs index 58f282944e..29797b2ce3 100644 --- a/src/mount/redox/resource.rs +++ b/src/mount/redox/resource.rs @@ -14,7 +14,7 @@ use syscall::flag::{ }; use syscall::{EBADFD, PAGE_SIZE}; -use crate::{Disk, Node, Transaction, TreePtr}; +use crate::{Disk, Node, Transaction, TreePtr, BLOCK_SIZE}; pub type Fmaps = BTreeMap; @@ -117,13 +117,15 @@ pub trait Resource { st_uid: node.data().uid(), st_gid: node.data().gid(), st_size: node.data().size(), + st_blksize: 512, + // Blocks is in 512 byte blocks, not in our block size + st_blocks: node.data().blocks() * (BLOCK_SIZE / 512), st_mtime: mtime.0, st_mtime_nsec: mtime.1, st_atime: atime.0, st_atime_nsec: atime.1, st_ctime: ctime.0, st_ctime_nsec: ctime.1, - ..Default::default() }; Ok(()) diff --git a/src/node.rs b/src/node.rs index be3bfdf947..afe831c0d3 100644 --- a/src/node.rs +++ b/src/node.rs @@ -107,17 +107,25 @@ pub struct Node { /// The length of this file, in bytes pub size: Le, + /// The disk usage of this file, in blocks + pub blocks: Le, + /// Creation time pub ctime: Le, pub ctime_nsec: Le, + + /// Modification time pub mtime: Le, pub mtime_nsec: Le, + + /// Access time pub atime: Le, pub atime_nsec: Le, + /// Record level pub record_level: Le, - pub padding: [u8; BLOCK_SIZE as usize - 4094], + pub padding: [u8; BLOCK_SIZE as usize - 4038], /// The first 128 blocks of this file. /// @@ -143,11 +151,11 @@ pub struct Node { /// Total size: 16 * 256 * 256 * 256 * RECORD_SIZE (32 TiB, 2 TiB each) pub level3: [BlockPtr; 16], - /// The next 12 * 256 * 256 * 256 * 256 blocks of this file, - /// stored behind 12 level four tables. + /// The next 8 * 256 * 256 * 256 * 256 blocks of this file, + /// stored behind 8 level four tables. /// - /// Total size: 12 * 256 * 256 * 256 * 256 * RECORD_SIZE (6 PiB, 512 TiB each) - pub level4: [BlockPtr; 12], + /// Total size: 8 * 256 * 256 * 256 * 256 * RECORD_SIZE (4 PiB, 512 TiB each) + pub level4: [BlockPtr; 8], } unsafe impl BlockTrait for Node { @@ -168,6 +176,9 @@ impl Default for Node { gid: 0.into(), links: 0.into(), size: 0.into(), + // Node counts as a block. + //TODO: track all the blocks in indirect levels + blocks: 1.into(), ctime: 0.into(), ctime_nsec: 0.into(), mtime: 0.into(), @@ -175,12 +186,12 @@ impl Default for Node { atime: 0.into(), atime_nsec: 0.into(), record_level: 0.into(), - padding: [0; BLOCK_SIZE as usize - 4094], + padding: [0; BLOCK_SIZE as usize - 4038], level0: [BlockPtr::default(); 128], level1: [BlockPtr::default(); 64], level2: [BlockPtr::default(); 32], level3: [BlockPtr::default(); 16], - level4: [BlockPtr::default(); 12], + level4: [BlockPtr::default(); 8], } } } @@ -253,6 +264,11 @@ impl Node { self.size.to_ne() } + /// The disk usage of this file, in blocks. + pub fn blocks(&self) -> u64 { + self.blocks.to_ne() + } + pub fn ctime(&self) -> (u64, u32) { (self.ctime.to_ne(), self.ctime_nsec.to_ne()) } @@ -289,6 +305,10 @@ impl Node { self.size = size.into(); } + pub fn set_blocks(&mut self, blocks: u64) { + self.blocks = blocks.into(); + } + pub fn set_mtime(&mut self, mtime: u64, mtime_nsec: u32) { self.mtime = mtime.into(); self.mtime_nsec = mtime_nsec.into(); diff --git a/src/tests.rs b/src/tests.rs index 2000d70ae6..0b6cd2328a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,6 +1,6 @@ use crate::htree::{HTreeHash, HTreeNode, HTreePtr, HTREE_IDX_ENTRIES}; use crate::{ - unmount_path, BlockAddr, BlockData, BlockLevel, BlockPtr, DirEntry, DirList, DiskMemory, + unmount_path, BlockAddr, BlockData, BlockMeta, BlockPtr, DirEntry, DirList, DiskMemory, DiskSparse, FileSystem, Node, TreePtr, ALLOC_GC_THRESHOLD, BLOCK_SIZE, }; use core::panic::AssertUnwindSafe; @@ -515,7 +515,7 @@ fn create_minimal_l2_htree( let mut parent = tx.read_tree(parent_ptr).unwrap(); let child1_block_data = BlockData::new( - unsafe { tx.allocate(BlockLevel::default()) }.unwrap(), + unsafe { tx.allocate(BlockMeta::default()) }.unwrap(), Node::new( Node::MODE_FILE, parent.data().uid(), diff --git a/src/transaction.rs b/src/transaction.rs index 98323bf279..e740ad6e08 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -84,7 +84,7 @@ impl<'a, D: Disk> Transaction<'a, D> { let mut found = false; for i in (0..self.allocator_log.len()).rev() { let entry = self.allocator_log[i]; - if entry.index() == addr.index() && entry.count() == -addr.level().blocks() { + if entry.index() == addr.index() && entry.count() == -addr.level().blocks::() { found = true; self.allocator_log.remove(i); break; @@ -100,14 +100,44 @@ impl<'a, D: Disk> Transaction<'a, D> { } } - fn deallocate_block(&mut self, ptr: BlockPtr) { - if !ptr.is_null() { - unsafe { - self.deallocate(ptr.addr()); - } + unsafe fn node_allocate( + &mut self, + node: &mut TreeData, + meta: BlockMeta, + ) -> Result { + let addr = self.allocate(meta)?; + let blocks = node.data().blocks(); + node.data_mut().set_blocks( + blocks + .checked_add(addr.level().blocks()) + .expect("node allocated too many blocks"), + ); + Ok(addr) + } + + unsafe fn node_deallocate(&mut self, node: &mut TreeData, addr: BlockAddr) -> bool { + if !addr.is_null() { + self.deallocate(addr); + let blocks = node.data().blocks(); + node.data_mut().set_blocks( + blocks + .checked_sub(addr.level().blocks()) + .expect("node deallocated too many blocks"), + ); + true + } else { + false } } + unsafe fn node_deallocate_block( + &mut self, + node: &mut TreeData, + ptr: BlockPtr, + ) -> bool { + self.node_deallocate(node, ptr.addr()) + } + /// Drain `self.allocator_log` and `self.deallocate`, /// updating the [`AllocList`] with the resulting state. /// @@ -1343,13 +1373,15 @@ impl<'a, D: Disk> Transaction<'a, D> { unsafe { match NodeLevel::new(record_offset).ok_or(Error::new(ERANGE))? { NodeLevel::L0(i0) => { - self.deallocate_block(node.data_mut().level0[i0].clear()); + let ptr = node.data_mut().level0[i0].clear(); + self.node_deallocate_block(node, ptr); } NodeLevel::L1(i1, i0) => { let mut l0 = self.read_block_or_empty(node.data().level1[i1])?; - self.deallocate_block(l0.data_mut().ptrs[i0].clear()); + self.node_deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { - self.deallocate_block(node.data_mut().level1[i1].clear()); + let ptr = node.data_mut().level1[i1].clear(); + self.node_deallocate_block(node, ptr); } else { node.data_mut().level1[i1] = self.sync_block(l0)?; } @@ -1357,14 +1389,15 @@ impl<'a, D: Disk> Transaction<'a, D> { NodeLevel::L2(i2, i1, i0) => { let mut l1 = self.read_block_or_empty(node.data().level2[i2])?; let mut l0 = self.read_block_or_empty(l1.data().ptrs[i1])?; - self.deallocate_block(l0.data_mut().ptrs[i0].clear()); + self.node_deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { - self.deallocate_block(l1.data_mut().ptrs[i1].clear()); + self.node_deallocate_block(node, l1.data_mut().ptrs[i1].clear()); } else { l1.data_mut().ptrs[i1] = self.sync_block(l0)?; } if l1.data().is_empty() { - self.deallocate_block(node.data_mut().level2[i2].clear()); + let ptr = node.data_mut().level2[i2].clear(); + self.node_deallocate_block(node, ptr); } else { node.data_mut().level2[i2] = self.sync_block(l1)?; } @@ -1373,19 +1406,20 @@ impl<'a, D: Disk> Transaction<'a, D> { let mut l2 = self.read_block_or_empty(node.data().level3[i3])?; let mut l1 = self.read_block_or_empty(l2.data().ptrs[i2])?; let mut l0 = self.read_block_or_empty(l1.data().ptrs[i1])?; - self.deallocate_block(l0.data_mut().ptrs[i0].clear()); + self.node_deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { - self.deallocate_block(l1.data_mut().ptrs[i1].clear()); + self.node_deallocate_block(node, l1.data_mut().ptrs[i1].clear()); } else { l1.data_mut().ptrs[i1] = self.sync_block(l0)?; } if l1.data().is_empty() { - self.deallocate_block(l2.data_mut().ptrs[i2].clear()); + self.node_deallocate_block(node, l2.data_mut().ptrs[i2].clear()); } else { l2.data_mut().ptrs[i2] = self.sync_block(l1)?; } if l2.data().is_empty() { - self.deallocate_block(node.data_mut().level3[i3].clear()); + let ptr = node.data_mut().level3[i3].clear(); + self.node_deallocate_block(node, ptr); } else { node.data_mut().level3[i3] = self.sync_block(l2)?; } @@ -1395,24 +1429,25 @@ impl<'a, D: Disk> Transaction<'a, D> { let mut l2 = self.read_block_or_empty(l3.data().ptrs[i3])?; let mut l1 = self.read_block_or_empty(l2.data().ptrs[i2])?; let mut l0 = self.read_block_or_empty(l1.data().ptrs[i1])?; - self.deallocate_block(l0.data_mut().ptrs[i0].clear()); + self.node_deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { - self.deallocate_block(l1.data_mut().ptrs[i1].clear()); + self.node_deallocate_block(node, l1.data_mut().ptrs[i1].clear()); } else { l1.data_mut().ptrs[i1] = self.sync_block(l0)?; } if l1.data().is_empty() { - self.deallocate_block(l2.data_mut().ptrs[i2].clear()); + self.node_deallocate_block(node, l2.data_mut().ptrs[i2].clear()); } else { l2.data_mut().ptrs[i2] = self.sync_block(l1)?; } if l2.data().is_empty() { - self.deallocate_block(l3.data_mut().ptrs[i3].clear()); + self.node_deallocate_block(node, l3.data_mut().ptrs[i3].clear()); } else { l3.data_mut().ptrs[i3] = self.sync_block(l2)?; } if l3.data().is_empty() { - self.deallocate_block(node.data_mut().level4[i4].clear()); + let ptr = node.data_mut().level4[i4].clear(); + self.node_deallocate_block(node, ptr); } else { node.data_mut().level4[i4] = self.sync_block(l3)?; } @@ -1676,8 +1711,8 @@ impl<'a, D: Disk> Transaction<'a, D> { } // CoW record using its current level - let mut old_addr = - unsafe { record.swap_addr(self.allocate(record.addr().meta())?) }; + let new_addr = unsafe { self.node_allocate(node, record.addr().meta())? }; + let mut old_addr = record.swap_addr(new_addr); // If the record was resized we need to dealloc the original ptr if old_addr.is_null() { @@ -1685,6 +1720,7 @@ impl<'a, D: Disk> Transaction<'a, D> { } // Write record to disk + //TODO: deallocate new_addr on failure? record_ptr = unsafe { self.write_block(record)? }; // Update record pointer @@ -1692,8 +1728,8 @@ impl<'a, D: Disk> Transaction<'a, D> { node_changed = true; // Deallocate old record - if !old_addr.is_null() { - unsafe { self.deallocate(old_addr) }; + unsafe { + self.node_deallocate(node, old_addr); } } diff --git a/src/tree.rs b/src/tree.rs index 9cdffda74c..dad29f0b47 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -224,7 +224,7 @@ impl Default for TreePtr { #[cfg(test)] mod tests { - use crate::{BlockAddr, BlockData}; + use crate::{BlockAddr, BlockData, BlockMeta}; use super::*; @@ -257,7 +257,7 @@ mod tests { } fn mock_block(addr: u64) -> BlockPtr { - let block_addr = unsafe { BlockAddr::new(addr, BlockLevel::default()) }; + let block_addr = unsafe { BlockAddr::new(addr, BlockMeta::default()) }; BlockData::empty(block_addr).unwrap().create_ptr() }