在 LetRecovery 的底层边界上开发:从兼容性到可恢复性的完整回顾
LetRecovery 最早看起来像一个“把 Windows 镜像写到另一个分区”的工具,真正开发之后才发现,它更接近一个小型操作系统交接系统:正常 Windows 负责收集意图和准备环境,WinPE 负责在旧系统已经离线后完成磁盘、镜像、驱动、引导和密钥处理,重启后的新系统还要继续完成账户、个人文件和清理收尾。
因此最难的工作从来不是接一个按钮、调用一个命令或画一个进度条,而是让这些步骤在不同 Windows 版本、不同磁盘布局、不同固件模式和不同失败时刻下仍然可解释。本文回顾 LetRecovery 早期和近期开发中最值得保留的经验:哪些 API 边界最容易被误用,哪些兼容性问题只能靠真实环境暴露,哪些看似更严格的检查反而会降低成功率,以及如何把这些经验沉淀成 Rust 类型、状态机、日志和可丢弃 VM 证据。
1. 从“能运行”到“能交接”的架构演进
LetRecovery 由三个层次组成:lr-core 共享核心、正常系统端和 PE 端。早期代码里,两端各自拥有相似的命令构建、路径校验和 Windows 适配;这种复制在功能少的时候很快,但当分区、BitLocker、日志和首登逻辑开始跨重启时,两个副本会逐渐出现不同的语义。
后来我把纯逻辑、数据契约和可测试的命令边界集中到 lr-core,两端只保留环境适配。这个调整的价值不只是复用代码,而是让危险动作先经过一个可以用普通临时目录、mock 和 DryRunCommandExecutor 验证的层。
pub struct CommandRequest {
pub program: String,
pub args: Vec<String>,
pub current_dir: Option<PathBuf>,
}
pub trait CommandExecutor {
fn run(&self, request: &CommandRequest) -> anyhow::Result<CommandOutput>;
}
pub fn build_dism_add_driver(inf: &Path, target: &Path) -> anyhow::Result<CommandRequest> {
let inf = checked_regular_path(inf)?;
let target = checked_directory_path(target)?;
Ok(CommandRequest {
program: "dism.exe".to_string(),
args: vec![
"/English".into(),
"/Image:".to_string() + target.to_str().unwrap(),
"/Add-Driver".into(),
"/Driver:".to_string() + inf.to_str().unwrap(),
],
current_dir: None,
})
}CommandExecutor 让“命令怎么构建”和“命令是否真的执行”分离。测试可以验证参数顺序、路径边界和退出码语义,生产端再把同一个请求交给真正的进程执行器。这个分层也阻止了旧的 DiskPart 脚本、PowerShell 文本解析和本地化命令输出重新进入承重路径。
2. Windows 7 兼容性不是一个编译参数
LetRecovery 的正常端需要覆盖 Windows 7,而 PE 端是另一套普通 Windows 目标。早期最容易犯的错,是在现代机器上直接执行普通 cargo build --release,再把生成的 EXE 当成 Win7 版本发布。编译目标、CRT、链接器导入表和构建日期都会影响实际兼容性。
现在正常端发布构建统一使用固定的 Rust 版本、x86_64-win7-windows-msvc、静态 CRT、rust-src 和 -Z build-std=std,panic_abort,返回成功前还会扫描导入表;PE 端继续使用普通 x86_64-pc-windows-msvc 单独构建。
$target = 'x86_64-win7-windows-msvc'
$env:RUSTFLAGS = '-C target-feature=+crt-static'
cargo +1.88.0 build `
-p letrecovery `
--release `
--locked `
--target $target `
-Z build-std=std,panic_abort
& .\.github\scripts\verify-win7-imports.ps1 `
-Executable ".\target\$target\release\LetRecovery.exe"
if ($LASTEXITCODE -ne 0) {
throw 'Win7 import boundary verification failed'
}真正的兼容性还包括 API 可用性、清单、资源、语言文件、默认配置和外部运行库。一个只在编译机上启动成功的二进制,不能证明它能在 Win7、精简 WinPE 或没有 WLAN 服务的环境中完成核心任务。
3. 磁盘和分区:provider 的事实高于经验布局
磁盘代码是整个项目里最容易把“经验”误当成“契约”的部分。真实 provider 返回的 extent 可能不是整 MiB,分区起点也可能因为固件、克隆、历史分区操作或虚拟磁盘几何而变化。把 offset % 1MiB == 0 当作合法性条件,只会把正常设备变成误报。
正确的检查集中在范围安全:非空、checked overflow、完全包含于授权 envelope、不能覆盖受保护区域,操作后必须按 canonical 布局回读。
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Extent {
pub offset: u64,
pub length: u64,
}
impl Extent {
pub fn end(self) -> Option<u64> {
self.offset.checked_add(self.length)
}
pub fn contains(self, child: Extent) -> bool {
let Some(parent_end) = self.end() else { return false };
let Some(child_end) = child.end() else { return false };
child.length > 0
&& child.offset >= self.offset
&& child_end <= parent_end
}
}CreatePartitionEx 成功启动异步操作后,Wait、Refresh 或首次布局回读失败,都不能证明分区没有创建。恢复流程必须重新读取当前布局,寻找唯一新增、角色正确、容量足够且完全位于本次授权 envelope 内的分区。找不到、找到多个、范围越界或回读失败,都应该报告 partial state 并保留现场,不能按请求 offset 猜测删除。
缩卷也遵循同一原则。Shrink 返回共享错误后,正常端必须重新读取同一当前卷,确认磁盘和起点没有变化,只能按实际观察到的尾部缩小量扩回。范围未变时不操作,查询失败或范围增长时严禁按请求值盲目扩容。
请求:shrink C: 释放 10,117,709,824 bytes
回读:offset=78,778,449,408 length=68,659,707,904
恢复:只按实际 delta=10,117,709,824 bytes 扩回这类设计看起来比“先对齐、再比较、最后写入”更朴素,却能覆盖更多真实设备,因为它尊重 Windows provider 已经返回的当前事实。
4. VDS、IOCTL 和句柄生命周期
Windows 存储 API 的难点常常不在函数名,而在参数单位、success warning、对象生命周期和操作后的权威回读。例如 IVdsCreatePartitionEx::CreatePartitionEx 的 ulAlign 是字节数,0 表示让 provider 决定;把 1 当成“启用默认对齐”会在某些设备上产生错误几何。
格式化入口必须从实际 IVdsVolume 对象核对 marker 当前匹配的卷 extent。成功后再回读 extent、文件系统和卷标。这个确认完成后,镜像释放、驱动、更新、PCA、引导和无人值守步骤不应再次用易变库存字段重复认证同一目标。
原始磁盘移动则更严格:首次写入前,要在同一个已打开的 PhysicalDrive 写句柄上复核 layout、capacity 和 StorageIdAssocDevice 身份;关闭句柄后重新打开,也必须用移动前的强卷身份和规范快照复核,不能继续相信可能已经复用的磁盘号。
pub struct OpenPhysicalDrive {
handle: OwnedHandle,
layout: CanonicalLayout,
capacity: u64,
storage_id: Option<Vec<u8>>,
}
impl OpenPhysicalDrive {
pub fn verify_before_write(&self, current: &CanonicalLayout) -> Result<()> {
ensure!(self.layout == *current, "disk layout changed before raw write");
ensure!(self.capacity == current.capacity, "disk capacity changed");
Ok(())
}
}句柄不是实现细节。一个 volume 对象、一个 IOCTL 回读和一次写操作之间如果混入重新枚举,磁盘号就可能指向另一个对象;把句柄生命周期和回读关系写进类型或状态结构,才能让调用图保持可审计。
5. 自动暂存分区:小事务里包含完整的交接哲学
当目标磁盘只有一个可用系统数据分区时,正常端要先缩出暂存空间,再把安装任务交给 PE。这个过程同时涉及 shrink、create、marker、盘符、跨重启认证和回收扩容,是最容易出现“每一步单看都成功,合在一起却无法恢复”的流程。
暂存 marker 不能只记录盘符。正常端必须把当前 SessionId、目标完整 extent、暂存完整 extent、规范目标布局摘要和授权 envelope 原子写入;PE 删除前要重新读取 marker,并用当前实时身份核对。旧版、缺字段、重复字段、异会话 marker 或范围变化,都应该保留分区并记录 warning。
{
"schema": 3,
"session_id": "8b1f...",
"target_extent": { "offset": 78223360000, "length": 68659707904 },
"staging_extent": { "offset": 146883174400, "length": 10117709824 },
"authorized_envelope": { "offset": 146883174400, "length": 10117709824 }
}删除成功后不能再按旧盘符清理路径。Mount Manager 可能立刻把同一个盘符分配给无关卷;之后只允许清理仍然通过当前目标身份绑定的 marker。这种“删除之后不相信盘符”的细节,是长期调试后留下的非常重要的经验。
6. 驱动预算与启动路径证明:部署工具只是执行边界
这里真正困难的不是调用部署工具,而是让驱动预算在选盘、暂存创建、导出物化和阶段回读之间保持同一语义。查询结果只能形成候选预算,权威事实仍来自实际文件树、启动存储拓扑和操作后回读。驱动导出的 Driver Store 视图可能小于 /Online /Export-Driver 物化后的普通文件树,把两者差值当成损坏会误报容量不足。当前做法是先用只读库存形成预算,真正导出后再用普通文件树的逻辑大小替换预算中的驱动项,并立即用 GetDiskFreeSpaceExW 回读 caller 可用空间。
预算也必须贯穿选择、创建和阶段回读:镜像全部 span、XP 文件树、在线 OEM 驱动、PCA、版本化用户驱动和 UefiSeven 文件大小,最后只加一次固定 2 GiB 操作余量。不能用磁盘总容量比例、固定 8–12 GiB 或重复叠加“安全保留”。
普通保存驱动的恢复流程也经历过重构。早期很容易想给每个 INF 叠加 CAT、WinVerifyTrust 和 SetupAPI 预验签,但精简 WinPE 里信任服务缺失会把整棵合法驱动树判成不可信。现在先做结构安全检查,再让 DISM 导入;批量失败时逐个隔离精确 INF,只有仍有包失败才读取目标镜像库存确认真实启动路径覆盖。打印机、虚拟机、网卡和 WLAN 等非启动存储驱动失败只能进入有界 warning。
结构安全检查
↓
DISM 目录导入
↓(批量失败)
逐个精确 INF 隔离
↓(仍有失败)
DismGetDrivers / dism.exe /Get-DriverInfo
↓
只对真实启动存储要求做承重判断这个流程的核心是不要用独立启发式替代微软 API 的真实结果。DISM 成功导入一个精确 INF 后,应该接受这个权威结果,而不是再用另一套容易误报的签名链把它否决。
7. API 不可用时:能力降级,而不是语义降级
VDS、Storage Management、部分 SetupAPI 或精简 WinPE 中的管理接口并不保证存在。真正难处理的不是“找不到 DLL 就换一条命令”,而是先判断缺失的 API 属于哪一层事实:枚举能力、诊断能力,还是承重写入能力。能力探测结果必须进入显式 capability matrix,不能在调用失败后把未知状态强行映射成成功。
pub enum Probe { Available, Unsupported, Failed(HResult) }
pub struct CapabilityMatrix {
pub canonical_layout: Probe,
pub vds_mutation: Probe,
pub ioctl_identity: Probe,
pub volume_format: Probe,
}回退顺序按权威性固定:优先使用同一对象句柄上的 VDS 属性和 IOCTL 回读;VDS 枚举不可用时,若已经持有并验证了 volume/physical-drive 句柄,则只允许使用 IOCTL 获取 extent、容量和卷身份;对象句柄也无法建立时,只能返回 Unknown 并保留现场。不能因为 Get-Volume、盘符存在、PowerShell provider 或磁盘号看起来正常,就把它们提升为写盘授权。
VDS object + post-readback
↓ unavailable
validated handle + IOCTL extent/capacity
↓ unavailable
read-only diagnostic snapshot
↓
Unknown / preserve state回退有三个硬边界:查询可以降级,写入不能伪造;管理 API 不可用不等于自动切换 DiskPart;可选属性缺失只能在不影响 canonical extent 和目标身份时记录 Unsupported。句柄、extent、容量或后置布局回读缺失,则是承重事实缺失,必须进入 partial/failed 状态。
pub fn reconcile_without_vds(
handle: &ValidatedHandle,
authorization: &AuthorizationEnvelope,
original_error: ApiError,
) -> Result<Reconciliation> {
let observed = ioctl_read_canonical_extent(handle)
.map_err(|e| Reconciliation::partial(original_error, e))?;
if !authorization.contains(observed.extent) {
return Ok(Reconciliation::partial(
original_error,
ApiError::UnauthorizedExtent,
));
}
Ok(Reconciliation::observed(original_error, observed))
}管理 API 失败后的结果收束仍保存原始 HRESULT/Win32 错误,重新读取当前布局,比较实际 delta,并把恢复结果和原始失败同时写入阶段证据。不能用备用 API 返回的盘符覆盖前一个错误,也不能用请求 offset、历史 GUID 或估算容量补全缺失事实。最终报告必须区分 unsupported capability、query failed、mutation failed 和 partial state。
7. BitLocker:受认证载荷与句柄生命周期
BitLocker 是正常端和 PE 端之间最敏感的一条链。ViaPE 安装和备份需要在正常系统收集当前 Windows 已持有的 48 位 RecoveryPassword,再通过受认证的私有启动 WIM 载荷交给 PE。PE 使用共享 FVEAPI 打开卷并用恢复密钥解锁,不能依赖 WinPE 里可能不存在的 manage-bde.exe。
这里最容易出现的错误是把“解锁”误写成“解密”:调用 manage-bde -off、删除保护器或暂停保护都会改变用户的数据保护状态,远远超出安装工具的职责。
pub fn unlock_for_install(api: &FveApi, volume: &Path, key: &str) -> Result<()> {
let handle = api.open_volume(volume)?;
handle.unlock_with_recovery_key(key)
.context("unlock BitLocker volume for the current operation")?;
// 只保留当前句柄生命周期内的解锁状态,不修改保护器和加密策略。
Ok(())
}没有密钥时,既有 best-effort 语义可以记录有界 warning,但不能因为所谓“安全”删除透传能力;有密钥时也不能把它写入公开数据分区、普通日志或命令行参数。密钥只存在于受认证的私有载荷和短生命周期内存中。
8. 驱动和控制器:分类名不是拓扑证明
VMD、HDC、SCSIAdapter 这些设备类名只能帮助定位问题,不能单独证明某个驱动是启动必需的。真正承重的存储驱动要求,要由源系统卷的 VOLUME_DISK_EXTENTS 对应磁盘 devnode 当前 ConfigMgr 父链证明,并且设备类属于存储控制器。
随包固定的 VMD 资源继续使用锁定清单、固定哈希和确定签名者验证;用户导出的普通 OEM INF 则走 DISM 目录导入和硬件 ID 完整等值匹配。不能把供应链固定资源的严格策略泛化到所有用户驱动,也不能因为 WinPE 缺少某项验证服务就把普通驱动树全部判成不可信。
pub fn hardware_id_covers_device(
device_ids: &[String],
candidate_ids: &[String],
) -> bool {
device_ids.iter().any(|source| {
candidate_ids.iter().any(|candidate| {
source.eq_ignore_ascii_case(candidate)
})
})
}完整 Hardware ID 或 Compatible ID 的不区分大小写等值匹配,才足以证明覆盖。截断成 VEN/DEV、前缀、后缀或只比较 SUBSYS 都会把不相关硬件误认为可启动覆盖。
9. WLAN 动态加载:可选能力不能拖垮核心安装
WePE 可能缺少 wlanapi.dll、某些导出或 WLAN 服务。正常端的
ative_wifi` 因此不能静态导入 WLAN API,而应该动态加载系统目录中的 DLL 和六个需要的导出。模块生命周期必须覆盖 WLAN 分配内存的使用期,先释放 profile、关闭句柄,再卸载模块。
let wlan = match WlanApi::load_system() {
Ok(api) => Some(api),
Err(error) => {
log::warn!("Wi-Fi migration disabled: {error:#}");
None
}
};
if let Some(api) = wlan.as_ref() {
let profiles = api.capture_profiles()?
.into_iter()
.filter(|profile| profile.is_current_session())
.collect::<Vec<_>>();
stage_profiles(profiles)?;
}Wi-Fi 迁移失败不应该让镜像释放、启动存储、引导和最小系统配置失败。这个错误分级原则也适用于打印机、虚拟机增强、可选语言包和非承重更新:记录有界 warning,继续得到可启动系统。
10. PCA、UefiSeven 和 Windows 7 引导
启动修复是另一个不能只看退出码的边界。BCD、BCDBoot、Bootsect 和 XP/2003 引导写入都属于承重步骤:进程启动、非零退出、关键文件缺失、活动分区写入或结果文件回读任一步失败,都必须停止。
Windows 7 x64 UEFI 还涉及 PCA2011/PCA2023、VMware 原生 Microsoft EFI 和 UefiSeven。经过多轮兼容处理后,逻辑变成:先根据 CPUID、SMBIOS 和 SetupAPI 只读识别 VMware;确认 VMware 后恢复微软原生 bootmgfw.efi 和 bootx64.efi 双入口;其它环境才使用锁定的 UefiSeven,并事务化保留两个原始 EFI 文件。
环境探测
├─ 明确 VMware → BCDBoot + Microsoft x64 EFI 双入口
├─ 明确其它环境 → UefiSeven 双入口事务部署
└─ Unknown → 不猜测,按安全路径停止或交给用户选择Unknown 不能被猜成 VMware,也不能因为固件通常会遵循 NVRAM 中的 Windows Boot Manager,就省略 EFI\Boot\bootx64.efi。固件行为的差异必须由真实文件和回读结果收束。
11. 个人文件和内置管理员:两次登录之间的状态机
个人文件保留不是简单复制 Users 目录。它要处理 Known Folder、Public 文件、快捷方式目标、未知顶层数据、desktop.ini、用户 SID、profile 删除和首登时机。FirstLogonCommands 启动早于桌面稳定,不能承担任意长度的恢复;现代 Windows 还会异步启动这些命令。
因此首登阶段只负责捕获精确 SID、准备一次性任务和完成账户状态切换,真正的资料恢复在第二次登录由受认证的高权限任务执行。进度 Shell 由原生 helper 创建真实顶层窗口,不依赖 cmd.exe、conhost.exe 或“进程存在”来假设用户看到了界面。
第一次登录
→ 证明临时账户 token 和 profile
→ 捕获 SID
→ 必要时准备 RID-500 改名和 LSA secret
→ 注册精确 SID 的一次性任务
→ 持久化回读后强制内部重启
第二次登录
→ 证明当前 token 是目标账户
→ 恢复个人目录和快捷方式
→ 删除临时账户与 profile
→ 恢复原 Shell
→ 等待稳定 GetShellWindow
→ 删除剩余 staging 和 launcher内置 Administrator 模式尤其不能把密码放进 Winlogon\DefaultPassword,因为 Windows 会在第一次登录后消费并删除它。当前做法是由 SYSTEM helper 使用 LsaStorePrivateData 保存本地加密 secret,第二次登录取回并在成功后退役 AutoLogon、LSA secret 和 marker。
12. 日志交接:每个阶段都要留下可读证据
跨重启安装最难调试的 bug,往往不是代码没有写日志,而是日志写到了即将被清理的卷,或者正常端快照还没有真正落盘,PE 就已经开始格式化。
当前日志流程分成几个阶段:正常端完成 flush barrier 后,把脱敏、限长、哈希绑定的日志写成内容寻址 blob;manifest 最后原子替换;PE 在清理数据分区前把快照复制到 RAM 盘;镜像或 XP 文件树一旦可用,就向新系统固定入口发布第一个检查点;最终终态再生成合并日志。
pub fn stage_desktop_log_contents(
contents: &[u8],
data_root: &Path,
session_id: &str,
) -> Result<DesktopLogManifest> {
let digest = sha256_hex(contents);
let dir = session_handoff_directory(data_root, session_id)?;
let blob = dir.join(format!("normal-{digest}.log"));
write_new_regular_file(&blob, contents)?;
let manifest = DesktopLogManifest {
schema: 2,
session_id: session_id.to_owned(),
blob_file: blob.file_name().unwrap().to_string_lossy().into_owned(),
sha256: digest,
bytes: contents.len() as u64,
};
atomic_replace_manifest(&dir.join("desktop-manifest.json"), &manifest)?;
Ok(manifest)
}如果 manifest 丢失但目录中只有一个文件名带完整 SHA-256 的 blob,读取端可以重新验证并恢复;普通同名日志、异会话文件和多个候选都必须拒绝。日志中继失败只记录 warning,不能覆盖真正的安装错误或阻止已经安全安排的重启。
13. 发布包:构建过程也必须可审计
发布不是把一个 EXE 压缩起来。最终 pkg 需要包含正常端程序、PE WIM、DISM 库、锁定驱动、PCA 资源、语言文件、配置和运行库,同时删除旧日志、开发目录、调试中间文件和 reparse point。
配置不能从维护机的 config.json 直接复制。发布脚本必须根据确定性默认值重建配置,保留本次构建产生的 pe_cache 元数据,明确保持日志开启、简易模式提示未关闭、高级选项关闭、自动化导出关闭和 PE 维护入口关闭。压缩后还要重新解出 config.json,逐字节比较已审计的 pkg/config.json。
& .\.github\scripts\assert-release-tree-clean.ps1 -Root .\pkg
if ($LASTEXITCODE -ne 0) { throw 'release tree audit failed' }
& 7z a -t7z -mx=9 -m0=lzma2 -ms=on .\LetRecovery.7z .\pkg\*
if ($LASTEXITCODE -ne 0) { throw 'archive creation failed' }
& 7z t .\LetRecovery.7z
if ($LASTEXITCODE -ne 0) { throw 'archive integrity test failed' }14. Hyper-V 回归:测试宿主也会失败
LetRecovery 的安装矩阵不是“启动 VM,然后看它有没有关机”。宿主要绑定唯一 RunId、检查点和证据 VHD;来宾通过启动脚本写入 install plan、阶段日志、账户和离线布局;证据采集必须放在短生命周期 child 中,避免只读挂载 VHD 和 Storage CIM 对象把宿主内存推到不可控。
宿主阶段至少要发布 PID、工作集、private bytes、句柄、线程、阶段名和 UTC。即使来宾已经关机,也要等证据盘卸载、validation 原子落盘、检查点恢复、DVD 和网络恢复、VHD 删除全部通过后,才允许发布成功。
initialized
→ baseline_restored
→ guest_started
→ guest_stopped
→ evidence_collection_started
→ evidence_collected
→ validation_written
→ cleanup_started
→ checkpoint_restored
→ evidence_vhd_deleted
→ completed一次回归曾因为传入的是 LetRecovery 产品 ISO,来宾没有找到 sources\install.wim;另一次因为 26100 ISO 的实际版次是 Professional,夹具却期待 ProfessionalCountrySpecific。这些都不是产品磁盘逻辑本身的失败,却说明测试介质和夹具契约同样需要在启动前验证。测试框架必须把介质错误、宿主错误和产品错误区分开,否则一条错误日志会把整个系统带偏。
15. 最有价值的测试不是理想输入
最有价值的回归专门覆盖合法但不整齐的环境:非整 MiB 的 offset 和 length、provider 返回值与请求略有差异、旧会话 marker、同名异内容文件、异步 Wait 失败但布局已经变化、可选驱动失败、没有 WLAN 服务、没有 DismApi.dll、缺少可选语言包,以及破坏性边界之后的部分失败。
| 场景 | 应观察到的行为 | 证明的边界 |
|---|---|---|
| provider 返回非整 MiB extent | 接受真实 extent | 没有经验几何门禁 |
| CreatePartition Wait 失败但布局出现唯一 delta | 按实际 extent 精确收束 | 没有按请求值猜测删除 |
| 旧 SessionId 的 marker 同名存在 | 静默忽略 | 身份只绑定当前会话 |
| WLAN DLL 或服务缺失 | 禁用迁移,安装继续 | 可选能力不拖垮核心结果 |
| 普通 OEM INF 导入成功 | 立即接受权威 DISM 结果 | 没有重复验签误报 |
| 正常端日志缺失、PE 日志有效 | 发布 PE 日志并保留占位 | 单端缺失不遮挡另一端 |
| 目标已格式化后可选更新失败 | 新系统继续收束并报告 warning | 破坏后不扩大失败 |
| VM 验证 child 未退出 | 先确认结果已原子落盘再回收 | 不凭内存阈值杀掉活跃测试 |
Rust 的类型、PowerShell 的结构化 JSON、Win32 的权威回读和 VM 的证据目录,最终都服务于同一个目标:让失败也成为可以解释的状态,而不是一堆互相矛盾的猜测。
16. 技术含量来自不变量,而不是术语密度
LetRecovery 最难的部分不是把 API 名称堆在一起,而是把 Windows 的非原子现实压缩成可审计不变量:跨重启只认本次随机 session;磁盘写入只认当前句柄和 canonical layout;异步操作只认后置回读;BitLocker 只透传短生命周期恢复能力;启动存储只认拓扑证明;首登只认精确 SID 和持久化状态;Hyper-V 只认完整证据链;发布只认归一化、重建和树审计后的资产。
如果一个检查无法回答“它证明了哪个真实威胁、误报如何恢复、后置回读为何不能替代”,它就不应该进入承重路径。真正高级的系统工程,是让每个不可逆动作都拥有可验证的前置授权、可观察的后置事实和明确的 partial-state 解释。
最后的感悟
LetRecovery 让我重新理解了“简单”。简单不是少写几行代码,而是拒绝把易变的磁盘号、容量、GUID、历史长度和整盘摘要叠加成一条脆弱的身份链;拒绝把查询预估当成真实写入能力;拒绝把可选组件失败扩大成不可启动;拒绝把“进程存在”当成用户看见了窗口;也拒绝把“命令退出码为 0”当成所有后置条件都成立。
真正可靠的系统工具通常有几个共同点:输入身份短而强,写入边界明确,操作后回读权威状态,跨重启只认本次会话 marker,错误根据实际影响分级,日志在每个阶段都留下可验证证据,测试覆盖合法的非典型环境。Rust 让这些约束可以被表达成类型和纯函数,Windows API 提供真实执行边界,而最终决定质量的,仍然是对失败路径的尊重。