tastty 0.1.0

Embeddable pseudoterminal sessions for Rust applications
//! Terminate the child process and any descendants it leaked.
//!
//! On Unix, the PTY slave becomes the session/process-group leader, so
//! `killpg` reaches the shell and every process it backgrounded. On
//! Windows there is no process-group concept, so we fall back to the
//! single-process `ChildKiller::kill` provided by `portable_pty`.

use portable_pty::ChildKiller;

#[cfg(unix)]
#[expect(
    unsafe_code,
    reason = "libc::killpg is FFI and unsafe by signature; the wrapper passes a validated pid and signal and treats ESRCH as success"
)]
fn kill_group(pid: u32, signal: i32) -> std::io::Result<()> {
    let pgid = pid as libc::pid_t;
    // SAFETY: libc::killpg has no memory-safety preconditions; it is FFI
    // and unsafe only by signature. `pgid` is a u32 widened to pid_t, and
    // `signal` is one of the SIGTERM/SIGKILL constants from libc above;
    // the kernel returns ESRCH for invalid pgid, which the caller treats
    // as success.
    let ret = unsafe { libc::killpg(pgid, signal) };
    if ret == -1 {
        let err = std::io::Error::last_os_error();
        if err.raw_os_error() == Some(libc::ESRCH) {
            return Ok(());
        }
        return Err(err);
    }
    Ok(())
}

#[cfg(unix)]
pub(crate) fn sigterm_group(
    pid: u32,
    _killer: &mut (dyn ChildKiller + Send + Sync),
) -> std::io::Result<()> {
    kill_group(pid, libc::SIGTERM)
}

#[cfg(unix)]
pub(crate) fn sigkill_group(
    pid: u32,
    _killer: &mut (dyn ChildKiller + Send + Sync),
) -> std::io::Result<()> {
    kill_group(pid, libc::SIGKILL)
}

#[cfg(not(unix))]
pub(crate) fn sigterm_group(
    _pid: u32,
    killer: &mut (dyn ChildKiller + Send + Sync),
) -> std::io::Result<()> {
    // Windows has no SIGTERM equivalent; `TerminateProcess` is the only
    // tool and it is what `ChildKiller::kill` invokes. There is no
    // graceful stage to do before the SIGKILL-equivalent fallback.
    killer.kill()
}

#[cfg(not(unix))]
pub(crate) fn sigkill_group(
    _pid: u32,
    killer: &mut (dyn ChildKiller + Send + Sync),
) -> std::io::Result<()> {
    killer.kill()
}