use std::fmt::{Debug, Formatter};
use std::io::{ErrorKind, IoSlice, IoSliceMut};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::path::Path;
use crate::async_runtime::{RuntimeType, RUNTIME_TYPE};
use crate::file_buf::FileVolatileBuf;
use crate::{off64_t, preadv64, pwritev64};
pub enum File {
Tokio(tokio::fs::File),
#[cfg(target_os = "linux")]
Uring(tokio_uring::fs::File),
}
impl File {
pub async fn async_open<P: AsRef<Path>>(
path: P,
write: bool,
create: bool,
) -> std::io::Result<Self> {
match *RUNTIME_TYPE {
RuntimeType::Tokio => tokio::fs::OpenOptions::new()
.read(true)
.write(write)
.create(create)
.open(path)
.await
.map(File::Tokio),
#[cfg(target_os = "linux")]
RuntimeType::Uring => tokio_uring::fs::OpenOptions::new()
.read(true)
.write(write)
.create(create)
.open(path)
.await
.map(File::Uring),
}
}
pub async fn async_read_at(
&self,
buf: FileVolatileBuf,
offset: u64,
) -> (std::io::Result<usize>, FileVolatileBuf) {
match self {
File::Tokio(f) => {
let mut bufs = [buf];
let res = preadv(f.as_raw_fd(), &mut bufs, offset);
(res, bufs[0])
}
#[cfg(target_os = "linux")]
File::Uring(f) => f.read_at(buf, offset).await,
}
}
pub async fn async_readv_at(
&self,
mut bufs: Vec<FileVolatileBuf>,
offset: u64,
) -> (std::io::Result<usize>, Vec<FileVolatileBuf>) {
match self {
File::Tokio(f) => {
let res = preadv(f.as_raw_fd(), &mut bufs, offset);
(res, bufs)
}
#[cfg(target_os = "linux")]
File::Uring(f) => f.readv_at(bufs, offset).await,
}
}
pub async fn async_write_at(
&self,
buf: FileVolatileBuf,
offset: u64,
) -> (std::io::Result<usize>, FileVolatileBuf) {
match self {
File::Tokio(f) => {
let bufs = [buf];
let res = pwritev(f.as_raw_fd(), &bufs, offset);
(res, bufs[0])
}
#[cfg(target_os = "linux")]
File::Uring(f) => f.write_at(buf, offset).await,
}
}
pub async fn async_writev_at(
&self,
bufs: Vec<FileVolatileBuf>,
offset: u64,
) -> (std::io::Result<usize>, Vec<FileVolatileBuf>) {
match self {
File::Tokio(f) => {
let res = pwritev(f.as_raw_fd(), &bufs, offset);
(res, bufs)
}
#[cfg(target_os = "linux")]
File::Uring(f) => f.writev_at(bufs, offset).await,
}
}
pub fn metadata(&self) -> std::io::Result<std::fs::Metadata> {
let file = unsafe { std::fs::File::from_raw_fd(self.as_raw_fd()) };
let res = file.metadata();
std::mem::forget(file);
res
}
pub async fn async_try_clone(&self) -> std::io::Result<Self> {
match self {
File::Tokio(f) => f.try_clone().await.map(File::Tokio),
#[cfg(target_os = "linux")]
File::Uring(f) => {
let fd = unsafe { libc::dup(f.as_raw_fd()) };
if fd < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(File::Uring(unsafe {
tokio_uring::fs::File::from_raw_fd(fd)
}))
}
}
}
}
}
impl AsRawFd for File {
fn as_raw_fd(&self) -> RawFd {
match self {
File::Tokio(f) => f.as_raw_fd(),
#[cfg(target_os = "linux")]
File::Uring(f) => f.as_raw_fd(),
}
}
}
impl Debug for File {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let fd = self.as_raw_fd();
write!(f, "Async File {}", fd)
}
}
pub fn preadv(fd: RawFd, bufs: &mut [FileVolatileBuf], offset: u64) -> std::io::Result<usize> {
let iov: Vec<IoSliceMut> = bufs.iter().map(|v| v.io_slice_mut()).collect();
loop {
let res = unsafe {
preadv64(
fd,
iov.as_ptr() as *const libc::iovec,
iov.len() as libc::c_int,
offset as off64_t,
)
};
if res >= 0 {
let mut count = res as usize;
for buf in bufs.iter_mut() {
let cnt = std::cmp::min(count, buf.cap() - buf.len());
unsafe { buf.set_size(buf.len() + cnt) };
count -= cnt;
if count == 0 {
break;
}
}
assert_eq!(count, 0);
return Ok(res as usize);
} else {
let e = std::io::Error::last_os_error();
if e.kind() != ErrorKind::Interrupted {
return Err(e);
}
}
}
}
pub fn pwritev(fd: RawFd, bufs: &[FileVolatileBuf], offset: u64) -> std::io::Result<usize> {
let iov: Vec<IoSlice> = bufs.iter().map(|v| v.io_slice()).collect();
loop {
let res = unsafe {
pwritev64(
fd,
iov.as_ptr() as *const libc::iovec,
iov.len() as libc::c_int,
offset as off64_t,
)
};
if res >= 0 {
return Ok(res as usize);
} else {
let e = std::io::Error::last_os_error();
if e.kind() != ErrorKind::Interrupted {
return Err(e);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::async_runtime::block_on;
use vmm_sys_util::tempdir::TempDir;
#[test]
fn test_new_async_file() {
let dir = TempDir::new().unwrap();
let path = dir.as_path().to_path_buf().join("test.txt");
std::fs::write(&path, b"test").unwrap();
let file = block_on(async { File::async_open(&path, false, false).await.unwrap() });
assert!(file.as_raw_fd() >= 0);
drop(file);
}
#[test]
fn test_async_file_metadata() {
let dir = TempDir::new().unwrap();
let path = dir.as_path().to_path_buf();
std::fs::write(path.join("test.txt"), b"test").unwrap();
let file = block_on(async {
File::async_open(path.join("test.txt"), false, false)
.await
.unwrap()
});
let md = file.metadata().unwrap();
assert!(md.is_file());
let md = file.metadata().unwrap();
assert!(md.is_file());
drop(file);
}
#[test]
fn test_async_read_at() {
let dir = TempDir::new().unwrap();
let path = dir.as_path().to_path_buf();
std::fs::write(path.join("test.txt"), b"test").unwrap();
block_on(async {
let file = File::async_open(path.join("test.txt"), false, false)
.await
.unwrap();
let mut buffer = [0u8; 3];
let buf = unsafe { FileVolatileBuf::new(&mut buffer) };
let (res, buf) = file.async_read_at(buf, 0).await;
assert_eq!(res.unwrap(), 3);
assert_eq!(buf.len(), 3);
let buf = unsafe { FileVolatileBuf::new(&mut buffer) };
let (res, buf) = file.async_read_at(buf, 2).await;
assert_eq!(res.unwrap(), 2);
assert_eq!(buf.len(), 2);
});
}
#[test]
fn test_async_readv_at() {
let dir = TempDir::new().unwrap();
let path = dir.as_path().to_path_buf();
std::fs::write(path.join("test.txt"), b"test").unwrap();
block_on(async {
let file = File::async_open(path.join("test.txt"), false, false)
.await
.unwrap();
let mut buffer = [0u8; 3];
let buf = unsafe { FileVolatileBuf::new(&mut buffer) };
let mut buffer2 = [0u8; 3];
let buf2 = unsafe { FileVolatileBuf::new(&mut buffer2) };
let bufs = vec![buf, buf2];
let (res, bufs) = file.async_readv_at(bufs, 0).await;
assert_eq!(res.unwrap(), 4);
assert_eq!(bufs[0].len(), 3);
assert_eq!(bufs[1].len(), 1);
});
}
#[test]
fn test_async_write_at() {
let dir = TempDir::new().unwrap();
let path = dir.as_path().to_path_buf();
block_on(async {
let file = File::async_open(path.join("test.txt"), true, true)
.await
.unwrap();
let buffer = b"test";
let buf = unsafe {
FileVolatileBuf::from_raw_ptr(
buffer.as_ptr() as *mut u8,
buffer.len(),
buffer.len(),
)
};
let (res, buf) = file.async_write_at(buf, 0).await;
assert_eq!(res.unwrap(), 4);
assert_eq!(buf.len(), 4);
let res = std::fs::read_to_string(path.join("test.txt")).unwrap();
assert_eq!(&res, "test");
});
}
#[test]
fn test_async_writev_at() {
let dir = TempDir::new().unwrap();
let path = dir.as_path().to_path_buf();
block_on(async {
let file = File::async_open(path.join("test.txt"), true, true)
.await
.unwrap();
let buffer = b"tes";
let buf = unsafe {
FileVolatileBuf::from_raw_ptr(
buffer.as_ptr() as *mut u8,
buffer.len(),
buffer.len(),
)
};
let buffer2 = b"t";
let buf2 = unsafe {
FileVolatileBuf::from_raw_ptr(
buffer2.as_ptr() as *mut u8,
buffer2.len(),
buffer2.len(),
)
};
let bufs = vec![buf, buf2];
let (res, bufs) = file.async_writev_at(bufs, 0).await;
assert_eq!(res.unwrap(), 4);
assert_eq!(bufs[0].len(), 3);
assert_eq!(bufs[1].len(), 1);
let res = std::fs::read_to_string(path.join("test.txt")).unwrap();
assert_eq!(&res, "test");
});
}
#[test]
fn test_async_try_clone() {
let dir = TempDir::new().unwrap();
let path = dir.as_path().to_path_buf();
block_on(async {
let file = File::async_open(path.join("test.txt"), true, true)
.await
.unwrap();
let file2 = file.async_try_clone().await.unwrap();
drop(file);
let buffer = b"test";
let buf = unsafe {
FileVolatileBuf::from_raw_ptr(
buffer.as_ptr() as *mut u8,
buffer.len(),
buffer.len(),
)
};
let (res, buf) = file2.async_write_at(buf, 0).await;
assert_eq!(res.unwrap(), 4);
assert_eq!(buf.len(), 4);
});
}
}