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