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]`
This commit is contained in:
Janis 2023-04-10 23:27:44 +02:00
parent e1f59b1b46
commit 3aa8ecbd77
4 changed files with 281 additions and 24 deletions

View file

@ -51,6 +51,16 @@ 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))] #[cfg(all(any(feature = "std", test), unix))]
impl Read for std::fs::File { impl Read for std::fs::File {
fn read(&self, dst: &mut [u8], address: u64) -> error::Result<()> { fn read(&self, dst: &mut [u8], address: u64) -> error::Result<()> {

View file

@ -1,8 +1,10 @@
use core::cell::Cell; use alloc::boxed::Box;
use core::cell::RefCell;
use core::fmt::Display; use core::fmt::Display;
use core::marker::PhantomData; use core::marker::PhantomData;
use core::mem::size_of; use core::mem::size_of;
use core::ops::Deref; use core::ops::Deref;
use core::ptr::NonNull;
use crate::structs::{Header, Item, Key, KeyPtr, KnownObjectId, ObjectType, TreeItem}; use crate::structs::{Header, Item, Key, KeyPtr, KnownObjectId, ObjectType, TreeItem};
use crate::{Error, Result}; use crate::{Error, Result};
@ -22,12 +24,24 @@ pub struct BTreeLeafNode {
pub items: Vec<Item>, pub items: Vec<Item>,
} }
#[derive(Debug, Clone)] #[derive(Debug)]
pub enum NodePtr { pub enum NodePtr {
Unvisited(KeyPtr), Unvisited(KeyPtr),
Visited { key: KeyPtr, node: BoxedNode }, // 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 { impl NodePtr {
pub fn key_ptr(&self) -> &KeyPtr { pub fn key_ptr(&self) -> &KeyPtr {
match self { match self {
@ -46,15 +60,25 @@ impl NodePtr {
pub fn key(&self) -> &Key { pub fn key(&self) -> &Key {
&self.key_ptr().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. /// An internal node in a btrfs tree, containing `KeyPtr`s to other internal nodes or leaf nodes.
#[derive(Derivative)] #[derive(Derivative, Clone)]
#[derivative(Debug)] #[derivative(Debug)]
pub struct BTreeInternalNode { pub struct BTreeInternalNode {
pub header: Header, pub header: Header,
#[derivative(Debug = "ignore")] #[derivative(Debug = "ignore")]
children: Vec<Cell<NodePtr>>, children: Vec<RefCell<NodePtr>>,
} }
impl BTreeInternalNode { impl BTreeInternalNode {
@ -74,16 +98,16 @@ impl BTreeInternalNode {
fn visit_child_inner<R: super::Read>( fn visit_child_inner<R: super::Read>(
&self, &self,
child: &Cell<NodePtr>, child: &RefCell<NodePtr>,
volume: &super::volume::Volume<R>, volume: &super::volume::Volume<R>,
) -> Result<BoxedNode> { ) -> Result<BoxedNode> {
match unsafe { &*child.as_ptr() } { match unsafe { &*child.as_ptr() } {
NodePtr::Unvisited(keyptr) => { NodePtr::Unvisited(keyptr) => {
let node = volume let node = volume
.read_keyptr(keyptr) .read_keyptr(keyptr)
.and_then(|bytes| Node::from_bytes(bytes)) .and_then(|bytes| Node::boxed_from_bytes(bytes))?;
.map(|node| Rc::new(node))?;
child.set(NodePtr::Visited { child.replace(NodePtr::Visited {
key: *keyptr, key: *keyptr,
node: node.clone(), node: node.clone(),
}); });
@ -131,13 +155,13 @@ impl PartialEq for BTreeLeafNode {
impl Eq for BTreeLeafNode {} impl Eq for BTreeLeafNode {}
impl Eq for BTreeInternalNode {} impl Eq for BTreeInternalNode {}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum BTreeNode { pub enum BTreeNode {
Internal(BTreeInternalNode), Internal(BTreeInternalNode),
Leaf(BTreeLeafNode), Leaf(BTreeLeafNode),
} }
#[derive(Derivative, Eq)] #[derive(Derivative, Eq, Clone)]
#[derivative(Debug, PartialEq)] #[derivative(Debug, PartialEq)]
pub struct Node { pub struct Node {
inner: BTreeNode, inner: BTreeNode,
@ -146,7 +170,41 @@ pub struct Node {
bytes: Vec<u8>, 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)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeHandle { pub struct NodeHandle {
@ -251,17 +309,26 @@ impl<R: super::Read> Clone for Tree<R> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
volume: self.volume.clone(), 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> { impl<R: super::Read> Tree<R> {
pub fn from_logical_offset(volume: Rc<Volume<R>>, logical: u64) -> Result<Self> { 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 let bytes = volume
.read_range_from_logical(logical)? .read_range_from_logical(logical)?
.ok_or(Error::BadLogicalAddress)?; // TODO: make this a better error .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 }) Ok(Self { volume, root })
} }
@ -301,14 +368,14 @@ 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 where
K: PartialEq<Key> + PartialOrd<Key>, K: PartialEq<Key> + PartialOrd<Key>,
{ {
self.find_node_inner(key, NodeHandle::find_key_rev) 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 where
K: PartialEq<Key> + PartialOrd<Key>, K: PartialEq<Key> + PartialOrd<Key>,
{ {
@ -408,7 +475,7 @@ impl BTreeInternalNode {
} }
}) })
.take(header.nritems.get() as usize) .take(header.nritems.get() as usize)
.map(|ptr| Cell::new(NodePtr::Unvisited(ptr))) .map(|ptr| RefCell::new(NodePtr::Unvisited(ptr)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Ok(Self { header, children }) Ok(Self { header, children })
@ -619,12 +686,48 @@ impl PartialEq for RootOrEdge {
} }
impl Node { 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> { pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
let inner = BTreeNode::parse(&bytes)?; let inner = BTreeNode::parse(&bytes)?;
Ok(Self { inner, 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 /// returns Ok(None) if `i` is out of bounds
fn read_nth_key(&self, i: usize) -> Option<Key> { fn read_nth_key(&self, i: usize) -> Option<Key> {
match &self.inner { match &self.inner {

View file

@ -82,8 +82,8 @@ pub struct Volume<R: super::Read> {
// TODO: find better name // TODO: find better name
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Volume2<R: super::Read> { pub struct Volume2<R: super::Read> {
inner: Rc<Volume<R>>, pub inner: Rc<Volume<R>>,
roots: BTreeMap<KnownObjectId, (RootItem, Tree<R>)>, pub roots: BTreeMap<KnownObjectId, (RootItem, Tree<R>)>,
} }
// TODO: find better name // 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>>> { pub fn read_range_from_logical(&self, logical: u64) -> Result<Option<Vec<u8>>> {
if let Some(range) = self.range_from_logical(logical) { 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)?)) Ok(Some(self.read_range(range)?))
} else { } else {
Ok(None) Ok(None)
@ -353,7 +359,7 @@ impl<R: super::Read> Volume2<R> {
} }
impl<R: super::Read> Fs<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((item, inoderef)) = self.find_inode_ref(inode_id)? {
if let Some(diritem) = self.find_dir_index(item.key.offset.get(), &inoderef)? { if let Some(diritem) = self.find_dir_index(item.key.offset.get(), &inoderef)? {
let inode = self.find_inode_item(&diritem)?; let inode = self.find_inode_item(&diritem)?;
@ -365,7 +371,11 @@ impl<R: super::Read> Fs<R> {
Ok(None) 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 { INode {
id: self.root_item.root_dirid.get(), id: self.root_item.root_dirid.get(),
path: vec![], path: vec![],
@ -514,7 +524,7 @@ 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 let Some(dir_entry) = self.get_inode_dir_index(inode_id)? {
if dir_entry.item().ty() == DirItemType::RegFile { if dir_entry.item().ty() == DirItemType::RegFile {
let key = let key =
@ -540,7 +550,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 mut contents = Vec::new();
let extents = self.get_inode_extents(inode.id)?; let extents = self.get_inode_extents(inode.id)?;
@ -649,6 +659,10 @@ impl<R: super::Read> Fs<R> {
super::tree::entry::Entry::Vacant(_) => Ok(None), super::tree::entry::Entry::Vacant(_) => Ok(None),
} }
} }
pub fn fs_root(&self) -> &Tree<R> {
&self.fs_root
}
} }
#[cfg(test)] #[cfg(test)]
@ -718,8 +732,8 @@ mod tests {
0x8dbfc2d2, // crc of "default" 0x8dbfc2d2, // crc of "default"
); );
let subvol_root = root_tree.find_node(&key)?; let subvol_root = root_tree.entry(&key)?;
let other = root_tree.find_node_rev(&key)?; let other = root_tree.entry_rev(&key)?;
assert_eq!(subvol_root, other); assert_eq!(subvol_root, other);
log::info!("{subvol_root:?}"); log::info!("{subvol_root:?}");
Ok(()) Ok(())

View file

@ -1 +1,131 @@
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_bytes!("../simple.img").as_slice();
let volume = Volume::new(filesystem_data)?.into_volume2()?;
Ok(volume)
}
#[test_log::test]
fn asdf() -> Result<()> {
let filesystem_data = include_bytes!("../simple.img").as_slice();
let volume = Volume::new(filesystem_data)?;
//.into_volume2();
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(Some(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, ..100)
.expect("file contents");
log::info!("license file:");
log::info!("{}", String::from_utf8_lossy(&file_contents));
Ok(())
}