mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Block devices (LVM volumes, loop devices, RBD, etc.) cannot be resized via ftruncate - they are resized externally. When vm.resize-disk is called for a block device backend, verify the device size matches the requested size instead of attempting ftruncate. This enables the resize-disk API to work with block device backends by validating the externally-resized device matches the expected size. Signed-off-by: Vincent Thomas <vincent@v-thomas.com>
365 lines
12 KiB
Rust
365 lines
12 KiB
Rust
// Copyright © 2021 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
|
|
use std::fs::File;
|
|
use std::io::{self, Error};
|
|
use std::os::unix::fs::FileTypeExt;
|
|
use std::os::unix::io::{AsRawFd, RawFd};
|
|
|
|
use io_uring::{IoUring, opcode, types};
|
|
use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE};
|
|
use log::warn;
|
|
use vmm_sys_util::eventfd::EventFd;
|
|
|
|
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError};
|
|
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
|
use crate::{
|
|
BatchRequest, DiskTopology, RequestType, SECTOR_SIZE, disk_file, probe_sparse_support,
|
|
query_device_size,
|
|
};
|
|
|
|
#[derive(Debug)]
|
|
pub struct RawFileDisk {
|
|
file: File,
|
|
}
|
|
|
|
impl RawFileDisk {
|
|
pub fn new(file: File) -> Self {
|
|
RawFileDisk { file }
|
|
}
|
|
}
|
|
|
|
impl disk_file::DiskSize for RawFileDisk {
|
|
fn logical_size(&self) -> BlockResult<u64> {
|
|
query_device_size(&self.file)
|
|
.map(|(logical_size, _)| logical_size)
|
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e)))
|
|
}
|
|
}
|
|
|
|
impl disk_file::PhysicalSize for RawFileDisk {
|
|
fn physical_size(&self) -> BlockResult<u64> {
|
|
query_device_size(&self.file)
|
|
.map(|(_, physical_size)| physical_size)
|
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e)))
|
|
}
|
|
}
|
|
|
|
impl disk_file::DiskFd for RawFileDisk {
|
|
fn fd(&self) -> BorrowedDiskFd<'_> {
|
|
BorrowedDiskFd::new(self.file.as_raw_fd())
|
|
}
|
|
}
|
|
|
|
impl disk_file::Geometry for RawFileDisk {
|
|
fn topology(&self) -> DiskTopology {
|
|
DiskTopology::probe(&self.file).unwrap_or_else(|_| {
|
|
warn!("Unable to get device topology. Using default topology");
|
|
DiskTopology::default()
|
|
})
|
|
}
|
|
}
|
|
|
|
impl disk_file::SparseCapable for RawFileDisk {
|
|
fn supports_sparse_operations(&self) -> bool {
|
|
probe_sparse_support(&self.file)
|
|
}
|
|
}
|
|
|
|
impl disk_file::Resizable for RawFileDisk {
|
|
fn resize(&mut self, size: u64) -> BlockResult<()> {
|
|
let fd_metadata = self
|
|
.file
|
|
.metadata()
|
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e)))?;
|
|
|
|
if fd_metadata.file_type().is_block_device() {
|
|
// Block devices cannot be resized via ftruncate - they are resized
|
|
// externally (LVM, losetup -c, etc.). Verify the size matches.
|
|
let (actual_size, _) = query_device_size(&self.file)
|
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e)))?;
|
|
if actual_size != size {
|
|
return Err(BlockError::new(
|
|
BlockErrorKind::Io,
|
|
DiskFileError::ResizeError(io::Error::other(format!(
|
|
"Block device size {actual_size} does not match requested size {size}"
|
|
))),
|
|
));
|
|
}
|
|
Ok(())
|
|
} else {
|
|
self.file
|
|
.set_len(size)
|
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e)))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl disk_file::DiskFile for RawFileDisk {}
|
|
|
|
impl disk_file::AsyncDiskFile for RawFileDisk {
|
|
fn try_clone(&self) -> BlockResult<Box<dyn disk_file::AsyncDiskFile>> {
|
|
let file = self
|
|
.file
|
|
.try_clone()
|
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?;
|
|
Ok(Box::new(RawFileDisk { file }))
|
|
}
|
|
|
|
fn new_async_io(&self, ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>> {
|
|
let mut raw = RawFileAsync::new(self.file.as_raw_fd(), ring_depth)
|
|
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)))?;
|
|
raw.alignment =
|
|
DiskTopology::probe(&self.file).map_or(SECTOR_SIZE, |t| t.logical_block_size);
|
|
Ok(Box::new(raw) as Box<dyn AsyncIo>)
|
|
}
|
|
}
|
|
|
|
pub struct RawFileAsync {
|
|
fd: RawFd,
|
|
io_uring: IoUring,
|
|
eventfd: EventFd,
|
|
alignment: u64,
|
|
}
|
|
|
|
impl RawFileAsync {
|
|
pub fn new(fd: RawFd, ring_depth: u32) -> std::io::Result<Self> {
|
|
let io_uring = IoUring::new(ring_depth)?;
|
|
let eventfd = EventFd::new(libc::EFD_NONBLOCK)?;
|
|
|
|
// Register the io_uring eventfd that will notify when something in
|
|
// the completion queue is ready.
|
|
io_uring.submitter().register_eventfd(eventfd.as_raw_fd())?;
|
|
|
|
Ok(RawFileAsync {
|
|
fd,
|
|
io_uring,
|
|
eventfd,
|
|
alignment: SECTOR_SIZE,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl AsyncIo for RawFileAsync {
|
|
fn notifier(&self) -> &EventFd {
|
|
&self.eventfd
|
|
}
|
|
|
|
fn alignment(&self) -> u64 {
|
|
self.alignment
|
|
}
|
|
|
|
fn read_vectored(
|
|
&mut self,
|
|
offset: libc::off_t,
|
|
iovecs: &[libc::iovec],
|
|
user_data: u64,
|
|
) -> AsyncIoResult<()> {
|
|
let (submitter, mut sq, _) = self.io_uring.split();
|
|
|
|
// SAFETY: we know the file descriptor is valid and we
|
|
// relied on vm-memory to provide the buffer address.
|
|
unsafe {
|
|
sq.push(
|
|
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
|
.offset(offset.try_into().unwrap())
|
|
.build()
|
|
.user_data(user_data),
|
|
)
|
|
.map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?;
|
|
};
|
|
|
|
// Update the submission queue and submit new operations to the
|
|
// io_uring instance.
|
|
sq.sync();
|
|
submitter.submit().map_err(AsyncIoError::ReadVectored)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn write_vectored(
|
|
&mut self,
|
|
offset: libc::off_t,
|
|
iovecs: &[libc::iovec],
|
|
user_data: u64,
|
|
) -> AsyncIoResult<()> {
|
|
let (submitter, mut sq, _) = self.io_uring.split();
|
|
|
|
// SAFETY: we know the file descriptor is valid and we
|
|
// relied on vm-memory to provide the buffer address.
|
|
unsafe {
|
|
sq.push(
|
|
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
|
.offset(offset.try_into().unwrap())
|
|
.build()
|
|
.user_data(user_data),
|
|
)
|
|
.map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?;
|
|
};
|
|
|
|
// Update the submission queue and submit new operations to the
|
|
// io_uring instance.
|
|
sq.sync();
|
|
submitter.submit().map_err(AsyncIoError::WriteVectored)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
if let Some(user_data) = user_data {
|
|
let (submitter, mut sq, _) = self.io_uring.split();
|
|
|
|
// SAFETY: we know the file descriptor is valid.
|
|
unsafe {
|
|
sq.push(
|
|
&opcode::Fsync::new(types::Fd(self.fd))
|
|
.build()
|
|
.user_data(user_data),
|
|
)
|
|
.map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?;
|
|
};
|
|
|
|
// Update the submission queue and submit new operations to the
|
|
// io_uring instance.
|
|
sq.sync();
|
|
submitter.submit().map_err(AsyncIoError::Fsync)?;
|
|
} else {
|
|
// SAFETY: FFI call with a valid fd
|
|
unsafe { libc::fsync(self.fd) };
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
|
self.io_uring
|
|
.completion()
|
|
.next()
|
|
.map(|entry| (entry.user_data(), entry.result()))
|
|
}
|
|
|
|
fn batch_requests_enabled(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
|
if !self.batch_requests_enabled() {
|
|
return Ok(());
|
|
}
|
|
|
|
let (submitter, mut sq, _) = self.io_uring.split();
|
|
let mut submitted = false;
|
|
|
|
for req in batch_request {
|
|
match req.request_type {
|
|
RequestType::In => {
|
|
// SAFETY: we know the file descriptor is valid and we
|
|
// relied on vm-memory to provide the buffer address.
|
|
unsafe {
|
|
sq.push(
|
|
&opcode::Readv::new(
|
|
types::Fd(self.fd),
|
|
req.iovecs.as_ptr(),
|
|
req.iovecs.len() as u32,
|
|
)
|
|
.offset(req.offset as u64)
|
|
.build()
|
|
.user_data(req.user_data),
|
|
)
|
|
.map_err(|_| {
|
|
AsyncIoError::ReadVectored(Error::other("Submission queue is full"))
|
|
})?;
|
|
};
|
|
submitted = true;
|
|
}
|
|
RequestType::Out => {
|
|
// SAFETY: we know the file descriptor is valid and we
|
|
// relied on vm-memory to provide the buffer address.
|
|
unsafe {
|
|
sq.push(
|
|
&opcode::Writev::new(
|
|
types::Fd(self.fd),
|
|
req.iovecs.as_ptr(),
|
|
req.iovecs.len() as u32,
|
|
)
|
|
.offset(req.offset as u64)
|
|
.build()
|
|
.user_data(req.user_data),
|
|
)
|
|
.map_err(|_| {
|
|
AsyncIoError::WriteVectored(Error::other("Submission queue is full"))
|
|
})?;
|
|
};
|
|
submitted = true;
|
|
}
|
|
_ => {
|
|
unreachable!("Unexpected batch request type: {:?}", req.request_type)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only submit if we actually queued something
|
|
if submitted {
|
|
// Update the submission queue and submit new operations to the
|
|
// io_uring instance.
|
|
sq.sync();
|
|
submitter
|
|
.submit()
|
|
.map_err(AsyncIoError::SubmitBatchRequests)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
|
let (submitter, mut sq, _) = self.io_uring.split();
|
|
|
|
let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
|
|
|
|
// SAFETY: The file descriptor is known to be valid.
|
|
unsafe {
|
|
sq.push(
|
|
&opcode::Fallocate::new(types::Fd(self.fd), length)
|
|
.offset(offset)
|
|
.mode(mode)
|
|
.build()
|
|
.user_data(user_data),
|
|
)
|
|
.map_err(|e| {
|
|
AsyncIoError::PunchHole(Error::other(format!("Submission queue is full: {e:?}")))
|
|
})?;
|
|
};
|
|
|
|
sq.sync();
|
|
submitter.submit().map_err(AsyncIoError::PunchHole)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
|
let (submitter, mut sq, _) = self.io_uring.split();
|
|
|
|
let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE;
|
|
|
|
// SAFETY: The file descriptor is known to be valid.
|
|
unsafe {
|
|
sq.push(
|
|
&opcode::Fallocate::new(types::Fd(self.fd), length)
|
|
.offset(offset)
|
|
.mode(mode)
|
|
.build()
|
|
.user_data(user_data),
|
|
)
|
|
.map_err(|e| {
|
|
AsyncIoError::WriteZeroes(Error::other(format!("Submission queue is full: {e:?}")))
|
|
})?;
|
|
};
|
|
|
|
sq.sync();
|
|
submitter.submit().map_err(AsyncIoError::WriteZeroes)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|