use std::{ffi::c_int, io, process::Command};
use super::{
ExitReason, HandleSigchld,
event::PollEvent,
event::{EventRegistry, Process, StopReason},
io_util::was_interrupted,
terminate_process,
};
use crate::{
common::bin_serde::BinPipe,
system::signal::{
SignalHandler, SignalHandlerBehavior, SignalNumber, SignalSet, SignalStream, SignalsState,
consts::*, register_handlers,
},
};
use crate::{
exec::{SpawnNoexecHandler, exec_command, handle_sigchld, signal_fmt},
log::{dev_error, dev_info, dev_warn},
system::{
ForkResult, fork, getpgid, getpgrp,
interface::ProcessId,
kill, killpg,
term::{Terminal, UserTerm},
wait::WaitOptions,
},
};
pub(super) fn exec_no_pty(
sudo_pid: ProcessId,
spawn_noexec_handler: Option<SpawnNoexecHandler>,
command: Command,
) -> io::Result<ExitReason> {
let original_signals = SignalsState::save()?;
let original_set = match SignalSet::full().and_then(|set| set.block()) {
Ok(original_set) => Some(original_set),
Err(err) => {
dev_warn!("cannot block signals: {err}");
None
}
};
let (errpipe_tx, errpipe_rx) = BinPipe::pair()?;
let ForkResult::Parent(command_pid) = unsafe { fork() }.map_err(|err| {
dev_warn!("unable to fork command process: {err}");
err
})?
else {
exec_command(command, original_set, original_signals, errpipe_tx);
};
if let Some(spawner) = spawn_noexec_handler {
spawner.spawn();
}
dev_info!("executed command with pid {command_pid}");
let mut registry = EventRegistry::new();
let mut closure = ExecClosure::new(
command_pid,
sudo_pid,
errpipe_rx,
&mut registry,
original_signals,
)?;
if let Some(set) = original_set {
if let Err(err) = set.set_mask() {
dev_warn!("cannot restore signal mask: {err}");
}
}
let command_exit_reason = match registry.event_loop(&mut closure) {
StopReason::Break(err) => return Err(err),
StopReason::Exit(reason) => reason,
};
drop(closure.signal_handlers);
Ok(command_exit_reason)
}
struct ExecClosure {
command_pid: Option<ProcessId>,
sudo_pid: ProcessId,
parent_pgrp: ProcessId,
errpipe_rx: BinPipe<i32>,
original_signals: SignalsState,
signal_stream: &'static SignalStream,
signal_handlers: [SignalHandler; ExecClosure::SIGNALS.len()],
}
impl ExecClosure {
const SIGNALS: [SignalNumber; 12] = [
SIGINT, SIGQUIT, SIGTSTP, SIGTERM, SIGHUP, SIGALRM, SIGPIPE, SIGUSR1, SIGUSR2, SIGCHLD,
SIGCONT, SIGWINCH,
];
fn new(
command_pid: ProcessId,
sudo_pid: ProcessId,
errpipe_rx: BinPipe<i32>,
registry: &mut EventRegistry<Self>,
mut original_signals: SignalsState,
) -> io::Result<Self> {
registry.register_event(&errpipe_rx, PollEvent::Readable, |_| ExecEvent::ErrPipe);
let signal_stream = SignalStream::init()?;
registry.register_event(signal_stream, PollEvent::Readable, |_| ExecEvent::Signal);
let signal_handlers = register_handlers(Self::SIGNALS, &mut original_signals)?;
Ok(Self {
command_pid: Some(command_pid),
errpipe_rx,
sudo_pid,
parent_pgrp: getpgrp(),
original_signals,
signal_stream,
signal_handlers,
})
}
fn is_self_terminating(&self, signaler_pid: ProcessId) -> bool {
if signaler_pid.is_valid() {
if Some(signaler_pid) == self.command_pid {
return true;
}
if let Ok(signaler_pgrp) = getpgid(signaler_pid) {
if Some(signaler_pgrp) == self.command_pid || signaler_pgrp == self.sudo_pid {
return true;
}
}
}
false
}
fn suspend_parent(&mut self, signal: SignalNumber) {
let mut opt_tty = UserTerm::open().ok();
let mut opt_pgrp = None;
if let Some(tty) = opt_tty.as_ref() {
if let Ok(saved_pgrp) = tty.tcgetpgrp() {
opt_pgrp = Some(saved_pgrp);
} else {
opt_tty.take();
}
}
if let Some(saved_pgrp) = opt_pgrp {
if let SIGTTOU | SIGTTIN = signal {
if saved_pgrp == self.parent_pgrp {
if let Some(command_pgrp) = self.command_pid.and_then(|pid| getpgid(pid).ok()) {
if command_pgrp != self.parent_pgrp
&& opt_tty
.as_ref()
.is_some_and(|tty| tty.tcsetpgrp_nobg(command_pgrp).is_ok())
{
if let Err(err) = killpg(command_pgrp, SIGCONT) {
dev_warn!("cannot send SIGCONT to command ({command_pgrp}): {err}");
}
return;
}
}
}
}
}
let sigtstp_handler = if signal == SIGTSTP {
SignalHandler::register(
signal,
SignalHandlerBehavior::Default,
&mut self.original_signals,
)
.map_err(|err| dev_warn!("cannot set handler for {}: {err}", signal_fmt(signal)))
.ok()
} else {
None
};
if let Err(err) = kill(self.sudo_pid, signal) {
dev_warn!(
"cannot send {} to {} (sudo): {err}",
signal_fmt(signal),
self.sudo_pid
);
}
drop(sigtstp_handler);
if let Some(saved_pgrp) = opt_pgrp {
if saved_pgrp != self.parent_pgrp {
if let Some(tty) = opt_tty {
tty.tcsetpgrp_nobg(saved_pgrp).ok();
}
}
}
}
fn on_signal(&mut self, registry: &mut EventRegistry<Self>) {
let info = match self.signal_stream.recv() {
Ok(info) => info,
Err(err) => {
dev_error!("sudo could not receive signal: {err}");
return;
}
};
dev_info!("received{}", info);
let Some(command_pid) = self.command_pid else {
dev_info!("command was terminated, ignoring signal");
return;
};
match info.signal() {
SIGCHLD => handle_sigchld(self, registry, "command", command_pid),
signal => {
if let Some(pid) = info.signaler_pid() {
if self.is_self_terminating(pid) {
return;
}
}
if signal == SIGALRM {
terminate_process(command_pid, false);
} else {
kill(command_pid, signal).ok();
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ExecEvent {
Signal,
ErrPipe,
}
impl Process for ExecClosure {
type Event = ExecEvent;
type Break = io::Error;
type Exit = ExitReason;
fn on_event(&mut self, event: Self::Event, registry: &mut EventRegistry<Self>) {
match event {
ExecEvent::Signal => self.on_signal(registry),
ExecEvent::ErrPipe => {
match self.errpipe_rx.read() {
Err(err) if was_interrupted(&err) => { }
Err(err) => registry.set_break(err),
Ok(error_code) => {
registry.set_break(io::Error::from_raw_os_error(error_code));
}
}
}
}
}
}
impl HandleSigchld for ExecClosure {
const OPTIONS: WaitOptions = WaitOptions::new().all().untraced().no_hang();
fn on_exit(&mut self, exit_code: c_int, registry: &mut EventRegistry<Self>) {
registry.set_exit(ExitReason::Code(exit_code));
self.command_pid = None;
}
fn on_term(&mut self, signal: SignalNumber, registry: &mut EventRegistry<Self>) {
registry.set_exit(ExitReason::Signal(signal));
self.command_pid = None;
}
fn on_stop(&mut self, signal: SignalNumber, _registry: &mut EventRegistry<Self>) {
self.suspend_parent(signal);
}
}