prefork 0.6.0

A library for forking processes
Documentation
use std::{collections::HashSet, hash::BuildHasher};

use crate::{error::Result, ForkErr};
use log::{debug, info, warn};
use nix::{
    sys::{
        signal::{kill, Signal},
        wait::{waitpid, WaitPidFlag, WaitStatus},
    },
    unistd::Pid,
};

/// Poll for child processes to change status.
///
/// This function will exit once there are no more status changes to report.
///
/// This function can be useful after sending a signal to one
/// or more children because it will remove any dead children from `pids`.
pub fn wait_process<H>(pids: &mut HashSet<Pid, H>)
where
    H: BuildHasher,
{
    while let Ok(status) = waitpid(None, Some(WaitPidFlag::WNOHANG)) {
        match status {
            WaitStatus::Exited(pid, exit_code) => {
                info!("Child {pid} exited with status {exit_code}");
                pids.remove(&pid);
            }
            WaitStatus::Signaled(pid, signal, ..) => {
                info!("Child {pid} killed by signal {signal}");
                pids.remove(&pid);
            }
            WaitStatus::StillAlive => break,
            _ => {
                info!("waitpid got status {status:?}");
            }
        }
    }
}

/// Kill the given processes
/// # Errors
/// If a process cannot be killed, the pid is returned as part of the
/// `ForkErr::KillAll` error.
pub fn kill_all<H>(pids: HashSet<Pid, H>) -> Result<()>
where
    H: BuildHasher,
{
    kill_all_with(pids, Signal::SIGKILL)
}

/// Sending specified signal to given processes
/// # Errors
/// If cannot send a signal to a process, the pid is returned as part of the
/// `ForkErr::KillAll` error.
pub fn kill_all_with<H>(pids: HashSet<Pid, H>, signal: Signal) -> Result<()>
where
    H: BuildHasher,
{
    let mut failures = Vec::new();
    for pid in pids {
        if let Err(err) = kill(pid, signal) {
            warn!("Kill failed: {err}");
            failures.push(pid.into());
        } else {
            debug!("Reaped {pid}");
        }
    }
    if failures.is_empty() {
        Ok(())
    } else {
        Err(ForkErr::KillAll(failures))
    }
}