bootloader/btrfs/src/structs.rs
2023-03-25 16:15:59 +01:00

1060 lines
26 KiB
Rust

use core::{mem::size_of, ops::Deref};
use bytemuck::{Pod, Zeroable};
use derivative::Derivative;
use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive};
use scroll::{ctx::TryFromCtx, Pread, SizeWith};
use zerocopy::{byteorder::LE, AsBytes, FromBytes, U16, U32, U64};
use crate::{Error, Result};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, IntoPrimitive)]
#[repr(u64)]
pub enum KnownObjectId {
RootTree = 1,
ExtentTree,
ChunkTree,
DevTree,
FsTree,
RootTreeDir,
CsumTree,
QuotaTree,
UuidTree,
FreeSpaceTree,
__FirstFreeId = 256,
__LastFreeId = u64::MAX - 256,
DataRelocTree = u64::MAX - 9,
TreeReloc = u64::MAX - 8,
TreeLog = u64::MAX - 7,
Orphan = u64::MAX - 5,
#[num_enum(catch_all)]
Custom(u64),
}
#[repr(C, packed)]
#[derive(Clone, Copy, PartialEq, Eq, FromBytes, AsBytes)]
pub struct ObjectId {
inner: U64<LE>,
}
impl core::fmt::Debug for ObjectId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ObjectTypeWrapper")
.field("inner", &self.as_id())
.finish()
}
}
impl ObjectId {
pub fn from_id(id: KnownObjectId) -> Self {
Self {
inner: U64::<LE>::new(id.into()),
}
}
pub fn as_id(self) -> KnownObjectId {
KnownObjectId::from_primitive(self.inner.get())
}
}
unsafe impl Pod for ObjectId {}
unsafe impl Zeroable for ObjectId {}
#[repr(u8)]
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, IntoPrimitive)]
//#[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,
#[num_enum(catch_all)]
Invalid(u8),
}
#[repr(C, packed)]
#[derive(Clone, Copy, PartialEq, Eq, Pread, SizeWith, FromBytes, AsBytes)]
pub struct ObjectTypeWrapper {
inner: u8,
}
impl core::fmt::Debug for ObjectTypeWrapper {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?}", self.as_type())
}
}
impl ObjectTypeWrapper {
pub fn from_ty(ty: ObjectType) -> Self {
Self { inner: ty.into() }
}
pub fn as_type(self) -> ObjectType {
ObjectType::from_primitive(self.inner)
}
}
unsafe impl Pod for ObjectTypeWrapper {}
unsafe impl Zeroable for ObjectTypeWrapper {}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, FromBytes, AsBytes)]
pub struct Uuid(uuid::Bytes);
impl core::fmt::Debug for Uuid {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
uuid::Uuid::from_bytes_ref(&self.0).fmt(f)
}
}
impl Deref for Uuid {
type Target = uuid::Uuid;
fn deref(&self) -> &Self::Target {
uuid::Uuid::from_bytes_ref(&self.0)
}
}
unsafe impl Pod for Uuid {}
unsafe impl Zeroable for Uuid {}
#[repr(C, packed(1))]
#[derive(Debug, Clone, Copy, Eq, FromBytes, AsBytes)]
pub struct Key {
pub id: ObjectId,
pub ty: ObjectTypeWrapper,
pub offset: U64<LE>,
}
impl Key {
pub fn new(id: KnownObjectId, ty: ObjectType, offset: u64) -> Self {
Self {
id: ObjectId::from_id(id),
ty: ObjectTypeWrapper::from_ty(ty),
offset: U64::new(offset),
}
}
pub fn ty(&self) -> ObjectType {
self.ty.as_type()
}
pub fn id(&self) -> KnownObjectId {
self.id.as_id()
}
}
impl PartialEq for Key {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id() && self.ty() == other.ty() && self.offset == other.offset
}
}
impl Ord for Key {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
impl PartialOrd for Key {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
match self.id().partial_cmp(&other.id()) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.ty().partial_cmp(&other.ty()) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
self.offset.get().partial_cmp(&other.offset.get())
}
}
macro_rules! impl_try_from_ctx {
($($ty:ty),*) => {
$(impl<'a> TryFromCtx<'a> for $ty {
type Error = scroll::Error;
fn try_from_ctx(
from: &'a [u8],
_: (),
) -> core::result::Result<(Self, usize), Self::Error> {
Self::read_from(&from[..size_of::<Self>()])
.map(|v| (v, size_of::<Self>()))
.ok_or(scroll::Error::TooBig {
size: size_of::<Self>(),
len: from.len(),
})
}
})*
};
}
macro_rules! impl_parse_try_from_ctx {
($($ty:ty),*) => {
$(impl $ty {
pub fn parse(bytes: &[u8]) -> Result<Self> {
Ok(bytes.pread(0)?)
}
})*
};
}
impl_parse_try_from_ctx!(Chunk, Header, Key, RootItem);
impl_try_from_ctx!(Key, Chunk, Header, Superblock, RootItem);
const MAX_LABEL_SIZE: usize = 0x100;
const SYS_CHUNK_ARRAY_SIZE: usize = 0x800;
const BTRFS_NUM_BACKUP_ROOTS: usize = 4;
fn format_u8str<T: AsRef<[u8]>>(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, FromBytes, AsBytes)]
#[derivative(Debug)]
pub struct INodeItem {
generation: U64<LE>,
transid: U64<LE>,
st_size: U64<LE>,
st_blocks: U64<LE>,
block_group: U64<LE>,
st_nlink: U32<LE>,
st_uid: U32<LE>,
st_gid: U32<LE>,
st_mode: U32<LE>,
st_rdev: U64<LE>,
flags: U64<LE>,
sequence: U64<LE>,
#[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)]
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)]
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)]
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, FromBytes, AsBytes)]
pub struct DevItem {
/// the internal btrfs device id
pub devid: U64<LE>,
/// size of the device
pub total_bytes: U64<LE>,
/// bytes used
pub bytes_used: U64<LE>,
/// optimal io alignment for this device
pub io_align: U32<LE>,
/// optimal io width for this device
pub io_width: U32<LE>,
/// minimal io size for this device
pub sector_size: U32<LE>,
/// type and info about this device
pub ty: U64<LE>,
/// expected generation for this device
pub generation: U64<LE>,
/// starting byte of this partition on the device, to allow for stripe alignment in the future
pub start_offset: U64<LE>,
/// grouping information for allocation decisions
pub dev_group: U32<LE>,
/// 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, FromBytes, AsBytes)]
pub struct RootBackup {
pub tree_root: U64<LE>,
pub tree_root_gen: U64<LE>,
pub chunk_root: U64<LE>,
pub chunk_root_gen: U64<LE>,
pub extent_root: U64<LE>,
pub extent_root_gen: U64<LE>,
pub fs_root: U64<LE>,
pub fs_root_gen: U64<LE>,
pub dev_root: U64<LE>,
pub dev_root_gen: U64<LE>,
pub csum_root: U64<LE>,
pub csum_root_gen: U64<LE>,
pub total_bytes: U64<LE>,
pub bytes_used: U64<LE>,
pub num_devices: U64<LE>,
/// 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, FromBytes, AsBytes)]
#[derivative(Debug)]
pub struct Superblock {
pub csum: [u8; 32],
pub fsid: Uuid,
/// Physical address of this block
pub bytenr: U64<LE>,
pub flags: U64<LE>,
pub magic: [u8; 0x8],
pub generation: U64<LE>,
/// Logical address of the root tree root
pub root: U64<LE>,
/// Logical address of the chunk tree root
pub chunk_root: U64<LE>,
/// Logical address of the log tree root
pub log_root: U64<LE>,
pub log_root_transid: U64<LE>,
pub total_bytes: U64<LE>,
pub bytes_used: U64<LE>,
pub root_dir_objectid: U64<LE>,
pub num_devices: U64<LE>,
pub sector_size: U32<LE>,
pub node_size: U32<LE>,
/// Unused and must be equal to `nodesize`
pub leafsize: U32<LE>,
pub stripesize: U32<LE>,
pub sys_chunk_array_size: U32<LE>,
pub chunk_root_generation: U64<LE>,
pub compat_flags: U64<LE>,
pub compat_ro_flags: U64<LE>,
pub incompat_flags: U64<LE>,
pub csum_type: U16<LE>,
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<LE>,
pub uuid_tree_generation: U64<LE>,
pub metadata_uuid: Uuid,
/// Future expansion
#[derivative(Debug = "ignore")]
_reserved: [u64; 28],
#[derivative(Debug = "ignore")]
pub sys_chunk_array: [u8; 0x800],
#[derivative(Debug = "ignore")]
pub root_backups: [RootBackup; 4],
#[derivative(Debug = "ignore")]
_reserved2: [u8; 565],
}
#[repr(u16)]
#[derive(Derivative, Clone, Copy, TryFromPrimitive)]
#[derivative(Debug)]
pub enum ChecksumType {
Crc32 = 0,
XxHash64,
Sha256,
Blake2B,
}
pub fn calculate_crc32c(bytes: &[u8]) -> [u8; 32] {
let crc = crc::Crc::<u32>::new(&crc::CRC_32_ISCSI);
let mut csum = [0u8; 32];
csum[..4].copy_from_slice(crc.checksum(bytes).as_bytes());
csum
}
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<Self> {
let superblock = Self::read_from(bytes).ok_or(Error::ReadFailed)?;
if !superblock.verify_magic() {
return Err(Error::InvalidMagic);
}
if !superblock.verify_checksum() {
return Err(Error::InvalidChecksum {
expected: superblock.csum,
actual: superblock.calculate_checksum(),
});
}
Ok(superblock)
}
pub fn calculate_checksum(&self) -> [u8; 32] {
match self.checksum_type().expect("csum type invalid") {
ChecksumType::Crc32 => calculate_crc32c(&self.as_bytes()[0x20..]),
ChecksumType::XxHash64 => todo!(),
ChecksumType::Sha256 => todo!(),
ChecksumType::Blake2B => todo!(),
}
}
pub fn verify_checksum(&self) -> bool {
self.calculate_checksum() == self.csum
}
pub fn checksum_type(&self) -> Option<ChecksumType> {
ChecksumType::try_from_primitive(self.csum_type.get()).ok()
}
pub fn verify_magic(&self) -> bool {
self.magic == Self::MAGIC
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct Stripe {
pub devid: U64<LE>,
pub offset: U64<LE>,
pub dev_uuid: Uuid,
}
impl Stripe {
pub fn parse(bytes: &[u8]) -> Result<Self> {
Self::read_from(bytes).ok_or(Error::ReadFailed)
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct Chunk {
/// size of this chunk in bytes
pub length: U64<LE>,
/// objectid of the root referencing this chunk
pub owner: U64<LE>,
pub stripe_len: U64<LE>,
pub ty: U64<LE>,
/// optimal io alignment for this chunk
pub io_align: U32<LE>,
/// optimal io width for this chunk
pub io_width: U32<LE>,
/// minimal io size for this chunk
pub sector_size: U32<LE>,
/// 2^16 stripes is quite a lot, a second limit is the size of a single item in the btree
pub num_stripes: U16<LE>,
/// sub stripes only matter for raid10
pub sub_stripes: U16<LE>,
pub stripe: Stripe,
// additional stripes go here
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct Timespec {
pub sec: U64<LE>,
pub nsec: U32<LE>,
}
#[repr(C, packed)]
#[derive(Derivative, Clone, Copy, FromBytes, AsBytes)]
#[derivative(Debug)]
pub struct InodeItem {
/// nfs style generation number
pub generation: U64<LE>,
/// transid that last touched this inode
pub transid: U64<LE>,
pub size: U64<LE>,
pub nbytes: U64<LE>,
pub block_group: U64<LE>,
pub nlink: U32<LE>,
pub uid: U32<LE>,
pub gid: U32<LE>,
pub mode: U32<LE>,
pub rdev: U64<LE>,
pub flags: U64<LE>,
/// modification sequence number for NFS
pub sequence: U64<LE>,
#[derivative(Debug = "ignore")]
pub reserved: [u64; 4],
pub atime: Timespec,
pub ctime: Timespec,
pub mtime: Timespec,
pub otime: Timespec,
}
#[repr(C, packed)]
#[derive(Derivative, Clone, Copy, FromBytes, AsBytes)]
#[derivative(Debug)]
pub struct RootItem {
pub inode: InodeItem,
pub generation: U64<LE>,
pub root_dirid: U64<LE>,
pub bytenr: U64<LE>,
pub byte_limit: U64<LE>,
pub bytes_used: U64<LE>,
pub last_snapshot: U64<LE>,
pub flags: U64<LE>,
pub refs: U32<LE>,
pub drop_progress: Key,
pub drop_level: u8,
pub level: u8,
pub generation_v2: U64<LE>,
pub uuid: Uuid,
pub parent_uuid: Uuid,
pub received_uuid: Uuid,
/// updated when an inode changes
pub ctransid: U64<LE>,
/// trans when created
pub otransid: U64<LE>,
/// trans when sent. non-zero for received subvol
pub stransid: U64<LE>,
/// trans when received. non-zero for received subvol
pub rtransid: U64<LE>,
pub ctime: Timespec,
pub otime: Timespec,
pub stime: Timespec,
pub rtime: Timespec,
#[derivative(Debug = "ignore")]
pub reserved: [u64; 8],
}
#[repr(u8)]
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)]
pub enum DirItemType {
Unknown,
RegFile,
Dir,
ChrDev,
BlkDev,
Fifo,
Sock,
Symlink,
Xattr,
#[num_enum(catch_all)]
Invalid(u8),
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct DirItem {
pub location: Key,
pub transid: U64<LE>,
pub data_len: U16<LE>,
pub name_len: U16<LE>,
ty: u8,
}
impl DirItem {
pub fn ty(&self) -> DirItemType {
DirItemType::from_primitive(self.ty)
}
pub fn parse_single(bytes: &[u8]) -> Result<(DirItemEntry)> {
let offset = &mut 0;
Self::parse_single_inner(bytes, offset)
}
fn parse_single_inner(bytes: &[u8], offset: &mut usize) -> Result<DirItemEntry> {
let dir_item = DirItem::read_from(&bytes[*offset..*offset + size_of::<DirItem>()])
.ok_or(Error::ReadFailed)?;
*offset += size_of::<DirItem>();
let name_len = dir_item.name_len.get() as usize;
let name = &bytes[*offset..*offset + name_len];
*offset += name_len;
Ok(DirItemEntry::new(dir_item, name.to_vec()))
}
pub fn parse(bytes: &[u8]) -> Result<Vec<(DirItemEntry)>> {
let offset = &mut 0;
let entries = core::iter::from_fn(|| {
if *offset + size_of::<DirItem>() < bytes.len() {
Some(Self::parse_single_inner(&bytes[*offset..], offset))
} else {
None
}
})
.collect::<Result<Vec<_>>>()?;
Ok(entries)
}
}
#[derive(Clone)]
pub struct DirItemEntry {
pub dir_item: DirItem,
pub name: Vec<u8>,
}
impl DirItemEntry {
pub fn new(dir_item: DirItem, name: Vec<u8>) -> Self {
Self { dir_item, name }
}
pub fn name_as_str(&self) -> core::result::Result<&str, core::str::Utf8Error> {
core::str::from_utf8(&self.name)
}
pub fn name_as_string_lossy(&self) -> alloc::borrow::Cow<str> {
alloc::string::String::from_utf8_lossy(&self.name)
}
}
impl core::fmt::Debug for DirItemEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DirItemEntry")
.field("dir_item", &self.dir_item)
.field("name", &self.name_as_string_lossy())
.finish()
}
}
#[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, PartialEq, Eq, Copy, FromBytes, AsBytes)]
pub struct Header {
pub csum: [u8; 32],
pub fsid: Uuid,
/// Which block this node is supposed to live in
pub bytenr: U64<LE>,
pub flags: U64<LE>,
pub chunk_tree_uuid: Uuid,
pub generation: U64<LE>,
pub owner: U64<LE>,
pub nritems: U32<LE>,
pub level: u8,
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
/// 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<LE>,
pub size: U32<LE>,
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
/// 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<LE>,
pub generation: U64<LE>,
}
use alloc::vec::Vec;
#[derive(Debug)]
pub enum TreeItem {
Chunk(Chunk),
Root(RootItem),
DirItem(Vec<DirItemEntry>),
DirIndex(DirItemEntry),
Unimplemented,
}
impl From<Chunk> for TreeItem {
fn from(value: Chunk) -> Self {
Self::Chunk(value)
}
}
impl From<RootItem> for TreeItem {
fn from(value: RootItem) -> Self {
Self::Root(value)
}
}
impl From<Vec<DirItemEntry>> for TreeItem {
fn from(value: Vec<DirItemEntry>) -> Self {
Self::DirItem(value)
}
}
impl From<DirItemEntry> for TreeItem {
fn from(value: DirItemEntry) -> Self {
Self::DirIndex(value)
}
}
impl TreeItem {
pub fn parse(item: &Item, bytes: &[u8]) -> Result<Self> {
Ok(match item.key.ty() {
ObjectType::RootItem => RootItem::parse(bytes)?.into(),
ObjectType::ChunkItem => Chunk::parse(bytes)?.into(),
ObjectType::DirItem => DirItem::parse(bytes)?.into(),
ObjectType::DirIndex => DirItem::parse_single(bytes)?.into(),
_ => TreeItem::Unimplemented,
})
}
}
impl TreeItem {
pub fn as_chunk(&self) -> Option<&Chunk> {
if let Self::Chunk(v) = self {
Some(v)
} else {
None
}
}
pub fn as_root(&self) -> Option<&RootItem> {
if let Self::Root(v) = self {
Some(v)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct BTreeLeafNode {
pub header: Header,
/// actual leaf data
pub items: Vec<Item>,
}
impl BTreeLeafNode {
pub fn parse(header: Header, bytes: &[u8]) -> Result<Self> {
log::debug!("leaf:");
let offset = &mut 0;
let items = core::iter::from_fn(|| {
if *offset as usize + size_of::<Item>() < bytes.len() {
let item = Item::read_from(&bytes[*offset..*offset + size_of::<Item>()]);
*offset += size_of::<Item>();
if let Some(item) = item.as_ref() {
log::debug!(
"\titem type {:?}: ty offset: {}",
item.key.ty.as_type(),
item.key.offset.get()
);
log::debug!("\t{item:?}");
}
item
} else {
None
}
})
.take(header.nritems.get() as usize)
.collect::<Vec<_>>();
Ok(Self { header, items })
}
}
#[derive(Debug, Clone)]
pub struct BTreeInternalNode {
pub header: Header,
pub children: Vec<KeyPtr>,
}
impl BTreeInternalNode {
pub fn parse(header: Header, bytes: &[u8]) -> Result<Self> {
log::debug!("internal lvl: {}", header.level);
let offset = &mut 0;
let size = size_of::<KeyPtr>();
let children = core::iter::from_fn(|| {
if *offset as usize + size < bytes.len() {
let item = KeyPtr::read_from(&bytes[*offset..*offset + size]);
*offset += size;
if let Some(item) = item.as_ref() {
log::debug!(
"\tchild gen: {} offset: {}",
item.generation.get(),
item.key.offset.get()
);
}
item
} else {
None
}
})
.take(header.nritems.get() as usize)
.collect::<Vec<_>>();
Ok(Self { header, children })
}
}
#[derive(Debug, Clone)]
pub enum BTreeNode {
Internal(BTreeInternalNode),
Leaf(BTreeLeafNode),
}
impl BTreeNode {
pub fn parse(bytes: &[u8]) -> Result<Self> {
let offset = &mut 0;
let header = bytes.gread::<Header>(offset)?;
if header.level == 0 {
Ok(Self::Leaf(BTreeLeafNode::parse(header, &bytes[*offset..])?))
} else {
Ok(Self::Internal(BTreeInternalNode::parse(
header,
&bytes[*offset..],
)?))
}
}
}