block: Implement write_zeroes and punch_hole for AIO backend

The AIO block backend advertises VIRTIO_BLK_F_WRITE_ZEROES
and VIRTIO_BLK_F_DISCARD to guests because the filesystem
probe (supports_sparse_operations) returns true on ext4/XFS.
However, RawFileAsyncAio::write_zeroes() and punch_hole()
return errors because Linux AIO (io_submit) has no IOCB
command for fallocate.

When io_uring is unavailable (e.g. io_uring_disabled=2, a
common security hardening on enterprise Linux), Cloud
Hypervisor falls back to the AIO backend. The guest
negotiates the feature, issues WRITE_ZEROES requests, and
gets I/O errors.

Implement write_zeroes and punch_hole using synchronous
libc::fallocate() calls, matching the pattern used by the
sync backend (RawFileSync). A VecDeque-based completion
list signals results to the caller via the existing eventfd
mechanism.

Unit tests mirror the existing raw_sync.rs test suite.
Integration tests add AIO-specific variants of the discard
and fstrim tests using _disable_io_uring=on.

Signed-off-by: Emir Beganovic <beganovic.emir@gmail.com>
This commit is contained in:
Emir Beganovic
2026-03-10 13:02:21 +01:00
committed by Rob Bradford
parent 8671e193ff
commit 623af62743
2 changed files with 292 additions and 12 deletions

View File

@@ -7532,6 +7532,24 @@ mod common_parallel {
extra_create_args: &[&str],
expect_discard_success: bool,
verify_disk: bool,
) {
_test_virtio_block_discard_with_backend(
format_name,
qemu_img_format,
extra_create_args,
expect_discard_success,
verify_disk,
false,
);
}
fn _test_virtio_block_discard_with_backend(
format_name: &str,
qemu_img_format: &str,
extra_create_args: &[&str],
expect_discard_success: bool,
verify_disk: bool,
disable_io_uring: bool,
) {
let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string());
let guest = Guest::new(Box::new(disk_config));
@@ -7574,9 +7592,14 @@ mod common_parallel {
)
.as_str(),
format!(
"path={},num_queues=4,image_type={}",
"path={},num_queues=4,image_type={}{}",
test_disk_path.to_str().unwrap(),
format_name.to_lowercase()
format_name.to_lowercase(),
if disable_io_uring {
",_disable_io_uring=on"
} else {
""
}
)
.as_str(),
])
@@ -7754,6 +7777,11 @@ mod common_parallel {
_test_virtio_block_discard("raw", "raw", &[], true, false);
}
#[test]
fn test_virtio_block_discard_raw_aio() {
_test_virtio_block_discard_with_backend("raw", "raw", &[], true, false, true);
}
#[test]
fn test_virtio_block_discard_unsupported_vhd() {
_test_virtio_block_discard("vhd", "vpc", &["-o", "subformat=fixed"], false, false);
@@ -8056,6 +8084,24 @@ mod common_parallel {
extra_create_args: &[&str],
expect_fstrim_success: bool,
verify_disk: bool,
) {
_test_virtio_block_fstrim_with_backend(
format_name,
qemu_img_format,
extra_create_args,
expect_fstrim_success,
verify_disk,
false,
);
}
fn _test_virtio_block_fstrim_with_backend(
format_name: &str,
qemu_img_format: &str,
extra_create_args: &[&str],
expect_fstrim_success: bool,
verify_disk: bool,
disable_io_uring: bool,
) {
let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string());
let guest = Guest::new(Box::new(disk_config));
@@ -8101,9 +8147,14 @@ mod common_parallel {
)
.as_str(),
format!(
"path={},num_queues=4,image_type={}",
"path={},num_queues=4,image_type={}{}",
test_disk_path.to_str().unwrap(),
format_name.to_lowercase()
format_name.to_lowercase(),
if disable_io_uring {
",_disable_io_uring=on"
} else {
""
}
)
.as_str(),
])
@@ -8242,6 +8293,11 @@ mod common_parallel {
_test_virtio_block_fstrim("raw", "raw", &[], true, false);
}
#[test]
fn test_virtio_block_fstrim_raw_aio() {
_test_virtio_block_fstrim_with_backend("raw", "raw", &[], true, false, true);
}
#[test]
fn test_virtio_block_fstrim_unsupported_vhd() {
_test_virtio_block_fstrim("vhd", "vpc", &["-o", "subformat=fixed"], false, false);