use crate::io::read_result::ReadResult;
use crate::{io::glommio_file::GlommioFile, GlommioError};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::path::{Path, PathBuf};
type Result<T> = crate::Result<T, ()>;
#[derive(Debug)]
pub struct BufferedFile {
file: GlommioFile,
}
impl AsRawFd for BufferedFile {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl FromRawFd for BufferedFile {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
BufferedFile {
file: GlommioFile::from_raw_fd(fd),
}
}
}
impl BufferedFile {
pub fn is_same(&self, other: &BufferedFile) -> bool {
self.file.is_same(&other.file)
}
pub async fn create<P: AsRef<Path>>(path: P) -> Result<BufferedFile> {
let flags = libc::O_CLOEXEC | libc::O_CREAT | libc::O_TRUNC | libc::O_WRONLY;
GlommioFile::open_at(-1_i32, path.as_ref(), flags, 0o644)
.await
.map_err(|source| GlommioError::create_enhanced(source, "Creating", Some(path), None))
.map(|file| BufferedFile { file })
}
pub async fn open<P: AsRef<Path>>(path: P) -> Result<BufferedFile> {
let flags = libc::O_CLOEXEC | libc::O_RDONLY;
GlommioFile::open_at(-1_i32, path.as_ref(), flags, 0o644)
.await
.map_err(|source| GlommioError::create_enhanced(source, "Reading", Some(path), None))
.map(|file| BufferedFile { file })
}
pub async fn write_at(&self, buf: Vec<u8>, pos: u64) -> Result<usize> {
let source =
self.file
.reactor
.upgrade()
.unwrap()
.write_buffered(self.as_raw_fd(), buf, pos);
source.collect_rw().await.map_err(|source| {
GlommioError::create_enhanced(
source,
"Writing",
self.file.path.as_ref(),
Some(self.as_raw_fd()),
)
})
}
pub async fn read_at(&self, pos: u64, size: usize) -> Result<ReadResult> {
let mut source =
self.file
.reactor
.upgrade()
.unwrap()
.read_buffered(self.as_raw_fd(), pos, size);
let read_size = source.collect_rw().await.map_err(|source| {
GlommioError::create_enhanced(
source,
"Reading",
self.file.path.as_ref(),
Some(self.as_raw_fd()),
)
})?;
let mut buffer = source.extract_dma_buffer();
buffer.trim_to_size(read_size);
Ok(ReadResult::from_whole_buffer(buffer))
}
pub async fn fdatasync(&self) -> Result<()> {
self.file.fdatasync().await.map_err(Into::into)
}
pub async fn pre_allocate(&self, size: u64) -> Result<()> {
self.file.pre_allocate(size).await.map_err(Into::into)
}
pub async fn truncate(&self, size: u64) -> Result<()> {
self.file.truncate(size).await.map_err(Into::into)
}
pub async fn rename<P: AsRef<Path>>(&mut self, new_path: P) -> Result<()> {
self.file.rename(new_path).await.map_err(Into::into)
}
pub async fn remove(&self) -> Result<()> {
self.file.remove().await.map_err(Into::into)
}
pub async fn file_size(&self) -> Result<u64> {
self.file.file_size().await.map_err(Into::into)
}
pub async fn close(self) -> Result<()> {
self.file.close().await.map_err(Into::into)
}
pub fn path(&self) -> Option<&Path> {
self.file.path()
}
pub(crate) fn discard(self) -> (RawFd, Option<PathBuf>) {
self.file.discard()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::io::dma_file::test::make_test_directories;
macro_rules! buffered_file_test {
( $name:ident, $dir:ident, $kind:ident, $code:block) => {
#[test]
fn $name() {
for dir in make_test_directories(&format!("buffered-{}", stringify!($name))) {
let $dir = dir.path.clone();
let $kind = dir.kind;
test_executor!(async move { $code });
}
}
};
}
macro_rules! check_contents {
( $buf:expr, $start:expr ) => {
for (idx, i) in $buf.iter().enumerate() {
assert_eq!(*i, ($start + (idx as u64)) as u8);
}
};
}
buffered_file_test!(file_create_close, path, _k, {
let new_file = BufferedFile::create(path.join("testfile"))
.await
.expect("failed to create file");
new_file.close().await.expect("failed to close file");
std::assert!(path.join("testfile").exists());
});
buffered_file_test!(file_open, path, _k, {
let new_file = BufferedFile::create(path.join("testfile"))
.await
.expect("failed to create file");
new_file.close().await.expect("failed to close file");
let file = BufferedFile::open(path.join("testfile"))
.await
.expect("failed to open file");
file.close().await.expect("failed to close file");
std::assert!(path.join("testfile").exists());
});
buffered_file_test!(file_open_nonexistent, path, _k, {
BufferedFile::open(path.join("testfile"))
.await
.expect_err("opened nonexistent file");
std::assert!(!path.join("testfile").exists());
});
buffered_file_test!(random_io, path, _k, {
let writer = BufferedFile::create(path.join("testfile")).await.unwrap();
let reader = BufferedFile::open(path.join("testfile")).await.unwrap();
let wb = vec![0, 1, 2, 3, 4, 5];
let r = writer.write_at(wb, 0).await.unwrap();
assert_eq!(r, 6);
let rb = reader.read_at(0, 6).await.unwrap();
assert_eq!(rb.len(), 6);
check_contents!(*rb, 0);
let rb = reader.read_at(0, 6).await.unwrap();
assert_eq!(rb.len(), 6);
check_contents!(*rb, 0);
let rb = reader.read_at(3, 6).await.unwrap();
assert_eq!(rb.len(), 3);
check_contents!(rb[0..3], 3);
writer.close().await.unwrap();
reader.close().await.unwrap();
});
buffered_file_test!(write_past_end, path, _k, {
let writer = BufferedFile::create(path.join("testfile")).await.unwrap();
let reader = BufferedFile::open(path.join("testfile")).await.unwrap();
let rb = reader.read_at(0, 6).await.unwrap();
assert_eq!(rb.len(), 0);
let wb = vec![0, 1, 2, 3, 4, 5];
let r = writer.write_at(wb, 10).await.unwrap();
assert_eq!(r, 6);
let rb = reader.read_at(0, 6).await.unwrap();
assert_eq!(rb.len(), 6);
for i in rb.iter() {
assert_eq!(*i, 0);
}
let rb = reader.read_at(10, 6).await.unwrap();
assert_eq!(rb.len(), 6);
check_contents!(*rb, 0);
writer.close().await.unwrap();
reader.close().await.unwrap();
});
}