diff --git a/CHANGELOG.md b/CHANGELOG.md index aae3182f..e75a82a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add non-standard CompressionOptions support ([#584](https://github.com/wcampbell0x2a/backhand/pull/584)) - Add `CompressionAction::compression_options` to override the default compression options emitted during writing. - Add `FilesystemWriter::set_emit_compression_options` +- Support sparse file extraction ([#624](https://github.com/wcampbell0x2a/backhand/pull/624)) ### `backhand-cli` - Add `--no-compression-options` to `add` and `replace` to remove compression options from image after modification. diff --git a/backhand-test/tests/test.rs b/backhand-test/tests/test.rs index 71360746..403b9931 100644 --- a/backhand-test/tests/test.rs +++ b/backhand-test/tests/test.rs @@ -507,7 +507,6 @@ fn test_socket_fifo() { #[test] #[cfg(any(feature = "zstd"))] fn no_qemu_test_crates_zstd() { - tracing::trace!("nice"); const FILE_NAME: &str = "crates-io.squashfs"; let asset_defs = [TestAssetDef { filename: FILE_NAME.to_string(), @@ -519,3 +518,17 @@ fn no_qemu_test_crates_zstd() { full_test(&asset_defs, FILE_NAME, TEST_PATH, 0, Verify::Extract, false); } + +#[test] +#[cfg(feature = "xz")] +fn test_slow_sparse_data_issue_623() { + const FILE_NAME: &str = "aosc-os_buildkit_20240916_amd64.squashfs"; + let asset_defs = [TestAssetDef { + filename: FILE_NAME.to_string(), + hash: "02b0069d480df7c351b64e640025cb6b84890d8f9e377f15cc24fc70d5617905".to_string(), + url: "https://releases.aosc.io/os-amd64/buildkit/aosc-os_buildkit_20240916_amd64.squashfs" + .to_string(), + }]; + const TEST_PATH: &str = "test-assets/test_sparse_data_issue_623"; + full_test(&asset_defs, FILE_NAME, TEST_PATH, 0, Verify::Extract, true); +} diff --git a/backhand/src/filesystem/reader.rs b/backhand/src/filesystem/reader.rs index 90f88bb8..2180d3f3 100644 --- a/backhand/src/filesystem/reader.rs +++ b/backhand/src/filesystem/reader.rs @@ -265,6 +265,11 @@ impl<'a, 'b> SquashfsRawData<'a, 'b> { match block { BlockFragment::Block(block) => { let block_size = block.size() as usize; + // sparse file, don't read from reader, just fill with superblock.block size of 0's + if block_size == 0 { + *data = vec![0; self.file.system.block_size as usize]; + return Ok(RawDataBlock { fragment: false, uncompressed: true }); + } data.resize(block_size, 0); //NOTE: storing/restoring the file-pos is not required at the //moment of writing, but in the future, it may. @@ -346,10 +351,10 @@ impl<'a, 'b> SquashfsRawData<'a, 'b> { input_buf: &mut Vec, output_buf: &mut Vec, ) -> Result<(), BackhandError> { - //append to the output_buf is not allowed, it need to be empty + // append to the output_buf is not allowed, it need to be empty assert!(output_buf.is_empty()); - //input is already decompress, so just swap the input/output, so the - //output_buf contains the final data. + // input is already decompress, so just swap the input/output, so the + // output_buf contains the final data. if data.uncompressed { std::mem::swap(input_buf, output_buf); } else {