use std::process::Command as OsCommand;
pub(crate) fn kill_tree(pid: u32) {
#[cfg(windows)]
{
use std::process::Stdio;
let _ = OsCommand::new("taskkill")
.args(["/T", "/F", "/PID", &pid.to_string()])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
}
#[cfg(unix)]
{
unsafe {
libc::kill(-(pid as i32), libc::SIGKILL);
}
}
}
pub(crate) fn detach_process_group(cmd: &mut OsCommand) {
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
unsafe {
cmd.pre_exec(|| {
libc::setsid();
Ok(())
});
}
}
#[cfg(windows)]
{
let _ = cmd;
}
}
pub(crate) fn shell_command(command_line: &str) -> OsCommand {
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
let mut c = OsCommand::new("cmd.exe");
c.raw_arg("/c ").raw_arg(command_line);
c
}
#[cfg(unix)]
{
let mut c = OsCommand::new("/bin/sh");
c.arg("-c").arg(command_line);
c
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::process::Stdio;
#[test]
fn shell_command_runs_a_trivial_command() {
let output = shell_command("echo shell-tunnel")
.stdin(Stdio::null())
.output()
.expect("shell should be available");
assert!(output.status.success());
assert!(String::from_utf8_lossy(&output.stdout).contains("shell-tunnel"));
}
#[test]
fn a_quoted_command_reaches_the_shell_intact() {
let output = shell_command(r#"echo ["quoted"]"#)
.stdin(Stdio::null())
.output()
.expect("shell should be available");
let text = String::from_utf8_lossy(&output.stdout);
#[cfg(windows)]
let expected = r#"["quoted"]"#;
#[cfg(unix)]
let expected = "[quoted]";
assert!(
text.contains(expected),
"the quote must reach the shell as a quote; wanted {expected:?}, got {text:?}"
);
assert!(
!text.contains(r#"\""#),
"a backslash the caller never wrote must not appear: {text:?}"
);
}
#[test]
fn a_quoted_path_is_one_argument() {
let root = env!("CARGO_MANIFEST_DIR");
#[cfg(windows)]
let line = format!(r#"dir /b "{root}\Cargo.toml""#);
#[cfg(unix)]
let line = format!(r#"ls "{root}/Cargo.toml""#);
let output = shell_command(&line)
.stdin(Stdio::null())
.output()
.expect("shell should be available");
assert!(
output.status.success(),
"a quoted path must be understood: {:?} / {:?}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn kill_tree_on_a_dead_pid_is_harmless() {
let mut child = shell_command("exit 0")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("spawn");
let pid = child.id();
let _ = child.wait();
kill_tree(pid);
}
}