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 <liyu.yukiteru@bytedance.com>
This commit is contained in:
parent
081a6ebb51
commit
e98ea329bf
1 changed files with 47 additions and 5 deletions
|
|
@ -673,6 +673,10 @@ impl QcowFile {
|
|||
Ok(qcow)
|
||||
}
|
||||
|
||||
pub fn set_backing_file(&mut self, backing: Option<Box<dyn BlockBackend>>) {
|
||||
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<F>(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<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)
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue