diff --git a/btrfs/src/v2/file.rs b/btrfs/src/v2/file.rs new file mode 100644 index 0000000..606a04a --- /dev/null +++ b/btrfs/src/v2/file.rs @@ -0,0 +1,45 @@ +use core::fmt::Debug; + +use alloc::string::String; +use alloc::vec::Vec; + +use crate::structs::KnownObjectId; + +#[derive(Clone)] +pub struct INode { + pub id: u64, + pub(crate) path: Vec>, +} + +pub struct PathDebug<'a>(&'a Vec>); + +impl<'a> Debug for PathDebug<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "\"")?; + for segment in self.0 { + write!(f, "/{}", String::from_utf8_lossy(&segment))?; + } + write!(f, "\"")?; + Ok(()) + } +} + +impl core::fmt::Debug for INode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("INode") + .field("id", &self.id) + .field("path", &PathDebug(&self.path)) + .finish() + } +} + +impl INode { + pub fn id(&self) -> KnownObjectId { + self.id.into() + } + + pub fn into_child(mut self, id: u64, path: Vec) -> Self { + self.path.push(path); + Self { id, ..self } + } +} diff --git a/btrfs/src/v2/mod.rs b/btrfs/src/v2/mod.rs index ba6c7ad..5623d8f 100644 --- a/btrfs/src/v2/mod.rs +++ b/btrfs/src/v2/mod.rs @@ -21,6 +21,8 @@ pub mod error { NoDefaultSubvolRoot, #[error("Default subvolume root fs tree could not be found")] NoDefaultSubvolFsRoot, + #[error("INode could not be found in FsTree")] + INodeNotFound, #[error("Invalid checksum: expected {expected:#?} but got {actual:#?}")] InvalidChecksum { expected: [u8; 32], @@ -55,5 +57,6 @@ impl Read for std::fs::File { } } +pub mod file; pub mod tree; pub mod volume; diff --git a/btrfs/src/v2/tree.rs b/btrfs/src/v2/tree.rs index 685a76e..f2f25bc 100644 --- a/btrfs/src/v2/tree.rs +++ b/btrfs/src/v2/tree.rs @@ -1,3 +1,4 @@ +use core::fmt::Display; use core::mem::size_of; use core::ops::Deref; @@ -65,11 +66,22 @@ pub struct NodeHandle { idx: u32, } +impl Display for NodeHandle { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "NodeHandle {{\n\tindex: {},\n\tnode: {:#?},\n\t ..\n}}\n", + self.idx, + self.node.inner.header() + ) + } +} + #[derive(Debug)] pub struct Range { volume: Rc>, - start: RootOrEdge, - end: RootOrEdge, + pub(crate) start: RootOrEdge, + pub(crate) end: RootOrEdge, } #[derive(Derivative)] @@ -314,10 +326,11 @@ pub enum SearchResult { } #[derive(Debug, Clone, Eq)] -enum RootOrEdge { +pub(crate) enum RootOrEdge { Root(NodeHandle), Edge(NodeHandle), } + impl RootOrEdge { pub fn into_handle(&self) -> NodeHandle { match self { @@ -433,7 +446,16 @@ impl Deref for RootOrEdge { impl PartialEq for RootOrEdge { fn eq(&self, other: &Self) -> bool { - Deref::deref(self).eq(Deref::deref(other)) + match self { + RootOrEdge::Root(root) => match other { + RootOrEdge::Root(_) => false, + RootOrEdge::Edge(other) => root.eq(other), + }, + RootOrEdge::Edge(edge) => match other { + RootOrEdge::Edge(_) => false, + RootOrEdge::Root(other) => edge.eq(other), + }, + } } } diff --git a/btrfs/src/v2/volume.rs b/btrfs/src/v2/volume.rs index 5deed2f..d69fdba 100644 --- a/btrfs/src/v2/volume.rs +++ b/btrfs/src/v2/volume.rs @@ -4,12 +4,16 @@ 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::structs::{ - Chunk, Key, KeyPtr, KnownObjectId, ObjectType, RootItem, Stripe, Superblock, TreeItem, + Chunk, DirItemEntry, DirItemType, ExtentData, INodeItem, INodeRefEntry, Item, Key, KeyPtr, + KnownObjectId, ObjectType, RootItem, Stripe, Superblock, TreeItem, }; use crate::{Error, Result}; -use super::tree::Tree; +use super::file::INode; +use super::tree::{PartialKey, Tree}; /// equal if overlapping, ordered by lower bound #[derive(Debug, Clone)] @@ -346,6 +350,165 @@ impl Volume2 { } } +impl Fs { + 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)?; + + return Ok(inode); + } + } + + Ok(None) + } + + fn get_root_dir(&self) -> INode { + INode { + id: self.root_item.root_dirid.get(), + path: vec![], + } + } + + pub fn get_inode_children(&self, inode: &INode) -> Result> { + let key = PartialKey::new(Some(inode.id()), Some(ObjectType::DirIndex), None); + + let children = self.fs_root.find_range(&key)?; + + let children = children + .map(|(_, v)| { + let dir_item = v.as_dir_index().expect("dir index"); + let id: u64 = dir_item.item().location.id().into(); + + inode.clone().into_child(id, dir_item.name().clone()) + }) + .collect::>(); + + Ok(children) + } + + pub fn get_inode_by_path

(&self, path: P) -> Result + where + P: Path, + { + let mut normalized = path.normalize(); + if !path.is_absolute() { + log::error!("path is not absolute!"); + } else { + // pop root + _ = normalized.pop_segment(); + } + + let mut inode = self.get_root_dir(); + + while let Some(segment) = normalized.pop_segment() { + 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()); + } + } + } + + Ok(inode) + } + + fn find_inode_child(&self, parent_inode: u64, child: &[u8]) -> Result> { + let crc = calculate_crc32c(0xfffffffe, child); + let key = PartialKey::new( + Some(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) + } + } + + fn get_inode_dir_index(&self, inode_id: u64) -> Result> { + if let Some((item, inoderef)) = self.find_inode_ref(inode_id)? { + self.find_dir_index(item.key.offset.get(), &inoderef) + } else { + Ok(None) + } + } + + 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 = + PartialKey::new(Some(inode_id.into()), Some(ObjectType::ExtentData), None); + + let extents = self.fs_root.find_range(&key)?; + + let extents = extents + .map(|(_, item)| item.as_extent_data().expect("extent data").clone()) + .collect::>(); + + return Ok(Some(extents)); + } + } + + Ok(None) + } + + fn find_inode_ref(&self, inode_id: u64) -> Result> { + let key = PartialKey::new(Some(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) + } + } + + fn find_dir_index( + &self, + parent_inode: u64, + inoderef: &INodeRefEntry, + ) -> Result> { + //let crc = calculate_crc32c(0xfffffffe, &inoderef.name()); + let key = PartialKey::new( + Some(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) + } + } + + fn find_inode_item(&self, dir_item: &DirItemEntry) -> Result> { + 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) + } + } +} + #[cfg(test)] mod tests { use crate::v2::tree::PartialKey; @@ -503,6 +666,98 @@ mod tests { Ok(()) } + #[test] + fn get_inode_items() -> Result<()> { + let file = open_btrfs_file(); + let vol = Volume::new(file).expect("volume"); + let v2 = vol.into_volume2().expect("volume2"); + let fs = v2.default_subvolume().expect("default subvol"); + + let search_key = PartialKey::new( + Some(fs.root_item.root_dirid.get().into()), + 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)?; + + if let Some(extents) = extents { + 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] + fn find_file() -> Result<()> { + let file = open_btrfs_file(); + let vol = Volume::new(file).expect("volume"); + let v2 = vol.into_volume2().expect("volume2"); + let fs = v2.default_subvolume().expect("default subvol"); + + let root_dir = fs.get_root_dir(); + let children = fs.get_inode_children(&root_dir)?; + log::info!("chidlren: {:?}", children); + + let home = fs.get_inode_by_path(b"/home/user")?; + let children = fs.get_inode_children(&home)?; + log::info!("chidlren: {:?}", children); + + let home = fs.get_inode_by_path(b"/home/user/hii.txt")?; + let extents = fs.get_inode_extents(home.id)?; + + if let Some(extents) = extents { + for extent in extents { + match extent { + ExtentData::Inline { header, data } => { + log::info!("{header:?}\n{}", String::from_utf8_lossy(&data)); + } + _ => {} + } + } + } + + let btrfs = fs.get_inode_by_path(b"/home/user/btrfs")?; + let children = fs.get_inode_children(&btrfs)?; + log::info!("chidlren: {:?}", children); + + let home = fs.get_inode_by_path(b"/home/user/btrfs/CMakeLists.txt")?; + let extents = fs.get_inode_extents(home.id)?; + + if let Some(extents) = extents { + for extent in extents { + match extent { + ExtentData::Inline { header, data } => { + log::info!("{header:?}\n{}", String::from_utf8_lossy(&data)); + } + _ => {} + } + } + } + + Ok(()) + } + #[test] fn iter_default_subvol() { let file = open_btrfs_file();