use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
fn cstring_arg(arg: &str) -> Result<CString, std::io::Error> {
CString::new(arg)
.map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "argument contains NUL"))
}
fn cstring_env(key: &str, value: &str) -> Result<CString, std::io::Error> {
CString::new(format!("{key}={value}")).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"environment entry contains NUL",
)
})
}
fn cstring_path(path: &str) -> Result<CString, std::io::Error> {
CString::new(path)
.map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "path contains NUL"))
}
pub(crate) fn exec_replace(
program: &str,
argv: &[String],
env: &[(String, String)],
cwd: &Path,
) -> Result<(), std::io::Error> {
let c_args: Vec<CString> = argv
.iter()
.map(|a| cstring_arg(a))
.collect::<Result<_, _>>()?;
let c_ptrs: Vec<*const libc::c_char> = c_args
.iter()
.map(|a| a.as_ptr())
.chain(std::iter::once(std::ptr::null()))
.collect();
let c_env: Vec<CString> = env
.iter()
.map(|(k, v)| cstring_env(k, v))
.collect::<Result<_, _>>()?;
let c_env_ptrs: Vec<*const libc::c_char> = c_env
.iter()
.map(|e| e.as_ptr())
.chain(std::iter::once(std::ptr::null()))
.collect();
let c_cwd = CString::new(cwd.as_os_str().as_bytes())
.map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "cwd contains NUL"))?;
if unsafe { libc::chdir(c_cwd.as_ptr()) } != 0 {
return Err(std::io::Error::last_os_error());
}
let mut candidates: Vec<String> = Vec::new();
if program.contains('/') {
candidates.push(program.to_string());
} else {
let path = env
.iter()
.find_map(|(k, v)| if k == "PATH" { Some(v.as_str()) } else { None })
.unwrap_or("/usr/bin:/bin");
for dir in path.split(':') {
candidates.push(format!("{dir}/{program}"));
}
}
let mut last_err = std::io::Error::from_raw_os_error(libc::ENOENT);
for candidate in &candidates {
let c_prog = cstring_path(candidate)?;
unsafe { libc::execve(c_prog.as_ptr(), c_ptrs.as_ptr(), c_env_ptrs.as_ptr()) };
let err = std::io::Error::last_os_error();
if err.raw_os_error() != Some(libc::ENOENT) {
last_err = err;
break;
}
last_err = err;
}
Err(last_err)
}