#![allow(non_snake_case)]
#![deny(missing_docs)]
mod binding;
use std::{
ffi::{OsStr, OsString},
fmt,
io::Error,
iter::once,
mem::size_of,
os::windows::ffi::OsStrExt,
path::{Path, PathBuf},
ptr::{null, null_mut},
};
use crate::binding::{
CloseHandle, CreateProcessW, GetExitCodeProcess, TerminateProcess, WaitForSingleObject, BOOL,
DWORD, INFINITE, PCWSTR, PDWORD, PROCESS_INFORMATION, PWSTR, STARTUPINFOW, STATUS_PENDING,
UINT, WAIT_OBJECT_0,
};
#[derive(Debug)]
pub struct Command {
command: OsString,
inherit_handles: bool,
current_directory: Option<PathBuf>,
}
impl Command {
pub fn new(command: impl Into<OsString>) -> Self {
Self {
command: command.into(),
inherit_handles: false,
current_directory: None,
}
}
pub fn inherit_handles(&mut self, inherit: bool) -> &mut Self {
self.inherit_handles = inherit;
self
}
pub fn current_dir(&mut self, dir: impl Into<PathBuf>) -> &mut Self {
self.current_directory = Some(dir.into());
self
}
pub fn spawn(&mut self) -> Result<Child, Error> {
Child::new(
&self.command,
self.inherit_handles,
self.current_directory.as_deref(),
)
}
pub fn status(&mut self) -> Result<ExitStatus, Error> {
self.spawn()?.wait()
}
}
#[derive(Debug)]
pub struct Child {
process_information: PROCESS_INFORMATION,
}
impl Child {
fn new(
command: &OsStr,
inherit_handles: bool,
current_directory: Option<&Path>,
) -> Result<Self, Error> {
let mut startup_information = STARTUPINFOW::default();
let mut process_information = PROCESS_INFORMATION::default();
startup_information.cb = size_of::<STARTUPINFOW>() as u32;
let process_creation_flags = 0 as DWORD;
let current_directory_ptr = current_directory
.map(|path| {
let wide_path: Vec<u16> = path.as_os_str().encode_wide().chain(once(0)).collect();
wide_path.as_ptr()
})
.unwrap_or(std::ptr::null_mut());
let command = command.encode_wide().chain(once(0)).collect::<Vec<_>>();
let res = unsafe {
CreateProcessW(
null(),
command.as_ptr() as PWSTR,
null_mut(),
null_mut(),
inherit_handles as BOOL,
process_creation_flags as DWORD,
null_mut(),
current_directory_ptr as PCWSTR,
&startup_information,
&mut process_information,
)
};
if res != 0 {
Ok(Self {
process_information,
})
} else {
Err(Error::last_os_error())
}
}
pub fn kill(&self) -> Result<(), Error> {
let res = unsafe { TerminateProcess(self.process_information.hProcess, 0 as UINT) };
if res != 0 {
Ok(())
} else {
Err(Error::last_os_error())
}
}
pub fn wait(&self) -> Result<ExitStatus, Error> {
let mut exit_code = 0;
let wait = unsafe {
WaitForSingleObject(self.process_information.hProcess, INFINITE) == WAIT_OBJECT_0
};
if wait {
let res = unsafe {
GetExitCodeProcess(self.process_information.hProcess, &mut exit_code as PDWORD)
};
if res != 0 {
unsafe {
CloseHandle(self.process_information.hProcess);
CloseHandle(self.process_information.hThread);
}
Ok(ExitStatus(exit_code))
} else {
Err(Error::last_os_error())
}
} else {
Err(Error::last_os_error())
}
}
pub fn try_wait(&self) -> Result<Option<ExitStatus>, Error> {
let mut exit_code = 0;
let res = unsafe {
GetExitCodeProcess(self.process_information.hProcess, &mut exit_code as PDWORD)
};
if res != 0 {
if exit_code == STATUS_PENDING {
Ok(None)
} else {
unsafe {
CloseHandle(self.process_information.hProcess);
CloseHandle(self.process_information.hThread);
}
Ok(Some(ExitStatus(exit_code)))
}
} else {
Err(Error::last_os_error())
}
}
pub fn id(&self) -> u32 {
self.process_information.dwProcessId
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExitStatus(u32);
impl ExitStatus {
pub fn success(&self) -> bool {
self.0 == 0
}
pub fn code(&self) -> u32 {
self.0
}
}
impl fmt::Display for ExitStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}