1use std::ffi::{OsStr, OsString};
4use std::fmt;
5use std::mem::{size_of, zeroed};
6use std::os::windows::ffi::OsStrExt;
7use std::path::Path;
8use std::ptr::NonNull;
9use std::time::{Duration, Instant};
10
11use native_ipc_core::layout::{RegionSetLayout, ValidatedRegionLayout, ValidationExpectations};
12use native_ipc_core::mapping::{
13 BindingError, ReadOnlyMapping, ReaderRegion, SoleWriterMapping, WriterRegion,
14};
15use windows_sys::Win32::Foundation::{
16 CloseHandle, DuplicateHandle, ERROR_NO_DATA, ERROR_PIPE_CONNECTED, ERROR_PIPE_LISTENING,
17 GENERIC_READ, GENERIC_WRITE, GetLastError, HANDLE, INVALID_HANDLE_VALUE, WAIT_OBJECT_0,
18 WAIT_TIMEOUT,
19};
20use windows_sys::Win32::Security::Cryptography::{
21 BCRYPT_USE_SYSTEM_PREFERRED_RNG, BCryptGenRandom,
22};
23use windows_sys::Win32::Storage::FileSystem::{
24 CreateFileW, FILE_FLAG_FIRST_PIPE_INSTANCE, OPEN_EXISTING, PIPE_ACCESS_DUPLEX, ReadFile,
25 WriteFile,
26};
27use windows_sys::Win32::System::JobObjects::{
28 AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
29 JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation,
30 SetInformationJobObject,
31};
32use windows_sys::Win32::System::Memory::{
33 CreateFileMappingW, FILE_MAP_READ, FILE_MAP_WRITE, MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile,
34 PAGE_READWRITE, SEC_COMMIT, UnmapViewOfFile,
35};
36use windows_sys::Win32::System::Pipes::{
37 ConnectNamedPipe, CreateNamedPipeW, GetNamedPipeClientProcessId, GetNamedPipeServerProcessId,
38 PIPE_NOWAIT, PIPE_READMODE_MESSAGE, PIPE_REJECT_REMOTE_CLIENTS, PIPE_TYPE_MESSAGE,
39 SetNamedPipeHandleState,
40};
41use windows_sys::Win32::System::SystemInformation::{GetSystemInfo, SYSTEM_INFO};
42use windows_sys::Win32::System::Threading::{
43 CREATE_SUSPENDED, CREATE_UNICODE_ENVIRONMENT, CreateProcessW, GetCurrentProcess,
44 GetCurrentProcessId, GetExitCodeProcess, PROCESS_INFORMATION, ResumeThread, STARTUPINFOW,
45 TerminateProcess, WaitForSingleObject,
46};
47
48use crate::BackendStatus;
49use crate::protocol::{CONTROL_FRAME_LEN, ManifestEntry, PeerAccess, TransferManifest};
50
51#[derive(Debug)]
53pub enum WindowsError {
54 Os {
56 operation: &'static str,
58 code: u32,
60 },
61 InvalidSize(usize),
63 WrongPeer,
65 InvalidHandle,
67 Layout(native_ipc_core::layout::LayoutError),
69 Binding(BindingError),
71 InvalidBootstrap,
73 CapabilityAlreadyTransferred,
75 TimedOut(&'static str),
77 ChildExit(u32),
79}
80
81impl fmt::Display for WindowsError {
82 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(formatter, "Windows transport failed: {self:?}")
84 }
85}
86impl std::error::Error for WindowsError {}
87impl From<native_ipc_core::layout::LayoutError> for WindowsError {
88 fn from(value: native_ipc_core::layout::LayoutError) -> Self {
89 Self::Layout(value)
90 }
91}
92impl From<BindingError> for WindowsError {
93 fn from(value: BindingError) -> Self {
94 Self::Binding(value)
95 }
96}
97
98pub fn session_nonce() -> Result<[u8; 32], WindowsError> {
100 let mut nonce = [0_u8; 32];
101 let status = unsafe {
103 BCryptGenRandom(
104 std::ptr::null_mut(),
105 nonce.as_mut_ptr(),
106 nonce.len() as u32,
107 BCRYPT_USE_SYSTEM_PREFERRED_RNG,
108 )
109 };
110 if status < 0 || nonce == [0; 32] {
111 Err(last_os("BCryptGenRandom"))
112 } else {
113 Ok(nonce)
114 }
115}
116
117pub unsafe fn authenticate_pipe_client(
123 pipe: HANDLE,
124 expected_pid: u32,
125) -> Result<(), WindowsError> {
126 let mut actual = 0;
127 if unsafe { GetNamedPipeClientProcessId(pipe, &mut actual) } == 0 {
129 return Err(last_os("GetNamedPipeClientProcessId"));
130 }
131 if actual == expected_pid {
132 Ok(())
133 } else {
134 Err(WindowsError::WrongPeer)
135 }
136}
137
138pub unsafe fn authenticate_pipe_server(
144 pipe: HANDLE,
145 expected_pid: u32,
146) -> Result<(), WindowsError> {
147 let mut actual = 0;
148 if unsafe { GetNamedPipeServerProcessId(pipe, &mut actual) } == 0 {
150 return Err(last_os("GetNamedPipeServerProcessId"));
151 }
152 if actual == expected_pid {
153 Ok(())
154 } else {
155 Err(WindowsError::WrongPeer)
156 }
157}
158
159pub struct QuiescentRegion {
161 section: OwnedHandle,
162 view: View,
163 logical_len: usize,
164}
165
166impl QuiescentRegion {
167 pub fn new(logical_len: usize) -> Result<Self, WindowsError> {
169 let len = page_align(logical_len)?;
170 let size = len as u64;
171 let section = unsafe {
173 CreateFileMappingW(
174 INVALID_HANDLE_VALUE,
175 std::ptr::null(),
176 PAGE_READWRITE | SEC_COMMIT,
177 (size >> 32) as u32,
178 size as u32,
179 std::ptr::null(),
180 )
181 };
182 let section = OwnedHandle::new(section)?;
183 let view = View::map(section.0, len, FILE_MAP_WRITE)?;
184 unsafe { std::slice::from_raw_parts_mut(view.base.as_ptr(), len) }.fill(0);
186 Ok(Self {
187 section,
188 view,
189 logical_len,
190 })
191 }
192 pub const fn len(&self) -> usize {
194 self.view.len
195 }
196 pub const fn is_empty(&self) -> bool {
198 false
199 }
200 pub const fn logical_len(&self) -> usize {
202 self.logical_len
203 }
204 pub fn as_bytes(&self) -> &[u8] {
206 unsafe { std::slice::from_raw_parts(self.view.base.as_ptr(), self.view.len) }
208 }
209 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
211 unsafe { std::slice::from_raw_parts_mut(self.view.base.as_ptr(), self.view.len) }
213 }
214
215 pub fn prepare_local_writer(
217 self,
218 expected: ValidationExpectations,
219 topology: RegionSetLayout,
220 ) -> Result<PreparedLocalWriter, WindowsError> {
221 let layout =
223 unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
224 let len = self.view.len;
225 let entry = ManifestEntry::validated(expected, len, PeerAccess::ReadOnly)
226 .ok_or(WindowsError::InvalidBootstrap)?;
227 Ok(PreparedLocalWriter {
228 section: self.section,
229 runtime: WriterRegion::new(WindowsWriterMapping { view: self.view }, layout, topology)?,
230 entry,
231 len,
232 reader_duplicated: false,
233 })
234 }
235
236 pub fn prepare_remote_writer(
238 self,
239 expected: ValidationExpectations,
240 topology: RegionSetLayout,
241 ) -> Result<PreparedRemoteWriter, WindowsError> {
242 let layout =
244 unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
245 let len = self.view.len;
246 drop(self.view);
247 let view = View::map(self.section.0, len, FILE_MAP_READ)?;
248 let entry = ManifestEntry::validated(expected, len, PeerAccess::SoleWriter)
249 .ok_or(WindowsError::InvalidBootstrap)?;
250 Ok(PreparedRemoteWriter {
251 section: self.section,
252 runtime: ReaderRegion::new(WindowsReaderMapping { view }, layout, topology)?,
253 entry,
254 len,
255 writer_duplicated: false,
256 })
257 }
258}
259
260#[derive(Clone, Copy, Debug, Eq, PartialEq)]
262pub struct RemoteHandle(pub usize);
263
264pub struct PreparedLocalWriter {
273 section: OwnedHandle,
274 runtime: WriterRegion<WindowsWriterMapping>,
275 entry: ManifestEntry,
276 len: usize,
277 reader_duplicated: bool,
278}
279impl PreparedLocalWriter {
280 unsafe fn duplicate_reader_to(
286 &mut self,
287 target_process: HANDLE,
288 ) -> Result<RemoteHandle, WindowsError> {
289 if self.reader_duplicated {
290 return Err(WindowsError::CapabilityAlreadyTransferred);
291 }
292 let handle = duplicate_to(self.section.0, target_process, FILE_MAP_READ)?;
293 self.reader_duplicated = true;
294 Ok(handle)
295 }
296}
297
298pub struct PreparedRemoteWriter {
300 section: OwnedHandle,
301 runtime: ReaderRegion<WindowsReaderMapping>,
302 entry: ManifestEntry,
303 len: usize,
304 writer_duplicated: bool,
305}
306impl PreparedRemoteWriter {
307 unsafe fn duplicate_writer_to(
313 &mut self,
314 target_process: HANDLE,
315 ) -> Result<RemoteHandle, WindowsError> {
316 if self.writer_duplicated {
317 return Err(WindowsError::CapabilityAlreadyTransferred);
318 }
319 let handle = duplicate_to(self.section.0, target_process, FILE_MAP_WRITE)?;
320 self.writer_duplicated = true;
321 Ok(handle)
322 }
323}
324
325pub struct PendingImportedReader {
334 runtime: ReaderRegion<WindowsReaderMapping>,
335 entry: ManifestEntry,
336}
337
338pub struct PendingImportedWriter {
340 runtime: WriterRegion<WindowsWriterMapping>,
341 entry: ManifestEntry,
342}
343
344pub unsafe fn import_reader(
351 handle: usize,
352 len: usize,
353 expected: ValidationExpectations,
354 topology: RegionSetLayout,
355) -> Result<PendingImportedReader, WindowsError> {
356 let section = OwnedHandle::new(handle as HANDLE)?;
357 let view = View::map(section.0, len, FILE_MAP_READ)?;
358 let bytes = unsafe { std::slice::from_raw_parts(view.base.as_ptr(), len) };
360 let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
361 let entry = ManifestEntry::validated(expected, len, PeerAccess::ReadOnly)
362 .ok_or(WindowsError::InvalidBootstrap)?;
363 drop(section);
364 Ok(PendingImportedReader {
365 runtime: ReaderRegion::new(WindowsReaderMapping { view }, layout, topology)?,
366 entry,
367 })
368}
369
370pub unsafe fn import_writer(
377 handle: usize,
378 len: usize,
379 expected: ValidationExpectations,
380 topology: RegionSetLayout,
381) -> Result<PendingImportedWriter, WindowsError> {
382 let section = OwnedHandle::new(handle as HANDLE)?;
383 let view = View::map(section.0, len, FILE_MAP_WRITE)?;
384 let bytes = unsafe { std::slice::from_raw_parts(view.base.as_ptr(), len) };
386 let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
387 let entry = ManifestEntry::validated(expected, len, PeerAccess::SoleWriter)
388 .ok_or(WindowsError::InvalidBootstrap)?;
389 drop(section);
390 Ok(PendingImportedWriter {
391 runtime: WriterRegion::new(WindowsWriterMapping { view }, layout, topology)?,
392 entry,
393 })
394}
395
396pub struct ChildJob(OwnedHandle);
398impl ChildJob {
399 pub fn new() -> Result<Self, WindowsError> {
401 let handle = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) };
403 let handle = OwnedHandle::new(handle)?;
404 let mut information: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { zeroed() };
405 information.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
406 if unsafe {
408 SetInformationJobObject(
409 handle.0,
410 JobObjectExtendedLimitInformation,
411 (&information as *const JOBOBJECT_EXTENDED_LIMIT_INFORMATION).cast(),
412 size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
413 )
414 } == 0
415 {
416 return Err(last_os("SetInformationJobObject"));
417 }
418 Ok(Self(handle))
419 }
420 pub unsafe fn assign_suspended(&self, process: HANDLE) -> Result<(), WindowsError> {
426 if unsafe { AssignProcessToJobObject(self.0.0, process) } == 0 {
428 Err(last_os("AssignProcessToJobObject"))
429 } else {
430 Ok(())
431 }
432 }
433}
434
435const PIPE_ENV: &str = "NATIVE_IPC_WINDOWS_PIPE";
436const NONCE_ENV: &str = "NATIVE_IPC_WINDOWS_NONCE";
437const PARENT_ENV: &str = "NATIVE_IPC_PARENT_PID";
438const BOOTSTRAP_MAGIC: [u8; 8] = *b"NIPCWIN1";
439const AUTH_MAGIC: [u8; 8] = *b"NIPCAUT1";
440const READY_MAGIC: [u8; 8] = *b"NIPCRDY1";
441const COMMIT_MAGIC: [u8; 8] = *b"NIPCCMT1";
442const CAPABILITY_MAGIC: [u8; 8] = *b"NIPCCAP1";
443#[cfg(not(test))]
444const WAIT_MS: u32 = 10_000;
445#[cfg(test)]
446const WAIT_MS: u32 = 1_000;
447
448#[repr(C)]
449#[derive(Clone, Copy)]
450struct BootstrapFrame {
451 magic: [u8; 8],
452 nonce: [u8; 32],
453 parent_pid: u32,
454 child_pid: u32,
455}
456
457const CAPABILITY_FRAME_LEN: usize = 40 + CONTROL_FRAME_LEN;
458
459fn encode_capability_frame(
460 reader: RemoteHandle,
461 reader_len: usize,
462 writer: RemoteHandle,
463 writer_len: usize,
464 transcript: &[u8; CONTROL_FRAME_LEN],
465) -> Result<[u8; CAPABILITY_FRAME_LEN], WindowsError> {
466 let mut frame = [0_u8; CAPABILITY_FRAME_LEN];
467 frame[..8].copy_from_slice(&CAPABILITY_MAGIC);
468 frame[8..16].copy_from_slice(
469 &u64::try_from(reader.0)
470 .map_err(|_| WindowsError::InvalidBootstrap)?
471 .to_le_bytes(),
472 );
473 frame[16..24].copy_from_slice(
474 &u64::try_from(writer.0)
475 .map_err(|_| WindowsError::InvalidBootstrap)?
476 .to_le_bytes(),
477 );
478 frame[24..32].copy_from_slice(
479 &u64::try_from(reader_len)
480 .map_err(|_| WindowsError::InvalidBootstrap)?
481 .to_le_bytes(),
482 );
483 frame[32..40].copy_from_slice(
484 &u64::try_from(writer_len)
485 .map_err(|_| WindowsError::InvalidBootstrap)?
486 .to_le_bytes(),
487 );
488 frame[40..].copy_from_slice(transcript);
489 Ok(frame)
490}
491
492pub struct ChildSession {
494 pipe: OwnedHandle,
495 process: OwnedHandle,
496 _job: ChildJob,
497 pid: u32,
498 nonce: [u8; 32],
499 reaped: bool,
500 next_transfer_id: u64,
501 pending_manifest: Option<TransferManifest>,
502}
503
504impl ChildSession {
505 pub fn spawn(path: &Path, arguments: &[OsString]) -> Result<Self, WindowsError> {
507 let nonce = session_nonce()?;
508 let name = format!(r"\\.\pipe\native-ipc-{}", hex(&nonce));
509 let pipe_name = wide_null(OsStr::new(&name));
510 let pipe = unsafe {
512 CreateNamedPipeW(
513 pipe_name.as_ptr(),
514 PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
515 PIPE_TYPE_MESSAGE
516 | PIPE_READMODE_MESSAGE
517 | PIPE_NOWAIT
518 | PIPE_REJECT_REMOTE_CLIENTS,
519 1,
520 4096,
521 4096,
522 WAIT_MS,
523 std::ptr::null(),
524 )
525 };
526 let pipe = OwnedHandle::new(pipe)?;
527 let job = ChildJob::new()?;
528
529 let application = wide_null(path.as_os_str());
530 let mut command = command_line(path.as_os_str(), arguments);
531 let parent_pid = unsafe { GetCurrentProcessId() };
532 let environment = environment_block(&[
533 (PIPE_ENV, name),
534 (NONCE_ENV, hex(&nonce)),
535 (PARENT_ENV, parent_pid.to_string()),
536 ]);
537 let mut startup: STARTUPINFOW = unsafe { zeroed() };
538 startup.cb = size_of::<STARTUPINFOW>() as u32;
539 let mut information: PROCESS_INFORMATION = unsafe { zeroed() };
540 if unsafe {
542 CreateProcessW(
543 application.as_ptr(),
544 command.as_mut_ptr(),
545 std::ptr::null(),
546 std::ptr::null(),
547 0,
548 CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT,
549 environment.as_ptr().cast(),
550 std::ptr::null(),
551 &startup,
552 &mut information,
553 )
554 } == 0
555 {
556 return Err(last_os("CreateProcessW"));
557 }
558 let process = OwnedHandle::new(information.hProcess)?;
559 let thread = OwnedHandle::new(information.hThread)?;
560 if let Err(error) = unsafe { job.assign_suspended(process.0) } {
562 let _ = unsafe { TerminateProcess(process.0, 127) };
564 return Err(error);
565 }
566 if unsafe { ResumeThread(thread.0) } == u32::MAX {
568 let error = last_os("ResumeThread");
569 let _ = unsafe { TerminateProcess(process.0, 127) };
570 return Err(error);
571 }
572 drop(thread);
573 connect_pipe(pipe.0, process.0)?;
574 unsafe { authenticate_pipe_client(pipe.0, information.dwProcessId)? };
576 let hello = BootstrapFrame {
577 magic: BOOTSTRAP_MAGIC,
578 nonce,
579 parent_pid,
580 child_pid: information.dwProcessId,
581 };
582 write_frame(pipe.0, &hello)?;
583 let ready = read_frame(pipe.0)?;
584 if ready.magic != AUTH_MAGIC
585 || ready.nonce != nonce
586 || ready.parent_pid != parent_pid
587 || ready.child_pid != information.dwProcessId
588 {
589 let _ = unsafe { TerminateProcess(process.0, 127) };
590 return Err(WindowsError::InvalidBootstrap);
591 }
592 Ok(Self {
593 pipe,
594 process,
595 _job: job,
596 pid: information.dwProcessId,
597 nonce,
598 reaped: false,
599 next_transfer_id: 1,
600 pending_manifest: None,
601 })
602 }
603
604 pub const fn process_handle(&self) -> HANDLE {
606 self.process.0
607 }
608 pub const fn pid(&self) -> u32 {
610 self.pid
611 }
612 fn send_capabilities(
614 &mut self,
615 reader_handle: RemoteHandle,
616 reader: &PreparedLocalWriter,
617 writer_handle: RemoteHandle,
618 remote_writer: &PreparedRemoteWriter,
619 ) -> Result<(), WindowsError> {
620 if self.pending_manifest.is_some() {
621 return Err(WindowsError::InvalidBootstrap);
622 }
623 let manifest = TransferManifest::new(
624 self.nonce,
625 unsafe { GetCurrentProcessId() },
626 self.pid,
627 self.next_transfer_id,
628 vec![reader.entry, remote_writer.entry],
629 )
630 .ok_or(WindowsError::InvalidBootstrap)?;
631 let frame = encode_capability_frame(
632 reader_handle,
633 reader.len,
634 writer_handle,
635 remote_writer.len,
636 &manifest.encode(CAPABILITY_MAGIC),
637 )?;
638 write_pod(self.pipe.0, &frame)?;
639 self.pending_manifest = Some(manifest);
640 Ok(())
641 }
642 pub fn commit_transfers(
667 &mut self,
668 writer: PreparedLocalWriter,
669 reader: PreparedRemoteWriter,
670 ) -> Result<
671 (
672 WriterRegion<WindowsWriterMapping>,
673 ReaderRegion<WindowsReaderMapping>,
674 ),
675 WindowsError,
676 > {
677 let result = self.commit_transfers_inner(writer, reader);
678 if result.is_err() {
679 self.abort_child();
680 }
681 result
682 }
683
684 fn commit_transfers_inner(
685 &mut self,
686 mut writer: PreparedLocalWriter,
687 mut reader: PreparedRemoteWriter,
688 ) -> Result<
689 (
690 WriterRegion<WindowsWriterMapping>,
691 ReaderRegion<WindowsReaderMapping>,
692 ),
693 WindowsError,
694 > {
695 let reader_handle = unsafe { writer.duplicate_reader_to(self.process.0)? };
697 let writer_handle = unsafe { reader.duplicate_writer_to(self.process.0)? };
699 self.send_capabilities(reader_handle, &writer, writer_handle, &reader)?;
700 let manifest = self
701 .pending_manifest
702 .as_ref()
703 .ok_or(WindowsError::InvalidBootstrap)?;
704 let ready: [u8; CONTROL_FRAME_LEN] = read_pod(self.pipe.0)?;
705 if !manifest.matches_frame(READY_MAGIC, &ready) {
706 return Err(WindowsError::InvalidBootstrap);
707 }
708 write_pod(self.pipe.0, &manifest.encode(COMMIT_MAGIC))?;
709 drop(writer.section);
710 drop(reader.section);
711 self.pending_manifest = None;
712 self.next_transfer_id = self
713 .next_transfer_id
714 .checked_add(1)
715 .ok_or(WindowsError::InvalidBootstrap)?;
716 Ok((writer.runtime, reader.runtime))
717 }
718
719 fn abort_child(&mut self) {
720 if !self.reaped {
721 let _ = unsafe { TerminateProcess(self.process.0, 127) };
723 let _ = unsafe { WaitForSingleObject(self.process.0, WAIT_MS) };
725 self.reaped = true;
726 }
727 }
728 pub fn wait(mut self) -> Result<(), WindowsError> {
730 match unsafe { WaitForSingleObject(self.process.0, WAIT_MS) } {
732 WAIT_OBJECT_0 => {
733 let mut code = 0;
734 if unsafe { GetExitCodeProcess(self.process.0, &mut code) } == 0 {
736 return Err(last_os("GetExitCodeProcess"));
737 }
738 if code != 0 {
739 return Err(WindowsError::ChildExit(code));
740 }
741 }
742 WAIT_TIMEOUT => return Err(WindowsError::TimedOut("helper exit")),
743 _ => return Err(last_os("WaitForSingleObject")),
744 }
745 self.reaped = true;
746 Ok(())
747 }
748}
749
750impl Drop for ChildSession {
751 fn drop(&mut self) {
752 if !self.reaped {
753 let _ = unsafe { TerminateProcess(self.process.0, 127) };
755 let _ = unsafe { WaitForSingleObject(self.process.0, WAIT_MS) };
756 }
757 }
758}
759
760pub fn connect_spawned_helper() -> Result<ChildChannel, WindowsError> {
762 let name = std::env::var_os(PIPE_ENV).ok_or(WindowsError::InvalidBootstrap)?;
763 let nonce =
764 parse_nonce(&std::env::var(NONCE_ENV).map_err(|_| WindowsError::InvalidBootstrap)?)?;
765 let parent_pid = std::env::var(PARENT_ENV)
766 .map_err(|_| WindowsError::InvalidBootstrap)?
767 .parse::<u32>()
768 .map_err(|_| WindowsError::InvalidBootstrap)?;
769 let name = wide_null(&name);
770 let pipe = unsafe {
772 CreateFileW(
773 name.as_ptr(),
774 GENERIC_READ | GENERIC_WRITE,
775 0,
776 std::ptr::null(),
777 OPEN_EXISTING,
778 0,
779 std::ptr::null_mut(),
780 )
781 };
782 let pipe = OwnedHandle::new(pipe)?;
783 let mode = PIPE_READMODE_MESSAGE | PIPE_NOWAIT;
784 if unsafe { SetNamedPipeHandleState(pipe.0, &mode, std::ptr::null(), std::ptr::null()) } == 0 {
786 return Err(last_os("SetNamedPipeHandleState"));
787 }
788 unsafe { authenticate_pipe_server(pipe.0, parent_pid)? };
790 let hello = read_frame(pipe.0)?;
791 let child_pid = unsafe { GetCurrentProcessId() };
792 if hello.magic != BOOTSTRAP_MAGIC
793 || hello.nonce != nonce
794 || hello.parent_pid != parent_pid
795 || hello.child_pid != child_pid
796 {
797 return Err(WindowsError::InvalidBootstrap);
798 }
799 write_frame(
800 pipe.0,
801 &BootstrapFrame {
802 magic: AUTH_MAGIC,
803 ..hello
804 },
805 )?;
806 Ok(ChildChannel {
807 pipe,
808 parent_pid,
809 nonce,
810 next_transfer_id: 1,
811 pending_transcript: None,
812 })
813}
814
815pub struct ChildChannel {
817 pipe: OwnedHandle,
818 parent_pid: u32,
819 nonce: [u8; 32],
820 next_transfer_id: u64,
821 pending_transcript: Option<[u8; CONTROL_FRAME_LEN]>,
822}
823impl ChildChannel {
824 pub const fn parent_pid(&self) -> u32 {
826 self.parent_pid
827 }
828 pub const fn pipe_handle(&self) -> HANDLE {
830 self.pipe.0
831 }
832 pub fn receive_capabilities(
843 &mut self,
844 ) -> Result<(RemoteHandle, usize, RemoteHandle, usize), WindowsError> {
845 if self.pending_transcript.is_some() {
846 return Err(WindowsError::InvalidBootstrap);
847 }
848 let frame: [u8; CAPABILITY_FRAME_LEN] = read_pod(self.pipe.0)?;
849 if frame[..8] != CAPABILITY_MAGIC {
850 return Err(WindowsError::InvalidBootstrap);
851 }
852 let reader_handle = usize::try_from(u64::from_le_bytes(
853 frame[8..16].try_into().expect("fixed range"),
854 ))
855 .map_err(|_| WindowsError::InvalidBootstrap)?;
856 let writer_handle = usize::try_from(u64::from_le_bytes(
857 frame[16..24].try_into().expect("fixed range"),
858 ))
859 .map_err(|_| WindowsError::InvalidBootstrap)?;
860 let reader_len = usize::try_from(u64::from_le_bytes(
861 frame[24..32].try_into().expect("fixed range"),
862 ))
863 .map_err(|_| WindowsError::InvalidBootstrap)?;
864 let writer_len = usize::try_from(u64::from_le_bytes(
865 frame[32..40].try_into().expect("fixed range"),
866 ))
867 .map_err(|_| WindowsError::InvalidBootstrap)?;
868 if reader_handle == 0 || writer_handle == 0 || reader_len == 0 || writer_len == 0 {
869 return Err(WindowsError::InvalidBootstrap);
870 }
871 let mut transcript = [0; CONTROL_FRAME_LEN];
872 transcript.copy_from_slice(&frame[40..]);
873 self.pending_transcript = Some(transcript);
874 Ok((
875 RemoteHandle(reader_handle),
876 reader_len,
877 RemoteHandle(writer_handle),
878 writer_len,
879 ))
880 }
881 pub fn commit_imports(
890 &mut self,
891 reader: PendingImportedReader,
892 writer: PendingImportedWriter,
893 ) -> Result<
894 (
895 ReaderRegion<WindowsReaderMapping>,
896 WriterRegion<WindowsWriterMapping>,
897 ),
898 WindowsError,
899 > {
900 let manifest = TransferManifest::new(
901 self.nonce,
902 self.parent_pid,
903 unsafe { GetCurrentProcessId() },
904 self.next_transfer_id,
905 vec![reader.entry, writer.entry],
906 )
907 .ok_or(WindowsError::InvalidBootstrap)?;
908 let transcript = self
909 .pending_transcript
910 .as_ref()
911 .ok_or(WindowsError::InvalidBootstrap)?;
912 if !manifest.matches_frame(CAPABILITY_MAGIC, transcript) {
913 return Err(WindowsError::InvalidBootstrap);
914 }
915 write_pod(self.pipe.0, &manifest.encode(READY_MAGIC))?;
916 let commit: [u8; CONTROL_FRAME_LEN] = read_pod(self.pipe.0)?;
917 if !manifest.matches_frame(COMMIT_MAGIC, &commit) {
918 return Err(WindowsError::InvalidBootstrap);
919 }
920 self.pending_transcript = None;
921 self.next_transfer_id = self
922 .next_transfer_id
923 .checked_add(1)
924 .ok_or(WindowsError::InvalidBootstrap)?;
925 Ok((reader.runtime, writer.runtime))
926 }
927}
928
929pub struct WindowsWriterMapping {
931 view: View,
932}
933unsafe impl SoleWriterMapping for WindowsWriterMapping {
935 fn base(&self) -> NonNull<u8> {
936 self.view.base
937 }
938 fn len(&self) -> usize {
939 self.view.len
940 }
941}
942pub struct WindowsReaderMapping {
944 view: View,
945}
946unsafe impl ReadOnlyMapping for WindowsReaderMapping {
948 fn base(&self) -> NonNull<u8> {
949 self.view.base
950 }
951 fn len(&self) -> usize {
952 self.view.len
953 }
954}
955
956struct OwnedHandle(HANDLE);
957impl OwnedHandle {
958 fn new(handle: HANDLE) -> Result<Self, WindowsError> {
959 if handle.is_null() || handle == INVALID_HANDLE_VALUE {
960 Err(WindowsError::InvalidHandle)
961 } else {
962 Ok(Self(handle))
963 }
964 }
965}
966impl Drop for OwnedHandle {
967 fn drop(&mut self) {
968 let _ = unsafe { CloseHandle(self.0) };
970 }
971}
972
973struct View {
974 base: NonNull<u8>,
975 len: usize,
976}
977impl View {
978 fn map(section: HANDLE, len: usize, access: u32) -> Result<Self, WindowsError> {
979 let address = unsafe { MapViewOfFile(section, access, 0, 0, len) };
981 let base = NonNull::new(address.Value.cast()).ok_or_else(|| last_os("MapViewOfFile"))?;
982 Ok(Self { base, len })
983 }
984}
985impl Drop for View {
986 fn drop(&mut self) {
987 let _ = unsafe {
989 UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
990 Value: self.base.as_ptr().cast(),
991 })
992 };
993 }
994}
995
996fn duplicate_to(source: HANDLE, target: HANDLE, access: u32) -> Result<RemoteHandle, WindowsError> {
997 let mut remote: HANDLE = std::ptr::null_mut();
998 if unsafe {
1000 DuplicateHandle(
1001 GetCurrentProcess(),
1002 source,
1003 target,
1004 &mut remote,
1005 access,
1006 0,
1007 0,
1008 )
1009 } == 0
1010 {
1011 return Err(last_os("DuplicateHandle"));
1012 }
1013 if remote.is_null() {
1014 Err(WindowsError::InvalidHandle)
1015 } else {
1016 Ok(RemoteHandle(remote as usize))
1017 }
1018}
1019
1020fn page_align(size: usize) -> Result<usize, WindowsError> {
1021 if size == 0 {
1022 return Err(WindowsError::InvalidSize(size));
1023 }
1024 let mut information: SYSTEM_INFO = unsafe { zeroed() };
1025 unsafe { GetSystemInfo(&mut information) };
1027 let page = information.dwPageSize as usize;
1028 if page == 0 || !page.is_power_of_two() {
1029 return Err(WindowsError::InvalidSize(size));
1030 }
1031 size.checked_add(page - 1)
1032 .map(|value| value & !(page - 1))
1033 .filter(|value| *value <= isize::MAX as usize)
1034 .ok_or(WindowsError::InvalidSize(size))
1035}
1036
1037fn wide_null(value: &OsStr) -> Vec<u16> {
1038 value.encode_wide().chain(std::iter::once(0)).collect()
1039}
1040
1041fn quote_argument(value: &OsStr, output: &mut Vec<u16>) {
1042 let units: Vec<u16> = value.encode_wide().collect();
1043 let needs_quotes = units.is_empty()
1044 || units
1045 .iter()
1046 .any(|unit| *unit == b' ' as u16 || *unit == b'\t' as u16);
1047 if !needs_quotes {
1048 output.extend(units);
1049 return;
1050 }
1051 output.push(b'"' as u16);
1052 let mut slashes = 0;
1053 for unit in units {
1054 if unit == b'\\' as u16 {
1055 slashes += 1;
1056 } else if unit == b'"' as u16 {
1057 output.extend(std::iter::repeat_n(b'\\' as u16, slashes * 2 + 1));
1058 output.push(unit);
1059 slashes = 0;
1060 } else {
1061 output.extend(std::iter::repeat_n(b'\\' as u16, slashes));
1062 output.push(unit);
1063 slashes = 0;
1064 }
1065 }
1066 output.extend(std::iter::repeat_n(b'\\' as u16, slashes * 2));
1067 output.push(b'"' as u16);
1068}
1069
1070fn command_line(path: &OsStr, arguments: &[OsString]) -> Vec<u16> {
1071 let mut result = Vec::new();
1072 quote_argument(path, &mut result);
1073 for argument in arguments {
1074 result.push(b' ' as u16);
1075 quote_argument(argument, &mut result);
1076 }
1077 result.push(0);
1078 result
1079}
1080
1081fn environment_block(overrides: &[(&str, String)]) -> Vec<u16> {
1082 let mut values: Vec<(OsString, OsString)> = std::env::vars_os()
1083 .filter(|(key, _)| {
1084 !overrides
1085 .iter()
1086 .any(|(name, _)| key.to_string_lossy().eq_ignore_ascii_case(name))
1087 })
1088 .collect();
1089 values.extend(
1090 overrides
1091 .iter()
1092 .map(|(key, value)| (OsString::from(key), OsString::from(value))),
1093 );
1094 values.sort_by(|left, right| {
1095 left.0
1096 .to_string_lossy()
1097 .to_ascii_lowercase()
1098 .cmp(&right.0.to_string_lossy().to_ascii_lowercase())
1099 });
1100 let mut block = Vec::new();
1101 for (key, value) in values {
1102 block.extend(key.encode_wide());
1103 block.push(b'=' as u16);
1104 block.extend(value.encode_wide());
1105 block.push(0);
1106 }
1107 block.push(0);
1108 block
1109}
1110
1111fn pod_bytes<T>(value: &T) -> &[u8] {
1112 unsafe { std::slice::from_raw_parts((value as *const T).cast(), size_of::<T>()) }
1114}
1115
1116fn write_frame(pipe: HANDLE, frame: &BootstrapFrame) -> Result<(), WindowsError> {
1117 write_pod(pipe, frame)
1118}
1119
1120fn write_pod<T>(pipe: HANDLE, value: &T) -> Result<(), WindowsError> {
1121 let bytes = pod_bytes(value);
1122 let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
1123 loop {
1124 let mut written = 0;
1125 if unsafe {
1127 WriteFile(
1128 pipe,
1129 bytes.as_ptr(),
1130 bytes.len() as u32,
1131 &mut written,
1132 std::ptr::null_mut(),
1133 )
1134 } != 0
1135 {
1136 return if written as usize == bytes.len() {
1137 Ok(())
1138 } else {
1139 Err(WindowsError::InvalidBootstrap)
1140 };
1141 }
1142 let code = unsafe { GetLastError() };
1143 if code != ERROR_NO_DATA && code != ERROR_PIPE_LISTENING {
1144 return Err(WindowsError::Os {
1145 operation: "WriteFile",
1146 code,
1147 });
1148 }
1149 wait_retry(deadline, "pipe write")?;
1150 }
1151}
1152
1153fn read_frame(pipe: HANDLE) -> Result<BootstrapFrame, WindowsError> {
1154 read_pod(pipe)
1155}
1156
1157fn read_pod<T>(pipe: HANDLE) -> Result<T, WindowsError> {
1158 let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
1159 loop {
1160 let mut value: T = unsafe { zeroed() };
1161 let mut read = 0;
1162 if unsafe {
1164 ReadFile(
1165 pipe,
1166 (&mut value as *mut T).cast(),
1167 size_of::<T>() as u32,
1168 &mut read,
1169 std::ptr::null_mut(),
1170 )
1171 } != 0
1172 {
1173 return if read as usize == size_of::<T>() {
1174 Ok(value)
1175 } else {
1176 Err(WindowsError::InvalidBootstrap)
1177 };
1178 }
1179 let code = unsafe { GetLastError() };
1180 if code != ERROR_NO_DATA && code != ERROR_PIPE_LISTENING {
1181 return Err(WindowsError::Os {
1182 operation: "ReadFile",
1183 code,
1184 });
1185 }
1186 wait_retry(deadline, "pipe read")?;
1187 }
1188}
1189
1190fn connect_pipe(pipe: HANDLE, process: HANDLE) -> Result<(), WindowsError> {
1191 let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
1192 loop {
1193 if unsafe { ConnectNamedPipe(pipe, std::ptr::null_mut()) } != 0 {
1195 return Ok(());
1196 }
1197 let code = unsafe { GetLastError() };
1198 if code == ERROR_PIPE_CONNECTED {
1199 return Ok(());
1200 }
1201 if code != ERROR_PIPE_LISTENING && code != ERROR_NO_DATA {
1202 return Err(WindowsError::Os {
1203 operation: "ConnectNamedPipe",
1204 code,
1205 });
1206 }
1207 if unsafe { WaitForSingleObject(process, 0) } == WAIT_OBJECT_0 {
1209 let mut exit = 0;
1210 if unsafe { GetExitCodeProcess(process, &mut exit) } != 0 {
1212 return Err(WindowsError::ChildExit(exit));
1213 }
1214 return Err(last_os("GetExitCodeProcess"));
1215 }
1216 wait_retry(deadline, "pipe connect")?;
1217 }
1218}
1219
1220fn wait_retry(deadline: Instant, operation: &'static str) -> Result<(), WindowsError> {
1221 if Instant::now() >= deadline {
1222 Err(WindowsError::TimedOut(operation))
1223 } else {
1224 std::thread::sleep(Duration::from_millis(1));
1225 Ok(())
1226 }
1227}
1228
1229fn hex(bytes: &[u8; 32]) -> String {
1230 let mut output = String::with_capacity(64);
1231 for byte in bytes {
1232 use std::fmt::Write as _;
1233 write!(&mut output, "{byte:02x}").expect("String writes are infallible");
1234 }
1235 output
1236}
1237
1238fn parse_nonce(value: &str) -> Result<[u8; 32], WindowsError> {
1239 if value.len() != 64 {
1240 return Err(WindowsError::InvalidBootstrap);
1241 }
1242 let mut nonce = [0; 32];
1243 for (index, output) in nonce.iter_mut().enumerate() {
1244 *output = u8::from_str_radix(&value[index * 2..index * 2 + 2], 16)
1245 .map_err(|_| WindowsError::InvalidBootstrap)?;
1246 }
1247 if nonce == [0; 32] {
1248 return Err(WindowsError::InvalidBootstrap);
1249 }
1250 Ok(nonce)
1251}
1252
1253fn last_os(operation: &'static str) -> WindowsError {
1254 WindowsError::Os {
1256 operation,
1257 code: unsafe { GetLastError() },
1258 }
1259}
1260
1261pub const fn status() -> BackendStatus {
1263 BackendStatus::Available
1264}
1265
1266#[cfg(test)]
1267mod tests {
1268 use super::*;
1269 use native_ipc_core::layout::{
1270 AcknowledgementRouteSpec, Endpoint, LayoutLimits, RegionSpec, RoleId,
1271 };
1272 use std::time::Duration;
1273 use windows_sys::Win32::System::Memory::{PAGE_EXECUTE_READWRITE, VirtualProtect};
1274
1275 fn topology() -> (RegionSetLayout, RoleId, RoleId) {
1276 let producer = RoleId::new(1).unwrap();
1277 let peer = RoleId::new(2).unwrap();
1278 let specs = [
1279 RegionSpec {
1280 role: producer,
1281 writer: Endpoint::Initiator,
1282 slot_count: 1,
1283 payload_bytes: 32,
1284 acknowledgement_count: 1,
1285 },
1286 RegionSpec {
1287 role: peer,
1288 writer: Endpoint::Responder,
1289 slot_count: 1,
1290 payload_bytes: 32,
1291 acknowledgement_count: 1,
1292 },
1293 ];
1294 let routes = [
1295 AcknowledgementRouteSpec {
1296 owner: peer,
1297 target: producer,
1298 slot_index: 0,
1299 cell_index: 0,
1300 },
1301 AcknowledgementRouteSpec {
1302 owner: producer,
1303 target: peer,
1304 slot_index: 0,
1305 cell_index: 0,
1306 },
1307 ];
1308 let topology = RegionSetLayout::calculate(
1309 [7; 32],
1310 23,
1311 &specs,
1312 &routes,
1313 LayoutLimits {
1314 maximum_mapping_size: 1 << 20,
1315 maximum_slot_count: 2,
1316 maximum_acknowledgement_count: 2,
1317 maximum_payload_bytes: 64,
1318 },
1319 )
1320 .unwrap();
1321 (topology, producer, peer)
1322 }
1323
1324 fn expected(topology: &RegionSetLayout, role: RoleId, len: usize) -> ValidationExpectations {
1325 let region = topology.region(role).unwrap();
1326 ValidationExpectations {
1327 schema_id: [7; 32],
1328 generation: 23,
1329 role,
1330 writer: region.writer(),
1331 maximum_mapping_size: len as u64,
1332 }
1333 }
1334
1335 #[test]
1336 fn nonce_is_nonzero_and_job_is_constructible() {
1337 assert_ne!(session_nonce().unwrap(), [0; 32]);
1338 let _job = ChildJob::new().unwrap();
1339 }
1340
1341 #[test]
1342 fn unnamed_section_is_page_rounded_and_zeroed() {
1343 let region = QuiescentRegion::new(37).unwrap();
1344 assert!(region.len() >= 37);
1345 assert!(region.as_bytes().iter().all(|byte| *byte == 0));
1346 let mut prior = 0;
1347 assert_eq!(
1349 unsafe {
1350 VirtualProtect(
1351 region.view.base.as_ptr().cast(),
1352 region.len(),
1353 PAGE_EXECUTE_READWRITE,
1354 &mut prior,
1355 )
1356 },
1357 0
1358 );
1359 }
1360
1361 #[test]
1362 fn read_only_duplicate_rejects_writable_mapping() {
1363 let region = QuiescentRegion::new(4096).unwrap();
1364 let duplicate = duplicate_to(
1365 region.section.0,
1366 unsafe { GetCurrentProcess() },
1367 FILE_MAP_READ,
1368 )
1369 .unwrap();
1370 let duplicate = OwnedHandle::new(duplicate.0 as HANDLE).unwrap();
1371 let denied = unsafe { MapViewOfFile(duplicate.0, FILE_MAP_WRITE, 0, 0, region.len()) };
1373 assert!(denied.Value.is_null());
1374 }
1375
1376 #[test]
1377 fn spawned_helper_is_pid_authenticated_and_job_owned() {
1378 let (topology, producer, peer) = topology();
1379 let producer_layout = topology.region(producer).unwrap();
1380 let mut producer_region =
1381 QuiescentRegion::new(producer_layout.total_size() as usize).unwrap();
1382 producer_layout
1383 .encode_into(producer_region.as_bytes_mut())
1384 .unwrap();
1385 let producer_expected = expected(&topology, producer, producer_region.len());
1386 let prepared_producer = producer_region
1387 .prepare_local_writer(producer_expected, topology.clone())
1388 .unwrap();
1389 let peer_layout = topology.region(peer).unwrap();
1390 let mut peer_region = QuiescentRegion::new(peer_layout.total_size() as usize).unwrap();
1391 peer_layout.encode_into(peer_region.as_bytes_mut()).unwrap();
1392 let peer_expected = expected(&topology, peer, peer_region.len());
1393 let prepared_peer = peer_region
1394 .prepare_remote_writer(peer_expected, topology.clone())
1395 .unwrap();
1396 let executable = std::env::current_exe().unwrap();
1397 let arguments = [
1398 OsString::from("--exact"),
1399 OsString::from("windows::tests::spawned_helper_entry"),
1400 OsString::from("--ignored"),
1401 OsString::from("--nocapture"),
1402 ];
1403 let mut child = ChildSession::spawn(&executable, &arguments).unwrap();
1404 assert_ne!(child.pid(), unsafe { GetCurrentProcessId() });
1405 let (mut writer, reader) = child
1406 .commit_transfers(prepared_producer, prepared_peer)
1407 .unwrap();
1408 writer
1409 .publish(0, 1, None, b"cross-process-windows")
1410 .unwrap();
1411 for _ in 0..10_000 {
1412 if let Ok(payload) = reader.copy_payload(0, 1) {
1413 assert_eq!(payload, b"child-windows-writer");
1414 child.wait().unwrap();
1415 return;
1416 }
1417 std::thread::sleep(Duration::from_millis(1));
1418 }
1419 panic!("child never published payload");
1420 }
1421
1422 #[test]
1423 fn helper_exit_before_connect_is_bounded() {
1424 let executable = std::env::current_exe().unwrap();
1425 let arguments = [
1426 OsString::from("--exact"),
1427 OsString::from("windows::tests::exit_before_connect_entry"),
1428 OsString::from("--ignored"),
1429 ];
1430 let result = ChildSession::spawn(&executable, &arguments);
1431 assert!(matches!(
1432 result,
1433 Err(WindowsError::ChildExit(0) | WindowsError::Os { .. })
1434 ));
1435 }
1436
1437 #[test]
1438 fn helper_stall_before_auth_is_bounded() {
1439 let executable = std::env::current_exe().unwrap();
1440 let arguments = [
1441 OsString::from("--exact"),
1442 OsString::from("windows::tests::stall_before_auth_entry"),
1443 OsString::from("--ignored"),
1444 ];
1445 let result = ChildSession::spawn(&executable, &arguments);
1446 assert!(matches!(result, Err(WindowsError::TimedOut("pipe read"))));
1447 }
1448
1449 #[test]
1450 #[ignore = "spawned only by the exact lifecycle test"]
1451 fn spawned_helper_entry() {
1452 let (topology, producer, peer) = topology();
1453 let mut channel = connect_spawned_helper().unwrap();
1454 assert_ne!(channel.parent_pid(), unsafe { GetCurrentProcessId() });
1455 let (reader_handle, reader_len, writer_handle, writer_len) =
1456 channel.receive_capabilities().unwrap();
1457 let reader = unsafe {
1459 import_reader(
1460 reader_handle.0,
1461 reader_len,
1462 expected(&topology, producer, reader_len),
1463 topology.clone(),
1464 )
1465 .unwrap()
1466 };
1467 let writer = unsafe {
1469 import_writer(
1470 writer_handle.0,
1471 writer_len,
1472 expected(&topology, peer, writer_len),
1473 topology,
1474 )
1475 .unwrap()
1476 };
1477 let (reader, mut writer) = channel.commit_imports(reader, writer).unwrap();
1478 for _ in 0..10_000 {
1479 if let Ok(payload) = reader.copy_payload(0, 1) {
1480 assert_eq!(payload, b"cross-process-windows");
1481 writer.publish(0, 1, None, b"child-windows-writer").unwrap();
1482 return;
1483 }
1484 std::thread::sleep(Duration::from_millis(1));
1485 }
1486 panic!("parent never published payload");
1487 }
1488
1489 #[test]
1490 #[ignore = "spawned only by the exit-before-connect lifecycle test"]
1491 fn exit_before_connect_entry() {}
1492
1493 #[test]
1494 #[ignore = "spawned only by the stalled-auth lifecycle test"]
1495 fn stall_before_auth_entry() {
1496 let name = wide_null(&std::env::var_os(PIPE_ENV).unwrap());
1497 let pipe = unsafe {
1499 CreateFileW(
1500 name.as_ptr(),
1501 GENERIC_READ | GENERIC_WRITE,
1502 0,
1503 std::ptr::null(),
1504 OPEN_EXISTING,
1505 0,
1506 std::ptr::null_mut(),
1507 )
1508 };
1509 let _pipe = OwnedHandle::new(pipe).unwrap();
1510 std::thread::sleep(Duration::from_secs(5));
1511 }
1512}