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");
108 command
109 .args(["-c", "sleep 30"])
110 .stdin(Stdio::null())
111 .stdout(Stdio::null())
112 .stderr(Stdio::null());
113 isolate(&mut command);
114 let mut child = command.spawn().expect("group-leader child spawns");
115 let pid = child.id();
116
117 assert!(terminate(pid), "terminating the process group succeeds");
118
119 let status = child.wait().expect("child is reaped");
120 assert_eq!(
121 status.signal(),
122 Some(ProcessSignal::Terminate.as_raw()),
123 "child should be terminated by the group SIGTERM"
124 );
125 }
126
127 #[test]
128 fn process_group_helpers_reject_zero_pid() {
129 assert!(!interrupt(0));
130 assert!(!terminate(0));
131 assert!(!kill(0));
132 assert!(!terminate_target(0, false));
133 assert!(!kill_target(0, false));
134 }
135
136 #[test]
137 fn terminate_and_kill_targets_can_signal_child_processes() {
138 let mut terminate_child = StdCommand::new("python3")
139 .args(["-c", "import time; time.sleep(30)"])
140 .stdin(Stdio::null())
141 .stdout(Stdio::null())
142 .stderr(Stdio::null())
143 .spawn()
144 .unwrap();
145 let terminate_pid = terminate_child.id();
146 assert!(terminate_target(terminate_pid, false));
147 let _ = terminate_child.wait().unwrap();
148
149 let mut kill_child = StdCommand::new("python3")
150 .args(["-c", "import time; time.sleep(30)"])
151 .stdin(Stdio::null())
152 .stdout(Stdio::null())
153 .stderr(Stdio::null())
154 .spawn()
155 .unwrap();
156 let kill_pid = kill_child.id();
157 assert!(kill_target(kill_pid, false));
158 let _ = kill_child.wait().unwrap();
159
160 std::thread::sleep(Duration::from_millis(10));
161 }
162}