mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Some block devices (ZFS volume) may require BLKDISCARD and BLKZEROOUT ioctls for discard and write_zeroes operations respectively. There is no good way to probe whether fallocate is supported on a block device. Arguably, punch_hole and write_zeroes are rare. Instead of having a complex scheme for the IO uring backend, we force it to always use ioctls. The code can be changed if the synchronized ioctls become a performance issue. Changes: - Detect block devices at construction time - Use BLKDISCARD ioctl for punch_hole (discard) on block devices - Use BLKZEROOUT ioctl for write_zeroes on block devices - Add BLKDISCARD/BLKZEROOUT to VirtioBlock seccomp whitelist - Keep fallocate() path for regular files (no behavior change) - Consolidate some helper functions to the new sparse module Signed-off-by: Wei Liu <liuwe@microsoft.com>
105 lines
3.8 KiB
Rust
105 lines
3.8 KiB
Rust
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
|
|
// Helpers for issuing `BLKDISCARD` / `BLKZEROOUT` ioctls on block devices,
|
|
// and the `punch_hole` / `write_zeroes` dispatchers used by the raw I/O
|
|
// backends.
|
|
//
|
|
// The kernel ioctl numbers and argument layout are stable userspace ABI
|
|
// (see `include/uapi/linux/fs.h`):
|
|
//
|
|
// ```c
|
|
// #define BLKDISCARD _IO(0x12, 119) /* arg: const __u64 range[2] = { start, len } */
|
|
// #define BLKZEROOUT _IO(0x12, 127) /* arg: const __u64 range[2] = { start, len } */
|
|
// ```
|
|
//
|
|
// The kernel does `copy_from_user(range, arg, sizeof(range))`, i.e. it reads
|
|
// 16 bytes through the single pointer it is given, so we must pass a single
|
|
// `__u64[2]` array rather than two separate `*const u64` pointers.
|
|
|
|
use std::io;
|
|
use std::os::unix::io::RawFd;
|
|
|
|
use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE};
|
|
|
|
// `_IO(0x12, 119)` — issue a discard request to a block device.
|
|
pub const BLKDISCARD: libc::c_ulong = 0x1277;
|
|
// `_IO(0x12, 127)` — write zeroes to a range of a block device, with a
|
|
// kernel-side fallback to writing zero pages when the hardware has no native
|
|
// `WRITE_ZEROES`.
|
|
pub const BLKZEROOUT: libc::c_ulong = 0x127f;
|
|
|
|
// Issue a `BLK*` range ioctl with proper `[start, len]` argument.
|
|
fn blk_range_ioctl(fd: RawFd, request: libc::c_ulong, offset: u64, length: u64) -> io::Result<()> {
|
|
let range: [u64; 2] = [offset, length];
|
|
// SAFETY: `fd` is a valid block-device fd owned by the caller; `&range`
|
|
// is a 16-byte array matching the kernel's expected `__u64[2]` layout
|
|
// and lives for the duration of the call.
|
|
let ret = unsafe { libc::ioctl(fd, request as _, &range) };
|
|
if ret == 0 {
|
|
Ok(())
|
|
} else {
|
|
Err(io::Error::last_os_error())
|
|
}
|
|
}
|
|
|
|
// Discard (TRIM/UNMAP) the byte range `[offset, offset + length)` on the
|
|
// block device referenced by `fd`.
|
|
pub(crate) fn blkdiscard(fd: RawFd, offset: u64, length: u64) -> io::Result<()> {
|
|
blk_range_ioctl(fd, BLKDISCARD, offset, length)
|
|
}
|
|
|
|
// Zero the byte range `[offset, offset + length)` on the block device
|
|
// referenced by `fd`. The kernel falls back to writing explicit zero pages
|
|
// when the device has no hardware `WRITE_ZEROES`.
|
|
pub(crate) fn blkzeroout(fd: RawFd, offset: u64, length: u64) -> io::Result<()> {
|
|
blk_range_ioctl(fd, BLKZEROOUT, offset, length)
|
|
}
|
|
|
|
// Punch a hole in `fd` over the byte range `[offset, offset + length)`.
|
|
//
|
|
// On block devices the kernel rejects `fallocate(PUNCH_HOLE)` (notably ZFS
|
|
// zvols), so route through `BLKDISCARD` instead. On regular files use
|
|
// `fallocate(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE)`.
|
|
pub(crate) fn punch_hole(fd: RawFd, is_blkdev: bool, offset: u64, length: u64) -> io::Result<()> {
|
|
if is_blkdev {
|
|
blkdiscard(fd, offset, length)
|
|
} else {
|
|
fallocate(
|
|
fd,
|
|
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
|
|
offset,
|
|
length,
|
|
)
|
|
}
|
|
}
|
|
|
|
// Zero the byte range `[offset, offset + length)` in `fd`.
|
|
//
|
|
// Uses `BLKZEROOUT` on block devices (see [`punch_hole`] for the rationale)
|
|
// and `fallocate(FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE)` on regular
|
|
// files.
|
|
pub(crate) fn write_zeroes(fd: RawFd, is_blkdev: bool, offset: u64, length: u64) -> io::Result<()> {
|
|
if is_blkdev {
|
|
blkzeroout(fd, offset, length)
|
|
} else {
|
|
fallocate(
|
|
fd,
|
|
FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
|
|
offset,
|
|
length,
|
|
)
|
|
}
|
|
}
|
|
|
|
fn fallocate(fd: RawFd, mode: libc::c_int, offset: u64, length: u64) -> io::Result<()> {
|
|
// SAFETY: FFI call with a valid fd; fallocate touches no userspace memory.
|
|
let ret = unsafe { libc::fallocate(fd, mode, offset as libc::off_t, length as libc::off_t) };
|
|
if ret == 0 {
|
|
Ok(())
|
|
} else {
|
|
Err(io::Error::last_os_error())
|
|
}
|
|
}
|