aboutsummaryrefslogtreecommitdiff
path: root/src/ipc.rs
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-08-05 19:34:24 +0300
committerGitHub <noreply@github.com>2026-08-05 19:34:24 +0300
commit2863b715132fe29e53089e06f105943d1df0b3b4 (patch)
tree6b4af2f6fdec6de2888fd2bff3c0829d6719b727 /src/ipc.rs
parenteb93ec8ce62b05de776f751f4d2f52ff60f93f16 (diff)
feat(windows): support remote attach to unix hosts (#2329)
* feat(windows): support remote attach to unix hosts * fix(remote): preserve unix bridge behavior
Diffstat (limited to 'src/ipc.rs')
-rw-r--r--src/ipc.rs100
1 files changed, 92 insertions, 8 deletions
diff --git a/src/ipc.rs b/src/ipc.rs
index c293a278..36e69ea2 100644
--- a/src/ipc.rs
+++ b/src/ipc.rs
@@ -16,6 +16,12 @@ pub(crate) enum LocalStreamRead {
Closed,
}
+pub(crate) enum LocalStreamReadCount {
+ Data(usize),
+ Pending,
+ Closed,
+}
+
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SocketFileIdentity {
#[cfg(unix)]
@@ -125,16 +131,69 @@ pub(crate) fn set_local_stream_polling(stream: &mut LocalStream, enabled: bool)
}
}
+#[cfg(unix)]
+pub(crate) fn shutdown_local_stream_write(stream: &LocalStream) -> io::Result<()> {
+ match stream {
+ LocalStream::UdSocket(stream) => stream.inner().shutdown(std::net::Shutdown::Write),
+ }
+}
+
+/// Binds a listener for private terminal traffic. Unix callers restrict the
+/// socket file after binding; Windows must set the named-pipe DACL at creation.
+pub(crate) fn bind_private_local_listener(path: &Path) -> io::Result<LocalListener> {
+ #[cfg(unix)]
+ {
+ bind_local_listener(path)
+ }
+
+ #[cfg(windows)]
+ {
+ use interprocess::local_socket::{prelude::*, GenericNamespaced, ListenerOptions};
+ use interprocess::os::windows::local_socket::ListenerOptionsExt as _;
+ use interprocess::os::windows::security_descriptor::SecurityDescriptor;
+ use widestring::U16CString;
+
+ let sddl = U16CString::from_str("D:P(A;;GA;;;SY)(A;;GA;;;OW)")
+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
+ let security_descriptor = SecurityDescriptor::deserialize(&sddl)?;
+ let name = path.to_string_lossy().to_string();
+ let name = name.to_ns_name::<GenericNamespaced>()?;
+ let listener = ListenerOptions::new()
+ .name(name)
+ .reclaim_name(false)
+ .security_descriptor(security_descriptor)
+ .create_sync()?;
+ fs::write(path, windows_socket_marker())?;
+ Ok(listener)
+ }
+}
+
pub(crate) fn poll_local_stream_read(
stream: &mut LocalStream,
buf: &mut [u8],
) -> io::Result<LocalStreamRead> {
+ match poll_local_stream_read_count(stream, buf)? {
+ LocalStreamReadCount::Data(read) => {
+ let _ = read;
+ Ok(LocalStreamRead::Data)
+ }
+ LocalStreamReadCount::Pending => Ok(LocalStreamRead::Pending),
+ LocalStreamReadCount::Closed => Ok(LocalStreamRead::Closed),
+ }
+}
+
+pub(crate) fn poll_local_stream_read_count(
+ stream: &mut LocalStream,
+ buf: &mut [u8],
+) -> io::Result<LocalStreamReadCount> {
#[cfg(unix)]
{
match stream.read(buf) {
- Ok(0) => Ok(LocalStreamRead::Closed),
- Ok(_) => Ok(LocalStreamRead::Data),
- Err(err) if err.kind() == io::ErrorKind::WouldBlock => Ok(LocalStreamRead::Pending),
+ Ok(0) => Ok(LocalStreamReadCount::Closed),
+ Ok(read) => Ok(LocalStreamReadCount::Data(read)),
+ Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
+ Ok(LocalStreamReadCount::Pending)
+ }
Err(err) => Err(err),
}
}
@@ -142,12 +201,12 @@ pub(crate) fn poll_local_stream_read(
#[cfg(windows)]
{
match windows_named_pipe_available(stream)? {
- None => Ok(LocalStreamRead::Closed),
- Some(0) => Ok(LocalStreamRead::Pending),
+ None => Ok(LocalStreamReadCount::Closed),
+ Some(0) => Ok(LocalStreamReadCount::Pending),
Some(_) => match stream.read(buf) {
- Ok(0) => Ok(LocalStreamRead::Closed),
- Ok(_) => Ok(LocalStreamRead::Data),
- Err(err) if is_connection_closed_error(&err) => Ok(LocalStreamRead::Closed),
+ Ok(0) => Ok(LocalStreamReadCount::Closed),
+ Ok(read) => Ok(LocalStreamReadCount::Data(read)),
+ Err(err) if is_connection_closed_error(&err) => Ok(LocalStreamReadCount::Closed),
Err(err) => Err(err),
},
}
@@ -306,6 +365,31 @@ mod tests {
#[cfg(windows)]
#[test]
+ fn private_named_pipe_accepts_same_user() {
+ use std::io::Write as _;
+
+ let path = temp_socket_marker_path("private-pipe");
+ let _ = fs::remove_file(&path);
+ let listener = bind_private_local_listener(&path).unwrap();
+ let mut client = connect_local_stream(&path).unwrap();
+ let mut server = listener.accept().unwrap();
+ client.write_all(b"remote").unwrap();
+
+ let mut buffer = [0_u8; 16];
+ assert!(matches!(
+ poll_local_stream_read_count(&mut server, &mut buffer).unwrap(),
+ LocalStreamReadCount::Data(6)
+ ));
+ assert_eq!(&buffer[..6], b"remote");
+
+ drop(client);
+ drop(server);
+ drop(listener);
+ let _ = fs::remove_file(path);
+ }
+
+ #[cfg(windows)]
+ #[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);