Compare commits

...

10 commits

Author SHA1 Message Date
Janis 05c0c7df8f zstd decompression support
- extracted common code out of get_inode_by_path functions
2023-04-12 21:03:32 +02:00
Janis 43a92bedcf fixed PartialKey
- removed unnecessary/old code
- PartialKey id is no longer Optional, since that made no sense.
- lazily compare partialkey to key (probably not needed but looks neater)
2023-04-12 17:19:08 +02:00
Janis 6dd58e3b65 ACTUAL proper handling of ranges, comments/documentatoin 2023-04-11 02:55:44 +02:00
Janis 15089abba2 proper compression handling of ranges 2023-04-11 01:58:37 +02:00
Janis 8ba04a0b94 compression support for zlib 2023-04-11 01:46:41 +02:00
Janis 3aa8ecbd77 integration tests, made more things publicly accessible
- switching from Cell to RefCell for NodePtr
- deep cloneing and destroying of trees
- BoxedNode is a NonNull now instead of an Rc
- implemented Read for `&[u8]`
2023-04-10 23:27:44 +02:00
Janis e1f59b1b46 using BoxedNode alias instead of Rc<Node> 2023-04-06 11:47:34 +02:00
Janis 6762812ec5 removed tree::find_key/find_key_rev in favour of entry/_rev 2023-04-05 21:31:56 +02:00
Janis db762e0187 cleanup + switching to entry instead of find_key() 2023-04-05 21:07:35 +02:00
Janis 577abca2db entry api 2023-04-05 17:32:33 +02:00
8 changed files with 784 additions and 515 deletions

View file

@ -23,6 +23,15 @@ thiserror = { version = "1.0", package = "thiserror-core", default-features = fa
num_enum = {version = "0.5.11", default-features = false}
replace_with = "0.1.7"
miniz_oxide = {version = "0.7.1"}
zstd-safe = "6.0.5+zstd.1.5.4"
[dev-dependencies]
env_logger = "*"
test-log = "*"
test-log = "*"
include-blob = "0.1.2"
[build-dependencies]
include-blob = "0.1.2"

View file

@ -36,7 +36,7 @@ pub mod std_io {
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::ReadFailed)?;
.map_err(|_| Error::ReadFailed)?;
self.read_exact(dst).map_err(|_| Error::ReadFailed)
}

View file

@ -23,8 +23,8 @@ pub enum KnownObjectId {
QuotaTree,
UuidTree,
FreeSpaceTree,
RootINode = 0x100,
__LastFreeId = u64::MAX - 256,
// RootINode = 0x100, // first free id, always the root inode of a fs
// __LastFreeId = u64::MAX - 256, // last free id
DataRelocTree = u64::MAX - 9,
TreeReloc = u64::MAX - 8,
TreeLog = u64::MAX - 7,
@ -360,6 +360,17 @@ impl Parseable for ExtentData {
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, FromPrimitive, IntoPrimitive)]
pub enum CompressionType {
None = 0,
Zlib,
Lzo,
ZStd,
#[num_enum(catch_all)]
Invalid(u8),
}
#[repr(C, packed(1))]
#[derive(Debug, Clone, Copy, FromBytes, AsBytes)]
pub struct ExtentData1 {
@ -372,6 +383,14 @@ pub struct ExtentData1 {
}
impl ExtentData1 {
pub fn decoded_size(&self) -> u64 {
self.decoded_size.get()
}
pub fn compression(&self) -> CompressionType {
self.compression.into()
}
pub fn ty(&self) -> ExtentDataType {
match self.ty {
0 => ExtentDataType::Inline,

View file

@ -16,8 +16,12 @@ pub struct PartialKey {
}
impl PartialKey {
pub fn new(id: Option<KnownObjectId>, ty: Option<ObjectType>, offset: Option<u64>) -> Self {
Self { id, ty, offset }
pub fn new(id: KnownObjectId, ty: Option<ObjectType>, offset: Option<u64>) -> Self {
Self {
id: Some(id),
ty,
offset,
}
}
}
@ -65,25 +69,18 @@ mod partial_key_tests {
0x8dbfc2d2, // crc of "default"
);
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
Some(ObjectType::DirItem),
None,
);
let pkey = PartialKey::new(KnownObjectId::ChunkTree, Some(ObjectType::DirItem), None);
assert_eq!(pkey.partial_cmp(&key), None);
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
KnownObjectId::ChunkTree,
Some(ObjectType::DirItem),
Some(0xdeadbeef),
);
assert_ne!(pkey.partial_cmp(&key), Some(core::cmp::Ordering::Equal));
let pkey = PartialKey::new(None, Some(ObjectType::DirItem), Some(0xdeadbeef));
assert_ne!(pkey.partial_cmp(&key), None);
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
KnownObjectId::ChunkTree,
Some(ObjectType::DirItem),
Some(0x8dbfc2d2),
);
@ -98,25 +95,18 @@ mod partial_key_tests {
0x8dbfc2d2, // crc of "default"
);
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
Some(ObjectType::DirItem),
None,
);
let pkey = PartialKey::new(KnownObjectId::ChunkTree, Some(ObjectType::DirItem), None);
assert!(pkey.eq(&key));
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
KnownObjectId::ChunkTree,
Some(ObjectType::DirItem),
Some(0xdeadbeef),
);
assert!(!pkey.eq(&key));
let pkey = PartialKey::new(None, Some(ObjectType::DirItem), Some(0xdeadbeef));
assert!(!pkey.eq(&key));
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
KnownObjectId::ChunkTree,
Some(ObjectType::DirItem),
Some(0x8dbfc2d2),
);

View file

@ -1,11 +1,3 @@
use core::{
cmp::Ordering,
fmt::Debug,
ops::{Bound, RangeBounds},
};
use crate::Error;
pub mod error {
use thiserror::Error;
@ -29,6 +21,8 @@ pub mod error {
NoDefaultSubvolFsRoot,
#[error("INode could not be found in FsTree")]
INodeNotFound,
#[error("decompression error")]
DecompressionError,
#[error("attempted to access {index}th item out of bounds {range:?}")]
OutOfBounds {
range: core::ops::Range<usize>,
@ -59,11 +53,22 @@ pub trait Read {
}
}
impl Read for &[u8] {
fn read(&self, dst: &mut [u8], address: u64) -> error::Result<()> {
let src = self
.get(address as usize..address as usize + dst.len())
.ok_or(error::Error::ReadFailed)?;
dst.copy_from_slice(src);
Ok(())
}
}
#[cfg(all(any(feature = "std", test), unix))]
impl Read for std::fs::File {
fn read(&self, dst: &mut [u8], address: u64) -> error::Result<()> {
use std::os::unix::prelude::FileExt;
self.read_at(dst, address).map_err(|_| Error::ReadFailed)?;
self.read_at(dst, address)
.map_err(|_| error::Error::ReadFailed)?;
Ok(())
}
}
@ -71,275 +76,3 @@ impl Read for std::fs::File {
pub mod file;
pub mod tree;
pub mod volume;
pub fn cmp_start_bound<T: PartialOrd>(
rhs: &core::ops::Bound<T>,
lhs: &core::ops::Bound<T>,
) -> Option<core::cmp::Ordering> {
match rhs {
core::ops::Bound::Included(r) => match lhs {
core::ops::Bound::Included(l) => r.partial_cmp(&l),
core::ops::Bound::Excluded(l) => match r.partial_cmp(&l) {
Some(core::cmp::Ordering::Equal) => Some(core::cmp::Ordering::Less),
i => i,
},
core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Greater),
},
core::ops::Bound::Excluded(r) => match lhs {
core::ops::Bound::Excluded(l) => r.partial_cmp(&l),
core::ops::Bound::Included(l) => match r.partial_cmp(&l) {
Some(core::cmp::Ordering::Equal) => Some(core::cmp::Ordering::Greater),
i => i,
},
core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Greater),
},
core::ops::Bound::Unbounded => match lhs {
core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Equal),
_ => Some(core::cmp::Ordering::Less),
},
}
}
pub fn cmp_end_bound<T: PartialOrd>(
rhs: &core::ops::Bound<T>,
lhs: &core::ops::Bound<T>,
) -> Option<core::cmp::Ordering> {
match rhs {
core::ops::Bound::Included(r) => match lhs {
core::ops::Bound::Included(l) => r.partial_cmp(&l),
core::ops::Bound::Excluded(l) => match r.partial_cmp(&l) {
Some(core::cmp::Ordering::Equal) => Some(core::cmp::Ordering::Greater),
i => i,
},
core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Less),
},
core::ops::Bound::Excluded(r) => match lhs {
core::ops::Bound::Excluded(l) => r.partial_cmp(&l),
core::ops::Bound::Included(l) => match r.partial_cmp(&l) {
Some(core::cmp::Ordering::Equal) => Some(core::cmp::Ordering::Greater),
i => i,
},
core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Less),
},
core::ops::Bound::Unbounded => match lhs {
core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Equal),
_ => Some(core::cmp::Ordering::Greater),
},
}
}
#[derive(Debug, PartialEq, Eq)]
struct StartBound<T: PartialOrd>(Bound<T>);
impl<T: PartialOrd> PartialOrd for StartBound<T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
cmp_start_bound(&self.0, &other.0)
}
}
impl<T: Ord> Ord for StartBound<T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
#[derive(Debug, PartialEq, Eq)]
struct EndBound<T: PartialOrd>(Bound<T>);
impl<T: PartialOrd> PartialOrd for EndBound<T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
cmp_end_bound(&self.0, &other.0)
}
}
impl<T: Ord> Ord for EndBound<T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
// safety: partial_cmp only returns None when returning T::partial_cmp
self.partial_cmp(other).unwrap()
}
}
pub fn range_countains_bound<T: Ord + Debug, R: RangeBounds<T>>(
range: R,
bound: Bound<&T>,
) -> bool {
let start = &StartBound(bound);
let end = &EndBound(bound);
let r_start = &StartBound(range.start_bound());
let r_end = &EndBound(range.end_bound());
log::info!(
"start: {start:?} <=> {r_start:?}: {:?} {:?}",
start.cmp(r_start),
r_start.cmp(start)
);
log::info!(
"end: {end:?} <=> {r_end:?}: {:?} {:?}",
end.cmp(r_end),
r_end.cmp(end)
);
(start.cmp(r_start).is_ge() || r_start.cmp(start).is_le())
&& (end.cmp(r_end).is_ge() || r_end.cmp(end).is_le())
}
#[cfg(test)]
mod bound_ord_tests {
use core::{cmp::Ordering, ops::RangeBounds};
use super::*;
use test_log::test;
#[test]
fn start_bound_ord() {
assert_eq!(
cmp_start_bound(&Bound::Unbounded, &Bound::Included(0)),
Some(Ordering::Less)
);
assert_eq!(
cmp_start_bound::<i32>(&Bound::Unbounded, &Bound::Unbounded),
Some(Ordering::Equal)
);
assert_eq!(
cmp_start_bound(&Bound::Included(0), &Bound::Included(0)),
Some(Ordering::Equal)
);
assert_eq!(
cmp_start_bound(&Bound::Excluded(0), &Bound::Included(0)),
Some(Ordering::Greater)
);
assert_ne!(
cmp_start_bound(&Bound::Excluded(0), &Bound::Included(1)),
Some(Ordering::Greater)
);
// This is actually WRONG and is why we have to test both ways because I
// can't think of a way to actually determine how the 2 bounds are
// ordered without knowing the smallest discrete step size of T.
//
// In this case technically they should be equal, but for floats (which
// arent ord anyways?) this would be wrong
assert_eq!(
cmp_start_bound(&Bound::Included(1), &Bound::Excluded(0)),
Some(Ordering::Greater)
);
assert_eq!(
cmp_start_bound(&Bound::Included(0), &Bound::Excluded(1)),
Some(Ordering::Less)
);
}
#[test]
fn end_bound_ord() {
assert_eq!(
cmp_end_bound::<i32>(&Bound::Unbounded, &Bound::Unbounded),
Some(Ordering::Equal)
);
assert_eq!(
cmp_end_bound(&Bound::Unbounded, &Bound::Included(0)),
Some(Ordering::Greater)
);
assert_eq!(
cmp_end_bound(&Bound::Included(0), &Bound::Included(0)),
Some(Ordering::Equal)
);
assert_eq!(
cmp_end_bound(&Bound::Excluded(0), &Bound::Included(0)),
Some(Ordering::Greater)
);
assert_ne!(
cmp_end_bound(&Bound::Excluded(0), &Bound::Included(1)),
Some(Ordering::Greater)
);
// This is actually WRONG and is why we have to test both ways because I
// can't think of a way to actually determine how the 2 bounds are
// ordered without knowing the smallest discrete step size of T.
//
// In this case technically they should be equal, but for floats (which
// arent ord anyways?) this would be wrong
assert_eq!(
cmp_end_bound(&Bound::Included(1), &Bound::Excluded(0)),
Some(Ordering::Greater)
);
assert_eq!(
cmp_end_bound(&Bound::Included(0), &Bound::Excluded(1)),
Some(Ordering::Less)
);
}
#[test]
fn test_bound_ord() {
let r1 = 0..4;
let r2 = 2..3;
assert_eq!(
cmp_start_bound(&r1.start_bound(), &r2.start_bound()),
Some(core::cmp::Ordering::Less)
);
assert_eq!(
cmp_end_bound(&r1.end_bound(), &r2.end_bound()),
Some(core::cmp::Ordering::Greater)
);
assert_eq!(
cmp_start_bound(&r2.start_bound(), &r1.start_bound()),
Some(core::cmp::Ordering::Greater)
);
assert_eq!(
cmp_end_bound(&r2.end_bound(), &r1.end_bound()),
Some(core::cmp::Ordering::Less)
);
let r1 = 0..=8;
let r2 = 0..9;
assert_eq!(
cmp_start_bound(&r1.start_bound(), &r2.start_bound()),
Some(core::cmp::Ordering::Equal)
);
assert_eq!(
cmp_end_bound(&r1.end_bound(), &r2.end_bound()),
Some(core::cmp::Ordering::Less)
);
assert_eq!(
cmp_end_bound(&r2.end_bound(), &r1.end_bound()),
Some(core::cmp::Ordering::Greater)
);
}
}
pub fn ranges_intersect<T, R1, R2>(rhs: R1, lhs: R2) -> ()
where
T: Ord,
R1: core::ops::RangeBounds<T>,
R2: core::ops::RangeBounds<T>,
{
let a = rhs.start_bound();
let b = rhs.end_bound();
let x = lhs.start_bound();
let y = lhs.end_bound();
// check that a <=> x is different than b <=> x
// a <=> x
match a {
Bound::Included(a) => match x {
Bound::Included(x) => a.partial_cmp(x),
Bound::Excluded(x) => match a.partial_cmp(x) {
Some(Ordering::Equal) => Some(Ordering::Less),
ord => ord,
},
Bound::Unbounded => Some(Ordering::Greater),
},
Bound::Excluded(a) => match x {
Bound::Included(x) => match a.partial_cmp(x) {
Some(Ordering::Equal) => Some(Ordering::Greater),
ord => ord,
},
Bound::Excluded(x) => a.partial_cmp(x),
Bound::Unbounded => Some(Ordering::Less),
},
Bound::Unbounded => match x {
Bound::Unbounded => Some(Ordering::Equal),
_ => Some(Ordering::Less),
},
};
todo!()
}

View file

@ -1,8 +1,10 @@
use core::cell::Cell;
use alloc::boxed::Box;
use core::cell::RefCell;
use core::fmt::Display;
use core::marker::PhantomData;
use core::mem::size_of;
use core::ops::Deref;
use core::ptr::NonNull;
use crate::structs::{Header, Item, Key, KeyPtr, KnownObjectId, ObjectType, TreeItem};
use crate::{Error, Result};
@ -22,21 +24,33 @@ pub struct BTreeLeafNode {
pub items: Vec<Item>,
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub enum NodePtr {
Unvisited(KeyPtr),
Visited { key: KeyPtr, node: Rc<Node> }, // TODO: this doesnt need to be an Rc, can just be a NonNull with manual memory management
Visited { key: KeyPtr, node: BoxedNode }, // TODO: this doesnt need to be an Rc, can just be a NonNull with manual memory management
}
impl Clone for NodePtr {
fn clone(&self) -> Self {
match self {
Self::Unvisited(arg0) => Self::Unvisited(arg0.clone()),
Self::Visited { key, node } => Self::Visited {
key: key.clone(),
node: Node::clone_from_nonnull(node),
},
}
}
}
impl NodePtr {
pub fn key_ptr(&self) -> &KeyPtr {
match self {
NodePtr::Unvisited(key) => key,
NodePtr::Visited { key, node } => key,
NodePtr::Visited { key, .. } => key,
}
}
pub fn node(&self) -> Option<&Rc<Node>> {
pub fn node(&self) -> Option<&BoxedNode> {
match self {
NodePtr::Unvisited(_) => None,
NodePtr::Visited { node, .. } => Some(&node),
@ -46,15 +60,25 @@ impl NodePtr {
pub fn key(&self) -> &Key {
&self.key_ptr().key
}
pub fn destroy(self) {
match self {
Self::Visited { node, .. } => {
_ = node;
// TODO: from box drop
}
_ => {}
}
}
}
/// An internal node in a btrfs tree, containing `KeyPtr`s to other internal nodes or leaf nodes.
#[derive(Derivative)]
#[derive(Derivative, Clone)]
#[derivative(Debug)]
pub struct BTreeInternalNode {
pub header: Header,
#[derivative(Debug = "ignore")]
children: Vec<Cell<NodePtr>>,
children: Vec<RefCell<NodePtr>>,
}
impl BTreeInternalNode {
@ -62,7 +86,7 @@ impl BTreeInternalNode {
&self,
idx: usize,
volume: &super::volume::Volume<R>,
) -> Result<Rc<Node>> {
) -> Result<BoxedNode> {
match self.children.get(idx) {
Some(child) => self.visit_child_inner(child, volume),
None => Err(Error::OutOfBounds {
@ -74,16 +98,16 @@ impl BTreeInternalNode {
fn visit_child_inner<R: super::Read>(
&self,
child: &Cell<NodePtr>,
child: &RefCell<NodePtr>,
volume: &super::volume::Volume<R>,
) -> Result<Rc<Node>> {
) -> Result<BoxedNode> {
match unsafe { &*child.as_ptr() } {
NodePtr::Unvisited(keyptr) => {
let node = volume
.read_keyptr(keyptr)
.and_then(|bytes| Node::from_bytes(bytes))
.map(|node| Rc::new(node))?;
child.set(NodePtr::Visited {
.and_then(|bytes| Node::boxed_from_bytes(bytes))?;
child.replace(NodePtr::Visited {
key: *keyptr,
node: node.clone(),
});
@ -105,7 +129,7 @@ impl BTreeInternalNode {
pub fn visit_children<'a, 'b, R: super::Read>(
&'a self,
volume: &'b super::volume::Volume<R>,
) -> impl Iterator<Item = (usize, Result<Rc<Node>>)> + 'a
) -> impl Iterator<Item = (usize, Result<BoxedNode>)> + 'a
where
'b: 'a,
{
@ -131,13 +155,13 @@ impl PartialEq for BTreeLeafNode {
impl Eq for BTreeLeafNode {}
impl Eq for BTreeInternalNode {}
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum BTreeNode {
Internal(BTreeInternalNode),
Leaf(BTreeLeafNode),
}
#[derive(Derivative, Eq)]
#[derive(Derivative, Eq, Clone)]
#[derivative(Debug, PartialEq)]
pub struct Node {
inner: BTreeNode,
@ -146,7 +170,41 @@ pub struct Node {
bytes: Vec<u8>,
}
type BoxedNode = Rc<Node>;
#[repr(transparent)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BoxedNode(NonNull<Node>);
impl Deref for BoxedNode {
type Target = Node;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl From<NonNull<Node>> for BoxedNode {
fn from(value: NonNull<Node>) -> Self {
Self(value)
}
}
impl From<Node> for BoxedNode {
fn from(value: Node) -> Self {
Self(unsafe { NonNull::new_unchecked(Box::leak(Box::new(value))) })
}
}
impl BoxedNode {
pub fn as_ref(&self) -> &Node {
unsafe { self.0.as_ref() }
}
pub fn as_ptr(self) -> *mut Node {
self.0.as_ptr()
}
}
//type BoxedNode = NonNull<Node>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeHandle {
@ -166,6 +224,7 @@ impl Display for NodeHandle {
}
}
/// range of nodes that iterates thru all leaf nodes from start to end, inclusively.
#[derive(Debug)]
pub struct Range<'tree, R: super::Read> {
volume: Rc<Volume<R>>,
@ -174,6 +233,70 @@ pub struct Range<'tree, R: super::Read> {
phantom: PhantomData<&'tree ()>,
}
pub mod entry {
use super::*;
#[derive(Debug, PartialEq, Eq)]
pub enum Entry<'tree> {
Occupied(OccupiedEntry<'tree>),
Vacant(VacantEntry<'tree>),
}
impl<'tree> From<VacantEntry<'tree>> for Entry<'tree> {
fn from(v: VacantEntry<'tree>) -> Self {
Self::Vacant(v)
}
}
impl<'tree> From<OccupiedEntry<'tree>> for Entry<'tree> {
fn from(v: OccupiedEntry<'tree>) -> Self {
Self::Occupied(v)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct OccupiedEntry<'tree> {
key: Key,
node: NodeHandle,
phantom: PhantomData<&'tree ()>,
}
impl<'tree> OccupiedEntry<'tree> {
pub fn new(key: Key, node: NodeHandle) -> Self {
Self {
key,
node,
phantom: PhantomData,
}
}
pub fn key(&self) -> Key {
self.key
}
pub fn item_and_value(&self) -> Result<(Item, TreeItem)> {
self.node.parse_item()
}
pub fn value(&self) -> Result<TreeItem> {
Ok(self.node.parse_item()?.1)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct VacantEntry<'tree> {
phantom: PhantomData<&'tree ()>,
}
impl<'tree> VacantEntry<'tree> {
pub fn new() -> Self {
Self {
phantom: PhantomData,
}
}
}
}
#[derive(Derivative)]
#[derivative(Debug)]
pub struct Tree<R: super::Read> {
@ -186,17 +309,26 @@ impl<R: super::Read> Clone for Tree<R> {
fn clone(&self) -> Self {
Self {
volume: self.volume.clone(),
root: self.root.clone(),
root: Node::clone_from_nonnull(&self.root),
}
}
}
impl<R: super::Read> Drop for Tree<R> {
fn drop(&mut self) {
log::debug!("======= cleaning up tree =======");
Node::destroy(self.root.clone());
log::debug!("======= [done] =======");
}
}
impl<R: super::Read> Tree<R> {
pub fn from_logical_offset(volume: Rc<Volume<R>>, logical: u64) -> Result<Self> {
// TODO: this might read a very big range, far more than needed
let bytes = volume
.read_range_from_logical(logical)?
.ok_or(Error::BadLogicalAddress)?; // TODO: make this a better error
let root = Rc::new(Node::from_bytes(bytes)?);
let root = Node::boxed_from_bytes(bytes)?;
Ok(Self { volume, root })
}
@ -236,38 +368,42 @@ impl<R: super::Read> Tree<R> {
}
}
pub fn find_node_rev<K>(&self, key: &K) -> Result<Option<NodeHandle>>
fn find_node_rev<K>(&self, key: &K) -> Result<Option<NodeHandle>>
where
K: PartialEq<Key> + PartialOrd<Key>,
{
self.find_node_inner(key, NodeHandle::find_key_rev)
}
pub fn find_node<K>(&self, key: &K) -> Result<Option<NodeHandle>>
fn find_node<K>(&self, key: &K) -> Result<Option<NodeHandle>>
where
K: PartialEq<Key> + PartialOrd<Key>,
{
self.find_node_inner(key, NodeHandle::find_key)
}
pub fn find_key<K>(&self, key: &K) -> Result<Option<(Item, TreeItem)>>
pub fn entry<K>(&self, key: &K) -> Result<entry::Entry>
where
K: PartialEq<Key> + PartialOrd<Key>,
{
match self.find_node(key)? {
Some(node) => node.parse_item(),
None => Ok(None),
}
let entry: entry::Entry = match self.find_node(key)? {
Some(node) => entry::OccupiedEntry::new(node.parse_key(), node).into(),
None => entry::VacantEntry::new().into(),
};
Ok(entry)
}
pub fn find_key_rev<K>(&self, key: &K) -> Result<Option<(Item, TreeItem)>>
pub fn entry_rev<K>(&self, key: &K) -> Result<entry::Entry>
where
K: PartialEq<Key> + PartialOrd<Key>,
{
match self.find_node_rev(key)? {
Some(node) => node.parse_item(),
None => Ok(None),
}
let entry: entry::Entry = match self.find_node_rev(key)? {
Some(node) => entry::OccupiedEntry::new(node.parse_key(), node).into(),
None => entry::VacantEntry::new().into(),
};
Ok(entry)
}
pub fn find_range<K>(&self, key: &K) -> Result<Range<R>>
@ -339,7 +475,7 @@ impl BTreeInternalNode {
}
})
.take(header.nritems.get() as usize)
.map(|ptr| Cell::new(NodePtr::Unvisited(ptr)))
.map(|ptr| RefCell::new(NodePtr::Unvisited(ptr)))
.collect::<Vec<_>>();
Ok(Self { header, children })
@ -550,25 +686,85 @@ impl PartialEq for RootOrEdge {
}
impl Node {
pub fn clone_from_nonnull(this: &BoxedNode) -> BoxedNode {
(*this.as_ref()).clone().into()
}
pub fn boxed_from_bytes(bytes: Vec<u8>) -> Result<BoxedNode> {
Ok(Self::from_bytes(bytes)?.into())
}
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
let inner = BTreeNode::parse(&bytes)?;
Ok(Self { inner, bytes })
}
/// must not be called if any outstanding live references to `this` exist
pub fn destroy(this: BoxedNode) {
log::debug!("Node::destroy");
{
log::debug!("free: {:?}", this);
let inner = &this.as_ref().inner;
match inner {
BTreeNode::Internal(node) => {
log::debug!("destroying children..");
for child in node.children.iter() {
match unsafe { &*child.as_ptr() } {
NodePtr::Visited { node, .. } => {
Self::destroy(node.clone());
}
_ => {}
}
}
log::debug!("destroying children [end]");
}
BTreeNode::Leaf(_) => {}
}
}
log::debug!("dropping: {:?}", this);
unsafe {
drop(Box::from_raw(this.as_ptr()));
}
}
/// returns Ok(None) if `i` is out of bounds
fn read_nth_key(&self, i: usize) -> Option<Key> {
match &self.inner {
BTreeNode::Internal(internal) => {
let item = internal
.children
.get(i)
.map(|child| *unsafe { &*child.as_ptr() }.key());
item
}
BTreeNode::Leaf(leaf) => {
let key = leaf.items.get(i).map(|item| item.key);
key
}
}
}
/// returns None if pointing at an internal node or `i` is out of bounds.
/// returns an Error if parsing the item failed.
pub fn read_nth_item(&self, i: usize) -> Result<Option<(Item, TreeItem)>> {
match &self.inner {
BTreeNode::Internal(_) => Ok(None),
BTreeNode::Leaf(leaf) => {
// TODO: better error to indicate that it was out of bounds
let item = leaf.items.get(i).ok_or(Error::ReadFailed)?;
let start = size_of::<Header>() + item.offset.get() as usize;
let size = item.size.get() as usize;
let bytes = &self.bytes[start..start + size];
let item = if let Some(item) = leaf.items.get(i) {
let start = size_of::<Header>() + item.offset.get() as usize;
let size = item.size.get() as usize;
let bytes = &self.bytes[start..start + size];
let value = TreeItem::parse(item, bytes)?;
let value = TreeItem::parse(item, bytes)?;
Some((*item, value))
} else {
None
};
Ok(Some((*item, value)))
Ok(item)
}
}
}
@ -587,7 +783,7 @@ where
}
}
pub fn new(volume: Rc<Volume<R>>, start: Rc<Node>, end: Rc<Node>) -> Self {
pub fn new(volume: Rc<Volume<R>>, start: BoxedNode, end: BoxedNode) -> Self {
Self::from_handles(volume, NodeHandle::start(start), NodeHandle::end(end))
}
@ -613,7 +809,7 @@ where
});
if self.start.node.inner.is_leaf() {
break self.start.as_handle().parse_item().expect("range item");
break Some(self.start.as_handle().parse_item().expect("range item"));
}
// else repeat
} else {
@ -638,7 +834,7 @@ where
});
if self.end.node.inner.is_leaf() {
break self.end.as_handle().parse_item().expect("range item");
break Some(self.end.as_handle().parse_item().expect("range item"));
}
// else repeat
} else {
@ -658,23 +854,19 @@ impl NodeHandle {
}
}
pub fn parse_item(&self) -> Result<Option<(Item, TreeItem)>> {
self.node.read_nth_item(self.idx as usize)
/// returns None if pointing at an internal node or `i` is out of bounds.
/// returns an Error if parsing the item failed.
pub fn parse_item(&self) -> Result<(Item, TreeItem)> {
self.node
.read_nth_item(self.idx as usize)
.map(|result| result.unwrap())
}
pub fn advance_sideways(self) -> NodeHandleAdvanceResult {
let header = self.node.inner.header();
if header.nritems.get() >= self.idx + 1 {
NodeHandleAdvanceResult::Ascend
} else {
match &self.node.inner {
BTreeNode::Internal(_) => NodeHandleAdvanceResult::Next(Self {
idx: self.idx + 1,
..self
}),
_ => unreachable!(),
}
}
/// returns an Error if the key read fails
pub fn parse_key(&self) -> Key {
self.node
.read_nth_key(self.idx as usize)
.expect("idx out of bounds")
}
pub fn find_key_rev<K: PartialEq<Key> + PartialOrd<Key>>(self, key: &K) -> SearchResult {
@ -727,12 +919,6 @@ impl NodeHandle {
}
BTreeNode::Leaf(node) => {
for (i, child) in node.items.iter().enumerate() {
// if key < &child.key {
// return SearchResult::Leaf(NodeHandle {
// node: self.clone(),
// idx: if i == 0 { 0 } else { i as u32 - 1 },
// });
// } else
if key.eq(&child.key) {
return SearchResult::Leaf(NodeHandle {
idx: i as u32,
@ -868,20 +1054,20 @@ impl NodeHandle {
/// key lookup that will find the first key that matches the present items in this partial key
pub struct PartialKey {
pub id: Option<KnownObjectId>,
pub id: KnownObjectId,
pub ty: Option<ObjectType>,
pub offset: Option<u64>,
}
impl PartialKey {
pub fn new(id: Option<KnownObjectId>, ty: Option<ObjectType>, offset: Option<u64>) -> Self {
pub fn new(id: KnownObjectId, ty: Option<ObjectType>, offset: Option<u64>) -> Self {
Self { id, ty, offset }
}
}
impl PartialEq<Key> for PartialKey {
fn eq(&self, other: &Key) -> bool {
self.id.map(|id| id == other.id()).unwrap_or(true)
self.id == other.id()
&& self.ty.map(|ty| ty == other.ty()).unwrap_or(true)
&& self
.offset
@ -893,17 +1079,15 @@ impl PartialEq<Key> for PartialKey {
/// compares Self to a key, by comparing each item with the element in key if present, and skipping to the next item if missing.
impl PartialOrd<Key> for PartialKey {
fn partial_cmp(&self, other: &Key) -> Option<core::cmp::Ordering> {
let id = self.id.and_then(|id| id.partial_cmp(&other.id()));
let ty = self.ty.and_then(|ty| ty.partial_cmp(&other.ty()));
let offset = self
.offset
.and_then(|offset| offset.partial_cmp(&other.offset.get()));
match id {
Some(core::cmp::Ordering::Equal) | None => match ty {
Some(core::cmp::Ordering::Equal) | None => offset,
ord => ord,
},
match self.id.partial_cmp(&other.id()) {
Some(core::cmp::Ordering::Equal) | None => {
match self.ty.and_then(|ty| ty.partial_cmp(&other.ty())) {
Some(core::cmp::Ordering::Equal) | None => self
.offset
.and_then(|offset| offset.partial_cmp(&other.offset.get())),
ord => ord,
}
}
ord => ord,
}
}
@ -923,25 +1107,18 @@ mod partial_key_tests {
0x8dbfc2d2, // crc of "default"
);
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
Some(ObjectType::DirItem),
None,
);
let pkey = PartialKey::new(KnownObjectId::ChunkTree, Some(ObjectType::DirItem), None);
assert_eq!(pkey.partial_cmp(&key), None);
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
KnownObjectId::ChunkTree,
Some(ObjectType::DirItem),
Some(0xdeadbeef),
);
assert_ne!(pkey.partial_cmp(&key), Some(core::cmp::Ordering::Equal));
let pkey = PartialKey::new(None, Some(ObjectType::DirItem), Some(0xdeadbeef));
assert_ne!(pkey.partial_cmp(&key), None);
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
KnownObjectId::ChunkTree,
Some(ObjectType::DirItem),
Some(0x8dbfc2d2),
);
@ -956,25 +1133,18 @@ mod partial_key_tests {
0x8dbfc2d2, // crc of "default"
);
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
Some(ObjectType::DirItem),
None,
);
let pkey = PartialKey::new(KnownObjectId::ChunkTree, Some(ObjectType::DirItem), None);
assert!(pkey.eq(&key));
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
KnownObjectId::ChunkTree,
Some(ObjectType::DirItem),
Some(0xdeadbeef),
);
assert!(!pkey.eq(&key));
let pkey = PartialKey::new(None, Some(ObjectType::DirItem), Some(0xdeadbeef));
assert!(!pkey.eq(&key));
let pkey = PartialKey::new(
Some(KnownObjectId::ChunkTree),
KnownObjectId::ChunkTree,
Some(ObjectType::DirItem),
Some(0x8dbfc2d2),
);

View file

@ -1,15 +1,15 @@
use core::mem::size_of;
use core::ops::{Range, RangeBounds};
use core::ops::RangeBounds;
use alloc::collections::btree_map::Entry;
use alloc::{collections::BTreeMap, rc::Rc, vec, vec::Vec};
use scroll::Pread;
use crate::crc32c::calculate_crc32c;
use crate::path::Path;
use crate::path::{NormalizedPath, Path};
use crate::structs::{
Chunk, DirItemEntry, DirItemType, ExtentData, INodeItem, INodeRefEntry, Item, Key, KeyPtr,
KnownObjectId, ObjectType, RootItem, Stripe, Superblock, TreeItem,
Chunk, CompressionType, DirItemEntry, DirItemType, ExtentData, INodeItem, INodeRefEntry, Item,
Key, KeyPtr, KnownObjectId, ObjectType, RootItem, Stripe, Superblock, TreeItem,
};
use crate::{Error, Result};
@ -82,8 +82,8 @@ pub struct Volume<R: super::Read> {
// TODO: find better name
#[derive(Debug, Clone)]
pub struct Volume2<R: super::Read> {
inner: Rc<Volume<R>>,
roots: BTreeMap<KnownObjectId, (RootItem, Tree<R>)>,
pub inner: Rc<Volume<R>>,
pub roots: BTreeMap<KnownObjectId, (RootItem, Tree<R>)>,
}
// TODO: find better name
@ -222,6 +222,12 @@ impl<R: super::Read> Volume<R> {
pub fn read_range_from_logical(&self, logical: u64) -> Result<Option<Vec<u8>>> {
if let Some(range) = self.range_from_logical(logical) {
log::debug!(
"reading [{}, {}) ({} bytes)",
range.start,
range.end,
range.end - range.start
);
Ok(Some(self.read_range(range)?))
} else {
Ok(None)
@ -322,14 +328,15 @@ impl<R: super::Read> Volume2<R> {
0x8dbfc2d2, // crc of "default"
);
let subvol_root = root_tree
.find_key(&key)?
.ok_or(Error::NoDefaultSubvolRoot)?;
let subvol_root = match root_tree.entry(&key)? {
super::tree::entry::Entry::Occupied(entry) => Some(entry.value()?),
super::tree::entry::Entry::Vacant(_) => None,
}
.ok_or(Error::NoDefaultSubvolRoot)?;
// if we found the dir entry of the "default subvol" (mharmstone nomenclature)
// we then look for the root fs tree in the root tree with the ID found in the `.location` of the dir_item only (from mharmstone)
let subvol_id = subvol_root
.1
.as_dir_item()
.expect("dir item")
.first()
@ -352,7 +359,7 @@ impl<R: super::Read> Volume2<R> {
}
impl<R: super::Read> Fs<R> {
fn get_inode_item(&self, inode_id: u64) -> Result<Option<INodeItem>> {
pub fn get_inode_item(&self, inode_id: u64) -> Result<Option<INodeItem>> {
if let Some((item, inoderef)) = self.find_inode_ref(inode_id)? {
if let Some(diritem) = self.find_dir_index(item.key.offset.get(), &inoderef)? {
let inode = self.find_inode_item(&diritem)?;
@ -364,7 +371,11 @@ impl<R: super::Read> Fs<R> {
Ok(None)
}
fn get_root_dir(&self) -> INode {
pub fn root_dir_id(&self) -> KnownObjectId {
self.root_item.root_dirid.get().into()
}
pub fn get_root_dir(&self) -> INode {
INode {
id: self.root_item.root_dirid.get(),
path: vec![],
@ -389,7 +400,7 @@ impl<R: super::Read> Fs<R> {
&self,
inode: &INode,
) -> Result<impl Iterator<Item = DirItemEntry> + '_> {
let key = PartialKey::new(Some(inode.id()), Some(ObjectType::DirIndex), None);
let key = PartialKey::new(inode.id(), Some(ObjectType::DirIndex), None);
let children = self.fs_root.find_range(&key)?;
@ -419,33 +430,9 @@ impl<R: super::Read> Fs<R> {
P: Path,
{
if path.is_absolute() {
// stuff
self.get_inode_by_path(path)
} else {
let path = path.normalize().into_iter();
let mut inode = inode;
for segment in path {
match segment {
crate::path::Segment::ParentDir => {
inode = self.get_inode_parent(&inode)?;
}
crate::path::Segment::File(child_name) => {
let child = self
.get_inode_children_inodes(&inode)?
.find(|child| {
child.path.last().map(|bytes| bytes.as_slice()) == Some(child_name)
})
.ok_or(Error::INodeNotFound)?
.clone();
// silly borrow checker
inode = child;
}
_ => unreachable!(),
}
}
Ok(inode)
self.get_inode_by_relative_normalized_path(inode, path.normalize())
}
}
@ -461,21 +448,33 @@ impl<R: super::Read> Fs<R> {
_ = normalized.pop_segment();
}
let mut inode = self.get_root_dir();
self.get_inode_by_relative_normalized_path(self.get_root_dir(), normalized)
}
while let Some(segment) = normalized.pop_segment() {
pub fn get_inode_by_relative_normalized_path(
&self,
inode: INode,
path: NormalizedPath,
) -> Result<INode> {
let mut inode = inode;
for segment in path.iter() {
match segment {
crate::path::Segment::Root | crate::path::Segment::NoOp => {} // do nothing
crate::path::Segment::CurrentDir | crate::path::Segment::ParentDir => {
unimplemented!()
} // not normalized?
crate::path::Segment::File(child) => {
let dir_item = self
.find_inode_child(inode.id, child)?
.ok_or(Error::INodeNotFound)?;
inode = inode.into_child(dir_item.item().location.id().into(), child.to_vec());
crate::path::Segment::ParentDir => {
inode = self.get_inode_parent(&inode)?;
}
crate::path::Segment::File(child_name) => {
let child = self
.get_inode_children_inodes(&inode)?
.find(|child| {
child.path.last().map(|bytes| bytes.as_slice()) == Some(child_name)
})
.ok_or(Error::INodeNotFound)?
.clone();
// silly borrow checker
inode = child;
}
_ => unreachable!(),
}
}
@ -485,18 +484,23 @@ impl<R: super::Read> Fs<R> {
fn find_inode_child(&self, parent_inode: u64, child: &[u8]) -> Result<Option<DirItemEntry>> {
let crc = calculate_crc32c(0xfffffffe, child);
let key = PartialKey::new(
Some(parent_inode.into()),
parent_inode.into(),
Some(ObjectType::DirItem),
Some(crc as u64),
);
if let Some((_, value)) = self.fs_root.find_key(&key)? {
let dir_items = value.as_dir_item().expect("dir index");
let item = dir_items.iter().find(|item| item.name() == child).cloned();
Ok(item)
} else {
Ok(None)
match self.fs_root.entry(&key)? {
super::tree::entry::Entry::Occupied(occupied) => {
let item = occupied
.value()?
.as_dir_item()
.expect("dir item")
.iter()
.find(|item| item.name() == child)
.cloned();
Ok(item)
}
super::tree::entry::Entry::Vacant(_) => Ok(None),
}
}
@ -508,11 +512,10 @@ impl<R: super::Read> Fs<R> {
}
}
fn get_inode_extents(&self, inode_id: u64) -> Result<Vec<(u64, ExtentData)>> {
pub fn get_inode_extents(&self, inode_id: u64) -> Result<Vec<(u64, ExtentData)>> {
if let Some(dir_entry) = self.get_inode_dir_index(inode_id)? {
if dir_entry.item().ty() == DirItemType::RegFile {
let key =
PartialKey::new(Some(inode_id.into()), Some(ObjectType::ExtentData), None);
let key = PartialKey::new(inode_id.into(), Some(ObjectType::ExtentData), None);
let extents = self.fs_root.find_range(&key)?;
@ -534,7 +537,7 @@ impl<R: super::Read> Fs<R> {
}
}
fn read_inode_raw<I: RangeBounds<u64>>(&self, inode: &INode, range: I) -> Result<Vec<u8>> {
pub fn read_inode_raw<I: RangeBounds<u64>>(&self, inode: &INode, range: I) -> Result<Vec<u8>> {
let mut contents = Vec::new();
let extents = self.get_inode_extents(inode.id)?;
@ -550,67 +553,188 @@ impl<R: super::Read> Fs<R> {
core::ops::Bound::Unbounded => None,
};
// FIXME: offsets need to be calculated with the uncompressed length and offset
// currently are calculated with compressed length and offset afaik
log::info!("extents: {}", extents.len());
log::info!("{:?}", extents);
for (offset, extent) in extents.into_iter().filter(|(offset, extent)| {
// bounds of the current extent
let extent_start = *offset;
let extent_end = extent_start + extent.len();
let extent_len = extent.len();
// entire range we want to read from the file
let range_len = end.map(|end| end - start);
let start2 = start.min(extent_start);
// start of the UNION (from lowest bound to highest bound) of the
// current extent and the entire range
let start = start.min(extent_start);
// end of the UNION of the current extent and the entire range
let end = end.map(|end| end.max(extent_end));
let len = end.map(|end| (end - start2));
// width of the union o fthe current extent and the entire range
let len = end.map(|end| (end - start));
if let (Some(len), Some(range_len)) = (len, range_len) {
range_len + range_len < len
// proceed if the widths of the 2 ranges (the range we want to
// read, and the current extent) are greater than the width of
// the union range:
//
// In the first example, the 2 ranges overlap, and the width of
// the union is smaller than the sum of the widths of the ranges:
//
// |------range-1------|
// |---range-2----|
// |-----width-of-union-----|
// |-------sum----|-of---widths-------|
// |------------width-of-union------------|
// |------range-1------|
// |---range-2----|
//
// In this second example, the ranges do not overlap, and the
// width of the unions is equal or greater than the sum of the
// widths.
len < extent_len + range_len
} else {
start2 < extent_end
start < extent_end
}
}) {
//
let start = start.saturating_sub(offset);
let end = end.map(|end| end - offset);
let end = end.map(|end| end - offset).unwrap_or(start + extent.len());
let len = end - start;
log::info!("reading {}..{:?} from extent.", start, end);
let data: alloc::borrow::Cow<[u8]> = match &extent {
ExtentData::Inline { data, .. } => {
// TODO: handle compression and encryption
let data = if let Some(end) = end {
&data[start as usize..end as usize]
} else {
&data[start as usize..]
};
data.into()
}
ExtentData::Inline { data, .. } => (&data[start as usize..end as usize]).into(),
ExtentData::Other(extent) => {
let address = extent.address() + extent.offset() + start;
let data = self
let address = extent.address() + extent.offset();
let address = self
.volume
.inner
.read_range(address..address + end.unwrap_or(extent.num_bytes()))
.expect("bytes");
.offset_from_logical(address)
.ok_or(Error::BadLogicalAddress)?;
let range = match extent.extent_data1().compression() {
// compressed size
CompressionType::Zlib | CompressionType::Lzo | CompressionType::ZStd => {
address..address + extent.size()
}
_ => address + start..address + start + len,
};
let data = self.volume.inner.read_range(range).expect("bytes");
data.into()
}
};
log::info!("reading {} bytes from file", data.len());
contents.extend_from_slice(&data);
log::info!("compression: {:?}", extent.header().compression());
match extent.header().compression() {
CompressionType::None => {
contents.extend_from_slice(&data);
}
CompressionType::Zlib => {
let mut state = miniz_oxide::inflate::stream::InflateState::new(
miniz_oxide::DataFormat::Zlib,
);
let mut output_data = vec![0u8; extent.header().decoded_size() as usize];
let mut output = &mut output_data[..];
let mut data = &data[..];
loop {
let result = miniz_oxide::inflate::stream::inflate(
&mut state,
&data,
&mut output,
miniz_oxide::MZFlush::None,
);
match result.status.map_err(|_| Error::DecompressionError)? {
miniz_oxide::MZStatus::Ok => {}
miniz_oxide::MZStatus::StreamEnd => break,
_ => {
log::error!("need dict ?!");
return Err(Error::DecompressionError);
}
}
data = &data[result.bytes_consumed..];
output = &mut output[result.bytes_written..];
}
_ = miniz_oxide::inflate::stream::inflate(
&mut state,
&data,
&mut output,
miniz_oxide::MZFlush::Finish,
)
.status
.map_err(|_| Error::DecompressionError)?;
// truncate inflated data if needed
contents
.extend_from_slice(&output_data[start as usize..(start + len) as usize]);
}
CompressionType::Lzo => {
todo!()
}
CompressionType::ZStd => {
let mut output_data = vec![0u8; extent.header().decoded_size() as usize];
let mut zstd = zstd_safe::DCtx::create();
zstd.init().map_err(|e| {
log::error!("zstd init error: {}", zstd_safe::get_error_name(e));
Error::DecompressionError
})?;
let mut input = zstd_safe::InBuffer::around(&data);
let mut output = zstd_safe::OutBuffer::around(&mut output_data[..]);
loop {
match zstd.decompress_stream(&mut output, &mut input) {
Ok(len) => {
if len == 0 {
break;
}
}
Err(e) => {
log::error!(
"zstd decompress stream error: {}",
zstd_safe::get_error_name(e)
);
return Err(Error::DecompressionError);
}
}
if output.pos() == extent.header().decoded_size() as usize {
break;
}
}
contents
.extend_from_slice(&output_data[start as usize..(start + len) as usize]);
}
c => {
log::error!("invalid compression type {:?}", c);
contents.extend_from_slice(&data);
}
}
}
Ok(contents)
}
fn find_inode_ref(&self, inode_id: u64) -> Result<Option<(Item, INodeRefEntry)>> {
let key = PartialKey::new(Some(inode_id.into()), Some(ObjectType::INodeRef), None);
let key = PartialKey::new(inode_id.into(), Some(ObjectType::INodeRef), None);
if let Some((item, value)) = self.fs_root.find_key(&key)? {
let inode = value.as_inode_ref().expect("inoderef").clone();
Ok(Some((item, inode)))
} else {
Ok(None)
match self.fs_root.entry(&key)? {
super::tree::entry::Entry::Occupied(entry) => {
entry.item_and_value().map(|(item, value)| {
Some((item, value.as_inode_ref().expect("inode ref").clone()))
})
}
super::tree::entry::Entry::Vacant(_) => Ok(None),
}
}
@ -621,29 +745,31 @@ impl<R: super::Read> Fs<R> {
) -> Result<Option<DirItemEntry>> {
//let crc = calculate_crc32c(0xfffffffe, &inoderef.name());
let key = PartialKey::new(
Some(parent_inode.into()),
parent_inode.into(),
Some(ObjectType::DirIndex),
Some(inoderef.item().index.get()),
);
if let Some((_, value)) = self.fs_root.find_key(&key)? {
let dir_index = value.as_dir_index().expect("dir index").clone();
Ok(Some(dir_index))
} else {
Ok(None)
match self.fs_root.entry(&key)? {
super::tree::entry::Entry::Occupied(entry) => entry
.item_and_value()
.map(|(_, value)| Some(value.as_dir_index().expect("dir index").clone())),
super::tree::entry::Entry::Vacant(_) => Ok(None),
}
}
fn find_inode_item(&self, dir_item: &DirItemEntry) -> Result<Option<INodeItem>> {
dir_item.item().location;
if let Some((_, value)) = self.fs_root.find_key(&dir_item.item().location)? {
let inode = value.as_inode_item().expect("inode item").clone();
Ok(Some(inode))
} else {
Ok(None)
match self.fs_root.entry(&dir_item.item().location)? {
super::tree::entry::Entry::Occupied(entry) => entry
.item_and_value()
.map(|(_, value)| Some(value.as_inode_item().expect("inode item").clone())),
super::tree::entry::Entry::Vacant(_) => Ok(None),
}
}
pub fn fs_root(&self) -> &Tree<R> {
&self.fs_root
}
}
#[cfg(test)]
@ -674,7 +800,7 @@ mod tests {
let v2 = vol.into_volume2().expect("volume2");
log::info!("roots:");
for (id, v) in v2.roots.iter() {
for (id, _) in v2.roots.iter() {
log::info!("[{id:?}] ");
}
}
@ -686,11 +812,11 @@ mod tests {
let v2 = vol.into_volume2().expect("volume2");
log::info!("roots:");
for (id, v) in v2.roots.iter() {
for (id, _) in v2.roots.iter() {
log::info!("[{id:?}] ");
}
log::info!("roots rev:");
for (id, v) in v2.roots.iter().rev() {
for (id, _) in v2.roots.iter().rev() {
log::info!("[{id:?}] ");
}
}
@ -713,8 +839,8 @@ mod tests {
0x8dbfc2d2, // crc of "default"
);
let subvol_root = root_tree.find_node(&key)?;
let other = root_tree.find_node_rev(&key)?;
let subvol_root = root_tree.entry(&key)?;
let other = root_tree.entry_rev(&key)?;
assert_eq!(subvol_root, other);
log::info!("{subvol_root:?}");
Ok(())
@ -726,7 +852,7 @@ mod tests {
let vol = Volume::new(file).expect("volume");
let v2 = vol.into_volume2().expect("volume2");
let fs = v2.default_subvolume().expect("subvol");
_ = v2.default_subvolume().expect("subvol");
Ok(())
}
@ -738,7 +864,7 @@ mod tests {
let fs = v2.default_subvolume().expect("default subvol");
let search_key = PartialKey::new(
Some(fs.root_item.root_dirid.get().into()),
fs.root_item.root_dirid.get().into(),
Some(ObjectType::DirIndex),
None,
);
@ -829,7 +955,7 @@ mod tests {
log::info!("files 1:");
let now = std::time::Instant::now();
for (_id, entry) in fs.fs_root.iter() {
if let Some(dir) = entry.as_dir_index() {
if let Some(_dir) = entry.as_dir_index() {
//log::info!("{}", dir.name_as_string_lossy());
}
}
@ -838,7 +964,7 @@ mod tests {
log::info!("files 2:");
let now = std::time::Instant::now();
for (_id, entry) in fs.fs_root.iter() {
if let Some(dir) = entry.as_dir_index() {
if let Some(_dir) = entry.as_dir_index() {
//log::info!("{}", dir.name_as_string_lossy());
}
}

View file

@ -1 +1,223 @@
use btrfs::structs::{ExtentData, ObjectType};
use btrfs::v2::error::Result;
use btrfs::v2::tree::PartialKey;
use btrfs::v2::volume::*;
use include_blob::include_blob;
fn open_filesystem() -> Result<std::rc::Rc<Volume2<&'static [u8]>>> {
let filesystem_data = include_blob!("simple.img").as_slice();
let volume = Volume::new(filesystem_data)?.into_volume2()?;
Ok(volume)
}
fn open_filesystem_lzo() -> Result<std::rc::Rc<Volume2<&'static [u8]>>> {
let filesystem_data = include_blob!("compressed-lzo.img").as_slice();
let volume = Volume::new(filesystem_data)?.into_volume2()?;
Ok(volume)
}
fn open_filesystem_zlib() -> Result<std::rc::Rc<Volume2<&'static [u8]>>> {
let filesystem_data = include_blob!("compressed-zlib.img").as_slice();
let volume = Volume::new(filesystem_data)?.into_volume2()?;
Ok(volume)
}
fn open_filesystem_zstd() -> Result<std::rc::Rc<Volume2<&'static [u8]>>> {
let filesystem_data = include_blob!("compressed-zstd.img").as_slice();
let volume = Volume::new(filesystem_data)?.into_volume2()?;
Ok(volume)
}
#[test_log::test]
fn asdf() -> Result<()> {
let a = open_filesystem()?;
Ok(())
}
#[test_log::test]
fn read_superblock() -> Result<()> {
let vol2 = open_filesystem()?;
let sb = vol2.inner.superblock();
println!("{sb:#?}");
assert!(sb.verify_magic());
assert!(sb.verify_checksum());
Ok(())
}
#[test_log::test]
fn iter_roots() -> Result<()> {
let vol2 = open_filesystem()?;
for (id, _) in vol2.roots.iter() {
log::info!("[{id:?}] ");
}
Ok(())
}
#[test_log::test]
fn iter_roots_rev() -> Result<()> {
let vol2 = open_filesystem()?;
for (id, _) in vol2.roots.iter().rev() {
log::info!("[{id:?}] ");
}
Ok(())
}
#[test_log::test]
fn iter_default_subvol() -> Result<()> {
let v2 = open_filesystem()?;
let fs = v2.default_subvolume().expect("default subvol");
log::info!("files 1:");
let now = std::time::Instant::now();
for (_id, entry) in fs.fs_root().iter() {
if let Some(_dir) = entry.as_dir_index() {
//log::info!("{}", dir.name_as_string_lossy());
}
}
log::info!("files 1: [took {}ms]", now.elapsed().as_millis());
log::info!("files 2:");
let now = std::time::Instant::now();
for (_id, entry) in fs.fs_root().iter() {
if let Some(_dir) = entry.as_dir_index() {
//log::info!("{}", dir.name_as_string_lossy());
}
}
log::info!("files 2: [took {}ms]", now.elapsed().as_millis());
Ok(())
}
#[test_log::test]
fn get_inode_items() -> Result<()> {
let v2 = open_filesystem()?;
let fs = v2.default_subvolume().expect("default subvol");
let search_key = PartialKey::new(fs.root_dir_id(), Some(ObjectType::DirIndex), None);
// with range
log::info!("range:");
for (key, v) in fs.fs_root().find_range(&search_key)? {
let dirindex = v.as_dir_index().unwrap();
let inode_id: u64 = dirindex.item().location.id().into();
log::info!("[{key:?}] {v:#?}");
log::info!("inode: {inode_id}");
let inode_item = fs.get_inode_item(inode_id)?;
log::info!("inode: {inode_item:#?}");
let extents = fs.get_inode_extents(inode_id)?;
for (_, extent) in extents {
match extent {
ExtentData::Inline { header, data } => {
log::info!("{header:?}\n{}", String::from_utf8_lossy(&data));
}
_ => {}
}
}
}
log::info!("range: [end]");
Ok(())
}
#[test_log::test]
fn find_file() -> Result<()> {
let v2 = open_filesystem()?;
let fs = v2.default_subvolume().expect("default subvol");
let root_dir = fs.get_root_dir();
let children = fs.get_inode_children(&root_dir)?.collect::<Vec<_>>();
log::info!("chidlren: {:?}", children);
let cmake_list = fs.get_inode_by_path(b"/quibble/LICENCE")?;
let file_contents = fs.read_inode_raw(&cmake_list, ..).expect("file contents");
assert_eq!(
&file_contents[..52],
b" GNU LESSER GENERAL PUBLIC LICENSE"
);
log::info!("license file:");
log::info!("{}", String::from_utf8_lossy(&file_contents));
Ok(())
}
#[test_log::test]
fn find_file_zlib() -> Result<()> {
let v2 = open_filesystem_zlib()?;
let fs = v2.default_subvolume().expect("default subvol");
let root_dir = fs.get_root_dir();
let children = fs.get_inode_children(&root_dir)?.collect::<Vec<_>>();
log::info!("chidlren: {:?}", children);
let cmake_list = fs.get_inode_by_path(b"/quibble/LICENCE")?;
let file_contents = fs
.read_inode_raw(&cmake_list, ..100)
.expect("file contents");
//assert_eq!(&file_contents[..11], b"hello world");
log::info!("license file:");
log::info!("{}", String::from_utf8_lossy(&file_contents));
assert_eq!(
&file_contents[..52],
b" GNU LESSER GENERAL PUBLIC LICENSE"
);
Ok(())
}
#[test_log::test]
fn find_file_lzo() -> Result<()> {
let v2 = open_filesystem_lzo()?;
let fs = v2.default_subvolume().expect("default subvol");
let root_dir = fs.get_root_dir();
let children = fs.get_inode_children(&root_dir)?.collect::<Vec<_>>();
log::info!("chidlren: {:?}", children);
let cmake_list = fs.get_inode_by_path(b"/quibble/LICENCE")?;
let file_contents = fs.read_inode_raw(&cmake_list, ..).expect("file contents");
assert_eq!(
&file_contents[..52],
b" GNU LESSER GENERAL PUBLIC LICENSE"
);
log::info!("license file:");
log::info!("{}", String::from_utf8_lossy(&file_contents));
Ok(())
}
#[test_log::test]
fn find_file_zstd() -> Result<()> {
let v2 = open_filesystem_zstd()?;
let fs = v2.default_subvolume().expect("default subvol");
let root_dir = fs.get_root_dir();
let children = fs.get_inode_children(&root_dir)?.collect::<Vec<_>>();
log::info!("chidlren: {:?}", children);
let cmake_list = fs.get_inode_by_path(b"/quibble/LICENCE")?;
let file_contents = fs
.read_inode_raw(&cmake_list, ..100)
.expect("file contents");
assert_eq!(
&file_contents[..52],
b" GNU LESSER GENERAL PUBLIC LICENSE"
);
log::info!("license file:");
log::info!("{}", String::from_utf8_lossy(&file_contents));
Ok(())
}