use std::os::fd::{AsRawFd as _, BorrowedFd, IntoRawFd as _, OwnedFd};
use crate::{
Errno,
io::fd,
kernel::{self, errno},
};
pub mod raw;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Failure {
Get(Errno),
Set(Errno),
}
pub trait Flags: From<Failure> + Sized {
const READ: libc::c_int;
const WRITE: libc::c_int;
const BIT: libc::c_int;
}
pub trait Fd: errno::Source {
fn update<E: Flags>(file: BorrowedFd<'_>) -> Result<(), E> {
update::<Self, E, true, true>(file)
}
fn remove<E: Flags>(file: BorrowedFd<'_>) -> Result<(), E> {
update::<Self, E, true, false>(file)
}
fn try_update<E: Flags>(file: BorrowedFd<'_>) -> Result<(), E> {
update::<Self, E, false, true>(file)
}
fn close<E: From<Errno>>(file: OwnedFd) -> Result<(), E> {
let raw = unsafe { libc::close(file.into_raw_fd()) };
unsafe { kernel::raw::Status::new(raw).try_zero::<Self, E>() }
}
fn duplicate<E: From<Errno>>(file: BorrowedFd<'_>) -> Result<OwnedFd, E> {
let raw = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
unsafe { <OwnedFd as fd::raw::Adopt>::try_adopt::<Self, E>(raw) }
}
fn set<const COMMAND: libc::c_int, E: From<Errno>>(
file: BorrowedFd<'_>,
flags: libc::c_int,
) -> Result<(), E> {
set::<Self, E, COMMAND, false>(file, flags, E::from)
}
fn set_retry<const COMMAND: libc::c_int, E: From<Errno>>(
file: BorrowedFd<'_>,
flags: libc::c_int,
) -> Result<(), E> {
set::<Self, E, COMMAND, true>(file, flags, E::from)
}
fn read<'a, E: From<Errno>>(
file: BorrowedFd<'_>,
output: &'a mut [u8],
) -> Result<&'a mut [u8], E> {
let raw = unsafe { libc::read(file.as_raw_fd(), output.as_mut_ptr().cast(), output.len()) };
unsafe { crate::io::raw::Buffer::try_native::<Self, E>(output, raw) }
}
fn write<'a, E: From<Errno>>(file: BorrowedFd<'_>, input: &'a [u8]) -> Result<&'a [u8], E> {
let raw = unsafe { libc::write(file.as_raw_fd(), input.as_ptr().cast(), input.len()) };
unsafe { crate::io::raw::Buffer::try_native::<Self, E>(input, raw) }
}
}
impl<T: errno::Source> Fd for T {}
fn update<S: Fd + ?Sized, E: Flags, const RETRY: bool, const ENABLED: bool>(
file: BorrowedFd<'_>,
) -> Result<(), E> {
const {
assert!(
(E::READ == libc::F_GETFD && E::WRITE == libc::F_SETFD)
|| (E::READ == libc::F_GETFL && E::WRITE == libc::F_SETFL)
);
}
let flags = unsafe {
kernel::raw::try_retry::<_, E, RETRY>(
|| {
kernel::raw::Status::new(libc::fcntl(file.as_raw_fd(), E::READ))
.try_value::<S, Errno>()
},
|error| E::from(Failure::Get(error)),
)
}?
.get();
let flags = if ENABLED {
flags | E::BIT
} else {
flags & !E::BIT
};
if E::WRITE == libc::F_SETFD {
set::<S, E, { libc::F_SETFD }, RETRY>(file, flags, |error| E::from(Failure::Set(error)))
} else {
set::<S, E, { libc::F_SETFL }, RETRY>(file, flags, |error| E::from(Failure::Set(error)))
}
}
fn set<S: Fd + ?Sized, E, const COMMAND: libc::c_int, const RETRY: bool>(
file: BorrowedFd<'_>,
flags: libc::c_int,
classify: impl FnOnce(Errno) -> E,
) -> Result<(), E> {
const { assert!(COMMAND == libc::F_SETFD || COMMAND == libc::F_SETFL) };
unsafe {
kernel::raw::try_retry::<_, E, RETRY>(
|| {
kernel::raw::Status::new(libc::fcntl(file.as_raw_fd(), COMMAND, flags))
.try_zero::<S, Errno>()
},
classify,
)
}
}