use super::blocked_signals_for_job;
use super::PATH_BSHELL;
use crate::exec::{is_thompson_shell_script, PgroupPolicy};
use crate::proc::Job;
use crate::redirection::Dup2List;
use crate::signal::SIGNALS_TO_DEFAULT;
use errno::Errno;
use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t};
use std::ffi::{CStr, CString};
use std::mem::MaybeUninit;
fn check_fail(res: i32) -> Result<(), Errno> {
match res {
0 => Ok(()),
err => Err(Errno(err)),
}
}
struct Attr(posix_spawnattr_t);
impl Attr {
fn new() -> Result<Self, Errno> {
unsafe {
let mut attr = MaybeUninit::uninit();
check_fail(libc::posix_spawnattr_init(attr.as_mut_ptr()))?;
Ok(Self(attr.assume_init()))
}
}
fn set_flags(&mut self, flags: libc::c_short) -> Result<(), Errno> {
unsafe { check_fail(libc::posix_spawnattr_setflags(&mut self.0, flags)) }
}
fn set_pgroup(&mut self, pgroup: libc::pid_t) -> Result<(), Errno> {
unsafe { check_fail(libc::posix_spawnattr_setpgroup(&mut self.0, pgroup)) }
}
fn set_sigdefault(&mut self, sigs: &libc::sigset_t) -> Result<(), Errno> {
unsafe { check_fail(libc::posix_spawnattr_setsigdefault(&mut self.0, sigs)) }
}
fn set_sigmask(&mut self, sigs: &libc::sigset_t) -> Result<(), Errno> {
unsafe { check_fail(libc::posix_spawnattr_setsigmask(&mut self.0, sigs)) }
}
}
impl Drop for Attr {
fn drop(&mut self) {
unsafe {
let _ = libc::posix_spawnattr_destroy(&mut self.0);
}
}
}
struct FileActions(posix_spawn_file_actions_t);
impl FileActions {
fn new() -> Result<Self, Errno> {
unsafe {
let mut actions = MaybeUninit::uninit();
check_fail(libc::posix_spawn_file_actions_init(actions.as_mut_ptr()))?;
Ok(Self(actions.assume_init()))
}
}
fn add_close(&mut self, fd: libc::c_int) -> Result<(), Errno> {
unsafe { check_fail(libc::posix_spawn_file_actions_addclose(&mut self.0, fd)) }
}
fn add_dup2(&mut self, src: libc::c_int, target: libc::c_int) -> Result<(), Errno> {
unsafe {
check_fail(libc::posix_spawn_file_actions_adddup2(
&mut self.0,
src,
target,
))
}
}
}
impl Drop for FileActions {
fn drop(&mut self) {
unsafe {
let _ = libc::posix_spawn_file_actions_destroy(&mut self.0);
}
}
}
pub struct PosixSpawner {
attr: Attr,
actions: FileActions,
}
impl PosixSpawner {
pub fn new(
j: &Job,
pgroup_policy: PgroupPolicy,
dup2s: &Dup2List,
) -> Result<PosixSpawner, Errno> {
let mut attr = Attr::new()?;
let mut actions = FileActions::new()?;
let desired_pgid = match pgroup_policy {
PgroupPolicy::Inherit => None,
PgroupPolicy::Lead => Some(0),
PgroupPolicy::Join(pid) => Some(pid),
};
let mut flags = 0;
flags |= libc::POSIX_SPAWN_SETSIGDEF;
flags |= libc::POSIX_SPAWN_SETSIGMASK;
if desired_pgid.is_some() {
flags |= libc::POSIX_SPAWN_SETPGROUP;
}
attr.set_flags(flags.try_into().expect("Flags should fit in c_short"))?;
if let Some(desired_pgid) = desired_pgid {
attr.set_pgroup(desired_pgid)?;
}
attr.set_sigdefault(&SIGNALS_TO_DEFAULT)?;
let mut sigmask = MaybeUninit::uninit();
unsafe { libc::sigemptyset(sigmask.as_mut_ptr()) };
let mut sigmask = unsafe { sigmask.assume_init() };
blocked_signals_for_job(j, &mut sigmask);
attr.set_sigmask(&sigmask)?;
for act in dup2s.get_actions() {
if act.target < 0 {
actions.add_close(act.src)?;
} else {
actions.add_dup2(act.src, act.target)?;
}
}
Ok(PosixSpawner { attr, actions })
}
pub(crate) fn spawn(
&mut self,
cmd: *const c_char,
argv: *const *mut c_char,
envp: *const *mut c_char,
) -> Result<libc::pid_t, Errno> {
let mut pid = -1;
let spawned = check_fail(unsafe {
libc::posix_spawn(&mut pid, cmd, &self.actions.0, &self.attr.0, argv, envp)
});
if spawned.is_ok() {
return Ok(pid);
}
let spawn_err = spawned.unwrap_err();
let cmdcstr = unsafe { CStr::from_ptr(cmd) };
if spawn_err.0 == libc::ENOEXEC && is_thompson_shell_script(cmdcstr) {
let mut argv2 = vec![PATH_BSHELL.as_ptr().cast_mut().cast()];
let cmd2: CString = CString::new(cmdcstr.to_bytes()).unwrap();
argv2.push(cmd2.as_ptr().cast_mut());
for i in 1.. {
let ptr = unsafe { argv.offset(i).read() };
if ptr.is_null() {
break;
}
argv2.push(ptr);
}
argv2.push(std::ptr::null_mut());
check_fail(unsafe {
libc::posix_spawn(
&mut pid,
PATH_BSHELL.as_ptr().cast(),
&self.actions.0,
&self.attr.0,
argv2.as_ptr(),
envp,
)
})?;
return Ok(pid);
}
Err(spawn_err)
}
}