From e98ea329bfaca97c6354cd0c8ac7aec833d8a234 Mon Sep 17 00:00:00 2001 From: Yu Li Date: Fri, 30 Jun 2023 17:05:38 +0800 Subject: [PATCH] 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: https://chromium.googlesource.com/crosvm/crosvm/+/d8144a56e26ca09e2c7ff97ed63c57e7e7965674 Signed-off-by: Yu Li --- block/src/qcow/mod.rs | 52 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 1900d6603..fdbc052d1 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -673,6 +673,10 @@ impl QcowFile { Ok(qcow) } + pub fn set_backing_file(&mut self, backing: Option>) { + self.backing_file = backing; + } + /// Returns the `QcowHeader` for this file. pub fn header(&self) -> &QcowHeader { &self.header @@ -1509,6 +1513,9 @@ impl Read for QcowFile { self.raw_file .file_mut() .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 { // Previously unwritten region, return zeros for b in &mut buf[nread..(nread + count)] { @@ -1867,16 +1874,19 @@ mod tests { ] } - fn with_basic_file(header: &[u8], mut testfn: F) - where - F: FnMut(RawFile), - { + fn basic_file(header: &[u8]) -> RawFile { let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false); disk_file.write_all(header).unwrap(); disk_file.set_len(0x1_0000_0000).unwrap(); disk_file.rewind().unwrap(); + disk_file + } - testfn(disk_file); // File closed when the function exits. + fn with_basic_file(header: &[u8], mut testfn: F) + where + F: FnMut(RawFile), + { + testfn(basic_file(header)); // File closed when the function exits. } fn with_default_file(file_size: u64, direct: bool, mut testfn: F) @@ -1889,6 +1899,38 @@ mod tests { 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] fn default_header_v2() { let header = QcowHeader::create_for_size_and_path(2, 0x10_0000, None);