#![allow(dead_code)]
use anyhow::{Context, Result};
use std::path::Path;
pub use interprocess::local_socket::tokio::Stream as TokioStream;
pub use interprocess::local_socket::{ListenerOptions, Name, Stream as SyncStream};
pub use interprocess::local_socket::tokio::Listener as TokioListener;
pub mod prelude {
pub use interprocess::local_socket::traits::tokio::{Listener as _, Stream as _};
pub use interprocess::local_socket::traits::{Listener as _, Stream as _};
}
pub fn socket_name(path: &Path) -> Result<Name<'static>> {
#[cfg(unix)]
{
use interprocess::local_socket::{GenericFilePath, ToFsName};
path.to_fs_name::<GenericFilePath>()
.with_context(|| format!("converting {} to local-socket name", path.display()))
.map(|n| n.into_owned())
}
#[cfg(windows)]
{
use interprocess::local_socket::{GenericNamespaced, ToNsName};
let hash = blake3::hash(path.as_os_str().as_encoded_bytes());
let short = &hash.to_hex()[..16];
let pipe_name = format!("kache-daemon-{short}");
pipe_name
.to_ns_name::<GenericNamespaced>()
.with_context(|| format!("converting {} to named pipe name", path.display()))
.map(|n| n.into_owned())
}
}
pub fn is_reachable(path: &Path) -> bool {
use interprocess::local_socket::traits::Stream as _;
let Ok(name) = socket_name(path) else {
return false;
};
SyncStream::connect(name).is_ok()
}
pub fn is_peer_disconnect(e: &std::io::Error) -> bool {
use std::io::ErrorKind::*;
matches!(
e.kind(),
BrokenPipe | ConnectionReset | ConnectionAborted | UnexpectedEof
) || e.raw_os_error() == Some(32) }
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Error, ErrorKind};
#[test]
fn socket_name_builds_for_a_plausible_path() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("kache-daemon.sock");
assert!(socket_name(&path).is_ok());
}
#[test]
fn is_reachable_is_false_when_nothing_listens() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("definitely-not-listening.sock");
assert!(!is_reachable(&path));
}
#[cfg(unix)]
#[test]
fn is_reachable_is_true_for_a_live_listener() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("live.sock");
let name = socket_name(&path).unwrap();
let _listener = ListenerOptions::new()
.name(name)
.create_sync()
.expect("bind listener");
assert!(is_reachable(&path));
}
#[test]
fn peer_disconnect_recognizes_disconnect_error_kinds() {
for kind in [
ErrorKind::BrokenPipe,
ErrorKind::ConnectionReset,
ErrorKind::ConnectionAborted,
ErrorKind::UnexpectedEof,
] {
assert!(
is_peer_disconnect(&Error::from(kind)),
"{kind:?} should be treated as a peer disconnect"
);
}
}
#[test]
fn peer_disconnect_recognizes_raw_epipe() {
let err = Error::from_raw_os_error(32);
assert!(is_peer_disconnect(&err));
}
#[test]
fn peer_disconnect_ignores_unrelated_errors() {
for kind in [
ErrorKind::NotFound,
ErrorKind::PermissionDenied,
ErrorKind::InvalidInput,
ErrorKind::TimedOut,
] {
assert!(
!is_peer_disconnect(&Error::from(kind)),
"{kind:?} should not be treated as a peer disconnect"
);
}
}
}