use std::{
env, fmt, fs, io,
os::unix::{
io::{AsFd, AsRawFd, BorrowedFd, RawFd},
net::{UnixListener, UnixStream},
},
path::{Path, PathBuf},
};
use crate::{util, wire::Backend, PendingRequestResult};
pub use crate::eiproto_eis::*;
#[derive(Debug)]
pub enum BindError {
RuntimeDirNotSet,
Io(io::Error),
}
impl fmt::Display for BindError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RuntimeDirNotSet => write!(
f,
"environment variable XDG_RUNTIME_DIR is not set or invalid"
),
Self::Io(err) => write!(f, "{err}"),
}
}
}
impl From<io::Error> for BindError {
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl std::error::Error for BindError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::RuntimeDirNotSet => None,
Self::Io(err) => Some(err),
}
}
}
#[derive(Debug)]
pub struct Listener {
listener: util::UnlinkOnDrop<UnixListener>,
_lock: Option<util::LockFile>,
}
impl Listener {
pub fn bind(path: &Path) -> io::Result<Self> {
Self::bind_inner(PathBuf::from(path), None)
}
pub fn bind_auto() -> Result<Self, BindError> {
let xdg_dir = if let Some(var) = env::var_os("XDG_RUNTIME_DIR") {
PathBuf::from(var)
} else {
return Err(BindError::RuntimeDirNotSet);
};
for i in 0.. {
let lock_path = xdg_dir.join(format!("eis-{i}.lock"));
let Some(lock_file) = util::LockFile::new(lock_path)? else {
continue;
};
let sock_path = xdg_dir.join(format!("eis-{i}"));
if sock_path.try_exists()? {
fs::remove_file(&sock_path)?;
}
return Ok(Self::bind_inner(sock_path, Some(lock_file))?);
}
Err(BindError::RuntimeDirNotSet)
}
fn bind_inner(path: PathBuf, lock: Option<util::LockFile>) -> io::Result<Self> {
let listener = UnixListener::bind(&path)?;
listener.set_nonblocking(true)?;
let listener = util::UnlinkOnDrop::new(listener, path);
Ok(Self {
listener,
_lock: lock,
})
}
#[must_use]
pub fn path(&self) -> &Path {
util::UnlinkOnDrop::path(&self.listener)
}
pub fn accept(&self) -> io::Result<Option<Context>> {
match self.listener.accept() {
Ok((socket, _)) => Ok(Some(Context::new(socket)?)),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
Err(e) => Err(e),
}
}
}
impl AsFd for Listener {
fn as_fd(&self) -> BorrowedFd<'_> {
self.listener.as_fd()
}
}
impl AsRawFd for Listener {
fn as_raw_fd(&self) -> RawFd {
self.listener.as_raw_fd()
}
}
#[derive(Clone, Debug)]
pub struct Context(pub(crate) Backend);
impl AsFd for Context {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
impl AsRawFd for Context {
fn as_raw_fd(&self) -> RawFd {
self.0.as_fd().as_raw_fd()
}
}
impl Context {
pub fn new(socket: UnixStream) -> io::Result<Self> {
Ok(Self(Backend::new(socket, false)?))
}
pub fn read(&self) -> io::Result<usize> {
self.0.read()
}
pub fn pending_request(&self) -> Option<PendingRequestResult<Request>> {
self.0.pending(Request::parse)
}
#[must_use]
#[allow(clippy::missing_panics_doc)] pub fn handshake(&self) -> handshake::Handshake {
self.0.object_for_id(0).unwrap().downcast_unchecked()
}
pub fn flush(&self) -> rustix::io::Result<()> {
self.0.flush()
}
}
#[doc(hidden)]
pub trait Interface: crate::wire::Interface {}