use anyhow::{Context, Result};
use std::io::Write;
use std::process::Command;
use std::time::Duration;
#[derive(Debug)]
pub struct SubprocessResult {
pub valid: bool,
pub exit_code: Option<i32>,
pub stderr: String,
pub timed_out: bool,
}
pub fn subprocess_verify(bundle_data: &[u8], timeout: Duration) -> Result<SubprocessResult> {
let tmp = tempfile::Builder::new()
.prefix("assay-sim-")
.suffix(".tar.gz")
.tempfile()
.context("creating temp file for subprocess verify")?;
tmp.as_file()
.write_all(bundle_data)
.context("writing bundle to temp file")?;
let assay_bin = find_assay_binary()?;
let mut child = Command::new(&assay_bin)
.args(["evidence", "verify", &tmp.path().to_string_lossy()])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.spawn()
.with_context(|| format!("spawning assay binary: {}", assay_bin.display()))?;
let result = match child.wait_timeout(timeout) {
Ok(Some(status)) => {
let stderr = read_stderr(&mut child);
SubprocessResult {
valid: status.success(),
exit_code: status.code(),
stderr,
timed_out: false,
}
}
Ok(None) => {
let _ = child.kill();
let _ = child.wait(); SubprocessResult {
valid: false,
exit_code: None,
stderr: "subprocess timed out".into(),
timed_out: true,
}
}
Err(e) => {
let _ = child.kill();
let _ = child.wait();
return Err(e).context("waiting for subprocess");
}
};
Ok(result)
}
fn find_assay_binary() -> Result<std::path::PathBuf> {
if let Ok(bin) = std::env::var("ASSAY_BIN") {
let path = std::path::PathBuf::from(bin);
if path.exists() {
return Ok(path);
}
}
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
let sibling = dir.join("assay");
if sibling.exists() {
return Ok(sibling);
}
}
}
Ok(std::path::PathBuf::from("assay"))
}
fn read_stderr(child: &mut std::process::Child) -> String {
use std::io::Read;
let mut buf = String::new();
if let Some(ref mut stderr) = child.stderr {
let _ = stderr.read_to_string(&mut buf);
}
buf.truncate(4096);
buf
}
trait ChildExt {
fn wait_timeout(
&mut self,
timeout: Duration,
) -> std::io::Result<Option<std::process::ExitStatus>>;
}
impl ChildExt for std::process::Child {
fn wait_timeout(
&mut self,
timeout: Duration,
) -> std::io::Result<Option<std::process::ExitStatus>> {
let start = std::time::Instant::now();
let poll_interval = Duration::from_millis(50);
loop {
match self.try_wait()? {
Some(status) => return Ok(Some(status)),
None => {
if start.elapsed() >= timeout {
return Ok(None);
}
std::thread::sleep(poll_interval);
}
}
}
}
}