display items as table rows

This commit is contained in:
Florian Stecker 2024-02-09 00:17:10 -05:00
parent e2fb0cbb47
commit 80942c8ed3
4 changed files with 289 additions and 60 deletions

View File

@ -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<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.chunk_root })
}
/// Read a node given its logical address
pub fn get_node(&self, addr: u64) -> Result<Rc<Node>, 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)?;

View File

@ -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<u8>,
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;

View File

@ -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###"<html>
<head>
<link rel="stylesheet" href="/style.css">
<script>
document.addEventListener("DOMContentLoaded", function() {
folds = document.getElementsByClassName("fold");
for(i = 0; i < folds.length; i++) {
// folds[i].classList.add("open");
folds[i].addEventListener("click", (event) => {
folds[i].classList.add("open");
});
}
});
</script>
</head>
<body>
"###;
static HTML_FOOTER: &str = r###"</body>
</html>
"###;
static EXPLANATION_TEXT: &str = "\
<h3>Chunks</h3>
@ -64,6 +84,24 @@ static EXPLANATION_TEXT: &str = "\
<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 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<u64, &str> = 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!("<tr class = \"{even}\"><td>{:X}</td><td>{:?}</td><td>{:X}</td><td>{}</td></tr>\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!("<tr class = \"{even}\"><td>{}</td><td>{:?}</td><td>{:X}</td><td>{}</td></tr>\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!("<a href=\"/tree/{}\">go</a>", key.key_id)
},
Value::Dir(dir_item) =>
format!("name: {:?}, location: <a href=\"/tree/{}/{:x}\">{:X}<a>",
&dir_item.name,
tree_id,
dir_item.location.key_id,
dir_item.location.key_id),
Value::DirIndex(dir_item) =>
format!("name: {:?}, location: <a href=\"/tree/{}/{:x}\">{:X}<a>",
&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<u64>) -> Response {
let mut table: Vec<String> = Vec::new();
table.push(String::from("<table><tbody>"));
let k = Key {key_id: key.unwrap_or(0), key_type: ItemType::Invalid, key_offset: 0};
let mut last_key_id: Option<u64> = 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("</tbody></table>"));
let mut result = String::new();
result.push_str(HTML_HEADER);
result.extend(table.iter().map(<String as AsRef<str>>::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);

52
style.css Normal file
View File

@ -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;
}
*/