initial version

This commit is contained in:
Florian Stecker 2023-07-27 08:05:24 -04:00
commit bdcc597b9a
2 changed files with 585 additions and 0 deletions

10
Cargo.toml Normal file
View File

@ -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"

575
src/main.rs Normal file
View File

@ -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("<body><table style=\"margin: 0 auto;\">\n<tr>\n");
for i in 0..nodes_in_chunk {
if i % 64 == 0 {
result.push_str("</tr>\n<tr>\n");
}
let node = read_node(&image, chunk_offset + i*0x4000);
let active = node.generation > 0 && ACTIVE_NODES.contains(&(i*0x4000));
let newbox = format!("<td style=\"{}\"></td>\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("</tr>\n</table></body>");
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("<body>\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!("<p style=\"{}\">{:x} {} {} {}\n<ul>\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!("<li style=\"{}\">{:016x} {:?} {:x}</li>\n",
style,
item.key.key_id,
item.key.key_type,
item.key.key_offset);
result.push_str(&newline);
}
result.push_str("</ul></p>\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<const N: usize> 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<BtrfsChunkItemStripe> = 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<Box<BtrfsNode>> {
let phys = trans.to_phys(log)?;
Some(read_node(image, phys as usize))
}
fn read_node(image: &[u8], offset: usize) -> Box<BtrfsNode> {
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<BtrfsItem>,
}
#[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<u8>),
}
#[derive(Debug)]
struct BtrfsExtentItem {
refs: u64,
generation: u64,
flags: u64,
data: Vec<u8>,
}
#[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<BtrfsChunkItemStripe>,
}
#[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<u64> {
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
}
}
}