use anyhow::Result;
use command_group::{GroupChild, Signal, UnixChildExt};
use log::info;
use crate::settings::Settings;
pub fn get_shell_command(settings: &Settings) -> Vec<String> {
let Some(ref shell_command) = settings.daemon.shell_command else {
return vec![
"sh".into(),
"-c".into(),
"{{ pueue_command_string }}".into(),
];
};
shell_command.clone()
}
pub fn send_signal_to_child<T>(child: &mut GroupChild, signal: T) -> Result<()>
where
T: Into<Signal>,
{
child.signal(signal.into())?;
Ok(())
}
pub fn kill_child(task_id: usize, child: &mut GroupChild) -> std::io::Result<()> {
match child.kill() {
Ok(_) => Ok(()),
Err(ref e) if e.kind() == std::io::ErrorKind::InvalidData => {
info!("Task {task_id} has already finished by itself.");
Ok(())
}
Err(err) => Err(err),
}
}
#[cfg(test)]
mod tests {
use std::process::Command;
use std::thread::sleep;
use std::time::Duration;
use anyhow::Result;
use command_group::CommandGroup;
use libproc::processes::{pids_by_type, ProcFilter};
use log::warn;
use pretty_assertions::assert_eq;
use super::*;
use crate::process_helper::{compile_shell_command, process_exists};
pub fn get_process_group_pids(pgrp: u32) -> Vec<u32> {
match pids_by_type(ProcFilter::ByProgramGroup { pgrpid: pgrp }) {
Err(error) => {
warn!("Failed to get list of processes in process group {pgrp}: {error}");
Vec::new()
}
Ok(mut processes) => {
if !processes.iter().any(|pid| pid == &pgrp) && !process_is_gone(pgrp) {
processes.push(pgrp)
}
processes
}
}
}
fn process_is_gone(pid: u32) -> bool {
!process_exists(pid)
}
#[test]
fn test_spawn_command() {
let settings = Settings::default();
let mut child = compile_shell_command(&settings, "sleep 0.1")
.group_spawn()
.expect("Failed to spawn echo");
let ecode = child.wait().expect("failed to wait on echo");
assert!(ecode.success());
}
#[test]
fn test_shell_command_is_killed() -> Result<()> {
let settings = Settings::default();
let mut child =
compile_shell_command(&settings, "sleep 60 & sleep 60 && echo 'this is a test'")
.group_spawn()
.expect("Failed to spawn echo");
let pid = child.id();
sleep(Duration::from_millis(500));
let group_pids = get_process_group_pids(pid);
assert_eq!(group_pids.len(), 3);
assert!(kill_child(0, &mut child).is_ok());
sleep(Duration::from_millis(500));
child.try_wait().unwrap_or_default();
assert!(process_is_gone(pid));
assert_eq!(get_process_group_pids(pid).len(), 0);
Ok(())
}
#[test]
fn test_shell_command_is_killed_with_signal() -> Result<()> {
let settings = Settings::default();
let mut child =
compile_shell_command(&settings, "sleep 60 & sleep 60 && echo 'this is a test'")
.group_spawn()
.expect("Failed to spawn echo");
let pid = child.id();
sleep(Duration::from_millis(500));
let group_pids = get_process_group_pids(pid);
assert_eq!(group_pids.len(), 3);
send_signal_to_child(&mut child, Signal::SIGKILL).unwrap();
sleep(Duration::from_millis(500));
child.try_wait().unwrap_or_default();
assert!(process_is_gone(pid));
assert_eq!(get_process_group_pids(pid).len(), 0);
Ok(())
}
#[test]
fn test_shell_command_children_are_killed() -> Result<()> {
let settings = Settings::default();
let mut child =
compile_shell_command(&settings, "bash -c 'sleep 60 && sleep 60' && sleep 60")
.group_spawn()
.expect("Failed to spawn echo");
let pid = child.id();
sleep(Duration::from_millis(500));
let group_pids = get_process_group_pids(pid);
assert_eq!(group_pids.len(), 3);
assert!(kill_child(0, &mut child).is_ok());
sleep(Duration::from_millis(500));
child.try_wait().unwrap_or_default();
assert!(process_is_gone(pid));
assert_eq!(get_process_group_pids(pid).len(), 0);
Ok(())
}
#[test]
fn test_normal_command_is_killed() -> Result<()> {
let mut child = Command::new("sleep")
.arg("60")
.group_spawn()
.expect("Failed to spawn echo");
let pid = child.id();
sleep(Duration::from_millis(500));
let group_pids = get_process_group_pids(pid);
assert_eq!(group_pids.len(), 1);
assert!(kill_child(0, &mut child).is_ok());
sleep(Duration::from_millis(500));
child.try_wait().unwrap_or_default();
assert!(process_is_gone(pid));
Ok(())
}
#[test]
fn test_normal_command_children_are_killed() -> Result<()> {
let mut child = Command::new("bash")
.arg("-c")
.arg("sleep 60 & sleep 60 && sleep 60")
.group_spawn()
.expect("Failed to spawn echo");
let pid = child.id();
sleep(Duration::from_millis(500));
let group_pids = get_process_group_pids(pid);
assert_eq!(group_pids.len(), 3);
assert!(kill_child(0, &mut child).is_ok());
sleep(Duration::from_millis(500));
child.try_wait().unwrap_or_default();
assert!(process_is_gone(pid));
assert_eq!(get_process_group_pids(pid).len(), 0);
Ok(())
}
}