mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: aligned: Add seeking cursor and drop RawFile
RawFile wrapped AlignedFile only to add a seek position and the file trait impls that the qcow and vhost_user_block code expects. Fold that position and every impl onto AlignedFile so the wrapper layer goes away and callers work with a single O_DIRECT aware file type. AlignedFile now tracks a cursor and implements Read, Write, Seek, WriteZeroesAt, PunchHole, FileSync, SeekHole, BlockBackend, Clone, AsRawFd and AsFd in addition to the positional FileExt path. The direct_io flag is dropped because alignment already encodes it, where a zero alignment means the file was not opened with O_DIRECT. All RawFile uses in the qcow internals and vhost_user_block move to AlignedFile, and raw_file.rs is removed. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
committed by
Rob Bradford
parent
4c7e2b83c1
commit
516f4e447d
+235
-11
@@ -2,13 +2,19 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::{File, Metadata};
|
||||||
use std::io;
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
|
use std::os::fd::{AsFd, BorrowedFd};
|
||||||
use std::os::unix::fs::FileExt;
|
use std::os::unix::fs::FileExt;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use std::result;
|
||||||
|
|
||||||
|
use vmm_sys_util::file_traits::FileSync;
|
||||||
|
use vmm_sys_util::seek_hole::SeekHole;
|
||||||
|
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
||||||
|
|
||||||
use crate::aligned_buffer::AlignedBuffer;
|
use crate::aligned_buffer::AlignedBuffer;
|
||||||
use crate::{SECTOR_SIZE, probe_direct_alignment};
|
use crate::{BlockBackend, SECTOR_SIZE, probe_direct_alignment, query_device_size};
|
||||||
|
|
||||||
/// True when `buf_ptr`/`len`/`offset` already satisfy `alignment`
|
/// True when `buf_ptr`/`len`/`offset` already satisfy `alignment`
|
||||||
/// (`alignment == 0` means no O_DIRECT, so everything is "aligned").
|
/// (`alignment == 0` means no O_DIRECT, so everything is "aligned").
|
||||||
@@ -25,9 +31,10 @@ fn is_aligned(alignment: usize, buf_ptr: usize, len: usize, offset: u64) -> bool
|
|||||||
/// For unaligned requests under O_DIRECT, I/O is bounced through an
|
/// For unaligned requests under O_DIRECT, I/O is bounced through an
|
||||||
/// `AlignedBuffer` (read-modify-write for writes).
|
/// `AlignedBuffer` (read-modify-write for writes).
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct AlignedFile {
|
pub struct AlignedFile {
|
||||||
file: File,
|
file: File,
|
||||||
alignment: usize,
|
alignment: usize,
|
||||||
|
position: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AlignedFile {
|
impl AlignedFile {
|
||||||
@@ -38,7 +45,11 @@ impl AlignedFile {
|
|||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
AlignedFile { file, alignment }
|
AlignedFile {
|
||||||
|
file,
|
||||||
|
alignment,
|
||||||
|
position: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn alignment(&self) -> usize {
|
pub fn alignment(&self) -> usize {
|
||||||
@@ -57,14 +68,49 @@ impl AlignedFile {
|
|||||||
Ok(AlignedFile {
|
Ok(AlignedFile {
|
||||||
file: self.file.try_clone()?,
|
file: self.file.try_clone()?,
|
||||||
alignment: self.alignment,
|
alignment: self.alignment,
|
||||||
|
position: self.position,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_len(&self, size: u64) -> io::Result<()> {
|
||||||
|
self.file.set_len(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn metadata(&self) -> io::Result<Metadata> {
|
||||||
|
self.file.metadata()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync_all(&self) -> io::Result<()> {
|
||||||
|
self.file.sync_all()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync_data(&self) -> io::Result<()> {
|
||||||
|
self.file.sync_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_direct(&self) -> bool {
|
||||||
|
self.alignment != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_writable(&self) -> bool {
|
||||||
|
// SAFETY: fcntl with F_GETFL is safe and doesn't modify the file descriptor
|
||||||
|
let flags = unsafe { libc::fcntl(self.file.as_raw_fd(), libc::F_GETFL) };
|
||||||
|
if flags < 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let access_mode = flags & libc::O_ACCMODE;
|
||||||
|
access_mode == libc::O_WRONLY || access_mode == libc::O_RDWR
|
||||||
|
}
|
||||||
|
|
||||||
/// Wrap `file` with an explicit alignment, bypassing the probe. Used by
|
/// Wrap `file` with an explicit alignment, bypassing the probe. Used by
|
||||||
/// tests to force the bounce/RMW path without a real O_DIRECT fd.
|
/// tests to force the bounce/RMW path without a real O_DIRECT fd.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn with_alignment(file: File, alignment: usize) -> Self {
|
pub fn with_alignment(file: File, alignment: usize) -> Self {
|
||||||
AlignedFile { file, alignment }
|
AlignedFile {
|
||||||
|
file,
|
||||||
|
alignment,
|
||||||
|
position: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,9 +143,123 @@ impl FileExt for AlignedFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Read for AlignedFile {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
let n = self.read_at(buf, self.position)?;
|
||||||
|
self.position += n as u64;
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for AlignedFile {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
let n = self.write_at(buf, self.position)?;
|
||||||
|
self.position += n as u64;
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.file.sync_all()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for AlignedFile {
|
||||||
|
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||||
|
let newpos = match pos {
|
||||||
|
SeekFrom::Start(o) => o,
|
||||||
|
SeekFrom::Current(d) => self
|
||||||
|
.position
|
||||||
|
.checked_add_signed(d)
|
||||||
|
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid seek"))?,
|
||||||
|
SeekFrom::End(d) => query_device_size(&self.file)?
|
||||||
|
.0
|
||||||
|
.checked_add_signed(d)
|
||||||
|
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid seek"))?,
|
||||||
|
};
|
||||||
|
self.position = newpos;
|
||||||
|
Ok(newpos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WriteZeroesAt for AlignedFile {
|
||||||
|
fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> {
|
||||||
|
self.file.write_zeroes_at(offset, length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PunchHole for AlignedFile {
|
||||||
|
fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
|
||||||
|
self.file.punch_hole(offset, length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSync for AlignedFile {
|
||||||
|
fn fsync(&mut self) -> io::Result<()> {
|
||||||
|
self.file.fsync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeekHole for AlignedFile {
|
||||||
|
fn seek_hole(&mut self, offset: u64) -> io::Result<Option<u64>> {
|
||||||
|
match self.file.seek_hole(offset) {
|
||||||
|
Ok(pos) => {
|
||||||
|
if let Some(p) = pos {
|
||||||
|
self.position = p;
|
||||||
|
}
|
||||||
|
Ok(pos)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn seek_data(&mut self, offset: u64) -> io::Result<Option<u64>> {
|
||||||
|
match self.file.seek_data(offset) {
|
||||||
|
Ok(pos) => {
|
||||||
|
if let Some(p) = pos {
|
||||||
|
self.position = p;
|
||||||
|
}
|
||||||
|
Ok(pos)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockBackend for AlignedFile {
|
||||||
|
fn logical_size(&self) -> result::Result<u64, crate::Error> {
|
||||||
|
Ok(query_device_size(&self.file)
|
||||||
|
.map_err(crate::Error::RawFileError)?
|
||||||
|
.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn physical_size(&self) -> result::Result<u64, crate::Error> {
|
||||||
|
Ok(query_device_size(&self.file)
|
||||||
|
.map_err(crate::Error::RawFileError)?
|
||||||
|
.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for AlignedFile {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
self.try_clone().expect("AlignedFile cloning failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for AlignedFile {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.file.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsFd for AlignedFile {
|
||||||
|
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||||
|
self.file.as_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::io::Write;
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
use std::os::unix::fs::FileExt;
|
use std::os::unix::fs::FileExt;
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
use vmm_sys_util::tempfile::TempFile;
|
||||||
@@ -115,14 +275,17 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn forced(file: File, alignment: usize) -> AlignedFile {
|
fn forced(file: File, alignment: usize) -> AlignedFile {
|
||||||
AlignedFile { file, alignment }
|
AlignedFile {
|
||||||
|
file,
|
||||||
|
alignment,
|
||||||
|
position: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_probes_alignment_and_accessors() {
|
fn new_probes_alignment_and_accessors() {
|
||||||
let tf = pattern_file(8192);
|
let tf = pattern_file(8192);
|
||||||
// A tempfile is not O_DIRECT, so probe_direct_alignment reports
|
// Not O_DIRECT, so new() falls back to SECTOR_SIZE (512).
|
||||||
// None and new() falls back to SECTOR_SIZE (512).
|
|
||||||
let mut af = AlignedFile::new(tf.as_file().try_clone().unwrap(), true);
|
let mut af = AlignedFile::new(tf.as_file().try_clone().unwrap(), true);
|
||||||
assert_eq!(af.alignment(), 512);
|
assert_eq!(af.alignment(), 512);
|
||||||
let _ = af.file();
|
let _ = af.file();
|
||||||
@@ -184,4 +347,65 @@ mod tests {
|
|||||||
let mut buf = vec![0u8; 50];
|
let mut buf = vec![0u8; 50];
|
||||||
assert_eq!(af.read_at(&mut buf, 10).unwrap(), 50);
|
assert_eq!(af.read_at(&mut buf, 10).unwrap(), 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unaligned_read_returns_short_read_at_eof() {
|
||||||
|
let file_size = 100usize;
|
||||||
|
let tf = pattern_file(file_size);
|
||||||
|
let mut af = forced(tf.as_file().try_clone().unwrap(), 512);
|
||||||
|
af.seek(SeekFrom::Start(10)).unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0u8; 200];
|
||||||
|
let bytes_read = af.read(&mut buf).unwrap();
|
||||||
|
|
||||||
|
let expected: Vec<u8> = (10..file_size).map(|i| (i % 251) as u8).collect();
|
||||||
|
assert_eq!(bytes_read, expected.len());
|
||||||
|
assert_eq!(&buf[..bytes_read], &expected[..]);
|
||||||
|
assert_eq!(af.position, file_size as u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unaligned_read_beyond_eof_returns_zero() {
|
||||||
|
let tf = pattern_file(100);
|
||||||
|
let mut af = forced(tf.as_file().try_clone().unwrap(), 512);
|
||||||
|
af.seek(SeekFrom::Start(200)).unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0u8; 16];
|
||||||
|
let bytes_read = af.read(&mut buf).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(bytes_read, 0);
|
||||||
|
assert_eq!(af.position, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unaligned_write_extends_at_eof() {
|
||||||
|
let file_size = 100usize;
|
||||||
|
let tf = pattern_file(file_size);
|
||||||
|
let mut af = forced(tf.as_file().try_clone().unwrap(), 512);
|
||||||
|
af.seek(SeekFrom::Start(file_size as u64)).unwrap();
|
||||||
|
|
||||||
|
let data = b"xyz";
|
||||||
|
let bytes_written = af.write(data).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(bytes_written, data.len());
|
||||||
|
assert_eq!(af.position, (file_size + data.len()) as u64);
|
||||||
|
|
||||||
|
let mut readback = vec![0u8; file_size + data.len()];
|
||||||
|
tf.as_file().read_exact_at(&mut readback, 0).unwrap();
|
||||||
|
let expected_prefix: Vec<u8> = (0..file_size).map(|i| (i % 251) as u8).collect();
|
||||||
|
assert_eq!(&readback[..file_size], &expected_prefix[..]);
|
||||||
|
assert_eq!(&readback[file_size..], data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_unaligned_io_is_noop() {
|
||||||
|
let tf = pattern_file(100);
|
||||||
|
let mut af = forced(tf.as_file().try_clone().unwrap(), 512);
|
||||||
|
af.seek(SeekFrom::Start(1)).unwrap();
|
||||||
|
|
||||||
|
let mut read_buf = [];
|
||||||
|
assert_eq!(af.read(&mut read_buf).unwrap(), 0);
|
||||||
|
assert_eq!(af.write(&[]).unwrap(), 0);
|
||||||
|
assert_eq!(af.position, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ use vmm_sys_util::file_traits::FileSync;
|
|||||||
|
|
||||||
use super::decoder::{Decoder, ZlibDecoder, ZstdDecoder};
|
use super::decoder::{Decoder, ZlibDecoder, ZstdDecoder};
|
||||||
use super::qcow_raw_file::BeUint;
|
use super::qcow_raw_file::BeUint;
|
||||||
use super::raw_file::RawFile;
|
|
||||||
use super::{Error, Result, div_round_up_u32, div_round_up_u64};
|
use super::{Error, Result, div_round_up_u32, div_round_up_u64};
|
||||||
|
use crate::aligned_file::AlignedFile;
|
||||||
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
@@ -210,7 +210,7 @@ pub struct QcowHeader {
|
|||||||
impl QcowHeader {
|
impl QcowHeader {
|
||||||
/// Read header extensions, optionally collecting feature names for error reporting.
|
/// Read header extensions, optionally collecting feature names for error reporting.
|
||||||
pub(super) fn read_header_extensions(
|
pub(super) fn read_header_extensions(
|
||||||
f: &mut RawFile,
|
f: &mut AlignedFile,
|
||||||
header: &mut QcowHeader,
|
header: &mut QcowHeader,
|
||||||
mut feature_table: Option<&mut Vec<(u8, String)>>,
|
mut feature_table: Option<&mut Vec<(u8, String)>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -269,7 +269,7 @@ impl QcowHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a QcowHeader from a reference to a file.
|
/// Creates a QcowHeader from a reference to a file.
|
||||||
pub fn new(f: &mut RawFile) -> Result<QcowHeader> {
|
pub fn new(f: &mut AlignedFile) -> Result<QcowHeader> {
|
||||||
f.rewind().map_err(Error::ReadingHeader)?;
|
f.rewind().map_err(Error::ReadingHeader)?;
|
||||||
let magic = u32::read_be(f).map_err(Error::ReadingHeader)?;
|
let magic = u32::read_be(f).map_err(Error::ReadingHeader)?;
|
||||||
if magic != QCOW_MAGIC {
|
if magic != QCOW_MAGIC {
|
||||||
@@ -277,12 +277,12 @@ impl QcowHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reads the next u32 from the file.
|
// Reads the next u32 from the file.
|
||||||
fn read_u32_be(f: &mut RawFile) -> Result<u32> {
|
fn read_u32_be(f: &mut AlignedFile) -> Result<u32> {
|
||||||
u32::read_be(f).map_err(Error::ReadingHeader)
|
u32::read_be(f).map_err(Error::ReadingHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads the next u64 from the file.
|
// Reads the next u64 from the file.
|
||||||
fn read_u64_be(f: &mut RawFile) -> Result<u64> {
|
fn read_u64_be(f: &mut AlignedFile) -> Result<u64> {
|
||||||
u64::read_be(f).map_err(Error::ReadingHeader)
|
u64::read_be(f).map_err(Error::ReadingHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ pub(crate) mod decoder;
|
|||||||
mod header;
|
mod header;
|
||||||
pub(crate) mod metadata;
|
pub(crate) mod metadata;
|
||||||
pub(crate) mod qcow_raw_file;
|
pub(crate) mod qcow_raw_file;
|
||||||
mod raw_file;
|
|
||||||
mod refcount;
|
mod refcount;
|
||||||
mod util;
|
mod util;
|
||||||
mod vec_cache;
|
mod vec_cache;
|
||||||
@@ -34,7 +33,6 @@ use header::{
|
|||||||
};
|
};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use qcow_raw_file::{BeUint, QcowRawFile};
|
use qcow_raw_file::{BeUint, QcowRawFile};
|
||||||
pub use raw_file::RawFile;
|
|
||||||
use refcount::RefCount;
|
use refcount::RefCount;
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -42,6 +40,7 @@ pub(crate) use util::MAX_NESTING_DEPTH;
|
|||||||
use util::{L1_TABLE_OFFSET_MASK, L2_TABLE_OFFSET_MASK, div_round_up_u32, div_round_up_u64};
|
use util::{L1_TABLE_OFFSET_MASK, L2_TABLE_OFFSET_MASK, div_round_up_u32, div_round_up_u64};
|
||||||
use vec_cache::{CacheMap, VecCache};
|
use vec_cache::{CacheMap, VecCache};
|
||||||
|
|
||||||
|
use crate::aligned_file::AlignedFile;
|
||||||
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
||||||
|
|
||||||
#[sorted]
|
#[sorted]
|
||||||
@@ -164,7 +163,7 @@ pub type Result<T> = result::Result<T, Error>;
|
|||||||
/// Concrete backing file variants.
|
/// Concrete backing file variants.
|
||||||
pub(crate) enum BackingKind {
|
pub(crate) enum BackingKind {
|
||||||
/// Raw backing file.
|
/// Raw backing file.
|
||||||
Raw(RawFile),
|
Raw(AlignedFile),
|
||||||
/// QCOW2 backing parsed into metadata and raw file.
|
/// QCOW2 backing parsed into metadata and raw file.
|
||||||
Qcow {
|
Qcow {
|
||||||
inner: Box<metadata::QcowState>,
|
inner: Box<metadata::QcowState>,
|
||||||
@@ -206,7 +205,7 @@ impl BackingFile {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut raw_file = RawFile::new(backing_raw_file, direct_io);
|
let mut raw_file = AlignedFile::new(backing_raw_file, direct_io);
|
||||||
|
|
||||||
// Determine backing file format from header extension or auto-detect
|
// Determine backing file format from header extension or auto-detect
|
||||||
let backing_format = match config.format {
|
let backing_format = match config.format {
|
||||||
@@ -274,7 +273,7 @@ impl Debug for BackingFile {
|
|||||||
///
|
///
|
||||||
/// Used by [`crate::formats::qcow::QcowDisk`] when opening an image.
|
/// Used by [`crate::formats::qcow::QcowDisk`] when opening an image.
|
||||||
pub(crate) fn parse_qcow(
|
pub(crate) fn parse_qcow(
|
||||||
mut file: RawFile,
|
mut file: AlignedFile,
|
||||||
max_nesting_depth: u32,
|
max_nesting_depth: u32,
|
||||||
sparse: bool,
|
sparse: bool,
|
||||||
) -> BlockResult<(metadata::QcowState, Option<BackingFile>, bool)> {
|
) -> BlockResult<(metadata::QcowState, Option<BackingFile>, bool)> {
|
||||||
@@ -874,7 +873,7 @@ fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> BlockRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Detect the type of an image file by checking for a valid qcow2 header.
|
/// Detect the type of an image file by checking for a valid qcow2 header.
|
||||||
pub fn detect_image_type(file: &mut RawFile) -> BlockResult<ImageType> {
|
pub fn detect_image_type(file: &mut AlignedFile) -> BlockResult<ImageType> {
|
||||||
let orig_seek = file
|
let orig_seek = file
|
||||||
.stream_position()
|
.stream_position()
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?;
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SeekingFile(e)))?;
|
||||||
@@ -975,8 +974,9 @@ mod unit_tests {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn basic_file(header: &[u8]) -> RawFile {
|
fn basic_file(header: &[u8]) -> AlignedFile {
|
||||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
let mut disk_file: AlignedFile =
|
||||||
|
AlignedFile::new(TempFile::new().unwrap().into_file(), false);
|
||||||
disk_file.write_all(header).unwrap();
|
disk_file.write_all(header).unwrap();
|
||||||
disk_file.set_len(0x1_0000_0000).unwrap();
|
disk_file.set_len(0x1_0000_0000).unwrap();
|
||||||
disk_file.rewind().unwrap();
|
disk_file.rewind().unwrap();
|
||||||
@@ -985,7 +985,7 @@ mod unit_tests {
|
|||||||
|
|
||||||
fn with_basic_file<F>(header: &[u8], mut testfn: F)
|
fn with_basic_file<F>(header: &[u8], mut testfn: F)
|
||||||
where
|
where
|
||||||
F: FnMut(RawFile),
|
F: FnMut(AlignedFile),
|
||||||
{
|
{
|
||||||
testfn(basic_file(header)); // File closed when the function exits.
|
testfn(basic_file(header)); // File closed when the function exits.
|
||||||
}
|
}
|
||||||
@@ -1007,7 +1007,7 @@ mod unit_tests {
|
|||||||
|
|
||||||
fn try_open_qcow_header(header: &QcowHeader, backing_files: bool) -> BlockResult<QcowDisk> {
|
fn try_open_qcow_header(header: &QcowHeader, backing_files: bool) -> BlockResult<QcowDisk> {
|
||||||
let temp = TempFile::new().unwrap();
|
let temp = TempFile::new().unwrap();
|
||||||
let mut raw = RawFile::new(temp.as_file().try_clone().unwrap(), false);
|
let mut raw = AlignedFile::new(temp.as_file().try_clone().unwrap(), false);
|
||||||
header.write_to(&mut raw).expect("write header");
|
header.write_to(&mut raw).expect("write header");
|
||||||
drop(raw);
|
drop(raw);
|
||||||
let file = temp.into_file();
|
let file = temp.into_file();
|
||||||
@@ -1032,13 +1032,13 @@ mod unit_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn header_read() {
|
fn header_read() {
|
||||||
with_basic_file(&valid_header_v2(), |mut disk_file: RawFile| {
|
with_basic_file(&valid_header_v2(), |mut disk_file: AlignedFile| {
|
||||||
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
||||||
assert_eq!(header.version, 2);
|
assert_eq!(header.version, 2);
|
||||||
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
||||||
assert_eq!(header.header_size, V2_BARE_HEADER_SIZE);
|
assert_eq!(header.header_size, V2_BARE_HEADER_SIZE);
|
||||||
});
|
});
|
||||||
with_basic_file(&valid_header_v3(), |mut disk_file: RawFile| {
|
with_basic_file(&valid_header_v3(), |mut disk_file: AlignedFile| {
|
||||||
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
||||||
assert_eq!(header.version, 3);
|
assert_eq!(header.version, 3);
|
||||||
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
||||||
@@ -1050,7 +1050,8 @@ mod unit_tests {
|
|||||||
fn header_v2_with_backing() {
|
fn header_v2_with_backing() {
|
||||||
let header = QcowHeader::create_for_size_and_path(2, 0x10_0000, Some("/my/path/to/a/file"))
|
let header = QcowHeader::create_for_size_and_path(2, 0x10_0000, Some("/my/path/to/a/file"))
|
||||||
.expect("Failed to create header.");
|
.expect("Failed to create header.");
|
||||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
let mut disk_file: AlignedFile =
|
||||||
|
AlignedFile::new(TempFile::new().unwrap().into_file(), false);
|
||||||
header
|
header
|
||||||
.write_to(&mut disk_file)
|
.write_to(&mut disk_file)
|
||||||
.expect("Failed to write header to shm.");
|
.expect("Failed to write header to shm.");
|
||||||
@@ -1070,7 +1071,8 @@ mod unit_tests {
|
|||||||
fn header_v3_with_backing() {
|
fn header_v3_with_backing() {
|
||||||
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/my/path/to/a/file"))
|
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/my/path/to/a/file"))
|
||||||
.expect("Failed to create header.");
|
.expect("Failed to create header.");
|
||||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
let mut disk_file: AlignedFile =
|
||||||
|
AlignedFile::new(TempFile::new().unwrap().into_file(), false);
|
||||||
header
|
header
|
||||||
.write_to(&mut disk_file)
|
.write_to(&mut disk_file)
|
||||||
.expect("Failed to write header to shm.");
|
.expect("Failed to write header to shm.");
|
||||||
@@ -1094,7 +1096,7 @@ mod unit_tests {
|
|||||||
.expect("Failed to create header.");
|
.expect("Failed to create header.");
|
||||||
header.backing_file_offset = offset;
|
header.backing_file_offset = offset;
|
||||||
header.backing_file_size = size;
|
header.backing_file_size = size;
|
||||||
let mut disk_file: RawFile = RawFile::new(
|
let mut disk_file: AlignedFile = AlignedFile::new(
|
||||||
TempFile::new()
|
TempFile::new()
|
||||||
.expect("Failed to create temp file.")
|
.expect("Failed to create temp file.")
|
||||||
.into_file(),
|
.into_file(),
|
||||||
@@ -1165,11 +1167,12 @@ mod unit_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to create a test file with header extensions
|
/// Helper to create a test file with header extensions
|
||||||
fn create_header_with_extension(ext_type: u32, ext_data: &[u8]) -> (RawFile, QcowHeader) {
|
fn create_header_with_extension(ext_type: u32, ext_data: &[u8]) -> (AlignedFile, QcowHeader) {
|
||||||
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None)
|
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None)
|
||||||
.expect("Failed to create header.");
|
.expect("Failed to create header.");
|
||||||
|
|
||||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
let mut disk_file: AlignedFile =
|
||||||
|
AlignedFile::new(TempFile::new().unwrap().into_file(), false);
|
||||||
header.write_to(&mut disk_file).unwrap();
|
header.write_to(&mut disk_file).unwrap();
|
||||||
|
|
||||||
// Write extension
|
// Write extension
|
||||||
@@ -1304,7 +1307,7 @@ mod unit_tests {
|
|||||||
/// the file until stack overflow.
|
/// the file until stack overflow.
|
||||||
fn new_self_referential_qcow(path: &Path) -> Result<()> {
|
fn new_self_referential_qcow(path: &Path) -> Result<()> {
|
||||||
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, path.to_str())?;
|
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, path.to_str())?;
|
||||||
let mut disk_file = RawFile::new(
|
let mut disk_file = AlignedFile::new(
|
||||||
File::create(path).expect("Failed to create image file."),
|
File::create(path).expect("Failed to create image file."),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@@ -1538,7 +1541,7 @@ mod unit_tests {
|
|||||||
let file = TempFile::new().unwrap().into_file();
|
let file = TempFile::new().unwrap().into_file();
|
||||||
let cluster_size = 0x10000u64;
|
let cluster_size = 0x10000u64;
|
||||||
file.set_len(cluster_size * 2).unwrap();
|
file.set_len(cluster_size * 2).unwrap();
|
||||||
let raw = RawFile::new(file, false);
|
let raw = AlignedFile::new(file, false);
|
||||||
let mut qcow_raw = QcowRawFile::from(raw, cluster_size, bits).unwrap();
|
let mut qcow_raw = QcowRawFile::from(raw, cluster_size, bits).unwrap();
|
||||||
|
|
||||||
let entries = (cluster_size * 8 / bits) as usize;
|
let entries = (cluster_size * 8 / bits) as usize;
|
||||||
@@ -1568,7 +1571,7 @@ mod unit_tests {
|
|||||||
let file = TempFile::new().unwrap().into_file();
|
let file = TempFile::new().unwrap().into_file();
|
||||||
let cluster_size = 0x10000u64;
|
let cluster_size = 0x10000u64;
|
||||||
file.set_len(cluster_size * 2).unwrap();
|
file.set_len(cluster_size * 2).unwrap();
|
||||||
let raw = RawFile::new(file, false);
|
let raw = AlignedFile::new(file, false);
|
||||||
let mut qcow_raw = QcowRawFile::from(raw, cluster_size, bits).unwrap();
|
let mut qcow_raw = QcowRawFile::from(raw, cluster_size, bits).unwrap();
|
||||||
|
|
||||||
let entries = (cluster_size * 8 / bits) as usize;
|
let entries = (cluster_size * 8 / bits) as usize;
|
||||||
@@ -1597,7 +1600,7 @@ mod unit_tests {
|
|||||||
let refcount_block_entries = cluster_size * 8 / refcount_bits;
|
let refcount_block_entries = cluster_size * 8 / refcount_bits;
|
||||||
file.set_len(cluster_size * 3).unwrap();
|
file.set_len(cluster_size * 3).unwrap();
|
||||||
|
|
||||||
let raw = RawFile::new(file, false);
|
let raw = AlignedFile::new(file, false);
|
||||||
let mut qcow_raw = QcowRawFile::from(raw, cluster_size, refcount_bits).unwrap();
|
let mut qcow_raw = QcowRawFile::from(raw, cluster_size, refcount_bits).unwrap();
|
||||||
|
|
||||||
// Set up refcount table pointing to refcount block
|
// Set up refcount table pointing to refcount block
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
|||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||||
|
|
||||||
use super::RawFile;
|
use crate::aligned_file::AlignedFile;
|
||||||
|
|
||||||
// Type aliases for the refcount read/write function pointers
|
// Type aliases for the refcount read/write function pointers
|
||||||
type RefcountReader = fn(&mut RawFile, usize) -> io::Result<Vec<u64>>;
|
type RefcountReader = fn(&mut AlignedFile, usize) -> io::Result<Vec<u64>>;
|
||||||
type RefcountWriter = fn(&mut RawFile, &[u64]) -> io::Result<()>;
|
type RefcountWriter = fn(&mut AlignedFile, &[u64]) -> io::Result<()>;
|
||||||
|
|
||||||
/// Big-endian file access trait.
|
/// Big-endian file access trait.
|
||||||
pub(super) trait BeUint: Sized + Copy {
|
pub(super) trait BeUint: Sized + Copy {
|
||||||
@@ -88,7 +88,7 @@ impl BeUint for u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read byte-aligned refcounts.
|
/// Read byte-aligned refcounts.
|
||||||
fn read_refcount<T: BeUint>(file: &mut RawFile, count: usize) -> io::Result<Vec<u64>> {
|
fn read_refcount<T: BeUint>(file: &mut AlignedFile, count: usize) -> io::Result<Vec<u64>> {
|
||||||
let bytes_per_entry = size_of::<T>();
|
let bytes_per_entry = size_of::<T>();
|
||||||
let mut data = vec![0u8; count * bytes_per_entry];
|
let mut data = vec![0u8; count * bytes_per_entry];
|
||||||
file.read_exact(&mut data)?;
|
file.read_exact(&mut data)?;
|
||||||
@@ -99,7 +99,7 @@ fn read_refcount<T: BeUint>(file: &mut RawFile, count: usize) -> io::Result<Vec<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Write byte-aligned refcounts.
|
/// Write byte-aligned refcounts.
|
||||||
fn write_refcount<T: BeUint + TryFrom<u64>>(file: &mut RawFile, table: &[u64]) -> io::Result<()>
|
fn write_refcount<T: BeUint + TryFrom<u64>>(file: &mut AlignedFile, table: &[u64]) -> io::Result<()>
|
||||||
where
|
where
|
||||||
<T as TryFrom<u64>>::Error: Debug,
|
<T as TryFrom<u64>>::Error: Debug,
|
||||||
{
|
{
|
||||||
@@ -114,7 +114,7 @@ where
|
|||||||
|
|
||||||
/// Read sub-byte refcounts. Bit 0 is the least significant bit.
|
/// Read sub-byte refcounts. Bit 0 is the least significant bit.
|
||||||
fn read_refcount_subbyte<const BITS: usize>(
|
fn read_refcount_subbyte<const BITS: usize>(
|
||||||
file: &mut RawFile,
|
file: &mut AlignedFile,
|
||||||
count: usize,
|
count: usize,
|
||||||
) -> io::Result<Vec<u64>> {
|
) -> io::Result<Vec<u64>> {
|
||||||
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
|
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
|
||||||
@@ -134,7 +134,10 @@ fn read_refcount_subbyte<const BITS: usize>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Write sub-byte refcounts. Bit 0 is the least significant bit.
|
/// Write sub-byte refcounts. Bit 0 is the least significant bit.
|
||||||
fn write_refcount_subbyte<const BITS: usize>(file: &mut RawFile, table: &[u64]) -> io::Result<()> {
|
fn write_refcount_subbyte<const BITS: usize>(
|
||||||
|
file: &mut AlignedFile,
|
||||||
|
table: &[u64],
|
||||||
|
) -> io::Result<()> {
|
||||||
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
|
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
|
||||||
let entries_per_byte = 8 / BITS;
|
let entries_per_byte = 8 / BITS;
|
||||||
let mask = (1u64 << BITS) - 1;
|
let mask = (1u64 << BITS) - 1;
|
||||||
@@ -154,7 +157,7 @@ fn write_refcount_subbyte<const BITS: usize>(file: &mut RawFile, table: &[u64])
|
|||||||
/// A qcow file. Allows reading/writing clusters and appending clusters.
|
/// A qcow file. Allows reading/writing clusters and appending clusters.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct QcowRawFile {
|
pub struct QcowRawFile {
|
||||||
file: RawFile,
|
file: AlignedFile,
|
||||||
cluster_size: u64,
|
cluster_size: u64,
|
||||||
cluster_mask: u64,
|
cluster_mask: u64,
|
||||||
refcount_block_entries: u64,
|
refcount_block_entries: u64,
|
||||||
@@ -165,7 +168,7 @@ pub struct QcowRawFile {
|
|||||||
impl QcowRawFile {
|
impl QcowRawFile {
|
||||||
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
|
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
|
||||||
/// a power of two or refcount_bits is invalid.
|
/// a power of two or refcount_bits is invalid.
|
||||||
pub fn from(file: RawFile, cluster_size: u64, refcount_bits: u64) -> Option<Self> {
|
pub fn from(file: AlignedFile, cluster_size: u64, refcount_bits: u64) -> Option<Self> {
|
||||||
if !cluster_size.is_power_of_two() {
|
if !cluster_size.is_power_of_two() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -289,12 +292,12 @@ impl QcowRawFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the underlying file.
|
/// Returns a reference to the underlying file.
|
||||||
pub fn file(&self) -> &RawFile {
|
pub fn file(&self) -> &AlignedFile {
|
||||||
&self.file
|
&self.file
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the underlying file.
|
/// Returns a mutable reference to the underlying file.
|
||||||
pub fn file_mut(&mut self) -> &mut RawFile {
|
pub fn file_mut(&mut self) -> &mut AlignedFile {
|
||||||
&mut self.file
|
&mut self.file
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +397,7 @@ mod unit_tests {
|
|||||||
temp_file.as_file().set_len(FILE_LEN).unwrap();
|
temp_file.as_file().set_len(FILE_LEN).unwrap();
|
||||||
|
|
||||||
let file = temp_file.as_file().try_clone().unwrap();
|
let file = temp_file.as_file().try_clone().unwrap();
|
||||||
let raw = RawFile::new(file, false);
|
let raw = AlignedFile::new(file, false);
|
||||||
let qcow_raw = QcowRawFile::from(raw, CLUSTER_SIZE, 16).expect("QcowRawFile::from");
|
let qcow_raw = QcowRawFile::from(raw, CLUSTER_SIZE, 16).expect("QcowRawFile::from");
|
||||||
(temp_file, qcow_raw)
|
(temp_file, qcow_raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,304 +0,0 @@
|
|||||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Portions Copyright 2017 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-BSD-3-Clause file.
|
|
||||||
//
|
|
||||||
// Copyright © 2020 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use std::fs::{File, Metadata};
|
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
|
||||||
use std::os::fd::{AsFd, BorrowedFd};
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
use std::result;
|
|
||||||
|
|
||||||
use vmm_sys_util::file_traits::FileSync;
|
|
||||||
use vmm_sys_util::seek_hole::SeekHole;
|
|
||||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
|
||||||
|
|
||||||
use crate::aligned_file::AlignedFile;
|
|
||||||
use crate::{BlockBackend, query_device_size};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RawFile {
|
|
||||||
aligned: AlignedFile,
|
|
||||||
position: u64,
|
|
||||||
direct_io: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawFile {
|
|
||||||
pub fn new(file: File, direct_io: bool) -> Self {
|
|
||||||
RawFile {
|
|
||||||
aligned: AlignedFile::new(file, direct_io),
|
|
||||||
position: 0,
|
|
||||||
direct_io,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_len(&self, size: u64) -> io::Result<()> {
|
|
||||||
self.aligned.file().set_len(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn metadata(&self) -> io::Result<Metadata> {
|
|
||||||
self.aligned.file().metadata()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_clone(&self) -> io::Result<RawFile> {
|
|
||||||
Ok(RawFile {
|
|
||||||
aligned: self.aligned.try_clone()?,
|
|
||||||
position: self.position,
|
|
||||||
direct_io: self.direct_io,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sync_all(&self) -> io::Result<()> {
|
|
||||||
self.aligned.file().sync_all()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sync_data(&self) -> io::Result<()> {
|
|
||||||
self.aligned.file().sync_data()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_direct(&self) -> bool {
|
|
||||||
self.direct_io
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn alignment(&self) -> usize {
|
|
||||||
self.aligned.alignment()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the file was opened with write access.
|
|
||||||
pub fn is_writable(&self) -> bool {
|
|
||||||
// SAFETY: fcntl with F_GETFL is safe and doesn't modify the file descriptor
|
|
||||||
let flags = unsafe { libc::fcntl(self.aligned.file().as_raw_fd(), libc::F_GETFL) };
|
|
||||||
if flags < 0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let access_mode = flags & libc::O_ACCMODE;
|
|
||||||
access_mode == libc::O_WRONLY || access_mode == libc::O_RDWR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for RawFile {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
let n = self.aligned.read_at(buf, self.position)?;
|
|
||||||
self.position += n as u64;
|
|
||||||
Ok(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for RawFile {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
let n = self.aligned.write_at(buf, self.position)?;
|
|
||||||
self.position += n as u64;
|
|
||||||
Ok(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.aligned.file().sync_all()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Seek for RawFile {
|
|
||||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
|
||||||
let newpos = match pos {
|
|
||||||
SeekFrom::Start(o) => o,
|
|
||||||
SeekFrom::Current(d) => self
|
|
||||||
.position
|
|
||||||
.checked_add_signed(d)
|
|
||||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid seek"))?,
|
|
||||||
SeekFrom::End(d) => self
|
|
||||||
.aligned
|
|
||||||
.file()
|
|
||||||
.metadata()?
|
|
||||||
.len()
|
|
||||||
.checked_add_signed(d)
|
|
||||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid seek"))?,
|
|
||||||
};
|
|
||||||
self.position = newpos;
|
|
||||||
Ok(newpos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WriteZeroesAt for RawFile {
|
|
||||||
fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> {
|
|
||||||
self.aligned.file_mut().write_zeroes_at(offset, length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PunchHole for RawFile {
|
|
||||||
fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
|
|
||||||
self.aligned.file_mut().punch_hole(offset, length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileSync for RawFile {
|
|
||||||
fn fsync(&mut self) -> io::Result<()> {
|
|
||||||
self.aligned.file_mut().fsync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SeekHole for RawFile {
|
|
||||||
fn seek_hole(&mut self, offset: u64) -> io::Result<Option<u64>> {
|
|
||||||
match self.aligned.file_mut().seek_hole(offset) {
|
|
||||||
Ok(pos) => {
|
|
||||||
if let Some(p) = pos {
|
|
||||||
self.position = p;
|
|
||||||
}
|
|
||||||
Ok(pos)
|
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn seek_data(&mut self, offset: u64) -> io::Result<Option<u64>> {
|
|
||||||
match self.aligned.file_mut().seek_data(offset) {
|
|
||||||
Ok(pos) => {
|
|
||||||
if let Some(p) = pos {
|
|
||||||
self.position = p;
|
|
||||||
}
|
|
||||||
Ok(pos)
|
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockBackend for RawFile {
|
|
||||||
fn logical_size(&self) -> result::Result<u64, crate::Error> {
|
|
||||||
Ok(query_device_size(self.aligned.file())
|
|
||||||
.map_err(crate::Error::RawFileError)?
|
|
||||||
.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn physical_size(&self) -> result::Result<u64, crate::Error> {
|
|
||||||
Ok(query_device_size(self.aligned.file())
|
|
||||||
.map_err(crate::Error::RawFileError)?
|
|
||||||
.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for RawFile {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
RawFile {
|
|
||||||
aligned: self.aligned.try_clone().expect("RawFile cloning failed"),
|
|
||||||
position: self.position,
|
|
||||||
direct_io: self.direct_io,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawFd for RawFile {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.aligned.file().as_raw_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsFd for RawFile {
|
|
||||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
|
||||||
self.aligned.file().as_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileExt for RawFile {
|
|
||||||
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
|
|
||||||
self.aligned.read_at(buf, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
|
|
||||||
self.aligned.write_at(buf, offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const TEST_ALIGNMENT: usize = 512;
|
|
||||||
|
|
||||||
fn create_pattern_file(size: usize) -> TempFile {
|
|
||||||
let tf = TempFile::new().unwrap();
|
|
||||||
let pattern: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
|
|
||||||
tf.as_file().write_all(&pattern).unwrap();
|
|
||||||
tf.as_file().sync_all().unwrap();
|
|
||||||
tf
|
|
||||||
}
|
|
||||||
|
|
||||||
fn raw_with_alignment(file: File) -> RawFile {
|
|
||||||
// A tempfile is not O_DIRECT, but its aligned probe read still
|
|
||||||
// succeeds, so RawFile::new selects the smallest candidate (512).
|
|
||||||
let raw = RawFile::new(file, true);
|
|
||||||
assert_eq!(raw.alignment(), TEST_ALIGNMENT);
|
|
||||||
raw
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unaligned_read_returns_short_read_at_eof() {
|
|
||||||
let file_size = 100usize;
|
|
||||||
let tf = create_pattern_file(file_size);
|
|
||||||
let mut raw = raw_with_alignment(tf.as_file().try_clone().unwrap());
|
|
||||||
raw.seek(SeekFrom::Start(10)).unwrap();
|
|
||||||
|
|
||||||
let mut buf = vec![0u8; 200];
|
|
||||||
let bytes_read = raw.read(&mut buf).unwrap();
|
|
||||||
|
|
||||||
let expected: Vec<u8> = (10..file_size).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(bytes_read, expected.len());
|
|
||||||
assert_eq!(&buf[..bytes_read], &expected[..]);
|
|
||||||
assert_eq!(raw.position, file_size as u64);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unaligned_read_beyond_eof_returns_zero() {
|
|
||||||
let tf = create_pattern_file(100);
|
|
||||||
let mut raw = raw_with_alignment(tf.as_file().try_clone().unwrap());
|
|
||||||
raw.seek(SeekFrom::Start(200)).unwrap();
|
|
||||||
|
|
||||||
let mut buf = vec![0u8; 16];
|
|
||||||
let bytes_read = raw.read(&mut buf).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(bytes_read, 0);
|
|
||||||
assert_eq!(raw.position, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unaligned_write_extends_at_eof() {
|
|
||||||
let file_size = 100usize;
|
|
||||||
let tf = create_pattern_file(file_size);
|
|
||||||
let mut raw = raw_with_alignment(tf.as_file().try_clone().unwrap());
|
|
||||||
raw.seek(SeekFrom::Start(file_size as u64)).unwrap();
|
|
||||||
|
|
||||||
let data = b"xyz";
|
|
||||||
let bytes_written = raw.write(data).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(bytes_written, data.len());
|
|
||||||
assert_eq!(raw.position, (file_size + data.len()) as u64);
|
|
||||||
|
|
||||||
let mut readback = vec![0u8; file_size + data.len()];
|
|
||||||
tf.as_file().read_exact_at(&mut readback, 0).unwrap();
|
|
||||||
let expected_prefix: Vec<u8> = (0..file_size).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(&readback[..file_size], &expected_prefix[..]);
|
|
||||||
assert_eq!(&readback[file_size..], data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty_unaligned_io_is_noop() {
|
|
||||||
let tf = create_pattern_file(100);
|
|
||||||
let mut raw = raw_with_alignment(tf.as_file().try_clone().unwrap());
|
|
||||||
raw.seek(SeekFrom::Start(1)).unwrap();
|
|
||||||
|
|
||||||
let mut read_buf = [];
|
|
||||||
assert_eq!(raw.read(&mut read_buf).unwrap(), 0);
|
|
||||||
assert_eq!(raw.write(&[]).unwrap(), 0);
|
|
||||||
assert_eq!(raw.position, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,10 +30,11 @@ use self::internal::metadata::{BackingRead, QcowMetadata};
|
|||||||
use self::internal::qcow_raw_file::QcowRawFile;
|
use self::internal::qcow_raw_file::QcowRawFile;
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
#[cfg(any(test, feature = "test-utils"))]
|
||||||
use self::internal::{BackingFileConfig, Error as QcowError, QcowHeader};
|
use self::internal::{BackingFileConfig, Error as QcowError, QcowHeader};
|
||||||
use self::internal::{MAX_NESTING_DEPTH, RawFile, parse_qcow};
|
use self::internal::{MAX_NESTING_DEPTH, parse_qcow};
|
||||||
#[cfg(feature = "io_uring")]
|
#[cfg(feature = "io_uring")]
|
||||||
use self::worker::async_uring::QcowAsync;
|
use self::worker::async_uring::QcowAsync;
|
||||||
use self::worker::sync::QcowSync;
|
use self::worker::sync::QcowSync;
|
||||||
|
use crate::aligned_file::AlignedFile;
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
#[cfg(any(test, feature = "test-utils"))]
|
||||||
use crate::async_io::GuestMemoryTarget;
|
use crate::async_io::GuestMemoryTarget;
|
||||||
use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError};
|
use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError};
|
||||||
@@ -91,7 +92,7 @@ impl QcowDisk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 };
|
let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 };
|
||||||
let raw_file = RawFile::new(file, direct_io);
|
let raw_file = AlignedFile::new(file, direct_io);
|
||||||
let (inner, backing_file, sparse) = parse_qcow(raw_file, max_nesting_depth, sparse)
|
let (inner, backing_file, sparse) = parse_qcow(raw_file, max_nesting_depth, sparse)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
let e = if !backing_files && matches!(e.kind(), BlockErrorKind::Overflow) {
|
let e = if !backing_files && matches!(e.kind(), BlockErrorKind::Overflow) {
|
||||||
@@ -163,7 +164,7 @@ pub(crate) fn create_image(
|
|||||||
{
|
{
|
||||||
backing_file.format = cfg.format;
|
backing_file.format = cfg.format;
|
||||||
}
|
}
|
||||||
let mut raw = RawFile::new(
|
let mut raw = AlignedFile::new(
|
||||||
file.try_clone()
|
file.try_clone()
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?,
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?,
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -340,13 +340,14 @@ mod unit_tests {
|
|||||||
use vmm_sys_util::tempfile::TempFile;
|
use vmm_sys_util::tempfile::TempFile;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::aligned_file::AlignedFile;
|
||||||
use crate::async_io::{AsyncIoCompletion, OwnedIoBuffer};
|
use crate::async_io::{AsyncIoCompletion, OwnedIoBuffer};
|
||||||
use crate::disk_file::{AsyncDiskFile, DiskSize, Resizable};
|
use crate::disk_file::{AsyncDiskFile, DiskSize, Resizable};
|
||||||
use crate::error::BlockErrorKind;
|
use crate::error::BlockErrorKind;
|
||||||
use crate::formats::qcow;
|
use crate::formats::qcow;
|
||||||
use crate::formats::qcow::common::unit_tests::compress_allocated_clusters;
|
use crate::formats::qcow::common::unit_tests::compress_allocated_clusters;
|
||||||
use crate::formats::qcow::internal::{
|
use crate::formats::qcow::internal::{
|
||||||
BackingFileConfig, Error as QcowError, ImageType, QcowHeader, RawFile,
|
BackingFileConfig, Error as QcowError, ImageType, QcowHeader,
|
||||||
};
|
};
|
||||||
use crate::formats::qcow::{QcowDisk, QcowTempDisk};
|
use crate::formats::qcow::{QcowDisk, QcowTempDisk};
|
||||||
|
|
||||||
@@ -397,7 +398,7 @@ mod unit_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn qcow_header_is_corrupt(file: &File) -> bool {
|
fn qcow_header_is_corrupt(file: &File) -> bool {
|
||||||
let mut raw = RawFile::new(file.try_clone().unwrap(), false);
|
let mut raw = AlignedFile::new(file.try_clone().unwrap(), false);
|
||||||
raw.seek(SeekFrom::Start(0)).unwrap();
|
raw.seek(SeekFrom::Start(0)).unwrap();
|
||||||
QcowHeader::new(&mut raw).unwrap().is_corrupt()
|
QcowHeader::new(&mut raw).unwrap().is_corrupt()
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -15,7 +15,7 @@ pub mod factory;
|
|||||||
mod io_impl;
|
mod io_impl;
|
||||||
pub use io_impl::{async_io, fcntl, request};
|
pub use io_impl::{async_io, fcntl, request};
|
||||||
pub(crate) mod aligned_buffer;
|
pub(crate) mod aligned_buffer;
|
||||||
pub(crate) mod aligned_file;
|
pub mod aligned_file;
|
||||||
pub mod formats;
|
pub mod formats;
|
||||||
mod sparse;
|
mod sparse;
|
||||||
use std::alloc::{Layout, alloc_zeroed};
|
use std::alloc::{Layout, alloc_zeroed};
|
||||||
@@ -29,6 +29,7 @@ use std::path::Path;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{cmp, mem, result};
|
use std::{cmp, mem, result};
|
||||||
|
|
||||||
|
pub use aligned_file::AlignedFile;
|
||||||
use formats::qcow::internal as qcow;
|
use formats::qcow::internal as qcow;
|
||||||
#[cfg(feature = "io_uring")]
|
#[cfg(feature = "io_uring")]
|
||||||
use io_uring::{IoUring, Probe, opcode};
|
use io_uring::{IoUring, Probe, opcode};
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ use std::sync::{Arc, Mutex, RwLock, RwLockWriteGuard};
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{convert, io, process, result};
|
use std::{convert, io, process, result};
|
||||||
|
|
||||||
use block::formats::qcow::internal::RawFile;
|
use block::{AlignedFile, Request, RequestType, VirtioBlockConfig, build_serial};
|
||||||
use block::{Request, RequestType, VirtioBlockConfig, build_serial};
|
|
||||||
use libc::EFD_NONBLOCK;
|
use libc::EFD_NONBLOCK;
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use option_parser::{OptionParser, OptionParserError, Toggle};
|
use option_parser::{OptionParser, OptionParserError, Toggle};
|
||||||
@@ -85,7 +84,7 @@ impl convert::From<Error> for io::Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct VhostUserBlkThread {
|
struct VhostUserBlkThread {
|
||||||
disk_image: Arc<Mutex<RawFile>>,
|
disk_image: Arc<Mutex<AlignedFile>>,
|
||||||
serial: Vec<u8>,
|
serial: Vec<u8>,
|
||||||
disk_nsectors: u64,
|
disk_nsectors: u64,
|
||||||
event_idx: bool,
|
event_idx: bool,
|
||||||
@@ -96,7 +95,7 @@ struct VhostUserBlkThread {
|
|||||||
|
|
||||||
impl VhostUserBlkThread {
|
impl VhostUserBlkThread {
|
||||||
fn new(
|
fn new(
|
||||||
disk_image: Arc<Mutex<RawFile>>,
|
disk_image: Arc<Mutex<AlignedFile>>,
|
||||||
serial: Vec<u8>,
|
serial: Vec<u8>,
|
||||||
disk_nsectors: u64,
|
disk_nsectors: u64,
|
||||||
writeback: Arc<AtomicBool>,
|
writeback: Arc<AtomicBool>,
|
||||||
@@ -230,7 +229,7 @@ impl VhostUserBlkBackend {
|
|||||||
options.custom_flags(libc::O_DIRECT);
|
options.custom_flags(libc::O_DIRECT);
|
||||||
}
|
}
|
||||||
let image: File = options.open(image_path).unwrap();
|
let image: File = options.open(image_path).unwrap();
|
||||||
let raw_img = RawFile::new(image, direct);
|
let raw_img = AlignedFile::new(image, direct);
|
||||||
|
|
||||||
let serial = build_serial(&PathBuf::from(&image_path));
|
let serial = build_serial(&PathBuf::from(&image_path));
|
||||||
let image = Arc::new(Mutex::new(raw_img));
|
let image = Arc::new(Mutex::new(raw_img));
|
||||||
|
|||||||
Reference in New Issue
Block a user