use std::ffi::OsString;
#[cfg(unix)]
use std::fs::File;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::ExitStatus;
#[cfg(unix)]
use std::process::{Child, Command, Stdio};
#[cfg(all(not(unix), not(windows)))]
use crate::unsupported_op;
#[cfg(all(not(unix), not(windows)))]
use crate::PtyError;
#[cfg(any(unix, windows))]
use crate::{backend, PtyPair};
use crate::{ProcessId, PtyMaster, Result, Signal, TerminalSize};
#[derive(Clone, Debug)]
#[cfg_attr(not(unix), allow(dead_code))]
pub struct ChildCommand {
pub(crate) program: PathBuf,
pub(crate) arg0: Option<OsString>,
pub(crate) args: Vec<OsString>,
pub(crate) env: Vec<(OsString, OsString)>,
pub(crate) clear_env: bool,
pub(crate) current_dir: Option<PathBuf>,
pub(crate) size: Option<TerminalSize>,
}
impl ChildCommand {
#[must_use]
pub fn new(program: impl Into<PathBuf>) -> Self {
Self {
program: program.into(),
arg0: None,
args: Vec::new(),
env: Vec::new(),
clear_env: false,
current_dir: None,
size: None,
}
}
#[must_use]
pub fn arg0(mut self, arg0: impl Into<OsString>) -> Self {
self.arg0 = Some(arg0.into());
self
}
#[must_use]
pub fn arg(mut self, arg: impl Into<OsString>) -> Self {
self.args.push(arg.into());
self
}
#[must_use]
pub fn args<I, S>(mut self, args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
self.args.extend(args.into_iter().map(Into::into));
self
}
#[must_use]
pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Self {
self.env.push((key.into(), value.into()));
self
}
#[must_use]
pub fn clear_env(mut self) -> Self {
self.clear_env = true;
self
}
#[must_use]
pub fn current_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.current_dir = Some(path.into());
self
}
#[must_use]
pub fn size(mut self, size: TerminalSize) -> Self {
self.size = Some(size);
self
}
pub fn spawn(self) -> Result<SpawnedPty> {
spawn_child(self)
}
}
#[derive(Debug)]
pub struct SpawnedPty {
master: PtyMaster,
child: PtyChild,
}
impl SpawnedPty {
#[must_use]
pub fn master(&self) -> &PtyMaster {
&self.master
}
#[must_use]
pub fn child(&self) -> &PtyChild {
&self.child
}
#[must_use]
pub fn child_mut(&mut self) -> &mut PtyChild {
&mut self.child
}
#[must_use]
pub fn into_parts(self) -> (PtyMaster, PtyChild) {
(self.master, self.child)
}
}
#[derive(Debug)]
pub struct PtyChild {
#[cfg(unix)]
child: Child,
#[cfg(windows)]
child: backend::WindowsChild,
pid: ProcessId,
}
impl PtyChild {
#[must_use]
pub fn pid(&self) -> ProcessId {
self.pid
}
pub fn wait(&mut self) -> Result<ExitStatus> {
#[cfg(unix)]
{
Ok(self.child.wait()?)
}
#[cfg(not(unix))]
{
#[cfg(windows)]
{
backend::wait_child(&mut self.child)
}
#[cfg(not(windows))]
{
Err(PtyError::Unsupported(unsupported_op::WAIT_FOR_PTY_CHILD))
}
}
}
pub fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
#[cfg(unix)]
{
Ok(self.child.try_wait()?)
}
#[cfg(not(unix))]
{
#[cfg(windows)]
{
backend::try_wait_child(&mut self.child)
}
#[cfg(not(windows))]
{
Err(PtyError::Unsupported(
unsupported_op::TRY_WAIT_FOR_PTY_CHILD,
))
}
}
}
#[cfg(windows)]
pub fn try_clone_for_wait(&self) -> Result<Self> {
Ok(Self {
child: backend::try_clone_child_for_wait(&self.child)?,
pid: self.pid,
})
}
#[cfg(windows)]
pub fn close_pseudoconsole(&self) {
backend::close_child_pseudoconsole(&self.child);
}
pub fn interrupt(&self) -> Result<()> {
self.kill(Signal::INT)
}
pub fn terminate_forcefully(&self) -> Result<()> {
self.kill(Signal::KILL)
}
pub fn kill(&self, signal: Signal) -> Result<()> {
#[cfg(unix)]
{
backend::kill_foreground_process_group(self.pid, signal)
}
#[cfg(not(unix))]
{
#[cfg(windows)]
{
backend::kill_child(&self.child, signal)
}
#[cfg(not(windows))]
{
let _ = signal;
Err(PtyError::Unsupported(unsupported_op::SIGNAL_PTY_FOREGROUND))
}
}
}
pub fn kill_session_leader(&self, signal: Signal) -> Result<()> {
#[cfg(unix)]
{
backend::kill_process(self.pid, signal)?;
Ok(())
}
#[cfg(not(unix))]
{
#[cfg(windows)]
{
backend::kill_child(&self.child, signal)
}
#[cfg(not(windows))]
{
let _ = signal;
Err(PtyError::Unsupported(
unsupported_op::SIGNAL_PTY_SESSION_LEADER,
))
}
}
}
#[cfg(unix)]
pub fn continue_if_stopped(&self) -> Result<bool> {
let Some(stop_signal) = backend::stopped_signal(self.pid)? else {
return Ok(false);
};
if stop_signal == libc::SIGTTIN || stop_signal == libc::SIGTTOU {
return Ok(false);
}
backend::kill_foreground_process_group(self.pid, Signal::CONT)
.or_else(|_| backend::kill_process(self.pid, Signal::CONT))?;
Ok(true)
}
}
#[cfg(unix)]
fn spawn_child(command: ChildCommand) -> Result<SpawnedPty> {
let pair = match command.size {
Some(size) => PtyPair::open_with_size(size)?,
None => PtyPair::open()?,
};
let (master, slave) = pair.into_split();
let raw_master_fd = master.raw_fd();
let stdin = File::from(slave.try_clone()?.into_owned_fd());
let stdout = File::from(slave.try_clone()?.into_owned_fd());
let stderr = File::from(slave.into_owned_fd());
let mut std_command = Command::new(&command.program);
if let Some(arg0) = &command.arg0 {
std_command.arg0(arg0);
}
std_command.args(&command.args);
std_command.stdin(Stdio::from(stdin));
std_command.stdout(Stdio::from(stdout));
std_command.stderr(Stdio::from(stderr));
if command.clear_env {
std_command.env_clear();
}
if let Some(current_dir) = &command.current_dir {
std_command.current_dir(current_dir);
}
for (key, value) in &command.env {
std_command.env(key, value);
}
let pre_exec = move || {
rmux_os::signals::reset_child_signal_dispositions()?;
backend::setup_child_controlling_terminal(raw_master_fd)
};
unsafe {
std_command.pre_exec(pre_exec);
}
let child = std_command.spawn()?;
let pid = ProcessId::new(child.id())?;
Ok(SpawnedPty {
master,
child: PtyChild { child, pid },
})
}
#[cfg(not(unix))]
fn spawn_child(_command: ChildCommand) -> Result<SpawnedPty> {
#[cfg(windows)]
{
let pair = match _command.size {
Some(size) => PtyPair::open_with_size(size)?,
None => PtyPair::open()?,
};
let master = pair.into_master();
let child = backend::spawn_child(_command, master.windows_pty())?;
let pid = child.pid();
Ok(SpawnedPty {
master,
child: PtyChild { child, pid },
})
}
#[cfg(not(windows))]
{
Err(PtyError::Unsupported(unsupported_op::SPAWN_PTY_CHILD))
}
}