//! Process management use futures::FutureExt; use crate::{error, sys}; /// A waitable future that will yield the results of a child process's execution. pub(crate) type WaitableChildProcess = std::pin::Pin< Box> + Send + Sync>, >; /// Tracks a child process being awaited. pub struct ChildProcess { /// A waitable future that will yield the results of a child process's execution. exec_future: WaitableChildProcess, /// If available, the process ID of the child. pid: Option, /// If available, the process group ID of the child. pgid: Option, // nash: gate token issued by `Gate::on_exec` for this command, reported // back via `Gate::on_exit` when the process completes. pub(crate) gate_token: Option, } impl ChildProcess { /// Wraps a child process and its future. pub fn new( child: sys::process::Child, pid: Option, pgid: Option, ) -> Self { Self { exec_future: Box::pin(child.wait_with_output()), pid, pgid, gate_token: None, } } /// Returns the process's ID. pub const fn pid(&self) -> Option { self.pid } /// Returns the process's group ID. pub const fn pgid(&self) -> Option { self.pgid } /// Waits for the process to exit. pub async fn wait(&mut self) -> Result { #[allow(unused_mut, reason = "only mutated on some platforms")] let mut sigtstp = sys::signal::tstp_signal_listener()?; #[allow(unused_mut, reason = "only mutated on some platforms")] let mut sigchld = sys::signal::chld_signal_listener()?; #[allow(clippy::ignored_unit_patterns)] loop { tokio::select! { output = &mut self.exec_future => { let output = output?; // nash: report process completion to the gate exactly once. if let Some(token) = self.gate_token.take() { crate::gate::gate().on_exit( token, crate::gate::ExecEnd { exit_code: exit_code_of(&output.status) }, ); } break Ok(ProcessWaitResult::Completed(output)) }, _ = sigtstp.recv() => { break Ok(ProcessWaitResult::Stopped) }, _ = sigchld.recv() => { if sys::signal::poll_for_stopped_children()? { break Ok(ProcessWaitResult::Stopped); } }, _ = sys::signal::await_ctrl_c() => { // SIGINT got thrown. Handle it and continue looping. The child should // have received it as well, and either handled it or ended up getting // terminated (in which case we'll see the child exit). }, } } } pub(crate) fn poll(&mut self) -> Option> { let checkable_future = &mut self.exec_future; let result: Option> = checkable_future .now_or_never() .map(|result| result.map_err(Into::into)); // nash: completion can also be observed via poll (e.g. job checks). if let Some(Ok(output)) = &result { if let Some(token) = self.gate_token.take() { crate::gate::gate().on_exit( token, crate::gate::ExecEnd { exit_code: exit_code_of(&output.status), }, ); } } result } } // nash: numeric exit code for gate reporting (128+signal for signal deaths). fn exit_code_of(status: &std::process::ExitStatus) -> i32 { if let Some(code) = status.code() { return code; } #[cfg(unix)] { use std::os::unix::process::ExitStatusExt; if let Some(signal) = status.signal() { return 128 + signal; } } 1 } /// Represents the result of waiting for an executing process. pub enum ProcessWaitResult { /// The process completed. Completed(std::process::Output), /// The process stopped and has not yet completed. Stopped, }