use dragonfly_client_core::Result;
use tokio::fs;
pub mod fd;
#[cfg(target_os = "linux")]
use tracing::warn;
#[allow(unused_variables)]
pub async fn fallocate(f: &fs::File, length: u64) -> Result<()> {
if length == 0 {
return Ok(());
}
#[cfg(target_os = "linux")]
{
use dragonfly_client_core::Error;
use rustix::fs::{fallocate, FallocateFlags};
use std::os::unix::io::AsFd;
use tokio::io;
f.set_len(length).await?;
let f = f.try_clone().await?;
tokio::task::spawn_blocking(move || {
let fd = f.as_fd();
let offset = 0;
let flags = FallocateFlags::KEEP_SIZE;
loop {
match fallocate(fd, flags, offset, length) {
Ok(_) => return Ok(()),
Err(rustix::io::Errno::INTR) => continue,
Err(err)
if err == rustix::io::Errno::NOTSUP
|| err == rustix::io::Errno::OPNOTSUPP =>
{
warn!("fallocate not supported, skipping preallocation");
return Ok(());
}
Err(err) => {
return Err(Error::IO(io::Error::from_raw_os_error(err.raw_os_error())))
}
}
}
})
.await
.map_err(io::Error::other)??;
}
Ok(())
}
#[allow(unused_variables)]
pub async fn fadvise_dontneed(f: &fs::File) -> Result<()> {
#[cfg(target_os = "linux")]
{
use dragonfly_client_core::Error;
use rustix::fs::{fadvise, Advice};
use std::os::unix::io::AsFd;
use tokio::io;
let f = f.try_clone().await?;
tokio::task::spawn_blocking(move || {
fadvise(f.as_fd(), 0, None, Advice::DontNeed)
.map_err(|err| Error::IO(io::Error::from_raw_os_error(err.raw_os_error())))
})
.await
.map_err(io::Error::other)??;
}
Ok(())
}
#[allow(unused_variables)]
pub fn fadvise_sequential(f: &std::fs::File) -> Result<()> {
#[cfg(target_os = "linux")]
{
use dragonfly_client_core::Error;
use rustix::fs::{fadvise, Advice};
use std::os::unix::io::AsFd;
fadvise(f.as_fd(), 0, None, Advice::Sequential)
.map_err(|err| Error::IO(std::io::Error::from_raw_os_error(err.raw_os_error())))?;
}
Ok(())
}
#[allow(unused_variables)]
pub async fn fadvise_willneed(f: &std::fs::File, offset: u64, length: u64) -> Result<()> {
if length == 0 {
return Ok(());
}
#[cfg(target_os = "linux")]
{
use dragonfly_client_core::Error;
use rustix::fs::{fadvise, Advice};
use std::num::NonZeroU64;
use std::os::unix::io::AsFd;
use tokio::io;
let f = f.try_clone()?;
tokio::task::spawn_blocking(move || {
fadvise(f.as_fd(), offset, NonZeroU64::new(length), Advice::WillNeed)
.map_err(|err| Error::IO(io::Error::from_raw_os_error(err.raw_os_error())))
})
.await
.map_err(io::Error::other)??;
}
Ok(())
}
#[allow(unused_variables)]
pub async fn sync_file_range(f: &std::fs::File, offset: u64, length: u64) -> Result<()> {
if length == 0 {
return Ok(());
}
#[cfg(target_os = "linux")]
{
use dragonfly_client_core::Error;
use std::os::unix::io::AsRawFd;
use tokio::io;
let f = f.try_clone()?;
tokio::task::spawn_blocking(move || {
match unsafe {
libc::sync_file_range(
f.as_raw_fd(),
offset as libc::off64_t,
length as libc::off64_t,
libc::SYNC_FILE_RANGE_WRITE,
)
} {
0 => Ok(()),
_ => Err(Error::IO(io::Error::last_os_error())),
}
})
.await
.map_err(io::Error::other)??;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[tokio::test]
async fn test_fadvise_dontneed() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
std::fs::write(&path, b"hello, world!").unwrap();
let f = fs::File::open(&path).await.unwrap();
fadvise_dontneed(&f).await.unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"hello, world!");
}
#[test]
fn test_fadvise_sequential() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
std::fs::write(&path, b"hello, world!").unwrap();
let f = std::fs::File::open(&path).unwrap();
fadvise_sequential(&f).unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"hello, world!");
}
#[tokio::test]
async fn test_fadvise_willneed() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
std::fs::write(&path, b"hello, world!").unwrap();
let f = std::fs::File::open(&path).unwrap();
fadvise_willneed(&f, 0, 13).await.unwrap();
fadvise_willneed(&f, 7, 5).await.unwrap();
fadvise_willneed(&f, 0, 0).await.unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"hello, world!");
}
#[tokio::test]
async fn test_sync_file_range() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("task");
std::fs::write(&path, b"hello, world!").unwrap();
let f = std::fs::OpenOptions::new()
.truncate(false)
.write(true)
.open(&path)
.unwrap();
sync_file_range(&f, 0, 13).await.unwrap();
sync_file_range(&f, 7, 5).await.unwrap();
sync_file_range(&f, 0, 0).await.unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"hello, world!");
}
}