use std::io;
use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
use std::time::Duration;
use rustix::io::Errno;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SyncError {
#[error("fence holds no descriptor")]
Empty,
#[error("fence wait timed out")]
TimedOut,
#[error("SYNC_IOC_MERGE returned no fence")]
MergeReturnedNothing,
#[error("fence syscall failed: {0}")]
Io(#[from] Errno),
}
impl From<SyncError> for io::Error {
fn from(e: SyncError) -> Self {
match e {
SyncError::Empty | SyncError::MergeReturnedNothing => {
Self::from_raw_os_error(Errno::BADF.raw_os_error())
}
SyncError::TimedOut => Self::from_raw_os_error(Errno::TIMEDOUT.raw_os_error()),
SyncError::Io(errno) => Self::from_raw_os_error(errno.raw_os_error()),
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct SyncMergeData {
name: [u8; 32],
fd2: i32,
fence: i32,
flags: u32,
pad: u32,
}
const SYNC_IOC_MERGE: rustix::ioctl::Opcode =
rustix::ioctl::opcode::read_write::<SyncMergeData>(b'>', 3);
#[derive(Debug, Default)]
pub struct SyncFence {
fd: Option<OwnedFd>,
}
impl SyncFence {
#[must_use]
pub const fn new() -> Self {
Self { fd: None }
}
pub fn import(fd: BorrowedFd<'_>) -> Result<Self, SyncError> {
let duped = fd.try_clone_to_owned().map_err(|e| {
Errno::from_io_error(&e).unwrap_or(Errno::MFILE)
})?;
Ok(Self { fd: Some(duped) })
}
#[must_use]
pub const fn from_owned(fd: OwnedFd) -> Self {
Self { fd: Some(fd) }
}
#[must_use]
pub const fn is_valid(&self) -> bool {
self.fd.is_some()
}
#[must_use]
pub fn as_fd(&self) -> Option<BorrowedFd<'_>> {
self.fd.as_ref().map(AsFd::as_fd)
}
#[must_use]
pub fn into_owned_fd(self) -> Option<OwnedFd> {
self.fd
}
pub fn wait(&self, timeout: Duration) -> Result<(), SyncError> {
let fd = self.as_fd().ok_or(SyncError::Empty)?;
let mut fds = [rustix::event::PollFd::new(
&fd,
rustix::event::PollFlags::IN,
)];
let spec = rustix::event::Timespec {
tv_sec: timeout.as_secs().try_into().unwrap_or(i64::MAX),
tv_nsec: timeout.subsec_nanos().into(),
};
match rustix::event::poll(&mut fds, Some(&spec))? {
0 => Err(SyncError::TimedOut),
_ => Ok(()),
}
}
pub fn merge(&mut self, other: Self) -> Result<(), SyncError> {
let ours = self.as_fd().ok_or(SyncError::Empty)?;
let theirs = other.as_fd().ok_or(SyncError::Empty)?;
let mut data = SyncMergeData {
name: [0; 32],
fd2: rustix::fd::AsRawFd::as_raw_fd(&theirs),
fence: -1,
flags: 0,
pad: 0,
};
unsafe {
rustix::ioctl::ioctl(
ours,
rustix::ioctl::Updater::<SYNC_IOC_MERGE, SyncMergeData>::new(&mut data),
)?;
}
drop(other);
if data.fence < 0 {
return Err(SyncError::MergeReturnedNothing);
}
let merged = unsafe { <OwnedFd as rustix::fd::FromRawFd>::from_raw_fd(data.fence) };
self.fd = Some(merged);
Ok(())
}
}
#[cfg(test)]
mod tests;