use crate::{
RuntimeError,
constants::INLINE_PAYLOAD,
executor,
futures::{
net::stream::{Pipe, RecvTask, SendTask, Source},
process::{
exit_status::ExitStatus,
process_task::{
Child, Env, Program, Setup, Stdio, as_dir, as_env, input_pipe, kill_and_reap, pipe,
spawn_child, wait_exit,
},
},
signal::{SignalKind, dispatch},
task::{
Nothing, Task,
sealed::{self},
},
},
modules::{input::Token, int_check::IntCheck, kqueue},
};
use std::{
ffi::OsStr,
fmt, mem,
path::Path,
sync::{
Arc, Mutex, PoisonError,
atomic::{AtomicBool, Ordering},
},
};
const _: () = assert!(mem::size_of::<Result<RunningChild, RuntimeError>>() <= INLINE_PAYLOAD);
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct SpawnTask {
program: Program,
setup: Setup,
}
impl SpawnTask {
pub(crate) fn new<S, A, I>(program: S, args: A) -> Self
where
S: AsRef<OsStr>,
A: IntoIterator<Item = I>,
I: AsRef<OsStr>,
{
Self {
program: Program::new(program, args),
setup: Setup::default(),
}
}
pub fn in_dir(mut self, path: impl AsRef<Path>) -> Self {
self.setup.dir = as_dir(path);
self
}
pub fn env<I, K, V>(mut self, vars: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.setup.env = as_env(vars, Env::Over);
self
}
pub fn env_only<I, K, V>(mut self, vars: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.setup.env = as_env(vars, Env::Only);
self
}
fn spawn(&self) -> Result<RunningChild, RuntimeError> {
let (in_read, in_write) = input_pipe()?;
let (out_read, out_write) = pipe()?;
let (err_read, err_write) = pipe()?;
nonblocking(&out_read)?;
nonblocking(&err_read)?;
let stdio = Stdio {
input: Some(in_read.raw()),
capture: Some((out_write.raw(), err_write.raw())),
};
let pid = spawn_child(&self.program, &self.setup, stdio)?;
Ok(RunningChild {
inner: Arc::new(Inner {
pid,
status: Mutex::new(None),
ended: AtomicBool::new(false),
stdin: Mutex::new(Some(Arc::new(Pipe::new(in_write)))),
stdout: Arc::new(Pipe::new(out_read)),
stderr: Arc::new(Pipe::new(err_read)),
}),
})
}
}
#[derive(Clone)]
pub struct RunningChild {
inner: Arc<Inner>,
}
struct Inner {
pid: libc::pid_t,
status: Mutex<Option<ExitStatus>>,
ended: AtomicBool,
stdin: Mutex<Option<Arc<Pipe>>>,
stdout: Arc<Pipe>,
stderr: Arc<Pipe>,
}
impl RunningChild {
pub fn id(&self) -> u32 {
self.inner.pid as u32
}
pub fn wait(&self) -> ChildWaitTask {
ChildWaitTask {
child: self.clone(),
}
}
pub fn signal(&self, kind: SignalKind) -> ChildSignalTask {
ChildSignalTask {
child: self.clone(),
signo: kind.number(),
group: false,
}
}
pub fn kill(&self) -> ChildSignalTask {
ChildSignalTask {
child: self.clone(),
signo: libc::SIGKILL,
group: true,
}
}
pub fn stdin(&self) -> Option<ChildStdin> {
let stdin = self
.inner
.stdin
.lock()
.unwrap_or_else(PoisonError::into_inner);
stdin.as_ref().map(|pipe| ChildStdin {
pipe: Arc::clone(pipe),
})
}
pub fn close_stdin(&self) {
let closed = self
.inner
.stdin
.lock()
.unwrap_or_else(PoisonError::into_inner)
.take();
drop(closed);
}
pub fn stdout(&self) -> ChildOutput {
ChildOutput {
pipe: Arc::clone(&self.inner.stdout),
}
}
pub fn stderr(&self) -> ChildOutput {
ChildOutput {
pipe: Arc::clone(&self.inner.stderr),
}
}
fn wait_here(&self) -> Result<ExitStatus, RuntimeError> {
let mut status = self
.inner
.status
.lock()
.unwrap_or_else(PoisonError::into_inner);
if let Some(status) = *status {
return Ok(status);
}
if executor::cancelled() {
return Err(RuntimeError::Cancelled);
}
let mut child = Child::new(self.inner.pid);
let waited = wait_exit(&mut child, kqueue::id().ok());
match waited {
Ok(ended) => {
*status = Some(ended);
self.inner.ended.store(true, Ordering::SeqCst);
}
Err(RuntimeError::Cancelled) => child.reaped(),
Err(_) => self.inner.ended.store(true, Ordering::SeqCst),
}
waited
}
}
impl Drop for Inner {
fn drop(&mut self) {
if !self.ended.load(Ordering::SeqCst) {
kill_and_reap(self.pid);
}
}
}
impl fmt::Debug for RunningChild {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("RunningChild")
.field("id", &self.inner.pid)
.finish_non_exhaustive()
}
}
#[derive(Clone)]
pub struct ChildStdin {
pipe: Arc<Pipe>,
}
impl ChildStdin {
pub fn send(&self, data: impl Into<Arc<[u8]>>) -> SendTask {
SendTask::new(Source::Pipe(Arc::clone(&self.pipe)), data.into())
}
}
impl fmt::Debug for ChildStdin {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_struct("ChildStdin").finish_non_exhaustive()
}
}
#[derive(Clone)]
pub struct ChildOutput {
pipe: Arc<Pipe>,
}
impl ChildOutput {
fn source(&self) -> Source {
Source::Pipe(Arc::clone(&self.pipe))
}
pub fn recv(&self, max: usize) -> RecvTask {
RecvTask::some(self.source(), max)
}
pub fn recv_exact(&self, len: usize) -> RecvTask {
RecvTask::exact(self.source(), len)
}
pub fn recv_until(&self, delimiter: impl AsRef<[u8]>, max: usize) -> RecvTask {
RecvTask::until(self.source(), Arc::from(delimiter.as_ref()), max)
}
pub fn recv_to_end(&self) -> RecvTask {
RecvTask::to_end(self.source())
}
}
impl fmt::Debug for ChildOutput {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ChildOutput")
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct ChildWaitTask {
child: RunningChild,
}
#[derive(Debug, Clone)]
#[must_use = "a task does nothing until it is run or spawned"]
pub struct ChildSignalTask {
child: RunningChild,
signo: libc::c_int,
group: bool,
}
impl ChildSignalTask {
fn send(&self) -> Result<(), RuntimeError> {
dispatch::sendable(self.signo)?;
let inner = &self.child.inner;
let reaped = inner.status.try_lock().map(|status| status.is_some());
if reaped.unwrap_or(false) || inner.ended.load(Ordering::SeqCst) {
return Err(RuntimeError::Finished);
}
if self.group {
let _ = unsafe { libc::kill(-inner.pid, self.signo) };
}
unsafe { libc::kill(inner.pid, self.signo) }.check()?;
Ok(())
}
}
impl sealed::Sealed for SpawnTask {}
impl sealed::Sealed for ChildWaitTask {}
impl sealed::Sealed for ChildSignalTask {}
impl Task for SpawnTask {
type Output = Result<RunningChild, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
if executor::cancelled() {
return Err(RuntimeError::Cancelled);
}
self.spawn()
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for ChildWaitTask {
type Output = Result<ExitStatus, RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
self.child.wait_here()
}
fn blocking(&self, _token: Token) -> bool {
true
}
}
impl Task for ChildSignalTask {
type Output = Result<(), RuntimeError>;
type Input = Nothing;
fn execute(&self, _token: Token, _reactor_id: i32, _task_id: usize) -> Self::Output {
self.send()
}
}
fn nonblocking(fd: &crate::modules::fd::Fd) -> Result<(), RuntimeError> {
let flags = unsafe { libc::fcntl(fd.raw(), libc::F_GETFL) }.check()?;
unsafe { libc::fcntl(fd.raw(), libc::F_SETFL, flags | libc::O_NONBLOCK) }.check()?;
Ok(())
}