summary refs log tree commit diff
diff options
context:
space:
mode:
authorDylan Reid <dgreid@chromium.org>2017-12-13 09:09:40 -0800
committerchrome-bot <chrome-bot@chromium.org>2018-01-19 23:29:52 -0800
commit76968703ad9f5c4edb4c533026ee35b6bd54a3d2 (patch)
treefb9e20a74397d184f794cc32362fc6e0075aa492
parente56e045b18259399b424a0dd2cb8c53309478b71 (diff)
downloadcrosvm-76968703ad9f5c4edb4c533026ee35b6bd54a3d2.tar
crosvm-76968703ad9f5c4edb4c533026ee35b6bd54a3d2.tar.gz
crosvm-76968703ad9f5c4edb4c533026ee35b6bd54a3d2.tar.bz2
crosvm-76968703ad9f5c4edb4c533026ee35b6bd54a3d2.tar.lz
crosvm-76968703ad9f5c4edb4c533026ee35b6bd54a3d2.tar.xz
crosvm-76968703ad9f5c4edb4c533026ee35b6bd54a3d2.tar.zst
crosvm-76968703ad9f5c4edb4c533026ee35b6bd54a3d2.zip
Add qcow support crate
QCOW2 files will be used to store the VM's data on ChromeOS. Add
support for basic reading and writing of qcow files.

CQ-DEPEND=CL:872451
BUG=none
TEST=qcow unit tests and use "qemu-image check" to verify generated qcow
files.

Change-Id: I4b9e77e58edca69f894ede19e79ebd2cd7b2623e
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/862628
Reviewed-by: Stephen Barber <smbarber@chromium.org>
-rw-r--r--Cargo.lock17
-rw-r--r--Cargo.toml1
-rw-r--r--qcow/Cargo.toml14
-rw-r--r--qcow/src/qcow.rs690
4 files changed, 722 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d09fc4c..954c1ed 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -14,6 +14,7 @@ dependencies = [
  "kernel_loader 0.1.0",
  "kvm 0.1.0",
  "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
+ "qcow 0.1.0",
  "sys_util 0.1.0",
  "vm_control 0.1.0",
  "x86_64 0.1.0",
@@ -45,6 +46,11 @@ version = "0.3.54"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "getopts"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
 name = "io_jail"
 version = "0.1.0"
 dependencies = [
@@ -98,6 +104,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "qcow"
+version = "0.1.0"
+dependencies = [
+ "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sys_util 0.1.0",
+]
+
+[[package]]
 name = "sys_util"
 version = "0.1.0"
 dependencies = [
@@ -154,4 +170,5 @@ dependencies = [
 [metadata]
 "checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d"
 "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
+"checksum getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "65922871abd2f101a2eb0eaebadc66668e54a87ad9c3dd82520b5f86ede5eff9"
 "checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0"
diff --git a/Cargo.toml b/Cargo.toml
index 98c824c..759f352 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@ libc = "=0.2.34"
 byteorder = "=1.1.0"
 vm_control = { path = "vm_control" }
 data_model = { path = "data_model" }
+qcow = { path = "qcow" }
 
 [target.'cfg(target_arch = "x86_64")'.dependencies]
 x86_64 = { path = "x86_64" }
diff --git a/qcow/Cargo.toml b/qcow/Cargo.toml
new file mode 100644
index 0000000..366449e
--- /dev/null
+++ b/qcow/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "qcow"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+
+[lib]
+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
new file mode 100644
index 0000000..c352e65
--- /dev/null
+++ b/qcow/src/qcow.rs
@@ -0,0 +1,690 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+extern crate byteorder;
+extern crate libc;
+
+use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
+use libc::{EINVAL, ENOTSUP};
+
+use std::cmp::min;
+use std::fs::File;
+use std::io::{self, Read, Seek, SeekFrom, Write};
+use std::mem::size_of;
+use std::os::unix::io::{AsRawFd, RawFd};
+
+#[derive(Debug)]
+pub enum Error {
+    BackingFilesNotSupported,
+    InvalidClusterSize,
+    InvalidL1TableOffset,
+    InvalidMagic,
+    InvalidOffset(u64),
+    InvalidRefcountTableOffset,
+    NoRefcountClusters,
+    ReadingHeader(io::Error),
+    SizeTooSmallForNumberOfClusters,
+    UnsupportedRefcountOrder,
+    UnsupportedVersion(u32),
+}
+pub type Result<T> = std::result::Result<T, Error>;
+
+const MAX_CLUSTER_BITS: u32 = 30;
+// bits 0-8 and 56-63 are reserved.
+const L1_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
+const L2_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
+// Flags
+const COMPRESSED_FLAG: u64 = 1 << 62;
+const CLUSTER_USED_FLAG: u64 = 1 << 63;
+
+/// Contains the information from the header of a qcow file.
+#[derive(Debug)]
+pub struct QcowHeader {
+    pub magic: u32,
+    pub version: u32,
+
+    pub backing_file_offset: u64,
+    pub backing_file_size: u32,
+
+    pub cluster_bits: u32,
+    pub size: u64,
+    pub crypt_method: u32,
+
+    pub l1_size: u32,
+    pub l1_table_offset: u64,
+
+    pub refcount_table_offset: u64,
+    pub refcount_table_clusters: u32,
+
+    pub nb_snapshots: u32,
+    pub snapshots_offset: u64,
+
+    // v3 entries
+    pub incompatible_features: u64,
+    pub compatible_features: u64,
+    pub autoclear_features: u64,
+    pub refcount_order: u32,
+    pub header_size: u32,
+}
+
+impl QcowHeader {
+    /// Creates a QcowHeader from a reference to a file.
+    pub fn new(f: &mut File) -> Result<QcowHeader> {
+        const QCOW_MAGIC: u32 = 0x5146_49fb;
+        f.seek(SeekFrom::Start(0)).map_err(Error::ReadingHeader)?;
+        let magic = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
+        if magic != QCOW_MAGIC {
+            return Err(Error::InvalidMagic);
+        }
+
+        // Reads the next u32 from the file.
+        fn read_u32_from_file(f: &mut File) -> Result<u32> {
+            f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)
+        }
+
+        // Reads the next u64 from the file.
+        fn read_u64_from_file(f: &mut File) -> Result<u64> {
+            f.read_u64::<BigEndian>().map_err(Error::ReadingHeader)
+        }
+
+        Ok(QcowHeader {
+            magic: magic,
+            version: read_u32_from_file(f)?,
+            backing_file_offset: read_u64_from_file(f)?,
+            backing_file_size: read_u32_from_file(f)?,
+            cluster_bits: read_u32_from_file(f)?,
+            size: read_u64_from_file(f)?,
+            crypt_method: read_u32_from_file(f)?,
+            l1_size: read_u32_from_file(f)?,
+            l1_table_offset: read_u64_from_file(f)?,
+            refcount_table_offset: read_u64_from_file(f)?,
+            refcount_table_clusters: read_u32_from_file(f)?,
+            nb_snapshots: read_u32_from_file(f)?,
+            snapshots_offset: read_u64_from_file(f)?,
+            incompatible_features: read_u64_from_file(f)?,
+            compatible_features: read_u64_from_file(f)?,
+            autoclear_features: read_u64_from_file(f)?,
+            refcount_order: read_u32_from_file(f)?,
+            header_size: read_u32_from_file(f)?,
+        })
+    }
+}
+
+/// Represents a qcow2 file. This is a sparse file format maintained by the qemu project.
+/// Full documentation of the format can be found in the qemu repository.
+///
+/// # Example
+///
+/// ```
+/// # use std::io::{Read, Seek, SeekFrom};
+/// # use qcow::{self, QcowFile};
+/// # fn test(file: std::fs::File) -> std::io::Result<()> {
+///     let mut q = QcowFile::from(file).expect("Can't open qcow file");
+///     let mut buf = [0u8; 12];
+///     q.seek(SeekFrom::Start(10 as u64))?;
+///     q.read(&mut buf[..])?;
+/// #   Ok(())
+/// # }
+/// ```
+#[derive(Debug)]
+pub struct QcowFile {
+    file: File,
+    header: QcowHeader,
+    l2_entries: u64,
+    cluster_size: u64,
+    cluster_mask: u64,
+    current_offset: u64,
+    refcount_bits: u64,
+    //TODO(dgreid) Add support for backing files. - backing_file: Option<Box<QcowFile<T>>>,
+}
+
+impl QcowFile {
+    /// Creates a QcowFile from `file`. File must be a valid qcow2 image.
+    pub fn from(mut file: File) -> Result<QcowFile> {
+        let header = QcowHeader::new(&mut file)?;
+
+        // Only v3 files are supported.
+        if header.version != 3 {
+            return Err(Error::UnsupportedVersion(header.version));
+        }
+
+        let cluster_bits: u32 = header.cluster_bits;
+        if cluster_bits > MAX_CLUSTER_BITS {
+            return Err(Error::InvalidClusterSize);
+        }
+        let cluster_size = 0x01u64 << cluster_bits;
+        if cluster_size < size_of::<u64>() as u64 {
+            // Can't fit an offset in a cluster, nothing is going to work.
+            return Err(Error::InvalidClusterSize);
+        }
+
+        // No current support for backing files.
+        if header.backing_file_offset != 0 {
+            return Err(Error::BackingFilesNotSupported);
+        }
+
+        // Only support two byte refcounts.
+        let refcount_bits: u64 = 0x01u64
+            .checked_shl(header.refcount_order)
+            .ok_or(Error::UnsupportedRefcountOrder)?;
+        if refcount_bits != 16 {
+            return Err(Error::UnsupportedRefcountOrder);
+        }
+
+        // Need at least one refcount cluster
+        if header.refcount_table_clusters == 0 {
+            return Err(Error::NoRefcountClusters);
+        }
+        offset_is_cluster_boundary(header.backing_file_offset, header.cluster_bits)?;
+        offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?;
+        offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?;
+        offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?;
+
+        let qcow = QcowFile {
+            file: file,
+            header: header,
+            l2_entries: cluster_size / size_of::<u64>() as u64,
+            cluster_size: cluster_size,
+            cluster_mask: cluster_size - 1,
+            current_offset: 0,
+            refcount_bits: refcount_bits,
+        };
+
+        // Check that the L1 and refcount tables fit in a 64bit address space.
+        qcow.header.l1_table_offset
+            .checked_add(qcow.l1_address_offset(qcow.virtual_size()))
+            .ok_or(Error::InvalidL1TableOffset)?;
+        qcow.header.refcount_table_offset
+            .checked_add(qcow.header.refcount_table_clusters as u64 * qcow.cluster_size)
+            .ok_or(Error::InvalidRefcountTableOffset)?;
+
+        Ok(qcow)
+    }
+
+    // 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() {
+            return 0;
+        }
+        min(count, self.virtual_size() as usize - address as usize)
+    }
+
+    // Limits the range so that it doesn't overflow the end of a cluster.
+    fn limit_range_cluster(&self, address: u64, count: usize) -> usize {
+        let offset: u64 = address & self.cluster_mask;
+        let limit = self.cluster_size - offset;
+        min(count, limit as usize)
+    }
+
+    // Gets the maximum virtual size of this image.
+    fn virtual_size(&self) -> u64 {
+        self.header.size
+    }
+
+    // Gets the offset of `address` in the L1 table.
+    fn l1_address_offset(&self, address: u64) -> u64 {
+        let l1_index = (address / self.cluster_size) / self.l2_entries;
+        l1_index * size_of::<u64>() as u64
+    }
+
+    // Gets the offset of `address` in the L2 table.
+    fn l2_address_offset(&self, address: u64) -> u64 {
+        let l2_index = (address / self.cluster_size) % self.l2_entries;
+        l2_index * size_of::<u64>() as u64
+    }
+
+    // Returns the offset of address within a cluster.
+    fn cluster_offset(&self, address: u64) -> u64 {
+        address & self.cluster_mask
+    }
+
+    // Returns the file offset for the given `address`. If `address` doesn't
+    // have a cluster allocated, the behavior is determined by the `allocate`
+    // argument. If `allocate` is true, then allocate the cluster and return the
+    // new offset, otherwise return None.  Returns an error if the address is
+    // beyond the end or there is an issue accessing the file.
+    fn file_offset(&mut self, address: u64, allocate: bool) -> std::io::Result<Option<u64>> {
+        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 in image. No data has ever been written.
+            if allocate {
+                self.file.set_len(
+                    self.header.l1_table_offset +
+                        self.l1_address_offset(self.virtual_size()),
+                )?;
+            } else {
+                return Ok(None);
+            }
+        }
+        let l2_addr_disk = read_u64_from_offset(&mut self.file, l1_entry_offset)?;
+        if l2_addr_disk & COMPRESSED_FLAG != 0 {
+            return Err(std::io::Error::from_raw_os_error(ENOTSUP));
+        }
+        let l2_addr_from_table: u64 = l2_addr_disk & L1_TABLE_OFFSET_MASK;
+        let l2_addr = if l2_addr_from_table == 0 {
+            if allocate {
+                self.append_data_cluster(l1_entry_offset)?
+            } else {
+                return Ok(None);
+            }
+        } else {
+            l2_addr_from_table
+        };
+        let l2_entry_addr: u64 = l2_addr + self.l2_address_offset(address);
+        let cluster_addr_disk: u64 = read_u64_from_offset(&mut self.file, l2_entry_addr)?;
+        let cluster_addr_from_table: u64 = cluster_addr_disk & L2_TABLE_OFFSET_MASK;
+        let cluster_addr = if cluster_addr_from_table == 0 {
+            if allocate {
+                self.append_data_cluster(l2_entry_addr)?
+            } else {
+                return Ok(None);
+            }
+        } else {
+            cluster_addr_from_table
+        };
+        Ok(Some(cluster_addr + self.cluster_offset(address)))
+    }
+
+    // Allocate a new cluster at the end of the current file, return the address.
+    fn append_new_cluster(&mut self) -> std::io::Result<u64> {
+        // Determine where the new end of the file should be and set_len, which
+        // translates to truncate(2).
+        let file_end: u64 = self.file.seek(SeekFrom::End(0))?;
+        let cluster_size: u64 = self.cluster_size;
+        let new_cluster_address: u64 = (file_end + cluster_size - 1) & !self.cluster_mask;
+        self.file.set_len(new_cluster_address + cluster_size)?;
+
+        Ok(new_cluster_address)
+    }
+
+    // Allocate and initialize a new data cluster. Returns the offset of the
+    // cluster in to the file on success. Write the address to the offset in
+    // `entry_addr` to fill in the L1 or L2 table.
+    fn append_data_cluster(&mut self, entry_addr: u64) -> std::io::Result<u64> {
+        let new_addr: u64 = self.append_new_cluster()?;
+        // Save the new block to the table and mark it as used.
+        write_u64_to_offset(&mut self.file, entry_addr, new_addr | CLUSTER_USED_FLAG)?;
+        // The cluster refcount starts at one indicating it is used but doesn't need COW.
+        self.set_cluster_refcount(new_addr, 1)?;
+        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<()> {
+        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 refcount_table_index = (address / cluster_size) / refcount_block_entries;
+        let refcount_block_entry_addr =
+            self.header.refcount_table_offset + refcount_table_index * size_of::<u64>() as u64;
+        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 {
+            let new_addr = self.append_new_cluster()?;
+            write_u64_to_offset(&mut self.file, refcount_block_entry_addr, new_addr)?;
+            self.set_cluster_refcount(new_addr, 1)?;
+            new_addr
+        } else {
+            refcount_block_address_from_file
+        };
+        let refcount_address: u64 = refcount_block_address
+                .checked_add(refcount_block_index * 2)
+                .ok_or(std::io::Error::from_raw_os_error(EINVAL))?;
+        self.file.seek(SeekFrom::Start(refcount_address))?;
+        self.file.write_u16::<BigEndian>(refcount)
+    }
+}
+
+impl AsRawFd for QcowFile {
+    fn as_raw_fd(&self) -> RawFd {
+        self.file.as_raw_fd()
+    }
+}
+
+impl Read for QcowFile {
+    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+        let address: u64 = self.current_offset as u64;
+        let read_count: usize = self.limit_range_file(address, buf.len());
+
+        let mut nread: usize = 0;
+        while nread < read_count {
+            let file_offset = self.file_offset(address + nread as u64, false)?;
+            let count = self.limit_range_cluster(address, read_count - nread);
+
+            if let Some(offset) = file_offset {
+                self.file.seek(SeekFrom::Start(offset))?;
+                self.file.read(&mut buf[nread..(nread + count)])?;
+            } else {
+                // Previously unwritten region, return zeros
+                for b in (&mut buf[nread..(nread + count)]).iter_mut() {
+                    *b = 0;
+                }
+            }
+
+            nread += count;
+        }
+        self.current_offset += read_count as u64;
+        Ok(read_count)
+    }
+}
+
+impl Seek for QcowFile {
+    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
+        let new_offset: Option<u64> = match pos {
+            SeekFrom::Start(off) => Some(off),
+            SeekFrom::End(off) => {
+                if off < 0 {
+                    0i64.checked_sub(off).and_then(|increment| {
+                        self.virtual_size().checked_sub(increment as u64)
+                    })
+                } else {
+                    self.virtual_size().checked_add(off as u64)
+                }
+            }
+            SeekFrom::Current(off) => {
+                if off < 0 {
+                    0i64.checked_sub(off).and_then(|increment| {
+                        self.current_offset.checked_sub(increment as u64)
+                    })
+                } else {
+                    self.current_offset.checked_add(off as u64)
+                }
+            }
+        };
+
+        if let Some(o) = new_offset {
+            if o <= self.virtual_size() {
+                self.current_offset = o;
+                return Ok(o);
+            }
+        }
+        Err(std::io::Error::from_raw_os_error(EINVAL))
+    }
+}
+
+impl Write for QcowFile {
+    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+        let address: u64 = self.current_offset as u64;
+        let write_count: usize = self.limit_range_file(address, buf.len());
+
+        let mut nwritten: usize = 0;
+        while nwritten < write_count {
+            // file_offset always returns an address when allocate == true.
+            let offset = self.file_offset(address + nwritten as u64, true)?.unwrap();
+            let count = self.limit_range_cluster(address, write_count - nwritten);
+
+            if let Err(e) = self.file.seek(SeekFrom::Start(offset)) {
+                return Err(e);
+            }
+            if let Err(e) = self.file.write(&buf[nwritten..(nwritten + count)]) {
+                return Err(e);
+            }
+
+            nwritten += count;
+        }
+        self.current_offset += write_count as u64;
+        Ok(write_count)
+    }
+
+    fn flush(&mut self) -> std::io::Result<()> {
+        self.file.flush()
+    }
+}
+
+// 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 {
+        return Err(Error::InvalidOffset(offset));
+    }
+    Ok(())
+}
+
+// Reads a big endian 64 bit number from `offset`.
+fn read_u64_from_offset(f: &mut File, offset: u64) -> std::io::Result<u64> {
+    f.seek(SeekFrom::Start(offset))?;
+    f.read_u64::<BigEndian>()
+}
+
+// Writes a big endian 64 bit number to `offset`.
+fn write_u64_to_offset(f: &mut File, offset: u64, value: u64) -> std::io::Result<()> {
+    f.seek(SeekFrom::Start(offset))?;
+    f.write_u64::<BigEndian>(value)
+}
+
+#[cfg(test)]
+extern crate sys_util;
+
+#[cfg(test)]
+mod tests {
+    use std::fs::File;
+    use std::io::{Read, Seek, SeekFrom, Write};
+    use super::*;
+    use sys_util::SharedMemory;
+
+    fn valid_header() -> Vec<u8> {
+        vec![
+            0x51u8, 0x46, 0x49, 0xfb, // magic
+            0x00, 0x00, 0x00, 0x03, // version
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // backing file offset
+            0x00, 0x00, 0x00, 0x00, // backing file size
+            0x00, 0x00, 0x00, 0x0c, // cluster_bits
+            0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // size
+            0x00, 0x00, 0x00, 0x00, // crypt method
+            0x00, 0x00, 0x00, 0x00, // L1 size
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // L1 table offset
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // refcount table offset
+            0x00, 0x00, 0x00, 0x01, // refcount table clusters
+            0x00, 0x00, 0x00, 0x00, // nb snapshots
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // snapshots offset
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // incompatible_features
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compatible_features
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // autoclear_features
+            0x00, 0x00, 0x00, 0x04, // refcount_order
+            0x00, 0x00, 0x00, 0x68, // header_length
+        ]
+    }
+
+    fn with_basic_file<F>(header: &[u8], mut testfn: F)
+    where
+        F: FnMut(File),
+    {
+        let shm = SharedMemory::new(None).unwrap();
+        let mut disk_file: File = shm.into();
+        disk_file.write_all(&header).unwrap();
+        disk_file.seek(SeekFrom::Start(0)).unwrap();
+
+        testfn(disk_file); // File closed when the function exits.
+    }
+
+    #[test]
+    fn header_read() {
+        with_basic_file(&valid_header(), |mut disk_file: File| {
+            QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
+        });
+    }
+
+    #[test]
+    fn invalid_magic() {
+        let invalid_header = vec![0x51u8, 0x46, 0x49, 0xfb];
+        with_basic_file(&invalid_header, |mut disk_file: File| {
+            QcowHeader::new(&mut disk_file).expect_err("Invalid header worked.");
+        });
+    }
+
+    #[test]
+    fn invalid_refcount_order() {
+        let mut header = valid_header();
+        header[99] = 2;
+        with_basic_file(&header, |disk_file: File| {
+            QcowFile::from(disk_file).expect_err("Invalid refcount order worked.");
+        });
+    }
+
+    #[test]
+    fn write_read_start() {
+        with_basic_file(&valid_header(), |disk_file: File| {
+            let mut q = QcowFile::from(disk_file).unwrap();
+            q.write(b"test first bytes").expect(
+                "Failed to write test string.",
+            );
+            let mut buf = [0u8; 4];
+            q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
+            q.read(&mut buf).expect("Failed to read.");
+            assert_eq!(&buf, b"test");
+        });
+    }
+
+    #[test]
+    fn offset_write_read() {
+        with_basic_file(&valid_header(), |disk_file: File| {
+            let mut q = QcowFile::from(disk_file).unwrap();
+            let b = [0x55u8; 0x1000];
+            q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
+            q.write(&b).expect("Failed to write test string.");
+            let mut buf = [0u8; 4];
+            q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
+            q.read(&mut buf).expect("Failed to read.");
+            assert_eq!(buf[0], 0x55);
+        });
+    }
+
+    #[test]
+    fn test_header() {
+        with_basic_file(&valid_header(), |disk_file: File| {
+            let q = QcowFile::from(disk_file).unwrap();
+            assert_eq!(q.virtual_size(), 0x1000_0000);
+        });
+    }
+
+    #[test]
+    fn read_small_buffer() {
+        with_basic_file(&valid_header(), |disk_file: File| {
+            let mut q = QcowFile::from(disk_file).unwrap();
+            let mut b = [5u8; 16];
+            q.seek(SeekFrom::Start(1000)).expect("Failed to seek.");
+            q.read(&mut b).expect("Failed to read.");
+            assert_eq!(0, b[0]);
+            assert_eq!(0, b[15]);
+        });
+    }
+
+    #[test]
+    fn replay_ext4() {
+        with_basic_file(&valid_header(), |disk_file: File| {
+            let mut q = QcowFile::from(disk_file).unwrap();
+            const BUF_SIZE: usize = 0x1000;
+            let mut b = [0u8; BUF_SIZE];
+
+            struct Transfer {
+                pub write: bool,
+                pub addr: u64,
+            };
+
+            // Write transactions from mkfs.ext4.
+            let xfers: Vec<Transfer> = vec![
+                Transfer {write: false, addr: 0xfff0000},
+                Transfer {write: false, addr: 0xfffe000},
+                Transfer {write: false, addr: 0x0},
+                Transfer {write: false, addr: 0x1000},
+                Transfer {write: false, addr: 0xffff000},
+                Transfer {write: false, addr: 0xffdf000},
+                Transfer {write: false, addr: 0xfff8000},
+                Transfer {write: false, addr: 0xffe0000},
+                Transfer {write: false, addr: 0xffce000},
+                Transfer {write: false, addr: 0xffb6000},
+                Transfer {write: false, addr: 0xffab000},
+                Transfer {write: false, addr: 0xffa4000},
+                Transfer {write: false, addr: 0xff8e000},
+                Transfer {write: false, addr: 0xff86000},
+                Transfer {write: false, addr: 0xff84000},
+                Transfer {write: false, addr: 0xff89000},
+                Transfer {write: false, addr: 0xfe7e000},
+                Transfer {write: false, addr: 0x100000},
+                Transfer {write: false, addr: 0x3000},
+                Transfer {write: false, addr: 0x7000},
+                Transfer {write: false, addr: 0xf000},
+                Transfer {write: false, addr: 0x2000},
+                Transfer {write: false, addr: 0x4000},
+                Transfer {write: false, addr: 0x5000},
+                Transfer {write: false, addr: 0x6000},
+                Transfer {write: false, addr: 0x8000},
+                Transfer {write: false, addr: 0x9000},
+                Transfer {write: false, addr: 0xa000},
+                Transfer {write: false, addr: 0xb000},
+                Transfer {write: false, addr: 0xc000},
+                Transfer {write: false, addr: 0xd000},
+                Transfer {write: false, addr: 0xe000},
+                Transfer {write: false, addr: 0x10000},
+                Transfer {write: false, addr: 0x11000},
+                Transfer {write: false, addr: 0x12000},
+                Transfer {write: false, addr: 0x13000},
+                Transfer {write: false, addr: 0x14000},
+                Transfer {write: false, addr: 0x15000},
+                Transfer {write: false, addr: 0x16000},
+                Transfer {write: false, addr: 0x17000},
+                Transfer {write: false, addr: 0x18000},
+                Transfer {write: false, addr: 0x19000},
+                Transfer {write: false, addr: 0x1a000},
+                Transfer {write: false, addr: 0x1b000},
+                Transfer {write: false, addr: 0x1c000},
+                Transfer {write: false, addr: 0x1d000},
+                Transfer {write: false, addr: 0x1e000},
+                Transfer {write: false, addr: 0x1f000},
+                Transfer {write: false, addr: 0x21000},
+                Transfer {write: false, addr: 0x22000},
+                Transfer {write: false, addr: 0x24000},
+                Transfer {write: false, addr: 0x40000},
+                Transfer {write: false, addr: 0x0},
+                Transfer {write: false, addr: 0x3000},
+                Transfer {write: false, addr: 0x7000},
+                Transfer {write: false, addr: 0x0},
+                Transfer {write: false, addr: 0x1000},
+                Transfer {write: false, addr: 0x2000},
+                Transfer {write: false, addr: 0x3000},
+                Transfer {write: false, addr: 0x0},
+                Transfer {write: false, addr: 0x449000},
+                Transfer {write: false, addr: 0x48000},
+                Transfer {write: false, addr: 0x48000},
+                Transfer {write: false, addr: 0x448000},
+                Transfer {write: false, addr: 0x44a000},
+                Transfer {write: false, addr: 0x48000},
+                Transfer {write: false, addr: 0x48000},
+                Transfer {write: true, addr: 0x0},
+                Transfer {write: true, addr: 0x448000},
+                Transfer {write: true, addr: 0x449000},
+                Transfer {write: true, addr: 0x44a000},
+                Transfer {write: true, addr: 0xfff0000},
+                Transfer {write: true, addr: 0xfff1000},
+                Transfer {write: true, addr: 0xfff2000},
+                Transfer {write: true, addr: 0xfff3000},
+                Transfer {write: true, addr: 0xfff4000},
+                Transfer {write: true, addr: 0xfff5000},
+                Transfer {write: true, addr: 0xfff6000},
+                Transfer {write: true, addr: 0xfff7000},
+                Transfer {write: true, addr: 0xfff8000},
+                Transfer {write: true, addr: 0xfff9000},
+                Transfer {write: true, addr: 0xfffa000},
+                Transfer {write: true, addr: 0xfffb000},
+                Transfer {write: true, addr: 0xfffc000},
+                Transfer {write: true, addr: 0xfffd000},
+                Transfer {write: true, addr: 0xfffe000},
+                Transfer {write: true, addr: 0xffff000},
+            ];
+
+            for xfer in xfers.iter() {
+                q.seek(SeekFrom::Start(xfer.addr)).expect("Failed to seek.");
+                if xfer.write {
+                    q.write(&b).expect("Failed to write.");
+                } else {
+                    let read_count: usize = q.read(&mut b).expect("Failed to read.");
+                    assert_eq!(read_count, BUF_SIZE);
+                }
+            }
+        });
+    }
+}