block: qcow: support compressed clusters (zlib, zstd)

Add support of reading and writing compressed clusters.
Support zlib and zstd compressions.

L2 cache: store entire L2 entries, not only standard cluster addresses.

Read path. Offsets of compressed clusters cannot be determined,
therefore replace QcowFile.file_offset_read() with QcowFile.file_read().
This method reads the cluster, decompresses it if necessary and returns
the data to the caller.

Write path. QcowFile.file_offset_write(): since writing to compressed
clusters is not generally possible, allocate a new standard
(non-compressed) cluster if compressed L2 entry is encountered; then
decompress compressed cluster into new cluster; then return offset
inside new cluster to the caller. Processing of standard clusters is
not changed.

Signed-off-by: Eugene Korenevsky <ekorenevsky@aliyun.com>
This commit is contained in:
Eugene Korenevsky
2025-11-04 03:34:56 +03:00
committed by Rob Bradford
parent 4d79709b5e
commit aa67250049
4 changed files with 311 additions and 72 deletions

59
Cargo.lock generated
View File

@@ -321,6 +321,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"crc-any", "crc-any",
"flate2",
"io-uring", "io-uring",
"libc", "libc",
"log", "log",
@@ -334,6 +335,7 @@ dependencies = [
"vm-memory", "vm-memory",
"vm-virtio", "vm-virtio",
"vmm-sys-util", "vmm-sys-util",
"zstd",
] ]
[[package]] [[package]]
@@ -368,6 +370,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver",
"libc",
"shlex", "shlex",
] ]
@@ -728,6 +732,16 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "flate2"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]] [[package]]
name = "flume" name = "flume"
version = "0.11.1" version = "0.11.1"
@@ -1076,6 +1090,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "jobserver"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.3",
"libc",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.77" version = "0.3.77"
@@ -1235,6 +1259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [ dependencies = [
"adler2", "adler2",
"simd-adler32",
] ]
[[package]] [[package]]
@@ -1965,6 +1990,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.11" version = "0.4.11"
@@ -2809,6 +2840,34 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "zstd"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.16+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
dependencies = [
"cc",
"pkg-config",
]
[[package]] [[package]]
name = "zvariant" name = "zvariant"
version = "5.7.0" version = "5.7.0"

View File

@@ -11,6 +11,7 @@ io_uring = ["dep:io-uring"]
[dependencies] [dependencies]
byteorder = { workspace = true } byteorder = { workspace = true }
crc-any = "2.5.0" crc-any = "2.5.0"
flate2 = "1.0"
io-uring = { version = "0.7.10", optional = true } io-uring = { version = "0.7.10", optional = true }
libc = { workspace = true } libc = { workspace = true }
log = { workspace = true } log = { workspace = true }
@@ -28,6 +29,7 @@ vm-memory = { workspace = true, features = [
] } ] }
vm-virtio = { path = "../vm-virtio" } vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = { workspace = true } vmm-sys-util = { workspace = true }
zstd = "0.13"
[lints] [lints]
workspace = true workspace = true

87
block/src/qcow/decoder.rs Normal file
View File

@@ -0,0 +1,87 @@
// Copyright 2025 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("Zlib decompress error")]
ZlibDecompress(#[source] flate2::DecompressError),
#[error("Zlib unexpected status: {0:?}")]
ZlibUnexpectedStatus(flate2::Status),
#[error("Zstd decompress error")]
ZstdDecompress(#[source] std::io::Error),
#[error("Zstd: failed to fill buffer")]
ZstdFillBuffer(#[source] std::io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
/// Generic trait for decoding zlib/zstd formats
pub trait Decoder {
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize>;
}
#[derive(Default)]
pub struct ZlibDecoder {}
impl Decoder for ZlibDecoder {
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
use flate2::{Decompress, FlushDecompress, Status};
let mut decompressor = Decompress::new(false);
let status = decompressor
.decompress(input, output, FlushDecompress::Finish)
.map_err(Error::ZlibDecompress)?;
if status == Status::StreamEnd {
Ok(decompressor.total_out() as usize)
} else {
Err(Error::ZlibUnexpectedStatus(status))
}
}
}
#[derive(Default)]
pub struct ZstdDecoder {}
impl Decoder for ZstdDecoder {
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
use std::io::Read;
let mut decoder = zstd::stream::read::Decoder::new(input).map_err(Error::ZstdDecompress)?;
let decoded_size = decoder.read(output).map_err(Error::ZstdFillBuffer)?;
Ok(decoded_size)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zlib_decode() {
let d = ZlibDecoder::default();
let valid_input = vec![99, 96, 100, 98, 6, 0];
let mut output1 = vec![0; 4];
d.decode(&valid_input, &mut output1).unwrap();
assert_eq!(&output1, b"\x00\x01\x02\x03");
let invalid_input = vec![1, 2, 3, 4];
let mut output2 = vec![0; 1024];
d.decode(&invalid_input, &mut output2).unwrap_err();
}
#[test]
fn test_zstd_decode() {
let d = ZstdDecoder::default();
let valid_input = vec![40, 181, 47, 253, 32, 2, 17, 0, 0, 1, 254];
let mut output1 = vec![0; 2];
d.decode(&valid_input, &mut output1).unwrap();
assert_eq!(&output1, b"\x01\xfe");
let invalid_input = vec![1, 2, 3, 4];
let mut output2 = vec![0; 1024];
d.decode(&invalid_input, &mut output2).unwrap_err();
}
}

View File

@@ -4,6 +4,7 @@
// //
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
mod decoder;
mod qcow_raw_file; mod qcow_raw_file;
mod raw_file; mod raw_file;
mod refcount; mod refcount;
@@ -17,7 +18,7 @@ use std::os::fd::{AsRawFd, RawFd};
use std::str; use std::str;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use libc::{EINVAL, ENOSPC, ENOTSUP}; use libc::{EINVAL, EIO, ENOSPC};
use remain::sorted; use remain::sorted;
use thiserror::Error; use thiserror::Error;
use vmm_sys_util::file_traits::{FileSetLen, FileSync}; use vmm_sys_util::file_traits::{FileSetLen, FileSync};
@@ -25,6 +26,7 @@ use vmm_sys_util::seek_hole::SeekHole;
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
use crate::BlockBackend; use crate::BlockBackend;
use crate::qcow::decoder::{Decoder, ZlibDecoder, ZstdDecoder};
use crate::qcow::qcow_raw_file::QcowRawFile; use crate::qcow::qcow_raw_file::QcowRawFile;
pub use crate::qcow::raw_file::RawFile; pub use crate::qcow::raw_file::RawFile;
use crate::qcow::refcount::RefCount; use crate::qcow::refcount::RefCount;
@@ -42,8 +44,6 @@ pub enum Error {
BackingFileOpen(#[source] Box<Error>), BackingFileOpen(#[source] Box<Error>),
#[error("Backing file name is too long: {0} bytes over")] #[error("Backing file name is too long: {0} bytes over")]
BackingFileTooLong(usize), BackingFileTooLong(usize),
#[error("Compressed blocks not supported")]
CompressedBlocksNotSupported,
#[error("Failed to evict cache")] #[error("Failed to evict cache")]
EvictingCache(#[source] io::Error), EvictingCache(#[source] io::Error),
#[error("File larger than max of {MAX_QCOW_FILE_SIZE}: {0}")] #[error("File larger than max of {MAX_QCOW_FILE_SIZE}: {0}")]
@@ -110,6 +110,8 @@ pub enum Error {
TooManyL1Entries(u64), TooManyL1Entries(u64),
#[error("Ref count table too large: {0}")] #[error("Ref count table too large: {0}")]
TooManyRefcounts(u64), TooManyRefcounts(u64),
#[error("Unsupported compression type")]
UnsupportedCompressionType,
#[error("Unsupported refcount order")] #[error("Unsupported refcount order")]
UnsupportedRefcountOrder, UnsupportedRefcountOrder,
#[error("Unsupported version: {0}")] #[error("Unsupported version: {0}")]
@@ -127,6 +129,12 @@ pub enum ImageType {
Qcow2, Qcow2,
} }
#[derive(Clone, Debug)]
pub enum CompressionType {
Zlib,
Zstd,
}
// Maximum data size supported. // Maximum data size supported.
const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB. const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB.
@@ -153,15 +161,50 @@ const L1_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
const L2_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00; const L2_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
// Flags // Flags
const COMPRESSED_FLAG: u64 = 1 << 62; const COMPRESSED_FLAG: u64 = 1 << 62;
const COMPRESSED_SECTOR_SIZE: u64 = 512;
const CLUSTER_USED_FLAG: u64 = 1 << 63; const CLUSTER_USED_FLAG: u64 = 1 << 63;
const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1; const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1;
// Compression types as defined in https://www.qemu.org/docs/master/interop/qcow2.html
const COMPRESSION_TYPE_ZLIB: u64 = 0; // zlib/deflate <https://www.ietf.org/rfc/rfc1951.txt>
const COMPRESSION_TYPE_ZSTD: u64 = 1; // zstd <http://github.com/facebook/zstd>
// The format supports a "header extension area", that crosvm does not use. // The format supports a "header extension area", that crosvm does not use.
const QCOW_EMPTY_HEADER_EXTENSION_SIZE: u32 = 8; const QCOW_EMPTY_HEADER_EXTENSION_SIZE: u32 = 8;
// Defined by the specification // Defined by the specification
const MAX_BACKING_FILE_SIZE: u32 = 1023; const MAX_BACKING_FILE_SIZE: u32 = 1023;
fn l2_entry_is_empty(l2_entry: u64) -> bool {
l2_entry == 0
}
fn l2_entry_is_compressed(l2_entry: u64) -> bool {
l2_entry & COMPRESSED_FLAG != 0
}
// Get file offset and size of compressed cluster data
fn l2_entry_compressed_cluster_layout(l2_entry: u64, cluster_bits: u32) -> (u64, usize) {
let compressed_size_shift = 62 - (cluster_bits - 8);
let compressed_size_mask = (1 << (cluster_bits - 8)) - 1;
let compressed_cluster_addr = l2_entry & ((1 << compressed_size_shift) - 1);
let nsectors = (l2_entry >> compressed_size_shift & compressed_size_mask) + 1;
let compressed_cluster_size = ((nsectors * COMPRESSED_SECTOR_SIZE)
- (compressed_cluster_addr & (COMPRESSED_SECTOR_SIZE - 1)))
as usize;
(compressed_cluster_addr, compressed_cluster_size)
}
// Get file offset of standard (non-compressed) cluster
fn l2_entry_std_cluster_addr(l2_entry: u64) -> u64 {
l2_entry & L2_TABLE_OFFSET_MASK
}
// Make L2 entry for standard (non-compressed) cluster
fn l2_entry_make_std(cluster_addr: u64) -> u64 {
(cluster_addr & L2_TABLE_OFFSET_MASK) | CLUSTER_USED_FLAG
}
/// Contains the information from the header of a qcow file. /// Contains the information from the header of a qcow file.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct QcowHeader { pub struct QcowHeader {
@@ -190,6 +233,7 @@ pub struct QcowHeader {
pub autoclear_features: u64, pub autoclear_features: u64,
pub refcount_order: u32, pub refcount_order: u32,
pub header_size: u32, pub header_size: u32,
pub compression_type: CompressionType,
// Post-header entries // Post-header entries
pub backing_file_path: Option<String>, pub backing_file_path: Option<String>,
@@ -255,8 +299,19 @@ impl QcowHeader {
} else { } else {
read_u32_from_file(f)? read_u32_from_file(f)?
}, },
compression_type: CompressionType::Zlib,
backing_file_path: None, backing_file_path: None,
}; };
if version == 3 && header.header_size > V3_BARE_HEADER_SIZE {
let raw_compression_type = read_u64_from_file(f)? >> (64 - 8);
header.compression_type = if raw_compression_type == COMPRESSION_TYPE_ZLIB {
Ok(CompressionType::Zlib)
} else if raw_compression_type == COMPRESSION_TYPE_ZSTD {
Ok(CompressionType::Zstd)
} else {
Err(Error::UnsupportedCompressionType)
}?;
}
if header.backing_file_size > MAX_BACKING_FILE_SIZE { if header.backing_file_size > MAX_BACKING_FILE_SIZE {
return Err(Error::BackingFileTooLong(header.backing_file_size as usize)); return Err(Error::BackingFileTooLong(header.backing_file_size as usize));
} }
@@ -274,6 +329,13 @@ impl QcowHeader {
Ok(header) Ok(header)
} }
pub fn get_decoder(&self) -> Box<dyn Decoder> {
match self.compression_type {
CompressionType::Zlib => Box::new(ZlibDecoder {}),
CompressionType::Zstd => Box::new(ZstdDecoder {}),
}
}
pub fn create_for_size_and_path( pub fn create_for_size_and_path(
version: u32, version: u32,
size: u64, size: u64,
@@ -337,6 +399,7 @@ impl QcowHeader {
autoclear_features: 0, autoclear_features: 0,
refcount_order: DEFAULT_REFCOUNT_ORDER, refcount_order: DEFAULT_REFCOUNT_ORDER,
header_size, header_size,
compression_type: CompressionType::Zlib,
backing_file_path: backing_file.map(String::from), backing_file_path: backing_file.map(String::from),
}) })
} }
@@ -588,17 +651,6 @@ impl QcowFile {
let l2_entries = cluster_size / size_of::<u64>() as u64; let l2_entries = cluster_size / size_of::<u64>() as u64;
// Check for compressed blocks
for l2_addr_disk in l1_table.get_values() {
if *l2_addr_disk != 0
&& let Err(e) = Self::read_l2_cluster(&mut raw_file, *l2_addr_disk)
&& let Some(os_error) = e.raw_os_error()
&& os_error == ENOTSUP
{
return Err(Error::CompressedBlocksNotSupported);
}
}
let mut qcow = QcowFile { let mut qcow = QcowFile {
raw_file, raw_file,
header, header,
@@ -714,11 +766,7 @@ impl QcowFile {
let raw_file = &mut self.raw_file; let raw_file = &mut self.raw_file;
self.l2_cache self.l2_cache
.insert(l1_index, table, |index, evicted| { .insert(l1_index, table, |index, evicted| {
raw_file.write_pointer_table( raw_file.write_pointer_table(l1_table[index], evicted.get_values(), 0)
l1_table[index],
evicted.get_values(),
CLUSTER_USED_FLAG,
)
}) })
.map_err(Error::EvictingCache)?; .map_err(Error::EvictingCache)?;
} }
@@ -1050,11 +1098,40 @@ impl QcowFile {
(address / self.raw_file.cluster_size()) % self.l2_entries (address / self.raw_file.cluster_size()) % self.l2_entries
} }
// Gets the offset of the given guest address in the host file. If L1, L2, or data clusters have // Decompress the cluster, return EIO on failure
// yet to be allocated, return None. fn decompress_l2_cluster(&mut self, l2_entry: u64) -> std::io::Result<Vec<u8>> {
fn file_offset_read(&mut self, address: u64) -> std::io::Result<Option<u64>> { let (compressed_cluster_addr, compressed_cluster_size) =
l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits);
// Read compressed cluster from raw file
self.raw_file
.file_mut()
.seek(SeekFrom::Start(compressed_cluster_addr))?;
let mut compressed_cluster = vec![0; compressed_cluster_size];
self.raw_file
.file_mut()
.read_exact(&mut compressed_cluster)?;
let decoder = self.header.get_decoder();
// Decompress
let cluster_size = self.raw_file.cluster_size() as usize;
let mut decompressed_cluster = vec![0; cluster_size];
let decompressed_size = decoder
.decode(&compressed_cluster, &mut decompressed_cluster)
.map_err(|_| std::io::Error::from_raw_os_error(EIO))?;
if decompressed_size as u64 != self.raw_file.cluster_size() {
return Err(std::io::Error::from_raw_os_error(EIO));
}
Ok(decompressed_cluster)
}
fn file_read(
&mut self,
address: u64,
count: usize,
buf: &mut [u8],
) -> std::io::Result<Option<()>> {
let err_inval = std::io::Error::from_raw_os_error(EINVAL);
if address >= self.virtual_size() { if address >= self.virtual_size() {
return Err(std::io::Error::from_raw_os_error(EINVAL)); return Err(err_inval);
} }
let l1_index = self.l1_table_index(address) as usize; let l1_index = self.l1_table_index(address) as usize;
@@ -1072,11 +1149,28 @@ impl QcowFile {
self.cache_l2_cluster(l1_index, l2_addr_disk, false)?; self.cache_l2_cluster(l1_index, l2_addr_disk, false)?;
let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index]; let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index];
if cluster_addr == 0 { if l2_entry_is_empty(l2_entry) {
// Reading from an unallocated cluster will return zeros.
return Ok(None); return Ok(None);
} else if l2_entry_is_compressed(l2_entry) {
// Compressed cluster.
// Read it, decompress, then return slice from decompressed data.
let mut decompressed_cluster = self.decompress_l2_cluster(l2_entry)?;
decompressed_cluster.resize(self.raw_file.cluster_size() as usize, 0);
let start = self.raw_file.cluster_offset(address) as usize;
let end = start.checked_add(count);
if end.is_none() || end.unwrap() > decompressed_cluster.len() {
return Err(err_inval);
}
buf[..count].copy_from_slice(&decompressed_cluster[start..end.unwrap()]);
} else {
let start = l2_entry_std_cluster_addr(l2_entry) + self.raw_file.cluster_offset(address);
let raw_file = self.raw_file.file_mut();
raw_file.seek(SeekFrom::Start(start))?;
raw_file.read_exact(buf)?;
} }
Ok(Some(cluster_addr + self.raw_file.cluster_offset(address))) Ok(Some(()))
} }
// Gets the offset of the given guest address in the host file. If L1, L2, or data clusters need // Gets the offset of the given guest address in the host file. If L1, L2, or data clusters need
@@ -1100,24 +1194,39 @@ impl QcowFile {
set_refcounts.push((new_addr, 1)); set_refcounts.push((new_addr, 1));
} }
let cluster_addr = match self.l2_cache.get(l1_index).unwrap()[l2_index] { let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index];
0 => { let cluster_addr = if l2_entry_is_compressed(l2_entry) {
let initial_data = if let Some(backing) = self.backing_file.as_mut() { // Writing to compressed cluster.
let cluster_size = self.raw_file.cluster_size(); // Allocate new cluster, decompress into new cluster, then use
let cluster_begin = address - (address % cluster_size); // offset of new cluster.
let mut cluster_data = vec![0u8; cluster_size as usize]; let decompressed_cluster = self.decompress_l2_cluster(l2_entry)?;
backing.seek(SeekFrom::Start(cluster_begin))?; let cluster_addr = self.append_data_cluster(None)?;
backing.read_exact(&mut cluster_data)?; self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?;
Some(cluster_data) self.raw_file
} else { .file_mut()
None .seek(SeekFrom::Start(cluster_addr))?;
}; let nwritten = self.raw_file.file_mut().write(&decompressed_cluster)?;
// Need to allocate a data cluster if nwritten != decompressed_cluster.len() {
let cluster_addr = self.append_data_cluster(initial_data)?; return Err(std::io::Error::from_raw_os_error(EIO));
self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?;
cluster_addr
} }
a => a, cluster_addr
} else if l2_entry_is_empty(l2_entry) {
let initial_data = if let Some(backing) = self.backing_file.as_mut() {
let cluster_size = self.raw_file.cluster_size();
let cluster_begin = address - (address % cluster_size);
let mut cluster_data = vec![0u8; cluster_size as usize];
backing.seek(SeekFrom::Start(cluster_begin))?;
backing.read_exact(&mut cluster_data)?;
Some(cluster_data)
} else {
None
};
// Need to allocate a data cluster
let cluster_addr = self.append_data_cluster(initial_data)?;
self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?;
cluster_addr
} else {
l2_entry_std_cluster_addr(l2_entry)
}; };
for (addr, count) in set_refcounts { for (addr, count) in set_refcounts {
@@ -1157,7 +1266,7 @@ impl QcowFile {
self.l1_table[l1_index] = new_addr; self.l1_table[l1_index] = new_addr;
} }
// 'unwrap' is OK because it was just added. // 'unwrap' is OK because it was just added.
self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = cluster_addr; self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = l2_entry_make_std(cluster_addr);
Ok(()) Ok(())
} }
@@ -1334,10 +1443,9 @@ impl QcowFile {
// Partial cluster - zero out the relevant bytes if it was allocated. // Partial cluster - zero out the relevant bytes if it was allocated.
// Any space in unallocated clusters can be left alone, since // Any space in unallocated clusters can be left alone, since
// unallocated clusters already read back as zeroes. // unallocated clusters already read back as zeroes.
if let Some(offset) = self.file_offset_read(curr_addr)? { let offset = self.file_offset_write(curr_addr)?;
// Partial cluster - zero it out. // Partial cluster - zero it out.
self.raw_file.file_mut().write_zeroes_at(offset, count)?; self.raw_file.file_mut().write_zeroes_at(offset, count)?;
}
} }
nwritten += count; nwritten += count;
@@ -1348,14 +1456,8 @@ impl QcowFile {
// Reads an L2 cluster from the disk, returning an error if the file can't be read or if any // Reads an L2 cluster from the disk, returning an error if the file can't be read or if any
// cluster is compressed. // cluster is compressed.
fn read_l2_cluster(raw_file: &mut QcowRawFile, cluster_addr: u64) -> std::io::Result<Vec<u64>> { fn read_l2_cluster(raw_file: &mut QcowRawFile, cluster_addr: u64) -> std::io::Result<Vec<u64>> {
let file_values = raw_file.read_pointer_cluster(cluster_addr, None)?; let l2_table = raw_file.read_pointer_cluster(cluster_addr, None)?;
if file_values.iter().any(|entry| entry & COMPRESSED_FLAG != 0) { Ok(l2_table)
return Err(std::io::Error::from_raw_os_error(ENOTSUP));
}
Ok(file_values
.iter()
.map(|entry| *entry & L2_TABLE_OFFSET_MASK)
.collect())
} }
// Put an L2 cluster to the cache with evicting less-used cluster // Put an L2 cluster to the cache with evicting less-used cluster
@@ -1383,11 +1485,7 @@ impl QcowFile {
let l1_table = &self.l1_table; let l1_table = &self.l1_table;
let raw_file = &mut self.raw_file; let raw_file = &mut self.raw_file;
self.l2_cache.insert(l1_index, l2_table, |index, evicted| { self.l2_cache.insert(l1_index, l2_table, |index, evicted| {
raw_file.write_pointer_table( raw_file.write_pointer_table(l1_table[index], evicted.get_values(), 0)
l1_table[index],
evicted.get_values(),
CLUSTER_USED_FLAG,
)
})?; })?;
} }
Ok(new_cluster) Ok(new_cluster)
@@ -1456,11 +1554,8 @@ impl QcowFile {
// The index must be valid from when we inserted it. // The index must be valid from when we inserted it.
let addr = self.l1_table[*l1_index]; let addr = self.l1_table[*l1_index];
if addr != 0 { if addr != 0 {
self.raw_file.write_pointer_table( self.raw_file
addr, .write_pointer_table(addr, l2_table.get_values(), 0)?;
l2_table.get_values(),
CLUSTER_USED_FLAG,
)?;
} else { } else {
return Err(std::io::Error::from_raw_os_error(EINVAL)); return Err(std::io::Error::from_raw_os_error(EINVAL));
} }
@@ -1512,14 +1607,10 @@ impl Read for QcowFile {
let mut nread: usize = 0; let mut nread: usize = 0;
while nread < read_count { while nread < read_count {
let curr_addr = address + nread as u64; let curr_addr = address + nread as u64;
let file_offset = self.file_offset_read(curr_addr)?;
let count = self.limit_range_cluster(curr_addr, read_count - nread); let count = self.limit_range_cluster(curr_addr, read_count - nread);
if let Some(offset) = file_offset { if (self.file_read(curr_addr, count, &mut buf[nread..(nread + count)])?).is_some() {
self.raw_file.file_mut().seek(SeekFrom::Start(offset))?; // Data is successfully read from the cluster
self.raw_file
.file_mut()
.read_exact(&mut buf[nread..(nread + count)])?;
} else if let Some(backing) = self.backing_file.as_mut() { } else if let Some(backing) = self.backing_file.as_mut() {
backing.seek(SeekFrom::Start(curr_addr))?; backing.seek(SeekFrom::Start(curr_addr))?;
backing.read_exact(&mut buf[nread..(nread + count)])?; backing.read_exact(&mut buf[nread..(nread + count)])?;