From 3c2b46d24aa6c8011a1e198143752f2aa414d585 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Sun, 7 Jun 2026 00:07:24 +0300 Subject: feat: add native windows beta support --- src/ipc.rs | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 12 deletions(-) (limited to 'src/ipc.rs') diff --git a/src/ipc.rs b/src/ipc.rs index 5f973ccf..5c1fe1b7 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -1,14 +1,66 @@ use std::fs; use std::io; -use std::os::unix::fs::MetadataExt; -use std::os::unix::fs::PermissionsExt; -use std::os::unix::net::UnixStream; +#[cfg(unix)] +use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::Path; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) type LocalListener = interprocess::local_socket::Listener; +pub(crate) type LocalStream = interprocess::local_socket::Stream; + +#[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct SocketFileIdentity { + #[cfg(unix)] dev: u64, + #[cfg(unix)] ino: u64, + #[cfg(windows)] + marker: Vec, +} + +pub(crate) fn connect_local_stream(path: &Path) -> io::Result { + #[cfg(unix)] + { + use interprocess::local_socket::{prelude::*, GenericFilePath}; + + let name = path.to_fs_name::()?; + LocalStream::connect(name) + } + + #[cfg(windows)] + { + use interprocess::local_socket::{prelude::*, GenericNamespaced}; + + let name = path.to_string_lossy().to_string(); + let name = name.to_ns_name::()?; + LocalStream::connect(name) + } +} + +pub(crate) fn bind_local_listener(path: &Path) -> io::Result { + #[cfg(unix)] + { + use interprocess::local_socket::{prelude::*, GenericFilePath, ListenerOptions}; + + let name = path.to_fs_name::()?; + ListenerOptions::new() + .name(name) + .reclaim_name(false) + .create_sync() + } + + #[cfg(windows)] + { + use interprocess::local_socket::{prelude::*, GenericNamespaced, ListenerOptions}; + + let name = path.to_string_lossy().to_string(); + let name = name.to_ns_name::()?; + let listener = ListenerOptions::new() + .name(name) + .reclaim_name(false) + .create_sync()?; + fs::write(path, windows_socket_marker())?; + Ok(listener) + } } pub(crate) fn prepare_socket_path( @@ -23,7 +75,7 @@ pub(crate) fn prepare_socket_path( return Ok(()); } - match UnixStream::connect(path) { + match connect_local_stream(path) { Ok(_) => { return Err(io::Error::new(io::ErrorKind::AddrInUse, busy_message(path))); } @@ -33,6 +85,7 @@ pub(crate) fn prepare_socket_path( io::ErrorKind::ConnectionRefused | io::ErrorKind::NotFound | io::ErrorKind::TimedOut + | io::ErrorKind::WouldBlock ) => {} Err(err) => return Err(err), } @@ -47,16 +100,26 @@ pub(crate) fn prepare_socket_path( } pub(crate) fn socket_file_identity(path: &Path) -> io::Result { - let metadata = fs::metadata(path)?; - Ok(SocketFileIdentity { - dev: metadata.dev(), - ino: metadata.ino(), - }) + #[cfg(windows)] + { + Ok(SocketFileIdentity { + marker: fs::read(path)?, + }) + } + + #[cfg(unix)] + { + let metadata = fs::metadata(path)?; + Ok(SocketFileIdentity { + dev: metadata.dev(), + ino: metadata.ino(), + }) + } } pub(crate) fn remove_socket_file_if_owned( path: &Path, - identity: SocketFileIdentity, + identity: &SocketFileIdentity, ) -> io::Result<()> { let current = match socket_file_identity(path) { Ok(current) => current, @@ -64,7 +127,7 @@ pub(crate) fn remove_socket_file_if_owned( Err(err) => return Err(err), }; - if current != identity { + if current != *identity { return Ok(()); } @@ -75,8 +138,49 @@ pub(crate) fn remove_socket_file_if_owned( } } +#[cfg(windows)] +fn windows_socket_marker() -> String { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|duration| duration.as_nanos()) + .unwrap_or(0); + format!("{}:{now}", std::process::id()) +} + +#[cfg(unix)] pub(crate) fn restrict_socket_permissions(path: &Path, mode: u32) -> io::Result<()> { let mut permissions = fs::metadata(path)?.permissions(); permissions.set_mode(mode); fs::set_permissions(path, permissions) } + +#[cfg(windows)] +pub(crate) fn restrict_socket_permissions(_path: &Path, _mode: u32) -> io::Result<()> { + Ok(()) +} + +#[cfg(all(test, windows))] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + fn remove_socket_file_if_owned_compares_windows_marker_contents() { + let path = temp_socket_marker_path("same-len-marker"); + let _ = fs::remove_file(&path); + + fs::write(&path, b"marker-aa").expect("write first marker"); + let identity = socket_file_identity(&path).expect("read first identity"); + fs::write(&path, b"marker-bb").expect("replace with same-length marker"); + + remove_socket_file_if_owned(&path, &identity).expect("remove owned marker"); + + assert!(path.exists(), "same-length replacement marker must survive"); + + let _ = fs::remove_file(&path); + } + + fn temp_socket_marker_path(name: &str) -> PathBuf { + std::env::temp_dir().join(format!("herdr-{name}-{}.sock", std::process::id())) + } +} -- cgit v1.2.3