#![allow(clippy::missing_errors_doc)]
use windows::core::HSTRING;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::System::HostComputeSystem::{
HcsCreateOperation, HcsCreateProcess, HcsGetProcessProperties, HcsModifyProcess,
HcsOpenProcess, HcsSignalProcess, HcsTerminateProcess, HcsWaitForOperationResultAndProcessInfo,
HCS_PROCESS, HCS_PROCESS_INFORMATION, HCS_SYSTEM,
};
use crate::error::{HcsError, HcsResult};
use crate::handle::{OwnedProcess, SendHandle};
use crate::operation::run_operation;
use crate::schema::ProcessParameters;
#[derive(Debug)]
pub struct CapturedProcess {
pub process: ComputeProcess,
stdout: Option<HANDLE>,
stderr: Option<HANDLE>,
}
#[derive(Debug)]
pub struct ComputeProcess {
inner: OwnedProcess,
}
impl ComputeProcess {
pub async fn create(
system: SendHandle<HCS_SYSTEM>,
process_parameters_json: &str,
) -> HcsResult<Self> {
let params_w = HSTRING::from(process_parameters_json);
let mut created: Option<SendHandle<HCS_PROCESS>> = None;
let created_slot = &mut created;
let _ = run_operation(|op| {
match unsafe { HcsCreateProcess(*system, ¶ms_w, op, None) } {
Ok(handle) => {
*created_slot = Some(SendHandle(handle));
windows::core::HRESULT(0)
}
Err(e) => e.code(),
}
})
.await?;
let raw = created.ok_or_else(|| HcsError::Other {
hresult: 0,
message: "HcsCreateProcess succeeded but produced no handle".to_string(),
})?;
if raw.0.is_invalid() {
return Err(HcsError::Other {
hresult: 0,
message: "HcsCreateProcess returned invalid handle".to_string(),
});
}
Ok(Self {
inner: unsafe { OwnedProcess::from_raw(raw.0) },
})
}
pub async fn spawn(
system: SendHandle<HCS_SYSTEM>,
params: &ProcessParameters,
) -> HcsResult<Self> {
let json = serde_json::to_string(params)?;
Self::create(system, &json).await
}
pub fn create_capturing_blocking(
system: SendHandle<HCS_SYSTEM>,
params: &ProcessParameters,
) -> HcsResult<CapturedProcess> {
let json = serde_json::to_string(params)?;
let params_w = HSTRING::from(json.as_str());
let op = unsafe { HcsCreateOperation(None, None) };
if op.is_invalid() {
return Err(HcsError::Other {
hresult: 0,
message: "HcsCreateOperation returned an invalid handle".to_string(),
});
}
let owned_op = unsafe { crate::handle::OwnedOperation::from_raw(op) };
let created =
match unsafe { HcsCreateProcess(*system, ¶ms_w, *owned_op.as_raw(), None) } {
Ok(handle) => handle,
Err(e) => return Err(HcsError::from_hresult(e.code(), "HcsCreateProcess")),
};
if created.is_invalid() {
return Err(HcsError::Other {
hresult: 0,
message: "HcsCreateProcess returned invalid handle".to_string(),
});
}
let mut info = HCS_PROCESS_INFORMATION::default();
let mut result_doc = windows::core::PWSTR::null();
let wait = unsafe {
HcsWaitForOperationResultAndProcessInfo(
*owned_op.as_raw(),
30_000,
Some(&raw mut info),
Some(&raw mut result_doc),
)
};
if let Err(e) = wait {
drop(unsafe { OwnedProcess::from_raw(created) });
return Err(HcsError::from_hresult(
e.code(),
"HcsWaitForOperationResultAndProcessInfo",
));
}
let stdout = (!info.StdOutput.is_invalid()).then_some(info.StdOutput);
let stderr = (!info.StdError.is_invalid()).then_some(info.StdError);
if !info.StdInput.is_invalid() {
unsafe {
let _ = windows::Win32::Foundation::CloseHandle(info.StdInput);
}
}
Ok(CapturedProcess {
process: Self {
inner: unsafe { OwnedProcess::from_raw(created) },
},
stdout,
stderr,
})
}
pub fn open(system: HCS_SYSTEM, process_id: u32, requested_access: u32) -> HcsResult<Self> {
let raw = unsafe { HcsOpenProcess(system, process_id, requested_access) }.map_err(|e| {
HcsError::from_hresult(e.code(), format!("HcsOpenProcess(pid={process_id})"))
})?;
if raw.is_invalid() {
return Err(HcsError::Other {
hresult: 0,
message: "HcsOpenProcess returned invalid handle".to_string(),
});
}
Ok(Self {
inner: unsafe { OwnedProcess::from_raw(raw) },
})
}
#[must_use]
pub fn raw(&self) -> SendHandle<HCS_PROCESS> {
self.inner.as_raw()
}
pub async fn signal(&self, options_json: &str) -> HcsResult<()> {
let opts_w = HSTRING::from(options_json);
let handle = self.inner.as_raw();
run_operation(move |op| {
match unsafe { HcsSignalProcess(*handle, op, &opts_w) } {
Ok(()) => windows::core::HRESULT(0),
Err(e) => e.code(),
}
})
.await?;
Ok(())
}
pub async fn terminate(&self, options_json: &str) -> HcsResult<()> {
let opts_w = HSTRING::from(options_json);
let handle = self.inner.as_raw();
run_operation(move |op| {
match unsafe { HcsTerminateProcess(*handle, op, &opts_w) } {
Ok(()) => windows::core::HRESULT(0),
Err(e) => e.code(),
}
})
.await?;
Ok(())
}
pub async fn modify(&self, modification_json: &str) -> HcsResult<()> {
let mod_w = HSTRING::from(modification_json);
let handle = self.inner.as_raw();
run_operation(move |op| {
match unsafe { HcsModifyProcess(*handle, op, &mod_w) } {
Ok(()) => windows::core::HRESULT(0),
Err(e) => e.code(),
}
})
.await?;
Ok(())
}
pub async fn resize_console(&self, cols: u16, rows: u16) -> HcsResult<()> {
let json = serde_json::to_string(&serde_json::json!({
"ConsoleSize": {
"Width": cols,
"Height": rows,
}
}))?;
self.modify(&json).await
}
pub async fn properties(&self, property_query_json: &str) -> HcsResult<String> {
let query_w = HSTRING::from(property_query_json);
let handle = self.inner.as_raw();
run_operation(move |op| {
match unsafe { HcsGetProcessProperties(*handle, op, &query_w) } {
Ok(()) => windows::core::HRESULT(0),
Err(e) => e.code(),
}
})
.await
}
}
impl CapturedProcess {
#[must_use]
pub fn process(&self) -> &ComputeProcess {
&self.process
}
#[must_use]
pub fn drain(self) -> (String, String) {
let out = self.stdout.map(drain_pipe).unwrap_or_default();
let err = self.stderr.map(drain_pipe).unwrap_or_default();
(
String::from_utf8_lossy(&out).into_owned(),
String::from_utf8_lossy(&err).into_owned(),
)
}
#[must_use]
pub fn drain_with_process(self) -> (ComputeProcess, String, String) {
let out = self.stdout.map(drain_pipe).unwrap_or_default();
let err = self.stderr.map(drain_pipe).unwrap_or_default();
(
self.process,
String::from_utf8_lossy(&out).into_owned(),
String::from_utf8_lossy(&err).into_owned(),
)
}
}
fn drain_pipe(handle: HANDLE) -> Vec<u8> {
use windows::Win32::Foundation::{CloseHandle, ERROR_BROKEN_PIPE};
use windows::Win32::Storage::FileSystem::ReadFile;
let mut out = Vec::new();
let mut buf = [0u8; 8192];
loop {
let mut read: u32 = 0;
let res = unsafe { ReadFile(handle, Some(&mut buf), Some(&raw mut read), None) };
match res {
Ok(()) => {
if read == 0 {
break; }
out.extend_from_slice(&buf[..read as usize]);
}
Err(e) => {
if e.code() != ERROR_BROKEN_PIPE.to_hresult() {
tracing::debug!(error = %e, "drain_pipe: ReadFile ended on a non-EOF error");
}
break;
}
}
}
unsafe {
let _ = CloseHandle(handle);
}
out
}