diff --git a/btrfs/src/lib.rs b/btrfs/src/lib.rs index 2662d7d..bab251c 100644 --- a/btrfs/src/lib.rs +++ b/btrfs/src/lib.rs @@ -1,9 +1,16 @@ #![feature(error_in_core)] #![cfg_attr(not(any(feature = "std", test)), no_std)] -use core::{borrow::Borrow, mem::size_of, ops::RangeBounds}; +use core::{ + borrow::Borrow, + cell::{RefCell, RefMut}, + mem::size_of, + ops::RangeBounds, +}; use alloc::{ + borrow::Cow, + boxed::Box, collections::{btree_map::Entry, BTreeMap}, vec, vec::Vec, @@ -11,11 +18,16 @@ use alloc::{ use scroll::{Endian, Pread}; use thiserror::Error; -use structs::{BTreeNode, Chunk, Header, Item, Key, KeyPtr, ObjectType, Stripe, Superblock}; +use structs::{ + BTreeNode, Chunk, Header, Item, Key, KeyPtr, KnownObjectId, ObjectType, RootItem, Stripe, + Superblock, +}; +use tree::Tree; extern crate alloc; pub mod structs; +pub mod tree; #[cfg(feature = "std")] pub mod std_io { @@ -50,6 +62,11 @@ pub enum Error { ExpectedInternalNode, #[error("Expected a leaf node")] ExpectedLeafNode, + #[error("Invalid checksum: expected {expected:#?} but got {actual:#?}")] + InvalidChecksum { + expected: [u8; 32], + actual: [u8; 32], + }, #[error("{0}")] ScrollError(scroll::Error), } @@ -110,8 +127,7 @@ impl Ord for ChunkTreeKey { impl PartialEq for ChunkTreeKey { fn eq(&self, other: &Self) -> bool { - (self.range.contains(&other.range.start) || self.range.contains(&other.range.end)) - || (other.range.contains(&self.range.start) || other.range.contains(&self.range.end)) + self.range.contains(&other.range.start) || other.range.contains(&self.range.start) } } @@ -129,23 +145,34 @@ pub struct ChunkCacheTree { type ChunkTree = BTreeMap; +#[derive(Debug)] pub struct Volume { - reader: R, + reader: Box>, superblock: Superblock, - chunk_cache: ChunkTree, + pub chunk_cache: ChunkTree, + roots: BTreeMap, } impl Volume { + pub fn reader(&self) -> RefMut { + self.reader.borrow_mut() + } + pub fn new(mut reader: R) -> Result { let mut buf = vec![0; size_of::()]; reader.read(&mut buf, Superblock::SUPERBLOCK_BASE_OFFSET as _)?; let superblock = Superblock::parse(&buf)?; let chunk_cache = Self::bootstrap_chunk_tree(&superblock)?; - Ok(Self { - reader, + let mut new = Self { + reader: Box::new(RefCell::new(reader)), superblock, chunk_cache, - }) + roots: Default::default(), + }; + + new.parse_chunk_tree()?; + + Ok(new) } pub fn size_from_logical(&self, logical: u64) -> Option { @@ -169,15 +196,86 @@ impl Volume { }) } - pub fn read_range(&mut self, range: core::ops::Range) -> Result> { + pub fn read_range(&self, range: core::ops::Range) -> Result> { let mut buf = vec![0; (range.end - range.start) as usize]; - self.reader.read(&mut buf, range.start)?; + self.reader().read(&mut buf, range.start)?; Ok(buf) } - pub fn asdf(&mut self) -> Result<()> { - let chunk_root = self.superblock.chunk_root; + fn parse_chunk_node(&mut self, chunk: Vec) -> Result<()> { + let node = BTreeNode::parse(&chunk)?; + let bytes = &chunk[size_of::
()..]; + match node { + BTreeNode::Leaf(leaf) => { + leaf.items + .iter() + .filter(|item| item.key.ty.as_type() == ObjectType::ChunkItem) + .map(|item| { + let chunk = Chunk::parse( + &bytes[item.offset.get() as usize + ..item.offset.get() as usize + item.size.get() as usize], + )?; + + let start = item.key.offset.get() as u64; + let end = start + chunk.length.get(); + + log::info!("chunk: [{start}, {end})"); + match self.chunk_cache.entry(ChunkTreeKey { range: start..end }) { + Entry::Vacant(entry) => { + log::info!("inserting chunk [{start}, {end})"); + entry.insert(chunk.stripe.offset.get()); + Ok(()) + } + Entry::Occupied(entry) => { + log::warn!("overlapping stripes!"); + log::warn!( + "\t{:?} and {:?}", + entry.key(), + ChunkTreeKey { range: start..end } + ); + log::warn!( + "\twith offsets: {} and {}", + entry.get(), + chunk.stripe.offset.get() + ); + + if *entry.get() != chunk.stripe.offset.get() { + log::error!("\tprobably an error?"); + } + Ok(()) + //Err(Error::InvalidOffset) + } + } + }) + .collect::>()?; + + () + } + BTreeNode::Internal(inode) => { + for keyptr in inode.children.iter() { + // TODO: make this another error that actually reports what went wrong + let chunk = self.read_range( + self.range_from_logical(keyptr.blockptr.get()) + .ok_or(Error::ReadFailed)?, + )?; + self.parse_chunk_node(chunk)?; + } + } + } + + Ok(()) + } + + pub fn read_keyptr(&self, keyptr: KeyPtr) -> Result> { + self.read_range( + self.range_from_logical(keyptr.blockptr.get()) + .ok_or(Error::ReadFailed)?, + ) + } + + pub fn parse_chunk_tree(&mut self) -> Result<()> { + let chunk_root = self.superblock.chunk_root.get(); // let size = self.size_from_logical(chunk_root).expect("size"); log::debug!("chunk_root: {chunk_root}"); @@ -187,15 +285,12 @@ impl Volume { let root = self.read_range(physical)?; - let node = BTreeNode::parse(&root)?; - - log::debug!("{node:#?}"); - + self.parse_chunk_node(root)?; Ok(()) } pub fn bootstrap_chunk_tree(superblock: &Superblock) -> Result { - let array_size = superblock.sys_chunk_array_size as usize; + let array_size = superblock.sys_chunk_array_size.get() as usize; let mut offset: usize = 0; let key_size = size_of::(); @@ -209,19 +304,20 @@ impl Volume { return Err(Error::InvalidOffset); } - let key = bytes.gread_with::(&mut offset, scroll::LE)?; - if key.ty.as_type() != Some(ObjectType::ChunkItem) { + let key = bytes.gread::(&mut offset)?; + if key.ty.as_type() != ObjectType::ChunkItem { log::error!("key is not of type ChunkItem"); return Err(Error::InvalidOffset); } - let chunk = bytes.gread_with::(&mut offset, scroll::LE)?; - if chunk.num_stripes == 0 { + let chunk = bytes.gread::(&mut offset)?; + let num_stripes = chunk.num_stripes.get(); // copy to prevent unaligned access + + if num_stripes == 0 { log::error!("num_stripes cannot be 0"); return Err(Error::InvalidOffset); } - let num_stripes = chunk.num_stripes; // copy to prevent unaligned access if num_stripes != 1 { log::warn!( "warning: {} stripes detected but only processing 1", @@ -229,11 +325,14 @@ impl Volume { ); } + let key_offset = key.offset.get(); + let chunk_length = chunk.length.get(); + match chunk_tree.entry(ChunkTreeKey { - range: key.offset..(key.offset + chunk.length), + range: key_offset..(key_offset + chunk_length), }) { Entry::Vacant(entry) => { - entry.insert(chunk.stripe.offset); + entry.insert(chunk.stripe.offset.get()); } Entry::Occupied(_) => { log::error!("overlapping stripes!"); @@ -251,6 +350,26 @@ impl Volume { Ok(chunk_tree) } + pub fn iter_roots(&self) { + let root_tree = + Tree::from_logical_offset(self, self.superblock().root.get()).expect("tree"); + + let range = root_tree.full_range(); + + for (item, value) in range { + if item.key.ty.as_type() == ObjectType::RootItem { + let root = value.as_root().expect("root"); + let tree = Tree::from_logical_offset(self, root.bytenr.get()).expect("tree"); + + log::info!("some tree idek which one innit"); + for (i, v) in tree.full_range() { + log::info!("{i:?}"); + log::info!("{v:?}"); + } + } + } + } + pub fn superblock(&self) -> &Superblock { &self.superblock } diff --git a/btrfs/src/structs.rs b/btrfs/src/structs.rs index ba1ba1b..b9dfdd0 100644 --- a/btrfs/src/structs.rs +++ b/btrfs/src/structs.rs @@ -3,17 +3,59 @@ use core::{mem::size_of, ops::Deref}; use bytemuck::{Pod, Zeroable}; use derivative::Derivative; use num_enum::{FromPrimitive, TryFromPrimitive}; -use scroll::{ - ctx::{SizeWith, TryFromCtx}, - Pread, SizeWith, -}; -use zerocopy::{AsBytes, FromBytes}; +use scroll::{ctx::TryFromCtx, Pread, SizeWith}; +use zerocopy::{byteorder::LE, AsBytes, FromBytes, U16, U32, U64}; use crate::{Error, Result}; +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive)] +#[repr(u64)] +pub enum KnownObjectId { + RootTree = 1, + ExtentTree, + ChunkTree, + DevTree, + FsTree, + RootTreeDir, + CsumTree, + QuotaTree, + UuidTree, + FreeSpaceTree, + DataRelocTree = u64::MAX - 9, + TreeReloc = u64::MAX - 8, + TreeLog = u64::MAX - 7, + Orphan = u64::MAX - 5, + #[num_enum(catch_all)] + Custom(u64), +} + +#[repr(C, packed)] +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, AsBytes)] +pub struct ObjectId { + inner: U64, +} + +impl core::fmt::Debug for ObjectId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ObjectTypeWrapper") + .field("inner", &self.as_id()) + .finish() + } +} + +impl ObjectId { + pub fn as_id(self) -> KnownObjectId { + KnownObjectId::from_primitive(self.inner.get()) + } +} + +unsafe impl Pod for ObjectId {} +unsafe impl Zeroable for ObjectId {} + #[repr(u8)] #[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive)] //#[rustc_nonnull_optimization_guaranteed] pub enum ObjectType { INodeItem = 0x01, @@ -46,25 +88,25 @@ pub enum ObjectType { DevStats = 0xF9, SubvolUuid = 0xFB, SubvolRecUuid = 0xFC, + #[num_enum(catch_all)] + Invalid(u8), } #[repr(C, packed)] -#[derive(Clone, Copy, PartialEq, Eq, Pread, SizeWith)] +#[derive(Clone, Copy, PartialEq, Eq, Pread, SizeWith, FromBytes, AsBytes)] pub struct ObjectTypeWrapper { inner: u8, } impl core::fmt::Debug for ObjectTypeWrapper { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("ObjectTypeWrapper") - .field("inner", &self.as_type()) - .finish() + write!(f, "{:?}", self.as_type()) } } impl ObjectTypeWrapper { - pub fn as_type(self) -> Option { - ObjectType::try_from_primitive(self.inner).ok() + pub fn as_type(self) -> ObjectType { + ObjectType::from_primitive(self.inner) } } @@ -72,24 +114,12 @@ unsafe impl Pod for ObjectTypeWrapper {} unsafe impl Zeroable for ObjectTypeWrapper {} #[repr(transparent)] -#[derive(Debug, Clone, Copy)] -pub struct Uuid(uuid::Uuid); +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, AsBytes)] +pub struct Uuid(uuid::Bytes); -impl SizeWith for Uuid { - fn size_with(_: &scroll::Endian) -> usize { - size_of::() - } -} - -impl<'a> TryFromCtx<'a, scroll::Endian> for Uuid { - type Error = scroll::Error; - - fn try_from_ctx( - from: &'a [u8], - _: scroll::Endian, - ) -> core::result::Result<(Self, usize), Self::Error> { - let size = size_of::(); - Ok((*bytemuck::from_bytes::(&from[..size]), size)) +impl core::fmt::Debug for Uuid { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + uuid::Uuid::from_bytes_ref(&self.0).fmt(f) } } @@ -97,7 +127,7 @@ impl Deref for Uuid { type Target = uuid::Uuid; fn deref(&self) -> &Self::Target { - &self.0 + uuid::Uuid::from_bytes_ref(&self.0) } } @@ -105,43 +135,81 @@ unsafe impl Pod for Uuid {} unsafe impl Zeroable for Uuid {} #[repr(C, packed(1))] -#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] +#[derive(Debug, Clone, Copy, Eq, FromBytes, AsBytes)] pub struct Key { - pub id: u64, + pub id: ObjectId, pub ty: ObjectTypeWrapper, - pub offset: u64, + pub offset: U64, } -#[repr(C, packed(1))] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -pub struct TreeHeader { - csum: [u8; 32], - fs_uuid: Uuid, - address: u64, - flags: u64, - chunk_tree_uuid: Uuid, - generation: u64, - tree_id: u64, - num_items: u32, - level: u8, +impl Key { + pub fn ty(&self) -> ObjectType { + self.ty.as_type() + } + pub fn id(&self) -> KnownObjectId { + self.id.as_id() + } } -#[repr(C, packed(1))] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -pub struct LeafNode { - key: Key, - offset: u32, - size: u32, +impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() && self.ty() == other.ty() && self.offset == other.offset + } } -#[repr(C, packed(1))] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -pub struct InternalNode { - key: Key, - address: u64, - generation: u64, +impl Ord for Key { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.partial_cmp(other).unwrap() + } } +impl PartialOrd for Key { + fn partial_cmp(&self, other: &Self) -> Option { + match self.id().partial_cmp(&other.id()) { + Some(core::cmp::Ordering::Equal) => {} + ord => return ord, + } + match self.ty().partial_cmp(&other.ty()) { + Some(core::cmp::Ordering::Equal) => {} + ord => return ord, + } + self.offset.get().partial_cmp(&other.offset.get()) + } +} + +macro_rules! impl_try_from_ctx { + ($($ty:ty),*) => { + $(impl<'a> TryFromCtx<'a> for $ty { + type Error = scroll::Error; + + fn try_from_ctx( + from: &'a [u8], + _: (), + ) -> core::result::Result<(Self, usize), Self::Error> { + Self::read_from(&from[..size_of::()]) + .map(|v| (v, size_of::())) + .ok_or(scroll::Error::TooBig { + size: size_of::(), + len: from.len(), + }) + } + })* + }; +} + +macro_rules! impl_parse_try_from_ctx { + ($($ty:ty),*) => { + $(impl $ty { + pub fn parse(bytes: &[u8]) -> Result { + Ok(bytes.pread(0)?) + } + })* + }; +} + +impl_parse_try_from_ctx!(Chunk, Header, Key, RootItem); +impl_try_from_ctx!(Key, Chunk, Header, Superblock, RootItem); + const MAX_LABEL_SIZE: usize = 0x100; const SYS_CHUNK_ARRAY_SIZE: usize = 0x800; const BTRFS_NUM_BACKUP_ROOTS: usize = 4; @@ -159,21 +227,21 @@ fn format_u8str>(s: &T, f: &mut core::fmt::Formatter) -> core::fm } #[repr(C, packed(1))] -#[derive(Derivative, Clone, Copy, Pod, Zeroable)] +#[derive(Derivative, Clone, Copy, FromBytes, AsBytes)] #[derivative(Debug)] pub struct INodeItem { - generation: u64, - transid: u64, - st_size: u64, - st_blocks: u64, - block_group: u64, - st_nlink: u32, - st_uid: u32, - st_gid: u32, - st_mode: u32, - st_rdev: u64, - flags: u64, - sequence: u64, + generation: U64, + transid: U64, + st_size: U64, + st_blocks: U64, + block_group: U64, + st_nlink: U32, + st_uid: U32, + st_gid: U32, + st_mode: U32, + st_rdev: U64, + flags: U64, + sequence: U64, #[derivative(Debug = "ignore")] reserved: [u8; 32], st_atime: Timespec, @@ -251,7 +319,7 @@ pub struct ExtentItem { } #[repr(C, packed(1))] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[derive(Debug, Clone, Copy)] pub struct ExtentItem2 { first_item: Key, level: u8, @@ -264,7 +332,7 @@ pub struct ExtentItemV0 { } #[repr(C, packed(1))] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[derive(Debug, Clone, Copy)] pub struct ExtentItemTree { extent_item: ExtentItem, first_item: Key, @@ -325,7 +393,7 @@ pub struct FreeSpaceEntry { } #[repr(C, packed(1))] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[derive(Debug, Clone, Copy)] pub struct FreeSpaceItem { key: Key, @@ -392,28 +460,28 @@ pub const CHUNK_ITEM_KEY: u8 = 228; pub const FT_REG_FILE: u8 = 1; #[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] +#[derive(Debug, Clone, Copy, FromBytes, AsBytes)] pub struct DevItem { /// the internal btrfs device id - pub devid: u64, + pub devid: U64, /// size of the device - pub total_bytes: u64, + pub total_bytes: U64, /// bytes used - pub bytes_used: u64, + pub bytes_used: U64, /// optimal io alignment for this device - pub io_align: u32, + pub io_align: U32, /// optimal io width for this device - pub io_width: u32, + pub io_width: U32, /// minimal io size for this device - pub sector_size: u32, + pub sector_size: U32, /// type and info about this device - pub ty: u64, + pub ty: U64, /// expected generation for this device - pub generation: u64, + pub generation: U64, /// starting byte of this partition on the device, to allow for stripe alignment in the future - pub start_offset: u64, + pub start_offset: U64, /// grouping information for allocation decisions - pub dev_group: u32, + pub dev_group: U32, /// seek speed 0-100 where 100 is fastest pub seek_speed: u8, /// bandwidth 0-100 where 100 is fastest @@ -425,23 +493,23 @@ pub struct DevItem { } #[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] +#[derive(Debug, Clone, Copy, FromBytes, AsBytes)] pub struct RootBackup { - pub tree_root: u64, - pub tree_root_gen: u64, - pub chunk_root: u64, - pub chunk_root_gen: u64, - pub extent_root: u64, - pub extent_root_gen: u64, - pub fs_root: u64, - pub fs_root_gen: u64, - pub dev_root: u64, - pub dev_root_gen: u64, - pub csum_root: u64, - pub csum_root_gen: u64, - pub total_bytes: u64, - pub bytes_used: u64, - pub num_devices: u64, + pub tree_root: U64, + pub tree_root_gen: U64, + pub chunk_root: U64, + pub chunk_root_gen: U64, + pub extent_root: U64, + pub extent_root_gen: U64, + pub fs_root: U64, + pub fs_root_gen: U64, + pub dev_root: U64, + pub dev_root_gen: U64, + pub csum_root: U64, + pub csum_root_gen: U64, + pub total_bytes: U64, + pub bytes_used: U64, + pub num_devices: U64, /// future pub unused_64: [u64; 4], pub tree_root_level: u8, @@ -455,55 +523,56 @@ pub struct RootBackup { } #[repr(C, packed)] -#[derive(Derivative, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] +#[derive(Derivative, Clone, Copy, FromBytes, AsBytes)] #[derivative(Debug)] pub struct Superblock { pub csum: [u8; 32], pub fsid: Uuid, /// Physical address of this block - pub bytenr: u64, - pub flags: u64, + pub bytenr: U64, + pub flags: U64, pub magic: [u8; 0x8], - pub generation: u64, + pub generation: U64, /// Logical address of the root tree root - pub root: u64, + pub root: U64, /// Logical address of the chunk tree root - pub chunk_root: u64, + pub chunk_root: U64, /// Logical address of the log tree root - pub log_root: u64, - pub log_root_transid: u64, - pub total_bytes: u64, - pub bytes_used: u64, - pub root_dir_objectid: u64, - pub num_devices: u64, - pub sector_size: u32, - pub node_size: u32, + pub log_root: U64, + pub log_root_transid: U64, + pub total_bytes: U64, + pub bytes_used: U64, + pub root_dir_objectid: U64, + pub num_devices: U64, + pub sector_size: U32, + pub node_size: U32, /// Unused and must be equal to `nodesize` - pub leafsize: u32, - pub stripesize: u32, - pub sys_chunk_array_size: u32, - pub chunk_root_generation: u64, - pub compat_flags: u64, - pub compat_ro_flags: u64, - pub incompat_flags: u64, - pub csum_type: u16, + pub leafsize: U32, + pub stripesize: U32, + pub sys_chunk_array_size: U32, + pub chunk_root_generation: U64, + pub compat_flags: U64, + pub compat_ro_flags: U64, + pub incompat_flags: U64, + pub csum_type: U16, pub root_level: u8, pub chunk_root_level: u8, pub log_root_level: u8, pub dev_item: DevItem, #[derivative(Debug(format_with = "format_u8str"))] pub label: [u8; 0x100], - pub cache_generation: u64, - pub uuid_tree_generation: u64, + pub cache_generation: U64, + pub uuid_tree_generation: U64, pub metadata_uuid: Uuid, /// Future expansion - reserved: [u64; 28], + #[derivative(Debug = "ignore")] + _reserved: [u64; 28], #[derivative(Debug = "ignore")] pub sys_chunk_array: [u8; 0x800], - pub root_backup0: RootBackup, - pub root_backup1: RootBackup, - pub root_backup2: RootBackup, - pub root_backup3: RootBackup, + #[derivative(Debug = "ignore")] + pub root_backups: [RootBackup; 4], + #[derivative(Debug = "ignore")] + _reserved2: [u8; 565], } #[repr(u16)] @@ -516,6 +585,13 @@ pub enum ChecksumType { Blake2B, } +fn calculate_crc32c(bytes: &[u8]) -> [u8; 32] { + let crc = crc::Crc::::new(&crc::CRC_32_ISCSI); + let mut csum = [0u8; 32]; + csum[..4].copy_from_slice(crc.checksum(bytes).as_bytes()); + csum +} + impl Superblock { pub const SUPERBLOCK_BASE_OFFSET: usize = 0x10000; pub const SUPERBLOCK_OFFSETS: [usize; 4] = [ @@ -527,17 +603,37 @@ impl Superblock { pub const MAGIC: [u8; 8] = *b"_BHRfS_M"; pub fn parse(bytes: &[u8]) -> Result { - let superblock = bytes.pread_with::(0, scroll::LE)?; + let superblock = Self::read_from(bytes).ok_or(Error::ReadFailed)?; if !superblock.verify_magic() { return Err(Error::InvalidMagic); } + if !superblock.verify_checksum() { + return Err(Error::InvalidChecksum { + expected: superblock.csum, + actual: superblock.calculate_checksum(), + }); + } + Ok(superblock) } + pub fn calculate_checksum(&self) -> [u8; 32] { + match self.checksum_type().expect("csum type invalid") { + ChecksumType::Crc32 => calculate_crc32c(&self.as_bytes()[0x20..]), + ChecksumType::XxHash64 => todo!(), + ChecksumType::Sha256 => todo!(), + ChecksumType::Blake2B => todo!(), + } + } + + pub fn verify_checksum(&self) -> bool { + self.calculate_checksum() == self.csum + } + pub fn checksum_type(&self) -> Option { - ChecksumType::try_from_primitive(self.csum_type).ok() + ChecksumType::try_from_primitive(self.csum_type.get()).ok() } pub fn verify_magic(&self) -> bool { @@ -546,73 +642,67 @@ impl Superblock { } #[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +#[derive(Debug, Clone, Copy, FromBytes, AsBytes)] pub struct Stripe { - pub devid: u64, - pub offset: u64, + pub devid: U64, + pub offset: U64, pub dev_uuid: Uuid, } impl Stripe { pub fn parse(bytes: &[u8]) -> Result { - Ok(bytes.pread_with(0, scroll::LE)?) + Self::read_from(bytes).ok_or(Error::ReadFailed) } } #[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +#[derive(Debug, Clone, Copy, FromBytes, AsBytes)] pub struct Chunk { /// size of this chunk in bytes - pub length: u64, + pub length: U64, /// objectid of the root referencing this chunk - pub owner: u64, - pub stripe_len: u64, - pub ty: u64, + pub owner: U64, + pub stripe_len: U64, + pub ty: U64, /// optimal io alignment for this chunk - pub io_align: u32, + pub io_align: U32, /// optimal io width for this chunk - pub io_width: u32, + pub io_width: U32, /// minimal io size for this chunk - pub sector_size: u32, + pub sector_size: U32, /// 2^16 stripes is quite a lot, a second limit is the size of a single item in the btree - pub num_stripes: u16, + pub num_stripes: U16, /// sub stripes only matter for raid10 - pub sub_stripes: u16, + pub sub_stripes: U16, pub stripe: Stripe, // additional stripes go here } -impl Chunk { - pub fn parse(bytes: &[u8]) -> Result { - Ok(bytes.pread_with(0, scroll::LE)?) - } -} - #[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +#[derive(Debug, Clone, Copy, FromBytes, AsBytes)] pub struct Timespec { - pub sec: u64, - pub nsec: u32, + pub sec: U64, + pub nsec: U32, } #[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[derive(Debug, Clone, Copy, FromBytes, AsBytes)] pub struct InodeItem { /// nfs style generation number - pub generation: u64, + pub generation: U64, /// transid that last touched this inode - pub transid: u64, - pub size: u64, - pub nbytes: u64, - pub block_group: u64, - pub nlink: u32, - pub uid: u32, - pub gid: u32, - pub mode: u32, - pub rdev: u64, - pub flags: u64, + pub transid: U64, + pub size: U64, + pub nbytes: U64, + pub block_group: U64, + pub nlink: U32, + pub uid: U32, + pub gid: U32, + pub mode: U32, + pub rdev: U64, + pub flags: U64, /// modification sequence number for NFS - pub sequence: u64, + pub sequence: U64, pub reserved: [u64; 4], pub atime: Timespec, pub ctime: Timespec, @@ -621,32 +711,32 @@ pub struct InodeItem { } #[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[derive(Debug, Clone, Copy, FromBytes, AsBytes)] pub struct RootItem { pub inode: InodeItem, - pub generation: u64, - pub root_dirid: u64, - pub bytenr: u64, - pub byte_limit: u64, - pub bytes_used: u64, - pub last_snapshot: u64, - pub flags: u64, - pub refs: u32, + pub generation: U64, + pub root_dirid: U64, + pub bytenr: U64, + pub byte_limit: U64, + pub bytes_used: U64, + pub last_snapshot: U64, + pub flags: U64, + pub refs: U32, pub drop_progress: Key, pub drop_level: u8, pub level: u8, - pub generation_v2: u64, + pub generation_v2: U64, pub uuid: Uuid, pub parent_uuid: Uuid, pub received_uuid: Uuid, /// updated when an inode changes - pub ctransid: u64, + pub ctransid: U64, /// trans when created - pub otransid: u64, + pub otransid: U64, /// trans when sent. non-zero for received subvol - pub stransid: u64, + pub stransid: U64, /// trans when received. non-zero for received subvol - pub rtransid: u64, + pub rtransid: U64, pub ctime: Timespec, pub otime: Timespec, pub stime: Timespec, @@ -654,14 +744,95 @@ pub struct RootItem { pub reserved: [u64; 8], } +#[repr(u8)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)] +pub enum DirItemType { + Unknown, + RegFile, + Dir, + ChrDev, + BlkDev, + Fifo, + Sock, + Symlink, + Xattr, + #[num_enum(catch_all)] + Invalid(u8), +} + #[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[derive(Debug, Clone, Copy, FromBytes, AsBytes)] pub struct DirItem { pub location: Key, - pub transid: u64, - pub data_len: u16, - pub name_len: u16, - pub ty: u8, + pub transid: U64, + pub data_len: U16, + pub name_len: U16, + ty: u8, +} + +impl DirItem { + pub fn ty(&self) -> DirItemType { + DirItemType::from_primitive(self.ty) + } + + pub fn parse_single(bytes: &[u8]) -> Result<(DirItemEntry)> { + let offset = &mut 0; + Self::parse_single_inner(bytes, offset) + } + + fn parse_single_inner(bytes: &[u8], offset: &mut usize) -> Result { + let dir_item = DirItem::read_from(&bytes[*offset..*offset + size_of::()]) + .ok_or(Error::ReadFailed)?; + *offset += size_of::(); + let name_len = dir_item.name_len.get() as usize; + let name = &bytes[*offset..*offset + name_len]; + *offset += name_len; + + Ok(DirItemEntry::new(dir_item, name.to_vec())) + } + + pub fn parse(bytes: &[u8]) -> Result> { + let offset = &mut 0; + let entries = core::iter::from_fn(|| { + if *offset + size_of::() < bytes.len() { + Some(Self::parse_single_inner(&bytes[*offset..], offset)) + } else { + None + } + }) + .collect::>>()?; + + Ok(entries) + } +} + +#[derive(Clone)] +pub struct DirItemEntry { + pub dir_item: DirItem, + pub name: Vec, +} + +impl DirItemEntry { + pub fn new(dir_item: DirItem, name: Vec) -> Self { + Self { dir_item, name } + } + + pub fn name_as_str(&self) -> core::result::Result<&str, core::str::Utf8Error> { + core::str::from_utf8(&self.name) + } + pub fn name_as_string_lossy(&self) -> alloc::borrow::Cow { + alloc::string::String::from_utf8_lossy(&self.name) + } +} + +impl core::fmt::Debug for DirItemEntry { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("DirItemEntry") + .field("dir_item", &self.dir_item) + .field("name", &self.name_as_string_lossy()) + .finish() + } } #[repr(C, packed)] @@ -672,101 +843,179 @@ pub struct InodeRef { } #[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, FromBytes, AsBytes)] pub struct Header { pub csum: [u8; 32], pub fsid: Uuid, /// Which block this node is supposed to live in - pub bytenr: u64, - pub flags: u64, + pub bytenr: U64, + pub flags: U64, pub chunk_tree_uuid: Uuid, - pub generation: u64, - pub owner: u64, - pub nritems: u32, + pub generation: U64, + pub owner: U64, + pub nritems: U32, pub level: u8, } #[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +#[derive(Debug, Clone, Copy, FromBytes, AsBytes)] /// A `BtrfsLeaf` is full of `BtrfsItem`s. `offset` and `size` (relative to start of data area) /// tell us where to find the item in the leaf. pub struct Item { pub key: Key, - pub offset: u32, - pub size: u32, + pub offset: U32, + pub size: U32, } #[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -pub struct Leaf { - pub header: Header, - // `Item`s begin here -} - -#[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +#[derive(Debug, Clone, Copy, FromBytes, AsBytes)] /// All non-leaf blocks are nodes and they hold only keys are pointers to other blocks pub struct KeyPtr { pub key: Key, - pub blockptr: u64, - pub generation: u64, -} - -#[repr(C, packed)] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -pub struct Node { - pub header: Header, - // `KeyPtr`s begin here + pub blockptr: U64, + pub generation: U64, } use alloc::vec::Vec; #[derive(Debug)] +pub enum TreeItem { + Chunk(Chunk), + Root(RootItem), + DirItem(Vec), + DirIndex(DirItemEntry), + Unimplemented, +} + +impl From for TreeItem { + fn from(value: Chunk) -> Self { + Self::Chunk(value) + } +} + +impl From for TreeItem { + fn from(value: RootItem) -> Self { + Self::Root(value) + } +} + +impl From> for TreeItem { + fn from(value: Vec) -> Self { + Self::DirItem(value) + } +} + +impl From for TreeItem { + fn from(value: DirItemEntry) -> Self { + Self::DirIndex(value) + } +} + +impl TreeItem { + pub fn parse(item: &Item, bytes: &[u8]) -> Result { + Ok(match item.key.ty() { + ObjectType::RootItem => RootItem::parse(bytes)?.into(), + ObjectType::ChunkItem => Chunk::parse(bytes)?.into(), + ObjectType::DirItem => DirItem::parse(bytes)?.into(), + ObjectType::DirIndex => DirItem::parse_single(bytes)?.into(), + _ => TreeItem::Unimplemented, + }) + } +} + +impl TreeItem { + pub fn as_chunk(&self) -> Option<&Chunk> { + if let Self::Chunk(v) = self { + Some(v) + } else { + None + } + } + + pub fn as_root(&self) -> Option<&RootItem> { + if let Self::Root(v) = self { + Some(v) + } else { + None + } + } +} + +#[derive(Debug, Clone)] pub struct BTreeLeafNode { - header: Header, + pub header: Header, /// actual leaf data - items: Vec, + pub items: Vec, } impl BTreeLeafNode { pub fn parse(header: Header, bytes: &[u8]) -> Result { + log::debug!("leaf:"); + let offset = &mut 0; let items = core::iter::from_fn(|| { if *offset as usize + size_of::() < bytes.len() { - Some(bytes.gread_with::(offset, scroll::LE)) + let item = Item::read_from(&bytes[*offset..*offset + size_of::()]); + *offset += size_of::(); + + if let Some(item) = item.as_ref() { + log::debug!( + "\titem type {:?}: ty offset: {}", + item.key.ty.as_type(), + item.key.offset.get() + ); + log::debug!("\t{item:?}"); + } + + item } else { None } }) - .take(header.nritems as usize) - .collect::, _>>()?; + .take(header.nritems.get() as usize) + .collect::>(); Ok(Self { header, items }) } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct BTreeInternalNode { - header: Header, - children: Vec, + pub header: Header, + pub children: Vec, } impl BTreeInternalNode { pub fn parse(header: Header, bytes: &[u8]) -> Result { + log::debug!("internal lvl: {}", header.level); + let offset = &mut 0; + let size = size_of::(); let children = core::iter::from_fn(|| { - if *offset as usize + size_of::() < bytes.len() { - Some(bytes.gread_with::(offset, scroll::LE)) + if *offset as usize + size < bytes.len() { + let item = KeyPtr::read_from(&bytes[*offset..*offset + size]); + *offset += size; + + if let Some(item) = item.as_ref() { + log::debug!( + "\tchild gen: {} offset: {}", + item.generation.get(), + item.key.offset.get() + ); + } + + item } else { None } }) - .take(header.nritems as usize) - .collect::, _>>()?; + .take(header.nritems.get() as usize) + .collect::>(); + Ok(Self { header, children }) } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum BTreeNode { Internal(BTreeInternalNode), Leaf(BTreeLeafNode), @@ -775,7 +1024,7 @@ pub enum BTreeNode { impl BTreeNode { pub fn parse(bytes: &[u8]) -> Result { let offset = &mut 0; - let header = bytes.gread_with::
(offset, scroll::LE)?; + let header = bytes.gread::
(offset)?; if header.level == 0 { Ok(Self::Leaf(BTreeLeafNode::parse(header, &bytes[*offset..])?)) diff --git a/btrfs/src/tree.rs b/btrfs/src/tree.rs new file mode 100644 index 0000000..e6e0c82 --- /dev/null +++ b/btrfs/src/tree.rs @@ -0,0 +1,480 @@ +use core::{mem::size_of, ops::Deref}; + +use crate::{ + structs::{Chunk, Header, Item, KeyPtr, RootItem, TreeItem}, + Error, Result, Volume, VolumeIo, +}; +use alloc::{ + borrow::Cow, + collections::VecDeque, + rc::{self, Rc}, + vec::Vec, +}; +use scroll::Pread; +use zerocopy::FromBytes; + +#[derive(Debug, Clone)] +pub struct BTreeLeafNode { + pub header: Header, + /// actual leaf data + pub items: Vec, +} + +impl BTreeLeafNode { + pub fn parse(header: Header, bytes: &[u8]) -> Result { + log::debug!("leaf:"); + + let offset = &mut 0; + let items = core::iter::from_fn(|| { + if *offset as usize + size_of::() < bytes.len() { + let item = Item::read_from(&bytes[*offset..*offset + size_of::()]); + *offset += size_of::(); + + if let Some(item) = item.as_ref() { + log::debug!("\t{item:?}"); + } + + item + } else { + None + } + }) + .take(header.nritems.get() as usize) + .collect::>(); + Ok(Self { header, items }) + } +} + +#[derive(Debug, Clone)] +pub struct BTreeInternalNode { + pub header: Header, + pub children: Vec, +} + +impl PartialEq for BTreeInternalNode { + fn eq(&self, other: &Self) -> bool { + self.header == other.header + } +} + +impl PartialEq for BTreeLeafNode { + fn eq(&self, other: &Self) -> bool { + self.header == other.header + } +} + +impl Eq for BTreeLeafNode {} +impl Eq for BTreeInternalNode {} + +impl BTreeInternalNode { + pub fn parse(header: Header, bytes: &[u8]) -> Result { + log::debug!("internal lvl: {}", header.level); + + let offset = &mut 0; + let size = size_of::(); + let children = core::iter::from_fn(|| { + if *offset as usize + size < bytes.len() { + let item = KeyPtr::read_from(&bytes[*offset..*offset + size]); + *offset += size; + + if let Some(item) = item.as_ref() { + log::debug!( + "\tchild gen: {} offset: {}", + item.generation.get(), + item.key.offset.get() + ); + } + + item + } else { + None + } + }) + .take(header.nritems.get() as usize) + .collect::>(); + + Ok(Self { header, children }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BTreeNode { + Internal(BTreeInternalNode), + Leaf(BTreeLeafNode), +} + +impl BTreeNode { + pub fn parse(bytes: &[u8]) -> Result { + let offset = &mut 0; + let header = bytes.gread::
(offset)?; + + if header.level == 0 { + Ok(Self::Leaf(BTreeLeafNode::parse(header, &bytes[*offset..])?)) + } else { + Ok(Self::Internal(BTreeInternalNode::parse( + header, + &bytes[*offset..], + )?)) + } + } + + pub fn header(&self) -> &Header { + match self { + BTreeNode::Internal(node) => &node.header, + BTreeNode::Leaf(node) => &node.header, + } + } + + /// Returns `true` if the btree node is [`Internal`]. + /// + /// [`Internal`]: BTreeNode::Internal + #[must_use] + pub fn is_internal(&self) -> bool { + matches!(self, Self::Internal(..)) + } + + /// Returns `true` if the btree node is [`Leaf`]. + /// + /// [`Leaf`]: BTreeNode::Leaf + #[must_use] + pub fn is_leaf(&self) -> bool { + matches!(self, Self::Leaf(..)) + } + + pub fn as_internal(&self) -> Option<&BTreeInternalNode> { + if let Self::Internal(v) = self { + Some(v) + } else { + None + } + } + + pub fn as_leaf(&self) -> Option<&BTreeLeafNode> { + if let Self::Leaf(v) = self { + Some(v) + } else { + None + } + } +} + +#[derive(Debug)] +pub struct Tree<'a, R: VolumeIo> { + // volume for reader and euperblock + volume: &'a Volume, + // offset of the root node, + root: BoxedNode<'a>, +} + +#[derive(Debug)] +pub struct Node<'a> { + inner: BTreeNode, + bytes: Cow<'a, [u8]>, +} + +impl<'a> Node<'a> { + pub fn read_nth_item(&self, i: usize) -> Result> { + match &self.inner { + BTreeNode::Internal(_) => Ok(None), + BTreeNode::Leaf(leaf) => { + // TODO: better error to indicate that i was out of bounds + let item = leaf.items.get(i).ok_or(Error::ReadFailed)?; + let start = size_of::
() + item.offset.get() as usize; + let size = item.size.get() as usize; + let bytes = &self.bytes[start..start + size]; + + let value = TreeItem::parse(item, bytes)?; + + Ok(Some((*item, value))) + } + } + } +} + +impl<'a> PartialEq for Node<'a> { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl<'a> Eq for Node<'a> {} + +impl<'a> Node<'a> { + pub fn from_bytes(bytes: B) -> Result + where + Cow<'a, [u8]>: From, + { + let bytes = Cow::from(bytes); + + let inner = BTreeNode::parse(&bytes)?; + + Ok(Self { inner, bytes }) + } +} + +impl<'a> Deref for Node<'a> { + type Target = BTreeNode; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +type BoxedNode<'a> = Rc>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NodeHandle<'a> { + node: BoxedNode<'a>, + idx: u32, +} + +pub enum NodeHandleAdvanceResult<'a> { + Decend { + parent: NodeHandle<'a>, + child_ptr: KeyPtr, + }, + Next(NodeHandle<'a>), + Ascend, +} + +impl<'a> NodeHandle<'a> { + pub fn start(node: BoxedNode<'a>) -> Self { + Self { node, idx: 0 } + } + + pub fn parse_item(&self) -> Result> { + self.node.read_nth_item(self.idx as usize) + } + + pub fn advance_sideways(self) -> NodeHandleAdvanceResult<'a> { + let header = self.node.inner.header(); + if header.nritems.get() >= self.idx + 1 { + NodeHandleAdvanceResult::Ascend + } else { + match &self.node.inner { + BTreeNode::Internal(_) => NodeHandleAdvanceResult::Next(Self { + idx: self.idx + 1, + ..self + }), + _ => unreachable!(), + } + } + } + + // returns the next node in ascending sequential order + pub fn advance_down(self) -> NodeHandleAdvanceResult<'a> { + let header = self.node.inner.header(); + if self.idx + 1 >= header.nritems.get() { + NodeHandleAdvanceResult::Ascend + } else { + match &self.node.inner { + BTreeNode::Internal(node) => NodeHandleAdvanceResult::Decend { + parent: self.clone(), + child_ptr: *node.children.first().expect("no children in node"), + }, + BTreeNode::Leaf(_) => NodeHandleAdvanceResult::Next(Self { + idx: self.idx + 1, + ..self + }), + } + } + } + + pub fn end(node: BoxedNode<'a>) -> Self { + Self { + idx: node.inner.header().nritems.get() - 1, + node, + } + } +} + +#[derive(Debug, Clone, Eq)] +enum RootOrEdge<'a> { + Root(NodeHandle<'a>), + Edge(NodeHandle<'a>), +} + +impl<'a> RootOrEdge<'a> { + pub fn into_handle(&self) -> NodeHandle<'a> { + match self { + RootOrEdge::Root(handle) => handle, + RootOrEdge::Edge(handle) => handle, + } + .clone() + } + + pub fn as_handle(&self) -> &NodeHandle<'a> { + match self { + RootOrEdge::Root(handle) => handle, + RootOrEdge::Edge(handle) => handle, + } + } +} + +impl<'a> Deref for RootOrEdge<'a> { + type Target = NodeHandle<'a>; + + fn deref(&self) -> &Self::Target { + match self { + RootOrEdge::Root(node) => node, + RootOrEdge::Edge(node) => node, + } + } +} + +impl<'a> PartialEq for RootOrEdge<'a> { + fn eq(&self, other: &Self) -> bool { + Deref::deref(self).eq(Deref::deref(other)) + } +} + +#[derive(Debug)] +pub struct Range<'a, R: VolumeIo> { + volume: &'a Volume, + parents: Vec>, + start: RootOrEdge<'a>, + end: RootOrEdge<'a>, +} + +impl<'a, R: VolumeIo> Iterator for Range<'a, R> { + type Item = (Item, TreeItem); + + fn next(&mut self) -> Option { + let handle = match &self.start { + RootOrEdge::Root(_) => { + self.init_start().expect("error"); + match &self.start.node.inner { + BTreeNode::Internal(_) => None, + BTreeNode::Leaf(leaf) => Some(self.start.into_handle()), + } + } + RootOrEdge::Edge(_) => self + .advance() + .expect("cant advance range") + .map(|_| self.start.into_handle()), + }; + + handle + .map(|handle| handle.parse_item())? + .expect("failed to parse item") + } +} + +impl<'a, R: VolumeIo> Range<'a, R> { + pub fn new(volume: &'a Volume, start: Rc>, end: Rc>) -> Self { + Self { + volume, + parents: Default::default(), + start: RootOrEdge::Root(NodeHandle::start(start)), + end: RootOrEdge::Root(NodeHandle::end(end)), + } + } + + pub fn is_empty(&self) -> bool { + return self.start == self.end; + } + + pub fn init_start(&mut self) -> Result<()> { + let start = match &self.start { + RootOrEdge::Root(root) => { + match root.node.inner { + BTreeNode::Internal(_) => { + // descend until leaf + let mut advance = root.clone().advance_down(); + loop { + match advance { + NodeHandleAdvanceResult::Decend { parent, child_ptr } => { + let bytes = self.volume.read_keyptr(child_ptr)?; + let child = + NodeHandle::start(Rc::new(Node::from_bytes(bytes)?)); + self.parents.push(parent); + + match &child.node.inner { + BTreeNode::Internal(_) => { + // continue loop + advance = child.advance_down(); + } + BTreeNode::Leaf(_) => { + break child; + } + } + } + NodeHandleAdvanceResult::Next(handle) => { + break handle; + } + NodeHandleAdvanceResult::Ascend => unreachable!(), + } + } + } + BTreeNode::Leaf(_) => root.clone(), + } + } + RootOrEdge::Edge(edge) => edge.clone(), + }; + + self.start = RootOrEdge::Edge(start); + + Ok(()) + } + + pub fn advance(&mut self) -> Result> { + if !self.is_empty() { + let start = self.start.into_handle(); + let mut advance = start.advance_down(); + loop { + match advance { + NodeHandleAdvanceResult::Decend { parent, child_ptr } => { + let bytes = self.volume.read_keyptr(child_ptr)?; + let child = NodeHandle::start(Rc::new(Node::from_bytes(bytes)?)); + self.parents.push(parent); + self.start = RootOrEdge::Edge(child); + + match &self.start.node.inner { + BTreeNode::Internal(_) => { + return self.advance(); + } + BTreeNode::Leaf(_) => { + break; + } + } + } + NodeHandleAdvanceResult::Next(next) => { + self.start = RootOrEdge::Edge(next); + break; + } + NodeHandleAdvanceResult::Ascend => { + if let Some(last) = self.parents.pop() { + advance = NodeHandle { + idx: last.idx + 1, + ..last + } + .advance_down(); + } else { + return Ok(None); + } + } + } + } + Ok(Some(())) + } else { + Ok(None) + } + } +} + +impl<'a, R: VolumeIo> Tree<'a, R> { + pub fn from_logical_offset(volume: &'a Volume, logical: u64) -> Result { + let physical = volume + .range_from_logical(logical) + .ok_or(Error::InvalidOffset)?; + + let bytes = volume.read_range(physical)?; + let root = Rc::new(Node::from_bytes(bytes)?); + + Ok(Self { volume, root }) + } + + pub fn full_range(&self) -> Range<'a, R> { + Range::new(self.volume, self.root.clone(), self.root.clone()) + } +} diff --git a/btrfs/tests/read_superblock.rs b/btrfs/tests/read_superblock.rs index f2b46ec..dfb5531 100644 --- a/btrfs/tests/read_superblock.rs +++ b/btrfs/tests/read_superblock.rs @@ -6,7 +6,7 @@ use std::{ use test_log::test; -use btrfs::{structs::*, Volume}; +use btrfs::{structs::*, tree::Tree, Volume}; #[test] fn superblock() { @@ -17,6 +17,9 @@ fn superblock() { let sb = volume.superblock(); println!("{sb:#?}"); + + assert!(sb.verify_magic()); + assert!(sb.verify_checksum()); } #[test] @@ -40,5 +43,7 @@ fn iter_root() { let mut volume = Volume::new(reader).expect("volume"); let sb = volume.superblock(); - volume.asdf(); + volume.iter_roots(); + + println!("{:#?}", volume.chunk_cache); }