mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: qcow: Share the deallocation path between the two engines
The sync and uring qcow engines each carried an identical apply_dealloc_action plus punch_hole and write_zeroes bodies that differed only by a flag and the completion sink. Move that shared deallocation logic into common.rs as apply_dealloc_action and deallocate_range_result. Each engine now calls it and injects the completion through its own queue. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
committed by
Rob Bradford
parent
9ea9019d29
commit
eeb71091db
@@ -10,7 +10,12 @@
|
|||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
||||||
|
|
||||||
use super::decoder::Decoder;
|
use super::decoder::Decoder;
|
||||||
|
use super::metadata::{BackingRead, DeallocAction, QcowMetadata};
|
||||||
|
use super::qcow_raw_file::QcowRawFile;
|
||||||
|
use crate::async_io::AsyncIoError;
|
||||||
|
|
||||||
/// Decompress a full QCOW2 cluster from compressed data.
|
/// Decompress a full QCOW2 cluster from compressed data.
|
||||||
///
|
///
|
||||||
@@ -32,6 +37,68 @@ pub(super) fn decompress_cluster(
|
|||||||
Ok(decompressed)
|
Ok(decompressed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Applies one deallocation action to the data file and refcount table.
|
||||||
|
pub(super) fn apply_dealloc_action(
|
||||||
|
metadata: &QcowMetadata,
|
||||||
|
data_file: &mut QcowRawFile,
|
||||||
|
action: &DeallocAction,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
match action {
|
||||||
|
DeallocAction::PunchHole {
|
||||||
|
host_offset,
|
||||||
|
length,
|
||||||
|
} => {
|
||||||
|
data_file.file_mut().punch_hole(*host_offset, *length)?;
|
||||||
|
metadata.complete_punch_hole(*host_offset);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
DeallocAction::WriteZeroes {
|
||||||
|
host_offset,
|
||||||
|
length,
|
||||||
|
} => data_file
|
||||||
|
.file_mut()
|
||||||
|
.write_zeroes_at(*host_offset, *length)
|
||||||
|
.map(|_| ()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deallocates a byte range and returns the completion result, 0 on
|
||||||
|
/// success or a negative errno on the first failing action.
|
||||||
|
pub(super) fn deallocate_range_result(
|
||||||
|
metadata: &QcowMetadata,
|
||||||
|
data_file: &mut QcowRawFile,
|
||||||
|
offset: u64,
|
||||||
|
length: usize,
|
||||||
|
sparse: bool,
|
||||||
|
write_zeroes: bool,
|
||||||
|
backing_file: Option<&dyn BackingRead>,
|
||||||
|
) -> i32 {
|
||||||
|
let wrap_error: fn(io::Error) -> AsyncIoError = if write_zeroes {
|
||||||
|
AsyncIoError::WriteZeroes
|
||||||
|
} else {
|
||||||
|
AsyncIoError::PunchHole
|
||||||
|
};
|
||||||
|
let result = metadata
|
||||||
|
.deallocate_bytes(offset, length, sparse, write_zeroes, backing_file)
|
||||||
|
.and_then(|actions| {
|
||||||
|
let mut first_error = None;
|
||||||
|
for action in &actions {
|
||||||
|
if let Err(e) = apply_dealloc_action(metadata, data_file, action) {
|
||||||
|
first_error.get_or_insert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first_error.map_or(Ok(()), Err)
|
||||||
|
})
|
||||||
|
.map_err(wrap_error);
|
||||||
|
match result {
|
||||||
|
Ok(()) => 0,
|
||||||
|
Err(AsyncIoError::PunchHole(e) | AsyncIoError::WriteZeroes(e)) => {
|
||||||
|
-e.raw_os_error().unwrap_or(libc::EIO)
|
||||||
|
}
|
||||||
|
Err(_) => -libc::EIO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod unit_tests {
|
pub(crate) mod unit_tests {
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|||||||
@@ -5,18 +5,14 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::io;
|
|
||||||
use std::os::unix::fs::FileExt;
|
use std::os::unix::fs::FileExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
|
||||||
|
|
||||||
use super::common::decompress_cluster;
|
use super::common::{deallocate_range_result, decompress_cluster};
|
||||||
use super::decoder::Decoder;
|
use super::decoder::Decoder;
|
||||||
use super::metadata::{
|
use super::metadata::{BackingRead, ClusterReadMapping, ClusterWriteMapping, QcowMetadata};
|
||||||
BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata,
|
|
||||||
};
|
|
||||||
use super::qcow_raw_file::QcowRawFile;
|
use super::qcow_raw_file::QcowRawFile;
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, CompletionCommon,
|
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, CompletionCommon,
|
||||||
@@ -51,29 +47,6 @@ impl QcowSync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_dealloc_action(&mut self, action: &DeallocAction) -> io::Result<()> {
|
|
||||||
match action {
|
|
||||||
DeallocAction::PunchHole {
|
|
||||||
host_offset,
|
|
||||||
length,
|
|
||||||
} => {
|
|
||||||
self.data_file
|
|
||||||
.file_mut()
|
|
||||||
.punch_hole(*host_offset, *length)?;
|
|
||||||
self.metadata.complete_punch_hole(*host_offset);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
DeallocAction::WriteZeroes {
|
|
||||||
host_offset,
|
|
||||||
length,
|
|
||||||
} => self
|
|
||||||
.data_file
|
|
||||||
.file_mut()
|
|
||||||
.write_zeroes_at(*host_offset, *length)
|
|
||||||
.map(|_| ()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_operation(&mut self, op: &mut AsyncIoOperation) -> AsyncIoResult<usize> {
|
fn read_operation(&mut self, op: &mut AsyncIoOperation) -> AsyncIoResult<usize> {
|
||||||
let address = op.offset() as u64;
|
let address = op.offset() as u64;
|
||||||
let total_len = op.total_len();
|
let total_len = op.total_len();
|
||||||
@@ -231,83 +204,33 @@ impl AsyncIo for QcowSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||||
let result = self
|
let result = deallocate_range_result(
|
||||||
.metadata
|
&self.metadata,
|
||||||
.deallocate_bytes(
|
&mut self.data_file,
|
||||||
offset,
|
offset,
|
||||||
length as usize,
|
length as usize,
|
||||||
self.sparse,
|
self.sparse,
|
||||||
false,
|
false,
|
||||||
self.backing_file.as_deref(),
|
self.backing_file.as_deref(),
|
||||||
)
|
);
|
||||||
.and_then(|actions| {
|
self.completions
|
||||||
let mut first_error = None;
|
.complete(AsyncIoCompletion::new(user_data, result, None));
|
||||||
for action in &actions {
|
Ok(())
|
||||||
if let Err(e) = self.apply_dealloc_action(action) {
|
|
||||||
first_error.get_or_insert(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first_error.map_or(Ok(()), Err)
|
|
||||||
})
|
|
||||||
.map_err(AsyncIoError::PunchHole);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(()) => {
|
|
||||||
self.completions
|
|
||||||
.complete(AsyncIoCompletion::new(user_data, 0, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let errno = if let AsyncIoError::PunchHole(ref io_err) = e {
|
|
||||||
-io_err.raw_os_error().unwrap_or(libc::EIO)
|
|
||||||
} else {
|
|
||||||
-libc::EIO
|
|
||||||
};
|
|
||||||
self.completions
|
|
||||||
.complete(AsyncIoCompletion::new(user_data, errno, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||||
let result = self
|
let result = deallocate_range_result(
|
||||||
.metadata
|
&self.metadata,
|
||||||
.deallocate_bytes(
|
&mut self.data_file,
|
||||||
offset,
|
offset,
|
||||||
length as usize,
|
length as usize,
|
||||||
self.sparse,
|
self.sparse,
|
||||||
true,
|
true,
|
||||||
self.backing_file.as_deref(),
|
self.backing_file.as_deref(),
|
||||||
)
|
);
|
||||||
.and_then(|actions| {
|
self.completions
|
||||||
let mut first_error = None;
|
.complete(AsyncIoCompletion::new(user_data, result, None));
|
||||||
for action in &actions {
|
Ok(())
|
||||||
if let Err(e) = self.apply_dealloc_action(action) {
|
|
||||||
first_error.get_or_insert(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first_error.map_or(Ok(()), Err)
|
|
||||||
})
|
|
||||||
.map_err(AsyncIoError::WriteZeroes);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(()) => {
|
|
||||||
self.completions
|
|
||||||
.complete(AsyncIoCompletion::new(user_data, 0, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let errno = if let AsyncIoError::WriteZeroes(ref io_err) = e {
|
|
||||||
-io_err.raw_os_error().unwrap_or(libc::EIO)
|
|
||||||
} else {
|
|
||||||
-libc::EIO
|
|
||||||
};
|
|
||||||
self.completions
|
|
||||||
.complete(AsyncIoCompletion::new(user_data, errno, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +252,9 @@ mod unit_tests {
|
|||||||
use crate::disk_file::{AsyncDiskFile, DiskSize, MetadataSync, Resizable};
|
use crate::disk_file::{AsyncDiskFile, DiskSize, MetadataSync, Resizable};
|
||||||
use crate::error::BlockErrorKind;
|
use crate::error::BlockErrorKind;
|
||||||
use crate::formats::qcow;
|
use crate::formats::qcow;
|
||||||
|
use crate::formats::qcow::common::apply_dealloc_action;
|
||||||
use crate::formats::qcow::common::unit_tests::compress_allocated_clusters;
|
use crate::formats::qcow::common::unit_tests::compress_allocated_clusters;
|
||||||
|
use crate::formats::qcow::metadata::DeallocAction;
|
||||||
use crate::formats::qcow::{
|
use crate::formats::qcow::{
|
||||||
BackingFileConfig, Error as QcowError, ImageType, QcowDisk, QcowHeader, QcowTempDisk,
|
BackingFileConfig, Error as QcowError, ImageType, QcowDisk, QcowHeader, QcowTempDisk,
|
||||||
};
|
};
|
||||||
@@ -2311,7 +2236,7 @@ mod unit_tests {
|
|||||||
|
|
||||||
// Queue A resumes. Completing the old action must not touch the new
|
// Queue A resumes. Completing the old action must not touch the new
|
||||||
// L2, and only now may the old data cluster enter unref_clusters.
|
// L2, and only now may the old data cluster enter unref_clusters.
|
||||||
aio.apply_dealloc_action(&actions[0]).unwrap();
|
apply_dealloc_action(&aio.metadata, &mut aio.data_file, &actions[0]).unwrap();
|
||||||
metadata.flush().unwrap();
|
metadata.flush().unwrap();
|
||||||
drop(aio);
|
drop(aio);
|
||||||
drop(metadata);
|
drop(metadata);
|
||||||
|
|||||||
@@ -15,13 +15,10 @@ use std::os::unix::io::AsRawFd;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
|
||||||
|
|
||||||
use super::common::decompress_cluster;
|
use super::common::{deallocate_range_result, decompress_cluster};
|
||||||
use super::decoder::Decoder;
|
use super::decoder::Decoder;
|
||||||
use super::metadata::{
|
use super::metadata::{BackingRead, ClusterReadMapping, ClusterWriteMapping, QcowMetadata};
|
||||||
BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata,
|
|
||||||
};
|
|
||||||
use super::qcow_raw_file::QcowRawFile;
|
use super::qcow_raw_file::QcowRawFile;
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, UringDataIo,
|
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, UringDataIo,
|
||||||
@@ -66,29 +63,6 @@ impl QcowAsync {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_dealloc_action(&mut self, action: &DeallocAction) -> io::Result<()> {
|
|
||||||
match action {
|
|
||||||
DeallocAction::PunchHole {
|
|
||||||
host_offset,
|
|
||||||
length,
|
|
||||||
} => {
|
|
||||||
self.data_file
|
|
||||||
.file_mut()
|
|
||||||
.punch_hole(*host_offset, *length)?;
|
|
||||||
self.metadata.complete_punch_hole(*host_offset);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
DeallocAction::WriteZeroes {
|
|
||||||
host_offset,
|
|
||||||
length,
|
|
||||||
} => self
|
|
||||||
.data_file
|
|
||||||
.file_mut()
|
|
||||||
.write_zeroes_at(*host_offset, *length)
|
|
||||||
.map(|_| ()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn async_error_result(error: &AsyncIoError) -> i32 {
|
fn async_error_result(error: &AsyncIoError) -> i32 {
|
||||||
let io_error = match error {
|
let io_error = match error {
|
||||||
AsyncIoError::ReadVectored(e)
|
AsyncIoError::ReadVectored(e)
|
||||||
@@ -196,83 +170,33 @@ impl AsyncIo for QcowAsync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||||
let result = self
|
let result = deallocate_range_result(
|
||||||
.metadata
|
&self.metadata,
|
||||||
.deallocate_bytes(
|
&mut self.data_file,
|
||||||
offset,
|
offset,
|
||||||
length as usize,
|
length as usize,
|
||||||
self.sparse,
|
self.sparse,
|
||||||
false,
|
false,
|
||||||
self.backing_file.as_deref(),
|
self.backing_file.as_deref(),
|
||||||
)
|
);
|
||||||
.and_then(|actions| {
|
self.data_io
|
||||||
let mut first_error = None;
|
.inject_completion(AsyncIoCompletion::new(user_data, result, None));
|
||||||
for action in &actions {
|
Ok(())
|
||||||
if let Err(e) = self.apply_dealloc_action(action) {
|
|
||||||
first_error.get_or_insert(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first_error.map_or(Ok(()), Err)
|
|
||||||
})
|
|
||||||
.map_err(AsyncIoError::PunchHole);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(()) => {
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::new(user_data, 0, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let errno = if let AsyncIoError::PunchHole(ref io_err) = e {
|
|
||||||
-io_err.raw_os_error().unwrap_or(libc::EIO)
|
|
||||||
} else {
|
|
||||||
-libc::EIO
|
|
||||||
};
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::new(user_data, errno, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||||
let result = self
|
let result = deallocate_range_result(
|
||||||
.metadata
|
&self.metadata,
|
||||||
.deallocate_bytes(
|
&mut self.data_file,
|
||||||
offset,
|
offset,
|
||||||
length as usize,
|
length as usize,
|
||||||
self.sparse,
|
self.sparse,
|
||||||
true,
|
true,
|
||||||
self.backing_file.as_deref(),
|
self.backing_file.as_deref(),
|
||||||
)
|
);
|
||||||
.and_then(|actions| {
|
self.data_io
|
||||||
let mut first_error = None;
|
.inject_completion(AsyncIoCompletion::new(user_data, result, None));
|
||||||
for action in &actions {
|
Ok(())
|
||||||
if let Err(e) = self.apply_dealloc_action(action) {
|
|
||||||
first_error.get_or_insert(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first_error.map_or(Ok(()), Err)
|
|
||||||
})
|
|
||||||
.map_err(AsyncIoError::WriteZeroes);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(()) => {
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::new(user_data, 0, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let errno = if let AsyncIoError::WriteZeroes(ref io_err) = e {
|
|
||||||
-io_err.raw_os_error().unwrap_or(libc::EIO)
|
|
||||||
} else {
|
|
||||||
-libc::EIO
|
|
||||||
};
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::new(user_data, errno, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn batch_requests_enabled(&self) -> bool {
|
fn batch_requests_enabled(&self) -> bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user