summary refs log tree commit diff
path: root/qcow
diff options
context:
space:
mode:
authorDaniel Verkamp <dverkamp@chromium.org>2018-08-23 10:00:06 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-09-05 01:25:50 -0700
commit9c7cd8632563ba50d1b0452b2ac1a8defd63e5f3 (patch)
tree0c3b3dadd977ffe0071622f6316bb7642d61db98 /qcow
parent95a8868aef2c1018b13b30f3afc2e11bb3ae2066 (diff)
downloadcrosvm-9c7cd8632563ba50d1b0452b2ac1a8defd63e5f3.tar
crosvm-9c7cd8632563ba50d1b0452b2ac1a8defd63e5f3.tar.gz
crosvm-9c7cd8632563ba50d1b0452b2ac1a8defd63e5f3.tar.bz2
crosvm-9c7cd8632563ba50d1b0452b2ac1a8defd63e5f3.tar.lz
crosvm-9c7cd8632563ba50d1b0452b2ac1a8defd63e5f3.tar.xz
crosvm-9c7cd8632563ba50d1b0452b2ac1a8defd63e5f3.tar.zst
crosvm-9c7cd8632563ba50d1b0452b2ac1a8defd63e5f3.zip
qcow: deallocate clusters in write_zeroes
When a write_zeroes call covers a whole cluster, we can deallocate the
storage for that cluster rather than writing zeroes.

This is currently implemented by removing the cluster allocation from
the mapping tables, then attempting to release the backing storage using
fallocate() with FALLOC_FL_PUNCH_HOLE.

BUG=chromium:850998
TEST=cargo test -p qcow

Change-Id: Ie4edb2e02bfaa1df9a19919b77eeb3c58c112d1c
Signed-off-by: Daniel Verkamp <dverkamp@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1187019
Reviewed-by: Dylan Reid <dgreid@chromium.org>
Diffstat (limited to 'qcow')
-rw-r--r--qcow/src/qcow.rs85
1 files changed, 77 insertions, 8 deletions
diff --git a/qcow/src/qcow.rs b/qcow/src/qcow.rs
index 7402402..781eb85 100644
--- a/qcow/src/qcow.rs
+++ b/qcow/src/qcow.rs
@@ -15,7 +15,7 @@ use std::io::{self, Read, Seek, SeekFrom, Write};
 use std::mem::size_of;
 use std::os::unix::io::{AsRawFd, RawFd};
 
-use sys_util::WriteZeroes;
+use sys_util::{fallocate, FallocateMode, WriteZeroes};
 
 #[derive(Debug)]
 pub enum Error {
@@ -463,6 +463,70 @@ impl QcowFile {
         Ok(new_addr)
     }
 
+    // Deallocate the storage for the cluster starting at `address`.
+    // Any future reads of this cluster will return all zeroes.
+    fn deallocate_cluster(&mut self, address: u64) -> std::io::Result<()> {
+        if address >= self.virtual_size() as u64 {
+            return Err(std::io::Error::from_raw_os_error(EINVAL));
+        }
+
+        let l1_entry_offset: u64 = self.header.l1_table_offset + self.l1_address_offset(address);
+        if l1_entry_offset >= self.file.metadata()?.len() {
+            // L1 table is not allocated, so the cluster must also be unallocated.
+            return Ok(());
+        }
+
+        let l2_addr_disk = read_u64_from_offset(&mut self.file, l1_entry_offset)?;
+        let l2_addr: u64 = l2_addr_disk & L1_TABLE_OFFSET_MASK;
+        if l2_addr == 0 {
+            // The whole L2 table for this address is not allocated yet,
+            // so the cluster must also be unallocated.
+            return Ok(());
+        }
+
+        let l2_entry_addr: u64 = l2_addr.checked_add(self.l2_address_offset(address))
+            .ok_or_else(|| std::io::Error::from_raw_os_error(EINVAL))?;
+        let cluster_addr_disk: u64 = read_u64_from_offset(&mut self.file, l2_entry_addr)?;
+        let cluster_addr: u64 = cluster_addr_disk & L2_TABLE_OFFSET_MASK;
+
+        if cluster_addr_disk & COMPRESSED_FLAG != 0 {
+            // Don't attempt to deallocate compressed sectors
+            return Err(std::io::Error::from_raw_os_error(ENOTSUP));
+        }
+
+        if cluster_addr == 0 {
+            // This cluster was already unallocated; nothing to do.
+            return Ok(());
+        }
+
+        // Decrement the refcount.
+        let refcount = self.get_cluster_refcount(cluster_addr)?;
+        if refcount == 0 {
+            return Err(std::io::Error::from_raw_os_error(EINVAL));
+        }
+
+        let new_refcount = refcount - 1;
+        self.set_cluster_refcount(cluster_addr, new_refcount)?;
+
+        // Rewrite the L2 entry to remove the cluster mapping.
+        write_u64_to_offset(&mut self.file, l2_entry_addr, 0)?;
+        self.file.sync_data()?;
+
+        if new_refcount == 0 {
+            // This cluster is no longer in use; deallocate the storage.
+            if let Err(_) = fallocate(
+                &self.file, FallocateMode::PunchHole, true,
+                cluster_addr, self.cluster_size
+            ) {
+                // The underlying FS may not support FALLOC_FL_PUNCH_HOLE,
+                // so don't treat this error as fatal.  Fall back to zeroing the data.
+                self.file.seek(SeekFrom::Start(cluster_addr))?;
+                self.file.write_zeroes(self.cluster_size as usize)?;
+            };
+        }
+        Ok(())
+    }
+
     // Gets the address of the refcount block and the index into the block for the given address.
     fn get_refcount_block(&self, address: u64) -> std::io::Result<(u64, u64)> {
         let cluster_size: u64 = self.cluster_size;
@@ -619,13 +683,18 @@ impl WriteZeroes for QcowFile {
             let curr_addr = address + nwritten as u64;
             let count = self.limit_range_cluster(curr_addr, write_count - nwritten);
 
-            // Zero out space that was previously allocated.
-            // Any space in unallocated clusters can be left alone, since
-            // unallocated clusters already read back as zeroes.
-            if let Some(offset) = self.file_offset(curr_addr, false)? {
-                // Space was previously allocated for this offset - zero it out.
-                self.file.seek(SeekFrom::Start(offset))?;
-                self.file.write_zeroes(count)?;
+            if count == self.cluster_size as usize {
+                // Full cluster - deallocate the storage.
+                self.deallocate_cluster(curr_addr)?;
+            } else {
+                // Partial cluster - zero out the relevant bytes if it was allocated.
+                // Any space in unallocated clusters can be left alone, since
+                // unallocated clusters already read back as zeroes.
+                if let Some(offset) = self.file_offset(curr_addr, false)? {
+                    // Partial cluster - zero it out.
+                    self.file.seek(SeekFrom::Start(offset))?;
+                    self.file.write_zeroes(count)?;
+                }
             }
 
             nwritten += count;