// 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()) } }