318 lines
10 KiB
Rust
318 lines
10 KiB
Rust
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, u64, &'a [u8])>, // item, node addr, data
|
|
pub first_key: Key,
|
|
pub last_key: Key,
|
|
}
|
|
|
|
pub fn render_table(table: TableResult) -> Markup {
|
|
|
|
let header: String = if let Some(desc) = table.tree_desc {
|
|
format!("Tree {} ({})", table.tree_id, desc)
|
|
} else {
|
|
format!("Tree {}", table.tree_id)
|
|
};
|
|
|
|
let key_input_value = table.key_id.map_or(String::new(), |x| format!("{:X}", x));
|
|
|
|
let first_key_url = format!("/tree/{}",
|
|
table.tree_id);
|
|
let prev_key_url = format!("/tree/{}/to/{:016X}-{:02X}-{:016X}",
|
|
table.tree_id,
|
|
table.first_key.key_id,
|
|
u8::from(table.first_key.key_type),
|
|
table.first_key.key_offset);
|
|
let next_key_url = format!("/tree/{}/from/{:016X}-{:02X}-{:016X}",
|
|
table.tree_id,
|
|
table.last_key.key_id,
|
|
u8::from(table.last_key.key_type),
|
|
table.first_key.key_offset);
|
|
let last_key_url = format!("/tree/{}/to/{:016X}-{:02X}-{:016X}",
|
|
table.tree_id,
|
|
u64::wrapping_sub(0,1),
|
|
u8::wrapping_sub(0,1),
|
|
u64::wrapping_sub(0,1));
|
|
|
|
let mut rows: Vec<Markup> = Vec::new();
|
|
|
|
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) {
|
|
summary {
|
|
span.key.key_id.(key_type_class(it.key)) {
|
|
(id_desc.0)
|
|
}
|
|
span.key.key_type.(key_type_class(it.key)) {
|
|
(id_desc.1)
|
|
}
|
|
span.key.key_offset.(key_type_class(it.key)) {
|
|
(id_desc.2)
|
|
}
|
|
span.itemvalue.(key_type_class(it.key)) {
|
|
(&value_string)
|
|
}
|
|
span.nodeaddr {
|
|
(Hex(node_addr))
|
|
}
|
|
}
|
|
|
|
div.details {
|
|
(&details_string)
|
|
|
|
details {
|
|
summary {
|
|
"show full value"
|
|
}
|
|
pre {
|
|
(&raw_string)
|
|
}
|
|
}
|
|
|
|
details {
|
|
summary {
|
|
"show hex data"
|
|
}
|
|
pre {
|
|
(&hex_data)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// the complete page
|
|
html! {
|
|
(DOCTYPE)
|
|
head {
|
|
link rel="stylesheet" href="/style.css";
|
|
}
|
|
body {
|
|
h1 {
|
|
(header)
|
|
}
|
|
|
|
@if table.tree_id != 1 {
|
|
a href="/tree/1" {
|
|
"go back to root tree"
|
|
}
|
|
}
|
|
|
|
form method="get" action={"/tree/" (table.tree_id)} {
|
|
input type="text" name="key" value=(key_input_value);
|
|
input type="submit" value="Search";
|
|
}
|
|
|
|
a.nav href=(first_key_url) { div.nav { "first" } }
|
|
a.nav href=(prev_key_url) { div.nav { "prev" } }
|
|
|
|
@for row in &rows { (row) }
|
|
|
|
a.nav href=(next_key_url) { div.nav { "next" } }
|
|
a.nav href=(last_key_url) { div.nav { "last" } }
|
|
}
|
|
}
|
|
}
|
|
|
|
fn key_type_class(key: Key) -> &'static str {
|
|
match key.key_type {
|
|
ItemType::Inode => "inode",
|
|
ItemType::Ref => "ref",
|
|
ItemType::RootRef => "ref",
|
|
ItemType::RootBackRef => "ref",
|
|
ItemType::ExtentData => "extent",
|
|
ItemType::Dir => "dir",
|
|
ItemType::DirIndex => "dir",
|
|
ItemType::Root => "root",
|
|
_ => "",
|
|
}
|
|
}
|
|
|
|
fn row_id_desc(key: Key, tree_id: u64) -> (Markup, Markup, Markup) {
|
|
let x = format!("{:X}", key.key_id);
|
|
let y = format!("{:?} ({:02X})", key.key_type, u8::from(key.key_type));
|
|
let z = if key.key_type == ItemType::RootRef || key.key_type == ItemType::Ref {
|
|
format!("<a href=\"/tree/{}/{:X}\">{:X}</a>", tree_id, key.key_offset, key.key_offset)
|
|
} else {
|
|
format!("{:X}", key.key_offset)
|
|
};
|
|
(PreEscaped(x),PreEscaped(y),PreEscaped(z))
|
|
}
|
|
|
|
fn item_value_string(tree_id: u64, item: &Item) -> Markup {
|
|
match &item.value {
|
|
Value::Root(_) => {
|
|
html! { a href={"/tree/" (item.key.key_id)} { "go to tree " (item.key.key_id) } }
|
|
},
|
|
Value::Dir(dir_item) | Value::DirIndex(dir_item) => {
|
|
let name = format!("{:?}", &dir_item.name);
|
|
let id = dir_item.location.key_id;
|
|
html! {
|
|
(name)
|
|
" @ "
|
|
@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))
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Value::Inode(inode_item) => {
|
|
let file_type = match inode_item.mode / (1<<12) {
|
|
4 => "directory",
|
|
2 => "character device",
|
|
6 => "block device",
|
|
8 => "regular file",
|
|
1 => "FIFO",
|
|
10 => "symbolic link",
|
|
12 => "socket",
|
|
_ => "unknown file type",
|
|
};
|
|
format_escape!("{}, mode {}{}{}{}", file_type,
|
|
(inode_item.mode / (1<<9)) % 8,
|
|
(inode_item.mode / (1<<6)) % 8,
|
|
(inode_item.mode / (1<<3)) % 8,
|
|
(inode_item.mode / (1<<0)) % 8)
|
|
},
|
|
Value::ExtentData(extent_data_item) =>
|
|
match &extent_data_item.data {
|
|
ExtentDataBody::Inline(data) =>
|
|
PreEscaped(format!("inline, length {}", size_name(data.len() as u64))),
|
|
ExtentDataBody::External(ext_extent) =>
|
|
PreEscaped(format!("external, length {}", size_name(ext_extent.num_bytes))),
|
|
},
|
|
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: {: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) =>
|
|
PreEscaped(format!("chunk_tree: {}, chunk_offset: {:x}, length: {}", dev_extent_item.chunk_tree, dev_extent_item.chunk_offset, size_name(dev_extent_item.length))),
|
|
Value::UUIDSubvol(uuid_subvol_item) =>
|
|
PreEscaped(format!("subvolume id: {}", uuid_subvol_item.subvol_id)),
|
|
Value::FreeSpaceInfo(free_space_info) =>
|
|
PreEscaped(format!("extent_count: {}, flags: {}", free_space_info.extent_count, free_space_info.flags)),
|
|
Value::Dev(dev_item) =>
|
|
PreEscaped(format!("total_bytes: {}", size_name(dev_item.total_bytes))),
|
|
Value::Chunk(chunk_item) =>
|
|
PreEscaped(format!("size: {}", size_name(chunk_item.size))),
|
|
_ => {
|
|
// println!("{:?} {:?}", item.key, item.valu);
|
|
PreEscaped(String::new())
|
|
},
|
|
}
|
|
}
|
|
|
|
fn item_details_string(_tree_id: u64, item: &Item) -> Markup {
|
|
match &item.value {
|
|
Value::Inode(inode_item) => {
|
|
html! { table { tbody {
|
|
tr { td { "size" } td { (inode_item.size) } }
|
|
tr { td { "mode" } td { (inode_item.mode) } }
|
|
tr { td { "uid" } td { (inode_item.uid) } }
|
|
tr { td { "gid" } td { (inode_item.gid) } }
|
|
tr { td { "nlink" } td { (inode_item.nlink) } }
|
|
tr { td { "atime" } td { (inode_item.atime.sec) } }
|
|
tr { td { "ctime" } td { (inode_item.ctime.sec) } }
|
|
tr { td { "mtime" } td { (inode_item.mtime.sec) } }
|
|
tr { td { "otime" } td { (inode_item.otime.sec) } }
|
|
}}}
|
|
},
|
|
Value::ExtentData(extent_item) => {
|
|
match &extent_item.data {
|
|
ExtentDataBody::Inline(_data) => {
|
|
html! {} // we really want data as string / hex
|
|
},
|
|
ExtentDataBody::External(ext_extent) => {
|
|
html! {
|
|
p {
|
|
@if ext_extent.disk_bytenr == 0 {
|
|
(size_name(ext_extent.num_bytes)) " of zeros."
|
|
} @ else {
|
|
(format!("{} on disk, starting at offset {:X} within the extent at address {:X}; {} in the file starting from offset {:X}.", size_name(ext_extent.disk_num_bytes), ext_extent.offset, ext_extent.disk_bytenr, size_name(ext_extent.num_bytes), item.key.key_offset))
|
|
}
|
|
}
|
|
table { tbody {
|
|
tr { td { "compression" } td { (extent_item.header.compression) } }
|
|
tr { td { "encryption" } td { (extent_item.header.encryption) } }
|
|
tr { td { "other_encoding" } td { (extent_item.header.other_encoding) } }
|
|
}}
|
|
}
|
|
},
|
|
}
|
|
},
|
|
Value::Ref(ref_item) => {
|
|
html! { table { tbody {
|
|
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) => {
|
|
html! { table { tbody {
|
|
tr { td { "root dir id" } td { (format!("{:X}", root_item.root_dirid)) } }
|
|
tr { td { "logical address" } td { (format!("{:X}", root_item.bytenr)) } }
|
|
tr { td { "bytes used" } td { (size_name(root_item.bytes_used)) } }
|
|
tr { td { "last snapshot" } td { (root_item.last_snapshot) } }
|
|
tr { td { "flags" } td { (root_item.flags) } }
|
|
tr { td { "refs" } td { (root_item.refs) } }
|
|
tr { td { "level" } td { (root_item.level) } }
|
|
tr { td { "UUID" } td { (format!("{:?}", root_item.uuid)) } }
|
|
tr { td { "parent UUID" } td { (format!("{:?}", root_item.parent_uuid)) } }
|
|
tr { td { "received UUID" } td { (format!("{:?}", root_item.received_uuid)) } }
|
|
tr { td { "ctransid" } td { (root_item.ctransid) } }
|
|
tr { td { "otransid" } td { (root_item.otransid) } }
|
|
tr { td { "stransid" } td { (root_item.stransid) } }
|
|
tr { td { "rtransid" } td { (root_item.rtransid) } }
|
|
tr { td { "ctime" } td { (root_item.ctime.sec) } }
|
|
tr { td { "otime" } td { (root_item.otime.sec) } }
|
|
tr { td { "stime" } td { (root_item.stime.sec) } }
|
|
tr { td { "rtime" } td { (root_item.rtime.sec) } }
|
|
}}}
|
|
},
|
|
Value::RootRef(root_ref_item) => {
|
|
html! { table { tbody {
|
|
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) } }
|
|
}}}
|
|
},
|
|
_ => {
|
|
html! {}
|
|
},
|
|
}
|
|
}
|