block: qcow: read from the backing file if present on read miss

Reads to qcow files with backing files will fall through to the backing
file if there is no allocated cluster. As of this change, a write will
still trash the cluster and hide any data already present.

This commit is based on crosvm implementation:
d8144a56e2

Signed-off-by: Yu Li <liyu.yukiteru@bytedance.com>
This commit is contained in:
Yu Li 2023-06-30 17:05:38 +08:00 committed by Rob Bradford
parent 081a6ebb51
commit e98ea329bf

View File

@ -673,6 +673,10 @@ impl QcowFile {
Ok(qcow) Ok(qcow)
} }
pub fn set_backing_file(&mut self, backing: Option<Box<dyn BlockBackend>>) {
self.backing_file = backing;
}
/// Returns the `QcowHeader` for this file. /// Returns the `QcowHeader` for this file.
pub fn header(&self) -> &QcowHeader { pub fn header(&self) -> &QcowHeader {
&self.header &self.header
@ -1509,6 +1513,9 @@ impl Read for QcowFile {
self.raw_file self.raw_file
.file_mut() .file_mut()
.read_exact(&mut buf[nread..(nread + count)])?; .read_exact(&mut buf[nread..(nread + count)])?;
} else if let Some(backing) = self.backing_file.as_mut() {
backing.seek(SeekFrom::Start(curr_addr))?;
backing.read_exact(&mut buf[nread..(nread + count)])?;
} else { } else {
// Previously unwritten region, return zeros // Previously unwritten region, return zeros
for b in &mut buf[nread..(nread + count)] { for b in &mut buf[nread..(nread + count)] {
@ -1867,16 +1874,19 @@ mod tests {
] ]
} }
fn with_basic_file<F>(header: &[u8], mut testfn: F) fn basic_file(header: &[u8]) -> RawFile {
where
F: FnMut(RawFile),
{
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false); let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
disk_file.write_all(header).unwrap(); disk_file.write_all(header).unwrap();
disk_file.set_len(0x1_0000_0000).unwrap(); disk_file.set_len(0x1_0000_0000).unwrap();
disk_file.rewind().unwrap(); disk_file.rewind().unwrap();
disk_file
}
testfn(disk_file); // File closed when the function exits. fn with_basic_file<F>(header: &[u8], mut testfn: F)
where
F: FnMut(RawFile),
{
testfn(basic_file(header)); // File closed when the function exits.
} }
fn with_default_file<F>(file_size: u64, direct: bool, mut testfn: F) fn with_default_file<F>(file_size: u64, direct: bool, mut testfn: F)
@ -1889,6 +1899,38 @@ mod tests {
testfn(qcow_file); // File closed when the function exits. testfn(qcow_file); // File closed when the function exits.
} }
#[test]
fn write_read_start_backing_v2() {
let disk_file = basic_file(&valid_header_v2());
let mut backing = QcowFile::from(disk_file).unwrap();
backing
.write_all(b"test first bytes")
.expect("Failed to write test string.");
let mut buf = [0u8; 4];
let wrapping_disk_file = basic_file(&valid_header_v2());
let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap();
wrapping.set_backing_file(Some(Box::new(backing)));
wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
wrapping.read_exact(&mut buf).expect("Failed to read.");
assert_eq!(&buf, b"test");
}
#[test]
fn write_read_start_backing_v3() {
let disk_file = basic_file(&valid_header_v3());
let mut backing = QcowFile::from(disk_file).unwrap();
backing
.write_all(b"test first bytes")
.expect("Failed to write test string.");
let mut buf = [0u8; 4];
let wrapping_disk_file = basic_file(&valid_header_v3());
let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap();
wrapping.set_backing_file(Some(Box::new(backing)));
wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
wrapping.read_exact(&mut buf).expect("Failed to read.");
assert_eq!(&buf, b"test");
}
#[test] #[test]
fn default_header_v2() { fn default_header_v2() {
let header = QcowHeader::create_for_size_and_path(2, 0x10_0000, None); let header = QcowHeader::create_for_size_and_path(2, 0x10_0000, None);