use std::{
mem,
os::fd::{AsRawFd as _, BorrowedFd, OwnedFd},
};
use o3::{
alloc,
base::num::bounded::{U32, U64},
buffer::bytes::{self, bounded},
};
pub use sync::Sync;
use crate::{
Errno,
io::{fd, request::Request},
kernel::{self, errno},
};
mod kind;
mod metadata;
mod mode;
mod open;
pub mod raw;
mod sync;
pub use kind::Kind;
pub use metadata::{Error as MetadataError, Metadata};
pub use mode::Mode;
pub use open::Open;
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct Offset(U64<0, { i64::MAX as u64 }>);
impl Offset {
pub const fn new(value: u64) -> Option<Self> {
match U64::new(value) {
Some(value) => Some(Self(value)),
None => None,
}
}
pub const fn clamp(value: u64) -> Self {
Self(U64::clamp(value))
}
pub const fn get(self) -> u64 {
self.0.get()
}
pub const fn checked_sub(self, value: u64) -> Option<Self> {
match self.0.checked_sub(value) {
Some(value) => Some(Self(value)),
None => None,
}
}
}
pub type Opened = Result<OwnedFd, Errno>;
#[repr(transparent)]
pub struct Dir<S: raw::Directory>(S::Stream);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OpenError<E> {
Syscall(E),
Allocation(alloc::Exhausted),
}
impl<S: File> Dir<S> {
pub fn open<E: From<Errno>>(path: &std::ffi::CStr) -> Result<Self, OpenError<E>> {
S::open_dir(path).map(Self)
}
pub fn from_fd<E: From<Errno>>(file: OwnedFd) -> Result<Self, OpenError<E>> {
S::from_fd(file).map(Self)
}
pub fn entries(&mut self) -> (BorrowedFd<'_>, Entries<'_, S>) {
let file = S::descriptor(&self.0);
(
unsafe { BorrowedFd::borrow_raw(file.get()) },
Entries(&mut self.0),
)
}
}
#[repr(transparent)]
pub struct Entries<'a, S: raw::Directory>(&'a mut S::Stream);
#[derive(Clone, Copy, Debug)]
pub struct Entry<'a> {
pub name: &'a std::ffi::CStr,
pub kind: Option<Kind>,
}
impl<S: raw::Directory> Entries<'_, S> {
pub fn try_next(&mut self) -> Result<Option<Entry<'_>>, Errno> {
S::next(self.0)
}
}
pub trait File: errno::Source + raw::Directory {
type Options;
type Metadata;
type Request<'a>: Request<
'a,
Options = Self::Options,
Metadata = Self::Metadata,
Buffer = bytes::Prefix<'a, { bounded::MAX }>,
>
where
Self: 'a;
fn open<E: From<Errno>>(path: &std::ffi::CStr, flags: Open, mode: Mode) -> Result<OwnedFd, E> {
let raw = unsafe { libc::open(path.as_ptr(), flags.raw(), mode.raw() as libc::c_uint) };
unsafe { <OwnedFd as fd::raw::Adopt>::try_adopt::<Self, E>(raw) }
}
fn metadata<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<Metadata, E> {
let mut value = mem::MaybeUninit::uninit();
let raw = unsafe { libc::fstat(file.as_raw_fd(), value.as_mut_ptr()) };
unsafe { kernel::raw::Status::new(raw).try_zero::<Self, E>() }?;
Ok(Metadata::new(unsafe { value.assume_init() }))
}
fn stat<E: From<Errno>>(path: &std::ffi::CStr) -> Result<Metadata, E> {
let mut value = mem::MaybeUninit::uninit();
let raw = unsafe { libc::stat(path.as_ptr(), value.as_mut_ptr()) };
unsafe { kernel::raw::Status::new(raw).try_zero::<Self, E>() }?;
Ok(Metadata::new(unsafe { value.assume_init() }))
}
fn mkdir<E: From<Errno>>(path: &std::ffi::CStr, mode: Mode) -> Result<(), E> {
unsafe {
kernel::raw::Status::new(libc::mkdir(path.as_ptr(), mode.raw())).try_zero::<Self, E>()
}
}
fn metadata_at<E: From<Errno>>(
dir: Option<BorrowedFd<'_>>,
name: &std::ffi::CStr,
) -> Result<Metadata, E> {
let mut value = mem::MaybeUninit::uninit();
let raw = unsafe {
libc::fstatat(
dir.map_or(libc::AT_FDCWD, |dir| dir.as_raw_fd()),
name.as_ptr(),
value.as_mut_ptr(),
libc::AT_SYMLINK_NOFOLLOW,
)
};
unsafe { kernel::raw::Status::new(raw).try_zero::<Self, E>() }?;
Ok(Metadata::new(unsafe { value.assume_init() }))
}
fn unlink<E: From<Errno>>(path: &std::ffi::CStr) -> Result<(), E> {
unsafe { kernel::raw::Status::new(libc::unlink(path.as_ptr())).try_zero::<Self, E>() }
}
fn rename<E: From<Errno>>(from: &std::ffi::CStr, to: &std::ffi::CStr) -> Result<(), E> {
unsafe {
kernel::raw::Status::new(libc::rename(from.as_ptr(), to.as_ptr())).try_zero::<Self, E>()
}
}
fn try_lock<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<(), E> {
unsafe {
kernel::raw::Status::new(libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB))
.try_zero::<Self, E>()
}
}
fn sync<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<(), E> {
unsafe { kernel::raw::Status::new(libc::fsync(file.as_raw_fd())).try_zero::<Self, E>() }
}
fn truncate<E: From<Errno>>(file: BorrowedFd<'_>, len: i64) -> Result<(), E> {
unsafe {
kernel::raw::Status::new(libc::ftruncate(file.as_raw_fd(), len)).try_zero::<Self, E>()
}
}
fn unlink_at<E: From<Errno>>(dir: BorrowedFd<'_>, name: &std::ffi::CStr) -> Result<(), E> {
unsafe {
kernel::raw::Status::new(libc::unlinkat(dir.as_raw_fd(), name.as_ptr(), 0))
.try_zero::<Self, E>()
}
}
fn remove_dir_at<E: From<Errno>>(
dir: Option<BorrowedFd<'_>>,
name: &std::ffi::CStr,
) -> Result<(), E> {
unsafe {
kernel::raw::Status::new(libc::unlinkat(
dir.map_or(libc::AT_FDCWD, |dir| dir.as_raw_fd()),
name.as_ptr(),
libc::AT_REMOVEDIR,
))
.try_zero::<Self, E>()
}
}
fn open_at<E: From<Errno>>(
dir: BorrowedFd<'_>,
name: &std::ffi::CStr,
flags: Open,
mode: Mode,
) -> Result<OwnedFd, E> {
let raw = unsafe {
libc::openat(
dir.as_raw_fd(),
name.as_ptr(),
flags.raw(),
mode.raw() as libc::c_uint,
)
};
unsafe { <OwnedFd as fd::raw::Adopt>::try_adopt::<Self, E>(raw) }
}
fn rename_at<E: From<Errno>>(
dir: BorrowedFd<'_>,
old: &std::ffi::CStr,
new: &std::ffi::CStr,
) -> Result<(), E> {
unsafe {
kernel::raw::Status::new(libc::renameat(
dir.as_raw_fd(),
old.as_ptr(),
dir.as_raw_fd(),
new.as_ptr(),
))
.try_zero::<Self, E>()
}
}
fn read_at<'a, E: From<Errno>>(
file: BorrowedFd<'_>,
output: &'a mut [u8],
offset: Offset,
) -> Result<&'a mut [u8], E> {
let output =
unsafe { std::slice::from_raw_parts_mut(output.as_mut_ptr().cast(), output.len()) };
raw::read_at::<Self, E>(file, output, offset)
}
fn read_uninit_at<'a, E: From<Errno>>(
file: BorrowedFd<'_>,
output: &'a mut [mem::MaybeUninit<u8>],
offset: Offset,
) -> Result<&'a mut [u8], E> {
raw::read_at::<Self, E>(file, output, offset)
}
fn write_at<E: From<Errno>>(
file: BorrowedFd<'_>,
input: bytes::Prefix<'_, { bounded::MAX }>,
offset: Offset,
) -> Result<U32<0, { bounded::MAX as u32 }>, E> {
use kernel::raw::Status;
const { assert!(libc::off_t::MAX as u64 == i64::MAX as u64) };
let input = input.as_slice();
let raw = unsafe {
libc::pwrite(
file.as_raw_fd(),
input.as_ptr().cast(),
input.len(),
offset.get() as libc::off_t,
)
};
Ok(unsafe { Status::from_count(raw).try_count::<Self, E>() }?.bounded())
}
}