use std::{ffi::c_int, io, os::unix::process::CommandExt, process::Command};
use crate::exec::{opt_fmt, signal_fmt};
use crate::system::signal::{
consts::*, register_handlers, SignalHandler, SignalHandlerBehavior, SignalNumber, SignalSet,
SignalStream,
};
use crate::{
common::bin_serde::BinPipe,
exec::{
event::{EventRegistry, Process},
io_util::{retry_while_interrupted, was_interrupted},
use_pty::backchannel::{MonitorBackchannel, MonitorMessage, ParentMessage},
},
};
use crate::{
exec::{
event::{PollEvent, StopReason},
use_pty::{SIGCONT_BG, SIGCONT_FG},
ProcessOutput,
},
log::{dev_error, dev_info, dev_warn},
system::FileCloser,
};
use crate::{
exec::{handle_sigchld, terminate_process, HandleSigchld},
system::{
fork, getpgid, getpgrp,
interface::ProcessId,
kill, setpgid, setsid,
term::{PtyFollower, Terminal},
wait::{Wait, WaitError, WaitOptions},
ForkResult,
},
};
use super::CommandStatus;
pub(super) fn exec_monitor(
pty_follower: PtyFollower,
command: Command,
foreground: bool,
backchannel: &mut MonitorBackchannel,
mut file_closer: FileCloser,
original_set: Option<SignalSet>,
) -> io::Result<ProcessOutput> {
match SignalHandler::register(SIGTTIN, SignalHandlerBehavior::Ignore) {
Ok(handler) => handler.forget(),
Err(err) => dev_warn!("cannot set handler for SIGTTIN: {err}"),
}
match SignalHandler::register(SIGTTOU, SignalHandlerBehavior::Ignore) {
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 (mut errpipe_tx, errpipe_rx) = BinPipe::pair()?;
file_closer.except(&errpipe_tx);
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::ExecCommand);
let ForkResult::Parent(command_pid) = fork().map_err(|err| {
dev_warn!("unable to fork command process: {err}");
err
})?
else {
drop(errpipe_rx);
let err = exec_command(command, foreground, pty_follower, file_closer, original_set);
dev_warn!("failed to execute command: {err}");
if let Some(error_code) = err.raw_os_error() {
errpipe_tx.write(&error_code).ok();
}
return Ok(ProcessOutput::ChildExit);
};
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,
)?;
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 progess 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}")
}
}
}
Ok(ProcessOutput::ChildExit)
}
fn exec_command(
mut command: Command,
foreground: bool,
pty_follower: PtyFollower,
file_closer: FileCloser,
original_set: Option<SignalSet>,
) -> io::Error {
let command_pid = std::process::id() as ProcessId;
setpgid(0, command_pid).ok();
if foreground {
while !pty_follower.tcgetpgrp().is_ok_and(|pid| pid == command_pid) {
std::thread::sleep(std::time::Duration::from_micros(1));
}
}
drop(pty_follower);
if let Err(err) = file_closer.close_the_universe() {
return err;
}
if let Some(set) = original_set {
if let Err(err) = set.set_mask() {
dev_warn!("cannot restore signal mask: {err}");
}
}
command.exec()
}
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>,
) -> 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)?;
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::ExecCommand => 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) => {
self.backchannel
.send(&ParentMessage::IoError(error_code))
.ok();
}
}
}
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{} {} from {}",
opt_fmt(info.is_user_signaled(), " user signaled"),
info.signal(),
info.pid()
);
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),
_ if info.is_user_signaled()
&& is_self_terminating(info.pid(), command_pid, self.command_pgrp) => {}
signal => self.send_signal(signal, command_pid, false),
}
}
}
fn is_self_terminating(
signaler_pid: ProcessId,
command_pid: ProcessId,
command_pgrp: ProcessId,
) -> bool {
if signaler_pid != 0 {
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<'a> Process for MonitorClosure<'a> {
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<'a> HandleSigchld for MonitorClosure<'a> {
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();
}
}