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::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<std::process::Child>,
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,
}
impl Child {
pub(super) fn new(
inner: std::process::Child,
dropped_env: Vec<String>,
tmpdir: Option<PathBuf>,
run: Option<Run>,
reporting: Reporting,
receipt: crate::receipt::RunFacts,
audit: crate::audit::RunAudit,
) -> Self {
Self {
inner: Some(inner),
dropped_env,
tmpdir,
run,
reporting,
ended: None,
receipt,
killed: false,
audit,
}
}
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))
}
fn inner(&self) -> &std::process::Child {
self.inner
.as_ref()
.unwrap_or_else(|| unreachable!("present until drop or detach"))
}
fn inner_mut(&mut self) -> &mut std::process::Child {
self.inner
.as_mut()
.unwrap_or_else(|| unreachable!("present until drop or detach"))
}
pub fn kill_all(&mut self) -> io::Result<()> {
let child = self.inner_mut();
let pid =
libc::pid_t::try_from(child.id()).map_err(|_| io::Error::other("pid out of range"))?;
if child.try_wait()?.is_none() {
self.killed = true;
for descendant in sys::descendants(pid) {
sys::kill(descendant);
}
}
sys::killpg(pid);
let waited = self.inner_mut().wait().map(drop);
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());
}
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()
}
pub fn detach(mut self) -> std::process::Child {
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 {
self.inner()
}
}
impl DerefMut for Child {
fn deref_mut(&mut self) -> &mut std::process::Child {
self.inner_mut()
}
}
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);
}
}
}