From 26dc32d710a9a940e7d81eb74360feb75f2f84a0 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 31 Jul 2025 18:48:17 -0600 Subject: [PATCH 01/21] v8: transparent lz4 compression --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/allocator.rs | 12 ++--- src/block.rs | 79 +++++++++++++++++++++++----- src/filesystem.rs | 14 ++--- src/lib.rs | 4 +- src/record.rs | 2 +- src/transaction.rs | 126 ++++++++++++++++++++++++++++++++++----------- 8 files changed, 186 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c11aa8c63..8f8205fa90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,6 +318,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lz4_flex" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" + [[package]] name = "memchr" version = "2.7.5" @@ -454,6 +460,7 @@ dependencies = [ "libc", "libredox", "log", + "lz4_flex", "range-tree", "redox-path", "redox-scheme", diff --git a/Cargo.toml b/Cargo.toml index 91415b1ee5..f13e0210ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ getrandom = { version = "0.2.5", optional = true } libc = "0.2" libredox = { version = "0.1.6", features = ["call"], optional = true } log = { version = "0.4.14", default-features = false, optional = true } +lz4_flex = { version = "0.11", default-features = false, features = ["checked-decode"] } range-tree = { version = "0.1", optional = true } redox-path = "0.3.0" redox-scheme = { version = "0.7.0", optional = true } diff --git a/src/allocator.rs b/src/allocator.rs index 286695c2bf..ff35b370c5 100644 --- a/src/allocator.rs +++ b/src/allocator.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use core::{fmt, mem, ops, slice}; use endian_num::Le; -use crate::{BlockAddr, BlockLevel, BlockPtr, BlockTrait, BLOCK_SIZE}; +use crate::{BlockAddr, BlockLevel, BlockMeta, BlockPtr, BlockTrait, BLOCK_SIZE}; pub const ALLOC_LIST_ENTRIES: usize = (BLOCK_SIZE as usize - mem::size_of::>()) / mem::size_of::(); @@ -52,10 +52,10 @@ impl Allocator { /// Find a free block of the given level, mark it as "used", and return its address. /// Returns [`None`] if there are no free blocks with this level. - pub fn allocate(&mut self, block_level: BlockLevel) -> Option { + pub fn allocate(&mut self, meta: BlockMeta) -> Option { // First, find the lowest level with a free block let mut index_opt = None; - let mut level = block_level.0; + let mut level = meta.level.0; // Start searching at the level we want. Smaller levels are too small! while level < self.levels.len() { if !self.levels[level].is_empty() { @@ -68,13 +68,13 @@ impl Allocator { // If a free block was found, split it until we find a usable block of the right level. // The left side of the split block is kept free, and the right side is allocated. let index = index_opt?; - while level > block_level.0 { + while level > meta.level.0 { level -= 1; let level_size = 1 << level; self.levels[level].push(index + level_size); } - Some(unsafe { BlockAddr::new(index, block_level) }) + Some(unsafe { BlockAddr::new(index, meta) }) } /// Try to allocate the exact block specified, making all necessary splits. @@ -112,7 +112,7 @@ impl Allocator { } } - Some(unsafe { BlockAddr::new(index_opt?, exact_addr.level()) }) + Some(unsafe { BlockAddr::new(index_opt?, exact_addr.meta()) }) } /// Deallocate the given block, marking it "free" so that it can be re-used later. diff --git a/src/block.rs b/src/block.rs index 2df0e7773b..3b6c95b9fb 100644 --- a/src/block.rs +++ b/src/block.rs @@ -9,38 +9,67 @@ const BLOCK_LIST_ENTRIES: usize = BLOCK_SIZE as usize / mem::size_of:: Self { - // Level must only use the lowest four bits - if level.0 > 0xF { - panic!("block level used more than four bits"); + pub(crate) unsafe fn new(index: u64, meta: BlockMeta) -> Self { + // Level must fit within LEVEL_MASK + if meta.level.0 > Self::LEVEL_MASK as usize { + panic!("block level too large"); } - // Index must not use the highest four bits + // Decomp level must fit within DECOMP_LEVEL_MASK + let decomp_level = meta.decomp_level.unwrap_or_default(); + if (decomp_level.0 << Self::DECOMP_LEVEL_SHIFT) > Self::DECOMP_LEVEL_MASK as usize { + panic!("decompressed block level too large"); + } + + // Index must not use the metadata bits let inner = index - .checked_shl(4) - .expect("block index used highest four bits") - | (level.0 as u64); + .checked_shl(Self::INDEX_SHIFT as u32) + .expect("block index too large") + | ((decomp_level.0 as u64) << Self::DECOMP_LEVEL_SHIFT) + | (meta.level.0 as u64); Self(inner) } - pub fn null(level: BlockLevel) -> Self { - unsafe { Self::new(0, level) } + pub fn null(meta: BlockMeta) -> Self { + unsafe { Self::new(0, meta) } } pub fn index(&self) -> u64 { // The first four bits store the level - self.0 >> 4 + self.0 >> Self::INDEX_SHIFT } pub fn level(&self) -> BlockLevel { // The first four bits store the level - BlockLevel((self.0 & 0xF) as usize) + BlockLevel((self.0 & Self::LEVEL_MASK) as usize) + } + + pub fn decomp_level(&self) -> Option { + let value = (self.0 & Self::DECOMP_LEVEL_MASK) >> Self::DECOMP_LEVEL_SHIFT; + if value != 0 { + Some(BlockLevel(value as usize)) + } else { + None + } + } + + pub fn meta(&self) -> BlockMeta { + BlockMeta { + level: self.level(), + decomp_level: self.decomp_level(), + } } pub fn is_null(&self) -> bool { @@ -48,6 +77,28 @@ impl BlockAddr { } } +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub struct BlockMeta { + pub(crate) level: BlockLevel, + pub(crate) decomp_level: Option, +} + +impl BlockMeta { + pub fn new(level: BlockLevel) -> Self { + Self { + level, + decomp_level: None, + } + } + + pub fn new_compressed(level: BlockLevel, decomp_level: BlockLevel) -> Self { + Self { + level, + decomp_level: Some(decomp_level), + } + } +} + /// The size of a block. /// /// Level 0 blocks are blocks of [`BLOCK_SIZE`] bytes. @@ -211,9 +262,9 @@ pub struct BlockPtr { } impl BlockPtr { - pub fn null(level: BlockLevel) -> Self { + pub fn null(meta: BlockMeta) -> Self { Self { - addr: BlockAddr::null(level).0.into(), + addr: BlockAddr::null(meta).0.into(), hash: 0.into(), phantom: PhantomData, } diff --git a/src/filesystem.rs b/src/filesystem.rs index 15b86665e3..e13b67bb19 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -5,7 +5,9 @@ use xts_mode::{get_tweak_default, Xts128}; #[cfg(feature = "std")] use crate::{AllocEntry, AllocList, BlockData, BlockTrait, Key, KeySlot, Node, Salt, TreeList}; -use crate::{Allocator, BlockAddr, BlockLevel, Disk, Header, Transaction, BLOCK_SIZE, HEADER_RING}; +use crate::{ + Allocator, BlockAddr, BlockLevel, BlockMeta, Disk, Header, Transaction, BLOCK_SIZE, HEADER_RING, +}; /// A file system pub struct FileSystem { @@ -174,12 +176,12 @@ impl FileSystem { // Set tree and alloc pointers and write header generation one fs.tx(|tx| unsafe { let tree = BlockData::new( - BlockAddr::new(HEADER_RING + 1, BlockLevel::default()), + BlockAddr::new(HEADER_RING + 1, BlockMeta::default()), TreeList::empty(BlockLevel::default()).unwrap(), ); let mut alloc = BlockData::new( - BlockAddr::new(HEADER_RING + 2, BlockLevel::default()), + BlockAddr::new(HEADER_RING + 2, BlockMeta::default()), AllocList::empty(BlockLevel::default()).unwrap(), ); @@ -199,7 +201,7 @@ impl FileSystem { fs.tx(|tx| unsafe { let mut root = BlockData::new( - BlockAddr::new(HEADER_RING + 3, BlockLevel::default()), + BlockAddr::new(HEADER_RING + 3, BlockMeta::default()), Node::new(Node::MODE_DIR | 0o755, 0, 0, ctime, ctime_nsec), ); root.data_mut().set_links(1); @@ -254,12 +256,12 @@ impl FileSystem { if count < 0 { for i in 0..-count { //TODO: replace assert with error? - let addr = BlockAddr::new(index + i as u64, BlockLevel::default()); + let addr = BlockAddr::new(index + i as u64, BlockMeta::default()); assert_eq!(self.allocator.allocate_exact(addr), Some(addr)); } } else { for i in 0..count { - let addr = BlockAddr::new(index + i as u64, BlockLevel::default()); + let addr = BlockAddr::new(index + i as u64, BlockMeta::default()); self.allocator.deallocate(addr); } } diff --git a/src/lib.rs b/src/lib.rs index 25d056a069..6f9de0d16e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ pub const BLOCK_SIZE: u64 = 4096; pub const RECORD_LEVEL: usize = 5; pub const RECORD_SIZE: u64 = BLOCK_SIZE << RECORD_LEVEL; pub const SIGNATURE: &[u8; 8] = b"RedoxFS\0"; -pub const VERSION: u64 = 7; +pub const VERSION: u64 = 8; pub const DIR_ENTRY_MAX_LENGTH: usize = 252; pub static IS_UMT: AtomicUsize = AtomicUsize::new(0); @@ -25,7 +25,7 @@ pub use self::allocator::{AllocEntry, AllocList, Allocator, ALLOC_LIST_ENTRIES}; #[cfg(feature = "std")] pub use self::archive::{archive, archive_at}; pub use self::block::{ - BlockAddr, BlockData, BlockLevel, BlockList, BlockPtr, BlockRaw, BlockTrait, + BlockAddr, BlockData, BlockLevel, BlockList, BlockMeta, BlockPtr, BlockRaw, BlockTrait, }; pub use self::dir::{DirEntry, DirList}; pub use self::disk::*; diff --git a/src/record.rs b/src/record.rs index e7815a42c0..7586cd9aed 100644 --- a/src/record.rs +++ b/src/record.rs @@ -5,7 +5,7 @@ use crate::{BlockLevel, BlockTrait, RECORD_LEVEL}; //TODO: this is a box to prevent stack overflows #[derive(Clone)] -pub struct RecordRaw(Box<[u8]>); +pub struct RecordRaw(pub(crate) Box<[u8]>); unsafe impl BlockTrait for RecordRaw { fn empty(level: BlockLevel) -> Option { diff --git a/src/transaction.rs b/src/transaction.rs index d51c1eae8c..8d60c13150 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -14,9 +14,9 @@ use syscall::error::{ use crate::{ htree::{self, HTreeHash, HTreeNode, HTreePtr}, - AllocEntry, AllocList, Allocator, BlockAddr, BlockData, BlockLevel, BlockPtr, BlockTrait, - DirEntry, DirList, Disk, FileSystem, Header, Node, NodeLevel, RecordRaw, TreeData, TreePtr, - ALLOC_GC_THRESHOLD, ALLOC_LIST_ENTRIES, DIR_ENTRY_MAX_LENGTH, HEADER_RING, + AllocEntry, AllocList, Allocator, BlockAddr, BlockData, BlockLevel, BlockMeta, BlockPtr, + BlockTrait, DirEntry, DirList, Disk, FileSystem, Header, Node, NodeLevel, RecordRaw, TreeData, + TreePtr, ALLOC_GC_THRESHOLD, ALLOC_LIST_ENTRIES, DIR_ENTRY_MAX_LENGTH, HEADER_RING, }; pub struct Transaction<'a, D: Disk> { @@ -57,11 +57,11 @@ impl<'a, D: Disk> Transaction<'a, D> { // MARK: block operations // - /// Allocate a new block of size `level`, returning its address. + /// Allocate a new block of size defined by `meta`, returning its address. /// - returns `Err(ENOSPC)` if a block of this size could not be alloated. /// - unsafe because order must be done carefully and changes must be flushed to disk - pub(crate) unsafe fn allocate(&mut self, level: BlockLevel) -> Result { - match self.allocator.allocate(level) { + pub(crate) unsafe fn allocate(&mut self, meta: BlockMeta) -> Result { + match self.allocator.allocate(meta) { Some(addr) => { self.allocator_log.push_back(AllocEntry::allocate(addr)); Ok(addr) @@ -180,7 +180,7 @@ impl<'a, D: Disk> Transaction<'a, D> { while new_blocks.len() * ALLOC_LIST_ENTRIES <= self.allocator_log.len() + self.deallocate.len() { - new_blocks.push(unsafe { self.allocate(BlockLevel::default())? }); + new_blocks.push(unsafe { self.allocate(BlockMeta::default())? }); } // De-allocate old blocks (after allocation to prevent re-use) @@ -321,8 +321,9 @@ impl<'a, D: Disk> Transaction<'a, D> { ptr: BlockPtr, ) -> Result> { if ptr.is_null() { - match T::empty(ptr.addr().level()) { - Some(empty) => Ok(BlockData::new(BlockAddr::default(), empty)), + let addr = ptr.addr(); + match T::empty(addr.level()) { + Some(empty) => Ok(BlockData::new(addr, empty)), None => { #[cfg(feature = "log")] log::error!("READ_BLOCK_OR_EMPTY: INVALID BLOCK LEVEL FOR TYPE"); @@ -336,15 +337,44 @@ impl<'a, D: Disk> Transaction<'a, D> { unsafe fn read_record>( &mut self, - ptr: BlockPtr, + mut ptr: BlockPtr, level: BlockLevel, ) -> Result> { - let record = unsafe { self.read_block_or_empty(ptr)? }; + // Set null pointers to correct size (reduces number of copies below) + if ptr.is_null() { + ptr = BlockPtr::::null(BlockMeta::new(level)); + } + + // Read record from disk, or construct empty one for null pointers + let mut record = unsafe { self.read_block_or_empty(ptr)? }; if record.addr().level() >= level { // Return record if it is larger than or equal to requested level return Ok(record); } + // Attempt to decompress if address metadata indicates compression + if let Some(decomp_level) = record.addr().decomp_level() { + // First 2 bytes store compressed data length + // This means only compressed record sizes up to 64 KiB are supported + let mut decomp = match T::empty(decomp_level) { + Some(empty) => empty, + None => { + #[cfg(feature = "log")] + log::error!("READ_RECORD: INVALID DECOMPRESSED BLOCK LEVEL FOR TYPE"); + return Err(Error::new(ENOENT)); + } + }; + let comp_len = record.data()[0] as usize | ((record.data()[1] as usize) << 8); + if let Err(err) = + lz4_flex::decompress_into(&record.data()[2..(comp_len + 2)], &mut decomp) + { + #[cfg(feature = "log")] + log::error!("READ_RECORD: FAILED TO DECOMPRESS: {:?}", err); + return Err(Error::new(EIO)); + } + record = BlockData::new(BlockAddr::null(BlockMeta::new(decomp_level)), decomp); + } + // If a larger level was requested, // create a fake record with the requested level // and fill it with the data in the original record. @@ -359,7 +389,8 @@ impl<'a, D: Disk> Transaction<'a, D> { }; let len = min(raw.len(), old_raw.len()); raw[..len].copy_from_slice(&old_raw[..len]); - Ok(BlockData::new(BlockAddr::null(level), raw)) + + Ok(BlockData::new(BlockAddr::null(BlockMeta::new(level)), raw)) } /// Write block data to a new address, returning new address @@ -368,8 +399,8 @@ impl<'a, D: Disk> Transaction<'a, D> { mut block: BlockData, ) -> Result> { // Swap block to new address - let level = block.addr().level(); - let old_addr = block.swap_addr(unsafe { self.allocate(level)? }); + let meta = block.addr().meta(); + let old_addr = block.swap_addr(unsafe { self.allocate(meta)? }); // Deallocate old address (will only take effect after sync_allocator, which helps to // prevent re-use before a new header is written if !old_addr.is_null() { @@ -773,7 +804,7 @@ impl<'a, D: Disk> Transaction<'a, D> { unsafe { let parent = self.read_tree(parent_ptr)?; let node_block_data = BlockData::new( - self.allocate(BlockLevel::default())?, + self.allocate(BlockMeta::default())?, Node::new( mode, parent.data().uid(), @@ -1594,7 +1625,8 @@ impl<'a, D: Disk> Transaction<'a, D> { let mut node_changed = false; let record_level = node.data().record_level(); - let node_records = node.data().size().div_ceil(record_level.bytes()); + let node_size = node.data().size(); + let node_records = node_size.div_ceil(record_level.bytes()); let mut i = 0; while i < buf.len() { @@ -1605,30 +1637,64 @@ impl<'a, D: Disk> Transaction<'a, D> { let mut record_ptr = if node_records > (*offset / record_level.bytes()) { self.node_record_ptr(node, *offset / record_level.bytes())? } else { - BlockPtr::null(level) + BlockPtr::null(BlockMeta::new(level)) }; let mut record = unsafe { self.read_record(record_ptr, level)? }; + // If record has changed if buf[i..i + len] != record.data()[j..j + len] { - unsafe { - // CoW record using its current level - let mut old_addr = record.swap_addr(self.allocate(record.addr().level())?); + // Update record in memory + record.data_mut()[j..j + len].copy_from_slice(&buf[i..i + len]); - // If the record was resized we need to dealloc the original ptr - if old_addr.is_null() { - old_addr = record_ptr.addr(); - } - - record.data_mut()[j..j + len].copy_from_slice(&buf[i..i + len]); - record_ptr = self.write_block(record)?; - - if !old_addr.is_null() { - self.deallocate(old_addr); + // Handle record compression, if record is larger than one block + let decomp_level = record.addr().level(); + if decomp_level.0 > 0 { + // Maximum compressed record size is 64 KiB + let mut comp = vec![0; 64 * 1024]; + // First two bytes store compressed data length + match lz4_flex::compress_into(record.data(), &mut comp[2..]) { + Ok(comp_len) => { + comp[0] = comp_len as u8; + comp[1] = (comp_len >> 8) as u8; + let comp_level = BlockLevel::for_bytes((comp_len as u64) + 2); + comp.truncate(comp_level.bytes() as usize); + // Replace record with compressed record, if it saves space + if comp_level < decomp_level { + record = BlockData::new( + BlockAddr::null(BlockMeta::new_compressed( + comp_level, + decomp_level, + )), + RecordRaw(comp.into_boxed_slice()), + ); + } + } + Err(_err) => { + // Failures to compress can be ignored, with the original record data used + } } } + // CoW record using its current level + let mut old_addr = + unsafe { record.swap_addr(self.allocate(record.addr().meta())?) }; + + // If the record was resized we need to dealloc the original ptr + if old_addr.is_null() { + old_addr = record_ptr.addr(); + } + + // Write record to disk + record_ptr = unsafe { self.write_block(record)? }; + + // Update record pointer self.sync_node_record_ptr(node, *offset / record_level.bytes(), record_ptr)?; node_changed = true; + + // Deallocate old record + if !old_addr.is_null() { + unsafe { self.deallocate(old_addr) }; + } } i += len; From f30b363ce11f4f4f2b986d39c714d68af1d40a4d Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 1 Aug 2025 09:55:21 -0600 Subject: [PATCH 02/21] Fix build with no_std --- Cargo.toml | 2 +- fuzz/Cargo.lock | 59 +++++++++++++++++++++++++++++++--------------- src/transaction.rs | 2 +- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f13e0210ca..7793fba75a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ lz4_flex = { version = "0.11", default-features = false, features = ["checked-de range-tree = { version = "0.1", optional = true } redox-path = "0.3.0" redox-scheme = { version = "0.7.0", optional = true } -redox_syscall = { version = "0.5.15", optional = true } +redox_syscall = "0.5.15" seahash = { version = "4.1.0", default-features = false } termion = { version = "4", optional = true } uuid = { version = "1.4", default-features = false } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 5e74fc8228..24f8dced3f 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -4,14 +4,13 @@ version = 4 [[package]] name = "aes" -version = "0.7.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", - "opaque-debug", ] [[package]] @@ -159,11 +158,12 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "cipher" -version = "0.3.0" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "generic-array", + "crypto-common", + "inout", ] [[package]] @@ -303,6 +303,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -361,9 +370,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags", "libc", @@ -382,6 +391,12 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "lz4_flex" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" + [[package]] name = "memchr" version = "2.7.4" @@ -418,12 +433,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "page_size" version = "0.6.0" @@ -493,9 +502,9 @@ checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" [[package]] name = "redox-scheme" -version = "0.2.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6143c4d307e1c99ac14f60b5b07b2dccaf9d17137f7cee4e4e29977dd8014a1b" +checksum = "4da6a0251965958189cdfd5ebb66f99754db4aa165394300aa2b958525d94b64" dependencies = [ "libredox", "redox_syscall", @@ -503,9 +512,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -518,7 +527,7 @@ checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" [[package]] name = "redoxfs" -version = "0.6.10" +version = "0.7.1" dependencies = [ "aes", "argon2", @@ -530,6 +539,7 @@ dependencies = [ "libc", "libredox", "log", + "lz4_flex", "range-tree", "redox-path", "redox-scheme", @@ -538,6 +548,7 @@ dependencies = [ "termion", "time", "uuid", + "xts-mode", ] [[package]] @@ -830,6 +841,16 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "xts-mode" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cbddb7545ca0b9ffa7bdc653e8743303e1712687a6918ced25f2cdbed42520" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/src/transaction.rs b/src/transaction.rs index 8d60c13150..98323bf279 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1650,7 +1650,7 @@ impl<'a, D: Disk> Transaction<'a, D> { let decomp_level = record.addr().level(); if decomp_level.0 > 0 { // Maximum compressed record size is 64 KiB - let mut comp = vec![0; 64 * 1024]; + let mut comp = alloc::vec![0; 64 * 1024]; // First two bytes store compressed data length match lz4_flex::compress_into(record.data(), &mut comp[2..]) { Ok(comp_len) => { From a1c94311db19a41c413d7402f886a5d5fa42de95 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 1 Aug 2025 11:51:37 -0600 Subject: [PATCH 03/21] 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() } From ca7ea3c0c8bafa4e182cc9b129acb9455ee463f7 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 1 Aug 2025 12:07:51 -0600 Subject: [PATCH 04/21] node: debug blocks --- src/node.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/node.rs b/src/node.rs index afe831c0d3..89027da7b4 100644 --- a/src/node.rs +++ b/src/node.rs @@ -368,6 +368,7 @@ impl fmt::Debug for Node { let gid = self.gid; let links = self.links; let size = self.size; + let blocks = self.blocks; let ctime = self.ctime; let ctime_nsec = self.ctime_nsec; let mtime = self.mtime; @@ -380,6 +381,7 @@ impl fmt::Debug for Node { .field("gid", &gid) .field("links", &links) .field("size", &size) + .field("blocks", &blocks) .field("ctime", &ctime) .field("ctime_nsec", &ctime_nsec) .field("mtime", &mtime) From b40fecf18532f03c1cfefa674ed5211d3a2b39b5 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 1 Aug 2025 12:55:49 -0600 Subject: [PATCH 05/21] Fix comment in node.rs --- src/node.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/node.rs b/src/node.rs index 89027da7b4..a070318fc9 100644 --- a/src/node.rs +++ b/src/node.rs @@ -176,8 +176,7 @@ 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 + // This node counts as a block blocks: 1.into(), ctime: 0.into(), ctime_nsec: 0.into(), From d03aa518feabe9984758049f8c3077b04d3282fa Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 1 Aug 2025 13:23:56 -0600 Subject: [PATCH 06/21] Fix compression with 128 KiB records, use compress cache --- src/filesystem.rs | 10 +++++++++- src/transaction.rs | 44 ++++++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/filesystem.rs b/src/filesystem.rs index e13b67bb19..e9f3e79b4d 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -6,9 +6,14 @@ use xts_mode::{get_tweak_default, Xts128}; #[cfg(feature = "std")] use crate::{AllocEntry, AllocList, BlockData, BlockTrait, Key, KeySlot, Node, Salt, TreeList}; use crate::{ - Allocator, BlockAddr, BlockLevel, BlockMeta, Disk, Header, Transaction, BLOCK_SIZE, HEADER_RING, + Allocator, BlockAddr, BlockLevel, BlockMeta, Disk, Header, Transaction, BLOCK_SIZE, + HEADER_RING, RECORD_SIZE, }; +fn compress_cache() -> Box<[u8]> { + vec![0; lz4_flex::block::get_maximum_output_size(RECORD_SIZE as usize)].into_boxed_slice() +} + /// A file system pub struct FileSystem { //TODO: make private @@ -19,6 +24,7 @@ pub struct FileSystem { pub header: Header, pub(crate) allocator: Allocator, pub(crate) cipher_opt: Option>, + pub(crate) compress_cache: Box<[u8]>, } impl FileSystem { @@ -83,6 +89,7 @@ impl FileSystem { header, allocator: Allocator::default(), cipher_opt, + compress_cache: compress_cache(), }; unsafe { fs.reset_allocator()? }; @@ -162,6 +169,7 @@ impl FileSystem { header, allocator: Allocator::default(), cipher_opt, + compress_cache: compress_cache(), }; // Write header generation zero diff --git a/src/transaction.rs b/src/transaction.rs index e740ad6e08..dd8f5ae849 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -395,9 +395,8 @@ impl<'a, D: Disk> Transaction<'a, D> { } }; let comp_len = record.data()[0] as usize | ((record.data()[1] as usize) << 8); - if let Err(err) = - lz4_flex::decompress_into(&record.data()[2..(comp_len + 2)], &mut decomp) - { + let total_len = comp_len + 2; + if let Err(err) = lz4_flex::decompress_into(&record.data()[2..total_len], &mut decomp) { #[cfg(feature = "log")] log::error!("READ_RECORD: FAILED TO DECOMPRESS: {:?}", err); return Err(Error::new(EIO)); @@ -1684,24 +1683,29 @@ impl<'a, D: Disk> Transaction<'a, D> { // Handle record compression, if record is larger than one block let decomp_level = record.addr().level(); if decomp_level.0 > 0 { - // Maximum compressed record size is 64 KiB - let mut comp = alloc::vec![0; 64 * 1024]; - // First two bytes store compressed data length - match lz4_flex::compress_into(record.data(), &mut comp[2..]) { + match lz4_flex::compress_into(record.data(), &mut self.fs.compress_cache) { Ok(comp_len) => { - comp[0] = comp_len as u8; - comp[1] = (comp_len >> 8) as u8; - let comp_level = BlockLevel::for_bytes((comp_len as u64) + 2); - comp.truncate(comp_level.bytes() as usize); - // Replace record with compressed record, if it saves space - if comp_level < decomp_level { - record = BlockData::new( - BlockAddr::null(BlockMeta::new_compressed( - comp_level, - decomp_level, - )), - RecordRaw(comp.into_boxed_slice()), - ); + let total_len = comp_len + 2; + // Maximum compressed record size is 64 KiB + if total_len <= 64 * 1024 { + let comp_level = BlockLevel::for_bytes(total_len as u64); + // Replace record with compressed record, if it saves space + if comp_level < decomp_level { + if let Some(mut comp) = RecordRaw::empty(comp_level) { + // First two bytes store compressed data length + comp[0] = comp_len as u8; + comp[1] = (comp_len >> 8) as u8; + comp[2..total_len] + .copy_from_slice(&self.fs.compress_cache[..comp_len]); + record = BlockData::new( + BlockAddr::null(BlockMeta::new_compressed( + comp_level, + decomp_level, + )), + comp, + ); + } + } } } Err(_err) => { From 0a14166c2f6f454c3282d5d11864bf5bf33c7069 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 1 Aug 2025 14:07:26 -0600 Subject: [PATCH 07/21] Fix issue where compressed data could be returned --- src/transaction.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/transaction.rs b/src/transaction.rs index dd8f5ae849..3ab47b3d03 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -377,10 +377,6 @@ impl<'a, D: Disk> Transaction<'a, D> { // Read record from disk, or construct empty one for null pointers let mut record = unsafe { self.read_block_or_empty(ptr)? }; - if record.addr().level() >= level { - // Return record if it is larger than or equal to requested level - return Ok(record); - } // Attempt to decompress if address metadata indicates compression if let Some(decomp_level) = record.addr().decomp_level() { @@ -404,6 +400,11 @@ impl<'a, D: Disk> Transaction<'a, D> { record = BlockData::new(BlockAddr::null(BlockMeta::new(decomp_level)), decomp); } + // Return record if it is larger than or equal to requested level + if record.addr().level() >= level { + return Ok(record); + } + // If a larger level was requested, // create a fake record with the requested level // and fill it with the data in the original record. @@ -1683,6 +1684,7 @@ impl<'a, D: Disk> Transaction<'a, D> { // Handle record compression, if record is larger than one block let decomp_level = record.addr().level(); if decomp_level.0 > 0 { + assert_eq!(decomp_level.bytes(), record.data().len() as u64); match lz4_flex::compress_into(record.data(), &mut self.fs.compress_cache) { Ok(comp_len) => { let total_len = comp_len + 2; From 5828e12e85b7b66172c74e34bc5d1642e4036314 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 1 Aug 2025 21:32:16 -0600 Subject: [PATCH 08/21] Provide context for all allocations and deallocations --- src/transaction.rs | 228 ++++++++++++++++++++++++--------------------- 1 file changed, 120 insertions(+), 108 deletions(-) diff --git a/src/transaction.rs b/src/transaction.rs index 3ab47b3d03..329bc5e7d5 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -19,6 +19,34 @@ use crate::{ TreePtr, ALLOC_GC_THRESHOLD, ALLOC_LIST_ENTRIES, DIR_ENTRY_MAX_LENGTH, HEADER_RING, }; +trait AllocCtx { + fn allocate(&mut self, _addr: BlockAddr) {} + fn deallocate(&mut self, _addr: BlockAddr) {} +} + +pub struct FsCtx; +impl AllocCtx for FsCtx {} + +impl AllocCtx for TreeData { + fn allocate(&mut self, addr: BlockAddr) { + let blocks = self.data().blocks(); + self.data_mut().set_blocks( + blocks + .checked_add(addr.level().blocks::()) + .expect("node block count overflow"), + ); + } + + fn deallocate(&mut self, addr: BlockAddr) { + let blocks = self.data().blocks(); + self.data_mut().set_blocks( + blocks + .checked_sub(addr.level().blocks::()) + .expect("node block count underflow"), + ); + } +} + pub struct Transaction<'a, D: Disk> { fs: &'a mut FileSystem, //TODO: make private @@ -60,10 +88,15 @@ impl<'a, D: Disk> Transaction<'a, D> { /// Allocate a new block of size defined by `meta`, returning its address. /// - returns `Err(ENOSPC)` if a block of this size could not be alloated. /// - unsafe because order must be done carefully and changes must be flushed to disk - pub(crate) unsafe fn allocate(&mut self, meta: BlockMeta) -> Result { + pub(crate) unsafe fn allocate( + &mut self, + ctx: &mut dyn AllocCtx, + meta: BlockMeta, + ) -> Result { match self.allocator.allocate(meta) { Some(addr) => { self.allocator_log.push_back(AllocEntry::allocate(addr)); + ctx.allocate(addr); Ok(addr) } None => Err(Error::new(ENOSPC)), @@ -72,7 +105,7 @@ impl<'a, D: Disk> Transaction<'a, D> { /// Deallocate the given block. /// - unsafe because order must be done carefully and changes must be flushed to disk - pub(crate) unsafe fn deallocate(&mut self, addr: BlockAddr) { + pub(crate) unsafe fn deallocate(&mut self, ctx: &mut dyn AllocCtx, addr: BlockAddr) { //TODO: should we use some sort of not-null abstraction? assert!(!addr.is_null()); @@ -98,46 +131,22 @@ impl<'a, D: Disk> Transaction<'a, D> { // Deallocate later when syncing filesystem, to avoid re-use self.deallocate.push(addr); } + ctx.deallocate(addr); } - unsafe fn node_allocate( + unsafe fn deallocate_block( &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"), - ); + ctx: &mut dyn AllocCtx, + ptr: BlockPtr, + ) -> bool { + if !ptr.is_null() { + self.deallocate(ctx, ptr.addr()); 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. /// @@ -210,7 +219,7 @@ impl<'a, D: Disk> Transaction<'a, D> { while new_blocks.len() * ALLOC_LIST_ENTRIES <= self.allocator_log.len() + self.deallocate.len() { - new_blocks.push(unsafe { self.allocate(BlockMeta::default())? }); + new_blocks.push(unsafe { self.allocate(&mut FsCtx, BlockMeta::default())? }); } // De-allocate old blocks (after allocation to prevent re-use) @@ -426,16 +435,17 @@ impl<'a, D: Disk> Transaction<'a, D> { /// Write block data to a new address, returning new address pub fn sync_block>( &mut self, + ctx: &mut dyn AllocCtx, mut block: BlockData, ) -> Result> { // Swap block to new address let meta = block.addr().meta(); - let old_addr = block.swap_addr(unsafe { self.allocate(meta)? }); + let old_addr = block.swap_addr(unsafe { self.allocate(ctx, meta)? }); // Deallocate old address (will only take effect after sync_allocator, which helps to // prevent re-use before a new header is written if !old_addr.is_null() { unsafe { - self.deallocate(old_addr); + self.deallocate(ctx, old_addr); } } // Write new block @@ -559,14 +569,14 @@ impl<'a, D: Disk> Transaction<'a, D> { l0.data_mut().ptrs[i0] = block_ptr.cast(); l1.data_mut() .set_branch_full(i1, l0.data().tree_list_is_full()); - l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + l1.data_mut().ptrs[i1] = self.sync_block(&mut FsCtx, l0)?; l2.data_mut() .set_branch_full(i2, l1.data().tree_list_is_full()); - l2.data_mut().ptrs[i2] = self.sync_block(l1)?; + l2.data_mut().ptrs[i2] = self.sync_block(&mut FsCtx, l1)?; l3.data_mut() .set_branch_full(i3, l2.data().tree_list_is_full()); - l3.data_mut().ptrs[i3] = self.sync_block(l2)?; - self.header.tree = self.sync_block(l3)?; + l3.data_mut().ptrs[i3] = self.sync_block(&mut FsCtx, l2)?; + self.header.tree = self.sync_block(&mut FsCtx, l3)?; self.header_changed = true; return Ok(tree_ptr); @@ -603,37 +613,37 @@ impl<'a, D: Disk> Transaction<'a, D> { l0.data_mut().set_branch_full(i0, false); l0.data_mut().ptrs[i0] = BlockPtr::default(); let l0_ptr = if l0.data().tree_list_is_empty() { - unsafe { self.deallocate(l0.addr()) }; + unsafe { self.deallocate(&mut FsCtx, l0.addr()) }; BlockPtr::default() } else { - self.sync_block(l0)? + self.sync_block(&mut FsCtx, l0)? }; l1.data_mut().set_branch_full(i1, false); l1.data_mut().ptrs[i1] = l0_ptr; let l1_ptr = if l1.data().tree_list_is_empty() { - unsafe { self.deallocate(l1.addr()) }; + unsafe { self.deallocate(&mut FsCtx, l1.addr()) }; BlockPtr::default() } else { - self.sync_block(l1)? + self.sync_block(&mut FsCtx, l1)? }; l2.data_mut().set_branch_full(i2, false); l2.data_mut().ptrs[i2] = l1_ptr; let l2_ptr = if l2.data().tree_list_is_empty() { - unsafe { self.deallocate(l2.addr()) }; + unsafe { self.deallocate(&mut FsCtx, l2.addr()) }; BlockPtr::default() } else { - self.sync_block(l2)? + self.sync_block(&mut FsCtx, l2)? }; l3.data_mut().set_branch_full(i3, false); l3.data_mut().ptrs[i3] = l2_ptr; let l3_ptr = if l3.data().tree_list_is_empty() { - unsafe { self.deallocate(l3.addr()) }; + unsafe { self.deallocate(&mut FsCtx, l3.addr()) }; BlockPtr::default() } else { - self.sync_block(l3)? + self.sync_block(&mut FsCtx, l3)? }; self.header.tree = l3_ptr; @@ -669,11 +679,11 @@ impl<'a, D: Disk> Transaction<'a, D> { raw.data_mut().copy_from_slice(node.data()); // Write updates to newly allocated blocks - l0.data_mut().ptrs[i0] = self.sync_block(raw)?; - l1.data_mut().ptrs[i1] = self.sync_block(l0)?; - l2.data_mut().ptrs[i2] = self.sync_block(l1)?; - l3.data_mut().ptrs[i3] = self.sync_block(l2)?; - self.header.tree = self.sync_block(l3)?; + l0.data_mut().ptrs[i0] = self.sync_block(&mut FsCtx, raw)?; + l1.data_mut().ptrs[i1] = self.sync_block(&mut FsCtx, l0)?; + l2.data_mut().ptrs[i2] = self.sync_block(&mut FsCtx, l1)?; + l3.data_mut().ptrs[i3] = self.sync_block(&mut FsCtx, l2)?; + self.header.tree = self.sync_block(&mut FsCtx, l3)?; self.header_changed = true; } @@ -834,7 +844,7 @@ impl<'a, D: Disk> Transaction<'a, D> { unsafe { let parent = self.read_tree(parent_ptr)?; let node_block_data = BlockData::new( - self.allocate(BlockMeta::default())?, + self.allocate(&mut FsCtx, BlockMeta::default())?, Node::new( mode, parent.data().uid(), @@ -880,7 +890,7 @@ impl<'a, D: Disk> Transaction<'a, D> { // Create the first DirList block let dir = BlockData::::empty(BlockAddr::default()).unwrap(); - let dir_ptr = self.sync_block(dir)?; + let dir_ptr = self.sync_block(&mut parent, dir)?; // Add the DirList directly to the parent directory parent.data_mut().level0[1] = unsafe { dir_ptr.cast() }; @@ -929,7 +939,7 @@ impl<'a, D: Disk> Transaction<'a, D> { let mut sibling = BlockData::>::empty(BlockAddr::default()).unwrap(); let _ = mem::replace(sibling.data_mut(), unallocated_sibling); - let sibling_block_ptr = self.sync_block(sibling)?; + let sibling_block_ptr = self.sync_block(&mut parent, sibling)?; let sibling_htree_ptr = HTreePtr::new(sibling_htree_hash, sibling_block_ptr); let sibling_record_ptr: HTreePtr = unsafe { sibling_htree_ptr.cast() }; @@ -938,7 +948,7 @@ impl<'a, D: Disk> Transaction<'a, D> { .data() .find_max_htree_hash() .ok_or(Error::new(EIO))?; - let root_block_ptr = self.sync_block(htree_root)?; + let root_block_ptr = self.sync_block(&mut parent, htree_root)?; let root_htree_ptr = HTreePtr::new(root_htree_hash, root_block_ptr); let root_record_ptr: HTreePtr = unsafe { root_htree_ptr.cast() }; @@ -948,7 +958,7 @@ impl<'a, D: Disk> Transaction<'a, D> { new_root.data_mut().ptrs[0] = sibling_record_ptr; let unexpected_sibling = htree::add_inner_node(new_root.data_mut(), root_record_ptr)?; assert!(unexpected_sibling.is_none()); - let new_root_ptr = self.sync_block(new_root)?; + let new_root_ptr = self.sync_block(&mut parent, new_root)?; // Add the parent node pointer, increase the level, and increase one block size per allocated block parent.data_mut().level0[0] = BlockPtr::marker(htree_levels as u8 + 1); @@ -957,7 +967,7 @@ impl<'a, D: Disk> Transaction<'a, D> { parent.data_mut().set_size(size); } else if htree_levels > 0 { // Update the parent node with the new root pointer - let root_block_ptr = self.sync_block(htree_root)?; + let root_block_ptr = self.sync_block(&mut parent, htree_root)?; parent.data_mut().level0[1] = unsafe { root_block_ptr.cast() }; } else { // Update the parent with the DirList block, ignoring the fake htree_root @@ -999,7 +1009,7 @@ impl<'a, D: Disk> Transaction<'a, D> { let mut dir = self.read_block(dir_ptr)?; let unallocated_sibling = htree::add_dir_entry(dir.data_mut(), &mut htree_ptr.htree_hash, dir_entry)?; - let dir_record_ptr = unsafe { self.sync_block(dir)?.cast() }; + let dir_record_ptr = unsafe { self.sync_block(parent_dir_node, dir)?.cast() }; parent_htree_node.ptrs[htree_ptr_idx] = HTreePtr::new(htree_ptr.htree_hash, dir_record_ptr); @@ -1007,7 +1017,7 @@ impl<'a, D: Disk> Transaction<'a, D> { // The DirList block was split, so we need to add it to the h-tree let mut dir = BlockData::::empty(BlockAddr::default()).unwrap(); let _ = mem::replace(dir.data_mut(), new_unallocated_dir); - let dir_ptr = self.sync_block(dir)?; + let dir_ptr = self.sync_block(parent_dir_node, dir)?; let dir_htree_ptr = HTreePtr::new(new_hash, dir_ptr); let dir_record_ptr: HTreePtr = unsafe { dir_htree_ptr.cast() }; let size = parent_dir_node.data().size() + record_byte_size; @@ -1032,7 +1042,7 @@ impl<'a, D: Disk> Transaction<'a, D> { // Write the muteated H-tree block back to disk and update the parent node's pointer let htree_hash = htree_block.data().find_max_htree_hash().unwrap(); - let htree_block_ptr = self.sync_block(htree_block)?; + let htree_block_ptr = self.sync_block(parent_dir_node, htree_block)?; let htree_record_ptr: BlockPtr = unsafe { htree_block_ptr.cast() }; parent_htree_node.ptrs[htree_ptr_idx] = HTreePtr::new(htree_hash, htree_record_ptr); @@ -1041,7 +1051,7 @@ impl<'a, D: Disk> Transaction<'a, D> { let mut sibling = BlockData::>::empty(BlockAddr::default()).unwrap(); let _ = mem::replace(sibling.data_mut(), new_unallocated_sibling); - let sibling_ptr = self.sync_block(sibling)?; + let sibling_ptr = self.sync_block(parent_dir_node, sibling)?; let sibling_htree_ptr = HTreePtr::new(new_hash, sibling_ptr); let sibling_record_ptr: HTreePtr = unsafe { sibling_htree_ptr.cast() }; let size = parent_dir_node.data().size() + record_byte_size; @@ -1145,7 +1155,7 @@ impl<'a, D: Disk> Transaction<'a, D> { // Dealocate the htree_root only if it was a real root node in the H-tree if htree_levels > 0 { unsafe { - self.deallocate(htree_root.addr()); + self.deallocate(&mut parent, htree_root.addr()); } let record_byte_size = parent.data().record_level().bytes(); let size = parent.data().size() - record_byte_size; @@ -1155,7 +1165,7 @@ impl<'a, D: Disk> Transaction<'a, D> { parent.data_mut().level0[1] = BlockPtr::default(); } else if htree_levels > 0 { // Update the real htree_root and update the ptr in the parent - let htree_root_block_ptr = self.sync_block(htree_root)?; + let htree_root_block_ptr = self.sync_block(&mut parent, htree_root)?; parent.data_mut().level0[1] = unsafe { htree_root_block_ptr.cast() }; } else { // The htree_root is fake, so update the parent with the ptr to the one and only directory list @@ -1167,7 +1177,7 @@ impl<'a, D: Disk> Transaction<'a, D> { self.sync_tree(parent)?; self.remove_tree(node.ptr())?; unsafe { - self.deallocate(node_addr); + self.deallocate(&mut FsCtx, node_addr); } Ok(Some(node_id)) @@ -1216,14 +1226,14 @@ impl<'a, D: Disk> Transaction<'a, D> { if let Some(new_tree_hash) = new_htree_hash { // The entry_ptr needs to be updated in the parent_htree_node - let dir_block_ptr = self.sync_block(dir_list)?; + let dir_block_ptr = self.sync_block(parent_dir_node, dir_list)?; let dir_record_ptr: BlockPtr = unsafe { dir_block_ptr.cast() }; parent_htree_node.ptrs[entry_idx] = HTreePtr::new(new_tree_hash, dir_record_ptr); } else { // The entry needs to be removed from the parent_htree_noce parent_htree_node.ptrs[entry_idx] = HTreePtr::default(); - unsafe { self.deallocate(dir_list.addr()) }; + unsafe { self.deallocate(parent_dir_node, dir_list.addr()) }; let size = parent_dir_node.data().size() - record_byte_size; parent_dir_node.data_mut().set_size(size); } @@ -1256,14 +1266,14 @@ impl<'a, D: Disk> Transaction<'a, D> { if let Some(new_htree_hash) = htree_node.data().find_max_htree_hash() { // The entry_ptr needs to be updated in the parent_htree_node - let htree_block_ptr = self.sync_block(htree_node)?; + let htree_block_ptr = self.sync_block(parent_dir_node, htree_node)?; let htree_record_ptr: BlockPtr = unsafe { htree_block_ptr.cast() }; parent_htree_node.ptrs[entry_idx] = HTreePtr::new(new_htree_hash, htree_record_ptr); } else { // The htree_node is now empty, so remove it parent_htree_node.ptrs[entry_idx] = HTreePtr::default(); - unsafe { self.deallocate(htree_node.addr()) }; + unsafe { self.deallocate(parent_dir_node, htree_node.addr()) }; let size = parent_dir_node.data().size() - record_byte_size; parent_dir_node.data_mut().set_size(size); } @@ -1374,54 +1384,54 @@ impl<'a, D: Disk> Transaction<'a, D> { match NodeLevel::new(record_offset).ok_or(Error::new(ERANGE))? { NodeLevel::L0(i0) => { let ptr = node.data_mut().level0[i0].clear(); - self.node_deallocate_block(node, ptr); + self.deallocate_block(node, ptr); } NodeLevel::L1(i1, i0) => { let mut l0 = self.read_block_or_empty(node.data().level1[i1])?; - self.node_deallocate_block(node, l0.data_mut().ptrs[i0].clear()); + self.deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { let ptr = node.data_mut().level1[i1].clear(); - self.node_deallocate_block(node, ptr); + self.deallocate_block(node, ptr); } else { - node.data_mut().level1[i1] = self.sync_block(l0)?; + node.data_mut().level1[i1] = self.sync_block(node, l0)?; } } 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.node_deallocate_block(node, l0.data_mut().ptrs[i0].clear()); + self.deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { - self.node_deallocate_block(node, l1.data_mut().ptrs[i1].clear()); + self.deallocate_block(node, l1.data_mut().ptrs[i1].clear()); } else { - l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + l1.data_mut().ptrs[i1] = self.sync_block(node, l0)?; } if l1.data().is_empty() { let ptr = node.data_mut().level2[i2].clear(); - self.node_deallocate_block(node, ptr); + self.deallocate_block(node, ptr); } else { - node.data_mut().level2[i2] = self.sync_block(l1)?; + node.data_mut().level2[i2] = self.sync_block(node, l1)?; } } NodeLevel::L3(i3, i2, i1, i0) => { 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.node_deallocate_block(node, l0.data_mut().ptrs[i0].clear()); + self.deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { - self.node_deallocate_block(node, l1.data_mut().ptrs[i1].clear()); + self.deallocate_block(node, l1.data_mut().ptrs[i1].clear()); } else { - l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + l1.data_mut().ptrs[i1] = self.sync_block(node, l0)?; } if l1.data().is_empty() { - self.node_deallocate_block(node, l2.data_mut().ptrs[i2].clear()); + self.deallocate_block(node, l2.data_mut().ptrs[i2].clear()); } else { - l2.data_mut().ptrs[i2] = self.sync_block(l1)?; + l2.data_mut().ptrs[i2] = self.sync_block(node, l1)?; } if l2.data().is_empty() { let ptr = node.data_mut().level3[i3].clear(); - self.node_deallocate_block(node, ptr); + self.deallocate_block(node, ptr); } else { - node.data_mut().level3[i3] = self.sync_block(l2)?; + node.data_mut().level3[i3] = self.sync_block(node, l2)?; } } NodeLevel::L4(i4, i3, i2, i1, i0) => { @@ -1429,27 +1439,27 @@ 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.node_deallocate_block(node, l0.data_mut().ptrs[i0].clear()); + self.deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { - self.node_deallocate_block(node, l1.data_mut().ptrs[i1].clear()); + self.deallocate_block(node, l1.data_mut().ptrs[i1].clear()); } else { - l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + l1.data_mut().ptrs[i1] = self.sync_block(node, l0)?; } if l1.data().is_empty() { - self.node_deallocate_block(node, l2.data_mut().ptrs[i2].clear()); + self.deallocate_block(node, l2.data_mut().ptrs[i2].clear()); } else { - l2.data_mut().ptrs[i2] = self.sync_block(l1)?; + l2.data_mut().ptrs[i2] = self.sync_block(node, l1)?; } if l2.data().is_empty() { - self.node_deallocate_block(node, l3.data_mut().ptrs[i3].clear()); + self.deallocate_block(node, l3.data_mut().ptrs[i3].clear()); } else { - l3.data_mut().ptrs[i3] = self.sync_block(l2)?; + l3.data_mut().ptrs[i3] = self.sync_block(node, l2)?; } if l3.data().is_empty() { let ptr = node.data_mut().level4[i4].clear(); - self.node_deallocate_block(node, ptr); + self.deallocate_block(node, ptr); } else { - node.data_mut().level4[i4] = self.sync_block(l3)?; + node.data_mut().level4[i4] = self.sync_block(node, l3)?; } } } @@ -1474,15 +1484,15 @@ impl<'a, D: Disk> Transaction<'a, D> { let mut l0 = self.read_block_or_empty(node.data().level1[i1])?; l0.data_mut().ptrs[i0] = ptr; - node.data_mut().level1[i1] = self.sync_block(l0)?; + node.data_mut().level1[i1] = self.sync_block(node, l0)?; } 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])?; l0.data_mut().ptrs[i0] = ptr; - l1.data_mut().ptrs[i1] = self.sync_block(l0)?; - node.data_mut().level2[i2] = self.sync_block(l1)?; + l1.data_mut().ptrs[i1] = self.sync_block(node, l0)?; + node.data_mut().level2[i2] = self.sync_block(node, l1)?; } NodeLevel::L3(i3, i2, i1, i0) => { let mut l2 = self.read_block_or_empty(node.data().level3[i3])?; @@ -1490,9 +1500,9 @@ impl<'a, D: Disk> Transaction<'a, D> { let mut l0 = self.read_block_or_empty(l1.data().ptrs[i1])?; l0.data_mut().ptrs[i0] = ptr; - l1.data_mut().ptrs[i1] = self.sync_block(l0)?; - l2.data_mut().ptrs[i2] = self.sync_block(l1)?; - node.data_mut().level3[i3] = self.sync_block(l2)?; + l1.data_mut().ptrs[i1] = self.sync_block(node, l0)?; + l2.data_mut().ptrs[i2] = self.sync_block(node, l1)?; + node.data_mut().level3[i3] = self.sync_block(node, l2)?; } NodeLevel::L4(i4, i3, i2, i1, i0) => { let mut l3 = self.read_block_or_empty(node.data().level4[i4])?; @@ -1501,10 +1511,10 @@ impl<'a, D: Disk> Transaction<'a, D> { let mut l0 = self.read_block_or_empty(l1.data().ptrs[i1])?; l0.data_mut().ptrs[i0] = ptr; - l1.data_mut().ptrs[i1] = self.sync_block(l0)?; - l2.data_mut().ptrs[i2] = self.sync_block(l1)?; - l3.data_mut().ptrs[i3] = self.sync_block(l2)?; - node.data_mut().level4[i4] = self.sync_block(l3)?; + l1.data_mut().ptrs[i1] = self.sync_block(node, l0)?; + l2.data_mut().ptrs[i2] = self.sync_block(node, l1)?; + l3.data_mut().ptrs[i3] = self.sync_block(node, l2)?; + node.data_mut().level4[i4] = self.sync_block(node, l3)?; } } } @@ -1717,7 +1727,7 @@ impl<'a, D: Disk> Transaction<'a, D> { } // CoW record using its current level - let new_addr = unsafe { self.node_allocate(node, record.addr().meta())? }; + let new_addr = unsafe { self.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 @@ -1734,8 +1744,10 @@ impl<'a, D: Disk> Transaction<'a, D> { node_changed = true; // Deallocate old record - unsafe { - self.node_deallocate(node, old_addr); + if !old_addr.is_null() { + unsafe { + self.deallocate(node, old_addr); + } } } From 98286ce752655adea8006b23bc7a9c1692291914 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Sun, 3 Aug 2025 20:02:09 -0600 Subject: [PATCH 09/21] Fix compilation with no_std --- src/filesystem.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filesystem.rs b/src/filesystem.rs index e9f3e79b4d..320ab3876f 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -1,5 +1,5 @@ use aes::Aes128; -use alloc::collections::VecDeque; +use alloc::{boxed::Box, collections::VecDeque, vec}; use syscall::error::{Error, Result, EKEYREJECTED, ENOENT, ENOKEY}; use xts_mode::{get_tweak_default, Xts128}; From ae034662e646e6b7787ceb95bfb1abfe4421d809 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 09:23:29 -0600 Subject: [PATCH 10/21] Fix documentation --- src/node.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/node.rs b/src/node.rs index a070318fc9..4f186cd1d0 100644 --- a/src/node.rs +++ b/src/node.rs @@ -101,8 +101,7 @@ pub struct Node { /// The gid that owns this file pub gid: Le, - /// The number of links to this file - /// (directory entries, symlinks, etc) + /// The number of hard links to this file pub links: Le, /// The length of this file, in bytes From 8a46a6df3992499ebf64765a6776a0e9fde21fcd Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 09:23:41 -0600 Subject: [PATCH 11/21] Implement From for DiskFile --- src/disk/file.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/disk/file.rs b/src/disk/file.rs index 55445ce42b..78d51bce05 100644 --- a/src/disk/file.rs +++ b/src/disk/file.rs @@ -76,3 +76,9 @@ impl Disk for DiskFile { self.file.seek(SeekFrom::End(0)).or_eio() } } + +impl From for DiskFile { + fn from(file: File) -> Self { + Self { file } + } +} From 5622165c9238db24c131526aa1e8f2bad23d4216 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 09:24:21 -0600 Subject: [PATCH 12/21] Support for fast cloning of RedoxFS partition --- Cargo.toml | 6 +++ src/bin/clone.rs | 134 +++++++++++++++++++++++++++++++++++++++++++++++ src/clone.rs | 78 +++++++++++++++++++++++++++ src/lib.rs | 4 ++ 4 files changed, 222 insertions(+) create mode 100644 src/bin/clone.rs create mode 100644 src/clone.rs diff --git a/Cargo.toml b/Cargo.toml index 7793fba75a..0a7091d7af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,12 @@ path = "src/bin/ar.rs" doc = false required-features = ["std"] +[[bin]] +name = "redoxfs-clone" +path = "src/bin/clone.rs" +doc = false +required-features = ["std"] + [[bin]] name = "redoxfs-mkfs" path = "src/bin/mkfs.rs" diff --git a/src/bin/clone.rs b/src/bin/clone.rs new file mode 100644 index 0000000000..13a6cdca73 --- /dev/null +++ b/src/bin/clone.rs @@ -0,0 +1,134 @@ +extern crate redoxfs; +extern crate syscall; +extern crate uuid; + +use std::io::Read; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::{env, fs, process}; + +use redoxfs::{clone, DiskFile, FileSystem}; +use uuid::Uuid; + +fn main() { + env_logger::init(); + + let mut args = env::args().skip(1); + + let disk_path_old = if let Some(path) = args.next() { + path + } else { + println!("redoxfs-clone: no old disk image provided"); + println!("redoxfs-clone NEW-DISK OLD-DISK [BOOTLOADER]"); + process::exit(1); + }; + + let disk_path = if let Some(path) = args.next() { + path + } else { + println!("redoxfs-clone: no new disk image provided"); + println!("redoxfs-clone NEW-DISK OLD-DISK [BOOTLOADER]"); + process::exit(1); + }; + + let bootloader_path_opt = args.next(); + + // Open old disk in readonly mode + let disk_old = match fs::OpenOptions::new() + .read(true) + .write(false) + .open(&disk_path_old) + .map(DiskFile::from) + { + Ok(disk) => disk, + Err(err) => { + println!( + "redoxfs-clone: failed to open old disk image {}: {}", + disk_path_old, err + ); + process::exit(1); + } + }; + + let mut fs_old = match FileSystem::open(disk_old, None, None, false) { + Ok(fs) => fs, + Err(err) => { + println!( + "redoxfs-clone: failed to open filesystem on {}: {}", + disk_path_old, err + ); + process::exit(1); + } + }; + + let disk = match DiskFile::open(&disk_path) { + Ok(disk) => disk, + Err(err) => { + println!( + "redoxfs-clone: failed to open new disk image {}: {}", + disk_path, err + ); + process::exit(1); + } + }; + + let mut bootloader = vec![]; + if let Some(bootloader_path) = bootloader_path_opt { + match fs::File::open(&bootloader_path) { + Ok(mut file) => match file.read_to_end(&mut bootloader) { + Ok(_) => (), + Err(err) => { + println!( + "redoxfs-clone: failed to read bootloader {}: {}", + bootloader_path, err + ); + process::exit(1); + } + }, + Err(err) => { + println!( + "redoxfs-clone: failed to open bootloader {}: {}", + bootloader_path, err + ); + process::exit(1); + } + } + }; + + let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let mut fs = match FileSystem::create_reserved( + disk, + None, + &bootloader, + ctime.as_secs(), + ctime.subsec_nanos(), + ) { + Ok(fs) => fs, + Err(err) => { + println!( + "redoxfs-clone: failed to create filesystem on {}: {}", + disk_path, err + ); + process::exit(1); + } + }; + + match clone(&mut fs_old, &mut fs) { + Ok(()) => (), + Err(err) => { + println!( + "redoxfs-clone: failed to clone {} to {}: {}", + disk_path_old, disk_path, err + ); + process::exit(1); + } + } + + let uuid = Uuid::from_bytes(fs.header.uuid()); + println!( + "redoxfs-clone: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}", + disk_path, + fs.block, + fs.header.size() / 1000 / 1000, + uuid.hyphenated() + ); +} diff --git a/src/clone.rs b/src/clone.rs new file mode 100644 index 0000000000..ea764f019e --- /dev/null +++ b/src/clone.rs @@ -0,0 +1,78 @@ +use std::fs; +use std::io; +use std::os::unix::ffi::OsStrExt; +use std::path::Path; + +use crate::{Disk, FileSystem, Node, Transaction, TreePtr, BLOCK_SIZE}; + +fn syscall_err(err: syscall::Error) -> io::Error { + io::Error::from_raw_os_error(err.errno) +} + +//TODO: handle hard links +pub fn clone_at( + tx_old: &mut Transaction, + parent_ptr_old: TreePtr, + fs: &mut FileSystem, + parent_ptr: TreePtr, + buf: &mut [u8], +) -> syscall::Result<()> { + let mut entries = Vec::new(); + tx_old.child_nodes(parent_ptr_old, &mut entries)?; + for entry in entries { + //TODO: return error instead? + let Some(name) = entry.name() else { + continue; + }; + let node_ptr_old = entry.node_ptr(); + let node_old = tx_old.read_tree(node_ptr_old)?; + + //TODO: doing the whole clone_at inside a single transaction works on Linux but not Redox + let node_ptr = fs.tx(|tx| { + let mode = node_old.data().mode(); + let (ctime, ctime_nsec) = node_old.data().ctime(); + let (mtime, mtime_nsec) = node_old.data().mtime(); + let mut node = tx.create_node(parent_ptr, &name, mode, ctime, ctime_nsec)?; + node.data_mut().set_uid(node_old.data().uid()); + node.data_mut().set_gid(node_old.data().gid()); + node.data_mut().set_mtime(mtime, mtime_nsec); + + if !node_old.data().is_dir() { + let mut offset = 0; + loop { + let count = tx_old.read_node_inner(&node_old, offset, buf)?; + if count == 0 { + break; + } + tx.write_node_inner(&mut node, &mut offset, &buf[..count])?; + } + } + + let node_ptr = node.ptr(); + tx.sync_tree(node)?; + Ok(node_ptr) + })?; + + if node_old.data().is_dir() { + clone_at(tx_old, node_ptr_old, fs, node_ptr, buf)?; + } + } + + Ok(()) +} + +pub fn clone( + fs_old: &mut FileSystem, + fs: &mut FileSystem, +) -> syscall::Result<()> { + fs_old.tx(|tx_old| { + // Clone at root node + let mut buf = vec![0; 4 * 1024 * 1024]; + clone_at(tx_old, TreePtr::root(), fs, TreePtr::root(), &mut buf)?; + + // Squash alloc log + //TODO tx.sync(true)?; + + Ok(()) + }) +} diff --git a/src/lib.rs b/src/lib.rs index 6f9de0d16e..1f50fa976f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,8 @@ pub use self::archive::{archive, archive_at}; pub use self::block::{ BlockAddr, BlockData, BlockLevel, BlockList, BlockMeta, BlockPtr, BlockRaw, BlockTrait, }; +#[cfg(feature = "std")] +pub use self::clone::{clone, clone_at}; pub use self::dir::{DirEntry, DirList}; pub use self::disk::*; pub use self::filesystem::FileSystem; @@ -45,6 +47,8 @@ mod allocator; #[cfg(feature = "std")] mod archive; mod block; +#[cfg(feature = "std")] +mod clone; mod dir; mod disk; mod filesystem; From 74582a09176cc6f415ea07ee578367c4b5d9e4a2 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 09:28:40 -0600 Subject: [PATCH 13/21] Squash allocation log in clone --- src/clone.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clone.rs b/src/clone.rs index ea764f019e..67eeb9a99c 100644 --- a/src/clone.rs +++ b/src/clone.rs @@ -71,7 +71,7 @@ pub fn clone( clone_at(tx_old, TreePtr::root(), fs, TreePtr::root(), &mut buf)?; // Squash alloc log - //TODO tx.sync(true)?; + fs.tx(|tx| tx.sync(true))?; Ok(()) }) From f37afb04bb45d989807ebf991fbbdde22a483c0d Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 10:08:54 -0600 Subject: [PATCH 14/21] Add progress callback to clone function --- src/bin/clone.rs | 28 ++++++++++++++++++++-------- src/clone.rs | 29 ++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/bin/clone.rs b/src/bin/clone.rs index 13a6cdca73..4ef8a299b3 100644 --- a/src/bin/clone.rs +++ b/src/bin/clone.rs @@ -112,7 +112,17 @@ fn main() { } }; - match clone(&mut fs_old, &mut fs) { + let size_old = fs_old.header.size(); + let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE; + let used_old = size_old - free_old; + match clone(&mut fs_old, &mut fs, |used| { + eprint!( + "{}%: {}/{}\r", + (used * 100) / used_old, + used / 1000 / 1000, + used_old / 1000 / 1000 + ); + }) { Ok(()) => (), Err(err) => { println!( @@ -124,11 +134,13 @@ fn main() { } let uuid = Uuid::from_bytes(fs.header.uuid()); - println!( - "redoxfs-clone: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}", - disk_path, - fs.block, - fs.header.size() / 1000 / 1000, - uuid.hyphenated() - ); + let size = fs.header.size(); + let free = fs.allocator().free() * redoxfs::BLOCK_SIZE; + let used = size - free; + println!("redoxfs-clone: created filesystem on {}", disk_path,); + println!("\treserved: {} blocks", fs.block); + println!("\tuuid: {}", uuid.hyphenated()); + println!("\tsize: {} MB", size / 1000 / 1000); + println!("\tused: {} MB", used / 1000 / 1000); + println!("\tfree: {} MB", free / 1000 / 1000); } diff --git a/src/clone.rs b/src/clone.rs index 67eeb9a99c..5d3c85ea28 100644 --- a/src/clone.rs +++ b/src/clone.rs @@ -10,12 +10,13 @@ fn syscall_err(err: syscall::Error) -> io::Error { } //TODO: handle hard links -pub fn clone_at( +pub fn clone_at( tx_old: &mut Transaction, parent_ptr_old: TreePtr, fs: &mut FileSystem, parent_ptr: TreePtr, buf: &mut [u8], + progress: &F, ) -> syscall::Result<()> { let mut entries = Vec::new(); tx_old.child_nodes(parent_ptr_old, &mut entries)?; @@ -53,26 +54,40 @@ pub fn clone_at( Ok(node_ptr) })?; + let size = fs.header.size(); + let free = fs.allocator().free() * BLOCK_SIZE; + progress(size - free); + if node_old.data().is_dir() { - clone_at(tx_old, node_ptr_old, fs, node_ptr, buf)?; + clone_at(tx_old, node_ptr_old, fs, node_ptr, buf, progress)?; } } Ok(()) } -pub fn clone( +pub fn clone( fs_old: &mut FileSystem, fs: &mut FileSystem, + progress: F, ) -> syscall::Result<()> { fs_old.tx(|tx_old| { // Clone at root node let mut buf = vec![0; 4 * 1024 * 1024]; - clone_at(tx_old, TreePtr::root(), fs, TreePtr::root(), &mut buf)?; + clone_at( + tx_old, + TreePtr::root(), + fs, + TreePtr::root(), + &mut buf, + &progress, + )?; - // Squash alloc log - fs.tx(|tx| tx.sync(true))?; + fs.tx(|tx| { + // Squash alloc log + tx.sync(true)?; - Ok(()) + Ok(()) + }) }) } From c796cc7413231e7d4597fd89d27daa846c857bec Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 10:10:22 -0600 Subject: [PATCH 15/21] Add units for clone progress --- src/bin/clone.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/clone.rs b/src/bin/clone.rs index 4ef8a299b3..2854b77e5a 100644 --- a/src/bin/clone.rs +++ b/src/bin/clone.rs @@ -117,7 +117,7 @@ fn main() { let used_old = size_old - free_old; match clone(&mut fs_old, &mut fs, |used| { eprint!( - "{}%: {}/{}\r", + "{}%: {} MB/{} MB\r", (used * 100) / used_old, used / 1000 / 1000, used_old / 1000 / 1000 From d7391bc6a8559fbdb6da56c888072e659ceb0fb1 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 10:18:54 -0600 Subject: [PATCH 16/21] Throttle clone progress messages --- src/bin/clone.rs | 19 ++++++++++++------- src/clone.rs | 10 +++++----- src/lib.rs | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/bin/clone.rs b/src/bin/clone.rs index 2854b77e5a..779151ad73 100644 --- a/src/bin/clone.rs +++ b/src/bin/clone.rs @@ -115,13 +115,18 @@ fn main() { let size_old = fs_old.header.size(); let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE; let used_old = size_old - free_old; - match clone(&mut fs_old, &mut fs, |used| { - eprint!( - "{}%: {} MB/{} MB\r", - (used * 100) / used_old, - used / 1000 / 1000, - used_old / 1000 / 1000 - ); + let mut last_percent = 0; + match clone(&mut fs_old, &mut fs, move |used| { + let percent = (used * 100) / used_old; + if percent != last_percent { + eprint!( + "{}%: {} MB/{} MB\r", + percent, + used / 1000 / 1000, + used_old / 1000 / 1000 + ); + last_percent = percent; + } }) { Ok(()) => (), Err(err) => { diff --git a/src/clone.rs b/src/clone.rs index 5d3c85ea28..3629db6916 100644 --- a/src/clone.rs +++ b/src/clone.rs @@ -10,13 +10,13 @@ fn syscall_err(err: syscall::Error) -> io::Error { } //TODO: handle hard links -pub fn clone_at( +fn clone_at( tx_old: &mut Transaction, parent_ptr_old: TreePtr, fs: &mut FileSystem, parent_ptr: TreePtr, buf: &mut [u8], - progress: &F, + progress: &mut F, ) -> syscall::Result<()> { let mut entries = Vec::new(); tx_old.child_nodes(parent_ptr_old, &mut entries)?; @@ -66,10 +66,10 @@ pub fn clone_at( Ok(()) } -pub fn clone( +pub fn clone( fs_old: &mut FileSystem, fs: &mut FileSystem, - progress: F, + mut progress: F, ) -> syscall::Result<()> { fs_old.tx(|tx_old| { // Clone at root node @@ -80,7 +80,7 @@ pub fn clone( fs, TreePtr::root(), &mut buf, - &progress, + &mut progress, )?; fs.tx(|tx| { diff --git a/src/lib.rs b/src/lib.rs index 1f50fa976f..82b9c01f4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ pub use self::block::{ BlockAddr, BlockData, BlockLevel, BlockList, BlockMeta, BlockPtr, BlockRaw, BlockTrait, }; #[cfg(feature = "std")] -pub use self::clone::{clone, clone_at}; +pub use self::clone::clone; pub use self::dir::{DirEntry, DirList}; pub use self::disk::*; pub use self::filesystem::FileSystem; From 5e044e88c0e221a6b2f6efe5b9f6ea7d6e9101e3 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 10:43:34 -0600 Subject: [PATCH 17/21] Fix cursor when printing clone progress --- src/bin/clone.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/clone.rs b/src/bin/clone.rs index 779151ad73..c3b1e8667a 100644 --- a/src/bin/clone.rs +++ b/src/bin/clone.rs @@ -120,7 +120,7 @@ fn main() { let percent = (used * 100) / used_old; if percent != last_percent { eprint!( - "{}%: {} MB/{} MB\r", + "\r{}%: {} MB/{} MB", percent, used / 1000 / 1000, used_old / 1000 / 1000 From eea5c96137c19aa2d15f2f01a2914562081e5c0d Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 11:25:22 -0600 Subject: [PATCH 18/21] Implement clone threshold to work around Redox problems --- src/bin/clone.rs | 6 ++++-- src/clone.rs | 38 ++++++++++++++++++++++---------------- src/transaction.rs | 4 ++-- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/bin/clone.rs b/src/bin/clone.rs index c3b1e8667a..c29f97220e 100644 --- a/src/bin/clone.rs +++ b/src/bin/clone.rs @@ -116,7 +116,7 @@ fn main() { let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE; let used_old = size_old - free_old; let mut last_percent = 0; - match clone(&mut fs_old, &mut fs, move |used| { + let clone_res = clone(&mut fs_old, &mut fs, move |used| { let percent = (used * 100) / used_old; if percent != last_percent { eprint!( @@ -127,7 +127,9 @@ fn main() { ); last_percent = percent; } - }) { + }); + eprintln!(); + match clone_res { Ok(()) => (), Err(err) => { println!( diff --git a/src/clone.rs b/src/clone.rs index 3629db6916..62a89247f7 100644 --- a/src/clone.rs +++ b/src/clone.rs @@ -9,11 +9,17 @@ fn syscall_err(err: syscall::Error) -> io::Error { io::Error::from_raw_os_error(err.errno) } +fn tx_progress(tx: &mut Transaction, progress: &mut F) { + let size = tx.header.size(); + let free = tx.allocator.free() * BLOCK_SIZE; + progress(size - free); +} + //TODO: handle hard links fn clone_at( tx_old: &mut Transaction, parent_ptr_old: TreePtr, - fs: &mut FileSystem, + tx: &mut Transaction, parent_ptr: TreePtr, buf: &mut [u8], progress: &mut F, @@ -28,8 +34,12 @@ fn clone_at( let node_ptr_old = entry.node_ptr(); let node_old = tx_old.read_tree(node_ptr_old)?; - //TODO: doing the whole clone_at inside a single transaction works on Linux but not Redox - let node_ptr = fs.tx(|tx| { + //TODO: this slows down the clone, but Redox has issues without this (Linux is fine) + if tx.write_cache.len() > 64 { + tx.sync(false)?; + } + + let node_ptr = { let mode = node_old.data().mode(); let (ctime, ctime_nsec) = node_old.data().ctime(); let (mtime, mtime_nsec) = node_old.data().mtime(); @@ -51,15 +61,13 @@ fn clone_at( let node_ptr = node.ptr(); tx.sync_tree(node)?; - Ok(node_ptr) - })?; + node_ptr + }; - let size = fs.header.size(); - let free = fs.allocator().free() * BLOCK_SIZE; - progress(size - free); + tx_progress(tx, progress); if node_old.data().is_dir() { - clone_at(tx_old, node_ptr_old, fs, node_ptr, buf, progress)?; + clone_at(tx_old, node_ptr_old, tx, node_ptr, buf, progress)?; } } @@ -72,22 +80,20 @@ pub fn clone( mut progress: F, ) -> syscall::Result<()> { fs_old.tx(|tx_old| { + let mut tx = Transaction::new(fs); + // Clone at root node let mut buf = vec![0; 4 * 1024 * 1024]; clone_at( tx_old, TreePtr::root(), - fs, + &mut tx, TreePtr::root(), &mut buf, &mut progress, )?; - fs.tx(|tx| { - // Squash alloc log - tx.sync(true)?; - - Ok(()) - }) + // Commit and squash alloc log + tx.commit(true) }) } diff --git a/src/transaction.rs b/src/transaction.rs index 329bc5e7d5..cf89d95fe1 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -53,10 +53,10 @@ pub struct Transaction<'a, D: Disk> { pub header: Header, //TODO: make private pub header_changed: bool, - allocator: Allocator, + pub(crate) allocator: Allocator, allocator_log: VecDeque, deallocate: Vec, - write_cache: BTreeMap>, + pub(crate) write_cache: BTreeMap>, } impl<'a, D: Disk> Transaction<'a, D> { From 5c54b189f92a000d4ee710b714bbc9743613ae9f Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 13:03:07 -0600 Subject: [PATCH 19/21] Prepare for inline node data support --- Cargo.lock | 1 + Cargo.toml | 1 + src/lib.rs | 2 +- src/node.rs | 179 ++++++++++++++++++++++++++++++++++++--------- src/tests.rs | 108 +++++++++++++-------------- src/transaction.rs | 123 ++++++++++++++++--------------- 6 files changed, 268 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4ffeb3669..6e058beb98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,6 +520,7 @@ dependencies = [ "argon2", "assert_cmd", "base64ct", + "bitflags", "endian-num", "env_logger", "fuser", diff --git a/Cargo.toml b/Cargo.toml index 146c0f1725..ef55e1f81e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ required-features = ["std"] aes = { version = "0.8", default-features = false } argon2 = { version = "0.4", default-features = false, features = ["alloc"] } base64ct = { version = "1", default-features = false } +bitflags = "2" endian-num = "0.1" env_logger = { version = "0.11", optional = true } getrandom = { version = "0.2.5", optional = true } diff --git a/src/lib.rs b/src/lib.rs index 82b9c01f4c..375a738b3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ pub use self::header::{Header, HEADER_RING}; pub use self::key::{Key, KeySlot, Salt}; #[cfg(feature = "std")] pub use self::mount::mount; -pub use self::node::{Node, NodeLevel}; +pub use self::node::{Node, NodeLevel, NodeLevelData}; pub use self::record::RecordRaw; pub use self::transaction::Transaction; pub use self::tree::{Tree, TreeData, TreeList, TreePtr}; diff --git a/src/node.rs b/src/node.rs index 4f186cd1d0..0786ce432a 100644 --- a/src/node.rs +++ b/src/node.rs @@ -3,6 +3,12 @@ use endian_num::Le; use crate::{BlockLevel, BlockList, BlockPtr, BlockTrait, RecordRaw, BLOCK_SIZE, RECORD_LEVEL}; +bitflags::bitflags! { + pub struct NodeFlags: u32 { + const INLINE_DATA = 0x1; + } +} + /// An index into a [`Node`]'s block table. pub enum NodeLevel { L0(usize), @@ -85,6 +91,51 @@ type BlockListL2 = BlockList; type BlockListL3 = BlockList; type BlockListL4 = BlockList; +#[repr(C, packed)] +pub struct NodeLevelData { + /// The first 128 blocks of this file. + /// + /// Total size: 128 * RECORD_SIZE (16 MiB, 128 KiB each) + pub level0: [BlockPtr; 128], + + /// The next 64 * 256 blocks of this file, + /// stored behind 64 level one tables. + /// + /// Total size: 64 * 256 * RECORD_SIZE (2 GiB, 32 MiB each) + pub level1: [BlockPtr; 64], + + /// The next 32 * 256 * 256 blocks of this file, + /// stored behind 32 level two tables. + /// Each level two table points to 256 level one tables. + /// + /// Total size: 32 * 256 * 256 * RECORD_SIZE (256 GiB, 8 GiB each) + pub level2: [BlockPtr; 32], + + /// The next 16 * 256 * 256 * 256 blocks of this file, + /// stored behind 16 level three tables. + /// + /// Total size: 16 * 256 * 256 * 256 * RECORD_SIZE (32 TiB, 2 TiB each) + pub level3: [BlockPtr; 16], + + /// The next 8 * 256 * 256 * 256 * 256 blocks of this file, + /// stored behind 8 level four tables. + /// + /// Total size: 8 * 256 * 256 * 256 * 256 * RECORD_SIZE (4 PiB, 512 TiB each) + pub level4: [BlockPtr; 8], +} + +impl Default for NodeLevelData { + fn default() -> Self { + Self { + level0: [BlockPtr::default(); 128], + level1: [BlockPtr::default(); 64], + level2: [BlockPtr::default(); 32], + level3: [BlockPtr::default(); 16], + level4: [BlockPtr::default(); 8], + } + } +} + /// A file/folder node #[repr(C, packed)] pub struct Node { @@ -124,37 +175,14 @@ pub struct Node { /// Record level pub record_level: Le, - pub padding: [u8; BLOCK_SIZE as usize - 4038], + /// Flags + pub flags: Le, - /// The first 128 blocks of this file. - /// - /// Total size: 128 * RECORD_SIZE (16 MiB, 128 KiB each) - pub level0: [BlockPtr; 128], + /// Padding + pub padding: [u8; BLOCK_SIZE as usize - 4042], - /// The next 64 * 256 blocks of this file, - /// stored behind 64 level one tables. - /// - /// Total size: 64 * 256 * RECORD_SIZE (2 GiB, 32 MiB each) - pub level1: [BlockPtr; 64], - - /// The next 32 * 256 * 256 blocks of this file, - /// stored behind 32 level two tables. - /// Each level two table points to 256 level one tables. - /// - /// Total size: 32 * 256 * 256 * RECORD_SIZE (256 GiB, 8 GiB each) - pub level2: [BlockPtr; 32], - - /// The next 16 * 256 * 256 * 256 blocks of this file, - /// stored behind 16 level three tables. - /// - /// Total size: 16 * 256 * 256 * 256 * RECORD_SIZE (32 TiB, 2 TiB each) - pub level3: [BlockPtr; 16], - - /// The next 8 * 256 * 256 * 256 * 256 blocks of this file, - /// stored behind 8 level four tables. - /// - /// Total size: 8 * 256 * 256 * 256 * 256 * RECORD_SIZE (4 PiB, 512 TiB each) - pub level4: [BlockPtr; 8], + /// Level data, kept private so inline data can be supported + level_data: NodeLevelData, } unsafe impl BlockTrait for Node { @@ -184,12 +212,9 @@ impl Default for Node { atime: 0.into(), atime_nsec: 0.into(), record_level: 0.into(), - 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(); 8], + flags: 0.into(), + padding: [0; BLOCK_SIZE as usize - 4042], + level_data: NodeLevelData::default(), } } } @@ -283,6 +308,10 @@ impl Node { BlockLevel(self.record_level.to_ne() as usize) } + pub fn flags(&self) -> NodeFlags { + NodeFlags::from_bits_retain(self.flags.to_ne()) + } + pub fn set_mode(&mut self, mode: u16) { self.mode = mode.into(); } @@ -317,6 +346,56 @@ impl Node { self.atime_nsec = atime_nsec.into(); } + pub fn set_flags(&mut self, flags: NodeFlags) { + self.flags = flags.bits().into(); + } + + pub fn has_inline_data(&self) -> bool { + self.flags().contains(NodeFlags::INLINE_DATA) + } + + pub fn inline_data(&self) -> Option<&[u8]> { + if self.has_inline_data() { + Some(unsafe { + slice::from_raw_parts( + &self.level_data as *const NodeLevelData as *const u8, + mem::size_of::(), + ) + }) + } else { + None + } + } + + pub fn inline_data_mut(&mut self) -> Option<&mut [u8]> { + if self.has_inline_data() { + Some(unsafe { + slice::from_raw_parts_mut( + &mut self.level_data as *mut NodeLevelData as *mut u8, + mem::size_of::(), + ) + }) + } else { + None + } + } + + pub fn level_data(&self) -> Option<&NodeLevelData> { + if !self.has_inline_data() { + Some(&self.level_data) + } else { + None + } + } + + pub fn level_data_mut(&mut self) -> Option<&mut NodeLevelData> { + if !self.has_inline_data() { + Some(&mut self.level_data) + } else { + None + } + } + pub fn is_dir(&self) -> bool { self.mode() & Self::MODE_TYPE == Self::MODE_DIR } @@ -415,6 +494,36 @@ fn node_size_test() { assert_eq!(mem::size_of::(), crate::BLOCK_SIZE as usize); } +#[test] +fn node_inline_data_test() { + let mut node = Node::default(); + assert!(!node.has_inline_data()); + assert!(node.inline_data().is_none()); + assert!(node.inline_data_mut().is_none()); + assert!(node.level_data().is_some()); + assert!(node.level_data_mut().is_some()); + + node.set_flags(NodeFlags::INLINE_DATA); + assert!(node.has_inline_data()); + assert!(node.level_data().is_none()); + assert!(node.level_data_mut().is_none()); + + let node_addr = &node as *const Node as usize; + let meta_size = 128; + { + let inline_data = node.inline_data().unwrap(); + let inline_data_addr = inline_data.as_ptr() as usize; + assert_eq!(node_addr + meta_size, inline_data_addr); + assert_eq!(inline_data.len(), (crate::BLOCK_SIZE as usize) - meta_size); + } + { + let inline_data = node.inline_data_mut().unwrap(); + let inline_data_addr = inline_data.as_ptr() as usize; + assert_eq!(node_addr + meta_size, inline_data_addr); + assert_eq!(inline_data.len(), (crate::BLOCK_SIZE as usize) - meta_size); + } +} + #[cfg(kani)] #[kani::proof] fn check_node_level() { diff --git a/src/tests.rs b/src/tests.rs index 28e33e8f53..a4ad10c939 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,8 +1,8 @@ use crate::htree::{HTreeHash, HTreeNode, HTreePtr, HTREE_IDX_ENTRIES}; use crate::{ - BlockAddr, BlockData, BlockMeta, BlockPtr, DirEntry, DirList, DiskMemory, - DiskSparse, FileSystem, Node, TreePtr, ALLOC_GC_THRESHOLD, BLOCK_SIZE, - transaction::FsCtx, + transaction::{level_data, level_data_mut, FsCtx}, + BlockAddr, BlockData, BlockMeta, BlockPtr, DirEntry, DirList, DiskMemory, DiskSparse, + FileSystem, Node, TreePtr, ALLOC_GC_THRESHOLD, BLOCK_SIZE, }; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering::Relaxed; @@ -289,8 +289,8 @@ fn create_minimal_l2_htree( let l2_ptr = tx.sync_block(&mut parent, l2).unwrap(); let l2_ptr = unsafe { l2_ptr.cast() }; - parent.data_mut().level0[0] = BlockPtr::marker(2); - parent.data_mut().level0[1] = l2_ptr; + level_data_mut(&mut parent)?.level0[0] = BlockPtr::marker(2); + level_data_mut(&mut parent)?.level0[1] = l2_ptr; let size = parent.data().size() + BLOCK_SIZE * 4; parent.data_mut().size = size.into(); tx.sync_tree(parent).unwrap(); @@ -321,10 +321,10 @@ fn insert_dir_entry_without_hash_change() { // THEN the child node is added, but the H-Tree retains its structure, and the updated nodes retain // the old HTreeHash value let parent = tx.read_tree(parent_ptr).unwrap(); - assert!(parent.data().level0[0].is_marker()); - assert_eq!(parent.data().level0[0].addr().level().0, 2); + assert!(level_data(&parent)?.level0[0].is_marker()); + assert_eq!(level_data(&parent)?.level0[0].addr().level().0, 2); - let l2_ptr = unsafe { parent.data().level0[1].cast() }; + let l2_ptr = unsafe { level_data(&parent)?.level0[1].cast() }; let l2: BlockData>> = tx.read_block(l2_ptr).unwrap(); let l1_ptr = l2.data().ptrs[0]; @@ -369,10 +369,10 @@ fn insert_dir_entry_without_hash_change() { // THEN the child node is removed, the H-Tree retains its structure, and the updated nodes retain // the old HTreeHash value let parent = tx.read_tree(parent_ptr).unwrap(); - assert!(parent.data().level0[0].is_marker()); - assert_eq!(parent.data().level0[0].addr().level().0, 2); + assert!(level_data(&parent)?.level0[0].is_marker()); + assert_eq!(level_data(&parent)?.level0[0].addr().level().0, 2); - let l2_ptr = unsafe { parent.data().level0[1].cast() }; + let l2_ptr = unsafe { level_data(&parent)?.level0[1].cast() }; let l2: BlockData>> = tx.read_block(l2_ptr).unwrap(); let l1_ptr = l2.data().ptrs[0]; @@ -433,10 +433,10 @@ fn insert_dir_entry_with_hash_change() { // the new HTreeHash value let child2_htree_hash = HTreeHash::from_name(child2_name); let parent = tx.read_tree(parent_ptr).unwrap(); - assert!(parent.data().level0[0].is_marker()); - assert_eq!(parent.data().level0[0].addr().level().0, 2); + assert!(level_data(&parent)?.level0[0].is_marker()); + assert_eq!(level_data(&parent)?.level0[0].addr().level().0, 2); - let l2_ptr = unsafe { parent.data().level0[1].cast() }; + let l2_ptr = unsafe { level_data(&parent)?.level0[1].cast() }; let l2: BlockData>> = tx.read_block(l2_ptr).unwrap(); let l1_ptr = l2.data().ptrs[0]; @@ -482,10 +482,10 @@ fn insert_dir_entry_with_hash_change() { // to child1's HTreeHash value let child1_htree_hash = HTreeHash::from_name(child1_name); let parent = tx.read_tree(parent_ptr).unwrap(); - assert!(parent.data().level0[0].is_marker()); - assert_eq!(parent.data().level0[0].addr().level().0, 2); + assert!(level_data(&parent)?.level0[0].is_marker()); + assert_eq!(level_data(&parent)?.level0[0].addr().level().0, 2); - let l2_ptr = unsafe { parent.data().level0[1].cast() }; + let l2_ptr = unsafe { level_data(&parent)?.level0[1].cast() }; let l2: BlockData>> = tx.read_block(l2_ptr).unwrap(); let l1_ptr = l2.data().ptrs[0]; @@ -546,8 +546,8 @@ fn delete_to_empty() { ); let parent = tx.read_tree(parent_ptr).unwrap(); - assert!(!parent.data().level0[0].is_marker()); - assert!(parent.data().level0[0].addr().is_null()); + assert!(!level_data(&parent)?.level0[0].is_marker()); + assert!(level_data(&parent)?.level0[0].addr().is_null()); Ok(()) }) @@ -570,11 +570,11 @@ fn split_htree_level0_to_level1() { // Confirm preconditions: the level 0 is full of the expected entries. let parent = tx.read_tree(parent_ptr).unwrap(); - assert!(parent.data().level0[0].is_marker()); - assert_eq!(parent.data().level0[0].addr().level().0, 0); - assert!(!parent.data().level0[0].addr().is_null()); + assert!(level_data(&parent)?.level0[0].is_marker()); + assert_eq!(level_data(&parent)?.level0[0].addr().level().0, 0); + assert!(!level_data(&parent)?.level0[0].addr().is_null()); - let dir_ptr: BlockPtr = unsafe { parent.data().level0[1].cast() }; + let dir_ptr: BlockPtr = unsafe { level_data(&parent)?.level0[1].cast() }; let dir_list = tx.read_block(dir_ptr).unwrap(); for (i, entry) in dir_list.data().entries().enumerate() { assert_eq!(entry.name().unwrap(), format!("child__{i:0243}")); @@ -599,11 +599,12 @@ fn split_htree_level0_to_level1() { // THEN the level is increased and the DirList is split fs.tx(|tx| { let parent = tx.read_tree(parent_ptr).unwrap(); - assert!(parent.data().level0[0].is_marker()); - assert_eq!(parent.data().level0[0].addr().level().0, 1); - assert!(!parent.data().level0[1].addr().is_null()); + assert!(level_data(&parent)?.level0[0].is_marker()); + assert_eq!(level_data(&parent)?.level0[0].addr().level().0, 1); + assert!(!level_data(&parent)?.level0[1].addr().is_null()); - let htree_ptr: BlockPtr> = unsafe { parent.data().level0[1].cast() }; + let htree_ptr: BlockPtr> = + unsafe { level_data(&parent)?.level0[1].cast() }; let htree_node = tx.read_block(htree_ptr).unwrap(); assert!(!htree_node.data().ptrs[0].is_null()); assert_eq!( @@ -654,11 +655,12 @@ fn split_htree_level0_to_level1() { // THEN only the other split remains fs.tx(|tx| { let parent = tx.read_tree(parent_ptr).unwrap(); - assert!(parent.data().level0[0].is_marker()); - assert_eq!(parent.data().level0[0].addr().level().0, 1); - assert!(!parent.data().level0[1].addr().is_null()); + assert!(level_data(&parent)?.level0[0].is_marker()); + assert_eq!(level_data(&parent)?.level0[0].addr().level().0, 1); + assert!(!level_data(&parent)?.level0[1].addr().is_null()); - let htree_ptr: BlockPtr> = unsafe { parent.data().level0[1].cast() }; + let htree_ptr: BlockPtr> = + unsafe { level_data(&parent)?.level0[1].cast() }; let htree_node = tx.read_block(htree_ptr).unwrap(); assert!(!htree_node.data().ptrs[0].is_null()); assert_eq!( @@ -692,8 +694,8 @@ fn split_htree_level0_to_level1() { // THEN the level1 is collapsed back to an empty state fs.tx(|tx| { let parent = tx.read_tree(parent_ptr).unwrap(); - assert!(!parent.data().level0[0].is_marker()); - assert!(parent.data().level0[1].is_null()); + assert!(!level_data(&parent)?.level0[0].is_marker()); + assert!(level_data(&parent)?.level0[1].is_null()); Ok(()) }) .unwrap(); @@ -717,11 +719,11 @@ fn split_htree_with_multiple_levels() { // Confirm preconditions: the level 0 is full of the expected entries. let mut parent = tx.read_tree(parent_ptr).unwrap(); - assert!(parent.data().level0[0].is_marker()); - assert_eq!(parent.data().level0[0].addr().level().0, 2); + assert!(level_data(&parent)?.level0[0].is_marker()); + assert_eq!(level_data(&parent)?.level0[0].addr().level().0, 2); let l2_ptr: BlockPtr>> = - unsafe { parent.data().level0[1].cast() }; + unsafe { level_data(&parent)?.level0[1].cast() }; let mut l2_node = tx.read_block(l2_ptr).unwrap(); for i in 0..HTREE_IDX_ENTRIES { if i == 0 { @@ -745,7 +747,7 @@ fn split_htree_with_multiple_levels() { l2_node.data_mut().ptrs[0].ptr = unsafe { tx.write_block(l1_node) }.unwrap(); let l2_record_ptr = unsafe { tx.write_block(l2_node) }.unwrap(); - parent.data_mut().level0[1] = unsafe { l2_record_ptr.cast() }; + level_data_mut(&mut parent)?.level0[1] = unsafe { l2_record_ptr.cast() }; tx.sync_tree(parent).unwrap(); Ok(()) @@ -767,12 +769,12 @@ fn split_htree_with_multiple_levels() { // THEN the branch splits all the way to the root, increasing the level fs.tx(|tx| { let parent = tx.read_tree(parent_ptr).unwrap(); - assert!(parent.data().level0[0].is_marker()); - assert_eq!(parent.data().level0[0].addr().level().0, 3); - assert!(!parent.data().level0[1].addr().is_null()); + assert!(level_data(&parent)?.level0[0].is_marker()); + assert_eq!(level_data(&parent)?.level0[0].addr().level().0, 3); + assert!(!level_data(&parent)?.level0[1].addr().is_null()); let htree_ptr: BlockPtr>>> = - unsafe { parent.data().level0[1].cast() }; + unsafe { level_data(&parent)?.level0[1].cast() }; let htree_node = tx.read_block(htree_ptr).unwrap(); // Note that while a split tries to evenly divide the H-tree entries between the new two sibling nodes, @@ -817,7 +819,7 @@ fn split_htree_with_multiple_levels() { fs.tx(|tx| { let parent = tx.read_tree(parent_ptr).unwrap(); let htree_ptr: BlockPtr>>> = - unsafe { parent.data().level0[1].cast() }; + unsafe { level_data(&parent)?.level0[1].cast() }; let htree_node = tx.read_block(htree_ptr).unwrap(); assert!(!htree_node.data().ptrs[0].is_null()); @@ -864,7 +866,7 @@ fn split_htree_with_multiple_levels() { fs.tx(|tx| { let parent = tx.read_tree(parent_ptr).unwrap(); let htree_ptr: BlockPtr>>> = - unsafe { parent.data().level0[1].cast() }; + unsafe { level_data(&parent)?.level0[1].cast() }; let htree_node = tx.read_block(htree_ptr).unwrap(); assert!(!htree_node.data().ptrs[0].is_null()); @@ -914,7 +916,7 @@ fn split_htree_with_multiple_levels() { fs.tx(|tx| { let parent = tx.read_tree(parent_ptr).unwrap(); let htree_ptr: BlockPtr>>> = - unsafe { parent.data().level0[1].cast() }; + unsafe { level_data(&parent)?.level0[1].cast() }; let htree_node = tx.read_block(htree_ptr).unwrap(); assert!(!htree_node.data().ptrs[0].is_null()); @@ -945,11 +947,11 @@ fn split_htree_with_multiple_levels_using_duplicates() { // Confirm preconditions: the level 0 is full of the expected entries. let mut parent = tx.read_tree(parent_ptr).unwrap(); - assert!(parent.data().level0[0].is_marker()); - assert_eq!(parent.data().level0[0].addr().level().0, 2); + assert!(level_data(&parent)?.level0[0].is_marker()); + assert_eq!(level_data(&parent)?.level0[0].addr().level().0, 2); let l2_ptr: BlockPtr>> = - unsafe { parent.data().level0[1].cast() }; + unsafe { level_data(&parent)?.level0[1].cast() }; let mut l2_node = tx.read_block(l2_ptr).unwrap(); for i in 0..HTREE_IDX_ENTRIES { if i == 0 { @@ -973,7 +975,7 @@ fn split_htree_with_multiple_levels_using_duplicates() { l2_node.data_mut().ptrs[0].ptr = unsafe { tx.write_block(l1_node) }.unwrap(); let l2_record_ptr = unsafe { tx.write_block(l2_node) }.unwrap(); - parent.data_mut().level0[1] = unsafe { l2_record_ptr.cast() }; + level_data_mut(&mut parent)?.level0[1] = unsafe { l2_record_ptr.cast() }; tx.sync_tree(parent).unwrap(); Ok(()) @@ -987,12 +989,12 @@ fn split_htree_with_multiple_levels_using_duplicates() { // THEN the branch splits all the way to the root, increasing the level fs.tx(|tx| { let parent = tx.read_tree(parent_ptr).unwrap(); - assert!(parent.data().level0[0].is_marker()); - assert_eq!(parent.data().level0[0].addr().level().0, 3); - assert!(!parent.data().level0[1].addr().is_null()); + assert!(level_data(&parent)?.level0[0].is_marker()); + assert_eq!(level_data(&parent)?.level0[0].addr().level().0, 3); + assert!(!level_data(&parent)?.level0[1].addr().is_null()); let htree_ptr: BlockPtr>>> = - unsafe { parent.data().level0[1].cast() }; + unsafe { level_data(&parent)?.level0[1].cast() }; let htree_node = tx.read_block(htree_ptr).unwrap(); // Note that while a split tries to evenly divide the H-tree entries between the new two sibling nodes, @@ -1040,7 +1042,7 @@ fn split_htree_with_multiple_levels_using_duplicates() { fs.tx(|tx| { let parent = tx.read_tree(parent_ptr).unwrap(); let htree_ptr: BlockPtr>>> = - unsafe { parent.data().level0[1].cast() }; + unsafe { level_data(&parent)?.level0[1].cast() }; let htree_node = tx.read_block(htree_ptr).unwrap(); assert!(!htree_node.data().ptrs[0].is_null()); diff --git a/src/transaction.rs b/src/transaction.rs index ba47e7d5a0..d42031ea75 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -15,10 +15,19 @@ use syscall::error::{ use crate::{ htree::{self, HTreeHash, HTreeNode, HTreePtr}, AllocEntry, AllocList, Allocator, BlockAddr, BlockData, BlockLevel, BlockMeta, BlockPtr, - BlockTrait, DirEntry, DirList, Disk, FileSystem, Header, Node, NodeLevel, RecordRaw, TreeData, - TreePtr, ALLOC_GC_THRESHOLD, ALLOC_LIST_ENTRIES, DIR_ENTRY_MAX_LENGTH, HEADER_RING, + BlockTrait, DirEntry, DirList, Disk, FileSystem, Header, Node, NodeLevel, NodeLevelData, + RecordRaw, TreeData, TreePtr, ALLOC_GC_THRESHOLD, ALLOC_LIST_ENTRIES, DIR_ENTRY_MAX_LENGTH, + HEADER_RING, }; +pub(crate) fn level_data(node: &TreeData) -> Result<&NodeLevelData> { + node.data().level_data().ok_or(Error::new(EIO)) +} + +pub(crate) fn level_data_mut(node: &mut TreeData) -> Result<&mut NodeLevelData> { + node.data_mut().level_data_mut().ok_or(Error::new(EIO)) +} + pub trait AllocCtx { fn allocate(&mut self, _addr: BlockAddr) {} fn deallocate(&mut self, _addr: BlockAddr) {} @@ -706,18 +715,18 @@ impl<'a, D: Disk> Transaction<'a, D> { children: &mut Vec, ) -> Result<()> { let parent = self.read_tree(parent_ptr)?; - if parent.data().level0[0].is_marker() { - let htree_levels = parent.data().level0[0].addr().level().0; + if level_data(&parent)?.level0[0].is_marker() { + let htree_levels = level_data(&parent)?.level0[0].addr().level().0; let htree_root = if htree_levels == 0 { // Create a fake root to satisfy the recursive child_nodes_inner function signature let mut fake_htree_node = BlockData::>::empty(BlockAddr::default()).unwrap(); - let dir_ptr = parent.data().level0[1]; + let dir_ptr = level_data(&parent)?.level0[1]; let htree_ptr = HTreePtr::new(HTreeHash::MAX, dir_ptr); fake_htree_node.data_mut().ptrs[0] = htree_ptr; fake_htree_node } else { - let htree_record_ptr = parent.data().level0[1]; + let htree_record_ptr = level_data(&parent)?.level0[1]; let htree_ptr: BlockPtr> = unsafe { htree_record_ptr.cast() }; self.read_block(htree_ptr)? }; @@ -756,23 +765,23 @@ impl<'a, D: Disk> Transaction<'a, D> { /// Returns ENOENT if this node is not found. pub fn find_node(&mut self, parent_ptr: TreePtr, name: &str) -> Result> { let parent = self.read_tree(parent_ptr)?; - if !parent.data().level0[0].is_marker() { + if !level_data(&parent)?.level0[0].is_marker() { return Err(Error::new(ENOENT)); } - let htree_levels = parent.data().level0[0].addr().level().0; + let htree_levels = level_data(&parent)?.level0[0].addr().level().0; let root_htree_node = if htree_levels == 0 { // Create a fake root to satisfy the recursive inner_find_node function signature let mut fake_htree_node = BlockData::>::empty(BlockAddr::default()).unwrap(); - let dir_ptr = parent.data().level0[1]; + let dir_ptr = level_data(&parent)?.level0[1]; let htree_ptr = HTreePtr::new(HTreeHash::MAX, dir_ptr); fake_htree_node.data_mut().ptrs[0] = htree_ptr; fake_htree_node } else { let root_htree_ptr: BlockPtr> = - unsafe { parent.data().level0[1].cast() }; + unsafe { level_data(&parent)?.level0[1].cast() }; self.read_block(root_htree_ptr)? }; @@ -881,37 +890,37 @@ impl<'a, D: Disk> Transaction<'a, D> { let record_byte_size = parent.data().record_level().bytes(); // If this is a brand new directory, create the first DirList block - if !parent.data().level0[0].is_marker() { + if !level_data(&parent)?.level0[0].is_marker() { let marker: BlockPtr = BlockPtr::marker(0); assert!(marker.is_marker()); - parent.data_mut().level0[0] = BlockPtr::marker(0); - assert!(parent.data().level0[0].is_marker()); + level_data_mut(&mut parent)?.level0[0] = BlockPtr::marker(0); + assert!(level_data(&parent)?.level0[0].is_marker()); // Create the first DirList block let dir = BlockData::::empty(BlockAddr::default()).unwrap(); let dir_ptr = self.sync_block(&mut parent, dir)?; // Add the DirList directly to the parent directory - parent.data_mut().level0[1] = unsafe { dir_ptr.cast() }; + level_data_mut(&mut parent)?.level0[1] = unsafe { dir_ptr.cast() }; let size = parent.data().size() + record_byte_size; parent.data_mut().set_size(size); } - let mut htree_levels = parent.data().level0[0].addr().level().0; + let mut htree_levels = level_data(&parent)?.level0[0].addr().level().0; let mut htree_root = if htree_levels == 0 { // If we have no H-tree root, create a fake one to satisfy the recurisve inner_link_node function let mut fake_htree_node = BlockData::>::empty(BlockAddr::default()).unwrap(); - let dir_ptr = parent.data().level0[1]; + let dir_ptr = level_data(&parent)?.level0[1]; let htree_ptr = HTreePtr::new(HTreeHash::MAX, dir_ptr); fake_htree_node.data_mut().ptrs[0] = htree_ptr; fake_htree_node } else { // Otherwise get the real H-tree root let htree_root_ptr: BlockPtr> = - unsafe { parent.data().level0[1].cast() }; + unsafe { level_data(&parent)?.level0[1].cast() }; self.read_block(htree_root_ptr)? }; @@ -926,7 +935,7 @@ impl<'a, D: Disk> Transaction<'a, D> { // If we used a fake root, and we grew beyond a single DirList block, we need to create a real root if htree_levels == 0 && !htree_root.data().ptrs[1].is_null() { htree_levels = 1; - parent.data_mut().level0[0] = BlockPtr::marker(1); + level_data_mut(&mut parent)?.level0[0] = BlockPtr::marker(1); let size = parent.data().size() + record_byte_size; parent.data_mut().set_size(size); } @@ -961,17 +970,17 @@ impl<'a, D: Disk> Transaction<'a, D> { let new_root_ptr = self.sync_block(&mut parent, new_root)?; // Add the parent node pointer, increase the level, and increase one block size per allocated block - parent.data_mut().level0[0] = BlockPtr::marker(htree_levels as u8 + 1); - parent.data_mut().level0[1] = unsafe { new_root_ptr.cast() }; + level_data_mut(&mut parent)?.level0[0] = BlockPtr::marker(htree_levels as u8 + 1); + level_data_mut(&mut parent)?.level0[1] = unsafe { new_root_ptr.cast() }; let size = parent.data().size() + 2 * record_byte_size; parent.data_mut().set_size(size); } else if htree_levels > 0 { // Update the parent node with the new root pointer let root_block_ptr = self.sync_block(&mut parent, htree_root)?; - parent.data_mut().level0[1] = unsafe { root_block_ptr.cast() }; + level_data_mut(&mut parent)?.level0[1] = unsafe { root_block_ptr.cast() }; } else { // Update the parent with the DirList block, ignoring the fake htree_root - parent.data_mut().level0[1] = htree_root.data().ptrs[0].ptr; + level_data_mut(&mut parent)?.level0[1] = htree_root.data().ptrs[0].ptr; } self.sync_trees(&[parent, node])?; Ok(()) @@ -1079,26 +1088,26 @@ impl<'a, D: Disk> Transaction<'a, D> { ); let mut parent = self.read_tree(parent_ptr)?; - if !parent.data().level0[0].is_marker() { + if !level_data(&parent)?.level0[0].is_marker() { #[cfg(feature = "log")] log::error!("REMOVE_NODE: Parent has no htree marker set (not a directory or empty)"); return Err(Error::new(ENOENT)); } - let htree_levels = parent.data().level0[0].addr().level().0; + let htree_levels = level_data(&parent)?.level0[0].addr().level().0; let name_hash = HTreeHash::from_name(name); let mut htree_root = if htree_levels == 0 { // If we have no H-tree root, create a fake one to satisfy the recurisve inner_link_node function let mut fake_htree_node = BlockData::>::empty(BlockAddr::default()).unwrap(); - let dir_ptr = parent.data().level0[1]; + let dir_ptr = level_data(&parent)?.level0[1]; let htree_ptr = HTreePtr::new(HTreeHash::MAX, dir_ptr); fake_htree_node.data_mut().ptrs[0] = htree_ptr; fake_htree_node } else { // Otherwise get the real H-tree root - let htree_root_record_ptr = parent.data().level0[1]; + let htree_root_record_ptr = level_data(&parent)?.level0[1]; let htree_root_ptr: BlockPtr> = unsafe { htree_root_record_ptr.cast() }; self.read_block(htree_root_ptr)? @@ -1161,16 +1170,16 @@ impl<'a, D: Disk> Transaction<'a, D> { let size = parent.data().size() - record_byte_size; parent.data_mut().set_size(size); } - parent.data_mut().level0[0] = BlockPtr::default(); - parent.data_mut().level0[1] = BlockPtr::default(); + level_data_mut(&mut parent)?.level0[0] = BlockPtr::default(); + level_data_mut(&mut parent)?.level0[1] = BlockPtr::default(); } else if htree_levels > 0 { // Update the real htree_root and update the ptr in the parent let htree_root_block_ptr = self.sync_block(&mut parent, htree_root)?; - parent.data_mut().level0[1] = unsafe { htree_root_block_ptr.cast() }; + level_data_mut(&mut parent)?.level0[1] = unsafe { htree_root_block_ptr.cast() }; } else { // The htree_root is fake, so update the parent with the ptr to the one and only directory list let dir_list_block_ptr = htree_root.data().ptrs[0].ptr; - parent.data_mut().level0[1] = unsafe { dir_list_block_ptr.cast() }; + level_data_mut(&mut parent)?.level0[1] = unsafe { dir_list_block_ptr.cast() }; } if remove_node { @@ -1348,24 +1357,24 @@ impl<'a, D: Disk> Transaction<'a, D> { ) -> Result> { unsafe { match NodeLevel::new(record_offset).ok_or(Error::new(ERANGE))? { - NodeLevel::L0(i0) => Ok(node.data().level0[i0]), + NodeLevel::L0(i0) => Ok(level_data(node)?.level0[i0]), NodeLevel::L1(i1, i0) => { - let l0 = self.read_block_or_empty(node.data().level1[i1])?; + let l0 = self.read_block_or_empty(level_data(node)?.level1[i1])?; Ok(l0.data().ptrs[i0]) } NodeLevel::L2(i2, i1, i0) => { - let l1 = self.read_block_or_empty(node.data().level2[i2])?; + let l1 = self.read_block_or_empty(level_data(node)?.level2[i2])?; let l0 = self.read_block_or_empty(l1.data().ptrs[i1])?; Ok(l0.data().ptrs[i0]) } NodeLevel::L3(i3, i2, i1, i0) => { - let l2 = self.read_block_or_empty(node.data().level3[i3])?; + let l2 = self.read_block_or_empty(level_data(node)?.level3[i3])?; let l1 = self.read_block_or_empty(l2.data().ptrs[i2])?; let l0 = self.read_block_or_empty(l1.data().ptrs[i1])?; Ok(l0.data().ptrs[i0]) } NodeLevel::L4(i4, i3, i2, i1, i0) => { - let l3 = self.read_block_or_empty(node.data().level4[i4])?; + let l3 = self.read_block_or_empty(level_data(node)?.level4[i4])?; let l2 = self.read_block_or_empty(l3.data().ptrs[i3])?; let l1 = self.read_block_or_empty(l2.data().ptrs[i2])?; let l0 = self.read_block_or_empty(l1.data().ptrs[i1])?; @@ -1383,21 +1392,21 @@ impl<'a, D: Disk> Transaction<'a, D> { unsafe { match NodeLevel::new(record_offset).ok_or(Error::new(ERANGE))? { NodeLevel::L0(i0) => { - let ptr = node.data_mut().level0[i0].clear(); + let ptr = level_data_mut(node)?.level0[i0].clear(); self.deallocate_block(node, ptr); } NodeLevel::L1(i1, i0) => { - let mut l0 = self.read_block_or_empty(node.data().level1[i1])?; + let mut l0 = self.read_block_or_empty(level_data(node)?.level1[i1])?; self.deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { - let ptr = node.data_mut().level1[i1].clear(); + let ptr = level_data_mut(node)?.level1[i1].clear(); self.deallocate_block(node, ptr); } else { - node.data_mut().level1[i1] = self.sync_block(node, l0)?; + level_data_mut(node)?.level1[i1] = self.sync_block(node, l0)?; } } NodeLevel::L2(i2, i1, i0) => { - let mut l1 = self.read_block_or_empty(node.data().level2[i2])?; + let mut l1 = self.read_block_or_empty(level_data(node)?.level2[i2])?; let mut l0 = self.read_block_or_empty(l1.data().ptrs[i1])?; self.deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { @@ -1406,14 +1415,14 @@ impl<'a, D: Disk> Transaction<'a, D> { l1.data_mut().ptrs[i1] = self.sync_block(node, l0)?; } if l1.data().is_empty() { - let ptr = node.data_mut().level2[i2].clear(); + let ptr = level_data_mut(node)?.level2[i2].clear(); self.deallocate_block(node, ptr); } else { - node.data_mut().level2[i2] = self.sync_block(node, l1)?; + level_data_mut(node)?.level2[i2] = self.sync_block(node, l1)?; } } NodeLevel::L3(i3, i2, i1, i0) => { - let mut l2 = self.read_block_or_empty(node.data().level3[i3])?; + let mut l2 = self.read_block_or_empty(level_data(node)?.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(node, l0.data_mut().ptrs[i0].clear()); @@ -1428,14 +1437,14 @@ impl<'a, D: Disk> Transaction<'a, D> { l2.data_mut().ptrs[i2] = self.sync_block(node, l1)?; } if l2.data().is_empty() { - let ptr = node.data_mut().level3[i3].clear(); + let ptr = level_data_mut(node)?.level3[i3].clear(); self.deallocate_block(node, ptr); } else { - node.data_mut().level3[i3] = self.sync_block(node, l2)?; + level_data_mut(node)?.level3[i3] = self.sync_block(node, l2)?; } } NodeLevel::L4(i4, i3, i2, i1, i0) => { - let mut l3 = self.read_block_or_empty(node.data().level4[i4])?; + let mut l3 = self.read_block_or_empty(level_data(node)?.level4[i4])?; 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])?; @@ -1456,10 +1465,10 @@ impl<'a, D: Disk> Transaction<'a, D> { l3.data_mut().ptrs[i3] = self.sync_block(node, l2)?; } if l3.data().is_empty() { - let ptr = node.data_mut().level4[i4].clear(); + let ptr = level_data_mut(node)?.level4[i4].clear(); self.deallocate_block(node, ptr); } else { - node.data_mut().level4[i4] = self.sync_block(node, l3)?; + level_data_mut(node)?.level4[i4] = self.sync_block(node, l3)?; } } } @@ -1478,34 +1487,34 @@ impl<'a, D: Disk> Transaction<'a, D> { unsafe { match NodeLevel::new(record_offset).ok_or(Error::new(ERANGE))? { NodeLevel::L0(i0) => { - node.data_mut().level0[i0] = ptr; + level_data_mut(node)?.level0[i0] = ptr; } NodeLevel::L1(i1, i0) => { - let mut l0 = self.read_block_or_empty(node.data().level1[i1])?; + let mut l0 = self.read_block_or_empty(level_data(node)?.level1[i1])?; l0.data_mut().ptrs[i0] = ptr; - node.data_mut().level1[i1] = self.sync_block(node, l0)?; + level_data_mut(node)?.level1[i1] = self.sync_block(node, l0)?; } NodeLevel::L2(i2, i1, i0) => { - let mut l1 = self.read_block_or_empty(node.data().level2[i2])?; + let mut l1 = self.read_block_or_empty(level_data(node)?.level2[i2])?; let mut l0 = self.read_block_or_empty(l1.data().ptrs[i1])?; l0.data_mut().ptrs[i0] = ptr; l1.data_mut().ptrs[i1] = self.sync_block(node, l0)?; - node.data_mut().level2[i2] = self.sync_block(node, l1)?; + level_data_mut(node)?.level2[i2] = self.sync_block(node, l1)?; } NodeLevel::L3(i3, i2, i1, i0) => { - let mut l2 = self.read_block_or_empty(node.data().level3[i3])?; + let mut l2 = self.read_block_or_empty(level_data(node)?.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])?; l0.data_mut().ptrs[i0] = ptr; l1.data_mut().ptrs[i1] = self.sync_block(node, l0)?; l2.data_mut().ptrs[i2] = self.sync_block(node, l1)?; - node.data_mut().level3[i3] = self.sync_block(node, l2)?; + level_data_mut(node)?.level3[i3] = self.sync_block(node, l2)?; } NodeLevel::L4(i4, i3, i2, i1, i0) => { - let mut l3 = self.read_block_or_empty(node.data().level4[i4])?; + let mut l3 = self.read_block_or_empty(level_data(node)?.level4[i4])?; 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])?; @@ -1514,7 +1523,7 @@ impl<'a, D: Disk> Transaction<'a, D> { l1.data_mut().ptrs[i1] = self.sync_block(node, l0)?; l2.data_mut().ptrs[i2] = self.sync_block(node, l1)?; l3.data_mut().ptrs[i3] = self.sync_block(node, l2)?; - node.data_mut().level4[i4] = self.sync_block(node, l3)?; + level_data_mut(node)?.level4[i4] = self.sync_block(node, l3)?; } } } From 437d06ab8c85aa0a76c2c364019c7d712d86eab7 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 14:17:03 -0600 Subject: [PATCH 20/21] Support inline data in files --- src/lib.rs | 2 +- src/node.rs | 12 +++++-- src/transaction.rs | 79 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 375a738b3e..89e30cefea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ pub use self::header::{Header, HEADER_RING}; pub use self::key::{Key, KeySlot, Salt}; #[cfg(feature = "std")] pub use self::mount::mount; -pub use self::node::{Node, NodeLevel, NodeLevelData}; +pub use self::node::{Node, NodeFlags, NodeLevel, NodeLevelData}; pub use self::record::RecordRaw; pub use self::transaction::Transaction; pub use self::tree::{Tree, TreeData, TreeList, TreePtr}; diff --git a/src/node.rs b/src/node.rs index 0786ce432a..13d35b9c58 100644 --- a/src/node.rs +++ b/src/node.rs @@ -181,8 +181,8 @@ pub struct Node { /// Padding pub padding: [u8; BLOCK_SIZE as usize - 4042], - /// Level data, kept private so inline data can be supported - level_data: NodeLevelData, + /// Level data, should not be used directly so inline data can be supported + pub(crate) level_data: NodeLevelData, } unsafe impl BlockTrait for Node { @@ -253,6 +253,14 @@ impl Node { 0 } .into(), + flags: if mode & Self::MODE_TYPE == Self::MODE_DIR { + // Directories must not use inline data (until h-tree supports it) + NodeFlags::empty() + } else { + NodeFlags::INLINE_DATA + } + .bits() + .into(), ..Default::default() } } diff --git a/src/transaction.rs b/src/transaction.rs index d42031ea75..24dc659ed6 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -15,17 +15,25 @@ use syscall::error::{ use crate::{ htree::{self, HTreeHash, HTreeNode, HTreePtr}, AllocEntry, AllocList, Allocator, BlockAddr, BlockData, BlockLevel, BlockMeta, BlockPtr, - BlockTrait, DirEntry, DirList, Disk, FileSystem, Header, Node, NodeLevel, NodeLevelData, - RecordRaw, TreeData, TreePtr, ALLOC_GC_THRESHOLD, ALLOC_LIST_ENTRIES, DIR_ENTRY_MAX_LENGTH, - HEADER_RING, + BlockTrait, DirEntry, DirList, Disk, FileSystem, Header, Node, NodeFlags, NodeLevel, + NodeLevelData, RecordRaw, TreeData, TreePtr, ALLOC_GC_THRESHOLD, ALLOC_LIST_ENTRIES, + DIR_ENTRY_MAX_LENGTH, HEADER_RING, }; pub(crate) fn level_data(node: &TreeData) -> Result<&NodeLevelData> { - node.data().level_data().ok_or(Error::new(EIO)) + node.data().level_data().ok_or_else(|| { + #[cfg(feature = "log")] + log::error!("LEVEL_DATA: NODE HAS INLINE DATA"); + Error::new(EIO) + }) } pub(crate) fn level_data_mut(node: &mut TreeData) -> Result<&mut NodeLevelData> { - node.data_mut().level_data_mut().ok_or(Error::new(EIO)) + node.data_mut().level_data_mut().ok_or_else(|| { + #[cfg(feature = "log")] + log::error!("LEVEL_DATA_MUT: NODE HAS INLINE DATA"); + Error::new(EIO) + }) } pub trait AllocCtx { @@ -1538,6 +1546,20 @@ impl<'a, D: Disk> Transaction<'a, D> { buf: &mut [u8], ) -> Result { let node_size = node.data().size(); + + // Try reading from inline data + if let Some(inline_data) = node.data().inline_data() { + if offset >= node_size { + return Ok(0); + } + + let end = min(node_size, offset + (buf.len() as u64)); + let len = end - offset; + buf[..len as usize].copy_from_slice(&inline_data[offset as usize..end as usize]); + offset += len; + return Ok(len as usize); + } + let record_level = node.data().record_level(); let mut bytes_read = 0; @@ -1631,7 +1653,7 @@ impl<'a, D: Disk> Transaction<'a, D> { self.write_node_inner(node, &mut offset, &zeroes[start as usize..end as usize])?; } assert_eq!(offset, size); - } else { + } else if !node.data().has_inline_data() { // Deallocate records for record in (size.div_ceil(record_level.bytes())..old_size / record_level.bytes()).rev() @@ -1670,7 +1692,7 @@ impl<'a, D: Disk> Transaction<'a, D> { Ok(()) } - pub fn write_node_inner( + fn write_node_inner_records( &mut self, node: &mut TreeData, offset: &mut u64, @@ -1772,6 +1794,49 @@ impl<'a, D: Disk> Transaction<'a, D> { Ok(node_changed) } + pub fn write_node_inner( + &mut self, + node: &mut TreeData, + offset: &mut u64, + buf: &[u8], + ) -> Result { + let mut node_changed = false; + + // Try writing to inline data + let node_size = node.data().size(); + let convert_inline = if let Some(inline_data) = node.data_mut().inline_data_mut() { + let end = *offset + (buf.len() as u64); + if end < inline_data.len() as u64 { + inline_data[*offset as usize..end as usize].copy_from_slice(buf); + *offset += buf.len() as u64; + if node.data().size() < *offset { + node.data_mut().set_size(*offset); + } + return Ok(true); + } else { + Some(inline_data[..node_size as usize].to_vec()) + } + } else { + None + }; + + if let Some(inline_data) = convert_inline { + // If inline data cannot fit, convert to records + let mut flags = node.data().flags(); + flags.remove(NodeFlags::INLINE_DATA); + node.data_mut().set_flags(flags); + node.data_mut().level_data = NodeLevelData::default(); + self.write_node_inner_records(node, &mut 0, &inline_data)?; + node_changed = true; + } + + if self.write_node_inner_records(node, offset, buf)? { + node_changed = true; + } + + Ok(node_changed) + } + /// Write the bytes at `buf` to `node` starting at `offset`. pub fn write_node( &mut self, From 5af2abde0aa3880596e41807ddf136d071e2256a Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 26 Sep 2025 14:36:22 -0600 Subject: [PATCH 21/21] Fix reading sparse data when using inline data --- src/transaction.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/transaction.rs b/src/transaction.rs index 24dc659ed6..5d0164146b 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1553,11 +1553,27 @@ impl<'a, D: Disk> Transaction<'a, D> { return Ok(0); } - let end = min(node_size, offset + (buf.len() as u64)); - let len = end - offset; - buf[..len as usize].copy_from_slice(&inline_data[offset as usize..end as usize]); - offset += len; - return Ok(len as usize); + // Read as much as possible from inline data + let mut i = 0; + if offset < inline_data.len() as u64 { + let len = min( + buf.len() as u64, + min(node_size - offset, inline_data.len() as u64 - offset), + ); + buf[i..len as usize] + .copy_from_slice(&inline_data[offset as usize..(offset + len) as usize]); + i += len as usize; + offset += len; + } + + // Handle sparse data (outside of inline data) + while i < buf.len() && offset < node_size { + buf[i] = 0; + i += 1; + offset += 1; + } + + return Ok(i); } let record_level = node.data().record_level();