summary refs log tree commit diff
path: root/qcow
diff options
context:
space:
mode:
authorDaniel Verkamp <dverkamp@chromium.org>2018-08-22 14:32:20 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-09-05 01:25:50 -0700
commit95a8868aef2c1018b13b30f3afc2e11bb3ae2066 (patch)
tree05860990f264b62389a3b767bf4ba8697b28ac28 /qcow
parenta3d11edaa6d728adcad0693ab63a7e9e49690126 (diff)
downloadcrosvm-95a8868aef2c1018b13b30f3afc2e11bb3ae2066.tar
crosvm-95a8868aef2c1018b13b30f3afc2e11bb3ae2066.tar.gz
crosvm-95a8868aef2c1018b13b30f3afc2e11bb3ae2066.tar.bz2
crosvm-95a8868aef2c1018b13b30f3afc2e11bb3ae2066.tar.lz
crosvm-95a8868aef2c1018b13b30f3afc2e11bb3ae2066.tar.xz
crosvm-95a8868aef2c1018b13b30f3afc2e11bb3ae2066.tar.zst
crosvm-95a8868aef2c1018b13b30f3afc2e11bb3ae2066.zip
qcow: implement WriteZeroes for QcowFile
Add a simple implementation of WriteZeroes for QcowFile that just
writes zeroes to allocated clusters and skips clusters that are already
unallocated (since they already read back as zeroes).

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

Change-Id: I8f26c8cc4016c129850aaf08c7188dfe08d6dacb
Signed-off-by: Daniel Verkamp <dverkamp@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1187018
Reviewed-by: Dylan Reid <dgreid@chromium.org>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'qcow')
-rw-r--r--qcow/Cargo.toml2
-rw-r--r--qcow/src/qcow.rs79
2 files changed, 76 insertions, 5 deletions
diff --git a/qcow/Cargo.toml b/qcow/Cargo.toml
index 366449e..980d0d6 100644
--- a/qcow/Cargo.toml
+++ b/qcow/Cargo.toml
@@ -9,6 +9,4 @@ path = "src/qcow.rs"
 [dependencies]
 byteorder = "*"
 libc = "*"
-
-[dev-dependencies]
 sys_util = { path = "../sys_util" }
diff --git a/qcow/src/qcow.rs b/qcow/src/qcow.rs
index 2d49968..7402402 100644
--- a/qcow/src/qcow.rs
+++ b/qcow/src/qcow.rs
@@ -4,6 +4,7 @@
 
 extern crate byteorder;
 extern crate libc;
+extern crate sys_util;
 
 use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
 use libc::{EINVAL, ENOTSUP};
@@ -14,6 +15,8 @@ use std::io::{self, Read, Seek, SeekFrom, Write};
 use std::mem::size_of;
 use std::os::unix::io::{AsRawFd, RawFd};
 
+use sys_util::WriteZeroes;
+
 #[derive(Debug)]
 pub enum Error {
     BackingFilesNotSupported,
@@ -606,6 +609,32 @@ impl Write for QcowFile {
     }
 }
 
+impl WriteZeroes for QcowFile {
+    fn write_zeroes(&mut self, length: usize) -> std::io::Result<usize> {
+        let address: u64 = self.current_offset as u64;
+        let write_count: usize = self.limit_range_file(address, length);
+
+        let mut nwritten: usize = 0;
+        while nwritten < write_count {
+            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)?;
+            }
+
+            nwritten += count;
+        }
+        self.current_offset += length as u64;
+        Ok(length)
+    }
+}
+
 // Returns an Error if the given offset doesn't align to a cluster boundary.
 fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
     if offset & ((0x01 << cluster_bits) - 1) != 0 {
@@ -637,9 +666,6 @@ fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
 }
 
 #[cfg(test)]
-extern crate sys_util;
-
-#[cfg(test)]
 mod tests {
     use std::fs::File;
     use std::io::{Read, Seek, SeekFrom, Write};
@@ -754,6 +780,53 @@ mod tests {
     }
 
     #[test]
+    fn write_zeroes_read() {
+        with_basic_file(&valid_header(), |disk_file: File| {
+            let mut q = QcowFile::from(disk_file).unwrap();
+            // Write some test data.
+            let b = [0x55u8; 0x1000];
+            q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
+            q.write(&b).expect("Failed to write test string.");
+            // Overwrite the test data with zeroes.
+            q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
+            let nwritten = q.write_zeroes(0x200).expect("Failed to write zeroes.");
+            assert_eq!(nwritten, 0x200);
+            // Verify that the correct part of the data was zeroed out.
+            let mut buf = [0u8; 0x1000];
+            q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
+            q.read(&mut buf).expect("Failed to read.");
+            assert_eq!(buf[0], 0);
+            assert_eq!(buf[0x1FF], 0);
+            assert_eq!(buf[0x200], 0x55);
+            assert_eq!(buf[0xFFF], 0x55);
+        });
+    }
+
+    #[test]
+    fn write_zeroes_full_cluster() {
+        // Choose a size that is larger than a cluster.
+        // valid_header uses cluster_bits = 12, which corresponds to a cluster size of 4096.
+        const CHUNK_SIZE: usize = 4096 * 2 + 512;
+        with_basic_file(&valid_header(), |disk_file: File| {
+            let mut q = QcowFile::from(disk_file).unwrap();
+            // Write some test data.
+            let b = [0x55u8; CHUNK_SIZE];
+            q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
+            q.write(&b).expect("Failed to write test string.");
+            // Overwrite the full cluster with zeroes.
+            q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
+            let nwritten = q.write_zeroes(CHUNK_SIZE).expect("Failed to write zeroes.");
+            assert_eq!(nwritten, CHUNK_SIZE);
+            // Verify that the data was zeroed out.
+            let mut buf = [0u8; CHUNK_SIZE];
+            q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
+            q.read(&mut buf).expect("Failed to read.");
+            assert_eq!(buf[0], 0);
+            assert_eq!(buf[CHUNK_SIZE - 1], 0);
+        });
+    }
+
+    #[test]
     fn test_header() {
         with_basic_file(&valid_header(), |disk_file: File| {
             let q = QcowFile::from(disk_file).unwrap();