use crate::fs::OpenOptions;
use crate::io::fallocate::AsyncFallocate;
use crate::io::open::Open;
use crate::io::remove::Remove;
use crate::io::rename::Rename;
use crate::io::sync_all::AsyncSyncAll;
use crate::io::sync_data::AsyncSyncData;
use crate::io::sys::OsPath::{get_os_path, OsPath};
use crate::io::sys::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
use crate::io::{AsyncClose, AsyncRead, AsyncWrite};
use crate::runtime::local_executor;
use std::io::{Error, Result};
use std::mem::ManuallyDrop;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::{io, mem};
pub struct File {
fd: RawFd,
}
impl File {
#[inline(always)]
pub fn fd(&self) -> RawFd {
self.fd
}
pub async fn open<P: AsRef<Path> + Send>(
as_path: P,
open_options: &OpenOptions,
) -> Result<Self> {
let path = as_path.as_ref();
if path == Path::new("") {
return Err(Error::new(io::ErrorKind::InvalidInput, "path is empty"));
}
let os_path = match OsPath::new(path.as_os_str().as_bytes()) {
Ok(path) => path,
Err(err) => return Err(Error::new(io::ErrorKind::InvalidInput, err)),
};
let os_open_options = open_options.into_os_options()?;
match Open::new(os_path, os_open_options).await {
Ok(file) => Ok(file),
Err(err) => Err(err),
}
}
#[inline(always)]
pub async fn rename<OldPath, NewPath>(old_path: OldPath, new_path: NewPath) -> Result<()>
where
OldPath: AsRef<Path> + Send,
NewPath: AsRef<Path> + Send,
{
let old_path = get_os_path(old_path.as_ref())?;
let new_path = get_os_path(new_path.as_ref())?;
Rename::new(old_path, new_path).await
}
#[inline(always)]
pub async fn remove<P: AsRef<Path> + Send>(path: P) -> Result<()> {
let path = get_os_path(path.as_ref())?;
Remove::new(path).await
}
#[inline(always)]
pub fn with_std_file<Ret, F: FnOnce(&std::fs::File) -> Ret>(&self, f: F) -> Ret {
unsafe {
let std_file = std::fs::File::from_raw_fd(self.fd);
let ret = f(&std_file);
mem::forget(std_file);
ret
}
}
#[inline(always)]
pub fn with_std_mut_file<Ret, F: FnOnce(&mut std::fs::File) -> Ret>(&mut self, f: F) -> Ret {
unsafe {
let mut std_file = std::fs::File::from_raw_fd(self.fd);
let ret = f(&mut std_file);
mem::forget(std_file);
ret
}
}
}
impl From<File> for std::fs::File {
fn from(file: File) -> Self {
unsafe { Self::from_raw_fd(ManuallyDrop::new(file).fd) }
}
}
impl From<std::fs::File> for File {
fn from(file: std::fs::File) -> Self {
Self {
fd: file.into_raw_fd(),
}
}
}
impl FromRawFd for File {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
Self { fd }
}
}
impl AsRawFd for File {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl AsyncFallocate for File {}
impl AsyncSyncAll for File {}
impl AsyncSyncData for File {}
impl AsyncRead for File {}
impl AsyncWrite for File {}
impl AsyncClose for File {}
impl Drop for File {
fn drop(&mut self) {
let close_future = self.close();
local_executor().exec_local_future(async {
close_future.await.expect("Failed to close file");
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate as orengine;
use crate::fs::test_helper::{create_test_dir_if_not_exist, is_exists, TEST_DIR_PATH};
use crate::io::{full_buffer, get_fixed_buffer, get_full_fixed_buffer, FixedBuffer};
use std::fs::{create_dir, create_dir_all};
use std::io::{Seek, SeekFrom};
use std::path::PathBuf;
#[orengine::test::test_local]
fn test_file_create_write_read_pread_pwrite_remove_close_with_nonfixed() {
const MSG: &[u8] = b"Hello, world!";
let test_file_dir_path: &str = &(TEST_DIR_PATH.to_string() + "/test_file_nonfixed/");
create_test_dir_if_not_exist();
let file_path = {
let mut file_path_ = PathBuf::from(test_file_dir_path);
let _ = create_dir(test_file_dir_path);
file_path_.push("test.txt");
file_path_
};
let options = OpenOptions::new()
.write(true)
.read(true)
.truncate(true)
.create(true);
let mut file = match File::open(&file_path, &options).await {
Ok(file) => file,
Err(err) => panic!("Can't open (create) file: {err}"),
};
assert!(is_exists(file_path.clone()));
let mut buf = Vec::with_capacity(0);
buf.extend(MSG);
match file.write_all_bytes(buf.as_ref()).await {
Ok(()) => (),
Err(err) => panic!("Can't write file: {err}"),
}
file.with_std_mut_file(|file| file.seek(SeekFrom::Start(0)))
.unwrap();
match file.read_bytes_exact(buf.as_mut()).await {
Ok(()) => assert_eq!(buf, MSG),
Err(err) => panic!("Can't read file: {err}"),
}
buf.clear();
buf.extend(b"great World!");
match file.pwrite_all_bytes(buf.as_ref(), 7).await {
Ok(()) => (),
Err(err) => panic!("Can't pwrite file: {err}"),
}
buf.clear();
unsafe { buf.set_len(b"Hello, great World!".len()) };
match file.pread_bytes_exact(buf.as_mut(), 0).await {
Ok(()) => assert_eq!(buf, b"Hello, great World!"),
Err(err) => panic!("Can't read file: {err}"),
}
File::rename(
test_file_dir_path.to_string() + "test.txt",
test_file_dir_path.to_string() + "test2.txt",
)
.await
.expect("Can't rename file");
assert!(is_exists(test_file_dir_path.to_string() + "/test2.txt"));
File::remove(test_file_dir_path.to_string() + "/test2.txt")
.await
.expect("Can't remove file");
assert!(!is_exists(file_path));
std::fs::remove_dir("./test/test_file_nonfixed").expect("failed to remove test file dir");
}
#[orengine::test::test_local]
fn test_file_unpositional_read_write_with_nonfixed() {
create_test_dir_if_not_exist();
let test_file_dir_path: &str =
&(TEST_DIR_PATH.to_string() + "/unpositional_file_nonfixed/");
let file_path = {
let mut file_path_ = PathBuf::from(test_file_dir_path);
let _ = create_dir_all(test_file_dir_path);
file_path_.push("test.txt");
file_path_
};
let options = OpenOptions::new()
.write(true)
.read(true)
.truncate(true)
.create(true);
let mut file = match File::open(&file_path, &options).await {
Ok(file) => file,
Err(err) => panic!("Can't open (create) file: {err}"),
};
let mut write_buf = vec![0u8; 4096];
for i in 0..write_buf.capacity() {
write_buf[i] = u8::try_from(i % 256).unwrap();
}
file.write_all_bytes(write_buf.as_ref()).await.unwrap();
let mut read_buf = [0; 10];
let mut read = 0usize;
let mut read_file = File::open(&file_path, &OpenOptions::new().read(true))
.await
.unwrap();
while read < write_buf.capacity() {
let n = read_file.read_bytes(&mut read_buf).await.unwrap();
assert_eq!(write_buf[read..read + n], read_buf[..n]);
read += n;
}
let mut large_big_buff = full_buffer();
read_file
.with_std_mut_file(|file| file.seek(SeekFrom::Start(0)))
.unwrap();
read_file
.read_bytes_exact(&mut large_big_buff[..write_buf.capacity()])
.await
.unwrap();
assert_eq!(large_big_buff.as_ref(), write_buf);
}
#[orengine::test::test_local]
fn test_file_create_write_read_pread_pwrite_remove_close_with_fixed() {
const MSG: &[u8] = b"Hello, world!";
let test_file_dir_path: &str = &(TEST_DIR_PATH.to_string() + "/test_file/");
create_test_dir_if_not_exist();
let file_path = {
let mut file_path_ = PathBuf::from(test_file_dir_path);
let _ = create_dir(test_file_dir_path);
file_path_.push("test.txt");
file_path_
};
let options = OpenOptions::new()
.write(true)
.read(true)
.truncate(true)
.create(true);
let mut file = match File::open(&file_path, &options).await {
Ok(file) => file,
Err(err) => panic!("Can't open (create) file: {err}"),
};
assert!(is_exists(file_path.clone()));
let mut buf = get_fixed_buffer().await;
buf.append(MSG);
match file.write_all(&buf).await {
Ok(()) => (),
Err(err) => panic!("Can't write file: {err}"),
}
file.with_std_mut_file(|file| file.seek(SeekFrom::Start(0)))
.unwrap();
match file.read_exact(&mut buf).await {
Ok(()) => assert_eq!(buf.as_ref(), MSG),
Err(err) => panic!("Can't read file: {err}"),
}
buf.clear();
buf.append(b"great World!");
match file.pwrite_all(&buf, 7).await {
Ok(()) => (),
Err(err) => panic!("Can't pwrite file: {err}"),
}
buf.clear();
buf.set_len(u32::try_from(b"Hello, great World!".len()).unwrap())
.unwrap();
match file.pread_exact(&mut buf, 0).await {
Ok(()) => assert_eq!(buf.as_ref(), b"Hello, great World!"),
Err(err) => panic!("Can't read file: {err}"),
}
File::rename(
test_file_dir_path.to_string() + "test.txt",
test_file_dir_path.to_string() + "test2.txt",
)
.await
.expect("Can't rename file");
assert!(is_exists(test_file_dir_path.to_string() + "/test2.txt"));
File::remove(test_file_dir_path.to_string() + "/test2.txt")
.await
.expect("Can't remove file");
assert!(!is_exists(file_path));
std::fs::remove_dir("./test/test_file").expect("failed to remove test file dir");
}
#[orengine::test::test_local]
fn test_file_unpositional_read_write_with_fixed() {
create_test_dir_if_not_exist();
let test_file_dir_path: &str = &(TEST_DIR_PATH.to_string() + "/unpositional_file/");
create_test_dir_if_not_exist();
let file_path = {
let mut file_path_ = PathBuf::from(test_file_dir_path);
let _ = create_dir_all(test_file_dir_path);
file_path_.push("test.txt");
file_path_
};
let options = OpenOptions::new()
.write(true)
.read(true)
.truncate(true)
.create(true);
let mut file = match File::open(&file_path, &options).await {
Ok(file) => file,
Err(err) => panic!("Can't open (create) file: {err}"),
};
let mut write_buf = get_full_fixed_buffer().await;
for i in 0..write_buf.capacity() as usize {
write_buf[i] = u8::try_from(i % 256).unwrap();
}
file.write_all(&write_buf).await.unwrap();
let mut read_buf = get_fixed_buffer().await;
read_buf.set_len(10).unwrap();
let mut read = 0;
let mut read_file = File::open(&file_path, &OpenOptions::new().read(true))
.await
.unwrap();
while read < write_buf.capacity() {
let n = read_file.read(&mut read_buf).await.unwrap();
assert_eq!(
write_buf.as_bytes()[read as usize..read as usize + n as usize],
read_buf.as_bytes()[..n as usize]
);
read += n;
}
let mut large_big_buff = full_buffer();
read_file
.with_std_mut_file(|file| file.seek(SeekFrom::Start(0)))
.unwrap();
read_file
.read_bytes_exact(&mut large_big_buff[..write_buf.capacity() as usize])
.await
.unwrap();
assert_eq!(large_big_buff.as_ref(), write_buf.as_ref());
}
}