summary refs log tree commit diff
diff options
context:
space:
mode:
authorCody Schuffelen <schuffelen@google.com>2019-07-02 16:54:05 -0700
committerCommit Bot <commit-bot@chromium.org>2019-08-28 23:52:36 +0000
commit7d533e5952b6bae600441e1183964d335a41d6fe (patch)
tree6f5fc375f4d7207fc02ee20c2c683f5c67d9beca
parent971589f7ec1af2904cd5a5b828e9008df9c0bba1 (diff)
downloadcrosvm-7d533e5952b6bae600441e1183964d335a41d6fe.tar
crosvm-7d533e5952b6bae600441e1183964d335a41d6fe.tar.gz
crosvm-7d533e5952b6bae600441e1183964d335a41d6fe.tar.bz2
crosvm-7d533e5952b6bae600441e1183964d335a41d6fe.tar.lz
crosvm-7d533e5952b6bae600441e1183964d335a41d6fe.tar.xz
crosvm-7d533e5952b6bae600441e1183964d335a41d6fe.tar.zst
crosvm-7d533e5952b6bae600441e1183964d335a41d6fe.zip
Extract disk creation logic out of qcow and src.
Bug: b/133432409
Change-Id: Iba25d5f6bb5f60619bb2f5a3d72ddfd3a81650b4
Signed-off-by: Cody Schuffelen <schuffelen@google.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1691460
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
-rw-r--r--Cargo.lock13
-rw-r--r--Cargo.toml1
-rw-r--r--devices/Cargo.toml1
-rw-r--r--devices/src/virtio/block.rs50
-rw-r--r--devices/src/virtio/descriptor_utils.rs8
-rw-r--r--disk/Cargo.toml14
-rw-r--r--disk/src/disk.rs209
-rw-r--r--qcow/src/qcow.rs136
-rw-r--r--qcow_utils/Cargo.toml1
-rw-r--r--qcow_utils/src/qcow_img.rs6
-rw-r--r--qcow_utils/src/qcow_utils.rs9
-rw-r--r--src/linux.rs28
12 files changed, 277 insertions, 199 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 98625dc..b4c5c6a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -85,6 +85,7 @@ dependencies = [
  "crosvm_plugin 0.17.0",
  "data_model 0.1.0",
  "devices 0.1.0",
+ "disk 0.1.0",
  "enumn 0.1.0",
  "gpu_buffer 0.1.0",
  "io_jail 0.1.0",
@@ -137,6 +138,7 @@ dependencies = [
  "audio_streams 0.1.0",
  "bit_field 0.1.0",
  "data_model 0.1.0",
+ "disk 0.1.0",
  "enumn 0.1.0",
  "gpu_buffer 0.1.0",
  "gpu_display 0.1.0",
@@ -163,6 +165,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "disk"
+version = "0.1.0"
+dependencies = [
+ "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
+ "qcow 0.1.0",
+ "remain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sys_util 0.1.0",
+]
+
+[[package]]
 name = "enumn"
 version = "0.1.0"
 dependencies = [
@@ -397,6 +409,7 @@ dependencies = [
 name = "qcow_utils"
 version = "0.1.0"
 dependencies = [
+ "disk 0.1.0",
  "getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
  "qcow 0.1.0",
diff --git a/Cargo.toml b/Cargo.toml
index 75d3b13..ec0d12b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -45,6 +45,7 @@ bit_field = { path = "bit_field" }
 crosvm_plugin = { path = "crosvm_plugin", optional = true }
 data_model = "*"
 devices = { path = "devices" }
+disk = { path = "disk" }
 enumn = { path = "enumn" }
 gpu_buffer = { path = "gpu_buffer", optional = true }
 io_jail = { path = "io_jail" }
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index 47f55df..8a91a4e 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -15,6 +15,7 @@ x = ["gpu_display/x"]
 audio_streams = "*"
 bit_field = { path = "../bit_field" }
 data_model = { path = "../data_model" }
+disk = { path = "../disk" }
 enumn = { path = "../enumn" }
 gpu_buffer = { path = "../gpu_buffer", optional = true }
 gpu_display = { path = "../gpu_display", optional = true }
diff --git a/devices/src/virtio/block.rs b/devices/src/virtio/block.rs
index a6d7a89..7a728d7 100644
--- a/devices/src/virtio/block.rs
+++ b/devices/src/virtio/block.rs
@@ -13,16 +13,13 @@ use std::thread;
 use std::time::Duration;
 use std::u32;
 
+use data_model::{DataInit, Le16, Le32, Le64};
+use disk::DiskFile;
+use msg_socket::{MsgReceiver, MsgSender};
 use sync::Mutex;
 use sys_util::Error as SysError;
 use sys_util::Result as SysResult;
-use sys_util::{
-    error, info, warn, EventFd, FileReadWriteVolatile, FileSetLen, FileSync, GuestMemory,
-    PollContext, PollToken, PunchHole, TimerFd, WriteZeroes,
-};
-
-use data_model::{DataInit, Le16, Le32, Le64};
-use msg_socket::{MsgReceiver, MsgSender};
+use sys_util::{error, info, warn, EventFd, GuestMemory, PollContext, PollToken, TimerFd};
 use vm_control::{DiskControlCommand, DiskControlResponseSocket, DiskControlResult};
 
 use super::{
@@ -129,15 +126,6 @@ const VIRTIO_BLK_DISCARD_WRITE_ZEROES_FLAG_UNMAP: u32 = 1 << 0;
 // Safe because it only has data and has no implicit padding.
 unsafe impl DataInit for virtio_blk_discard_write_zeroes {}
 
-pub trait DiskFile:
-    FileSetLen + FileSync + FileReadWriteVolatile + PunchHole + Seek + WriteZeroes
-{
-}
-impl<D: FileSetLen + FileSync + PunchHole + FileReadWriteVolatile + Seek + WriteZeroes> DiskFile
-    for D
-{
-}
-
 #[derive(Debug)]
 enum ExecuteError {
     Descriptor(DescriptorError),
@@ -275,10 +263,10 @@ impl ExecuteError {
     }
 }
 
-struct Worker<T: DiskFile> {
+struct Worker {
     queues: Vec<Queue>,
     mem: GuestMemory,
-    disk_image: T,
+    disk_image: Box<dyn DiskFile>,
     disk_size: Arc<Mutex<u64>>,
     read_only: bool,
     interrupt_status: Arc<AtomicUsize>,
@@ -286,7 +274,7 @@ struct Worker<T: DiskFile> {
     interrupt_resample_evt: EventFd,
 }
 
-impl<T: DiskFile> Worker<T> {
+impl Worker {
     fn process_queue(
         &mut self,
         queue_index: usize,
@@ -305,7 +293,7 @@ impl<T: DiskFile> Worker<T> {
             let status = match Block::execute_request(
                 avail_desc,
                 self.read_only,
-                &mut self.disk_image,
+                &mut *self.disk_image,
                 *disk_size,
                 flush_timer,
                 flush_timer_armed,
@@ -480,9 +468,9 @@ impl<T: DiskFile> Worker<T> {
 }
 
 /// Virtio device for exposing block level read/write operations on a host file.
-pub struct Block<T: DiskFile> {
+pub struct Block {
     kill_evt: Option<EventFd>,
-    disk_image: Option<T>,
+    disk_image: Option<Box<dyn DiskFile>>,
     disk_size: Arc<Mutex<u64>>,
     avail_features: u64,
     read_only: bool,
@@ -504,15 +492,15 @@ fn build_config_space(disk_size: u64) -> virtio_blk_config {
     }
 }
 
-impl<T: DiskFile> Block<T> {
+impl Block {
     /// Create a new virtio block device that operates on the given file.
     ///
     /// The given file must be seekable and sizable.
     pub fn new(
-        mut disk_image: T,
+        mut disk_image: Box<dyn DiskFile>,
         read_only: bool,
         control_socket: Option<DiskControlResponseSocket>,
-    ) -> SysResult<Block<T>> {
+    ) -> SysResult<Block> {
         let disk_size = disk_image.seek(SeekFrom::End(0))? as u64;
         if disk_size % SECTOR_SIZE != 0 {
             warn!(
@@ -545,7 +533,7 @@ impl<T: DiskFile> Block<T> {
     fn execute_request(
         avail_desc: DescriptorChain,
         read_only: bool,
-        disk: &mut T,
+        disk: &mut DiskFile,
         disk_size: u64,
         flush_timer: &mut TimerFd,
         flush_timer_armed: &mut bool,
@@ -705,7 +693,7 @@ impl<T: DiskFile> Block<T> {
     }
 }
 
-impl<T: DiskFile> Drop for Block<T> {
+impl Drop for Block {
     fn drop(&mut self) {
         if let Some(kill_evt) = self.kill_evt.take() {
             // Ignore the result because there is nothing we can do about it.
@@ -714,7 +702,7 @@ impl<T: DiskFile> Drop for Block<T> {
     }
 }
 
-impl<T: 'static + AsRawFd + DiskFile + Send> VirtioDevice for Block<T> {
+impl VirtioDevice for Block {
     fn keep_fds(&self) -> Vec<RawFd> {
         let mut keep_fds = Vec::new();
 
@@ -820,7 +808,7 @@ mod tests {
         let f = File::create(&path).unwrap();
         f.set_len(0x1000).unwrap();
 
-        let b = Block::new(f, true, None).unwrap();
+        let b = Block::new(Box::new(f), true, None).unwrap();
         let mut num_sectors = [0u8; 4];
         b.read_config(0, &mut num_sectors);
         // size is 0x1000, so num_sectors is 8 (4096/512).
@@ -840,7 +828,7 @@ mod tests {
         // read-write block device
         {
             let f = File::create(&path).unwrap();
-            let b = Block::new(f, false, None).unwrap();
+            let b = Block::new(Box::new(f), false, None).unwrap();
             // writable device should set VIRTIO_BLK_F_FLUSH + VIRTIO_BLK_F_DISCARD
             // + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE
             assert_eq!(0x100006240, b.features());
@@ -849,7 +837,7 @@ mod tests {
         // read-only block device
         {
             let f = File::create(&path).unwrap();
-            let b = Block::new(f, true, None).unwrap();
+            let b = Block::new(Box::new(f), true, None).unwrap();
             // read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO
             // + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE
             assert_eq!(0x100000260, b.features());
diff --git a/devices/src/virtio/descriptor_utils.rs b/devices/src/virtio/descriptor_utils.rs
index fb2df16..5142ecf 100644
--- a/devices/src/virtio/descriptor_utils.rs
+++ b/devices/src/virtio/descriptor_utils.rs
@@ -281,9 +281,9 @@ impl<'a> Reader<'a> {
     /// Returns the number of bytes read from the descriptor chain buffer.
     /// The number of bytes read can be less than `count` if there isn't
     /// enough data in the descriptor chain buffer.
-    pub fn read_to_volatile(
+    pub fn read_to_volatile<T: FileReadWriteVolatile + ?Sized>(
         &mut self,
-        dst: &mut dyn FileReadWriteVolatile,
+        dst: &mut T,
         count: usize,
     ) -> Result<usize> {
         let mem = self.mem;
@@ -399,9 +399,9 @@ impl<'a> Writer<'a> {
     /// Returns the number of bytes written to the descriptor chain buffer.
     /// The number of bytes written can be less than `count` if
     /// there isn't enough data in the descriptor chain buffer.
-    pub fn write_from_volatile(
+    pub fn write_from_volatile<T: FileReadWriteVolatile + ?Sized>(
         &mut self,
-        src: &mut dyn FileReadWriteVolatile,
+        src: &mut T,
         count: usize,
     ) -> Result<usize> {
         let mem = self.mem;
diff --git a/disk/Cargo.toml b/disk/Cargo.toml
new file mode 100644
index 0000000..c26cf4b
--- /dev/null
+++ b/disk/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "disk"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[lib]
+path = "src/disk.rs"
+
+[dependencies]
+libc = "*"
+remain = "*"
+qcow = { path = "../qcow" }
+sys_util = { path = "../sys_util" }
diff --git a/disk/src/disk.rs b/disk/src/disk.rs
new file mode 100644
index 0000000..c9ce1b3
--- /dev/null
+++ b/disk/src/disk.rs
@@ -0,0 +1,209 @@
+// Copyright 2019 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.
+
+use std::cmp::min;
+use std::fmt::{self, Display};
+use std::fs::File;
+use std::io::{self, Read, Seek, SeekFrom, Write};
+use std::os::unix::io::AsRawFd;
+
+use libc::EINVAL;
+use qcow::{QcowFile, QCOW_MAGIC};
+use remain::sorted;
+use sys_util::{FileReadWriteVolatile, FileSetLen, FileSync, PunchHole, SeekHole, WriteZeroes};
+
+#[sorted]
+#[derive(Debug)]
+pub enum Error {
+    BlockDeviceNew(sys_util::Error),
+    QcowError(qcow::Error),
+    ReadingData(io::Error),
+    ReadingHeader(io::Error),
+    SeekingFile(io::Error),
+    SettingFileSize(io::Error),
+    UnknownType,
+    WritingData(io::Error),
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// The prerequisites necessary to support a block device.
+pub trait DiskFile:
+    FileSetLen + FileSync + FileReadWriteVolatile + PunchHole + Seek + WriteZeroes + Send + AsRawFd
+{
+}
+impl<
+        D: FileSetLen
+            + FileSync
+            + PunchHole
+            + FileReadWriteVolatile
+            + Seek
+            + WriteZeroes
+            + Send
+            + AsRawFd,
+    > DiskFile for D
+{
+}
+
+impl Display for Error {
+    #[remain::check]
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::Error::*;
+
+        #[sorted]
+        match self {
+            BlockDeviceNew(e) => write!(f, "failed to create block device: {}", e),
+            QcowError(e) => write!(f, "failure in qcow: {}", e),
+            ReadingData(e) => write!(f, "failed to read data: {}", e),
+            ReadingHeader(e) => write!(f, "failed to read header: {}", e),
+            SeekingFile(e) => write!(f, "failed to seek file: {}", e),
+            SettingFileSize(e) => write!(f, "failed to set file size: {}", e),
+            UnknownType => write!(f, "unknown disk type"),
+            WritingData(e) => write!(f, "failed to write data: {}", e),
+        }
+    }
+}
+
+/// The variants of image files on the host that can be used as virtual disks.
+#[derive(Debug, PartialEq, Eq)]
+pub enum ImageType {
+    Raw,
+    Qcow2,
+}
+
+fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
+where
+    R: Read + Seek,
+    W: Write + Seek,
+{
+    const CHUNK_SIZE: usize = 65536;
+    let mut buf = [0; CHUNK_SIZE];
+    let mut read_count = 0;
+    reader
+        .seek(SeekFrom::Start(offset))
+        .map_err(Error::SeekingFile)?;
+    writer
+        .seek(SeekFrom::Start(offset))
+        .map_err(Error::SeekingFile)?;
+    loop {
+        let this_count = min(CHUNK_SIZE as u64, size - read_count) as usize;
+        let nread = reader
+            .read(&mut buf[..this_count])
+            .map_err(Error::ReadingData)?;
+        writer.write(&buf[..nread]).map_err(Error::WritingData)?;
+        read_count += nread as u64;
+        if nread == 0 || read_count == size {
+            break;
+        }
+    }
+
+    Ok(())
+}
+
+fn convert_reader_writer<R, W>(reader: &mut R, writer: &mut W, size: u64) -> Result<()>
+where
+    R: Read + Seek + SeekHole,
+    W: Write + Seek,
+{
+    let mut offset = 0;
+    while offset < size {
+        // Find the next range of data.
+        let next_data = match reader.seek_data(offset).map_err(Error::SeekingFile)? {
+            Some(o) => o,
+            None => {
+                // No more data in the file.
+                break;
+            }
+        };
+        let next_hole = match reader.seek_hole(next_data).map_err(Error::SeekingFile)? {
+            Some(o) => o,
+            None => {
+                // This should not happen - there should always be at least one hole
+                // after any data.
+                return Err(Error::SeekingFile(io::Error::from_raw_os_error(EINVAL)));
+            }
+        };
+        let count = next_hole - next_data;
+        convert_copy(reader, writer, next_data, count)?;
+        offset = next_hole;
+    }
+
+    Ok(())
+}
+
+fn convert_reader<R>(reader: &mut R, dst_file: File, dst_type: ImageType) -> Result<()>
+where
+    R: Read + Seek + SeekHole,
+{
+    let src_size = reader.seek(SeekFrom::End(0)).map_err(Error::SeekingFile)?;
+    reader
+        .seek(SeekFrom::Start(0))
+        .map_err(Error::SeekingFile)?;
+
+    // Ensure the destination file is empty before writing to it.
+    dst_file.set_len(0).map_err(Error::SettingFileSize)?;
+
+    match dst_type {
+        ImageType::Qcow2 => {
+            let mut dst_writer = QcowFile::new(dst_file, src_size).map_err(Error::QcowError)?;
+            convert_reader_writer(reader, &mut dst_writer, src_size)
+        }
+        ImageType::Raw => {
+            let mut dst_writer = dst_file;
+            // Set the length of the destination file to convert it into a sparse file
+            // of the desired size.
+            dst_writer
+                .set_len(src_size)
+                .map_err(Error::SettingFileSize)?;
+            convert_reader_writer(reader, &mut dst_writer, src_size)
+        }
+    }
+}
+
+/// Copy the contents of a disk image in `src_file` into `dst_file`.
+/// The type of `src_file` is automatically detected, and the output file type is
+/// determined by `dst_type`.
+pub fn convert(src_file: File, dst_file: File, dst_type: ImageType) -> Result<()> {
+    let src_type = detect_image_type(&src_file)?;
+    match src_type {
+        ImageType::Qcow2 => {
+            let mut src_reader = QcowFile::from(src_file).map_err(Error::QcowError)?;
+            convert_reader(&mut src_reader, dst_file, dst_type)
+        }
+        ImageType::Raw => {
+            // src_file is a raw file.
+            let mut src_reader = src_file;
+            convert_reader(&mut src_reader, dst_file, dst_type)
+        }
+    }
+}
+
+/// Detect the type of an image file by checking for a valid qcow2 header.
+pub fn detect_image_type(file: &File) -> Result<ImageType> {
+    let mut f = file;
+    let orig_seek = f.seek(SeekFrom::Current(0)).map_err(Error::SeekingFile)?;
+    f.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
+    let mut magic = [0u8; 4];
+    f.read_exact(&mut magic).map_err(Error::ReadingHeader)?;
+    let magic = u32::from_be_bytes(magic);
+    let image_type = if magic == QCOW_MAGIC {
+        ImageType::Qcow2
+    } else {
+        ImageType::Raw
+    };
+    f.seek(SeekFrom::Start(orig_seek))
+        .map_err(Error::SeekingFile)?;
+    Ok(image_type)
+}
+
+/// Inspect the image file type and create an appropriate disk file to match it.
+pub fn create_disk_file(raw_image: File) -> Result<Box<dyn DiskFile>> {
+    let image_type = detect_image_type(&raw_image)?;
+    Ok(match image_type {
+        ImageType::Raw => Box::new(raw_image) as Box<dyn DiskFile>,
+        ImageType::Qcow2 => {
+            Box::new(QcowFile::from(raw_image).map_err(Error::QcowError)?) as Box<dyn DiskFile>
+        }
+    })
+}
diff --git a/qcow/src/qcow.rs b/qcow/src/qcow.rs
index 57e42fc..3153df1 100644
--- a/qcow/src/qcow.rs
+++ b/qcow/src/qcow.rs
@@ -46,7 +46,6 @@ pub enum Error {
     NoRefcountClusters,
     NotEnoughSpaceForRefcounts,
     OpeningFile(io::Error),
-    ReadingData(io::Error),
     ReadingHeader(io::Error),
     ReadingPointers(io::Error),
     ReadingRefCountBlock(refcount::Error),
@@ -55,14 +54,12 @@ pub enum Error {
     RefcountTableOffEnd,
     RefcountTableTooLarge,
     SeekingFile(io::Error),
-    SettingFileSize(io::Error),
     SettingRefcountRefcount(io::Error),
     SizeTooSmallForNumberOfClusters,
     TooManyL1Entries(u64),
     TooManyRefcounts(u64),
     UnsupportedRefcountOrder,
     UnsupportedVersion(u32),
-    WritingData(io::Error),
     WritingHeader(io::Error),
 }
 
@@ -98,7 +95,6 @@ impl Display for Error {
             NoRefcountClusters => write!(f, "no refcount clusters"),
             NotEnoughSpaceForRefcounts => write!(f, "not enough space for refcounts"),
             OpeningFile(e) => write!(f, "failed to open file: {}", e),
-            ReadingData(e) => write!(f, "failed to read data: {}", e),
             ReadingHeader(e) => write!(f, "failed to read header: {}", e),
             ReadingPointers(e) => write!(f, "failed to read pointers: {}", e),
             ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {}", e),
@@ -107,29 +103,22 @@ impl Display for Error {
             RefcountTableOffEnd => write!(f, "refcount table offset past file end"),
             RefcountTableTooLarge => write!(f, "too many clusters specified for refcount table"),
             SeekingFile(e) => write!(f, "failed to seek file: {}", e),
-            SettingFileSize(e) => write!(f, "failed to set file size: {}", e),
             SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {}", e),
             SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
             TooManyL1Entries(count) => write!(f, "l1 entry table too large: {}", count),
             TooManyRefcounts(count) => write!(f, "ref count table too large: {}", count),
             UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
             UnsupportedVersion(v) => write!(f, "unsupported version: {}", v),
-            WritingData(e) => write!(f, "failed to write data: {}", e),
             WritingHeader(e) => write!(f, "failed to write header: {}", e),
         }
     }
 }
 
-pub enum ImageType {
-    Raw,
-    Qcow2,
-}
-
 // Maximum data size supported.
 const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB.
 
 // QCOW magic constant that starts the header.
-const QCOW_MAGIC: u32 = 0x5146_49fb;
+pub const QCOW_MAGIC: u32 = 0x5146_49fb;
 // Default to a cluster size of 2^DEFAULT_CLUSTER_BITS
 const DEFAULT_CLUSTER_BITS: u32 = 16;
 // Limit clusters to reasonable sizes. Choose the same limits as qemu. Making the clusters smaller
@@ -1599,129 +1588,6 @@ fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
     dividend / divisor + if dividend % divisor != 0 { 1 } else { 0 }
 }
 
-fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
-where
-    R: Read + Seek,
-    W: Write + Seek,
-{
-    const CHUNK_SIZE: usize = 65536;
-    let mut buf = [0; CHUNK_SIZE];
-    let mut read_count = 0;
-    reader
-        .seek(SeekFrom::Start(offset))
-        .map_err(Error::SeekingFile)?;
-    writer
-        .seek(SeekFrom::Start(offset))
-        .map_err(Error::SeekingFile)?;
-    loop {
-        let this_count = min(CHUNK_SIZE as u64, size - read_count) as usize;
-        let nread = reader
-            .read(&mut buf[..this_count])
-            .map_err(Error::ReadingData)?;
-        writer.write(&buf[..nread]).map_err(Error::WritingData)?;
-        read_count += nread as u64;
-        if nread == 0 || read_count == size {
-            break;
-        }
-    }
-
-    Ok(())
-}
-
-fn convert_reader_writer<R, W>(reader: &mut R, writer: &mut W, size: u64) -> Result<()>
-where
-    R: Read + Seek + SeekHole,
-    W: Write + Seek,
-{
-    let mut offset = 0;
-    while offset < size {
-        // Find the next range of data.
-        let next_data = match reader.seek_data(offset).map_err(Error::SeekingFile)? {
-            Some(o) => o,
-            None => {
-                // No more data in the file.
-                break;
-            }
-        };
-        let next_hole = match reader.seek_hole(next_data).map_err(Error::SeekingFile)? {
-            Some(o) => o,
-            None => {
-                // This should not happen - there should always be at least one hole
-                // after any data.
-                return Err(Error::SeekingFile(io::Error::from_raw_os_error(EINVAL)));
-            }
-        };
-        let count = next_hole - next_data;
-        convert_copy(reader, writer, next_data, count)?;
-        offset = next_hole;
-    }
-
-    Ok(())
-}
-
-fn convert_reader<R>(reader: &mut R, dst_file: File, dst_type: ImageType) -> Result<()>
-where
-    R: Read + Seek + SeekHole,
-{
-    let src_size = reader.seek(SeekFrom::End(0)).map_err(Error::SeekingFile)?;
-    reader
-        .seek(SeekFrom::Start(0))
-        .map_err(Error::SeekingFile)?;
-
-    // Ensure the destination file is empty before writing to it.
-    dst_file.set_len(0).map_err(Error::SettingFileSize)?;
-
-    match dst_type {
-        ImageType::Qcow2 => {
-            let mut dst_writer = QcowFile::new(dst_file, src_size)?;
-            convert_reader_writer(reader, &mut dst_writer, src_size)
-        }
-        ImageType::Raw => {
-            let mut dst_writer = dst_file;
-            // Set the length of the destination file to convert it into a sparse file
-            // of the desired size.
-            dst_writer
-                .set_len(src_size)
-                .map_err(Error::SettingFileSize)?;
-            convert_reader_writer(reader, &mut dst_writer, src_size)
-        }
-    }
-}
-
-/// Copy the contents of a disk image in `src_file` into `dst_file`.
-/// The type of `src_file` is automatically detected, and the output file type is
-/// determined by `dst_type`.
-pub fn convert(src_file: File, dst_file: File, dst_type: ImageType) -> Result<()> {
-    let src_type = detect_image_type(&src_file)?;
-    match src_type {
-        ImageType::Qcow2 => {
-            let mut src_reader = QcowFile::from(src_file)?;
-            convert_reader(&mut src_reader, dst_file, dst_type)
-        }
-        ImageType::Raw => {
-            // src_file is a raw file.
-            let mut src_reader = src_file;
-            convert_reader(&mut src_reader, dst_file, dst_type)
-        }
-    }
-}
-
-/// Detect the type of an image file by checking for a valid qcow2 header.
-pub fn detect_image_type(file: &File) -> Result<ImageType> {
-    let mut f = file;
-    let orig_seek = f.seek(SeekFrom::Current(0)).map_err(Error::SeekingFile)?;
-    f.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
-    let magic = read_u32_from_file(f)?;
-    let image_type = if magic == QCOW_MAGIC {
-        ImageType::Qcow2
-    } else {
-        ImageType::Raw
-    };
-    f.seek(SeekFrom::Start(orig_seek))
-        .map_err(Error::SeekingFile)?;
-    Ok(image_type)
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/qcow_utils/Cargo.toml b/qcow_utils/Cargo.toml
index 67b9d28..88326db 100644
--- a/qcow_utils/Cargo.toml
+++ b/qcow_utils/Cargo.toml
@@ -15,5 +15,6 @@ path = "src/qcow_img.rs"
 [dependencies]
 getopts = "*"
 libc = "*"
+disk = { path = "../disk" }
 qcow = { path = "../qcow" }
 sys_util = { path = "../sys_util" }
diff --git a/qcow_utils/src/qcow_img.rs b/qcow_utils/src/qcow_img.rs
index 66230da..08040a1 100644
--- a/qcow_utils/src/qcow_img.rs
+++ b/qcow_utils/src/qcow_img.rs
@@ -304,12 +304,12 @@ fn convert(src_path: &str, dst_path: &str) -> std::result::Result<(), ()> {
     };
 
     let dst_type = if dst_path.ends_with("qcow2") {
-        qcow::ImageType::Qcow2
+        disk::ImageType::Qcow2
     } else {
-        qcow::ImageType::Raw
+        disk::ImageType::Raw
     };
 
-    match qcow::convert(src_file, dst_file, dst_type) {
+    match disk::convert(src_file, dst_file, dst_type) {
         Ok(_) => {
             println!("Converted {} to {}", src_path, dst_path);
             Ok(())
diff --git a/qcow_utils/src/qcow_utils.rs b/qcow_utils/src/qcow_utils.rs
index 6c8b86f..4c3ede7 100644
--- a/qcow_utils/src/qcow_utils.rs
+++ b/qcow_utils/src/qcow_utils.rs
@@ -13,7 +13,8 @@ use std::os::raw::{c_char, c_int};
 use std::os::unix::io::FromRawFd;
 use std::panic::catch_unwind;
 
-use qcow::{ImageType, QcowFile};
+use disk::ImageType;
+use qcow::QcowFile;
 use sys_util::{flock, FileSetLen, FlockOperation};
 
 trait DiskFile: FileSetLen + Seek {}
@@ -71,7 +72,7 @@ pub unsafe extern "C" fn expand_disk_image(path: *const c_char, virtual_size: u6
         return -EIO;
     }
 
-    let image_type = match qcow::detect_image_type(&raw_image) {
+    let image_type = match disk::detect_image_type(&raw_image) {
         Ok(t) => t,
         Err(_) => return -EINVAL,
     };
@@ -120,7 +121,7 @@ pub unsafe extern "C" fn convert_to_qcow2(src_fd: c_int, dst_fd: c_int) -> c_int
     match (src_file_owned, dst_file_owned) {
         (Ok(src_file), Ok(dst_file)) => {
             catch_unwind(
-                || match qcow::convert(src_file, dst_file, ImageType::Qcow2) {
+                || match disk::convert(src_file, dst_file, ImageType::Qcow2) {
                     Ok(_) => 0,
                     Err(_) => -EIO,
                 },
@@ -145,7 +146,7 @@ pub unsafe extern "C" fn convert_to_raw(src_fd: c_int, dst_fd: c_int) -> c_int {
 
     match (src_file_owned, dst_file_owned) {
         (Ok(src_file), Ok(dst_file)) => {
-            catch_unwind(|| match qcow::convert(src_file, dst_file, ImageType::Raw) {
+            catch_unwind(|| match disk::convert(src_file, dst_file, ImageType::Raw) {
                 Ok(_) => 0,
                 Err(_) => -EIO,
             })
diff --git a/src/linux.rs b/src/linux.rs
index 75b09b5..7ce7c82 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -32,7 +32,6 @@ use kvm::*;
 use libcras::CrasClient;
 use msg_socket::{MsgError, MsgReceiver, MsgSender, MsgSocket};
 use net_util::{Error as NetError, MacAddress, Tap};
-use qcow::{self, ImageType, QcowFile};
 use rand_ish::SimpleRng;
 use remain::sorted;
 use resources::{Alloc, SystemAllocator};
@@ -82,6 +81,7 @@ pub enum Error {
     ChownTpmStorage(sys_util::Error),
     CloneEventFd(sys_util::Error),
     CreateCrasClient(libcras::Error),
+    CreateDiskError(disk::Error),
     CreateEventFd(sys_util::Error),
     CreatePollContext(sys_util::Error),
     CreateSignalFd(sys_util::SignalFdError),
@@ -90,7 +90,6 @@ pub enum Error {
     CreateTimerFd(sys_util::Error),
     CreateTpmStorage(PathBuf, io::Error),
     CreateUsbProvider(devices::usb::host_backend::error::Error),
-    DetectImageType(qcow::Error),
     DeviceJail(io_jail::Error),
     DevicePivotRoot(io_jail::Error),
     Disk(io::Error),
@@ -114,7 +113,6 @@ pub enum Error {
     PmemDeviceNew(sys_util::Error),
     PollContextAdd(sys_util::Error),
     PollContextDelete(sys_util::Error),
-    QcowDeviceCreate(qcow::Error),
     ReadLowmemAvailable(io::Error),
     ReadLowmemMargin(io::Error),
     RegisterBalloon(arch::DeviceRegistrationError),
@@ -162,6 +160,7 @@ impl Display for Error {
             ChownTpmStorage(e) => write!(f, "failed to chown tpm storage: {}", e),
             CloneEventFd(e) => write!(f, "failed to clone eventfd: {}", e),
             CreateCrasClient(e) => write!(f, "failed to create cras client: {}", e),
+            CreateDiskError(e) => write!(f, "failed to create virtual disk: {}", e),
             CreateEventFd(e) => write!(f, "failed to create eventfd: {}", e),
             CreatePollContext(e) => write!(f, "failed to create poll context: {}", e),
             CreateSignalFd(e) => write!(f, "failed to create signalfd: {}", e),
@@ -172,7 +171,6 @@ impl Display for Error {
                 write!(f, "failed to create tpm storage dir {}: {}", p.display(), e)
             }
             CreateUsbProvider(e) => write!(f, "failed to create usb provider: {}", e),
-            DetectImageType(e) => write!(f, "failed to detect disk image type: {}", e),
             DeviceJail(e) => write!(f, "failed to jail device: {}", e),
             DevicePivotRoot(e) => write!(f, "failed to pivot root device: {}", e),
             Disk(e) => write!(f, "failed to load disk image: {}", e),
@@ -203,7 +201,6 @@ impl Display for Error {
             PmemDeviceNew(e) => write!(f, "failed to create pmem device: {}", e),
             PollContextAdd(e) => write!(f, "failed to add fd to poll context: {}", e),
             PollContextDelete(e) => write!(f, "failed to remove fd from poll context: {}", e),
-            QcowDeviceCreate(e) => write!(f, "failed to read qcow formatted file {}", e),
             ReadLowmemAvailable(e) => write!(
                 f,
                 "failed to read /sys/kernel/mm/chromeos-low_mem/available: {}",
@@ -351,25 +348,12 @@ fn create_block_device(
     };
     flock(&raw_image, lock_op, true).map_err(Error::DiskImageLock)?;
 
-    let image_type = qcow::detect_image_type(&raw_image).map_err(Error::DetectImageType)?;
-    let dev = match image_type {
-        ImageType::Raw => {
-            // Access as a raw block device.
-            let dev = virtio::Block::new(raw_image, disk.read_only, Some(disk_device_socket))
-                .map_err(Error::BlockDeviceNew)?;
-            Box::new(dev) as Box<dyn VirtioDevice>
-        }
-        ImageType::Qcow2 => {
-            // Valid qcow header present
-            let qcow_image = QcowFile::from(raw_image).map_err(Error::QcowDeviceCreate)?;
-            let dev = virtio::Block::new(qcow_image, disk.read_only, Some(disk_device_socket))
-                .map_err(Error::BlockDeviceNew)?;
-            Box::new(dev) as Box<dyn VirtioDevice>
-        }
-    };
+    let disk_file = disk::create_disk_file(raw_image).map_err(Error::CreateDiskError)?;
+    let dev = virtio::Block::new(disk_file, disk.read_only, Some(disk_device_socket))
+        .map_err(Error::BlockDeviceNew)?;
 
     Ok(VirtioDeviceStub {
-        dev,
+        dev: Box::new(dev),
         jail: simple_jail(&cfg, "block_device.policy")?,
     })
 }