diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-07 00:07:24 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-07 16:29:44 +0300 |
| commit | 3c2b46d24aa6c8011a1e198143752f2aa414d585 (patch) | |
| tree | a52280bccc824ca656f8bc986a756f30901974e4 /src/ipc.rs | |
| parent | 5d7e56737ef30d9de5802b0684a9428c40131dda (diff) | |
feat: add native windows beta support
Diffstat (limited to 'src/ipc.rs')
| -rw-r--r-- | src/ipc.rs | 128 |
1 files changed, 116 insertions, 12 deletions
@@ -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<u8>, +} + +pub(crate) fn connect_local_stream(path: &Path) -> io::Result<LocalStream> { + #[cfg(unix)] + { + use interprocess::local_socket::{prelude::*, GenericFilePath}; + + let name = path.to_fs_name::<GenericFilePath>()?; + 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::<GenericNamespaced>()?; + LocalStream::connect(name) + } +} + +pub(crate) fn bind_local_listener(path: &Path) -> io::Result<LocalListener> { + #[cfg(unix)] + { + use interprocess::local_socket::{prelude::*, GenericFilePath, ListenerOptions}; + + let name = path.to_fs_name::<GenericFilePath>()?; + 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::<GenericNamespaced>()?; + 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<SocketFileIdentity> { - 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())) + } +} |
