mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: qcow: Implement dirty bit support for QCOW2 v3 images
Add support for the dirty bit (bit 0 of incompatible_features) which indicates the image was not closed cleanly. This improves data integrity by allowing detection of potentially corrupted images. On open: - If dirty bit is already set, log a warning and trigger refcount rebuild - Set the dirty bit and write it to disk immediately - Sync to ensure persistence before any writes - Skip dirty bit and refcount rebuild for readonly files On clean close: - Clear the dirty bit in the header - Write it to disk and sync Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
committed by
Rob Bradford
parent
a6aecad635
commit
cc96fc14b4
@@ -231,7 +231,8 @@ bitflags! {
|
||||
|
||||
impl IncompatFeatures {
|
||||
/// Features supported by this implementation.
|
||||
const SUPPORTED: IncompatFeatures = IncompatFeatures::COMPRESSION;
|
||||
const SUPPORTED: IncompatFeatures =
|
||||
IncompatFeatures::DIRTY.union(IncompatFeatures::COMPRESSION);
|
||||
|
||||
/// Get the fallback name for a known feature bit.
|
||||
fn flag_name(bit: u8) -> Option<&'static str> {
|
||||
@@ -680,6 +681,39 @@ impl QcowHeader {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write only the incompatible_features field to the file at its fixed offset.
|
||||
fn write_incompatible_features<F: Seek + Write>(&self, file: &mut F) -> Result<()> {
|
||||
if self.version != 3 {
|
||||
return Ok(());
|
||||
}
|
||||
file.seek(SeekFrom::Start(V2_BARE_HEADER_SIZE as u64))
|
||||
.map_err(Error::WritingHeader)?;
|
||||
file.write_u64::<BigEndian>(self.incompatible_features)
|
||||
.map_err(Error::WritingHeader)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set or clear the dirty bit for QCOW2 v3 images.
|
||||
///
|
||||
/// When `dirty` is true, sets the bit to indicate the image is in use.
|
||||
/// When `dirty` is false, clears the bit to indicate a clean shutdown.
|
||||
pub fn set_dirty_bit<F: Seek + Write + FileSync>(
|
||||
&mut self,
|
||||
file: &mut F,
|
||||
dirty: bool,
|
||||
) -> Result<()> {
|
||||
if self.version == 3 {
|
||||
if dirty {
|
||||
self.incompatible_features |= IncompatFeatures::DIRTY.bits();
|
||||
} else {
|
||||
self.incompatible_features &= !IncompatFeatures::DIRTY.bits();
|
||||
}
|
||||
self.write_incompatible_features(file)?;
|
||||
file.fsync().map_err(Error::WritingHeader)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> u64 {
|
||||
@@ -892,7 +926,18 @@ impl QcowFile {
|
||||
|
||||
let mut raw_file = QcowRawFile::from(file, cluster_size, refcount_bits)
|
||||
.ok_or(Error::InvalidClusterSize)?;
|
||||
if refcount_rebuild_required {
|
||||
let is_writable = raw_file.file().is_writable();
|
||||
|
||||
// Image already has dirty bit set. Refcounts may be invalid.
|
||||
if IncompatFeatures::from_bits_truncate(header.incompatible_features)
|
||||
.contains(IncompatFeatures::DIRTY)
|
||||
{
|
||||
log::warn!("QCOW2 image not cleanly closed, rebuilding refcounts");
|
||||
refcount_rebuild_required = true;
|
||||
}
|
||||
|
||||
// Skip refcount rebuilding for readonly files.
|
||||
if refcount_rebuild_required && is_writable {
|
||||
QcowFile::rebuild_refcounts(&mut raw_file, header.clone())?;
|
||||
}
|
||||
|
||||
@@ -965,6 +1010,13 @@ impl QcowFile {
|
||||
|
||||
qcow.find_avail_clusters()?;
|
||||
|
||||
if !IncompatFeatures::from_bits_truncate(qcow.header.incompatible_features)
|
||||
.contains(IncompatFeatures::DIRTY)
|
||||
&& is_writable
|
||||
{
|
||||
qcow.header.set_dirty_bit(qcow.raw_file.file_mut(), true)?;
|
||||
}
|
||||
|
||||
Ok(qcow)
|
||||
}
|
||||
|
||||
@@ -1999,6 +2051,7 @@ impl QcowFile {
|
||||
if sync_required {
|
||||
self.raw_file.file_mut().sync_data()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2012,6 +2065,9 @@ impl AsRawFd for QcowFile {
|
||||
impl Drop for QcowFile {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.sync_caches();
|
||||
if self.raw_file.file().is_writable() {
|
||||
let _ = self.header.set_dirty_bit(self.raw_file.file_mut(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -276,6 +276,11 @@ impl QcowRawFile {
|
||||
Ok(Some(new_cluster_address))
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying file.
|
||||
pub fn file(&self) -> &RawFile {
|
||||
&self.file
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying file.
|
||||
pub fn file_mut(&mut self) -> &mut RawFile {
|
||||
&mut self.file
|
||||
|
||||
@@ -15,6 +15,7 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::slice;
|
||||
|
||||
use libc::c_void;
|
||||
use vmm_sys_util::file_traits::FileSync;
|
||||
use vmm_sys_util::seek_hole::SeekHole;
|
||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
||||
|
||||
@@ -122,6 +123,17 @@ impl RawFile {
|
||||
pub fn is_direct(&self) -> bool {
|
||||
self.direct_io
|
||||
}
|
||||
|
||||
/// 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.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 {
|
||||
@@ -327,6 +339,12 @@ impl PunchHole for RawFile {
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSync for RawFile {
|
||||
fn fsync(&mut self) -> std::io::Result<()> {
|
||||
self.file.fsync()
|
||||
}
|
||||
}
|
||||
|
||||
impl SeekHole for RawFile {
|
||||
fn seek_hole(&mut self, offset: u64) -> std::io::Result<Option<u64>> {
|
||||
match self.file.seek_hole(offset) {
|
||||
|
||||
Reference in New Issue
Block a user