diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-08-05 19:34:24 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-05 19:34:24 +0300 |
| commit | 2863b715132fe29e53089e06f105943d1df0b3b4 (patch) | |
| tree | 6b4af2f6fdec6de2888fd2bff3c0829d6719b727 /src/ipc.rs | |
| parent | eb93ec8ce62b05de776f751f4d2f52ff60f93f16 (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.rs | 100 |
1 files changed, 92 insertions, 8 deletions
@@ -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); |
