btrfs learning tool
This commit is contained in:
parent
47710fe4fa
commit
f1d968fbf5
169
btrfs/src/lib.rs
169
btrfs/src/lib.rs
|
@ -1,9 +1,16 @@
|
|||
#![feature(error_in_core)]
|
||||
#![cfg_attr(not(any(feature = "std", test)), no_std)]
|
||||
|
||||
use core::{borrow::Borrow, mem::size_of, ops::RangeBounds};
|
||||
use core::{
|
||||
borrow::Borrow,
|
||||
cell::{RefCell, RefMut},
|
||||
mem::size_of,
|
||||
ops::RangeBounds,
|
||||
};
|
||||
|
||||
use alloc::{
|
||||
borrow::Cow,
|
||||
boxed::Box,
|
||||
collections::{btree_map::Entry, BTreeMap},
|
||||
vec,
|
||||
vec::Vec,
|
||||
|
@ -11,11 +18,16 @@ use alloc::{
|
|||
use scroll::{Endian, Pread};
|
||||
use thiserror::Error;
|
||||
|
||||
use structs::{BTreeNode, Chunk, Header, Item, Key, KeyPtr, ObjectType, Stripe, Superblock};
|
||||
use structs::{
|
||||
BTreeNode, Chunk, Header, Item, Key, KeyPtr, KnownObjectId, ObjectType, RootItem, Stripe,
|
||||
Superblock,
|
||||
};
|
||||
use tree::Tree;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod structs;
|
||||
pub mod tree;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub mod std_io {
|
||||
|
@ -50,6 +62,11 @@ pub enum Error {
|
|||
ExpectedInternalNode,
|
||||
#[error("Expected a leaf node")]
|
||||
ExpectedLeafNode,
|
||||
#[error("Invalid checksum: expected {expected:#?} but got {actual:#?}")]
|
||||
InvalidChecksum {
|
||||
expected: [u8; 32],
|
||||
actual: [u8; 32],
|
||||
},
|
||||
#[error("{0}")]
|
||||
ScrollError(scroll::Error),
|
||||
}
|
||||
|
@ -110,8 +127,7 @@ impl Ord for ChunkTreeKey {
|
|||
|
||||
impl PartialEq for ChunkTreeKey {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
(self.range.contains(&other.range.start) || self.range.contains(&other.range.end))
|
||||
|| (other.range.contains(&self.range.start) || other.range.contains(&self.range.end))
|
||||
self.range.contains(&other.range.start) || other.range.contains(&self.range.start)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,23 +145,34 @@ pub struct ChunkCacheTree {
|
|||
|
||||
type ChunkTree = BTreeMap<ChunkTreeKey, u64>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Volume<R: VolumeIo> {
|
||||
reader: R,
|
||||
reader: Box<RefCell<R>>,
|
||||
superblock: Superblock,
|
||||
chunk_cache: ChunkTree,
|
||||
pub chunk_cache: ChunkTree,
|
||||
roots: BTreeMap<KnownObjectId, RootItem>,
|
||||
}
|
||||
|
||||
impl<R: VolumeIo> Volume<R> {
|
||||
pub fn reader(&self) -> RefMut<R> {
|
||||
self.reader.borrow_mut()
|
||||
}
|
||||
|
||||
pub fn new(mut reader: R) -> Result<Self> {
|
||||
let mut buf = vec![0; size_of::<Superblock>()];
|
||||
reader.read(&mut buf, Superblock::SUPERBLOCK_BASE_OFFSET as _)?;
|
||||
let superblock = Superblock::parse(&buf)?;
|
||||
let chunk_cache = Self::bootstrap_chunk_tree(&superblock)?;
|
||||
Ok(Self {
|
||||
reader,
|
||||
let mut new = Self {
|
||||
reader: Box::new(RefCell::new(reader)),
|
||||
superblock,
|
||||
chunk_cache,
|
||||
})
|
||||
roots: Default::default(),
|
||||
};
|
||||
|
||||
new.parse_chunk_tree()?;
|
||||
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
pub fn size_from_logical(&self, logical: u64) -> Option<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];
|
||||
self.reader.read(&mut buf, range.start)?;
|
||||
self.reader().read(&mut buf, range.start)?;
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub fn asdf(&mut self) -> Result<()> {
|
||||
let chunk_root = self.superblock.chunk_root;
|
||||
fn parse_chunk_node(&mut self, chunk: Vec<u8>) -> Result<()> {
|
||||
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");
|
||||
|
||||
log::debug!("chunk_root: {chunk_root}");
|
||||
|
@ -187,15 +285,12 @@ impl<R: VolumeIo> Volume<R> {
|
|||
|
||||
let root = self.read_range(physical)?;
|
||||
|
||||
let node = BTreeNode::parse(&root)?;
|
||||
|
||||
log::debug!("{node:#?}");
|
||||
|
||||
self.parse_chunk_node(root)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bootstrap_chunk_tree(superblock: &Superblock) -> Result<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 key_size = size_of::<Key>();
|
||||
|
@ -209,19 +304,20 @@ impl<R: VolumeIo> Volume<R> {
|
|||
return Err(Error::InvalidOffset);
|
||||
}
|
||||
|
||||
let key = bytes.gread_with::<Key>(&mut offset, scroll::LE)?;
|
||||
if key.ty.as_type() != Some(ObjectType::ChunkItem) {
|
||||
let key = bytes.gread::<Key>(&mut offset)?;
|
||||
if key.ty.as_type() != ObjectType::ChunkItem {
|
||||
log::error!("key is not of type ChunkItem");
|
||||
return Err(Error::InvalidOffset);
|
||||
}
|
||||
|
||||
let chunk = bytes.gread_with::<Chunk>(&mut offset, scroll::LE)?;
|
||||
if chunk.num_stripes == 0 {
|
||||
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);
|
||||
}
|
||||
|
||||
let num_stripes = chunk.num_stripes; // copy to prevent unaligned access
|
||||
if num_stripes != 1 {
|
||||
log::warn!(
|
||||
"warning: {} stripes detected but only processing 1",
|
||||
|
@ -229,11 +325,14 @@ impl<R: VolumeIo> Volume<R> {
|
|||
);
|
||||
}
|
||||
|
||||
let key_offset = key.offset.get();
|
||||
let chunk_length = chunk.length.get();
|
||||
|
||||
match chunk_tree.entry(ChunkTreeKey {
|
||||
range: key.offset..(key.offset + chunk.length),
|
||||
range: key_offset..(key_offset + chunk_length),
|
||||
}) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(chunk.stripe.offset);
|
||||
entry.insert(chunk.stripe.offset.get());
|
||||
}
|
||||
Entry::Occupied(_) => {
|
||||
log::error!("overlapping stripes!");
|
||||
|
@ -251,6 +350,26 @@ impl<R: VolumeIo> Volume<R> {
|
|||
Ok(chunk_tree)
|
||||
}
|
||||
|
||||
pub fn iter_roots(&self) {
|
||||
let root_tree =
|
||||
Tree::from_logical_offset(self, self.superblock().root.get()).expect("tree");
|
||||
|
||||
let range = root_tree.full_range();
|
||||
|
||||
for (item, value) in range {
|
||||
if item.key.ty.as_type() == ObjectType::RootItem {
|
||||
let root = value.as_root().expect("root");
|
||||
let tree = Tree::from_logical_offset(self, root.bytenr.get()).expect("tree");
|
||||
|
||||
log::info!("some tree idek which one innit");
|
||||
for (i, v) in tree.full_range() {
|
||||
log::info!("{i:?}");
|
||||
log::info!("{v:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn superblock(&self) -> &Superblock {
|
||||
&self.superblock
|
||||
}
|
||||
|
|
|
@ -3,17 +3,59 @@ use core::{mem::size_of, ops::Deref};
|
|||
use bytemuck::{Pod, Zeroable};
|
||||
use derivative::Derivative;
|
||||
use num_enum::{FromPrimitive, TryFromPrimitive};
|
||||
use scroll::{
|
||||
ctx::{SizeWith, TryFromCtx},
|
||||
Pread, SizeWith,
|
||||
};
|
||||
use zerocopy::{AsBytes, FromBytes};
|
||||
use scroll::{ctx::TryFromCtx, Pread, SizeWith};
|
||||
use zerocopy::{byteorder::LE, AsBytes, FromBytes, U16, U32, U64};
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive)]
|
||||
#[repr(u64)]
|
||||
pub enum KnownObjectId {
|
||||
RootTree = 1,
|
||||
ExtentTree,
|
||||
ChunkTree,
|
||||
DevTree,
|
||||
FsTree,
|
||||
RootTreeDir,
|
||||
CsumTree,
|
||||
QuotaTree,
|
||||
UuidTree,
|
||||
FreeSpaceTree,
|
||||
DataRelocTree = u64::MAX - 9,
|
||||
TreeReloc = u64::MAX - 8,
|
||||
TreeLog = u64::MAX - 7,
|
||||
Orphan = u64::MAX - 5,
|
||||
#[num_enum(catch_all)]
|
||||
Custom(u64),
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, FromBytes, AsBytes)]
|
||||
pub struct ObjectId {
|
||||
inner: U64<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)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive)]
|
||||
//#[rustc_nonnull_optimization_guaranteed]
|
||||
pub enum ObjectType {
|
||||
INodeItem = 0x01,
|
||||
|
@ -46,25 +88,25 @@ pub enum ObjectType {
|
|||
DevStats = 0xF9,
|
||||
SubvolUuid = 0xFB,
|
||||
SubvolRecUuid = 0xFC,
|
||||
#[num_enum(catch_all)]
|
||||
Invalid(u8),
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Pread, SizeWith)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Pread, SizeWith, FromBytes, AsBytes)]
|
||||
pub struct ObjectTypeWrapper {
|
||||
inner: u8,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for ObjectTypeWrapper {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("ObjectTypeWrapper")
|
||||
.field("inner", &self.as_type())
|
||||
.finish()
|
||||
write!(f, "{:?}", self.as_type())
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectTypeWrapper {
|
||||
pub fn as_type(self) -> Option<ObjectType> {
|
||||
ObjectType::try_from_primitive(self.inner).ok()
|
||||
pub fn as_type(self) -> ObjectType {
|
||||
ObjectType::from_primitive(self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,24 +114,12 @@ unsafe impl Pod for ObjectTypeWrapper {}
|
|||
unsafe impl Zeroable for ObjectTypeWrapper {}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Uuid(uuid::Uuid);
|
||||
#[derive(Clone, Copy, PartialEq, Eq, FromBytes, AsBytes)]
|
||||
pub struct Uuid(uuid::Bytes);
|
||||
|
||||
impl SizeWith<scroll::Endian> for Uuid {
|
||||
fn size_with(_: &scroll::Endian) -> usize {
|
||||
size_of::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
impl core::fmt::Debug for Uuid {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
uuid::Uuid::from_bytes_ref(&self.0).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +127,7 @@ impl Deref for Uuid {
|
|||
type Target = uuid::Uuid;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
uuid::Uuid::from_bytes_ref(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,43 +135,81 @@ unsafe impl Pod for Uuid {}
|
|||
unsafe impl Zeroable for Uuid {}
|
||||
|
||||
#[repr(C, packed(1))]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)]
|
||||
#[derive(Debug, Clone, Copy, Eq, FromBytes, AsBytes)]
|
||||
pub struct Key {
|
||||
pub id: u64,
|
||||
pub id: ObjectId,
|
||||
pub ty: ObjectTypeWrapper,
|
||||
pub offset: u64,
|
||||
pub offset: U64<LE>,
|
||||
}
|
||||
|
||||
#[repr(C, packed(1))]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
pub struct TreeHeader {
|
||||
csum: [u8; 32],
|
||||
fs_uuid: Uuid,
|
||||
address: u64,
|
||||
flags: u64,
|
||||
chunk_tree_uuid: Uuid,
|
||||
generation: u64,
|
||||
tree_id: u64,
|
||||
num_items: u32,
|
||||
level: u8,
|
||||
impl Key {
|
||||
pub fn ty(&self) -> ObjectType {
|
||||
self.ty.as_type()
|
||||
}
|
||||
pub fn id(&self) -> KnownObjectId {
|
||||
self.id.as_id()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed(1))]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
pub struct LeafNode {
|
||||
key: Key,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
impl PartialEq for Key {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id() == other.id() && self.ty() == other.ty() && self.offset == other.offset
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed(1))]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
pub struct InternalNode {
|
||||
key: Key,
|
||||
address: u64,
|
||||
generation: u64,
|
||||
impl Ord for Key {
|
||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
self.partial_cmp(other).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Key {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<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 SYS_CHUNK_ARRAY_SIZE: usize = 0x800;
|
||||
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))]
|
||||
#[derive(Derivative, Clone, Copy, Pod, Zeroable)]
|
||||
#[derive(Derivative, Clone, Copy, FromBytes, AsBytes)]
|
||||
#[derivative(Debug)]
|
||||
pub struct INodeItem {
|
||||
generation: u64,
|
||||
transid: u64,
|
||||
st_size: u64,
|
||||
st_blocks: u64,
|
||||
block_group: u64,
|
||||
st_nlink: u32,
|
||||
st_uid: u32,
|
||||
st_gid: u32,
|
||||
st_mode: u32,
|
||||
st_rdev: u64,
|
||||
flags: u64,
|
||||
sequence: u64,
|
||||
generation: U64<LE>,
|
||||
transid: U64<LE>,
|
||||
st_size: U64<LE>,
|
||||
st_blocks: U64<LE>,
|
||||
block_group: U64<LE>,
|
||||
st_nlink: U32<LE>,
|
||||
st_uid: U32<LE>,
|
||||
st_gid: U32<LE>,
|
||||
st_mode: U32<LE>,
|
||||
st_rdev: U64<LE>,
|
||||
flags: U64<LE>,
|
||||
sequence: U64<LE>,
|
||||
#[derivative(Debug = "ignore")]
|
||||
reserved: [u8; 32],
|
||||
st_atime: Timespec,
|
||||
|
@ -251,7 +319,7 @@ pub struct ExtentItem {
|
|||
}
|
||||
|
||||
#[repr(C, packed(1))]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ExtentItem2 {
|
||||
first_item: Key,
|
||||
level: u8,
|
||||
|
@ -264,7 +332,7 @@ pub struct ExtentItemV0 {
|
|||
}
|
||||
|
||||
#[repr(C, packed(1))]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ExtentItemTree {
|
||||
extent_item: ExtentItem,
|
||||
first_item: Key,
|
||||
|
@ -325,7 +393,7 @@ pub struct FreeSpaceEntry {
|
|||
}
|
||||
|
||||
#[repr(C, packed(1))]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FreeSpaceItem {
|
||||
key: Key,
|
||||
|
||||
|
@ -392,28 +460,28 @@ pub const CHUNK_ITEM_KEY: u8 = 228;
|
|||
pub const FT_REG_FILE: u8 = 1;
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)]
|
||||
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
|
||||
pub struct DevItem {
|
||||
/// the internal btrfs device id
|
||||
pub devid: u64,
|
||||
pub devid: U64<LE>,
|
||||
/// size of the device
|
||||
pub total_bytes: u64,
|
||||
pub total_bytes: U64<LE>,
|
||||
/// bytes used
|
||||
pub bytes_used: u64,
|
||||
pub bytes_used: U64<LE>,
|
||||
/// optimal io alignment for this device
|
||||
pub io_align: u32,
|
||||
pub io_align: U32<LE>,
|
||||
/// optimal io width for this device
|
||||
pub io_width: u32,
|
||||
pub io_width: U32<LE>,
|
||||
/// minimal io size for this device
|
||||
pub sector_size: u32,
|
||||
pub sector_size: U32<LE>,
|
||||
/// type and info about this device
|
||||
pub ty: u64,
|
||||
pub ty: U64<LE>,
|
||||
/// 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
|
||||
pub start_offset: u64,
|
||||
pub start_offset: U64<LE>,
|
||||
/// grouping information for allocation decisions
|
||||
pub dev_group: u32,
|
||||
pub dev_group: U32<LE>,
|
||||
/// seek speed 0-100 where 100 is fastest
|
||||
pub seek_speed: u8,
|
||||
/// bandwidth 0-100 where 100 is fastest
|
||||
|
@ -425,23 +493,23 @@ pub struct DevItem {
|
|||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)]
|
||||
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
|
||||
pub struct RootBackup {
|
||||
pub tree_root: u64,
|
||||
pub tree_root_gen: u64,
|
||||
pub chunk_root: u64,
|
||||
pub chunk_root_gen: u64,
|
||||
pub extent_root: u64,
|
||||
pub extent_root_gen: u64,
|
||||
pub fs_root: u64,
|
||||
pub fs_root_gen: u64,
|
||||
pub dev_root: u64,
|
||||
pub dev_root_gen: u64,
|
||||
pub csum_root: u64,
|
||||
pub csum_root_gen: u64,
|
||||
pub total_bytes: u64,
|
||||
pub bytes_used: u64,
|
||||
pub num_devices: u64,
|
||||
pub tree_root: U64<LE>,
|
||||
pub tree_root_gen: U64<LE>,
|
||||
pub chunk_root: U64<LE>,
|
||||
pub chunk_root_gen: U64<LE>,
|
||||
pub extent_root: U64<LE>,
|
||||
pub extent_root_gen: U64<LE>,
|
||||
pub fs_root: U64<LE>,
|
||||
pub fs_root_gen: U64<LE>,
|
||||
pub dev_root: U64<LE>,
|
||||
pub dev_root_gen: U64<LE>,
|
||||
pub csum_root: U64<LE>,
|
||||
pub csum_root_gen: U64<LE>,
|
||||
pub total_bytes: U64<LE>,
|
||||
pub bytes_used: U64<LE>,
|
||||
pub num_devices: U64<LE>,
|
||||
/// future
|
||||
pub unused_64: [u64; 4],
|
||||
pub tree_root_level: u8,
|
||||
|
@ -455,55 +523,56 @@ pub struct RootBackup {
|
|||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Derivative, Clone, Copy, Pod, Zeroable, Pread, SizeWith)]
|
||||
#[derive(Derivative, Clone, Copy, FromBytes, AsBytes)]
|
||||
#[derivative(Debug)]
|
||||
pub struct Superblock {
|
||||
pub csum: [u8; 32],
|
||||
pub fsid: Uuid,
|
||||
/// Physical address of this block
|
||||
pub bytenr: u64,
|
||||
pub flags: u64,
|
||||
pub bytenr: U64<LE>,
|
||||
pub flags: U64<LE>,
|
||||
pub magic: [u8; 0x8],
|
||||
pub generation: u64,
|
||||
pub generation: U64<LE>,
|
||||
/// Logical address of the root tree root
|
||||
pub root: u64,
|
||||
pub root: U64<LE>,
|
||||
/// Logical address of the chunk tree root
|
||||
pub chunk_root: u64,
|
||||
pub chunk_root: U64<LE>,
|
||||
/// Logical address of the log tree root
|
||||
pub log_root: u64,
|
||||
pub log_root_transid: u64,
|
||||
pub total_bytes: u64,
|
||||
pub bytes_used: u64,
|
||||
pub root_dir_objectid: u64,
|
||||
pub num_devices: u64,
|
||||
pub sector_size: u32,
|
||||
pub node_size: u32,
|
||||
pub log_root: U64<LE>,
|
||||
pub log_root_transid: U64<LE>,
|
||||
pub total_bytes: U64<LE>,
|
||||
pub bytes_used: U64<LE>,
|
||||
pub root_dir_objectid: U64<LE>,
|
||||
pub num_devices: U64<LE>,
|
||||
pub sector_size: U32<LE>,
|
||||
pub node_size: U32<LE>,
|
||||
/// Unused and must be equal to `nodesize`
|
||||
pub leafsize: u32,
|
||||
pub stripesize: u32,
|
||||
pub sys_chunk_array_size: u32,
|
||||
pub chunk_root_generation: u64,
|
||||
pub compat_flags: u64,
|
||||
pub compat_ro_flags: u64,
|
||||
pub incompat_flags: u64,
|
||||
pub csum_type: u16,
|
||||
pub leafsize: U32<LE>,
|
||||
pub stripesize: U32<LE>,
|
||||
pub sys_chunk_array_size: U32<LE>,
|
||||
pub chunk_root_generation: U64<LE>,
|
||||
pub compat_flags: U64<LE>,
|
||||
pub compat_ro_flags: U64<LE>,
|
||||
pub incompat_flags: U64<LE>,
|
||||
pub csum_type: U16<LE>,
|
||||
pub root_level: u8,
|
||||
pub chunk_root_level: u8,
|
||||
pub log_root_level: u8,
|
||||
pub dev_item: DevItem,
|
||||
#[derivative(Debug(format_with = "format_u8str"))]
|
||||
pub label: [u8; 0x100],
|
||||
pub cache_generation: u64,
|
||||
pub uuid_tree_generation: u64,
|
||||
pub cache_generation: U64<LE>,
|
||||
pub uuid_tree_generation: U64<LE>,
|
||||
pub metadata_uuid: Uuid,
|
||||
/// Future expansion
|
||||
reserved: [u64; 28],
|
||||
#[derivative(Debug = "ignore")]
|
||||
_reserved: [u64; 28],
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub sys_chunk_array: [u8; 0x800],
|
||||
pub root_backup0: RootBackup,
|
||||
pub root_backup1: RootBackup,
|
||||
pub root_backup2: RootBackup,
|
||||
pub root_backup3: RootBackup,
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub root_backups: [RootBackup; 4],
|
||||
#[derivative(Debug = "ignore")]
|
||||
_reserved2: [u8; 565],
|
||||
}
|
||||
|
||||
#[repr(u16)]
|
||||
|
@ -516,6 +585,13 @@ pub enum ChecksumType {
|
|||
Blake2B,
|
||||
}
|
||||
|
||||
fn calculate_crc32c(bytes: &[u8]) -> [u8; 32] {
|
||||
let crc = crc::Crc::<u32>::new(&crc::CRC_32_ISCSI);
|
||||
let mut csum = [0u8; 32];
|
||||
csum[..4].copy_from_slice(crc.checksum(bytes).as_bytes());
|
||||
csum
|
||||
}
|
||||
|
||||
impl Superblock {
|
||||
pub const SUPERBLOCK_BASE_OFFSET: usize = 0x10000;
|
||||
pub const SUPERBLOCK_OFFSETS: [usize; 4] = [
|
||||
|
@ -527,17 +603,37 @@ impl Superblock {
|
|||
pub const MAGIC: [u8; 8] = *b"_BHRfS_M";
|
||||
|
||||
pub fn parse(bytes: &[u8]) -> Result<Self> {
|
||||
let superblock = bytes.pread_with::<Self>(0, scroll::LE)?;
|
||||
let superblock = Self::read_from(bytes).ok_or(Error::ReadFailed)?;
|
||||
|
||||
if !superblock.verify_magic() {
|
||||
return Err(Error::InvalidMagic);
|
||||
}
|
||||
|
||||
if !superblock.verify_checksum() {
|
||||
return Err(Error::InvalidChecksum {
|
||||
expected: superblock.csum,
|
||||
actual: superblock.calculate_checksum(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(superblock)
|
||||
}
|
||||
|
||||
pub fn calculate_checksum(&self) -> [u8; 32] {
|
||||
match self.checksum_type().expect("csum type invalid") {
|
||||
ChecksumType::Crc32 => calculate_crc32c(&self.as_bytes()[0x20..]),
|
||||
ChecksumType::XxHash64 => todo!(),
|
||||
ChecksumType::Sha256 => todo!(),
|
||||
ChecksumType::Blake2B => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_checksum(&self) -> bool {
|
||||
self.calculate_checksum() == self.csum
|
||||
}
|
||||
|
||||
pub fn checksum_type(&self) -> Option<ChecksumType> {
|
||||
ChecksumType::try_from_primitive(self.csum_type).ok()
|
||||
ChecksumType::try_from_primitive(self.csum_type.get()).ok()
|
||||
}
|
||||
|
||||
pub fn verify_magic(&self) -> bool {
|
||||
|
@ -546,73 +642,67 @@ impl Superblock {
|
|||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)]
|
||||
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
|
||||
pub struct Stripe {
|
||||
pub devid: u64,
|
||||
pub offset: u64,
|
||||
pub devid: U64<LE>,
|
||||
pub offset: U64<LE>,
|
||||
pub dev_uuid: Uuid,
|
||||
}
|
||||
|
||||
impl Stripe {
|
||||
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)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)]
|
||||
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
|
||||
pub struct Chunk {
|
||||
/// size of this chunk in bytes
|
||||
pub length: u64,
|
||||
pub length: U64<LE>,
|
||||
/// objectid of the root referencing this chunk
|
||||
pub owner: u64,
|
||||
pub stripe_len: u64,
|
||||
pub ty: u64,
|
||||
pub owner: U64<LE>,
|
||||
pub stripe_len: U64<LE>,
|
||||
pub ty: U64<LE>,
|
||||
/// optimal io alignment for this chunk
|
||||
pub io_align: u32,
|
||||
pub io_align: U32<LE>,
|
||||
/// optimal io width for this chunk
|
||||
pub io_width: u32,
|
||||
pub io_width: U32<LE>,
|
||||
/// 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
|
||||
pub num_stripes: u16,
|
||||
pub num_stripes: U16<LE>,
|
||||
/// sub stripes only matter for raid10
|
||||
pub sub_stripes: u16,
|
||||
pub sub_stripes: U16<LE>,
|
||||
pub stripe: Stripe,
|
||||
// additional stripes go here
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn parse(bytes: &[u8]) -> Result<Self> {
|
||||
Ok(bytes.pread_with(0, scroll::LE)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)]
|
||||
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
|
||||
pub struct Timespec {
|
||||
pub sec: u64,
|
||||
pub nsec: u32,
|
||||
pub sec: U64<LE>,
|
||||
pub nsec: U32<LE>,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
|
||||
pub struct InodeItem {
|
||||
/// nfs style generation number
|
||||
pub generation: u64,
|
||||
pub generation: U64<LE>,
|
||||
/// transid that last touched this inode
|
||||
pub transid: u64,
|
||||
pub size: u64,
|
||||
pub nbytes: u64,
|
||||
pub block_group: u64,
|
||||
pub nlink: u32,
|
||||
pub uid: u32,
|
||||
pub gid: u32,
|
||||
pub mode: u32,
|
||||
pub rdev: u64,
|
||||
pub flags: u64,
|
||||
pub transid: U64<LE>,
|
||||
pub size: U64<LE>,
|
||||
pub nbytes: U64<LE>,
|
||||
pub block_group: U64<LE>,
|
||||
pub nlink: U32<LE>,
|
||||
pub uid: U32<LE>,
|
||||
pub gid: U32<LE>,
|
||||
pub mode: U32<LE>,
|
||||
pub rdev: U64<LE>,
|
||||
pub flags: U64<LE>,
|
||||
/// modification sequence number for NFS
|
||||
pub sequence: u64,
|
||||
pub sequence: U64<LE>,
|
||||
pub reserved: [u64; 4],
|
||||
pub atime: Timespec,
|
||||
pub ctime: Timespec,
|
||||
|
@ -621,32 +711,32 @@ pub struct InodeItem {
|
|||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
|
||||
pub struct RootItem {
|
||||
pub inode: InodeItem,
|
||||
pub generation: u64,
|
||||
pub root_dirid: u64,
|
||||
pub bytenr: u64,
|
||||
pub byte_limit: u64,
|
||||
pub bytes_used: u64,
|
||||
pub last_snapshot: u64,
|
||||
pub flags: u64,
|
||||
pub refs: u32,
|
||||
pub generation: U64<LE>,
|
||||
pub root_dirid: U64<LE>,
|
||||
pub bytenr: U64<LE>,
|
||||
pub byte_limit: U64<LE>,
|
||||
pub bytes_used: U64<LE>,
|
||||
pub last_snapshot: U64<LE>,
|
||||
pub flags: U64<LE>,
|
||||
pub refs: U32<LE>,
|
||||
pub drop_progress: Key,
|
||||
pub drop_level: u8,
|
||||
pub level: u8,
|
||||
pub generation_v2: u64,
|
||||
pub generation_v2: U64<LE>,
|
||||
pub uuid: Uuid,
|
||||
pub parent_uuid: Uuid,
|
||||
pub received_uuid: Uuid,
|
||||
/// updated when an inode changes
|
||||
pub ctransid: u64,
|
||||
pub ctransid: U64<LE>,
|
||||
/// trans when created
|
||||
pub otransid: u64,
|
||||
pub otransid: U64<LE>,
|
||||
/// trans when sent. non-zero for received subvol
|
||||
pub stransid: u64,
|
||||
pub stransid: U64<LE>,
|
||||
/// trans when received. non-zero for received subvol
|
||||
pub rtransid: u64,
|
||||
pub rtransid: U64<LE>,
|
||||
pub ctime: Timespec,
|
||||
pub otime: Timespec,
|
||||
pub stime: Timespec,
|
||||
|
@ -654,14 +744,95 @@ pub struct RootItem {
|
|||
pub reserved: [u64; 8],
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)]
|
||||
pub enum DirItemType {
|
||||
Unknown,
|
||||
RegFile,
|
||||
Dir,
|
||||
ChrDev,
|
||||
BlkDev,
|
||||
Fifo,
|
||||
Sock,
|
||||
Symlink,
|
||||
Xattr,
|
||||
#[num_enum(catch_all)]
|
||||
Invalid(u8),
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
|
||||
pub struct DirItem {
|
||||
pub location: Key,
|
||||
pub transid: u64,
|
||||
pub data_len: u16,
|
||||
pub name_len: u16,
|
||||
pub ty: u8,
|
||||
pub transid: U64<LE>,
|
||||
pub data_len: U16<LE>,
|
||||
pub name_len: U16<LE>,
|
||||
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)]
|
||||
|
@ -672,101 +843,179 @@ pub struct InodeRef {
|
|||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Copy, FromBytes, AsBytes)]
|
||||
pub struct Header {
|
||||
pub csum: [u8; 32],
|
||||
pub fsid: Uuid,
|
||||
/// Which block this node is supposed to live in
|
||||
pub bytenr: u64,
|
||||
pub flags: u64,
|
||||
pub bytenr: U64<LE>,
|
||||
pub flags: U64<LE>,
|
||||
pub chunk_tree_uuid: Uuid,
|
||||
pub generation: u64,
|
||||
pub owner: u64,
|
||||
pub nritems: u32,
|
||||
pub generation: U64<LE>,
|
||||
pub owner: U64<LE>,
|
||||
pub nritems: U32<LE>,
|
||||
pub level: u8,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)]
|
||||
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
|
||||
/// A `BtrfsLeaf` is full of `BtrfsItem`s. `offset` and `size` (relative to start of data area)
|
||||
/// tell us where to find the item in the leaf.
|
||||
pub struct Item {
|
||||
pub key: Key,
|
||||
pub offset: u32,
|
||||
pub size: u32,
|
||||
pub offset: U32<LE>,
|
||||
pub size: U32<LE>,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
pub struct Leaf {
|
||||
pub header: Header,
|
||||
// `Item`s begin here
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)]
|
||||
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
|
||||
/// All non-leaf blocks are nodes and they hold only keys are pointers to other blocks
|
||||
pub struct KeyPtr {
|
||||
pub key: Key,
|
||||
pub blockptr: u64,
|
||||
pub generation: u64,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
pub struct Node {
|
||||
pub header: Header,
|
||||
// `KeyPtr`s begin here
|
||||
pub blockptr: U64<LE>,
|
||||
pub generation: U64<LE>,
|
||||
}
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
#[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 {
|
||||
header: Header,
|
||||
pub header: Header,
|
||||
/// actual leaf data
|
||||
items: Vec<Item>,
|
||||
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() {
|
||||
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 {
|
||||
None
|
||||
}
|
||||
})
|
||||
.take(header.nritems as usize)
|
||||
.collect::<core::result::Result<Vec<_>, _>>()?;
|
||||
.take(header.nritems.get() as usize)
|
||||
.collect::<Vec<_>>();
|
||||
Ok(Self { header, items })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BTreeInternalNode {
|
||||
header: Header,
|
||||
children: Vec<KeyPtr>,
|
||||
pub header: Header,
|
||||
pub children: Vec<KeyPtr>,
|
||||
}
|
||||
|
||||
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_of::<KeyPtr>() < bytes.len() {
|
||||
Some(bytes.gread_with::<KeyPtr>(offset, scroll::LE))
|
||||
if *offset as usize + size < bytes.len() {
|
||||
let item = KeyPtr::read_from(&bytes[*offset..*offset + size]);
|
||||
*offset += size;
|
||||
|
||||
if let Some(item) = item.as_ref() {
|
||||
log::debug!(
|
||||
"\tchild gen: {} offset: {}",
|
||||
item.generation.get(),
|
||||
item.key.offset.get()
|
||||
);
|
||||
}
|
||||
|
||||
item
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.take(header.nritems as usize)
|
||||
.collect::<core::result::Result<Vec<_>, _>>()?;
|
||||
.take(header.nritems.get() as usize)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Self { header, children })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BTreeNode {
|
||||
Internal(BTreeInternalNode),
|
||||
Leaf(BTreeLeafNode),
|
||||
|
@ -775,7 +1024,7 @@ pub enum BTreeNode {
|
|||
impl BTreeNode {
|
||||
pub fn parse(bytes: &[u8]) -> Result<Self> {
|
||||
let offset = &mut 0;
|
||||
let header = bytes.gread_with::<Header>(offset, scroll::LE)?;
|
||||
let header = bytes.gread::<Header>(offset)?;
|
||||
|
||||
if header.level == 0 {
|
||||
Ok(Self::Leaf(BTreeLeafNode::parse(header, &bytes[*offset..])?))
|
||||
|
|
480
btrfs/src/tree.rs
Normal file
480
btrfs/src/tree.rs
Normal 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())
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
|
||||
use test_log::test;
|
||||
|
||||
use btrfs::{structs::*, Volume};
|
||||
use btrfs::{structs::*, tree::Tree, Volume};
|
||||
|
||||
#[test]
|
||||
fn superblock() {
|
||||
|
@ -17,6 +17,9 @@ fn superblock() {
|
|||
let sb = volume.superblock();
|
||||
|
||||
println!("{sb:#?}");
|
||||
|
||||
assert!(sb.verify_magic());
|
||||
assert!(sb.verify_checksum());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -40,5 +43,7 @@ fn iter_root() {
|
|||
let mut volume = Volume::new(reader).expect("volume");
|
||||
let sb = volume.superblock();
|
||||
|
||||
volume.asdf();
|
||||
volume.iter_roots();
|
||||
|
||||
println!("{:#?}", volume.chunk_cache);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue