use std::io;
use std::path::Path;
#[cfg(unix)]
pub use unix_impl::{connect, peer_is_same_user, DaemonListener, DaemonStream};
#[cfg(windows)]
pub use windows_impl::{connect, peer_is_same_user, pipe_name, DaemonListener, DaemonStream};
#[cfg_attr(not(unix), allow(clippy::unused_async))] pub async fn restrict_to_owner(path: &Path, mode: u32) -> io::Result<()> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _;
tokio::fs::set_permissions(path, std::fs::Permissions::from_mode(mode)).await
}
#[cfg(not(unix))]
{
let _ = (path, mode);
Ok(())
}
}
#[cfg(unix)]
mod unix_impl {
use std::io;
use std::path::Path;
use tokio::net::{UnixListener, UnixStream};
pub type DaemonStream = UnixStream;
pub struct DaemonListener {
inner: UnixListener,
}
impl DaemonListener {
pub fn bind(path: &Path) -> io::Result<Self> {
Ok(Self { inner: UnixListener::bind(path)? })
}
pub async fn accept(&mut self) -> io::Result<DaemonStream> {
self.inner.accept().await.map(|(stream, _)| stream)
}
}
pub async fn connect(path: &Path) -> io::Result<DaemonStream> {
UnixStream::connect(path).await
}
pub fn peer_is_same_user(stream: &DaemonStream) -> io::Result<bool> {
Ok(stream.peer_cred()?.uid() == crate::os::unix::effective_uid())
}
}
#[cfg(windows)]
mod windows_impl {
use std::io;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
use sha2::{Digest as _, Sha256};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::net::windows::named_pipe::{
ClientOptions, NamedPipeClient, NamedPipeServer, ServerOptions,
};
use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY;
const PIPE_BUSY_RETRY_WINDOW: Duration = Duration::from_secs(2);
pub fn pipe_name(path: &Path) -> String {
let normalized = path.to_string_lossy().to_lowercase();
let digest = Sha256::digest(normalized.as_bytes());
let mut tag = String::with_capacity(24);
for byte in &digest[..12] {
use std::fmt::Write as _;
let _ = write!(tag, "{byte:02x}");
}
format!(r"\\.\pipe\winx-{tag}")
}
#[derive(Debug)]
pub enum DaemonStream {
Server(NamedPipeServer),
Client(NamedPipeClient),
}
impl AsyncRead for DaemonStream {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
match self.get_mut() {
Self::Server(pipe) => Pin::new(pipe).poll_read(cx, buf),
Self::Client(pipe) => Pin::new(pipe).poll_read(cx, buf),
}
}
}
impl AsyncWrite for DaemonStream {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
data: &[u8],
) -> Poll<io::Result<usize>> {
match self.get_mut() {
Self::Server(pipe) => Pin::new(pipe).poll_write(cx, data),
Self::Client(pipe) => Pin::new(pipe).poll_write(cx, data),
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match self.get_mut() {
Self::Server(pipe) => Pin::new(pipe).poll_flush(cx),
Self::Client(pipe) => Pin::new(pipe).poll_flush(cx),
}
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match self.get_mut() {
Self::Server(pipe) => Pin::new(pipe).poll_shutdown(cx),
Self::Client(pipe) => Pin::new(pipe).poll_shutdown(cx),
}
}
}
impl DaemonStream {
pub fn try_read(&self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Self::Server(pipe) => pipe.try_read(buf),
Self::Client(pipe) => pipe.try_read(buf),
}
}
}
pub struct DaemonListener {
name: String,
marker: PathBuf,
spare: Option<NamedPipeServer>,
}
impl DaemonListener {
pub fn bind(path: &Path) -> io::Result<Self> {
let name = pipe_name(path);
let spare = ServerOptions::new()
.first_pipe_instance(true)
.reject_remote_clients(true)
.create(&name)?;
std::fs::write(path, format!("{name}\n{}\n", std::process::id()))?;
Ok(Self { name, marker: path.to_path_buf(), spare: Some(spare) })
}
pub async fn accept(&mut self) -> io::Result<DaemonStream> {
let server = match self.spare.take() {
Some(server) => server,
None => ServerOptions::new().reject_remote_clients(true).create(&self.name)?,
};
server.connect().await?;
self.spare = ServerOptions::new().reject_remote_clients(true).create(&self.name).ok();
Ok(DaemonStream::Server(server))
}
pub fn marker_path(&self) -> &Path {
&self.marker
}
}
pub async fn connect(path: &Path) -> io::Result<DaemonStream> {
let name = pipe_name(path);
let deadline = Instant::now() + PIPE_BUSY_RETRY_WINDOW;
loop {
match ClientOptions::new().open(&name) {
Ok(client) => return Ok(DaemonStream::Client(client)),
Err(error)
if error.raw_os_error() == Some(ERROR_PIPE_BUSY.cast_signed())
&& Instant::now() < deadline =>
{
tokio::time::sleep(Duration::from_millis(20)).await;
}
Err(error) => return Err(error),
}
}
}
pub fn peer_is_same_user(_stream: &DaemonStream) -> io::Result<bool> {
Ok(true)
}
#[cfg(test)]
mod tests {
use super::pipe_name;
use std::path::Path;
#[test]
fn pipe_names_are_stable_and_case_insensitive() {
let lower = pipe_name(Path::new(r"c:\users\x\winx\winxd.sock"));
let upper = pipe_name(Path::new(r"C:\Users\X\winx\winxd.sock"));
assert_eq!(lower, upper);
assert!(lower.starts_with(r"\\.\pipe\winx-"));
assert_ne!(lower, pipe_name(Path::new(r"C:\Users\X\winx\other.sock")));
}
}
}