imago/qcow2/
compressed.rs

1//! Support for compressed clusters.
2
3use super::*;
4use crate::io_buffers::IoBuffer;
5use miniz_oxide::inflate::core::{decompress as inflate, DecompressorOxide};
6use miniz_oxide::inflate::TINFLStatus;
7
8impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> Qcow2<S, F> {
9    /// Read one compressed cluster.
10    ///
11    /// Read the compressed data at `compressed_offset` of length `compressed_length` (which must
12    /// be the values from the L2 compressed cluster descriptor) into a bounce buffer, then
13    /// decompress it into `buf` (which must have a length of exactly one cluster).
14    pub(super) async fn read_compressed_cluster(
15        &self,
16        buf: &mut [u8],
17        compressed_offset: HostOffset,
18        compressed_length: u64,
19    ) -> io::Result<()> {
20        debug_assert!(buf.len() == self.header.cluster_size());
21
22        let storage = self.storage();
23
24        // Must fit (really shouldn’t be compressed if this exceeds the cluster size anyway)
25        let compressed_length = compressed_length.try_into().map_err(io::Error::other)?;
26        let mut compressed_buf = IoBuffer::new(compressed_length, storage.mem_align())?;
27        storage
28            .read(&mut compressed_buf, compressed_offset.0)
29            .await?;
30
31        let mut dec_ox = DecompressorOxide::new();
32        let (status, _read, written) =
33            inflate(&mut dec_ox, compressed_buf.as_ref().into_slice(), buf, 0, 0);
34
35        // Because `compressed_length` will generally exceed the actual length, `HasMoreOutput` is
36        // expected and can be ignored
37        if status != TINFLStatus::Done && status != TINFLStatus::HasMoreOutput {
38            return Err(io::Error::other(format!(
39                "Failed to decompress cluster (host offset {compressed_offset}+{compressed_length}): {status:?}",
40            )));
41        }
42        if written < buf.len() {
43            return Err(io::Error::other(format!(
44                "Failed to decompress cluster (host offset {compressed_offset}+{compressed_length}): Decompressed {written} bytes, expected {}",
45                buf.len(),
46            )));
47        }
48
49        Ok(())
50    }
51}