#[cfg(all(target_os = "linux", not(target_env = "musl")))]
use std::{ffi::CString, mem};
#[cfg(target_os = "linux")]
use std::{fs, path::PathBuf};
use std::{io, path::Path};
#[derive(Debug, PartialEq, Eq)]
pub enum DirectIoSupport {
Unsupported,
Supported,
Uncertain,
}
#[cfg(target_os = "linux")]
pub fn check_direct_io_capability(path: impl AsRef<Path>) -> io::Result<DirectIoSupport> {
let path = path.as_ref();
if let Some(file) = find_any_file_under_path(path)? {
check_direct_io_for_file(&file)
} else {
if !path.is_dir() {
return Ok(DirectIoSupport::Uncertain);
}
let Ok(tmp) = tempfile::NamedTempFile::new_in(path) else {
return Ok(DirectIoSupport::Uncertain);
};
let result = check_direct_io_for_file(tmp.path());
tmp.close()?;
result
}
}
#[cfg(target_os = "linux")]
fn check_direct_io_for_file(file: &Path) -> io::Result<DirectIoSupport> {
#[cfg(not(target_env = "musl"))]
{
let statx_result = check_direct_io_via_statx(file);
if !matches!(&statx_result, Ok(DirectIoSupport::Uncertain)) {
return statx_result;
}
}
Ok(check_direct_io_via_open_probe(file))
}
#[cfg(not(target_os = "linux"))]
pub fn check_direct_io_capability(_path: impl AsRef<Path>) -> io::Result<DirectIoSupport> {
Ok(DirectIoSupport::Uncertain)
}
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
fn check_direct_io_via_statx(path: &Path) -> io::Result<DirectIoSupport> {
let path_cstr = CString::new(path.as_os_str().as_encoded_bytes())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contains a null byte"))?;
let mut statx_info: libc::statx = unsafe { mem::zeroed() };
let ret = unsafe {
libc::statx(
libc::AT_FDCWD,
path_cstr.as_ptr(),
libc::AT_NO_AUTOMOUNT,
libc::STATX_DIOALIGN,
&mut statx_info,
)
};
if ret != 0 {
return Err(io::Error::last_os_error());
}
let supported = if statx_info.stx_mask & libc::STATX_DIOALIGN == 0 {
DirectIoSupport::Uncertain
} else if statx_info.stx_dio_mem_align != 0 {
DirectIoSupport::Supported
} else {
DirectIoSupport::Unsupported
};
Ok(supported)
}
#[cfg(target_os = "linux")]
fn check_direct_io_via_open_probe(file: &Path) -> DirectIoSupport {
use std::{fs::OpenOptions, os::unix::fs::OpenOptionsExt as _};
if OpenOptions::new().read(true).open(file).is_err() {
return DirectIoSupport::Uncertain;
}
if OpenOptions::new()
.read(true)
.custom_flags(libc::O_DIRECT)
.open(file)
.is_ok()
{
DirectIoSupport::Supported
} else {
DirectIoSupport::Unsupported
}
}
#[cfg(target_os = "linux")]
fn find_any_file_under_path(path: &Path) -> io::Result<Option<PathBuf>> {
if path.is_file() {
return Ok(Some(path.to_path_buf()));
}
if path.is_dir() {
for entry in fs::read_dir(path)? {
let entry = entry?;
if let Some(path) = find_any_file_under_path(&entry.path())? {
return Ok(Some(path));
}
}
}
Ok(None)
}
#[cfg(all(test, target_os = "linux"))]
mod tests {
use {super::*, tempfile::TempDir};
fn make_temp_file(dir: &TempDir, name: &str, content: &[u8]) -> std::path::PathBuf {
let path = dir.path().join(name);
std::fs::write(&path, content).unwrap();
path
}
#[test]
fn test_find_any_file_under_path_file() {
let dir = TempDir::new().unwrap();
let file = make_temp_file(&dir, "f.bin", b"hello");
assert_eq!(find_any_file_under_path(&file).unwrap(), Some(file));
}
#[test]
fn test_find_any_file_under_path_dir() {
let dir = TempDir::new().unwrap();
make_temp_file(&dir, "a.bin", b"data");
let candidate = find_any_file_under_path(dir.path()).unwrap();
assert!(candidate.is_some());
assert!(candidate.unwrap().is_file());
}
#[test]
fn test_find_any_file_under_path_empty_dir() {
let dir = TempDir::new().unwrap();
assert_eq!(find_any_file_under_path(dir.path()).unwrap(), None);
}
#[test]
fn test_path_supports_direct_io_file() {
let dir = TempDir::new().unwrap();
let file = make_temp_file(&dir, "probe.bin", &[0u8; 4096]);
let result = check_direct_io_capability(&file).expect("check must not fail");
assert_eq!(
result,
DirectIoSupport::Supported,
"dev filesystem must support direct I/O"
);
}
#[test]
fn test_path_supports_direct_io_dir() {
let dir = TempDir::new().unwrap();
make_temp_file(&dir, "probe.bin", &[0u8; 4096]);
let result = check_direct_io_capability(dir.path()).expect("check must not fail");
assert_eq!(
result,
DirectIoSupport::Supported,
"dev filesystem must support direct I/O"
);
}
#[test]
fn test_path_supports_direct_io_empty_dir() {
let dir = TempDir::new().unwrap();
let result = check_direct_io_capability(dir.path()).expect("check must not fail");
assert_eq!(
result,
DirectIoSupport::Supported,
"dev filesystem must support direct I/O"
);
}
#[test]
fn test_path_supports_direct_io_nonexistent_path() {
let dir = TempDir::new().unwrap();
let result = check_direct_io_capability(dir.path().join("does-not-exist"))
.expect("check must not fail");
assert_eq!(result, DirectIoSupport::Uncertain);
}
}