1use std::io::{self, Read, Write};
4use std::os::windows::io::{AsHandle, BorrowedHandle, OwnedHandle};
5use std::process::{ExitStatus, Output};
6use std::thread;
7
8use crate::handles::Job;
9use crate::sys;
10
11#[derive(Debug)]
13pub struct ChildStdin {
14 handle: OwnedHandle,
15}
16
17impl Write for ChildStdin {
18 fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
19 sys::write_handle(self.handle.as_handle(), buffer)
20 }
21
22 fn flush(&mut self) -> io::Result<()> {
23 Ok(())
24 }
25}
26
27impl AsHandle for ChildStdin {
28 fn as_handle(&self) -> BorrowedHandle<'_> {
29 self.handle.as_handle()
30 }
31}
32
33#[derive(Debug)]
35pub struct ChildStdout {
36 handle: OwnedHandle,
37}
38
39impl Read for ChildStdout {
40 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
41 sys::read_handle(self.handle.as_handle(), buffer)
42 }
43}
44
45impl AsHandle for ChildStdout {
46 fn as_handle(&self) -> BorrowedHandle<'_> {
47 self.handle.as_handle()
48 }
49}
50
51#[derive(Debug)]
53pub struct ChildStderr {
54 handle: OwnedHandle,
55}
56
57impl Read for ChildStderr {
58 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
59 sys::read_handle(self.handle.as_handle(), buffer)
60 }
61}
62
63impl AsHandle for ChildStderr {
64 fn as_handle(&self) -> BorrowedHandle<'_> {
65 self.handle.as_handle()
66 }
67}
68
69#[derive(Debug)]
71pub struct Child {
72 kill_job: Option<Job>,
75 pub stdin: Option<ChildStdin>,
77 pub stdout: Option<ChildStdout>,
79 pub stderr: Option<ChildStderr>,
81 process: OwnedHandle,
82 pid: u32,
83 exit: Option<ExitStatus>,
84}
85
86impl Child {
87 pub(crate) fn new(
88 process: OwnedHandle,
89 pid: u32,
90 kill_job: Option<Job>,
91 stdin: Option<OwnedHandle>,
92 stdout: Option<OwnedHandle>,
93 stderr: Option<OwnedHandle>,
94 ) -> Self {
95 Self {
96 kill_job,
97 stdin: stdin.map(|handle| ChildStdin { handle }),
98 stdout: stdout.map(|handle| ChildStdout { handle }),
99 stderr: stderr.map(|handle| ChildStderr { handle }),
100 process,
101 pid,
102 exit: None,
103 }
104 }
105
106 pub(crate) fn process_handle(&self) -> BorrowedHandle<'_> {
107 self.process.as_handle()
108 }
109
110 #[must_use]
112 pub const fn id(&self) -> u32 {
113 self.pid
114 }
115
116 pub fn kill(&mut self) -> io::Result<()> {
122 if self.exit.is_some() {
123 return Ok(());
124 }
125 sys::terminate_process(self.process.as_handle(), 1)
126 }
127
128 pub fn wait(&mut self) -> io::Result<ExitStatus> {
134 if let Some(status) = self.exit {
135 return Ok(status);
136 }
137 drop(self.stdin.take());
138 sys::wait_process(self.process.as_handle())?;
139 let status = sys::exit_status(self.process.as_handle())?;
140 self.exit = Some(status);
141 Ok(status)
142 }
143
144 pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
150 if self.exit.is_some() {
151 return Ok(self.exit);
152 }
153 if !sys::try_wait_process(self.process.as_handle())? {
154 return Ok(None);
155 }
156 let status = sys::exit_status(self.process.as_handle())?;
157 self.exit = Some(status);
158 Ok(self.exit)
159 }
160
161 pub fn wait_with_output(mut self) -> io::Result<Output> {
171 drop(self.stdin.take());
172
173 let stdout_reader = self
174 .stdout
175 .take()
176 .map(|stream| thread::spawn(move || drain_output(stream.handle.as_handle())));
177 let stderr_reader = self
178 .stderr
179 .take()
180 .map(|stream| thread::spawn(move || drain_output(stream.handle.as_handle())));
181
182 let status = self.wait();
183 let termination = self
184 .kill_job
185 .as_ref()
186 .map_or(Ok(()), |job| job.terminate(1));
187 let (stdout, stderr) = join_readers(stdout_reader, stderr_reader)?;
188 termination?;
189
190 Ok(Output {
191 status: status?,
192 stdout,
193 stderr,
194 })
195 }
196}
197
198impl AsHandle for Child {
199 fn as_handle(&self) -> BorrowedHandle<'_> {
200 self.process.as_handle()
201 }
202}
203
204fn drain_output(handle: BorrowedHandle<'_>) -> io::Result<Vec<u8>> {
205 let mut bytes = Vec::new();
206 let mut buffer = [0_u8; 8_192];
207 loop {
208 let Some(read) = std::num::NonZeroUsize::new(sys::read_handle(handle, &mut buffer)?) else {
209 return Ok(bytes);
210 };
211 bytes.extend_from_slice(&buffer[..read.get()]);
212 }
213}
214
215fn join_reader(reader: Option<thread::JoinHandle<io::Result<Vec<u8>>>>) -> io::Result<Vec<u8>> {
216 match reader {
217 Some(reader) => reader
218 .join()
219 .map_err(|_| io::Error::other("output reader thread panicked"))?,
220 None => Ok(Vec::new()),
221 }
222}
223
224fn join_readers(
225 stdout: Option<thread::JoinHandle<io::Result<Vec<u8>>>>,
226 stderr: Option<thread::JoinHandle<io::Result<Vec<u8>>>>,
227) -> io::Result<(Vec<u8>, Vec<u8>)> {
228 let stdout = join_reader(stdout);
229 let stderr = join_reader(stderr);
230 Ok((stdout?, stderr?))
231}
232
233#[derive(Debug)]
247#[must_use = "dropping a suspended child terminates it"]
248pub struct SuspendedChild {
249 child: Option<Child>,
250 main_thread: OwnedHandle,
251}
252
253impl SuspendedChild {
254 pub(crate) fn new(child: Child, main_thread: OwnedHandle) -> Self {
255 Self {
256 child: Some(child),
257 main_thread,
258 }
259 }
260
261 #[must_use]
268 pub fn id(&self) -> u32 {
269 self.child
270 .as_ref()
271 .expect("a suspended child owns its process until resume")
272 .id()
273 }
274
275 #[must_use]
281 pub fn primary_thread_handle(&self) -> BorrowedHandle<'_> {
282 self.main_thread.as_handle()
283 }
284
285 pub fn resume(mut self) -> io::Result<Child> {
294 let previous = sys::resume_thread(self.main_thread.as_handle())?;
295 if previous != 1 {
296 return Err(io::Error::new(
297 io::ErrorKind::InvalidData,
298 format!("primary thread suspend count was {previous}, expected 1"),
299 ));
300 }
301 self.child
302 .take()
303 .ok_or_else(|| io::Error::other("suspended child lost its process"))
304 }
305}
306
307impl AsHandle for SuspendedChild {
308 fn as_handle(&self) -> BorrowedHandle<'_> {
309 self.child
310 .as_ref()
311 .expect("a suspended child owns its process until resume")
312 .as_handle()
313 }
314}
315
316impl Drop for SuspendedChild {
317 fn drop(&mut self) {
318 if let Some(child) = &mut self.child {
319 let _ = child.kill();
320 }
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use std::sync::atomic::{AtomicBool, Ordering};
327 use std::sync::Arc;
328
329 use super::*;
330
331 #[test]
332 fn absent_and_panicked_output_readers_become_results() {
333 assert!(join_reader(None).unwrap().is_empty());
334 let panicked = thread::spawn(|| -> io::Result<Vec<u8>> { panic!("reader panic") });
335 assert_eq!(
336 join_reader(Some(panicked)).unwrap_err().kind(),
337 io::ErrorKind::Other
338 );
339 }
340
341 #[test]
342 fn both_output_readers_are_joined_when_the_first_panics() {
343 let joined = Arc::new(AtomicBool::new(false));
344 let stdout = thread::spawn(|| -> io::Result<Vec<u8>> { panic!("stdout panic") });
345 let stderr_joined = Arc::clone(&joined);
346 let stderr = thread::spawn(move || {
347 stderr_joined.store(true, Ordering::Release);
348 Ok(Vec::new())
349 });
350
351 assert_eq!(
352 join_readers(Some(stdout), Some(stderr)).unwrap_err().kind(),
353 io::ErrorKind::Other
354 );
355 assert!(joined.load(Ordering::Acquire));
356 }
357}