1use crate::host_identity::{HostConnectError, HostExpectation, HostProcess};
6use crate::suite::Suite;
7use std::io::{self, Read, Write};
8#[cfg(windows)]
9use std::os::windows::io::IntoRawHandle;
10use std::sync::Arc;
11
12pub enum Accepted {
15 Conn(Conn),
17 Rejected(String),
20 Unidentified(crate::peer::PeerRefusal),
24 Vanished,
27}
28
29pub const DEFAULT_MAX_PIPE_INSTANCES: u32 = 16;
31
32pub struct Conn {
34 pub reader: Box<dyn Read + Send>,
35 pub writer: Box<dyn Write + Send>,
36 pub peer_closed: Arc<dyn Fn() -> bool + Send + Sync>,
38 pub shutdown: Arc<dyn Fn() + Send + Sync>,
39 pub peer: crate::peer::PeerIdentity,
42}
43
44fn fnv(s: &str) -> u32 {
45 let mut h: u32 = 0x811c_9dc5;
46 for b in s
47 .to_lowercase()
48 .replace('\\', "/")
49 .trim_end_matches('/')
50 .bytes()
51 {
52 h ^= u32::from(b);
53 h = h.wrapping_mul(0x0100_0193);
54 }
55 h
56}
57
58pub const MAX_UNIX_SOCKET_PATH: usize = 103;
61
62const IN_SUITE_PATH_LIMIT: usize = 100;
66
67#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct EndpointError {
71 pub path: String,
72 pub len: usize,
73 pub limit: usize,
74}
75
76impl std::fmt::Display for EndpointError {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 write!(
79 f,
80 "service socket path {:?} is {} bytes, over the {}-byte limit; set a shorter TMPDIR",
81 self.path, self.len, self.limit
82 )
83 }
84}
85impl std::error::Error for EndpointError {}
86
87fn fnv1a64(s: &str) -> u64 {
90 let mut h: u64 = 0xcbf2_9ce4_8422_2325;
91 for b in s.bytes() {
92 h ^= u64::from(b);
93 h = h.wrapping_mul(0x0000_0100_0000_01b3);
94 }
95 h
96}
97
98fn fallback_file_name(tag: &str, service: &str) -> String {
101 format!("rk-{tag}-{:016x}.sock", fnv1a64(service))
102}
103
104fn legacy_fallback_file_name(tag: &str, service: &str) -> String {
106 format!("rk-{tag}-{service}.sock")
107}
108
109fn suite_tag(suite: &Suite) -> String {
110 format!("{:08x}", fnv(&suite.root().to_string_lossy()))
111}
112
113fn unix_endpoint_in(
116 suite: &Suite,
117 service: &str,
118 temp: &std::path::Path,
119) -> Result<String, EndpointError> {
120 let p = suite.service_dir(service).join(format!("{service}.sock"));
121 if p.as_os_str().len() <= IN_SUITE_PATH_LIMIT {
122 return Ok(p.to_string_lossy().into_owned());
123 }
124 let f = temp
125 .join(fallback_file_name(&suite_tag(suite), service))
126 .to_string_lossy()
127 .into_owned();
128 if f.len() <= MAX_UNIX_SOCKET_PATH {
129 Ok(f)
130 } else {
131 Err(EndpointError {
132 len: f.len(),
133 path: f,
134 limit: MAX_UNIX_SOCKET_PATH,
135 })
136 }
137}
138
139fn legacy_unix_endpoint_in(suite: &Suite, service: &str, temp: &std::path::Path) -> Option<String> {
142 let p = suite.service_dir(service).join(format!("{service}.sock"));
143 if p.as_os_str().len() <= IN_SUITE_PATH_LIMIT {
144 return None;
145 }
146 let f = temp
147 .join(legacy_fallback_file_name(&suite_tag(suite), service))
148 .to_string_lossy()
149 .into_owned();
150 (f.len() <= MAX_UNIX_SOCKET_PATH).then_some(f)
151}
152
153pub fn try_endpoint_for(suite: &Suite, service: &str) -> Result<String, EndpointError> {
163 if cfg!(windows) {
164 let tag = suite_tag(suite);
165 let user: String = std::env::var("USERNAME")
171 .unwrap_or_default()
172 .chars()
173 .filter(|c| c.is_ascii_alphanumeric())
174 .take(20)
175 .collect();
176 Ok(format!(r"\\.\pipe\rightkit-{service}-{user}-{tag}"))
177 } else {
178 unix_endpoint_in(suite, service, &std::env::temp_dir())
179 }
180}
181
182pub fn endpoint_for(suite: &Suite, service: &str) -> String {
185 try_endpoint_for(suite, service).unwrap_or_else(|e| e.path)
186}
187
188pub fn endpoint_candidates(suite: &Suite, service: &str) -> Vec<String> {
194 candidates_in(suite, service, &std::env::temp_dir())
195}
196
197fn candidates_in(suite: &Suite, service: &str, temp: &std::path::Path) -> Vec<String> {
198 let mut v = Vec::new();
199 if cfg!(windows) {
200 if let Ok(e) = try_endpoint_for(suite, service) {
201 v.push(e);
202 }
203 return v;
204 }
205 if let Ok(e) = unix_endpoint_in(suite, service, temp) {
206 v.push(e);
207 }
208 if let Some(old) = legacy_unix_endpoint_in(suite, service, temp) {
209 if !v.contains(&old) {
210 v.push(old);
211 }
212 }
213 v
214}
215
216pub fn find_live_endpoint(suite: &Suite, service: &str) -> Option<String> {
218 endpoint_candidates(suite, service)
219 .into_iter()
220 .find(|e| probe_endpoint(e))
221}
222
223pub fn probe_endpoint(endpoint: &str) -> bool {
225 connect(endpoint).is_ok()
226}
227
228#[cfg(unix)]
229mod imp {
230 use super::*;
231 use std::os::fd::AsRawFd;
232 use std::os::unix::fs::{MetadataExt, PermissionsExt};
233 use std::os::unix::net::{UnixListener, UnixStream};
234 use std::path::PathBuf;
235
236 use crate::peer::{verify_peer_is_current_user, PeerError, PeerIdentity};
237
238 extern "C" {
239 fn recv(fd: i32, buf: *mut u8, len: usize, flags: i32) -> isize;
240 }
241 #[cfg(target_os = "macos")]
243 const PEEK_NOWAIT: i32 = 0x2 | 0x80;
244 #[cfg(not(target_os = "macos"))]
245 const PEEK_NOWAIT: i32 = 0x2 | 0x40;
246
247 pub fn current_uid() -> u32 {
248 crate::peer::current_euid()
249 }
250
251 fn admit(
254 s: UnixStream,
255 verify: impl FnOnce(&UnixStream) -> Result<PeerIdentity, PeerError>,
256 ) -> io::Result<Accepted> {
257 match verify(&s) {
258 Ok(peer) => wrap(s).map(|conn| Accepted::Conn(Conn { peer, ..conn })),
259 Err(e) if e.peer_gone() => Ok(Accepted::Vanished),
260 Err(e) => Ok(match e.refusal() {
261 Some(refusal) => Accepted::Unidentified(refusal),
262 None => Accepted::Rejected(e.to_string()),
263 }),
264 }
265 }
266
267 fn wrap(s: UnixStream) -> io::Result<Conn> {
268 let r = s.try_clone()?;
269 let w = s.try_clone()?;
270 let watch = s.try_clone()?;
271 let sd = s;
272 Ok(Conn {
273 peer: crate::peer::PeerIdentity::default(),
274 reader: Box::new(r),
275 writer: Box::new(w),
276 peer_closed: Arc::new(move || {
277 let mut b = [0u8; 1];
278 let n = unsafe { recv(watch.as_raw_fd(), b.as_mut_ptr(), 1, PEEK_NOWAIT) };
279 n == 0
280 || (n < 0
281 && !matches!(
282 io::Error::last_os_error().kind(),
283 io::ErrorKind::WouldBlock | io::ErrorKind::Interrupted
284 ))
285 }),
286 shutdown: Arc::new(move || {
287 let _ = sd.shutdown(std::net::Shutdown::Both);
288 }),
289 })
290 }
291
292 pub struct Listener {
293 l: UnixListener,
294 path: PathBuf,
295 }
296
297 impl Listener {
298 pub fn bind(endpoint: &str, _max_instances: u32) -> io::Result<Listener> {
300 let path = PathBuf::from(endpoint);
301 if let Ok(md) = std::fs::symlink_metadata(&path) {
302 if md.uid() != current_uid() || !md.file_type().is_socket_like() {
304 return Err(io::Error::new(
305 io::ErrorKind::AddrInUse,
306 "endpoint path is occupied by something that is not our socket",
307 ));
308 }
309 if UnixStream::connect(&path).is_ok() {
310 return Err(io::Error::new(
311 io::ErrorKind::AddrInUse,
312 "a live host already serves this endpoint",
313 ));
314 }
315 let _ = std::fs::remove_file(&path);
316 }
317 let l = UnixListener::bind(&path)?;
318 std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?;
319 Ok(Listener { l, path })
320 }
321 pub fn accept(&self) -> io::Result<Accepted> {
323 let (s, _) = self.l.accept()?;
324 admit(s, verify_peer_is_current_user)
325 }
326 pub fn wake(&self) {
327 let _ = UnixStream::connect(&self.path);
328 }
329 pub fn cleanup(&self) {
330 let _ = std::fs::remove_file(&self.path);
331 }
332 }
333
334 trait SocketLike {
335 fn is_socket_like(&self) -> bool;
336 }
337 impl SocketLike for std::fs::FileType {
338 fn is_socket_like(&self) -> bool {
339 use std::os::unix::fs::FileTypeExt;
340 self.is_socket()
341 }
342 }
343
344 pub fn connect(endpoint: &str) -> io::Result<Conn> {
347 let s = UnixStream::connect(endpoint)?;
348 verify_peer_is_current_user(&s).map_err(io::Error::from)?;
349 wrap(s)
350 }
351
352 pub fn connect_expecting(
358 endpoint: &str,
359 expect: &HostExpectation,
360 ) -> Result<(Conn, HostProcess), HostConnectError> {
361 let s = UnixStream::connect(endpoint).map_err(HostConnectError::Io)?;
362 let host = crate::host_identity::verify_unix_host(&s, expect)?;
363 let conn = wrap(s).map_err(HostConnectError::Io)?;
364 Ok((conn, host))
365 }
366}
367
368#[cfg(windows)]
369mod imp {
370 use super::*;
371 use crate::peer::{
372 owner_only_security_attributes, verify_pipe_peer_is_current_user,
373 verify_pipe_server_is_current_user, OwnerOnlySecurityAttributes,
374 };
375 use std::os::windows::io::RawHandle;
376 use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
377 use std::sync::{Mutex, RwLock};
378 use windows::core::PCWSTR;
379 use windows::Win32::Foundation::{CloseHandle, HANDLE};
380 use windows::Win32::Storage::FileSystem::{
381 ReadFile, WriteFile, FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED,
382 PIPE_ACCESS_DUPLEX,
383 };
384 use windows::Win32::System::Pipes::{
385 ConnectNamedPipe, CreateNamedPipeW, PeekNamedPipe, PIPE_READMODE_BYTE,
386 PIPE_REJECT_REMOTE_CLIENTS, PIPE_TYPE_BYTE, PIPE_WAIT,
387 };
388 use windows::Win32::System::Threading::CreateEventW;
389 use windows::Win32::System::IO::{CancelIoEx, GetOverlappedResult, OVERLAPPED};
390
391 pub fn current_uid() -> u32 {
392 0
393 }
394
395 struct SendHandle(HANDLE);
396 unsafe impl Send for SendHandle {}
397
398 pub struct Listener {
399 name: Vec<u16>,
400 attrs: OwnerOnlySecurityAttributes,
401 max_instances: u32,
403 first: AtomicBool,
404 endpoint: String,
405 pending: Mutex<Option<SendHandle>>,
408 }
409
410 fn wide(s: &str) -> Vec<u16> {
411 s.encode_utf16().chain(std::iter::once(0)).collect()
412 }
413
414 const ERROR_BROKEN_PIPE: u32 = 109;
415 const ERROR_IO_PENDING: u32 = 997;
416 const ERROR_OPERATION_ABORTED: u32 = 995;
417 const ERROR_NO_DATA: u32 = 232;
418 const ERROR_PIPE_CONNECTED: u32 = 535;
419 const FILE_FLAG_OVERLAPPED_BITS: u32 = 0x4000_0000;
420 const SECURITY_SQOS_PRESENT_BITS: u32 = 0x0010_0000;
422 const SECURITY_IDENTIFICATION_BITS: u32 = 0x0001_0000;
423
424 fn code(e: &windows::core::Error) -> u32 {
425 (e.code().0 as u32) & 0xFFFF
426 }
427
428 struct PipeInner {
434 h: RwLock<Option<HANDLE>>,
438 raw: AtomicIsize,
439 cancelled: AtomicBool,
440 closing: Mutex<()>,
441 }
442 unsafe impl Send for PipeInner {}
443 unsafe impl Sync for PipeInner {}
444 impl Drop for PipeInner {
445 fn drop(&mut self) {
446 if let Some(h) = self.h.get_mut().unwrap_or_else(|e| e.into_inner()).take() {
447 unsafe {
448 let _ = CloseHandle(h);
449 }
450 }
451 }
452 }
453
454 #[derive(Clone)]
455 struct Pipe(Arc<PipeInner>);
456
457 impl Pipe {
458 fn new(h: HANDLE) -> Pipe {
459 Pipe(Arc::new(PipeInner {
460 h: RwLock::new(Some(h)),
461 raw: AtomicIsize::new(h.0 as isize),
462 cancelled: AtomicBool::new(false),
463 closing: Mutex::new(()),
464 }))
465 }
466
467 fn cancel(&self) {
470 let _one = self.0.closing.lock().unwrap_or_else(|e| e.into_inner());
471 self.0.cancelled.store(true, Ordering::SeqCst);
472 let raw = HANDLE(self.0.raw.load(Ordering::SeqCst) as *mut _);
473 if self.0.h.read().map(|g| g.is_some()).unwrap_or(false) {
474 unsafe {
476 let _ = CancelIoEx(raw, None);
477 }
478 }
479 let mut g = self.0.h.write().unwrap_or_else(|e| e.into_inner()); if let Some(h) = g.take() {
481 unsafe {
482 let _ = CloseHandle(h);
483 }
484 }
485 }
486
487 fn run(
490 &self,
491 start: impl FnOnce(HANDLE, *mut OVERLAPPED) -> windows::core::Result<()>,
492 ) -> io::Result<usize> {
493 if self.0.cancelled.load(Ordering::SeqCst) {
494 return Err(io::Error::new(
495 io::ErrorKind::ConnectionAborted,
496 "connection closed",
497 ));
498 }
499 let guard = self.0.h.read().unwrap_or_else(|e| e.into_inner());
500 let Some(handle) = *guard else {
501 return Err(io::Error::new(
502 io::ErrorKind::ConnectionAborted,
503 "connection closed",
504 ));
505 };
506 unsafe {
507 let ev =
508 CreateEventW(None, true, false, PCWSTR::null()).map_err(io::Error::other)?;
509 let mut ov = OVERLAPPED {
510 hEvent: ev,
511 ..Default::default()
512 };
513 let started = start(handle, &mut ov);
514 if self.0.cancelled.load(Ordering::SeqCst) {
517 let _ = CancelIoEx(handle, Some(&ov));
518 }
519 let outcome = match started {
520 Ok(()) => Ok(true),
521 Err(e) if code(&e) == ERROR_IO_PENDING => Ok(true),
522 Err(e) if code(&e) == ERROR_PIPE_CONNECTED => Ok(false),
524 Err(e) => Err(e),
525 };
526 let res = match outcome {
527 Ok(false) => Ok(0),
528 Ok(true) => {
529 let mut n = 0u32;
530 match GetOverlappedResult(handle, &ov, &mut n, true) {
532 Ok(()) => Ok(n as usize),
533 Err(e) => Err(e),
534 }
535 }
536 Err(e) => Err(e),
537 };
538 let _ = CloseHandle(ev);
539 match res {
540 Ok(n) => Ok(n),
541 Err(e) => match code(&e) {
542 ERROR_BROKEN_PIPE | ERROR_NO_DATA => {
543 Err(io::Error::new(io::ErrorKind::BrokenPipe, "pipe closed"))
544 }
545 ERROR_OPERATION_ABORTED => Err(io::Error::new(
546 io::ErrorKind::ConnectionAborted,
547 "operation cancelled",
548 )),
549 _ => Err(io::Error::other(e)),
550 },
551 }
552 }
553 }
554 }
555
556 struct Half(Pipe);
557 impl Read for Half {
558 fn read(&mut self, b: &mut [u8]) -> io::Result<usize> {
559 match self
560 .0
561 .run(|h, ov| unsafe { ReadFile(h, Some(b), None, Some(ov)) })
562 {
563 Err(e) if e.kind() == io::ErrorKind::BrokenPipe => Ok(0), r => r,
565 }
566 }
567 }
568 impl Write for Half {
569 fn write(&mut self, b: &[u8]) -> io::Result<usize> {
570 self.0
571 .run(|h, ov| unsafe { WriteFile(h, Some(b), None, Some(ov)) })
572 }
573 fn flush(&mut self) -> io::Result<()> {
574 Ok(()) }
576 }
577
578 fn wrap(pipe: Pipe) -> Conn {
579 let (w, watch, sd) = (pipe.clone(), pipe.clone(), pipe.clone());
580 Conn {
581 peer: crate::peer::PeerIdentity::default(),
582 reader: Box::new(Half(pipe)),
583 writer: Box::new(Half(w)),
584 peer_closed: Arc::new(move || {
585 if watch.0.cancelled.load(Ordering::SeqCst) {
586 return true;
587 }
588 let g = watch.0.h.read().unwrap_or_else(|e| e.into_inner());
590 match *g {
591 Some(h) => unsafe { PeekNamedPipe(h, None, 0, None, None, None).is_err() },
592 None => true,
593 }
594 }),
595 shutdown: Arc::new(move || sd.cancel()),
598 }
599 }
600
601 impl Listener {
602 pub fn bind(endpoint: &str, max_instances: u32) -> io::Result<Listener> {
605 let attrs = owner_only_security_attributes()?;
606 let l = Listener {
607 name: wide(endpoint),
608 attrs,
609 max_instances: max_instances.clamp(1, 254),
610 first: AtomicBool::new(true),
611 endpoint: endpoint.to_string(),
612 pending: Mutex::new(None),
613 };
614 let h = l.instance().map_err(|e| {
618 if e.raw_os_error() == Some(5) {
619 io::Error::new(
620 io::ErrorKind::AddrInUse,
621 format!("pipe {endpoint} still held: {e}"),
622 )
623 } else {
624 e
625 }
626 })?;
627 *l.pending.lock().unwrap_or_else(|e| e.into_inner()) = Some(SendHandle(h));
628 Ok(l)
629 }
630
631 fn instance(&self) -> io::Result<HANDLE> {
632 let mut open = PIPE_ACCESS_DUPLEX;
633 if self.first.swap(false, Ordering::SeqCst) {
634 open |= FILE_FLAG_FIRST_PIPE_INSTANCE;
635 }
636 let h = unsafe {
637 CreateNamedPipeW(
638 PCWSTR(self.name.as_ptr()),
639 open | FILE_FLAG_OVERLAPPED,
640 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
641 self.max_instances,
642 64 * 1024,
643 64 * 1024,
644 0,
645 Some(self.attrs.raw()),
646 )
647 };
648 if h.is_invalid() {
649 return Err(io::Error::last_os_error());
650 }
651 Ok(h)
652 }
653
654 pub fn accept(&self) -> io::Result<Accepted> {
655 let pending = self
656 .pending
657 .lock()
658 .unwrap_or_else(|e| e.into_inner())
659 .take();
660 let h = match pending {
661 Some(p) => p.0,
662 None => self.instance()?,
663 };
664 let pipe = Pipe::new(h); let connected = pipe.run(|h, ov| unsafe { ConnectNamedPipe(h, Some(ov)) });
666 connected.map_err(|e| io::Error::new(e.kind(), format!("accept: {e}")))?;
667 let raw = pipe.0.raw.load(Ordering::SeqCst) as RawHandle;
670 match verify_pipe_peer_is_current_user(raw) {
671 Ok(peer) => Ok(Accepted::Conn(Conn { peer, ..wrap(pipe) })),
672 Err(e) => {
673 pipe.cancel();
674 if e.peer_gone() {
675 Ok(Accepted::Vanished)
676 } else if let Some(refusal) = e.refusal() {
677 Ok(Accepted::Unidentified(refusal))
678 } else {
679 Ok(Accepted::Rejected(e.to_string()))
680 }
681 }
682 }
683 }
684 pub fn wake(&self) {
685 let _ = open_overlapped(&self.endpoint);
686 }
687 pub fn cleanup(&self) {}
688 }
689
690 pub fn connect(endpoint: &str) -> io::Result<Conn> {
694 let pipe = open_overlapped(endpoint)?;
695 let raw = pipe.0.raw.load(Ordering::SeqCst) as RawHandle;
696 if let Err(e) = verify_pipe_server_is_current_user(raw) {
697 pipe.cancel();
698 return Err(e.into());
699 }
700 Ok(wrap(pipe))
701 }
702
703 pub fn connect_expecting(
707 endpoint: &str,
708 expect: &HostExpectation,
709 ) -> Result<(Conn, HostProcess), HostConnectError> {
710 let pipe = open_overlapped(endpoint).map_err(HostConnectError::Io)?;
711 let raw = pipe.0.raw.load(Ordering::SeqCst) as RawHandle;
712 if let Err(e) = verify_pipe_server_is_current_user(raw) {
713 pipe.cancel();
714 return Err(HostConnectError::Io(e.into()));
715 }
716 match crate::host_identity::verify_pipe_host(raw, expect) {
717 Ok(host) => Ok((wrap(pipe), host)),
718 Err(e) => {
719 pipe.cancel();
720 Err(e)
721 }
722 }
723 }
724
725 fn open_overlapped(endpoint: &str) -> io::Result<Pipe> {
726 use std::os::windows::fs::OpenOptionsExt;
727 const ERROR_FILE_NOT_FOUND: i32 = 2;
728 const ERROR_PIPE_BUSY: i32 = 231;
729 let start = std::time::Instant::now();
730 loop {
731 match std::fs::OpenOptions::new()
732 .read(true)
733 .write(true)
734 .custom_flags(FILE_FLAG_OVERLAPPED_BITS)
735 .security_qos_flags(SECURITY_SQOS_PRESENT_BITS | SECURITY_IDENTIFICATION_BITS)
736 .open(endpoint)
737 {
738 Ok(f) => return Ok(Pipe::new(HANDLE(f.into_raw_handle() as *mut _))),
739 Err(e) => {
740 let limit = match e.raw_os_error() {
741 Some(ERROR_PIPE_BUSY) => std::time::Duration::from_secs(3),
742 Some(ERROR_FILE_NOT_FOUND) => std::time::Duration::from_secs(1),
743 _ => return Err(e),
744 };
745 if start.elapsed() >= limit {
746 return Err(e);
747 }
748 std::thread::sleep(std::time::Duration::from_millis(25));
749 }
750 }
751 }
752 }
753}
754
755pub use imp::Listener;
756pub(crate) use imp::{connect, connect_expecting, current_uid};
757
758#[cfg(unix)]
763pub fn pid_alive(pid: u32) -> bool {
764 extern "C" {
765 fn kill(pid: i32, sig: i32) -> i32;
766 }
767 if pid == 0 || pid > i32::MAX as u32 {
768 return false;
769 }
770 unsafe {
771 kill(pid as i32, 0) == 0 || io::Error::last_os_error().raw_os_error() == Some(1)
772 }
774}
775#[cfg(windows)]
776pub fn pid_alive(pid: u32) -> bool {
777 use windows::Win32::Foundation::{CloseHandle, ERROR_ACCESS_DENIED};
778 use windows::Win32::System::Threading::{
779 GetExitCodeProcess, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
780 };
781 const STILL_ACTIVE: u32 = 259;
782 if pid == 0 {
783 return false;
784 }
785 unsafe {
786 match OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) {
787 Ok(h) => {
788 let mut code = 0u32;
789 let ok = GetExitCodeProcess(h, &mut code).is_ok();
790 let _ = CloseHandle(h);
791 ok && code == STILL_ACTIVE
792 }
793 Err(e) => e.code() == ERROR_ACCESS_DENIED.to_hresult(),
794 }
795 }
796}
797
798pub struct Stream {
803 pub reader: Box<dyn Read + Send>,
804 pub writer: Box<dyn Write + Send>,
805 shutdown: Arc<dyn Fn() + Send + Sync>,
806}
807
808impl Stream {
809 pub fn abort_handle(&self) -> Arc<dyn Fn() + Send + Sync> {
812 self.shutdown.clone()
813 }
814}
815
816pub fn open_stream(endpoint: &str) -> io::Result<Stream> {
823 let c = connect(endpoint)?;
824 Ok(Stream {
825 reader: c.reader,
826 writer: c.writer,
827 shutdown: c.shutdown,
828 })
829}
830
831pub fn open_stream_expecting(
838 endpoint: &str,
839 expect: &HostExpectation,
840) -> Result<Stream, crate::ConnectError> {
841 match connect_expecting(endpoint, expect) {
842 Ok((c, _host)) => Ok(Stream {
843 reader: c.reader,
844 writer: c.writer,
845 shutdown: c.shutdown,
846 }),
847 Err(HostConnectError::Io(e)) => Err(crate::ConnectError::Service(
848 crate::ServiceError::retryable(
849 crate::ERROR_UNAVAILABLE,
850 format!("host unreachable: {e}"),
851 ),
852 )),
853 Err(HostConnectError::Identity(e)) => Err(crate::ConnectError::HostIdentity(e)),
854 }
855}