use std::ffi::c_void;
use std::io;
use std::mem::size_of;
use std::os::windows::io::{FromRawHandle, OwnedHandle};
use std::ptr;
#[cfg(test)]
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(test)]
use std::sync::Arc;
use windows_sys::Win32::Foundation::{ERROR_INSUFFICIENT_BUFFER, HANDLE, INVALID_HANDLE_VALUE};
use windows_sys::Win32::System::Threading::{
CreateProcessW, DeleteProcThreadAttributeList, InitializeProcThreadAttributeList,
UpdateProcThreadAttribute, CREATE_UNICODE_ENVIRONMENT, EXTENDED_STARTUPINFO_PRESENT,
LPPROC_THREAD_ATTRIBUTE_LIST, PROCESS_INFORMATION, PROC_THREAD_ATTRIBUTE_JOB_LIST,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, STARTF_USESTDHANDLES, STARTUPINFOEXW, STARTUPINFOW,
};
use crate::backend::HPCON;
use crate::command::{to_wide_nul, Command};
use crate::core::job::Job;
const ATTRIBUTE_COUNT: u32 = 2;
#[derive(Debug)]
pub(super) struct SpawnedChild {
pub(super) process: OwnedHandle,
pub(super) pid: u32,
}
pub(super) fn spawn(cmd: &Command, hpcon: HPCON, job: &Job) -> io::Result<SpawnedChild> {
let mut command_line = cmd.build_command_line()?;
let environment = cmd.build_environment_block()?;
let working_dir = cmd
.get_current_dir()
.map(|dir| to_wide_nul(dir.as_os_str()))
.transpose()?;
let job_handle: HANDLE = job.raw_handle();
let mut attributes = AttributeList::new(ATTRIBUTE_COUNT)?;
unsafe {
attributes.set(
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE as usize,
hpcon as *const c_void,
size_of::<HPCON>(),
)?;
}
unsafe {
attributes.set(
PROC_THREAD_ATTRIBUTE_JOB_LIST as usize,
ptr::addr_of!(job_handle).cast(),
size_of::<HANDLE>(),
)?;
}
let startup_info = startup_info(&mut attributes);
let flags = EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT;
let mut process_info = PROCESS_INFORMATION::default();
let created = unsafe {
CreateProcessW(
ptr::null(),
command_line.as_mut_ptr(),
ptr::null(),
ptr::null(),
0, flags,
environment
.as_ref()
.map_or(ptr::null(), |block| block.as_ptr().cast()),
working_dir.as_ref().map_or(ptr::null(), Vec::as_ptr),
ptr::addr_of!(startup_info).cast::<STARTUPINFOW>(),
&mut process_info,
)
};
if created == 0 {
return Err(io::Error::last_os_error());
}
let process = unsafe { OwnedHandle::from_raw_handle(process_info.hProcess) };
let thread = unsafe { OwnedHandle::from_raw_handle(process_info.hThread) };
drop(thread);
Ok(SpawnedChild {
process,
pid: process_info.dwProcessId,
})
}
fn startup_info(attributes: &mut AttributeList) -> STARTUPINFOEXW {
STARTUPINFOEXW {
StartupInfo: STARTUPINFOW {
cb: u32::try_from(size_of::<STARTUPINFOEXW>()).unwrap_or(u32::MAX),
dwFlags: STARTF_USESTDHANDLES,
hStdInput: INVALID_HANDLE_VALUE,
hStdOutput: INVALID_HANDLE_VALUE,
hStdError: INVALID_HANDLE_VALUE,
..Default::default()
},
lpAttributeList: attributes.as_ptr(),
}
}
struct AttributeList {
buffer: Vec<usize>,
#[cfg(test)]
drop_observer: Option<Arc<AtomicBool>>,
}
impl AttributeList {
fn new(attributes: u32) -> io::Result<Self> {
let mut size: usize = 0;
let probed =
unsafe { InitializeProcThreadAttributeList(ptr::null_mut(), attributes, 0, &mut size) };
if probed != 0 {
return Err(io::Error::other(
"InitializeProcThreadAttributeList unexpectedly succeeded while probing \
for the attribute list size",
));
}
let probe_error = io::Error::last_os_error();
if probe_error.raw_os_error() != i32::try_from(ERROR_INSUFFICIENT_BUFFER).ok() {
return Err(probe_error);
}
let words = size.div_ceil(size_of::<usize>()).max(1);
let mut buffer = vec![0usize; words];
let ok = unsafe {
InitializeProcThreadAttributeList(buffer.as_mut_ptr().cast(), attributes, 0, &mut size)
};
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(Self {
buffer,
#[cfg(test)]
drop_observer: None,
})
}
fn as_ptr(&mut self) -> LPPROC_THREAD_ATTRIBUTE_LIST {
self.buffer.as_mut_ptr().cast()
}
unsafe fn set(
&mut self,
attribute: usize,
value: *const c_void,
size: usize,
) -> io::Result<()> {
let ok = unsafe {
UpdateProcThreadAttribute(
self.as_ptr(),
0,
attribute,
value,
size,
ptr::null_mut(),
ptr::null(),
)
};
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
}
impl Drop for AttributeList {
fn drop(&mut self) {
#[cfg(test)]
if let Some(observer) = &self.drop_observer {
observer.store(true, Ordering::SeqCst);
}
unsafe { DeleteProcThreadAttributeList(self.as_ptr()) };
}
}
#[cfg(test)]
#[path = "proc_tests.rs"]
mod tests;