vv_agent/runtime/processes/
capture.rs1use 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}