adapt address translator to derive parser

This commit is contained in:
Florian Stecker 2023-08-03 19:15:38 -04:00
parent 7ac28455cc
commit 98b6f5d1f6
4 changed files with 332 additions and 110 deletions

View File

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.72"
binparse_derive = { path = "../binparse_derive" }
memmap2 = "0.7.1"
rouille = "3.6.2"

86
src/addrmap.rs Normal file
View File

@ -0,0 +1,86 @@
use crate::btrfs_structs::{ParseBin, Key, ChunkItem, Leaf, Value, Superblock, LogToPhys};
#[derive(Debug)]
pub struct AddressMap(Vec<(u64,u64,Vec<(u64,u64)>)>);
// TODO: support for internal nodes, multiple devices?
impl AddressMap {
pub fn new(image: &[u8]) -> Result<AddressMap,String> {
let superblock = Superblock::parse(&image[0x10000..])?;
let bootstrap_addr = AddressMap::from_superblock(&superblock)?;
let chunk_root_log = superblock.chunk_root;
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 = Leaf::parse(&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 Value::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);
Ok(AddressMap(addr_map))
}
pub fn from_superblock(superblock: &Superblock) -> Result<AddressMap,String> {
let sys_chunk_array_size = superblock.sys_chunk_array_size;
let sys_chunk_array: &[u8] = &superblock.sys_chunk_array;
let mut addr_map = Vec::new();
let mut len: usize = 0;
while len < sys_chunk_array_size as usize {
let chunk_key: Key = Key::parse(&sys_chunk_array[len .. ])?;
let chunk_value: ChunkItem = ChunkItem::parse(&sys_chunk_array[len+0x11 .. ])?;
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);
Ok(AddressMap(addr_map))
}
}
impl LogToPhys for AddressMap {
fn to_phys(&self, log: u64) -> Option<u64> {
let index = match self.0.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.0[index].0;
let size = self.0[index].1;
let phys_offset = self.0[index].2[0].1;
if log >= log_offset && log < log_offset + size {
Some(phys_offset + (log - log_offset))
} else {
None
}
}
}

View File

@ -1,14 +1,16 @@
use binparse_derive::AllVariants;
use binparse_derive::ParseBin;
use std::fmt;
use std::error;
use std::io;
use std::ffi::CString;
/***** BTRFS structures *****/
const NODE_SIZE: usize = 0x4000;
pub const NODE_SIZE: usize = 0x4000;
#[allow(unused)]
#[derive(Debug,Clone,Copy,AllVariants)]
#[derive(Debug,Clone,Copy,AllVariants,PartialEq,Eq,PartialOrd,Ord)]
#[repr(u8)]
pub enum ItemType {
Invalid = 0x00,
@ -41,7 +43,7 @@ pub enum ItemType {
FreeSpaceBitmap = 0xc8,
DevExtent = 0xcc, // implemented
Dev = 0xd8, // implemented
Chunk = 0xe4, // implemented? (awaiting len feature)
Chunk = 0xe4, // implemented
QGroupStatus = 0xf0,
QGroupInfo = 0xf2,
QGroupLimit = 0xf4,
@ -55,15 +57,15 @@ pub enum ItemType {
}
#[allow(unused)]
#[derive(Debug,Clone,Copy,ParseBin)]
#[derive(Debug,Clone,Copy,ParseBin,PartialEq,Eq,PartialOrd,Ord)]
pub struct Key {
key_id: u64,
key_type: ItemType,
key_offset: u64,
pub key_id: u64,
pub key_type: ItemType,
pub key_offset: u64,
}
#[allow(unused)]
#[derive(Debug)]
#[derive(Debug,Clone)]
pub enum Value {
Extent(ExtentItem),
BlockGroup(BlockGroupItem),
@ -82,144 +84,192 @@ pub enum Value {
#[allow(unused)]
#[allow(unused)]
#[derive(Debug)]
#[derive(Debug,Clone)]
pub struct Item {
key: Key,
value: Value,
pub key: Key,
pub value: Value,
}
#[allow(unused)]
#[derive(ParseBin)]
#[derive(Clone,ParseBin)]
pub struct Checksum([u8; 32]);
#[allow(unused)]
#[derive(ParseBin)]
#[derive(Clone,ParseBin)]
pub struct UUID([u8; 16]);
#[allow(unused)]
#[derive(Debug,ParseBin)]
pub struct Superblock {
pub csum: Checksum,
pub fsid: UUID,
pub bytenr: u64,
pub flags: u64,
pub magic: u64,
pub generation: u64,
pub root: u64,
pub chunk_root: u64,
pub log_root: u64,
#[skip_bytes = 8]
pub total_bytes: u64,
pub bytes_used: u64,
pub root_dir_objectid: u64,
pub num_devices: u64,
pub sectorsize: u32,
pub nodesize: u32,
#[skip_bytes = 4]
pub stripesize: u32,
pub sys_chunk_array_size: u32,
pub chunk_root_generation: u64,
pub compat_flags: u64,
pub compat_ro_flags: u64,
pub incompat_flags: u64,
pub csum_type: u16,
pub root_level: u8,
pub chunk_root_level: u8,
pub log_root_level: u8,
pub dev_item: DevItem,
pub label: [u8; 0x100],
pub cache_generation: u64,
pub uuid_tree_generation: u64,
pub metadata_uuid: UUID,
pub nr_global_roots: u64,
#[skip_bytes = 216]
pub sys_chunk_array: [u8; 0x800],
// next up would be the root backups, but we don't read them for now
}
#[allow(unused)]
#[derive(Debug,Clone,ParseBin)]
pub struct NodeHeader {
csum: Checksum,
fs_uid: UUID,
bytenr: u64,
flags: u64,
chunk_tree_uid: UUID,
generation: u64,
owner: u64,
nritems: u32,
level: u8,
pub csum: Checksum,
pub fs_uid: UUID,
pub bytenr: u64,
pub flags: u64,
pub chunk_tree_uid: UUID,
pub generation: u64,
pub owner: u64,
pub nritems: u32,
pub level: u8,
}
#[allow(unused)]
#[derive(Debug)]
#[derive(Debug,Clone)]
pub struct Leaf {
header: NodeHeader,
items: Vec<Item>,
pub header: NodeHeader,
pub items: Vec<Item>,
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct BlockGroupItem {
used: u64,
chunk_objectid: u64,
flags: u64,
pub used: u64,
pub chunk_objectid: u64,
pub flags: u64,
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct ExtentItem {
refs: u64,
generation: u64,
flags: u64,
data: Vec<u8>,
pub refs: u64,
pub generation: u64,
pub flags: u64,
pub data: Vec<u8>,
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct Time {
sec: u64,
nsec: u32,
pub sec: u64,
pub nsec: u32,
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct InodeItem {
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,
pub generation: u64,
pub transid: u64,
pub size: u64,
pub nbytes: u64,
pub block_group: u64,
pub nlink: u32,
pub uid: u32,
pub gid: u32,
pub mode: u32,
pub rdev: u64,
pub flags: u64,
pub sequence: u64,
#[skip_bytes = 32]
atime: Time,
ctime: Time,
mtime: Time,
otime: Time,
pub atime: Time,
pub ctime: Time,
pub mtime: Time,
pub otime: Time,
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct ChunkItem {
size: u64,
root: u64,
stripelen: u64,
chunktype: u64,
align: u32,
width: u32,
sectorsize: u32,
nrstripes: u16,
substripes: u16,
pub size: u64,
pub root: u64,
pub stripelen: u64,
pub chunktype: u64,
pub align: u32,
pub width: u32,
pub sectorsize: u32,
pub nrstripes: u16,
pub substripes: u16,
// #[vector_length = nrstripes]
// stripes: Vec<ChunkItemStripe>,
#[len = "nrstripes"]
pub stripes: Vec<ChunkItemStripe>,
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct ChunkItemStripe {
devid: u64,
offset: u64,
devuuid: UUID,
pub devid: u64,
pub offset: u64,
pub devuuid: UUID,
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct RootItem {
inode: InodeItem,
generation: u64,
root_dirid: u64,
bytenr: u64,
byte_limit: u64,
bytes_used: u64,
last_snapshot: u64,
flags: u64,
refs: u32,
drop_progress: Key,
drop_level: u8,
level: u8,
pub inode: InodeItem,
pub generation: u64,
pub root_dirid: u64,
pub bytenr: u64,
pub byte_limit: u64,
pub bytes_used: u64,
pub last_snapshot: u64,
pub flags: u64,
pub refs: u32,
pub drop_progress: Key,
pub drop_level: u8,
pub level: u8,
generation_v2: u64,
uuid: UUID,
parent_uuid: UUID,
received_uuid: UUID,
ctransid: u64,
otransid: u64,
stransid: u64,
rtransid: u64,
ctime: Time,
otime: Time,
stime: Time,
rtime: Time,
pub generation_v2: u64,
pub uuid: UUID,
pub parent_uuid: UUID,
pub received_uuid: UUID,
pub ctransid: u64,
pub otransid: u64,
pub stransid: u64,
pub rtransid: u64,
pub ctime: Time,
pub otime: Time,
pub stime: Time,
pub rtime: Time,
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct DirItem {
location: Key,
transid: u64,
@ -227,25 +277,25 @@ pub struct DirItem {
name_len: u16,
dir_type: u8,
#[len = "name_len"]
// #[len = "name_len"]
name: CString,
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct FreeSpaceInfoItem {
extent_count: u32,
flags: u32,
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct UUIDSubvolItem {
subvol_id: u64,
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct DevItem {
devid: u64,
total_bytes: u64,
@ -264,7 +314,7 @@ pub struct DevItem {
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct DevExtentItem {
chunk_tree: u64,
chunk_objectid: u64,
@ -274,21 +324,21 @@ pub struct DevExtentItem {
}
#[allow(unused)]
#[derive(Debug)]
#[derive(Debug,Clone)]
pub struct ExtentDataItem {
header: ExtentDataHeader,
data: ExtentDataBody,
}
#[allow(unused)]
#[derive(Debug)]
#[derive(Debug,Clone)]
pub enum ExtentDataBody {
Inline(Vec<u8>),
External(ExternalExtent),
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct ExternalExtent {
disk_bytenr: u64,
disk_num_bytes: u64,
@ -297,7 +347,7 @@ pub struct ExternalExtent {
}
#[allow(unused)]
#[derive(Debug,ParseBin)]
#[derive(Debug,Clone,ParseBin)]
pub struct ExtentDataHeader {
generation: u64,
ram_bytes: u64,
@ -310,6 +360,35 @@ pub struct ExtentDataHeader {
/***** trait for parsing, and implementations for basic types *****/
// most of the more complex types will be parsed using derive macros
#[derive(Debug)]
pub struct ParseError(String);
impl error::Error for ParseError {}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.0)
}
}
impl From<String> for ParseError {
fn from(value: String) -> ParseError {
ParseError(value)
}
}
impl From<&str> for ParseError {
fn from(value: &str) -> ParseError {
ParseError::from(String::from(value))
}
}
impl From<ParseError> for io::Error {
fn from(value: ParseError) -> io::Error {
io::Error::other(value)
}
}
pub trait ParseBin where Self: Sized {
fn parse_len(bytes: &[u8]) -> Result<(Self, usize), String>;
@ -454,7 +533,6 @@ impl ParseBin for Leaf {
Value::DevExtent(DevExtentItem::parse(data_slice)?),
ItemType::ExtentData =>
Value::ExtentData(ExtentDataItem::parse(data_slice)?),
_ =>
Value::Unknown(Vec::from(data_slice)),
};
@ -489,6 +567,28 @@ impl ParseBin for ExtentDataItem {
}
}
/***** looking up keys *****/
impl Leaf {
pub fn find_key(&self, key: Key) -> Option<Item> {
self.items.iter().find(|x|x.key == key).map(|x|x.clone())
}
}
pub fn node_at_log<'a, T: LogToPhys>(image: &'a [u8], addr: &T, log: u64) -> Option<&'a [u8]> {
let phys_addr = addr.to_phys(log)?;
Some(&image[phys_addr as usize .. phys_addr as usize + NODE_SIZE])
}
pub trait LogToPhys {
fn to_phys(&self, log: u64) -> Option<u64>;
}
pub fn find_key_in_tree<T: LogToPhys>(image: &[u8], addr: &T, root_addr_log: u64, key: Key) -> Option<Item> {
// assuming level is 0
let leaf = Leaf::parse(node_at_log(image, addr, root_addr_log)?).ok()?;
leaf.find_key(key)
}
/***** prettier debug output for UUIDs and checksums *****/
@ -520,3 +620,9 @@ impl fmt::Debug for Checksum {
x24, x25, x26, x27, x28, x29, x30, x31)
}
}
impl Key {
pub fn new(key_id: u64, key_type: ItemType, key_offset: u64) -> Key {
Key { key_id, key_type, key_offset }
}
}

View File

@ -1,19 +1,48 @@
mod btrfs_structs;
#![feature(io_error_other)]
mod btrfs_structs;
mod addrmap;
use addrmap::AddressMap;
use memmap2::Mmap;
use std::fs::File;
use std::io::Error as IOError;
use anyhow::Error as AError;
use rouille::Request;
use rouille::Response;
use btrfs_structs::{ItemType, ParseBin, Key, Item, Leaf, NodeHeader};
use btrfs_structs::{ItemType, ParseBin, Key, Item, Leaf, NodeHeader, NODE_SIZE, Superblock, Value, LogToPhys};
//const ACTIVE_NODES: &'static[usize] = &[0x14000, 0x18000, 0x1c000, 0x20000, 0x28000, 0x2c000, 0x3c000, 0x40000];
fn main() -> Result<(), std::io::Error> {
const EXTENT_TREE: u64 = 2;
const FS_TREE: u64 = 5;
fn main() -> Result<(), IOError> {
let file = File::open("../image")?;
let image = unsafe { Mmap::map(&file)? };
let addr = AddressMap::new(&image).map_err(|e|IOError::other(e))?;
let superblock = Superblock::parse(&image[0x10000..]).map_err(|e|IOError::other(e))?;
let chunk_root_phys = addr.to_phys(superblock.chunk_root).unwrap() as usize;
let root_phys = addr.to_phys(superblock.root).unwrap() as usize;
let root_tree_leaf = Leaf::parse(&image[root_phys .. root_phys + NODE_SIZE]).map_err(|e|IOError::other(e))?;
let root_key = Key::new(EXTENT_TREE, ItemType::Root, 0);
let root = root_tree_leaf.find_key(root_key).unwrap();
let root_addr = if let Value::Root(root_item) = root.value {
addr.to_phys(root_item.bytenr)
} else {
None
}.unwrap() as usize;
let tree_leaf = Leaf::parse(&image[root_addr .. root_addr + NODE_SIZE]).map_err(|e|IOError::other(e))?;
println!("{:#?}", &tree_leaf);
// println!("{:#?}", Leaf::parse(&image[0x253c000..0x2540000]));
println!("{:#?}", Leaf::parse(&image[0x1d04000..0x1d08000]));
Ok(())
}