summary refs log tree commit diff
path: root/disk
diff options
context:
space:
mode:
authorA. Cody Schuffelen <schuffelen@google.com>2019-12-26 13:05:10 -0800
committerCommit Bot <commit-bot@chromium.org>2020-02-25 20:28:07 +0000
commit5ad3bc345904b252efd6dd2ef4853f5ee06ae3c5 (patch)
tree16b281f8be0f7daa06cd8b98937e8395ab589f2a /disk
parent0275efb3a00b2cbe15ec92a314bf163a3ca1433e (diff)
downloadcrosvm-5ad3bc345904b252efd6dd2ef4853f5ee06ae3c5.tar
crosvm-5ad3bc345904b252efd6dd2ef4853f5ee06ae3c5.tar.gz
crosvm-5ad3bc345904b252efd6dd2ef4853f5ee06ae3c5.tar.bz2
crosvm-5ad3bc345904b252efd6dd2ef4853f5ee06ae3c5.tar.lz
crosvm-5ad3bc345904b252efd6dd2ef4853f5ee06ae3c5.tar.xz
crosvm-5ad3bc345904b252efd6dd2ef4853f5ee06ae3c5.tar.zst
crosvm-5ad3bc345904b252efd6dd2ef4853f5ee06ae3c5.zip
Initialize qcow clusters off the backing file when present
This preserves any data that the backing file had on a cluster when
doing a write to a subset of that cluster. These writes cause a
performance penalty on creating new clusters if a backing file is
present.

TEST=unit tests
BUG=b:140069322
Change-Id: I724990225617c05e5f2dea39e39ce84c940328fc
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1982832
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Cody Schuffelen <schuffelen@google.com>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Diffstat (limited to 'disk')
-rw-r--r--disk/src/qcow/mod.rs54
-rw-r--r--disk/src/qcow/qcow_raw_file.rs12
2 files changed, 57 insertions, 9 deletions
diff --git a/disk/src/qcow/mod.rs b/disk/src/qcow/mod.rs
index dfc8acd..c5e119d 100644
--- a/disk/src/qcow/mod.rs
+++ b/disk/src/qcow/mod.rs
@@ -1065,7 +1065,7 @@ impl QcowFile {
             let l2_table = if l2_addr_disk == 0 {
                 // Allocate a new cluster to store the L2 table and update the L1 table to point
                 // to the new table.
-                let new_addr: u64 = self.get_new_cluster()?;
+                let new_addr: u64 = self.get_new_cluster(None)?;
                 // The cluster refcount starts at one meaning it is used but doesn't need COW.
                 set_refcounts.push((new_addr, 1));
                 self.l1_table[l1_index] = new_addr;
@@ -1086,8 +1086,19 @@ impl QcowFile {
 
         let cluster_addr = match self.l2_cache.get(&l1_index).unwrap()[l2_index] {
             0 => {
+                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];
+                    let raw_slice = cluster_data.as_mut_slice();
+                    let volatile_slice = raw_slice.get_slice(0, cluster_size).unwrap();
+                    backing.read_exact_at_volatile(volatile_slice, cluster_begin)?;
+                    Some(cluster_data)
+                } else {
+                    None
+                };
                 // Need to allocate a data cluster
-                let cluster_addr = self.append_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
             }
@@ -1124,7 +1135,7 @@ impl QcowFile {
             // Allocate a new cluster to store the L2 table and update the L1 table to point
             // to the new table. The cluster will be written when the cache is flushed, no
             // need to copy the data now.
-            let new_addr: u64 = self.get_new_cluster()?;
+            let new_addr: u64 = self.get_new_cluster(None)?;
             // The cluster refcount starts at one indicating it is used but doesn't need
             // COW.
             set_refcounts.push((new_addr, 1));
@@ -1136,15 +1147,22 @@ impl QcowFile {
     }
 
     // Allocate a new cluster and return its offset within the raw file.
-    fn get_new_cluster(&mut self) -> std::io::Result<u64> {
+    fn get_new_cluster(&mut self, initial_data: Option<Vec<u8>>) -> std::io::Result<u64> {
         // First use a pre allocated cluster if one is available.
         if let Some(free_cluster) = self.avail_clusters.pop() {
-            self.raw_file.zero_cluster(free_cluster)?;
+            if let Some(initial_data) = initial_data {
+                self.raw_file.write_cluster(free_cluster, initial_data)?;
+            } else {
+                self.raw_file.zero_cluster(free_cluster)?;
+            }
             return Ok(free_cluster);
         }
 
         let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset();
         if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? {
+            if let Some(initial_data) = initial_data {
+                self.raw_file.write_cluster(new_cluster, initial_data)?;
+            }
             Ok(new_cluster)
         } else {
             error!("No free clusters in get_new_cluster()");
@@ -1154,8 +1172,8 @@ impl QcowFile {
 
     // Allocate and initialize a new data cluster. Returns the offset of the
     // cluster in to the file on success.
-    fn append_data_cluster(&mut self) -> std::io::Result<u64> {
-        let new_addr: u64 = self.get_new_cluster()?;
+    fn append_data_cluster(&mut self, initial_data: Option<Vec<u8>>) -> std::io::Result<u64> {
+        let new_addr: u64 = self.get_new_cluster(initial_data)?;
         // The cluster refcount starts at one indicating it is used but doesn't need COW.
         let mut newly_unref = self.set_cluster_refcount(new_addr, 1)?;
         self.unref_clusters.append(&mut newly_unref);
@@ -1386,7 +1404,7 @@ impl QcowFile {
                 }
                 Err(refcount::Error::NeedNewCluster) => {
                     // Allocate the cluster and call set_cluster_refcount again.
-                    let addr = self.get_new_cluster()?;
+                    let addr = self.get_new_cluster(None)?;
                     added_clusters.push(addr);
                     new_cluster = Some((
                         addr,
@@ -1996,6 +2014,26 @@ mod tests {
     }
 
     #[test]
+    fn write_read_start_backing_overlap() {
+        let disk_file = basic_file(&valid_header());
+        let mut backing = QcowFile::from(disk_file).unwrap();
+        backing
+            .write(b"test first bytes")
+            .expect("Failed to write test string.");
+        let wrapping_disk_file = basic_file(&valid_header());
+        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
+            .write(b"TEST")
+            .expect("Failed to write second test string.");
+        let mut buf = [0u8; 10];
+        wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
+        wrapping.read(&mut buf).expect("Failed to read.");
+        assert_eq!(&buf, b"TEST first");
+    }
+
+    #[test]
     fn offset_write_read() {
         with_basic_file(&valid_header(), |disk_file: File| {
             let mut q = QcowFile::from(disk_file).unwrap();
diff --git a/disk/src/qcow/qcow_raw_file.rs b/disk/src/qcow/qcow_raw_file.rs
index ede28d8..09d2176 100644
--- a/disk/src/qcow/qcow_raw_file.rs
+++ b/disk/src/qcow/qcow_raw_file.rs
@@ -6,7 +6,8 @@ use std::fs::File;
 use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write};
 use std::mem::size_of;
 
-use sys_util::WriteZeroes;
+use data_model::VolatileMemory;
+use sys_util::{FileReadWriteAtVolatile, WriteZeroes};
 
 /// A qcow file. Allows reading/writing clusters and appending clusters.
 #[derive(Debug)]
@@ -145,4 +146,13 @@ impl QcowRawFile {
         self.file.write_zeroes_all(cluster_size)?;
         Ok(())
     }
+
+    /// Writes
+    pub fn write_cluster(&mut self, address: u64, mut initial_data: Vec<u8>) -> io::Result<()> {
+        let raw_slice = initial_data.as_mut_slice();
+        let volatile_slice = raw_slice
+            .get_slice(0, self.cluster_size)
+            .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{:?}", e)))?;
+        self.file.write_all_at_volatile(volatile_slice, address)
+    }
 }