rskit_process/
process_group.rs1use std::process::Command as StdCommand;
4
5use crate::signal::ProcessSignal;
6
7pub fn isolate(command: &mut StdCommand) {
9 #[cfg(unix)]
10 {
11 use std::os::unix::process::CommandExt;
12
13 unsafe {
17 command.pre_exec(|| {
18 if libc::setpgid(0, 0) != 0 {
19 return Err(std::io::Error::last_os_error());
20 }
21 Ok(())
22 });
23 }
24 }
25}
26
27#[must_use]
29pub fn interrupt(pid: u32) -> bool {
30 signal(pid, ProcessSignal::Interrupt)
31}
32
33#[must_use]
35pub fn terminate(pid: u32) -> bool {
36 signal(pid, ProcessSignal::Terminate)
37}
38
39#[must_use]
41pub fn kill(pid: u32) -> bool {
42 signal(pid, ProcessSignal::Kill)
43}
44
45pub(crate) fn terminate_target(pid: u32, process_group: bool) -> bool {
46 signal_target(pid, ProcessSignal::Terminate, process_group)
47}
48
49pub(crate) fn kill_target(pid: u32, process_group: bool) -> bool {
50 signal_target(pid, ProcessSignal::Kill, process_group)
51}
52
53fn signal(pid: u32, signal: ProcessSignal) -> bool {
54 signal_target(pid, signal, true)
55}
56
57fn signal_target(pid: u32, signal: ProcessSignal, process_group: bool) -> bool {
58 if pid == 0 {
59 return false;
60 }
61
62 #[cfg(unix)]
63 {
64 let Ok(pid) = i32::try_from(pid) else {
67 return false;
68 };
69 let target = if process_group { -pid } else { pid };
70 unsafe {
74 let result = libc::kill(target, signal.as_raw());
75 if result != 0 {
76 let error = std::io::Error::last_os_error();
77 if error.raw_os_error() != Some(libc::ESRCH) {
78 return false;
79 }
80 }
81 true
82 }
83 }
84 #[cfg(not(unix))]
85 {
86 let _ = pid;
87 let _ = signal;
88 let _ = process_group;
89 false
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use std::process::Stdio;
96 use std::time::Duration;
97
98 use super::*;
99
100 #[cfg(unix)]
101 #[test]
102 fn public_helpers_signal_a_real_process_group_leader() {
103 use std::os::unix::process::ExitStatusExt;
104
105 let mut command = StdCommand::new("/bin/sh");
109 command
110 .args(["-c", "sleep 30"])
111 .stdin(Stdio::null())
112 .stdout(Stdio::null())
113 .stderr(Stdio::null());
114 isolate(&mut command);
115 let mut child = command.spawn().expect("group-leader child spawns");
116 let pid = child.id();
117
118 assert!(terminate(pid), "terminating the process group succeeds");
119
120 let status = child.wait().expect("child is reaped");
121 assert_eq!(
122 status.signal(),
123 Some(ProcessSignal::Terminate.as_raw()),
124 "child should be terminated by the group SIGTERM"
125 );
126 }
127
128 #[test]
129 fn process_group_helpers_reject_zero_pid() {
130 assert!(!interrupt(0));
131 assert!(!terminate(0));
132 assert!(!kill(0));
133 assert!(!terminate_target(0, false));
134 assert!(!kill_target(0, false));
135 }
136
137 #[test]
138 fn terminate_and_kill_targets_can_signal_child_processes() {
139 let mut terminate_child = StdCommand::new("python3")
140 .args(["-c", "import time; time.sleep(30)"])
141 .stdin(Stdio::null())
142 .stdout(Stdio::null())
143 .stderr(Stdio::null())
144 .spawn()
145 .unwrap();
146 let terminate_pid = terminate_child.id();
147 assert!(terminate_target(terminate_pid, false));
148 let _ = terminate_child.wait().unwrap();
149
150 let mut kill_child = StdCommand::new("python3")
151 .args(["-c", "import time; time.sleep(30)"])
152 .stdin(Stdio::null())
153 .stdout(Stdio::null())
154 .stderr(Stdio::null())
155 .spawn()
156 .unwrap();
157 let kill_pid = kill_child.id();
158 assert!(kill_target(kill_pid, false));
159 let _ = kill_child.wait().unwrap();
160
161 std::thread::sleep(Duration::from_millis(10));
162 }
163}