diff --git a/btrfs/Cargo.toml b/btrfs/Cargo.toml index e0f7550..09da630 100644 --- a/btrfs/Cargo.toml +++ b/btrfs/Cargo.toml @@ -23,6 +23,14 @@ thiserror = { version = "1.0", package = "thiserror-core", default-features = fa num_enum = {version = "0.5.11", default-features = false} replace_with = "0.1.7" +miniz_oxide = {version = "0.7.1"} + + [dev-dependencies] env_logger = "*" -test-log = "*" \ No newline at end of file +test-log = "*" + +include-blob = "0.1.2" + +[build-dependencies] +include-blob = "0.1.2" \ No newline at end of file diff --git a/btrfs/src/structs.rs b/btrfs/src/structs.rs index 98cb325..dd7ef11 100644 --- a/btrfs/src/structs.rs +++ b/btrfs/src/structs.rs @@ -360,6 +360,17 @@ impl Parseable for ExtentData { } } +#[repr(u8)] +#[derive(Debug, Clone, Copy, FromPrimitive, IntoPrimitive)] +pub enum CompressionType { + None = 0, + Zlib, + Lzo, + ZStd, + #[num_enum(catch_all)] + Invalid(u8), +} + #[repr(C, packed(1))] #[derive(Debug, Clone, Copy, FromBytes, AsBytes)] pub struct ExtentData1 { @@ -372,6 +383,14 @@ pub struct ExtentData1 { } impl ExtentData1 { + pub fn decoded_size(&self) -> u64 { + self.decoded_size.get() + } + + pub fn compression(&self) -> CompressionType { + self.compression.into() + } + pub fn ty(&self) -> ExtentDataType { match self.ty { 0 => ExtentDataType::Inline, diff --git a/btrfs/src/v2/mod.rs b/btrfs/src/v2/mod.rs index 4ba30b5..db09859 100644 --- a/btrfs/src/v2/mod.rs +++ b/btrfs/src/v2/mod.rs @@ -21,6 +21,8 @@ pub mod error { NoDefaultSubvolFsRoot, #[error("INode could not be found in FsTree")] INodeNotFound, + #[error("decompression error")] + DecompressionError, #[error("attempted to access {index}th item out of bounds {range:?}")] OutOfBounds { range: core::ops::Range, diff --git a/btrfs/src/v2/volume.rs b/btrfs/src/v2/volume.rs index 063c1eb..68b0141 100644 --- a/btrfs/src/v2/volume.rs +++ b/btrfs/src/v2/volume.rs @@ -566,6 +566,8 @@ impl Fs { core::ops::Bound::Unbounded => None, }; + log::info!("extents: {}", extents.len()); + log::info!("{:?}", extents); for (offset, extent) in extents.into_iter().filter(|(offset, extent)| { let extent_start = *offset; let extent_end = extent_start + extent.len(); @@ -602,6 +604,18 @@ impl Fs { } ExtentData::Other(extent) => { let address = extent.address() + extent.offset() + start; + log::info!( + "address: {} = {} + {} + {}", + address, + extent.address(), + extent.offset(), + start + ); + let address = self + .volume + .inner + .offset_from_logical(address) + .ok_or(Error::BadLogicalAddress)?; let data = self .volume .inner @@ -612,6 +626,63 @@ impl Fs { }; log::info!("reading {} bytes from file", data.len()); + log::info!("extent type: {:?}", extent.header().ty()); + log::info!("compression: {:?}", extent.header().compression()); + log::info!("raw bytes: {:?}", data); + let data = match extent.header().compression() { + crate::structs::CompressionType::None => data, + crate::structs::CompressionType::Zlib => { + let mut state = miniz_oxide::inflate::stream::InflateState::new( + miniz_oxide::DataFormat::Zlib, + ); + let mut output_data = vec![0u8; extent.header().decoded_size() as usize]; + let mut output = &mut output_data[..]; + let mut data = &data[..]; + loop { + let result = miniz_oxide::inflate::stream::inflate( + &mut state, + &data, + &mut output, + miniz_oxide::MZFlush::None, + ); + + log::info!("inflated: {:?}", result); + + match result.status.map_err(|_| Error::DecompressionError)? { + miniz_oxide::MZStatus::Ok => {} + miniz_oxide::MZStatus::StreamEnd => break, + _ => { + log::error!("need dict ?!"); + return Err(Error::DecompressionError); + } + } + + data = &data[result.bytes_consumed..]; + output = &mut output[result.bytes_written..]; + } + _ = miniz_oxide::inflate::stream::inflate( + &mut state, + &data, + &mut output, + miniz_oxide::MZFlush::Finish, + ) + .status + .map_err(|_| Error::DecompressionError)?; + + output_data.into() + } + crate::structs::CompressionType::Lzo => { + todo!() + } + crate::structs::CompressionType::ZStd => { + todo!() + } + c => { + log::error!("invalid compression type {:?}", c); + data + } + }; + contents.extend_from_slice(&data); } diff --git a/btrfs/tests/read_superblock.rs b/btrfs/tests/read_superblock.rs index 5ad5b35..ced839b 100644 --- a/btrfs/tests/read_superblock.rs +++ b/btrfs/tests/read_superblock.rs @@ -5,7 +5,31 @@ use btrfs::v2::volume::*; use include_blob::include_blob; fn open_filesystem() -> Result>> { - let filesystem_data = include_bytes!("../simple.img").as_slice(); + let filesystem_data = include_blob!("simple.img").as_slice(); + + let volume = Volume::new(filesystem_data)?.into_volume2()?; + + Ok(volume) +} + +fn open_filesystem_lzo() -> Result>> { + let filesystem_data = include_blob!("compressed-lzo.img").as_slice(); + + let volume = Volume::new(filesystem_data)?.into_volume2()?; + + Ok(volume) +} + +fn open_filesystem_zlib() -> Result>> { + let filesystem_data = include_blob!("compressed-zlib.img").as_slice(); + + let volume = Volume::new(filesystem_data)?.into_volume2()?; + + Ok(volume) +} + +fn open_filesystem_zstd() -> Result>> { + let filesystem_data = include_blob!("compressed-zstd.img").as_slice(); let volume = Volume::new(filesystem_data)?.into_volume2()?; @@ -14,9 +38,7 @@ fn open_filesystem() -> Result>> { #[test_log::test] fn asdf() -> Result<()> { - let filesystem_data = include_bytes!("../simple.img").as_slice(); - let volume = Volume::new(filesystem_data)?; - //.into_volume2(); + let a = open_filesystem()?; Ok(()) } @@ -121,9 +143,75 @@ fn find_file() -> Result<()> { 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"); + let file_contents = fs.read_inode_raw(&cmake_list, ..).expect("file contents"); + assert_eq!( + &file_contents[..52], + b" GNU LESSER GENERAL PUBLIC LICENSE" + ); + log::info!("license file:"); + log::info!("{}", String::from_utf8_lossy(&file_contents)); + + Ok(()) +} + +#[test_log::test] +fn find_file_zlib() -> Result<()> { + let v2 = open_filesystem_zlib()?; + 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, ..).expect("file contents"); + //assert_eq!(&file_contents[..11], b"hello world"); + log::info!("license file:"); + log::info!("{}", String::from_utf8_lossy(&file_contents)); + assert_eq!( + &file_contents[..52], + b" GNU LESSER GENERAL PUBLIC LICENSE" + ); + + Ok(()) +} + +#[test_log::test] +fn find_file_lzo() -> Result<()> { + let v2 = open_filesystem_lzo()?; + 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, ..).expect("file contents"); + assert_eq!( + &file_contents[..52], + b" GNU LESSER GENERAL PUBLIC LICENSE" + ); + log::info!("license file:"); + log::info!("{}", String::from_utf8_lossy(&file_contents)); + + Ok(()) +} + +#[test_log::test] +fn find_file_zstd() -> Result<()> { + let v2 = open_filesystem_zstd()?; + 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, ..).expect("file contents"); + assert_eq!( + &file_contents[..52], + b" GNU LESSER GENERAL PUBLIC LICENSE" + ); log::info!("license file:"); log::info!("{}", String::from_utf8_lossy(&file_contents));