use std::io;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
use super::lifetime::{self, Run};
use super::sys;
use super::violations::{self, Sink};
use crate::unix::Proc;
use crate::violation::{Coverage, Violation, Violations};
#[derive(Debug)]
pub(super) struct Reporting {
pub(super) forking: bool,
pub(super) record: Option<Sink>,
}
#[derive(Debug)]
pub struct Child {
inner: Option<Proc>,
pid: u32,
dropped_env: Vec<String>,
tmpdir: Option<PathBuf>,
run: Option<Run>,
reporting: Reporting,
ended: Option<Violations>,
receipt: crate::receipt::RunFacts,
killed: bool,
audit: crate::audit::RunAudit,
#[cfg(feature = "proxy")]
proxy: Option<procage_proxy::Run>,
}
impl Child {
pub(super) fn new(
inner: Proc,
dropped_env: Vec<String>,
tmpdir: Option<PathBuf>,
run: Option<Run>,
reporting: Reporting,
receipt: crate::receipt::RunFacts,
audit: crate::audit::RunAudit,
) -> Self {
Self {
pid: inner.pid(),
inner: Some(inner),
dropped_env,
tmpdir,
run,
reporting,
ended: None,
receipt,
killed: false,
audit,
#[cfg(feature = "proxy")]
proxy: None,
}
}
#[cfg(feature = "proxy")]
pub(super) fn set_proxy(&mut self, run: Option<procage_proxy::Run>) {
self.proxy = run;
}
pub fn audit(&mut self) -> Option<crate::Audit> {
self.inner_mut().try_wait().ok()??;
let ended = self.kill_all().is_ok();
Some(self.audit.finish(ended))
}
pub fn receipt(&mut self) -> Option<crate::Receipt> {
let status = self.inner_mut().try_wait().ok()??;
let ended = self.kill_all().is_ok();
let record = self.violations();
let audit = self.audit.finish(ended);
Some(self.receipt.receipt(status, &record, self.killed, &audit))
}
pub(crate) fn inner(&self) -> &Proc {
self.inner
.as_ref()
.unwrap_or_else(|| unreachable!("present until drop or detach"))
}
pub(crate) fn inner_mut(&mut self) -> &mut Proc {
self.inner
.as_mut()
.unwrap_or_else(|| unreachable!("present until drop or detach"))
}
pub fn kill_all(&mut self) -> io::Result<()> {
self.signal()?;
let waited = self.inner_mut().wait().map(drop);
self.end(waited)
}
pub(crate) fn signal(&mut self) -> io::Result<()> {
let pid =
libc::pid_t::try_from(self.pid).map_err(|_| io::Error::other("pid out of range"))?;
if self.inner_mut().try_wait()?.is_none() {
self.killed = true;
for descendant in sys::descendants(pid) {
sys::kill(descendant);
}
}
sys::killpg(pid);
Ok(())
}
pub(crate) fn end(&mut self, waited: io::Result<()>) -> io::Result<()> {
#[cfg(feature = "proxy")]
drop(self.proxy.take());
let ended = match &self.run {
Some(run) if self.reporting.forking => lifetime::end(run),
_ => Ok(()),
};
let delivered = self.finish_record();
ended?;
if let Some(run) = self.run.take()
&& delivered
{
run.free();
}
waited
}
fn finish_record(&mut self) -> bool {
let Some(record) = self.reporting.record.take() else {
return true;
};
let record = &record;
let delivered = violations::fence();
if delivered {
violations::lock(record).finish();
}
if let Some(run) = &self.run {
violations::unregister(run.marker());
}
violations::lock(record).close();
self.ended = Some(violations::lock(record).snapshot());
delivered
}
pub fn violations(&mut self) -> Violations {
let Some(record) = self.reporting.record.clone() else {
return self
.ended
.clone()
.unwrap_or_else(|| Violations::empty(Coverage::Off));
};
let exited = self
.inner
.as_mut()
.is_some_and(|c| matches!(c.try_wait(), Ok(Some(_))));
if exited && !violations::lock(&record).is_complete() && violations::fence() {
violations::lock(&record).finish();
}
violations::lock(&record).snapshot()
}
pub fn violation_receiver(&mut self) -> Option<std::sync::mpsc::Receiver<Violation>> {
let record = self.reporting.record.as_ref()?;
let mut record = violations::lock(record);
if record.snapshot().coverage() != &Coverage::Observed {
return None;
}
record.take_receiver()
}
#[cfg(feature = "tokio")]
pub(crate) fn violation_stream(&mut self) -> Option<crate::ViolationStream> {
let record = self.reporting.record.as_ref()?;
let mut record = violations::lock(record);
if record.snapshot().coverage() != &Coverage::Observed {
return None;
}
record.take_stream()
}
pub fn detach(self) -> std::process::Child {
match self.release() {
Proc::Std(c) => c,
#[cfg(feature = "tokio")]
Proc::Tokio(_) => unreachable!("an async handle detaches through AsyncChild"),
}
}
pub(crate) fn release(mut self) -> Proc {
self.tmpdir = None;
if let (Some(_), Some(run)) = (&self.reporting.record, &self.run) {
violations::unregister(run.marker());
}
self.reporting.record = None;
self.run = None;
self.inner
.take()
.unwrap_or_else(|| unreachable!("present until drop or detach"))
}
pub(super) fn run(&self) -> Option<&Run> {
self.run.as_ref()
}
pub fn dropped_env(&self) -> &[String] {
&self.dropped_env
}
pub fn tmpdir(&self) -> Option<&Path> {
self.tmpdir.as_deref()
}
}
impl Deref for Child {
type Target = std::process::Child;
fn deref(&self) -> &std::process::Child {
match self.inner() {
Proc::Std(c) => c,
#[cfg(feature = "tokio")]
Proc::Tokio(_) => unreachable!("an async handle derefs through AsyncChild"),
}
}
}
impl DerefMut for Child {
fn deref_mut(&mut self) -> &mut std::process::Child {
match self.inner_mut() {
Proc::Std(c) => c,
#[cfg(feature = "tokio")]
Proc::Tokio(_) => unreachable!("an async handle derefs through AsyncChild"),
}
}
}
impl Drop for Child {
fn drop(&mut self) {
if self.inner.is_some() {
let _ = self.kill_all();
}
if let Some(dir) = self.tmpdir.take() {
let _ = std::fs::remove_dir_all(dir);
}
}
}