summary refs log tree commit diff
path: root/disk
diff options
context:
space:
mode:
authorA. Cody Schuffelen <schuffelen@google.com>2019-12-23 18:27:11 -0800
committerCommit Bot <commit-bot@chromium.org>2020-02-21 21:52:53 +0000
commit9ca6039b030a5c83062cfec9a5ff52f42814fa13 (patch)
treeda4efa052a2f0afe1b736a26ad83eb216c90b7e0 /disk
parent4f48eab6027a3ab3e4b87893f7ec275f67f62922 (diff)
downloadcrosvm-9ca6039b030a5c83062cfec9a5ff52f42814fa13.tar
crosvm-9ca6039b030a5c83062cfec9a5ff52f42814fa13.tar.gz
crosvm-9ca6039b030a5c83062cfec9a5ff52f42814fa13.tar.bz2
crosvm-9ca6039b030a5c83062cfec9a5ff52f42814fa13.tar.lz
crosvm-9ca6039b030a5c83062cfec9a5ff52f42814fa13.tar.xz
crosvm-9ca6039b030a5c83062cfec9a5ff52f42814fa13.tar.zst
crosvm-9ca6039b030a5c83062cfec9a5ff52f42814fa13.zip
Support generating and opening backing files
The new functionality can be invoked through
"crosvm create_qcow2 --backing_file=backing new_file".

The old behavior of creating a qcow image with a particular size is
still available with its original syntax.

This is relevant to implement as by default something like qemu-img will
create a new image that assumes the backing file is raw or qcow, while
crosvm can use its knowledge of other formats (such as composite disk,
and later android sparse) to determine the true size of the backing
file.

TEST=unit tests
BUG=b:140069322
Change-Id: I22de6a79c6d8566a9fcb0bc8124e2d74fea9ca55
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1982833
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Daniel Verkamp <dverkamp@chromium.org>
Diffstat (limited to 'disk')
-rw-r--r--disk/src/qcow/mod.rs162
1 files changed, 131 insertions, 31 deletions
diff --git a/disk/src/qcow/mod.rs b/disk/src/qcow/mod.rs
index add4f48..1e3a451 100644
--- a/disk/src/qcow/mod.rs
+++ b/disk/src/qcow/mod.rs
@@ -10,31 +10,35 @@ use data_model::{VolatileMemory, VolatileSlice};
 use libc::{EINVAL, ENOSPC, ENOTSUP};
 use remain::sorted;
 use sys_util::{
-    error, FileAllocate, FileReadWriteAtVolatile, FileReadWriteVolatile, FileSetLen, FileSync,
-    PunchHole, SeekHole, WriteZeroesAt,
+    error, AsRawFds, FileAllocate, FileReadWriteAtVolatile, FileReadWriteVolatile, FileSetLen,
+    FileSync, PunchHole, SeekHole, WriteZeroesAt,
 };
 
 use std::cmp::{max, min};
 use std::fmt::{self, Display};
-use std::fs::File;
+use std::fs::{File, OpenOptions};
 use std::io::{self, Read, Seek, SeekFrom, Write};
 use std::mem::size_of;
 use std::os::unix::io::{AsRawFd, RawFd};
+use std::str;
 
 use crate::qcow::qcow_raw_file::QcowRawFile;
 use crate::qcow::refcount::RefCount;
 use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
-use crate::{DiskFile, DiskGetLen};
+use crate::{create_disk_file, DiskFile, DiskGetLen};
 
 #[sorted]
 #[derive(Debug)]
 pub enum Error {
-    BackingFilesNotSupported,
+    BackingFileIo(io::Error),
+    BackingFileOpen(Box<crate::Error>),
+    BackingFileTooLong(usize),
     CompressedBlocksNotSupported,
     EvictingCache(io::Error),
     FileTooBig(u64),
     GettingFileSize(io::Error),
     GettingRefcount(refcount::Error),
+    InvalidBackingFileName(str::Utf8Error),
     InvalidClusterIndex,
     InvalidClusterSize,
     InvalidIndex,
@@ -74,7 +78,11 @@ impl Display for Error {
 
         #[sorted]
         match self {
-            BackingFilesNotSupported => write!(f, "backing files not supported"),
+            BackingFileIo(e) => write!(f, "backing file io error: {}", e),
+            BackingFileOpen(e) => write!(f, "backing file open error: {}", *e),
+            BackingFileTooLong(len) => {
+                write!(f, "backing file name is too long: {} bytes over", len)
+            }
             CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
             EvictingCache(e) => write!(f, "failed to evict cache: {}", e),
             FileTooBig(size) => write!(
@@ -84,6 +92,7 @@ impl Display for Error {
             ),
             GettingFileSize(e) => write!(f, "failed to get file size: {}", e),
             GettingRefcount(e) => write!(f, "failed to get refcount: {}", e),
+            InvalidBackingFileName(e) => write!(f, "failed to parse filename: {}", e),
             InvalidClusterIndex => write!(f, "invalid cluster index"),
             InvalidClusterSize => write!(f, "invalid cluster size"),
             InvalidIndex => write!(f, "invalid index"),
@@ -144,8 +153,14 @@ const COMPRESSED_FLAG: u64 = 1 << 62;
 const CLUSTER_USED_FLAG: u64 = 1 << 63;
 const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1 << 0;
 
+// The format supports a "header extension area", that crosvm does not use.
+const QCOW_EMPTY_HEADER_EXTENSION_SIZE: u32 = 8;
+
+// Defined by the specification
+const MAX_BACKING_FILE_SIZE: u32 = 1023;
+
 /// Contains the information from the header of a qcow file.
-#[derive(Copy, Clone, Debug)]
+#[derive(Clone, Debug)]
 pub struct QcowHeader {
     pub magic: u32,
     pub version: u32,
@@ -172,6 +187,9 @@ pub struct QcowHeader {
     pub autoclear_features: u64,
     pub refcount_order: u32,
     pub header_size: u32,
+
+    // Post-header entries
+    pub backing_file_path: Option<String>,
 }
 
 // Reads the next u16 from the file.
@@ -211,7 +229,7 @@ impl QcowHeader {
             return Err(Error::InvalidMagic);
         }
 
-        Ok(QcowHeader {
+        let mut header = QcowHeader {
             magic,
             version: read_u32_from_file(f)?,
             backing_file_offset: read_u64_from_file(f)?,
@@ -230,24 +248,50 @@ impl QcowHeader {
             autoclear_features: read_u64_from_file(f)?,
             refcount_order: read_u32_from_file(f)?,
             header_size: read_u32_from_file(f)?,
-        })
+            backing_file_path: None,
+        };
+        if header.backing_file_size > MAX_BACKING_FILE_SIZE {
+            return Err(Error::BackingFileTooLong(header.backing_file_size as usize));
+        }
+        if header.backing_file_offset != 0 {
+            f.seek(SeekFrom::Start(header.backing_file_offset))
+                .map_err(Error::ReadingHeader)?;
+            let mut backing_file_name_bytes = vec![0u8; header.backing_file_size as usize];
+            f.read_exact(&mut backing_file_name_bytes)
+                .map_err(Error::ReadingHeader)?;
+            header.backing_file_path = Some(
+                String::from_utf8(backing_file_name_bytes)
+                    .map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?,
+            );
+        }
+        Ok(header)
     }
 
-    /// Create a header for the given `size`.
-    pub fn create_for_size(size: u64) -> QcowHeader {
+    pub fn create_for_size_and_path(size: u64, backing_file: Option<&str>) -> Result<QcowHeader> {
         let cluster_bits: u32 = DEFAULT_CLUSTER_BITS;
         let cluster_size: u32 = 0x01 << cluster_bits;
+        let max_length: usize =
+            (cluster_size - V3_BARE_HEADER_SIZE - QCOW_EMPTY_HEADER_EXTENSION_SIZE) as usize;
+        if let Some(path) = backing_file {
+            if path.len() > max_length {
+                return Err(Error::BackingFileTooLong(path.len() - max_length));
+            }
+        }
         // L2 blocks are always one cluster long. They contain cluster_size/sizeof(u64) addresses.
         let l2_size: u32 = cluster_size / size_of::<u64>() as u32;
         let num_clusters: u32 = div_round_up_u64(size, u64::from(cluster_size)) as u32;
         let num_l2_clusters: u32 = div_round_up_u32(num_clusters, l2_size);
         let l1_clusters: u32 = div_round_up_u32(num_l2_clusters, cluster_size);
         let header_clusters = div_round_up_u32(size_of::<QcowHeader>() as u32, cluster_size);
-        QcowHeader {
+        Ok(QcowHeader {
             magic: QCOW_MAGIC,
             version: 3,
-            backing_file_offset: 0,
-            backing_file_size: 0,
+            backing_file_offset: (if backing_file.is_none() {
+                0
+            } else {
+                V3_BARE_HEADER_SIZE + QCOW_EMPTY_HEADER_EXTENSION_SIZE
+            }) as u64,
+            backing_file_size: backing_file.map_or(0, |x| x.len()) as u32,
             cluster_bits: DEFAULT_CLUSTER_BITS,
             size,
             crypt_method: 0,
@@ -277,7 +321,8 @@ impl QcowHeader {
             autoclear_features: 0,
             refcount_order: DEFAULT_REFCOUNT_ORDER,
             header_size: V3_BARE_HEADER_SIZE,
-        }
+            backing_file_path: backing_file.map(|x| String::from(x)),
+        })
     }
 
     /// Write the header to `file`.
@@ -312,6 +357,11 @@ impl QcowHeader {
         write_u64_to_file(file, self.autoclear_features)?;
         write_u32_to_file(file, self.refcount_order)?;
         write_u32_to_file(file, self.header_size)?;
+        write_u32_to_file(file, 0)?; // header extension type: end of header extension area
+        write_u32_to_file(file, 0)?; // length of header extension data: 0
+        if let Some(backing_file_path) = self.backing_file_path.as_ref() {
+            write!(file, "{}", backing_file_path).map_err(Error::WritingHeader)?;
+        }
 
         // Set the file length by seeking and writing a zero to the last byte. This avoids needing
         // a `File` instead of anything that implements seek as the `file` argument.
@@ -365,7 +415,7 @@ pub struct QcowFile {
     // List of unreferenced clusters available to be used. unref clusters become available once the
     // removal of references to them have been synced to disk.
     avail_clusters: Vec<u64>,
-    //TODO(dgreid) Add support for backing files. - backing_file: Option<Box<QcowFile<T>>>,
+    backing_file: Option<Box<dyn DiskFile>>,
 }
 
 impl QcowFile {
@@ -394,10 +444,18 @@ impl QcowFile {
             return Err(Error::FileTooBig(header.size));
         }
 
-        // No current support for backing files.
-        if header.backing_file_offset != 0 {
-            return Err(Error::BackingFilesNotSupported);
-        }
+        let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() {
+            let path = backing_file_path.clone();
+            let backing_raw_file = OpenOptions::new()
+                .read(true)
+                .open(path)
+                .map_err(Error::BackingFileIo)?;
+            let backing_file = create_disk_file(backing_raw_file)
+                .map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
+            Some(backing_file)
+        } else {
+            None
+        };
 
         // Only support two byte refcounts.
         let refcount_bits: u64 = 0x01u64
@@ -412,7 +470,6 @@ impl QcowFile {
         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.snapshots_offset, header.cluster_bits)?;
         // refcount table must be a cluster boundary, and within the file's virtual or actual size.
@@ -444,7 +501,7 @@ impl QcowFile {
         let mut raw_file =
             QcowRawFile::from(file, cluster_size).ok_or(Error::InvalidClusterSize)?;
         if refcount_rebuild_required {
-            QcowFile::rebuild_refcounts(&mut raw_file, header)?;
+            QcowFile::rebuild_refcounts(&mut raw_file, header.clone())?;
         }
 
         let l2_size = cluster_size / size_of::<u64>() as u64;
@@ -500,6 +557,7 @@ impl QcowFile {
             current_offset: 0,
             unref_clusters: Vec::new(),
             avail_clusters: Vec::new(),
+            backing_file,
         };
 
         // Check that the L1 and refcount tables fit in a 64bit address space.
@@ -518,8 +576,27 @@ impl QcowFile {
     }
 
     /// 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);
+    pub fn new(file: File, virtual_size: u64) -> Result<QcowFile> {
+        let header = QcowHeader::create_for_size_and_path(virtual_size, None)?;
+        QcowFile::new_from_header(file, header)
+    }
+
+    /// Creates a new QcowFile at the given path.
+    pub fn new_from_backing(file: File, backing_file_name: &str) -> Result<QcowFile> {
+        let backing_raw_file = OpenOptions::new()
+            .read(true)
+            .open(backing_file_name)
+            .map_err(Error::BackingFileIo)?;
+        let backing_file =
+            create_disk_file(backing_raw_file).map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
+        let size = backing_file.get_len().map_err(Error::BackingFileIo)?;
+        let header = QcowHeader::create_for_size_and_path(size, Some(backing_file_name))?;
+        let mut result = QcowFile::new_from_header(file, header)?;
+        result.backing_file = Some(backing_file);
+        Ok(result)
+    }
+
+    fn new_from_header(mut file: File, header: QcowHeader) -> Result<QcowFile> {
         file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
         header.write_to(&mut file)?;
 
@@ -862,9 +939,9 @@ impl QcowFile {
 
         // Find all references clusters and rebuild refcounts.
         set_header_refcount(&mut refcounts, cluster_size)?;
-        set_l1_refcounts(&mut refcounts, header, cluster_size)?;
-        set_data_refcounts(&mut refcounts, header, cluster_size, raw_file)?;
-        set_refcount_table_refcounts(&mut refcounts, header, cluster_size)?;
+        set_l1_refcounts(&mut refcounts, header.clone(), cluster_size)?;
+        set_data_refcounts(&mut refcounts, header.clone(), cluster_size, raw_file)?;
+        set_refcount_table_refcounts(&mut refcounts, header.clone(), cluster_size)?;
 
         // Allocate clusters to store the new reference count blocks.
         let ref_table = alloc_refblocks(
@@ -1424,9 +1501,13 @@ impl Drop for QcowFile {
     }
 }
 
-impl AsRawFd for QcowFile {
-    fn as_raw_fd(&self) -> RawFd {
-        self.raw_file.file().as_raw_fd()
+impl AsRawFds for QcowFile {
+    fn as_raw_fds(&self) -> Vec<RawFd> {
+        let mut fds = vec![self.raw_file.file().as_raw_fd()];
+        if let Some(backing) = &self.backing_file {
+            fds.append(&mut backing.as_raw_fds());
+        }
+        fds
     }
 }
 
@@ -1739,10 +1820,11 @@ mod tests {
 
     #[test]
     fn default_header() {
-        let header = QcowHeader::create_for_size(0x10_0000);
+        let header = QcowHeader::create_for_size_and_path(0x10_0000, None);
         let shm = SharedMemory::anon().unwrap();
         let mut disk_file: File = shm.into();
         header
+            .expect("Failed to create header.")
             .write_to(&mut disk_file)
             .expect("Failed to write header to shm.");
         disk_file.seek(SeekFrom::Start(0)).unwrap();
@@ -1757,6 +1839,24 @@ mod tests {
     }
 
     #[test]
+    fn header_with_backing() {
+        let header = QcowHeader::create_for_size_and_path(0x10_0000, Some("/my/path/to/a/file"))
+            .expect("Failed to create header.");
+        let shm = SharedMemory::anon().unwrap();
+        let mut disk_file: File = shm.into();
+        header
+            .write_to(&mut disk_file)
+            .expect("Failed to write header to shm.");
+        disk_file.seek(SeekFrom::Start(0)).unwrap();
+        let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header.");
+        assert_eq!(
+            header.backing_file_path,
+            Some(String::from("/my/path/to/a/file"))
+        );
+        assert_eq!(read_header.backing_file_path, header.backing_file_path);
+    }
+
+    #[test]
     fn invalid_magic() {
         let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];
         with_basic_file(&invalid_header, |mut disk_file: File| {