block: aligned: Query direct alignment in AlignedFile

Move the statx STATX_DIOALIGN probe out of DiskTopology into a free
probe_direct_alignment helper keyed on a raw fd. The helper gates on the
O_DIRECT open flag and returns the kernel reported alignment only when
direct I/O is in effect, and None otherwise. DiskTopology::probe keeps
the same call path and result.

AlignedFile::new now determines its O_DIRECT block alignment from
probe_direct_alignment instead of trial reads at 512 and 4096, falling
back to SECTOR_SIZE when the kernel does not report a value. This
matches how the raw and fixed VHD workers determine alignment, so all
backends agree on one source of truth.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
Anatol Belski
2026-06-17 22:06:20 +02:00
committed by Rob Bradford
parent 80cc980d05
commit 4c7e2b83c1
2 changed files with 68 additions and 74 deletions

View File

@@ -324,6 +324,63 @@ pub(crate) fn is_block_device(fd: RawFd) -> bool {
ret == 0 && stat.st_mode & S_IFMT == S_IFBLK
}
/// Returns the kernel reported direct I/O alignment for `fd`, or `None`
/// when `fd` was not opened with O_DIRECT.
///
/// When O_DIRECT is set, uses `statx(STATX_DIOALIGN)` (Linux >= 6.1) to obtain
/// the exact memory and offset alignment the kernel requires for direct I/O on
/// this specific fd. Unlike `fstatvfs().f_bsize`, which only returns the
/// filesystem's preferred I/O block size, `STATX_DIOALIGN` reports the true per
/// fd direct I/O constraint accounting for the filesystem, underlying block
/// device, and any stacking such as loop or device mapper. Falls back to
/// [`SECTOR_SIZE`] when the kernel does not report a value.
pub(crate) fn probe_direct_alignment(fd: RawFd) -> Option<u64> {
// SAFETY: fcntl(F_GETFL) is always safe on a valid fd.
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
if flags < 0 || (flags & libc::O_DIRECT) == 0 {
return None;
}
// The libc crate does not expose statx / STATX_DIOALIGN on all targets,
// for example musl, so define the constant and a minimal repr(C) struct
// locally and invoke the syscall directly.
const STATX_DIOALIGN: u32 = 0x2000;
// Minimal statx layout, only the needed fields, everything else is
// padding.
#[repr(C)]
struct Statx {
stx_mask: u32,
_pad: [u8; 148],
stx_dio_mem_align: u32,
stx_dio_offset_align: u32,
_pad2: [u8; 96],
}
let mut stx = mem::MaybeUninit::<Statx>::zeroed();
// SAFETY: FFI syscall with valid fd and correctly sized buffer.
let ret = unsafe {
libc::syscall(
libc::SYS_statx,
fd,
c"".as_ptr(),
libc::AT_EMPTY_PATH,
STATX_DIOALIGN,
stx.as_mut_ptr(),
)
};
if ret == 0 {
// SAFETY: statx succeeded, the struct is fully initialized.
let stx = unsafe { stx.assume_init() };
if stx.stx_mask & STATX_DIOALIGN != 0 && stx.stx_dio_mem_align > 0 {
return Some(cmp::max(stx.stx_dio_mem_align, stx.stx_dio_offset_align) as u64);
}
}
debug!("O_DIRECT alignment query failed, falling back to default {SECTOR_SIZE}");
Some(SECTOR_SIZE)
}
/// Probe whether the file/device supports punch hole and zero range
pub fn probe_sparse_support(file: &File) -> bool {
let fd = file.as_raw_fd();
@@ -617,66 +674,12 @@ impl DiskTopology {
Ok(block_size)
}
/// Query the O_DIRECT alignment requirement for a regular file.
///
/// Uses `statx(STATX_DIOALIGN)` (Linux >= 6.1) to obtain the exact
/// memory and offset alignment the kernel requires for direct I/O on
/// this specific file. Unlike `fstatvfs().f_bsize`, which only returns
/// the filesystem's preferred I/O block size, `STATX_DIOALIGN` reports
/// the true per-file DIO constraints accounting for the filesystem,
/// underlying block device, and any stacking (loop, dm, etc.).
fn query_file_alignment(f: &File) -> u64 {
// The libc crate does not expose statx / STATX_DIOALIGN on all
// targets (e.g. musl), so define the constant and a minimal repr(C)
// struct locally and invoke the syscall directly.
const STATX_DIOALIGN: u32 = 0x2000;
// Minimal statx layout, only the needed fields,
// everything else is padding.
#[repr(C)]
struct Statx {
stx_mask: u32,
_pad: [u8; 148],
stx_dio_mem_align: u32,
stx_dio_offset_align: u32,
_pad2: [u8; 96],
}
let mut stx = mem::MaybeUninit::<Statx>::zeroed();
// SAFETY: FFI syscall with valid fd and correctly sized buffer.
let ret = unsafe {
libc::syscall(
libc::SYS_statx,
f.as_raw_fd(),
c"".as_ptr(),
libc::AT_EMPTY_PATH,
STATX_DIOALIGN,
stx.as_mut_ptr(),
)
};
if ret == 0 {
// SAFETY: statx succeeded, the struct is fully initialized.
let stx = unsafe { stx.assume_init() };
if stx.stx_mask & STATX_DIOALIGN != 0 && stx.stx_dio_mem_align > 0 {
let align = cmp::max(stx.stx_dio_mem_align, stx.stx_dio_offset_align) as u64;
debug!("statx(STATX_DIOALIGN) returned alignment {align}");
return align;
}
}
debug!("O_DIRECT alignment query failed, falling back to default {SECTOR_SIZE}");
SECTOR_SIZE
}
pub fn probe(f: &File) -> io::Result<Self> {
if !is_block_device(f.as_raw_fd()) {
// For regular files opened with O_DIRECT, the logical block size
// must reflect the filesystem DIO alignment so the guest issues
// correctly sized I/O.
// SAFETY: fcntl(F_GETFL) is always safe on a valid fd.
let flags = unsafe { libc::fcntl(f.as_raw_fd(), libc::F_GETFL) };
if flags >= 0 && (flags & libc::O_DIRECT) != 0 {
let alignment = Self::query_file_alignment(f);
if let Some(alignment) = probe_direct_alignment(f.as_raw_fd()) {
return Ok(DiskTopology {
logical_block_size: alignment,
physical_block_size: alignment,