only-engine 0.5.1

Execution planning and runtime engine for only tasks.
Documentation
use std::path::Path;
use std::sync::mpsc::Sender;
use std::thread;
use std::time::Duration;

use super::{
    CommandStatus, OutputChunk, OutputStream, TerminalContext, join_output_reader,
    pty_command_status, spawn_output_reader, wait_for_pty_child,
};
use crate::EngineError;

pub(crate) struct ProcessTree {
    pid: u32,
}

impl ProcessTree {
    pub(crate) fn attach_pipe(
        child: &mut std::process::Child,
        _program: &str,
    ) -> Result<Self, EngineError> {
        Ok(Self { pid: child.id() })
    }

    pub(crate) fn attach_pty(child: &mut dyn portable_pty::Child) -> Result<Self, EngineError> {
        let pid = child.process_id().ok_or_else(|| {
            EngineError::Runtime("PTY child did not expose a process id".to_string())
        })?;
        Ok(Self { pid })
    }

    pub(crate) fn terminate(&self) -> Result<(), EngineError> {
        send_group_signal(self.pid, libc::SIGTERM);
        thread::sleep(Duration::from_millis(200));
        send_group_signal(self.pid, libc::SIGKILL);
        Ok(())
    }
}

pub(crate) fn configure_process_group(command: &mut std::process::Command) {
    use std::os::unix::process::CommandExt;
    command.process_group(0);
}

pub(crate) fn add_powershell_process_flags(_process: &mut std::process::Command, _program: &str) {}

pub(crate) fn uses_pipe_for_system_shell(_program: &str) -> bool {
    false
}

pub(crate) fn run_with_system_shell_pty(
    program: &str,
    arg: &str,
    command: &str,
    working_dir: &Path,
    output: &Sender<OutputChunk>,
    terminal: &TerminalContext,
) -> Result<Option<CommandStatus>, EngineError> {
    let pty_system = portable_pty::native_pty_system();
    let pair = match pty_system.openpty(terminal.size.into()) {
        Ok(pair) => pair,
        Err(_) => return Ok(None),
    };
    let reader = match pair.master.try_clone_reader() {
        Ok(reader) => reader,
        Err(_) => return Ok(None),
    };
    let closed_input = match pair.master.take_writer() {
        Ok(writer) => writer,
        Err(_) => return Ok(None),
    };
    let mut builder = portable_pty::CommandBuilder::new(program);
    builder.set_controlling_tty(true);
    builder.arg(arg);
    builder.arg(command);
    builder.cwd(working_dir);
    for (name, value) in super::build_command_env(terminal) {
        builder.env(name, value);
    }
    let mut child = match pair.slave.spawn_command(builder) {
        Ok(child) => child,
        Err(_) => return Ok(None),
    };
    let process_tree = ProcessTree::attach_pty(child.as_mut())?;
    drop(pair.slave);
    drop(closed_input);
    let reader_handle = spawn_output_reader(reader, OutputStream::Stdout, output.clone());
    let status = wait_for_pty_child(
        child.as_mut(),
        &process_tree,
        pair.master.as_ref(),
        terminal.size,
        &terminal.cancelled,
    )?;
    drop(pair.master);
    join_output_reader(reader_handle)?;
    Ok(Some(pty_command_status(status)))
}

fn send_group_signal(pid: u32, signal: libc::c_int) {
    let Ok(pid) = libc::pid_t::try_from(pid) else {
        return;
    };
    unsafe {
        libc::kill(-pid, signal);
    }
}