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:
Anatol Belski
2026-07-02 21:51:46 +02:00
committed by Rob Bradford
parent 4abdb3419b
commit 43682e22b0
6 changed files with 164 additions and 171 deletions
+117 -91
View File
@@ -9,7 +9,8 @@
//! QCOW2 header parsing, validation, and creation. //! QCOW2 header parsing, validation, and creation.
use std::fmt::{Display, Formatter, Result as FmtResult}; 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 std::str::FromStr;
use bitflags::bitflags; use bitflags::bitflags;
@@ -209,27 +210,34 @@ 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 AlignedFile, f: &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<()> {
// Extensions start directly after the header // Extensions start directly after the header.
f.seek(SeekFrom::Start(header.header_size as u64)) let mut offset = header.header_size as u64;
.map_err(Error::ReadingHeader)?;
loop { 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 { if ext_type == HEADER_EXT_END {
break; 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 { match ext_type {
HEADER_EXT_BACKING_FORMAT => { HEADER_EXT_BACKING_FORMAT => {
let mut format_bytes = vec![0u8; ext_length as usize]; 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)?; .map_err(Error::ReadingHeader)?;
offset += format_bytes.len() as u64;
let format_str = String::from_utf8(format_bytes) let format_str = String::from_utf8(format_bytes)
.map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?; .map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?;
if let Some(backing_file) = &mut header.backing_file { 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() => { HEADER_EXT_FEATURE_NAME_TABLE if feature_table.is_some() => {
const FEATURE_NAME_ENTRY_SIZE: usize = 1 + 1 + 46; // type + bit + name const FEATURE_NAME_ENTRY_SIZE: usize = 1 + 1 + 46; // type + bit + name
let mut data = vec![0u8; ext_length as usize]; 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(); let table = feature_table.as_mut().unwrap();
for entry in data.chunks_exact(FEATURE_NAME_ENTRY_SIZE) { for entry in data.chunks_exact(FEATURE_NAME_ENTRY_SIZE) {
if entry[0] == FEAT_TYPE_INCOMPATIBLE { if entry[0] == FEAT_TYPE_INCOMPATIBLE {
@@ -253,72 +263,92 @@ impl QcowHeader {
} }
_ => { _ => {
// Skip unknown extension // Skip unknown extension
f.seek(SeekFrom::Current(ext_length as i64)) offset += ext_length as u64;
.map_err(Error::ReadingHeader)?;
} }
} }
// Skip to the next 8 byte boundary // Skip to the next 8 byte boundary
let padding = (8 - (ext_length % 8)) % 8; let padding = (8 - (ext_length % 8)) % 8;
f.seek(SeekFrom::Current(padding as i64)) offset += padding as u64;
.map_err(Error::ReadingHeader)?;
} }
Ok(()) Ok(())
} }
/// Creates a QcowHeader from a reference to a file. /// Creates a QcowHeader from a reference to a file.
pub fn new(f: &mut AlignedFile) -> Result<QcowHeader> { pub fn new(f: &AlignedFile) -> Result<QcowHeader> {
f.rewind().map_err(Error::ReadingHeader)?; // Decode the next big endian u32 from the buffer and advance `pos`.
let magic = u32::read_be(f).map_err(Error::ReadingHeader)?; 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 { if magic != QCOW_MAGIC {
return Err(Error::InvalidMagic); return Err(Error::InvalidMagic);
} }
// Reads the next u32 from the file. let version = next_u32(&buf, &mut pos);
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 mut header = QcowHeader { let mut header = QcowHeader {
magic, magic,
version, version,
backing_file_offset: read_u64_be(f)?, backing_file_offset: next_u64(&buf, &mut pos),
backing_file_size: read_u32_be(f)?, backing_file_size: next_u32(&buf, &mut pos),
cluster_bits: read_u32_be(f)?, cluster_bits: next_u32(&buf, &mut pos),
size: read_u64_be(f)?, size: next_u64(&buf, &mut pos),
crypt_method: read_u32_be(f)?, crypt_method: next_u32(&buf, &mut pos),
l1_size: read_u32_be(f)?, l1_size: next_u32(&buf, &mut pos),
l1_table_offset: read_u64_be(f)?, l1_table_offset: next_u64(&buf, &mut pos),
refcount_table_offset: read_u64_be(f)?, refcount_table_offset: next_u64(&buf, &mut pos),
refcount_table_clusters: read_u32_be(f)?, refcount_table_clusters: next_u32(&buf, &mut pos),
nb_snapshots: read_u32_be(f)?, nb_snapshots: next_u32(&buf, &mut pos),
snapshots_offset: read_u64_be(f)?, snapshots_offset: next_u64(&buf, &mut pos),
incompatible_features: if version == 2 { 0 } else { read_u64_be(f)? }, incompatible_features: if version == 2 {
compatible_features: if version == 2 { 0 } else { read_u64_be(f)? }, 0
autoclear_features: if version == 2 { 0 } else { read_u64_be(f)? }, } 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 { refcount_order: if version == 2 {
DEFAULT_REFCOUNT_ORDER DEFAULT_REFCOUNT_ORDER
} else { } else {
read_u32_be(f)? next_u32(&buf, &mut pos)
}, },
header_size: if version == 2 { header_size: if version == 2 {
V2_BARE_HEADER_SIZE V2_BARE_HEADER_SIZE
} else { } else {
read_u32_be(f)? next_u32(&buf, &mut pos)
}, },
compression_type: CompressionType::Zlib, compression_type: CompressionType::Zlib,
backing_file: None, backing_file: None,
}; };
if version == 3 && header.header_size > V3_BARE_HEADER_SIZE { 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 { header.compression_type = if raw_compression_type == COMPRESSION_TYPE_ZLIB {
Ok(CompressionType::Zlib) Ok(CompressionType::Zlib)
} else if raw_compression_type == COMPRESSION_TYPE_ZSTD { } else if raw_compression_type == COMPRESSION_TYPE_ZSTD {
@@ -360,10 +390,8 @@ impl QcowHeader {
cluster_size, 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]; 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)?; .map_err(Error::ReadingHeader)?;
let path = String::from_utf8(backing_file_name_bytes) let path = String::from_utf8(backing_file_name_bytes)
.map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?; .map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?;
@@ -476,65 +504,63 @@ impl QcowHeader {
}) })
} }
/// Write the header to `file`. /// Write the header to `f`.
pub fn write_to<F: Write + Seek>(&self, file: &mut F) -> Result<()> { pub fn write_to(&self, f: &AlignedFile) -> Result<()> {
// Writes the next u32 to the file. // Build the header in memory, then write it in one positional write.
fn write_u32_be<F: Write>(f: &mut F, value: u32) -> Result<()> { let mut buf = Vec::new();
u32::write_be(f, value).map_err(Error::WritingHeader) 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());
// Writes the next u64 to the file. buf.extend_from_slice(&self.backing_file_size.to_be_bytes());
fn write_u64_be<F: Write>(f: &mut F, value: u64) -> Result<()> { buf.extend_from_slice(&self.cluster_bits.to_be_bytes());
u64::write_be(f, value).map_err(Error::WritingHeader) 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());
write_u32_be(file, self.magic)?; buf.extend_from_slice(&self.l1_table_offset.to_be_bytes());
write_u32_be(file, self.version)?; buf.extend_from_slice(&self.refcount_table_offset.to_be_bytes());
write_u64_be(file, self.backing_file_offset)?; buf.extend_from_slice(&self.refcount_table_clusters.to_be_bytes());
write_u32_be(file, self.backing_file_size)?; buf.extend_from_slice(&self.nb_snapshots.to_be_bytes());
write_u32_be(file, self.cluster_bits)?; buf.extend_from_slice(&self.snapshots_offset.to_be_bytes());
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)?;
if self.version == 3 { if self.version == 3 {
write_u64_be(file, self.incompatible_features)?; buf.extend_from_slice(&self.incompatible_features.to_be_bytes());
write_u64_be(file, self.compatible_features)?; buf.extend_from_slice(&self.compatible_features.to_be_bytes());
write_u64_be(file, self.autoclear_features)?; buf.extend_from_slice(&self.autoclear_features.to_be_bytes());
write_u32_be(file, self.refcount_order)?; buf.extend_from_slice(&self.refcount_order.to_be_bytes());
write_u32_be(file, self.header_size)?; buf.extend_from_slice(&self.header_size.to_be_bytes());
if self.header_size > V3_BARE_HEADER_SIZE { 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 // header extension type: end of header extension area
write_u32_be(file, 0)?; // length of header extension data: 0 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 let Some(backing_file_path) = self.backing_file.as_ref().map(|bf| &bf.path) {
if self.backing_file_offset > 0 { let offset = if self.backing_file_offset > 0 {
file.seek(SeekFrom::Start(self.backing_file_offset)) self.backing_file_offset
.map_err(Error::WritingHeader)?; } else {
} buf.len() as u64
write!(file, "{backing_file_path}").map_err(Error::WritingHeader)?; };
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 // Set the file length by writing a zero to the last byte. This also
// a `File` instead of anything that implements seek as the `file` argument. // zeros the l1 and refcount table clusters.
// Zeros out the l1 and refcount table clusters.
let cluster_size = 0x01u64 << self.cluster_bits; let cluster_size = 0x01u64 << self.cluster_bits;
let refcount_blocks_size = u64::from(self.refcount_table_clusters) * cluster_size; 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, self.refcount_table_offset + refcount_blocks_size - 2,
)) )
.map_err(Error::WritingHeader)?; .map_err(Error::WritingHeader)?;
file.write(&[0u8]).map_err(Error::WritingHeader)?;
Ok(()) Ok(())
} }
+1 -4
View File
@@ -17,10 +17,9 @@
//! operations upgrade to a write lock. //! operations upgrade to a write lock.
use std::cmp::min; use std::cmp::min;
use std::io::{self, Seek};
use std::mem;
use std::os::unix::fs::FileExt; use std::os::unix::fs::FileExt;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::{io, mem};
use libc::{EINVAL, EIO}; use libc::{EINVAL, EIO};
use vmm_sys_util::write_zeroes::WriteZeroesAt; use vmm_sys_util::write_zeroes::WriteZeroesAt;
@@ -756,7 +755,6 @@ impl QcowState {
self.header.size = new_size; self.header.size = new_size;
self.raw_file.file_mut().rewind()?;
self.header self.header
.write_to(self.raw_file.file_mut()) .write_to(self.raw_file.file_mut())
.map_err(|e| io::Error::other(format!("failed to write header during resize: {e}")))?; .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_size = new_l1_size;
self.header.l1_table_offset = new_l1_offset; self.header.l1_table_offset = new_l1_offset;
self.raw_file.file_mut().rewind()?;
self.header self.header
.write_to(self.raw_file.file_mut()) .write_to(self.raw_file.file_mut())
.map_err(|e| io::Error::other(format!("failed to write header during resize: {e}")))?; .map_err(|e| io::Error::other(format!("failed to write header during resize: {e}")))?;
+31 -41
View File
@@ -16,11 +16,10 @@ mod vec_cache;
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::fmt::{Debug, Formatter, Result as FmtResult}; use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::fs::{OpenOptions, read_link}; use std::fs::{OpenOptions, read_link};
use std::io::{self, Seek};
use std::os::fd::AsRawFd; use std::os::fd::AsRawFd;
use std::os::unix::fs::FileExt; use std::os::unix::fs::FileExt;
use std::path::Path; use std::path::Path;
use std::{result, str}; use std::{io, result, str};
pub use header::{ pub use header::{
BackingFileConfig, CompressionType, ImageType, IncompatFeatures, MissingFeatureError, BackingFileConfig, CompressionType, ImageType, IncompatFeatures, MissingFeatureError,
@@ -127,8 +126,6 @@ pub enum Error {
ResizeIo(#[source] io::Error), ResizeIo(#[source] io::Error),
#[error("Resize not supported with backing file")] #[error("Resize not supported with backing file")]
ResizeWithBackingFile, ResizeWithBackingFile,
#[error("Failed to seek file")]
SeekingFile(#[source] io::Error),
#[error("Failed to set file size")] #[error("Failed to set file size")]
SettingFileSize(#[source] io::Error), SettingFileSize(#[source] io::Error),
#[error("Failed to set refcount refcount")] #[error("Failed to set refcount refcount")]
@@ -270,11 +267,11 @@ 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: AlignedFile, 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)> {
let mut header = QcowHeader::new(&mut file).map_err(|e| { let mut header = QcowHeader::new(&file).map_err(|e| {
let kind = match &e { let kind = match &e {
Error::InvalidMagic Error::InvalidMagic
| Error::BackingFileTooLong(_) | Error::BackingFileTooLong(_)
@@ -730,7 +727,6 @@ fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> BlockRes
) -> Result<()> { ) -> Result<()> {
// Rewrite the header with lazy refcounts enabled while we are rebuilding the tables. // Rewrite the header with lazy refcounts enabled while we are rebuilding the tables.
header.compatible_features |= COMPATIBLE_FEATURES_LAZY_REFCOUNTS; header.compatible_features |= COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
raw_file.file_mut().rewind().map_err(Error::SeekingFile)?;
header.write_to(raw_file.file_mut())?; header.write_to(raw_file.file_mut())?;
for (i, refblock_addr) in ref_table.iter().enumerate() { 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. // Rewrite the header again, now with lazy refcounts disabled.
header.compatible_features &= !COMPATIBLE_FEATURES_LAZY_REFCOUNTS; header.compatible_features &= !COMPATIBLE_FEATURES_LAZY_REFCOUNTS;
raw_file.file_mut().rewind().map_err(Error::SeekingFile)?;
header.write_to(raw_file.file_mut())?; header.write_to(raw_file.file_mut())?;
Ok(()) Ok(())
@@ -1000,8 +995,8 @@ 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 = AlignedFile::new(temp.as_file().try_clone().unwrap(), false); let raw = AlignedFile::new(temp.as_file().try_clone().unwrap(), false);
header.write_to(&mut raw).expect("write header"); header.write_to(&raw).expect("write header");
drop(raw); drop(raw);
let file = temp.into_file(); let file = temp.into_file();
QcowDisk::new(file, false, backing_files, true, false) QcowDisk::new(file, false, backing_files, true, false)
@@ -1037,14 +1032,14 @@ mod unit_tests {
#[test] #[test]
fn header_read() { fn header_read() {
with_basic_file(&valid_header_v2(), |mut disk_file: AlignedFile| { with_basic_file(&valid_header_v2(), |disk_file: AlignedFile| {
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header."); let header = QcowHeader::new(&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: AlignedFile| { with_basic_file(&valid_header_v3(), |disk_file: AlignedFile| {
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header."); let header = QcowHeader::new(&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);
assert_eq!(header.header_size, V3_BARE_HEADER_SIZE); assert_eq!(header.header_size, V3_BARE_HEADER_SIZE);
@@ -1055,13 +1050,11 @@ 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: AlignedFile = let disk_file: AlignedFile = AlignedFile::new(TempFile::new().unwrap().into_file(), false);
AlignedFile::new(TempFile::new().unwrap().into_file(), false);
header header
.write_to(&mut disk_file) .write_to(&disk_file)
.expect("Failed to write header to shm."); .expect("Failed to write header to shm.");
disk_file.rewind().unwrap(); let read_header = QcowHeader::new(&disk_file).expect("Failed to create header.");
let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header.");
assert_eq!( assert_eq!(
header.backing_file.as_ref().map(|bf| bf.path.clone()), header.backing_file.as_ref().map(|bf| bf.path.clone()),
Some(String::from("/my/path/to/a/file")) Some(String::from("/my/path/to/a/file"))
@@ -1076,13 +1069,11 @@ 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: AlignedFile = let disk_file: AlignedFile = AlignedFile::new(TempFile::new().unwrap().into_file(), false);
AlignedFile::new(TempFile::new().unwrap().into_file(), false);
header header
.write_to(&mut disk_file) .write_to(&disk_file)
.expect("Failed to write header to shm."); .expect("Failed to write header to shm.");
disk_file.rewind().unwrap(); let read_header = QcowHeader::new(&disk_file).expect("Failed to create header.");
let read_header = QcowHeader::new(&mut disk_file).expect("Failed to create header.");
assert_eq!( assert_eq!(
header.backing_file.as_ref().map(|bf| bf.path.clone()), header.backing_file.as_ref().map(|bf| bf.path.clone()),
Some(String::from("/my/path/to/a/file")) Some(String::from("/my/path/to/a/file"))
@@ -1101,17 +1092,16 @@ 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: AlignedFile = AlignedFile::new( let 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(),
false, false,
); );
header header
.write_to(&mut disk_file) .write_to(&disk_file)
.expect("Failed to write header."); .expect("Failed to write header.");
disk_file.rewind().expect("Failed to rewind disk file."); QcowHeader::new(&disk_file)
QcowHeader::new(&mut disk_file)
} }
#[test] #[test]
@@ -1178,7 +1168,7 @@ mod unit_tests {
let mut disk_file: AlignedFile = let mut disk_file: AlignedFile =
AlignedFile::new(TempFile::new().unwrap().into_file(), false); AlignedFile::new(TempFile::new().unwrap().into_file(), false);
header.write_to(&mut disk_file).unwrap(); header.write_to(&disk_file).unwrap();
// Write extension // Write extension
disk_file disk_file
@@ -1203,7 +1193,7 @@ mod unit_tests {
#[test] #[test]
fn read_header_extensions_unknown_extension() { 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 0x12345678, // unknown type
"test".as_bytes(), "test".as_bytes(),
); );
@@ -1214,13 +1204,13 @@ mod unit_tests {
format: None, 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); assert_eq!(header.backing_file.as_ref().and_then(|bf| bf.format), None);
} }
#[test] #[test]
fn read_header_extensions_raw_format() { 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()); create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "raw".as_bytes());
header.backing_file = Some(BackingFileConfig { header.backing_file = Some(BackingFileConfig {
@@ -1228,7 +1218,7 @@ mod unit_tests {
format: None, 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!( assert_eq!(
header.backing_file.as_ref().and_then(|bf| bf.format), header.backing_file.as_ref().and_then(|bf| bf.format),
Some(ImageType::Raw) Some(ImageType::Raw)
@@ -1237,7 +1227,7 @@ mod unit_tests {
#[test] #[test]
fn read_header_extensions_qcow2_format() { 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()); create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "qcow2".as_bytes());
header.backing_file = Some(BackingFileConfig { header.backing_file = Some(BackingFileConfig {
@@ -1245,7 +1235,7 @@ mod unit_tests {
format: None, 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!( assert_eq!(
header.backing_file.as_ref().and_then(|bf| bf.format), header.backing_file.as_ref().and_then(|bf| bf.format),
Some(ImageType::Qcow2) Some(ImageType::Qcow2)
@@ -1254,7 +1244,7 @@ mod unit_tests {
#[test] #[test]
fn read_header_extensions_invalid_format() { 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()); create_header_with_extension(HEADER_EXT_BACKING_FORMAT, "vmdk".as_bytes());
header.backing_file = Some(BackingFileConfig { header.backing_file = Some(BackingFileConfig {
@@ -1262,7 +1252,7 @@ mod unit_tests {
format: None, 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!( assert!(matches!(
result.unwrap_err(), result.unwrap_err(),
Error::UnsupportedBackingFileFormat(_) Error::UnsupportedBackingFileFormat(_)
@@ -1271,12 +1261,12 @@ mod unit_tests {
#[test] #[test]
fn read_header_extensions_invalid_utf8() { 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, HEADER_EXT_BACKING_FORMAT,
&[0xFF, 0xFE, 0xFD], // invalid UTF-8 &[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 // Should fail with InvalidBackingFileName error
assert!(matches!( assert!(matches!(
result.unwrap_err(), result.unwrap_err(),
@@ -1312,11 +1302,11 @@ 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 = AlignedFile::new( let disk_file = AlignedFile::new(
File::create(path).expect("Failed to create image file."), File::create(path).expect("Failed to create image file."),
false, false,
); );
header.write_to(&mut disk_file)?; header.write_to(&disk_file)?;
Ok(()) Ok(())
} }
@@ -5,11 +5,11 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::fmt::Debug; 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::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::os::unix::fs::FileExt; use std::os::unix::fs::FileExt;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, WriteBytesExt};
use vmm_sys_util::write_zeroes::WriteZeroesAt; use vmm_sys_util::write_zeroes::WriteZeroesAt;
use crate::aligned_file::AlignedFile; use crate::aligned_file::AlignedFile;
@@ -21,7 +21,6 @@ type RefcountWriter = fn(&mut AlignedFile, u64, &[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 {
fn from_be_slice(bytes: &[u8]) -> u64; 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<()>; fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()>;
} }
@@ -31,10 +30,6 @@ impl BeUint for u8 {
bytes[0] as u64 bytes[0] as u64
} }
#[inline(always)] #[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<()> { fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u8(val) w.write_u8(val)
} }
@@ -46,10 +41,6 @@ impl BeUint for u16 {
u16::from_be_bytes([bytes[0], bytes[1]]) as u64 u16::from_be_bytes([bytes[0], bytes[1]]) as u64
} }
#[inline(always)] #[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<()> { fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u16::<BigEndian>(val) 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 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u64
} }
#[inline(always)] #[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<()> { fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u32::<BigEndian>(val) w.write_u32::<BigEndian>(val)
} }
@@ -78,10 +65,6 @@ impl BeUint for u64 {
]) ])
} }
#[inline(always)] #[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<()> { fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u64::<BigEndian>(val) w.write_u64::<BigEndian>(val)
} }
+3 -7
View File
@@ -12,8 +12,6 @@ pub mod internal;
pub mod worker; pub mod worker;
use std::fs::File; use std::fs::File;
#[cfg(any(test, feature = "test-utils"))]
use std::io::Seek;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
#[cfg(any(test, feature = "test-utils"))] #[cfg(any(test, feature = "test-utils"))]
use std::path::Path; use std::path::Path;
@@ -29,7 +27,7 @@ use self::internal::backing::shared_backing_from;
use self::internal::metadata::{BackingRead, QcowMetadata}; 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, QcowHeader};
use self::internal::{MAX_NESTING_DEPTH, 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;
@@ -164,15 +162,13 @@ pub(crate) fn create_image(
{ {
backing_file.format = cfg.format; backing_file.format = cfg.format;
} }
let mut raw = AlignedFile::new( let 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,
); );
raw.rewind()
.map_err(|e| BlockError::new(BlockErrorKind::Io, QcowError::SeekingFile(e)))?;
header header
.write_to(&mut raw) .write_to(&raw)
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?; .map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
let (inner, _backing, _sparse) = parse_qcow(raw, MAX_NESTING_DEPTH, true)?; let (inner, _backing, _sparse) = parse_qcow(raw, MAX_NESTING_DEPTH, true)?;
// Flush dirty caches and clear the dirty bit // Flush dirty caches and clear the dirty bit
+10 -9
View File
@@ -373,9 +373,8 @@ mod unit_tests {
} }
fn qcow_header_is_corrupt(file: &File) -> bool { fn qcow_header_is_corrupt(file: &File) -> bool {
let mut raw = AlignedFile::new(file.try_clone().unwrap(), false); let raw = AlignedFile::new(file.try_clone().unwrap(), false);
raw.seek(SeekFrom::Start(0)).unwrap(); QcowHeader::new(&raw).unwrap().is_corrupt()
QcowHeader::new(&mut raw).unwrap().is_corrupt()
} }
fn create_disk_with_data( 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) { 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) .read(true)
.write(true) .write(true)
.create(true) .create(true)
@@ -875,8 +874,9 @@ mod unit_tests {
.unwrap(); .unwrap();
let header = let header =
QcowHeader::create_for_size_and_path(3, file_size, Some(backing_path)).unwrap(); QcowHeader::create_for_size_and_path(3, file_size, Some(backing_path)).unwrap();
header.write_to(&mut file).unwrap(); let raw = AlignedFile::new(file, false);
file.sync_all().unwrap(); header.write_to(&raw).unwrap();
raw.sync_all().unwrap();
} }
#[test] #[test]
@@ -989,11 +989,12 @@ mod unit_tests {
let file_size = cluster_size * 2; 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 = let header =
QcowHeader::create_for_size_and_path(3, file_size, Some("missing.raw")).unwrap(); QcowHeader::create_for_size_and_path(3, file_size, Some("missing.raw")).unwrap();
header.write_to(&mut file).unwrap(); let raw = AlignedFile::new(file, false);
file.sync_all().unwrap(); header.write_to(&raw).unwrap();
raw.sync_all().unwrap();
} }
let mut overlay_file = overlay_temp.into_file(); let mut overlay_file = overlay_temp.into_file();