commit bdcc597b9a723ea12200d6881d75db197c30221d
Author: Florian Stecker
Date: Thu Jul 27 08:05:24 2023 -0400
initial version
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..36817bb
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "parsebtrfs"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+memmap2 = "0.7.1"
+rouille = "3.6.2"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..0e6d6ae
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,575 @@
+use memmap2::Mmap;
+use std::fs::File;
+use rouille::Request;
+use rouille::Response;
+
+const ACTIVE_NODES: &'static[usize] = &[0x14000, 0x18000, 0x1c000, 0x20000, 0x28000, 0x2c000, 0x3c000, 0x40000];
+
+fn main() -> Result<(), std::io::Error> {
+ let file = File::open("../image")?;
+ let image = unsafe { Mmap::map(&file)? };
+
+ let addr = AddressTranslation::new(&image);
+
+ rouille::start_server("127.0.0.1:8080", move |request| {
+ http_main_list(&image, &addr, request)
+ });
+}
+
+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("\n\n");
+
+ for i in 0..nodes_in_chunk {
+ if i % 64 == 0 {
+ result.push_str("
\n\n");
+ }
+
+ let node = read_node(&image, chunk_offset + i*0x4000);
+
+ let active = node.generation > 0 && ACTIVE_NODES.contains(&(i*0x4000));
+
+ let newbox = format!(" | \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("
\n
");
+
+ Response::html(result)
+}
+
+fn http_main_list(image: &[u8], addr: &AddressTranslation, req: &Request) -> Response {
+ let chunk_offset = 0x02500000;
+ let nodes_in_chunk = 2048;
+ let mut result = String::new();
+
+ result.push_str("\n");
+
+ for i in 0..nodes_in_chunk {
+ let node = read_node(&image, chunk_offset + i*0x4000);
+
+ let active = ACTIVE_NODES.contains(&(i*0x4000));
+ let style = if active { "color:black;" } else { "color:lightgray;" };
+
+ let newline = format!("{:x} {} {} {}\n
\n",
+ style,
+ chunk_offset + i*0x4000,
+ node.level,
+ node.items.len(),
+ node.generation);
+ result.push_str(&newline);
+
+ for item in &node.items {
+ let newline = format!("- {:016x} {:?} {:x}
\n",
+ style,
+ item.key.key_id,
+ item.key.key_type,
+ item.key.key_offset);
+ result.push_str(&newline);
+ }
+
+ result.push_str("
\n");
+ }
+
+ Response::html(result)
+}
+
+/**********************************************************************/
+
+trait FromBytes {
+ fn get(bytes: &[u8], offset: usize) -> Self;
+}
+
+impl FromBytes for u8 {
+ fn get(bytes: &[u8], offset: usize) -> u8 {
+ bytes[offset]
+ }
+}
+
+impl FromBytes for u16 {
+ fn get(bytes: &[u8], offset: usize) -> u16 {
+ u16::from_le_bytes(bytes[offset..offset+2].try_into().unwrap())
+ }
+}
+
+impl FromBytes for u32 {
+ fn get(bytes: &[u8], offset: usize) -> u32 {
+ u32::from_le_bytes(bytes[offset..offset+4].try_into().unwrap())
+ }
+}
+
+impl FromBytes for u64 {
+ fn get(bytes: &[u8], offset: usize) -> u64 {
+ u64::from_le_bytes(bytes[offset..offset+8].try_into().unwrap())
+ }
+}
+
+impl FromBytes for [u8; N] {
+ fn get(bytes: &[u8], offset: usize) -> [u8; N] {
+ bytes[offset..offset+N].try_into().unwrap()
+ }
+}
+
+impl FromBytes for BtrfsKey {
+ fn get(bytes: &[u8], offset: usize) -> BtrfsKey {
+ BtrfsKey {
+ key_id: FromBytes::get(bytes, offset),
+ key_type: itemtype_from_code(FromBytes::get(bytes, offset+8)),
+ key_offset: FromBytes::get(bytes, offset+9),
+ }
+ }
+}
+
+impl FromBytes for BtrfsTime {
+ fn get(bytes: &[u8], offset: usize) -> BtrfsTime {
+ BtrfsTime {
+ sec: FromBytes::get(bytes, offset),
+ nsec: FromBytes::get(bytes, offset+8),
+ }
+ }
+}
+
+impl FromBytes for BtrfsBlockGroupItem {
+ fn get(bytes: &[u8], offset: usize) -> BtrfsBlockGroupItem {
+ BtrfsBlockGroupItem {
+ used: FromBytes::get(bytes, 0),
+ chunk_objectid: FromBytes::get(bytes, 8),
+ flags: FromBytes::get(bytes, 16),
+ }
+ }
+}
+
+impl FromBytes for BtrfsExtentItem {
+ fn get(bytes: &[u8], offset: usize) -> BtrfsExtentItem {
+ BtrfsExtentItem {
+ refs: FromBytes::get(bytes, 0),
+ generation: FromBytes::get(bytes, 8),
+ flags: FromBytes::get(bytes, 16),
+ data: Vec::from(&bytes[24..]),
+ }
+ }
+}
+
+impl FromBytes for BtrfsChunkItem {
+ fn get(bytes: &[u8], offset: usize) -> BtrfsChunkItem {
+ let data_slice = &bytes[offset..];
+ let nrstripes: u16 = FromBytes::get(data_slice, 0x2c);
+ let mut stripes: Vec = Vec::new();
+
+ for i in 0..nrstripes as usize {
+ stripes.push(BtrfsChunkItemStripe {
+ devid: FromBytes::get(data_slice, 0x30 + i*0x20),
+ offset: FromBytes::get(data_slice, 0x38 + i*0x20),
+ devuuid: FromBytes::get(data_slice, 0x40 + i*0x20),
+ });
+ }
+
+ BtrfsChunkItem {
+ size: FromBytes::get(data_slice, 0x00),
+ root: FromBytes::get(data_slice, 0x08),
+ stripelen: FromBytes::get(data_slice, 0x10),
+ chunktype: FromBytes::get(data_slice, 0x18),
+ align: FromBytes::get(data_slice, 0x20),
+ width: FromBytes::get(data_slice, 0x24),
+ sectorsize: FromBytes::get(data_slice, 0x28),
+ nrstripes: FromBytes::get(data_slice, 0x2c),
+ substripes: FromBytes::get(data_slice, 0x2e),
+ stripes: stripes,
+ }
+ }
+}
+
+impl FromBytes for BtrfsInodeItem {
+ fn get(bytes: &[u8], offset: usize) -> BtrfsInodeItem {
+ let data_slice = &bytes[offset..];
+ BtrfsInodeItem {
+ generation: FromBytes::get(data_slice, 0x00),
+ transid: FromBytes::get(data_slice, 0x08),
+ size: FromBytes::get(data_slice, 0x10),
+ nbytes: FromBytes::get(data_slice, 0x18),
+ block_group: FromBytes::get(data_slice, 0x20),
+ nlink: FromBytes::get(data_slice, 0x28),
+ uid: FromBytes::get(data_slice, 0x2c),
+ gid: FromBytes::get(data_slice, 0x30),
+ mode: FromBytes::get(data_slice, 0x34),
+ rdev: FromBytes::get(data_slice, 0x38),
+ flags: FromBytes::get(data_slice, 0x40),
+ sequence: FromBytes::get(data_slice, 0x48),
+ atime: FromBytes::get(data_slice, 0x54),
+ ctime: FromBytes::get(data_slice, 0x60),
+ mtime: FromBytes::get(data_slice, 0x6c),
+ otime: FromBytes::get(data_slice, 0x78),
+ }
+ }
+}
+
+impl FromBytes for BtrfsRootItem {
+ fn get(bytes: &[u8], offset: usize) -> BtrfsRootItem {
+ let data_slice = &bytes[offset..];
+ BtrfsRootItem {
+ inode: FromBytes::get(data_slice, 0x00),
+ generation: FromBytes::get(data_slice, 0xa0),
+ root_dirid: FromBytes::get(data_slice, 0xa8),
+ bytenr: FromBytes::get(data_slice, 0xb0),
+ byte_limit: FromBytes::get(data_slice, 0xb9),
+ bytes_used: FromBytes::get(data_slice, 0xc0),
+ last_snapshot: FromBytes::get(data_slice, 0xc8),
+ flags: FromBytes::get(data_slice, 0xd0),
+ refs: FromBytes::get(data_slice, 0xd8),
+ drop_progress: FromBytes::get(data_slice, 0xdc),
+ drop_level: FromBytes::get(data_slice, 0xed),
+ level: FromBytes::get(data_slice, 0xee),
+
+ /*
+ generation_v2: FromBytes::get(data_slice, 0xd3),
+ uuid: FromBytes::get(data_slice, 0xeb),
+ parent_uuid: FromBytes::get(data_slice, 0xfb),
+ received_uuid: FromBytes::get(data_slice, 0x10b),
+ ctransid: FromBytes::get(data_slice, 0x11b),
+ otransid: FromBytes::get(data_slice, 0x123),
+ stransid: FromBytes::get(data_slice, 0x12b),
+ rtransid: FromBytes::get(data_slice, 0x133),
+ ctime: FromBytes::get(data_slice, 0x13b),
+ otime: FromBytes::get(data_slice, 0x147),
+ stime: FromBytes::get(data_slice, 0x153),
+ rtime: FromBytes::get(data_slice, 0x15f),
+ */
+ }
+ }
+}
+
+
+fn read_node_log(image: &[u8], trans: &AddressTranslation, log: u64) -> Option> {
+ let phys = trans.to_phys(log)?;
+ Some(read_node(image, phys as usize))
+}
+
+fn read_node(image: &[u8], offset: usize) -> Box {
+ let mut result = Box::new(BtrfsNode {
+ csum: FromBytes::get(image, offset),
+ fs_uid: FromBytes::get(image, offset + 0x20),
+ bytenr: FromBytes::get(image, offset + 0x30),
+ flags: FromBytes::get(image, offset + 0x38),
+ chunk_tree_uid: FromBytes::get(image, offset + 0x40),
+ generation: FromBytes::get(image, offset + 0x50),
+ owner: FromBytes::get(image, offset + 0x58),
+ nritems: FromBytes::get(image, offset + 0x60),
+ level: FromBytes::get(image, offset + 0x64),
+ items: Vec::new(),
+ });
+
+ // assuming leaf for now
+
+ for i in 0..result.nritems as usize {
+ let key_id: u64 = FromBytes::get(image, offset + 0x65 + i*0x19);
+ let key_type_code: u8 = FromBytes::get(image, offset + 0x65 + i*0x19 + 0x08);
+ let key_offset: u64 = FromBytes::get(image, offset + 0x65 + i*0x19 + 0x09);
+ let data_offset: u32 = FromBytes::get(image, offset + 0x65 + i*0x19 + 0x11);
+ let data_size: u32 = FromBytes::get(image, offset + 0x65 + i*0x19 + 0x15);
+
+ let key_type = itemtype_from_code(key_type_code);
+ let data_slice = &image[(offset + 0x65 + data_offset as usize) .. (offset + 0x65 + data_offset as usize + data_size as usize)];
+
+ let value = match key_type {
+ BtrfsItemType::BlockGroup => BtrfsValue::BlockGroup(FromBytes::get(data_slice, 0)),
+ BtrfsItemType::Metadata => BtrfsValue::Extent(FromBytes::get(data_slice, 0)),
+ BtrfsItemType::Chunk => BtrfsValue::Chunk(FromBytes::get(data_slice, 0)),
+ BtrfsItemType::Root => BtrfsValue::Root(FromBytes::get(data_slice, 0)),
+ _ => BtrfsValue::Unknown(Vec::from(data_slice)),
+ };
+
+ result.items.push(BtrfsItem {
+ key: BtrfsKey {
+ key_id: key_id,
+ key_type: key_type,
+ key_offset: key_offset,
+ },
+ value: value,
+ });
+ }
+
+ result
+}
+
+fn itemtype_from_code(code: u8) -> BtrfsItemType {
+ match BTRFS_ITEM_TYPE_VALUES.binary_search_by_key(&code, |x|*x as u8) {
+ Ok(i) => BTRFS_ITEM_TYPE_VALUES[i],
+ Err(_) => { panic!("expected BtrfsItemType, found {code}"); }
+ }
+}
+
+#[derive(Debug,Clone,Copy)]
+#[repr(u8)]
+enum BtrfsItemType {
+ Invalid = 0x00,
+ Inode = 0x01,
+ Ref = 0x0c,
+ ExtRef = 0x0d,
+ XAttr = 0x18,
+ VerityDesc = 0x24,
+ VerityMerkle = 0x25,
+ Orphan = 0x30,
+ DirLog = 0x3c,
+ DirLogIndex = 0x48,
+ Dir = 0x54,
+ DirIndex = 0x60,
+ ExtentData = 0x6c,
+ ExtentCsum = 0x80,
+ Root = 0x84,
+ RootBackref = 0x90,
+ RootRef = 0x9c,
+ Extent = 0xa8,
+ Metadata = 0xa9,
+ TreeBlockRef = 0xb0,
+ ExtentDataRef = 0xb2,
+ ExtentRefV0 = 0xb4,
+ SharedBlockRef = 0xb6,
+ SharedDataRef = 0xb8,
+ BlockGroup = 0xc0,
+ FreeSpaceInfo = 0xc6,
+ FreeSpaceExtent = 0xc7,
+ FreeSpaceBitmap = 0xc8,
+ DevExtent = 0xcc,
+ Dev = 0xd8,
+ Chunk = 0xe4,
+ QGroupStatus = 0xf0,
+ QGroupInfo = 0xf2,
+ QGroupLimit = 0xf4,
+ QGroupRelation = 0xf6,
+ Temporary = 0xf8,
+ Persistent = 0xf9,
+ DevReplace = 0xfa,
+ UUIDSubvol = 0xfb,
+ UUIDReceivedSubvol = 0xfc,
+ String = 0xfd,
+}
+
+const BTRFS_ITEM_TYPE_VALUES: &[BtrfsItemType] = &[BtrfsItemType::Invalid, BtrfsItemType::Inode, BtrfsItemType::Ref, BtrfsItemType::ExtRef, BtrfsItemType::XAttr, BtrfsItemType::VerityDesc, BtrfsItemType::VerityMerkle, BtrfsItemType::Orphan, BtrfsItemType::DirLog, BtrfsItemType::DirLogIndex, BtrfsItemType::Dir, BtrfsItemType::DirIndex, BtrfsItemType::ExtentData, BtrfsItemType::ExtentCsum, BtrfsItemType::Root, BtrfsItemType::RootBackref, BtrfsItemType::RootRef, BtrfsItemType::Extent, BtrfsItemType::Metadata, BtrfsItemType::TreeBlockRef, BtrfsItemType::ExtentDataRef, BtrfsItemType::ExtentRefV0, BtrfsItemType::SharedBlockRef, BtrfsItemType::SharedDataRef, BtrfsItemType::BlockGroup, BtrfsItemType::FreeSpaceInfo, BtrfsItemType::FreeSpaceExtent, BtrfsItemType::FreeSpaceBitmap, BtrfsItemType::DevExtent, BtrfsItemType::Dev, BtrfsItemType::Chunk, BtrfsItemType::QGroupStatus, BtrfsItemType::QGroupInfo, BtrfsItemType::QGroupLimit, BtrfsItemType::QGroupRelation, BtrfsItemType::Temporary, BtrfsItemType::Persistent, BtrfsItemType::DevReplace, BtrfsItemType::UUIDSubvol, BtrfsItemType::UUIDReceivedSubvol, BtrfsItemType::String];
+
+#[derive(Debug)]
+struct BtrfsNode {
+ csum: [u8; 32],
+ fs_uid: BtrfsUUID,
+ bytenr: u64,
+ flags: u64,
+ chunk_tree_uid: BtrfsUUID,
+ generation: u64,
+ owner: u64,
+ nritems: u32,
+ level: u8,
+
+ items: Vec,
+}
+
+#[derive(Debug)]
+struct BtrfsItem {
+ key: BtrfsKey,
+ value: BtrfsValue,
+}
+
+#[derive(Debug)]
+struct BtrfsKey {
+ key_id: u64,
+ key_type: BtrfsItemType,
+ key_offset: u64,
+}
+
+#[derive(Debug)]
+enum BtrfsValue {
+ NodePtr(u64),
+ Extent(BtrfsExtentItem),
+ BlockGroup(BtrfsBlockGroupItem),
+ Chunk(BtrfsChunkItem),
+ Root(BtrfsRootItem),
+ Unknown(Vec),
+}
+
+#[derive(Debug)]
+struct BtrfsExtentItem {
+ refs: u64,
+ generation: u64,
+ flags: u64,
+ data: Vec,
+}
+
+#[derive(Debug)]
+struct BtrfsBlockGroupItem {
+ used: u64,
+ chunk_objectid: u64,
+ flags: u64,
+}
+
+#[derive(Debug)]
+struct BtrfsDevItem {
+}
+
+#[derive(Debug)]
+struct BtrfsChunkItem {
+ size: u64,
+ root: u64,
+ stripelen: u64,
+ chunktype: u64,
+ align: u32,
+ width: u32,
+ sectorsize: u32,
+ nrstripes: u16,
+ substripes: u16,
+
+ stripes: Vec,
+}
+
+#[derive(Debug)]
+struct BtrfsChunkItemStripe {
+ devid: u64,
+ offset: u64,
+ devuuid: BtrfsUUID,
+}
+
+#[derive(Debug)]
+struct BtrfsTime {
+ sec: u64,
+ nsec: u32,
+}
+
+type BtrfsUUID = [u8; 16];
+
+#[derive(Debug)]
+struct BtrfsInodeItem {
+ generation: u64,
+ transid: u64,
+ size: u64,
+ nbytes: u64,
+ block_group: u64,
+ nlink: u32,
+ uid: u32,
+ gid: u32,
+ mode: u32,
+ rdev: u64,
+ flags: u64,
+ sequence: u64,
+ atime: BtrfsTime,
+ ctime: BtrfsTime,
+ mtime: BtrfsTime,
+ otime: BtrfsTime,
+}
+
+#[derive(Debug)]
+struct BtrfsRootItem {
+ inode: BtrfsInodeItem,
+ generation: u64,
+ root_dirid: u64,
+ bytenr: u64,
+ byte_limit: u64,
+ bytes_used: u64,
+ last_snapshot: u64,
+ flags: u64,
+ refs: u32,
+ drop_progress: BtrfsKey,
+ drop_level: u8,
+ level: u8,
+
+ /*
+ generation_v2: u64,
+ uuid: BtrfsUUID,
+ parent_uuid: BtrfsUUID,
+ received_uuid: BtrfsUUID,
+ ctransid: u64,
+ otransid: u64,
+ stransid: u64,
+ rtransid: u64,
+ ctime: BtrfsTime,
+ otime: BtrfsTime,
+ stime: BtrfsTime,
+ rtime: BtrfsTime,
+ */
+}
+
+struct AddressTranslation {
+ addr_map: Vec<(u64,u64,Vec<(u64,u64)>)>,
+}
+
+// TODO: support for internal nodes, multiple devices?
+impl AddressTranslation {
+ fn new(image: &[u8]) -> AddressTranslation {
+ let bootstrap_addr = AddressTranslation::from_superblock(&image);
+
+ let chunk_root_log: u64 = FromBytes::get(&image, 0x10058);
+ println!("Chunk Tree Root Logical Address: {:016x}", chunk_root_log);
+
+ let chunk_root_phys = bootstrap_addr.to_phys(chunk_root_log).unwrap();
+ println!("Chunk Tree Root Physical Address: {:016x}", chunk_root_phys);
+
+ let chunk_root = read_node(&image, chunk_root_phys as usize);
+
+ let mut addr_map = Vec::new();
+ for item in chunk_root.items {
+ let chunk_key = item.key;
+ if let BtrfsValue::Chunk(chunk_value) = item.value {
+ addr_map.push((
+ chunk_key.key_offset,
+ chunk_value.size,
+ chunk_value.stripes.iter()
+ .map(|stripe|(stripe.devid, stripe.offset))
+ .collect()
+ ));
+ }
+ }
+
+ addr_map.sort_by_key(|x|x.0);
+ println!("Address Table: {:?}", addr_map);
+ AddressTranslation { addr_map }
+ }
+
+ fn from_superblock(image: &[u8]) -> AddressTranslation {
+ let sys_chunk_array_size: u32 = FromBytes::get(&image, 0x100a0);
+ let mut addr_map = Vec::new();
+ let mut len: usize = 0;
+
+ while len < sys_chunk_array_size as usize {
+ let chunk_key: BtrfsKey = FromBytes::get(&image, 0x1032b + len);
+ let chunk_value: BtrfsChunkItem = FromBytes::get(&image, 0x1033c + len);
+
+ addr_map.push((
+ chunk_key.key_offset,
+ chunk_value.size,
+ chunk_value.stripes.iter()
+ .map(|stripe|(stripe.devid, stripe.offset))
+ .collect()
+ ));
+ len += 0x41 + 0x20 * chunk_value.stripes.len();
+ }
+
+ addr_map.sort_by_key(|x|x.0);
+ println!("Bootstrap Address Table: {:?}", addr_map);
+ AddressTranslation { addr_map }
+ }
+
+ fn to_phys(&self, log: u64) -> Option {
+ let index = match self.addr_map.binary_search_by_key(&log, |x|x.0) {
+ Ok(idx) => idx as usize,
+ Err(idx) => if idx == 0 {
+ return None;
+ } else {
+ idx-1 as usize
+ }
+ };
+
+ let log_offset = self.addr_map[index].0;
+ let size = self.addr_map[index].1;
+ let phys_offset = self.addr_map[index].2[0].1;
+
+ if log >= log_offset && log < log_offset + size {
+ Some(phys_offset + (log - log_offset))
+ } else {
+ None
+ }
+ }
+}