use std::ffi::c_void;
use std::io;
use std::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle};
use std::process::{Command as StdCommand, ExitStatus as StdExitStatus};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE};
use windows_sys::Win32::System::Threading::{
INFINITE, RegisterWaitForSingleObject, UnregisterWaitEx, WT_EXECUTEINWAITTHREAD,
WT_EXECUTEONLYONCE,
};
use crate::op::completion::{CompletionHandle, completion_for_current_thread};
use crate::process::pipe::Pipe;
use crate::process::{CommandSpec, EnvChange, StdioKind};
use crate::sys::handle::{RawFile, raw_file};
use crate::sys::windows::overlapped;
pub(crate) struct Child {
inner: Option<std::process::Child>,
status: Option<StdExitStatus>,
pub(crate) stdin: Option<Pipe>,
pub(crate) stdout: Option<Pipe>,
pub(crate) stderr: Option<Pipe>,
}
pub(crate) fn spawn(spec: &CommandSpec) -> io::Result<Child> {
let mut command = StdCommand::new(&spec.program);
command.args(&spec.args);
for change in &spec.env {
match change {
EnvChange::Set(key, value) => {
command.env(key, value);
}
EnvChange::Remove(key) => {
command.env_remove(key);
}
EnvChange::Clear => {
command.env_clear();
}
}
}
if let Some(dir) = &spec.current_dir {
command.current_dir(dir);
}
command.stdin(stdio(spec.stdin));
command.stdout(stdio(spec.stdout));
command.stderr(stdio(spec.stderr));
let mut child = command.spawn()?;
let stdin = child.stdin.take().map(adopt_pipe).transpose()?;
let stdout = child.stdout.take().map(adopt_pipe).transpose()?;
let stderr = child.stderr.take().map(adopt_pipe).transpose()?;
Ok(Child {
inner: Some(child),
status: None,
stdin,
stdout,
stderr,
})
}
impl Child {
pub fn id(&self) -> Option<u32> {
if self.status.is_some() {
return None;
}
self.inner.as_ref().map(std::process::Child::id)
}
pub fn try_wait(&mut self) -> io::Result<Option<StdExitStatus>> {
if let Some(status) = self.status {
return Ok(Some(status));
}
let Some(child) = self.inner.as_mut() else {
return Ok(self.status);
};
match child.try_wait()? {
Some(status) => {
self.status = Some(status);
Ok(Some(status))
}
None => Ok(None),
}
}
pub async fn wait(&mut self) -> io::Result<StdExitStatus> {
if let Some(status) = self.status {
return Ok(status);
}
if self.inner.is_none() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"child status is unavailable",
));
}
loop {
if let Some(status) = self.try_wait()? {
return Ok(status);
}
let process = self
.inner
.as_ref()
.expect("child handle is present until reaped")
.as_raw_handle();
wait_process_exit(process).await?;
}
}
pub fn kill(&mut self) -> io::Result<()> {
if self.try_wait()?.is_some() {
return Ok(());
}
let Some(child) = self.inner.as_mut() else {
return Ok(());
};
match child.kill() {
Ok(()) => Ok(()),
Err(error) => {
if self.try_wait()?.is_some() {
Ok(())
} else {
Err(error)
}
}
}
}
}
type PinPipeRead =
std::pin::Pin<Box<dyn std::future::Future<Output = io::Result<Vec<u8>>> + 'static>>;
type PinPipeWrite =
std::pin::Pin<Box<dyn std::future::Future<Output = io::Result<usize>> + 'static>>;
pub(crate) fn read_pipe_future(fd: RawFile, len: usize) -> PinPipeRead {
Box::pin(overlapped::read_at(fd, len, 0))
}
pub(crate) fn write_pipe_future(fd: RawFile, data: Vec<u8>) -> PinPipeWrite {
Box::pin(overlapped::write_at(fd, data, 0))
}
fn adopt_pipe<H: IntoRawHandle>(pipe: H) -> io::Result<Pipe> {
let handle = unsafe { OwnedHandle::from_raw_handle(pipe.into_raw_handle()) };
overlapped::associate_file(raw_file(&handle))?;
Ok(Pipe::new(handle))
}
struct WaitContext {
handle: CompletionHandle<io::Result<()>>,
fired: AtomicBool,
}
unsafe extern "system" fn child_exit_callback(context: *mut c_void, _timed_out: bool) {
let context = unsafe { Arc::from_raw(context.cast_const().cast::<WaitContext>()) };
context.fired.store(true, Ordering::Release);
context.handle.clone().complete(Ok(()));
}
struct WaitRegistration {
wait_object: HANDLE,
context: Arc<WaitContext>,
}
impl Drop for WaitRegistration {
fn drop(&mut self) {
unsafe { UnregisterWaitEx(self.wait_object, INVALID_HANDLE_VALUE) };
if !self.context.fired.load(Ordering::Acquire) {
unsafe { drop(Arc::from_raw(Arc::as_ptr(&self.context))) };
}
}
}
async fn wait_process_exit(process: RawHandle) -> io::Result<()> {
let (future, handle) = completion_for_current_thread::<io::Result<()>>();
let context = Arc::new(WaitContext {
handle: handle.clone(),
fired: AtomicBool::new(false),
});
let context_for_callback = Arc::into_raw(Arc::clone(&context));
let mut wait_object: HANDLE = std::ptr::null_mut();
let registered = unsafe {
RegisterWaitForSingleObject(
&mut wait_object,
process as HANDLE,
Some(child_exit_callback),
context_for_callback as *const c_void,
INFINITE,
WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD,
)
};
if registered == 0 {
let error = io::Error::last_os_error();
unsafe { drop(Arc::from_raw(context_for_callback)) };
handle.complete(Err(error));
return future.await;
}
let registration = WaitRegistration {
wait_object,
context: Arc::clone(&context),
};
let result = future.await;
drop(registration);
result
}
fn stdio(kind: StdioKind) -> std::process::Stdio {
match kind {
StdioKind::Inherit => std::process::Stdio::inherit(),
StdioKind::Null => std::process::Stdio::null(),
StdioKind::Piped => std::process::Stdio::piped(),
}
}