use std::{
any::Any,
io::{Read, Result},
process::{Child, ChildStderr, ChildStdin, ChildStdout, Command, ExitStatus, Output},
};
#[cfg(unix)]
use nix::{
sys::signal::{Signal, kill},
unistd::Pid,
};
crate::generic_wrap::Wrap!(Command, Child, ChildWrapper, |child| child);
pub trait ChildWrapper: Any + std::fmt::Debug + Send + Sync {
fn inner(&self) -> &dyn ChildWrapper;
fn inner_mut(&mut self) -> &mut dyn ChildWrapper;
fn into_inner(self: Box<Self>) -> Box<dyn ChildWrapper>;
fn try_clone(&self) -> Option<Box<dyn ChildWrapper>> {
None
}
fn stdin(&mut self) -> &mut Option<ChildStdin> {
self.inner_mut().stdin()
}
fn stdout(&mut self) -> &mut Option<ChildStdout> {
self.inner_mut().stdout()
}
fn stderr(&mut self) -> &mut Option<ChildStderr> {
self.inner_mut().stderr()
}
fn id(&self) -> u32 {
self.inner().id()
}
fn kill(&mut self) -> Result<()> {
self.start_kill()?;
self.wait()?;
Ok(())
}
fn start_kill(&mut self) -> Result<()> {
self.inner_mut().start_kill()
}
fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
self.inner_mut().try_wait()
}
fn wait(&mut self) -> Result<ExitStatus> {
self.inner_mut().wait()
}
fn wait_with_output(mut self: Box<Self>) -> Result<Output>
where
Self: 'static,
{
drop(self.stdin().take());
let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
match (self.stdout().take(), self.stderr().take()) {
(None, None) => {}
(Some(mut out), None) => {
let res = out.read_to_end(&mut stdout);
res.unwrap();
}
(None, Some(mut err)) => {
let res = err.read_to_end(&mut stderr);
res.unwrap();
}
(Some(out), Some(err)) => {
let res = read2(out, &mut stdout, err, &mut stderr);
res.unwrap();
}
}
let status = self.wait()?;
Ok(Output {
status,
stdout,
stderr,
})
}
#[cfg(unix)]
fn signal(&self, sig: i32) -> Result<()> {
self.inner().signal(sig)
}
}
impl ChildWrapper for Child {
fn inner(&self) -> &dyn ChildWrapper {
self
}
fn inner_mut(&mut self) -> &mut dyn ChildWrapper {
self
}
fn into_inner(self: Box<Self>) -> Box<dyn ChildWrapper> {
self
}
fn stdin(&mut self) -> &mut Option<ChildStdin> {
&mut self.stdin
}
fn stdout(&mut self) -> &mut Option<ChildStdout> {
&mut self.stdout
}
fn stderr(&mut self) -> &mut Option<ChildStderr> {
&mut self.stderr
}
fn id(&self) -> u32 {
Child::id(self)
}
fn start_kill(&mut self) -> Result<()> {
#[cfg(unix)]
{
self.signal(Signal::SIGKILL as _)
}
#[cfg(not(unix))]
{
Child::kill(self)
}
}
fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
Child::try_wait(self)
}
fn wait(&mut self) -> Result<ExitStatus> {
Child::wait(self)
}
#[cfg(unix)]
fn signal(&self, sig: i32) -> Result<()> {
kill(
Pid::from_raw(i32::try_from(self.id()).map_err(std::io::Error::other)?),
Signal::try_from(sig)?,
)
.map_err(std::io::Error::from)
}
}
impl dyn ChildWrapper {
fn downcast_ref<T: 'static>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref()
}
fn is_raw_child(&self) -> bool {
self.downcast_ref::<Child>().is_some()
}
pub fn inner_child(&self) -> &Child {
let mut inner = self;
while !inner.is_raw_child() {
inner = inner.inner();
}
inner.downcast_ref().unwrap()
}
pub unsafe fn inner_child_mut(&mut self) -> &mut Child {
let mut inner = self;
while !inner.is_raw_child() {
inner = inner.inner_mut();
}
(inner as &mut dyn Any).downcast_mut().unwrap()
}
pub unsafe fn into_inner_child(self: Box<Self>) -> Child {
let mut inner = self;
while !inner.is_raw_child() {
inner = inner.into_inner();
}
*(inner as Box<dyn Any>).downcast().unwrap()
}
}
#[cfg(unix)]
fn read2(
mut out_r: ChildStdout,
out_v: &mut Vec<u8>,
mut err_r: ChildStderr,
err_v: &mut Vec<u8>,
) -> Result<()> {
use nix::{
errno::Errno,
libc,
poll::{PollFd, PollFlags, PollTimeout, poll},
};
use std::{
io::Error,
os::fd::{AsRawFd, BorrowedFd},
};
let out_fd = out_r.as_raw_fd();
let err_fd = err_r.as_raw_fd();
let out_bfd = unsafe { BorrowedFd::borrow_raw(out_fd) };
let err_bfd = unsafe { BorrowedFd::borrow_raw(err_fd) };
set_nonblocking(out_bfd, true)?;
set_nonblocking(err_bfd, true)?;
let mut fds = [
PollFd::new(out_bfd, PollFlags::POLLIN),
PollFd::new(err_bfd, PollFlags::POLLIN),
];
loop {
poll(&mut fds, PollTimeout::NONE)?;
if fds[0].revents().is_some() && read(&mut out_r, out_v)? {
set_nonblocking(err_bfd, false)?;
return err_r.read_to_end(err_v).map(drop);
}
if fds[1].revents().is_some() && read(&mut err_r, err_v)? {
set_nonblocking(out_bfd, false)?;
return out_r.read_to_end(out_v).map(drop);
}
}
fn read(r: &mut impl Read, dst: &mut Vec<u8>) -> Result<bool> {
match r.read_to_end(dst) {
Ok(_) => Ok(true),
Err(e) => {
if e.raw_os_error() == Some(libc::EWOULDBLOCK)
|| e.raw_os_error() == Some(libc::EAGAIN)
{
Ok(false)
} else {
Err(e)
}
}
}
}
#[cfg(target_os = "linux")]
fn set_nonblocking(fd: BorrowedFd, nonblocking: bool) -> Result<()> {
let v = nonblocking as libc::c_int;
let res = unsafe { libc::ioctl(fd.as_raw_fd(), libc::FIONBIO, &v) };
Errno::result(res).map_err(Error::from).map(drop)
}
#[cfg(not(target_os = "linux"))]
fn set_nonblocking(fd: BorrowedFd, nonblocking: bool) -> Result<()> {
use nix::fcntl::{FcntlArg, OFlag, fcntl};
let mut flags = OFlag::from_bits_truncate(fcntl(fd, FcntlArg::F_GETFL)?);
flags.set(OFlag::O_NONBLOCK, nonblocking);
fcntl(fd, FcntlArg::F_SETFL(flags))
.map_err(Error::from)
.map(drop)
}
}
#[cfg(not(unix))]
fn read2(
mut out_r: ChildStdout,
out_v: &mut Vec<u8>,
mut err_r: ChildStderr,
err_v: &mut Vec<u8>,
) -> Result<()> {
out_r.read_to_end(out_v)?;
err_r.read_to_end(err_v)?;
Ok(())
}
const _: () = {
const fn assert_sync<T: ?Sized + Sync>() {}
assert_sync::<dyn ChildWrapper>();
};