block: Use logical block size for alignment

O_DIRECT requires buffer addresses to be aligned to the backend
device's logical block size. The existing bounce buffer logic in
execute_async() hardcodes SECTOR_SIZE (512) for the alignment check
and bounce buffer allocation. This is insufficient for devices with
a 4096-byte logical block size, where misaligned buffers cause
-EINVAL from the host kernel.

Add an alignment() method to the AsyncIo trait that returns the
backend's logical block size, defaulting to SECTOR_SIZE. The three
raw I/O backends (io_uring, AIO, synchronous) probe the device
topology via DiskTopology::probe() at creation time and return the
actual logical block size. All image format backends would simply
use the default value of 512 bytes since their underlying are
not block devices.

execute_async() now queries disk_image.alignment() instead of using
the hardcoded SECTOR_SIZE

Fixes: #7720

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
This commit is contained in:
Saravanan D
2026-02-18 14:23:03 -08:00
committed by Rob Bradford
parent 272fa624ef
commit 00c05f4761
5 changed files with 55 additions and 20 deletions

View File

@@ -8,7 +8,7 @@ use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use thiserror::Error;
use vmm_sys_util::eventfd::EventFd;
use crate::{BatchRequest, DiskTopology};
use crate::{BatchRequest, DiskTopology, SECTOR_SIZE};
#[derive(Error, Debug)]
pub enum DiskFileError {
@@ -145,4 +145,7 @@ pub trait AsyncIo: Send {
fn submit_batch_requests(&mut self, _batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
Ok(())
}
fn alignment(&self) -> u64 {
SECTOR_SIZE
}
}

View File

@@ -436,6 +436,7 @@ impl Request {
let sector = self.sector;
let request_type = self.request_type;
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
let alignment = disk_image.alignment();
let mut iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
SmallVec::with_capacity(self.data_descriptors.len());
@@ -466,14 +467,15 @@ impl Request {
assert!(origin_ptr.len() >= data_len);
let origin_ptr = origin_ptr.ptr_guard();
// Verify the buffer alignment.
// In case it's not properly aligned, an intermediate buffer is
// created with the correct alignment, and a copy from/to the
// origin buffer is performed, depending on the type of operation.
let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) {
// O_DIRECT requires buffer addresses to be aligned to the
// backend device's logical block size. In case it's not properly
// aligned, an intermediate buffer is created with the correct
// alignment, and a copy from/to the origin buffer is performed,
// depending on the type of operation.
let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(alignment) {
origin_ptr.as_ptr() as *mut libc::c_void
} else {
let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap();
let layout = Layout::from_size_align(data_len, alignment as usize).unwrap();
// SAFETY: layout has non-zero size
let aligned_ptr = unsafe { alloc_zeroed(layout) };
if aligned_ptr.is_null() {

View File

@@ -13,7 +13,7 @@ use vmm_sys_util::eventfd::EventFd;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::{BatchRequest, DiskTopology, RequestType, probe_sparse_support};
use crate::{BatchRequest, DiskTopology, RequestType, SECTOR_SIZE, probe_sparse_support};
pub struct RawFileDisk {
file: File,
@@ -40,10 +40,12 @@ impl DiskFile for RawFileDisk {
}
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
RawFileAsync::new(self.file.as_raw_fd(), ring_depth)
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
let mut raw = RawFileAsync::new(self.file.as_raw_fd(), ring_depth)
.map_err(DiskFileError::NewAsyncIo)?;
raw.alignment = DiskTopology::probe(&self.file)
.map(|t| t.logical_block_size)
.unwrap_or(SECTOR_SIZE);
Ok(Box::new(raw) as Box<dyn AsyncIo>)
}
fn topology(&mut self) -> DiskTopology {
@@ -72,6 +74,7 @@ pub struct RawFileAsync {
fd: RawFd,
io_uring: IoUring,
eventfd: EventFd,
alignment: u64,
}
impl RawFileAsync {
@@ -87,6 +90,7 @@ impl RawFileAsync {
fd,
io_uring,
eventfd,
alignment: SECTOR_SIZE,
})
}
}
@@ -96,6 +100,10 @@ impl AsyncIo for RawFileAsync {
&self.eventfd
}
fn alignment(&self) -> u64 {
self.alignment
}
fn read_vectored(
&mut self,
offset: libc::off_t,

View File

@@ -16,7 +16,7 @@ use vmm_sys_util::eventfd::EventFd;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::{DiskTopology, probe_sparse_support};
use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support};
pub struct RawFileDiskAio {
file: File,
@@ -43,10 +43,12 @@ impl DiskFile for RawFileDiskAio {
}
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth)
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
let mut raw = RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth)
.map_err(DiskFileError::NewAsyncIo)?;
raw.alignment = DiskTopology::probe(&self.file)
.map(|t| t.logical_block_size)
.unwrap_or(SECTOR_SIZE);
Ok(Box::new(raw) as Box<dyn AsyncIo>)
}
fn topology(&mut self) -> DiskTopology {
@@ -71,6 +73,7 @@ pub struct RawFileAsyncAio {
fd: RawFd,
ctx: aio::IoContext,
eventfd: EventFd,
alignment: u64,
}
impl RawFileAsyncAio {
@@ -78,7 +81,12 @@ impl RawFileAsyncAio {
let eventfd = EventFd::new(libc::EFD_NONBLOCK)?;
let ctx = aio::IoContext::new(queue_depth)?;
Ok(RawFileAsyncAio { fd, ctx, eventfd })
Ok(RawFileAsyncAio {
fd,
ctx,
eventfd,
alignment: SECTOR_SIZE,
})
}
}
@@ -87,6 +95,10 @@ impl AsyncIo for RawFileAsyncAio {
&self.eventfd
}
fn alignment(&self) -> u64 {
self.alignment
}
fn read_vectored(
&mut self,
offset: libc::off_t,

View File

@@ -13,7 +13,7 @@ use vmm_sys_util::eventfd::EventFd;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::{DiskTopology, probe_sparse_support};
use crate::{DiskTopology, SECTOR_SIZE, probe_sparse_support};
pub struct RawFileDiskSync {
file: File,
@@ -40,7 +40,11 @@ impl DiskFile for RawFileDiskSync {
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(RawFileSync::new(self.file.as_raw_fd())) as Box<dyn AsyncIo>)
let mut raw = RawFileSync::new(self.file.as_raw_fd());
raw.alignment = DiskTopology::probe(&self.file)
.map(|t| t.logical_block_size)
.unwrap_or(SECTOR_SIZE);
Ok(Box::new(raw) as Box<dyn AsyncIo>)
}
fn topology(&mut self) -> DiskTopology {
@@ -65,6 +69,7 @@ pub struct RawFileSync {
fd: RawFd,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
alignment: u64,
}
impl RawFileSync {
@@ -73,6 +78,7 @@ impl RawFileSync {
fd,
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
completion_list: VecDeque::new(),
alignment: SECTOR_SIZE,
}
}
}
@@ -82,6 +88,10 @@ impl AsyncIo for RawFileSync {
&self.eventfd
}
fn alignment(&self) -> u64 {
self.alignment
}
fn read_vectored(
&mut self,
offset: libc::off_t,