mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: qcow: Access the qcow header positionally
Convert the qcow header reader and writer to positional access instead of the AlignedFile Read, Write, and Seek cursor. The writes still go through the AlignedFile O_DIRECT bounce, so alignment is unchanged. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
committed by
Rob Bradford
parent
4abdb3419b
commit
43682e22b0
@@ -9,7 +9,8 @@
|
||||
//! QCOW2 header parsing, validation, and creation.
|
||||
|
||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
use std::os::unix::fs::FileExt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitflags::bitflags;
|
||||
@@ -209,27 +210,34 @@ pub struct QcowHeader {
|
||||
impl QcowHeader {
|
||||
/// Read header extensions, optionally collecting feature names for error reporting.
|
||||
pub(super) fn read_header_extensions(
|
||||
f: &mut AlignedFile,
|
||||
f: &AlignedFile,
|
||||
header: &mut QcowHeader,
|
||||
mut feature_table: Option<&mut Vec<(u8, String)>>,
|
||||
) -> Result<()> {
|
||||
// Extensions start directly after the header
|
||||
f.seek(SeekFrom::Start(header.header_size as u64))
|
||||
.map_err(Error::ReadingHeader)?;
|
||||
// Extensions start directly after the header.
|
||||
let mut offset = header.header_size as u64;
|
||||
|
||||
loop {
|
||||
let ext_type = u32::read_be(f).map_err(Error::ReadingHeader)?;
|
||||
let mut field = [0u8; size_of::<u32>()];
|
||||
f.read_exact_at(&mut field, offset)
|
||||
.map_err(Error::ReadingHeader)?;
|
||||
offset += field.len() as u64;
|
||||
let ext_type = u32::from_be_slice(&field) as u32;
|
||||
if ext_type == HEADER_EXT_END {
|
||||
break;
|
||||
}
|
||||
|
||||
let ext_length = u32::read_be(f).map_err(Error::ReadingHeader)?;
|
||||
f.read_exact_at(&mut field, offset)
|
||||
.map_err(Error::ReadingHeader)?;
|
||||
offset += field.len() as u64;
|
||||
let ext_length = u32::from_be_slice(&field) as u32;
|
||||
|
||||
match ext_type {
|
||||
HEADER_EXT_BACKING_FORMAT => {
|
||||
let mut format_bytes = vec![0u8; ext_length as usize];
|
||||
f.read_exact(&mut format_bytes)
|
||||
f.read_exact_at(&mut format_bytes, offset)
|
||||
.map_err(Error::ReadingHeader)?;
|
||||
offset += format_bytes.len() as u64;
|
||||
let format_str = String::from_utf8(format_bytes)
|
||||
.map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?;
|
||||
if let Some(backing_file) = &mut header.backing_file {
|
||||
@@ -239,7 +247,9 @@ impl QcowHeader {
|
||||
HEADER_EXT_FEATURE_NAME_TABLE if feature_table.is_some() => {
|
||||
const FEATURE_NAME_ENTRY_SIZE: usize = 1 + 1 + 46; // type + bit + name
|
||||
let mut data = vec![0u8; ext_length as usize];
|
||||
f.read_exact(&mut data).map_err(Error::ReadingHeader)?;
|
||||
f.read_exact_at(&mut data, offset)
|
||||
.map_err(Error::ReadingHeader)?;
|
||||
offset += data.len() as u64;
|
||||
let table = feature_table.as_mut().unwrap();
|
||||
for entry in data.chunks_exact(FEATURE_NAME_ENTRY_SIZE) {
|
||||
if entry[0] == FEAT_TYPE_INCOMPATIBLE {
|
||||
@@ -253,72 +263,92 @@ impl QcowHeader {
|
||||
}
|
||||
_ => {
|
||||
// Skip unknown extension
|
||||
f.seek(SeekFrom::Current(ext_length as i64))
|
||||
.map_err(Error::ReadingHeader)?;
|
||||
offset += ext_length as u64;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip to the next 8 byte boundary
|
||||
let padding = (8 - (ext_length % 8)) % 8;
|
||||
f.seek(SeekFrom::Current(padding as i64))
|
||||
.map_err(Error::ReadingHeader)?;
|
||||
offset += padding as u64;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a QcowHeader from a reference to a file.
|
||||
pub fn new(f: &mut AlignedFile) -> Result<QcowHeader> {
|
||||
f.rewind().map_err(Error::ReadingHeader)?;
|
||||
let magic = u32::read_be(f).map_err(Error::ReadingHeader)?;
|
||||
pub fn new(f: &AlignedFile) -> Result<QcowHeader> {
|
||||
// Decode the next big endian u32 from the buffer and advance `pos`.
|
||||
fn next_u32(buf: &[u8], pos: &mut usize) -> u32 {
|
||||
let value = u32::from_be_slice(&buf[*pos..]) as u32;
|
||||
*pos += size_of::<u32>();
|
||||
value
|
||||
}
|
||||
|
||||
// Decode the next big endian u64 from the buffer and advance `pos`.
|
||||
fn next_u64(buf: &[u8], pos: &mut usize) -> u64 {
|
||||
let value = u64::from_be_slice(&buf[*pos..]);
|
||||
*pos += size_of::<u64>();
|
||||
value
|
||||
}
|
||||
|
||||
// The bare header fits in V3_BARE_HEADER_SIZE plus the optional
|
||||
// compression field. Read it once, then decode each field from the
|
||||
// buffer at its running offset.
|
||||
let mut buf = [0u8; V3_BARE_HEADER_SIZE as usize + size_of::<u64>()];
|
||||
f.read_exact_at(&mut buf, 0).map_err(Error::ReadingHeader)?;
|
||||
|
||||
let mut pos = 0;
|
||||
let magic = next_u32(&buf, &mut pos);
|
||||
if magic != QCOW_MAGIC {
|
||||
return Err(Error::InvalidMagic);
|
||||
}
|
||||
|
||||
// Reads the next u32 from the file.
|
||||
fn read_u32_be(f: &mut AlignedFile) -> Result<u32> {
|
||||
u32::read_be(f).map_err(Error::ReadingHeader)
|
||||
}
|
||||
|
||||
// Reads the next u64 from the file.
|
||||
fn read_u64_be(f: &mut AlignedFile) -> Result<u64> {
|
||||
u64::read_be(f).map_err(Error::ReadingHeader)
|
||||
}
|
||||
|
||||
let version = read_u32_be(f)?;
|
||||
let version = next_u32(&buf, &mut pos);
|
||||
|
||||
let mut header = QcowHeader {
|
||||
magic,
|
||||
version,
|
||||
backing_file_offset: read_u64_be(f)?,
|
||||
backing_file_size: read_u32_be(f)?,
|
||||
cluster_bits: read_u32_be(f)?,
|
||||
size: read_u64_be(f)?,
|
||||
crypt_method: read_u32_be(f)?,
|
||||
l1_size: read_u32_be(f)?,
|
||||
l1_table_offset: read_u64_be(f)?,
|
||||
refcount_table_offset: read_u64_be(f)?,
|
||||
refcount_table_clusters: read_u32_be(f)?,
|
||||
nb_snapshots: read_u32_be(f)?,
|
||||
snapshots_offset: read_u64_be(f)?,
|
||||
incompatible_features: if version == 2 { 0 } else { read_u64_be(f)? },
|
||||
compatible_features: if version == 2 { 0 } else { read_u64_be(f)? },
|
||||
autoclear_features: if version == 2 { 0 } else { read_u64_be(f)? },
|
||||
backing_file_offset: next_u64(&buf, &mut pos),
|
||||
backing_file_size: next_u32(&buf, &mut pos),
|
||||
cluster_bits: next_u32(&buf, &mut pos),
|
||||
size: next_u64(&buf, &mut pos),
|
||||
crypt_method: next_u32(&buf, &mut pos),
|
||||
l1_size: next_u32(&buf, &mut pos),
|
||||
l1_table_offset: next_u64(&buf, &mut pos),
|
||||
refcount_table_offset: next_u64(&buf, &mut pos),
|
||||
refcount_table_clusters: next_u32(&buf, &mut pos),
|
||||
nb_snapshots: next_u32(&buf, &mut pos),
|
||||
snapshots_offset: next_u64(&buf, &mut pos),
|
||||
incompatible_features: if version == 2 {
|
||||
0
|
||||
} else {
|
||||
next_u64(&buf, &mut pos)
|
||||
},
|
||||
compatible_features: if version == 2 {
|
||||
0
|
||||
} else {
|
||||
next_u64(&buf, &mut pos)
|
||||
},
|
||||
autoclear_features: if version == 2 {
|
||||
0
|
||||
} else {
|
||||
next_u64(&buf, &mut pos)
|
||||
},
|
||||
refcount_order: if version == 2 {
|
||||
DEFAULT_REFCOUNT_ORDER
|
||||
} else {
|
||||
read_u32_be(f)?
|
||||
next_u32(&buf, &mut pos)
|
||||
},
|
||||
header_size: if version == 2 {
|
||||
V2_BARE_HEADER_SIZE
|
||||
} else {
|
||||
read_u32_be(f)?
|
||||
next_u32(&buf, &mut pos)
|
||||
},
|
||||
compression_type: CompressionType::Zlib,
|
||||
backing_file: None,
|
||||
};
|
||||
if version == 3 && header.header_size > V3_BARE_HEADER_SIZE {
|
||||
let raw_compression_type = read_u64_be(f)? >> (64 - 8);
|
||||
let raw_compression_type = next_u64(&buf, &mut pos) >> (64 - 8);
|
||||
header.compression_type = if raw_compression_type == COMPRESSION_TYPE_ZLIB {
|
||||
Ok(CompressionType::Zlib)
|
||||
} else if raw_compression_type == COMPRESSION_TYPE_ZSTD {
|
||||
@@ -360,10 +390,8 @@ impl QcowHeader {
|
||||
cluster_size,
|
||||
));
|
||||
}
|
||||
f.seek(SeekFrom::Start(header.backing_file_offset))
|
||||
.map_err(Error::ReadingHeader)?;
|
||||
let mut backing_file_name_bytes = vec![0u8; header.backing_file_size as usize];
|
||||
f.read_exact(&mut backing_file_name_bytes)
|
||||
f.read_exact_at(&mut backing_file_name_bytes, header.backing_file_offset)
|
||||
.map_err(Error::ReadingHeader)?;
|
||||
let path = String::from_utf8(backing_file_name_bytes)
|
||||
.map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?;
|
||||
@@ -476,65 +504,63 @@ impl QcowHeader {
|
||||
})
|
||||
}
|
||||
|
||||
/// Write the header to `file`.
|
||||
pub fn write_to<F: Write + Seek>(&self, file: &mut F) -> Result<()> {
|
||||
// Writes the next u32 to the file.
|
||||
fn write_u32_be<F: Write>(f: &mut F, value: u32) -> Result<()> {
|
||||
u32::write_be(f, value).map_err(Error::WritingHeader)
|
||||
}
|
||||
|
||||
// Writes the next u64 to the file.
|
||||
fn write_u64_be<F: Write>(f: &mut F, value: u64) -> Result<()> {
|
||||
u64::write_be(f, value).map_err(Error::WritingHeader)
|
||||
}
|
||||
|
||||
write_u32_be(file, self.magic)?;
|
||||
write_u32_be(file, self.version)?;
|
||||
write_u64_be(file, self.backing_file_offset)?;
|
||||
write_u32_be(file, self.backing_file_size)?;
|
||||
write_u32_be(file, self.cluster_bits)?;
|
||||
write_u64_be(file, self.size)?;
|
||||
write_u32_be(file, self.crypt_method)?;
|
||||
write_u32_be(file, self.l1_size)?;
|
||||
write_u64_be(file, self.l1_table_offset)?;
|
||||
write_u64_be(file, self.refcount_table_offset)?;
|
||||
write_u32_be(file, self.refcount_table_clusters)?;
|
||||
write_u32_be(file, self.nb_snapshots)?;
|
||||
write_u64_be(file, self.snapshots_offset)?;
|
||||
/// Write the header to `f`.
|
||||
pub fn write_to(&self, f: &AlignedFile) -> Result<()> {
|
||||
// Build the header in memory, then write it in one positional write.
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(&self.magic.to_be_bytes());
|
||||
buf.extend_from_slice(&self.version.to_be_bytes());
|
||||
buf.extend_from_slice(&self.backing_file_offset.to_be_bytes());
|
||||
buf.extend_from_slice(&self.backing_file_size.to_be_bytes());
|
||||
buf.extend_from_slice(&self.cluster_bits.to_be_bytes());
|
||||
buf.extend_from_slice(&self.size.to_be_bytes());
|
||||
buf.extend_from_slice(&self.crypt_method.to_be_bytes());
|
||||
buf.extend_from_slice(&self.l1_size.to_be_bytes());
|
||||
buf.extend_from_slice(&self.l1_table_offset.to_be_bytes());
|
||||
buf.extend_from_slice(&self.refcount_table_offset.to_be_bytes());
|
||||
buf.extend_from_slice(&self.refcount_table_clusters.to_be_bytes());
|
||||
buf.extend_from_slice(&self.nb_snapshots.to_be_bytes());
|
||||
buf.extend_from_slice(&self.snapshots_offset.to_be_bytes());
|
||||
|
||||
if self.version == 3 {
|
||||
write_u64_be(file, self.incompatible_features)?;
|
||||
write_u64_be(file, self.compatible_features)?;
|
||||
write_u64_be(file, self.autoclear_features)?;
|
||||
write_u32_be(file, self.refcount_order)?;
|
||||
write_u32_be(file, self.header_size)?;
|
||||
buf.extend_from_slice(&self.incompatible_features.to_be_bytes());
|
||||
buf.extend_from_slice(&self.compatible_features.to_be_bytes());
|
||||
buf.extend_from_slice(&self.autoclear_features.to_be_bytes());
|
||||
buf.extend_from_slice(&self.refcount_order.to_be_bytes());
|
||||
buf.extend_from_slice(&self.header_size.to_be_bytes());
|
||||
|
||||
if self.header_size > V3_BARE_HEADER_SIZE {
|
||||
write_u64_be(file, 0)?; // no compression
|
||||
// no compression
|
||||
buf.extend_from_slice(&0u64.to_be_bytes());
|
||||
}
|
||||
|
||||
write_u32_be(file, 0)?; // header extension type: end of header extension area
|
||||
write_u32_be(file, 0)?; // length of header extension data: 0
|
||||
// header extension type: end of header extension area
|
||||
buf.extend_from_slice(&0u32.to_be_bytes());
|
||||
// length of header extension data: 0
|
||||
buf.extend_from_slice(&0u32.to_be_bytes());
|
||||
}
|
||||
|
||||
f.write_all_at(&buf, 0).map_err(Error::WritingHeader)?;
|
||||
|
||||
if let Some(backing_file_path) = self.backing_file.as_ref().map(|bf| &bf.path) {
|
||||
if self.backing_file_offset > 0 {
|
||||
file.seek(SeekFrom::Start(self.backing_file_offset))
|
||||
.map_err(Error::WritingHeader)?;
|
||||
}
|
||||
write!(file, "{backing_file_path}").map_err(Error::WritingHeader)?;
|
||||
let offset = if self.backing_file_offset > 0 {
|
||||
self.backing_file_offset
|
||||
} else {
|
||||
buf.len() as u64
|
||||
};
|
||||
f.write_all_at(backing_file_path.as_bytes(), offset)
|
||||
.map_err(Error::WritingHeader)?;
|
||||
}
|
||||
|
||||
// Set the file length by seeking and writing a zero to the last byte. This avoids needing
|
||||
// a `File` instead of anything that implements seek as the `file` argument.
|
||||
// Zeros out the l1 and refcount table clusters.
|
||||
// Set the file length by writing a zero to the last byte. This also
|
||||
// zeros the l1 and refcount table clusters.
|
||||
let cluster_size = 0x01u64 << self.cluster_bits;
|
||||
let refcount_blocks_size = u64::from(self.refcount_table_clusters) * cluster_size;
|
||||
file.seek(SeekFrom::Start(
|
||||
f.write_all_at(
|
||||
&[0u8],
|
||||
self.refcount_table_offset + refcount_blocks_size - 2,
|
||||
))
|
||||
)
|
||||
.map_err(Error::WritingHeader)?;
|
||||
file.write(&[0u8]).map_err(Error::WritingHeader)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,10 +17,9 @@
|
||||
//! operations upgrade to a write lock.
|
||||
|
||||
use std::cmp::min;
|
||||
use std::io::{self, Seek};
|
||||
use std::mem;
|
||||
use std::os::unix::fs::FileExt;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::{io, mem};
|
||||
|
||||
use libc::{EINVAL, EIO};
|
||||
use vmm_sys_util::write_zeroes::WriteZeroesAt;
|
||||
@@ -756,7 +755,6 @@ impl QcowState {
|
||||
|
||||
self.header.size = new_size;
|
||||
|
||||
self.raw_file.file_mut().rewind()?;
|
||||
self.header
|
||||
.write_to(self.raw_file.file_mut())
|
||||
.map_err(|e| io::Error::other(format!("failed to write header during resize: {e}")))?;
|
||||
@@ -812,7 +810,6 @@ impl QcowState {
|
||||
self.header.l1_size = new_l1_size;
|
||||
self.header.l1_table_offset = new_l1_offset;
|
||||
|
||||
self.raw_file.file_mut().rewind()?;
|
||||
self.header
|
||||
.write_to(self.raw_file.file_mut())
|
||||
.map_err(|e| io::Error::other(format!("failed to write header during resize: {e}")))?;
|
||||
|
||||
@@ -16,11 +16,10 @@ mod vec_cache;
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt::{Debug, Formatter, Result as FmtResult};
|
||||
use std::fs::{OpenOptions, read_link};
|
||||
use std::io::{self, Seek};
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::os::unix::fs::FileExt;
|
||||
use std::path::Path;
|
||||
use std::{result, str};
|
||||
use std::{io, result, str};
|
||||
|
||||
pub use header::{
|
||||
BackingFileConfig, CompressionType, ImageType, IncompatFeatures, MissingFeatureError,
|
||||
@@ -127,8 +126,6 @@ pub enum Error {
|
||||
ResizeIo(#[source] io::Error),
|
||||
#[error("Resize not supported with backing file")]
|
||||
ResizeWithBackingFile,
|
||||
#[error("Failed to seek file")]
|
||||
SeekingFile(#[source] io::Error),
|
||||
#[error("Failed to set file size")]
|
||||
SettingFileSize(#[source] io::Error),
|
||||
#[error("Failed to set refcount refcount")]
|
||||
@@ -270,11 +267,11 @@ impl Debug for BackingFile {
|
||||
///
|
||||
/// Used by [`crate::formats::qcow::QcowDisk`] when opening an image.
|
||||
pub(crate) fn parse_qcow(
|
||||
mut file: AlignedFile,
|
||||
file: AlignedFile,
|
||||
max_nesting_depth: u32,
|
||||
sparse: bool,
|
||||
) -> BlockResult<(metadata::QcowState, Option<BackingFile>, bool)> {
|
||||
let mut header = QcowHeader::new(&mut file).map_err(|e| {
|
||||
let mut header = QcowHeader::new(&file).map_err(|e| {
|
||||
let kind = match &e {
|
||||
Error::InvalidMagic
|
||||
| Error::BackingFileTooLong(_)
|
||||
@@ -730,7 +727,6 @@ fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> BlockRes
|
||||
) -> Result<()> {
|
||||
// Rewrite the header with lazy refcounts enabled while we are rebuilding the tables.
|
||||
header.compatible_features |= COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
|
||||
raw_file.file_mut().rewind().map_err(Error::SeekingFile)?;
|
||||
header.write_to(raw_file.file_mut())?;
|
||||
|
||||
for (i, refblock_addr) in ref_table.iter().enumerate() {
|
||||
@@ -763,7 +759,6 @@ fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> BlockRes
|
||||
|
||||
// Rewrite the header again, now with lazy refcounts disabled.
|
||||
header.compatible_features &= !COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
|
||||
raw_file.file_mut().rewind().map_err(Error::SeekingFile)?;
|
||||
header.write_to(raw_file.file_mut())?;
|
||||
|
||||
Ok(())
|
||||
@@ -1000,8 +995,8 @@ mod unit_tests {
|
||||
|
||||
fn try_open_qcow_header(header: &QcowHeader, backing_files: bool) -> BlockResult<QcowDisk> {
|
||||
let temp = TempFile::new().unwrap();
|
||||
let mut raw = AlignedFile::new(temp.as_file().try_clone().unwrap(), false);
|
||||
header.write_to(&mut raw).expect("write header");
|
||||
let raw = AlignedFile::new(temp.as_file().try_clone().unwrap(), false);
|
||||
header.write_to(&raw).expect("write header");
|
||||
drop(raw);
|
||||
let file = temp.into_file();
|
||||
QcowDisk::new(file, false, backing_files, true, false)
|
||||
@@ -1037,14 +1032,14 @@ mod unit_tests {
|
||||
|
||||
#[test]
|
||||
fn header_read() {
|
||||
with_basic_file(&valid_header_v2(), |mut disk_file: AlignedFile| {
|
||||
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
||||
with_basic_file(&valid_header_v2(), |disk_file: AlignedFile| {
|
||||
let header = QcowHeader::new(&disk_file).expect("Failed to create Header.");
|
||||
assert_eq!(header.version, 2);
|
||||
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
||||
assert_eq!(header.header_size, V2_BARE_HEADER_SIZE);
|
||||
});
|
||||
with_basic_file(&valid_header_v3(), |mut disk_file: AlignedFile| {
|
||||
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
||||
with_basic_file(&valid_header_v3(), |disk_file: AlignedFile| {
|
||||
let header = QcowHeader::new(&disk_file).expect("Failed to create Header.");
|
||||
assert_eq!(header.version, 3);
|
||||
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
||||
assert_eq!(header.header_size, V3_BARE_HEADER_SIZE);
|
||||
@@ -1055,13 +1050,11 @@ mod unit_tests {
|
||||
fn header_v2_with_backing() {
|
||||
let header = QcowHeader::create_for_size_and_path(2, 0x10_0000, Some("/my/path/to/a/file"))
|
||||
.expect("Failed to create header.");
|
||||
let mut disk_file: AlignedFile =
|
||||
AlignedFile::new(TempFile::new().unwrap().into_file(), false);
|
||||
let disk_file: AlignedFile = AlignedFile::new(TempFile::new().unwrap().into_file(), false);
|
||||
header
|
||||
.write_to(&mut disk_file)
|
||||
.write_to(&disk_file)
|
||||
.expect("Failed to write header to shm.");
|
||||
disk_file.rewind().unwrap();
|
||||
let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header.");
|
||||
let read_header = QcowHeader::new(&disk_file).expect("Failed to create header.");
|
||||
assert_eq!(
|
||||
header.backing_file.as_ref().map(|bf| bf.path.clone()),
|
||||
Some(String::from("/my/path/to/a/file"))
|
||||
@@ -1076,13 +1069,11 @@ mod unit_tests {
|
||||
fn header_v3_with_backing() {
|
||||
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/my/path/to/a/file"))
|
||||
.expect("Failed to create header.");
|
||||
let mut disk_file: AlignedFile =
|
||||
AlignedFile::new(TempFile::new().unwrap().into_file(), false);
|
||||
let disk_file: AlignedFile = AlignedFile::new(TempFile::new().unwrap().into_file(), false);
|
||||
header
|
||||
.write_to(&mut disk_file)
|
||||
.write_to(&disk_file)
|
||||
.expect("Failed to write header to shm.");
|
||||
disk_file.rewind().unwrap();
|
||||
let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header.");
|
||||
let read_header = QcowHeader::new(&disk_file).expect("Failed to create header.");
|
||||
assert_eq!(
|
||||
header.backing_file.as_ref().map(|bf| bf.path.clone()),
|
||||
Some(String::from("/my/path/to/a/file"))
|
||||
@@ -1101,17 +1092,16 @@ mod unit_tests {
|
||||
.expect("Failed to create header.");
|
||||
header.backing_file_offset = offset;
|
||||
header.backing_file_size = size;
|
||||
let mut disk_file: AlignedFile = AlignedFile::new(
|
||||
let disk_file: AlignedFile = AlignedFile::new(
|
||||
TempFile::new()
|
||||
.expect("Failed to create temp file.")
|
||||
.into_file(),
|
||||
false,
|
||||
);
|
||||
header
|
||||
.write_to(&mut disk_file)
|
||||
.write_to(&disk_file)
|
||||
.expect("Failed to write header.");
|
||||
disk_file.rewind().expect("Failed to rewind disk file.");
|
||||
QcowHeader::new(&mut disk_file)
|
||||
QcowHeader::new(&disk_file)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1178,7 +1168,7 @@ mod unit_tests {
|
||||
|
||||
let mut disk_file: AlignedFile =
|
||||
AlignedFile::new(TempFile::new().unwrap().into_file(), false);
|
||||
header.write_to(&mut disk_file).unwrap();
|
||||
header.write_to(&disk_file).unwrap();
|
||||
|
||||
// Write extension
|
||||
disk_file
|
||||
@@ -1203,7 +1193,7 @@ mod unit_tests {
|
||||
|
||||
#[test]
|
||||
fn read_header_extensions_unknown_extension() {
|
||||
let (mut disk_file, mut header) = create_header_with_extension(
|
||||
let (disk_file, mut header) = create_header_with_extension(
|
||||
0x12345678, // unknown type
|
||||
"test".as_bytes(),
|
||||
);
|
||||
@@ -1214,13 +1204,13 @@ mod unit_tests {
|
||||
format: None,
|
||||
});
|
||||
|
||||
QcowHeader::read_header_extensions(&mut disk_file, &mut header, None).unwrap();
|
||||
QcowHeader::read_header_extensions(&disk_file, &mut header, None).unwrap();
|
||||
assert_eq!(header.backing_file.as_ref().and_then(|bf| bf.format), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_header_extensions_raw_format() {
|
||||
let (mut disk_file, mut header) =
|
||||
let (disk_file, mut header) =
|
||||
create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "raw".as_bytes());
|
||||
|
||||
header.backing_file = Some(BackingFileConfig {
|
||||
@@ -1228,7 +1218,7 @@ mod unit_tests {
|
||||
format: None,
|
||||
});
|
||||
|
||||
QcowHeader::read_header_extensions(&mut disk_file, &mut header, None).unwrap();
|
||||
QcowHeader::read_header_extensions(&disk_file, &mut header, None).unwrap();
|
||||
assert_eq!(
|
||||
header.backing_file.as_ref().and_then(|bf| bf.format),
|
||||
Some(ImageType::Raw)
|
||||
@@ -1237,7 +1227,7 @@ mod unit_tests {
|
||||
|
||||
#[test]
|
||||
fn read_header_extensions_qcow2_format() {
|
||||
let (mut disk_file, mut header) =
|
||||
let (disk_file, mut header) =
|
||||
create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "qcow2".as_bytes());
|
||||
|
||||
header.backing_file = Some(BackingFileConfig {
|
||||
@@ -1245,7 +1235,7 @@ mod unit_tests {
|
||||
format: None,
|
||||
});
|
||||
|
||||
QcowHeader::read_header_extensions(&mut disk_file, &mut header, None).unwrap();
|
||||
QcowHeader::read_header_extensions(&disk_file, &mut header, None).unwrap();
|
||||
assert_eq!(
|
||||
header.backing_file.as_ref().and_then(|bf| bf.format),
|
||||
Some(ImageType::Qcow2)
|
||||
@@ -1254,7 +1244,7 @@ mod unit_tests {
|
||||
|
||||
#[test]
|
||||
fn read_header_extensions_invalid_format() {
|
||||
let (mut disk_file, mut header) =
|
||||
let (disk_file, mut header) =
|
||||
create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "vmdk".as_bytes());
|
||||
|
||||
header.backing_file = Some(BackingFileConfig {
|
||||
@@ -1262,7 +1252,7 @@ mod unit_tests {
|
||||
format: None,
|
||||
});
|
||||
|
||||
let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header, None);
|
||||
let result = QcowHeader::read_header_extensions(&disk_file, &mut header, None);
|
||||
assert!(matches!(
|
||||
result.unwrap_err(),
|
||||
Error::UnsupportedBackingFileFormat(_)
|
||||
@@ -1271,12 +1261,12 @@ mod unit_tests {
|
||||
|
||||
#[test]
|
||||
fn read_header_extensions_invalid_utf8() {
|
||||
let (mut disk_file, mut header) = create_header_with_extension(
|
||||
let (disk_file, mut header) = create_header_with_extension(
|
||||
HEADER_EXT_BACKING_FORMAT,
|
||||
&[0xFF, 0xFE, 0xFD], // invalid UTF-8
|
||||
);
|
||||
|
||||
let result = QcowHeader::read_header_extensions(&mut disk_file, &mut header, None);
|
||||
let result = QcowHeader::read_header_extensions(&disk_file, &mut header, None);
|
||||
// Should fail with InvalidBackingFileName error
|
||||
assert!(matches!(
|
||||
result.unwrap_err(),
|
||||
@@ -1312,11 +1302,11 @@ mod unit_tests {
|
||||
/// the file until stack overflow.
|
||||
fn new_self_referential_qcow(path: &Path) -> Result<()> {
|
||||
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, path.to_str())?;
|
||||
let mut disk_file = AlignedFile::new(
|
||||
let disk_file = AlignedFile::new(
|
||||
File::create(path).expect("Failed to create image file."),
|
||||
false,
|
||||
);
|
||||
header.write_to(&mut disk_file)?;
|
||||
header.write_to(&disk_file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::io::{self, Write};
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::os::unix::fs::FileExt;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
use vmm_sys_util::write_zeroes::WriteZeroesAt;
|
||||
|
||||
use crate::aligned_file::AlignedFile;
|
||||
@@ -21,7 +21,6 @@ type RefcountWriter = fn(&mut AlignedFile, u64, &[u64]) -> io::Result<()>;
|
||||
/// Big-endian file access trait.
|
||||
pub(super) trait BeUint: Sized + Copy {
|
||||
fn from_be_slice(bytes: &[u8]) -> u64;
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self>;
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
@@ -31,10 +30,6 @@ impl BeUint for u8 {
|
||||
bytes[0] as u64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u8()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u8(val)
|
||||
}
|
||||
@@ -46,10 +41,6 @@ impl BeUint for u16 {
|
||||
u16::from_be_bytes([bytes[0], bytes[1]]) as u64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u16::<BigEndian>()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u16::<BigEndian>(val)
|
||||
}
|
||||
@@ -61,10 +52,6 @@ impl BeUint for u32 {
|
||||
u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u32::<BigEndian>()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u32::<BigEndian>(val)
|
||||
}
|
||||
@@ -78,10 +65,6 @@ impl BeUint for u64 {
|
||||
])
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u64::<BigEndian>()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u64::<BigEndian>(val)
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ pub mod internal;
|
||||
pub mod worker;
|
||||
|
||||
use std::fs::File;
|
||||
#[cfg(any(test, feature = "test-utils"))]
|
||||
use std::io::Seek;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
#[cfg(any(test, feature = "test-utils"))]
|
||||
use std::path::Path;
|
||||
@@ -29,7 +27,7 @@ use self::internal::backing::shared_backing_from;
|
||||
use self::internal::metadata::{BackingRead, QcowMetadata};
|
||||
use self::internal::qcow_raw_file::QcowRawFile;
|
||||
#[cfg(any(test, feature = "test-utils"))]
|
||||
use self::internal::{BackingFileConfig, Error as QcowError, QcowHeader};
|
||||
use self::internal::{BackingFileConfig, QcowHeader};
|
||||
use self::internal::{MAX_NESTING_DEPTH, parse_qcow};
|
||||
#[cfg(feature = "io_uring")]
|
||||
use self::worker::async_uring::QcowAsync;
|
||||
@@ -164,15 +162,13 @@ pub(crate) fn create_image(
|
||||
{
|
||||
backing_file.format = cfg.format;
|
||||
}
|
||||
let mut raw = AlignedFile::new(
|
||||
let raw = AlignedFile::new(
|
||||
file.try_clone()
|
||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?,
|
||||
false,
|
||||
);
|
||||
raw.rewind()
|
||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, QcowError::SeekingFile(e)))?;
|
||||
header
|
||||
.write_to(&mut raw)
|
||||
.write_to(&raw)
|
||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
||||
let (inner, _backing, _sparse) = parse_qcow(raw, MAX_NESTING_DEPTH, true)?;
|
||||
// Flush dirty caches and clear the dirty bit
|
||||
|
||||
@@ -373,9 +373,8 @@ mod unit_tests {
|
||||
}
|
||||
|
||||
fn qcow_header_is_corrupt(file: &File) -> bool {
|
||||
let mut raw = AlignedFile::new(file.try_clone().unwrap(), false);
|
||||
raw.seek(SeekFrom::Start(0)).unwrap();
|
||||
QcowHeader::new(&mut raw).unwrap().is_corrupt()
|
||||
let raw = AlignedFile::new(file.try_clone().unwrap(), false);
|
||||
QcowHeader::new(&raw).unwrap().is_corrupt()
|
||||
}
|
||||
|
||||
fn create_disk_with_data(
|
||||
@@ -866,7 +865,7 @@ mod unit_tests {
|
||||
}
|
||||
|
||||
fn create_qcow2_overlay_header(overlay_path: &Path, backing_path: &str, file_size: u64) {
|
||||
let mut file = OpenOptions::new()
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
@@ -875,8 +874,9 @@ mod unit_tests {
|
||||
.unwrap();
|
||||
let header =
|
||||
QcowHeader::create_for_size_and_path(3, file_size, Some(backing_path)).unwrap();
|
||||
header.write_to(&mut file).unwrap();
|
||||
file.sync_all().unwrap();
|
||||
let raw = AlignedFile::new(file, false);
|
||||
header.write_to(&raw).unwrap();
|
||||
raw.sync_all().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -989,11 +989,12 @@ mod unit_tests {
|
||||
let file_size = cluster_size * 2;
|
||||
|
||||
{
|
||||
let mut file = overlay_temp.as_file().try_clone().unwrap();
|
||||
let file = overlay_temp.as_file().try_clone().unwrap();
|
||||
let header =
|
||||
QcowHeader::create_for_size_and_path(3, file_size, Some("missing.raw")).unwrap();
|
||||
header.write_to(&mut file).unwrap();
|
||||
file.sync_all().unwrap();
|
||||
let raw = AlignedFile::new(file, false);
|
||||
header.write_to(&raw).unwrap();
|
||||
raw.sync_all().unwrap();
|
||||
}
|
||||
|
||||
let mut overlay_file = overlay_temp.into_file();
|
||||
|
||||
Reference in New Issue
Block a user