lots of changes everywhere

This commit is contained in:
Florian Stecker 2023-09-15 09:38:20 -04:00
parent 4d1e08863a
commit d5f7ff4c14
10 changed files with 799 additions and 165 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
Cargo.lock

View File

@ -3,10 +3,7 @@ name = "parsebtrfs"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1.0.72"
binparse_derive = { path = "../binparse_derive" } binparse_derive = { path = "../binparse_derive" }
memmap2 = "0.7.1" memmap2 = "0.7.1"
rouille = "3.6.2" rouille = "3.6.2"

BIN
circle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

View File

@ -1,7 +1,10 @@
use crate::btrfs_structs::{ParseBin, Key, ChunkItem, Leaf, Value, Superblock, LogToPhys, ParseError}; use std::rc::Rc;
#[derive(Debug)] use crate::btrfs_structs::{ParseBin, Key, ChunkItem, Value, Superblock, ParseError, NODE_SIZE};
pub struct AddressMap(Vec<(u64,u64,Vec<(u64,u64)>)>); use crate::btrfs_lookup::Tree;
#[derive(Debug, Clone)]
pub struct AddressMap(pub Vec<(u64,u64,Vec<(u64,u64)>)>);
// TODO: support for internal nodes, multiple devices? // TODO: support for internal nodes, multiple devices?
impl AddressMap { impl AddressMap {
@ -9,16 +12,15 @@ impl AddressMap {
let superblock = Superblock::parse(&image[0x10000..])?; let superblock = Superblock::parse(&image[0x10000..])?;
let bootstrap_addr = AddressMap::from_superblock(&superblock)?; let bootstrap_addr = AddressMap::from_superblock(&superblock)?;
let chunk_root_log = superblock.chunk_root; let chunk_tree = Tree {
println!("Chunk Tree Root Logical Address: {:016x}", chunk_root_log); image: image,
addr_map: Rc::new(bootstrap_addr),
let chunk_root_phys = bootstrap_addr.to_phys(chunk_root_log).unwrap(); root_addr_log: superblock.chunk_root,
println!("Chunk Tree Root Physical Address: {:016x}", chunk_root_phys); };
let chunk_root = Leaf::parse(&image[chunk_root_phys as usize..])?;
let mut addr_map = Vec::new(); let mut addr_map = Vec::new();
for item in chunk_root.items {
for item in chunk_tree.iter() {
let chunk_key = item.key; let chunk_key = item.key;
if let Value::Chunk(chunk_value) = item.value { if let Value::Chunk(chunk_value) = item.value {
addr_map.push(( addr_map.push((
@ -31,8 +33,11 @@ impl AddressMap {
} }
} }
// almost certainly unnecessary?
addr_map.sort_by_key(|x|x.0); addr_map.sort_by_key(|x|x.0);
println!("Address Table: {:?}", addr_map);
// addr_map.iter().for_each(|x| println!("{:x?}", x));
Ok(AddressMap(addr_map)) Ok(AddressMap(addr_map))
} }
@ -57,7 +62,6 @@ impl AddressMap {
} }
addr_map.sort_by_key(|x|x.0); addr_map.sort_by_key(|x|x.0);
println!("Bootstrap Address Table: {:?}", addr_map);
Ok(AddressMap(addr_map)) Ok(AddressMap(addr_map))
} }
} }
@ -84,3 +88,16 @@ impl LogToPhys for AddressMap {
} }
} }
} }
pub fn node_at_log<'a, T: LogToPhys>(image: &'a [u8], addr: &T, log: u64) -> Result<&'a [u8], ParseError> {
if let Some(phys_addr) = addr.to_phys(log) {
Ok(&image[phys_addr as usize .. phys_addr as usize + NODE_SIZE])
} else {
err!("Logical address {:x} could not be translated to physical address", log)
}
}
pub trait LogToPhys {
fn to_phys(&self, log: u64) -> Option<u64>;
}

275
src/btrfs_lookup.rs Normal file
View File

@ -0,0 +1,275 @@
use std::rc::Rc;
use std::ops::Deref;
use crate::btrfs_structs::{Leaf, Key, Item, InteriorNode, Node, ParseError, ParseBin, Value, Superblock, ItemType};
use crate::addrmap::{node_at_log, LogToPhys, AddressMap};
pub struct Tree<'a> {
pub image: &'a [u8],
pub addr_map: Rc<AddressMap>,
pub root_addr_log: u64,
}
impl<'a> Tree<'a> {
pub fn new<T: Into<u64>>(image: &'a [u8], tree_id: T) -> Result<Tree<'a>, ParseError> {
let addr_map = Rc::new(AddressMap::new(image)?);
let superblock = Superblock::parse(&image[0x10000..])?;
let root_tree = Tree {
image: image,
addr_map: Rc::clone(&addr_map),
root_addr_log: superblock.root
};
let tree_root_item = root_tree.find_key(Key::new(tree_id.into(), ItemType::Root, 0))?;
let root_addr_log = match tree_root_item.value {
Value::Root(root) => root.bytenr,
_ => return Err("root item invalid".into())
};
Ok(Tree { image, addr_map, root_addr_log })
}
pub fn root(image: &'a [u8]) -> Result<Tree<'a>, ParseError> {
let addr_map = Rc::new(AddressMap::new(image)?);
let superblock = Superblock::parse(&image[0x10000..])?;
Ok(Tree { image, addr_map, root_addr_log: superblock.root })
}
}
/***** looking up keys *****/
impl Leaf {
pub fn find_key(&self, key: Key) -> Option<Item> {
self.items
.iter()
.find(|x|x.key == key)
.map(|x|x.clone())
}
pub fn find_key_or_previous(&self, key: Key) -> Option<Item> {
self.items
.iter()
.take_while(|x|x.key <= key)
.last()
.map(|x|x.clone())
}
}
impl InteriorNode {
pub fn find_key_or_previous(&self, key: Key) -> Option<u64> {
self.children
.iter()
.take_while(|x|x.key <= key)
.last()
.map(|x|x.ptr)
}
}
fn find_key_in_node<T: LogToPhys>(image: &[u8], addr: &T, root_addr_log: u64, key: Key) -> Result<Item, ParseError> {
let node = Node::parse(node_at_log(image, addr, root_addr_log)?)?;
match node {
Node::Interior(interior_node) => {
let next_node_log = interior_node.find_key_or_previous(key).unwrap();
find_key_in_node(image, addr, next_node_log, key)
},
Node::Leaf(leaf) => {
leaf.find_key(key).ok_or(
error!(
"Item with key ({},{:?},{}) was not found in the leaf at logical address 0x{:x}",
key.key_id, key.key_type, key.key_offset, root_addr_log)
)
}
}
}
impl Tree<'_> {
pub fn find_key(&self, key: Key) -> Result<Item, ParseError> {
find_key_in_node(self.image, self.addr_map.deref(), self.root_addr_log, key)
}
}
/***** iterator *****/
pub struct Iter<'a> {
tree: &'a Tree<'a>,
// path to the last returned item
nodes: Vec<InteriorNode>,
leaf: Option<Box<Leaf>>,
indices: Vec<usize>,
lower_limit: Option<Key>,
upper_limit: Option<Key>,
}
impl Tree<'_> {
pub fn iter<'a>(&'a self) -> Iter<'a> {
self.range(None, None)
}
pub fn range<'a>(&'a self, lower: Option<Key>, upper: Option<Key>) -> Iter<'a> {
Iter {
tree: self,
nodes: Vec::new(),
leaf: None,
indices: Vec::new(), // in nodes and leaf
lower_limit: lower,
upper_limit: upper,
}
}
pub fn range_id<'a>(&'a self, id: u64) -> Iter<'a> {
if id == u64::MAX {
self.range(
Some(Key::new(id, ItemType::Invalid, 0)),
None
)
} else {
self.range(
Some(Key::new(id, ItemType::Invalid, 0)),
Some(Key::new(id+1, ItemType::Invalid, 0))
)
}
}
}
impl Iter<'_> {
fn move_down_and_get_first_item(&mut self, mut node_addr: u64) -> Option<Item> {
loop {
let node = Node::parse(node_at_log(self.tree.image, self.tree.addr_map.deref(), node_addr).ok()?).ok()?;
match node {
Node::Interior(int_node) => {
node_addr = int_node.children.first()?.ptr;
self.nodes.push(int_node);
self.indices.push(0);
},
Node::Leaf(leaf_node) => {
let result = leaf_node.items.first()?.clone();
self.leaf = Some(Box::new(leaf_node));
self.indices.push(0);
return Some(result);
},
}
}
}
fn move_down_and_get_item_or_previous(&mut self, mut node_addr: u64, key: Key) -> Option<Item> {
loop {
let node = Node::parse(node_at_log(self.tree.image, self.tree.addr_map.deref(), node_addr).ok()?).ok()?;
match node {
Node::Interior(int_node) => {
let (i, new_node_ptr) = int_node
.children
.iter()
.enumerate()
.take_while(|(_,bp)|bp.key <= key)
.last()?;
node_addr = new_node_ptr.ptr;
self.nodes.push(int_node);
self.indices.push(i);
},
Node::Leaf(leaf_node) => {
let (i, result) = leaf_node
.items
.iter()
.enumerate()
.take_while(|(_,item)|item.key <= key)
.last()?;
let result_cloned = result.clone();
self.leaf = Some(Box::new(leaf_node));
self.indices.push(i);
return Some(result_cloned);
},
}
}
}
}
impl Iterator for Iter<'_> {
type Item = Item;
// for now we just silently stop when we encounter an error, maybe that isn't the best solution
fn next(&mut self) -> Option<Item> {
if self.leaf.is_none() && self.nodes.len() == 0 {
// first item
// finding the first item is a bit tricky
// if there is a lower limit, the B+ tree only allows us to either find the item
// or the previous one if there is no exact match; in the latter case, go one further
let result = if let Some(lim) = self.lower_limit {
let first_res = self.move_down_and_get_item_or_previous(self.tree.root_addr_log, lim);
if let Some(item) = first_res {
if item.key == lim {
// found exactly the limit, that's the easy case
Some(item)
} else {
// found a previous item; so we want the next one
self.next()
}
} else {
// did not find an item, so everything must come after lower limit
// just get the first
self.move_down_and_get_first_item(self.tree.root_addr_log)
}
} else {
// there is no lower limit, so also just get the first
self.move_down_and_get_first_item(self.tree.root_addr_log)
};
result.filter(|item|self.upper_limit.is_none() || item.key < self.upper_limit.unwrap())
} else if self.leaf.is_none() {
// already through the iterator
return None;
} else {
let height = self.indices.len(); // must be at least 1
let leaf = self.leaf.as_ref().unwrap();
self.indices[height-1] += 1;
if let Some(item) = leaf.items.get(self.indices[height-1]) {
// there's a next item in the same leaf
if self.upper_limit.is_none() || item.key < self.upper_limit.unwrap() {
return Some(item.clone());
} else {
return None;
}
} else if height == 1 {
// the tree has height 1 and we're through the (only) leaf, there's nothing left
return None;
} else {
// try to advance in one of the higher nodes
self.leaf = None;
self.indices.pop();
let mut level = height - 2;
// go up until we can move forward in a node
let node_addr = loop {
let node = &self.nodes[level];
self.indices[level] += 1;
if let Some(blockptr) = node.children.get(self.indices[level]) {
break blockptr.ptr;
} else {
if level == 0 {
return None;
}
self.indices.pop();
self.nodes.pop();
level -= 1;
}
};
// first first item under this node
self.move_down_and_get_first_item(node_addr)
.filter(|item|self.upper_limit.is_none() || item.key < self.upper_limit.unwrap())
}
}
}
}

View File

@ -2,7 +2,6 @@ use binparse_derive::AllVariants;
use binparse_derive::ParseBin; use binparse_derive::ParseBin;
use std::fmt; use std::fmt;
use std::error; use std::error;
use std::io;
use std::ffi::CString; use std::ffi::CString;
/***** BTRFS structures *****/ /***** BTRFS structures *****/
@ -13,9 +12,9 @@ pub const NODE_SIZE: usize = 0x4000;
#[derive(Debug,Clone,Copy,AllVariants,PartialEq,Eq,PartialOrd,Ord)] #[derive(Debug,Clone,Copy,AllVariants,PartialEq,Eq,PartialOrd,Ord)]
#[repr(u8)] #[repr(u8)]
pub enum ItemType { pub enum ItemType {
Invalid = 0x00, Invalid = 0x00, // invalid
Inode = 0x01, // implemented Inode = 0x01, // implemented
Ref = 0x0c, // TODO Ref = 0x0c, // implemented
ExtRef = 0x0d, ExtRef = 0x0d,
XAttr = 0x18, XAttr = 0x18,
VerityDesc = 0x24, VerityDesc = 0x24,
@ -23,15 +22,15 @@ pub enum ItemType {
Orphan = 0x30, Orphan = 0x30,
DirLog = 0x3c, DirLog = 0x3c,
DirLogIndex = 0x48, DirLogIndex = 0x48,
Dir = 0x54, // implemented (better with len feature) Dir = 0x54, // implemented (better with len feature; allow multiple?)
DirIndex = 0x60, // TODO DirIndex = 0x60, // implemented
ExtentData = 0x6c, // implemented ExtentData = 0x6c, // implemented
ExtentCsum = 0x80, ExtentCsum = 0x80,
Root = 0x84, // implemented Root = 0x84, // implemented
RootBackref = 0x90, RootBackRef = 0x90,
RootRef = 0x9c, RootRef = 0x9c,
Extent = 0xa8, // implemented (except extra data) Extent = 0xa8, // implemented (with only one version of extra data)
Metadata = 0xa9, // implemented (except extra data) Metadata = 0xa9, // implemented (with only one version of extra data)
TreeBlockRef = 0xb0, TreeBlockRef = 0xb0,
ExtentDataRef = 0xb2, ExtentDataRef = 0xb2,
ExtentRefV0 = 0xb4, ExtentRefV0 = 0xb4,
@ -73,12 +72,14 @@ pub enum Value {
Chunk(ChunkItem), Chunk(ChunkItem),
Root(RootItem), Root(RootItem),
Dir(DirItem), Dir(DirItem),
DirIndex(DirItem),
FreeSpaceInfo(FreeSpaceInfoItem), FreeSpaceInfo(FreeSpaceInfoItem),
FreeSpaceExtent, FreeSpaceExtent,
UUIDSubvol(UUIDSubvolItem), UUIDSubvol(UUIDSubvolItem),
Dev(DevItem), Dev(DevItem),
DevExtent(DevExtentItem), DevExtent(DevExtentItem),
ExtentData(ExtentDataItem), ExtentData(ExtentDataItem),
Ref(RefItem),
Unknown(Vec<u8>), Unknown(Vec<u8>),
} }
@ -167,6 +168,28 @@ pub struct Leaf {
pub items: Vec<Item>, pub items: Vec<Item>,
} }
#[allow(unused)]
#[derive(Debug,Clone,ParseBin)]
pub struct KeyPointer {
pub key: Key,
pub ptr: u64,
pub generation: u64,
}
#[allow(unused)]
#[derive(Debug,Clone)]
pub struct InteriorNode {
pub header: NodeHeader,
pub children: Vec<KeyPointer>,
}
#[allow(unused)]
#[derive(Debug,Clone)]
pub enum Node {
Interior(InteriorNode),
Leaf(Leaf),
}
#[allow(unused)] #[allow(unused)]
#[derive(Debug,Clone,ParseBin)] #[derive(Debug,Clone,ParseBin)]
pub struct BlockGroupItem { pub struct BlockGroupItem {
@ -176,12 +199,17 @@ pub struct BlockGroupItem {
} }
#[allow(unused)] #[allow(unused)]
#[derive(Debug,Clone,ParseBin)] #[derive(Debug,Clone)]
pub struct ExtentItem { pub struct ExtentItem {
pub refs: u64, pub refs: u64,
pub generation: u64, pub generation: u64,
pub flags: u64, pub flags: u64,
pub data: Vec<u8>, // pub data: Vec<u8>,
// this is only correct if flags == 2, fix later!
pub block_refs: Vec<(ItemType, u64)>,
// pub tree_block_key_type: ItemType,
// pub tree_block_key_id: u64,
} }
#[allow(unused)] #[allow(unused)]
@ -357,6 +385,39 @@ pub struct ExtentDataHeader {
extent_type: u8, extent_type: u8,
} }
#[allow(unused)]
#[derive(Debug,Clone,ParseBin)]
pub struct RefItem {
index: u64,
name_len: u16,
// #[len = "name_len"]
name: Vec<u8>,
}
#[allow(unused)]
#[repr(u64)]
#[derive(Clone,Copy,Debug)]
pub enum TreeID {
Root = 1,
Extent = 2,
Chunk = 3,
Dev = 4,
FS = 5,
RootDir = 6,
CSum = 7,
Quota = 8,
UUID = 9,
FreeSpace = 10,
BlockGroup = 11,
}
impl From<TreeID> for u64 {
fn from(value: TreeID) -> u64 {
value as u64
}
}
/***** trait for parsing, and implementations for basic types *****/ /***** trait for parsing, and implementations for basic types *****/
// most of the more complex types will be parsed using derive macros // most of the more complex types will be parsed using derive macros
@ -383,12 +444,6 @@ impl From<&str> for ParseError {
} }
} }
impl From<ParseError> for io::Error {
fn from(value: ParseError) -> io::Error {
io::Error::other(value)
}
}
pub trait ParseBin where Self: Sized { pub trait ParseBin where Self: Sized {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError>; fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError>;
@ -400,7 +455,7 @@ pub trait ParseBin where Self: Sized {
impl ParseBin for u8 { impl ParseBin for u8 {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> { fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
if bytes.len() < 1 { if bytes.len() < 1 {
Err("not enough data".into()) err!("not enough data")
} else { } else {
Ok((bytes[0], 1)) Ok((bytes[0], 1))
} }
@ -410,7 +465,7 @@ impl ParseBin for u8 {
impl ParseBin for u16 { impl ParseBin for u16 {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> { fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
if bytes.len() < 2 { if bytes.len() < 2 {
Err("not enough data".into()) err!("not enough data")
} else { } else {
let result = u16::from_le_bytes(bytes[0..2].try_into().unwrap()); let result = u16::from_le_bytes(bytes[0..2].try_into().unwrap());
Ok((result, 2)) Ok((result, 2))
@ -421,7 +476,7 @@ impl ParseBin for u16 {
impl ParseBin for u32 { impl ParseBin for u32 {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> { fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
if bytes.len() < 4 { if bytes.len() < 4 {
Err("not enough data".into()) err!("not enough data")
} else { } else {
let result = u32::from_le_bytes(bytes[0..4].try_into().unwrap()); let result = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
Ok((result, 4)) Ok((result, 4))
@ -432,7 +487,7 @@ impl ParseBin for u32 {
impl ParseBin for u64 { impl ParseBin for u64 {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> { fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
if bytes.len() < 8 { if bytes.len() < 8 {
Err("not enough data".into()) err!("not enough data")
} else { } else {
let result = u64::from_le_bytes(bytes[0..8].try_into().unwrap()); let result = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
Ok((result, 8)) Ok((result, 8))
@ -443,7 +498,7 @@ impl ParseBin for u64 {
impl<const N: usize> ParseBin for [u8; N] { impl<const N: usize> ParseBin for [u8; N] {
fn parse_len(bytes: &[u8]) -> Result<([u8; N], usize), ParseError> { fn parse_len(bytes: &[u8]) -> Result<([u8; N], usize), ParseError> {
if bytes.len() < N { if bytes.len() < N {
Err("not enough data".into()) err!("not enough data")
} else { } else {
Ok((bytes[0..N].try_into().unwrap(), N)) Ok((bytes[0..N].try_into().unwrap(), N))
} }
@ -489,13 +544,26 @@ impl ParseBin for ItemType {
} }
} }
impl ParseBin for Leaf { impl ParseBin for Node {
fn parse_len(bytes: &[u8]) -> Result<(Leaf, usize), ParseError> { fn parse_len(bytes: &[u8]) -> Result<(Node, usize), ParseError> {
if bytes.len() < 0x65 { if bytes.len() < 0x65 {
return Err("not enough data".into()); return err!("Not enough data to parse node header");
} }
let header = NodeHeader::parse(&bytes[0..0x65])?; let header = NodeHeader::parse(&bytes[0..0x65])?;
if header.level > 0 {
// interior node
let mut children = Vec::new();
let num = header.nritems as usize;
for i in 0 .. num {
children.push(KeyPointer::parse(&bytes[0x65 + i*0x21 .. 0x86 + i*0x21])?);
}
Ok((Node::Interior(InteriorNode { header, children }), NODE_SIZE))
} else {
// leaf node
let mut items = Vec::new(); let mut items = Vec::new();
for i in 0..header.nritems as usize { for i in 0..header.nritems as usize {
@ -509,8 +577,13 @@ impl ParseBin for Leaf {
let value = match key.key_type { let value = match key.key_type {
ItemType::BlockGroup => ItemType::BlockGroup =>
Value::BlockGroup(BlockGroupItem::parse(data_slice)?), Value::BlockGroup(BlockGroupItem::parse(data_slice)?),
ItemType::Metadata => ItemType::Metadata => {
Value::Extent(ExtentItem::parse(data_slice)?), let item = ExtentItem::parse(data_slice)?;
if item.flags != 2 || item.refs > 1 {
println!("Metadata item with refs = {}, flags = {}, data = {:x?}", item.refs, item.flags, &data_slice[0x18..]);
}
Value::Extent(item)
},
ItemType::Extent => ItemType::Extent =>
Value::Extent(ExtentItem::parse(data_slice)?), Value::Extent(ExtentItem::parse(data_slice)?),
ItemType::Inode => ItemType::Inode =>
@ -519,6 +592,8 @@ impl ParseBin for Leaf {
Value::Root(RootItem::parse(data_slice)?), Value::Root(RootItem::parse(data_slice)?),
ItemType::Dir => ItemType::Dir =>
Value::Dir(DirItem::parse(data_slice)?), Value::Dir(DirItem::parse(data_slice)?),
ItemType::DirIndex =>
Value::DirIndex(DirItem::parse(data_slice)?),
ItemType::Chunk => ItemType::Chunk =>
Value::Chunk(ChunkItem::parse(data_slice)?), Value::Chunk(ChunkItem::parse(data_slice)?),
ItemType::FreeSpaceInfo => ItemType::FreeSpaceInfo =>
@ -533,6 +608,8 @@ impl ParseBin for Leaf {
Value::DevExtent(DevExtentItem::parse(data_slice)?), Value::DevExtent(DevExtentItem::parse(data_slice)?),
ItemType::ExtentData => ItemType::ExtentData =>
Value::ExtentData(ExtentDataItem::parse(data_slice)?), Value::ExtentData(ExtentDataItem::parse(data_slice)?),
ItemType::Ref =>
Value::Ref(RefItem::parse(data_slice)?),
_ => _ =>
Value::Unknown(Vec::from(data_slice)), Value::Unknown(Vec::from(data_slice)),
}; };
@ -540,12 +617,26 @@ impl ParseBin for Leaf {
items.push(Item { key, value }); items.push(Item { key, value });
} }
let result = Leaf { Ok((Node::Leaf(Leaf { header, items }), NODE_SIZE))
header, }
items, }
}; }
Ok((result, NODE_SIZE)) impl ParseBin for InteriorNode {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
match Node::parse_len(bytes)? {
(Node::Interior(int_node), len) => Ok((int_node, len)),
_ => err!("Expected interior node, found leaf"),
}
}
}
impl ParseBin for Leaf {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
match Node::parse_len(bytes)? {
(Node::Leaf(leaf_node), len) => Ok((leaf_node, len)),
_ => err!("Expected leaf, found interior node"),
}
} }
} }
@ -567,27 +658,24 @@ impl ParseBin for ExtentDataItem {
} }
} }
/***** looking up keys *****/ impl ParseBin for ExtentItem {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
let refs = u64::parse(bytes)?;
let generation = u64::parse(&bytes[0x08..])?;
let flags = u64::parse(&bytes[0x10..])?;
impl Leaf { let mut block_refs = Vec::new();
pub fn find_key(&self, key: Key) -> Option<Item> {
self.items.iter().find(|x|x.key == key).map(|x|x.clone()) if flags & 0x03 == 0x02 {
for i in 0 .. refs as usize {
let key_type = ItemType::parse(&bytes[0x18 + i*0x09 .. ])?;
let key_id = u64::parse(&bytes[0x19 + i*0x09 .. ])?;
block_refs.push((key_type, key_id));
} }
} }
pub fn node_at_log<'a, T: LogToPhys>(image: &'a [u8], addr: &T, log: u64) -> Option<&'a [u8]> { Ok((ExtentItem { refs, generation, flags, block_refs }, 0x18 + refs as usize * 0x09))
let phys_addr = addr.to_phys(log)?;
Some(&image[phys_addr as usize .. phys_addr as usize + NODE_SIZE])
} }
pub trait LogToPhys {
fn to_phys(&self, log: u64) -> Option<u64>;
}
pub fn find_key_in_tree<T: LogToPhys>(image: &[u8], addr: &T, root_addr_log: u64, key: Key) -> Option<Item> {
// assuming level is 0
let leaf = Leaf::parse(node_at_log(image, addr, root_addr_log)?).ok()?;
leaf.find_key(key)
} }
/***** prettier debug output for UUIDs and checksums *****/ /***** prettier debug output for UUIDs and checksums *****/

8
src/lib.rs Normal file
View File

@ -0,0 +1,8 @@
#[macro_use]
pub mod util;
pub mod btrfs_structs;
pub mod btrfs_lookup;
pub mod addrmap;
#[cfg(test)]
mod test;

View File

@ -1,53 +1,313 @@
#![feature(io_error_other)] use memmap2::{Mmap, MmapOptions};
mod btrfs_structs;
mod addrmap;
use addrmap::AddressMap;
use memmap2::Mmap;
use std::fs::File; use std::fs::File;
use std::io::Error as IOError;
use anyhow::Error as AError;
use rouille::Request; use rouille::Request;
use rouille::Response; use rouille::Response;
use btrfs_structs::{ItemType, ParseBin, Key, Item, Leaf, NodeHeader, NODE_SIZE, Superblock, Value, LogToPhys}; use rouille::router;
use std::iter;
use std::env;
use std::{fs::OpenOptions, os::unix::fs::OpenOptionsExt};
use std::collections::HashMap;
//const ACTIVE_NODES: &'static[usize] = &[0x14000, 0x18000, 0x1c000, 0x20000, 0x28000, 0x2c000, 0x3c000, 0x40000]; use parsebtrfs::btrfs_structs::{TreeID, Value::Extent, Value::BlockGroup, ParseError, NODE_SIZE, ItemType, Node, ParseBin};
use parsebtrfs::btrfs_lookup::Tree;
const EXTENT_TREE: u64 = 2; use parsebtrfs::addrmap::{AddressMap, LogToPhys};
const FS_TREE: u64 = 5;
fn main() -> Result<(), IOError> { const COLORS: &[&str] = &["#e6194b", "#3cb44b", "#ffe119", "#4363d8", "#f58231", "#911eb4", "#46f0f0", "#f032e6", "#bcf60c", "#fabebe", "#008080", "#e6beff", "#9a6324", "#fffac8", "#800000", "#aaffc3", "#808000", "#ffd8b1", "#000075", "#808080", "#000000"];
let file = File::open("../image")?;
fn main() -> Result<(), MainError> {
let filename = env::args().skip(1).next().ok_or("Argument required")?;
let file = OpenOptions::new().read(true).open(filename)?;
let image = unsafe { Mmap::map(&file)? }; let image = unsafe { Mmap::map(&file)? };
let addr = AddressMap::new(&image).unwrap(); const O_DIRECT: i32 = 0x4000;
let superblock = Superblock::parse(&image[0x10000..]).unwrap(); // let file = OpenOptions::new().read(true).custom_flags(O_DIRECT).open(filename)?;
// let image = unsafe { MmapOptions::new().len(493921239040usize).map(&file)? };
let chunk_root_phys = addr.to_phys(superblock.chunk_root) // return Ok(());
.ok_or(IOError::other("Chunk root address stored in superblock is invalid"))? as usize;
let root_phys = addr.to_phys(superblock.root)
.ok_or(IOError::other("Root address stored in superblock is invalid"))? as usize;
let root_tree_leaf = Leaf::parse(&image[root_phys .. root_phys + NODE_SIZE])?; /*
let mystery_addr = 0x2f_2251_c000;
let addr_map = AddressMap::new(&image)?;
let mystery_addr_phys = addr_map.to_phys(mystery_addr).unwrap() as usize;
let mystery_node = Node::parse(&image[mystery_addr_phys .. ])?;
let root_key = Key::new(EXTENT_TREE, ItemType::Root, 0); println!("{:#x?}", &mystery_node);
let root = root_tree_leaf.find_key(root_key) */
.ok_or(IOError::other("Could not find extent tree in tree of roots"))?;
let root_addr = if let Value::Root(root_item) = root.value { rouille::start_server("127.0.0.1:8080", move |request| {
addr.to_phys(root_item.bytenr) router!(
request,
(GET) ["/"] => http_main_boxes(&image, request),
(GET) ["/favicon.ico"] => Response::empty_404(),
_ => Response::empty_404(),
)
});
}
static CIRCLE_IMAGE: &str =
"data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9\
kT1Iw0AcxV9bpUUqInYo4pChOtnFLxxrFYpQIdQKrTqYXPoFTRqSFBdHwbXg4Mdi1cHFWVcHV0EQ\
/ABxdXFSdJES/5cUWsR4cNyPd/ced+8Af7PKVLMnAaiaZWRSSSGXXxWCrwhhEAFEMS0xU58TxTQ8\
x9c9fHy9i/Ms73N/jn6lYDLAJxAnmG5YxBvEM5uWznmfOMLKkkJ8Tjxu0AWJH7kuu/zGueSwn2dG\
jGxmnjhCLJS6WO5iVjZU4inimKJqlO/Puaxw3uKsVuusfU/+wnBBW1nmOs0RpLCIJYgQIKOOCqqw\
EKdVI8VEhvaTHv5hxy+SSyZXBYwcC6hBheT4wf/gd7dmcXLCTQongd4X2/4YBYK7QKth29/Htt06\
AQLPwJXW8deawOwn6Y2OFjsCBraBi+uOJu8BlztA9EmXDMmRAjT9xSLwfkbflAeGboG+Nbe39j5O\
H4AsdZW+AQ4OgbESZa97vDvU3du/Z9r9/QChS3K5hXof0gAAAAZiS0dEAP8A/wD/oL2nkwAAAAlw\
SFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+cIEQMcKM7EsV8AAAA/SURBVBjTY2CgGfj//7/8////\
J/3///8NFE/6//+/PDaFk/5jgknYFL7BovANTJ6JWKchK1yGRX4Z+Z6hGgAAmotacR/hRugAAAAA\
SUVORK5CYII=";
static EXPLANATION_TEXT: &str = "\
<h3>Chunks</h3>
<p>On the highest level, btrfs splits the disk into <b>chunks</b> (also called <b>block groups</b>). They can have different sizes, with 1GiB being typical in a large file system. Each chunk can either contain data or metadata.<p>
<p>Here we look at the metadata chunks. They contain the B-treesm which btrfs gets its name from. They are key-value stores for different kinds of information. For example, the filesystem tree stores which files and directories are in the filesystem, and the extent tree stores which areas of the disk are in use. Each B-tree consists of a number of 16KiB <b>nodes</b>, here symbolized by colorful boxes, with the color indicating which tree the node belongs to. Most of the nodes are <b>leaves</b>, which contain the actual key-value pairs. The others are <b>interior nodes</b>, and we indicate them with a little white circle. They are important to find the leaf a key is stored in.</p>";
fn http_main_boxes(image: &[u8], _req: &Request) -> Response {
let mut treecolors: HashMap<u64, &str> = HashMap::new();
let mut result = String::new();
let explanation_tablerowformat = |c: &str, t: &str| format!(
"<tr>\
<td><table><tr><td style=\"height:10px;width:10px;padding:0;background:{};\"></td></tr></table></td>\
<td><table><tr><td style=\"height:10px;width:10px;padding:0;background:{};\"><img src=\"{}\" /></td></tr></table></td>\
<td>{}</td>\
</tr>\n",
c, c, CIRCLE_IMAGE, t);
let explanation_tablerowformat_leafonly = |c,t| format!(
"<tr>\
<td><table><tr><td style=\"height:10px;width:10px;padding:0;background:{};\"></td></tr></table></td>\
<td></td>\
<td>{}</td>\
</tr>\n",
c, t);
let cellformat = |c| format!(
"<td style=\"height:10px;width:10px;padding:0;background:{};\"></td>\n",
c);
let cellformat_higher = |c,_| format!(
"<td style=\"height:10px;width:10px;padding:0;background:{}\"><img src=\"{}\" /></td>\n",
c, CIRCLE_IMAGE);
result.push_str(&"<details>\n<summary>What am I seeing here?</summary>");
result.push_str(EXPLANATION_TEXT);
// tree explanations
result.push_str(&"<table style=\"margin: 0 auto;\">\n");
result.push_str(&explanation_tablerowformat_leafonly("lightgrey", "unused or outdated node"));
treecolors.insert(1, COLORS[treecolors.len() % COLORS.len()]);
result.push_str(&explanation_tablerowformat(treecolors[&1], "root tree"));
treecolors.insert(3, COLORS[treecolors.len() % COLORS.len()]);
result.push_str(&explanation_tablerowformat(treecolors[&3], "chunk tree"));
let roots = Tree::root(image).unwrap();
for item in roots.iter() {
if item.key.key_type == ItemType::Root {
let treedesc: String = match &item.key.key_id {
1 => format!("root tree"),
2 => format!("extent tree"),
3 => format!("chunk tree"),
4 => format!("device tree"),
5 => format!("filesystem tree"),
6 => format!("root directory"),
7 => format!("checksum tree"),
8 => format!("quota tree"),
9 => format!("UUID tree"),
10 => format!("free space tree"),
11 => format!("block group tree"),
0xffff_ffff_ffff_fff7 => format!("data reloc tree"),
x @ 0x100 ..= 0xffff_ffff_ffff_feff => format!("file tree, id = {}", x),
x => format!("other tree, id = {}", x),
};
treecolors.insert(item.key.key_id, COLORS[treecolors.len() % COLORS.len()]);
result.push_str(&explanation_tablerowformat(
treecolors[&item.key.key_id],
&treedesc
));
}
}
result.push_str(&"</table>\n");
result.push_str(&"</details>\n");
let extent_tree = Tree::new(&image, TreeID::Extent).unwrap();
let mut extent_tree_iterator = extent_tree.iter();
// current_blockgroup == None: haven't encountered a blockgroup yet
// metadata_items == None: current blockgroup is not metadata or system
let mut current_blockgroup = None;
let mut metadata_items: Option<Vec<Option<(u64, u64)>>> = None;
let metadata_blockgroups = iter::from_fn(|| {
while let Some(item) = extent_tree_iterator.next() {
// println!("Got key: {:x?}", &item.key);
match &item.value {
BlockGroup(bg) => {
println!("{:x?}", item.key);
let result = (current_blockgroup.take(), metadata_items.take());
let nodes_in_blockgroup = item.key.key_offset as usize / NODE_SIZE;
if bg.flags & 0x01 == 0 {
metadata_items = Some(vec![None; nodes_in_blockgroup]);
} else { } else {
None metadata_items = None;
}.unwrap() as usize; }
current_blockgroup = Some(item);
let tree_leaf = Leaf::parse(&image[root_addr .. root_addr + NODE_SIZE])?; if let (Some(bg), met) = result {
return Some((bg, met));
}
},
Extent(e) => {
if let Some(bg_item) = &current_blockgroup {
if let Some(met) = &mut metadata_items {
let bg_start = bg_item.key.key_id;
let node_addr = item.key.key_id;
let tree_id = e.block_refs.iter().filter(|&(t,_)|t == &ItemType::TreeBlockRef).count() as u64;
let index = (node_addr - bg_start) as usize / NODE_SIZE;
if index < met.len() {
met[index] = Some((tree_id, item.key.key_offset));
} else {
println!("Warning: extent out of block group range: {:x?}", &item.key);
}
}
} else {
println!("Warning: extent without matching block group: {:x?}", &item.key);
}
},
_ => {},//panic!("Unexpected item in extent tree: {:x?}", item.key)
}
}
println!("{:#?}", &tree_leaf); let result = (current_blockgroup.take(), metadata_items.take());
if let (Some(bg), met) = result {
return Some((bg, met));
} else {
return None;
}
});
// println!("{:#?}", Leaf::parse(&image[0x253c000..0x2540000])); let mut last_key = 0;
Ok(()) // colorful table
for (bg, nodes) in metadata_blockgroups {
if bg.key.key_id < last_key {
println!("Error: going backwards!");
break;
} else {
last_key = bg.key.key_id;
}
let bg_value = match &bg.value {
BlockGroup(bgv) => bgv,
_ => panic!("Expected BlockGroup value"),
};
// header
result.push_str(
&format!(
"<h3 style=\"text-align: center;\">{:x} - {:x} ({}, {})</h3><p>Physical: {}</p>\n",
bg.key.key_id,
bg.key.key_id + bg.key.key_offset,
match bg.key.key_offset {
x if x <= (1<<11) => format!("{} B", x),
x if x <= (1<<21) => format!("{} KiB", x as f64 / (1u64<<10) as f64),
x if x <= (1<<31) => format!("{} MiB", x as f64 / (1u64<<20) as f64),
x if x <= (1<<41) => format!("{} GiB", x as f64 / (1u64<<30) as f64),
x if x <= (1<<51) => format!("{} TiB", x as f64 / (1u64<<40) as f64),
x @ _ => format!("{} PiB", x as f64 / (1u64<<50) as f64),
},
match bg_value.flags & 0x07 {
0x01 => "Data",
0x02 => "System",
0x04 => "Metadata",
_ => "???",
},
match extent_tree.addr_map.as_ref().0.binary_search_by_key(&bg.key.key_id, |x|x.0) {
Ok(i) => format!("{:x?}", &extent_tree.addr_map.as_ref().0[i].2),
_ => String::from(""),
}
)
);
if let Some(nodes) = nodes {
result.push_str("<table style=\"margin: 0 auto;\">\n<tr>\n");
for (i, &n) in nodes.iter().enumerate() {
if i % 64 == 0 && i != 0 {
result.push_str("</tr>\n<tr>\n");
}
if let Some((tid, level)) = n {
let color: Option<&str> = treecolors.get(&tid).map(|x|*x);
let color = color.unwrap_or_else(|| {
println!("Unknown color for id: {}", &tid);
let color: &str = COLORS[treecolors.len() % COLORS.len()];
treecolors.insert(tid, color);
color
});
if level == 0 {
result.push_str(&cellformat(color));
} else {
result.push_str(&cellformat_higher(color, level));
}
} else {
result.push_str(&cellformat("lightgrey"));
}
}
result.push_str("</tr>\n</table>\n");
}
}
Response::html(result)
}
// ----- Error handling -----
pub struct MainError(String);
impl std::error::Error for MainError {}
impl std::fmt::Debug for MainError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.0)
}
}
impl std::fmt::Display for MainError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.0)
}
}
impl From<String> for MainError {
fn from(value: String) -> MainError {
MainError(value)
}
}
impl From<&str> for MainError {
fn from(value: &str) -> MainError {
MainError::from(String::from(value))
}
}
impl From<ParseError> for MainError {
fn from(value: ParseError) -> MainError {
MainError::from(format!("BTRFS format error: {value}"))
}
}
impl From<std::io::Error> for MainError {
fn from(value: std::io::Error) -> MainError {
MainError::from(format!("IO error: {value}"))
}
} }
/* /*
@ -62,35 +322,6 @@ fn main() -> Result<(), std::io::Error> {
}); });
} }
fn http_main_boxes(image: &[u8], addr: &AddressTranslation, req: &Request) -> Response {
let chunk_offset = 0x02500000;
let nodes_in_chunk = 2048;
let mut result = String::new();
result.push_str("<body><table style=\"margin: 0 auto;\">\n<tr>\n");
for i in 0..nodes_in_chunk {
if i % 64 == 0 {
result.push_str("</tr>\n<tr>\n");
}
let node = read_node(&image, chunk_offset + i*0x4000);
let active = node.generation > 0 && ACTIVE_NODES.contains(&(i*0x4000));
let newbox = format!("<td style=\"{}\"></td>\n",
if active {
"height:10px;width:10px;padding:0;background:black;"
} else {
"height:10px;width:10px;padding:0;background:lightgray;"
});
result.push_str(&newbox);
}
result.push_str("</tr>\n</table></body>");
Response::html(result)
}
fn http_main_list(image: &[u8], addr: &AddressTranslation, req: &Request) -> Response { fn http_main_list(image: &[u8], addr: &AddressTranslation, req: &Request) -> Response {
let chunk_offset = 0x02500000; let chunk_offset = 0x02500000;

10
src/test.rs Normal file
View File

@ -0,0 +1,10 @@
use super::*;
use btrfs_structs::{Key, ItemType};
#[test]
fn test_key_new() {
assert_eq!(
Key::new(1, ItemType::Root, 2),
Key { key_id: 1, key_type: ItemType::Root, key_offset: 2 }
);
}

7
src/util.rs Normal file
View File

@ -0,0 +1,7 @@
macro_rules! error {
($($i:expr),*) => { format!($($i),*).into() };
}
macro_rules! err {
($($i:expr),*) => { Err(error!($($i),*)) };
}