use std::{
io,
path::{Path, PathBuf},
sync::Arc,
};
use crate::{
blocking::spawn_blocking,
buf::{BufResult, IoBuf, IoBufMut},
};
fn join_err(e: crate::executor::JoinError) -> io::Error {
io::Error::other(format!("file operation failed: {e}"))
}
pub struct File {
file: Arc<std::fs::File>,
}
fn pread(file: &std::fs::File, buf: &mut impl IoBufMut, pos: u64) -> io::Result<usize> {
let total = buf.bytes_total();
let ptr = buf.stable_ptr_mut();
#[cfg(unix)]
{
use std::os::fd::AsRawFd;
let n = unsafe {
libc::pread(
file.as_raw_fd(),
ptr as *mut libc::c_void,
total,
pos as libc::off_t,
)
};
if n < 0 {
Err(io::Error::last_os_error())
} else {
Ok(n as usize)
}
}
#[cfg(windows)]
{
use std::os::windows::fs::FileExt;
let slice = unsafe {
std::ptr::write_bytes(ptr, 0, total);
std::slice::from_raw_parts_mut(ptr, total)
};
file.seek_read(slice, pos)
}
}
fn pwrite(file: &std::fs::File, data: &[u8], pos: u64) -> io::Result<usize> {
#[cfg(unix)]
{
use std::os::unix::fs::FileExt;
file.write_at(data, pos)
}
#[cfg(windows)]
{
use std::os::windows::fs::FileExt;
file.seek_write(data, pos)
}
}
impl File {
pub async fn open(path: impl AsRef<Path>) -> io::Result<Self> {
OpenOptions::new().read(true).open(path).await
}
pub async fn create(path: impl AsRef<Path>) -> io::Result<Self> {
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)
.await
}
pub fn from_std(file: std::fs::File) -> Self {
Self {
file: Arc::new(file),
}
}
pub async fn read_at<B>(&self, mut buf: B, pos: u64) -> BufResult<usize, B>
where
B: IoBufMut + Send + 'static,
{
let file = self.file.clone();
let job = spawn_blocking(move || {
let res = pread(&file, &mut buf, pos);
if let Ok(n) = res {
unsafe { buf.set_init(n) };
}
(res, buf)
});
match job.await {
Ok(out) => out,
Err(e) => panic!("{}", join_err(e)),
}
}
pub async fn read_exact_at<B>(&self, mut buf: B, pos: u64) -> BufResult<(), B>
where
B: IoBufMut + Send + 'static,
{
let file = self.file.clone();
let job = spawn_blocking(move || {
let total = buf.bytes_total();
let mut got = 0usize;
while got < total {
let tail = unsafe { buf.stable_ptr_mut().add(got) };
let mut view = RawTail {
ptr: tail,
len: total - got,
};
match pread(&file, &mut view, pos + got as u64) {
Ok(0) => {
let e = io::Error::new(
io::ErrorKind::UnexpectedEof,
"failed to fill whole buffer",
);
return (Err(e), buf);
}
Ok(n) => got += n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return (Err(e), buf),
}
}
unsafe { buf.set_init(total) };
(Ok(()), buf)
});
match job.await {
Ok(out) => out,
Err(e) => panic!("{}", join_err(e)),
}
}
pub async fn write_at<B>(&self, buf: B, pos: u64) -> BufResult<usize, B>
where
B: IoBuf + Send + 'static,
{
let file = self.file.clone();
let job = spawn_blocking(move || {
let data = unsafe { std::slice::from_raw_parts(buf.stable_ptr(), buf.bytes_init()) };
let res = pwrite(&file, data, pos);
(res, buf)
});
match job.await {
Ok(out) => out,
Err(e) => panic!("{}", join_err(e)),
}
}
pub async fn write_all_at<B>(&self, buf: B, pos: u64) -> BufResult<(), B>
where
B: IoBuf + Send + 'static,
{
let file = self.file.clone();
let job = spawn_blocking(move || {
let total = buf.bytes_init();
let mut sent = 0usize;
while sent < total {
let data =
unsafe { std::slice::from_raw_parts(buf.stable_ptr().add(sent), total - sent) };
match pwrite(&file, data, pos + sent as u64) {
Ok(0) => {
let e = io::Error::new(
io::ErrorKind::WriteZero,
"failed to write whole buffer",
);
return (Err(e), buf);
}
Ok(n) => sent += n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return (Err(e), buf),
}
}
(Ok(()), buf)
});
match job.await {
Ok(out) => out,
Err(e) => panic!("{}", join_err(e)),
}
}
pub async fn sync_all(&self) -> io::Result<()> {
let file = self.file.clone();
spawn_blocking(move || file.sync_all())
.await
.map_err(join_err)?
}
pub async fn sync_data(&self) -> io::Result<()> {
let file = self.file.clone();
spawn_blocking(move || file.sync_data())
.await
.map_err(join_err)?
}
pub async fn close(self) -> io::Result<()> {
drop(self);
Ok(())
}
}
struct RawTail {
ptr: *mut u8,
len: usize,
}
unsafe impl IoBufMut for RawTail {
fn stable_ptr_mut(&mut self) -> *mut u8 {
self.ptr
}
fn bytes_total(&self) -> usize {
self.len
}
unsafe fn set_init(&mut self, _n: usize) {}
}
#[derive(Clone, Debug)]
pub struct OpenOptions {
inner: std::fs::OpenOptions,
}
impl Default for OpenOptions {
fn default() -> Self {
Self::new()
}
}
impl OpenOptions {
pub fn new() -> Self {
Self {
inner: std::fs::OpenOptions::new(),
}
}
pub fn read(&mut self, read: bool) -> &mut Self {
self.inner.read(read);
self
}
pub fn write(&mut self, write: bool) -> &mut Self {
self.inner.write(write);
self
}
pub fn append(&mut self, append: bool) -> &mut Self {
self.inner.append(append);
self
}
pub fn truncate(&mut self, truncate: bool) -> &mut Self {
self.inner.truncate(truncate);
self
}
pub fn create(&mut self, create: bool) -> &mut Self {
self.inner.create(create);
self
}
pub fn create_new(&mut self, create_new: bool) -> &mut Self {
self.inner.create_new(create_new);
self
}
#[cfg(unix)]
pub fn mode(&mut self, mode: u32) -> &mut Self {
use std::os::unix::fs::OpenOptionsExt;
self.inner.mode(mode);
self
}
pub async fn open(&self, path: impl AsRef<Path>) -> io::Result<File> {
let opts = self.inner.clone();
let path: PathBuf = path.as_ref().to_path_buf();
let file = spawn_blocking(move || opts.open(path))
.await
.map_err(join_err)??;
Ok(File::from_std(file))
}
}