use std::io;
use std::path::Path;
use interprocess::local_socket::{GenericNamespaced, ListenerOptions, ToNsName};
use interprocess::os::windows::local_socket::ListenerOptionsExt;
use interprocess::os::windows::security_descriptor::SecurityDescriptor;
use widestring::u16cstr;
pub enum Bound {
Won(interprocess::local_socket::tokio::Listener),
AlreadyRunning,
}
#[derive(Debug)]
pub enum BindError {
Io(io::Error),
}
pub fn acquire(endpoint: &Path) -> Result<Bound, BindError> {
let endpoint = endpoint.to_string_lossy();
let name = endpoint
.to_ns_name::<GenericNamespaced>()
.map_err(BindError::Io)?;
let security =
SecurityDescriptor::deserialize(u16cstr!("D:P(A;;GA;;;OW)(A;;GA;;;SY)(A;;GA;;;BA)"))
.map_err(BindError::Io)?;
match ListenerOptions::new()
.name(name)
.security_descriptor(security)
.create_tokio()
{
Ok(listener) => Ok(Bound::Won(listener)),
Err(e) if e.kind() == io::ErrorKind::AddrInUse || e.raw_os_error() == Some(231) => {
Ok(Bound::AlreadyRunning)
}
Err(e) if e.raw_os_error() == Some(5) => {
match super::windows::WindowsDuplex::connect_once(&endpoint) {
Ok(peer) if peer.verify_peer_user().is_ok() => Ok(Bound::AlreadyRunning),
_ => Err(BindError::Io(e)),
}
}
Err(e) => Err(BindError::Io(e)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn a_live_pipe_owner_excludes_another_listener_and_drop_releases_it() {
let unique = tempfile::tempdir().unwrap();
let name = format!(
"daemon-owner-{}-{}",
std::process::id(),
unique.path().file_name().unwrap().to_string_lossy()
);
let endpoint = Path::new(&name);
let Bound::Won(owner) = acquire(endpoint).unwrap() else {
panic!("first owner")
};
assert!(matches!(acquire(endpoint).unwrap(), Bound::AlreadyRunning));
let other_name = format!("{name}-other");
assert!(matches!(
acquire(Path::new(&other_name)).unwrap(),
Bound::Won(_)
));
drop(owner);
assert!(matches!(acquire(endpoint).unwrap(), Bound::Won(_)));
}
}