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"); + } + + 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 + } + } +}