use crate::com::HandleMtaLease;
use crate::com_memory::ComAnsiString;
use crate::error::WslcError;
use crate::process::{ProcessStreams, StreamState, WslcProcessHandle, setup_streaming_channels};
use crate::session::WslcSessionHandle;
use std::ffi::{CStr, CString};
use std::sync::{Arc, Mutex};
use wslcsdk_sys::types::{
WSLC_CONTAINER_ID_BUFFER_SIZE, WslcContainer, WslcContainerStartFlags, WslcContainerState,
WslcDeleteContainerFlags, WslcProcess, WslcSignal,
};
use wslcsdk_sys::{
WslcDeleteContainer, WslcGetContainerID, WslcGetContainerInitProcess, WslcGetContainerState,
WslcInspectContainer, WslcOpenContainer, WslcReleaseContainer, WslcReleaseProcess,
WslcSetContainerInitProcessIOCallbacks, WslcStartContainer, WslcStopContainer,
};
#[derive(Debug)]
enum InitProcessSource {
Unfetched,
Borrowed(WslcProcess),
}
#[derive(Debug)]
struct ContainerInner {
raw: WslcContainer,
_session: WslcSessionHandle,
_mta: HandleMtaLease,
init_stream_state: Mutex<Option<Arc<StreamState>>>,
init_process_raw: Mutex<InitProcessSource>,
}
impl Drop for ContainerInner {
fn drop(&mut self) {
let proc_raw = {
let mut slot = self
.init_process_raw
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let raw = match *slot {
InitProcessSource::Borrowed(raw) => raw,
InitProcessSource::Unfetched => WslcProcess::NULL,
};
*slot = InitProcessSource::Unfetched;
raw
};
if !proc_raw.is_null() {
unsafe {
let _ = WslcReleaseProcess(proc_raw);
}
}
if !self.raw.is_null() {
unsafe {
let _ = WslcReleaseContainer(self.raw);
}
self.raw = WslcContainer::NULL;
}
}
}
unsafe impl Send for ContainerInner {}
unsafe impl Sync for ContainerInner {}
#[derive(Clone, Debug)]
pub struct WslcContainerHandle {
inner: Arc<ContainerInner>,
}
impl WslcContainerHandle {
pub(crate) fn from_raw_inner(
raw: WslcContainer,
session: WslcSessionHandle,
init_stream_state: Option<Arc<StreamState>>,
) -> Result<Self, WslcError> {
unsafe {
HandleMtaLease::wrap_raw(
raw,
|h| {
let _ = WslcReleaseContainer(h);
},
|raw, mta| Self {
inner: Arc::new(ContainerInner {
raw,
_session: session,
_mta: mta,
init_stream_state: Mutex::new(init_stream_state),
init_process_raw: Mutex::new(InitProcessSource::Unfetched),
}),
},
)
}
}
pub fn open(session: &WslcSessionHandle, name_or_id: &str) -> Result<Self, WslcError> {
let _com_guard = crate::com::try_initialize_mta()?;
let c_str = CString::new(name_or_id)
.map_err(|e| WslcError::NulError(format!("名称或 ID 包含非法空字节: {e}")))?;
let mut raw = WslcContainer::NULL;
let mut err_msg: *mut u16 = std::ptr::null_mut();
let hr =
unsafe { WslcOpenContainer(session.as_raw(), c_str.as_ptr(), &mut raw, &mut err_msg) };
unsafe {
WslcError::check(
hr,
err_msg,
format!("打开容器失败,名称或 ID: '{name_or_id}'"),
)?;
}
if raw.is_null() {
return Err(WslcError::InvalidHandle);
}
Self::from_raw_inner(raw, session.clone(), None)
}
pub fn session(&self) -> WslcSessionHandle {
self.inner._session.clone()
}
pub fn as_raw(&self) -> WslcContainer {
self.inner.raw
}
pub fn id(&self) -> Result<String, WslcError> {
let mut buffer = [0i8; WSLC_CONTAINER_ID_BUFFER_SIZE];
let hr = unsafe { WslcGetContainerID(self.inner.raw, buffer.as_mut_ptr()) };
WslcError::check_hr(hr, "获取容器 ID 失败")?;
let c_str = unsafe { CStr::from_ptr(buffer.as_ptr()) };
c_str
.to_str()
.map(|s| s.to_string())
.map_err(|e| WslcError::Utf8Error(e.to_string()))
}
pub fn state(&self) -> Result<WslcContainerState, WslcError> {
let mut state = WslcContainerState::Invalid;
let hr = unsafe { WslcGetContainerState(self.inner.raw, &mut state) };
WslcError::check_hr(hr, "获取容器状态失败")?;
Ok(state)
}
pub fn start(&self, attach: bool) -> Result<(), WslcError> {
let flags: WslcContainerStartFlags = if attach {
wslcsdk_sys::types::WSLC_CONTAINER_START_FLAG_ATTACH
} else {
wslcsdk_sys::types::WSLC_CONTAINER_START_FLAG_NONE
};
let mut err_msg: *mut u16 = std::ptr::null_mut();
let hr = unsafe { WslcStartContainer(self.inner.raw, flags, &mut err_msg) };
unsafe {
WslcError::check(hr, err_msg, format!("启动容器失败,attach 模式: {attach}"))?;
}
log::info!("WSLC 容器启动成功,attach 模式: {attach}");
Ok(())
}
pub fn stop(&self, signal: WslcSignal, timeout_seconds: u32) -> Result<(), WslcError> {
let mut err_msg: *mut u16 = std::ptr::null_mut();
let hr =
unsafe { WslcStopContainer(self.inner.raw, signal, timeout_seconds, &mut err_msg) };
unsafe {
WslcError::check(
hr,
err_msg,
format!("停止容器失败,信号: {signal:?},超时: {timeout_seconds} 秒"),
)?;
}
log::info!("WSLC 容器停止成功,信号: {signal:?},超时: {timeout_seconds} 秒");
Ok(())
}
pub fn delete(&self, force: bool) -> Result<(), WslcError> {
let flags: WslcDeleteContainerFlags = if force {
wslcsdk_sys::types::WSLC_DELETE_CONTAINER_FLAG_FORCE
} else {
wslcsdk_sys::types::WSLC_DELETE_CONTAINER_FLAG_NONE
};
let mut err_msg: *mut u16 = std::ptr::null_mut();
let hr = unsafe { WslcDeleteContainer(self.inner.raw, flags, &mut err_msg) };
unsafe {
WslcError::check(hr, err_msg, format!("删除容器失败,强制标志: {force}"))?;
}
log::info!("WSLC 容器删除成功,强制标志: {force}");
Ok(())
}
pub fn inspect(&self) -> Result<serde_json::Value, WslcError> {
let mut inspect_ptr: *mut i8 = std::ptr::null_mut();
let hr = unsafe { WslcInspectContainer(self.inner.raw, &mut inspect_ptr) };
if hr < 0 {
return Err(WslcError::from_hresult(hr, "检查容器元数据失败"));
}
let json = unsafe { ComAnsiString::from_raw(inspect_ptr) }
.ok_or_else(|| WslcError::missing_output("WslcInspectContainer", hr))?;
let text = json.as_str().map_err(|e| {
WslcError::JsonError(format!(
"容器检查数据非合法 UTF-8(官方返回的字节序列含非法编码): {e}"
))
})?;
serde_json::from_str(text).map_err(|e| WslcError::JsonError(e.to_string()))
}
pub fn get_init_process(&self) -> Result<WslcProcessHandle, WslcError> {
if let Some(raw) = self.cached_init_process() {
return self.wrap_init_process(raw);
}
let mut raw = WslcProcess::NULL;
let hr = unsafe { WslcGetContainerInitProcess(self.inner.raw, &mut raw) };
if hr < 0 {
return Err(WslcError::from_hresult(hr, "获取容器主进程句柄失败"));
}
if raw.is_null() {
return Err(WslcError::missing_output("WslcGetContainerInitProcess", hr));
}
let effective = {
let mut slot = self
.inner
.init_process_raw
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match *slot {
InitProcessSource::Borrowed(existing) => {
unsafe {
let _ = WslcReleaseProcess(raw);
}
existing
}
InitProcessSource::Unfetched => {
*slot = InitProcessSource::Borrowed(raw);
raw
}
}
};
self.wrap_init_process(effective)
}
fn cached_init_process(&self) -> Option<WslcProcess> {
let slot = self
.inner
.init_process_raw
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match *slot {
InitProcessSource::Borrowed(raw) => Some(raw),
InitProcessSource::Unfetched => None,
}
}
fn wrap_init_process(&self, raw: WslcProcess) -> Result<WslcProcessHandle, WslcError> {
let stream_state = self
.inner
.init_stream_state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
WslcProcessHandle::try_from_borrowed(raw, self.clone(), stream_state)
}
pub fn with_init_process_io_callbacks(
&self,
capacity: usize,
) -> Result<ProcessStreams, WslcError> {
if self.inner.raw.is_null() {
return Err(WslcError::InvalidHandle);
}
if self.has_init_io_callbacks() {
return Err(WslcError::InvalidConfiguration(
"init 进程 IO 回调已注册,不可重复注册:重复注册会使先前返回的 \
ProcessStreams 永久收不到退出通知"
.to_string(),
));
}
let setup = setup_streaming_channels(capacity);
let hr = unsafe {
WslcSetContainerInitProcessIOCallbacks(
self.inner.raw,
&setup.callbacks,
setup.context as *mut core::ffi::c_void,
)
};
WslcError::check_hr(hr, "设置主进程 IO 回调失败")?;
{
let mut slot = self
.inner
.init_stream_state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot.is_some() {
return Err(WslcError::InvalidConfiguration(
"init 进程 IO 回调已被并发注册,本次注册作废".to_string(),
));
}
*slot = Some(setup.state);
}
Ok(setup.streams)
}
fn has_init_io_callbacks(&self) -> bool {
self.inner
.init_stream_state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_some()
}
}