Skip to content

Commit

Permalink
Add non-standard CompressionOptions support
Browse files Browse the repository at this point in the history
* You ever wonder "what if a squashfs had custom compression options
  that I am forced to emit because the kernel is expecting this?". If this
  is you, then have fun with this addition!
  This allows one to implement compression_options for CompressionAction
  and emit.. whatever you want as the the compression option bytes.
* Change superblock struct in FilesystemWriter As Soon As Possible to make
  sure everything can just use those settings.
* Update tests to be un-padded for custom compression, easier to see
  the diff's with biodiff and such.
  • Loading branch information
wcampbell0x2a committed Aug 7, 2024
1 parent e32b488 commit 11af4c5
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 42 deletions.
35 changes: 32 additions & 3 deletions backhand-test/tests/non_standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ fn full_test(
test_path: &str,
offset: u64,
kind: &Kind,
pad: Option<u32>,
) {
test_assets::download_test_files(assets_defs, test_path, true).unwrap();
let og_path = format!("{test_path}/{filepath}");
Expand All @@ -36,6 +37,9 @@ fn full_test(
)
.unwrap();
let mut new_filesystem = FilesystemWriter::from_fs_reader(&og_filesystem).unwrap();
if let Some(pad) = pad {
new_filesystem.set_kib_padding(pad);
}

// Test Debug is impl'ed properly on FilesystemWriter
let _ = format!("{new_filesystem:#02x?}");
Expand Down Expand Up @@ -72,7 +76,14 @@ fn test_non_standard_be_v4_0() {
.to_string(),
}];
const TEST_PATH: &str = "test-assets/non_standard_be_v4_0";
full_test(&asset_defs, FILE_NAME, TEST_PATH, 0, &Kind::from_const(kind::BE_V4_0).unwrap());
full_test(
&asset_defs,
FILE_NAME,
TEST_PATH,
0,
&Kind::from_const(kind::BE_V4_0).unwrap(),
None,
);

// test custom kind "builder-lite"
let _kind = Kind::new(&DefaultCompressor)
Expand All @@ -91,12 +102,21 @@ fn test_non_standard_be_v4_1() {
.to_string(),
}];
const TEST_PATH: &str = "test-assets/non_standard_be_v4_1";
full_test(&asset_defs, FILE_NAME, TEST_PATH, 0, &Kind::from_const(kind::BE_V4_0).unwrap());
full_test(
&asset_defs,
FILE_NAME,
TEST_PATH,
0,
&Kind::from_const(kind::BE_V4_0).unwrap(),
None,
);
}

#[test]
#[cfg(feature = "gzip")]
fn test_custom_compressor() {
use backhand::SuperBlock;

const FILE_NAME: &str = "squashfs_v4.nopad.unblob.bin";
let asset_defs = [TestAssetDef {
filename: FILE_NAME.to_string(),
Expand Down Expand Up @@ -138,10 +158,19 @@ fn test_custom_compressor() {
) -> Result<Vec<u8>, BackhandError> {
DefaultCompressor.compress(bytes, fc, block_size)
}

fn compression_options(
&self,
_superblock: &mut SuperBlock,
_kind: &Kind,
_fs_compressor: FilesystemCompressor,
) -> Result<Vec<u8>, BackhandError> {
DefaultCompressor.compression_options(_superblock, _kind, _fs_compressor)
}
}

let kind = Kind::new_with_const(&CustomCompressor, kind::BE_V4_0);

const TEST_PATH: &str = "test-assets/custom_compressor";
full_test(&asset_defs, FILE_NAME, TEST_PATH, 0, &kind);
full_test(&asset_defs, FILE_NAME, TEST_PATH, 0, &kind, Some(0));
}
68 changes: 67 additions & 1 deletion backhand/src/compressor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Types of supported compression algorithms
use std::io::{Cursor, Read};
use std::io::{Cursor, Read, Write};

use deku::prelude::*;
#[cfg(feature = "gzip")]
Expand All @@ -14,6 +14,10 @@ use xz2::stream::{Check, Filters, LzmaOptions, MtStreamBuilder};

use crate::error::BackhandError;
use crate::filesystem::writer::{CompressionExtra, FilesystemCompressor};
use crate::kind::Kind;
use crate::metadata::MetadataWriter;
use crate::squashfs::Flags;
use crate::SuperBlock;

#[derive(Copy, Clone, Debug, PartialEq, Eq, DekuRead, DekuWrite, Default)]
#[deku(endian = "endian", ctx = "endian: deku::ctx::Endian")]
Expand Down Expand Up @@ -175,6 +179,25 @@ pub trait CompressionAction {
fc: FilesystemCompressor,
block_size: u32,
) -> Result<Vec<u8>, BackhandError>;

/// Compression Options for non-default compression specific options
///
/// This function is called when calling [FilesystemWriter::write](crate::FilesystemWriter::write), and the returned bytes are the
/// section right after the SuperBlock.
///
/// # Arguments
/// * `superblock` - Mutatable squashfs superblock info that will be written to disk after
/// this function is called. The fields `inode_count`, `block_size`,
/// `block_log` and `mod_time` *will* be set to `FilesystemWriter` options and can be trusted
/// in this function.
/// * `kind` - Kind information
/// * `fs_compressor` - Compression Options
fn compression_options(
&self,
superblock: &mut SuperBlock,
kind: &Kind,
fs_compressor: FilesystemCompressor,
) -> Result<Vec<u8>, BackhandError>;
}

/// Default compressor that handles the compression features that are enabled
Expand Down Expand Up @@ -228,6 +251,7 @@ impl CompressionAction for DefaultCompressor {
Ok(())
}

/// Using the current compressor from the superblock, compress bytes
fn compress(
&self,
bytes: &[u8],
Expand Down Expand Up @@ -336,4 +360,46 @@ impl CompressionAction for DefaultCompressor {
_ => Err(BackhandError::UnsupportedCompression(fc.id)),
}
}

/// Using the current compressor options, create compression options
fn compression_options(
&self,
superblock: &mut SuperBlock,
kind: &Kind,
fs_compressor: FilesystemCompressor,
) -> Result<Vec<u8>, BackhandError> {
let mut w = Cursor::new(vec![]);

// Write compression options, if any
if let Some(options) = &fs_compressor.options {
superblock.flags |= Flags::CompressorOptionsArePresent as u16;
let mut compression_opt_buf_out = vec![];
let mut writer = Writer::new(&mut compression_opt_buf_out);
match options {
CompressionOptions::Gzip(gzip) => {
gzip.to_writer(&mut writer, kind.inner.type_endian)?
}
CompressionOptions::Lz4(lz4) => {
lz4.to_writer(&mut writer, kind.inner.type_endian)?
}
CompressionOptions::Zstd(zstd) => {
zstd.to_writer(&mut writer, kind.inner.type_endian)?
}
CompressionOptions::Xz(xz) => xz.to_writer(&mut writer, kind.inner.type_endian)?,
CompressionOptions::Lzo(lzo) => {
lzo.to_writer(&mut writer, kind.inner.type_endian)?
}
CompressionOptions::Lzma => {}
}
let mut metadata = MetadataWriter::new(
fs_compressor,
superblock.block_size,
Kind { inner: kind.inner.clone() },
);
metadata.write_all(&compression_opt_buf_out)?;
metadata.finalize(&mut w)?;
}

Ok(w.into_inner())
}
}
51 changes: 13 additions & 38 deletions backhand/src/filesystem/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::kind::Kind;
use crate::kinds::LE_V4_0;
use crate::metadata::{self, MetadataWriter, METADATA_MAXSIZE};
use crate::reader::WriteSeek;
use crate::squashfs::{Flags, SuperBlock};
use crate::squashfs::SuperBlock;
use crate::{
fragment, FilesystemReader, Node, NodeHeader, SquashfsBlockDevice, SquashfsCharacterDevice,
SquashfsDir, SquashfsFileWriter, DEFAULT_BLOCK_SIZE, DEFAULT_PAD_LEN, MAX_BLOCK_SIZE,
Expand Down Expand Up @@ -637,43 +637,16 @@ impl<'a, 'b, 'c> FilesystemWriter<'a, 'b, 'c> {
let mut superblock =
SuperBlock::new(self.fs_compressor.id, Kind { inner: self.kind.inner.clone() });

superblock.inode_count = self.root.nodes.len().try_into().unwrap();
superblock.block_size = self.block_size;
superblock.block_log = self.block_log;
superblock.mod_time = self.mod_time;

trace!("{:#02x?}", self.root);

// Empty Squashfs Superblock
w.write_all(&[0x00; 96])?;

// Write compression options, if any
if let Some(options) = &self.fs_compressor.options {
superblock.flags |= Flags::CompressorOptionsArePresent as u16;
let mut compression_opt_buf_out = vec![];
let mut writer = Writer::new(&mut compression_opt_buf_out);
match options {
CompressionOptions::Gzip(gzip) => {
gzip.to_writer(&mut writer, self.kind.inner.type_endian)?
}
CompressionOptions::Lz4(lz4) => {
lz4.to_writer(&mut writer, self.kind.inner.type_endian)?
}
CompressionOptions::Zstd(zstd) => {
zstd.to_writer(&mut writer, self.kind.inner.type_endian)?
}
CompressionOptions::Xz(xz) => {
xz.to_writer(&mut writer, self.kind.inner.type_endian)?
}
CompressionOptions::Lzo(lzo) => {
lzo.to_writer(&mut writer, self.kind.inner.type_endian)?
}
CompressionOptions::Lzma => {}
}
let mut metadata = MetadataWriter::new(
self.fs_compressor,
self.block_size,
Kind { inner: self.kind.inner.clone() },
);
metadata.write_all(&compression_opt_buf_out)?;
metadata.finalize(&mut w)?;
}

let mut data_writer =
DataWriter::new(self.kind.inner.compressor, self.fs_compressor, self.block_size);
let mut inode_writer = MetadataWriter::new(
Expand All @@ -687,6 +660,13 @@ impl<'a, 'b, 'c> FilesystemWriter<'a, 'b, 'c> {
Kind { inner: self.kind.inner.clone() },
);

let options = self.kind.inner.compressor.compression_options(
&mut superblock,
&self.kind,
self.fs_compressor,
)?;
w.write_all(&options)?;

info!("Creating Inodes and Dirs");
//trace!("TREE: {:#02x?}", &self.root);
info!("Writing Data");
Expand All @@ -705,12 +685,7 @@ impl<'a, 'b, 'c> FilesystemWriter<'a, 'b, 'c> {
&self.kind,
&self.id_table,
)?;

superblock.root_inode = ((root.start as u64) << 16) | ((root.offset as u64) & 0xffff);
superblock.inode_count = self.root.nodes.len().try_into().unwrap();
superblock.block_size = self.block_size;
superblock.block_log = self.block_log;
superblock.mod_time = self.mod_time;

info!("Writing Inodes");
superblock.inode_table = w.stream_position()?;
Expand Down
11 changes: 11 additions & 0 deletions backhand/src/kinds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ impl Kind {
/// # Example
/// ```rust
/// # use backhand::{compression::Compressor, kind, FilesystemCompressor, kind::Kind, compression::CompressionAction, compression::DefaultCompressor, BackhandError};
/// # use backhand::SuperBlock;
/// # use std::io::Write;
/// #[derive(Copy, Clone)]
/// pub struct CustomCompressor;
Expand Down Expand Up @@ -112,6 +113,16 @@ impl Kind {
/// ) -> Result<Vec<u8>, BackhandError> {
/// DefaultCompressor.compress(bytes, fc, block_size)
/// }
///
/// // pass the default options
/// fn compression_options(
/// &self,
/// _superblock: &mut SuperBlock,
/// _kind: &Kind,
/// _fs_compressor: FilesystemCompressor,
/// ) -> Result<Vec<u8>, BackhandError> {
/// DefaultCompressor.compression_options(_superblock, _kind, _fs_compressor)
/// }
/// }
///
/// let kind = Kind::new(&CustomCompressor);
Expand Down

0 comments on commit 11af4c5

Please sign in to comment.