use std::{fs, mem, os::fd};
use dope_sys::io::{fd::Fd as _, file, request::Request};
use file::File as _;
use o3::{base::marker::ThreadBound, buffer::bytes};
use crate::{
Backend, Errno,
driver::{
route,
route::{
kind,
kind::{Class, Open, Write},
},
},
io::{fs::metadata::Metadata, transfer},
};
mod dir;
mod len;
pub mod metadata;
pub mod open;
pub mod path;
pub use dir::Dir;
pub use len::Len;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct Kind(file::Kind);
impl Kind {
pub const fn is_regular(self) -> bool {
self.0.is_regular()
}
}
impl From<file::Kind> for Kind {
fn from(kind: file::Kind) -> Self {
Self(kind)
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct Offset(file::Offset);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Overflow {
pub actual: u64,
pub maximum: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Extent {
offset: Offset,
length: transfer::Len,
}
impl From<Len> for Offset {
fn from(length: Len) -> Self {
Self(length.0)
}
}
impl From<transfer::Len> for Offset {
fn from(length: transfer::Len) -> Self {
Self(file::Offset::clamp(length.into_raw() as u64))
}
}
impl Offset {
pub const fn fixed<const OFFSET: u64>() -> Self {
const { assert!(OFFSET <= i64::MAX as u64) };
Self(file::Offset::clamp(OFFSET))
}
pub const fn try_new(value: u64) -> Result<Self, Overflow> {
match file::Offset::new(value) {
Some(value) => Ok(Self(value)),
None => Err(Overflow {
actual: value,
maximum: i64::MAX as u64,
}),
}
}
pub const fn get(self) -> u64 {
self.0.get()
}
pub const fn checked_add(self, length: transfer::Len) -> Result<Self, Extent> {
let value = self.get() + length.into_raw() as u64;
match Self::try_new(value) {
Ok(value) => Ok(value),
Err(_) => Err(Extent::new(self, length)),
}
}
pub const fn checked_sub(self, length: transfer::Len) -> Result<Self, Extent> {
match self.0.checked_sub(length.into_raw() as u64) {
Some(value) => Ok(Self(value)),
None => Err(Extent::new(self, length)),
}
}
}
impl Extent {
const fn new(offset: Offset, length: transfer::Len) -> Self {
Self { offset, length }
}
}
const _: () = {
assert!(mem::size_of::<Kind>() == mem::size_of::<file::Kind>());
assert!(mem::align_of::<Kind>() == mem::align_of::<file::Kind>());
assert!(mem::size_of::<Offset>() == mem::size_of::<u64>());
assert!(mem::align_of::<Offset>() == mem::align_of::<u64>());
assert!(mem::size_of::<Extent>() == 2 * mem::size_of::<u64>());
assert!(mem::align_of::<Extent>() == mem::align_of::<u64>());
assert!(mem::size_of::<Result<Offset, Extent>>() == 3 * mem::size_of::<u64>());
assert!(mem::align_of::<Result<Offset, Extent>>() == mem::align_of::<u64>());
};
pub struct Submission<'a, 'd, Tag, C = <Tag as route::Tag>::Class>
where
Tag: route::Tag,
C: Class,
{
raw: <Backend as file::File>::Request<'a>,
target: route::Operation<'d, Tag, C>,
}
pub struct OpenRequest {
path: path::Confined,
beneath: <Backend as file::File>::Options,
}
impl<'a, 'd, Tag, C> Submission<'a, 'd, Tag, C>
where
Tag: route::Tag,
C: Class,
{
fn bind(
raw: <Backend as file::File>::Request<'a>,
target: route::Operation<'d, Tag, C>,
) -> Self {
Self { raw, target }
}
pub(crate) fn into_parts(
self,
) -> (
<Backend as file::File>::Request<'a>,
route::Operation<'d, Tag, C>,
) {
(self.raw, self.target)
}
}
impl<'a, 'd, Tag> Submission<'a, 'd, Tag, Write>
where
Tag: route::Tag,
{
#[doc(hidden)]
pub fn write_prefix(
fd: fd::BorrowedFd<'a>,
buffer: &'a [u8],
offset: Offset,
target: route::Target<'d, Tag>,
) -> Self
where
Tag: route::Select<Write>,
{
let input = bytes::Prefix::new(buffer);
Self::bind(
Request::write(fd, input, offset.0),
target.operation::<Write>(),
)
}
#[doc(hidden)]
pub fn write_current_prefix(
fd: fd::BorrowedFd<'a>,
buffer: bytes::Prefix<'a, { transfer::MAX_BYTES }>,
target: route::Target<'d, Tag>,
) -> Self
where
Tag: route::Select<Write>,
{
Self::bind(
Request::write_current(fd, buffer),
target.operation::<Write>(),
)
}
}
impl<'a, 'd, Tag> Submission<'a, 'd, Tag, kind::Read>
where
Tag: route::Tag,
{
pub fn read_at(
fd: fd::BorrowedFd<'a>,
output: bytes::bounded::Value<&'a mut [mem::MaybeUninit<u8>]>,
offset: Offset,
target: route::Target<'d, Tag>,
) -> Self
where
Tag: route::Select<kind::Read>,
{
Self::bind(
Request::read(fd, output, offset.0),
target.operation::<kind::Read>(),
)
}
}
impl<'a, 'd, Tag> Submission<'a, 'd, Tag, kind::Sync>
where
Tag: route::Tag,
{
pub fn metadata(
fd: fd::BorrowedFd<'a>,
output: &'a mut metadata::Output,
target: route::Target<'d, Tag>,
) -> Self
where
Tag: route::Select<kind::Sync>,
{
Self::bind(
Request::metadata(fd, output),
target.operation::<kind::Sync>(),
)
}
pub fn sync(fd: fd::BorrowedFd<'a>, mode: Sync, target: route::Target<'d, Tag>) -> Self
where
Tag: route::Select<kind::Sync>,
{
Self::bind(
Request::sync(
fd,
match mode {
Sync::Data => file::Sync::Data,
Sync::All => file::Sync::All,
},
),
target.operation::<kind::Sync>(),
)
}
#[doc(hidden)]
pub fn rename(rename: path::Rename<'a>, target: route::Target<'d, Tag>) -> Self
where
Tag: route::Select<kind::Sync>,
{
Self::bind(
Request::rename(
fd::AsFd::as_fd(rename.directory()),
rename.old(),
rename.new_name(),
),
target.operation::<kind::Sync>(),
)
}
#[doc(hidden)]
pub fn unlink(entry: path::Entry<'a>, target: route::Target<'d, Tag>) -> Self
where
Tag: route::Select<kind::Sync>,
{
Self::bind(
Request::unlink(fd::AsFd::as_fd(entry.directory()), entry.name()),
target.operation::<kind::Sync>(),
)
}
}
impl OpenRequest {
pub fn new(path: path::Confined, options: open::Options) -> Self {
let (flags, mode) = options.nofollow().lower();
Self {
path,
beneath: <<Backend as file::File>::Request<'_> as Request>::options(flags, mode),
}
}
pub fn submission<'d, Tag>(
&self,
target: route::Target<'d, Tag>,
) -> Submission<'_, 'd, Tag, Open>
where
Tag: route::Tag + route::Select<Open>,
{
let path = self.path.view();
Submission::bind(
Request::open(
fd::AsFd::as_fd(path.directory()),
path.name(),
&self.beneath,
),
target.operation::<Open>(),
)
}
pub fn into_path(self) -> path::Confined {
self.path
}
}
pub enum Sync {
Data,
All,
}
#[repr(transparent)]
pub struct File {
fd: fd::OwnedFd,
thread: ThreadBound,
}
impl File {
pub fn try_read<'a>(&self, bytes: &'a mut [u8]) -> Result<&'a mut [u8], Errno> {
Backend::read::<dope_sys::Errno>(fd::AsFd::as_fd(self), bytes)
}
pub fn try_read_at<'a>(
&self,
bytes: &'a mut [u8],
offset: Offset,
) -> Result<&'a mut [u8], Errno> {
Backend::read_at::<dope_sys::Errno>(fd::AsFd::as_fd(self), bytes, offset.0)
}
pub fn try_write<'a>(&self, bytes: &'a [u8]) -> Result<&'a [u8], Errno> {
Backend::write::<dope_sys::Errno>(fd::AsFd::as_fd(self), bytes)
}
pub fn metadata(&self) -> Result<Metadata, Errno> {
let value = Backend::metadata::<dope_sys::Errno>(fd::AsFd::as_fd(self))?;
Ok(Metadata {
kind: Kind::from(value.kind()),
length: value.size(),
})
}
pub fn try_lock(&self) -> Result<(), Errno> {
Backend::try_lock::<dope_sys::Errno>(fd::AsFd::as_fd(self))
}
pub fn try_sync(&self) -> Result<(), Errno> {
Backend::sync::<dope_sys::Errno>(fd::AsFd::as_fd(self))
}
}
impl fd::AsFd for File {
fn as_fd(&self) -> fd::BorrowedFd<'_> {
self.fd.as_fd()
}
}
impl From<fd::OwnedFd> for File {
fn from(file: fd::OwnedFd) -> Self {
Self {
fd: file,
thread: ThreadBound::NEW,
}
}
}
impl From<fs::File> for File {
fn from(file: fs::File) -> Self {
Self {
fd: file.into(),
thread: ThreadBound::NEW,
}
}
}
impl From<File> for fs::File {
fn from(file: File) -> Self {
file.fd.into()
}
}
impl From<File> for fd::OwnedFd {
fn from(file: File) -> Self {
file.fd
}
}
const _: () = {
assert!(size_of::<File>() == size_of::<fd::OwnedFd>());
assert!(align_of::<File>() == align_of::<fd::OwnedFd>());
};