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>
This commit is contained in:
Dylan Reid 2018-07-13 10:42:48 -07:00 committed by chrome-bot
parent 5ad21740e4
commit 2dcb632405
3 changed files with 137 additions and 35 deletions

View file

@ -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);
});
}
}

View file

@ -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,
}
}

View file

@ -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::<u64>() {
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();