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