Skip to main content

vv_agent/runtime/processes/
capture.rs

1use std::collections::BTreeMap;
2use std::io::Write;
3use std::path::Path;
4use std::process::{Child, Command, ExitStatus, Stdio};
5use std::time::{Duration, Instant};
6
7use super::output::{next_output_path, open_output_file, remove_captured_output};
8use super::platform::configure_process_group;
9
10#[derive(Debug)]
11pub struct CapturedProcess {
12    pub child: Child,
13    pub output_path: std::path::PathBuf,
14}
15
16pub fn start_captured_process(
17    command: &[String],
18    cwd: &Path,
19    stdin_text: Option<&str>,
20) -> std::io::Result<CapturedProcess> {
21    start_captured_process_with_env(command, cwd, stdin_text, None)
22}
23
24pub fn start_captured_process_with_env(
25    command: &[String],
26    cwd: &Path,
27    stdin_text: Option<&str>,
28    env: Option<&BTreeMap<String, String>>,
29) -> std::io::Result<CapturedProcess> {
30    let Some(program) = command.first() else {
31        return Err(std::io::Error::new(
32            std::io::ErrorKind::InvalidInput,
33            "empty command",
34        ));
35    };
36    let output_path = next_output_path();
37    let stdout_file = open_output_file(&output_path)?;
38    let stderr_file = stdout_file.try_clone()?;
39
40    let mut child_command = Command::new(program);
41    child_command
42        .args(&command[1..])
43        .current_dir(cwd)
44        .stdin(if stdin_text.is_some() {
45            Stdio::piped()
46        } else {
47            Stdio::null()
48        })
49        .stdout(Stdio::from(stdout_file))
50        .stderr(Stdio::from(stderr_file));
51    if let Some(env) = env {
52        child_command.envs(env);
53    }
54
55    configure_process_group(&mut child_command);
56
57    let mut child = match child_command.spawn() {
58        Ok(child) => child,
59        Err(error) => {
60            remove_captured_output(&output_path);
61            return Err(error);
62        }
63    };
64
65    if let Some(stdin_text) = stdin_text {
66        if let Some(mut stdin) = child.stdin.take() {
67            stdin.write_all(stdin_text.as_bytes())?;
68        }
69    }
70
71    Ok(CapturedProcess { child, output_path })
72}
73
74pub fn wait_for_child(child: &mut Child, timeout: Duration) -> std::io::Result<Option<ExitStatus>> {
75    let started_at = Instant::now();
76    loop {
77        if let Some(status) = child.try_wait()? {
78            return Ok(Some(status));
79        }
80        if started_at.elapsed() >= timeout {
81            return Ok(None);
82        }
83        std::thread::sleep(Duration::from_millis(25));
84    }
85}