use std::ffi::{OsStr, OsString};
use std::process::Command;
#[derive(Debug)]
pub struct NotStarted {
program: OsString,
error: std::io::Error,
}
impl NotStarted {
pub fn status(&self) -> u8 {
if self.error.kind() == std::io::ErrorKind::NotFound {
127
} else {
126
}
}
}
impl std::fmt::Display for NotStarted {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let program = self.program.to_string_lossy();
if self.error.kind() == std::io::ErrorKind::NotFound {
write!(f, "cannot run {program}: no such program on PATH")
} else {
write!(f, "cannot run {program}: {}", self.error)
}
}
}
impl std::error::Error for NotStarted {}
pub fn replace_with(
program: &OsStr,
arguments: &[OsString],
unset: Option<&str>,
set: Vec<(String, String)>,
) -> Result<std::convert::Infallible, NotStarted> {
let mut command = Command::new(resolve(program));
command.args(arguments);
if let Some(variable) = unset {
command.env_remove(variable);
}
command.envs(set);
hand_over(command, program)
}
#[cfg(unix)]
fn hand_over(
mut command: Command,
program: &OsStr,
) -> Result<std::convert::Infallible, NotStarted> {
use std::os::unix::process::CommandExt;
Err(NotStarted {
program: program.to_owned(),
error: command.exec(),
})
}
#[cfg(windows)]
fn hand_over(
mut command: Command,
program: &OsStr,
) -> Result<std::convert::Infallible, NotStarted> {
leave_interrupts_to_the_command();
let mut child = command.spawn().map_err(|error| NotStarted {
program: program.to_owned(),
error,
})?;
drop(command);
let status = child.wait().map_err(|error| NotStarted {
program: program.to_owned(),
error,
})?;
std::process::exit(status.code().unwrap_or(1));
}
#[cfg(windows)]
fn leave_interrupts_to_the_command() {
type Handler = unsafe extern "system" fn(u32) -> i32;
#[link(name = "kernel32")]
unsafe extern "system" {
fn SetConsoleCtrlHandler(handler: Option<Handler>, add: i32) -> i32;
}
const CTRL_C_EVENT: u32 = 0;
const CTRL_BREAK_EVENT: u32 = 1;
unsafe extern "system" fn leave_it(event: u32) -> i32 {
i32::from(event == CTRL_C_EVENT || event == CTRL_BREAK_EVENT)
}
unsafe {
SetConsoleCtrlHandler(Some(leave_it), 1);
}
}
#[cfg(windows)]
fn resolve(program: &OsStr) -> std::path::PathBuf {
use std::path::{Component, Path};
const STARTABLE: [&str; 4] = [".COM", ".EXE", ".BAT", ".CMD"];
let path = Path::new(program);
let bare = path.extension().is_none()
&& matches!(
path.components().collect::<Vec<_>>().as_slice(),
[Component::Normal(_)]
);
if !bare {
return path.to_path_buf();
}
let pathext = std::env::var("PATHEXT").unwrap_or_else(|_| STARTABLE.join(";"));
let extensions: Vec<&str> = pathext
.split(';')
.filter(|extension| {
STARTABLE
.iter()
.any(|startable| startable.eq_ignore_ascii_case(extension))
})
.collect();
let directories = std::env::var_os("PATH").unwrap_or_default();
for directory in std::env::split_paths(&directories) {
for extension in &extensions {
let mut name = program.to_os_string();
name.push(extension);
let candidate = directory.join(name);
if candidate.is_file() {
return candidate;
}
}
}
path.to_path_buf()
}
#[cfg(not(windows))]
fn resolve(program: &OsStr) -> &OsStr {
program
}