diff --git a/Cargo.lock b/Cargo.lock index 1a97bc1e76..6e058beb98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,6 +357,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" @@ -514,6 +520,7 @@ dependencies = [ "argon2", "assert_cmd", "base64ct", + "bitflags", "endian-num", "env_logger", "fuser", @@ -521,6 +528,7 @@ dependencies = [ "libc", "libredox", "log", + "lz4_flex", "range-tree", "redox-path", "redox-scheme", diff --git a/Cargo.toml b/Cargo.toml index 6201d3b5fb..ef55e1f81e 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" @@ -34,16 +40,18 @@ 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 } 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 } -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/allocator.rs b/src/allocator.rs index 286695c2bf..6447159882 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. @@ -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/bin/clone.rs b/src/bin/clone.rs new file mode 100644 index 0000000000..c29f97220e --- /dev/null +++ b/src/bin/clone.rs @@ -0,0 +1,153 @@ +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); + } + }; + + let size_old = fs_old.header.size(); + let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE; + let used_old = size_old - free_old; + let mut last_percent = 0; + let clone_res = clone(&mut fs_old, &mut fs, move |used| { + let percent = (used * 100) / used_old; + if percent != last_percent { + eprint!( + "\r{}%: {} MB/{} MB", + percent, + used / 1000 / 1000, + used_old / 1000 / 1000 + ); + last_percent = percent; + } + }); + eprintln!(); + match clone_res { + 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()); + 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/block.rs b/src/block.rs index 2df0e7773b..c6e5833363 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. @@ -74,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 @@ -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/clone.rs b/src/clone.rs new file mode 100644 index 0000000000..62a89247f7 --- /dev/null +++ b/src/clone.rs @@ -0,0 +1,99 @@ +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) +} + +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, + tx: &mut Transaction, + parent_ptr: TreePtr, + buf: &mut [u8], + progress: &mut F, +) -> 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: 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(); + 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)?; + node_ptr + }; + + tx_progress(tx, progress); + + if node_old.data().is_dir() { + clone_at(tx_old, node_ptr_old, tx, node_ptr, buf, progress)?; + } + } + + Ok(()) +} + +pub fn clone( + fs_old: &mut FileSystem, + fs: &mut FileSystem, + 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(), + &mut tx, + TreePtr::root(), + &mut buf, + &mut progress, + )?; + + // Commit and squash alloc log + tx.commit(true) + }) +} 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 } + } +} diff --git a/src/filesystem.rs b/src/filesystem.rs index 15b86665e3..320ab3876f 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -1,11 +1,18 @@ 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}; #[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, 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 { @@ -17,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 { @@ -81,6 +89,7 @@ impl FileSystem { header, allocator: Allocator::default(), cipher_opt, + compress_cache: compress_cache(), }; unsafe { fs.reset_allocator()? }; @@ -160,6 +169,7 @@ impl FileSystem { header, allocator: Allocator::default(), cipher_opt, + compress_cache: compress_cache(), }; // Write header generation zero @@ -174,12 +184,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 +209,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 +264,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..89e30cefea 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,8 +25,10 @@ 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, }; +#[cfg(feature = "std")] +pub use self::clone::clone; pub use self::dir::{DirEntry, DirList}; pub use self::disk::*; pub use self::filesystem::FileSystem; @@ -34,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, NodeFlags, NodeLevel, NodeLevelData}; pub use self::record::RecordRaw; pub use self::transaction::Transaction; pub use self::tree::{Tree, TreeData, TreeList, TreePtr}; @@ -45,6 +47,8 @@ mod allocator; #[cfg(feature = "std")] mod archive; mod block; +#[cfg(feature = "std")] +mod clone; mod dir; mod disk; mod filesystem; 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 75096aabb8..d1afa3da8e 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..13d35b9c58 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,40 +91,8 @@ type BlockListL2 = BlockList; type BlockListL3 = BlockList; type BlockListL4 = BlockList; -/// A file/folder node #[repr(C, packed)] -pub struct Node { - /// This node's type & permissions. - /// - four most significant bits are the node's type - /// - next four bits are permissions for the node's user - /// - next four bits are permissions for the node's group - /// - four least significant bits are permissions for everyone else - pub mode: Le, - - /// The uid that owns this file - pub uid: Le, - - /// The gid that owns this file - pub gid: Le, - - /// The number of links to this file - /// (directory entries, symlinks, etc) - pub links: Le, - - /// The length of this file, in bytes - pub size: Le, - - pub ctime: Le, - pub ctime_nsec: Le, - pub mtime: Le, - pub mtime_nsec: Le, - pub atime: Le, - pub atime_nsec: Le, - - pub record_level: Le, - - pub padding: [u8; BLOCK_SIZE as usize - 4094], - +pub struct NodeLevelData { /// The first 128 blocks of this file. /// /// Total size: 128 * RECORD_SIZE (16 MiB, 128 KiB each) @@ -143,11 +117,72 @@ 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], +} + +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 { + /// This node's type & permissions. + /// - four most significant bits are the node's type + /// - next four bits are permissions for the node's user + /// - next four bits are permissions for the node's group + /// - four least significant bits are permissions for everyone else + pub mode: Le, + + /// The uid that owns this file + pub uid: Le, + + /// The gid that owns this file + pub gid: Le, + + /// The number of hard links to this file + pub links: Le, + + /// 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, + + /// Flags + pub flags: Le, + + /// Padding + pub padding: [u8; BLOCK_SIZE as usize - 4042], + + /// Level data, should not be used directly so inline data can be supported + pub(crate) level_data: NodeLevelData, } unsafe impl BlockTrait for Node { @@ -168,6 +203,8 @@ impl Default for Node { gid: 0.into(), links: 0.into(), size: 0.into(), + // This node counts as a block + blocks: 1.into(), ctime: 0.into(), ctime_nsec: 0.into(), mtime: 0.into(), @@ -175,12 +212,9 @@ impl Default for Node { atime: 0.into(), atime_nsec: 0.into(), record_level: 0.into(), - padding: [0; BLOCK_SIZE as usize - 4094], - level0: [BlockPtr::default(); 128], - level1: [BlockPtr::default(); 64], - level2: [BlockPtr::default(); 32], - level3: [BlockPtr::default(); 16], - level4: [BlockPtr::default(); 12], + flags: 0.into(), + padding: [0; BLOCK_SIZE as usize - 4042], + level_data: NodeLevelData::default(), } } } @@ -219,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() } } @@ -253,6 +295,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()) } @@ -269,6 +316,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(); } @@ -289,6 +340,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(); @@ -299,6 +354,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 } @@ -348,6 +453,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; @@ -360,6 +466,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) @@ -395,6 +502,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/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/tests.rs b/src/tests.rs index 2640038a41..a4ad10c939 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,6 +1,7 @@ use crate::htree::{HTreeHash, HTreeNode, HTreePtr, HTREE_IDX_ENTRIES}; use crate::{ - BlockAddr, BlockData, BlockLevel, BlockPtr, DirEntry, DirList, DiskMemory, DiskSparse, + 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; @@ -260,7 +261,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(&mut FsCtx, BlockMeta::default()) }.unwrap(), Node::new( Node::MODE_FILE, parent.data().uid(), @@ -276,20 +277,20 @@ fn create_minimal_l2_htree( let mut dir_list = BlockData::::empty(BlockAddr::default()).unwrap(); dir_list.data_mut().append(&child1_dir_entry); - let dir_ptr = tx.sync_block(dir_list).unwrap(); + let dir_ptr = tx.sync_block(&mut parent, dir_list).unwrap(); let mut l1 = BlockData::>::empty(BlockAddr::default()).unwrap(); l1.data_mut().ptrs[0] = HTreePtr::new(child1_htree_hash, dir_ptr); - let l1_ptr = tx.sync_block(l1).unwrap(); + let l1_ptr = tx.sync_block(&mut parent, l1).unwrap(); let mut l2 = BlockData::>>::empty(BlockAddr::default()).unwrap(); l2.data_mut().ptrs[0] = HTreePtr::new(child1_htree_hash, l1_ptr); - let l2_ptr = tx.sync_block(l2).unwrap(); + 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(); @@ -320,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]; @@ -368,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]; @@ -432,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]; @@ -481,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]; @@ -545,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(()) }) @@ -569,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}")); @@ -598,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!( @@ -653,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!( @@ -691,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(); @@ -716,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 { @@ -744,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(()) @@ -766,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, @@ -816,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()); @@ -863,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()); @@ -913,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()); @@ -944,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 { @@ -972,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(()) @@ -986,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, @@ -1039,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 d51c1eae8c..5d0164146b 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -14,21 +14,66 @@ 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, 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_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_else(|| { + #[cfg(feature = "log")] + log::error!("LEVEL_DATA_MUT: NODE HAS INLINE DATA"); + Error::new(EIO) + }) +} + +pub 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 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> { @@ -57,13 +102,18 @@ 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, + 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 +122,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()); @@ -84,7 +134,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; @@ -98,13 +148,19 @@ impl<'a, D: Disk> Transaction<'a, D> { // Deallocate later when syncing filesystem, to avoid re-use self.deallocate.push(addr); } + ctx.deallocate(addr); } - fn deallocate_block(&mut self, ptr: BlockPtr) { + unsafe fn deallocate_block( + &mut self, + ctx: &mut dyn AllocCtx, + ptr: BlockPtr, + ) -> bool { if !ptr.is_null() { - unsafe { - self.deallocate(ptr.addr()); - } + self.deallocate(ctx, ptr.addr()); + true + } else { + false } } @@ -180,7 +236,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(&mut FsCtx, BlockMeta::default())? }); } // De-allocate old blocks (after allocation to prevent re-use) @@ -321,8 +377,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,12 +393,41 @@ 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)? }; + + // 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); + 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)); + } + 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 record if it is larger than or equal to requested level return Ok(record); } @@ -359,22 +445,24 @@ 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 pub fn sync_block>( &mut self, + ctx: &mut dyn AllocCtx, 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(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 @@ -498,14 +586,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); @@ -542,37 +630,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; @@ -608,11 +696,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; } @@ -635,18 +723,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)? }; @@ -685,23 +773,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)? }; @@ -773,7 +861,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(&mut FsCtx, BlockMeta::default())?, Node::new( mode, parent.data().uid(), @@ -810,37 +898,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(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() }; + 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)? }; @@ -855,7 +943,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); } @@ -868,7 +956,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() }; @@ -877,7 +965,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() }; @@ -887,20 +975,20 @@ 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); - 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(htree_root)?; - parent.data_mut().level0[1] = unsafe { root_block_ptr.cast() }; + let root_block_ptr = self.sync_block(&mut parent, htree_root)?; + 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(()) @@ -938,7 +1026,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); @@ -946,7 +1034,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; @@ -971,7 +1059,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); @@ -980,7 +1068,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; @@ -1008,26 +1096,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)? @@ -1084,29 +1172,29 @@ 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; 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(htree_root)?; - parent.data_mut().level0[1] = unsafe { htree_root_block_ptr.cast() }; + let htree_root_block_ptr = self.sync_block(&mut parent, htree_root)?; + 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 { self.sync_tree(parent)?; self.remove_tree(node.ptr())?; unsafe { - self.deallocate(node_addr); + self.deallocate(&mut FsCtx, node_addr); } Ok(Some(node_id)) @@ -1155,14 +1243,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); } @@ -1195,14 +1283,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); } @@ -1277,24 +1365,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])?; @@ -1312,78 +1400,83 @@ 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 = 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])?; - self.deallocate_block(l0.data_mut().ptrs[i0].clear()); + 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() { - self.deallocate_block(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(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(l0.data_mut().ptrs[i0].clear()); + self.deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { - self.deallocate_block(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.deallocate_block(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(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(l0.data_mut().ptrs[i0].clear()); + self.deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { - self.deallocate_block(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.deallocate_block(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.deallocate_block(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(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])?; - self.deallocate_block(l0.data_mut().ptrs[i0].clear()); + self.deallocate_block(node, l0.data_mut().ptrs[i0].clear()); if l0.data().is_empty() { - self.deallocate_block(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.deallocate_block(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.deallocate_block(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() { - self.deallocate_block(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(l3)?; + level_data_mut(node)?.level4[i4] = self.sync_block(node, l3)?; } } } @@ -1402,43 +1495,43 @@ 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(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(l0)?; - node.data_mut().level2[i2] = self.sync_block(l1)?; + l1.data_mut().ptrs[i1] = self.sync_block(node, l0)?; + 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(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)?; + 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])?; 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)?; + level_data_mut(node)?.level4[i4] = self.sync_block(node, l3)?; } } } @@ -1453,6 +1546,36 @@ 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); + } + + // 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(); let mut bytes_read = 0; @@ -1546,7 +1669,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() @@ -1585,7 +1708,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, @@ -1594,7 +1717,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 +1729,73 @@ 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 { + 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; + // 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) => { + // Failures to compress can be ignored, with the original record data used + } } } + // CoW record using its current level + 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 + if old_addr.is_null() { + old_addr = record_ptr.addr(); + } + + // Write record to disk + //TODO: deallocate new_addr on failure? + 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(node, old_addr); + } + } } i += len; @@ -1643,6 +1810,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, 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() }