mod drop_guard;
mod group;
pub(crate) mod output_collection;
mod replay;
#[cfg(any(unix, windows))]
mod signal;
mod spawn;
pub(crate) mod termination;
mod wait;
pub(crate) mod wait_builder;
use crate::output_stream::OutputStream;
use crate::process_handle::drop_guard::DropMode;
use crate::process_handle::group::ProcessGroup;
use std::borrow::Cow;
use std::io;
use std::mem::ManuallyDrop;
use std::process::ExitStatus;
use tokio::process::Child;
use tokio::process::ChildStdin;
#[derive(Debug)]
pub enum Stdin {
Open(ChildStdin),
Closed,
}
impl Stdin {
#[must_use]
pub fn is_open(&self) -> bool {
matches!(self, Stdin::Open(_))
}
pub fn as_mut(&mut self) -> Option<&mut ChildStdin> {
match self {
Stdin::Open(stdin) => Some(stdin),
Stdin::Closed => None,
}
}
pub fn close(&mut self) {
*self = Stdin::Closed;
}
}
#[derive(Debug)]
#[must_use = "Discarding the state defeats the purpose of probing; match on the variants \
(or call `is_definitely_running`) to distinguish Running, Terminated, and \
Uncertain."]
pub enum RunningState {
Running,
Terminated(ExitStatus),
Uncertain(io::Error),
}
impl RunningState {
#[must_use]
pub fn is_definitely_running(&self) -> bool {
matches!(self, RunningState::Running)
}
}
#[derive(Debug)]
pub struct ProcessHandle<Stdout, Stderr = Stdout>
where
Stdout: OutputStream,
Stderr: OutputStream,
{
pub(crate) name: Cow<'static, str>,
pub(super) group: ProcessGroup,
std_in: Stdin,
std_out_stream: Stdout,
std_err_stream: Stderr,
pub(super) drop_mode: DropMode,
}
impl<Stdout, Stderr> ProcessHandle<Stdout, Stderr>
where
Stdout: OutputStream,
Stderr: OutputStream,
{
pub fn name(&self) -> &str {
&self.name
}
pub fn stdin(&mut self) -> &mut Stdin {
&mut self.std_in
}
pub fn stdout(&self) -> &Stdout {
&self.std_out_stream
}
pub fn stderr(&self) -> &Stderr {
&self.std_err_stream
}
}
impl<Stdout, Stderr> ProcessHandle<Stdout, Stderr>
where
Stdout: OutputStream,
Stderr: OutputStream,
{
pub(super) fn send_kill_signal(&mut self) -> io::Result<()> {
self.group.send_kill()
}
pub fn id(&self) -> Option<u32> {
self.group.id()
}
#[doc(hidden)]
pub fn try_reap_exit_status(&mut self) -> Result<Option<ExitStatus>, io::Error> {
self.group.try_wait()
}
pub fn is_running(&mut self) -> RunningState {
match self.try_reap_exit_status() {
Ok(None) => RunningState::Running,
Ok(Some(exit_status)) => RunningState::Terminated(exit_status),
Err(err) => RunningState::Uncertain(err),
}
}
}
impl<Stdout, Stderr> ProcessHandle<Stdout, Stderr>
where
Stdout: OutputStream,
Stderr: OutputStream,
{
pub fn into_inner(mut self) -> (Child, Stdin, Stdout, Stderr) {
self.must_not_be_terminated();
let mut this = ManuallyDrop::new(self);
unsafe {
let group = std::ptr::read(&raw const this.group);
let stdin = std::ptr::read(&raw const this.std_in);
let stdout = std::ptr::read(&raw const this.std_out_stream);
let stderr = std::ptr::read(&raw const this.std_err_stream);
std::ptr::drop_in_place(&raw mut this.name);
std::ptr::drop_in_place(&raw mut this.drop_mode);
(group.into_leader(), stdin, stdout, stderr)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use assertr::prelude::*;
#[test]
fn closed_stdin_reports_closed() {
let mut stdin = Stdin::Closed;
assert_that!(stdin.is_open()).is_false();
assert_that!(stdin.as_mut()).is_none();
stdin.close();
assert_that!(stdin.is_open()).is_false();
assert_that!(stdin.as_mut()).is_none();
}
}