use std::io;
use std::path::Path;
use super::Duplex;
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 use super::Bound;
#[derive(Debug)]
pub enum BindError {
Io(io::Error),
}
fn options(name: &str) -> Result<ListenerOptions<'_>, BindError> {
let name = name
.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)?;
Ok(ListenerOptions::new()
.name(name)
.security_descriptor(security))
}
fn classify<L>(endpoint: &str, error: io::Error) -> Result<Bound<L>, BindError> {
if error.kind() == io::ErrorKind::AddrInUse || error.raw_os_error() == Some(231) {
return Ok(Bound::AlreadyRunning);
}
if error.raw_os_error() == Some(5) {
return match super::windows::WindowsDuplex::connect_once(endpoint) {
Ok(peer) if peer.verify_peer_user().is_ok() => Ok(Bound::AlreadyRunning),
_ => Err(BindError::Io(error)),
};
}
Err(BindError::Io(error))
}
pub fn acquire(endpoint: &Path) -> Result<Bound<interprocess::local_socket::Listener>, BindError> {
let endpoint = endpoint.to_string_lossy();
match options(&endpoint)?.create_sync() {
Ok(listener) => Ok(Bound::Won(listener)),
Err(error) => classify(&endpoint, error),
}
}
#[cfg(feature = "local-async")]
pub fn acquire_tokio(
endpoint: &Path,
) -> Result<Bound<interprocess::local_socket::tokio::Listener>, BindError> {
let endpoint = endpoint.to_string_lossy();
match options(&endpoint)?.create_tokio() {
Ok(listener) => Ok(Bound::Won(listener)),
Err(error) => classify(&endpoint, error),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn unique_name(tag: &str) -> String {
let unique = tempfile::tempdir().unwrap();
format!(
"daemon-{tag}-{}-{}",
std::process::id(),
unique.path().file_name().unwrap().to_string_lossy()
)
}
#[test]
fn a_live_pipe_owner_excludes_another_listener_and_drop_releases_it() {
let name = unique_name("owner");
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(_)));
}
#[cfg(feature = "local-async")]
#[tokio::test]
async fn the_tokio_acquisition_contends_with_the_blocking_one_for_the_same_endpoint() {
let name = unique_name("tokio-owner");
let endpoint = Path::new(&name);
let Bound::Won(owner) = acquire_tokio(endpoint).unwrap() else {
panic!("first owner")
};
assert!(matches!(acquire(endpoint).unwrap(), Bound::AlreadyRunning));
drop(owner);
assert!(matches!(acquire_tokio(endpoint).unwrap(), Bound::Won(_)));
}
}