summaryrefslogtreecommitdiff
path: root/src/ipc.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-07 00:07:24 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-07 16:29:44 +0300
commit3c2b46d24aa6c8011a1e198143752f2aa414d585 (patch)
treea52280bccc824ca656f8bc986a756f30901974e4 /src/ipc.rs
parent5d7e56737ef30d9de5802b0684a9428c40131dda (diff)
feat: add native windows beta support
Diffstat (limited to 'src/ipc.rs')
-rw-r--r--src/ipc.rs128
1 files changed, 116 insertions, 12 deletions
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<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()))
+ }
+}