use std::os::unix::io::RawFd;
use crate::CoreError;
use crate::error::syscall_ret;
use crate::signal::SignalRuntime;
use libc::{c_char, pid_t};
use super::exec::ExecArgv;
use super::{Pipes, SpawnDrain, SpawnFdPolicy, SpawnOptions, environ, errno, make_cloexec_pipe};
#[repr(u8)]
#[derive(Clone, Copy)]
enum ChildSetupOp {
DupStdin = 1,
DupStdout = 2,
DupStderr = 3,
Setsid = 4,
Chdir = 5,
Setpgid = 6,
SignalMask = 7,
Execve = 8,
}
impl ChildSetupOp {
fn as_str(self) -> &'static str {
match self {
Self::DupStdin => "fork child dup2 stdin",
Self::DupStdout => "fork child dup2 stdout",
Self::DupStderr => "fork child dup2 stderr",
Self::Setsid => "fork child setsid",
Self::Chdir => "fork child chdir",
Self::Setpgid => "fork child setpgid",
Self::SignalMask => "fork child signal setup",
Self::Execve => "fork child execve",
}
}
fn from_u8(value: u8) -> Self {
match value {
1 => Self::DupStdin,
2 => Self::DupStdout,
3 => Self::DupStderr,
4 => Self::Setsid,
5 => Self::Chdir,
6 => Self::Setpgid,
7 => Self::SignalMask,
_ => Self::Execve,
}
}
}
unsafe fn report_child_setup_error(fd: RawFd, op: ChildSetupOp, code: i32) -> ! {
let mut msg = [0u8; 5];
msg[..4].copy_from_slice(&code.to_ne_bytes());
msg[4] = op as u8;
let mut written = 0;
while written < msg.len() {
let n = unsafe {
libc::write(
fd,
msg[written..].as_ptr().cast::<libc::c_void>(),
msg.len() - written,
)
};
if n <= 0 {
break;
}
written += n as usize;
}
unsafe {
libc::_exit(127);
}
}
fn read_child_setup_error(fd: RawFd) -> Result<Option<CoreError>, CoreError> {
let mut msg = [0u8; 5];
let mut read_len = 0;
loop {
let n = unsafe {
libc::read(
fd,
msg[read_len..].as_mut_ptr().cast::<libc::c_void>(),
msg.len() - read_len,
)
};
if n == 0 {
return Ok(None);
}
if n < 0 {
let code = errno();
if code == libc::EINTR {
continue;
}
return Err(CoreError::sys(code, "read fork child setup error"));
}
read_len += n as usize;
if read_len == msg.len() {
let code = i32::from_ne_bytes([msg[0], msg[1], msg[2], msg[3]]);
return Ok(Some(CoreError::sys(
code,
ChildSetupOp::from_u8(msg[4]).as_str(),
)));
}
}
}
fn collect_required_pipe_fds(pipes: &Pipes) -> Vec<RawFd> {
let mut fds = Vec::new();
if let Some(fd) = &pipes.stdin_r {
fds.push(fd.raw());
}
if let Some(fd) = &pipes.stdin_w {
fds.push(fd.raw());
}
if let Some(fd) = &pipes.stdout_r {
fds.push(fd.raw());
}
if let Some(fd) = &pipes.stdout_w {
fds.push(fd.raw());
}
if let Some(fd) = &pipes.stderr_r {
fds.push(fd.raw());
}
if let Some(fd) = &pipes.stderr_w {
fds.push(fd.raw());
}
fds
}
fn collect_open_fds_for_child_policy(policy: &SpawnFdPolicy) -> Result<Vec<RawFd>, CoreError> {
match policy {
SpawnFdPolicy::CloexecOnly => Ok(Vec::new()),
SpawnFdPolicy::CloseFrom3 | SpawnFdPolicy::Allowlist(_) => {
let dir_fd = unsafe {
libc::open(
c"/proc/self/fd".as_ptr(),
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
)
};
if dir_fd < 0 {
return Err(CoreError::sys(errno(), "open /proc/self/fd"));
}
let dir = unsafe { libc::fdopendir(dir_fd) };
if dir.is_null() {
let code = errno();
unsafe {
libc::close(dir_fd);
}
return Err(CoreError::sys(code, "fdopendir /proc/self/fd"));
}
let mut open_fds = Vec::new();
loop {
let entry = unsafe { libc::readdir(dir) };
if entry.is_null() {
break;
}
let name = unsafe { std::ffi::CStr::from_ptr((*entry).d_name.as_ptr()) };
if let Ok(s) = name.to_str()
&& let Ok(fd) = s.parse::<RawFd>()
&& fd != dir_fd
{
open_fds.push(fd);
}
}
unsafe {
libc::closedir(dir);
}
Ok(open_fds)
}
}
}
fn close_child_fds_for_policy(policy: &SpawnFdPolicy, required_fds: &[RawFd], open_fds: &[RawFd]) {
match policy {
SpawnFdPolicy::CloexecOnly => {}
SpawnFdPolicy::CloseFrom3 | SpawnFdPolicy::Allowlist(_) => {
for &fd in open_fds {
if fd > 2
&& !required_fds.contains(&fd)
&& !matches!(policy, SpawnFdPolicy::Allowlist(allowlist) if allowlist.contains(&fd))
{
unsafe {
libc::close(fd);
}
}
}
}
}
}
pub(super) fn spawn_fork_internal(opts: SpawnOptions) -> Result<(pid_t, SpawnDrain), CoreError> {
let mut pipes = Pipes::new(
opts.stdin.as_deref(),
opts.capture_stdout,
opts.capture_stderr,
)?;
let exe_ptr = match &opts.ctx.argv {
ExecArgv::Dynamic(v) => v[0].as_ptr(),
};
let argv = opts.ctx.get_argv_ptrs();
let envp = opts.ctx.get_envp_ptrs();
let cwd_cstr = &opts.ctx.cwd;
let (child_error_r, child_error_w) = make_cloexec_pipe()?;
let mut required_fds = collect_required_pipe_fds(&pipes);
required_fds.push(child_error_w);
let open_fds = collect_open_fds_for_child_policy(&opts.fd_policy)?;
let pid = unsafe { libc::fork() };
if pid < 0 {
unsafe {
libc::close(child_error_r);
libc::close(child_error_w);
}
pipes.close_all();
syscall_ret(-1, "fork")?;
}
if pid == 0 {
unsafe {
libc::close(child_error_r);
}
if let (Some(r), Some(_)) = (&pipes.stdin_r, &pipes.stdin_w) {
unsafe {
if libc::dup2(r.raw(), 0) < 0 {
report_child_setup_error(child_error_w, ChildSetupOp::DupStdin, errno());
}
}
}
if let (Some(_), Some(w)) = (&pipes.stdout_r, &pipes.stdout_w) {
unsafe {
if libc::dup2(w.raw(), 1) < 0 {
report_child_setup_error(child_error_w, ChildSetupOp::DupStdout, errno());
}
}
}
if let (Some(_), Some(w)) = (&pipes.stderr_r, &pipes.stderr_w) {
unsafe {
if libc::dup2(w.raw(), 2) < 0 {
report_child_setup_error(child_error_w, ChildSetupOp::DupStderr, errno());
}
}
}
pipes.close_all();
close_child_fds_for_policy(&opts.fd_policy, &required_fds, &open_fds);
if opts.pgroup.isolated {
unsafe {
if libc::setsid() < 0 {
report_child_setup_error(child_error_w, ChildSetupOp::Setsid, errno());
}
}
}
if let Some(cwd) = cwd_cstr {
unsafe {
if libc::chdir(cwd.as_ptr()) != 0 {
report_child_setup_error(child_error_w, ChildSetupOp::Chdir, errno());
}
}
}
if let Some(pg) = opts.pgroup.leader {
unsafe {
if libc::setpgid(0, pg) < 0 {
report_child_setup_error(child_error_w, ChildSetupOp::Setpgid, errno());
}
}
}
let envp_ptr = envp.as_ref().map_or_else(
|| unsafe { environ as *const *mut c_char },
|e: &Vec<*mut c_char>| e.as_ptr(),
);
if let Err(err) = SignalRuntime::unblock_all() {
unsafe {
report_child_setup_error(
child_error_w,
ChildSetupOp::SignalMask,
err.raw_os_error().unwrap_or(libc::EIO),
);
}
}
if let Err(err) = SignalRuntime::reset_default(libc::SIGPIPE) {
unsafe {
report_child_setup_error(
child_error_w,
ChildSetupOp::SignalMask,
err.raw_os_error().unwrap_or(libc::EIO),
);
}
}
unsafe {
libc::execve(
exe_ptr,
argv.as_ptr() as *const *const _,
envp_ptr as *const *const _,
);
report_child_setup_error(child_error_w, ChildSetupOp::Execve, errno());
}
}
unsafe {
libc::close(child_error_w);
}
match read_child_setup_error(child_error_r) {
Ok(Some(err)) => {
unsafe {
libc::close(child_error_r);
let mut status = 0;
let _ = libc::waitpid(pid, &mut status, 0);
}
pipes.close_all();
return Err(err);
}
Ok(None) => {}
Err(err) => {
unsafe {
libc::close(child_error_r);
}
pipes.close_all();
return Err(err);
}
}
unsafe {
libc::close(child_error_r);
}
drop(pipes.stdin_r.take());
drop(pipes.stdout_w.take());
drop(pipes.stderr_w.take());
let drain = crate::io::DrainState::new(
pipes.stdin_w.take().filter(|_| opts.stdin.is_some()),
opts.stdin,
pipes.stdout_r.take(),
pipes.stderr_r.take(),
opts.max_output,
opts.early_exit,
)?;
Ok((pid, drain))
}