@@ -1,4 +1,5 @@ |
| 1 | 1 | use std::collections::HashSet; |
| 2 | +use std::os::unix::process::CommandExt; |
| 2 | 3 | use std::path::PathBuf; |
| 3 | 4 | use std::sync::{Arc, Mutex}; |
| 4 | 5 | |
@@ -95,8 +96,10 @@ impl LuaState { |
| 95 | 96 | // SAFETY: Sending signal 0 just checks if process exists |
| 96 | 97 | let exists = unsafe { libc::kill(pid as i32, 0) == 0 }; |
| 97 | 98 | if exists { |
| 98 | | - tracing::debug!("Sending SIGTERM to PID {}", pid); |
| 99 | | - unsafe { libc::kill(pid as i32, libc::SIGTERM); } |
| 99 | + // Kill the entire process group (negative PID) to get children too |
| 100 | + // This handles cases like "sh -c garterm" where sh spawns garterm |
| 101 | + tracing::debug!("Sending SIGTERM to process group {}", pid); |
| 102 | + unsafe { libc::kill(-(pid as i32), libc::SIGTERM); } |
| 100 | 103 | } |
| 101 | 104 | } |
| 102 | 105 | } |
@@ -643,9 +646,12 @@ impl LuaConfig { |
| 643 | 646 | let state = Arc::clone(&self.state); |
| 644 | 647 | let exec_fn = self.lua.create_function(move |_, cmd: String| { |
| 645 | 648 | tracing::debug!("exec: {}", cmd); |
| 649 | + // process_group(0) makes the child its own process group leader |
| 650 | + // so we can kill the entire group (including grandchildren) on exit |
| 646 | 651 | if let Ok(child) = std::process::Command::new("sh") |
| 647 | 652 | .arg("-c") |
| 648 | 653 | .arg(&cmd) |
| 654 | + .process_group(0) |
| 649 | 655 | .spawn() |
| 650 | 656 | { |
| 651 | 657 | let pid = child.id(); |
@@ -669,9 +675,12 @@ impl LuaConfig { |
| 669 | 675 | } |
| 670 | 676 | } |
| 671 | 677 | tracing::info!("exec_once: {}", cmd); |
| 678 | + // process_group(0) makes the child its own process group leader |
| 679 | + // so we can kill the entire group (including grandchildren) on exit |
| 672 | 680 | if let Ok(child) = std::process::Command::new("sh") |
| 673 | 681 | .arg("-c") |
| 674 | 682 | .arg(&cmd) |
| 683 | + .process_group(0) |
| 675 | 684 | .spawn() |
| 676 | 685 | { |
| 677 | 686 | let pid = child.id(); |