From 2dcb632405718305b7443501bd3839987e9971f7 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 13 Jul 2018 10:42:48 -0700 Subject: [PATCH] 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 Reviewed-on: https://chromium-review.googlesource.com/1136900 Reviewed-by: Stephen Barber --- qcow/src/qcow.rs | 113 +++++++++++++++++++++++++++-------- qcow_utils/src/qcow_utils.rs | 20 +++---- src/main.rs | 39 ++++++++++++ 3 files changed, 137 insertions(+), 35 deletions(-) diff --git a/qcow/src/qcow.rs b/qcow/src/qcow.rs index d4cb6affb7..9124ab1d15 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 { + 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> { + 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::() 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::() 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::(refcount) } + + // Gets the refcount for a cluster with the given address. + fn get_cluster_refcount(&mut self, address: u64) -> 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 { + 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::() + } } impl AsRawFd for QcowFile { @@ -618,15 +683,12 @@ mod tests { fn with_default_file(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); }); } } diff --git a/qcow_utils/src/qcow_utils.rs b/qcow_utils/src/qcow_utils.rs index 308fb012a7..c96678e5c4 100644 --- a/qcow_utils/src/qcow_utils.rs +++ b/qcow_utils/src/qcow_utils.rs @@ -7,12 +7,12 @@ extern crate libc; extern crate qcow; +use libc::EINVAL; use std::ffi::CStr; use std::fs::OpenOptions; use std::os::raw::{c_char, c_int}; -use libc::EINVAL; -use qcow::QcowHeader; +use qcow::QcowFile; #[no_mangle] pub unsafe extern "C" fn create_qcow_with_size(path: *const c_char, virtual_size: u64) -> c_int { @@ -27,18 +27,18 @@ pub unsafe extern "C" fn create_qcow_with_size(path: *const c_char, virtual_size Err(_) => return -EINVAL, }; - let h = QcowHeader::create_for_size(virtual_size); - let mut file = match OpenOptions::new() - .create(true) - .read(true) - .write(true) - .open(file_path) { + let file = match OpenOptions::new() + .create(true) + .read(true) + .write(true) + .open(file_path) + { Ok(f) => f, Err(_) => return -1, }; - match h.write_to(&mut file) { - Ok(()) => 0, + match QcowFile::new(file, virtual_size) { + Ok(_) => 0, Err(_) => -1, } } diff --git a/src/main.rs b/src/main.rs index 5ebd517eff..1b4fb641df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,7 @@ pub mod linux; #[cfg(feature = "plugin")] pub mod plugin; +use std::fs::OpenOptions; use std::net; use std::os::unix::io::RawFd; use std::os::unix::net::UnixDatagram; @@ -46,6 +47,7 @@ use std::thread::sleep; use std::time::Duration; use sys_util::{Scm, getpid, kill_process_group, reap_child, syslog}; +use qcow::QcowFile; use argument::{Argument, set_arguments, print_help}; use vm_control::VmRequest; @@ -534,11 +536,45 @@ fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> { return_result } +fn create_qcow2(mut args: std::env::Args) -> std::result::Result<(), ()> { + if args.len() != 2 { + print_help("crosvm create_qcow2", "PATH SIZE", &[]); + println!("Create a new QCOW2 image at `PATH` of the specified `SIZE` in megabytes."); + } + let file_path = args.nth(0).unwrap(); + let size: u64 = match args.nth(0).unwrap().parse::() { + Ok(n) => n, + Err(_) => { + error!("Failed to parse size of the disk."); + return Err(()); + }, + }; + + let file = OpenOptions::new() + .create(true) + .read(true) + .write(true) + .open(&file_path) + .map_err(|e| { + error!("Failed opening qcow file at '{}': {:?}", file_path, e); + () + })?; + + QcowFile::new(file, size) + .map_err(|e| { + error!("Failed to create qcow file at '{}': {:?}", file_path, e); + () + })?; + + Ok(()) +} + fn print_usage() { print_help("crosvm", "[stop|run]", &[]); println!("Commands:"); println!(" stop - Stops crosvm instances via their control sockets."); println!(" run - Start a new crosvm instance."); + println!(" create_qcow2 - Create a new qcow2 disk image file."); } fn crosvm_main() -> std::result::Result<(), ()> { @@ -568,6 +604,9 @@ fn crosvm_main() -> std::result::Result<(), ()> { Some("balloon") => { balloon_vms(args) } + Some("create_qcow2") => { + create_qcow2(args) + } Some(c) => { println!("invalid subcommand: {:?}", c); print_usage();