fully implement ExtentItem, hex data, node addresses, and some other details

This commit is contained in:
Florian Stecker 2024-03-06 00:50:43 -05:00
parent b41547ddcb
commit 053a8ff77f
7 changed files with 238 additions and 94 deletions

View File

@ -2,7 +2,7 @@ use std::convert::identity;
use std::rc::Rc;
use std::ops::{Deref, RangeBounds, Bound};
use crate::btrfs_structs::{Leaf, Key, Item, InteriorNode, Node, ParseError, ParseBin, Value, Superblock, ItemType, ZERO_KEY, LAST_KEY};
use crate::btrfs_structs::{DirItem, InteriorNode, Item, ItemType, Key, Leaf, Node, ParseBin, ParseError, Superblock, Value, LAST_KEY, ZERO_KEY};
use crate::nodereader::NodeReader;
/// Represents a B-Tree inside a filesystem image. Can be used to look up keys,
@ -32,7 +32,7 @@ impl<'a> Tree<'a> {
.filter(|x| x.key.key_id == tree_id && x.key.key_type == ItemType::Root);
let root_addr_log = match tree_root_item {
Some(Item { key: _, value: Value::Root(root)}) => root.bytenr,
Some(Item { key: _, range: _, value: Value::Root(root)}) => root.bytenr,
_ => return Err("root item not found or invalid".into())
};
@ -119,7 +119,7 @@ impl Tree<'_> {
/***** iterator *****/
pub struct RangeIter<'a, 'b> {
pub struct RangeIterWithAddr<'a, 'b> {
tree: &'b Tree<'a>,
start: Bound<Key>,
@ -128,6 +128,8 @@ pub struct RangeIter<'a, 'b> {
backward_skip_fn: Box<dyn Fn(Key) -> Key>,
}
pub struct RangeIter<'a, 'b> (RangeIterWithAddr<'a, 'b>);
impl<'a> Tree<'a> {
/// Given a tree, a range of indices, and two "skip functions", produces a double
/// ended iterator which iterates through the keys contained in the range, in ascending
@ -144,33 +146,36 @@ impl<'a> Tree<'a> {
R: RangeBounds<Key>,
F1: Fn(Key) -> Key + 'static,
F2: Fn(Key) -> Key + 'static {
RangeIter {
RangeIter(RangeIterWithAddr {
tree: self,
start: range.start_bound().cloned(),
end: range.end_bound().cloned(),
forward_skip_fn: Box::new(forward_skip_fn),
backward_skip_fn: Box::new(backward_skip_fn),
}
})
}
pub fn range<'b, R: RangeBounds<Key>>(&'b self, range: R) -> RangeIter<'a, 'b> {
RangeIter {
tree: self,
start: range.start_bound().cloned(),
end: range.end_bound().cloned(),
forward_skip_fn: Box::new(identity),
backward_skip_fn: Box::new(identity),
}
}
pub fn iter<'b>(&'b self) -> RangeIter<'a, 'b> {
RangeIter {
RangeIter(RangeIterWithAddr {
tree: self,
start: Bound::Unbounded,
end: Bound::Unbounded,
forward_skip_fn: Box::new(identity),
backward_skip_fn: Box::new(identity),
})
}
pub fn range<'b, R: RangeBounds<Key>>(&'b self, range: R) -> RangeIter<'a, 'b> {
RangeIter(self.range_with_node_addr(range))
}
pub fn range_with_node_addr<'b, R: RangeBounds<Key>>(&'b self, range: R) -> RangeIterWithAddr<'a, 'b> {
RangeIterWithAddr {
tree: self,
start: range.start_bound().cloned(),
end: range.end_bound().cloned(),
forward_skip_fn: Box::new(identity),
backward_skip_fn: Box::new(identity),
}
}
}
@ -178,19 +183,19 @@ impl<'a> Tree<'a> {
/// Get the first item under the node at logical address `addr`.
/// This function panics if there are no items
fn get_first_item(tree: &Tree, addr: u64) -> Result<Item, ParseError> {
fn get_first_item(tree: &Tree, addr: u64) -> Result<(Item, u64), ParseError> {
match tree.reader.get_node(addr)?.deref() {
Node::Interior(intnode) => get_first_item(tree, intnode.children[0].ptr),
Node::Leaf(leafnode) => Ok(leafnode.items[0].clone()),
Node::Leaf(leafnode) => Ok((leafnode.items[0].clone(), addr)),
}
}
/// Get the last item under the node at logical address `addr`.
/// This function panics if there are no items
fn get_last_item(tree: &Tree, addr: u64) -> Result<Item, ParseError> {
fn get_last_item(tree: &Tree, addr: u64) -> Result<(Item, u64), ParseError> {
match tree.reader.get_node(addr)?.deref() {
Node::Interior(intnode) => get_last_item(tree, intnode.children.last().unwrap().ptr),
Node::Leaf(leafnode) => Ok(leafnode.items.last().unwrap().clone()),
Node::Leaf(leafnode) => Ok((leafnode.items.last().unwrap().clone(), addr)),
}
}
@ -201,7 +206,7 @@ enum FindKeyMode {LT, GT, GE, LE}
/// the "closest" match. The exact meaning of "closest" is given by the `mode` argument:
/// If `mode` is `LT`/`GT`/`GE`/`LE`, return the item with the greatest / least / greatest / least
/// key less than / greater than / greater or equal to / less or equal to `key`.
fn find_closest_key(tree: &Tree, key: Key, mode: FindKeyMode) -> Result<Option<Item>, ParseError> {
fn find_closest_key(tree: &Tree, key: Key, mode: FindKeyMode) -> Result<Option<(Item, u64)>, ParseError> {
// in some cases, this task can't be accomplished by a single traversal
// but we might have to go back up the tree; prev/next allows to quickly go back to the right node
@ -244,14 +249,14 @@ fn find_closest_key(tree: &Tree, key: Key, mode: FindKeyMode) -> Result<Option<I
match leafnode.find_key_or_previous(key) {
Some(idx) => {
// the standard case, we found a key `k` with the guarantee that `k <= key`
let Item {key: k, value: v} = leafnode.items[idx].clone();
let it = leafnode.items[idx].clone();
if mode == FindKeyMode::LE || mode == FindKeyMode::LT && k < key || mode == FindKeyMode::GE && k == key {
return Ok(Some(Item {key: k, value: v}))
} else if mode == FindKeyMode::LT && k == key {
if mode == FindKeyMode::LE || mode == FindKeyMode::LT && it.key < key || mode == FindKeyMode::GE && it.key == key {
return Ok(Some((it, current)))
} else if mode == FindKeyMode::LT && it.key == key {
// prev
if idx > 0 {
return Ok(Some(leafnode.items[idx-1].clone()));
return Ok(Some((leafnode.items[idx-1].clone(), current)));
} else {
// use prev
if let Some(addr) = prev {
@ -263,7 +268,7 @@ fn find_closest_key(tree: &Tree, key: Key, mode: FindKeyMode) -> Result<Option<I
} else {
// next
if let Some(item) = leafnode.items.get(idx+1) {
return Ok(Some(item.clone()));
return Ok(Some((item.clone(), current)));
} else {
// use next
if let Some(addr) = next {
@ -280,7 +285,7 @@ fn find_closest_key(tree: &Tree, key: Key, mode: FindKeyMode) -> Result<Option<I
return Ok(None);
} else {
// return the first item in tree if it exists
return Ok(leafnode.items.get(0).map(|x|x.clone()));
return Ok(leafnode.items.get(0).map(|x|(x.clone(), current)));
}
},
}
@ -299,10 +304,10 @@ fn range_valid<T: Ord>(start: Bound<T>, end: Bound<T>) -> bool {
}
}
impl<'a, 'b> Iterator for RangeIter<'a, 'b> {
type Item = Item;
impl<'a, 'b> Iterator for RangeIterWithAddr<'a, 'b> {
type Item = (Item, u64);
fn next(&mut self) -> Option<Item> {
fn next(&mut self) -> Option<Self::Item> {
if !range_valid(self.start.as_ref(), self.end.as_ref()) {
return None;
}
@ -317,11 +322,11 @@ impl<'a, 'b> Iterator for RangeIter<'a, 'b> {
let result = find_closest_key(self.tree, start_key, mode)
.expect("file system should be consistent (or this is a bug)");
if let Some(item) = &result {
if let Some((item, _)) = &result {
self.start = Bound::Excluded((self.forward_skip_fn)(item.key));
}
let end_filter = |item: &Item| {
let end_filter = |(item, _): &(Item, u64)| {
match &self.end {
&Bound::Included(x) => item.key <= x,
&Bound::Excluded(x) => item.key < x,
@ -335,8 +340,8 @@ impl<'a, 'b> Iterator for RangeIter<'a, 'b> {
}
}
impl<'a, 'b> DoubleEndedIterator for RangeIter<'a, 'b> {
fn next_back(&mut self) -> Option<Item> {
impl<'a, 'b> DoubleEndedIterator for RangeIterWithAddr<'a, 'b> {
fn next_back(&mut self) -> Option<Self::Item> {
if !range_valid(self.start.as_ref(), self.end.as_ref()) {
return None;
}
@ -350,11 +355,11 @@ impl<'a, 'b> DoubleEndedIterator for RangeIter<'a, 'b> {
let result = find_closest_key(self.tree, start_key, mode)
.expect("file system should be consistent (or this is a bug)");
if let Some(item) = &result {
if let Some((item,_)) = &result {
self.end = Bound::Excluded((self.backward_skip_fn)(item.key));
}
let start_filter = |item: &Item| {
let start_filter = |(item, _): &(Item, u64)| {
match &self.start {
&Bound::Included(x) => item.key >= x,
&Bound::Excluded(x) => item.key > x,
@ -367,3 +372,17 @@ impl<'a, 'b> DoubleEndedIterator for RangeIter<'a, 'b> {
.map(|item|item.clone())
}
}
impl<'a, 'b> Iterator for RangeIter<'a, 'b> {
type Item = Item;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|x|x.0)
}
}
impl<'a, 'b> DoubleEndedIterator for RangeIter<'a, 'b> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0.next_back().map(|x|x.0)
}
}

View File

@ -1,9 +1,11 @@
use btrfs_parse_derive::AllVariants;
use btrfs_parse_derive::ParseBin;
use std::any::TypeId;
use std::fmt;
use std::error;
use std::ffi::CString;
/***** BTRFS structures *****/
pub const NODE_SIZE: usize = 0x4000;
@ -29,13 +31,13 @@ pub enum ItemType {
Root = 0x84, // implemented
RootBackRef = 0x90, // implemented
RootRef = 0x9c, // implemented
Extent = 0xa8, // implemented (with only one version of extra data!!)
Metadata = 0xa9, // implemented (with only one version of extra data!!)
TreeBlockRef = 0xb0,
ExtentDataRef = 0xb2,
Extent = 0xa8, // implemented
Metadata = 0xa9, // implemented
TreeBlockRef = 0xb0, // implemented (inside ExtentItem)
ExtentDataRef = 0xb2, // implemented (inside ExtentItem)
ExtentRefV0 = 0xb4,
SharedBlockRef = 0xb6,
SharedDataRef = 0xb8,
SharedBlockRef = 0xb6, // implemented (inside ExtentItem)
SharedDataRef = 0xb8, // implemented (inside ExtentItem)
BlockGroup = 0xc0, // implemented
FreeSpaceInfo = 0xc6, // implemented
FreeSpaceExtent = 0xc7, // implemented
@ -93,8 +95,8 @@ pub enum Value {
Dev(DevItem),
DevExtent(DevExtentItem),
ExtentData(ExtentDataItem),
Ref(RefItem),
RootRef(RootRefItem),
Ref(Vec<RefItem>),
RootRef(Vec<RootRefItem>),
Unknown(Vec<u8>),
}
@ -103,6 +105,7 @@ pub enum Value {
#[derive(Debug,Clone)]
pub struct Item {
pub key: Key,
pub range: (u32, u32), // start and end offset within node
pub value: Value,
}
@ -214,7 +217,7 @@ pub struct BlockGroupItem {
}
#[allow(unused)]
#[derive(Debug,Clone)]
#[derive(Debug,Clone,ParseBin)]
pub struct ExtentItem {
pub refs: u64,
pub generation: u64,
@ -222,9 +225,16 @@ pub struct ExtentItem {
// 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,
pub block_refs: Vec<BlockRef>,
}
#[allow(unused)]
#[derive(Debug,Clone)]
pub enum BlockRef {
Tree { id: u64, },
ExtentData { root: u64, id: u64, offset: u64, count: u32, },
SharedData { offset: u64, count: u32, },
SharedBlockRef { offset: u64 },
}
#[allow(unused)]
@ -534,12 +544,31 @@ impl<const N: usize> ParseBin for [u8; N] {
}
// we use Vec<u8> for "unknown extra data", so just eat up everything
impl ParseBin for Vec<u8> {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
Ok((Vec::from(bytes), bytes.len()))
}
}
trait ParseBinVecFallback: ParseBin { }
impl ParseBinVecFallback for BlockRef { }
impl<T: ParseBinVecFallback> ParseBin for Vec<T> {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
let mut result: Vec<T> = Vec::new();
let mut offset: usize = 0;
while offset < bytes.len() {
let (item, len) = <T as ParseBin>::parse_len(&bytes[offset..])?;
result.push(item);
offset += len;
}
Ok((result, bytes.len()))
}
}
impl ParseBin for CString {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
let mut chars = Vec::from(bytes);
@ -617,14 +646,7 @@ impl ParseBin for Node {
let value = match key.key_type {
ItemType::BlockGroup =>
Value::BlockGroup(parse_check_size(data_slice)?),
ItemType::Metadata => {
let item: ExtentItem = parse_check_size(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 | ItemType::Metadata =>
Value::Extent(parse_check_size(data_slice)?),
ItemType::Inode =>
Value::Inode(parse_check_size(data_slice)?),
@ -649,17 +671,32 @@ impl ParseBin for Node {
ItemType::ExtentData =>
Value::ExtentData(parse_check_size(data_slice)?),
ItemType::Ref => {
Value::Ref(parse_check_size(data_slice)?)
let mut result: Vec<RefItem> = vec![];
let mut item_offset = 0;
while item_offset < data_slice.len() {
let (item, len) = RefItem::parse_len(&data_slice[item_offset..])?;
result.push(item);
item_offset += len;
}
Value::Ref(result)
}
ItemType::RootRef =>
Value::RootRef(parse_check_size(data_slice)?),
ItemType::RootBackRef =>
Value::RootRef(parse_check_size(data_slice)?),
ItemType::RootRef | ItemType::RootBackRef => {
let mut result: Vec<RootRefItem> = vec![];
let mut item_offset = 0;
while item_offset < data_slice.len() {
let (item, len) = RootRefItem::parse_len(&data_slice[item_offset..])?;
result.push(item);
item_offset += len;
}
Value::RootRef(result)
},
_ =>
Value::Unknown(Vec::from(data_slice)),
};
items.push(Item { key, value });
items.push(Item { key, range: (0x65 + offset, 0x65 + offset + size), value });
}
Ok((Node::Leaf(Leaf { header, items }), NODE_SIZE))
@ -691,18 +728,19 @@ impl ParseBin for ExtentDataItem {
let (header, header_size) = ExtentDataHeader::parse_len(bytes)?;
if header.extent_type == 1 { // external extent
let (body, body_size) = ExternalExtent::parse_len(&bytes[header_size..])?;
return Ok((ExtentDataItem { header: header, data: ExtentDataBody::External(body)},
Ok((ExtentDataItem { header, data: ExtentDataBody::External(body)},
header_size + body_size))
} else { // inline extent
let data_slice = &bytes[header_size..];
return Ok((ExtentDataItem {
header: header,
Ok((ExtentDataItem {
header,
data: ExtentDataBody::Inline(Vec::from(data_slice))
}, header_size + data_slice.len()))
}
}
}
/*
impl ParseBin for ExtentItem {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
let refs = u64::parse(bytes)?;
@ -722,6 +760,35 @@ impl ParseBin for ExtentItem {
Ok((ExtentItem { refs, generation, flags, block_refs }, 0x18 + refs as usize * 0x09))
}
}
*/
impl ParseBin for BlockRef {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
match ItemType::parse(bytes)? {
ItemType::ExtentDataRef => {
let root = u64::parse(&bytes[0x01..])?;
let id = u64::parse(&bytes[0x09..])?;
let offset = u64::parse(&bytes[0x11..])?;
let count = u32::parse(&bytes[0x19..])?;
Ok((BlockRef::ExtentData { root, id, offset, count }, 0x1d))
},
ItemType::SharedDataRef => {
let offset = u64::parse(&bytes[0x01..])?;
let count = u32::parse(&bytes[0x09..])?;
Ok((BlockRef::SharedData { offset, count }, 0x0d))
},
ItemType::TreeBlockRef => {
let id = u64::parse(&bytes[0x01..])?;
Ok((BlockRef::Tree { id }, 0x09))
},
ItemType::SharedBlockRef => {
let offset = u64::parse(&bytes[0x01..])?;
Ok((BlockRef::SharedBlockRef { offset }, 0x09))
}
x => err!("unknown block ref type: {:?}", x)
}
}
}
/***** prettier debug output for UUIDs and checksums *****/

View File

@ -1,4 +1,6 @@
use std::str::FromStr;
use std::{
str::FromStr,
};
use rouille::{Request, Response};
use crate::{
btrfs_structs::{ItemType, Item, Key, ZERO_KEY, LAST_KEY},
@ -18,41 +20,45 @@ enum TreeDisplayMode {
fn http_tree_internal(tree: &Tree, tree_id: u64, mode: TreeDisplayMode) -> Response {
let mut items: Vec<Item>;
let mut items: Vec<(Item, u64)>;
let mut highlighted_key_id: Option<u64> = None;
match mode {
TreeDisplayMode::Highlight(key_id, before, after) => {
let key = Key {key_id, key_type: ItemType::Invalid, key_offset: 0 };
items = tree.range(..key).rev().take(before).collect();
items = tree.range_with_node_addr(..key).rev().take(before).collect();
items.reverse();
items.extend(tree.range(key..).take(after));
items.extend(tree.range_with_node_addr(key..).take(after));
highlighted_key_id = Some(key_id);
},
TreeDisplayMode::From(key, num_lines) => {
items = tree.range(key..).take(num_lines).collect();
items = tree.range_with_node_addr(key..).take(num_lines).collect();
if items.len() < num_lines {
items.reverse();
items.extend(tree.range(..key).rev().take(num_lines - items.len()));
items.extend(tree.range_with_node_addr(..key).rev().take(num_lines - items.len()));
items.reverse();
}
},
TreeDisplayMode::To(key, num_lines) => {
items = tree.range(..key).rev().take(num_lines).collect();
items = tree.range_with_node_addr(..key).rev().take(num_lines).collect();
items.reverse();
if items.len() < num_lines {
items.extend(tree.range(key..).take(num_lines - items.len()));
items.extend(tree.range_with_node_addr(key..).take(num_lines - items.len()));
}
}
};
let data_slice = |item: &Item, node_addr: u64| -> &[u8] {
tree.reader.get_raw_data(node_addr, item.range.0, item.range.1).unwrap()
};
let table_result = TableResult {
tree_id,
tree_desc: root_key_desc(tree_id).map(|x|x.to_string()),
key_id: highlighted_key_id,
items: items.iter().map(|it|(it,&[] as &[u8])).collect(),
first_key: items.first().map(|it|it.key).unwrap_or(LAST_KEY),
last_key: items.last().map(|it|it.key).unwrap_or(ZERO_KEY),
items: items.iter().map(|(it, addr)|(it, *addr, data_slice(it, *addr))).collect(),
first_key: items.first().map(|x|x.0.key).unwrap_or(LAST_KEY),
last_key: items.last().map(|x|x.0.key).unwrap_or(ZERO_KEY),
};
Response::html(render_table(table_result))

View File

@ -2,6 +2,7 @@ use std::{
collections::HashMap,
sync::Arc,
cell::RefCell,
time::Instant,
};
use crate::btrfs_structs::{Node, ParseError, ParseBin};
@ -29,16 +30,25 @@ impl<'a> NodeReader<'a> {
return Ok(Arc::clone(node))
}
println!("Reading node at {:X}", addr);
let start_time = Instant::now();
let node_data = self.addr_map.node_at_log(self.image, addr)?;
let node = Arc::new(Node::parse(node_data)?);
self.cache.borrow_mut().insert(addr, Arc::clone(&node));
let t = Instant::now().duration_since(start_time);
println!("Read node {:X} in {:?}", addr, t);
Ok(node)
}
pub fn get_raw_data(&self, addr: u64, start: u32, end: u32) -> Result<&'a [u8], ParseError> {
let node_data = self.addr_map.node_at_log(self.image, addr)?;
Ok(&node_data[start as usize .. end as usize])
}
pub fn addr_map(&self) -> &AddressMap {
&self.addr_map
}

View File

@ -1,13 +1,14 @@
use crate::btrfs_structs::{Item, Key, ItemType, Value, ExtentDataBody};
use crate::render_common::{Hex, size_name};
use maud::{Markup, html, DOCTYPE, PreEscaped};
use std::ffi::CStr;
#[derive(Debug)]
pub struct TableResult<'a> {
pub tree_id: u64,
pub tree_desc: Option<String>,
pub key_id: Option<u64>,
pub items: Vec<(&'a Item, &'a [u8])>,
pub items: Vec<(&'a Item, u64, &'a [u8])>, // item, node addr, data
pub first_key: Key,
pub last_key: Key,
}
@ -42,12 +43,13 @@ pub fn render_table(table: TableResult) -> Markup {
let mut rows: Vec<Markup> = Vec::new();
for &(it, _it_data) in table.items.iter() {
for &(it, node_addr, it_data) in table.items.iter() {
let highlighted = if table.key_id.filter(|x|*x == it.key.key_id).is_some() { "highlight" } else { "" };
let value_string = item_value_string(table.tree_id, it);
let details_string = item_details_string(table.tree_id, it);
let raw_string = format!("{:#?}", &it.value);
let id_desc = row_id_desc(it.key, table.tree_id);
let hex_data: String = it_data.iter().map(|x|format!("{:02X} ", x)).collect();
rows.push(html! {
details.item.(highlighted) {
@ -64,6 +66,9 @@ pub fn render_table(table: TableResult) -> Markup {
span.itemvalue.(key_type_class(it.key)) {
(&value_string)
}
span.nodeaddr {
(Hex(node_addr))
}
}
div.details {
@ -77,6 +82,15 @@ pub fn render_table(table: TableResult) -> Markup {
(&raw_string)
}
}
details {
summary {
"show hex data"
}
pre {
(&hex_data)
}
}
}
}
});
@ -151,8 +165,15 @@ fn item_value_string(tree_id: u64, item: &Item) -> Markup {
html! {
(name)
" @ "
a href=(format!("/tree/{tree_id}/{id:x}")) {
(Hex(id))
@if dir_item.location.key_type == ItemType::Root {
a href=(format!("/tree/{id}")) {
"subvolume "
(Hex(id))
}
} @else {
a href=(format!("/tree/{tree_id}/{id:x}")) {
(Hex(id))
}
}
}
},
@ -180,12 +201,17 @@ fn item_value_string(tree_id: u64, item: &Item) -> Markup {
ExtentDataBody::External(ext_extent) =>
PreEscaped(format!("external, length {}", size_name(ext_extent.num_bytes))),
},
Value::Ref(ref_item) =>
html! { (format!("{:?}", &ref_item.name)) },
Value::RootRef(ref_item) =>
html! { (format!("{:?}", &ref_item.name)) },
Value::Ref(ref_item) => {
let names: Vec<&CStr> = ref_item.iter().map(|x|x.name.as_ref()).collect();
html! { (format!("{:?}", &names)) }
},
Value::RootRef(ref_item) => {
let names: Vec<&CStr> = ref_item.iter().map(|x|x.name.as_ref()).collect();
html! { (format!("{:?}", &names)) }
},
Value::Extent(extent_item) =>
PreEscaped(format!("flags: {}, block_refs: {:?}", extent_item.flags, extent_item.block_refs)),
PreEscaped(format!("flags: {}, block_refs: {:X?}", extent_item.flags, extent_item.block_refs)),
Value::BlockGroup(blockgroup_item) =>
PreEscaped(format!("{} used", size_name(blockgroup_item.used))),
Value::DevExtent(dev_extent_item) =>
@ -245,13 +271,14 @@ fn item_details_string(_tree_id: u64, item: &Item) -> Markup {
},
Value::Ref(ref_item) => {
html! { table { tbody {
tr { td { "name" } td { (format!("{:?}", ref_item.name)) } }
tr { td { "index" } td { (ref_item.index) } }
tr { td { "name" } td { (format!("{:?}", ref_item[0].name)) } }
tr { td { "index" } td { (ref_item[0].index) } }
}}}
},
Value::Dir(dir_item) | Value::DirIndex(dir_item) => {
html! { table { tbody {
tr { td { "name" } td { (format!("{:?}", dir_item.name)) } }
tr { td { "target key" } td { (format!("{:X} {:?} {:X}", dir_item.location.key_id, dir_item.location.key_type, dir_item.location.key_offset)) } }
}}}
},
Value::Root(root_item) => {
@ -278,9 +305,9 @@ fn item_details_string(_tree_id: u64, item: &Item) -> Markup {
},
Value::RootRef(root_ref_item) => {
html! { table { tbody {
tr { td { "name" } td { (format!("{:?}", root_ref_item.name)) } }
tr { td { "directory" } td { (root_ref_item.directory) } }
tr { td { "index" } td { (root_ref_item.index) } }
tr { td { "name" } td { (format!("{:?}", root_ref_item[0].name)) } }
tr { td { "directory" } td { (root_ref_item[0].directory) } }
tr { td { "index" } td { (root_ref_item[0].index) } }
}}}
},
_ => {

View File

@ -171,7 +171,7 @@ fn http_main_boxes(image: &[u8], _req: &Request) -> Response {
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 tree_id = e.block_refs.iter().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));

View File

@ -133,6 +133,17 @@ span.key_type.root {
background-color: #111;
}
span.nodeaddr {
color: white;
background-color: #999;
text-align: right;
border-radius: 4px;
padding: 3px;
float: right;
font-family: monospace;
font-size: 12pt;
}
.details table {
border-collapse: collapse;
margin-bottom: 10px;
@ -151,3 +162,7 @@ span.key_type.root {
padding: 0;
margin: 5px 0;
}
pre {
white-space: pre-wrap;
}