mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
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:
committed by
Rob Bradford
parent
80cc980d05
commit
4c7e2b83c1
@@ -5,11 +5,10 @@
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::os::unix::fs::FileExt;
|
use std::os::unix::fs::FileExt;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|
||||||
use crate::aligned_buffer::AlignedBuffer;
|
use crate::aligned_buffer::AlignedBuffer;
|
||||||
|
use crate::{SECTOR_SIZE, probe_direct_alignment};
|
||||||
/// O_DIRECT block-alignment candidates, smallest first.
|
|
||||||
const BLK_ALIGNMENTS: [usize; 2] = [512, 4096];
|
|
||||||
|
|
||||||
/// True when `buf_ptr`/`len`/`offset` already satisfy `alignment`
|
/// True when `buf_ptr`/`len`/`offset` already satisfy `alignment`
|
||||||
/// (`alignment == 0` means no O_DIRECT, so everything is "aligned").
|
/// (`alignment == 0` means no O_DIRECT, so everything is "aligned").
|
||||||
@@ -32,21 +31,13 @@ pub(crate) struct AlignedFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AlignedFile {
|
impl AlignedFile {
|
||||||
/// Wrap `file`, probing the O_DIRECT block alignment when `direct_io`.
|
/// Wrap `file`, querying the O_DIRECT block alignment when `direct_io`.
|
||||||
pub fn new(file: File, direct_io: bool) -> Self {
|
pub fn new(file: File, direct_io: bool) -> Self {
|
||||||
let mut alignment = 0;
|
let alignment = if direct_io {
|
||||||
if direct_io {
|
probe_direct_alignment(file.as_raw_fd()).unwrap_or(SECTOR_SIZE) as usize
|
||||||
for align in &BLK_ALIGNMENTS {
|
} else {
|
||||||
// Probe: an aligned read at `align` succeeds (a short read at
|
0
|
||||||
// EOF still counts) iff the fd accepts that O_DIRECT block size.
|
};
|
||||||
let ok = AlignedBuffer::new(*align as u64, *align, *align)
|
|
||||||
.is_ok_and(|mut b| b.read_from(&file).is_ok());
|
|
||||||
if ok {
|
|
||||||
alignment = *align;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AlignedFile { file, alignment }
|
AlignedFile { file, alignment }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,8 +121,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn new_probes_alignment_and_accessors() {
|
fn new_probes_alignment_and_accessors() {
|
||||||
let tf = pattern_file(8192);
|
let tf = pattern_file(8192);
|
||||||
// Non-O_DIRECT tempfile: an aligned read at `align` still succeeds,
|
// A tempfile is not O_DIRECT, so probe_direct_alignment reports
|
||||||
// so the probe selects 512 (exercises new + probe).
|
// None and new() falls back to SECTOR_SIZE (512).
|
||||||
let mut af = AlignedFile::new(tf.as_file().try_clone().unwrap(), true);
|
let mut af = AlignedFile::new(tf.as_file().try_clone().unwrap(), true);
|
||||||
assert_eq!(af.alignment(), 512);
|
assert_eq!(af.alignment(), 512);
|
||||||
let _ = af.file();
|
let _ = af.file();
|
||||||
|
|||||||
113
block/src/lib.rs
113
block/src/lib.rs
@@ -324,6 +324,63 @@ pub(crate) fn is_block_device(fd: RawFd) -> bool {
|
|||||||
ret == 0 && stat.st_mode & S_IFMT == S_IFBLK
|
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
|
/// Probe whether the file/device supports punch hole and zero range
|
||||||
pub fn probe_sparse_support(file: &File) -> bool {
|
pub fn probe_sparse_support(file: &File) -> bool {
|
||||||
let fd = file.as_raw_fd();
|
let fd = file.as_raw_fd();
|
||||||
@@ -617,66 +674,12 @@ impl DiskTopology {
|
|||||||
Ok(block_size)
|
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> {
|
pub fn probe(f: &File) -> io::Result<Self> {
|
||||||
if !is_block_device(f.as_raw_fd()) {
|
if !is_block_device(f.as_raw_fd()) {
|
||||||
// For regular files opened with O_DIRECT, the logical block size
|
// For regular files opened with O_DIRECT, the logical block size
|
||||||
// must reflect the filesystem DIO alignment so the guest issues
|
// must reflect the filesystem DIO alignment so the guest issues
|
||||||
// correctly sized I/O.
|
// correctly sized I/O.
|
||||||
// SAFETY: fcntl(F_GETFL) is always safe on a valid fd.
|
if let Some(alignment) = probe_direct_alignment(f.as_raw_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);
|
|
||||||
return Ok(DiskTopology {
|
return Ok(DiskTopology {
|
||||||
logical_block_size: alignment,
|
logical_block_size: alignment,
|
||||||
physical_block_size: alignment,
|
physical_block_size: alignment,
|
||||||
|
|||||||
Reference in New Issue
Block a user