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