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:
parent
6148e1b089
commit
9602b399e0
45
btrfs/src/v2/file.rs
Normal file
45
btrfs/src/v2/file.rs
Normal 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 }
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<R: super::Read> {
|
||||
volume: Rc<Volume<R>>,
|
||||
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),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<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)]
|
||||
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();
|
||||
|
|
Loading…
Reference in a new issue