btrfs crate
This commit is contained in:
parent
c22758f860
commit
47710fe4fa
27
btrfs/Cargo.toml
Normal file
27
btrfs/Cargo.toml
Normal file
|
@ -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 = "*"
|
257
btrfs/src/lib.rs
Normal file
257
btrfs/src/lib.rs
Normal file
|
@ -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<T: Read + Seek> 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<scroll::Error> for Error {
|
||||||
|
fn from(value: scroll::Error) -> Self {
|
||||||
|
Self::ScrollError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
|
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<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> 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<u64> {
|
||||||
|
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<core::cmp::Ordering> {
|
||||||
|
self.eq(other)
|
||||||
|
.then_some(core::cmp::Ordering::Equal)
|
||||||
|
.or_else(|| self.range.start.partial_cmp(&other.range.start))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChunkCacheTree {
|
||||||
|
inner: BTreeMap<ChunkTreeKey, u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChunkTree = BTreeMap<ChunkTreeKey, u64>;
|
||||||
|
|
||||||
|
pub struct Volume<R: VolumeIo> {
|
||||||
|
reader: R,
|
||||||
|
superblock: Superblock,
|
||||||
|
chunk_cache: ChunkTree,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: VolumeIo> Volume<R> {
|
||||||
|
pub fn new(mut reader: R) -> Result<Self> {
|
||||||
|
let mut buf = vec![0; size_of::<Superblock>()];
|
||||||
|
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<u64> {
|
||||||
|
self.chunk_cache
|
||||||
|
.get_key_value(&logical.into())
|
||||||
|
.map(|(key, _)| key.size())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset_from_logical(&self, logical: u64) -> Option<u64> {
|
||||||
|
self.chunk_cache
|
||||||
|
.get_key_value(&logical.into())
|
||||||
|
.map(|(key, offset)| offset + key.delta(logical))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn range_from_logical(&self, logical: u64) -> Option<core::ops::Range<u64>> {
|
||||||
|
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<u64>) -> Result<Vec<u8>> {
|
||||||
|
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<ChunkTree> {
|
||||||
|
let array_size = superblock.sys_chunk_array_size as usize;
|
||||||
|
let mut offset: usize = 0;
|
||||||
|
|
||||||
|
let key_size = size_of::<Key>();
|
||||||
|
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::<Key>(&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::<Chunk>(&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::<Stripe>();
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
789
btrfs/src/structs.rs
Normal file
789
btrfs/src/structs.rs
Normal file
|
@ -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> {
|
||||||
|
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<scroll::Endian> for Uuid {
|
||||||
|
fn size_with(_: &scroll::Endian) -> usize {
|
||||||
|
size_of::<Self>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<Self>();
|
||||||
|
Ok((*bytemuck::from_bytes::<Self>(&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<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, 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<Self> {
|
||||||
|
let superblock = bytes.pread_with::<Self>(0, scroll::LE)?;
|
||||||
|
|
||||||
|
if !superblock.verify_magic() {
|
||||||
|
return Err(Error::InvalidMagic);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(superblock)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checksum_type(&self) -> Option<ChecksumType> {
|
||||||
|
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<Self> {
|
||||||
|
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<Self> {
|
||||||
|
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<Item>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BTreeLeafNode {
|
||||||
|
pub fn parse(header: Header, bytes: &[u8]) -> Result<Self> {
|
||||||
|
let offset = &mut 0;
|
||||||
|
let items = core::iter::from_fn(|| {
|
||||||
|
if *offset as usize + size_of::<Item>() < bytes.len() {
|
||||||
|
Some(bytes.gread_with::<Item>(offset, scroll::LE))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.take(header.nritems as usize)
|
||||||
|
.collect::<core::result::Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self { header, items })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BTreeInternalNode {
|
||||||
|
header: Header,
|
||||||
|
children: Vec<KeyPtr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BTreeInternalNode {
|
||||||
|
pub fn parse(header: Header, bytes: &[u8]) -> Result<Self> {
|
||||||
|
let offset = &mut 0;
|
||||||
|
let children = core::iter::from_fn(|| {
|
||||||
|
if *offset as usize + size_of::<KeyPtr>() < bytes.len() {
|
||||||
|
Some(bytes.gread_with::<KeyPtr>(offset, scroll::LE))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.take(header.nritems as usize)
|
||||||
|
.collect::<core::result::Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self { header, children })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum BTreeNode {
|
||||||
|
Internal(BTreeInternalNode),
|
||||||
|
Leaf(BTreeLeafNode),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BTreeNode {
|
||||||
|
pub fn parse(bytes: &[u8]) -> Result<Self> {
|
||||||
|
let offset = &mut 0;
|
||||||
|
let header = bytes.gread_with::<Header>(offset, scroll::LE)?;
|
||||||
|
|
||||||
|
if header.level == 0 {
|
||||||
|
Ok(Self::Leaf(BTreeLeafNode::parse(header, &bytes[*offset..])?))
|
||||||
|
} else {
|
||||||
|
Ok(Self::Internal(BTreeInternalNode::parse(
|
||||||
|
header,
|
||||||
|
&bytes[*offset..],
|
||||||
|
)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
btrfs/tests/read_superblock.rs
Normal file
44
btrfs/tests/read_superblock.rs
Normal file
|
@ -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::<BufReader<std::fs::File>>::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();
|
||||||
|
}
|
120
notes.org
Normal file
120
notes.org
Normal file
|
@ -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
|
Loading…
Reference in a new issue