use std::{
io,
path::{Path, PathBuf},
sync::Arc,
};
use crate::{
blocking::spawn_blocking,
buf::{BufResult, IoBuf, IoBufMut},
};
pub use crate::fs_ops::*;
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 read_vectored_at<B>(&self, mut bufs: Vec<B>, pos: u64) -> BufResult<usize, Vec<B>>
where
B: IoBufMut + Send + 'static,
{
if bufs.is_empty() {
return (Ok(0), bufs);
}
let file = self.file.clone();
let job = spawn_blocking(move || {
let mut total_read = 0usize;
for b in &mut bufs {
match pread(&file, b, pos + total_read as u64) {
Ok(0) => break,
Ok(n) => {
unsafe { b.set_init(n) };
total_read += n;
if n < b.bytes_total() {
break;
}
}
Err(e) => {
return if total_read > 0 {
(Ok(total_read), bufs)
} else {
(Err(e), bufs)
};
}
}
}
(Ok(total_read), bufs)
});
match job.await {
Ok(out) => out,
Err(e) => panic!("{}", join_err(e)),
}
}
pub async fn write_vectored_at<B>(&self, bufs: Vec<B>, pos: u64) -> BufResult<usize, Vec<B>>
where
B: IoBuf + Send + 'static,
{
if bufs.is_empty() {
return (Ok(0), bufs);
}
let file = self.file.clone();
let job = spawn_blocking(move || {
let mut total_written = 0usize;
for b in &bufs {
let data = unsafe { std::slice::from_raw_parts(b.stable_ptr(), b.bytes_init()) };
if data.is_empty() {
continue;
}
match pwrite(&file, data, pos + total_written as u64) {
Ok(0) => break,
Ok(n) => {
total_written += n;
if n < data.len() {
break;
}
}
Err(e) => {
return if total_written > 0 {
(Ok(total_written), bufs)
} else {
(Err(e), bufs)
};
}
}
}
(Ok(total_written), bufs)
});
match job.await {
Ok(out) => out,
Err(e) => panic!("{}", join_err(e)),
}
}
pub async fn write_all_vectored_at<B>(&self, bufs: Vec<B>, pos: u64) -> BufResult<(), Vec<B>>
where
B: IoBuf + Send + 'static,
{
let file = self.file.clone();
let job = spawn_blocking(move || {
let mut file_off = pos;
for b in &bufs {
let total = b.bytes_init();
let mut sent = 0usize;
while sent < total {
let data = unsafe {
std::slice::from_raw_parts(b.stable_ptr().add(sent), total - sent)
};
match pwrite(&file, data, file_off) {
Ok(0) => {
return (
Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to write whole vectored buffer",
)),
bufs,
);
}
Ok(n) => {
sent += n;
file_off += n as u64;
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return (Err(e), bufs),
}
}
}
(Ok(()), bufs)
});
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 metadata(&self) -> io::Result<std::fs::Metadata> {
let file = self.file.clone();
spawn_blocking(move || file.metadata())
.await
.map_err(join_err)?
}
pub async fn set_len(&self, size: u64) -> io::Result<()> {
let file = self.file.clone();
spawn_blocking(move || file.set_len(size))
.await
.map_err(join_err)?
}
pub async fn set_permissions(&self, perm: std::fs::Permissions) -> io::Result<()> {
let file = self.file.clone();
spawn_blocking(move || file.set_permissions(perm))
.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,
noatime: bool,
}
impl Default for OpenOptions {
fn default() -> Self {
Self::new()
}
}
impl OpenOptions {
pub fn new() -> Self {
Self {
inner: std::fs::OpenOptions::new(),
noatime: false,
}
}
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 fn noatime(&mut self, noatime: bool) -> &mut Self {
self.noatime = noatime;
self
}
pub async fn open(&self, path: impl AsRef<Path>) -> io::Result<File> {
let opts = self.inner.clone();
#[cfg(target_os = "linux")]
let noatime = self.noatime;
let path: PathBuf = path.as_ref().to_path_buf();
let file = spawn_blocking(move || {
#[cfg(target_os = "linux")]
if noatime {
use std::os::unix::fs::OpenOptionsExt;
let mut with = opts.clone();
with.custom_flags(libc::O_NOATIME);
match with.open(&path) {
Err(e) if e.raw_os_error() == Some(libc::EPERM) => {}
other => return other,
}
}
opts.open(path)
})
.await
.map_err(join_err)??;
Ok(File::from_std(file))
}
}