1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
use crate::error::{WslcDomainError, WslcError};
use core::ffi::c_void;
use wslcsdk_sys::types::{WslcComponentFlags, WslcInstallOptions, WslcVersion};
use wslcsdk_sys::*;
/// WSLC 系统服务门面
///
/// 全部方法均为关联函数,不持有状态。
#[derive(Debug, Clone, Copy, Default)]
pub struct WslcSystem;
impl WslcSystem {
/// 探测 `wslcsdk.dll` 是否可加载
///
/// **该结果不代表运行时已就绪**:宿主可能存在该 DLL 却缺少 WSL
/// 虚拟机平台、WSL 包,或 SDK 版本过旧。因此本方法只作为
/// `build.rs` `/DELAYLOAD` 链接策略的崩溃防护——DLL 不可用时,
/// 任何后续调用都会在首次触达延迟加载时触发 SEH(0xC06D007E),
/// 必须先行拦下。
///
/// 真正的就绪判定见 [`ensure_sdk_available`](Self::ensure_sdk_available)。
pub fn is_sdk_available() -> bool {
let dll_name: Vec<u16> = "wslcsdk.dll"
.encode_utf16()
.chain(std::iter::once(0))
.collect();
// SAFETY: 库名为已 NUL 结尾的宽字符串缓冲区,
// 标志位组合合法,加载失败时返回空句柄并由调用方处理。
let handle = unsafe {
windows_sys::Win32::System::LibraryLoader::LoadLibraryExW(
dll_name.as_ptr(),
std::ptr::null_mut(),
windows_sys::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
)
};
if !handle.is_null() {
// SAFETY: 句柄来自同一次成功的 LoadLibraryExW 且已被判非空,
// 期间未发生其他释放,不存在双重释放。
unsafe {
windows_sys::Win32::Foundation::FreeLibrary(handle);
}
true
} else {
false
}
}
/// 确保 `wslcsdk.dll` 可加载
///
/// 作为所有官方调用的前置守卫:官方函数均以 delayload 方式链接,
/// DLL 缺失时会在调用瞬间抛出 SEH 而非返回错误码。故凡要触达官方
/// 接口之处(如 [`get_version`](Self::get_version))必须先经本方法。
///
/// 该守卫只回答「库能否加载」,不判断运行时组件是否齐备——
/// 后者由 [`ensure_sdk_available`](Self::ensure_sdk_available) 负责。
fn ensure_dll_loadable() -> Result<(), WslcError> {
if Self::is_sdk_available() {
Ok(())
} else {
Err(WslcDomainError::SdkUpdateNeeded(
"宿主机系统中未检测到可用的 wslcsdk.dll 运行时,请先安装 WSL Containers 支持组件"
.to_string(),
)
.into())
}
}
/// 确保 WSLC 运行时真正就绪,否则返回友好领域错误
///
/// 判据分两层,缺一不可:
///
/// 1. [`is_sdk_available`](Self::is_sdk_available) —— `wslcsdk.dll`
/// 可加载。此层是delayload 的崩溃防护:DLL 缺失时调用官方接口
/// 会抛SEH(0xC06D007E) 而非返回错误码,必须先行拦下。
/// 2. [`get_missing_components`](Self::get_missing_components) 的
/// 官方判定 —— DLL 存在不等于运行时可用:宿主可能缺少 WSL
/// 虚拟机平台或 WSL 包,SDK 版本也可能过旧。仅靠第1 层会把这些
/// 情况误判为「就绪」,使调用方跳过前置门禁、直至撞上后续 RPC
/// 调用的晦涩失败码,丧失本函数提供的友好诊断价值。
///
/// 第 2 层刻意复用官方接口而非自行推断版本号:`WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE`
/// 是官方为「SDK 需升级」这一状态专设的标志位,自行比较版本号
/// 无法表达该语义。
///
/// # Errors
///
/// DLL 不可加载、或官方组件检查判定运行时未就绪时返回
/// [`WslcDomainError::SdkUpdateNeeded`]。
pub fn ensure_sdk_available() -> Result<(), WslcError> {
Self::ensure_dll_loadable()?;
// 官方接口同样需合法的 COM 上下文:本方法是多个创建路径的前置门禁
// (如 `SessionBuilder::build`、`WslcClientBuilder::build`),
// 那些路径会在其后自行初始化套间,此处不能假定调用方已就绪。
let _com_guard = crate::com::try_initialize_mta()?;
// DLL 可载后再问官方「还缺什么」。此处刻意不走
// get_missing_components(),因该方法自身以本方法的 DLL 守卫为
// 前置条件,直接复用会造成无限递归;故只复用其底层的官方调用。
let mut missing = 0u32;
// SAFETY: 入参为已初始化且存活期覆盖本次调用的本地缓冲区,
// 出参为合法的可写指针,不涉及未定义行为。
let hr = unsafe { WslcGetMissingComponents(&mut missing) };
WslcError::check_hr(hr, "检测缺失组件失败")?;
if missing & WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE != 0 {
return Err(WslcDomainError::SdkUpdateNeeded(format!(
"WSLC 运行时组件未就绪,缺失组件标志位: 0x{missing:08X};\
请先安装 WSL Containers 支持组件或升级 WSL"
))
.into());
}
Ok(())
}
/// 获取当前系统安装的 WSLC 运行时版本
///
/// # Errors
///
/// `wslcsdk.dll` 不可加载,或 COM 套间初始化失败时返回错误。
pub fn get_version() -> Result<WslcVersion, WslcError> {
// 只要求 DLL 可加载:版本查询本身正是判断运行时状态的手段之一,
// 若在此要求组件齐备,缺失组件的宿主将永远查不到版本号
Self::ensure_dll_loadable()?;
let _com_guard = crate::com::try_initialize_mta()?;
let mut ver = WslcVersion::default();
// SAFETY: 入参均为已初始化且存活期覆盖本次调用的本地缓冲区或官方句柄,
// 出参为合法的可写指针,不涉及未定义行为。
let hr = unsafe { WslcGetVersion(&mut ver) };
WslcError::check_hr(hr, "获取 WSLC 版本失败")?;
Ok(ver)
}
/// 检测当前宿主机缺失的组件依赖
///
/// # Errors
///
/// `wslcsdk.dll` 不可加载,或 COM 套间初始化失败时返回错误。
pub fn get_missing_components() -> Result<WslcComponentFlags, WslcError> {
// 同get_version:组件检查自身即用于判定就绪状态,
// 不可反过来要求运行时已就绪,否则缺失组件的宿主永远查不出结果
Self::ensure_dll_loadable()?;
let _com_guard = crate::com::try_initialize_mta()?;
let mut missing = 0u32;
// SAFETY: 入参均为已初始化且存活期覆盖本次调用的本地缓冲区或官方句柄,
// 出参为合法的可写指针,不涉及未定义行为。
let hr = unsafe { WslcGetMissingComponents(&mut missing) };
WslcError::check_hr(hr, "检测缺失组件失败")?;
Ok(missing)
}
/// 安装缺失组件与依赖
///
/// # 参数
/// - `components`: 需安装的组件标志位
/// - `options`: 安装选项 (如修复重装)
/// - `on_progress`: 可选进度回调函数: `Fn(组件, 当前步, 总步数)`
///
/// # Errors
/// 当 COM 上下文初始化失败、系统拒绝提权或安装底层组件报错时返回对应的领域错误。
pub fn install_with_dependencies<F>(
components: WslcComponentFlags,
options: WslcInstallOptions,
on_progress: Option<F>,
) -> Result<(), WslcError>
where
F: FnMut(WslcComponentFlags, u32, u32) + Send,
{
// 只要求 DLL 可加载:本方法的职责正是补齐缺失组件,
// 若要求运行时已就绪,则组件缺失的宿主永远无法调用它,
// 形成「必须先就绪才能修复不就绪」的死锁
Self::ensure_dll_loadable()?;
let _com_guard = crate::com::try_initialize_mta()?;
unsafe extern "system" fn progress_trampoline<F>(
component: WslcComponentFlags,
progress_steps: u32,
total_steps: u32,
context: *mut c_void,
) where
F: FnMut(WslcComponentFlags, u32, u32),
{
// SAFETY: context 由下方以 Box<Mutex<F>> 拆出并原样回传,
// 类型与拆出时严格一致。
unsafe {
crate::callback::invoke_callback::<F, _>(context, |callback| {
callback(component, progress_steps, total_steps);
});
}
}
let (cb, ctx) = match on_progress {
Some(f) => {
let boxed = Box::into_raw(Box::new(std::sync::Mutex::new(f)));
(Some(progress_trampoline::<F> as _), boxed as *mut c_void)
}
None => (None, std::ptr::null_mut()),
};
// SAFETY: 入参均为已初始化且存活期覆盖本次调用的本地缓冲区或官方句柄,
// 出参为合法的可写指针,不涉及未定义行为。
let hr = unsafe { WslcInstallWithDependencies(components, options, cb, ctx) };
if !ctx.is_null() {
// 与镜像拉取/推送进度的处理一致:在官方调用返回后立刻回收,
// 依赖「官方只在本次调用期间同步派发进度回调」这一未在官方文档
// 中载明的假设。组件安装耗时尤长,若官方改为后台线程异步派发,
// 此处即构成 use-after-free,须改为先注销回调再释放上下文的
// 二段式生命周期(参见 image.rs::progress_trampoline 的文档)。
//
// SAFETY: ctx 在本函数开头由 Box::into_raw 分配,调用结束立即安全回收
let _ = unsafe { Box::from_raw(ctx as *mut std::sync::Mutex<F>) };
}
WslcError::check_hr(hr, "安装依赖组件失败")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sdk_probe_never_panics() {
// 无论宿主机是否安装了 WSLC 运行时,探测接口都必须安全返回而非崩溃,
// 且失败时必须是明确的领域错误或 HRESULT 错误
match WslcSystem::get_version() {
Ok(version) => {
assert!(version.major > 0 || version.minor > 0 || version.revision > 0);
}
Err(WslcError::Domain(WslcDomainError::SdkUpdateNeeded(_))) => {}
Err(WslcError::Hresult(code, _)) => assert_ne!(code, 0),
Err(other) => panic!("预期返回 SdkUpdateNeeded 或 Hresult 错误,实际为: {other:?}"),
}
}
#[test]
fn test_missing_components_probe_never_panics() {
match WslcSystem::get_missing_components() {
Ok(flags) => {
// 返回值为官方组件标志位的组合,合法取值必须落在已知位掩码内
let known = WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM
| WSLC_COMPONENT_FLAG_WSL_PACKAGE
| WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE;
assert_eq!(flags & !known, 0, "返回了未定义的组件标志位: {flags}");
}
Err(WslcError::Domain(WslcDomainError::SdkUpdateNeeded(_))) => {}
Err(WslcError::Hresult(code, _)) => assert_ne!(code, 0),
Err(other) => panic!("预期返回 SdkUpdateNeeded 或 Hresult 错误,实际为: {other:?}"),
}
}
/// 就绪判定必须严格强于 DLL 探测,且两者在 DLL 缺失时保持一致
///
/// 原用例名为 `test_ensure_sdk_available_agrees_with_probe`,断言两者
/// 结果**完全相等**。该断言本身即缺陷的固化:`ensure_sdk_available`
/// 当时只是 `is_sdk_available` 的同义包装,二者同源故自然相等——
/// 一旦就绪判定按修复方案增强(叠加官方组件检查),二者即不再等价。
///
/// 现断言的正确不变量是**单向蕴含**:DLL 不可加载时必然不就绪;
/// 而 DLL 可加载时既可能就绪、也可能因组件缺失而不就绪。
#[test]
fn test_readiness_is_stricter_than_dll_probe() {
let dll_loadable = WslcSystem::is_sdk_available();
let ensured = WslcSystem::ensure_sdk_available();
if !dll_loadable {
// DLL 都载不进来,必然不具备运行条件
assert!(
ensured.is_err(),
"DLL 不可加载时 ensure_sdk_available 必须返回错误,实际为 Ok"
);
assert!(
matches!(
ensured,
Err(WslcError::Domain(WslcDomainError::SdkUpdateNeeded(_)))
),
"DLL 缺失应归入 SdkUpdateNeeded 领域错误,实际为: {ensured:?}"
);
} else {
// DLL 可加载:就绪与否取决于官方组件检查,两种结果都属合法,
// 但若判定为不就绪,错误必须来自组件层而非其他基础设施
if let Err(e) = &ensured {
assert!(
matches!(e, WslcError::Domain(WslcDomainError::SdkUpdateNeeded(_))),
"DLL 可加载时的失败只应源于组件未就绪,实际为: {e:?}"
);
}
}
}
}