summary refs log tree commit diff
path: root/qcow
diff options
context:
space:
mode:
authorDylan Reid <dgreid@chromium.org>2018-07-13 10:42:48 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-07-16 03:42:07 -0700
commit2dcb632405718305b7443501bd3839987e9971f7 (patch)
tree56ca608f62d68845cb70fb4f74b9797f4c78428b /qcow
parent5ad21740e4fbb4a10c1395396d058b85cd6c9dab (diff)
downloadcrosvm-2dcb632405718305b7443501bd3839987e9971f7.tar
crosvm-2dcb632405718305b7443501bd3839987e9971f7.tar.gz
crosvm-2dcb632405718305b7443501bd3839987e9971f7.tar.bz2
crosvm-2dcb632405718305b7443501bd3839987e9971f7.tar.lz
crosvm-2dcb632405718305b7443501bd3839987e9971f7.tar.xz
crosvm-2dcb632405718305b7443501bd3839987e9971f7.tar.zst
crosvm-2dcb632405718305b7443501bd3839987e9971f7.zip
qcow: Set refcounts for initial clusters.
All qcow clusters need to have their refcounts set. Add a `new` method
to `Qcowfile` and use it instead of just headers from the library.

The new method will loop over the initial clusters and initialize their
refcounts.

Add a `create_qcow2` option to the main executable so there is a way to
test image creation that doesn't require DBUS and Concierge.

BUG=none
TEST='crosvm create_qcow2 /tmp/file.qcow2 1000000'
'qemu-img check /tmp/file.qcow2'
no errors reported.

Change-Id: I8798df5942fb23f79cc7ca86820d0783d1f2b608
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1136900
Reviewed-by: Stephen Barber <smbarber@chromium.org>
Diffstat (limited to 'qcow')
-rw-r--r--qcow/src/qcow.rs113
1 files changed, 88 insertions, 25 deletions
diff --git a/qcow/src/qcow.rs b/qcow/src/qcow.rs
index d4cb6af..9124ab1 100644
--- a/qcow/src/qcow.rs
+++ b/qcow/src/qcow.rs
@@ -17,13 +17,18 @@ use std::os::unix::io::{AsRawFd, RawFd};
 #[derive(Debug)]
 pub enum Error {
     BackingFilesNotSupported,
+    GettingFileSize(io::Error),
+    GettingRefcount(io::Error),
     InvalidClusterSize,
     InvalidL1TableOffset,
     InvalidMagic,
     InvalidOffset(u64),
     InvalidRefcountTableOffset,
     NoRefcountClusters,
+    OpeningFile(io::Error),
     ReadingHeader(io::Error),
+    SeekingFile(io::Error),
+    SettingRefcountRefcount(io::Error),
     SizeTooSmallForNumberOfClusters,
     WritingHeader(io::Error),
     UnsupportedRefcountOrder,
@@ -297,6 +302,45 @@ impl QcowFile {
         Ok(qcow)
     }
 
+    /// Creates a new QcowFile at the given path.
+    pub fn new(mut file: File, virtual_size: u64) -> Result<QcowFile> {
+        let header = QcowHeader::create_for_size(virtual_size);
+        file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
+        header.write_to(&mut file)?;
+
+        let mut qcow = Self::from(file)?;
+
+        // Set the refcount for each refcount table cluster.
+        let cluster_size = 0x01u64 << qcow.header.cluster_bits;
+        let refcount_table_base = qcow.header.refcount_table_offset as u64;
+        let end_cluster_addr = refcount_table_base +
+            u64::from(qcow.header.refcount_table_clusters) * cluster_size;
+
+        let mut cluster_addr = 0;
+        while cluster_addr < end_cluster_addr {
+            qcow.set_cluster_refcount(cluster_addr, 1).map_err(Error::SettingRefcountRefcount)?;
+            cluster_addr += cluster_size;
+        }
+
+        Ok(qcow)
+    }
+
+    /// Returns the first cluster in the file with a 0 refcount. Used for testing.
+    pub fn first_zero_refcount(&mut self) -> Result<Option<u64>> {
+        let file_size = self.file.metadata().map_err(Error::GettingFileSize)?.len();
+        let cluster_size = 0x01u64 << self.header.cluster_bits;
+
+        let mut cluster_addr = 0;
+        while cluster_addr < file_size {
+            match self.get_cluster_refcount(cluster_addr).map_err(Error::GettingRefcount)? {
+                0 => return Ok(Some(cluster_addr)),
+                _ => (),
+            }
+            cluster_addr += cluster_size;
+        }
+        Ok(None)
+    }
+
     // Limits the range so that it doesn't exceed the virtual size of the file.
     fn limit_range_file(&self, address: u64, count: usize) -> usize {
         if address.checked_add(count as u64).is_none() || address > self.virtual_size() {
@@ -416,31 +460,52 @@ impl QcowFile {
         Ok(new_addr)
     }
 
-    // Set the refcount for a cluster with the given address.
-    fn set_cluster_refcount(&mut self, address: u64, refcount: u16) -> std::io::Result<()> {
+    // 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;
         let refcount_block_entries = cluster_size * size_of::<u64>() as u64 / self.refcount_bits;
-        let refcount_block_index = (address / cluster_size) % refcount_block_entries;
+        let block_index = (address / cluster_size) % refcount_block_entries;
         let refcount_table_index = (address / cluster_size) / refcount_block_entries;
         let refcount_block_entry_addr = self.header.refcount_table_offset
                 .checked_add(refcount_table_index * size_of::<u64>() as u64)
                 .ok_or_else(|| std::io::Error::from_raw_os_error(EINVAL))?;
-        let refcount_block_address_from_file =
-            read_u64_from_offset(&mut self.file, refcount_block_entry_addr)?;
-        let refcount_block_address = if refcount_block_address_from_file == 0 {
+        Ok((refcount_block_entry_addr, block_index))
+    }
+
+    // Set the refcount for a cluster with the given address.
+    fn set_cluster_refcount(&mut self, address: u64, refcount: u16) -> std::io::Result<()> {
+        let (entry_addr, block_index) = self.get_refcount_block(address)?;
+        let stored_addr = read_u64_from_offset(&mut self.file, entry_addr)?;
+        let refcount_block_address = if stored_addr == 0 {
             let new_addr = self.append_new_cluster()?;
-            write_u64_to_offset(&mut self.file, refcount_block_entry_addr, new_addr)?;
+            write_u64_to_offset(&mut self.file, entry_addr, new_addr)?;
             self.set_cluster_refcount(new_addr, 1)?;
             new_addr
         } else {
-            refcount_block_address_from_file
+            stored_addr
         };
         let refcount_address: u64 = refcount_block_address
-                .checked_add(refcount_block_index * 2)
+                .checked_add(block_index * 2)
                 .ok_or_else(|| std::io::Error::from_raw_os_error(EINVAL))?;
         self.file.seek(SeekFrom::Start(refcount_address))?;
         self.file.write_u16::<BigEndian>(refcount)
     }
+
+    // Gets the refcount for a cluster with the given address.
+    fn get_cluster_refcount(&mut self, address: u64) -> std::io::Result<u16> {
+        let (entry_addr, block_index) = self.get_refcount_block(address)?;
+        let stored_addr = read_u64_from_offset(&mut self.file, entry_addr)?;
+        let refcount_block_address = if stored_addr == 0 {
+            return Ok(0);
+        } else {
+            stored_addr
+        };
+        let refcount_address: u64 = refcount_block_address
+                .checked_add(block_index * 2)
+                .ok_or_else(|| std::io::Error::from_raw_os_error(EINVAL))?;
+        self.file.seek(SeekFrom::Start(refcount_address))?;
+        self.file.read_u16::<BigEndian>()
+    }
 }
 
 impl AsRawFd for QcowFile {
@@ -618,15 +683,12 @@ mod tests {
 
     fn with_default_file<F>(file_size: u64, mut testfn: F)
     where
-        F: FnMut(File),
+        F: FnMut(QcowFile),
     {
         let shm = SharedMemory::new(None).unwrap();
-        let mut disk_file: File = shm.into();
-        let header = QcowHeader::create_for_size(file_size);
-        header.write_to(&mut disk_file).unwrap();
-        disk_file.seek(SeekFrom::Start(0)).unwrap();
+        let qcow_file = QcowFile::new(shm.into(), file_size).unwrap();
 
-        testfn(disk_file); // File closed when the function exits.
+        testfn(qcow_file); // File closed when the function exits.
     }
 
     #[test]
@@ -828,29 +890,28 @@ mod tests {
 
     #[test]
     fn combo_write_read() {
-        with_default_file(1024 * 1024 * 1024 * 256, |disk_file: File| {
+        with_default_file(1024 * 1024 * 1024 * 256, |mut qcow_file| {
             const NUM_BLOCKS: usize = 555;
             const BLOCK_SIZE: usize = 0x1_0000;
             const OFFSET: usize = 0x1_0000_0020;
-            let mut q = QcowFile::from(disk_file).unwrap();
             let data = [0x55u8; BLOCK_SIZE];
             let mut readback = [0u8; BLOCK_SIZE];
             for i in 0..NUM_BLOCKS {
                 let seek_offset = OFFSET + i * BLOCK_SIZE;
-                q.seek(SeekFrom::Start(seek_offset as u64)).expect("Failed to seek.");
-                let nwritten = q.write(&data).expect("Failed to write test data.");
+                qcow_file.seek(SeekFrom::Start(seek_offset as u64)).expect("Failed to seek.");
+                let nwritten = qcow_file.write(&data).expect("Failed to write test data.");
                 assert_eq!(nwritten, BLOCK_SIZE);
                 // Read back the data to check it was written correctly.
-                q.seek(SeekFrom::Start(seek_offset as u64)).expect("Failed to seek.");
-                let nread = q.read(&mut readback).expect("Failed to read.");
+                qcow_file.seek(SeekFrom::Start(seek_offset as u64)).expect("Failed to seek.");
+                let nread = qcow_file.read(&mut readback).expect("Failed to read.");
                 assert_eq!(nread, BLOCK_SIZE);
                 for (orig, read) in data.iter().zip(readback.iter()) {
                     assert_eq!(orig, read);
                 }
             }
             // Check that address 0 is still zeros.
-            q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
-            let nread = q.read(&mut readback).expect("Failed to read.");
+            qcow_file.seek(SeekFrom::Start(0)).expect("Failed to seek.");
+            let nread = qcow_file.read(&mut readback).expect("Failed to read.");
             assert_eq!(nread, BLOCK_SIZE);
             for read in readback.iter() {
                 assert_eq!(*read, 0);
@@ -858,13 +919,15 @@ mod tests {
             // Check the data again after the writes have happened.
             for i in 0..NUM_BLOCKS {
                 let seek_offset = OFFSET + i * BLOCK_SIZE;
-                q.seek(SeekFrom::Start(seek_offset as u64)).expect("Failed to seek.");
-                let nread = q.read(&mut readback).expect("Failed to read.");
+                qcow_file.seek(SeekFrom::Start(seek_offset as u64)).expect("Failed to seek.");
+                let nread = qcow_file.read(&mut readback).expect("Failed to read.");
                 assert_eq!(nread, BLOCK_SIZE);
                 for (orig, read) in data.iter().zip(readback.iter()) {
                     assert_eq!(orig, read);
                 }
             }
+
+            assert_eq!(qcow_file.first_zero_refcount().unwrap(), None);
         });
     }
 }