From e0b1ddf3f633c15e3a6df31cdedcb8c30761cd47 Mon Sep 17 00:00:00 2001 From: Janis Date: Tue, 4 Apr 2023 22:25:14 +0200 Subject: [PATCH] idk --- btrfs/src/structs.rs | 78 +++++++++++- btrfs/src/v2/mod.rs | 278 +++++++++++++++++++++++++++++++++++++++++ btrfs/src/v2/volume.rs | 180 +++++++++++++++++--------- 3 files changed, 475 insertions(+), 61 deletions(-) diff --git a/btrfs/src/structs.rs b/btrfs/src/structs.rs index 063063c..98cb325 100644 --- a/btrfs/src/structs.rs +++ b/btrfs/src/structs.rs @@ -328,6 +328,22 @@ pub enum ExtentData { Other(ExtentData2), } +impl ExtentData { + pub fn header(&self) -> &ExtentData1 { + match self { + ExtentData::Inline { header, .. } => header, + ExtentData::Other(extent) => extent.extent_data1(), + } + } + + pub fn len(&self) -> u64 { + match self { + ExtentData::Inline { data, .. } => data.len() as u64, + ExtentData::Other(extent) => extent.num_bytes(), + } + } +} + impl Parseable for ExtentData { fn parse(bytes: &[u8]) -> Result { let header = ExtentData1::parse(bytes)?; @@ -966,6 +982,10 @@ where &self.name } + pub fn into_name(self) -> Vec { + self.name + } + pub fn name_as_str(&self) -> core::result::Result<&str, core::str::Utf8Error> { core::str::from_utf8(&self.name) } @@ -1107,9 +1127,7 @@ impl TreeItem { None } } -} -impl TreeItem { pub fn as_chunk(&self) -> Option<&Chunk> { if let Self::Chunk(v) = self { Some(v) @@ -1157,6 +1175,62 @@ impl TreeItem { None } } + + pub fn try_into_chunk(self) -> core::result::Result { + if let Self::Chunk(v) = self { + Ok(v) + } else { + Err(self) + } + } + + pub fn try_into_root(self) -> core::result::Result { + if let Self::Root(v) = self { + Ok(v) + } else { + Err(self) + } + } + + pub fn try_into_extent_data(self) -> core::result::Result { + if let Self::ExtentData(v) = self { + Ok(v) + } else { + Err(self) + } + } + + pub fn try_into_dir_item(self) -> core::result::Result, Self> { + if let Self::DirItem(v) = self { + Ok(v) + } else { + Err(self) + } + } + + pub fn try_into_dir_index(self) -> core::result::Result { + if let Self::DirIndex(v) = self { + Ok(v) + } else { + Err(self) + } + } + + pub fn try_into_inode_item(self) -> core::result::Result { + if let Self::INodeItem(v) = self { + Ok(v) + } else { + Err(self) + } + } + + pub fn try_into_inode_ref(self) -> core::result::Result { + if let Self::INodeRef(v) = self { + Ok(v) + } else { + Err(self) + } + } } #[derive(Debug, Clone)] diff --git a/btrfs/src/v2/mod.rs b/btrfs/src/v2/mod.rs index 55a56b5..b3a40ad 100644 --- a/btrfs/src/v2/mod.rs +++ b/btrfs/src/v2/mod.rs @@ -1,3 +1,9 @@ +use core::{ + cmp::Ordering, + fmt::Debug, + ops::{Bound, RangeBounds}, +}; + use crate::Error; pub mod error { @@ -65,3 +71,275 @@ impl Read for std::fs::File { pub mod file; pub mod tree; pub mod volume; + +pub fn cmp_start_bound( + rhs: &core::ops::Bound, + lhs: &core::ops::Bound, +) -> Option { + match rhs { + core::ops::Bound::Included(r) => match lhs { + core::ops::Bound::Included(l) => r.partial_cmp(&l), + core::ops::Bound::Excluded(l) => match r.partial_cmp(&l) { + Some(core::cmp::Ordering::Equal) => Some(core::cmp::Ordering::Less), + i => i, + }, + core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Greater), + }, + core::ops::Bound::Excluded(r) => match lhs { + core::ops::Bound::Excluded(l) => r.partial_cmp(&l), + core::ops::Bound::Included(l) => match r.partial_cmp(&l) { + Some(core::cmp::Ordering::Equal) => Some(core::cmp::Ordering::Greater), + i => i, + }, + core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Greater), + }, + core::ops::Bound::Unbounded => match lhs { + core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Equal), + _ => Some(core::cmp::Ordering::Less), + }, + } +} + +pub fn cmp_end_bound( + rhs: &core::ops::Bound, + lhs: &core::ops::Bound, +) -> Option { + match rhs { + core::ops::Bound::Included(r) => match lhs { + core::ops::Bound::Included(l) => r.partial_cmp(&l), + core::ops::Bound::Excluded(l) => match r.partial_cmp(&l) { + Some(core::cmp::Ordering::Equal) => Some(core::cmp::Ordering::Greater), + i => i, + }, + core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Less), + }, + core::ops::Bound::Excluded(r) => match lhs { + core::ops::Bound::Excluded(l) => r.partial_cmp(&l), + core::ops::Bound::Included(l) => match r.partial_cmp(&l) { + Some(core::cmp::Ordering::Equal) => Some(core::cmp::Ordering::Greater), + i => i, + }, + core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Less), + }, + core::ops::Bound::Unbounded => match lhs { + core::ops::Bound::Unbounded => Some(core::cmp::Ordering::Equal), + _ => Some(core::cmp::Ordering::Greater), + }, + } +} + +#[derive(Debug, PartialEq, Eq)] +struct StartBound(Bound); +impl PartialOrd for StartBound { + fn partial_cmp(&self, other: &Self) -> Option { + cmp_start_bound(&self.0, &other.0) + } +} +impl Ord for StartBound { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + +#[derive(Debug, PartialEq, Eq)] +struct EndBound(Bound); + +impl PartialOrd for EndBound { + fn partial_cmp(&self, other: &Self) -> Option { + cmp_end_bound(&self.0, &other.0) + } +} +impl Ord for EndBound { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + // safety: partial_cmp only returns None when returning T::partial_cmp + self.partial_cmp(other).unwrap() + } +} + +pub fn range_countains_bound>( + range: R, + bound: Bound<&T>, +) -> bool { + let start = &StartBound(bound); + let end = &EndBound(bound); + let r_start = &StartBound(range.start_bound()); + let r_end = &EndBound(range.end_bound()); + log::info!( + "start: {start:?} <=> {r_start:?}: {:?} {:?}", + start.cmp(r_start), + r_start.cmp(start) + ); + log::info!( + "end: {end:?} <=> {r_end:?}: {:?} {:?}", + end.cmp(r_end), + r_end.cmp(end) + ); + (start.cmp(r_start).is_ge() || r_start.cmp(start).is_le()) + && (end.cmp(r_end).is_ge() || r_end.cmp(end).is_le()) +} + +#[cfg(test)] +mod bound_ord_tests { + use core::{cmp::Ordering, ops::RangeBounds}; + + use super::*; + use test_log::test; + + #[test] + fn start_bound_ord() { + assert_eq!( + cmp_start_bound(&Bound::Unbounded, &Bound::Included(0)), + Some(Ordering::Less) + ); + assert_eq!( + cmp_start_bound::(&Bound::Unbounded, &Bound::Unbounded), + Some(Ordering::Equal) + ); + assert_eq!( + cmp_start_bound(&Bound::Included(0), &Bound::Included(0)), + Some(Ordering::Equal) + ); + assert_eq!( + cmp_start_bound(&Bound::Excluded(0), &Bound::Included(0)), + Some(Ordering::Greater) + ); + assert_ne!( + cmp_start_bound(&Bound::Excluded(0), &Bound::Included(1)), + Some(Ordering::Greater) + ); + // This is actually WRONG and is why we have to test both ways because I + // can't think of a way to actually determine how the 2 bounds are + // ordered without knowing the smallest discrete step size of T. + // + // In this case technically they should be equal, but for floats (which + // arent ord anyways?) this would be wrong + assert_eq!( + cmp_start_bound(&Bound::Included(1), &Bound::Excluded(0)), + Some(Ordering::Greater) + ); + + assert_eq!( + cmp_start_bound(&Bound::Included(0), &Bound::Excluded(1)), + Some(Ordering::Less) + ); + } + + #[test] + fn end_bound_ord() { + assert_eq!( + cmp_end_bound::(&Bound::Unbounded, &Bound::Unbounded), + Some(Ordering::Equal) + ); + assert_eq!( + cmp_end_bound(&Bound::Unbounded, &Bound::Included(0)), + Some(Ordering::Greater) + ); + assert_eq!( + cmp_end_bound(&Bound::Included(0), &Bound::Included(0)), + Some(Ordering::Equal) + ); + assert_eq!( + cmp_end_bound(&Bound::Excluded(0), &Bound::Included(0)), + Some(Ordering::Greater) + ); + assert_ne!( + cmp_end_bound(&Bound::Excluded(0), &Bound::Included(1)), + Some(Ordering::Greater) + ); + // This is actually WRONG and is why we have to test both ways because I + // can't think of a way to actually determine how the 2 bounds are + // ordered without knowing the smallest discrete step size of T. + // + // In this case technically they should be equal, but for floats (which + // arent ord anyways?) this would be wrong + assert_eq!( + cmp_end_bound(&Bound::Included(1), &Bound::Excluded(0)), + Some(Ordering::Greater) + ); + + assert_eq!( + cmp_end_bound(&Bound::Included(0), &Bound::Excluded(1)), + Some(Ordering::Less) + ); + } + + #[test] + fn test_bound_ord() { + let r1 = 0..4; + let r2 = 2..3; + + assert_eq!( + cmp_start_bound(&r1.start_bound(), &r2.start_bound()), + Some(core::cmp::Ordering::Less) + ); + assert_eq!( + cmp_end_bound(&r1.end_bound(), &r2.end_bound()), + Some(core::cmp::Ordering::Greater) + ); + + assert_eq!( + cmp_start_bound(&r2.start_bound(), &r1.start_bound()), + Some(core::cmp::Ordering::Greater) + ); + assert_eq!( + cmp_end_bound(&r2.end_bound(), &r1.end_bound()), + Some(core::cmp::Ordering::Less) + ); + + let r1 = 0..=8; + let r2 = 0..9; + + assert_eq!( + cmp_start_bound(&r1.start_bound(), &r2.start_bound()), + Some(core::cmp::Ordering::Equal) + ); + assert_eq!( + cmp_end_bound(&r1.end_bound(), &r2.end_bound()), + Some(core::cmp::Ordering::Less) + ); + assert_eq!( + cmp_end_bound(&r2.end_bound(), &r1.end_bound()), + Some(core::cmp::Ordering::Greater) + ); + } +} + +pub fn ranges_intersect(rhs: R1, lhs: R2) -> () +where + T: Ord, + R1: core::ops::RangeBounds, + R2: core::ops::RangeBounds, +{ + let a = rhs.start_bound(); + let b = rhs.end_bound(); + let x = lhs.start_bound(); + let y = lhs.end_bound(); + + // check that a <=> x is different than b <=> x + + // a <=> x + match a { + Bound::Included(a) => match x { + Bound::Included(x) => a.partial_cmp(x), + Bound::Excluded(x) => match a.partial_cmp(x) { + Some(Ordering::Equal) => Some(Ordering::Less), + ord => ord, + }, + Bound::Unbounded => Some(Ordering::Greater), + }, + Bound::Excluded(a) => match x { + Bound::Included(x) => match a.partial_cmp(x) { + Some(Ordering::Equal) => Some(Ordering::Greater), + ord => ord, + }, + Bound::Excluded(x) => a.partial_cmp(x), + Bound::Unbounded => Some(Ordering::Less), + }, + Bound::Unbounded => match x { + Bound::Unbounded => Some(Ordering::Equal), + _ => Some(Ordering::Less), + }, + }; + + todo!() +} diff --git a/btrfs/src/v2/volume.rs b/btrfs/src/v2/volume.rs index 95f8334..182a67d 100644 --- a/btrfs/src/v2/volume.rs +++ b/btrfs/src/v2/volume.rs @@ -1,4 +1,5 @@ use core::mem::size_of; +use core::ops::{Range, RangeBounds}; use alloc::collections::btree_map::Entry; use alloc::{collections::BTreeMap, rc::Rc, vec, vec::Vec}; @@ -370,21 +371,30 @@ impl Fs { } } - pub fn get_inode_children(&self, inode: &INode) -> Result> { + pub fn get_inode_children_inodes<'a>( + &self, + inode: &'a INode, + ) -> Result + 'a> + where + R: 'a, + { + self.get_inode_children(inode).map(|children| { + children.map(|child| { + let id: u64 = child.item().location.id().into(); + + inode.clone().into_child(id, child.into_name()) + }) + }) + } + + 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(); + let a = children.map(|(_, v)| v.try_into_dir_index().expect("dir index")); - inode.clone().into_child(id, dir_item.name().clone()) - }) - .collect::>(); - - Ok(children) + Ok(a) } pub fn get_inode_parent(&self, inode: &INode) -> Result { @@ -420,14 +430,15 @@ impl Fs { inode = self.get_inode_parent(&inode)?; } crate::path::Segment::File(child_name) => { - inode = self - .get_inode_children(&inode)? - .iter() + let child = self + .get_inode_children_inodes(&inode)? .find(|child| { child.path.last().map(|bytes| bytes.as_slice()) == Some(child_name) }) .ok_or(Error::INodeNotFound)? - .clone() + .clone(); + // silly borrow checker + inode = child; } _ => unreachable!(), } @@ -496,7 +507,7 @@ impl Fs { } } - fn get_inode_extents(&self, inode_id: u64) -> Result>> { + 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 = @@ -513,11 +524,81 @@ impl Fs { }) .collect::>(); - return Ok(Some(extents)); + Ok(extents) + } else { + Ok(vec![]) } + } else { + Err(Error::INodeNotFound) + } + } + + fn read_inode_raw>(&self, inode: &INode, range: I) -> Result> { + let mut contents = Vec::new(); + let extents = self.get_inode_extents(inode.id)?; + + let start = match range.start_bound() { + core::ops::Bound::Included(v) => *v, + core::ops::Bound::Excluded(v) => *v + 1, + core::ops::Bound::Unbounded => 0, + }; + + let end = match range.end_bound() { + core::ops::Bound::Included(v) => Some(*v + 1), + core::ops::Bound::Excluded(v) => Some(*v), + core::ops::Bound::Unbounded => None, + }; + + for (offset, extent) in extents.into_iter().filter(|(offset, extent)| { + let extent_start = *offset; + let extent_end = extent_start + extent.len(); + + let range_len = end.map(|end| end - start); + + let start2 = start.min(extent_start); + let end = end.map(|end| end.max(extent_end)); + let len = end.map(|end| (end - start2)); + + if let (Some(len), Some(range_len)) = (len, range_len) { + range_len + range_len < len + } else { + start2 < extent_end + } + }) { + // + let start = start.saturating_sub(offset); + + let end = end.map(|end| end - offset); + + log::info!("reading {}..{:?} from extent.", start, end); + + let data: alloc::borrow::Cow<[u8]> = match &extent { + ExtentData::Inline { data, .. } => { + // TODO: handle compression and encryption + let data = if let Some(end) = end { + &data[start as usize..end as usize] + } else { + &data[start as usize..] + }; + + data.into() + } + ExtentData::Other(extent) => { + let address = extent.address() + extent.offset() + start; + let data = self + .volume + .inner + .read_range(address..address + end.unwrap_or(extent.num_bytes())) + .expect("bytes"); + data.into() + } + }; + + log::info!("reading {} bytes from file", data.len()); + contents.extend_from_slice(&data); } - Ok(None) + Ok(contents) } fn find_inode_ref(&self, inode_id: u64) -> Result> { @@ -674,14 +755,12 @@ mod tests { 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)); - } - _ => {} + for (_, extent) in extents { + match extent { + ExtentData::Inline { header, data } => { + log::info!("{header:?}\n{}", String::from_utf8_lossy(&data)); } + _ => {} } } } @@ -698,11 +777,11 @@ mod tests { let fs = v2.default_subvolume().expect("default subvol"); let root_dir = fs.get_root_dir(); - let children = fs.get_inode_children(&root_dir)?; + let children = fs.get_inode_children(&root_dir)?.collect::>(); log::info!("chidlren: {:?}", children); let home = fs.get_inode_by_path(b"/home/user")?; - let children = fs.get_inode_children(&home)?; + let children = fs.get_inode_children(&home)?.collect::>(); log::info!("chidlren: {:?}", children); let hii = fs.get_inode_by_path(b"/home/user/hii.txt")?; @@ -711,48 +790,31 @@ mod tests { let extents = fs.get_inode_extents(hii.id)?; assert_eq!(hii, hii2); - if let Some(extents) = extents { - for (_offset, extent) in extents { - match extent { - ExtentData::Inline { header, data } => { - log::info!("{header:?}\n{}", String::from_utf8_lossy(&data)); - } - _ => {} + for (_offset, 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)?; + let children = fs.get_inode_children_inodes(&btrfs)?.collect::>(); 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 (offset, extent) in extents { - log::info!("extent {:x}..", offset); - match extent { - ExtentData::Inline { header, data } => { - log::info!("{header:?}\n{}", String::from_utf8_lossy(&data)); - } - ExtentData::Other(extent) => { - let address = extent.address() + extent.offset(); - let data = fs - .volume - .inner - .read_range(address..address + extent.num_bytes()) - .expect("bytes"); - log::info!( - "{:?}\n{}", - extent.extent_data1(), - String::from_utf8_lossy(&data) - ); - } - } - } + for child in children { + let file_contents = fs.read_inode_raw(&child, ..).expect("file contents"); + log::info!("{}", String::from_utf8_lossy(&file_contents)); } + let cmake_list = fs.get_inode_by_path(b"/home/user/btrfs/CMakeLists.txt")?; + let file_contents = fs + .read_inode_raw(&cmake_list, ..100) + .expect("file contents"); + log::info!("cmakelists file:"); + log::info!("{}", String::from_utf8_lossy(&file_contents)); + Ok(()) }