use crate::{
RuntimeError,
constants::{INLINE_PAYLOAD, LOCK_POLL},
executor,
futures::{
file::{
file_task::{directory, hint, open_at, read_range, write_all},
metadata::Metadata,
},
task::{
Nothing, Task,
sealed::{self},
},
},
modules::{c_path::c_path, fd::Fd, input::Token, int_check::IntCheck, retried::retried},
};
use std::{ffi::CString, fmt, mem, path::Path, sync::Arc, thread};
const _: () = assert!(mem::size_of::<Result<OpenFile, RuntimeError>>() <= INLINE_PAYLOAD);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LockKind {
Exclusive,
Shared,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct OpenTask {
path: Option<CString>,
read: bool,
write: bool,
append: bool,
create: bool,
create_new: bool,
truncate: bool,
}
impl OpenTask {
pub(crate) fn new(path: impl AsRef<Path>) -> Self {
Self {
path: c_path(path),
read: true,
write: false,
append: false,
create: false,
create_new: false,
truncate: false,
}
}
pub fn read(mut self, read: bool) -> Self {
self.read = read;
self
}
pub fn write(mut self, write: bool) -> Self {
self.write = write;
self
}
pub fn append(mut self, append: bool) -> Self {
self.append = append;
self
}
pub fn create(mut self, create: bool) -> Self {
self.create = create;
self
}
pub fn create_new(mut self, create_new: bool) -> Self {
self.create_new = create_new;
self
}
pub fn truncate(mut self, truncate: bool) -> Self {
self.truncate = truncate;
self
}
fn flags(&self) -> Result<libc::c_int, RuntimeError> {
let writes = self.write || self.append;
let mut flags = match (self.read, writes) {
(true, false) => libc::O_RDONLY,
(false, true) => libc::O_WRONLY,
(true, true) => libc::O_RDWR,
(false, false) => return Err(RuntimeError::BadArgument),
};
if (self.create || self.create_new) && !writes {
return Err(RuntimeError::BadArgument);
}
if self.truncate && (!self.write || self.append) {
return Err(RuntimeError::BadArgument);
}
if self.append {
flags |= libc::O_APPEND;
}
if self.create_new {
flags |= libc::O_CREAT | libc::O_EXCL;
} else if self.create {
flags |= libc::O_CREAT;
}
if self.truncate {
flags |= libc::O_TRUNC;
}
Ok(flags)
}
}
#[derive(Clone)]
pub struct OpenFile {
fd: Arc<Fd>,
appends: bool,
}
impl OpenFile {
pub fn read_at(&self, offset: u64, len: usize) -> FileReadTask {
FileReadTask {
file: self.clone(),
offset,
len,
}
}
pub fn write_at(&self, offset: u64, data: impl Into<Arc<[u8]>>) -> FileWriteTask {
FileWriteTask {
file: self.clone(),
data: data.into(),
at: Some(offset),
}
}
pub fn append(&self, data: impl Into<Arc<[u8]>>) -> FileWriteTask {
FileWriteTask {
file: self.clone(),
data: data.into(),
at: None,
}
}
pub fn set_len(&self, len: u64) -> FileOpTask {
self.op(FileOp::SetLen(len))
}
pub fn sync(&self) -> FileOpTask {
self.op(FileOp::Sync)
}
pub fn lock(&self, kind: LockKind) -> FileOpTask {
self.op(FileOp::Lock(kind, true))
}
pub fn try_lock(&self, kind: LockKind) -> FileOpTask {
self.op(FileOp::Lock(kind, false))
}
pub fn unlock(&self) -> FileOpTask {
self.op(FileOp::Unlock)
}
pub fn metadata(&self) -> FileMetadataTask {
FileMetadataTask { file: self.clone() }
}
fn op(&self, op: FileOp) -> FileOpTask {
FileOpTask {
file: self.clone(),
op,
}
}
}
impl fmt::Debug for OpenFile {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("OpenFile")
.field("fd", &self.fd.raw())
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy)]
enum FileOp {
SetLen(u64),
Sync,
Lock(LockKind, bool),
Unlock,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct FileReadTask {
file: OpenFile,
offset: u64,
len: usize,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct FileWriteTask {
file: OpenFile,
data: Arc<[u8]>,
at: Option<u64>,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct FileOpTask {
file: OpenFile,
op: FileOp,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct FileMetadataTask {
file: OpenFile,
}
impl sealed::Sealed for OpenTask {}
impl sealed::Sealed for FileReadTask {}
impl sealed::Sealed for FileWriteTask {}
impl sealed::Sealed for FileOpTask {}
impl sealed::Sealed for FileMetadataTask {}
impl Task for OpenTask {
type Output = Result<OpenFile, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
let path = self.path.as_ref().ok_or(RuntimeError::BadPath)?;
let fd = open_at(path, self.flags()?, 0o666)?;
if (self.write || self.append) && directory(&fd)? {
return Err(RuntimeError::CheckError(Some(libc::EISDIR)));
}
Ok(OpenFile {
fd: Arc::new(fd),
appends: self.append,
})
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for FileReadTask {
type Output = Result<Vec<u8>, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
read_range(&self.file.fd, self.offset, self.len)
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for FileWriteTask {
type Output = Result<usize, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
let fd = &self.file.fd;
let at = match (self.at, self.file.appends) {
(_, true) => None,
(Some(at), false) => Some(at),
(None, false) => Some(hint(fd)? as u64),
};
write_all(fd, &self.data, at)
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for FileOpTask {
type Output = Result<(), RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
let fd = self.file.fd.raw();
match self.op {
FileOp::SetLen(len) => {
let len = libc::off_t::try_from(len)
.map_err(|_| RuntimeError::CheckError(Some(libc::EINVAL)))?;
retried(|| unsafe { libc::ftruncate(fd, len) })?;
}
FileOp::Sync => {
retried(|| unsafe { libc::fsync(fd) })?;
}
FileOp::Unlock => {
retried(|| unsafe { libc::flock(fd, libc::LOCK_UN) })?;
}
FileOp::Lock(kind, wait) => lock(fd, kind, wait)?,
}
Ok(())
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for FileMetadataTask {
type Output = Result<Metadata, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
let mut raw: libc::stat = unsafe { mem::zeroed() };
retried(|| unsafe { libc::fstat(self.file.fd.raw(), &mut raw) })?;
Ok(Metadata::from_stat(&raw))
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
fn lock(fd: libc::c_int, kind: LockKind, wait: bool) -> Result<(), RuntimeError> {
let operation = match kind {
LockKind::Exclusive => libc::LOCK_EX,
LockKind::Shared => libc::LOCK_SH,
} | libc::LOCK_NB;
loop {
match unsafe { libc::flock(fd, operation) }.check() {
Ok(_) => return Ok(()),
Err(RuntimeError::CheckError(Some(libc::EINTR))) => continue,
Err(RuntimeError::CheckError(Some(libc::EWOULDBLOCK))) if !wait => {
return Err(RuntimeError::NotReady);
}
Err(RuntimeError::CheckError(Some(libc::EWOULDBLOCK))) => {}
Err(error) => return Err(error),
}
if executor::cancelled() {
return Err(RuntimeError::Cancelled);
}
thread::sleep(LOCK_POLL);
}
}