diff --git a/btrfs/src/v2/mod.rs b/btrfs/src/v2/mod.rs new file mode 100644 index 0000000..ba6c7ad --- /dev/null +++ b/btrfs/src/v2/mod.rs @@ -0,0 +1,59 @@ +use crate::Error; + +pub mod error { + use thiserror::Error; + + #[derive(Debug, Error)] + pub enum Error { + #[error("read failed")] + ReadFailed, + #[error("invalid magic signature")] + InvalidMagic, + #[error("invalid offset")] + InvalidOffset, + #[error("Expected an internal node")] + ExpectedInternalNode, + #[error("Expected a leaf node")] + ExpectedLeafNode, + #[error("Failed to read with logical offset")] + BadLogicalAddress, + #[error("Default subvolume root could not be found")] + NoDefaultSubvolRoot, + #[error("Default subvolume root fs tree could not be found")] + NoDefaultSubvolFsRoot, + #[error("Invalid checksum: expected {expected:#?} but got {actual:#?}")] + InvalidChecksum { + expected: [u8; 32], + actual: [u8; 32], + }, + #[error("{0}")] + ScrollError(scroll::Error), + } + + impl From for Error { + fn from(value: scroll::Error) -> Self { + Self::ScrollError(value) + } + } + + pub type Result = core::result::Result; +} + +pub trait Read { + fn read(&self, dst: &mut [u8], address: u64) -> error::Result<()>; + fn alignment(&self) -> usize { + 1 + } +} + +#[cfg(all(any(feature = "std", test), unix))] +impl Read for std::fs::File { + fn read(&self, dst: &mut [u8], address: u64) -> error::Result<()> { + use std::os::unix::prelude::FileExt; + self.read_at(dst, address).map_err(|_| Error::ReadFailed)?; + Ok(()) + } +} + +pub mod tree; +pub mod volume; diff --git a/btrfs/src/v2/tree.rs b/btrfs/src/v2/tree.rs new file mode 100644 index 0000000..285c8bb --- /dev/null +++ b/btrfs/src/v2/tree.rs @@ -0,0 +1,674 @@ +use core::mem::size_of; +use core::ops::Deref; + +use crate::structs::{Header, Item, Key, KeyPtr, KnownObjectId, ObjectType, TreeItem}; +use crate::{Error, Result}; +use alloc::rc::Rc; +use alloc::vec::Vec; +use derivative::Derivative; +use scroll::Pread; +use zerocopy::FromBytes; + +use super::volume::Volume; + +/// A leaf node in a btrfs tree, containing different items +#[derive(Debug, Clone)] +pub struct BTreeLeafNode { + pub header: Header, + /// actual leaf data + pub items: Vec, +} + +/// An internal node in a btrfs tree, containing `KeyPtr`s to other internal nodes or leaf nodes. +#[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 {} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BTreeNode { + Internal(BTreeInternalNode), + Leaf(BTreeLeafNode), +} + +#[derive(Derivative, Eq)] +#[derivative(Debug, PartialEq)] +pub struct Node { + inner: BTreeNode, + #[derivative(Debug = "ignore")] + #[derivative(PartialEq = "ignore")] + bytes: Vec, +} + +type BoxedNode = Rc; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NodeHandle { + node: BoxedNode, + idx: u32, +} + +pub struct Range { + volume: Rc>, + parents: Vec, + start: RootOrEdge, + end: RootOrEdge, +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Tree { + #[derivative(Debug = "ignore")] + volume: Rc>, + root: BoxedNode, +} + +impl Clone for Tree { + fn clone(&self) -> Self { + Self { + volume: self.volume.clone(), + root: self.root.clone(), + } + } +} + +impl Tree { + pub fn from_logical_offset(volume: Rc>, logical: u64) -> Result { + let bytes = volume + .read_range_from_logical(logical)? + .ok_or(Error::BadLogicalAddress)?; // TODO: make this a better error + let root = Rc::new(Node::from_bytes(bytes)?); + + Ok(Self { volume, root }) + } + + pub fn find_key + PartialOrd>( + &self, + key: &K, + ) -> Result> { + let mut node = self.root.clone(); + + loop { + let search = node.find_key(key); + match search { + SearchResult::Leaf(a) => { + return a.parse_item(); + } + SearchResult::Edge(edge) => match &edge.node.inner { + BTreeNode::Internal(internal) => { + let child_ptr = internal.children.get(edge.idx as usize).expect("adsf"); + + let bytes = self.volume.read_keyptr(child_ptr)?; + node = Rc::new(Node::from_bytes(bytes)?); + // recurse + } + BTreeNode::Leaf(_) => { + // leaf node returning and edge means key is not present + return Ok(None); + } + }, + } + } + } + + pub fn iter(&self) -> Range { + Range::new(self.volume.clone(), self.root.clone(), self.root.clone()) + } +} + +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 }) + } +} + +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 }) + } +} + +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 + } + } +} + +pub enum NodeHandleAdvanceResult { + Decend { + parent: NodeHandle, + child_ptr: KeyPtr, + }, + Next(NodeHandle), + Ascend, +} + +pub enum SearchResult { + Leaf(NodeHandle), + Edge(NodeHandle), +} + +#[derive(Debug, Clone, Eq)] +enum RootOrEdge { + Root(NodeHandle), + Edge(NodeHandle), +} +impl RootOrEdge { + pub fn into_handle(&self) -> NodeHandle { + match self { + RootOrEdge::Root(handle) => handle, + RootOrEdge::Edge(handle) => handle, + } + .clone() + } + + pub fn as_handle(&self) -> &NodeHandle { + match self { + RootOrEdge::Root(handle) => handle, + RootOrEdge::Edge(handle) => handle, + } + } +} + +impl Deref for RootOrEdge { + type Target = NodeHandle; + + fn deref(&self) -> &Self::Target { + match self { + RootOrEdge::Root(node) => node, + RootOrEdge::Edge(node) => node, + } + } +} + +impl PartialEq for RootOrEdge { + fn eq(&self, other: &Self) -> bool { + Deref::deref(self).eq(Deref::deref(other)) + } +} + +impl Node { + pub fn from_bytes(bytes: Vec) -> Result { + let inner = BTreeNode::parse(&bytes)?; + + Ok(Self { inner, bytes }) + } + + 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))) + } + } + } + + pub fn find_key + PartialOrd>(self: &Rc, key: &K) -> SearchResult { + match &self.inner { + BTreeNode::Internal(node) => { + for (i, child) in node.children.iter().enumerate() { + match key.partial_cmp(&child.key) { + Some(core::cmp::Ordering::Less) => { + return SearchResult::Edge(NodeHandle { + node: self.clone(), + idx: if i == 0 { 0 } else { i as u32 - 1 }, + }); + } + Some(core::cmp::Ordering::Equal) | None => { + return SearchResult::Edge(NodeHandle { + node: self.clone(), + idx: i as u32, + }); + } + _ => {} + } + } + + SearchResult::Edge(NodeHandle { + node: self.clone(), + idx: node.children.len() as u32 - 1, + }) + } + BTreeNode::Leaf(node) => { + for (i, child) in node.items.iter().enumerate() { + // if key < &child.key { + // return SearchResult::Leaf(NodeHandle { + // node: self.clone(), + // idx: if i == 0 { 0 } else { i as u32 - 1 }, + // }); + // } else + if key.eq(&child.key) { + return SearchResult::Leaf(NodeHandle { + node: self.clone(), + idx: i as u32, + }); + } + } + + log::debug!("key definitely not found!"); + SearchResult::Edge(NodeHandle { + node: self.clone(), + idx: node.items.len() as u32 - 1, + }) + } + } + } +} + +impl Range +where + R: super::Read, +{ + pub fn new(volume: Rc>, 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 Iterator for Range +where + R: super::Read, +{ + 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(_) => 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 NodeHandle { + pub fn start(node: BoxedNode) -> 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 { + 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 { + 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) -> Self { + Self { + idx: node.inner.header().nritems.get() - 1, + node, + } + } +} + +// PARTIAL KEY + +/// key lookup that will find the first key that matches the present items in this partial key +pub struct PartialKey { + pub id: Option, + pub ty: Option, + pub offset: Option, +} + +impl PartialKey { + pub fn new(id: Option, ty: Option, offset: Option) -> Self { + Self { id, ty, offset } + } +} + +impl PartialEq for PartialKey { + fn eq(&self, other: &Key) -> bool { + self.id.map(|id| id == other.id()).unwrap_or(true) + && self.ty.map(|ty| ty == other.ty()).unwrap_or(true) + && self + .offset + .map(|offset| offset == other.offset.get()) + .unwrap_or(true) + } +} + +/// compares Self to a key, by comparing each item with the element in key if present, and skipping to the next item if missing. +impl PartialOrd for PartialKey { + fn partial_cmp(&self, other: &Key) -> Option { + let id = self.id.and_then(|id| id.partial_cmp(&other.id())); + let ty = self.ty.and_then(|ty| ty.partial_cmp(&other.ty())); + let offset = self + .offset + .and_then(|offset| offset.partial_cmp(&other.offset.get())); + + match id { + Some(core::cmp::Ordering::Equal) | None => match ty { + Some(core::cmp::Ordering::Equal) | None => offset, + ord => ord, + }, + ord => ord, + } + } +} + +#[cfg(test)] +mod partial_key_tests { + use test_log::test; + + use super::*; + + #[test] + fn test_partial_key_ord() { + let key = Key::new( + KnownObjectId::ChunkTree, + ObjectType::DirItem, + 0x8dbfc2d2, // crc of "default" + ); + + let pkey = PartialKey::new( + Some(KnownObjectId::ChunkTree), + Some(ObjectType::DirItem), + None, + ); + assert_eq!(pkey.partial_cmp(&key), None); + + let pkey = PartialKey::new( + Some(KnownObjectId::ChunkTree), + Some(ObjectType::DirItem), + Some(0xdeadbeef), + ); + assert_ne!(pkey.partial_cmp(&key), Some(core::cmp::Ordering::Equal)); + + let pkey = PartialKey::new(None, Some(ObjectType::DirItem), Some(0xdeadbeef)); + assert_ne!(pkey.partial_cmp(&key), None); + + let pkey = PartialKey::new( + Some(KnownObjectId::ChunkTree), + Some(ObjectType::DirItem), + Some(0x8dbfc2d2), + ); + assert_eq!(pkey.partial_cmp(&key), Some(core::cmp::Ordering::Equal)); + } + + #[test] + fn test_partial_eq_partial_key() { + let key = Key::new( + KnownObjectId::ChunkTree, + ObjectType::DirItem, + 0x8dbfc2d2, // crc of "default" + ); + + let pkey = PartialKey::new( + Some(KnownObjectId::ChunkTree), + Some(ObjectType::DirItem), + None, + ); + assert!(pkey.eq(&key)); + + let pkey = PartialKey::new( + Some(KnownObjectId::ChunkTree), + Some(ObjectType::DirItem), + Some(0xdeadbeef), + ); + assert!(!pkey.eq(&key)); + + let pkey = PartialKey::new(None, Some(ObjectType::DirItem), Some(0xdeadbeef)); + assert!(!pkey.eq(&key)); + + let pkey = PartialKey::new( + Some(KnownObjectId::ChunkTree), + Some(ObjectType::DirItem), + Some(0x8dbfc2d2), + ); + assert!(pkey.eq(&key)); + } +} diff --git a/btrfs/src/v2/volume.rs b/btrfs/src/v2/volume.rs new file mode 100644 index 0000000..a826892 --- /dev/null +++ b/btrfs/src/v2/volume.rs @@ -0,0 +1,385 @@ +use core::mem::size_of; + +use alloc::collections::btree_map::Entry; +use alloc::{collections::BTreeMap, rc::Rc, vec, vec::Vec}; +use scroll::Pread; + +use crate::structs::{ + Chunk, Key, KeyPtr, KnownObjectId, ObjectType, RootItem, Stripe, Superblock, TreeItem, +}; +use crate::v2::tree::PartialKey; +use crate::{Error, Result}; + +use super::tree::Tree; + +/// equal if overlapping, ordered by lower bound +#[derive(Debug, Clone)] +pub struct ChunkTreeKey { + range: core::ops::Range, +} + +impl From for ChunkTreeKey { + fn from(value: u64) -> Self { + Self { + range: value..value, + } + } +} + +impl ChunkTreeKey { + pub fn start(&self) -> u64 { + self.range.start + } + pub fn end(&self) -> u64 { + self.range.end + } + pub fn size(&self) -> u64 { + self.range.end - self.range.start + } + + pub fn delta(&self, point: u64) -> u64 { + point - self.range.start + } + + pub fn sub_range(&self, point: u64) -> core::ops::Range { + self.delta(point)..self.end() + } +} + +impl Eq for ChunkTreeKey {} +impl Ord for ChunkTreeKey { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + +impl PartialEq for ChunkTreeKey { + fn eq(&self, other: &Self) -> bool { + self.range.contains(&other.range.start) || other.range.contains(&self.range.start) + } +} + +impl PartialOrd for ChunkTreeKey { + fn partial_cmp(&self, other: &Self) -> Option { + self.eq(other) + .then_some(core::cmp::Ordering::Equal) + .or_else(|| self.range.start.partial_cmp(&other.range.start)) + } +} + +/// inner volume struct that contains all the info needed to read/parse trees +#[derive(Debug, Clone)] +pub struct Volume { + read: R, + superblock: Superblock, + chunk_cache: BTreeMap, +} + +// TODO: find better name +#[derive(Debug, Clone)] +pub struct Volume2 { + inner: Rc>, + roots: BTreeMap>, +} + +// TODO: find better name +#[derive(Debug, Clone)] +pub struct Fs { + volume: Rc>, + fs_root: Tree, +} + +impl Volume { + pub fn new(read: R) -> Result> { + let mut sb = vec![0u8; size_of::()]; + read.read(&mut sb, Superblock::SUPERBLOCK_BASE_OFFSET as _)?; + + let superblock = Superblock::parse(&sb)?; + let chunk_cache = Self::bootstrap_chunk_tree(&superblock)?; + + let volume = Rc::new( + Self { + read, + superblock, + chunk_cache, + } + .parse_chunk_tree()?, + ); + + Ok(volume) + } + + pub fn into_volume2(self: Rc) -> Result>> { + Ok(Rc::new(Volume2 { + inner: self.clone(), + roots: self.parse_root_tree()?, + })) + } + + fn parse_chunk_tree(mut self) -> Result { + let this = Rc::new(self); + + let chunk_tree = + Tree::from_logical_offset(this.clone(), this.superblock().chunk_root.get())?; + + let chunks = chunk_tree + .iter() + .filter_map(|(item, v)| match v { + TreeItem::Chunk(chunk) => Some((item, chunk)), + _ => None, + }) + .collect::>(); + + drop(chunk_tree); + + self = match Rc::try_unwrap(this) { + Ok(v) => v, + Err(_) => unreachable!(), + }; + + for (item, chunk) in chunks { + let start = item.key.offset.get() as u64; + let end = start + chunk.length.get(); + + match self.chunk_cache.entry(ChunkTreeKey { range: start..end }) { + Entry::Vacant(entry) => { + log::info!("inserting chunk [{start}, {end})"); + entry.insert(chunk.stripe.offset.get()); + } + 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(self) + } + + fn parse_root_tree(self: Rc) -> Result>> { + let root_tree_root = self.superblock().root.get(); + let root_tree = Tree::from_logical_offset(self.clone(), root_tree_root)?; + + let roots = root_tree + .iter() + .filter_map(|(item, v)| match v { + TreeItem::Root(root) => Some((item, root)), + _ => None, + }) + .map(|(item, root)| { + let id = item.key.id(); + let a = Tree::from_logical_offset(self.clone(), root.bytenr.get()); + + a.map(|root| (id, root)) + }) + .collect::>>()?; + + Ok(roots) + } + + fn size_from_logical(&self, logical: u64) -> Option { + self.chunk_cache + .get_key_value(&logical.into()) + .map(|(key, _)| key.size()) + } + + fn offset_from_logical(&self, logical: u64) -> Option { + self.chunk_cache + .get_key_value(&logical.into()) + .map(|(key, offset)| offset + key.delta(logical)) + } + + fn range_from_logical(&self, logical: u64) -> Option> { + self.chunk_cache + .get_key_value(&logical.into()) + .map(|(key, offset)| { + let delta = key.delta(logical); + (offset + delta)..(offset + key.size() - delta) + }) + } + + pub fn read_range_from_logical(&self, logical: u64) -> Result>> { + if let Some(range) = self.range_from_logical(logical) { + Ok(Some(self.read_range(range)?)) + } else { + Ok(None) + } + } + + pub fn read_range(&self, range: core::ops::Range) -> Result> { + let mut buf = vec![0; (range.end - range.start) as usize]; + self.read.read(&mut buf, range.start)?; + + Ok(buf) + } + + pub fn read_keyptr(&self, keyptr: &KeyPtr) -> Result> { + self.read_range( + self.range_from_logical(keyptr.blockptr.get()) + .ok_or(Error::ReadFailed)?, + ) + } + + fn bootstrap_chunk_tree(superblock: &Superblock) -> Result> { + let array_size = superblock.sys_chunk_array_size.get() as usize; + let mut offset: usize = 0; + + let key_size = size_of::(); + let mut chunk_tree = BTreeMap::new(); + + let bytes = &superblock.sys_chunk_array; + + while offset < array_size { + if offset + key_size > array_size { + log::error!("short key read"); + return Err(Error::InvalidOffset); + } + + let key = bytes.gread::(&mut offset)?; + if key.ty() != ObjectType::ChunkItem { + log::error!("key is not of type ChunkItem"); + return Err(Error::InvalidOffset); + } + + 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); + } + + if num_stripes != 1 { + log::warn!( + "warning: {} stripes detected but only processing 1", + num_stripes + ); + } + + let key_offset = key.offset.get(); + let chunk_length = chunk.length.get(); + + match chunk_tree.entry(ChunkTreeKey { + range: key_offset..(key_offset + chunk_length), + }) { + Entry::Vacant(entry) => { + entry.insert(chunk.stripe.offset.get()); + } + Entry::Occupied(_) => { + log::error!("overlapping stripes!"); + return Err(Error::InvalidOffset); + } + }; + + offset += (num_stripes - 1) as usize * size_of::(); + if offset > array_size { + log::error!("short chunk item + stripes read"); + return Err(Error::InvalidOffset); + } + } + + Ok(chunk_tree) + } + + pub fn superblock(&self) -> Superblock { + self.superblock + } +} + +impl Volume2 { + pub fn default_subvolume(self: Rc) -> Result> { + let root_tree = + Tree::from_logical_offset(self.inner.clone(), self.inner.superblock().root.get())?; + + // we are looking for the root tree directory (?) + // this is a DIR_ITEM entry in the root tree, with the name "default", + // and the crc32 of "default" as its offset + let key = Key::new( + KnownObjectId::Custom(self.inner.superblock().root_dir_objectid.get()), + ObjectType::DirItem, + 0x8dbfc2d2, // crc of "default" + ); + + let subvol_root = root_tree + .find_key(&key)? + .ok_or(Error::NoDefaultSubvolRoot)?; + // if we found the dir entry of the "default subvol" (mharmstone nomenclature) + // we then look for the root fs tree in the root tree with the ID found in the `.location` of the dir_item only (from mharmstone) + + let subvol_id = subvol_root + .1 + .as_dir_item() + .expect("dir item") + .first() + .expect("dir item entry") + .dir_item + .location + .id(); + + let root = self + .roots + .get(&subvol_id) + .ok_or(Error::NoDefaultSubvolFsRoot)? + .clone(); + Ok(Fs { + volume: self.clone(), + fs_root: root.clone(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use test_log::test; + + fn open_btrfs_file() -> File { + let file = std::fs::File::open("btrfs.img").expect("btrfs image"); + file + } + + #[test] + fn create_volume() { + let file = open_btrfs_file(); + let vol = Volume::new(file).expect("volume"); + let v2 = vol.into_volume2().expect("volume2"); + v2.default_subvolume().expect("default subvol"); + } + + #[test] + fn iter_roots() { + let file = open_btrfs_file(); + let vol = Volume::new(file).expect("volume"); + let v2 = vol.into_volume2().expect("volume2"); + + for (id, v) in v2.roots.iter() { + log::info!("[{id:?}] {v:#?}"); + } + } + + #[test] + fn iter_default_subvol() { + let file = open_btrfs_file(); + let vol = Volume::new(file).expect("volume"); + let v2 = vol.into_volume2().expect("volume2"); + let fs = v2.default_subvolume().expect("default subvol"); + + for (id, entry) in fs.fs_root.iter() { + log::info!("[{id:?}] {entry:#?}"); + } + } +}