use std::{convert::Infallible, ffi::c_int, io, process::Command};
use crate::exec::{opt_fmt, signal_fmt};
use crate::system::signal::{
SignalHandler, SignalHandlerBehavior, SignalNumber, SignalSet, SignalStream, SignalsState,
consts::*, register_handlers,
};
use crate::{
common::bin_serde::BinPipe,
exec::{
event::{EventRegistry, Process},
exec_command,
io_util::{retry_while_interrupted, was_interrupted},
use_pty::backchannel::{MonitorBackchannel, MonitorMessage, ParentMessage},
},
};
use crate::{
exec::{HandleSigchld, handle_sigchld, terminate_process},
system::{
_exit, ForkResult, fork, getpgid, getpgrp,
interface::ProcessId,
kill, setpgid, setsid,
term::{PtyFollower, Terminal},
wait::{Wait, WaitError, WaitOptions},
},
};
use crate::{
exec::{
event::{PollEvent, StopReason},
use_pty::{SIGCONT_BG, SIGCONT_FG},
},
log::{dev_error, dev_info, dev_warn},
};
use super::CommandStatus;
pub(super) fn exec_monitor(
pty_follower: PtyFollower,
command: Command,
foreground: bool,
backchannel: &mut MonitorBackchannel,
original_set: Option<SignalSet>,
mut original_signals: SignalsState,
) -> io::Result<Infallible> {
match SignalHandler::register(
SIGTTIN,
SignalHandlerBehavior::Ignore,
&mut original_signals,
) {
Ok(handler) => handler.forget(),
Err(err) => dev_warn!("cannot set handler for SIGTTIN: {err}"),
}
match SignalHandler::register(
SIGTTOU,
SignalHandlerBehavior::Ignore,
&mut original_signals,
) {
Ok(handler) => handler.forget(),
Err(err) => dev_warn!("cannot set handler for SIGTTOU: {err}"),
}
setsid().map_err(|err| {
dev_warn!("cannot start a new session: {err}");
err
})?;
pty_follower.make_controlling_terminal().map_err(|err| {
dev_warn!("cannot set the controlling terminal: {err}");
err
})?;
let (errpipe_tx, errpipe_rx) = BinPipe::pair()?;
let event = retry_while_interrupted(|| backchannel.recv()).map_err(|err| {
dev_warn!("cannot receive green light from parent: {err}");
err
})?;
debug_assert_eq!(event, MonitorMessage::Edge);
let ForkResult::Parent(command_pid) = unsafe { fork() }.map_err(|err| {
dev_warn!("unable to fork command process: {err}");
err
})?
else {
drop(errpipe_rx);
let command_pid = ProcessId::new(std::process::id() as i32);
setpgid(ProcessId::new(0), command_pid).ok();
if foreground {
while !pty_follower.tcgetpgrp().is_ok_and(|pid| pid == command_pid) {
std::thread::yield_now();
}
}
drop(pty_follower);
exec_command(command, original_set, original_signals, errpipe_tx)
};
if let Err(err) = backchannel.send(&ParentMessage::CommandPid(command_pid)) {
dev_warn!("cannot send command PID to parent: {err}");
}
let mut registry = EventRegistry::new();
let mut closure = MonitorClosure::new(
command_pid,
pty_follower,
errpipe_rx,
backchannel,
&mut registry,
&mut original_signals,
)?;
if let Some(set) = original_set {
if let Err(err) = set.set_mask() {
dev_warn!("cannot restore signal mask: {err}");
}
}
if foreground {
if let Err(err) = closure.pty_follower.tcsetpgrp(closure.command_pgrp) {
dev_error!(
"cannot set foreground process group to {} (command): {err}",
closure.command_pgrp
);
}
}
let reason = registry.event_loop(&mut closure);
if let Some(command_pid) = closure.command_pid {
terminate_process(command_pid, true);
loop {
match command_pid.wait(WaitOptions::new()) {
Err(WaitError::Io(err)) if was_interrupted(&err) => {}
_ => break,
}
}
}
if let Err(err) = closure.pty_follower.tcsetpgrp(closure.monitor_pgrp) {
dev_error!(
"cannot set foreground process group to {} (monitor): {err}",
closure.monitor_pgrp
);
}
closure.backchannel.set_nonblocking_assertions(false);
match reason {
StopReason::Break(err) => match err.try_into() {
Ok(msg) => {
if let Err(err) = closure.backchannel.send(&msg) {
dev_warn!("cannot send message over backchannel: {err}")
}
}
Err(err) => {
dev_warn!("socket error `{err:?}` cannot be converted to a message")
}
},
StopReason::Exit(command_status) => {
if let Err(err) = closure.backchannel.send(&command_status.into()) {
dev_warn!("cannot send message over backchannel: {err}")
}
}
}
let event = retry_while_interrupted(|| backchannel.recv()).map_err(|err| {
dev_warn!("cannot receive red light from parent: {err}");
err
})?;
debug_assert_eq!(event, MonitorMessage::Edge);
_exit(1);
}
struct MonitorClosure<'a> {
command_pid: Option<ProcessId>,
command_pgrp: ProcessId,
monitor_pgrp: ProcessId,
pty_follower: PtyFollower,
errpipe_rx: BinPipe<i32>,
backchannel: &'a mut MonitorBackchannel,
signal_stream: &'static SignalStream,
_signal_handlers: [SignalHandler; MonitorClosure::SIGNALS.len()],
}
impl<'a> MonitorClosure<'a> {
const SIGNALS: [SignalNumber; 8] = [
SIGINT, SIGQUIT, SIGTSTP, SIGTERM, SIGHUP, SIGUSR1, SIGUSR2, SIGCHLD,
];
fn new(
command_pid: ProcessId,
pty_follower: PtyFollower,
errpipe_rx: BinPipe<i32>,
backchannel: &'a mut MonitorBackchannel,
registry: &mut EventRegistry<Self>,
original_signals: &mut SignalsState,
) -> io::Result<Self> {
let monitor_pgrp = getpgrp();
registry.register_event(&errpipe_rx, PollEvent::Readable, |_| {
MonitorEvent::ReadableErrPipe
});
backchannel.set_nonblocking_assertions(true);
registry.register_event(backchannel, PollEvent::Readable, |_| {
MonitorEvent::ReadableBackchannel
});
let signal_stream = SignalStream::init()?;
registry.register_event(signal_stream, PollEvent::Readable, |_| MonitorEvent::Signal);
let signal_handlers = register_handlers(Self::SIGNALS, original_signals)?;
let command_pgrp = command_pid;
if let Err(err) = setpgid(command_pid, command_pgrp) {
dev_warn!("cannot set process group ID for process: {err}");
};
Ok(Self {
command_pid: Some(command_pid),
command_pgrp,
monitor_pgrp,
pty_follower,
errpipe_rx,
backchannel,
signal_stream,
_signal_handlers: signal_handlers,
})
}
fn read_backchannel(&mut self, registry: &mut EventRegistry<Self>) {
match self.backchannel.recv() {
Err(err) => {
if err.kind() != io::ErrorKind::Interrupted {
dev_warn!("cannot read from backchannel: {err}");
registry.set_break(err);
}
}
Ok(event) => {
match event {
MonitorMessage::Edge => unreachable!(),
MonitorMessage::Signal(signal) => {
if let Some(command_pid) = self.command_pid {
self.send_signal(signal, command_pid, true)
}
}
}
}
}
}
fn read_errpipe(&mut self, registry: &mut EventRegistry<Self>) {
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));
}
}
}
fn send_signal(&self, signal: c_int, command_pid: ProcessId, from_parent: bool) {
dev_info!(
"sending {}{} to command",
signal_fmt(signal),
opt_fmt(from_parent, " from parent"),
);
match signal {
SIGALRM => {
terminate_process(command_pid, false);
}
SIGCONT_FG => {
if let Err(err) = self.pty_follower.tcsetpgrp(self.command_pgrp) {
dev_error!(
"cannot set the foreground process group to {} (command): {err}",
self.command_pgrp
);
}
kill(command_pid, SIGCONT).ok();
}
SIGCONT_BG => {
if let Err(err) = self.pty_follower.tcsetpgrp(self.monitor_pgrp) {
dev_error!(
"cannot set the foreground process group to {} (monitor): {err}",
self.monitor_pgrp
);
}
kill(command_pid, SIGCONT).ok();
}
signal => {
kill(command_pid, signal).ok();
}
}
}
fn on_signal(&mut self, registry: &mut EventRegistry<Self>) {
let info = match self.signal_stream.recv() {
Ok(info) => info,
Err(err) => {
dev_error!("could not receive signal: {err}");
return;
}
};
dev_info!("monitor 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 is_self_terminating(pid, command_pid, self.command_pgrp) {
return;
}
}
self.send_signal(signal, command_pid, false)
}
}
}
}
fn is_self_terminating(
signaler_pid: ProcessId,
command_pid: ProcessId,
command_pgrp: ProcessId,
) -> bool {
if signaler_pid.is_valid() {
if signaler_pid == command_pid {
return true;
}
if let Ok(grp_leader) = getpgid(signaler_pid) {
if grp_leader == command_pgrp {
return true;
}
}
}
false
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MonitorEvent {
Signal,
ReadableErrPipe,
ReadableBackchannel,
}
impl Process for MonitorClosure<'_> {
type Event = MonitorEvent;
type Break = io::Error;
type Exit = CommandStatus;
fn on_event(&mut self, event: Self::Event, registry: &mut EventRegistry<Self>) {
match event {
MonitorEvent::Signal => self.on_signal(registry),
MonitorEvent::ReadableErrPipe => self.read_errpipe(registry),
MonitorEvent::ReadableBackchannel => self.read_backchannel(registry),
}
}
}
impl HandleSigchld for MonitorClosure<'_> {
const OPTIONS: WaitOptions = WaitOptions::new().untraced().no_hang();
fn on_exit(&mut self, exit_code: c_int, registry: &mut EventRegistry<Self>) {
registry.set_exit(CommandStatus::Exit(exit_code));
self.command_pid = None;
}
fn on_term(&mut self, signal: c_int, registry: &mut EventRegistry<Self>) {
registry.set_exit(CommandStatus::Term(signal));
self.command_pid = None;
}
fn on_stop(&mut self, signal: c_int, _registry: &mut EventRegistry<Self>) {
if let Ok(pgrp) = self.pty_follower.tcgetpgrp() {
if pgrp != self.monitor_pgrp {
self.command_pgrp = pgrp;
}
}
self.backchannel
.send(&CommandStatus::Stop(signal).into())
.ok();
}
}