mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: Fall back to write when fallocate returns EOPNOTSUPP
Filesystems such as tmpfs do not support fallocate with FALLOC_FL_ZERO_RANGE or FALLOC_FL_PUNCH_HOLE and return EOPNOTSUPP. When a raw disk image lives on such a filesystem, virtio write zeroes and discard requests fail with IOERR. Use the WriteZeroesAt trait from vmm_sys_util through AlignedFile, which already bundles fallocate with a positional write fallback. For punch_hole, catch EOPNOTSUPP and fall back to the same trait. The io_uring engine previously submitted fallocate directly through the ring, where the async EOPNOTSUPP completion had no retry path. Route it through the same sync helpers that the other engines already use. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
committed by
Rob Bradford
parent
e37f63282c
commit
d7a7d73622
@@ -94,13 +94,8 @@ impl AsyncIo for RawAio {
|
|||||||
// Linux AIO has no IOCB command for fallocate, so perform the
|
// Linux AIO has no IOCB command for fallocate, so perform the
|
||||||
// operation synchronously and signal completion via the completion
|
// operation synchronously and signal completion via the completion
|
||||||
// list, matching the pattern used by the sync backend (RawSync).
|
// list, matching the pattern used by the sync backend (RawSync).
|
||||||
punch_hole(
|
punch_hole(&mut self.raw_file, self.is_block_device, offset, length)
|
||||||
self.raw_file.as_raw_fd(),
|
.map_err(AsyncIoError::PunchHole)?;
|
||||||
self.is_block_device,
|
|
||||||
offset,
|
|
||||||
length,
|
|
||||||
)
|
|
||||||
.map_err(AsyncIoError::PunchHole)?;
|
|
||||||
self.data_io
|
self.data_io
|
||||||
.inject_completion(AsyncIoCompletion::new(user_data, 0, None));
|
.inject_completion(AsyncIoCompletion::new(user_data, 0, None));
|
||||||
|
|
||||||
@@ -109,13 +104,8 @@ impl AsyncIo for RawAio {
|
|||||||
|
|
||||||
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<()> {
|
||||||
// Same as punch_hole().
|
// Same as punch_hole().
|
||||||
write_zeroes(
|
write_zeroes(&mut self.raw_file, self.is_block_device, offset, length)
|
||||||
self.raw_file.as_raw_fd(),
|
.map_err(AsyncIoError::WriteZeroes)?;
|
||||||
self.is_block_device,
|
|
||||||
offset,
|
|
||||||
length,
|
|
||||||
)
|
|
||||||
.map_err(AsyncIoError::WriteZeroes)?;
|
|
||||||
self.data_io
|
self.data_io
|
||||||
.inject_completion(AsyncIoCompletion::new(user_data, 0, None));
|
.inject_completion(AsyncIoCompletion::new(user_data, 0, None));
|
||||||
|
|
||||||
|
|||||||
@@ -120,13 +120,8 @@ impl AsyncIo for RawSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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<()> {
|
||||||
punch_hole(
|
punch_hole(&mut self.raw_file, self.is_block_device, offset, length)
|
||||||
self.raw_file.as_raw_fd(),
|
.map_err(AsyncIoError::PunchHole)?;
|
||||||
self.is_block_device,
|
|
||||||
offset,
|
|
||||||
length,
|
|
||||||
)
|
|
||||||
.map_err(AsyncIoError::PunchHole)?;
|
|
||||||
self.completion_list
|
self.completion_list
|
||||||
.push_back(AsyncIoCompletion::new(user_data, 0, None));
|
.push_back(AsyncIoCompletion::new(user_data, 0, None));
|
||||||
self.eventfd.write(1).unwrap();
|
self.eventfd.write(1).unwrap();
|
||||||
@@ -134,13 +129,8 @@ impl AsyncIo for RawSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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<()> {
|
||||||
write_zeroes(
|
write_zeroes(&mut self.raw_file, self.is_block_device, offset, length)
|
||||||
self.raw_file.as_raw_fd(),
|
.map_err(AsyncIoError::WriteZeroes)?;
|
||||||
self.is_block_device,
|
|
||||||
offset,
|
|
||||||
length,
|
|
||||||
)
|
|
||||||
.map_err(AsyncIoError::WriteZeroes)?;
|
|
||||||
self.completion_list
|
self.completion_list
|
||||||
.push_back(AsyncIoCompletion::new(user_data, 0, None));
|
.push_back(AsyncIoCompletion::new(user_data, 0, None));
|
||||||
self.eventfd.write(1).unwrap();
|
self.eventfd.write(1).unwrap();
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|
||||||
use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE};
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use super::{operation_is_aligned, run_unaligned_operation};
|
use super::{operation_is_aligned, run_unaligned_operation};
|
||||||
@@ -14,7 +13,7 @@ use crate::async_io::{
|
|||||||
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, UringDataIo,
|
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, UringDataIo,
|
||||||
};
|
};
|
||||||
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
||||||
use crate::sparse::{blkdiscard, blkzeroout};
|
use crate::sparse::{punch_hole, write_zeroes};
|
||||||
use crate::{AlignedFile, is_block_device};
|
use crate::{AlignedFile, is_block_device};
|
||||||
|
|
||||||
pub(crate) struct RawAsync {
|
pub(crate) struct RawAsync {
|
||||||
@@ -119,44 +118,27 @@ impl AsyncIo for RawAsync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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<()> {
|
||||||
// Some block devices don't support fallocate(). Use ioctl instead. The assumption is that
|
// Run synchronously rather than submitting a fallocate request through
|
||||||
// this happens rarely and we don't need to introduce unnecessary complexity by submitting
|
// the ring. This avoids reaping ENOTSUPP in the completion routine and
|
||||||
// a fallocate request, reaping ENOTSUPP in the completion routine, and reissuing the
|
// reissuing the request, and lets the sparse helper handle the ioctl
|
||||||
// request with an ioctl.
|
// path for block devices and the write fallback for unsupported
|
||||||
if self.is_block_device {
|
// filesystems.
|
||||||
blkdiscard(self.raw_file.as_raw_fd(), offset, length)
|
punch_hole(&mut self.raw_file, self.is_block_device, offset, length)
|
||||||
.map_err(AsyncIoError::PunchHole)?;
|
.map_err(AsyncIoError::PunchHole)?;
|
||||||
// Deliver the completion through the normal io_uring path by
|
// Deliver the completion through the normal io_uring path by
|
||||||
// queuing a NOP carrying `user_data`. The registered eventfd will
|
// queuing a NOP carrying `user_data`. The registered eventfd will
|
||||||
// fire when it completes, just like any other request.
|
// fire when it completes, just like any other request.
|
||||||
return self
|
|
||||||
.data_io
|
|
||||||
.submit_nop(user_data)
|
|
||||||
.map_err(AsyncIoError::PunchHole);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
|
|
||||||
|
|
||||||
self.data_io
|
self.data_io
|
||||||
.submit_fallocate(self.raw_file.as_raw_fd(), offset, length, mode, user_data)
|
.submit_nop(user_data)
|
||||||
.map_err(AsyncIoError::PunchHole)
|
.map_err(AsyncIoError::PunchHole)
|
||||||
}
|
}
|
||||||
|
|
||||||
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<()> {
|
||||||
// Same rationale as punch_hole().
|
// Same rationale as punch_hole().
|
||||||
if self.is_block_device {
|
write_zeroes(&mut self.raw_file, self.is_block_device, offset, length)
|
||||||
blkzeroout(self.raw_file.as_raw_fd(), offset, length)
|
.map_err(AsyncIoError::WriteZeroes)?;
|
||||||
.map_err(AsyncIoError::WriteZeroes)?;
|
|
||||||
return self
|
|
||||||
.data_io
|
|
||||||
.submit_nop(user_data)
|
|
||||||
.map_err(AsyncIoError::WriteZeroes);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE;
|
|
||||||
|
|
||||||
self.data_io
|
self.data_io
|
||||||
.submit_fallocate(self.raw_file.as_raw_fd(), offset, length, mode, user_data)
|
.submit_nop(user_data)
|
||||||
.map_err(AsyncIoError::WriteZeroes)
|
.map_err(AsyncIoError::WriteZeroes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,25 +211,6 @@ impl UringDataIo {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Submits a fallocate operation carrying `user_data`.
|
|
||||||
pub fn submit_fallocate(
|
|
||||||
&mut self,
|
|
||||||
fd: RawFd,
|
|
||||||
offset: u64,
|
|
||||||
length: u64,
|
|
||||||
mode: i32,
|
|
||||||
user_data: u64,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
self.submit_kernel_entry(
|
|
||||||
user_data,
|
|
||||||
&opcode::Fallocate::new(types::Fd(fd), length)
|
|
||||||
.offset(offset)
|
|
||||||
.mode(mode)
|
|
||||||
.build()
|
|
||||||
.user_data(user_data),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Injects a completion that did not come from a kernel CQE.
|
/// Injects a completion that did not come from a kernel CQE.
|
||||||
///
|
///
|
||||||
/// The notifier is signaled so callers can drain it with
|
/// The notifier is signaled so callers can drain it with
|
||||||
@@ -360,13 +341,6 @@ mod tests {
|
|||||||
data_io.submit_nop(7).unwrap_err().kind(),
|
data_io.submit_nop(7).unwrap_err().kind(),
|
||||||
io::ErrorKind::AlreadyExists
|
io::ErrorKind::AlreadyExists
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
data_io
|
|
||||||
.submit_fallocate(fd, 0, 512, 0, 7)
|
|
||||||
.unwrap_err()
|
|
||||||
.kind(),
|
|
||||||
io::ErrorKind::AlreadyExists
|
|
||||||
);
|
|
||||||
|
|
||||||
let completion = wait_for_completion(&mut data_io);
|
let completion = wait_for_completion(&mut data_io);
|
||||||
assert_eq!(completion.user_data, 7);
|
assert_eq!(completion.user_data, 7);
|
||||||
|
|||||||
+35
-32
@@ -19,9 +19,11 @@
|
|||||||
// `__u64[2]` array rather than two separate `*const u64` pointers.
|
// `__u64[2]` array rather than two separate `*const u64` pointers.
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE};
|
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
||||||
|
|
||||||
|
use crate::AlignedFile;
|
||||||
|
|
||||||
// `_IO(0x12, 119)` — issue a discard request to a block device.
|
// `_IO(0x12, 119)` — issue a discard request to a block device.
|
||||||
pub const BLKDISCARD: libc::c_ulong = 0x1277;
|
pub const BLKDISCARD: libc::c_ulong = 0x1277;
|
||||||
@@ -61,44 +63,45 @@ pub(crate) fn blkzeroout(fd: RawFd, offset: u64, length: u64) -> io::Result<()>
|
|||||||
//
|
//
|
||||||
// On block devices the kernel rejects `fallocate(PUNCH_HOLE)` (notably ZFS
|
// On block devices the kernel rejects `fallocate(PUNCH_HOLE)` (notably ZFS
|
||||||
// zvols), so route through `BLKDISCARD` instead. On regular files use
|
// zvols), so route through `BLKDISCARD` instead. On regular files use
|
||||||
// `fallocate(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE)`.
|
// `PunchHole`, falling back to `WriteZeroesAt` on EOPNOTSUPP (e.g. tmpfs).
|
||||||
pub(crate) fn punch_hole(fd: RawFd, is_blkdev: bool, offset: u64, length: u64) -> io::Result<()> {
|
pub(crate) fn punch_hole(
|
||||||
|
file: &mut AlignedFile,
|
||||||
|
is_blkdev: bool,
|
||||||
|
offset: u64,
|
||||||
|
length: u64,
|
||||||
|
) -> io::Result<()> {
|
||||||
if is_blkdev {
|
if is_blkdev {
|
||||||
blkdiscard(fd, offset, length)
|
return match blkdiscard(file.as_raw_fd(), offset, length) {
|
||||||
} else {
|
Ok(()) => Ok(()),
|
||||||
fallocate(
|
Err(e) if e.raw_os_error() == Some(libc::EOPNOTSUPP) => Ok(()),
|
||||||
fd,
|
Err(e) => Err(e),
|
||||||
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
|
};
|
||||||
offset,
|
}
|
||||||
length,
|
match file.punch_hole(offset, length) {
|
||||||
)
|
Ok(()) => Ok(()),
|
||||||
|
Err(e) if e.raw_os_error() == Some(libc::EOPNOTSUPP) => {
|
||||||
|
file.write_all_zeroes_at(offset, length as usize)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zero the byte range `[offset, offset + length)` in `fd`.
|
// Zero the byte range `[offset, offset + length)` in `fd`.
|
||||||
//
|
//
|
||||||
// Uses `BLKZEROOUT` on block devices (see [`punch_hole`] for the rationale)
|
// Uses `BLKZEROOUT` on block devices (see [`punch_hole`] for the rationale)
|
||||||
// and `fallocate(FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE)` on regular
|
// and `WriteZeroesAt` on regular files, which tries fallocate and falls
|
||||||
// files.
|
// back to positional writes on EOPNOTSUPP (e.g. tmpfs).
|
||||||
pub(crate) fn write_zeroes(fd: RawFd, is_blkdev: bool, offset: u64, length: u64) -> io::Result<()> {
|
pub(crate) fn write_zeroes(
|
||||||
|
file: &mut AlignedFile,
|
||||||
|
is_blkdev: bool,
|
||||||
|
offset: u64,
|
||||||
|
length: u64,
|
||||||
|
) -> io::Result<()> {
|
||||||
if is_blkdev {
|
if is_blkdev {
|
||||||
blkzeroout(fd, offset, length)
|
return blkzeroout(file.as_raw_fd(), offset, length);
|
||||||
} else {
|
|
||||||
fallocate(
|
|
||||||
fd,
|
|
||||||
FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
|
|
||||||
offset,
|
|
||||||
length,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
file.write_all_zeroes_at(offset, length as usize)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user