| 1 | //go:build !windows |
| 2 | |
| 3 | package proc |
| 4 | |
| 5 | import ( |
| 6 | "os/exec" |
| 7 | "syscall" |
| 8 | ) |
| 9 | |
| 10 | // KillTree kills cmd's whole process group. StartTracked (and |
| 11 | // SetProcessGroupKill for children started outside it) put the child in a new |
| 12 | // session/process group, so the negative-pid signal reaches every descendant, |
| 13 | // including a launcher whose sub-daemon survives the parent, where a plain |
| 14 | // Process.Kill would only hit the direct child and orphan the grandchild. |
| 15 | func KillTree(cmd *exec.Cmd) { |
| 16 | if cmd == nil || cmd.Process == nil { |
| 17 | return |
| 18 | } |
| 19 | if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL); err != nil { |
| 20 | _ = cmd.Process.Kill() // not a group leader — at least kill the child |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | // SetProcessGroupKill makes cmd start in its own session/process group so |
| 25 | // KillTree can reap its whole tree. The new session also keeps interactive |
| 26 | // children from taking over the caller's controlling terminal. Use it for |
| 27 | // children started outside StartTracked (e.g. a one-shot CombinedOutput). It is |
| 28 | // a no-op on Windows, where the Job Object that TrackTree/StartTracked assigns |
| 29 | // handles the tree instead. |
| 30 | func SetProcessGroupKill(cmd *exec.Cmd) { |
| 31 | if cmd.SysProcAttr == nil { |
| 32 | cmd.SysProcAttr = &syscall.SysProcAttr{} |
| 33 | } |
| 34 | cmd.SysProcAttr.Setsid = true |
| 35 | } |
| 36 | |
| 37 | // StartTracked starts cmd in its own session/process group so KillTracked / |
| 38 | // KillTree can reap its whole tree. Off Windows the process group is the |
| 39 | // equivalent of the Windows Job Object; it returns a 0 handle, and KillTracked |
| 40 | // falls back to KillTree. |
| 41 | func StartTracked(cmd *exec.Cmd) (uintptr, error) { |
| 42 | SetProcessGroupKill(cmd) |
| 43 | return 0, cmd.Start() |
| 44 | } |
| 45 | |
| 46 | // StartTrackedRequired uses the same process-group guarantee off Windows. |
| 47 | func StartTrackedRequired(cmd *exec.Cmd) (uintptr, error) { return StartTracked(cmd) } |
| 48 | |
| 49 | // KillTracked terminates cmd's process tree; the handle is unused off Windows. |
| 50 | func KillTracked(cmd *exec.Cmd, _ uintptr) { KillTree(cmd) } |
| 51 | |
| 52 | // FinishTracked is a no-op off Windows, where StartTracked owns no OS handle. |
| 53 | func FinishTracked(uintptr) {} |
| 54 |