v2 refactor

This commit is contained in:
Janis 2023-03-29 17:52:20 +02:00
parent 899b759b3d
commit 2f2311d13a
3 changed files with 1118 additions and 0 deletions

59
btrfs/src/v2/mod.rs Normal file
View file

@ -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<scroll::Error> for Error {
fn from(value: scroll::Error) -> Self {
Self::ScrollError(value)
}
}
pub type Result<T> = core::result::Result<T, Error>;
}
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;

674
btrfs/src/v2/tree.rs Normal file
View file

@ -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<Item>,
}
/// 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<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 {}
#[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<u8>,
}
type BoxedNode = Rc<Node>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeHandle {
node: BoxedNode,
idx: u32,
}
pub struct Range<R: super::Read> {
volume: Rc<Volume<R>>,
parents: Vec<NodeHandle>,
start: RootOrEdge,
end: RootOrEdge,
}
#[derive(Derivative)]
#[derivative(Debug)]
pub struct Tree<R: super::Read> {
#[derivative(Debug = "ignore")]
volume: Rc<Volume<R>>,
root: BoxedNode,
}
impl<R: super::Read> Clone for Tree<R> {
fn clone(&self) -> Self {
Self {
volume: self.volume.clone(),
root: self.root.clone(),
}
}
}
impl<R: super::Read> Tree<R> {
pub fn from_logical_offset(volume: Rc<Volume<R>>, logical: u64) -> Result<Self> {
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<K: PartialEq<Key> + PartialOrd<Key>>(
&self,
key: &K,
) -> Result<Option<(Item, TreeItem)>> {
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<R> {
Range::new(self.volume.clone(), self.root.clone(), self.root.clone())
}
}
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 })
}
}
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 })
}
}
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
}
}
}
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<u8>) -> Result<Self> {
let inner = BTreeNode::parse(&bytes)?;
Ok(Self { inner, bytes })
}
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)))
}
}
}
pub fn find_key<K: PartialEq<Key> + PartialOrd<Key>>(self: &Rc<Self>, 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<R> Range<R>
where
R: super::Read,
{
pub fn new(volume: Rc<Volume<R>>, start: Rc<Node>, end: Rc<Node>) -> 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<R> Iterator for Range<R>
where
R: super::Read,
{
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(_) => 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<Option<(Item, TreeItem)>> {
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<KnownObjectId>,
pub ty: Option<ObjectType>,
pub offset: Option<u64>,
}
impl PartialKey {
pub fn new(id: Option<KnownObjectId>, ty: Option<ObjectType>, offset: Option<u64>) -> Self {
Self { id, ty, offset }
}
}
impl PartialEq<Key> 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<Key> for PartialKey {
fn partial_cmp(&self, other: &Key) -> Option<core::cmp::Ordering> {
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));
}
}

385
btrfs/src/v2/volume.rs Normal file
View file

@ -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<u64>,
}
impl From<u64> 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<u64> {
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<core::cmp::Ordering> {
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<R: super::Read> {
read: R,
superblock: Superblock,
chunk_cache: BTreeMap<ChunkTreeKey, u64>,
}
// TODO: find better name
#[derive(Debug, Clone)]
pub struct Volume2<R: super::Read> {
inner: Rc<Volume<R>>,
roots: BTreeMap<KnownObjectId, Tree<R>>,
}
// TODO: find better name
#[derive(Debug, Clone)]
pub struct Fs<R: super::Read> {
volume: Rc<Volume2<R>>,
fs_root: Tree<R>,
}
impl<R: super::Read> Volume<R> {
pub fn new(read: R) -> Result<Rc<Self>> {
let mut sb = vec![0u8; size_of::<Superblock>()];
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<Self>) -> Result<Rc<Volume2<R>>> {
Ok(Rc::new(Volume2 {
inner: self.clone(),
roots: self.parse_root_tree()?,
}))
}
fn parse_chunk_tree(mut self) -> Result<Self> {
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::<Vec<_>>();
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<Self>) -> Result<BTreeMap<KnownObjectId, Tree<R>>> {
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::<Result<BTreeMap<_, _>>>()?;
Ok(roots)
}
fn size_from_logical(&self, logical: u64) -> Option<u64> {
self.chunk_cache
.get_key_value(&logical.into())
.map(|(key, _)| key.size())
}
fn offset_from_logical(&self, logical: u64) -> Option<u64> {
self.chunk_cache
.get_key_value(&logical.into())
.map(|(key, offset)| offset + key.delta(logical))
}
fn range_from_logical(&self, logical: u64) -> Option<core::ops::Range<u64>> {
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<Option<Vec<u8>>> {
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<u64>) -> Result<Vec<u8>> {
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<Vec<u8>> {
self.read_range(
self.range_from_logical(keyptr.blockptr.get())
.ok_or(Error::ReadFailed)?,
)
}
fn bootstrap_chunk_tree(superblock: &Superblock) -> Result<BTreeMap<ChunkTreeKey, u64>> {
let array_size = superblock.sys_chunk_array_size.get() as usize;
let mut offset: usize = 0;
let key_size = size_of::<Key>();
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::<Key>(&mut offset)?;
if key.ty() != ObjectType::ChunkItem {
log::error!("key is not of type ChunkItem");
return Err(Error::InvalidOffset);
}
let chunk = bytes.gread::<Chunk>(&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::<Stripe>();
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<R: super::Read> Volume2<R> {
pub fn default_subvolume(self: Rc<Self>) -> Result<Fs<R>> {
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:#?}");
}
}
}