use std::sync::{Arc, atomic::AtomicU32};
use std::io;
use std::process::{Child, Command};
use crate::ExitStatus;
#[cfg(unix)]
use std::{io::IsTerminal, sync::atomic::Ordering};
#[cfg(unix)]
pub use child_pgroup::stdin_fd;
#[cfg(unix)]
use nix::{sys::signal, sys::wait, unistd::Pid};
pub struct ForegroundChild {
inner: Child,
#[cfg(unix)]
pipeline_state: Option<Arc<(AtomicU32, AtomicU32)>>,
#[cfg(unix)]
interactive: bool,
}
impl ForegroundChild {
#[cfg(not(unix))]
pub fn spawn(mut command: Command) -> io::Result<Self> {
command.spawn().map(|child| Self { inner: child })
}
#[cfg(unix)]
pub fn spawn(
mut command: Command,
interactive: bool,
background: bool,
pipeline_state: &Arc<(AtomicU32, AtomicU32)>,
) -> io::Result<Self> {
let interactive = interactive && io::stdin().is_terminal();
let uses_dedicated_process_group = interactive || background;
if uses_dedicated_process_group {
let (pgrp, pcnt) = pipeline_state.as_ref();
let existing_pgrp = pgrp.load(Ordering::SeqCst);
child_pgroup::prepare_command(&mut command, existing_pgrp, background);
command
.spawn()
.map(|child| {
child_pgroup::set(&child, existing_pgrp, background);
let _ = pcnt.fetch_add(1, Ordering::SeqCst);
if existing_pgrp == 0 {
pgrp.store(child.id(), Ordering::SeqCst);
}
Self {
inner: child,
pipeline_state: Some(pipeline_state.clone()),
interactive,
}
})
.inspect_err(|_e| {
if interactive {
child_pgroup::reset();
}
})
} else {
command.spawn().map(|child| Self {
inner: child,
pipeline_state: None,
interactive,
})
}
}
pub fn wait(&mut self) -> io::Result<ForegroundWaitStatus> {
#[cfg(unix)]
{
let child_pid = Pid::from_raw(self.inner.id() as i32);
unix_wait(child_pid).inspect(|result| {
if let (true, ForegroundWaitStatus::Frozen(_)) = (self.interactive, result) {
child_pgroup::reset();
}
})
}
#[cfg(not(unix))]
self.as_mut().wait().map(Into::into)
}
pub fn pid(&self) -> u32 {
self.inner.id()
}
}
#[cfg(unix)]
fn unix_wait(child_pid: Pid) -> std::io::Result<ForegroundWaitStatus> {
use ForegroundWaitStatus::*;
loop {
let status = wait::waitpid(child_pid, Some(wait::WaitPidFlag::WUNTRACED));
match status {
Err(e) => {
return Err(e.into());
}
Ok(wait::WaitStatus::Exited(_, status)) => {
return Ok(Finished(ExitStatus::Exited(status)));
}
Ok(wait::WaitStatus::Signaled(_, signal, core_dumped)) => {
return Ok(Finished(ExitStatus::Signaled {
signal: signal as i32,
core_dumped,
}));
}
Ok(wait::WaitStatus::Stopped(_, _)) => {
return Ok(Frozen(UnfreezeHandle { child_pid }));
}
Ok(_) => {
}
};
}
}
pub enum ForegroundWaitStatus {
Finished(ExitStatus),
Frozen(UnfreezeHandle),
}
impl From<std::process::ExitStatus> for ForegroundWaitStatus {
fn from(status: std::process::ExitStatus) -> Self {
ForegroundWaitStatus::Finished(status.into())
}
}
#[derive(Debug)]
pub struct UnfreezeHandle {
#[cfg(unix)]
child_pid: Pid,
}
impl UnfreezeHandle {
#[cfg(unix)]
pub fn unfreeze(
self,
pipeline_state: Option<Arc<(AtomicU32, AtomicU32)>>,
) -> io::Result<ForegroundWaitStatus> {
let _guard = pipeline_state.map(|pipeline_state| {
ForegroundGuard::new(self.child_pid.as_raw() as u32, &pipeline_state)
});
if let Err(err) = signal::killpg(self.child_pid, signal::SIGCONT) {
return Err(err.into());
}
let child_pid = self.child_pid;
unix_wait(child_pid)
}
pub fn pid(&self) -> u32 {
#[cfg(unix)]
{
self.child_pid.as_raw() as u32
}
#[cfg(not(unix))]
0
}
}
impl AsMut<Child> for ForegroundChild {
fn as_mut(&mut self) -> &mut Child {
&mut self.inner
}
}
#[cfg(unix)]
impl Drop for ForegroundChild {
fn drop(&mut self) {
if let Some((pgrp, pcnt)) = self.pipeline_state.as_deref()
&& pcnt.fetch_sub(1, Ordering::SeqCst) == 1
{
pgrp.store(0, Ordering::SeqCst);
if self.interactive {
child_pgroup::reset()
}
}
}
}
#[derive(Debug)]
pub struct ForegroundGuard {
#[cfg(unix)]
pgrp: Option<u32>,
#[cfg(unix)]
pipeline_state: Arc<(AtomicU32, AtomicU32)>,
}
impl ForegroundGuard {
#[cfg(unix)]
pub fn new(
pid: u32,
pipeline_state: &Arc<(AtomicU32, AtomicU32)>,
) -> std::io::Result<ForegroundGuard> {
use nix::unistd::{self, Pid};
let pid_nix = Pid::from_raw(pid as i32);
let (pgrp, pcnt) = pipeline_state.as_ref();
loop {
if pgrp
.compare_exchange(0, pid, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
let _ = pcnt.fetch_add(1, Ordering::SeqCst);
let guard = ForegroundGuard {
pgrp: None,
pipeline_state: pipeline_state.clone(),
};
log::trace!("Giving control of the terminal to the process group, pid={pid}");
unistd::tcsetpgrp(unsafe { stdin_fd() }, pid_nix)?;
return Ok(guard);
} else if pcnt
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
if count > 0 { Some(count + 1) } else { None }
})
.is_ok()
{
let pgrp = pgrp.load(Ordering::SeqCst);
log::trace!(
"Will ask the process pid={pid} to join pgrp={pgrp} for control of the \
terminal"
);
return Ok(ForegroundGuard {
pgrp: Some(pgrp),
pipeline_state: pipeline_state.clone(),
});
} else {
continue;
}
}
}
#[cfg(not(unix))]
pub fn new(
pid: u32,
pipeline_state: &Arc<(AtomicU32, AtomicU32)>,
) -> std::io::Result<ForegroundGuard> {
let _ = (pid, pipeline_state);
Ok(ForegroundGuard {})
}
pub fn pgrp(&self) -> Option<u32> {
#[cfg(unix)]
{
self.pgrp
}
#[cfg(not(unix))]
{
None
}
}
fn reset_internal(&mut self) {
#[cfg(unix)]
{
log::trace!("Leaving the foreground group");
let (pgrp, pcnt) = self.pipeline_state.as_ref();
if pcnt.fetch_sub(1, Ordering::SeqCst) == 1 {
pgrp.store(0, Ordering::SeqCst);
child_pgroup::reset()
}
}
}
}
impl Drop for ForegroundGuard {
fn drop(&mut self) {
self.reset_internal();
}
}
#[cfg(unix)]
mod child_pgroup {
use nix::{
sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction},
unistd::{self, Pid},
};
use std::{
io::Write,
os::{
fd::{AsFd, BorrowedFd},
unix::prelude::CommandExt,
},
process::{Child, Command},
};
pub unsafe fn stdin_fd() -> impl AsFd {
unsafe { BorrowedFd::borrow_raw(nix::libc::STDIN_FILENO) }
}
pub fn prepare_command(external_command: &mut Command, existing_pgrp: u32, background: bool) {
unsafe {
external_command.pre_exec(move || {
set_foreground_pid(Pid::this(), existing_pgrp, background);
let default = SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty());
let _ = sigaction(Signal::SIGQUIT, &default);
let _ = sigaction(Signal::SIGTSTP, &default);
let _ = sigaction(Signal::SIGTERM, &default);
Ok(())
});
}
}
pub fn set(process: &Child, existing_pgrp: u32, background: bool) {
set_foreground_pid(
Pid::from_raw(process.id() as i32),
existing_pgrp,
background,
);
}
fn set_foreground_pid(pid: Pid, existing_pgrp: u32, background: bool) {
let pgrp = if existing_pgrp == 0 {
pid
} else {
Pid::from_raw(existing_pgrp as i32)
};
let _ = unistd::setpgid(pid, pgrp);
if !background {
let _ = unistd::tcsetpgrp(unsafe { stdin_fd() }, pgrp);
}
}
pub fn reset() {
if let Err(e) = unistd::tcsetpgrp(unsafe { stdin_fd() }, unistd::getpgrp()) {
let _ = writeln!(
std::io::stderr(),
"ERROR: reset foreground id failed, tcsetpgrp result: {e:?}"
);
}
}
}