use std::{
io,
process::{Child as StdChild, Command as StdCommand, ExitStatus, Output},
};
use crate::{
io::AsyncRead,
process::stdio::{ChildStderr, ChildStdin, ChildStdout},
runtime::local::{spawn_blocking, spawn_local},
};
#[cfg(target_os = "linux")]
use crate::driver::ops::Op;
#[cfg(target_os = "linux")]
use std::os::fd::OwnedFd;
pub struct Child {
child: Option<StdChild>,
status: Option<ExitStatus>,
pub(crate) stdin: Option<ChildStdin>,
pub(crate) stdout: Option<ChildStdout>,
pub(crate) stderr: Option<ChildStderr>,
kill_on_drop: bool,
}
impl Child {
pub(crate) fn spawn(inner: &mut StdCommand, kill_on_drop: bool) -> io::Result<Child> {
let child = inner.spawn()?;
Child::from_std(child, kill_on_drop)
}
pub(crate) fn from_std(mut child: StdChild, kill_on_drop: bool) -> io::Result<Child> {
let stdin = child.stdin.take().map(ChildStdin::from_std).transpose()?;
let stdout = child.stdout.take().map(ChildStdout::from_std).transpose()?;
let stderr = child.stderr.take().map(ChildStderr::from_std).transpose()?;
Ok(Child {
child: Some(child),
status: None,
stdin,
stdout,
stderr,
kill_on_drop,
})
}
pub fn id(&self) -> Option<u32> {
self.child.as_ref().map(|c| c.id())
}
pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
if let Some(status) = self.status {
return Ok(Some(status));
}
match self.child.as_mut() {
Some(child) => {
let status = child.try_wait()?;
if let Some(status) = status {
self.status = Some(status);
}
Ok(status)
}
None => Ok(self.status),
}
}
pub async fn wait(&mut self) -> io::Result<ExitStatus> {
if let Some(status) = self.status {
return Ok(status);
}
if let Some(status) = self.try_wait()? {
return Ok(status);
}
let mut child = self.child.take().expect("wait already in progress");
#[cfg(target_os = "linux")]
if let Ok(pidfd) = pidfd_for_child(child.id()) {
let status = Op::wait_process(child, pidfd)?.await?;
self.status = Some(status);
return Ok(status);
}
let status = spawn_blocking(move || child.wait())
.await
.map_err(|_| io::Error::other("failed to wait for child"))??;
self.status = Some(status);
Ok(status)
}
pub fn kill(&mut self) -> io::Result<()> {
match self.child.as_mut() {
Some(child) => child.kill(),
None => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"child has already been awaited",
)),
}
}
pub fn start_kill(&mut self) -> io::Result<()> {
match self.child.as_mut() {
Some(child) => child.kill(),
None => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"child has already been awaited",
)),
}
}
pub fn take_stdin(&mut self) -> Option<ChildStdin> {
self.stdin.take()
}
pub fn take_stdout(&mut self) -> Option<ChildStdout> {
self.stdout.take()
}
pub fn take_stderr(&mut self) -> Option<ChildStderr> {
self.stderr.take()
}
pub async fn wait_with_output(mut self) -> io::Result<Output> {
self.stdin.take();
let stdout = self.stdout.take();
let stderr = self.stderr.take();
let out_handle = spawn_local(async move {
match stdout {
Some(mut s) => read_to_end(&mut s).await,
None => Ok(Vec::new()),
}
});
let err_handle = spawn_local(async move {
match stderr {
Some(mut s) => read_to_end(&mut s).await,
None => Ok(Vec::new()),
}
});
let stdout_data = out_handle
.await
.map_err(|_| io::Error::other("failed to read child stdout"))??;
let stderr_data = err_handle
.await
.map_err(|_| io::Error::other("failed to read child stderr"))??;
let status = self.wait().await?;
Ok(Output {
status,
stdout: stdout_data,
stderr: stderr_data,
})
}
}
impl Drop for Child {
fn drop(&mut self) {
if self.kill_on_drop
&& let Some(child) = self.child.as_mut()
{
let _ = child.kill();
}
}
}
async fn read_to_end<R: AsyncRead + Unpin>(reader: &mut R) -> io::Result<Vec<u8>> {
let mut data = Vec::new();
loop {
let (res, buf) = reader.read([0u8; 8192]).await.into_parts();
let n = res?;
if n == 0 {
break;
}
data.extend_from_slice(&buf[..n]);
}
Ok(data)
}
#[cfg(target_os = "linux")]
fn pidfd_for_child(pid: u32) -> io::Result<OwnedFd> {
let pid = rustix::process::Pid::from_raw(pid as _)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid child process id"))?;
rustix::process::pidfd_open(pid, rustix::process::PidfdFlags::empty()).map_err(io::Error::from)
}