mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-24 12:34:31 +00:00
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>
This commit is contained in:
parent
971589f7ec
commit
7d533e5952
12 changed files with 277 additions and 199 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -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",
|
||||
|
@ -162,6 +164,16 @@ dependencies = [
|
|||
"vm_control 0.1.0",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -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",
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
14
disk/Cargo.toml
Normal file
14
disk/Cargo.toml
Normal file
|
@ -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" }
|
209
disk/src/disk.rs
Normal file
209
disk/src/disk.rs
Normal file
|
@ -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>
|
||||
}
|
||||
})
|
||||
}
|
136
qcow/src/qcow.rs
136
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::*;
|
||||
|
|
|
@ -15,5 +15,6 @@ path = "src/qcow_img.rs"
|
|||
[dependencies]
|
||||
getopts = "*"
|
||||
libc = "*"
|
||||
disk = { path = "../disk" }
|
||||
qcow = { path = "../qcow" }
|
||||
sys_util = { path = "../sys_util" }
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
28
src/linux.rs
28
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")?,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue