use std::ffi::CString;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use super::fd::{FileDescriptor, fd_has_cloexec};
use super::unix_exec;
use super::{ExternalCommand, PassedFileDescriptor, ProcessHandle, SpawnStdio};
unsafe extern "C" {
fn posix_spawn_file_actions_addchdir_np(
actions: *mut libc::posix_spawn_file_actions_t,
path: *const libc::c_char,
) -> libc::c_int;
}
pub(crate) enum FdAction {
Close(FileDescriptor),
}
fn cstring_arg(s: &str) -> Result<CString, io::Error> {
CString::new(s)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "argument contains NUL"))
}
fn cstring_env(key: &str, value: &str) -> Result<CString, io::Error> {
CString::new(format!("{key}={value}")).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"environment entry contains NUL",
)
})
}
pub fn exec(
program: &str,
argv: &[String],
env: &[(String, String)],
cwd: &Path,
) -> Result<(), io::Error> {
unix_exec::exec_replace(program, argv, env, cwd)
}
pub(crate) fn spawn_command(
command: &ExternalCommand,
create_process_group: bool,
stdio: SpawnStdio,
fd_actions: &[FdAction],
) -> Result<libc::pid_t, io::Error> {
spawn(SpawnRequest {
program: &command.program,
argv: &command.argv,
env: &command.env,
cwd: &command.cwd,
create_process_group,
passed_fds: &command.passed_fds,
stdio,
fd_actions,
})
}
pub(crate) fn pid_from_handle(handle: ProcessHandle) -> Result<libc::pid_t, io::Error> {
if handle.as_u64() > libc::pid_t::MAX as u64 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"process handle is out of range for pid_t",
));
}
Ok(handle.as_u64() as libc::pid_t)
}
pub(crate) struct SpawnRequest<'a> {
pub(crate) program: &'a str,
pub(crate) argv: &'a [String],
pub(crate) env: &'a [(String, String)],
pub(crate) cwd: &'a Path,
pub(crate) create_process_group: bool,
pub(crate) passed_fds: &'a [PassedFileDescriptor],
pub(crate) stdio: SpawnStdio,
pub(crate) fd_actions: &'a [FdAction],
}
pub(crate) fn spawn(request: SpawnRequest<'_>) -> Result<libc::pid_t, io::Error> {
let c_prog = CString::new(request.program)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "program contains NUL"))?;
let c_cwd = CString::new(request.cwd.as_os_str().as_bytes())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "cwd contains NUL"))?;
let c_argv: Vec<CString> = request
.argv
.iter()
.map(|a| cstring_arg(a))
.collect::<Result<_, _>>()?;
let mut c_argv_ptrs: Vec<*mut libc::c_char> = c_argv
.iter()
.map(|a| a.as_ptr() as *mut libc::c_char)
.collect();
c_argv_ptrs.push(std::ptr::null_mut());
let c_env: Vec<CString> = request
.env
.iter()
.map(|(k, v)| cstring_env(k, v))
.collect::<Result<_, _>>()?;
let mut c_env_ptrs: Vec<*mut libc::c_char> = c_env
.iter()
.map(|e| e.as_ptr() as *mut libc::c_char)
.collect();
c_env_ptrs.push(std::ptr::null_mut());
let mut file_actions: libc::posix_spawn_file_actions_t = unsafe { std::mem::zeroed() };
let init_ret = unsafe { libc::posix_spawn_file_actions_init(&mut file_actions) };
if init_ret != 0 {
return Err(io::Error::from_raw_os_error(init_ret));
}
if let Err(err) = add_spawn_chdir_action(&mut file_actions, &c_cwd, request.cwd) {
unsafe { libc::posix_spawn_file_actions_destroy(&mut file_actions) };
return Err(err);
}
let mut spawn_stdio = request.stdio;
let mut parent_temp_stdio = Vec::new();
for (from_fd, to_fd) in [
(&mut spawn_stdio.stdin_fd, FileDescriptor::STDIN),
(&mut spawn_stdio.stdout_fd, FileDescriptor::STDOUT),
(&mut spawn_stdio.stderr_fd, FileDescriptor::STDERR),
] {
if *from_fd != to_fd {
continue;
}
if !fd_has_cloexec(from_fd.into_raw_fd())? {
continue;
}
let duped = from_fd.dup()?;
*from_fd = duped;
parent_temp_stdio.push(duped);
}
for (from_fd, to_fd) in [
(spawn_stdio.stdin_fd, FileDescriptor::STDIN),
(spawn_stdio.stdout_fd, FileDescriptor::STDOUT),
(spawn_stdio.stderr_fd, FileDescriptor::STDERR),
] {
if from_fd == to_fd {
continue;
}
let ret = unsafe {
libc::posix_spawn_file_actions_adddup2(
&mut file_actions,
from_fd.into_raw_fd(),
to_fd.into_raw_fd(),
)
};
if ret != 0 {
for fd in parent_temp_stdio {
fd.close();
}
unsafe { libc::posix_spawn_file_actions_destroy(&mut file_actions) };
return Err(io::Error::from_raw_os_error(ret));
}
}
for passed_fd in request.passed_fds {
let mut parent_fd = passed_fd.parent_fd;
let mut close_parent_fd_immediately = true;
if fd_has_cloexec(parent_fd.into_raw_fd())? {
let duped = parent_fd.dup()?;
parent_fd = duped;
parent_temp_stdio.push(duped);
close_parent_fd_immediately = false;
}
let ret = unsafe {
libc::posix_spawn_file_actions_adddup2(
&mut file_actions,
parent_fd.into_raw_fd(),
passed_fd.child_fd.into_raw_fd(),
)
};
if ret != 0 {
for fd in parent_temp_stdio {
fd.close();
}
unsafe { libc::posix_spawn_file_actions_destroy(&mut file_actions) };
return Err(io::Error::from_raw_os_error(ret));
}
if parent_fd == passed_fd.child_fd || !close_parent_fd_immediately {
continue;
}
let ret = unsafe {
libc::posix_spawn_file_actions_addclose(&mut file_actions, parent_fd.into_raw_fd())
};
if ret != 0 {
for fd in parent_temp_stdio {
fd.close();
}
unsafe { libc::posix_spawn_file_actions_destroy(&mut file_actions) };
return Err(io::Error::from_raw_os_error(ret));
}
}
for action in request.fd_actions {
match action {
FdAction::Close(fd) => {
let ret = unsafe {
libc::posix_spawn_file_actions_addclose(&mut file_actions, fd.into_raw_fd())
};
if ret != 0 {
for fd in parent_temp_stdio {
fd.close();
}
unsafe { libc::posix_spawn_file_actions_destroy(&mut file_actions) };
return Err(io::Error::from_raw_os_error(ret));
}
}
}
}
for fd in &parent_temp_stdio {
let ret =
unsafe { libc::posix_spawn_file_actions_addclose(&mut file_actions, fd.into_raw_fd()) };
if ret != 0 {
for fd in parent_temp_stdio {
fd.close();
}
unsafe { libc::posix_spawn_file_actions_destroy(&mut file_actions) };
return Err(io::Error::from_raw_os_error(ret));
}
}
let mut attr: libc::posix_spawnattr_t = unsafe { std::mem::zeroed() };
let attr_ret = unsafe { libc::posix_spawnattr_init(&mut attr) };
if attr_ret != 0 {
for fd in parent_temp_stdio {
fd.close();
}
unsafe { libc::posix_spawn_file_actions_destroy(&mut file_actions) };
return Err(io::Error::from_raw_os_error(attr_ret));
}
if let Err(err) = configure_spawn_attributes(&mut attr, request.create_process_group) {
for fd in parent_temp_stdio {
fd.close();
}
unsafe {
libc::posix_spawnattr_destroy(&mut attr);
libc::posix_spawn_file_actions_destroy(&mut file_actions);
}
return Err(err);
}
let mut pid: libc::pid_t = 0;
let ret = unsafe {
libc::posix_spawnp(
&mut pid,
c_prog.as_ptr(),
&file_actions,
&attr,
c_argv_ptrs.as_ptr(),
c_env_ptrs.as_ptr(),
)
};
unsafe {
libc::posix_spawnattr_destroy(&mut attr);
libc::posix_spawn_file_actions_destroy(&mut file_actions);
}
for fd in parent_temp_stdio {
fd.close();
}
if ret != 0 {
Err(io::Error::from_raw_os_error(ret))
} else {
Ok(pid)
}
}
fn configure_spawn_attributes(
attr: &mut libc::posix_spawnattr_t,
create_process_group: bool,
) -> Result<(), io::Error> {
let mut defaults: libc::sigset_t = unsafe { std::mem::zeroed() };
if unsafe { libc::sigemptyset(&mut defaults) } != 0 {
return Err(io::Error::last_os_error());
}
for sig in [libc::SIGTTOU, libc::SIGTTIN, libc::SIGTSTP, libc::SIGPIPE] {
if unsafe { libc::sigaddset(&mut defaults, sig) } != 0 {
return Err(io::Error::last_os_error());
}
}
let ret = unsafe { libc::posix_spawnattr_setsigdefault(attr, &defaults) };
if ret != 0 {
return Err(io::Error::from_raw_os_error(ret));
}
let mut flags = libc::POSIX_SPAWN_SETSIGDEF as libc::c_short;
if create_process_group {
let ret = unsafe { libc::posix_spawnattr_setpgroup(attr, 0) };
if ret != 0 {
return Err(io::Error::from_raw_os_error(ret));
}
flags |= libc::POSIX_SPAWN_SETPGROUP as libc::c_short;
}
let ret = unsafe { libc::posix_spawnattr_setflags(attr, flags) };
if ret != 0 {
return Err(io::Error::from_raw_os_error(ret));
}
Ok(())
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "linux",
target_os = "android"
))]
fn add_spawn_chdir_action(
file_actions: &mut libc::posix_spawn_file_actions_t,
c_cwd: &CString,
_cwd: &Path,
) -> Result<(), io::Error> {
let ret = unsafe { posix_spawn_file_actions_addchdir_np(file_actions, c_cwd.as_ptr()) };
if ret != 0 {
return Err(io::Error::from_raw_os_error(ret));
}
Ok(())
}
#[cfg(not(any(
target_os = "macos",
target_os = "ios",
target_os = "linux",
target_os = "android"
)))]
fn add_spawn_chdir_action(
_file_actions: &mut libc::posix_spawn_file_actions_t,
_c_cwd: &CString,
cwd: &Path,
) -> Result<(), io::Error> {
let here = std::env::current_dir()?;
if here == cwd {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"spawn cwd changes are unsupported on this platform",
))
}
}