diff --git a/btrfs/src/v2/mod.rs b/btrfs/src/v2/mod.rs index a868019..4ba30b5 100644 --- a/btrfs/src/v2/mod.rs +++ b/btrfs/src/v2/mod.rs @@ -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))] impl Read for std::fs::File { fn read(&self, dst: &mut [u8], address: u64) -> error::Result<()> { diff --git a/btrfs/src/v2/tree.rs b/btrfs/src/v2/tree.rs index 5209f66..a552001 100644 --- a/btrfs/src/v2/tree.rs +++ b/btrfs/src/v2/tree.rs @@ -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,12 +24,24 @@ pub struct BTreeLeafNode { pub items: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum NodePtr { Unvisited(KeyPtr), 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 { @@ -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>, + children: Vec>, } impl BTreeInternalNode { @@ -74,16 +98,16 @@ impl BTreeInternalNode { fn visit_child_inner( &self, - child: &Cell, + child: &RefCell, volume: &super::volume::Volume, ) -> Result { 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(), }); @@ -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, } -type BoxedNode = Rc; +#[repr(transparent)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BoxedNode(NonNull); + +impl Deref for BoxedNode { + type Target = Node; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl From> for BoxedNode { + fn from(value: NonNull) -> Self { + Self(value) + } +} + +impl From 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; #[derive(Debug, Clone, PartialEq, Eq)] pub struct NodeHandle { @@ -251,17 +309,26 @@ impl Clone for Tree { fn clone(&self) -> Self { Self { volume: self.volume.clone(), - root: self.root.clone(), + root: Node::clone_from_nonnull(&self.root), } } } +impl Drop for Tree { + fn drop(&mut self) { + log::debug!("======= cleaning up tree ======="); + Node::destroy(self.root.clone()); + log::debug!("======= [done] ======="); + } +} + impl Tree { pub fn from_logical_offset(volume: Rc>, logical: u64) -> Result { + // 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 }) } @@ -301,14 +368,14 @@ impl Tree { } } - pub fn find_node_rev(&self, key: &K) -> Result> + fn find_node_rev(&self, key: &K) -> Result> where K: PartialEq + PartialOrd, { self.find_node_inner(key, NodeHandle::find_key_rev) } - pub fn find_node(&self, key: &K) -> Result> + fn find_node(&self, key: &K) -> Result> where K: PartialEq + PartialOrd, { @@ -408,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::>(); Ok(Self { header, children }) @@ -619,12 +686,48 @@ 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) -> Result { + Ok(Self::from_bytes(bytes)?.into()) + } + pub fn from_bytes(bytes: Vec) -> Result { 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 { match &self.inner { diff --git a/btrfs/src/v2/volume.rs b/btrfs/src/v2/volume.rs index 04c2d04..063c1eb 100644 --- a/btrfs/src/v2/volume.rs +++ b/btrfs/src/v2/volume.rs @@ -82,8 +82,8 @@ pub struct Volume { // TODO: find better name #[derive(Debug, Clone)] pub struct Volume2 { - inner: Rc>, - roots: BTreeMap)>, + pub inner: Rc>, + pub roots: BTreeMap)>, } // TODO: find better name @@ -222,6 +222,12 @@ impl Volume { pub fn read_range_from_logical(&self, logical: u64) -> Result>> { 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) @@ -353,7 +359,7 @@ impl Volume2 { } impl Fs { - fn get_inode_item(&self, inode_id: u64) -> Result> { + pub fn get_inode_item(&self, inode_id: u64) -> Result> { 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)?; @@ -365,7 +371,11 @@ impl Fs { 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![], @@ -514,7 +524,7 @@ impl Fs { } } - fn get_inode_extents(&self, inode_id: u64) -> Result> { + pub fn get_inode_extents(&self, inode_id: u64) -> Result> { if let Some(dir_entry) = self.get_inode_dir_index(inode_id)? { if dir_entry.item().ty() == DirItemType::RegFile { let key = @@ -540,7 +550,7 @@ impl Fs { } } - fn read_inode_raw>(&self, inode: &INode, range: I) -> Result> { + pub fn read_inode_raw>(&self, inode: &INode, range: I) -> Result> { let mut contents = Vec::new(); let extents = self.get_inode_extents(inode.id)?; @@ -649,6 +659,10 @@ impl Fs { super::tree::entry::Entry::Vacant(_) => Ok(None), } } + + pub fn fs_root(&self) -> &Tree { + &self.fs_root + } } #[cfg(test)] @@ -718,8 +732,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(()) diff --git a/btrfs/tests/read_superblock.rs b/btrfs/tests/read_superblock.rs index 8b13789..5ad5b35 100644 --- a/btrfs/tests/read_superblock.rs +++ b/btrfs/tests/read_superblock.rs @@ -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>> { + 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::>(); + 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(()) +}