use std::ffi::{CString, OsString};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::FromRawFd;
use std::process::Command;
use super::super::stderr_piped::{StderrPipedHandle, StderrPipedSpawn};
use super::{OwnedFd, pipe_cloexec, posix_spawn_file_actions_addchdir_np, resolve_disclaim_fn};
pub(crate) fn spawn_stderr_piped_disclaimed(cmd: &Command) -> io::Result<StderrPipedSpawn> {
let prog_c = CString::new(cmd.get_program().as_bytes())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "program contains NUL"))?;
let mut argv_c: Vec<CString> = vec![prog_c.clone()];
for a in cmd.get_args() {
argv_c.push(
CString::new(a.as_bytes()).map_err(|_| {
io::Error::new(io::ErrorKind::InvalidInput, "argument contains NUL")
})?,
);
}
let mut argv_ptr: Vec<*mut libc::c_char> = argv_c
.iter()
.map(|c| c.as_ptr() as *mut libc::c_char)
.collect();
argv_ptr.push(std::ptr::null_mut());
let mut env_map: std::collections::BTreeMap<OsString, OsString> = std::env::vars_os().collect();
for (k, v) in cmd.get_envs() {
match v {
Some(val) => {
env_map.insert(k.to_os_string(), val.to_os_string());
}
None => {
env_map.remove(k);
}
}
}
let mut envp_c: Vec<CString> = Vec::with_capacity(env_map.len());
for (k, v) in &env_map {
let mut buf = Vec::with_capacity(k.as_bytes().len() + v.as_bytes().len() + 1);
buf.extend_from_slice(k.as_bytes());
buf.push(b'=');
buf.extend_from_slice(v.as_bytes());
envp_c.push(CString::new(buf).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"environment variable contains NUL",
)
})?);
}
let mut envp_ptr: Vec<*mut libc::c_char> = envp_c
.iter()
.map(|c| c.as_ptr() as *mut libc::c_char)
.collect();
envp_ptr.push(std::ptr::null_mut());
let cwd_c = cmd
.get_current_dir()
.map(|p| {
CString::new(p.as_os_str().as_bytes())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "cwd contains NUL"))
})
.transpose()?;
let (err_rd, err_wr) = pipe_cloexec()?;
let mut err_rd = OwnedFd(err_rd);
let err_wr = OwnedFd(err_wr);
let dev_null = c"/dev/null";
let pid = {
unsafe {
let mut file_actions: libc::posix_spawn_file_actions_t = std::mem::zeroed();
let mut attr: libc::posix_spawnattr_t = std::mem::zeroed();
if libc::posix_spawn_file_actions_init(&mut file_actions) != 0 {
return Err(io::Error::last_os_error());
}
if libc::posix_spawnattr_init(&mut attr) != 0 {
libc::posix_spawn_file_actions_destroy(&mut file_actions);
return Err(io::Error::last_os_error());
}
if let Some(dir_c) = &cwd_c {
let rc = posix_spawn_file_actions_addchdir_np(&mut file_actions, dir_c.as_ptr());
if rc != 0 {
libc::posix_spawnattr_destroy(&mut attr);
libc::posix_spawn_file_actions_destroy(&mut file_actions);
return Err(io::Error::from_raw_os_error(rc));
}
}
let rc = libc::posix_spawn_file_actions_addopen(
&mut file_actions,
1,
dev_null.as_ptr(),
libc::O_WRONLY,
0,
);
if rc != 0 {
libc::posix_spawnattr_destroy(&mut attr);
libc::posix_spawn_file_actions_destroy(&mut file_actions);
return Err(io::Error::from_raw_os_error(rc));
}
let rc = libc::posix_spawn_file_actions_adddup2(&mut file_actions, err_wr.0, 2);
if rc != 0 {
libc::posix_spawnattr_destroy(&mut attr);
libc::posix_spawn_file_actions_destroy(&mut file_actions);
return Err(io::Error::from_raw_os_error(rc));
}
if let Some(disclaim) = resolve_disclaim_fn() {
let _ = disclaim(&mut attr, 1);
}
let mut pid: libc::pid_t = 0;
let rc = libc::posix_spawnp(
&mut pid,
prog_c.as_ptr(),
&file_actions,
&attr,
argv_ptr.as_ptr(),
envp_ptr.as_ptr(),
);
libc::posix_spawnattr_destroy(&mut attr);
libc::posix_spawn_file_actions_destroy(&mut file_actions);
if rc != 0 {
return Err(io::Error::from_raw_os_error(rc));
}
pid
}
};
drop(err_wr);
let err_fd = std::mem::replace(&mut err_rd, OwnedFd(-1)).into_raw();
let err_file = unsafe { std::fs::File::from_raw_fd(err_fd) };
Ok(StderrPipedSpawn {
stderr: Box::new(err_file),
handle: StderrPipedHandle::Disclaimed(pid),
})
}