From 80942c8ed3be5d2d5d3cb3c53828e2d56bcaaa02 Mon Sep 17 00:00:00 2001 From: Florian Stecker Date: Fri, 9 Feb 2024 00:17:10 -0500 Subject: [PATCH] display items as table rows --- src/btrfs_lookup.rs | 13 ++- src/btrfs_structs.rs | 89 ++++++++++---------- src/main.rs | 195 ++++++++++++++++++++++++++++++++++++++++--- style.css | 52 ++++++++++++ 4 files changed, 289 insertions(+), 60 deletions(-) create mode 100644 style.css diff --git a/src/btrfs_lookup.rs b/src/btrfs_lookup.rs index 86f31cb..bc1df67 100644 --- a/src/btrfs_lookup.rs +++ b/src/btrfs_lookup.rs @@ -2,8 +2,8 @@ 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, DirItem}; -use crate::addrmap::{node_at_log, LogToPhys, AddressMap}; +use crate::btrfs_structs::{Leaf, Key, Item, InteriorNode, Node, ParseError, ParseBin, Value, Superblock, ItemType, ZERO_KEY}; +use crate::addrmap::{node_at_log, AddressMap}; /// Represents a B-Tree inside a filesystem image. Can be used to look up keys, /// and handles the tree traversal and the virtual address translation. @@ -40,9 +40,16 @@ impl<'a> Tree<'a> { Ok(Tree { image, addr_map, root_addr_log: superblock.root }) } + pub fn chunk(image: &'a [u8]) -> Result, ParseError> { + let addr_map = Rc::new(AddressMap::new(image)?); + let superblock = Superblock::parse(&image[0x10000..])?; + + Ok(Tree { image, addr_map, root_addr_log: superblock.chunk_root }) + } + /// Read a node given its logical address pub fn get_node(&self, addr: u64) -> Result, ParseError> { - eprintln!("Reading node at {:x}", addr); +// eprintln!("Reading node at {:x}", addr); let node_data = node_at_log(self.image, self.addr_map.as_ref(), addr)?; let node = Node::parse(node_data)?; diff --git a/src/btrfs_structs.rs b/src/btrfs_structs.rs index d2f4c17..29ad183 100644 --- a/src/btrfs_structs.rs +++ b/src/btrfs_structs.rs @@ -311,63 +311,63 @@ pub struct RootItem { #[allow(unused)] #[derive(Debug,Clone,ParseBin)] pub struct DirItem { - location: Key, - transid: u64, - data_len: u16, - name_len: u16, - dir_type: u8, + pub location: Key, + pub transid: u64, + pub data_len: u16, + pub name_len: u16, + pub dir_type: u8, // #[len = "name_len"] - name: CString, + pub name: CString, } #[allow(unused)] #[derive(Debug,Clone,ParseBin)] pub struct FreeSpaceInfoItem { - extent_count: u32, - flags: u32, + pub extent_count: u32, + pub flags: u32, } #[allow(unused)] #[derive(Debug,Clone,ParseBin)] pub struct UUIDSubvolItem { - subvol_id: u64, + pub subvol_id: u64, } #[allow(unused)] #[derive(Debug,Clone,ParseBin)] pub struct DevItem { - devid: u64, - total_bytes: u64, - bytes_used: u64, - io_align: u32, - io_width: u32, - sector_size: u32, - dev_type: u64, - generation: u64, - start_offset: u64, - dev_group: u32, - seek_speed: u8, - bandwidth: u8, - uuid: UUID, - fsid: UUID, + pub devid: u64, + pub total_bytes: u64, + pub bytes_used: u64, + pub io_align: u32, + pub io_width: u32, + pub sector_size: u32, + pub dev_type: u64, + pub generation: u64, + pub start_offset: u64, + pub dev_group: u32, + pub seek_speed: u8, + pub bandwidth: u8, + pub uuid: UUID, + pub fsid: UUID, } #[allow(unused)] #[derive(Debug,Clone,ParseBin)] pub struct DevExtentItem { - chunk_tree: u64, - chunk_objectid: u64, - chunk_offset: u64, - length: u64, - chunk_tree_uuid: UUID, + pub chunk_tree: u64, + pub chunk_objectid: u64, + pub chunk_offset: u64, + pub length: u64, + pub chunk_tree_uuid: UUID, } #[allow(unused)] #[derive(Debug,Clone)] pub struct ExtentDataItem { - header: ExtentDataHeader, - data: ExtentDataBody, + pub header: ExtentDataHeader, + pub data: ExtentDataBody, } #[allow(unused)] @@ -380,31 +380,31 @@ pub enum ExtentDataBody { #[allow(unused)] #[derive(Debug,Clone,ParseBin)] pub struct ExternalExtent { - disk_bytenr: u64, - disk_num_bytes: u64, - offset: u64, - num_bytes: u64, + pub disk_bytenr: u64, + pub disk_num_bytes: u64, + pub offset: u64, + pub num_bytes: u64, } #[allow(unused)] #[derive(Debug,Clone,ParseBin)] pub struct ExtentDataHeader { - generation: u64, - ram_bytes: u64, - compression: u8, - encryption: u8, - other_encoding: u16, - extent_type: u8, + pub generation: u64, + pub ram_bytes: u64, + pub compression: u8, + pub encryption: u8, + pub other_encoding: u16, + pub extent_type: u8, } #[allow(unused)] #[derive(Debug,Clone,ParseBin)] pub struct RefItem { - index: u64, - name_len: u16, + pub index: u64, + pub name_len: u16, // #[len = "name_len"] - name: Vec, + pub name: CString, } #[allow(unused)] @@ -721,6 +721,7 @@ impl fmt::Debug for Checksum { } } +#[macro_export] macro_rules! key { ($arg1:expr) => { btrfs_structs::Key { key_id: $arg1, key_type: btrfs_structs::ItemType::Invalid, key_offset: 0 } @@ -733,4 +734,4 @@ macro_rules! key { }; } -pub(crate) use key; +//pub(crate) use key; diff --git a/src/main.rs b/src/main.rs index c93f4ca..e3d8c8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,14 @@ use std::{ env, fs::OpenOptions, collections::HashMap, + str::FromStr, + fs::File, }; use memmap2::Mmap; use rouille::{Request, Response, router}; use parsebtrfs::{ - btrfs_structs::{TreeID, Value::Extent, Value::BlockGroup, ParseError, NODE_SIZE, ItemType}, - btrfs_lookup::Tree, + btrfs_structs::{TreeID, Value::Extent, Value::BlockGroup, ParseError, NODE_SIZE, ItemType, Item, RootItem, Value, Key, ExtentDataBody}, + btrfs_lookup::Tree, key, }; const COLORS: &[&str] = &["#e6194b", "#3cb44b", "#ffe119", "#4363d8", "#f58231", "#911eb4", "#46f0f0", "#f032e6", "#bcf60c", "#fabebe", "#008080", "#e6beff", "#9a6324", "#fffac8", "#800000", "#aaffc3", "#808000", "#ffd8b1", "#000075", "#808080", "#000000"]; @@ -38,7 +40,11 @@ fn main() -> Result<(), MainError> { router!( request, (GET) ["/"] => http_main_boxes(&image, request), + (GET) ["/root"] => http_root(&image, None, request), + (GET) ["/tree/{tree}", tree: String] => http_tree(&image, &tree, None, request), + (GET) ["/tree/{tree}/{key}", tree: String, key: String] => http_tree(&image, &tree, Some(&key), request), (GET) ["/favicon.ico"] => Response::empty_404(), + (GET) ["/style.css"] => Response::from_file("text/css", File::open("style.css").unwrap()), _ => Response::empty_404(), ) }); @@ -46,17 +52,31 @@ fn main() -> Result<(), MainError> { 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="; + iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAP0lEQVQY02NgoBn4//+//P///yf9\ + ////DRRP+v//vzw2hZP+Y4JJ2BS+waLwDUyeiVinIStchkV+GfmeoRoAAJqLWnEf4UboAAAAAElF\ + TkSuQmCC"; + +static HTML_HEADER: &str = r###" + + + + + +"###; + +static HTML_FOOTER: &str = r###" + +"###; static EXPLANATION_TEXT: &str = "\

Chunks

@@ -64,6 +84,24 @@ static EXPLANATION_TEXT: &str = "\

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 nodes, here symbolized by colorful boxes, with the color indicating which tree the node belongs to. Most of the nodes are leaves, which contain the actual key-value pairs. The others are interior nodes, and we indicate them with a little white circle. They are important to find the leaf a key is stored in.

"; +fn root_key_desc(id: u64) -> Option<&'static str> { + match id { + 1 => Some("root"), + 2 => Some("extent"), + 3 => Some("chunk"), + 4 => Some("device"), + 5 => Some("filesystem"), + 6 => Some("root directory"), + 7 => Some("checksum"), + 8 => Some("quota"), + 9 => Some("UUID"), + 10 => Some("free space"), + 11 => Some("block group"), + 0xffff_ffff_ffff_fff7 => Some("data reloc"), + _ => None, + } +} + fn http_main_boxes(image: &[u8], _req: &Request) -> Response { let mut treecolors: HashMap = HashMap::new(); @@ -266,6 +304,137 @@ fn http_main_boxes(image: &[u8], _req: &Request) -> Response { Response::html(result) } +fn html_item_row(key: Key, value: String, index: usize) -> String { + let even = if index % 2 == 0 { "even" } else { "odd" }; + format!("{:X}{:?}{:X}{}\n", + key.key_id, key.key_type, key.key_offset, value) +} + +fn html_item_row_id_desc(key: Key, value: String, index: usize, desc: &str) -> String { + let even = if index % 2 == 0 { "even" } else { "odd" }; + format!("{}{:?}{:X}{}\n", + desc, key.key_type, key.key_offset, value) +} + +fn item_value_string(tree_id: u64, key: Key, val: &Value) -> String { + match val { + Value::Root(_) => { + format!("go", key.key_id) + }, + Value::Dir(dir_item) => + format!("name: {:?}, location: {:X}", + &dir_item.name, + tree_id, + dir_item.location.key_id, + dir_item.location.key_id), + Value::DirIndex(dir_item) => + format!("name: {:?}, location: {:X}", + &dir_item.name, + tree_id, + dir_item.location.key_id, + dir_item.location.key_id), + Value::Inode(inode_item) => + format!("mode: {:o}, ctime: {}, mtime: {}, otime: {}", + inode_item.mode, + inode_item.ctime.sec, + inode_item.mtime.sec, + inode_item.otime.sec), + Value::ExtentData(extent_data_item) => + match &extent_data_item.data { + ExtentDataBody::Inline(data) => format!("inline, len: {}", data.len()), + ExtentDataBody::External(ext_extent) => + format!("external, disk_bytenr: {}, disk_num_bytes: {}, offset: {}, num_bytes: {}", + ext_extent.disk_bytenr, + ext_extent.disk_num_bytes, + ext_extent.offset, + ext_extent.num_bytes), + }, + Value::Ref(ref_item) => + format!("name: {:?}, index: {}", + &ref_item.name, + ref_item.index), + Value::Extent(extent_item) => + format!("flags: {}, block_refs: {:?}", extent_item.flags, extent_item.block_refs), + Value::BlockGroup(blockgroup_item) => + format!("used: {} bytes", blockgroup_item.used), + Value::DevExtent(dev_extent_item) => + format!("chunk_tree: {}, chunk_offset: {:x}, length: {} bytes", dev_extent_item.chunk_tree, dev_extent_item.chunk_offset, dev_extent_item.length), + Value::UUIDSubvol(uuid_subvol_item) => + format!("subvolume id: {}", uuid_subvol_item.subvol_id), + Value::FreeSpaceInfo(free_space_info) => + format!("extent_count: {}, flags: {}", free_space_info.extent_count, free_space_info.flags), + Value::Dev(dev_item) => + format!("total_bytes: {}", dev_item.total_bytes), + Value::Chunk(chunk_item) => + format!("size: {}", chunk_item.size), + _ => { + println!("{:?}", val); + String::new() + }, + } +} + +fn http_tree_internal(tree: &Tree, tree_id: u64, key: Option) -> Response { + let mut table: Vec = Vec::new(); + + table.push(String::from("")); + + let k = Key {key_id: key.unwrap_or(0), key_type: ItemType::Invalid, key_offset: 0}; + let mut last_key_id: Option = None; + let mut key_index = 0; + + for item in tree.range(k..).take(3000) { + if last_key_id.replace(item.key.key_id).filter(|x| *x != item.key.key_id).is_some() { + key_index += 1; + } + + match &item.value { + Value::Root(_) => { + let id = match root_key_desc(item.key.key_id) { + Some(desc) => format!("{:X} ({})", item.key.key_id, desc), + None => format!("{:X}", item.key.key_id), + }; + let value = item_value_string(tree_id, item.key, &item.value); + table.push(html_item_row_id_desc(item.key, value, key_index, &id)); + }, + _ => { + table.push(html_item_row(item.key, item_value_string(tree_id, item.key, &item.value), key_index)); + }, + } + } + + table.push(String::from("
")); + + let mut result = String::new(); + + result.push_str(HTML_HEADER); + result.extend(table.iter().map(>::as_ref)); + result.push_str(HTML_FOOTER); + + Response::html(result) + +} + +fn http_tree(image: &[u8], tree_id: &str, key: Option<&str>, _req: &Request) -> Response { + let key_id = key.and_then(|x|u64::from_str_radix(x, 16).ok()); + let tree_id = u64::from_str(tree_id).unwrap(); + let tree = if tree_id == 1 { + Tree::root(image).unwrap() + } else if tree_id == 3 { + Tree::chunk(image).unwrap() + } else { + Tree::new(image, tree_id).unwrap() + }; + http_tree_internal(&tree, tree_id, key_id) +} + +fn http_root(image: &[u8], key: Option<&str>, _req: &Request) -> Response { + let key_id = key.and_then(|x|u64::from_str_radix(x, 16).ok()); + let tree = Tree::root(image).unwrap(); + http_tree_internal(&tree, 1, key_id) +} + + // ----- Error handling ----- pub struct MainError(String); diff --git a/style.css b/style.css new file mode 100644 index 0000000..29da254 --- /dev/null +++ b/style.css @@ -0,0 +1,52 @@ +body { + padding: 0.2em 2em; +} + +table { + width: 100%; +} + +table td { + padding: 0.1em 0.2em; +} + +table tr:nth-child(even) { +} + +table th { + text-align: left; + border-bottom: 1px solid #ccc; +} + +table > tbody > tr.view { + cursor: pointer; +} + +table > tbody > tr.even { + background: #eee; +} + +table > tbody > tr.fold { + display: none; +} + +table > tbody > tr.fold > td { + padding-left: 1em; +} + +table > tbody > tr.fold.open { + display: table-row; +} + +/* +table > tbody > tr.view td:first-child:before { + color: #999; + content: "x"; + transition: all 0.3s ease; +} + +table > tbody > tr.view.open td:first-child:before { + transform: rotate(-180deg); + color: #333; +} +*/