find inodes by path, inodeid and by parent

- impl Display for NodeHandle which only shows the node header and the index of
the handle
- Fs<T> has functionality for retrieving the root directory, looking up inodeids
by absolute path, getting the inodeitem, inoderef, dirindex and diritem by
inodeid, listing extents of a inodeid (if regfile)
- simple INode structure holding the inodeid and the path to the inode as a list
of byte sequences
This commit is contained in:
Janis 2023-04-02 00:28:23 +02:00
parent 6148e1b089
commit 9602b399e0
4 changed files with 331 additions and 6 deletions

45
btrfs/src/v2/file.rs Normal file
View file

@ -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<Vec<u8>>,
}
pub struct PathDebug<'a>(&'a Vec<Vec<u8>>);
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<u8>) -> Self {
self.path.push(path);
Self { id, ..self }
}
}

View file

@ -21,6 +21,8 @@ pub mod error {
NoDefaultSubvolRoot, NoDefaultSubvolRoot,
#[error("Default subvolume root fs tree could not be found")] #[error("Default subvolume root fs tree could not be found")]
NoDefaultSubvolFsRoot, NoDefaultSubvolFsRoot,
#[error("INode could not be found in FsTree")]
INodeNotFound,
#[error("Invalid checksum: expected {expected:#?} but got {actual:#?}")] #[error("Invalid checksum: expected {expected:#?} but got {actual:#?}")]
InvalidChecksum { InvalidChecksum {
expected: [u8; 32], expected: [u8; 32],
@ -55,5 +57,6 @@ impl Read for std::fs::File {
} }
} }
pub mod file;
pub mod tree; pub mod tree;
pub mod volume; pub mod volume;

View file

@ -1,3 +1,4 @@
use core::fmt::Display;
use core::mem::size_of; use core::mem::size_of;
use core::ops::Deref; use core::ops::Deref;
@ -65,11 +66,22 @@ pub struct NodeHandle {
idx: u32, 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)] #[derive(Debug)]
pub struct Range<R: super::Read> { pub struct Range<R: super::Read> {
volume: Rc<Volume<R>>, volume: Rc<Volume<R>>,
start: RootOrEdge, pub(crate) start: RootOrEdge,
end: RootOrEdge, pub(crate) end: RootOrEdge,
} }
#[derive(Derivative)] #[derive(Derivative)]
@ -314,10 +326,11 @@ pub enum SearchResult {
} }
#[derive(Debug, Clone, Eq)] #[derive(Debug, Clone, Eq)]
enum RootOrEdge { pub(crate) enum RootOrEdge {
Root(NodeHandle), Root(NodeHandle),
Edge(NodeHandle), Edge(NodeHandle),
} }
impl RootOrEdge { impl RootOrEdge {
pub fn into_handle(&self) -> NodeHandle { pub fn into_handle(&self) -> NodeHandle {
match self { match self {
@ -433,7 +446,16 @@ impl Deref for RootOrEdge {
impl PartialEq for RootOrEdge { impl PartialEq for RootOrEdge {
fn eq(&self, other: &Self) -> bool { 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),
},
}
} }
} }

View file

@ -4,12 +4,16 @@ use alloc::collections::btree_map::Entry;
use alloc::{collections::BTreeMap, rc::Rc, vec, vec::Vec}; use alloc::{collections::BTreeMap, rc::Rc, vec, vec::Vec};
use scroll::Pread; use scroll::Pread;
use crate::crc32c::calculate_crc32c;
use crate::path::Path;
use crate::structs::{ 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 crate::{Error, Result};
use super::tree::Tree; use super::file::INode;
use super::tree::{PartialKey, Tree};
/// equal if overlapping, ordered by lower bound /// equal if overlapping, ordered by lower bound
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -346,6 +350,165 @@ impl<R: super::Read> Volume2<R> {
} }
} }
impl<R: super::Read> Fs<R> {
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)?;
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<Vec<INode>> {
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::<Vec<_>>();
Ok(children)
}
pub fn get_inode_by_path<P>(&self, path: P) -> Result<INode>
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<Option<DirItemEntry>> {
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<Option<DirItemEntry>> {
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<Option<Vec<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 extents = self.fs_root.find_range(&key)?;
let extents = extents
.map(|(_, item)| item.as_extent_data().expect("extent data").clone())
.collect::<Vec<_>>();
return Ok(Some(extents));
}
}
Ok(None)
}
fn find_inode_ref(&self, inode_id: u64) -> Result<Option<(Item, INodeRefEntry)>> {
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<Option<DirItemEntry>> {
//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<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)
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::v2::tree::PartialKey; use crate::v2::tree::PartialKey;
@ -503,6 +666,98 @@ mod tests {
Ok(()) 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] #[test]
fn iter_default_subvol() { fn iter_default_subvol() {
let file = open_btrfs_file(); let file = open_btrfs_file();