command_group/stdlib/child/
unix.rs

1use std::{
2	convert::TryInto,
3	io::{Error, Read, Result},
4	os::{
5		fd::BorrowedFd,
6		unix::{
7			io::{AsRawFd, RawFd},
8			process::ExitStatusExt,
9		},
10	},
11	process::{Child, ChildStderr, ChildStdin, ChildStdout, ExitStatus},
12};
13
14use nix::{
15	errno::Errno,
16	libc,
17	poll::{poll, PollFd, PollFlags},
18	sys::{
19		signal::{killpg, Signal},
20		wait::WaitPidFlag,
21	},
22	unistd::Pid,
23};
24
25pub(super) struct ChildImp {
26	pgid: Pid,
27	inner: Child,
28}
29
30impl ChildImp {
31	pub(super) fn new(inner: Child) -> Self {
32		Self {
33			pgid: Pid::from_raw(inner.id().try_into().expect("Command PID > i32::MAX")),
34			inner,
35		}
36	}
37
38	pub(super) fn take_stdin(&mut self) -> Option<ChildStdin> {
39		self.inner.stdin.take()
40	}
41
42	pub(super) fn take_stdout(&mut self) -> Option<ChildStdout> {
43		self.inner.stdout.take()
44	}
45
46	pub(super) fn take_stderr(&mut self) -> Option<ChildStderr> {
47		self.inner.stderr.take()
48	}
49
50	pub fn inner(&mut self) -> &mut Child {
51		&mut self.inner
52	}
53
54	pub fn into_inner(self) -> Child {
55		self.inner
56	}
57
58	pub(super) fn signal_imp(&self, sig: Signal) -> Result<()> {
59		killpg(self.pgid, sig).map_err(Error::from)
60	}
61
62	pub fn kill(&mut self) -> Result<()> {
63		self.signal_imp(Signal::SIGKILL)
64	}
65
66	pub fn id(&self) -> u32 {
67		self.inner.id()
68	}
69
70	fn wait_imp(&mut self, flag: WaitPidFlag) -> Result<Option<ExitStatus>> {
71		let negpid = Pid::from_raw(-self.pgid.as_raw());
72
73		// Wait for processes in a loop until every process in this
74		// process group has exited (this ensures that we reap any
75		// zombies that may have been created if the parent exited after
76		// spawning children, but didn't wait for those children to
77		// exit).
78		let mut parent_exit_status: Option<ExitStatus> = None;
79		loop {
80			// we can't use the safe wrapper directly because it doesn't
81			// return the raw status, and we need it to convert to the
82			// std's ExitStatus.
83			let mut status: i32 = 0;
84			match unsafe {
85				libc::waitpid(negpid.into(), &mut status as *mut libc::c_int, flag.bits())
86			} {
87				0 => {
88					// Zero should only happen if WNOHANG was passed in,
89					// and means that no processes have yet to exit.
90					return Ok(None);
91				}
92				-1 => {
93					match Errno::last() {
94						Errno::ECHILD => {
95							// No more children to reap; this is a
96							// graceful exit.
97							return Ok(parent_exit_status);
98						}
99						errno => {
100							return Err(Error::from(errno));
101						}
102					}
103				}
104				pid => {
105					// *A* process exited. Was it the parent process
106					// that we started? If so, collect the exit signal,
107					// otherwise we reaped a zombie process and should
108					// continue in the loop.
109					if self.pgid.as_raw() == pid {
110						parent_exit_status = Some(ExitStatus::from_raw(status));
111					} else {
112						// Reaped a zombie child; keep looping.
113					}
114				}
115			};
116		}
117	}
118
119	pub fn wait(&mut self) -> Result<ExitStatus> {
120		if let Some(status) = self.try_wait()? {
121			return Ok(status);
122		}
123
124		match self.wait_imp(WaitPidFlag::empty()).transpose() {
125			None => self.inner.wait(),
126			Some(status) => status,
127		}
128	}
129
130	pub fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
131		match self.wait_imp(WaitPidFlag::WNOHANG) {
132			Ok(None) => self.inner.try_wait(),
133			otherwise => otherwise,
134		}
135	}
136
137	pub(super) fn read_both(
138		mut out_r: ChildStdout,
139		out_v: &mut Vec<u8>,
140		mut err_r: ChildStderr,
141		err_v: &mut Vec<u8>,
142	) -> Result<()> {
143		let out_fd = out_r.as_raw_fd();
144		let err_fd = err_r.as_raw_fd();
145		set_nonblocking(out_fd, true)?;
146		set_nonblocking(err_fd, true)?;
147
148		// SAFETY: these are dropped at the same time as all other FDs here
149		let out_bfd = unsafe { BorrowedFd::borrow_raw(out_fd) };
150		let err_bfd = unsafe { BorrowedFd::borrow_raw(err_fd) };
151
152		let mut fds = [
153			PollFd::new(&out_bfd, PollFlags::POLLIN),
154			PollFd::new(&err_bfd, PollFlags::POLLIN),
155		];
156
157		loop {
158			poll(&mut fds, -1)?;
159
160			if fds[0].revents().is_some() && read(&mut out_r, out_v)? {
161				set_nonblocking(err_fd, false)?;
162				return err_r.read_to_end(err_v).map(drop);
163			}
164			if fds[1].revents().is_some() && read(&mut err_r, err_v)? {
165				set_nonblocking(out_fd, false)?;
166				return out_r.read_to_end(out_v).map(drop);
167			}
168		}
169
170		fn read(r: &mut impl Read, dst: &mut Vec<u8>) -> Result<bool> {
171			match r.read_to_end(dst) {
172				Ok(_) => Ok(true),
173				Err(e) => {
174					if e.raw_os_error() == Some(libc::EWOULDBLOCK)
175						|| e.raw_os_error() == Some(libc::EAGAIN)
176					{
177						Ok(false)
178					} else {
179						Err(e)
180					}
181				}
182			}
183		}
184
185		#[cfg(target_os = "linux")]
186		fn set_nonblocking(fd: RawFd, nonblocking: bool) -> Result<()> {
187			let v = nonblocking as libc::c_int;
188			let res = unsafe { libc::ioctl(fd, libc::FIONBIO, &v) };
189
190			Errno::result(res).map_err(Error::from).map(drop)
191		}
192
193		#[cfg(not(target_os = "linux"))]
194		fn set_nonblocking(fd: RawFd, nonblocking: bool) -> Result<()> {
195			use nix::fcntl::{fcntl, FcntlArg, OFlag};
196
197			let mut flags = OFlag::from_bits_truncate(fcntl(fd, FcntlArg::F_GETFL)?);
198			flags.set(OFlag::O_NONBLOCK, nonblocking);
199
200			fcntl(fd, FcntlArg::F_SETFL(flags))
201				.map_err(Error::from)
202				.map(drop)
203		}
204	}
205}
206
207pub trait UnixChildExt {
208	fn signal(&self, sig: Signal) -> Result<()>;
209}
210
211impl UnixChildExt for ChildImp {
212	fn signal(&self, sig: Signal) -> Result<()> {
213		self.signal_imp(sig)
214	}
215}