use std::path::Path;
use std::process::Stdio;
use process_wrap::tokio::{ChildWrapper, CommandWrap};
use tokio::process::Command;
use super::{Exit, OnLine, ProcessError, ProcessRunner, ProcessSpec, lines, shim};
use crate::BoxFuture;
#[derive(Debug, Clone, Default)]
pub struct SystemProcesses {
strip_engine_overrides: bool,
}
impl SystemProcesses {
#[must_use]
pub fn new(docker_cli_config: Option<&str>) -> SystemProcesses {
SystemProcesses {
strip_engine_overrides: shim::strips_engine_overrides(docker_cli_config),
}
}
pub fn for_host(var: impl Fn(&str) -> Option<String>, home: Option<&Path>) -> SystemProcesses {
let dir = var("DOCKER_CONFIG").map(std::path::PathBuf::from).or_else(|| home.map(|h| h.join(".docker")));
let config = dir.and_then(|d| std::fs::read_to_string(d.join("config.json")).ok());
SystemProcesses::new(config.as_deref())
}
async fn run_to_end(&self, spec: &ProcessSpec, on_line: OnLine<'_>) -> Result<Exit, ProcessError> {
if let Some(arg) = shim::refused_arg(&spec.program, &spec.args) {
return Err(ProcessError::Refused {
program: spec.program.clone(),
arg: arg.escape_debug().to_string(),
});
}
let io = |source| ProcessError::Io {
program: spec.program.clone(),
source,
};
tracing::debug!(program = %spec.program, args = ?spec.args, "starting a process");
let mut cmd = self.command(spec).map_err(io)?;
let child = cmd.spawn().map_err(|e| spawned(&spec.program, e))?;
let mut tree = Tree(Some(child));
let status = match tree.0.as_deref_mut() {
Some(started) => lines::forward_lines(started, on_line).await.map_err(io)?,
None => return Err(io(std::io::Error::other("the process went away"))),
};
tree.0 = None;
tracing::debug!(program = %spec.program, code = ?status.code(), "process exited");
if shim::missing_behind_cmd(&spec.program, status.code()) {
return Err(ProcessError::Missing { program: spec.program.clone() });
}
Ok(Exit { code: status.code() })
}
fn command(&self, spec: &ProcessSpec) -> std::io::Result<CommandWrap> {
let stdin = match &spec.stdin {
Some(path) => Stdio::from(std::fs::File::open(path)?),
None => Stdio::null(),
};
let strip = self.strip_engine_overrides && shim::uses_engine(&spec.program);
let inherited = std::env::vars_os().filter_map(|(k, _)| k.into_string().ok());
let withheld = shim::withheld(&spec.program, inherited);
let (program, args) = wrapped(spec);
let mut cmd = CommandWrap::with_new(program, |c: &mut Command| {
c.args(args).stdin(stdin).stdout(Stdio::piped()).stderr(Stdio::piped());
for var in &withheld {
c.env_remove(var);
}
c.envs(spec.env.iter().map(|(k, v)| (k.as_str(), v.as_str())));
if let Some(dir) = &spec.cwd {
c.current_dir(dir);
}
if strip {
for var in shim::ENGINE_OVERRIDES {
c.env_remove(var);
}
}
});
group(&mut cmd);
Ok(cmd)
}
}
impl ProcessRunner for SystemProcesses {
fn run<'a>(&'a self, spec: &'a ProcessSpec, on_line: OnLine<'a>) -> BoxFuture<'a, Result<Exit, ProcessError>> {
Box::pin(self.run_to_end(spec, on_line))
}
}
struct Tree(Option<Box<dyn ChildWrapper>>);
impl Drop for Tree {
fn drop(&mut self) {
if let Some(child) = self.0.as_mut() {
tracing::debug!(pid = ?child.id(), "ending a process tree cut short");
if let Err(e) = child.start_kill() {
tracing::debug!(error = %e, "the process tree had already ended");
}
}
}
}
fn wrapped(spec: &ProcessSpec) -> (&str, Vec<&str>) {
let args = spec.args.iter().map(String::as_str);
if cfg!(windows) && shim::SHIMMED.contains(&spec.program.as_str()) {
return ("cmd", ["/C", spec.program.as_str()].into_iter().chain(args).collect());
}
(spec.program.as_str(), args.collect())
}
#[cfg(unix)]
fn group(cmd: &mut CommandWrap) {
cmd.wrap(process_wrap::tokio::ProcessGroup::leader());
}
#[cfg(windows)]
fn group(cmd: &mut CommandWrap) {
use windows::Win32::System::Threading::CREATE_NO_WINDOW;
cmd.wrap(process_wrap::tokio::CreationFlags(CREATE_NO_WINDOW));
cmd.wrap(process_wrap::tokio::JobObject);
}
fn spawned(program: &str, e: std::io::Error) -> ProcessError {
match e.kind() {
std::io::ErrorKind::NotFound => ProcessError::Missing { program: program.to_owned() },
_ => ProcessError::Io {
program: program.to_owned(),
source: e,
},
}
}
#[cfg(test)]
#[path = "system_tests.rs"]
mod tests;