process_wrap/std/
process_group.rs1use std::{
2 io::{Error, Result},
3 ops::ControlFlow,
4 os::unix::process::{CommandExt, ExitStatusExt},
5 process::{Child, Command, ExitStatus},
6};
7
8use nix::{
9 errno::Errno,
10 libc,
11 sys::{
12 signal::{killpg, Signal},
13 wait::WaitPidFlag,
14 },
15 unistd::Pid,
16};
17#[cfg(feature = "tracing")]
18use tracing::instrument;
19
20use crate::ChildExitStatus;
21
22use super::{StdChildWrapper, StdCommandWrap, StdCommandWrapper};
23
24#[derive(Clone, Copy, Debug)]
36pub struct ProcessGroup {
37 leader: Pid,
38}
39
40impl ProcessGroup {
41 pub fn leader() -> Self {
43 Self {
44 leader: Pid::from_raw(0),
45 }
46 }
47
48 pub fn attach_to(leader: u32) -> Self {
50 Self {
51 leader: Pid::from_raw(leader as _),
52 }
53 }
54}
55
56#[derive(Debug)]
58pub struct ProcessGroupChild {
59 inner: Box<dyn StdChildWrapper>,
60 exit_status: ChildExitStatus,
61 pgid: Pid,
62}
63
64impl ProcessGroupChild {
65 #[cfg_attr(feature = "tracing", instrument(level = "debug"))]
66 pub(crate) fn new(inner: Box<dyn StdChildWrapper>, pgid: Pid) -> Self {
67 Self {
68 inner,
69 exit_status: ChildExitStatus::Running,
70 pgid,
71 }
72 }
73
74 pub fn pgid(&self) -> u32 {
78 self.pgid.as_raw() as _
79 }
80}
81
82impl StdCommandWrapper for ProcessGroup {
83 #[cfg_attr(feature = "tracing", instrument(level = "debug", skip(self)))]
84 fn pre_spawn(&mut self, command: &mut Command, _core: &StdCommandWrap) -> Result<()> {
85 command.process_group(self.leader.as_raw());
86 Ok(())
87 }
88
89 #[cfg_attr(feature = "tracing", instrument(level = "debug", skip(self)))]
90 fn wrap_child(
91 &mut self,
92 inner: Box<dyn StdChildWrapper>,
93 _core: &StdCommandWrap,
94 ) -> Result<Box<dyn StdChildWrapper>> {
95 let pgid = Pid::from_raw(i32::try_from(inner.id()).expect("Command PID > i32::MAX"));
96
97 Ok(Box::new(ProcessGroupChild::new(inner, pgid)))
98 }
99}
100
101impl ProcessGroupChild {
102 #[cfg_attr(feature = "tracing", instrument(level = "debug", skip(self)))]
103 fn signal_imp(&self, sig: Signal) -> Result<()> {
104 killpg(self.pgid, sig).map_err(Error::from)
105 }
106
107 #[cfg_attr(feature = "tracing", instrument(level = "debug"))]
108 fn wait_imp(pgid: Pid, flag: WaitPidFlag) -> Result<ControlFlow<Option<ExitStatus>>> {
109 let mut parent_exit_status: Option<ExitStatus> = None;
114 loop {
115 let mut status: i32 = 0;
118 match unsafe {
119 libc::waitpid(-pgid.as_raw(), &mut status as *mut libc::c_int, flag.bits())
120 } {
121 0 => {
122 return Ok(ControlFlow::Continue(()));
125 }
126 -1 => {
127 match Errno::last() {
128 Errno::ECHILD => {
129 return Ok(ControlFlow::Break(parent_exit_status));
131 }
132 errno => {
133 return Err(Error::from(errno));
134 }
135 }
136 }
137 pid => {
138 if pgid == Pid::from_raw(pid) {
142 parent_exit_status = Some(ExitStatus::from_raw(status));
143 } else {
144 }
146 }
147 };
148 }
149 }
150}
151
152impl StdChildWrapper for ProcessGroupChild {
153 fn inner(&self) -> &Child {
154 self.inner.inner()
155 }
156 fn inner_mut(&mut self) -> &mut Child {
157 self.inner.inner_mut()
158 }
159 fn into_inner(self: Box<Self>) -> Child {
160 self.inner.into_inner()
161 }
162
163 #[cfg_attr(feature = "tracing", instrument(level = "debug", skip(self)))]
164 fn start_kill(&mut self) -> Result<()> {
165 self.signal_imp(Signal::SIGKILL)
166 }
167
168 #[cfg_attr(feature = "tracing", instrument(level = "debug", skip(self)))]
169 fn wait(&mut self) -> Result<ExitStatus> {
170 if let ChildExitStatus::Exited(status) = &self.exit_status {
171 return Ok(*status);
172 }
173
174 let status = self.inner.wait()?;
177 self.exit_status = ChildExitStatus::Exited(status);
178
179 Self::wait_imp(self.pgid, WaitPidFlag::empty())?;
181 Ok(status)
182 }
183
184 #[cfg_attr(feature = "tracing", instrument(level = "debug", skip(self)))]
185 fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
186 if let ChildExitStatus::Exited(status) = &self.exit_status {
187 return Ok(Some(*status));
188 }
189
190 match Self::wait_imp(self.pgid, WaitPidFlag::WNOHANG)? {
191 ControlFlow::Break(res) => {
192 if let Some(status) = res {
193 self.exit_status = ChildExitStatus::Exited(status);
194 }
195 Ok(res)
196 }
197 ControlFlow::Continue(()) => {
198 let exited = self.inner.try_wait()?;
199 if let Some(exited) = exited {
200 self.exit_status = ChildExitStatus::Exited(exited);
201 }
202 Ok(exited)
203 }
204 }
205 }
206
207 fn signal(&self, sig: i32) -> Result<()> {
208 self.signal_imp(Signal::try_from(sig)?)
209 }
210}