btrfs learning tool

This commit is contained in:
Janis 2023-03-23 22:30:30 +01:00
parent 47710fe4fa
commit f1d968fbf5
4 changed files with 1106 additions and 253 deletions

View file

@ -1,9 +1,16 @@
#![feature(error_in_core)] #![feature(error_in_core)]
#![cfg_attr(not(any(feature = "std", test)), no_std)] #![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::{ use alloc::{
borrow::Cow,
boxed::Box,
collections::{btree_map::Entry, BTreeMap}, collections::{btree_map::Entry, BTreeMap},
vec, vec,
vec::Vec, vec::Vec,
@ -11,11 +18,16 @@ use alloc::{
use scroll::{Endian, Pread}; use scroll::{Endian, Pread};
use thiserror::Error; 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; extern crate alloc;
pub mod structs; pub mod structs;
pub mod tree;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod std_io { pub mod std_io {
@ -50,6 +62,11 @@ pub enum Error {
ExpectedInternalNode, ExpectedInternalNode,
#[error("Expected a leaf node")] #[error("Expected a leaf node")]
ExpectedLeafNode, ExpectedLeafNode,
#[error("Invalid checksum: expected {expected:#?} but got {actual:#?}")]
InvalidChecksum {
expected: [u8; 32],
actual: [u8; 32],
},
#[error("{0}")] #[error("{0}")]
ScrollError(scroll::Error), ScrollError(scroll::Error),
} }
@ -110,8 +127,7 @@ impl Ord for ChunkTreeKey {
impl PartialEq for ChunkTreeKey { impl PartialEq for ChunkTreeKey {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
(self.range.contains(&other.range.start) || self.range.contains(&other.range.end)) self.range.contains(&other.range.start) || other.range.contains(&self.range.start)
|| (other.range.contains(&self.range.start) || other.range.contains(&self.range.end))
} }
} }
@ -129,23 +145,34 @@ pub struct ChunkCacheTree {
type ChunkTree = BTreeMap<ChunkTreeKey, u64>; type ChunkTree = BTreeMap<ChunkTreeKey, u64>;
#[derive(Debug)]
pub struct Volume<R: VolumeIo> { pub struct Volume<R: VolumeIo> {
reader: R, reader: Box<RefCell<R>>,
superblock: Superblock, superblock: Superblock,
chunk_cache: ChunkTree, pub chunk_cache: ChunkTree,
roots: BTreeMap<KnownObjectId, RootItem>,
} }
impl<R: VolumeIo> Volume<R> { impl<R: VolumeIo> Volume<R> {
pub fn reader(&self) -> RefMut<R> {
self.reader.borrow_mut()
}
pub fn new(mut reader: R) -> Result<Self> { pub fn new(mut reader: R) -> Result<Self> {
let mut buf = vec![0; size_of::<Superblock>()]; let mut buf = vec![0; size_of::<Superblock>()];
reader.read(&mut buf, Superblock::SUPERBLOCK_BASE_OFFSET as _)?; reader.read(&mut buf, Superblock::SUPERBLOCK_BASE_OFFSET as _)?;
let superblock = Superblock::parse(&buf)?; let superblock = Superblock::parse(&buf)?;
let chunk_cache = Self::bootstrap_chunk_tree(&superblock)?; let chunk_cache = Self::bootstrap_chunk_tree(&superblock)?;
Ok(Self { let mut new = Self {
reader, reader: Box::new(RefCell::new(reader)),
superblock, superblock,
chunk_cache, chunk_cache,
}) roots: Default::default(),
};
new.parse_chunk_tree()?;
Ok(new)
} }
pub fn size_from_logical(&self, logical: u64) -> Option<u64> { pub fn size_from_logical(&self, logical: u64) -> Option<u64> {
@ -169,15 +196,86 @@ impl<R: VolumeIo> Volume<R> {
}) })
} }
pub fn read_range(&mut self, range: core::ops::Range<u64>) -> Result<Vec<u8>> { pub fn read_range(&self, range: core::ops::Range<u64>) -> Result<Vec<u8>> {
let mut buf = vec![0; (range.end - range.start) as usize]; 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) Ok(buf)
} }
pub fn asdf(&mut self) -> Result<()> { fn parse_chunk_node(&mut self, chunk: Vec<u8>) -> Result<()> {
let chunk_root = self.superblock.chunk_root; let node = BTreeNode::parse(&chunk)?;
let bytes = &chunk[size_of::<Header>()..];
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::<Result<()>>()?;
()
}
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<Vec<u8>> {
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"); // let size = self.size_from_logical(chunk_root).expect("size");
log::debug!("chunk_root: {chunk_root}"); log::debug!("chunk_root: {chunk_root}");
@ -187,15 +285,12 @@ impl<R: VolumeIo> Volume<R> {
let root = self.read_range(physical)?; let root = self.read_range(physical)?;
let node = BTreeNode::parse(&root)?; self.parse_chunk_node(root)?;
log::debug!("{node:#?}");
Ok(()) Ok(())
} }
pub fn bootstrap_chunk_tree(superblock: &Superblock) -> Result<ChunkTree> { pub fn bootstrap_chunk_tree(superblock: &Superblock) -> Result<ChunkTree> {
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 mut offset: usize = 0;
let key_size = size_of::<Key>(); let key_size = size_of::<Key>();
@ -209,19 +304,20 @@ impl<R: VolumeIo> Volume<R> {
return Err(Error::InvalidOffset); return Err(Error::InvalidOffset);
} }
let key = bytes.gread_with::<Key>(&mut offset, scroll::LE)?; let key = bytes.gread::<Key>(&mut offset)?;
if key.ty.as_type() != Some(ObjectType::ChunkItem) { if key.ty.as_type() != ObjectType::ChunkItem {
log::error!("key is not of type ChunkItem"); log::error!("key is not of type ChunkItem");
return Err(Error::InvalidOffset); return Err(Error::InvalidOffset);
} }
let chunk = bytes.gread_with::<Chunk>(&mut offset, scroll::LE)?; let chunk = bytes.gread::<Chunk>(&mut offset)?;
if chunk.num_stripes == 0 { let num_stripes = chunk.num_stripes.get(); // copy to prevent unaligned access
if num_stripes == 0 {
log::error!("num_stripes cannot be 0"); log::error!("num_stripes cannot be 0");
return Err(Error::InvalidOffset); return Err(Error::InvalidOffset);
} }
let num_stripes = chunk.num_stripes; // copy to prevent unaligned access
if num_stripes != 1 { if num_stripes != 1 {
log::warn!( log::warn!(
"warning: {} stripes detected but only processing 1", "warning: {} stripes detected but only processing 1",
@ -229,11 +325,14 @@ impl<R: VolumeIo> Volume<R> {
); );
} }
let key_offset = key.offset.get();
let chunk_length = chunk.length.get();
match chunk_tree.entry(ChunkTreeKey { match chunk_tree.entry(ChunkTreeKey {
range: key.offset..(key.offset + chunk.length), range: key_offset..(key_offset + chunk_length),
}) { }) {
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
entry.insert(chunk.stripe.offset); entry.insert(chunk.stripe.offset.get());
} }
Entry::Occupied(_) => { Entry::Occupied(_) => {
log::error!("overlapping stripes!"); log::error!("overlapping stripes!");
@ -251,6 +350,26 @@ impl<R: VolumeIo> Volume<R> {
Ok(chunk_tree) 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 { pub fn superblock(&self) -> &Superblock {
&self.superblock &self.superblock
} }

View file

@ -3,17 +3,59 @@ use core::{mem::size_of, ops::Deref};
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use derivative::Derivative; use derivative::Derivative;
use num_enum::{FromPrimitive, TryFromPrimitive}; use num_enum::{FromPrimitive, TryFromPrimitive};
use scroll::{ use scroll::{ctx::TryFromCtx, Pread, SizeWith};
ctx::{SizeWith, TryFromCtx}, use zerocopy::{byteorder::LE, AsBytes, FromBytes, U16, U32, U64};
Pread, SizeWith,
};
use zerocopy::{AsBytes, FromBytes};
use crate::{Error, Result}; 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<LE>,
}
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)] #[repr(u8)]
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive)]
//#[rustc_nonnull_optimization_guaranteed] //#[rustc_nonnull_optimization_guaranteed]
pub enum ObjectType { pub enum ObjectType {
INodeItem = 0x01, INodeItem = 0x01,
@ -46,25 +88,25 @@ pub enum ObjectType {
DevStats = 0xF9, DevStats = 0xF9,
SubvolUuid = 0xFB, SubvolUuid = 0xFB,
SubvolRecUuid = 0xFC, SubvolRecUuid = 0xFC,
#[num_enum(catch_all)]
Invalid(u8),
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Clone, Copy, PartialEq, Eq, Pread, SizeWith)] #[derive(Clone, Copy, PartialEq, Eq, Pread, SizeWith, FromBytes, AsBytes)]
pub struct ObjectTypeWrapper { pub struct ObjectTypeWrapper {
inner: u8, inner: u8,
} }
impl core::fmt::Debug for ObjectTypeWrapper { impl core::fmt::Debug for ObjectTypeWrapper {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ObjectTypeWrapper") write!(f, "{:?}", self.as_type())
.field("inner", &self.as_type())
.finish()
} }
} }
impl ObjectTypeWrapper { impl ObjectTypeWrapper {
pub fn as_type(self) -> Option<ObjectType> { pub fn as_type(self) -> ObjectType {
ObjectType::try_from_primitive(self.inner).ok() ObjectType::from_primitive(self.inner)
} }
} }
@ -72,24 +114,12 @@ unsafe impl Pod for ObjectTypeWrapper {}
unsafe impl Zeroable for ObjectTypeWrapper {} unsafe impl Zeroable for ObjectTypeWrapper {}
#[repr(transparent)] #[repr(transparent)]
#[derive(Debug, Clone, Copy)] #[derive(Clone, Copy, PartialEq, Eq, FromBytes, AsBytes)]
pub struct Uuid(uuid::Uuid); pub struct Uuid(uuid::Bytes);
impl SizeWith<scroll::Endian> for Uuid { impl core::fmt::Debug for Uuid {
fn size_with(_: &scroll::Endian) -> usize { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
size_of::<Self>() uuid::Uuid::from_bytes_ref(&self.0).fmt(f)
}
}
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::<Self>();
Ok((*bytemuck::from_bytes::<Self>(&from[..size]), size))
} }
} }
@ -97,7 +127,7 @@ impl Deref for Uuid {
type Target = uuid::Uuid; type Target = uuid::Uuid;
fn deref(&self) -> &Self::Target { 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 {} unsafe impl Zeroable for Uuid {}
#[repr(C, packed(1))] #[repr(C, packed(1))]
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] #[derive(Debug, Clone, Copy, Eq, FromBytes, AsBytes)]
pub struct Key { pub struct Key {
pub id: u64, pub id: ObjectId,
pub ty: ObjectTypeWrapper, pub ty: ObjectTypeWrapper,
pub offset: u64, pub offset: U64<LE>,
} }
#[repr(C, packed(1))] impl Key {
#[derive(Debug, Clone, Copy, Pod, Zeroable)] pub fn ty(&self) -> ObjectType {
pub struct TreeHeader { self.ty.as_type()
csum: [u8; 32], }
fs_uuid: Uuid, pub fn id(&self) -> KnownObjectId {
address: u64, self.id.as_id()
flags: u64, }
chunk_tree_uuid: Uuid,
generation: u64,
tree_id: u64,
num_items: u32,
level: u8,
} }
#[repr(C, packed(1))] impl PartialEq for Key {
#[derive(Debug, Clone, Copy, Pod, Zeroable)] fn eq(&self, other: &Self) -> bool {
pub struct LeafNode { self.id() == other.id() && self.ty() == other.ty() && self.offset == other.offset
key: Key, }
offset: u32,
size: u32,
} }
#[repr(C, packed(1))] impl Ord for Key {
#[derive(Debug, Clone, Copy, Pod, Zeroable)] fn cmp(&self, other: &Self) -> core::cmp::Ordering {
pub struct InternalNode { self.partial_cmp(other).unwrap()
key: Key, }
address: u64,
generation: u64,
} }
impl PartialOrd for Key {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
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::<Self>()])
.map(|v| (v, size_of::<Self>()))
.ok_or(scroll::Error::TooBig {
size: size_of::<Self>(),
len: from.len(),
})
}
})*
};
}
macro_rules! impl_parse_try_from_ctx {
($($ty:ty),*) => {
$(impl $ty {
pub fn parse(bytes: &[u8]) -> Result<Self> {
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 MAX_LABEL_SIZE: usize = 0x100;
const SYS_CHUNK_ARRAY_SIZE: usize = 0x800; const SYS_CHUNK_ARRAY_SIZE: usize = 0x800;
const BTRFS_NUM_BACKUP_ROOTS: usize = 4; const BTRFS_NUM_BACKUP_ROOTS: usize = 4;
@ -159,21 +227,21 @@ fn format_u8str<T: AsRef<[u8]>>(s: &T, f: &mut core::fmt::Formatter) -> core::fm
} }
#[repr(C, packed(1))] #[repr(C, packed(1))]
#[derive(Derivative, Clone, Copy, Pod, Zeroable)] #[derive(Derivative, Clone, Copy, FromBytes, AsBytes)]
#[derivative(Debug)] #[derivative(Debug)]
pub struct INodeItem { pub struct INodeItem {
generation: u64, generation: U64<LE>,
transid: u64, transid: U64<LE>,
st_size: u64, st_size: U64<LE>,
st_blocks: u64, st_blocks: U64<LE>,
block_group: u64, block_group: U64<LE>,
st_nlink: u32, st_nlink: U32<LE>,
st_uid: u32, st_uid: U32<LE>,
st_gid: u32, st_gid: U32<LE>,
st_mode: u32, st_mode: U32<LE>,
st_rdev: u64, st_rdev: U64<LE>,
flags: u64, flags: U64<LE>,
sequence: u64, sequence: U64<LE>,
#[derivative(Debug = "ignore")] #[derivative(Debug = "ignore")]
reserved: [u8; 32], reserved: [u8; 32],
st_atime: Timespec, st_atime: Timespec,
@ -251,7 +319,7 @@ pub struct ExtentItem {
} }
#[repr(C, packed(1))] #[repr(C, packed(1))]
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy)]
pub struct ExtentItem2 { pub struct ExtentItem2 {
first_item: Key, first_item: Key,
level: u8, level: u8,
@ -264,7 +332,7 @@ pub struct ExtentItemV0 {
} }
#[repr(C, packed(1))] #[repr(C, packed(1))]
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy)]
pub struct ExtentItemTree { pub struct ExtentItemTree {
extent_item: ExtentItem, extent_item: ExtentItem,
first_item: Key, first_item: Key,
@ -325,7 +393,7 @@ pub struct FreeSpaceEntry {
} }
#[repr(C, packed(1))] #[repr(C, packed(1))]
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy)]
pub struct FreeSpaceItem { pub struct FreeSpaceItem {
key: Key, key: Key,
@ -392,28 +460,28 @@ pub const CHUNK_ITEM_KEY: u8 = 228;
pub const FT_REG_FILE: u8 = 1; pub const FT_REG_FILE: u8 = 1;
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] #[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct DevItem { pub struct DevItem {
/// the internal btrfs device id /// the internal btrfs device id
pub devid: u64, pub devid: U64<LE>,
/// size of the device /// size of the device
pub total_bytes: u64, pub total_bytes: U64<LE>,
/// bytes used /// bytes used
pub bytes_used: u64, pub bytes_used: U64<LE>,
/// optimal io alignment for this device /// optimal io alignment for this device
pub io_align: u32, pub io_align: U32<LE>,
/// optimal io width for this device /// optimal io width for this device
pub io_width: u32, pub io_width: U32<LE>,
/// minimal io size for this device /// minimal io size for this device
pub sector_size: u32, pub sector_size: U32<LE>,
/// type and info about this device /// type and info about this device
pub ty: u64, pub ty: U64<LE>,
/// expected generation for this device /// expected generation for this device
pub generation: u64, pub generation: U64<LE>,
/// starting byte of this partition on the device, to allow for stripe alignment in the future /// starting byte of this partition on the device, to allow for stripe alignment in the future
pub start_offset: u64, pub start_offset: U64<LE>,
/// grouping information for allocation decisions /// grouping information for allocation decisions
pub dev_group: u32, pub dev_group: U32<LE>,
/// seek speed 0-100 where 100 is fastest /// seek speed 0-100 where 100 is fastest
pub seek_speed: u8, pub seek_speed: u8,
/// bandwidth 0-100 where 100 is fastest /// bandwidth 0-100 where 100 is fastest
@ -425,23 +493,23 @@ pub struct DevItem {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] #[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct RootBackup { pub struct RootBackup {
pub tree_root: u64, pub tree_root: U64<LE>,
pub tree_root_gen: u64, pub tree_root_gen: U64<LE>,
pub chunk_root: u64, pub chunk_root: U64<LE>,
pub chunk_root_gen: u64, pub chunk_root_gen: U64<LE>,
pub extent_root: u64, pub extent_root: U64<LE>,
pub extent_root_gen: u64, pub extent_root_gen: U64<LE>,
pub fs_root: u64, pub fs_root: U64<LE>,
pub fs_root_gen: u64, pub fs_root_gen: U64<LE>,
pub dev_root: u64, pub dev_root: U64<LE>,
pub dev_root_gen: u64, pub dev_root_gen: U64<LE>,
pub csum_root: u64, pub csum_root: U64<LE>,
pub csum_root_gen: u64, pub csum_root_gen: U64<LE>,
pub total_bytes: u64, pub total_bytes: U64<LE>,
pub bytes_used: u64, pub bytes_used: U64<LE>,
pub num_devices: u64, pub num_devices: U64<LE>,
/// future /// future
pub unused_64: [u64; 4], pub unused_64: [u64; 4],
pub tree_root_level: u8, pub tree_root_level: u8,
@ -455,55 +523,56 @@ pub struct RootBackup {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Derivative, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] #[derive(Derivative, Clone, Copy, FromBytes, AsBytes)]
#[derivative(Debug)] #[derivative(Debug)]
pub struct Superblock { pub struct Superblock {
pub csum: [u8; 32], pub csum: [u8; 32],
pub fsid: Uuid, pub fsid: Uuid,
/// Physical address of this block /// Physical address of this block
pub bytenr: u64, pub bytenr: U64<LE>,
pub flags: u64, pub flags: U64<LE>,
pub magic: [u8; 0x8], pub magic: [u8; 0x8],
pub generation: u64, pub generation: U64<LE>,
/// Logical address of the root tree root /// Logical address of the root tree root
pub root: u64, pub root: U64<LE>,
/// Logical address of the chunk tree root /// Logical address of the chunk tree root
pub chunk_root: u64, pub chunk_root: U64<LE>,
/// Logical address of the log tree root /// Logical address of the log tree root
pub log_root: u64, pub log_root: U64<LE>,
pub log_root_transid: u64, pub log_root_transid: U64<LE>,
pub total_bytes: u64, pub total_bytes: U64<LE>,
pub bytes_used: u64, pub bytes_used: U64<LE>,
pub root_dir_objectid: u64, pub root_dir_objectid: U64<LE>,
pub num_devices: u64, pub num_devices: U64<LE>,
pub sector_size: u32, pub sector_size: U32<LE>,
pub node_size: u32, pub node_size: U32<LE>,
/// Unused and must be equal to `nodesize` /// Unused and must be equal to `nodesize`
pub leafsize: u32, pub leafsize: U32<LE>,
pub stripesize: u32, pub stripesize: U32<LE>,
pub sys_chunk_array_size: u32, pub sys_chunk_array_size: U32<LE>,
pub chunk_root_generation: u64, pub chunk_root_generation: U64<LE>,
pub compat_flags: u64, pub compat_flags: U64<LE>,
pub compat_ro_flags: u64, pub compat_ro_flags: U64<LE>,
pub incompat_flags: u64, pub incompat_flags: U64<LE>,
pub csum_type: u16, pub csum_type: U16<LE>,
pub root_level: u8, pub root_level: u8,
pub chunk_root_level: u8, pub chunk_root_level: u8,
pub log_root_level: u8, pub log_root_level: u8,
pub dev_item: DevItem, pub dev_item: DevItem,
#[derivative(Debug(format_with = "format_u8str"))] #[derivative(Debug(format_with = "format_u8str"))]
pub label: [u8; 0x100], pub label: [u8; 0x100],
pub cache_generation: u64, pub cache_generation: U64<LE>,
pub uuid_tree_generation: u64, pub uuid_tree_generation: U64<LE>,
pub metadata_uuid: Uuid, pub metadata_uuid: Uuid,
/// Future expansion /// Future expansion
reserved: [u64; 28], #[derivative(Debug = "ignore")]
_reserved: [u64; 28],
#[derivative(Debug = "ignore")] #[derivative(Debug = "ignore")]
pub sys_chunk_array: [u8; 0x800], pub sys_chunk_array: [u8; 0x800],
pub root_backup0: RootBackup, #[derivative(Debug = "ignore")]
pub root_backup1: RootBackup, pub root_backups: [RootBackup; 4],
pub root_backup2: RootBackup, #[derivative(Debug = "ignore")]
pub root_backup3: RootBackup, _reserved2: [u8; 565],
} }
#[repr(u16)] #[repr(u16)]
@ -516,6 +585,13 @@ pub enum ChecksumType {
Blake2B, Blake2B,
} }
fn calculate_crc32c(bytes: &[u8]) -> [u8; 32] {
let crc = crc::Crc::<u32>::new(&crc::CRC_32_ISCSI);
let mut csum = [0u8; 32];
csum[..4].copy_from_slice(crc.checksum(bytes).as_bytes());
csum
}
impl Superblock { impl Superblock {
pub const SUPERBLOCK_BASE_OFFSET: usize = 0x10000; pub const SUPERBLOCK_BASE_OFFSET: usize = 0x10000;
pub const SUPERBLOCK_OFFSETS: [usize; 4] = [ pub const SUPERBLOCK_OFFSETS: [usize; 4] = [
@ -527,17 +603,37 @@ impl Superblock {
pub const MAGIC: [u8; 8] = *b"_BHRfS_M"; pub const MAGIC: [u8; 8] = *b"_BHRfS_M";
pub fn parse(bytes: &[u8]) -> Result<Self> { pub fn parse(bytes: &[u8]) -> Result<Self> {
let superblock = bytes.pread_with::<Self>(0, scroll::LE)?; let superblock = Self::read_from(bytes).ok_or(Error::ReadFailed)?;
if !superblock.verify_magic() { if !superblock.verify_magic() {
return Err(Error::InvalidMagic); return Err(Error::InvalidMagic);
} }
if !superblock.verify_checksum() {
return Err(Error::InvalidChecksum {
expected: superblock.csum,
actual: superblock.calculate_checksum(),
});
}
Ok(superblock) 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> { pub fn checksum_type(&self) -> Option<ChecksumType> {
ChecksumType::try_from_primitive(self.csum_type).ok() ChecksumType::try_from_primitive(self.csum_type.get()).ok()
} }
pub fn verify_magic(&self) -> bool { pub fn verify_magic(&self) -> bool {
@ -546,73 +642,67 @@ impl Superblock {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] #[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct Stripe { pub struct Stripe {
pub devid: u64, pub devid: U64<LE>,
pub offset: u64, pub offset: U64<LE>,
pub dev_uuid: Uuid, pub dev_uuid: Uuid,
} }
impl Stripe { impl Stripe {
pub fn parse(bytes: &[u8]) -> Result<Self> { pub fn parse(bytes: &[u8]) -> Result<Self> {
Ok(bytes.pread_with(0, scroll::LE)?) Self::read_from(bytes).ok_or(Error::ReadFailed)
} }
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] #[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct Chunk { pub struct Chunk {
/// size of this chunk in bytes /// size of this chunk in bytes
pub length: u64, pub length: U64<LE>,
/// objectid of the root referencing this chunk /// objectid of the root referencing this chunk
pub owner: u64, pub owner: U64<LE>,
pub stripe_len: u64, pub stripe_len: U64<LE>,
pub ty: u64, pub ty: U64<LE>,
/// optimal io alignment for this chunk /// optimal io alignment for this chunk
pub io_align: u32, pub io_align: U32<LE>,
/// optimal io width for this chunk /// optimal io width for this chunk
pub io_width: u32, pub io_width: U32<LE>,
/// minimal io size for this chunk /// minimal io size for this chunk
pub sector_size: u32, pub sector_size: U32<LE>,
/// 2^16 stripes is quite a lot, a second limit is the size of a single item in the btree /// 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<LE>,
/// sub stripes only matter for raid10 /// sub stripes only matter for raid10
pub sub_stripes: u16, pub sub_stripes: U16<LE>,
pub stripe: Stripe, pub stripe: Stripe,
// additional stripes go here // additional stripes go here
} }
impl Chunk {
pub fn parse(bytes: &[u8]) -> Result<Self> {
Ok(bytes.pread_with(0, scroll::LE)?)
}
}
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] #[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct Timespec { pub struct Timespec {
pub sec: u64, pub sec: U64<LE>,
pub nsec: u32, pub nsec: U32<LE>,
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct InodeItem { pub struct InodeItem {
/// nfs style generation number /// nfs style generation number
pub generation: u64, pub generation: U64<LE>,
/// transid that last touched this inode /// transid that last touched this inode
pub transid: u64, pub transid: U64<LE>,
pub size: u64, pub size: U64<LE>,
pub nbytes: u64, pub nbytes: U64<LE>,
pub block_group: u64, pub block_group: U64<LE>,
pub nlink: u32, pub nlink: U32<LE>,
pub uid: u32, pub uid: U32<LE>,
pub gid: u32, pub gid: U32<LE>,
pub mode: u32, pub mode: U32<LE>,
pub rdev: u64, pub rdev: U64<LE>,
pub flags: u64, pub flags: U64<LE>,
/// modification sequence number for NFS /// modification sequence number for NFS
pub sequence: u64, pub sequence: U64<LE>,
pub reserved: [u64; 4], pub reserved: [u64; 4],
pub atime: Timespec, pub atime: Timespec,
pub ctime: Timespec, pub ctime: Timespec,
@ -621,32 +711,32 @@ pub struct InodeItem {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct RootItem { pub struct RootItem {
pub inode: InodeItem, pub inode: InodeItem,
pub generation: u64, pub generation: U64<LE>,
pub root_dirid: u64, pub root_dirid: U64<LE>,
pub bytenr: u64, pub bytenr: U64<LE>,
pub byte_limit: u64, pub byte_limit: U64<LE>,
pub bytes_used: u64, pub bytes_used: U64<LE>,
pub last_snapshot: u64, pub last_snapshot: U64<LE>,
pub flags: u64, pub flags: U64<LE>,
pub refs: u32, pub refs: U32<LE>,
pub drop_progress: Key, pub drop_progress: Key,
pub drop_level: u8, pub drop_level: u8,
pub level: u8, pub level: u8,
pub generation_v2: u64, pub generation_v2: U64<LE>,
pub uuid: Uuid, pub uuid: Uuid,
pub parent_uuid: Uuid, pub parent_uuid: Uuid,
pub received_uuid: Uuid, pub received_uuid: Uuid,
/// updated when an inode changes /// updated when an inode changes
pub ctransid: u64, pub ctransid: U64<LE>,
/// trans when created /// trans when created
pub otransid: u64, pub otransid: U64<LE>,
/// trans when sent. non-zero for received subvol /// trans when sent. non-zero for received subvol
pub stransid: u64, pub stransid: U64<LE>,
/// trans when received. non-zero for received subvol /// trans when received. non-zero for received subvol
pub rtransid: u64, pub rtransid: U64<LE>,
pub ctime: Timespec, pub ctime: Timespec,
pub otime: Timespec, pub otime: Timespec,
pub stime: Timespec, pub stime: Timespec,
@ -654,14 +744,95 @@ pub struct RootItem {
pub reserved: [u64; 8], 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)] #[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct DirItem { pub struct DirItem {
pub location: Key, pub location: Key,
pub transid: u64, pub transid: U64<LE>,
pub data_len: u16, pub data_len: U16<LE>,
pub name_len: u16, pub name_len: U16<LE>,
pub ty: u8, 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<DirItemEntry> {
let dir_item = DirItem::read_from(&bytes[*offset..*offset + size_of::<DirItem>()])
.ok_or(Error::ReadFailed)?;
*offset += size_of::<DirItem>();
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<Vec<(DirItemEntry)>> {
let offset = &mut 0;
let entries = core::iter::from_fn(|| {
if *offset + size_of::<DirItem>() < bytes.len() {
Some(Self::parse_single_inner(&bytes[*offset..], offset))
} else {
None
}
})
.collect::<Result<Vec<_>>>()?;
Ok(entries)
}
}
#[derive(Clone)]
pub struct DirItemEntry {
pub dir_item: DirItem,
pub name: Vec<u8>,
}
impl DirItemEntry {
pub fn new(dir_item: DirItem, name: Vec<u8>) -> 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<str> {
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)] #[repr(C, packed)]
@ -672,101 +843,179 @@ pub struct InodeRef {
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] #[derive(Debug, Clone, PartialEq, Eq, Copy, FromBytes, AsBytes)]
pub struct Header { pub struct Header {
pub csum: [u8; 32], pub csum: [u8; 32],
pub fsid: Uuid, pub fsid: Uuid,
/// Which block this node is supposed to live in /// Which block this node is supposed to live in
pub bytenr: u64, pub bytenr: U64<LE>,
pub flags: u64, pub flags: U64<LE>,
pub chunk_tree_uuid: Uuid, pub chunk_tree_uuid: Uuid,
pub generation: u64, pub generation: U64<LE>,
pub owner: u64, pub owner: U64<LE>,
pub nritems: u32, pub nritems: U32<LE>,
pub level: u8, pub level: u8,
} }
#[repr(C, packed)] #[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) /// 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. /// tell us where to find the item in the leaf.
pub struct Item { pub struct Item {
pub key: Key, pub key: Key,
pub offset: u32, pub offset: U32<LE>,
pub size: u32, pub size: U32<LE>,
} }
#[repr(C, packed)] #[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable)] #[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct Leaf {
pub header: Header,
// `Item`s begin here
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)]
/// All non-leaf blocks are nodes and they hold only keys are pointers to other blocks /// All non-leaf blocks are nodes and they hold only keys are pointers to other blocks
pub struct KeyPtr { pub struct KeyPtr {
pub key: Key, pub key: Key,
pub blockptr: u64, pub blockptr: U64<LE>,
pub generation: u64, pub generation: U64<LE>,
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub struct Node {
pub header: Header,
// `KeyPtr`s begin here
} }
use alloc::vec::Vec; use alloc::vec::Vec;
#[derive(Debug)] #[derive(Debug)]
pub enum TreeItem {
Chunk(Chunk),
Root(RootItem),
DirItem(Vec<DirItemEntry>),
DirIndex(DirItemEntry),
Unimplemented,
}
impl From<Chunk> for TreeItem {
fn from(value: Chunk) -> Self {
Self::Chunk(value)
}
}
impl From<RootItem> for TreeItem {
fn from(value: RootItem) -> Self {
Self::Root(value)
}
}
impl From<Vec<DirItemEntry>> for TreeItem {
fn from(value: Vec<DirItemEntry>) -> Self {
Self::DirItem(value)
}
}
impl From<DirItemEntry> for TreeItem {
fn from(value: DirItemEntry) -> Self {
Self::DirIndex(value)
}
}
impl TreeItem {
pub fn parse(item: &Item, bytes: &[u8]) -> Result<Self> {
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 { pub struct BTreeLeafNode {
header: Header, pub header: Header,
/// actual leaf data /// actual leaf data
items: Vec<Item>, pub items: Vec<Item>,
} }
impl BTreeLeafNode { impl BTreeLeafNode {
pub fn parse(header: Header, bytes: &[u8]) -> Result<Self> { pub fn parse(header: Header, bytes: &[u8]) -> Result<Self> {
log::debug!("leaf:");
let offset = &mut 0; let offset = &mut 0;
let items = core::iter::from_fn(|| { let items = core::iter::from_fn(|| {
if *offset as usize + size_of::<Item>() < bytes.len() { if *offset as usize + size_of::<Item>() < bytes.len() {
Some(bytes.gread_with::<Item>(offset, scroll::LE)) let item = Item::read_from(&bytes[*offset..*offset + size_of::<Item>()]);
*offset += size_of::<Item>();
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 { } else {
None None
} }
}) })
.take(header.nritems as usize) .take(header.nritems.get() as usize)
.collect::<core::result::Result<Vec<_>, _>>()?; .collect::<Vec<_>>();
Ok(Self { header, items }) Ok(Self { header, items })
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct BTreeInternalNode { pub struct BTreeInternalNode {
header: Header, pub header: Header,
children: Vec<KeyPtr>, pub children: Vec<KeyPtr>,
} }
impl BTreeInternalNode { impl BTreeInternalNode {
pub fn parse(header: Header, bytes: &[u8]) -> Result<Self> { pub fn parse(header: Header, bytes: &[u8]) -> Result<Self> {
log::debug!("internal lvl: {}", header.level);
let offset = &mut 0; let offset = &mut 0;
let size = size_of::<KeyPtr>();
let children = core::iter::from_fn(|| { let children = core::iter::from_fn(|| {
if *offset as usize + size_of::<KeyPtr>() < bytes.len() { if *offset as usize + size < bytes.len() {
Some(bytes.gread_with::<KeyPtr>(offset, scroll::LE)) 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 { } else {
None None
} }
}) })
.take(header.nritems as usize) .take(header.nritems.get() as usize)
.collect::<core::result::Result<Vec<_>, _>>()?; .collect::<Vec<_>>();
Ok(Self { header, children }) Ok(Self { header, children })
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum BTreeNode { pub enum BTreeNode {
Internal(BTreeInternalNode), Internal(BTreeInternalNode),
Leaf(BTreeLeafNode), Leaf(BTreeLeafNode),
@ -775,7 +1024,7 @@ pub enum BTreeNode {
impl BTreeNode { impl BTreeNode {
pub fn parse(bytes: &[u8]) -> Result<Self> { pub fn parse(bytes: &[u8]) -> Result<Self> {
let offset = &mut 0; let offset = &mut 0;
let header = bytes.gread_with::<Header>(offset, scroll::LE)?; let header = bytes.gread::<Header>(offset)?;
if header.level == 0 { if header.level == 0 {
Ok(Self::Leaf(BTreeLeafNode::parse(header, &bytes[*offset..])?)) Ok(Self::Leaf(BTreeLeafNode::parse(header, &bytes[*offset..])?))

480
btrfs/src/tree.rs Normal file
View file

@ -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<Item>,
}
impl BTreeLeafNode {
pub fn parse(header: Header, bytes: &[u8]) -> Result<Self> {
log::debug!("leaf:");
let offset = &mut 0;
let items = core::iter::from_fn(|| {
if *offset as usize + size_of::<Item>() < bytes.len() {
let item = Item::read_from(&bytes[*offset..*offset + size_of::<Item>()]);
*offset += size_of::<Item>();
if let Some(item) = item.as_ref() {
log::debug!("\t{item:?}");
}
item
} else {
None
}
})
.take(header.nritems.get() as usize)
.collect::<Vec<_>>();
Ok(Self { header, items })
}
}
#[derive(Debug, Clone)]
pub struct BTreeInternalNode {
pub header: Header,
pub children: Vec<KeyPtr>,
}
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<Self> {
log::debug!("internal lvl: {}", header.level);
let offset = &mut 0;
let size = size_of::<KeyPtr>();
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::<Vec<_>>();
Ok(Self { header, children })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BTreeNode {
Internal(BTreeInternalNode),
Leaf(BTreeLeafNode),
}
impl BTreeNode {
pub fn parse(bytes: &[u8]) -> Result<Self> {
let offset = &mut 0;
let header = bytes.gread::<Header>(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<R>,
// 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<Option<(Item, TreeItem)>> {
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::<Header>() + 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<B>(bytes: B) -> Result<Self>
where
Cow<'a, [u8]>: From<B>,
{
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<Node<'a>>;
#[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<Option<(Item, TreeItem)>> {
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<R>,
parents: Vec<NodeHandle<'a>>,
start: RootOrEdge<'a>,
end: RootOrEdge<'a>,
}
impl<'a, R: VolumeIo> Iterator for Range<'a, R> {
type Item = (Item, TreeItem);
fn next(&mut self) -> Option<Self::Item> {
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<R>, start: Rc<Node<'a>>, end: Rc<Node<'a>>) -> 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<Option<()>> {
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<R>, logical: u64) -> Result<Self> {
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())
}
}

View file

@ -6,7 +6,7 @@ use std::{
use test_log::test; use test_log::test;
use btrfs::{structs::*, Volume}; use btrfs::{structs::*, tree::Tree, Volume};
#[test] #[test]
fn superblock() { fn superblock() {
@ -17,6 +17,9 @@ fn superblock() {
let sb = volume.superblock(); let sb = volume.superblock();
println!("{sb:#?}"); println!("{sb:#?}");
assert!(sb.verify_magic());
assert!(sb.verify_checksum());
} }
#[test] #[test]
@ -40,5 +43,7 @@ fn iter_root() {
let mut volume = Volume::new(reader).expect("volume"); let mut volume = Volume::new(reader).expect("volume");
let sb = volume.superblock(); let sb = volume.superblock();
volume.asdf(); volume.iter_roots();
println!("{:#?}", volume.chunk_cache);
} }