isothrum_runner/process/
system.rs1use std::path::Path;
7use std::process::Stdio;
8
9use process_wrap::tokio::{ChildWrapper, CommandWrap};
10use tokio::process::Command;
11
12use super::{Exit, OnLine, ProcessError, ProcessRunner, ProcessSpec, lines, shim};
13use crate::BoxFuture;
14
15#[derive(Debug, Clone, Default)]
17pub struct SystemProcesses {
18 strip_engine_overrides: bool,
20}
21
22impl SystemProcesses {
23 #[must_use]
26 pub fn new(docker_cli_config: Option<&str>) -> SystemProcesses {
27 SystemProcesses {
28 strip_engine_overrides: shim::strips_engine_overrides(docker_cli_config),
29 }
30 }
31
32 pub fn for_host(var: impl Fn(&str) -> Option<String>, home: Option<&Path>) -> SystemProcesses {
35 let dir = var("DOCKER_CONFIG").map(std::path::PathBuf::from).or_else(|| home.map(|h| h.join(".docker")));
36 let config = dir.and_then(|d| std::fs::read_to_string(d.join("config.json")).ok());
37 SystemProcesses::new(config.as_deref())
38 }
39
40 async fn run_to_end(&self, spec: &ProcessSpec, on_line: OnLine<'_>) -> Result<Exit, ProcessError> {
41 if let Some(arg) = shim::refused_arg(&spec.program, &spec.args) {
42 return Err(ProcessError::Refused {
43 program: spec.program.clone(),
44 arg: arg.escape_debug().to_string(),
45 });
46 }
47 let io = |source| ProcessError::Io {
48 program: spec.program.clone(),
49 source,
50 };
51 tracing::debug!(program = %spec.program, args = ?spec.args, "starting a process");
54 let mut cmd = self.command(spec).map_err(io)?;
55 let child = cmd.spawn().map_err(|e| spawned(&spec.program, e))?;
56 let mut tree = Tree(Some(child));
57 let status = match tree.0.as_deref_mut() {
58 Some(started) => lines::forward_lines(started, on_line).await.map_err(io)?,
59 None => return Err(io(std::io::Error::other("the process went away"))),
60 };
61 tree.0 = None;
63 tracing::debug!(program = %spec.program, code = ?status.code(), "process exited");
64 if shim::missing_behind_cmd(&spec.program, status.code()) {
65 return Err(ProcessError::Missing { program: spec.program.clone() });
66 }
67 Ok(Exit { code: status.code() })
68 }
69
70 fn command(&self, spec: &ProcessSpec) -> std::io::Result<CommandWrap> {
72 let stdin = match &spec.stdin {
73 Some(path) => Stdio::from(std::fs::File::open(path)?),
74 None => Stdio::null(),
75 };
76 let strip = self.strip_engine_overrides && shim::uses_engine(&spec.program);
77 let inherited = std::env::vars_os().filter_map(|(k, _)| k.into_string().ok());
78 let withheld = shim::withheld(&spec.program, inherited);
79 let (program, args) = wrapped(spec);
80 let mut cmd = CommandWrap::with_new(program, |c: &mut Command| {
81 c.args(args).stdin(stdin).stdout(Stdio::piped()).stderr(Stdio::piped());
82 for var in &withheld {
84 c.env_remove(var);
85 }
86 c.envs(spec.env.iter().map(|(k, v)| (k.as_str(), v.as_str())));
87 if let Some(dir) = &spec.cwd {
88 c.current_dir(dir);
89 }
90 if strip {
91 for var in shim::ENGINE_OVERRIDES {
92 c.env_remove(var);
93 }
94 }
95 });
96 group(&mut cmd);
97 Ok(cmd)
98 }
99}
100
101impl ProcessRunner for SystemProcesses {
102 fn run<'a>(&'a self, spec: &'a ProcessSpec, on_line: OnLine<'a>) -> BoxFuture<'a, Result<Exit, ProcessError>> {
103 Box::pin(self.run_to_end(spec, on_line))
104 }
105}
106
107struct Tree(Option<Box<dyn ChildWrapper>>);
109
110impl Drop for Tree {
111 fn drop(&mut self) {
112 if let Some(child) = self.0.as_mut() {
113 tracing::debug!(pid = ?child.id(), "ending a process tree cut short");
116 if let Err(e) = child.start_kill() {
117 tracing::debug!(error = %e, "the process tree had already ended");
118 }
119 }
120 }
121}
122
123fn wrapped(spec: &ProcessSpec) -> (&str, Vec<&str>) {
125 let args = spec.args.iter().map(String::as_str);
126 if cfg!(windows) && shim::SHIMMED.contains(&spec.program.as_str()) {
127 return ("cmd", ["/C", spec.program.as_str()].into_iter().chain(args).collect());
128 }
129 (spec.program.as_str(), args.collect())
130}
131
132#[cfg(unix)]
133fn group(cmd: &mut CommandWrap) {
134 cmd.wrap(process_wrap::tokio::ProcessGroup::leader());
135}
136
137#[cfg(windows)]
138fn group(cmd: &mut CommandWrap) {
139 use windows::Win32::System::Threading::CREATE_NO_WINDOW;
140 cmd.wrap(process_wrap::tokio::CreationFlags(CREATE_NO_WINDOW));
142 cmd.wrap(process_wrap::tokio::JobObject);
143}
144
145fn spawned(program: &str, e: std::io::Error) -> ProcessError {
147 match e.kind() {
148 std::io::ErrorKind::NotFound => ProcessError::Missing { program: program.to_owned() },
149 _ => ProcessError::Io {
150 program: program.to_owned(),
151 source: e,
152 },
153 }
154}
155
156#[cfg(test)]
157#[path = "system_tests.rs"]
158mod tests;