diff --git a/btrfs/Cargo.toml b/btrfs/Cargo.toml new file mode 100644 index 0000000..41cb102 --- /dev/null +++ b/btrfs/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "btrfs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = [] +std = [] + +[dependencies] +log = "*" +uuid = {version = "1.3.0", default-features = false} +bytemuck = {version = "1.13.1", features = ["derive"]} +byteorder = {version = "1.4.3", default-features = false} +scroll = {version = "0.11.0", features = ["derive"], default-features = false} +derivative = {version = "2.2.0", features = ["use_core"]} +hex = {version = "0.4.3", default-features = false} +zerocopy = "0.6.1" +crc = "3.0.1" +thiserror = { version = "1.0", package = "thiserror-core", default-features = false } +num_enum = {version = "0.5.11", default-features = false} + +[dev-dependencies] +env_logger = "*" +test-log = "*" \ No newline at end of file diff --git a/btrfs/src/lib.rs b/btrfs/src/lib.rs new file mode 100644 index 0000000..2662d7d --- /dev/null +++ b/btrfs/src/lib.rs @@ -0,0 +1,257 @@ +#![feature(error_in_core)] +#![cfg_attr(not(any(feature = "std", test)), no_std)] + +use core::{borrow::Borrow, mem::size_of, ops::RangeBounds}; + +use alloc::{ + collections::{btree_map::Entry, BTreeMap}, + vec, + vec::Vec, +}; +use scroll::{Endian, Pread}; +use thiserror::Error; + +use structs::{BTreeNode, Chunk, Header, Item, Key, KeyPtr, ObjectType, Stripe, Superblock}; + +extern crate alloc; + +pub mod structs; + +#[cfg(feature = "std")] +pub mod std_io { + use std::io::{Cursor, Read, Seek}; + + use crate::{Error, VolumeIo}; + + impl VolumeIo for T { + fn read(&mut self, dst: &mut [u8], address: u64) -> Result<(), Error> { + self.seek(std::io::SeekFrom::Start(address)) + .map_err(|a| Error::SeekFailed)?; + self.read_exact(dst).map_err(|_| Error::ReadFailed) + } + + fn alignment(&self) -> usize { + 1 + } + } +} + +#[derive(Debug, Error)] +pub enum Error { + #[error("read failed")] + ReadFailed, + #[error("seek failed")] + SeekFailed, + #[error("invalid magic signature")] + InvalidMagic, + #[error("invalid offset")] + InvalidOffset, + #[error("Expected an internal node")] + ExpectedInternalNode, + #[error("Expected a leaf node")] + ExpectedLeafNode, + #[error("{0}")] + ScrollError(scroll::Error), +} + +impl From for Error { + fn from(value: scroll::Error) -> Self { + Self::ScrollError(value) + } +} + +pub type Result = core::result::Result; + +pub trait VolumeIo { + fn read(&mut self, dst: &mut [u8], address: u64) -> Result<()>; + fn alignment(&self) -> usize; +} + +/// equal if overlapping, ordered by lower bound +#[derive(Debug)] +pub struct ChunkTreeKey { + range: core::ops::Range, +} + +impl From for ChunkTreeKey { + fn from(value: u64) -> Self { + Self { + range: value..value, + } + } +} + +impl ChunkTreeKey { + pub fn start(&self) -> u64 { + self.range.start + } + pub fn end(&self) -> u64 { + self.range.end + } + pub fn size(&self) -> u64 { + self.range.end - self.range.start + } + + pub fn delta(&self, point: u64) -> u64 { + point - self.range.start + } + + pub fn sub_range(&self, point: u64) -> core::ops::Range { + self.delta(point)..self.end() + } +} + +impl Eq for ChunkTreeKey {} +impl Ord for ChunkTreeKey { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + +impl PartialEq for ChunkTreeKey { + fn eq(&self, other: &Self) -> bool { + (self.range.contains(&other.range.start) || self.range.contains(&other.range.end)) + || (other.range.contains(&self.range.start) || other.range.contains(&self.range.end)) + } +} + +impl PartialOrd for ChunkTreeKey { + fn partial_cmp(&self, other: &Self) -> Option { + self.eq(other) + .then_some(core::cmp::Ordering::Equal) + .or_else(|| self.range.start.partial_cmp(&other.range.start)) + } +} + +pub struct ChunkCacheTree { + inner: BTreeMap, +} + +type ChunkTree = BTreeMap; + +pub struct Volume { + reader: R, + superblock: Superblock, + chunk_cache: ChunkTree, +} + +impl Volume { + pub fn new(mut reader: R) -> Result { + let mut buf = vec![0; size_of::()]; + reader.read(&mut buf, Superblock::SUPERBLOCK_BASE_OFFSET as _)?; + let superblock = Superblock::parse(&buf)?; + let chunk_cache = Self::bootstrap_chunk_tree(&superblock)?; + Ok(Self { + reader, + superblock, + chunk_cache, + }) + } + + pub fn size_from_logical(&self, logical: u64) -> Option { + self.chunk_cache + .get_key_value(&logical.into()) + .map(|(key, _)| key.size()) + } + + pub fn offset_from_logical(&self, logical: u64) -> Option { + self.chunk_cache + .get_key_value(&logical.into()) + .map(|(key, offset)| offset + key.delta(logical)) + } + + pub fn range_from_logical(&self, logical: u64) -> Option> { + self.chunk_cache + .get_key_value(&logical.into()) + .map(|(key, offset)| { + let delta = key.delta(logical); + (offset + delta)..(offset + key.size() - delta) + }) + } + + pub fn read_range(&mut self, range: core::ops::Range) -> Result> { + let mut buf = vec![0; (range.end - range.start) as usize]; + self.reader.read(&mut buf, range.start)?; + + Ok(buf) + } + + pub fn asdf(&mut self) -> Result<()> { + let chunk_root = self.superblock.chunk_root; + // let size = self.size_from_logical(chunk_root).expect("size"); + + log::debug!("chunk_root: {chunk_root}"); + let contains = self.chunk_cache.contains_key(&chunk_root.into()); + log::debug!("chunk_cache: {contains} {:?}", self.chunk_cache); + let physical = self.range_from_logical(chunk_root).expect("range"); + + let root = self.read_range(physical)?; + + let node = BTreeNode::parse(&root)?; + + log::debug!("{node:#?}"); + + Ok(()) + } + + pub fn bootstrap_chunk_tree(superblock: &Superblock) -> Result { + let array_size = superblock.sys_chunk_array_size as usize; + let mut offset: usize = 0; + + let key_size = size_of::(); + let mut chunk_tree = ChunkTree::new(); + + let bytes = &superblock.sys_chunk_array; + + while offset < array_size { + if offset + key_size > array_size { + log::error!("short key read"); + return Err(Error::InvalidOffset); + } + + let key = bytes.gread_with::(&mut offset, scroll::LE)?; + if key.ty.as_type() != Some(ObjectType::ChunkItem) { + log::error!("key is not of type ChunkItem"); + return Err(Error::InvalidOffset); + } + + let chunk = bytes.gread_with::(&mut offset, scroll::LE)?; + if chunk.num_stripes == 0 { + log::error!("num_stripes cannot be 0"); + return Err(Error::InvalidOffset); + } + + let num_stripes = chunk.num_stripes; // copy to prevent unaligned access + if num_stripes != 1 { + log::warn!( + "warning: {} stripes detected but only processing 1", + num_stripes + ); + } + + match chunk_tree.entry(ChunkTreeKey { + range: key.offset..(key.offset + chunk.length), + }) { + Entry::Vacant(entry) => { + entry.insert(chunk.stripe.offset); + } + Entry::Occupied(_) => { + log::error!("overlapping stripes!"); + return Err(Error::InvalidOffset); + } + }; + + offset += (num_stripes - 1) as usize * size_of::(); + if offset > array_size { + log::error!("short chunk item + stripes read"); + return Err(Error::InvalidOffset); + } + } + + Ok(chunk_tree) + } + + pub fn superblock(&self) -> &Superblock { + &self.superblock + } +} diff --git a/btrfs/src/structs.rs b/btrfs/src/structs.rs new file mode 100644 index 0000000..ba1ba1b --- /dev/null +++ b/btrfs/src/structs.rs @@ -0,0 +1,789 @@ +use core::{mem::size_of, ops::Deref}; + +use bytemuck::{Pod, Zeroable}; +use derivative::Derivative; +use num_enum::{FromPrimitive, TryFromPrimitive}; +use scroll::{ + ctx::{SizeWith, TryFromCtx}, + Pread, SizeWith, +}; +use zerocopy::{AsBytes, FromBytes}; + +use crate::{Error, Result}; + +#[repr(u8)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] +//#[rustc_nonnull_optimization_guaranteed] +pub enum ObjectType { + INodeItem = 0x01, + InodeRef = 0x0C, + InodeExtref = 0x0D, + XattrItem = 0x18, + OrphanInode = 0x30, + DirItem = 0x54, + DirIndex = 0x60, + ExtentData = 0x6C, + ExtentCsum = 0x80, + RootItem = 0x84, + TypeRootBackref = 0x90, + RootRef = 0x9C, + ExtentItem = 0xA8, + MetadataItem = 0xA9, + TreeBlockRef = 0xB0, + ExtentDataRef = 0xB2, + ExtentRefV0 = 0xB4, + SharedBlockRef = 0xB6, + SharedDataRef = 0xB8, + BlockGroupItem = 0xC0, + FreeSpaceInfo = 0xC6, + FreeSpaceExtent = 0xC7, + FreeSpaceBitmap = 0xC8, + DevExtent = 0xCC, + DevItem = 0xD8, + ChunkItem = 0xE4, + TempItem = 0xF8, + DevStats = 0xF9, + SubvolUuid = 0xFB, + SubvolRecUuid = 0xFC, +} + +#[repr(C, packed)] +#[derive(Clone, Copy, PartialEq, Eq, Pread, SizeWith)] +pub struct ObjectTypeWrapper { + inner: u8, +} + +impl core::fmt::Debug for ObjectTypeWrapper { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ObjectTypeWrapper") + .field("inner", &self.as_type()) + .finish() + } +} + +impl ObjectTypeWrapper { + pub fn as_type(self) -> Option { + ObjectType::try_from_primitive(self.inner).ok() + } +} + +unsafe impl Pod for ObjectTypeWrapper {} +unsafe impl Zeroable for ObjectTypeWrapper {} + +#[repr(transparent)] +#[derive(Debug, Clone, Copy)] +pub struct Uuid(uuid::Uuid); + +impl SizeWith for Uuid { + fn size_with(_: &scroll::Endian) -> usize { + size_of::() + } +} + +impl<'a> TryFromCtx<'a, scroll::Endian> for Uuid { + type Error = scroll::Error; + + fn try_from_ctx( + from: &'a [u8], + _: scroll::Endian, + ) -> core::result::Result<(Self, usize), Self::Error> { + let size = size_of::(); + Ok((*bytemuck::from_bytes::(&from[..size]), size)) + } +} + +impl Deref for Uuid { + type Target = uuid::Uuid; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +unsafe impl Pod for Uuid {} +unsafe impl Zeroable for Uuid {} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] +pub struct Key { + pub id: u64, + pub ty: ObjectTypeWrapper, + pub offset: u64, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct TreeHeader { + csum: [u8; 32], + fs_uuid: Uuid, + address: u64, + flags: u64, + chunk_tree_uuid: Uuid, + generation: u64, + tree_id: u64, + num_items: u32, + level: u8, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct LeafNode { + key: Key, + offset: u32, + size: u32, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct InternalNode { + key: Key, + address: u64, + generation: u64, +} + +const MAX_LABEL_SIZE: usize = 0x100; +const SYS_CHUNK_ARRAY_SIZE: usize = 0x800; +const BTRFS_NUM_BACKUP_ROOTS: usize = 4; + +fn format_u8str>(s: &T, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let bytes = s.as_ref(); + let end = bytes + .iter() + .position(|&b| b == 0) + .map(|i| i + 1) + .unwrap_or(bytes.len()); + core::ffi::CStr::from_bytes_with_nul(&bytes[..end]) + .map(|s| write!(f, "{:?}", s)) + .map_err(|_| core::fmt::Error)? +} + +#[repr(C, packed(1))] +#[derive(Derivative, Clone, Copy, Pod, Zeroable)] +#[derivative(Debug)] +pub struct INodeItem { + generation: u64, + transid: u64, + st_size: u64, + st_blocks: u64, + block_group: u64, + st_nlink: u32, + st_uid: u32, + st_gid: u32, + st_mode: u32, + st_rdev: u64, + flags: u64, + sequence: u64, + #[derivative(Debug = "ignore")] + reserved: [u8; 32], + st_atime: Timespec, + st_ctime: Timespec, + st_mtime: Timespec, + otime: Timespec, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct ChunkItem { + size: u64, + root_id: u64, + stripe_length: u64, + ty: u64, + opt_io_alignment: u32, + opt_io_width: u32, + sector_size: u32, + num_stripes: u16, + sub_stripes: u16, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct ChunkItemStripe { + dev_id: u64, + offset: u64, + dev_uuid: Uuid, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct ExtentData { + generation: u64, + decoded_size: u64, + compression: u8, + encryption: u8, + encoding: u16, + ty: u8, + data: [u8; 1], +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct ExtentData2 { + address: u64, + size: u64, + offset: u64, + num_bytes: u64, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct INodeRef { + index: u64, + n: u16, + name: [u8; 1], +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct INodeExtRef { + dir: u64, + index: u64, + n: u16, + name: [u8; 1], +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct ExtentItem { + ref_count: u64, + generation: u64, + flags: u64, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct ExtentItem2 { + first_item: Key, + level: u8, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct ExtentItemV0 { + ref_count: u32, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct ExtentItemTree { + extent_item: ExtentItem, + first_item: Key, + level: u8, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct TreeBlockRef { + offset: u64, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct ExtentDataRef { + root: u64, + objid: u64, + offset: u64, + count: u32, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct BlockGroupItem { + used: u64, + chunk_tree: u64, + flags: u64, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct ExtentRefV0 { + root: u64, + gen: u64, + objid: u64, + count: u32, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct SharedBlockRef { + offset: u64, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct SharedDataRef { + offset: u64, + count: u32, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct FreeSpaceEntry { + offset: u64, + size: u64, + ty: u8, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct FreeSpaceItem { + key: Key, + + generation: u64, + num_entries: u64, + num_bitmaps: u64, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct RootRef { + dir: u64, + index: u64, + n: u16, + name: [u8; 1], +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct DevExtent { + chunktree: u64, + objid: u64, + address: u64, + length: u64, + chunktree_uuid: Uuid, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct BalanceArgs {} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct BalanceItem {} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct FreeSpaceInfo { + count: u32, + flags: u32, +} + +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct SendHeader {} +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct SendCommand {} +#[repr(C, packed(1))] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct SendTlv {} + +const CSUM_SIZE: usize = 32; +const LABEL_SIZE: usize = 256; +const SYSTEM_CHUNK_ARRAY_SIZE: usize = 2048; + +pub const FS_TREE_OBJECTID: u64 = 5; + +pub const INODE_REF_KEY: u8 = 12; +pub const DIR_ITEM_KEY: u8 = 84; +pub const ROOT_ITEM_KEY: u8 = 132; +pub const CHUNK_ITEM_KEY: u8 = 228; + +pub const FT_REG_FILE: u8 = 1; + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] +pub struct DevItem { + /// the internal btrfs device id + pub devid: u64, + /// size of the device + pub total_bytes: u64, + /// bytes used + pub bytes_used: u64, + /// optimal io alignment for this device + pub io_align: u32, + /// optimal io width for this device + pub io_width: u32, + /// minimal io size for this device + pub sector_size: u32, + /// type and info about this device + pub ty: u64, + /// expected generation for this device + pub generation: u64, + /// starting byte of this partition on the device, to allow for stripe alignment in the future + pub start_offset: u64, + /// grouping information for allocation decisions + pub dev_group: u32, + /// seek speed 0-100 where 100 is fastest + pub seek_speed: u8, + /// bandwidth 0-100 where 100 is fastest + pub bandwidth: u8, + /// btrfs generated uuid for this device + pub uuid: Uuid, + /// uuid of FS who owns this device + pub fsid: Uuid, +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] +pub struct RootBackup { + pub tree_root: u64, + pub tree_root_gen: u64, + pub chunk_root: u64, + pub chunk_root_gen: u64, + pub extent_root: u64, + pub extent_root_gen: u64, + pub fs_root: u64, + pub fs_root_gen: u64, + pub dev_root: u64, + pub dev_root_gen: u64, + pub csum_root: u64, + pub csum_root_gen: u64, + pub total_bytes: u64, + pub bytes_used: u64, + pub num_devices: u64, + /// future + pub unused_64: [u64; 4], + pub tree_root_level: u8, + pub chunk_root_level: u8, + pub extent_root_level: u8, + pub fs_root_level: u8, + pub dev_root_level: u8, + pub csum_root_level: u8, + /// future and to align + pub unused_8: [u8; 10], +} + +#[repr(C, packed)] +#[derive(Derivative, Clone, Copy, Pod, Zeroable, Pread, SizeWith)] +#[derivative(Debug)] +pub struct Superblock { + pub csum: [u8; 32], + pub fsid: Uuid, + /// Physical address of this block + pub bytenr: u64, + pub flags: u64, + pub magic: [u8; 0x8], + pub generation: u64, + /// Logical address of the root tree root + pub root: u64, + /// Logical address of the chunk tree root + pub chunk_root: u64, + /// Logical address of the log tree root + pub log_root: u64, + pub log_root_transid: u64, + pub total_bytes: u64, + pub bytes_used: u64, + pub root_dir_objectid: u64, + pub num_devices: u64, + pub sector_size: u32, + pub node_size: u32, + /// Unused and must be equal to `nodesize` + pub leafsize: u32, + 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, + #[derivative(Debug(format_with = "format_u8str"))] + pub label: [u8; 0x100], + pub cache_generation: u64, + pub uuid_tree_generation: u64, + pub metadata_uuid: Uuid, + /// Future expansion + reserved: [u64; 28], + #[derivative(Debug = "ignore")] + pub sys_chunk_array: [u8; 0x800], + pub root_backup0: RootBackup, + pub root_backup1: RootBackup, + pub root_backup2: RootBackup, + pub root_backup3: RootBackup, +} + +#[repr(u16)] +#[derive(Derivative, Clone, Copy, TryFromPrimitive)] +#[derivative(Debug)] +pub enum ChecksumType { + Crc32 = 0, + XxHash64, + Sha256, + Blake2B, +} + +impl Superblock { + pub const SUPERBLOCK_BASE_OFFSET: usize = 0x10000; + pub const SUPERBLOCK_OFFSETS: [usize; 4] = [ + Self::SUPERBLOCK_BASE_OFFSET, + 0x4000000, + 0x4000000000, + 0x4000000000000, + ]; + pub const MAGIC: [u8; 8] = *b"_BHRfS_M"; + + pub fn parse(bytes: &[u8]) -> Result { + let superblock = bytes.pread_with::(0, scroll::LE)?; + + if !superblock.verify_magic() { + return Err(Error::InvalidMagic); + } + + Ok(superblock) + } + + pub fn checksum_type(&self) -> Option { + ChecksumType::try_from_primitive(self.csum_type).ok() + } + + pub fn verify_magic(&self) -> bool { + self.magic == Self::MAGIC + } +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +pub struct Stripe { + pub devid: u64, + pub offset: u64, + pub dev_uuid: Uuid, +} + +impl Stripe { + pub fn parse(bytes: &[u8]) -> Result { + Ok(bytes.pread_with(0, scroll::LE)?) + } +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +pub struct Chunk { + /// size of this chunk in bytes + pub length: u64, + /// objectid of the root referencing this chunk + pub owner: u64, + pub stripe_len: u64, + pub ty: u64, + /// optimal io alignment for this chunk + pub io_align: u32, + /// optimal io width for this chunk + pub io_width: u32, + /// minimal io size for this chunk + pub sector_size: u32, + /// 2^16 stripes is quite a lot, a second limit is the size of a single item in the btree + pub num_stripes: u16, + /// sub stripes only matter for raid10 + pub sub_stripes: u16, + pub stripe: Stripe, + // additional stripes go here +} + +impl Chunk { + pub fn parse(bytes: &[u8]) -> Result { + Ok(bytes.pread_with(0, scroll::LE)?) + } +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +pub struct Timespec { + pub sec: u64, + pub nsec: u32, +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct InodeItem { + /// nfs style generation number + pub generation: u64, + /// transid that last touched this inode + 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, + /// modification sequence number for NFS + pub sequence: u64, + pub reserved: [u64; 4], + pub atime: Timespec, + pub ctime: Timespec, + pub mtime: Timespec, + pub otime: Timespec, +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct RootItem { + 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, + pub generation_v2: u64, + pub uuid: Uuid, + pub parent_uuid: Uuid, + pub received_uuid: Uuid, + /// updated when an inode changes + pub ctransid: u64, + /// trans when created + pub otransid: u64, + /// trans when sent. non-zero for received subvol + pub stransid: u64, + /// trans when received. non-zero for received subvol + pub rtransid: u64, + pub ctime: Timespec, + pub otime: Timespec, + pub stime: Timespec, + pub rtime: Timespec, + pub reserved: [u64; 8], +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct DirItem { + pub location: Key, + pub transid: u64, + pub data_len: u16, + pub name_len: u16, + pub ty: u8, +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct InodeRef { + pub index: u64, + pub name_len: u16, +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +pub struct Header { + pub csum: [u8; 32], + pub fsid: Uuid, + /// Which block this node is supposed to live in + pub bytenr: u64, + pub flags: u64, + pub chunk_tree_uuid: Uuid, + pub generation: u64, + pub owner: u64, + pub nritems: u32, + pub level: u8, +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +/// A `BtrfsLeaf` is full of `BtrfsItem`s. `offset` and `size` (relative to start of data area) +/// tell us where to find the item in the leaf. +pub struct Item { + pub key: Key, + pub offset: u32, + pub size: u32, +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct Leaf { + pub header: Header, + // `Item`s begin here +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable, Pread)] +/// All non-leaf blocks are nodes and they hold only keys are pointers to other blocks +pub struct KeyPtr { + pub key: Key, + pub blockptr: u64, + pub generation: u64, +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +pub struct Node { + pub header: Header, + // `KeyPtr`s begin here +} + +use alloc::vec::Vec; + +#[derive(Debug)] +pub struct BTreeLeafNode { + header: Header, + /// actual leaf data + items: Vec, +} + +impl BTreeLeafNode { + pub fn parse(header: Header, bytes: &[u8]) -> Result { + let offset = &mut 0; + let items = core::iter::from_fn(|| { + if *offset as usize + size_of::() < bytes.len() { + Some(bytes.gread_with::(offset, scroll::LE)) + } else { + None + } + }) + .take(header.nritems as usize) + .collect::, _>>()?; + Ok(Self { header, items }) + } +} + +#[derive(Debug)] +pub struct BTreeInternalNode { + header: Header, + children: Vec, +} + +impl BTreeInternalNode { + pub fn parse(header: Header, bytes: &[u8]) -> Result { + let offset = &mut 0; + let children = core::iter::from_fn(|| { + if *offset as usize + size_of::() < bytes.len() { + Some(bytes.gread_with::(offset, scroll::LE)) + } else { + None + } + }) + .take(header.nritems as usize) + .collect::, _>>()?; + Ok(Self { header, children }) + } +} + +#[derive(Debug)] +pub enum BTreeNode { + Internal(BTreeInternalNode), + Leaf(BTreeLeafNode), +} + +impl BTreeNode { + pub fn parse(bytes: &[u8]) -> Result { + let offset = &mut 0; + let header = bytes.gread_with::
(offset, scroll::LE)?; + + if header.level == 0 { + Ok(Self::Leaf(BTreeLeafNode::parse(header, &bytes[*offset..])?)) + } else { + Ok(Self::Internal(BTreeInternalNode::parse( + header, + &bytes[*offset..], + )?)) + } + } +} diff --git a/btrfs/tests/read_superblock.rs b/btrfs/tests/read_superblock.rs new file mode 100644 index 0000000..f2b46ec --- /dev/null +++ b/btrfs/tests/read_superblock.rs @@ -0,0 +1,44 @@ +#![cfg(feature = "std")] +use std::{ + io::{BufReader, Read, Seek}, + mem::size_of, +}; + +use test_log::test; + +use btrfs::{structs::*, Volume}; + +#[test] +fn superblock() { + let mut file = std::fs::File::open("btrfs.img").expect("btrfs image"); + + let reader = BufReader::new(file); + let volume = Volume::new(reader).expect("volume"); + let sb = volume.superblock(); + + println!("{sb:#?}"); +} + +#[test] +fn iter_sys_chunks() { + let mut file = std::fs::File::open("btrfs.img").expect("btrfs image"); + + let reader = BufReader::new(file); + let volume = Volume::new(reader).expect("volume"); + let sb = volume.superblock(); + + let result = Volume::>::bootstrap_chunk_tree(&sb); + + println!("{result:#?}"); +} + +#[test] +fn iter_root() { + let mut file = std::fs::File::open("btrfs.img").expect("btrfs image"); + + let reader = BufReader::new(file); + let mut volume = Volume::new(reader).expect("volume"); + let sb = volume.superblock(); + + volume.asdf(); +} diff --git a/notes.org b/notes.org new file mode 100644 index 0000000..d9ff10b --- /dev/null +++ b/notes.org @@ -0,0 +1,120 @@ +#+TITLE:Quibble bootloader reasoned +#+AUTHOR: Janis Böhm +#+email: janis@nirgendwo.xyz +#+STARTUP: indent inlineimages +#+INFOJS_OPT: +#+BABEL: :session *R* :cache yes :results output graphics :exports both :tangle yes +-------------------------- + +* Btrfs driver + +** structures + +*** volume +**** EFI_SIMPLE_FILE_SYSTEM_PROTOCOL: proto +**** EFI_QUIBBLE_PROTOCOL: quibble_proto +**** EFI_OPEN_SUBVOL_PROTOCOL: open_subvol_proto +**** superblock*: sb +***** checksum +***** uuid +**** EFI_HANDLE: controller +**** EFI_BLOCK_IO_PROTOCOL*: block +**** EFI_DISK_IO_PROTOCOL*: disk_io +**** bool: chunks_loaded +**** LIST_ENTRY: chunks +**** LIST_ENTRY: roots +**** root*: root_root +**** root*: chunk_root +**** LIST_ENTRY: list_entry +**** root*: fsroot +** efi_main + +- initialize volumes list +- construct global driver bind structure +- install driver protocol + +** drv_start + - walk list of volumes and check for controller handle + - open block_io protocol and ensure media->blocksize isnt 0 + - open disk_io protocol + - read superblock and check magic and crc32 + - check for incompat flags + - setup volume struct with protocols/functions: + - install protocols and store volume in global list + +*** open_volume +- ensure chunks are loaded +- populate file handle +- construct inode from fsroot and vol +**** load_chunks +**** populate_file_handle + +*** get_arc_name +returns superblock uuid field + +*** get_driver_name +just returns "btrfs" + +*** open_subvol + +** drv_stop +- cleanup / deallocating +- close diskio/blockio protocols + +** drv_supported +- checks for disk_io and block_io + +* boot function + +find and open btrfs disk and subvol, if specified + +check for, and open system32 folder +add_image kernel executable and hal library + +** add_image +*** args +- images list +- image name and dir +- memory type + SystemCode for kernel, HalCode for hal.dll +- is dll? +- bdle / boot driver list entry + always null +- order + load order of drivers +- no_reloc + false for kernel and HAL, and drivers + +*** function +keeps track of image in a linked list + +** add kernel and hal to image list + +*** kernel image +should we look for alternate kernel images? *mp,*pa,*amp + +** process memory map + +** load kernel + +** load registry + +** load apiset if >= win8 + +** add crashdmp driver if >= win8 blue to image list + +** load image list, drivers, add dependencies to image list + +** fix image order + +** iterate thru images again, resolve imports + +** make images contiguous + +** construct kernel stack/arguments + +** map images? + +** stuff + +** call kernel entry