From e1ea5ac7944145fd73207c53a397002927cebd94 Mon Sep 17 00:00:00 2001 From: Daman Mulye Date: Mon, 21 Sep 2026 11:06:26 -0700 Subject: [PATCH 01/10] Initial work --- .pipelines/wsl-build-pr.yml | 1 + CMakeLists.txt | 11 + WSL-openvmm.md | 281 ++++++++ msipackage/CMakeLists.txt | 2 +- msipackage/package.wix.in | 3 +- packages.config | 1 + src/windows/service/exe/CMakeLists.txt | 11 + .../service/exe/IVirtualMachineBackend.h | 494 ++++++++++++++ .../exe/OpenVmmVirtualMachineBackend.cpp | 600 ++++++++++++++++++ .../exe/OpenVmmVirtualMachineBackend.h | 94 +++ .../service/exe/VirtualMachineBackend.cpp | 45 ++ test/windows/CMakeLists.txt | 11 + .../OpenVmmVirtualMachineBackendTests.cpp | 234 +++++++ 13 files changed, 1786 insertions(+), 2 deletions(-) create mode 100644 WSL-openvmm.md create mode 100644 src/windows/service/exe/IVirtualMachineBackend.h create mode 100644 src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp create mode 100644 src/windows/service/exe/OpenVmmVirtualMachineBackend.h create mode 100644 src/windows/service/exe/VirtualMachineBackend.cpp create mode 100644 test/windows/OpenVmmVirtualMachineBackendTests.cpp diff --git a/.pipelines/wsl-build-pr.yml b/.pipelines/wsl-build-pr.yml index 4a7e2d9128..1dcdaf701b 100644 --- a/.pipelines/wsl-build-pr.yml +++ b/.pipelines/wsl-build-pr.yml @@ -3,6 +3,7 @@ trigger: include: - master - release/* + - feature/* stages: - template: build-stage.yml@self diff --git a/CMakeLists.txt b/CMakeLists.txt index fc28169f95..2e48c4daba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ find_nuget_package(Microsoft.WSL.TestData WSL_TEST_DATA /) find_nuget_package(Microsoft.WSLg WSLG /build/native/bin) find_nuget_package(vswhere VSWHERE /tools) find_nuget_package(Wix WIX /tools/net6.0/any) +find_nuget_package(Microsoft.WSL.OpenVMM WSL_OPENVMM /build/native) # Architecture-specific nuget packages from the OS repo. if (${TARGET_PLATFORM} STREQUAL "x64") @@ -276,6 +277,16 @@ if (NOT WSL_DEVICEHOST_BIN) file(CREATE_LINK ${WSL_DEVICE_HOST_SOURCE_DIR}/bin/${TARGET_PLATFORM}/wsldevicehost.pdb ${BIN}/wsldevicehost.pdb) endif() +foreach(binary openvmm.exe openvmm.pdb wslopenvmm.dll wslopenvmm.pdb) + file(CREATE_LINK "${WSL_OPENVMM_SOURCE_DIR}/bin/${TARGET_PLATFORM}/${binary}" "${BIN}/${binary}") +endforeach() + +add_library(wslopenvmm_client SHARED IMPORTED GLOBAL) +set_target_properties(wslopenvmm_client PROPERTIES + IMPORTED_IMPLIB "${WSL_OPENVMM_SOURCE_DIR}/lib/${TARGET_PLATFORM}/wslopenvmm.dll.lib" + IMPORTED_LOCATION "${WSL_OPENVMM_SOURCE_DIR}/bin/${TARGET_PLATFORM}/wslopenvmm.dll" + INTERFACE_INCLUDE_DIRECTORIES "${WSL_OPENVMM_SOURCE_DIR}/include") + if (${SKIP_PACKAGE_SIGNING}) set(PACKAGE_SIGN_COMMAND echo Skipped package signing for:) else() diff --git a/WSL-openvmm.md b/WSL-openvmm.md new file mode 100644 index 0000000000..3b070d83ee --- /dev/null +++ b/WSL-openvmm.md @@ -0,0 +1,281 @@ +# OpenVMM WSL implementation tracker + +## Current-stack assessment (2026-09-14) + +This assessment compares each local branch with the branch below it, starting at `master` (`4bfbacae`). The original audit covered backend tip `13daf737`; the snapshot below includes the local rebase, committed PR 3 and PR 5 follow-ups, and uncommitted PR 6 RPC work. The PR numbers below are proposed work packages, not existing GitHub PR numbers. + +| Layer | Actual branch and tip | Work present | +|---|---|---| +| refactor | `user/damanmmulye/wsl-openvmm-refactor` at `b956db18` | `IWslCoreVm`, HCS implementation adaptation, session/interface plumbing, guest connection entry point, accepted ownership comments, and lifecycle regression. | +| rpc | `user/damanmulye/wsl-openvmm-rpc` at `dd6dc8ed` plus uncommitted C2 follow-up | Rust DLL/FFI, VM/resource RPCs, bounded AF_UNIX/gRPC calls, fail-closed recovery, cancellation, error categories, and transport regressions. | +| backend | `user/damanmmulye/wsl-openvmm-backend` at `b8b146cf` | WSL backend selection, process/VM lifecycle, guest transport wiring, VirtioFS, initial networking, console logging, private RPC socket and gated packaging; accepted selection/rollback policy and selection regressions. | + +**Legend:** `[x] Implemented` means the scoped WSL implementation is present in source, not that it has been built, run, merged, or approved for release. `[ ] Partial` means useful work exists but the bullet still has a gap or an unresolved design deviation. `[ ] TODO` means the requested outcome is not evidenced by this stack. `[ ] External` means completion must be established outside this WSL stack; it does not mean work in OpenVMM or offline design discussions has not happened. + +**Scope totals:** 14 implemented, 10 partial, 19 TODO, 7 external (50 unique bullets). Split validation bullets are counted once; G4 is partial overall because coverage is limited to selected mock/transport and early configuration-failure cases. The implemented bullets are **A3, B1, B2, B3, B4, B5, B6, B7, C1, C2, C5, C7, D1, and D4**. These totals include the accepted PR 3 and PR 5 decisions and the PR 6 RPC-layer closeout for C2/C7; backend follow-ups remain explicitly tracked below. + +The stack does not add WSLC backend call-site integration: the separate WSLC prototype from the earlier summary is not credited as completed work here. PR 3 adds a Windows lifecycle regression; PR 5 adds selection regressions and policy documentation; the uncommitted PR 6 follow-up expands the Rust transport coverage. End-to-end results, baselines, and rollout decisions cannot be inferred from a commit named "boot successful". + +**Important differences from the original plan:** + +- **Accepted PR 3/PR 5 design:** `IWslCoreVm` is the service-facing backend contract. `WslCoreVm` remains the HCS implementation; OpenVMM is a sibling implementation, not a backend underneath a shared `WslCoreVm` facade. This explicitly replaces the original lower-level extraction in A3/B1/B2, rather than claiming that extraction happened. The dedicated factory was removed by `2caf8dba`; `LxssUserSessionImpl::_CreateVm()` is accepted as B3's centralized creation/selection point. A2's full lifecycle contract remains separate follow-up work. +- **Accepted PR 5 policy:** HCS is the default; explicit OpenVMM opt-in fails rather than silently falling back when unavailable or when initialization fails. Rollback is manual: disable the setting and shut down WSL. A live VM retains its recorded backend until shutdown. +- **Accepted PR 6 contract:** Keep gRPC over AF_UNIX, not ttrpc. Replace transparent reconciliation with fail-closed recovery after uncertain mutations; teardown and fresh-process recreation are required. +- **C2/C7 closeout:** Close these bullets for the accepted RPC-layer scope: bounded, fail-closed RPC behavior and ordinary debugger-output tracing macros. Backend cancellation/lifetime integration, recreation-path coverage, and broader service/process diagnostics remain follow-ups, not claims of completed end-to-end integration. +- Mixed admin/non-admin access is **not implemented**: `InitializeDrvFs` and `AddVirtioFsShare` reject elevation different from the VM creator. Pass-through disks are also explicitly unsupported. +- Memory remains capped at 4 GiB. GUI/GPU, debug shell, and DNS tunneling are disabled in this backend configuration; pmem and virtio-rng configuration are not wired through the new RPC builder. +- Console/dmesg capture is implemented, but it is not kernel-panic extraction or a saved-state/crash-artifact collection pipeline. + +### Source evidence + +S1-S10 paths and line numbers refer to the original audited tips; S11-S13 identify committed follow-ups; S14 identifies uncommitted RPC diagnostics. Source IDs identify implementation evidence, not successful runtime results. + +| ID | Evidence | +|---|---| +| S1 | `src\windows\service\exe\IWslCoreVm.h:8-88`; `src\windows\service\exe\WslCoreVm.h:43` (`WslCoreVm : IWslCoreVm`). Refactor commit `2caf8dba` removes the dedicated factory. | +| S2 | `src\windows\service\exe\LxssUserSession.cpp:2999-3028` (inline selection and failure cleanup), `:2211-2239` (backend-specific force termination); `src\windows\common\WslCoreConfig.h:298,388` (opt-in key/default); `CMakeLists.txt:44` (build gate defaults off). | +| S3 | `src\shared\inc\SocketChannel.h:620-749` (AF_UNIX I/O); `src\windows\service\exe\LxssCreateProcess.h:54,76-111`; `src\windows\service\exe\WslCoreInstance.cpp:38-50,244-247,439-442,550-580`; `src\windows\service\exe\OpenVmmWslCoreVm.cpp:38-101` (guest bridge). | +| S4 | `src\windows\wslopenvmm\src\af_unix.rs:14-45` (connect retries/timeouts); `src\windows\wslopenvmm\src\client.rs:43-79,363-383` (Tonic client, deadlines, HRESULT mapping); `src\windows\service\exe\OpenVmmWslCoreVm.cpp:459-462` (`transport=grpc`). | +| S5 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:117-187,207-256,540-586,984-1026,1335-1392` (launch, cleanup, teardown/quit, process wait and callbacks). | +| S6 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:258-350,464-538` (configuration overrides, 4-GiB cap, boot/device setup); `src\windows\wslopenvmm\src\client.rs:81-172` (configuration builder). | +| S7 | `src\windows\service\exe\VirtioFsShareRequest.cpp:8-68`; `src\windows\service\exe\OpenVmmWslCoreVm.cpp:653-768,1247-1263,1393-1406` (share requests, worker, elevation restriction); `:1035-1039` (pass-through rejection); `src\windows\wslopenvmm\src\client.rs:223-275` (share RPCs). | +| S8 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:515-521,807-875` (consomme NIC, DHCP, IPv6 enabled, port tracker); `src\windows\wslopenvmm\src\client.rs:313-349` (IPv4/IPv6 localhost port requests). | +| S9 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:342,386-430,554-577,984-1012` (dump-count override, collector/debug console, stdout/stderr log, process-exit trace); `src\windows\common\Dmesg.cpp:45-78,156-211` (pipe access and raw guest-log capture, not panic parsing). | +| S10 | `src\windows\wslopenvmm\src\client\tests.rs:86-110,133-207`: TCP-loopback mock server; port protocol/address-family assertions and share add/remove failure/retry assertions. No real VM or AF_UNIX connection/recovery test. | +| S11 | PR 3 commit `b956db18` on the refactor branch: contract comments in `src\windows\service\exe\IWslCoreVm.h` and `WslCoreVm.h`; `SimpleTests::VmBackendShutdownAndReconnect` in `test\windows\SimpleTests.cpp`. Execution is delegated to CI, with results not yet recorded here. | +| S12 | PR 5 commit `b8b146cf` on the backend branch: selection matrix in `doc\docs\technical-documentation\wslservice.exe.md`; parser, backend identity, failure/retry, live-configuration shutdown, and rollback cases in `test\windows\VmBackendTests.cpp`; invalid-boolean warning assertion in `UnitTests.cpp`; source registration, availability definition, and `configfile` linkage in `test\windows\CMakeLists.txt`. | +| S13 | PR 6 RPC follow-up committed as `3e729f2d`: `src\windows\wslopenvmm\src\rpc.rs`, `af_unix.rs`, `client.rs`, `lib.rs`, and `client\tests.rs`; private exports in `wslopenvmm.h`; revised contract in the local `src\windows\wslopenvmm\README.md`. Detailed coverage and remaining backend integration are in the PR 6 page. | +| S14 | Uncommitted C7 RPC diagnostics: epci2-style once-initialized debugger subscriber and stack-backed writer in `src\windows\wslopenvmm\src\diagnostics.rs`; initialization and ordinary tracing macros in `client.rs` and `rpc.rs`. Messages omit payloads and server error text. The accepted approach uses debugger output, not an ETW provider, custom event schemas, or request correlation metadata. | + +## Original plan, annotated by scope bullet + +Organize this as small, dependency-ordered PRs—not one PR per deliverable. The main sequence should be contracts → HCS-preserving abstraction → OpenVMM boot → filesystem/networking compatibility → diagnostics → rollout gates. Start WHP memory work in parallel; it should block removing the memory cap, not the initial capped-memory backend. + +The order below maps all 50 description bullets into proposed PRs. Some validation bullets intentionally span an early foundation PR and a later completion PR. + +Account for work you already have + +These are foundations to consume, not features to implement again. Merged PRs do not, by themselves, establish completion of their broader deliverables. + +| Area | Existing work | Planning implication | +| --- | --- | --- | +| Backend prototype | WSL #40629, open; your current WSL branch also contains backend and private AF_UNIX RPC work | Extract cohesive changes into the backend PRs below rather than starting over. | +| VirtioFS | OpenVMM #3821, WSL #41129, WSL #41151, all merged | Focus on OpenVMM integration and identity/elevation gaps, not rebuilding aggregate shares. | +| Networking | OpenVMM #2398, IPv6, merged; #4378, control/data-path separation, open | Consume existing IPv6 support and identify the remaining integration gaps. Treat the networking refactor as a dependency only where needed. | +| RPC configuration | OpenVMM #4420, open | Land the required network/filesystem RPC capabilities before their WSL consumers. | +| Crash artifacts | OpenVMM #3882, triple-fault .vmrs, merged | Extend and integrate the existing mechanism; distinguish triple faults from kernel panics and host-process crashes. | + +At the initial ADO lookup, all seven deliverables said Proposed. That state is not a reliable measure of implementation progress; the checklist below records local-stack evidence separately. + +Proposed PR order + +References:  A1  means the first Scope bullet in deliverable A. + +| Key | Deliverable | +| --- | --- | +| A | 62679114 — Architecture and compatibility matrix | +| B | 63428739 — Pluggable VM backends | +| C | 63428740 — OpenVMM backend | +| D | 63428744 — Filesystem and networking | +| E | 63428746 — Memory elasticity and nested virtualization | +| F | 63428745 — Crash diagnostics | +| G | 63428747 — Compatibility and regression validation | + +### PR 1 — Architecture, compatibility, and ownership contract + +Design/documentation PR. First; approve the relevant decisions before implementing their consumers. + +- [ ] **TODO - A1:** Define the supported WSL and WSLC scenario-compatibility matrix. No matrix is added by this stack. +- [ ] **Partial - A2:** Define `IWslVmBackend` responsibilities and lifecycle contract. `IWslCoreVm` and lifecycle implementations exist (refactor/backend; S1, S5), but the approved contract must reflect the actual interface and ownership model. +- [x] **Implemented - A3:** Define the boundary between service orchestration and HCS-specific behavior. The sibling HCS/OpenVMM design is explicitly accepted and documented in PR 3 below and the interface/class comments (S1, S11). This supersedes the originally proposed split within `WslCoreVm`. +- [ ] **Partial - A4:** Define backend selection, feature control, rollback, and configuration behavior. Compile-time and `.wslconfig` gates and the accepted selection/failure/manual-rollback matrix are documented (backend; S2, S12). The broader unsupported-setting compatibility contract remains outstanding. +- [ ] **Partial - A5:** Define the guest communication abstraction for HvSocket and vsock. Code implements callbacks and the guest bridge (refactor/backend; S3); the reviewed transport/lifecycle contract is not evidenced. +- [ ] **Partial - A6:** Record the initial VirtioFS-only and consomme-only constraints. These are enforced by configuration overrides (backend; S6), but a reviewed compatibility/limitations document is still needed. +- [ ] **TODO - A7:** Identify repository, component, and DRI ownership for every gap. No ownership table is added. +- [ ] **TODO - A8:** Resolve ownership overlap between scenarios 62917985 and 61024686. No recorded resolution is evidenced by the branch changes. +- [ ] **TODO - D3:** Design elevation/broker behavior for pass-through disk file opens. The current backend rejects non-VHD disks and does not implement a pass-through broker (backend; S7). +- [ ] **TODO - D6:** Define the migration path to converged WSL networking. Forcing consomme in configuration is not a migration plan (S6). +- [ ] **TODO - F5:** Define artifact retention, size, privacy, and upload behavior. Socket/pipe ACLs and local logging exist, but no artifact policy is added (S9). + +Keep this focused on decisions, not implementations. In particular, define when fallback is allowed; do not leave “safe fallback” to become an arbitrary retry after a partially created VM. + +### PR 2 — Backend comparison harness and baseline measurements + +WSL test/infrastructure PR. Start after PR 1; develop alongside the implementation. + +- [ ] **TODO - G1:** Create the end-to-end matrix for WSL and WSLC on HCS and OpenVMM. Mock RPC tests are not a backend matrix (S10). +- [ ] **TODO - G5, foundation:** Establish measurement tooling and HCS startup, memory, CPU, I/O, networking, and reliability baselines; collect OpenVMM results once available. No benchmark harness or baseline results are added. +- [ ] **TODO - G7, definition:** Agree preview/GA pass rates and regression thresholds before deciding whether results are acceptable. No threshold definitions are added. + +This is infrastructure, not a reason to defer feature-specific tests until the end. + +### PR 3 — Isolate the HCS backend behind the accepted service contract + +WSL PR. Depends on PR 1. + +- [x] **Implemented - B1 (accepted revised scope):** Isolate HCS-specific VM operations from service callers behind `IWslCoreVm`, retaining HCS ownership in `WslCoreVm` (S1, S11). +- [x] **Implemented - B2 (accepted revised scope):** Put existing HCS behavior behind the service-facing backend contract; use sibling HCS/OpenVMM implementations rather than a shared facade (S1, S11). +- [x] **Implemented - B6:** Preserve HCS initialization, networking, VirtioFS, shutdown, and error telemetry, with regression coverage (S11). + +Keep OpenVMM implementation out of this PR. Its review question should be: does the abstraction preserve HCS behavior? + +[PR 3 details: ownership, test coverage, CI sign-off, and build evidence](WSL-openvmm/PR-3.md). + +### PR 4 — Make guest control channels transport-neutral + +WSL PR. Depends on the agreed transport contract and PR 3. + +- [x] **Implemented - B5:** Abstract guest control channels away from the HvSocket-specific implementation. `ConnectToGuest` and connector callbacks are wired through instance/process/session creation; `SocketChannel` handles AF_UNIX separately from existing Windows I/O (refactor/backend; S3). + +Introduce and exercise the abstraction with existing behavior first. Do not conflate the host-side gRPC socket with the guest-control transport; they are separate contracts. + +### PR 5 — Backend selection, fail-fast behavior, and manual rollback + +WSL PR. Depends on PRs 3–4. + +- [x] **Implemented - B3 (accepted revised scope):** Centralize WSL VM creation/selection in `_CreateVm()` with `IWslCoreVm` callers; no separate factory is required (S1, S2, S12). +- [x] **Implemented - B4 (accepted policy):** Keep HCS as default, OpenVMM explicitly opt-in, failures explicit, and rollback manual after shutdown (S2, S12). +- [x] **Implemented - B7:** Add configuration-parsing and integration coverage for backend selection, failure handling, shutdown, and rollback (S12). + +[PR 5 details: accepted policy, test cases, CI requirements, and build evidence](WSL-openvmm/PR-5.md). + +### PR 6 — OpenVMM process supervision and gRPC client + +WSL PR. Depends on the contracts and shared abstractions. + +- [x] **Implemented - C1:** Implement process launch, lifetime, and termination handling. User-token launch, kill-on-close job, process registry/wait, cleanup, timeout-based forced termination, and exit callbacks are present (backend; S5). +- [x] **Implemented - C2 (accepted RPC-layer scope):** Bounded gRPC/AF_UNIX calls, fail-closed status handling, cancellation, and HRESULT mapping are implemented with client-owned per-handle synchronization (rpc; S13). Closed for this scope; backend lifecycle integration remains a separate follow-up. +- [x] **Implemented - C7 (accepted logging scope):** Existing backend traces, distinct RPC error categories, and ordinary tracing macros through the once-initialized debugger subscriber are implemented (S3, S5, S7, S9, S13, S14). No ETW provider, structured event schema, or correlation metadata is required for closeout. +- [ ] **Partial - G4, transport portion:** AF_UNIX startup, deadlines, cancellation races, fail-closed status, and silent/wrong-protocol peers have regression coverage (rpc; S13). Remaining: actual process crashes, lost-response resource state, and service shutdown races. + +**Backend follow-ups retained outside the C2/C7 closeout:** + +- [ ] Wire cancellation into process exit/termination, coordinate RPC-handle lifetime, and cover teardown followed by fresh-process recovery through service call sites. +- [ ] Integrate service/process diagnostics and define the backend-wide failure taxonomy. + +Your current AF_UNIX work belongs here. Distinguish establishing/re-establishing a connection from replaying a VM-management operation whose outcome is unknown. + +[PR 6 details: revised RPC contract, regression coverage, and remaining backend integration](WSL-openvmm/PR-6.md). + +### PR 7 — Configure, boot, and manage an OpenVMM VM + +WSL PR. Depends on PRs 4–6 and the required upstream RPC/device capabilities. + +- [ ] **Partial - C3:** Translate WSL VM settings into OpenVMM configuration. Kernel/initrd/modules, command line, CPU, capped memory, disks and NIC are translated (rpc/backend; S6). Several settings are forcibly disabled/overridden; complete or explicitly approve the supported-setting matrix. +- [ ] **Partial - C4:** Configure boot, memory, processors, serial, vsock, disks, pmem, and virtio-rng. Boot/CPU/memory/serial/virtio-console/vsock/SCSI configuration exists (rpc/backend; S6); pmem and virtio-rng are not configured by the new builder. Sending boot entropy is not virtio-rng support. +- [x] **Implemented - C5:** Implement start, stop, shutdown, terminate, and unexpected-exit handling. Create/resume, channel shutdown, teardown/quit, timed force termination, process-exit signaling and session callback routing are wired (rpc/backend; S2, S5). Runtime reliability coverage is tracked separately in G2/G4. +- [ ] **TODO - G2, initial slice:** Automate boot, distro launch, basic disk, vsock, console, and shutdown scenarios. The stack contains implementation and mock RPC tests, not real-VM scenario automation (S10). +- [ ] **Partial - G4, configuration portion:** Add malformed-configuration negative tests. PR 5 adds invalid backend-boolean parsing/warnings and early unsupported-system-distro failure/retry coverage (S12). Broader malformed VM/RPC configuration and post-allocation failure cases remain outstanding (S6, S10). + +This is the first usable, gated backend milestone, initially retaining the memory cap. Consume already-implemented boot/RPC functionality rather than duplicating it. + +### PR 8 — VirtioFS integration and mixed-elevation access + +WSL integration PR. Depends on PR 7 and upstream filesystem capabilities. + +- [x] **Implemented - D1:** Implement VirtioFS-based cross-OS filesystem access. Share request/response handling, guest listener/worker, canonical host paths, read-only options, and VPCI share RPCs are connected (rpc/backend; S7). This is creator-elevation access; D2 remains separate. +- [ ] **TODO - D2:** Support admin and non-admin Windows file access from the same VM. `AddVirtioFsShare` rejects `Admin != m_creatorElevated`; `InitializeDrvFs` explicitly rejects switching elevation context after creation (backend; S7). The guard is a limitation, not mixed-elevation support. + +Apply the broker/identity decisions from PR 1. If additional OpenVMM or DeviceHost mechanisms are required, land those as separate prerequisite PRs; do not bundle cross-repository implementation into this WSL PR. + +### PR 9 — Consommé networking integration and compatibility + +WSL integration PR. Depends on PR 7 and the required OpenVMM networking/RPC changes. + +- [x] **Implemented - D4:** Integrate initial consomme networking. NIC configuration, mini_init networking/DHCP setup, port tracker and localhost bind/unbind RPCs are wired (rpc/backend; S8). +- [ ] **Partial - D5:** Consume the required consomme IPv6 changes. Guest configuration enables IPv6 and RPCs handle `AF_INET6`/`::1`, with mock field assertions (rpc/backend; S8, S10). Confirm the consumed OpenVMM version satisfies the upstream dependency and exercise real IPv6 behavior. +- [ ] **TODO - D7:** Validate DNS, localhost, VPN, proxy, firewall, IPv6, and multi-distro behavior. Configuration checks and mock port serialization are not networking compatibility results; no such matrix/automation is added. + +PRs 8 and 9 can proceed independently. Existing IPv6 support is a starting point—not proof that the entire WSL networking matrix passes. + +### PR 10 — Complete the OpenVMM crash-artifact producer + +OpenVMM PR. Can proceed in parallel once the artifact contract is agreed. + +- [ ] **External - F1:** Complete the mechanism to produce a VM saved-state or crash artifact. Earlier evidence identified merged OpenVMM #3882 for triple faults; this WSL stack neither implements nor proves the complete producer contract. Track upstream completion and consumption separately. +- [ ] **External - F2:** Add the compatible compression writer for the selected format, or update the consumer. Neither change is present in this WSL stack; verify the selected upstream format and remaining consumer work. + +Build on merged #3882. If the chosen solution instead changes the consumer, place F2 in PR 11, rather than implementing both approaches. + +The previously observed OpenVMM `Add crash dump path option` commit belongs to this diagnostics work, not the network/filesystem RPC story in #4420. It is outside the WSL stack assessed here. + +### PR 11 — WSL/WSLC diagnostic collection and debugger integration + +WSL PR. Depends on PR 7; artifact collection additionally depends on PR 10. + +- [ ] **TODO - C6:** Add kernel debugger support. Debug console/early-console output is wired, but OpenVMM kernel-debugger configuration is not (backend; S6, S9). Do not count a debug console as a debugger. +- [ ] **Partial - F3:** Integrate artifact collection into WSL and WSLC diagnostics. WSL reuses `DmesgCollector`, adds user-accessible console pipes, and writes OpenVMM stdout/stderr locally (backend; S9). Saved-state/crash-artifact collection and WSLC integration are not added. +- [ ] **TODO - F4:** Extract kernel-panic details from dmesg collector output. The reused collector buffers/emits raw guest log lines; the stack adds pipe access, not panic parsing or attribution (S9). +- [ ] **TODO - F6:** Update log-collection scripts for OpenVMM logs and traces. No `diagnostics` scripts change; creating a local `.log` file is only a prerequisite. +- [ ] **Partial - F7:** Add telemetry for dump success/failure, parsing, and backend crash buckets. Process-exit code/VM-ID traces and guest logs exist (backend; S9), but dump outcome, parsing and crash-bucket telemetry are not implemented. + +Bring this forward alongside filesystem/networking work: actionable diagnostics are useful before broad stress testing, not just before release. + +### PR 12 — WHP memory contract and accounting design + +Design/documentation PR. Start alongside PR 1, despite its position in this implementation sequence. + +- [ ] **External - E1:** Confirm WHP deferred-commit and sparse-allocation requirements with the WHP owner. Owner agreement is not evidenced by WSL branch changes; attach the decision separately. +- [ ] **External - E5:** Define host-commit versus guest-visible memory accounting and telemetry. No accounting contract or new memory telemetry is present; the 4-GiB clamp is not accounting (S6). + +Owner agreement is a prerequisite, not something a code PR alone can accomplish. Explicitly determine whether host/WHP changes are required; ballooning alone should not be assumed to solve upfront host commit. + +### PR 13 — Virtio-balloon support + +OpenVMM PR. Depends on PR 12. + +- [ ] **External - E2:** Implement the virtio-balloon support required by WSL and WSLC. No balloon configuration/control integration is added in this WSL stack (S6). Track separate OpenVMM implementation and its WSL/WSLC consumption. + +Keep this independently reviewable from cold-discard and nested virtualization. Any WSL policy/configuration wiring should be a separate consuming PR if it requires code changes there. + +### PR 14 — Cold-discard support and memory-elasticity integration + +OpenVMM PR, followed by a WSL integration PR where needed. Depends on PRs 12–13. + +- [ ] **External - E3:** Implement qemu-style cold-discard hints or the approved equivalent. No new host memory-discard integration is present. Existing guest reclaim settings and disk trim commands are not evidence of this host-memory feature. +- [ ] **TODO - E4:** Validate grow, shrink, reclaim, pressure, suspend, and multi-VM behavior. No elasticity scenario coverage/results are added; the memory cap remains (S6). + +Do not remove the WSL memory cap merely because the device exists. Removal should follow demonstrated host-commit and reclaim behavior plus the memory stress/performance coverage below. + +### PR 15 — Remaining nested-virtualization support + +OpenVMM/WHP-owned implementation PRs. Independent of balloon/discard unless a concrete shared dependency emerges. + +- [ ] **External - E6:** Complete the remaining nested-virtualization work. The new WSL RPC configuration does not wire a nested-virtualization setting (S6); track OpenVMM/WHP completion and explicit WSL consumption separately. + +Keep this a separate workstream. The deliverable groups nesting with memory, but its description does not establish that they must form one linear code stack. + +### PR 16 — Complete compatibility automation and cross-feature stress + +Test PRs in the repository owning each harness. Depends on the applicable feature PRs. + +- [ ] **TODO - G2, completion:** Complete automated disk, VirtioFS, vsock, console, networking, boot, launch, and shutdown coverage. Two mock RPC tests do not exercise these real-VM scenarios (S10). +- [ ] **TODO - G3:** Add multi-distro, repeated attach/detach, restart, update, and hot-add stress. Share retry assertions are not repeated real-device or multi-VM stress. +- [ ] **TODO - G4, completion:** Add host-resource-pressure tests and complete cross-feature failure coverage. Only the narrow mock-RPC portion in PR 6 is present (S10). +- [ ] **TODO - E7:** Add memory-elasticity and nested-workload stress/performance coverage. No such harness/results are added. + +The feature PRs should already carry their focused tests. This layer covers interactions, longer-running workloads, and the full matrix. + +### PR 17 — Enforce performance and rollout gates + +WSL validation/release-infrastructure PR. Depends on representative results from the preceding work. + +- [ ] **TODO - G5, completion:** Establish the comparable OpenVMM baselines across startup, memory, CPU, I/O, networking, and reliability. Slow-operation logging does not supply comparative baseline results. +- [ ] **TODO - G6:** Use WSLC startup-time P95 measure 63134665 as a rollout signal. No measure integration is added. +- [ ] **TODO - G7, enforcement:** Make the agreed preview/GA thresholds enforceable gates. Compile-time and config opt-in gates are not health/performance release gates. + +Do not invent numerical thresholds from the work-item text; it specifies that they must be defined, not what their values are. + +## Stack boundaries and dependencies outside your seven items + +Use a short WSL foundation stack for PRs 3–7, then separate filesystem, networking, diagnostics, and memory workstreams. OpenVMM prerequisite PRs belong in OpenVMM stacks; connect them to WSL consumers through explicit dependency links and consumed versions—not one cross-repository branch chain. + +Three sibling deliverables need to remain visible in the dependency map: + +| Dependency | Where it matters | +| --- | --- | +| 63428742 — Guest channels and virtio devices | PRs 4, 7, and 8 require the selected mini_init transport, independent control/diagnostic channels, and appropriate VirtioFS/device support. This is a real dependency omitted from the seven-item list. | +| 62679000 — Productization and release pipeline | Required to consume supported, versioned OpenVMM artifacts and ship the result; avoid making prototype completion synonymous with release readiness. | +| 63428748 — Preview rollout and GA readiness | Owns rollout execution. PRs 5 and 17 should provide selection controls and gates without duplicating its rollout ownership. | + +Also align PRs 6 and 11 with 63459355 — Diagnosability. GPU support is explicitly non-blocking in the parent scenario and should not hold up this core sequence. diff --git a/msipackage/CMakeLists.txt b/msipackage/CMakeLists.txt index b99808de5e..3272b3afef 100644 --- a/msipackage/CMakeLists.txt +++ b/msipackage/CMakeLists.txt @@ -17,7 +17,7 @@ set(OUTPUT_PACKAGE ${BIN}/wsl.msi) set(PACKAGE_WIX_IN ${CMAKE_CURRENT_LIST_DIR}/package.wix.in) set(PACKAGE_WIX ${BIN}/package.wix) set(CAB_CACHE ${BIN}/cab) -set(WINDOWS_BINARIES wsl.exe;wslg.exe;wslhost.exe;wslrelay.exe;wslservice.exe;wslserviceproxystub.dll;wsldevicehostproxystub.dll;wslinstall.dll;wslc.exe;wslcsession.exe) +set(WINDOWS_BINARIES wsl.exe;wslg.exe;wslhost.exe;wslrelay.exe;wslservice.exe;wslserviceproxystub.dll;wsldevicehostproxystub.dll;wslinstall.dll;wslc.exe;wslcsession.exe;openvmm.exe;wslopenvmm.dll) if (WSL_BUILD_WSL_SETTINGS) list(APPEND WINDOWS_BINARIES "wslsettings/wslsettings.dll;wslsettings/wslsettings.exe;libwsl.dll") endif() diff --git a/msipackage/package.wix.in b/msipackage/package.wix.in index aea504780d..f216a7cb71 100644 --- a/msipackage/package.wix.in +++ b/msipackage/package.wix.in @@ -294,7 +294,8 @@ - + + diff --git a/packages.config b/packages.config index 747e41ac23..5350f63cc5 100644 --- a/packages.config +++ b/packages.config @@ -22,6 +22,7 @@ + diff --git a/src/windows/service/exe/CMakeLists.txt b/src/windows/service/exe/CMakeLists.txt index c7b4ebb0dd..91710be030 100644 --- a/src/windows/service/exe/CMakeLists.txt +++ b/src/windows/service/exe/CMakeLists.txt @@ -57,6 +57,16 @@ set(HEADERS WSLCSessionManagerFactory.h WSLCPluginNotifier.h) +add_library(virtualmachinebackend STATIC + VirtualMachineBackend.cpp + IVirtualMachineBackend.h + OpenVmmVirtualMachineBackend.cpp + OpenVmmVirtualMachineBackend.h) +target_include_directories(virtualmachinebackend PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_precompile_headers(virtualmachinebackend REUSE_FROM common) +target_link_libraries(virtualmachinebackend PUBLIC common ${COMMON_LINK_LIBRARIES} wslopenvmm_client) +set_target_properties(virtualmachinebackend PROPERTIES FOLDER windows) + add_executable(wslservice ${SOURCES} ${HEADERS}) add_dependencies(wslservice wslserviceidl wslservicemc) add_compile_definitions(__WRL_CLASSIC_COM__) @@ -72,6 +82,7 @@ target_link_libraries(wslservice configfile legacy_stdio_definitions VirtDisk.lib + virtualmachinebackend Winhttp.lib Synchronization.lib yaml-cpp) diff --git a/src/windows/service/exe/IVirtualMachineBackend.h b/src/windows/service/exe/IVirtualMachineBackend.h new file mode 100644 index 0000000000..4cb74648e5 --- /dev/null +++ b/src/windows/service/exe/IVirtualMachineBackend.h @@ -0,0 +1,494 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + IVirtualMachineBackend.h + +Abstract: + + Interface for virtual machine backends, providing a common API for managing VMs across different implementations. + +--*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "defs.h" + +enum class BackendKind +{ + Hcs, + OpenVmm +}; + +struct VmInstanceId +{ + GUID VmId{}; +}; + +template +struct VmResourceId +{ + VmInstanceId Owner; + std::uint64_t Value = 0; +}; + +struct VmDiskTag; +struct VmDeviceTag; +struct VmShareTag; +struct VmListenerTag; +struct VmPortBindingTag; + +using VmDiskId = VmResourceId; +using VmDeviceId = VmResourceId; +using VmShareId = VmResourceId; +using VmListenerId = VmResourceId; +using VmPortBindingId = VmResourceId; + +enum class VmFeatureRequest +{ + Disabled, + Preferred, + Required +}; + +enum class VmSelectionPolicy +{ + Required, + Preferred +}; + +enum class VmFeature +{ + LinuxDirectBoot, + LinuxFirmwareBoot, + NestedVirtualization, + PerfmonPmu, + PerfmonLbr, + SmallPageMemory, + MemoryOvercommit, + DeferredMemoryCommit, + ColdDiscard, + SerialConsole, + VirtioConsole, + Vhd, + Vhdx, + PhysicalDisk, + PersistentMemory, + Plan9Socket, + Plan9Virtio, + VirtioFsFileBacked, + VirtioFsAggregate, + SectionBackedSharedMemory, + MirroredGpu, + GpuVendorExtension, + GpuDisableGdiAcceleration, + GpuDisablePresentation, + SavedStateOnCrash, + GuestDmaWindow, + HostEndpointNetwork, + UserModeNatNetwork, + TcpPortBinding, + UdpPortBinding, + Ipv6PortBinding, + ScopedIpv6PortBinding, + DynamicHostPort, + VirtualHostAddress, + StaticDnsARecord, + Count +}; + +static_assert(static_cast(VmFeature::Count) == 35); + +enum class VmOperation +{ + Create, + Start, + Terminate, + CreateGuestListener, + AcceptGuestConnection, + ConnectGuest, + CloseGuestListener, + AttachDisk, + DetachDisk, + AddPersistentMemory, + CreateFileSystemDevice, + AddFileSystemShare, + RemoveFileSystemShare, + GetFileSystemDeviceStatus, + AddGpu, + AddSharedMemory, + ConfigureGuestDma, + RemoveDevice, + AddNetworkAdapter, + UpdateNetworkAdapter, + BindPort, + UnbindPort, + CreateVirtualAddress, + CreateDnsRecord, + Count +}; + +static_assert(static_cast(VmOperation::Count) == 24); + +struct VmPlatformCapabilities +{ + BackendKind Backend; + std::bitset(VmFeature::Count)> Features; + std::bitset(VmOperation::Count)> Operations; +}; + +struct GuestServicePort +{ + std::uint32_t Value = 0; +}; + +struct VmGuestListener +{ + VmListenerId Id; + GuestServicePort Port; +}; + +struct VmProcessorRequest +{ + std::uint32_t Count = 0; + VmFeatureRequest NestedVirtualization = VmFeatureRequest::Disabled; + VmFeatureRequest PerfmonPmu = VmFeatureRequest::Disabled; + VmFeatureRequest PerfmonLbr = VmFeatureRequest::Disabled; +}; + +struct VmMemoryRequest +{ + std::uint64_t SizeBytes = 0; + VmFeatureRequest AllowOvercommit = VmFeatureRequest::Disabled; + VmFeatureRequest DeferredCommit = VmFeatureRequest::Disabled; + VmFeatureRequest ColdDiscard = VmFeatureRequest::Disabled; +}; + +enum class VmBootMethod +{ + Automatic, + LinuxDirect, + Uefi +}; + +struct VmLinuxBootRequest +{ + std::filesystem::path KernelPath; + std::filesystem::path InitrdPath; + VmBootMethod Method = VmBootMethod::Automatic; + std::wstring GuestCommandLine; + std::wstring UserCommandLine; + std::optional RequestedDmaBounceBufferBytes; +}; + +enum class VmConsoleRole +{ + EarlyBoot, + KernelConsole, + Telemetry, + DebugShell, + KernelDebugger +}; + +struct VmSerialConsole +{ + std::uint32_t Port = 0; + std::filesystem::path NamedPipe; +}; + +struct VmVirtioConsole +{ + std::uint32_t Port = 0; + std::wstring GuestName; + std::filesystem::path NamedPipe; +}; + +struct VmConsoleRequest +{ + VmConsoleRole Role = VmConsoleRole::KernelConsole; + std::variant Device; +}; + +using VmBootResourceKey = std::wstring; + +struct VmScsiAddress +{ + std::uint32_t Controller = 0; + std::uint32_t Lun = 0; +}; + +using VmGuestDiskAddress = VmScsiAddress; + +enum class VmDiskFormat +{ + Vhd, + Vhdx +}; + +struct VmVirtualDiskSource +{ + std::filesystem::path Path; + VmDiskFormat Format = VmDiskFormat::Vhdx; +}; + +struct VmPhysicalDiskSource +{ + std::wstring DevicePath; +}; + +struct VmScsiPlacement +{ + VmScsiAddress Address; +}; + +struct VmDiskRequest +{ + std::variant Source; + bool ReadOnly = true; + std::optional Placement; +}; + +struct VmBootDiskRequest +{ + VmBootResourceKey Key; + VmDiskRequest Disk; +}; + +struct VmDiskAttachment +{ + VmDiskId Id; + VmGuestDiskAddress GuestAddress; + bool ReadOnly = true; +}; + +struct VmCrashCaptureRequest +{ + std::filesystem::path SavedStatePath; + VmSelectionPolicy Policy = VmSelectionPolicy::Required; +}; + +struct VmCreateRequest +{ + GUID VmId{}; + VmProcessorRequest Processor; + VmMemoryRequest Memory; + VmLinuxBootRequest Boot; + std::vector BootDisks; + std::vector Consoles; + std::optional CrashCapture; +}; + +struct VmEffectiveProcessor +{ + std::uint32_t Count = 0; + bool NestedVirtualization = false; + bool PerfmonPmu = false; + bool PerfmonLbr = false; +}; + +struct VmEffectiveMemory +{ + std::uint64_t SizeBytes = 0; + bool AllowOvercommit = false; + bool DeferredCommit = false; + bool ColdDiscard = false; + std::optional HighMmioBaseBytes; + std::optional HighMmioSizeBytes; +}; + +struct VmEffectiveBoot +{ + VmBootMethod Method = VmBootMethod::Automatic; + std::wstring KernelCommandLine; + std::optional PageReportingOrder; + std::vector Consoles; +}; + +struct VmDescription +{ + VmInstanceId Identity; + BackendKind Backend; + VmEffectiveProcessor Processor; + VmEffectiveMemory Memory; + VmEffectiveBoot Boot; + std::map BootDisks; +}; + +struct VmIpv4Address +{ + std::array Bytes{}; +}; + +struct VmIpv6Address +{ + std::array Bytes{}; + std::uint32_t ScopeId = 0; +}; + +using VmIpAddress = std::variant; + +struct VmEthernetAddress +{ + std::array Bytes{}; +}; + +struct VmIpEndpoint +{ + VmIpAddress Address; + std::uint16_t Port = 0; +}; + +struct VmUserModeNatNetwork +{ + VmIpv4Address ClientIpv4; + std::optional ClientIpv6; + VmEthernetAddress ClientMac; + VmIpv4Address GatewayIpv4; + VmEthernetAddress GatewayMacIpv4; + VmEthernetAddress GatewayMacIpv6; + VmIpv4Address Netmask; + std::vector Nameservers; +}; + +struct VmNetworkAdapterRequest +{ + std::wstring Tag; + VmUserModeNatNetwork Configuration; +}; + +struct VmNetworkAttachment +{ + VmDeviceId Id; + std::wstring Tag; + std::optional GuestInstanceId; + VmUserModeNatNetwork EffectiveConfiguration; +}; + +enum class VmTransportProtocol +{ + Tcp, + Udp +}; + +struct VmPortBindingRequest +{ + VmTransportProtocol Protocol = VmTransportProtocol::Tcp; + VmIpEndpoint Listen; + std::uint16_t GuestPort = 0; +}; + +struct VmPortBinding +{ + VmPortBindingId Id; + VmDeviceId Device; + VmTransportProtocol Protocol = VmTransportProtocol::Tcp; + VmIpEndpoint EffectiveListen; + std::uint16_t GuestPort = 0; +}; + +enum class VmVirtioFsLayout +{ + SingleShare, + Aggregate +}; + +struct VmVirtioFsDevice +{ + std::wstring Tag; + VmVirtioFsLayout Layout = VmVirtioFsLayout::Aggregate; +}; + +struct VmFileSystemDeviceRequest +{ + VmVirtioFsDevice Transport; +}; + +enum class VmFileSystemDeviceState +{ + Prepared, + Serving, + Unavailable +}; + +struct VmFileSystemDevice +{ + VmDeviceId Id; + VmFileSystemDeviceState State = VmFileSystemDeviceState::Prepared; +}; + +struct VmVirtioFsShareOptions +{ + std::map MountOptions; +}; + +struct VmFileSystemShareRequest +{ + std::filesystem::path HostPath; + std::wstring Name; + bool ReadOnly = true; + VmVirtioFsShareOptions Options; +}; + +struct VmVirtioFsShareAddress +{ + std::wstring Tag; + std::optional ChildName; +}; + +struct VmFileSystemShare +{ + VmShareId Id; + VmDeviceId Device; + VmVirtioFsShareAddress GuestAddress; + std::filesystem::path EffectiveHostPath; + bool ReadOnly = true; +}; + +class IVirtualMachineBackend +{ +public: + virtual ~IVirtualMachineBackend() noexcept = default; + + virtual VmPlatformCapabilities GetCapabilities() const = 0; + virtual wil::unique_handle GetTerminationEvent() const = 0; + virtual void Start() = 0; + virtual void Terminate() = 0; + virtual void CancelPendingOperations() noexcept = 0; + + virtual VmGuestListener CreateGuestListener(GuestServicePort Port) = 0; + virtual wil::unique_socket AcceptGuestConnection(VmListenerId Listener) = 0; + virtual wil::unique_socket ConnectGuest(GuestServicePort Port) = 0; + virtual void CloseGuestListener(VmListenerId Listener) = 0; + + virtual VmDiskAttachment AttachDisk(const VmDiskRequest& Request) = 0; + virtual void DetachDisk(VmDiskId Disk) = 0; + + virtual VmFileSystemDevice CreateFileSystemDevice(const VmFileSystemDeviceRequest& Request) = 0; + virtual VmFileSystemShare AddFileSystemShare(VmDeviceId Device, const VmFileSystemShareRequest& Request) = 0; + virtual void RemoveFileSystemShare(VmShareId Share) = 0; + + virtual VmNetworkAttachment AddNetworkAdapter(const VmNetworkAdapterRequest& Request) = 0; + virtual VmPortBinding BindPort(VmDeviceId Device, const VmPortBindingRequest& Request) = 0; + virtual void UnbindPort(VmPortBindingId Binding) = 0; +}; + +VmPlatformCapabilities QueryVirtualMachineBackendCapabilities(BackendKind Kind); + +std::unique_ptr CreateVirtualMachineBackend(BackendKind Kind, const VmCreateRequest& Request); \ No newline at end of file diff --git a/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp b/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp new file mode 100644 index 0000000000..af0f976ef2 --- /dev/null +++ b/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp @@ -0,0 +1,600 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + OpenVmmVirtualMachineBackend.cpp + +Abstract: + + Implementation of IVirtualMachineBackend - represents a single OpenVMM-based VM instance. + +--*/ + +#include "precomp.h" +#include "OpenVmmVirtualMachineBackend.h" +#include +#include +#include "SubProcess.h" +#include "wslopenvmm.h" + +namespace { + +constexpr UINT64 c_memoryGranularity = 2ULL * 1024 * 1024; +constexpr UINT64 c_maximumMemory = 4ULL * 1024 * 1024 * 1024; +constexpr UINT32 c_maximumDisks = 254; +constexpr UINT32 c_rpcTimeoutMs = 30000; +constexpr HRESULT c_notSupported = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + +void DestroyConfig(WslOpenVmmConfig* Config) noexcept +{ + WslOpenVmmDestroyConfig(&Config); +} + +using UniqueConfig = wil::unique_any; + +void ValidateFeature(VmFeatureRequest Request, PCWSTR Setting) +{ + switch (Request) + { + case VmFeatureRequest::Disabled: + case VmFeatureRequest::Preferred: + return; + case VmFeatureRequest::Required: + THROW_HR_MSG(c_notSupported, "OpenVMM does not support the required %ls setting", Setting); + } + + THROW_HR(E_INVALIDARG); +} + +void ValidatePath(const std::filesystem::path& Path) +{ + THROW_HR_IF_MSG( + E_INVALIDARG, + Path.empty() || !Path.is_absolute() || Path.native().find(L'\0') != std::wstring::npos, + "OpenVMM requires an absolute, nonempty host path"); +} + +const VmVirtualDiskSource& ValidateDiskRequest(const VmDiskRequest& Request) +{ + const auto* source = std::get_if(&Request.Source); + THROW_HR_IF(c_notSupported, source == nullptr); + ValidatePath(source->Path); + switch (source->Format) + { + case VmDiskFormat::Vhd: + THROW_HR_IF(E_INVALIDARG, _wcsicmp(source->Path.extension().c_str(), L".vhd") != 0); + break; + case VmDiskFormat::Vhdx: + THROW_HR_IF(E_INVALIDARG, _wcsicmp(source->Path.extension().c_str(), L".vhdx") != 0); + break; + default: + THROW_HR(E_INVALIDARG); + } + + if (Request.Placement) + { + THROW_HR_IF( + c_notSupported, Request.Placement->Address.Controller != 0 || Request.Placement->Address.Lun >= c_maximumDisks); + } + + return *source; +} + +wil::unique_hfile OpenBackingFile(const std::filesystem::path& Path, bool ReadOnly) +{ + wil::unique_hfile file{CreateFileW( + Path.c_str(), + GENERIC_READ | (ReadOnly ? 0 : GENERIC_WRITE), + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr)}; + THROW_LAST_ERROR_IF(!file); + file.reset(); + + // Pin the file without conflicting with the VMM's disk sharing mode. + file.reset(CreateFileW(Path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); + THROW_LAST_ERROR_IF(!file); + return file; +} + +void ValidateConsolePath(const std::filesystem::path& Path) +{ + ValidatePath(Path); + THROW_HR_IF_MSG( + c_notSupported, !Path.native().starts_with(L"\\\\.\\pipe\\"), "OpenVMM consoles require a caller-provided named pipe"); +} + +void DeleteOwnedFile(const std::filesystem::path& Path) noexcept +{ + if (!Path.empty() && !DeleteFileW(Path.c_str())) + { + const auto error = GetLastError(); + if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND) + { + LOG_WIN32(error); + } + } +} + +} // namespace + +VmDescription wsl::windows::common::vm::openvmm::ValidateCreateRequest(const VmCreateRequest& Request) +{ + THROW_HR_IF(E_INVALIDARG, IsEqualGUID(Request.VmId, GUID_NULL)); + THROW_HR_IF(E_INVALIDARG, Request.Processor.Count == 0 || Request.Memory.SizeBytes == 0); + THROW_HR_IF_MSG(c_notSupported, wsl::shared::Arm64, "OpenVMM direct boot is currently supported only on x64"); + ValidatePath(Request.Boot.KernelPath); + ValidatePath(Request.Boot.InitrdPath); + THROW_HR_IF( + E_INVALIDARG, + Request.Boot.GuestCommandLine.find(L'\0') != std::wstring::npos || Request.Boot.UserCommandLine.find(L'\0') != std::wstring::npos); + THROW_HR_IF( + E_INVALIDARG, + Request.Boot.Method != VmBootMethod::Automatic && Request.Boot.Method != VmBootMethod::LinuxDirect && + Request.Boot.Method != VmBootMethod::Uefi); + THROW_HR_IF(c_notSupported, Request.Boot.Method == VmBootMethod::Uefi); + THROW_HR_IF(c_notSupported, Request.Boot.RequestedDmaBounceBufferBytes.has_value()); + + VmDescription description; + description.Identity.VmId = Request.VmId; + description.Backend = BackendKind::OpenVmm; + description.Processor.Count = Request.Processor.Count; + description.Memory.SizeBytes = Request.Memory.SizeBytes; + THROW_HR_IF(E_INVALIDARG, Request.Memory.SizeBytes % c_memoryGranularity != 0); + + THROW_HR_IF_MSG( + c_notSupported, + description.Memory.SizeBytes < c_memoryGranularity || Request.Memory.SizeBytes > c_maximumMemory, + "OpenVMM currently supports memory sizes from 2 MiB to 4 GiB; memory is not silently capped"); + ValidateFeature(Request.Processor.NestedVirtualization, L"nested virtualization"); + ValidateFeature(Request.Processor.PerfmonPmu, L"PMU"); + ValidateFeature(Request.Processor.PerfmonLbr, L"LBR"); + ValidateFeature(Request.Memory.AllowOvercommit, L"memory overcommit"); + ValidateFeature(Request.Memory.DeferredCommit, L"deferred memory commit"); + ValidateFeature(Request.Memory.ColdDiscard, L"cold discard"); + + if (Request.CrashCapture) + { + THROW_HR_IF(E_INVALIDARG, Request.CrashCapture->Policy != VmSelectionPolicy::Required && Request.CrashCapture->Policy != VmSelectionPolicy::Preferred); + THROW_HR_IF(c_notSupported, Request.CrashCapture->Policy == VmSelectionPolicy::Required); + } + + description.Boot.Method = VmBootMethod::LinuxDirect; + description.Boot.KernelCommandLine = Request.Boot.GuestCommandLine; + if (!Request.Boot.UserCommandLine.empty()) + { + if (!description.Boot.KernelCommandLine.empty()) + { + description.Boot.KernelCommandLine += L" "; + } + description.Boot.KernelCommandLine += Request.Boot.UserCommandLine; + } + + bool serialConfigured = false; + bool virtioConfigured = false; + for (const auto& console : Request.Consoles) + { + if (const auto* serial = std::get_if(&console.Device)) + { + THROW_HR_IF(c_notSupported, serial->Port != 0); + THROW_HR_IF(E_INVALIDARG, serialConfigured); + ValidateConsolePath(serial->NamedPipe); + serialConfigured = true; + } + else + { + const auto& virtio = std::get(console.Device); + THROW_HR_IF(c_notSupported, virtio.Port != 0 || !virtio.GuestName.empty()); + THROW_HR_IF(E_INVALIDARG, virtioConfigured); + ValidateConsolePath(virtio.NamedPipe); + virtioConfigured = true; + } + description.Boot.Consoles.push_back(console); + } + + THROW_HR_IF(c_notSupported, Request.BootDisks.size() > c_maximumDisks); + std::bitset allocated; + for (const auto& disk : Request.BootDisks) + { + THROW_HR_IF(E_INVALIDARG, disk.Key.empty() || description.BootDisks.contains(disk.Key)); + description.BootDisks.emplace(disk.Key, VmDiskAttachment{}); + ValidateDiskRequest(disk.Disk); + if (disk.Disk.Placement) + { + const auto& placement = *disk.Disk.Placement; + THROW_HR_IF(E_INVALIDARG, allocated.test(placement.Address.Lun)); + allocated.set(placement.Address.Lun); + } + } + + std::uint64_t nextId = 1; + for (const auto& disk : Request.BootDisks) + { + std::uint32_t lun = 0; + if (disk.Disk.Placement) + { + lun = disk.Disk.Placement->Address.Lun; + } + else + { + while (lun < c_maximumDisks && allocated.test(lun)) + { + ++lun; + } + THROW_HR_IF(E_BOUNDS, lun == c_maximumDisks); + allocated.set(lun); + } + description.BootDisks.at(disk.Key) = {{description.Identity, nextId++}, {0, lun}, disk.Disk.ReadOnly}; + } + + return description; +} + +void OpenVmmVirtualMachineBackend::DestroyVm(WslOpenVmmVm* Vm) noexcept +{ + WslOpenVmmDestroyVm(&Vm); +} + +OpenVmmVirtualMachineBackend::OpenVmmVirtualMachineBackend() : m_state(std::make_unique()) +{ +} + +OpenVmmVirtualMachineBackend::~OpenVmmVirtualMachineBackend() noexcept +{ + if (m_state->m_processWait) + { + SetThreadpoolWait(m_state->m_processWait.get(), nullptr, nullptr); + WaitForThreadpoolWaitCallbacks(m_state->m_processWait.get(), TRUE); + m_state->m_processWait.reset(); + } + if (m_state->m_vm && WaitForSingleObject(m_state->m_process.get(), 0) == WAIT_TIMEOUT) + { + LOG_IF_FAILED(WslOpenVmmVmTeardown(m_state->m_vm.get())); + LOG_IF_FAILED(WslOpenVmmVmQuit(m_state->m_vm.get())); + } + m_state->m_vm.reset(); + m_state->m_job.reset(); + if (m_state->m_process) + { + // Confirm exit before releasing backing files or deleting socket paths. + LOG_LAST_ERROR_IF(WaitForSingleObject(m_state->m_process.get(), INFINITE) == WAIT_FAILED); + } + m_state->m_backingFiles.clear(); + if (m_state->m_directoryCreated) + { + DeleteOwnedFile(m_state->m_rpcSocketPath); + DeleteOwnedFile(m_state->m_vsockPath); + DeleteOwnedFile(m_state->m_socketDirectory / L"openvmm.log"); + LOG_IF_WIN32_BOOL_FALSE(RemoveDirectoryW(m_state->m_socketDirectory.c_str())); + } +} + +std::unique_ptr OpenVmmVirtualMachineBackend::Create(const VmCreateRequest& Request) +{ + auto description = wsl::windows::common::vm::openvmm::ValidateCreateRequest(Request); + auto backend = std::unique_ptr{new OpenVmmVirtualMachineBackend{}}; + backend->m_state->m_description = std::move(description); + backend->Initialize(Request); + return backend; +} + +void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) +{ + using namespace wsl::windows::common; + const auto executable = wslutil::GetBasePath() / L"openvmm.exe"; + THROW_HR_IF_MSG( + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), + !filesystem::FileExists(executable.c_str()), + "openvmm.exe not found at: %ls", + executable.c_str()); + + auto id = wsl::shared::string::GuidToString(Request.VmId, wsl::shared::string::GuidToStringFlags::None); + std::erase(id, L'-'); + // An exclusive directory creation prevents shortened path IDs from aliasing another VM. + m_state->m_socketDirectory = filesystem::GetTempFolderPath(GetCurrentProcessToken()) / (L"ov-" + id.substr(0, 16)); + m_state->m_rpcSocketPath = m_state->m_socketDirectory / L"r"; + m_state->m_vsockPath = m_state->m_socketDirectory / L"v"; + const auto longestPath = + wsl::shared::string::WideToMultiByte(m_state->m_vsockPath.native() + L"_ffffffff-facb-11e6-bd58-64006a7986d3"); + SOCKADDR_UN address{}; + THROW_HR_IF_MSG( + E_INVALIDARG, + longestPath.size() >= sizeof(address.sun_path), + "OpenVMM guest socket path exceeds the AF_UNIX limit: %hs", + longestPath.c_str()); + THROW_HR_IF(E_INVALIDARG, m_state->m_rpcSocketPath.native().find_first_of(L",\"\r\n") != std::wstring::npos); + + const auto tokenUser = wil::get_token_information(GetCurrentProcessToken()); + const auto sid = wslutil::SidToString(tokenUser->User.Sid); + const auto sddl = std::format(L"D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;{})", sid.get()); + wil::unique_hlocal_security_descriptor security; + THROW_IF_WIN32_BOOL_FALSE(ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), SDDL_REVISION_1, &security, nullptr)); + SECURITY_ATTRIBUTES attributes{sizeof(attributes), security.get(), FALSE}; + { + m_state->m_backingFiles.push_back(OpenBackingFile(Request.Boot.KernelPath, true)); + m_state->m_backingFiles.push_back(OpenBackingFile(Request.Boot.InitrdPath, true)); + auto lock = m_state->m_lock.lock_exclusive(); + for (const auto& disk : Request.BootDisks) + { + const auto& attachment = m_state->m_description.BootDisks.at(disk.Key); + auto backingFile = OpenBackingFile(std::get(disk.Disk.Source).Path, disk.Disk.ReadOnly); + m_state->m_attachedDisks.emplace( + attachment.Id.Value, State::AttachedDisk{attachment, std::move(backingFile)}); + } + m_state->m_nextDiskId = Request.BootDisks.size() + 1; + THROW_IF_WIN32_BOOL_FALSE(CreateDirectoryW(m_state->m_socketDirectory.c_str(), &attributes)); + m_state->m_directoryCreated = true; + } + + UniqueConfig config; + THROW_IF_FAILED(WslOpenVmmCreateConfig(config.put())); + THROW_IF_FAILED(WslOpenVmmConfigSetKernelPath(config.get(), Request.Boot.KernelPath.c_str())); + THROW_IF_FAILED(WslOpenVmmConfigSetInitrdPath(config.get(), Request.Boot.InitrdPath.c_str())); + THROW_IF_FAILED(WslOpenVmmConfigSetKernelCmdLine(config.get(), m_state->m_description.Boot.KernelCommandLine.c_str())); + THROW_IF_FAILED(WslOpenVmmConfigSetMemoryMb(config.get(), m_state->m_description.Memory.SizeBytes / (1024 * 1024))); + THROW_IF_FAILED(WslOpenVmmConfigSetProcessorCount(config.get(), Request.Processor.Count)); + THROW_IF_FAILED(WslOpenVmmConfigSetHvSocketPath(config.get(), m_state->m_vsockPath.c_str())); + for (const auto& disk : Request.BootDisks) + { + const auto& attachment = m_state->m_description.BootDisks.at(disk.Key); + THROW_IF_FAILED(WslOpenVmmConfigAddBootDisk( + config.get(), + attachment.GuestAddress.Controller, + attachment.GuestAddress.Lun, + std::get(disk.Disk.Source).Path.c_str(), + disk.Disk.ReadOnly)); + } + for (const auto& console : Request.Consoles) + { + if (const auto* serial = std::get_if(&console.Device)) + { + THROW_IF_FAILED(WslOpenVmmConfigAddSerialPort(config.get(), serial->Port, serial->NamedPipe.c_str())); + } + else + { + THROW_IF_FAILED(WslOpenVmmConfigSetVirtioConsolePath(config.get(), std::get(console.Device).NamedPipe.c_str())); + } + } + + m_state->m_job = helpers::CreateKillOnCloseJob(); + const auto commandLine = + std::format(L"\"{}\" --rpc \"path={},transport=grpc\"", executable.native(), m_state->m_rpcSocketPath.native()); + SubProcess process{executable.c_str(), commandLine.c_str()}; + process.SetFlags(CREATE_NO_WINDOW); + process.SetJobObject(m_state->m_job.get()); + SECURITY_ATTRIBUTES inheritable{sizeof(inheritable), nullptr, TRUE}; + wil::unique_hfile logFile; + wil::unique_hfile input; + { + logFile.reset(CreateFileW( + (m_state->m_socketDirectory / L"openvmm.log").c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); + THROW_LAST_ERROR_IF(!logFile); + input.reset(CreateFileW( + L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); + THROW_LAST_ERROR_IF(!input); + } + // OpenVMM closes stdout at startup; stderr must have a distinct handle value. + wil::unique_hfile errorLogFile; + THROW_IF_WIN32_BOOL_FALSE( + DuplicateHandle(GetCurrentProcess(), logFile.get(), GetCurrentProcess(), errorLogFile.put(), 0, TRUE, DUPLICATE_SAME_ACCESS)); + process.SetStdHandles(input.get(), logFile.get(), errorLogFile.get()); + m_state->m_process = process.Start(); + m_state->m_processWait.reset(CreateThreadpoolWait(OnProcessExit, this, nullptr)); + THROW_LAST_ERROR_IF(!m_state->m_processWait); + SetThreadpoolWait(m_state->m_processWait.get(), m_state->m_process.get(), nullptr); + THROW_IF_FAILED_MSG( + WslOpenVmmCreateVm(config.addressof(), m_state->m_rpcSocketPath.c_str(), c_rpcTimeoutMs, m_state->m_vm.put()), + "Failed to create OpenVMM VM"); + const auto result = WaitForSingleObject(m_state->m_process.get(), 0); + THROW_LAST_ERROR_IF(result == WAIT_FAILED); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED), result == WAIT_OBJECT_0); +} + +void CALLBACK OpenVmmVirtualMachineBackend::OnProcessExit(PTP_CALLBACK_INSTANCE, void* Context, PTP_WAIT, TP_WAIT_RESULT) noexcept +{ + auto& backend = *static_cast(Context); + LOG_IF_WIN32_BOOL_FALSE(SetEvent(backend.m_state->m_exitEvent.get())); +} + +VmPlatformCapabilities OpenVmmVirtualMachineBackend::QueryCapabilities() +{ + VmPlatformCapabilities capabilities; + capabilities.Backend = BackendKind::OpenVmm; + // Report known OpenVMM support independently of which backend methods are wired through the C ABI. + for (const auto operation : + {VmOperation::Create, + VmOperation::Start, + VmOperation::Terminate, + VmOperation::CreateGuestListener, + VmOperation::AcceptGuestConnection, + VmOperation::ConnectGuest, + VmOperation::CloseGuestListener, + VmOperation::AttachDisk, + VmOperation::DetachDisk, + VmOperation::CreateFileSystemDevice, + VmOperation::AddFileSystemShare, + VmOperation::RemoveFileSystemShare, + VmOperation::RemoveDevice, + VmOperation::AddNetworkAdapter, + VmOperation::UpdateNetworkAdapter, + VmOperation::BindPort, + VmOperation::UnbindPort}) + { + capabilities.Operations.set(static_cast(operation)); + } + for (const auto feature : + {VmFeature::LinuxDirectBoot, + VmFeature::LinuxFirmwareBoot, + VmFeature::MemoryOvercommit, + VmFeature::SerialConsole, + VmFeature::VirtioConsole, + VmFeature::Vhd, + VmFeature::Vhdx, + VmFeature::VirtioFsFileBacked, + VmFeature::SavedStateOnCrash, + VmFeature::UserModeNatNetwork, + VmFeature::TcpPortBinding, + VmFeature::UdpPortBinding, + VmFeature::Ipv6PortBinding, + VmFeature::ScopedIpv6PortBinding}) + { + capabilities.Features.set(static_cast(feature)); + } + return capabilities; +} + +VmPlatformCapabilities OpenVmmVirtualMachineBackend::GetCapabilities() const +{ + return QueryCapabilities(); +} + +wil::unique_handle OpenVmmVirtualMachineBackend::GetTerminationEvent() const +{ + wil::unique_handle event; + THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle( + GetCurrentProcess(), m_state->m_exitEvent.get(), GetCurrentProcess(), event.put(), 0, FALSE, DUPLICATE_SAME_ACCESS)); + return event; +} + +void OpenVmmVirtualMachineBackend::Start() +{ + auto lock = m_state->m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); + THROW_IF_FAILED(WslOpenVmmVmResume(m_state->m_vm.get())); +} + +void OpenVmmVirtualMachineBackend::Terminate() +{ + auto lock = m_state->m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); + THROW_IF_FAILED(WslOpenVmmVmTeardown(m_state->m_vm.get())); + const auto quitResult = WslOpenVmmVmQuit(m_state->m_vm.get()); + const auto waitResult = WaitForSingleObject(m_state->m_process.get(), c_rpcTimeoutMs); + THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); + if (waitResult != WAIT_OBJECT_0) + { + THROW_IF_FAILED(quitResult); + THROW_HR(HRESULT_FROM_WIN32(WAIT_TIMEOUT)); + } + + m_state->m_vm.reset(); + m_state->m_attachedDisks.clear(); +} + +void OpenVmmVirtualMachineBackend::CancelPendingOperations() noexcept +{ + LOG_HR(E_NOTIMPL); +} + +VmGuestListener OpenVmmVirtualMachineBackend::CreateGuestListener(GuestServicePort) +{ + THROW_HR(E_NOTIMPL); +} + +wil::unique_socket OpenVmmVirtualMachineBackend::AcceptGuestConnection(VmListenerId) +{ + THROW_HR(E_NOTIMPL); +} + +wil::unique_socket OpenVmmVirtualMachineBackend::ConnectGuest(GuestServicePort) +{ + THROW_HR(E_NOTIMPL); +} + +void OpenVmmVirtualMachineBackend::CloseGuestListener(VmListenerId) +{ + THROW_HR(E_NOTIMPL); +} + +VmDiskAttachment OpenVmmVirtualMachineBackend::AttachDisk(const VmDiskRequest& Request) +{ + const auto& source = ValidateDiskRequest(Request); + auto backingFile = OpenBackingFile(source.Path, Request.ReadOnly); + auto lock = m_state->m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); + + const auto lunInUse = [&](std::uint32_t Lun) { + for (const auto& entry : m_state->m_attachedDisks) + { + if (entry.second.Attachment.GuestAddress.Lun == Lun) + { + return true; + } + } + return false; + }; + + std::uint32_t lun = 0; + if (Request.Placement) + { + lun = Request.Placement->Address.Lun; + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), lunInUse(lun)); + } + else + { + while (lun < c_maximumDisks && lunInUse(lun)) + { + ++lun; + } + THROW_HR_IF(WSL_E_TOO_MANY_DISKS_ATTACHED, lun == c_maximumDisks); + } + + THROW_HR_IF(E_BOUNDS, m_state->m_nextDiskId == UINT64_MAX); + const VmDiskAttachment attachment{ + {m_state->m_description.Identity, m_state->m_nextDiskId}, {0, lun}, Request.ReadOnly}; + const auto [disk, inserted] = m_state->m_attachedDisks.emplace( + attachment.Id.Value, State::AttachedDisk{attachment, std::move(backingFile)}); + WI_ASSERT(inserted); + auto rollback = wil::scope_exit([&] { m_state->m_attachedDisks.erase(disk); }); + THROW_IF_FAILED(WslOpenVmmVmAttachScsiDisk( + m_state->m_vm.get(), attachment.GuestAddress.Controller, attachment.GuestAddress.Lun, source.Path.c_str(), Request.ReadOnly)); + ++m_state->m_nextDiskId; + rollback.release(); + return attachment; +} + +void OpenVmmVirtualMachineBackend::DetachDisk(VmDiskId Disk) +{ + THROW_HR_IF(E_INVALIDARG, Disk.Value == 0 || !IsEqualGUID(Disk.Owner.VmId, m_state->m_description.Identity.VmId)); + auto lock = m_state->m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); + const auto disk = m_state->m_attachedDisks.find(Disk.Value); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), disk == m_state->m_attachedDisks.end()); + THROW_IF_FAILED(WslOpenVmmVmDetachScsiDisk( + m_state->m_vm.get(), disk->second.Attachment.GuestAddress.Controller, disk->second.Attachment.GuestAddress.Lun)); + m_state->m_attachedDisks.erase(disk); +} + +VmFileSystemDevice OpenVmmVirtualMachineBackend::CreateFileSystemDevice(const VmFileSystemDeviceRequest&) +{ + THROW_HR(E_NOTIMPL); +} + +VmFileSystemShare OpenVmmVirtualMachineBackend::AddFileSystemShare(VmDeviceId, const VmFileSystemShareRequest&) +{ + THROW_HR(E_NOTIMPL); +} + +void OpenVmmVirtualMachineBackend::RemoveFileSystemShare(VmShareId) +{ + THROW_HR(E_NOTIMPL); +} + +VmNetworkAttachment OpenVmmVirtualMachineBackend::AddNetworkAdapter(const VmNetworkAdapterRequest&) +{ + THROW_HR(E_NOTIMPL); +} + +VmPortBinding OpenVmmVirtualMachineBackend::BindPort(VmDeviceId, const VmPortBindingRequest&) +{ + THROW_HR(E_NOTIMPL); +} + +void OpenVmmVirtualMachineBackend::UnbindPort(VmPortBindingId) +{ + THROW_HR(E_NOTIMPL); +} \ No newline at end of file diff --git a/src/windows/service/exe/OpenVmmVirtualMachineBackend.h b/src/windows/service/exe/OpenVmmVirtualMachineBackend.h new file mode 100644 index 0000000000..4ad86152ee --- /dev/null +++ b/src/windows/service/exe/OpenVmmVirtualMachineBackend.h @@ -0,0 +1,94 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + OpenVmmVirtualMachineBackend.h + +Abstract: + + Implementation of IVirtualMachineBackend - represents a single OpenVMM-based VM instance. + +--*/ + +#pragma once + +#include "IVirtualMachineBackend.h" + +struct WslOpenVmmVm; + +namespace wsl::windows::common::vm::openvmm { + +VmDescription ValidateCreateRequest(const VmCreateRequest& Request); + +} + +class OpenVmmVirtualMachineBackend : public IVirtualMachineBackend +{ +public: + ~OpenVmmVirtualMachineBackend() noexcept override; + + static std::unique_ptr Create(const VmCreateRequest& Request); + + static VmPlatformCapabilities QueryCapabilities(); + + VmPlatformCapabilities GetCapabilities() const override; + wil::unique_handle GetTerminationEvent() const override; + void Start() override; + void Terminate() override; + void CancelPendingOperations() noexcept override; + + VmGuestListener CreateGuestListener(GuestServicePort Port) override; + wil::unique_socket AcceptGuestConnection(VmListenerId Listener) override; + wil::unique_socket ConnectGuest(GuestServicePort Port) override; + void CloseGuestListener(VmListenerId Listener) override; + + VmDiskAttachment AttachDisk(const VmDiskRequest& Request) override; + void DetachDisk(VmDiskId Disk) override; + + VmFileSystemDevice CreateFileSystemDevice(const VmFileSystemDeviceRequest& Request) override; + VmFileSystemShare AddFileSystemShare(VmDeviceId Device, const VmFileSystemShareRequest& Request) override; + void RemoveFileSystemShare(VmShareId Share) override; + + VmNetworkAttachment AddNetworkAdapter(const VmNetworkAdapterRequest& Request) override; + VmPortBinding BindPort(VmDeviceId Device, const VmPortBindingRequest& Request) override; + void UnbindPort(VmPortBindingId Binding) override; + +private: + OpenVmmVirtualMachineBackend(); + NON_COPYABLE(OpenVmmVirtualMachineBackend); + NON_MOVABLE(OpenVmmVirtualMachineBackend); + + static void CALLBACK OnProcessExit(PTP_CALLBACK_INSTANCE, void* Context, PTP_WAIT, TP_WAIT_RESULT) noexcept; + void Initialize(const VmCreateRequest& Request); + + static void DestroyVm(WslOpenVmmVm* Vm) noexcept; + using UniqueVm = wil::unique_any; + + struct State + { + struct AttachedDisk + { + VmDiskAttachment Attachment; + wil::unique_hfile BackingFile; + }; + + wil::srwlock m_lock; + VmDescription m_description; + _Guarded_by_(m_lock) std::map m_attachedDisks; + _Guarded_by_(m_lock) std::uint64_t m_nextDiskId = 1; + UniqueVm m_vm; + wil::unique_handle m_process; + wil::unique_handle m_job; + std::vector m_backingFiles; + std::filesystem::path m_socketDirectory; + std::filesystem::path m_rpcSocketPath; + std::filesystem::path m_vsockPath; + bool m_directoryCreated = false; + wil::unique_event m_exitEvent{wil::EventOptions::ManualReset}; + wil::unique_threadpool_wait m_processWait; + }; + + std::unique_ptr m_state; +}; \ No newline at end of file diff --git a/src/windows/service/exe/VirtualMachineBackend.cpp b/src/windows/service/exe/VirtualMachineBackend.cpp new file mode 100644 index 0000000000..f34d24407a --- /dev/null +++ b/src/windows/service/exe/VirtualMachineBackend.cpp @@ -0,0 +1,45 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + VirtualMachineBackend.cpp + +Abstract: + + Provides factory functions for creating and querying virtual machine backends. + +--*/ + +#include "precomp.h" +#include "IVirtualMachineBackend.h" +#include "OpenVmmVirtualMachineBackend.h" + +std::unique_ptr CreateVirtualMachineBackend(BackendKind Kind, const VmCreateRequest& Request) +{ + switch (Kind) + { + case BackendKind::OpenVmm: + return OpenVmmVirtualMachineBackend::Create(Request); + + case BackendKind::Hcs: + THROW_HR(E_NOTIMPL); + } + + THROW_HR(E_INVALIDARG); +} + +VmPlatformCapabilities QueryVirtualMachineBackendCapabilities(BackendKind Kind) +{ + switch (Kind) + { + case BackendKind::OpenVmm: + return OpenVmmVirtualMachineBackend::QueryCapabilities(); + + case BackendKind::Hcs: + THROW_HR(E_NOTIMPL); + } + + THROW_HR(E_INVALIDARG); +} \ No newline at end of file diff --git a/test/windows/CMakeLists.txt b/test/windows/CMakeLists.txt index 6786f735ed..9e3a0c2161 100644 --- a/test/windows/CMakeLists.txt +++ b/test/windows/CMakeLists.txt @@ -25,6 +25,17 @@ add_compile_definitions(INLINE_TEST_METHOD_MARKUP) add_library(wsltests SHARED ${SOURCES} ${HEADERS}) +target_sources(wsltests PRIVATE OpenVmmVirtualMachineBackendTests.cpp) +target_link_libraries(wsltests virtualmachinebackend) +add_dependencies(wsltests initramfs) + +add_custom_command( + TARGET wsltests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${KERNEL_SOURCE_DIR}/bin/${TARGET_PLATFORM}/kernel" + "$/kernel" + VERBATIM) + target_include_directories(wsltests PRIVATE ${CMAKE_SOURCE_DIR}/src/windows/WslcSDK ${CMAKE_BINARY_DIR}/src/windows/WslcSDK/winrt/${TARGET_PLATFORM}/${CMAKE_BUILD_TYPE}) diff --git a/test/windows/OpenVmmVirtualMachineBackendTests.cpp b/test/windows/OpenVmmVirtualMachineBackendTests.cpp new file mode 100644 index 0000000000..52b0705493 --- /dev/null +++ b/test/windows/OpenVmmVirtualMachineBackendTests.cpp @@ -0,0 +1,234 @@ +#include "precomp.h" +#include "Common.h" +#include "OpenVmmVirtualMachineBackend.h" + +using wsl::windows::common::vm::openvmm::ValidateCreateRequest; + +namespace { + +constexpr UINT64 c_mib = 1024 * 1024; +constexpr HRESULT c_notSupported = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + +VmCreateRequest CreateRequest() +{ + VmCreateRequest request; + THROW_IF_FAILED(CoCreateGuid(&request.VmId)); + request.Processor.Count = 2; + request.Memory.SizeBytes = 512 * c_mib; + request.Boot.KernelPath = L"C:\\images\\kernel"; + request.Boot.InitrdPath = L"C:\\images\\initrd"; + return request; +} + +VmBootDiskRequest CreateDisk(std::wstring Key) +{ + VmBootDiskRequest disk; + disk.Key = std::move(Key); + disk.Disk.Source = VmVirtualDiskSource{L"C:\\images\\disk.vhdx", VmDiskFormat::Vhdx}; + return disk; +} + +HRESULT DescribeResult(const VmCreateRequest& Request) +{ + return wil::ResultFromException([&] { ValidateCreateRequest(Request); }); +} + +} // namespace + +namespace OpenVmmVirtualMachineBackendTests { + +class OpenVmmVirtualMachineBackendTests +{ + WSL_TEST_CLASS(OpenVmmVirtualMachineBackendTests) + + TEST_METHOD(PreservesCallerIdentityAndBootInputs) + { + SKIP_TEST_ARM64(); + auto request = CreateRequest(); + request.Boot.GuestCommandLine = L"personality=caller console=hvc0"; + request.Boot.UserCommandLine = L"console=ttyS0 custom=value"; + request.BootDisks.push_back(CreateDisk(L"arbitrary-key")); + request.BootDisks[0].Disk.ReadOnly = false; + const auto description = ValidateCreateRequest(request); + + VERIFY_IS_TRUE(IsEqualGUID(request.VmId, description.Identity.VmId)); + VERIFY_ARE_EQUAL(request.Processor.Count, description.Processor.Count); + VERIFY_ARE_EQUAL(request.Memory.SizeBytes, description.Memory.SizeBytes); + VERIFY_ARE_EQUAL(VmBootMethod::LinuxDirect, description.Boot.Method); + VERIFY_ARE_EQUAL(request.Boot.GuestCommandLine + L" " + request.Boot.UserCommandLine, description.Boot.KernelCommandLine); + VERIFY_ARE_EQUAL(size_t{1}, description.BootDisks.size()); + const auto& disk = description.BootDisks.at(L"arbitrary-key"); + VERIFY_IS_TRUE(IsEqualGUID(request.VmId, disk.Id.Owner.VmId)); + VERIFY_ARE_EQUAL(UINT64{1}, disk.Id.Value); + VERIFY_ARE_EQUAL(UINT32{0}, disk.GuestAddress.Lun); + VERIFY_IS_FALSE(disk.ReadOnly); + } + + TEST_METHOD(ReservesExactPlacementsBeforeAutomaticDisks) + { + SKIP_TEST_ARM64(); + auto request = CreateRequest(); + request.BootDisks = {CreateDisk(L"automatic-1"), CreateDisk(L"automatic-2"), CreateDisk(L"exact")}; + request.BootDisks[2].Disk.Placement = VmScsiPlacement{{0, 0}}; + const auto description = ValidateCreateRequest(request); + VERIFY_ARE_EQUAL(UINT32{1}, description.BootDisks.at(L"automatic-1").GuestAddress.Lun); + VERIFY_ARE_EQUAL(UINT32{2}, description.BootDisks.at(L"automatic-2").GuestAddress.Lun); + VERIFY_ARE_EQUAL(UINT32{0}, description.BootDisks.at(L"exact").GuestAddress.Lun); + + request.BootDisks[0].Disk.Placement = VmScsiPlacement{{0, 0}}; + VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); + request.BootDisks[0].Disk.Placement.reset(); + request.BootDisks[1].Key = request.BootDisks[0].Key; + VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); + } + + TEST_METHOD(DoesNotCapMemoryAndRequiresGranularSizing) + { + SKIP_TEST_ARM64(); + auto request = CreateRequest(); + request.Memory.SizeBytes = 4096 * c_mib; + VERIFY_ARE_EQUAL(request.Memory.SizeBytes, ValidateCreateRequest(request).Memory.SizeBytes); + request.Memory.SizeBytes += 2 * c_mib; + VERIFY_ARE_EQUAL(c_notSupported, DescribeResult(request)); + request.Memory.SizeBytes = 33 * c_mib; + VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); + request.Memory.SizeBytes = c_mib; + VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); + request.Memory.SizeBytes = 0; + VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); + request.Memory.SizeBytes = 2 * c_mib; + VERIFY_ARE_EQUAL(request.Memory.SizeBytes, ValidateCreateRequest(request).Memory.SizeBytes); + } + + TEST_METHOD(RejectsInvalidDiskFormats) + { + SKIP_TEST_ARM64(); + auto request = CreateRequest(); + request.BootDisks.push_back(CreateDisk(L"disk")); + request.BootDisks[0].Disk.Source = VmVirtualDiskSource{L"C:\\images\\disk.vhd", VmDiskFormat::Vhdx}; + VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); + } + + TEST_METHOD(EnforcesDiskLimitsAndKeepsIdsVmScoped) + { + SKIP_TEST_ARM64(); + auto request = CreateRequest(); + for (UINT32 index = 0; index < 254; ++index) + { + request.BootDisks.push_back(CreateDisk(std::to_wstring(index))); + } + const auto first = ValidateCreateRequest(request); + VERIFY_ARE_EQUAL(UINT32{253}, first.BootDisks.at(L"253").GuestAddress.Lun); + THROW_IF_FAILED(CoCreateGuid(&request.VmId)); + const auto second = ValidateCreateRequest(request); + VERIFY_ARE_EQUAL(first.BootDisks.at(L"0").Id.Value, second.BootDisks.at(L"0").Id.Value); + VERIFY_IS_FALSE(IsEqualGUID(first.BootDisks.at(L"0").Id.Owner.VmId, second.BootDisks.at(L"0").Id.Owner.VmId)); + request.BootDisks.push_back(CreateDisk(L"overflow")); + VERIFY_ARE_EQUAL(c_notSupported, DescribeResult(request)); + } + + TEST_METHOD(ValidatesConsoleFamiliesIndependently) + { + SKIP_TEST_ARM64(); + auto request = CreateRequest(); + request.Consoles = { + {VmConsoleRole::EarlyBoot, VmSerialConsole{0, L"\\\\.\\pipe\\early"}}, + {VmConsoleRole::KernelConsole, VmVirtioConsole{0, L"", L"\\\\.\\pipe\\console"}}}; + VERIFY_ARE_EQUAL(size_t{2}, ValidateCreateRequest(request).Boot.Consoles.size()); + request.Consoles.push_back(request.Consoles[0]); + VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); + request.Consoles.pop_back(); + std::get(request.Consoles[1].Device).GuestName = L"unsupported-name"; + VERIFY_ARE_EQUAL(c_notSupported, DescribeResult(request)); + } + + TEST_METHOD(RejectsInvalidIdentityAndBootPaths) + { + SKIP_TEST_ARM64(); + auto request = CreateRequest(); + request.VmId = GUID_NULL; + VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); + THROW_IF_FAILED(CoCreateGuid(&request.VmId)); + request.Boot.KernelPath = L"relative-kernel"; + VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); + request.Boot.KernelPath = L"C:\\images\\kernel"; + request.Boot.Method = VmBootMethod::Uefi; + VERIFY_ARE_EQUAL(c_notSupported, DescribeResult(request)); + } + + TEST_METHOD(BootsAndTerminates) + { + SKIP_TEST_ARM64(); + auto request = CreateRequest(); + const auto basePath = wsl::windows::common::wslutil::GetBasePath(); + request.Boot.KernelPath = basePath / L"kernel"; + request.Boot.InitrdPath = basePath / LXSS_VM_MODE_INITRD_NAME; + request.Boot.GuestCommandLine = L"panic=-1"; + + auto backend = OpenVmmVirtualMachineBackend::Create(request); + auto terminationEvent = backend->GetTerminationEvent(); + backend->Start(); + + const auto runningResult = WaitForSingleObject(terminationEvent.get(), 100); + VERIFY_ARE_EQUAL(static_cast(WAIT_TIMEOUT), runningResult); + if (runningResult == WAIT_TIMEOUT) + { + backend->Terminate(); + } + + VERIFY_ARE_EQUAL(static_cast(WAIT_OBJECT_0), WaitForSingleObject(terminationEvent.get(), 30 * 1000)); + } + + TEST_METHOD(CapabilitiesReflectVmServiceProtocol) + { + const auto capabilities = OpenVmmVirtualMachineBackend::QueryCapabilities(); + VERIFY_ARE_EQUAL(BackendKind::OpenVmm, capabilities.Backend); + + decltype(capabilities.Operations) expectedOperations; + for (const auto operation : + {VmOperation::Create, + VmOperation::Start, + VmOperation::Terminate, + VmOperation::CreateGuestListener, + VmOperation::AcceptGuestConnection, + VmOperation::ConnectGuest, + VmOperation::CloseGuestListener, + VmOperation::AttachDisk, + VmOperation::DetachDisk, + VmOperation::CreateFileSystemDevice, + VmOperation::AddFileSystemShare, + VmOperation::RemoveFileSystemShare, + VmOperation::RemoveDevice, + VmOperation::AddNetworkAdapter, + VmOperation::UpdateNetworkAdapter, + VmOperation::BindPort, + VmOperation::UnbindPort}) + { + expectedOperations.set(static_cast(operation)); + } + VERIFY_IS_TRUE(capabilities.Operations == expectedOperations); + + decltype(capabilities.Features) expectedFeatures; + for (const auto feature : + {VmFeature::LinuxDirectBoot, + VmFeature::LinuxFirmwareBoot, + VmFeature::MemoryOvercommit, + VmFeature::SerialConsole, + VmFeature::VirtioConsole, + VmFeature::Vhd, + VmFeature::Vhdx, + VmFeature::VirtioFsFileBacked, + VmFeature::SavedStateOnCrash, + VmFeature::UserModeNatNetwork, + VmFeature::TcpPortBinding, + VmFeature::UdpPortBinding, + VmFeature::Ipv6PortBinding, + VmFeature::ScopedIpv6PortBinding}) + { + expectedFeatures.set(static_cast(feature)); + } + VERIFY_IS_TRUE(capabilities.Features == expectedFeatures); + } +}; + +} \ No newline at end of file From 3020b12075f0645af7e59d599461dfd0cdfba50d Mon Sep 17 00:00:00 2001 From: Daman Mulye Date: Mon, 21 Sep 2026 11:11:44 -0700 Subject: [PATCH 02/10] . --- WSL-openvmm.md | 281 ------------------------------------------------- 1 file changed, 281 deletions(-) delete mode 100644 WSL-openvmm.md diff --git a/WSL-openvmm.md b/WSL-openvmm.md deleted file mode 100644 index 3b070d83ee..0000000000 --- a/WSL-openvmm.md +++ /dev/null @@ -1,281 +0,0 @@ -# OpenVMM WSL implementation tracker - -## Current-stack assessment (2026-09-14) - -This assessment compares each local branch with the branch below it, starting at `master` (`4bfbacae`). The original audit covered backend tip `13daf737`; the snapshot below includes the local rebase, committed PR 3 and PR 5 follow-ups, and uncommitted PR 6 RPC work. The PR numbers below are proposed work packages, not existing GitHub PR numbers. - -| Layer | Actual branch and tip | Work present | -|---|---|---| -| refactor | `user/damanmmulye/wsl-openvmm-refactor` at `b956db18` | `IWslCoreVm`, HCS implementation adaptation, session/interface plumbing, guest connection entry point, accepted ownership comments, and lifecycle regression. | -| rpc | `user/damanmulye/wsl-openvmm-rpc` at `dd6dc8ed` plus uncommitted C2 follow-up | Rust DLL/FFI, VM/resource RPCs, bounded AF_UNIX/gRPC calls, fail-closed recovery, cancellation, error categories, and transport regressions. | -| backend | `user/damanmmulye/wsl-openvmm-backend` at `b8b146cf` | WSL backend selection, process/VM lifecycle, guest transport wiring, VirtioFS, initial networking, console logging, private RPC socket and gated packaging; accepted selection/rollback policy and selection regressions. | - -**Legend:** `[x] Implemented` means the scoped WSL implementation is present in source, not that it has been built, run, merged, or approved for release. `[ ] Partial` means useful work exists but the bullet still has a gap or an unresolved design deviation. `[ ] TODO` means the requested outcome is not evidenced by this stack. `[ ] External` means completion must be established outside this WSL stack; it does not mean work in OpenVMM or offline design discussions has not happened. - -**Scope totals:** 14 implemented, 10 partial, 19 TODO, 7 external (50 unique bullets). Split validation bullets are counted once; G4 is partial overall because coverage is limited to selected mock/transport and early configuration-failure cases. The implemented bullets are **A3, B1, B2, B3, B4, B5, B6, B7, C1, C2, C5, C7, D1, and D4**. These totals include the accepted PR 3 and PR 5 decisions and the PR 6 RPC-layer closeout for C2/C7; backend follow-ups remain explicitly tracked below. - -The stack does not add WSLC backend call-site integration: the separate WSLC prototype from the earlier summary is not credited as completed work here. PR 3 adds a Windows lifecycle regression; PR 5 adds selection regressions and policy documentation; the uncommitted PR 6 follow-up expands the Rust transport coverage. End-to-end results, baselines, and rollout decisions cannot be inferred from a commit named "boot successful". - -**Important differences from the original plan:** - -- **Accepted PR 3/PR 5 design:** `IWslCoreVm` is the service-facing backend contract. `WslCoreVm` remains the HCS implementation; OpenVMM is a sibling implementation, not a backend underneath a shared `WslCoreVm` facade. This explicitly replaces the original lower-level extraction in A3/B1/B2, rather than claiming that extraction happened. The dedicated factory was removed by `2caf8dba`; `LxssUserSessionImpl::_CreateVm()` is accepted as B3's centralized creation/selection point. A2's full lifecycle contract remains separate follow-up work. -- **Accepted PR 5 policy:** HCS is the default; explicit OpenVMM opt-in fails rather than silently falling back when unavailable or when initialization fails. Rollback is manual: disable the setting and shut down WSL. A live VM retains its recorded backend until shutdown. -- **Accepted PR 6 contract:** Keep gRPC over AF_UNIX, not ttrpc. Replace transparent reconciliation with fail-closed recovery after uncertain mutations; teardown and fresh-process recreation are required. -- **C2/C7 closeout:** Close these bullets for the accepted RPC-layer scope: bounded, fail-closed RPC behavior and ordinary debugger-output tracing macros. Backend cancellation/lifetime integration, recreation-path coverage, and broader service/process diagnostics remain follow-ups, not claims of completed end-to-end integration. -- Mixed admin/non-admin access is **not implemented**: `InitializeDrvFs` and `AddVirtioFsShare` reject elevation different from the VM creator. Pass-through disks are also explicitly unsupported. -- Memory remains capped at 4 GiB. GUI/GPU, debug shell, and DNS tunneling are disabled in this backend configuration; pmem and virtio-rng configuration are not wired through the new RPC builder. -- Console/dmesg capture is implemented, but it is not kernel-panic extraction or a saved-state/crash-artifact collection pipeline. - -### Source evidence - -S1-S10 paths and line numbers refer to the original audited tips; S11-S13 identify committed follow-ups; S14 identifies uncommitted RPC diagnostics. Source IDs identify implementation evidence, not successful runtime results. - -| ID | Evidence | -|---|---| -| S1 | `src\windows\service\exe\IWslCoreVm.h:8-88`; `src\windows\service\exe\WslCoreVm.h:43` (`WslCoreVm : IWslCoreVm`). Refactor commit `2caf8dba` removes the dedicated factory. | -| S2 | `src\windows\service\exe\LxssUserSession.cpp:2999-3028` (inline selection and failure cleanup), `:2211-2239` (backend-specific force termination); `src\windows\common\WslCoreConfig.h:298,388` (opt-in key/default); `CMakeLists.txt:44` (build gate defaults off). | -| S3 | `src\shared\inc\SocketChannel.h:620-749` (AF_UNIX I/O); `src\windows\service\exe\LxssCreateProcess.h:54,76-111`; `src\windows\service\exe\WslCoreInstance.cpp:38-50,244-247,439-442,550-580`; `src\windows\service\exe\OpenVmmWslCoreVm.cpp:38-101` (guest bridge). | -| S4 | `src\windows\wslopenvmm\src\af_unix.rs:14-45` (connect retries/timeouts); `src\windows\wslopenvmm\src\client.rs:43-79,363-383` (Tonic client, deadlines, HRESULT mapping); `src\windows\service\exe\OpenVmmWslCoreVm.cpp:459-462` (`transport=grpc`). | -| S5 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:117-187,207-256,540-586,984-1026,1335-1392` (launch, cleanup, teardown/quit, process wait and callbacks). | -| S6 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:258-350,464-538` (configuration overrides, 4-GiB cap, boot/device setup); `src\windows\wslopenvmm\src\client.rs:81-172` (configuration builder). | -| S7 | `src\windows\service\exe\VirtioFsShareRequest.cpp:8-68`; `src\windows\service\exe\OpenVmmWslCoreVm.cpp:653-768,1247-1263,1393-1406` (share requests, worker, elevation restriction); `:1035-1039` (pass-through rejection); `src\windows\wslopenvmm\src\client.rs:223-275` (share RPCs). | -| S8 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:515-521,807-875` (consomme NIC, DHCP, IPv6 enabled, port tracker); `src\windows\wslopenvmm\src\client.rs:313-349` (IPv4/IPv6 localhost port requests). | -| S9 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:342,386-430,554-577,984-1012` (dump-count override, collector/debug console, stdout/stderr log, process-exit trace); `src\windows\common\Dmesg.cpp:45-78,156-211` (pipe access and raw guest-log capture, not panic parsing). | -| S10 | `src\windows\wslopenvmm\src\client\tests.rs:86-110,133-207`: TCP-loopback mock server; port protocol/address-family assertions and share add/remove failure/retry assertions. No real VM or AF_UNIX connection/recovery test. | -| S11 | PR 3 commit `b956db18` on the refactor branch: contract comments in `src\windows\service\exe\IWslCoreVm.h` and `WslCoreVm.h`; `SimpleTests::VmBackendShutdownAndReconnect` in `test\windows\SimpleTests.cpp`. Execution is delegated to CI, with results not yet recorded here. | -| S12 | PR 5 commit `b8b146cf` on the backend branch: selection matrix in `doc\docs\technical-documentation\wslservice.exe.md`; parser, backend identity, failure/retry, live-configuration shutdown, and rollback cases in `test\windows\VmBackendTests.cpp`; invalid-boolean warning assertion in `UnitTests.cpp`; source registration, availability definition, and `configfile` linkage in `test\windows\CMakeLists.txt`. | -| S13 | PR 6 RPC follow-up committed as `3e729f2d`: `src\windows\wslopenvmm\src\rpc.rs`, `af_unix.rs`, `client.rs`, `lib.rs`, and `client\tests.rs`; private exports in `wslopenvmm.h`; revised contract in the local `src\windows\wslopenvmm\README.md`. Detailed coverage and remaining backend integration are in the PR 6 page. | -| S14 | Uncommitted C7 RPC diagnostics: epci2-style once-initialized debugger subscriber and stack-backed writer in `src\windows\wslopenvmm\src\diagnostics.rs`; initialization and ordinary tracing macros in `client.rs` and `rpc.rs`. Messages omit payloads and server error text. The accepted approach uses debugger output, not an ETW provider, custom event schemas, or request correlation metadata. | - -## Original plan, annotated by scope bullet - -Organize this as small, dependency-ordered PRs—not one PR per deliverable. The main sequence should be contracts → HCS-preserving abstraction → OpenVMM boot → filesystem/networking compatibility → diagnostics → rollout gates. Start WHP memory work in parallel; it should block removing the memory cap, not the initial capped-memory backend. - -The order below maps all 50 description bullets into proposed PRs. Some validation bullets intentionally span an early foundation PR and a later completion PR. - -Account for work you already have - -These are foundations to consume, not features to implement again. Merged PRs do not, by themselves, establish completion of their broader deliverables. - -| Area | Existing work | Planning implication | -| --- | --- | --- | -| Backend prototype | WSL #40629, open; your current WSL branch also contains backend and private AF_UNIX RPC work | Extract cohesive changes into the backend PRs below rather than starting over. | -| VirtioFS | OpenVMM #3821, WSL #41129, WSL #41151, all merged | Focus on OpenVMM integration and identity/elevation gaps, not rebuilding aggregate shares. | -| Networking | OpenVMM #2398, IPv6, merged; #4378, control/data-path separation, open | Consume existing IPv6 support and identify the remaining integration gaps. Treat the networking refactor as a dependency only where needed. | -| RPC configuration | OpenVMM #4420, open | Land the required network/filesystem RPC capabilities before their WSL consumers. | -| Crash artifacts | OpenVMM #3882, triple-fault .vmrs, merged | Extend and integrate the existing mechanism; distinguish triple faults from kernel panics and host-process crashes. | - -At the initial ADO lookup, all seven deliverables said Proposed. That state is not a reliable measure of implementation progress; the checklist below records local-stack evidence separately. - -Proposed PR order - -References:  A1  means the first Scope bullet in deliverable A. - -| Key | Deliverable | -| --- | --- | -| A | 62679114 — Architecture and compatibility matrix | -| B | 63428739 — Pluggable VM backends | -| C | 63428740 — OpenVMM backend | -| D | 63428744 — Filesystem and networking | -| E | 63428746 — Memory elasticity and nested virtualization | -| F | 63428745 — Crash diagnostics | -| G | 63428747 — Compatibility and regression validation | - -### PR 1 — Architecture, compatibility, and ownership contract - -Design/documentation PR. First; approve the relevant decisions before implementing their consumers. - -- [ ] **TODO - A1:** Define the supported WSL and WSLC scenario-compatibility matrix. No matrix is added by this stack. -- [ ] **Partial - A2:** Define `IWslVmBackend` responsibilities and lifecycle contract. `IWslCoreVm` and lifecycle implementations exist (refactor/backend; S1, S5), but the approved contract must reflect the actual interface and ownership model. -- [x] **Implemented - A3:** Define the boundary between service orchestration and HCS-specific behavior. The sibling HCS/OpenVMM design is explicitly accepted and documented in PR 3 below and the interface/class comments (S1, S11). This supersedes the originally proposed split within `WslCoreVm`. -- [ ] **Partial - A4:** Define backend selection, feature control, rollback, and configuration behavior. Compile-time and `.wslconfig` gates and the accepted selection/failure/manual-rollback matrix are documented (backend; S2, S12). The broader unsupported-setting compatibility contract remains outstanding. -- [ ] **Partial - A5:** Define the guest communication abstraction for HvSocket and vsock. Code implements callbacks and the guest bridge (refactor/backend; S3); the reviewed transport/lifecycle contract is not evidenced. -- [ ] **Partial - A6:** Record the initial VirtioFS-only and consomme-only constraints. These are enforced by configuration overrides (backend; S6), but a reviewed compatibility/limitations document is still needed. -- [ ] **TODO - A7:** Identify repository, component, and DRI ownership for every gap. No ownership table is added. -- [ ] **TODO - A8:** Resolve ownership overlap between scenarios 62917985 and 61024686. No recorded resolution is evidenced by the branch changes. -- [ ] **TODO - D3:** Design elevation/broker behavior for pass-through disk file opens. The current backend rejects non-VHD disks and does not implement a pass-through broker (backend; S7). -- [ ] **TODO - D6:** Define the migration path to converged WSL networking. Forcing consomme in configuration is not a migration plan (S6). -- [ ] **TODO - F5:** Define artifact retention, size, privacy, and upload behavior. Socket/pipe ACLs and local logging exist, but no artifact policy is added (S9). - -Keep this focused on decisions, not implementations. In particular, define when fallback is allowed; do not leave “safe fallback” to become an arbitrary retry after a partially created VM. - -### PR 2 — Backend comparison harness and baseline measurements - -WSL test/infrastructure PR. Start after PR 1; develop alongside the implementation. - -- [ ] **TODO - G1:** Create the end-to-end matrix for WSL and WSLC on HCS and OpenVMM. Mock RPC tests are not a backend matrix (S10). -- [ ] **TODO - G5, foundation:** Establish measurement tooling and HCS startup, memory, CPU, I/O, networking, and reliability baselines; collect OpenVMM results once available. No benchmark harness or baseline results are added. -- [ ] **TODO - G7, definition:** Agree preview/GA pass rates and regression thresholds before deciding whether results are acceptable. No threshold definitions are added. - -This is infrastructure, not a reason to defer feature-specific tests until the end. - -### PR 3 — Isolate the HCS backend behind the accepted service contract - -WSL PR. Depends on PR 1. - -- [x] **Implemented - B1 (accepted revised scope):** Isolate HCS-specific VM operations from service callers behind `IWslCoreVm`, retaining HCS ownership in `WslCoreVm` (S1, S11). -- [x] **Implemented - B2 (accepted revised scope):** Put existing HCS behavior behind the service-facing backend contract; use sibling HCS/OpenVMM implementations rather than a shared facade (S1, S11). -- [x] **Implemented - B6:** Preserve HCS initialization, networking, VirtioFS, shutdown, and error telemetry, with regression coverage (S11). - -Keep OpenVMM implementation out of this PR. Its review question should be: does the abstraction preserve HCS behavior? - -[PR 3 details: ownership, test coverage, CI sign-off, and build evidence](WSL-openvmm/PR-3.md). - -### PR 4 — Make guest control channels transport-neutral - -WSL PR. Depends on the agreed transport contract and PR 3. - -- [x] **Implemented - B5:** Abstract guest control channels away from the HvSocket-specific implementation. `ConnectToGuest` and connector callbacks are wired through instance/process/session creation; `SocketChannel` handles AF_UNIX separately from existing Windows I/O (refactor/backend; S3). - -Introduce and exercise the abstraction with existing behavior first. Do not conflate the host-side gRPC socket with the guest-control transport; they are separate contracts. - -### PR 5 — Backend selection, fail-fast behavior, and manual rollback - -WSL PR. Depends on PRs 3–4. - -- [x] **Implemented - B3 (accepted revised scope):** Centralize WSL VM creation/selection in `_CreateVm()` with `IWslCoreVm` callers; no separate factory is required (S1, S2, S12). -- [x] **Implemented - B4 (accepted policy):** Keep HCS as default, OpenVMM explicitly opt-in, failures explicit, and rollback manual after shutdown (S2, S12). -- [x] **Implemented - B7:** Add configuration-parsing and integration coverage for backend selection, failure handling, shutdown, and rollback (S12). - -[PR 5 details: accepted policy, test cases, CI requirements, and build evidence](WSL-openvmm/PR-5.md). - -### PR 6 — OpenVMM process supervision and gRPC client - -WSL PR. Depends on the contracts and shared abstractions. - -- [x] **Implemented - C1:** Implement process launch, lifetime, and termination handling. User-token launch, kill-on-close job, process registry/wait, cleanup, timeout-based forced termination, and exit callbacks are present (backend; S5). -- [x] **Implemented - C2 (accepted RPC-layer scope):** Bounded gRPC/AF_UNIX calls, fail-closed status handling, cancellation, and HRESULT mapping are implemented with client-owned per-handle synchronization (rpc; S13). Closed for this scope; backend lifecycle integration remains a separate follow-up. -- [x] **Implemented - C7 (accepted logging scope):** Existing backend traces, distinct RPC error categories, and ordinary tracing macros through the once-initialized debugger subscriber are implemented (S3, S5, S7, S9, S13, S14). No ETW provider, structured event schema, or correlation metadata is required for closeout. -- [ ] **Partial - G4, transport portion:** AF_UNIX startup, deadlines, cancellation races, fail-closed status, and silent/wrong-protocol peers have regression coverage (rpc; S13). Remaining: actual process crashes, lost-response resource state, and service shutdown races. - -**Backend follow-ups retained outside the C2/C7 closeout:** - -- [ ] Wire cancellation into process exit/termination, coordinate RPC-handle lifetime, and cover teardown followed by fresh-process recovery through service call sites. -- [ ] Integrate service/process diagnostics and define the backend-wide failure taxonomy. - -Your current AF_UNIX work belongs here. Distinguish establishing/re-establishing a connection from replaying a VM-management operation whose outcome is unknown. - -[PR 6 details: revised RPC contract, regression coverage, and remaining backend integration](WSL-openvmm/PR-6.md). - -### PR 7 — Configure, boot, and manage an OpenVMM VM - -WSL PR. Depends on PRs 4–6 and the required upstream RPC/device capabilities. - -- [ ] **Partial - C3:** Translate WSL VM settings into OpenVMM configuration. Kernel/initrd/modules, command line, CPU, capped memory, disks and NIC are translated (rpc/backend; S6). Several settings are forcibly disabled/overridden; complete or explicitly approve the supported-setting matrix. -- [ ] **Partial - C4:** Configure boot, memory, processors, serial, vsock, disks, pmem, and virtio-rng. Boot/CPU/memory/serial/virtio-console/vsock/SCSI configuration exists (rpc/backend; S6); pmem and virtio-rng are not configured by the new builder. Sending boot entropy is not virtio-rng support. -- [x] **Implemented - C5:** Implement start, stop, shutdown, terminate, and unexpected-exit handling. Create/resume, channel shutdown, teardown/quit, timed force termination, process-exit signaling and session callback routing are wired (rpc/backend; S2, S5). Runtime reliability coverage is tracked separately in G2/G4. -- [ ] **TODO - G2, initial slice:** Automate boot, distro launch, basic disk, vsock, console, and shutdown scenarios. The stack contains implementation and mock RPC tests, not real-VM scenario automation (S10). -- [ ] **Partial - G4, configuration portion:** Add malformed-configuration negative tests. PR 5 adds invalid backend-boolean parsing/warnings and early unsupported-system-distro failure/retry coverage (S12). Broader malformed VM/RPC configuration and post-allocation failure cases remain outstanding (S6, S10). - -This is the first usable, gated backend milestone, initially retaining the memory cap. Consume already-implemented boot/RPC functionality rather than duplicating it. - -### PR 8 — VirtioFS integration and mixed-elevation access - -WSL integration PR. Depends on PR 7 and upstream filesystem capabilities. - -- [x] **Implemented - D1:** Implement VirtioFS-based cross-OS filesystem access. Share request/response handling, guest listener/worker, canonical host paths, read-only options, and VPCI share RPCs are connected (rpc/backend; S7). This is creator-elevation access; D2 remains separate. -- [ ] **TODO - D2:** Support admin and non-admin Windows file access from the same VM. `AddVirtioFsShare` rejects `Admin != m_creatorElevated`; `InitializeDrvFs` explicitly rejects switching elevation context after creation (backend; S7). The guard is a limitation, not mixed-elevation support. - -Apply the broker/identity decisions from PR 1. If additional OpenVMM or DeviceHost mechanisms are required, land those as separate prerequisite PRs; do not bundle cross-repository implementation into this WSL PR. - -### PR 9 — Consommé networking integration and compatibility - -WSL integration PR. Depends on PR 7 and the required OpenVMM networking/RPC changes. - -- [x] **Implemented - D4:** Integrate initial consomme networking. NIC configuration, mini_init networking/DHCP setup, port tracker and localhost bind/unbind RPCs are wired (rpc/backend; S8). -- [ ] **Partial - D5:** Consume the required consomme IPv6 changes. Guest configuration enables IPv6 and RPCs handle `AF_INET6`/`::1`, with mock field assertions (rpc/backend; S8, S10). Confirm the consumed OpenVMM version satisfies the upstream dependency and exercise real IPv6 behavior. -- [ ] **TODO - D7:** Validate DNS, localhost, VPN, proxy, firewall, IPv6, and multi-distro behavior. Configuration checks and mock port serialization are not networking compatibility results; no such matrix/automation is added. - -PRs 8 and 9 can proceed independently. Existing IPv6 support is a starting point—not proof that the entire WSL networking matrix passes. - -### PR 10 — Complete the OpenVMM crash-artifact producer - -OpenVMM PR. Can proceed in parallel once the artifact contract is agreed. - -- [ ] **External - F1:** Complete the mechanism to produce a VM saved-state or crash artifact. Earlier evidence identified merged OpenVMM #3882 for triple faults; this WSL stack neither implements nor proves the complete producer contract. Track upstream completion and consumption separately. -- [ ] **External - F2:** Add the compatible compression writer for the selected format, or update the consumer. Neither change is present in this WSL stack; verify the selected upstream format and remaining consumer work. - -Build on merged #3882. If the chosen solution instead changes the consumer, place F2 in PR 11, rather than implementing both approaches. - -The previously observed OpenVMM `Add crash dump path option` commit belongs to this diagnostics work, not the network/filesystem RPC story in #4420. It is outside the WSL stack assessed here. - -### PR 11 — WSL/WSLC diagnostic collection and debugger integration - -WSL PR. Depends on PR 7; artifact collection additionally depends on PR 10. - -- [ ] **TODO - C6:** Add kernel debugger support. Debug console/early-console output is wired, but OpenVMM kernel-debugger configuration is not (backend; S6, S9). Do not count a debug console as a debugger. -- [ ] **Partial - F3:** Integrate artifact collection into WSL and WSLC diagnostics. WSL reuses `DmesgCollector`, adds user-accessible console pipes, and writes OpenVMM stdout/stderr locally (backend; S9). Saved-state/crash-artifact collection and WSLC integration are not added. -- [ ] **TODO - F4:** Extract kernel-panic details from dmesg collector output. The reused collector buffers/emits raw guest log lines; the stack adds pipe access, not panic parsing or attribution (S9). -- [ ] **TODO - F6:** Update log-collection scripts for OpenVMM logs and traces. No `diagnostics` scripts change; creating a local `.log` file is only a prerequisite. -- [ ] **Partial - F7:** Add telemetry for dump success/failure, parsing, and backend crash buckets. Process-exit code/VM-ID traces and guest logs exist (backend; S9), but dump outcome, parsing and crash-bucket telemetry are not implemented. - -Bring this forward alongside filesystem/networking work: actionable diagnostics are useful before broad stress testing, not just before release. - -### PR 12 — WHP memory contract and accounting design - -Design/documentation PR. Start alongside PR 1, despite its position in this implementation sequence. - -- [ ] **External - E1:** Confirm WHP deferred-commit and sparse-allocation requirements with the WHP owner. Owner agreement is not evidenced by WSL branch changes; attach the decision separately. -- [ ] **External - E5:** Define host-commit versus guest-visible memory accounting and telemetry. No accounting contract or new memory telemetry is present; the 4-GiB clamp is not accounting (S6). - -Owner agreement is a prerequisite, not something a code PR alone can accomplish. Explicitly determine whether host/WHP changes are required; ballooning alone should not be assumed to solve upfront host commit. - -### PR 13 — Virtio-balloon support - -OpenVMM PR. Depends on PR 12. - -- [ ] **External - E2:** Implement the virtio-balloon support required by WSL and WSLC. No balloon configuration/control integration is added in this WSL stack (S6). Track separate OpenVMM implementation and its WSL/WSLC consumption. - -Keep this independently reviewable from cold-discard and nested virtualization. Any WSL policy/configuration wiring should be a separate consuming PR if it requires code changes there. - -### PR 14 — Cold-discard support and memory-elasticity integration - -OpenVMM PR, followed by a WSL integration PR where needed. Depends on PRs 12–13. - -- [ ] **External - E3:** Implement qemu-style cold-discard hints or the approved equivalent. No new host memory-discard integration is present. Existing guest reclaim settings and disk trim commands are not evidence of this host-memory feature. -- [ ] **TODO - E4:** Validate grow, shrink, reclaim, pressure, suspend, and multi-VM behavior. No elasticity scenario coverage/results are added; the memory cap remains (S6). - -Do not remove the WSL memory cap merely because the device exists. Removal should follow demonstrated host-commit and reclaim behavior plus the memory stress/performance coverage below. - -### PR 15 — Remaining nested-virtualization support - -OpenVMM/WHP-owned implementation PRs. Independent of balloon/discard unless a concrete shared dependency emerges. - -- [ ] **External - E6:** Complete the remaining nested-virtualization work. The new WSL RPC configuration does not wire a nested-virtualization setting (S6); track OpenVMM/WHP completion and explicit WSL consumption separately. - -Keep this a separate workstream. The deliverable groups nesting with memory, but its description does not establish that they must form one linear code stack. - -### PR 16 — Complete compatibility automation and cross-feature stress - -Test PRs in the repository owning each harness. Depends on the applicable feature PRs. - -- [ ] **TODO - G2, completion:** Complete automated disk, VirtioFS, vsock, console, networking, boot, launch, and shutdown coverage. Two mock RPC tests do not exercise these real-VM scenarios (S10). -- [ ] **TODO - G3:** Add multi-distro, repeated attach/detach, restart, update, and hot-add stress. Share retry assertions are not repeated real-device or multi-VM stress. -- [ ] **TODO - G4, completion:** Add host-resource-pressure tests and complete cross-feature failure coverage. Only the narrow mock-RPC portion in PR 6 is present (S10). -- [ ] **TODO - E7:** Add memory-elasticity and nested-workload stress/performance coverage. No such harness/results are added. - -The feature PRs should already carry their focused tests. This layer covers interactions, longer-running workloads, and the full matrix. - -### PR 17 — Enforce performance and rollout gates - -WSL validation/release-infrastructure PR. Depends on representative results from the preceding work. - -- [ ] **TODO - G5, completion:** Establish the comparable OpenVMM baselines across startup, memory, CPU, I/O, networking, and reliability. Slow-operation logging does not supply comparative baseline results. -- [ ] **TODO - G6:** Use WSLC startup-time P95 measure 63134665 as a rollout signal. No measure integration is added. -- [ ] **TODO - G7, enforcement:** Make the agreed preview/GA thresholds enforceable gates. Compile-time and config opt-in gates are not health/performance release gates. - -Do not invent numerical thresholds from the work-item text; it specifies that they must be defined, not what their values are. - -## Stack boundaries and dependencies outside your seven items - -Use a short WSL foundation stack for PRs 3–7, then separate filesystem, networking, diagnostics, and memory workstreams. OpenVMM prerequisite PRs belong in OpenVMM stacks; connect them to WSL consumers through explicit dependency links and consumed versions—not one cross-repository branch chain. - -Three sibling deliverables need to remain visible in the dependency map: - -| Dependency | Where it matters | -| --- | --- | -| 63428742 — Guest channels and virtio devices | PRs 4, 7, and 8 require the selected mini_init transport, independent control/diagnostic channels, and appropriate VirtioFS/device support. This is a real dependency omitted from the seven-item list. | -| 62679000 — Productization and release pipeline | Required to consume supported, versioned OpenVMM artifacts and ship the result; avoid making prototype completion synonymous with release readiness. | -| 63428748 — Preview rollout and GA readiness | Owns rollout execution. PRs 5 and 17 should provide selection controls and gates without duplicating its rollout ownership. | - -Also align PRs 6 and 11 with 63459355 — Diagnosability. GPU support is explicitly non-blocking in the parent scenario and should not hold up this core sequence. From 4ea05c42361e804c99e77e762350fa3ea8a12761 Mon Sep 17 00:00:00 2001 From: Daman Mulye Date: Mon, 21 Sep 2026 11:19:36 -0700 Subject: [PATCH 03/10] flatten --- .../exe/OpenVmmVirtualMachineBackend.cpp | 153 +++++++++--------- .../exe/OpenVmmVirtualMachineBackend.h | 39 ++--- 2 files changed, 92 insertions(+), 100 deletions(-) diff --git a/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp b/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp index af0f976ef2..b55bc9a61a 100644 --- a/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp +++ b/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp @@ -239,37 +239,35 @@ void OpenVmmVirtualMachineBackend::DestroyVm(WslOpenVmmVm* Vm) noexcept WslOpenVmmDestroyVm(&Vm); } -OpenVmmVirtualMachineBackend::OpenVmmVirtualMachineBackend() : m_state(std::make_unique()) -{ -} +OpenVmmVirtualMachineBackend::OpenVmmVirtualMachineBackend() = default; OpenVmmVirtualMachineBackend::~OpenVmmVirtualMachineBackend() noexcept { - if (m_state->m_processWait) + if (m_processWait) { - SetThreadpoolWait(m_state->m_processWait.get(), nullptr, nullptr); - WaitForThreadpoolWaitCallbacks(m_state->m_processWait.get(), TRUE); - m_state->m_processWait.reset(); + SetThreadpoolWait(m_processWait.get(), nullptr, nullptr); + WaitForThreadpoolWaitCallbacks(m_processWait.get(), TRUE); + m_processWait.reset(); } - if (m_state->m_vm && WaitForSingleObject(m_state->m_process.get(), 0) == WAIT_TIMEOUT) + if (m_vm && WaitForSingleObject(m_process.get(), 0) == WAIT_TIMEOUT) { - LOG_IF_FAILED(WslOpenVmmVmTeardown(m_state->m_vm.get())); - LOG_IF_FAILED(WslOpenVmmVmQuit(m_state->m_vm.get())); + LOG_IF_FAILED(WslOpenVmmVmTeardown(m_vm.get())); + LOG_IF_FAILED(WslOpenVmmVmQuit(m_vm.get())); } - m_state->m_vm.reset(); - m_state->m_job.reset(); - if (m_state->m_process) + m_vm.reset(); + m_job.reset(); + if (m_process) { // Confirm exit before releasing backing files or deleting socket paths. - LOG_LAST_ERROR_IF(WaitForSingleObject(m_state->m_process.get(), INFINITE) == WAIT_FAILED); + LOG_LAST_ERROR_IF(WaitForSingleObject(m_process.get(), INFINITE) == WAIT_FAILED); } - m_state->m_backingFiles.clear(); - if (m_state->m_directoryCreated) + m_backingFiles.clear(); + if (m_directoryCreated) { - DeleteOwnedFile(m_state->m_rpcSocketPath); - DeleteOwnedFile(m_state->m_vsockPath); - DeleteOwnedFile(m_state->m_socketDirectory / L"openvmm.log"); - LOG_IF_WIN32_BOOL_FALSE(RemoveDirectoryW(m_state->m_socketDirectory.c_str())); + DeleteOwnedFile(m_rpcSocketPath); + DeleteOwnedFile(m_vsockPath); + DeleteOwnedFile(m_socketDirectory / L"openvmm.log"); + LOG_IF_WIN32_BOOL_FALSE(RemoveDirectoryW(m_socketDirectory.c_str())); } } @@ -277,7 +275,7 @@ std::unique_ptr OpenVmmVirtualMachineBackend::Crea { auto description = wsl::windows::common::vm::openvmm::ValidateCreateRequest(Request); auto backend = std::unique_ptr{new OpenVmmVirtualMachineBackend{}}; - backend->m_state->m_description = std::move(description); + backend->m_description = std::move(description); backend->Initialize(Request); return backend; } @@ -295,18 +293,18 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) auto id = wsl::shared::string::GuidToString(Request.VmId, wsl::shared::string::GuidToStringFlags::None); std::erase(id, L'-'); // An exclusive directory creation prevents shortened path IDs from aliasing another VM. - m_state->m_socketDirectory = filesystem::GetTempFolderPath(GetCurrentProcessToken()) / (L"ov-" + id.substr(0, 16)); - m_state->m_rpcSocketPath = m_state->m_socketDirectory / L"r"; - m_state->m_vsockPath = m_state->m_socketDirectory / L"v"; + m_socketDirectory = filesystem::GetTempFolderPath(GetCurrentProcessToken()) / (L"ov-" + id.substr(0, 16)); + m_rpcSocketPath = m_socketDirectory / L"r"; + m_vsockPath = m_socketDirectory / L"v"; const auto longestPath = - wsl::shared::string::WideToMultiByte(m_state->m_vsockPath.native() + L"_ffffffff-facb-11e6-bd58-64006a7986d3"); + wsl::shared::string::WideToMultiByte(m_vsockPath.native() + L"_ffffffff-facb-11e6-bd58-64006a7986d3"); SOCKADDR_UN address{}; THROW_HR_IF_MSG( E_INVALIDARG, longestPath.size() >= sizeof(address.sun_path), "OpenVMM guest socket path exceeds the AF_UNIX limit: %hs", longestPath.c_str()); - THROW_HR_IF(E_INVALIDARG, m_state->m_rpcSocketPath.native().find_first_of(L",\"\r\n") != std::wstring::npos); + THROW_HR_IF(E_INVALIDARG, m_rpcSocketPath.native().find_first_of(L",\"\r\n") != std::wstring::npos); const auto tokenUser = wil::get_token_information(GetCurrentProcessToken()); const auto sid = wslutil::SidToString(tokenUser->User.Sid); @@ -315,32 +313,31 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) THROW_IF_WIN32_BOOL_FALSE(ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), SDDL_REVISION_1, &security, nullptr)); SECURITY_ATTRIBUTES attributes{sizeof(attributes), security.get(), FALSE}; { - m_state->m_backingFiles.push_back(OpenBackingFile(Request.Boot.KernelPath, true)); - m_state->m_backingFiles.push_back(OpenBackingFile(Request.Boot.InitrdPath, true)); - auto lock = m_state->m_lock.lock_exclusive(); + m_backingFiles.push_back(OpenBackingFile(Request.Boot.KernelPath, true)); + m_backingFiles.push_back(OpenBackingFile(Request.Boot.InitrdPath, true)); + auto lock = m_lock.lock_exclusive(); for (const auto& disk : Request.BootDisks) { - const auto& attachment = m_state->m_description.BootDisks.at(disk.Key); + const auto& attachment = m_description.BootDisks.at(disk.Key); auto backingFile = OpenBackingFile(std::get(disk.Disk.Source).Path, disk.Disk.ReadOnly); - m_state->m_attachedDisks.emplace( - attachment.Id.Value, State::AttachedDisk{attachment, std::move(backingFile)}); + m_attachedDisks.emplace(attachment.Id.Value, AttachedDisk{attachment, std::move(backingFile)}); } - m_state->m_nextDiskId = Request.BootDisks.size() + 1; - THROW_IF_WIN32_BOOL_FALSE(CreateDirectoryW(m_state->m_socketDirectory.c_str(), &attributes)); - m_state->m_directoryCreated = true; + m_nextDiskId = Request.BootDisks.size() + 1; + THROW_IF_WIN32_BOOL_FALSE(CreateDirectoryW(m_socketDirectory.c_str(), &attributes)); + m_directoryCreated = true; } UniqueConfig config; THROW_IF_FAILED(WslOpenVmmCreateConfig(config.put())); THROW_IF_FAILED(WslOpenVmmConfigSetKernelPath(config.get(), Request.Boot.KernelPath.c_str())); THROW_IF_FAILED(WslOpenVmmConfigSetInitrdPath(config.get(), Request.Boot.InitrdPath.c_str())); - THROW_IF_FAILED(WslOpenVmmConfigSetKernelCmdLine(config.get(), m_state->m_description.Boot.KernelCommandLine.c_str())); - THROW_IF_FAILED(WslOpenVmmConfigSetMemoryMb(config.get(), m_state->m_description.Memory.SizeBytes / (1024 * 1024))); + THROW_IF_FAILED(WslOpenVmmConfigSetKernelCmdLine(config.get(), m_description.Boot.KernelCommandLine.c_str())); + THROW_IF_FAILED(WslOpenVmmConfigSetMemoryMb(config.get(), m_description.Memory.SizeBytes / (1024 * 1024))); THROW_IF_FAILED(WslOpenVmmConfigSetProcessorCount(config.get(), Request.Processor.Count)); - THROW_IF_FAILED(WslOpenVmmConfigSetHvSocketPath(config.get(), m_state->m_vsockPath.c_str())); + THROW_IF_FAILED(WslOpenVmmConfigSetHvSocketPath(config.get(), m_vsockPath.c_str())); for (const auto& disk : Request.BootDisks) { - const auto& attachment = m_state->m_description.BootDisks.at(disk.Key); + const auto& attachment = m_description.BootDisks.at(disk.Key); THROW_IF_FAILED(WslOpenVmmConfigAddBootDisk( config.get(), attachment.GuestAddress.Controller, @@ -360,18 +357,18 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) } } - m_state->m_job = helpers::CreateKillOnCloseJob(); + m_job = helpers::CreateKillOnCloseJob(); const auto commandLine = - std::format(L"\"{}\" --rpc \"path={},transport=grpc\"", executable.native(), m_state->m_rpcSocketPath.native()); + std::format(L"\"{}\" --rpc \"path={},transport=grpc\"", executable.native(), m_rpcSocketPath.native()); SubProcess process{executable.c_str(), commandLine.c_str()}; process.SetFlags(CREATE_NO_WINDOW); - process.SetJobObject(m_state->m_job.get()); + process.SetJobObject(m_job.get()); SECURITY_ATTRIBUTES inheritable{sizeof(inheritable), nullptr, TRUE}; wil::unique_hfile logFile; wil::unique_hfile input; { logFile.reset(CreateFileW( - (m_state->m_socketDirectory / L"openvmm.log").c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); + (m_socketDirectory / L"openvmm.log").c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); THROW_LAST_ERROR_IF(!logFile); input.reset(CreateFileW( L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); @@ -382,14 +379,14 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) THROW_IF_WIN32_BOOL_FALSE( DuplicateHandle(GetCurrentProcess(), logFile.get(), GetCurrentProcess(), errorLogFile.put(), 0, TRUE, DUPLICATE_SAME_ACCESS)); process.SetStdHandles(input.get(), logFile.get(), errorLogFile.get()); - m_state->m_process = process.Start(); - m_state->m_processWait.reset(CreateThreadpoolWait(OnProcessExit, this, nullptr)); - THROW_LAST_ERROR_IF(!m_state->m_processWait); - SetThreadpoolWait(m_state->m_processWait.get(), m_state->m_process.get(), nullptr); + m_process = process.Start(); + m_processWait.reset(CreateThreadpoolWait(OnProcessExit, this, nullptr)); + THROW_LAST_ERROR_IF(!m_processWait); + SetThreadpoolWait(m_processWait.get(), m_process.get(), nullptr); THROW_IF_FAILED_MSG( - WslOpenVmmCreateVm(config.addressof(), m_state->m_rpcSocketPath.c_str(), c_rpcTimeoutMs, m_state->m_vm.put()), + WslOpenVmmCreateVm(config.addressof(), m_rpcSocketPath.c_str(), c_rpcTimeoutMs, m_vm.put()), "Failed to create OpenVMM VM"); - const auto result = WaitForSingleObject(m_state->m_process.get(), 0); + const auto result = WaitForSingleObject(m_process.get(), 0); THROW_LAST_ERROR_IF(result == WAIT_FAILED); THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED), result == WAIT_OBJECT_0); } @@ -397,7 +394,7 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) void CALLBACK OpenVmmVirtualMachineBackend::OnProcessExit(PTP_CALLBACK_INSTANCE, void* Context, PTP_WAIT, TP_WAIT_RESULT) noexcept { auto& backend = *static_cast(Context); - LOG_IF_WIN32_BOOL_FALSE(SetEvent(backend.m_state->m_exitEvent.get())); + LOG_IF_WIN32_BOOL_FALSE(SetEvent(backend.m_exitEvent.get())); } VmPlatformCapabilities OpenVmmVirtualMachineBackend::QueryCapabilities() @@ -456,24 +453,24 @@ wil::unique_handle OpenVmmVirtualMachineBackend::GetTerminationEvent() const { wil::unique_handle event; THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle( - GetCurrentProcess(), m_state->m_exitEvent.get(), GetCurrentProcess(), event.put(), 0, FALSE, DUPLICATE_SAME_ACCESS)); + GetCurrentProcess(), m_exitEvent.get(), GetCurrentProcess(), event.put(), 0, FALSE, DUPLICATE_SAME_ACCESS)); return event; } void OpenVmmVirtualMachineBackend::Start() { - auto lock = m_state->m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); - THROW_IF_FAILED(WslOpenVmmVmResume(m_state->m_vm.get())); + auto lock = m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); + THROW_IF_FAILED(WslOpenVmmVmResume(m_vm.get())); } void OpenVmmVirtualMachineBackend::Terminate() { - auto lock = m_state->m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); - THROW_IF_FAILED(WslOpenVmmVmTeardown(m_state->m_vm.get())); - const auto quitResult = WslOpenVmmVmQuit(m_state->m_vm.get()); - const auto waitResult = WaitForSingleObject(m_state->m_process.get(), c_rpcTimeoutMs); + auto lock = m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); + THROW_IF_FAILED(WslOpenVmmVmTeardown(m_vm.get())); + const auto quitResult = WslOpenVmmVmQuit(m_vm.get()); + const auto waitResult = WaitForSingleObject(m_process.get(), c_rpcTimeoutMs); THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); if (waitResult != WAIT_OBJECT_0) { @@ -481,8 +478,8 @@ void OpenVmmVirtualMachineBackend::Terminate() THROW_HR(HRESULT_FROM_WIN32(WAIT_TIMEOUT)); } - m_state->m_vm.reset(); - m_state->m_attachedDisks.clear(); + m_vm.reset(); + m_attachedDisks.clear(); } void OpenVmmVirtualMachineBackend::CancelPendingOperations() noexcept @@ -514,11 +511,11 @@ VmDiskAttachment OpenVmmVirtualMachineBackend::AttachDisk(const VmDiskRequest& R { const auto& source = ValidateDiskRequest(Request); auto backingFile = OpenBackingFile(source.Path, Request.ReadOnly); - auto lock = m_state->m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); + auto lock = m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); const auto lunInUse = [&](std::uint32_t Lun) { - for (const auto& entry : m_state->m_attachedDisks) + for (const auto& entry : m_attachedDisks) { if (entry.second.Attachment.GuestAddress.Lun == Lun) { @@ -543,30 +540,30 @@ VmDiskAttachment OpenVmmVirtualMachineBackend::AttachDisk(const VmDiskRequest& R THROW_HR_IF(WSL_E_TOO_MANY_DISKS_ATTACHED, lun == c_maximumDisks); } - THROW_HR_IF(E_BOUNDS, m_state->m_nextDiskId == UINT64_MAX); + THROW_HR_IF(E_BOUNDS, m_nextDiskId == UINT64_MAX); const VmDiskAttachment attachment{ - {m_state->m_description.Identity, m_state->m_nextDiskId}, {0, lun}, Request.ReadOnly}; - const auto [disk, inserted] = m_state->m_attachedDisks.emplace( - attachment.Id.Value, State::AttachedDisk{attachment, std::move(backingFile)}); + {m_description.Identity, m_nextDiskId}, {0, lun}, Request.ReadOnly}; + const auto [disk, inserted] = m_attachedDisks.emplace( + attachment.Id.Value, AttachedDisk{attachment, std::move(backingFile)}); WI_ASSERT(inserted); - auto rollback = wil::scope_exit([&] { m_state->m_attachedDisks.erase(disk); }); + auto rollback = wil::scope_exit([&] { m_attachedDisks.erase(disk); }); THROW_IF_FAILED(WslOpenVmmVmAttachScsiDisk( - m_state->m_vm.get(), attachment.GuestAddress.Controller, attachment.GuestAddress.Lun, source.Path.c_str(), Request.ReadOnly)); - ++m_state->m_nextDiskId; + m_vm.get(), attachment.GuestAddress.Controller, attachment.GuestAddress.Lun, source.Path.c_str(), Request.ReadOnly)); + ++m_nextDiskId; rollback.release(); return attachment; } void OpenVmmVirtualMachineBackend::DetachDisk(VmDiskId Disk) { - THROW_HR_IF(E_INVALIDARG, Disk.Value == 0 || !IsEqualGUID(Disk.Owner.VmId, m_state->m_description.Identity.VmId)); - auto lock = m_state->m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); - const auto disk = m_state->m_attachedDisks.find(Disk.Value); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), disk == m_state->m_attachedDisks.end()); + THROW_HR_IF(E_INVALIDARG, Disk.Value == 0 || !IsEqualGUID(Disk.Owner.VmId, m_description.Identity.VmId)); + auto lock = m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); + const auto disk = m_attachedDisks.find(Disk.Value); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), disk == m_attachedDisks.end()); THROW_IF_FAILED(WslOpenVmmVmDetachScsiDisk( - m_state->m_vm.get(), disk->second.Attachment.GuestAddress.Controller, disk->second.Attachment.GuestAddress.Lun)); - m_state->m_attachedDisks.erase(disk); + m_vm.get(), disk->second.Attachment.GuestAddress.Controller, disk->second.Attachment.GuestAddress.Lun)); + m_attachedDisks.erase(disk); } VmFileSystemDevice OpenVmmVirtualMachineBackend::CreateFileSystemDevice(const VmFileSystemDeviceRequest&) diff --git a/src/windows/service/exe/OpenVmmVirtualMachineBackend.h b/src/windows/service/exe/OpenVmmVirtualMachineBackend.h index 4ad86152ee..4e0dcca4dc 100644 --- a/src/windows/service/exe/OpenVmmVirtualMachineBackend.h +++ b/src/windows/service/exe/OpenVmmVirtualMachineBackend.h @@ -66,29 +66,24 @@ class OpenVmmVirtualMachineBackend : public IVirtualMachineBackend static void DestroyVm(WslOpenVmmVm* Vm) noexcept; using UniqueVm = wil::unique_any; - struct State + struct AttachedDisk { - struct AttachedDisk - { - VmDiskAttachment Attachment; - wil::unique_hfile BackingFile; - }; - - wil::srwlock m_lock; - VmDescription m_description; - _Guarded_by_(m_lock) std::map m_attachedDisks; - _Guarded_by_(m_lock) std::uint64_t m_nextDiskId = 1; - UniqueVm m_vm; - wil::unique_handle m_process; - wil::unique_handle m_job; - std::vector m_backingFiles; - std::filesystem::path m_socketDirectory; - std::filesystem::path m_rpcSocketPath; - std::filesystem::path m_vsockPath; - bool m_directoryCreated = false; - wil::unique_event m_exitEvent{wil::EventOptions::ManualReset}; - wil::unique_threadpool_wait m_processWait; + VmDiskAttachment Attachment; + wil::unique_hfile BackingFile; }; - std::unique_ptr m_state; + wil::srwlock m_lock; + VmDescription m_description{}; + _Guarded_by_(m_lock) std::map m_attachedDisks; + _Guarded_by_(m_lock) std::uint64_t m_nextDiskId = 1; + UniqueVm m_vm; + wil::unique_handle m_process; + wil::unique_handle m_job; + std::vector m_backingFiles; + std::filesystem::path m_socketDirectory; + std::filesystem::path m_rpcSocketPath; + std::filesystem::path m_vsockPath; + bool m_directoryCreated = false; + wil::unique_event m_exitEvent{wil::EventOptions::ManualReset}; + wil::unique_threadpool_wait m_processWait; }; \ No newline at end of file From 28bccf15ddc886a114468e942f56b34b0a85a5cd Mon Sep 17 00:00:00 2001 From: Daman Mulye Date: Mon, 21 Sep 2026 14:16:35 -0700 Subject: [PATCH 04/10] previous PR feedback --- CMakeLists.txt | 4 ++-- msipackage/CMakeLists.txt | 6 +++++- msipackage/package.wix.in | 5 +++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c58ed1679..9093babe1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,7 +135,6 @@ find_nuget_package(Microsoft.WSL.TestData WSL_TEST_DATA /) find_nuget_package(Microsoft.WSLg WSLG /build/native/bin) find_nuget_package(vswhere VSWHERE /tools) find_nuget_package(Wix WIX /tools/net6.0/any) -find_nuget_package(Microsoft.WSL.OpenVMM WSL_OPENVMM /build/native) # Architecture-specific nuget packages from the OS repo. if (${TARGET_PLATFORM} STREQUAL "x64") @@ -278,7 +277,8 @@ if (NOT WSL_DEVICEHOST_BIN) file(CREATE_LINK ${WSL_DEVICE_HOST_SOURCE_DIR}/bin/${TARGET_PLATFORM}/wsldevicehost.pdb ${BIN}/wsldevicehost.pdb) endif() -foreach(binary openvmm.exe openvmm.pdb wslopenvmm.dll wslopenvmm.pdb) +# OpenVMM binaries are packaged directly from NuGet; retain PDBs for symbol publishing. +foreach(binary openvmm.pdb wslopenvmm.pdb) file(CREATE_LINK "${WSL_OPENVMM_SOURCE_DIR}/bin/${TARGET_PLATFORM}/${binary}" "${BIN}/${binary}") endforeach() diff --git a/msipackage/CMakeLists.txt b/msipackage/CMakeLists.txt index 3272b3afef..59c39eb185 100644 --- a/msipackage/CMakeLists.txt +++ b/msipackage/CMakeLists.txt @@ -17,7 +17,7 @@ set(OUTPUT_PACKAGE ${BIN}/wsl.msi) set(PACKAGE_WIX_IN ${CMAKE_CURRENT_LIST_DIR}/package.wix.in) set(PACKAGE_WIX ${BIN}/package.wix) set(CAB_CACHE ${BIN}/cab) -set(WINDOWS_BINARIES wsl.exe;wslg.exe;wslhost.exe;wslrelay.exe;wslservice.exe;wslserviceproxystub.dll;wsldevicehostproxystub.dll;wslinstall.dll;wslc.exe;wslcsession.exe;openvmm.exe;wslopenvmm.dll) +set(WINDOWS_BINARIES wsl.exe;wslg.exe;wslhost.exe;wslrelay.exe;wslservice.exe;wslserviceproxystub.dll;wsldevicehostproxystub.dll;wslinstall.dll;wslc.exe;wslcsession.exe) if (WSL_BUILD_WSL_SETTINGS) list(APPEND WINDOWS_BINARIES "wslsettings/wslsettings.dll;wslsettings/wslsettings.exe;libwsl.dll") endif() @@ -27,6 +27,10 @@ foreach(binary ${WINDOWS_BINARIES}) list(APPEND BINARIES_DEPENDENCIES "${PACKAGE_INPUT_DIR}/${binary}") endforeach() +foreach(binary openvmm.exe wslopenvmm.dll) + list(APPEND BINARIES_DEPENDENCIES "${WSL_OPENVMM_SOURCE_DIR}/bin/${TARGET_PLATFORM}/${binary}") +endforeach() + set(LINUX_BINARIES init;initrd.img) foreach(binary ${LINUX_BINARIES}) list(APPEND BINARIES_DEPENDENCIES "${BIN}/${binary}") diff --git a/msipackage/package.wix.in b/msipackage/package.wix.in index f216a7cb71..802c6abd77 100644 --- a/msipackage/package.wix.in +++ b/msipackage/package.wix.in @@ -294,8 +294,9 @@ - - + + + From 6816b1d6fc4d8d6b314f046198e3e6106fa2b793 Mon Sep 17 00:00:00 2001 From: Daman Mulye Date: Mon, 21 Sep 2026 22:17:11 +0000 Subject: [PATCH 05/10] Update cleanup logic in OpenVmmVirtualMachineBackend Replaced file deletion with recursive directory removal for improved error handling. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp | 5 +---- test/windows/OpenVmmVirtualMachineBackendTests.cpp | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp b/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp index b55bc9a61a..525d7f5c5a 100644 --- a/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp +++ b/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp @@ -264,10 +264,7 @@ OpenVmmVirtualMachineBackend::~OpenVmmVirtualMachineBackend() noexcept m_backingFiles.clear(); if (m_directoryCreated) { - DeleteOwnedFile(m_rpcSocketPath); - DeleteOwnedFile(m_vsockPath); - DeleteOwnedFile(m_socketDirectory / L"openvmm.log"); - LOG_IF_WIN32_BOOL_FALSE(RemoveDirectoryW(m_socketDirectory.c_str())); + LOG_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(m_socketDirectory.c_str())); } } diff --git a/test/windows/OpenVmmVirtualMachineBackendTests.cpp b/test/windows/OpenVmmVirtualMachineBackendTests.cpp index 52b0705493..22998b1757 100644 --- a/test/windows/OpenVmmVirtualMachineBackendTests.cpp +++ b/test/windows/OpenVmmVirtualMachineBackendTests.cpp @@ -1,3 +1,5 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. + #include "precomp.h" #include "Common.h" #include "OpenVmmVirtualMachineBackend.h" From 078afca67cd7354ace0ba3e0c7ab975d80bfa064 Mon Sep 17 00:00:00 2001 From: Daman Mulye Date: Mon, 21 Sep 2026 11:06:26 -0700 Subject: [PATCH 06/10] Initial work --- CMakeLists.txt | 1 + WSL-openvmm.md | 281 ++++++++++++++++++ .../exe/OpenVmmVirtualMachineBackend.cpp | 150 +++++----- .../exe/OpenVmmVirtualMachineBackend.h | 39 +-- .../OpenVmmVirtualMachineBackendTests.cpp | 2 - 5 files changed, 382 insertions(+), 91 deletions(-) create mode 100644 WSL-openvmm.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 9093babe1a..7451e1793a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,7 @@ find_nuget_package(Microsoft.WSL.TestData WSL_TEST_DATA /) find_nuget_package(Microsoft.WSLg WSLG /build/native/bin) find_nuget_package(vswhere VSWHERE /tools) find_nuget_package(Wix WIX /tools/net6.0/any) +find_nuget_package(Microsoft.WSL.OpenVMM WSL_OPENVMM /build/native) # Architecture-specific nuget packages from the OS repo. if (${TARGET_PLATFORM} STREQUAL "x64") diff --git a/WSL-openvmm.md b/WSL-openvmm.md new file mode 100644 index 0000000000..3b070d83ee --- /dev/null +++ b/WSL-openvmm.md @@ -0,0 +1,281 @@ +# OpenVMM WSL implementation tracker + +## Current-stack assessment (2026-09-14) + +This assessment compares each local branch with the branch below it, starting at `master` (`4bfbacae`). The original audit covered backend tip `13daf737`; the snapshot below includes the local rebase, committed PR 3 and PR 5 follow-ups, and uncommitted PR 6 RPC work. The PR numbers below are proposed work packages, not existing GitHub PR numbers. + +| Layer | Actual branch and tip | Work present | +|---|---|---| +| refactor | `user/damanmmulye/wsl-openvmm-refactor` at `b956db18` | `IWslCoreVm`, HCS implementation adaptation, session/interface plumbing, guest connection entry point, accepted ownership comments, and lifecycle regression. | +| rpc | `user/damanmulye/wsl-openvmm-rpc` at `dd6dc8ed` plus uncommitted C2 follow-up | Rust DLL/FFI, VM/resource RPCs, bounded AF_UNIX/gRPC calls, fail-closed recovery, cancellation, error categories, and transport regressions. | +| backend | `user/damanmmulye/wsl-openvmm-backend` at `b8b146cf` | WSL backend selection, process/VM lifecycle, guest transport wiring, VirtioFS, initial networking, console logging, private RPC socket and gated packaging; accepted selection/rollback policy and selection regressions. | + +**Legend:** `[x] Implemented` means the scoped WSL implementation is present in source, not that it has been built, run, merged, or approved for release. `[ ] Partial` means useful work exists but the bullet still has a gap or an unresolved design deviation. `[ ] TODO` means the requested outcome is not evidenced by this stack. `[ ] External` means completion must be established outside this WSL stack; it does not mean work in OpenVMM or offline design discussions has not happened. + +**Scope totals:** 14 implemented, 10 partial, 19 TODO, 7 external (50 unique bullets). Split validation bullets are counted once; G4 is partial overall because coverage is limited to selected mock/transport and early configuration-failure cases. The implemented bullets are **A3, B1, B2, B3, B4, B5, B6, B7, C1, C2, C5, C7, D1, and D4**. These totals include the accepted PR 3 and PR 5 decisions and the PR 6 RPC-layer closeout for C2/C7; backend follow-ups remain explicitly tracked below. + +The stack does not add WSLC backend call-site integration: the separate WSLC prototype from the earlier summary is not credited as completed work here. PR 3 adds a Windows lifecycle regression; PR 5 adds selection regressions and policy documentation; the uncommitted PR 6 follow-up expands the Rust transport coverage. End-to-end results, baselines, and rollout decisions cannot be inferred from a commit named "boot successful". + +**Important differences from the original plan:** + +- **Accepted PR 3/PR 5 design:** `IWslCoreVm` is the service-facing backend contract. `WslCoreVm` remains the HCS implementation; OpenVMM is a sibling implementation, not a backend underneath a shared `WslCoreVm` facade. This explicitly replaces the original lower-level extraction in A3/B1/B2, rather than claiming that extraction happened. The dedicated factory was removed by `2caf8dba`; `LxssUserSessionImpl::_CreateVm()` is accepted as B3's centralized creation/selection point. A2's full lifecycle contract remains separate follow-up work. +- **Accepted PR 5 policy:** HCS is the default; explicit OpenVMM opt-in fails rather than silently falling back when unavailable or when initialization fails. Rollback is manual: disable the setting and shut down WSL. A live VM retains its recorded backend until shutdown. +- **Accepted PR 6 contract:** Keep gRPC over AF_UNIX, not ttrpc. Replace transparent reconciliation with fail-closed recovery after uncertain mutations; teardown and fresh-process recreation are required. +- **C2/C7 closeout:** Close these bullets for the accepted RPC-layer scope: bounded, fail-closed RPC behavior and ordinary debugger-output tracing macros. Backend cancellation/lifetime integration, recreation-path coverage, and broader service/process diagnostics remain follow-ups, not claims of completed end-to-end integration. +- Mixed admin/non-admin access is **not implemented**: `InitializeDrvFs` and `AddVirtioFsShare` reject elevation different from the VM creator. Pass-through disks are also explicitly unsupported. +- Memory remains capped at 4 GiB. GUI/GPU, debug shell, and DNS tunneling are disabled in this backend configuration; pmem and virtio-rng configuration are not wired through the new RPC builder. +- Console/dmesg capture is implemented, but it is not kernel-panic extraction or a saved-state/crash-artifact collection pipeline. + +### Source evidence + +S1-S10 paths and line numbers refer to the original audited tips; S11-S13 identify committed follow-ups; S14 identifies uncommitted RPC diagnostics. Source IDs identify implementation evidence, not successful runtime results. + +| ID | Evidence | +|---|---| +| S1 | `src\windows\service\exe\IWslCoreVm.h:8-88`; `src\windows\service\exe\WslCoreVm.h:43` (`WslCoreVm : IWslCoreVm`). Refactor commit `2caf8dba` removes the dedicated factory. | +| S2 | `src\windows\service\exe\LxssUserSession.cpp:2999-3028` (inline selection and failure cleanup), `:2211-2239` (backend-specific force termination); `src\windows\common\WslCoreConfig.h:298,388` (opt-in key/default); `CMakeLists.txt:44` (build gate defaults off). | +| S3 | `src\shared\inc\SocketChannel.h:620-749` (AF_UNIX I/O); `src\windows\service\exe\LxssCreateProcess.h:54,76-111`; `src\windows\service\exe\WslCoreInstance.cpp:38-50,244-247,439-442,550-580`; `src\windows\service\exe\OpenVmmWslCoreVm.cpp:38-101` (guest bridge). | +| S4 | `src\windows\wslopenvmm\src\af_unix.rs:14-45` (connect retries/timeouts); `src\windows\wslopenvmm\src\client.rs:43-79,363-383` (Tonic client, deadlines, HRESULT mapping); `src\windows\service\exe\OpenVmmWslCoreVm.cpp:459-462` (`transport=grpc`). | +| S5 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:117-187,207-256,540-586,984-1026,1335-1392` (launch, cleanup, teardown/quit, process wait and callbacks). | +| S6 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:258-350,464-538` (configuration overrides, 4-GiB cap, boot/device setup); `src\windows\wslopenvmm\src\client.rs:81-172` (configuration builder). | +| S7 | `src\windows\service\exe\VirtioFsShareRequest.cpp:8-68`; `src\windows\service\exe\OpenVmmWslCoreVm.cpp:653-768,1247-1263,1393-1406` (share requests, worker, elevation restriction); `:1035-1039` (pass-through rejection); `src\windows\wslopenvmm\src\client.rs:223-275` (share RPCs). | +| S8 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:515-521,807-875` (consomme NIC, DHCP, IPv6 enabled, port tracker); `src\windows\wslopenvmm\src\client.rs:313-349` (IPv4/IPv6 localhost port requests). | +| S9 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:342,386-430,554-577,984-1012` (dump-count override, collector/debug console, stdout/stderr log, process-exit trace); `src\windows\common\Dmesg.cpp:45-78,156-211` (pipe access and raw guest-log capture, not panic parsing). | +| S10 | `src\windows\wslopenvmm\src\client\tests.rs:86-110,133-207`: TCP-loopback mock server; port protocol/address-family assertions and share add/remove failure/retry assertions. No real VM or AF_UNIX connection/recovery test. | +| S11 | PR 3 commit `b956db18` on the refactor branch: contract comments in `src\windows\service\exe\IWslCoreVm.h` and `WslCoreVm.h`; `SimpleTests::VmBackendShutdownAndReconnect` in `test\windows\SimpleTests.cpp`. Execution is delegated to CI, with results not yet recorded here. | +| S12 | PR 5 commit `b8b146cf` on the backend branch: selection matrix in `doc\docs\technical-documentation\wslservice.exe.md`; parser, backend identity, failure/retry, live-configuration shutdown, and rollback cases in `test\windows\VmBackendTests.cpp`; invalid-boolean warning assertion in `UnitTests.cpp`; source registration, availability definition, and `configfile` linkage in `test\windows\CMakeLists.txt`. | +| S13 | PR 6 RPC follow-up committed as `3e729f2d`: `src\windows\wslopenvmm\src\rpc.rs`, `af_unix.rs`, `client.rs`, `lib.rs`, and `client\tests.rs`; private exports in `wslopenvmm.h`; revised contract in the local `src\windows\wslopenvmm\README.md`. Detailed coverage and remaining backend integration are in the PR 6 page. | +| S14 | Uncommitted C7 RPC diagnostics: epci2-style once-initialized debugger subscriber and stack-backed writer in `src\windows\wslopenvmm\src\diagnostics.rs`; initialization and ordinary tracing macros in `client.rs` and `rpc.rs`. Messages omit payloads and server error text. The accepted approach uses debugger output, not an ETW provider, custom event schemas, or request correlation metadata. | + +## Original plan, annotated by scope bullet + +Organize this as small, dependency-ordered PRs—not one PR per deliverable. The main sequence should be contracts → HCS-preserving abstraction → OpenVMM boot → filesystem/networking compatibility → diagnostics → rollout gates. Start WHP memory work in parallel; it should block removing the memory cap, not the initial capped-memory backend. + +The order below maps all 50 description bullets into proposed PRs. Some validation bullets intentionally span an early foundation PR and a later completion PR. + +Account for work you already have + +These are foundations to consume, not features to implement again. Merged PRs do not, by themselves, establish completion of their broader deliverables. + +| Area | Existing work | Planning implication | +| --- | --- | --- | +| Backend prototype | WSL #40629, open; your current WSL branch also contains backend and private AF_UNIX RPC work | Extract cohesive changes into the backend PRs below rather than starting over. | +| VirtioFS | OpenVMM #3821, WSL #41129, WSL #41151, all merged | Focus on OpenVMM integration and identity/elevation gaps, not rebuilding aggregate shares. | +| Networking | OpenVMM #2398, IPv6, merged; #4378, control/data-path separation, open | Consume existing IPv6 support and identify the remaining integration gaps. Treat the networking refactor as a dependency only where needed. | +| RPC configuration | OpenVMM #4420, open | Land the required network/filesystem RPC capabilities before their WSL consumers. | +| Crash artifacts | OpenVMM #3882, triple-fault .vmrs, merged | Extend and integrate the existing mechanism; distinguish triple faults from kernel panics and host-process crashes. | + +At the initial ADO lookup, all seven deliverables said Proposed. That state is not a reliable measure of implementation progress; the checklist below records local-stack evidence separately. + +Proposed PR order + +References:  A1  means the first Scope bullet in deliverable A. + +| Key | Deliverable | +| --- | --- | +| A | 62679114 — Architecture and compatibility matrix | +| B | 63428739 — Pluggable VM backends | +| C | 63428740 — OpenVMM backend | +| D | 63428744 — Filesystem and networking | +| E | 63428746 — Memory elasticity and nested virtualization | +| F | 63428745 — Crash diagnostics | +| G | 63428747 — Compatibility and regression validation | + +### PR 1 — Architecture, compatibility, and ownership contract + +Design/documentation PR. First; approve the relevant decisions before implementing their consumers. + +- [ ] **TODO - A1:** Define the supported WSL and WSLC scenario-compatibility matrix. No matrix is added by this stack. +- [ ] **Partial - A2:** Define `IWslVmBackend` responsibilities and lifecycle contract. `IWslCoreVm` and lifecycle implementations exist (refactor/backend; S1, S5), but the approved contract must reflect the actual interface and ownership model. +- [x] **Implemented - A3:** Define the boundary between service orchestration and HCS-specific behavior. The sibling HCS/OpenVMM design is explicitly accepted and documented in PR 3 below and the interface/class comments (S1, S11). This supersedes the originally proposed split within `WslCoreVm`. +- [ ] **Partial - A4:** Define backend selection, feature control, rollback, and configuration behavior. Compile-time and `.wslconfig` gates and the accepted selection/failure/manual-rollback matrix are documented (backend; S2, S12). The broader unsupported-setting compatibility contract remains outstanding. +- [ ] **Partial - A5:** Define the guest communication abstraction for HvSocket and vsock. Code implements callbacks and the guest bridge (refactor/backend; S3); the reviewed transport/lifecycle contract is not evidenced. +- [ ] **Partial - A6:** Record the initial VirtioFS-only and consomme-only constraints. These are enforced by configuration overrides (backend; S6), but a reviewed compatibility/limitations document is still needed. +- [ ] **TODO - A7:** Identify repository, component, and DRI ownership for every gap. No ownership table is added. +- [ ] **TODO - A8:** Resolve ownership overlap between scenarios 62917985 and 61024686. No recorded resolution is evidenced by the branch changes. +- [ ] **TODO - D3:** Design elevation/broker behavior for pass-through disk file opens. The current backend rejects non-VHD disks and does not implement a pass-through broker (backend; S7). +- [ ] **TODO - D6:** Define the migration path to converged WSL networking. Forcing consomme in configuration is not a migration plan (S6). +- [ ] **TODO - F5:** Define artifact retention, size, privacy, and upload behavior. Socket/pipe ACLs and local logging exist, but no artifact policy is added (S9). + +Keep this focused on decisions, not implementations. In particular, define when fallback is allowed; do not leave “safe fallback” to become an arbitrary retry after a partially created VM. + +### PR 2 — Backend comparison harness and baseline measurements + +WSL test/infrastructure PR. Start after PR 1; develop alongside the implementation. + +- [ ] **TODO - G1:** Create the end-to-end matrix for WSL and WSLC on HCS and OpenVMM. Mock RPC tests are not a backend matrix (S10). +- [ ] **TODO - G5, foundation:** Establish measurement tooling and HCS startup, memory, CPU, I/O, networking, and reliability baselines; collect OpenVMM results once available. No benchmark harness or baseline results are added. +- [ ] **TODO - G7, definition:** Agree preview/GA pass rates and regression thresholds before deciding whether results are acceptable. No threshold definitions are added. + +This is infrastructure, not a reason to defer feature-specific tests until the end. + +### PR 3 — Isolate the HCS backend behind the accepted service contract + +WSL PR. Depends on PR 1. + +- [x] **Implemented - B1 (accepted revised scope):** Isolate HCS-specific VM operations from service callers behind `IWslCoreVm`, retaining HCS ownership in `WslCoreVm` (S1, S11). +- [x] **Implemented - B2 (accepted revised scope):** Put existing HCS behavior behind the service-facing backend contract; use sibling HCS/OpenVMM implementations rather than a shared facade (S1, S11). +- [x] **Implemented - B6:** Preserve HCS initialization, networking, VirtioFS, shutdown, and error telemetry, with regression coverage (S11). + +Keep OpenVMM implementation out of this PR. Its review question should be: does the abstraction preserve HCS behavior? + +[PR 3 details: ownership, test coverage, CI sign-off, and build evidence](WSL-openvmm/PR-3.md). + +### PR 4 — Make guest control channels transport-neutral + +WSL PR. Depends on the agreed transport contract and PR 3. + +- [x] **Implemented - B5:** Abstract guest control channels away from the HvSocket-specific implementation. `ConnectToGuest` and connector callbacks are wired through instance/process/session creation; `SocketChannel` handles AF_UNIX separately from existing Windows I/O (refactor/backend; S3). + +Introduce and exercise the abstraction with existing behavior first. Do not conflate the host-side gRPC socket with the guest-control transport; they are separate contracts. + +### PR 5 — Backend selection, fail-fast behavior, and manual rollback + +WSL PR. Depends on PRs 3–4. + +- [x] **Implemented - B3 (accepted revised scope):** Centralize WSL VM creation/selection in `_CreateVm()` with `IWslCoreVm` callers; no separate factory is required (S1, S2, S12). +- [x] **Implemented - B4 (accepted policy):** Keep HCS as default, OpenVMM explicitly opt-in, failures explicit, and rollback manual after shutdown (S2, S12). +- [x] **Implemented - B7:** Add configuration-parsing and integration coverage for backend selection, failure handling, shutdown, and rollback (S12). + +[PR 5 details: accepted policy, test cases, CI requirements, and build evidence](WSL-openvmm/PR-5.md). + +### PR 6 — OpenVMM process supervision and gRPC client + +WSL PR. Depends on the contracts and shared abstractions. + +- [x] **Implemented - C1:** Implement process launch, lifetime, and termination handling. User-token launch, kill-on-close job, process registry/wait, cleanup, timeout-based forced termination, and exit callbacks are present (backend; S5). +- [x] **Implemented - C2 (accepted RPC-layer scope):** Bounded gRPC/AF_UNIX calls, fail-closed status handling, cancellation, and HRESULT mapping are implemented with client-owned per-handle synchronization (rpc; S13). Closed for this scope; backend lifecycle integration remains a separate follow-up. +- [x] **Implemented - C7 (accepted logging scope):** Existing backend traces, distinct RPC error categories, and ordinary tracing macros through the once-initialized debugger subscriber are implemented (S3, S5, S7, S9, S13, S14). No ETW provider, structured event schema, or correlation metadata is required for closeout. +- [ ] **Partial - G4, transport portion:** AF_UNIX startup, deadlines, cancellation races, fail-closed status, and silent/wrong-protocol peers have regression coverage (rpc; S13). Remaining: actual process crashes, lost-response resource state, and service shutdown races. + +**Backend follow-ups retained outside the C2/C7 closeout:** + +- [ ] Wire cancellation into process exit/termination, coordinate RPC-handle lifetime, and cover teardown followed by fresh-process recovery through service call sites. +- [ ] Integrate service/process diagnostics and define the backend-wide failure taxonomy. + +Your current AF_UNIX work belongs here. Distinguish establishing/re-establishing a connection from replaying a VM-management operation whose outcome is unknown. + +[PR 6 details: revised RPC contract, regression coverage, and remaining backend integration](WSL-openvmm/PR-6.md). + +### PR 7 — Configure, boot, and manage an OpenVMM VM + +WSL PR. Depends on PRs 4–6 and the required upstream RPC/device capabilities. + +- [ ] **Partial - C3:** Translate WSL VM settings into OpenVMM configuration. Kernel/initrd/modules, command line, CPU, capped memory, disks and NIC are translated (rpc/backend; S6). Several settings are forcibly disabled/overridden; complete or explicitly approve the supported-setting matrix. +- [ ] **Partial - C4:** Configure boot, memory, processors, serial, vsock, disks, pmem, and virtio-rng. Boot/CPU/memory/serial/virtio-console/vsock/SCSI configuration exists (rpc/backend; S6); pmem and virtio-rng are not configured by the new builder. Sending boot entropy is not virtio-rng support. +- [x] **Implemented - C5:** Implement start, stop, shutdown, terminate, and unexpected-exit handling. Create/resume, channel shutdown, teardown/quit, timed force termination, process-exit signaling and session callback routing are wired (rpc/backend; S2, S5). Runtime reliability coverage is tracked separately in G2/G4. +- [ ] **TODO - G2, initial slice:** Automate boot, distro launch, basic disk, vsock, console, and shutdown scenarios. The stack contains implementation and mock RPC tests, not real-VM scenario automation (S10). +- [ ] **Partial - G4, configuration portion:** Add malformed-configuration negative tests. PR 5 adds invalid backend-boolean parsing/warnings and early unsupported-system-distro failure/retry coverage (S12). Broader malformed VM/RPC configuration and post-allocation failure cases remain outstanding (S6, S10). + +This is the first usable, gated backend milestone, initially retaining the memory cap. Consume already-implemented boot/RPC functionality rather than duplicating it. + +### PR 8 — VirtioFS integration and mixed-elevation access + +WSL integration PR. Depends on PR 7 and upstream filesystem capabilities. + +- [x] **Implemented - D1:** Implement VirtioFS-based cross-OS filesystem access. Share request/response handling, guest listener/worker, canonical host paths, read-only options, and VPCI share RPCs are connected (rpc/backend; S7). This is creator-elevation access; D2 remains separate. +- [ ] **TODO - D2:** Support admin and non-admin Windows file access from the same VM. `AddVirtioFsShare` rejects `Admin != m_creatorElevated`; `InitializeDrvFs` explicitly rejects switching elevation context after creation (backend; S7). The guard is a limitation, not mixed-elevation support. + +Apply the broker/identity decisions from PR 1. If additional OpenVMM or DeviceHost mechanisms are required, land those as separate prerequisite PRs; do not bundle cross-repository implementation into this WSL PR. + +### PR 9 — Consommé networking integration and compatibility + +WSL integration PR. Depends on PR 7 and the required OpenVMM networking/RPC changes. + +- [x] **Implemented - D4:** Integrate initial consomme networking. NIC configuration, mini_init networking/DHCP setup, port tracker and localhost bind/unbind RPCs are wired (rpc/backend; S8). +- [ ] **Partial - D5:** Consume the required consomme IPv6 changes. Guest configuration enables IPv6 and RPCs handle `AF_INET6`/`::1`, with mock field assertions (rpc/backend; S8, S10). Confirm the consumed OpenVMM version satisfies the upstream dependency and exercise real IPv6 behavior. +- [ ] **TODO - D7:** Validate DNS, localhost, VPN, proxy, firewall, IPv6, and multi-distro behavior. Configuration checks and mock port serialization are not networking compatibility results; no such matrix/automation is added. + +PRs 8 and 9 can proceed independently. Existing IPv6 support is a starting point—not proof that the entire WSL networking matrix passes. + +### PR 10 — Complete the OpenVMM crash-artifact producer + +OpenVMM PR. Can proceed in parallel once the artifact contract is agreed. + +- [ ] **External - F1:** Complete the mechanism to produce a VM saved-state or crash artifact. Earlier evidence identified merged OpenVMM #3882 for triple faults; this WSL stack neither implements nor proves the complete producer contract. Track upstream completion and consumption separately. +- [ ] **External - F2:** Add the compatible compression writer for the selected format, or update the consumer. Neither change is present in this WSL stack; verify the selected upstream format and remaining consumer work. + +Build on merged #3882. If the chosen solution instead changes the consumer, place F2 in PR 11, rather than implementing both approaches. + +The previously observed OpenVMM `Add crash dump path option` commit belongs to this diagnostics work, not the network/filesystem RPC story in #4420. It is outside the WSL stack assessed here. + +### PR 11 — WSL/WSLC diagnostic collection and debugger integration + +WSL PR. Depends on PR 7; artifact collection additionally depends on PR 10. + +- [ ] **TODO - C6:** Add kernel debugger support. Debug console/early-console output is wired, but OpenVMM kernel-debugger configuration is not (backend; S6, S9). Do not count a debug console as a debugger. +- [ ] **Partial - F3:** Integrate artifact collection into WSL and WSLC diagnostics. WSL reuses `DmesgCollector`, adds user-accessible console pipes, and writes OpenVMM stdout/stderr locally (backend; S9). Saved-state/crash-artifact collection and WSLC integration are not added. +- [ ] **TODO - F4:** Extract kernel-panic details from dmesg collector output. The reused collector buffers/emits raw guest log lines; the stack adds pipe access, not panic parsing or attribution (S9). +- [ ] **TODO - F6:** Update log-collection scripts for OpenVMM logs and traces. No `diagnostics` scripts change; creating a local `.log` file is only a prerequisite. +- [ ] **Partial - F7:** Add telemetry for dump success/failure, parsing, and backend crash buckets. Process-exit code/VM-ID traces and guest logs exist (backend; S9), but dump outcome, parsing and crash-bucket telemetry are not implemented. + +Bring this forward alongside filesystem/networking work: actionable diagnostics are useful before broad stress testing, not just before release. + +### PR 12 — WHP memory contract and accounting design + +Design/documentation PR. Start alongside PR 1, despite its position in this implementation sequence. + +- [ ] **External - E1:** Confirm WHP deferred-commit and sparse-allocation requirements with the WHP owner. Owner agreement is not evidenced by WSL branch changes; attach the decision separately. +- [ ] **External - E5:** Define host-commit versus guest-visible memory accounting and telemetry. No accounting contract or new memory telemetry is present; the 4-GiB clamp is not accounting (S6). + +Owner agreement is a prerequisite, not something a code PR alone can accomplish. Explicitly determine whether host/WHP changes are required; ballooning alone should not be assumed to solve upfront host commit. + +### PR 13 — Virtio-balloon support + +OpenVMM PR. Depends on PR 12. + +- [ ] **External - E2:** Implement the virtio-balloon support required by WSL and WSLC. No balloon configuration/control integration is added in this WSL stack (S6). Track separate OpenVMM implementation and its WSL/WSLC consumption. + +Keep this independently reviewable from cold-discard and nested virtualization. Any WSL policy/configuration wiring should be a separate consuming PR if it requires code changes there. + +### PR 14 — Cold-discard support and memory-elasticity integration + +OpenVMM PR, followed by a WSL integration PR where needed. Depends on PRs 12–13. + +- [ ] **External - E3:** Implement qemu-style cold-discard hints or the approved equivalent. No new host memory-discard integration is present. Existing guest reclaim settings and disk trim commands are not evidence of this host-memory feature. +- [ ] **TODO - E4:** Validate grow, shrink, reclaim, pressure, suspend, and multi-VM behavior. No elasticity scenario coverage/results are added; the memory cap remains (S6). + +Do not remove the WSL memory cap merely because the device exists. Removal should follow demonstrated host-commit and reclaim behavior plus the memory stress/performance coverage below. + +### PR 15 — Remaining nested-virtualization support + +OpenVMM/WHP-owned implementation PRs. Independent of balloon/discard unless a concrete shared dependency emerges. + +- [ ] **External - E6:** Complete the remaining nested-virtualization work. The new WSL RPC configuration does not wire a nested-virtualization setting (S6); track OpenVMM/WHP completion and explicit WSL consumption separately. + +Keep this a separate workstream. The deliverable groups nesting with memory, but its description does not establish that they must form one linear code stack. + +### PR 16 — Complete compatibility automation and cross-feature stress + +Test PRs in the repository owning each harness. Depends on the applicable feature PRs. + +- [ ] **TODO - G2, completion:** Complete automated disk, VirtioFS, vsock, console, networking, boot, launch, and shutdown coverage. Two mock RPC tests do not exercise these real-VM scenarios (S10). +- [ ] **TODO - G3:** Add multi-distro, repeated attach/detach, restart, update, and hot-add stress. Share retry assertions are not repeated real-device or multi-VM stress. +- [ ] **TODO - G4, completion:** Add host-resource-pressure tests and complete cross-feature failure coverage. Only the narrow mock-RPC portion in PR 6 is present (S10). +- [ ] **TODO - E7:** Add memory-elasticity and nested-workload stress/performance coverage. No such harness/results are added. + +The feature PRs should already carry their focused tests. This layer covers interactions, longer-running workloads, and the full matrix. + +### PR 17 — Enforce performance and rollout gates + +WSL validation/release-infrastructure PR. Depends on representative results from the preceding work. + +- [ ] **TODO - G5, completion:** Establish the comparable OpenVMM baselines across startup, memory, CPU, I/O, networking, and reliability. Slow-operation logging does not supply comparative baseline results. +- [ ] **TODO - G6:** Use WSLC startup-time P95 measure 63134665 as a rollout signal. No measure integration is added. +- [ ] **TODO - G7, enforcement:** Make the agreed preview/GA thresholds enforceable gates. Compile-time and config opt-in gates are not health/performance release gates. + +Do not invent numerical thresholds from the work-item text; it specifies that they must be defined, not what their values are. + +## Stack boundaries and dependencies outside your seven items + +Use a short WSL foundation stack for PRs 3–7, then separate filesystem, networking, diagnostics, and memory workstreams. OpenVMM prerequisite PRs belong in OpenVMM stacks; connect them to WSL consumers through explicit dependency links and consumed versions—not one cross-repository branch chain. + +Three sibling deliverables need to remain visible in the dependency map: + +| Dependency | Where it matters | +| --- | --- | +| 63428742 — Guest channels and virtio devices | PRs 4, 7, and 8 require the selected mini_init transport, independent control/diagnostic channels, and appropriate VirtioFS/device support. This is a real dependency omitted from the seven-item list. | +| 62679000 — Productization and release pipeline | Required to consume supported, versioned OpenVMM artifacts and ship the result; avoid making prototype completion synonymous with release readiness. | +| 63428748 — Preview rollout and GA readiness | Owns rollout execution. PRs 5 and 17 should provide selection controls and gates without duplicating its rollout ownership. | + +Also align PRs 6 and 11 with 63459355 — Diagnosability. GPU support is explicitly non-blocking in the parent scenario and should not hold up this core sequence. diff --git a/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp b/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp index 525d7f5c5a..af0f976ef2 100644 --- a/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp +++ b/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp @@ -239,32 +239,37 @@ void OpenVmmVirtualMachineBackend::DestroyVm(WslOpenVmmVm* Vm) noexcept WslOpenVmmDestroyVm(&Vm); } -OpenVmmVirtualMachineBackend::OpenVmmVirtualMachineBackend() = default; +OpenVmmVirtualMachineBackend::OpenVmmVirtualMachineBackend() : m_state(std::make_unique()) +{ +} OpenVmmVirtualMachineBackend::~OpenVmmVirtualMachineBackend() noexcept { - if (m_processWait) + if (m_state->m_processWait) { - SetThreadpoolWait(m_processWait.get(), nullptr, nullptr); - WaitForThreadpoolWaitCallbacks(m_processWait.get(), TRUE); - m_processWait.reset(); + SetThreadpoolWait(m_state->m_processWait.get(), nullptr, nullptr); + WaitForThreadpoolWaitCallbacks(m_state->m_processWait.get(), TRUE); + m_state->m_processWait.reset(); } - if (m_vm && WaitForSingleObject(m_process.get(), 0) == WAIT_TIMEOUT) + if (m_state->m_vm && WaitForSingleObject(m_state->m_process.get(), 0) == WAIT_TIMEOUT) { - LOG_IF_FAILED(WslOpenVmmVmTeardown(m_vm.get())); - LOG_IF_FAILED(WslOpenVmmVmQuit(m_vm.get())); + LOG_IF_FAILED(WslOpenVmmVmTeardown(m_state->m_vm.get())); + LOG_IF_FAILED(WslOpenVmmVmQuit(m_state->m_vm.get())); } - m_vm.reset(); - m_job.reset(); - if (m_process) + m_state->m_vm.reset(); + m_state->m_job.reset(); + if (m_state->m_process) { // Confirm exit before releasing backing files or deleting socket paths. - LOG_LAST_ERROR_IF(WaitForSingleObject(m_process.get(), INFINITE) == WAIT_FAILED); + LOG_LAST_ERROR_IF(WaitForSingleObject(m_state->m_process.get(), INFINITE) == WAIT_FAILED); } - m_backingFiles.clear(); - if (m_directoryCreated) + m_state->m_backingFiles.clear(); + if (m_state->m_directoryCreated) { - LOG_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(m_socketDirectory.c_str())); + DeleteOwnedFile(m_state->m_rpcSocketPath); + DeleteOwnedFile(m_state->m_vsockPath); + DeleteOwnedFile(m_state->m_socketDirectory / L"openvmm.log"); + LOG_IF_WIN32_BOOL_FALSE(RemoveDirectoryW(m_state->m_socketDirectory.c_str())); } } @@ -272,7 +277,7 @@ std::unique_ptr OpenVmmVirtualMachineBackend::Crea { auto description = wsl::windows::common::vm::openvmm::ValidateCreateRequest(Request); auto backend = std::unique_ptr{new OpenVmmVirtualMachineBackend{}}; - backend->m_description = std::move(description); + backend->m_state->m_description = std::move(description); backend->Initialize(Request); return backend; } @@ -290,18 +295,18 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) auto id = wsl::shared::string::GuidToString(Request.VmId, wsl::shared::string::GuidToStringFlags::None); std::erase(id, L'-'); // An exclusive directory creation prevents shortened path IDs from aliasing another VM. - m_socketDirectory = filesystem::GetTempFolderPath(GetCurrentProcessToken()) / (L"ov-" + id.substr(0, 16)); - m_rpcSocketPath = m_socketDirectory / L"r"; - m_vsockPath = m_socketDirectory / L"v"; + m_state->m_socketDirectory = filesystem::GetTempFolderPath(GetCurrentProcessToken()) / (L"ov-" + id.substr(0, 16)); + m_state->m_rpcSocketPath = m_state->m_socketDirectory / L"r"; + m_state->m_vsockPath = m_state->m_socketDirectory / L"v"; const auto longestPath = - wsl::shared::string::WideToMultiByte(m_vsockPath.native() + L"_ffffffff-facb-11e6-bd58-64006a7986d3"); + wsl::shared::string::WideToMultiByte(m_state->m_vsockPath.native() + L"_ffffffff-facb-11e6-bd58-64006a7986d3"); SOCKADDR_UN address{}; THROW_HR_IF_MSG( E_INVALIDARG, longestPath.size() >= sizeof(address.sun_path), "OpenVMM guest socket path exceeds the AF_UNIX limit: %hs", longestPath.c_str()); - THROW_HR_IF(E_INVALIDARG, m_rpcSocketPath.native().find_first_of(L",\"\r\n") != std::wstring::npos); + THROW_HR_IF(E_INVALIDARG, m_state->m_rpcSocketPath.native().find_first_of(L",\"\r\n") != std::wstring::npos); const auto tokenUser = wil::get_token_information(GetCurrentProcessToken()); const auto sid = wslutil::SidToString(tokenUser->User.Sid); @@ -310,31 +315,32 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) THROW_IF_WIN32_BOOL_FALSE(ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), SDDL_REVISION_1, &security, nullptr)); SECURITY_ATTRIBUTES attributes{sizeof(attributes), security.get(), FALSE}; { - m_backingFiles.push_back(OpenBackingFile(Request.Boot.KernelPath, true)); - m_backingFiles.push_back(OpenBackingFile(Request.Boot.InitrdPath, true)); - auto lock = m_lock.lock_exclusive(); + m_state->m_backingFiles.push_back(OpenBackingFile(Request.Boot.KernelPath, true)); + m_state->m_backingFiles.push_back(OpenBackingFile(Request.Boot.InitrdPath, true)); + auto lock = m_state->m_lock.lock_exclusive(); for (const auto& disk : Request.BootDisks) { - const auto& attachment = m_description.BootDisks.at(disk.Key); + const auto& attachment = m_state->m_description.BootDisks.at(disk.Key); auto backingFile = OpenBackingFile(std::get(disk.Disk.Source).Path, disk.Disk.ReadOnly); - m_attachedDisks.emplace(attachment.Id.Value, AttachedDisk{attachment, std::move(backingFile)}); + m_state->m_attachedDisks.emplace( + attachment.Id.Value, State::AttachedDisk{attachment, std::move(backingFile)}); } - m_nextDiskId = Request.BootDisks.size() + 1; - THROW_IF_WIN32_BOOL_FALSE(CreateDirectoryW(m_socketDirectory.c_str(), &attributes)); - m_directoryCreated = true; + m_state->m_nextDiskId = Request.BootDisks.size() + 1; + THROW_IF_WIN32_BOOL_FALSE(CreateDirectoryW(m_state->m_socketDirectory.c_str(), &attributes)); + m_state->m_directoryCreated = true; } UniqueConfig config; THROW_IF_FAILED(WslOpenVmmCreateConfig(config.put())); THROW_IF_FAILED(WslOpenVmmConfigSetKernelPath(config.get(), Request.Boot.KernelPath.c_str())); THROW_IF_FAILED(WslOpenVmmConfigSetInitrdPath(config.get(), Request.Boot.InitrdPath.c_str())); - THROW_IF_FAILED(WslOpenVmmConfigSetKernelCmdLine(config.get(), m_description.Boot.KernelCommandLine.c_str())); - THROW_IF_FAILED(WslOpenVmmConfigSetMemoryMb(config.get(), m_description.Memory.SizeBytes / (1024 * 1024))); + THROW_IF_FAILED(WslOpenVmmConfigSetKernelCmdLine(config.get(), m_state->m_description.Boot.KernelCommandLine.c_str())); + THROW_IF_FAILED(WslOpenVmmConfigSetMemoryMb(config.get(), m_state->m_description.Memory.SizeBytes / (1024 * 1024))); THROW_IF_FAILED(WslOpenVmmConfigSetProcessorCount(config.get(), Request.Processor.Count)); - THROW_IF_FAILED(WslOpenVmmConfigSetHvSocketPath(config.get(), m_vsockPath.c_str())); + THROW_IF_FAILED(WslOpenVmmConfigSetHvSocketPath(config.get(), m_state->m_vsockPath.c_str())); for (const auto& disk : Request.BootDisks) { - const auto& attachment = m_description.BootDisks.at(disk.Key); + const auto& attachment = m_state->m_description.BootDisks.at(disk.Key); THROW_IF_FAILED(WslOpenVmmConfigAddBootDisk( config.get(), attachment.GuestAddress.Controller, @@ -354,18 +360,18 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) } } - m_job = helpers::CreateKillOnCloseJob(); + m_state->m_job = helpers::CreateKillOnCloseJob(); const auto commandLine = - std::format(L"\"{}\" --rpc \"path={},transport=grpc\"", executable.native(), m_rpcSocketPath.native()); + std::format(L"\"{}\" --rpc \"path={},transport=grpc\"", executable.native(), m_state->m_rpcSocketPath.native()); SubProcess process{executable.c_str(), commandLine.c_str()}; process.SetFlags(CREATE_NO_WINDOW); - process.SetJobObject(m_job.get()); + process.SetJobObject(m_state->m_job.get()); SECURITY_ATTRIBUTES inheritable{sizeof(inheritable), nullptr, TRUE}; wil::unique_hfile logFile; wil::unique_hfile input; { logFile.reset(CreateFileW( - (m_socketDirectory / L"openvmm.log").c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); + (m_state->m_socketDirectory / L"openvmm.log").c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); THROW_LAST_ERROR_IF(!logFile); input.reset(CreateFileW( L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); @@ -376,14 +382,14 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) THROW_IF_WIN32_BOOL_FALSE( DuplicateHandle(GetCurrentProcess(), logFile.get(), GetCurrentProcess(), errorLogFile.put(), 0, TRUE, DUPLICATE_SAME_ACCESS)); process.SetStdHandles(input.get(), logFile.get(), errorLogFile.get()); - m_process = process.Start(); - m_processWait.reset(CreateThreadpoolWait(OnProcessExit, this, nullptr)); - THROW_LAST_ERROR_IF(!m_processWait); - SetThreadpoolWait(m_processWait.get(), m_process.get(), nullptr); + m_state->m_process = process.Start(); + m_state->m_processWait.reset(CreateThreadpoolWait(OnProcessExit, this, nullptr)); + THROW_LAST_ERROR_IF(!m_state->m_processWait); + SetThreadpoolWait(m_state->m_processWait.get(), m_state->m_process.get(), nullptr); THROW_IF_FAILED_MSG( - WslOpenVmmCreateVm(config.addressof(), m_rpcSocketPath.c_str(), c_rpcTimeoutMs, m_vm.put()), + WslOpenVmmCreateVm(config.addressof(), m_state->m_rpcSocketPath.c_str(), c_rpcTimeoutMs, m_state->m_vm.put()), "Failed to create OpenVMM VM"); - const auto result = WaitForSingleObject(m_process.get(), 0); + const auto result = WaitForSingleObject(m_state->m_process.get(), 0); THROW_LAST_ERROR_IF(result == WAIT_FAILED); THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED), result == WAIT_OBJECT_0); } @@ -391,7 +397,7 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) void CALLBACK OpenVmmVirtualMachineBackend::OnProcessExit(PTP_CALLBACK_INSTANCE, void* Context, PTP_WAIT, TP_WAIT_RESULT) noexcept { auto& backend = *static_cast(Context); - LOG_IF_WIN32_BOOL_FALSE(SetEvent(backend.m_exitEvent.get())); + LOG_IF_WIN32_BOOL_FALSE(SetEvent(backend.m_state->m_exitEvent.get())); } VmPlatformCapabilities OpenVmmVirtualMachineBackend::QueryCapabilities() @@ -450,24 +456,24 @@ wil::unique_handle OpenVmmVirtualMachineBackend::GetTerminationEvent() const { wil::unique_handle event; THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle( - GetCurrentProcess(), m_exitEvent.get(), GetCurrentProcess(), event.put(), 0, FALSE, DUPLICATE_SAME_ACCESS)); + GetCurrentProcess(), m_state->m_exitEvent.get(), GetCurrentProcess(), event.put(), 0, FALSE, DUPLICATE_SAME_ACCESS)); return event; } void OpenVmmVirtualMachineBackend::Start() { - auto lock = m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); - THROW_IF_FAILED(WslOpenVmmVmResume(m_vm.get())); + auto lock = m_state->m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); + THROW_IF_FAILED(WslOpenVmmVmResume(m_state->m_vm.get())); } void OpenVmmVirtualMachineBackend::Terminate() { - auto lock = m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); - THROW_IF_FAILED(WslOpenVmmVmTeardown(m_vm.get())); - const auto quitResult = WslOpenVmmVmQuit(m_vm.get()); - const auto waitResult = WaitForSingleObject(m_process.get(), c_rpcTimeoutMs); + auto lock = m_state->m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); + THROW_IF_FAILED(WslOpenVmmVmTeardown(m_state->m_vm.get())); + const auto quitResult = WslOpenVmmVmQuit(m_state->m_vm.get()); + const auto waitResult = WaitForSingleObject(m_state->m_process.get(), c_rpcTimeoutMs); THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); if (waitResult != WAIT_OBJECT_0) { @@ -475,8 +481,8 @@ void OpenVmmVirtualMachineBackend::Terminate() THROW_HR(HRESULT_FROM_WIN32(WAIT_TIMEOUT)); } - m_vm.reset(); - m_attachedDisks.clear(); + m_state->m_vm.reset(); + m_state->m_attachedDisks.clear(); } void OpenVmmVirtualMachineBackend::CancelPendingOperations() noexcept @@ -508,11 +514,11 @@ VmDiskAttachment OpenVmmVirtualMachineBackend::AttachDisk(const VmDiskRequest& R { const auto& source = ValidateDiskRequest(Request); auto backingFile = OpenBackingFile(source.Path, Request.ReadOnly); - auto lock = m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); + auto lock = m_state->m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); const auto lunInUse = [&](std::uint32_t Lun) { - for (const auto& entry : m_attachedDisks) + for (const auto& entry : m_state->m_attachedDisks) { if (entry.second.Attachment.GuestAddress.Lun == Lun) { @@ -537,30 +543,30 @@ VmDiskAttachment OpenVmmVirtualMachineBackend::AttachDisk(const VmDiskRequest& R THROW_HR_IF(WSL_E_TOO_MANY_DISKS_ATTACHED, lun == c_maximumDisks); } - THROW_HR_IF(E_BOUNDS, m_nextDiskId == UINT64_MAX); + THROW_HR_IF(E_BOUNDS, m_state->m_nextDiskId == UINT64_MAX); const VmDiskAttachment attachment{ - {m_description.Identity, m_nextDiskId}, {0, lun}, Request.ReadOnly}; - const auto [disk, inserted] = m_attachedDisks.emplace( - attachment.Id.Value, AttachedDisk{attachment, std::move(backingFile)}); + {m_state->m_description.Identity, m_state->m_nextDiskId}, {0, lun}, Request.ReadOnly}; + const auto [disk, inserted] = m_state->m_attachedDisks.emplace( + attachment.Id.Value, State::AttachedDisk{attachment, std::move(backingFile)}); WI_ASSERT(inserted); - auto rollback = wil::scope_exit([&] { m_attachedDisks.erase(disk); }); + auto rollback = wil::scope_exit([&] { m_state->m_attachedDisks.erase(disk); }); THROW_IF_FAILED(WslOpenVmmVmAttachScsiDisk( - m_vm.get(), attachment.GuestAddress.Controller, attachment.GuestAddress.Lun, source.Path.c_str(), Request.ReadOnly)); - ++m_nextDiskId; + m_state->m_vm.get(), attachment.GuestAddress.Controller, attachment.GuestAddress.Lun, source.Path.c_str(), Request.ReadOnly)); + ++m_state->m_nextDiskId; rollback.release(); return attachment; } void OpenVmmVirtualMachineBackend::DetachDisk(VmDiskId Disk) { - THROW_HR_IF(E_INVALIDARG, Disk.Value == 0 || !IsEqualGUID(Disk.Owner.VmId, m_description.Identity.VmId)); - auto lock = m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); - const auto disk = m_attachedDisks.find(Disk.Value); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), disk == m_attachedDisks.end()); + THROW_HR_IF(E_INVALIDARG, Disk.Value == 0 || !IsEqualGUID(Disk.Owner.VmId, m_state->m_description.Identity.VmId)); + auto lock = m_state->m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); + const auto disk = m_state->m_attachedDisks.find(Disk.Value); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), disk == m_state->m_attachedDisks.end()); THROW_IF_FAILED(WslOpenVmmVmDetachScsiDisk( - m_vm.get(), disk->second.Attachment.GuestAddress.Controller, disk->second.Attachment.GuestAddress.Lun)); - m_attachedDisks.erase(disk); + m_state->m_vm.get(), disk->second.Attachment.GuestAddress.Controller, disk->second.Attachment.GuestAddress.Lun)); + m_state->m_attachedDisks.erase(disk); } VmFileSystemDevice OpenVmmVirtualMachineBackend::CreateFileSystemDevice(const VmFileSystemDeviceRequest&) diff --git a/src/windows/service/exe/OpenVmmVirtualMachineBackend.h b/src/windows/service/exe/OpenVmmVirtualMachineBackend.h index 4e0dcca4dc..4ad86152ee 100644 --- a/src/windows/service/exe/OpenVmmVirtualMachineBackend.h +++ b/src/windows/service/exe/OpenVmmVirtualMachineBackend.h @@ -66,24 +66,29 @@ class OpenVmmVirtualMachineBackend : public IVirtualMachineBackend static void DestroyVm(WslOpenVmmVm* Vm) noexcept; using UniqueVm = wil::unique_any; - struct AttachedDisk + struct State { - VmDiskAttachment Attachment; - wil::unique_hfile BackingFile; + struct AttachedDisk + { + VmDiskAttachment Attachment; + wil::unique_hfile BackingFile; + }; + + wil::srwlock m_lock; + VmDescription m_description; + _Guarded_by_(m_lock) std::map m_attachedDisks; + _Guarded_by_(m_lock) std::uint64_t m_nextDiskId = 1; + UniqueVm m_vm; + wil::unique_handle m_process; + wil::unique_handle m_job; + std::vector m_backingFiles; + std::filesystem::path m_socketDirectory; + std::filesystem::path m_rpcSocketPath; + std::filesystem::path m_vsockPath; + bool m_directoryCreated = false; + wil::unique_event m_exitEvent{wil::EventOptions::ManualReset}; + wil::unique_threadpool_wait m_processWait; }; - wil::srwlock m_lock; - VmDescription m_description{}; - _Guarded_by_(m_lock) std::map m_attachedDisks; - _Guarded_by_(m_lock) std::uint64_t m_nextDiskId = 1; - UniqueVm m_vm; - wil::unique_handle m_process; - wil::unique_handle m_job; - std::vector m_backingFiles; - std::filesystem::path m_socketDirectory; - std::filesystem::path m_rpcSocketPath; - std::filesystem::path m_vsockPath; - bool m_directoryCreated = false; - wil::unique_event m_exitEvent{wil::EventOptions::ManualReset}; - wil::unique_threadpool_wait m_processWait; + std::unique_ptr m_state; }; \ No newline at end of file diff --git a/test/windows/OpenVmmVirtualMachineBackendTests.cpp b/test/windows/OpenVmmVirtualMachineBackendTests.cpp index 22998b1757..52b0705493 100644 --- a/test/windows/OpenVmmVirtualMachineBackendTests.cpp +++ b/test/windows/OpenVmmVirtualMachineBackendTests.cpp @@ -1,5 +1,3 @@ -// Copyright (C) Microsoft Corporation. All rights reserved. - #include "precomp.h" #include "Common.h" #include "OpenVmmVirtualMachineBackend.h" From c4f36a759c9d49ed68566e315aa1157eff584604 Mon Sep 17 00:00:00 2001 From: Daman Mulye Date: Mon, 21 Sep 2026 14:16:35 -0700 Subject: [PATCH 07/10] previous PR feedback --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7451e1793a..9093babe1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,7 +135,6 @@ find_nuget_package(Microsoft.WSL.TestData WSL_TEST_DATA /) find_nuget_package(Microsoft.WSLg WSLG /build/native/bin) find_nuget_package(vswhere VSWHERE /tools) find_nuget_package(Wix WIX /tools/net6.0/any) -find_nuget_package(Microsoft.WSL.OpenVMM WSL_OPENVMM /build/native) # Architecture-specific nuget packages from the OS repo. if (${TARGET_PLATFORM} STREQUAL "x64") From e60bae45c80aac6137b81ff4c52058ab1e79fce0 Mon Sep 17 00:00:00 2001 From: Daman Mulye Date: Tue, 22 Sep 2026 16:33:29 -0700 Subject: [PATCH 08/10] feedback --- src/windows/common/CMakeLists.txt | 10 +- .../exe => common}/IVirtualMachineBackend.h | 0 .../OpenVmmVirtualMachineBackend.cpp | 312 +++++++----------- .../OpenVmmVirtualMachineBackend.h | 41 +-- .../exe => common}/VirtualMachineBackend.cpp | 0 src/windows/service/exe/CMakeLists.txt | 11 - test/windows/CMakeLists.txt | 1 - .../OpenVmmVirtualMachineBackendTests.cpp | 46 +-- 8 files changed, 140 insertions(+), 281 deletions(-) rename src/windows/{service/exe => common}/IVirtualMachineBackend.h (100%) rename src/windows/{service/exe => common}/OpenVmmVirtualMachineBackend.cpp (54%) rename src/windows/{service/exe => common}/OpenVmmVirtualMachineBackend.h (72%) rename src/windows/{service/exe => common}/VirtualMachineBackend.cpp (100%) diff --git a/src/windows/common/CMakeLists.txt b/src/windows/common/CMakeLists.txt index ab9377ed7f..918e584a48 100644 --- a/src/windows/common/CMakeLists.txt +++ b/src/windows/common/CMakeLists.txt @@ -52,6 +52,8 @@ set(SOURCES WslCoreNetworkingSupport.cpp WslInstall.cpp WslSecurity.cpp + VirtualMachineBackend.cpp + OpenVmmVirtualMachineBackend.cpp SlowOperationWatcher.cpp WslTelemetry.cpp wslutil.cpp @@ -143,6 +145,8 @@ set(HEADERS WslCoreNetworkingSupport.h WslInstall.h WslSecurity.h + IVirtualMachineBackend.h + OpenVmmVirtualMachineBackend.h SlowOperationWatcher.h WslTelemetry.h wslutil.h @@ -156,12 +160,14 @@ add_dependencies(common wslserviceidl wsldevicehostidl localization wslservicemc target_precompile_headers(common PRIVATE precomp.h) set_target_properties(common PROPERTIES FOLDER windows) -target_include_directories(common PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/../service/mc/${TARGET_PLATFORM}/${CMAKE_BUILD_TYPE}) +target_include_directories(common PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/../service/mc/${TARGET_PLATFORM}/${CMAKE_BUILD_TYPE}) # common calls into HCS, HNS, the IP helper API, the VHD APIs, MSI, WinTrust and # the config file parser, so anything linking common needs those libraries too. # PUBLIC propagates them instead of making each consumer restate them. -target_link_libraries(common PUBLIC ${HCS_LINK_LIBRARIES} ${MSI_LINK_LIBRARIES} VirtDisk.lib configfile) +target_link_libraries(common PUBLIC ${HCS_LINK_LIBRARIES} ${MSI_LINK_LIBRARIES} VirtDisk.lib configfile wslopenvmm_client) # WSLCUserSettings.cpp uses yaml-cpp headers. # The per-source include path and definition below only apply when the file is diff --git a/src/windows/service/exe/IVirtualMachineBackend.h b/src/windows/common/IVirtualMachineBackend.h similarity index 100% rename from src/windows/service/exe/IVirtualMachineBackend.h rename to src/windows/common/IVirtualMachineBackend.h diff --git a/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp b/src/windows/common/OpenVmmVirtualMachineBackend.cpp similarity index 54% rename from src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp rename to src/windows/common/OpenVmmVirtualMachineBackend.cpp index af0f976ef2..9a00db2cf9 100644 --- a/src/windows/service/exe/OpenVmmVirtualMachineBackend.cpp +++ b/src/windows/common/OpenVmmVirtualMachineBackend.cpp @@ -16,13 +16,12 @@ Module Name: #include "OpenVmmVirtualMachineBackend.h" #include #include +#include "HandleIO.h" #include "SubProcess.h" #include "wslopenvmm.h" namespace { -constexpr UINT64 c_memoryGranularity = 2ULL * 1024 * 1024; -constexpr UINT64 c_maximumMemory = 4ULL * 1024 * 1024 * 1024; constexpr UINT32 c_maximumDisks = 254; constexpr UINT32 c_rpcTimeoutMs = 30000; constexpr HRESULT c_notSupported = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); @@ -48,66 +47,13 @@ void ValidateFeature(VmFeatureRequest Request, PCWSTR Setting) THROW_HR(E_INVALIDARG); } -void ValidatePath(const std::filesystem::path& Path) -{ - THROW_HR_IF_MSG( - E_INVALIDARG, - Path.empty() || !Path.is_absolute() || Path.native().find(L'\0') != std::wstring::npos, - "OpenVMM requires an absolute, nonempty host path"); -} - -const VmVirtualDiskSource& ValidateDiskRequest(const VmDiskRequest& Request) +const VmVirtualDiskSource& GetVirtualDiskSource(const VmDiskRequest& Request) { const auto* source = std::get_if(&Request.Source); THROW_HR_IF(c_notSupported, source == nullptr); - ValidatePath(source->Path); - switch (source->Format) - { - case VmDiskFormat::Vhd: - THROW_HR_IF(E_INVALIDARG, _wcsicmp(source->Path.extension().c_str(), L".vhd") != 0); - break; - case VmDiskFormat::Vhdx: - THROW_HR_IF(E_INVALIDARG, _wcsicmp(source->Path.extension().c_str(), L".vhdx") != 0); - break; - default: - THROW_HR(E_INVALIDARG); - } - - if (Request.Placement) - { - THROW_HR_IF( - c_notSupported, Request.Placement->Address.Controller != 0 || Request.Placement->Address.Lun >= c_maximumDisks); - } - return *source; } -wil::unique_hfile OpenBackingFile(const std::filesystem::path& Path, bool ReadOnly) -{ - wil::unique_hfile file{CreateFileW( - Path.c_str(), - GENERIC_READ | (ReadOnly ? 0 : GENERIC_WRITE), - FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - nullptr)}; - THROW_LAST_ERROR_IF(!file); - file.reset(); - - // Pin the file without conflicting with the VMM's disk sharing mode. - file.reset(CreateFileW(Path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); - THROW_LAST_ERROR_IF(!file); - return file; -} - -void ValidateConsolePath(const std::filesystem::path& Path) -{ - ValidatePath(Path); - THROW_HR_IF_MSG( - c_notSupported, !Path.native().starts_with(L"\\\\.\\pipe\\"), "OpenVMM consoles require a caller-provided named pipe"); -} - void DeleteOwnedFile(const std::filesystem::path& Path) noexcept { if (!Path.empty() && !DeleteFileW(Path.c_str())) @@ -124,19 +70,9 @@ void DeleteOwnedFile(const std::filesystem::path& Path) noexcept VmDescription wsl::windows::common::vm::openvmm::ValidateCreateRequest(const VmCreateRequest& Request) { - THROW_HR_IF(E_INVALIDARG, IsEqualGUID(Request.VmId, GUID_NULL)); - THROW_HR_IF(E_INVALIDARG, Request.Processor.Count == 0 || Request.Memory.SizeBytes == 0); THROW_HR_IF_MSG(c_notSupported, wsl::shared::Arm64, "OpenVMM direct boot is currently supported only on x64"); - ValidatePath(Request.Boot.KernelPath); - ValidatePath(Request.Boot.InitrdPath); - THROW_HR_IF( - E_INVALIDARG, - Request.Boot.GuestCommandLine.find(L'\0') != std::wstring::npos || Request.Boot.UserCommandLine.find(L'\0') != std::wstring::npos); - THROW_HR_IF( - E_INVALIDARG, - Request.Boot.Method != VmBootMethod::Automatic && Request.Boot.Method != VmBootMethod::LinuxDirect && - Request.Boot.Method != VmBootMethod::Uefi); THROW_HR_IF(c_notSupported, Request.Boot.Method == VmBootMethod::Uefi); + THROW_HR_IF(c_notSupported, Request.Boot.Method != VmBootMethod::Automatic && Request.Boot.Method != VmBootMethod::LinuxDirect); THROW_HR_IF(c_notSupported, Request.Boot.RequestedDmaBounceBufferBytes.has_value()); VmDescription description; @@ -144,12 +80,6 @@ VmDescription wsl::windows::common::vm::openvmm::ValidateCreateRequest(const VmC description.Backend = BackendKind::OpenVmm; description.Processor.Count = Request.Processor.Count; description.Memory.SizeBytes = Request.Memory.SizeBytes; - THROW_HR_IF(E_INVALIDARG, Request.Memory.SizeBytes % c_memoryGranularity != 0); - - THROW_HR_IF_MSG( - c_notSupported, - description.Memory.SizeBytes < c_memoryGranularity || Request.Memory.SizeBytes > c_maximumMemory, - "OpenVMM currently supports memory sizes from 2 MiB to 4 GiB; memory is not silently capped"); ValidateFeature(Request.Processor.NestedVirtualization, L"nested virtualization"); ValidateFeature(Request.Processor.PerfmonPmu, L"PMU"); ValidateFeature(Request.Processor.PerfmonLbr, L"LBR"); @@ -159,7 +89,6 @@ VmDescription wsl::windows::common::vm::openvmm::ValidateCreateRequest(const VmC if (Request.CrashCapture) { - THROW_HR_IF(E_INVALIDARG, Request.CrashCapture->Policy != VmSelectionPolicy::Required && Request.CrashCapture->Policy != VmSelectionPolicy::Preferred); THROW_HR_IF(c_notSupported, Request.CrashCapture->Policy == VmSelectionPolicy::Required); } @@ -174,40 +103,29 @@ VmDescription wsl::windows::common::vm::openvmm::ValidateCreateRequest(const VmC description.Boot.KernelCommandLine += Request.Boot.UserCommandLine; } - bool serialConfigured = false; - bool virtioConfigured = false; for (const auto& console : Request.Consoles) { - if (const auto* serial = std::get_if(&console.Device)) - { - THROW_HR_IF(c_notSupported, serial->Port != 0); - THROW_HR_IF(E_INVALIDARG, serialConfigured); - ValidateConsolePath(serial->NamedPipe); - serialConfigured = true; - } - else + if (!std::holds_alternative(console.Device)) { const auto& virtio = std::get(console.Device); THROW_HR_IF(c_notSupported, virtio.Port != 0 || !virtio.GuestName.empty()); - THROW_HR_IF(E_INVALIDARG, virtioConfigured); - ValidateConsolePath(virtio.NamedPipe); - virtioConfigured = true; } description.Boot.Consoles.push_back(console); } - THROW_HR_IF(c_notSupported, Request.BootDisks.size() > c_maximumDisks); std::bitset allocated; for (const auto& disk : Request.BootDisks) { THROW_HR_IF(E_INVALIDARG, disk.Key.empty() || description.BootDisks.contains(disk.Key)); description.BootDisks.emplace(disk.Key, VmDiskAttachment{}); - ValidateDiskRequest(disk.Disk); + GetVirtualDiskSource(disk.Disk); if (disk.Disk.Placement) { const auto& placement = *disk.Disk.Placement; - THROW_HR_IF(E_INVALIDARG, allocated.test(placement.Address.Lun)); - allocated.set(placement.Address.Lun); + if (placement.Address.Lun < c_maximumDisks) + { + allocated.set(placement.Address.Lun); + } } } @@ -239,37 +157,33 @@ void OpenVmmVirtualMachineBackend::DestroyVm(WslOpenVmmVm* Vm) noexcept WslOpenVmmDestroyVm(&Vm); } -OpenVmmVirtualMachineBackend::OpenVmmVirtualMachineBackend() : m_state(std::make_unique()) -{ -} +OpenVmmVirtualMachineBackend::OpenVmmVirtualMachineBackend() = default; OpenVmmVirtualMachineBackend::~OpenVmmVirtualMachineBackend() noexcept { - if (m_state->m_processWait) + if (m_processWait) { - SetThreadpoolWait(m_state->m_processWait.get(), nullptr, nullptr); - WaitForThreadpoolWaitCallbacks(m_state->m_processWait.get(), TRUE); - m_state->m_processWait.reset(); + m_processWait.reset(); } - if (m_state->m_vm && WaitForSingleObject(m_state->m_process.get(), 0) == WAIT_TIMEOUT) + if (m_vm && WaitForSingleObject(m_process.get(), 0) == WAIT_TIMEOUT) { - LOG_IF_FAILED(WslOpenVmmVmTeardown(m_state->m_vm.get())); - LOG_IF_FAILED(WslOpenVmmVmQuit(m_state->m_vm.get())); + LOG_IF_FAILED(WslOpenVmmVmTeardown(m_vm.get())); + LOG_IF_FAILED(WslOpenVmmVmQuit(m_vm.get())); } - m_state->m_vm.reset(); - m_state->m_job.reset(); - if (m_state->m_process) + m_vm.reset(); + m_job.reset(); + if (m_process) { // Confirm exit before releasing backing files or deleting socket paths. - LOG_LAST_ERROR_IF(WaitForSingleObject(m_state->m_process.get(), INFINITE) == WAIT_FAILED); + LOG_LAST_ERROR_IF(WaitForSingleObject(m_process.get(), INFINITE) == WAIT_FAILED); + } + if (m_processLogThread.joinable()) + { + m_processLogThread.join(); } - m_state->m_backingFiles.clear(); - if (m_state->m_directoryCreated) + if (m_directoryCreated) { - DeleteOwnedFile(m_state->m_rpcSocketPath); - DeleteOwnedFile(m_state->m_vsockPath); - DeleteOwnedFile(m_state->m_socketDirectory / L"openvmm.log"); - LOG_IF_WIN32_BOOL_FALSE(RemoveDirectoryW(m_state->m_socketDirectory.c_str())); + LOG_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(m_socketDirectory.c_str())); } } @@ -277,7 +191,7 @@ std::unique_ptr OpenVmmVirtualMachineBackend::Crea { auto description = wsl::windows::common::vm::openvmm::ValidateCreateRequest(Request); auto backend = std::unique_ptr{new OpenVmmVirtualMachineBackend{}}; - backend->m_state->m_description = std::move(description); + backend->m_description = std::move(description); backend->Initialize(Request); return backend; } @@ -286,27 +200,22 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) { using namespace wsl::windows::common; const auto executable = wslutil::GetBasePath() / L"openvmm.exe"; - THROW_HR_IF_MSG( - HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), - !filesystem::FileExists(executable.c_str()), - "openvmm.exe not found at: %ls", - executable.c_str()); auto id = wsl::shared::string::GuidToString(Request.VmId, wsl::shared::string::GuidToStringFlags::None); std::erase(id, L'-'); // An exclusive directory creation prevents shortened path IDs from aliasing another VM. - m_state->m_socketDirectory = filesystem::GetTempFolderPath(GetCurrentProcessToken()) / (L"ov-" + id.substr(0, 16)); - m_state->m_rpcSocketPath = m_state->m_socketDirectory / L"r"; - m_state->m_vsockPath = m_state->m_socketDirectory / L"v"; - const auto longestPath = - wsl::shared::string::WideToMultiByte(m_state->m_vsockPath.native() + L"_ffffffff-facb-11e6-bd58-64006a7986d3"); + m_socketDirectory = filesystem::GetTempFolderPath(GetCurrentProcessToken()) / (L"ov-" + id.substr(0, 16)); + m_rpcSocketPath = m_socketDirectory / L"r"; + m_vsockPath = m_socketDirectory / L"v"; + constexpr size_t c_guidStringLength = 38; + constexpr size_t c_guestSocketSuffixLength = 1 + c_guidStringLength; + const auto vsockPath = wsl::shared::string::WideToMultiByte(m_vsockPath.native()); SOCKADDR_UN address{}; THROW_HR_IF_MSG( E_INVALIDARG, - longestPath.size() >= sizeof(address.sun_path), + vsockPath.size() + c_guestSocketSuffixLength >= sizeof(address.sun_path), "OpenVMM guest socket path exceeds the AF_UNIX limit: %hs", - longestPath.c_str()); - THROW_HR_IF(E_INVALIDARG, m_state->m_rpcSocketPath.native().find_first_of(L",\"\r\n") != std::wstring::npos); + vsockPath.c_str()); const auto tokenUser = wil::get_token_information(GetCurrentProcessToken()); const auto sid = wslutil::SidToString(tokenUser->User.Sid); @@ -314,33 +223,26 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) wil::unique_hlocal_security_descriptor security; THROW_IF_WIN32_BOOL_FALSE(ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), SDDL_REVISION_1, &security, nullptr)); SECURITY_ATTRIBUTES attributes{sizeof(attributes), security.get(), FALSE}; + for (const auto& disk : Request.BootDisks) { - m_state->m_backingFiles.push_back(OpenBackingFile(Request.Boot.KernelPath, true)); - m_state->m_backingFiles.push_back(OpenBackingFile(Request.Boot.InitrdPath, true)); - auto lock = m_state->m_lock.lock_exclusive(); - for (const auto& disk : Request.BootDisks) - { - const auto& attachment = m_state->m_description.BootDisks.at(disk.Key); - auto backingFile = OpenBackingFile(std::get(disk.Disk.Source).Path, disk.Disk.ReadOnly); - m_state->m_attachedDisks.emplace( - attachment.Id.Value, State::AttachedDisk{attachment, std::move(backingFile)}); - } - m_state->m_nextDiskId = Request.BootDisks.size() + 1; - THROW_IF_WIN32_BOOL_FALSE(CreateDirectoryW(m_state->m_socketDirectory.c_str(), &attributes)); - m_state->m_directoryCreated = true; + const auto& attachment = m_description.BootDisks.at(disk.Key); + m_attachedDisks.emplace(attachment.Id.Value, attachment); } + m_nextDiskId = Request.BootDisks.size() + 1; + THROW_IF_WIN32_BOOL_FALSE(CreateDirectoryW(m_socketDirectory.c_str(), &attributes)); + m_directoryCreated = true; UniqueConfig config; THROW_IF_FAILED(WslOpenVmmCreateConfig(config.put())); THROW_IF_FAILED(WslOpenVmmConfigSetKernelPath(config.get(), Request.Boot.KernelPath.c_str())); THROW_IF_FAILED(WslOpenVmmConfigSetInitrdPath(config.get(), Request.Boot.InitrdPath.c_str())); - THROW_IF_FAILED(WslOpenVmmConfigSetKernelCmdLine(config.get(), m_state->m_description.Boot.KernelCommandLine.c_str())); - THROW_IF_FAILED(WslOpenVmmConfigSetMemoryMb(config.get(), m_state->m_description.Memory.SizeBytes / (1024 * 1024))); + THROW_IF_FAILED(WslOpenVmmConfigSetKernelCmdLine(config.get(), m_description.Boot.KernelCommandLine.c_str())); + THROW_IF_FAILED(WslOpenVmmConfigSetMemoryMb(config.get(), m_description.Memory.SizeBytes / (1024 * 1024))); THROW_IF_FAILED(WslOpenVmmConfigSetProcessorCount(config.get(), Request.Processor.Count)); - THROW_IF_FAILED(WslOpenVmmConfigSetHvSocketPath(config.get(), m_state->m_vsockPath.c_str())); + THROW_IF_FAILED(WslOpenVmmConfigSetHvSocketPath(config.get(), m_vsockPath.c_str())); for (const auto& disk : Request.BootDisks) { - const auto& attachment = m_state->m_description.BootDisks.at(disk.Key); + const auto& attachment = m_description.BootDisks.at(disk.Key); THROW_IF_FAILED(WslOpenVmmConfigAddBootDisk( config.get(), attachment.GuestAddress.Controller, @@ -360,44 +262,62 @@ void OpenVmmVirtualMachineBackend::Initialize(const VmCreateRequest& Request) } } - m_state->m_job = helpers::CreateKillOnCloseJob(); + m_job = helpers::CreateKillOnCloseJob(); const auto commandLine = - std::format(L"\"{}\" --rpc \"path={},transport=grpc\"", executable.native(), m_state->m_rpcSocketPath.native()); + std::format(L"\"{}\" --rpc \"path={},transport=grpc\"", executable.native(), m_rpcSocketPath.native()); SubProcess process{executable.c_str(), commandLine.c_str()}; process.SetFlags(CREATE_NO_WINDOW); - process.SetJobObject(m_state->m_job.get()); + process.SetJobObject(m_job.get()); SECURITY_ATTRIBUTES inheritable{sizeof(inheritable), nullptr, TRUE}; - wil::unique_hfile logFile; wil::unique_hfile input; - { - logFile.reset(CreateFileW( - (m_state->m_socketDirectory / L"openvmm.log").c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); - THROW_LAST_ERROR_IF(!logFile); - input.reset(CreateFileW( - L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); - THROW_LAST_ERROR_IF(!input); - } - // OpenVMM closes stdout at startup; stderr must have a distinct handle value. - wil::unique_hfile errorLogFile; - THROW_IF_WIN32_BOOL_FALSE( - DuplicateHandle(GetCurrentProcess(), logFile.get(), GetCurrentProcess(), errorLogFile.put(), 0, TRUE, DUPLICATE_SAME_ACCESS)); - process.SetStdHandles(input.get(), logFile.get(), errorLogFile.get()); - m_state->m_process = process.Start(); - m_state->m_processWait.reset(CreateThreadpoolWait(OnProcessExit, this, nullptr)); - THROW_LAST_ERROR_IF(!m_state->m_processWait); - SetThreadpoolWait(m_state->m_processWait.get(), m_state->m_process.get(), nullptr); + input.reset(CreateFileW( + L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); + THROW_LAST_ERROR_IF(!input); + wil::unique_hfile output{CreateFileW( + L"NUL", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)}; + THROW_LAST_ERROR_IF(!output); + auto [logPipeRead, logPipeWrite] = wslutil::OpenAnonymousPipe(0, true, false); + THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(logPipeWrite.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)); + process.SetStdHandles(input.get(), output.get(), logPipeWrite.get()); + m_process = process.Start(); + logPipeWrite.reset(); + m_processLogThread = std::thread(&OpenVmmVirtualMachineBackend::ReadProcessLog, this, std::move(logPipeRead)); + m_processWait.reset(CreateThreadpoolWait(OnProcessExit, this, nullptr)); + THROW_LAST_ERROR_IF(!m_processWait); + SetThreadpoolWait(m_processWait.get(), m_process.get(), nullptr); THROW_IF_FAILED_MSG( - WslOpenVmmCreateVm(config.addressof(), m_state->m_rpcSocketPath.c_str(), c_rpcTimeoutMs, m_state->m_vm.put()), + WslOpenVmmCreateVm(config.addressof(), m_rpcSocketPath.c_str(), c_rpcTimeoutMs, m_vm.put()), "Failed to create OpenVMM VM"); - const auto result = WaitForSingleObject(m_state->m_process.get(), 0); + const auto result = WaitForSingleObject(m_process.get(), 0); THROW_LAST_ERROR_IF(result == WAIT_FAILED); THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED), result == WAIT_OBJECT_0); } +void OpenVmmVirtualMachineBackend::ReadProcessLog(wil::unique_hfile Pipe) noexcept +try +{ + wsl::windows::common::io::MultiHandleWait io; + io.AddHandle(std::make_unique( + std::move(Pipe), + [this](const gsl::span& Buffer) { + if (!Buffer.empty()) + { + const std::string entry{Buffer.begin(), Buffer.end()}; + WSL_LOG( + "OpenVmmLog", + TraceLoggingGuid(m_description.Identity.VmId, "VmId"), + TraceLoggingValue(entry.c_str(), "Content")); + } + })); + io.AddHandle(std::make_unique(m_process.get())); + io.Run(std::nullopt); +} +CATCH_LOG() + void CALLBACK OpenVmmVirtualMachineBackend::OnProcessExit(PTP_CALLBACK_INSTANCE, void* Context, PTP_WAIT, TP_WAIT_RESULT) noexcept { auto& backend = *static_cast(Context); - LOG_IF_WIN32_BOOL_FALSE(SetEvent(backend.m_state->m_exitEvent.get())); + LOG_IF_WIN32_BOOL_FALSE(SetEvent(backend.m_exitEvent.get())); } VmPlatformCapabilities OpenVmmVirtualMachineBackend::QueryCapabilities() @@ -456,24 +376,24 @@ wil::unique_handle OpenVmmVirtualMachineBackend::GetTerminationEvent() const { wil::unique_handle event; THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle( - GetCurrentProcess(), m_state->m_exitEvent.get(), GetCurrentProcess(), event.put(), 0, FALSE, DUPLICATE_SAME_ACCESS)); + GetCurrentProcess(), m_exitEvent.get(), GetCurrentProcess(), event.put(), 0, FALSE, DUPLICATE_SAME_ACCESS)); return event; } void OpenVmmVirtualMachineBackend::Start() { - auto lock = m_state->m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); - THROW_IF_FAILED(WslOpenVmmVmResume(m_state->m_vm.get())); + auto lock = m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); + THROW_IF_FAILED(WslOpenVmmVmResume(m_vm.get())); } void OpenVmmVirtualMachineBackend::Terminate() { - auto lock = m_state->m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); - THROW_IF_FAILED(WslOpenVmmVmTeardown(m_state->m_vm.get())); - const auto quitResult = WslOpenVmmVmQuit(m_state->m_vm.get()); - const auto waitResult = WaitForSingleObject(m_state->m_process.get(), c_rpcTimeoutMs); + auto lock = m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); + THROW_IF_FAILED(WslOpenVmmVmTeardown(m_vm.get())); + const auto quitResult = WslOpenVmmVmQuit(m_vm.get()); + const auto waitResult = WaitForSingleObject(m_process.get(), c_rpcTimeoutMs); THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); if (waitResult != WAIT_OBJECT_0) { @@ -481,8 +401,8 @@ void OpenVmmVirtualMachineBackend::Terminate() THROW_HR(HRESULT_FROM_WIN32(WAIT_TIMEOUT)); } - m_state->m_vm.reset(); - m_state->m_attachedDisks.clear(); + m_vm.reset(); + m_attachedDisks.clear(); } void OpenVmmVirtualMachineBackend::CancelPendingOperations() noexcept @@ -512,15 +432,14 @@ void OpenVmmVirtualMachineBackend::CloseGuestListener(VmListenerId) VmDiskAttachment OpenVmmVirtualMachineBackend::AttachDisk(const VmDiskRequest& Request) { - const auto& source = ValidateDiskRequest(Request); - auto backingFile = OpenBackingFile(source.Path, Request.ReadOnly); - auto lock = m_state->m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); + const auto& source = GetVirtualDiskSource(Request); + auto lock = m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); const auto lunInUse = [&](std::uint32_t Lun) { - for (const auto& entry : m_state->m_attachedDisks) + for (const auto& entry : m_attachedDisks) { - if (entry.second.Attachment.GuestAddress.Lun == Lun) + if (entry.second.GuestAddress.Lun == Lun) { return true; } @@ -543,30 +462,29 @@ VmDiskAttachment OpenVmmVirtualMachineBackend::AttachDisk(const VmDiskRequest& R THROW_HR_IF(WSL_E_TOO_MANY_DISKS_ATTACHED, lun == c_maximumDisks); } - THROW_HR_IF(E_BOUNDS, m_state->m_nextDiskId == UINT64_MAX); + THROW_HR_IF(E_BOUNDS, m_nextDiskId == UINT64_MAX); const VmDiskAttachment attachment{ - {m_state->m_description.Identity, m_state->m_nextDiskId}, {0, lun}, Request.ReadOnly}; - const auto [disk, inserted] = m_state->m_attachedDisks.emplace( - attachment.Id.Value, State::AttachedDisk{attachment, std::move(backingFile)}); + {m_description.Identity, m_nextDiskId}, {0, lun}, Request.ReadOnly}; + const auto [disk, inserted] = m_attachedDisks.emplace(attachment.Id.Value, attachment); WI_ASSERT(inserted); - auto rollback = wil::scope_exit([&] { m_state->m_attachedDisks.erase(disk); }); + auto rollback = wil::scope_exit([&] { m_attachedDisks.erase(disk); }); THROW_IF_FAILED(WslOpenVmmVmAttachScsiDisk( - m_state->m_vm.get(), attachment.GuestAddress.Controller, attachment.GuestAddress.Lun, source.Path.c_str(), Request.ReadOnly)); - ++m_state->m_nextDiskId; + m_vm.get(), attachment.GuestAddress.Controller, attachment.GuestAddress.Lun, source.Path.c_str(), Request.ReadOnly)); + ++m_nextDiskId; rollback.release(); return attachment; } void OpenVmmVirtualMachineBackend::DetachDisk(VmDiskId Disk) { - THROW_HR_IF(E_INVALIDARG, Disk.Value == 0 || !IsEqualGUID(Disk.Owner.VmId, m_state->m_description.Identity.VmId)); - auto lock = m_state->m_lock.lock_exclusive(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_state->m_vm); - const auto disk = m_state->m_attachedDisks.find(Disk.Value); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), disk == m_state->m_attachedDisks.end()); + THROW_HR_IF(E_INVALIDARG, Disk.Value == 0 || !IsEqualGUID(Disk.Owner.VmId, m_description.Identity.VmId)); + auto lock = m_lock.lock_exclusive(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vm); + const auto disk = m_attachedDisks.find(Disk.Value); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), disk == m_attachedDisks.end()); THROW_IF_FAILED(WslOpenVmmVmDetachScsiDisk( - m_state->m_vm.get(), disk->second.Attachment.GuestAddress.Controller, disk->second.Attachment.GuestAddress.Lun)); - m_state->m_attachedDisks.erase(disk); + m_vm.get(), disk->second.GuestAddress.Controller, disk->second.GuestAddress.Lun)); + m_attachedDisks.erase(disk); } VmFileSystemDevice OpenVmmVirtualMachineBackend::CreateFileSystemDevice(const VmFileSystemDeviceRequest&) diff --git a/src/windows/service/exe/OpenVmmVirtualMachineBackend.h b/src/windows/common/OpenVmmVirtualMachineBackend.h similarity index 72% rename from src/windows/service/exe/OpenVmmVirtualMachineBackend.h rename to src/windows/common/OpenVmmVirtualMachineBackend.h index 4ad86152ee..7d81399d10 100644 --- a/src/windows/service/exe/OpenVmmVirtualMachineBackend.h +++ b/src/windows/common/OpenVmmVirtualMachineBackend.h @@ -14,6 +14,7 @@ Module Name: #pragma once +#include #include "IVirtualMachineBackend.h" struct WslOpenVmmVm; @@ -61,34 +62,24 @@ class OpenVmmVirtualMachineBackend : public IVirtualMachineBackend NON_MOVABLE(OpenVmmVirtualMachineBackend); static void CALLBACK OnProcessExit(PTP_CALLBACK_INSTANCE, void* Context, PTP_WAIT, TP_WAIT_RESULT) noexcept; + void ReadProcessLog(wil::unique_hfile Pipe) noexcept; void Initialize(const VmCreateRequest& Request); static void DestroyVm(WslOpenVmmVm* Vm) noexcept; using UniqueVm = wil::unique_any; - struct State - { - struct AttachedDisk - { - VmDiskAttachment Attachment; - wil::unique_hfile BackingFile; - }; - - wil::srwlock m_lock; - VmDescription m_description; - _Guarded_by_(m_lock) std::map m_attachedDisks; - _Guarded_by_(m_lock) std::uint64_t m_nextDiskId = 1; - UniqueVm m_vm; - wil::unique_handle m_process; - wil::unique_handle m_job; - std::vector m_backingFiles; - std::filesystem::path m_socketDirectory; - std::filesystem::path m_rpcSocketPath; - std::filesystem::path m_vsockPath; - bool m_directoryCreated = false; - wil::unique_event m_exitEvent{wil::EventOptions::ManualReset}; - wil::unique_threadpool_wait m_processWait; - }; - - std::unique_ptr m_state; + wil::srwlock m_lock; + VmDescription m_description{}; + _Guarded_by_(m_lock) std::map m_attachedDisks; + _Guarded_by_(m_lock) std::uint64_t m_nextDiskId = 1; + UniqueVm m_vm; + wil::unique_handle m_process; + wil::unique_handle m_job; + std::thread m_processLogThread; + std::filesystem::path m_socketDirectory; + std::filesystem::path m_rpcSocketPath; + std::filesystem::path m_vsockPath; + bool m_directoryCreated = false; + wil::unique_event m_exitEvent{wil::EventOptions::ManualReset}; + wil::unique_threadpool_wait m_processWait; }; \ No newline at end of file diff --git a/src/windows/service/exe/VirtualMachineBackend.cpp b/src/windows/common/VirtualMachineBackend.cpp similarity index 100% rename from src/windows/service/exe/VirtualMachineBackend.cpp rename to src/windows/common/VirtualMachineBackend.cpp diff --git a/src/windows/service/exe/CMakeLists.txt b/src/windows/service/exe/CMakeLists.txt index 91710be030..c7b4ebb0dd 100644 --- a/src/windows/service/exe/CMakeLists.txt +++ b/src/windows/service/exe/CMakeLists.txt @@ -57,16 +57,6 @@ set(HEADERS WSLCSessionManagerFactory.h WSLCPluginNotifier.h) -add_library(virtualmachinebackend STATIC - VirtualMachineBackend.cpp - IVirtualMachineBackend.h - OpenVmmVirtualMachineBackend.cpp - OpenVmmVirtualMachineBackend.h) -target_include_directories(virtualmachinebackend PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_precompile_headers(virtualmachinebackend REUSE_FROM common) -target_link_libraries(virtualmachinebackend PUBLIC common ${COMMON_LINK_LIBRARIES} wslopenvmm_client) -set_target_properties(virtualmachinebackend PROPERTIES FOLDER windows) - add_executable(wslservice ${SOURCES} ${HEADERS}) add_dependencies(wslservice wslserviceidl wslservicemc) add_compile_definitions(__WRL_CLASSIC_COM__) @@ -82,7 +72,6 @@ target_link_libraries(wslservice configfile legacy_stdio_definitions VirtDisk.lib - virtualmachinebackend Winhttp.lib Synchronization.lib yaml-cpp) diff --git a/test/windows/CMakeLists.txt b/test/windows/CMakeLists.txt index 9e3a0c2161..f692570e7c 100644 --- a/test/windows/CMakeLists.txt +++ b/test/windows/CMakeLists.txt @@ -26,7 +26,6 @@ add_compile_definitions(INLINE_TEST_METHOD_MARKUP) add_library(wsltests SHARED ${SOURCES} ${HEADERS}) target_sources(wsltests PRIVATE OpenVmmVirtualMachineBackendTests.cpp) -target_link_libraries(wsltests virtualmachinebackend) add_dependencies(wsltests initramfs) add_custom_command( diff --git a/test/windows/OpenVmmVirtualMachineBackendTests.cpp b/test/windows/OpenVmmVirtualMachineBackendTests.cpp index 52b0705493..a92e57fee5 100644 --- a/test/windows/OpenVmmVirtualMachineBackendTests.cpp +++ b/test/windows/OpenVmmVirtualMachineBackendTests.cpp @@ -74,39 +74,6 @@ class OpenVmmVirtualMachineBackendTests VERIFY_ARE_EQUAL(UINT32{1}, description.BootDisks.at(L"automatic-1").GuestAddress.Lun); VERIFY_ARE_EQUAL(UINT32{2}, description.BootDisks.at(L"automatic-2").GuestAddress.Lun); VERIFY_ARE_EQUAL(UINT32{0}, description.BootDisks.at(L"exact").GuestAddress.Lun); - - request.BootDisks[0].Disk.Placement = VmScsiPlacement{{0, 0}}; - VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); - request.BootDisks[0].Disk.Placement.reset(); - request.BootDisks[1].Key = request.BootDisks[0].Key; - VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); - } - - TEST_METHOD(DoesNotCapMemoryAndRequiresGranularSizing) - { - SKIP_TEST_ARM64(); - auto request = CreateRequest(); - request.Memory.SizeBytes = 4096 * c_mib; - VERIFY_ARE_EQUAL(request.Memory.SizeBytes, ValidateCreateRequest(request).Memory.SizeBytes); - request.Memory.SizeBytes += 2 * c_mib; - VERIFY_ARE_EQUAL(c_notSupported, DescribeResult(request)); - request.Memory.SizeBytes = 33 * c_mib; - VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); - request.Memory.SizeBytes = c_mib; - VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); - request.Memory.SizeBytes = 0; - VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); - request.Memory.SizeBytes = 2 * c_mib; - VERIFY_ARE_EQUAL(request.Memory.SizeBytes, ValidateCreateRequest(request).Memory.SizeBytes); - } - - TEST_METHOD(RejectsInvalidDiskFormats) - { - SKIP_TEST_ARM64(); - auto request = CreateRequest(); - request.BootDisks.push_back(CreateDisk(L"disk")); - request.BootDisks[0].Disk.Source = VmVirtualDiskSource{L"C:\\images\\disk.vhd", VmDiskFormat::Vhdx}; - VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); } TEST_METHOD(EnforcesDiskLimitsAndKeepsIdsVmScoped) @@ -123,8 +90,6 @@ class OpenVmmVirtualMachineBackendTests const auto second = ValidateCreateRequest(request); VERIFY_ARE_EQUAL(first.BootDisks.at(L"0").Id.Value, second.BootDisks.at(L"0").Id.Value); VERIFY_IS_FALSE(IsEqualGUID(first.BootDisks.at(L"0").Id.Owner.VmId, second.BootDisks.at(L"0").Id.Owner.VmId)); - request.BootDisks.push_back(CreateDisk(L"overflow")); - VERIFY_ARE_EQUAL(c_notSupported, DescribeResult(request)); } TEST_METHOD(ValidatesConsoleFamiliesIndependently) @@ -135,23 +100,14 @@ class OpenVmmVirtualMachineBackendTests {VmConsoleRole::EarlyBoot, VmSerialConsole{0, L"\\\\.\\pipe\\early"}}, {VmConsoleRole::KernelConsole, VmVirtioConsole{0, L"", L"\\\\.\\pipe\\console"}}}; VERIFY_ARE_EQUAL(size_t{2}, ValidateCreateRequest(request).Boot.Consoles.size()); - request.Consoles.push_back(request.Consoles[0]); - VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); - request.Consoles.pop_back(); std::get(request.Consoles[1].Device).GuestName = L"unsupported-name"; VERIFY_ARE_EQUAL(c_notSupported, DescribeResult(request)); } - TEST_METHOD(RejectsInvalidIdentityAndBootPaths) + TEST_METHOD(RejectsUnsupportedBootMethod) { SKIP_TEST_ARM64(); auto request = CreateRequest(); - request.VmId = GUID_NULL; - VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); - THROW_IF_FAILED(CoCreateGuid(&request.VmId)); - request.Boot.KernelPath = L"relative-kernel"; - VERIFY_ARE_EQUAL(E_INVALIDARG, DescribeResult(request)); - request.Boot.KernelPath = L"C:\\images\\kernel"; request.Boot.Method = VmBootMethod::Uefi; VERIFY_ARE_EQUAL(c_notSupported, DescribeResult(request)); } From ee086a085dde78696619ad87a805bbb9207b290a Mon Sep 17 00:00:00 2001 From: Daman Mulye Date: Tue, 22 Sep 2026 16:44:10 -0700 Subject: [PATCH 09/10] Remove accidentally committed file --- WSL-openvmm.md | 281 ------------------------------------------------- 1 file changed, 281 deletions(-) delete mode 100644 WSL-openvmm.md diff --git a/WSL-openvmm.md b/WSL-openvmm.md deleted file mode 100644 index 3b070d83ee..0000000000 --- a/WSL-openvmm.md +++ /dev/null @@ -1,281 +0,0 @@ -# OpenVMM WSL implementation tracker - -## Current-stack assessment (2026-09-14) - -This assessment compares each local branch with the branch below it, starting at `master` (`4bfbacae`). The original audit covered backend tip `13daf737`; the snapshot below includes the local rebase, committed PR 3 and PR 5 follow-ups, and uncommitted PR 6 RPC work. The PR numbers below are proposed work packages, not existing GitHub PR numbers. - -| Layer | Actual branch and tip | Work present | -|---|---|---| -| refactor | `user/damanmmulye/wsl-openvmm-refactor` at `b956db18` | `IWslCoreVm`, HCS implementation adaptation, session/interface plumbing, guest connection entry point, accepted ownership comments, and lifecycle regression. | -| rpc | `user/damanmulye/wsl-openvmm-rpc` at `dd6dc8ed` plus uncommitted C2 follow-up | Rust DLL/FFI, VM/resource RPCs, bounded AF_UNIX/gRPC calls, fail-closed recovery, cancellation, error categories, and transport regressions. | -| backend | `user/damanmmulye/wsl-openvmm-backend` at `b8b146cf` | WSL backend selection, process/VM lifecycle, guest transport wiring, VirtioFS, initial networking, console logging, private RPC socket and gated packaging; accepted selection/rollback policy and selection regressions. | - -**Legend:** `[x] Implemented` means the scoped WSL implementation is present in source, not that it has been built, run, merged, or approved for release. `[ ] Partial` means useful work exists but the bullet still has a gap or an unresolved design deviation. `[ ] TODO` means the requested outcome is not evidenced by this stack. `[ ] External` means completion must be established outside this WSL stack; it does not mean work in OpenVMM or offline design discussions has not happened. - -**Scope totals:** 14 implemented, 10 partial, 19 TODO, 7 external (50 unique bullets). Split validation bullets are counted once; G4 is partial overall because coverage is limited to selected mock/transport and early configuration-failure cases. The implemented bullets are **A3, B1, B2, B3, B4, B5, B6, B7, C1, C2, C5, C7, D1, and D4**. These totals include the accepted PR 3 and PR 5 decisions and the PR 6 RPC-layer closeout for C2/C7; backend follow-ups remain explicitly tracked below. - -The stack does not add WSLC backend call-site integration: the separate WSLC prototype from the earlier summary is not credited as completed work here. PR 3 adds a Windows lifecycle regression; PR 5 adds selection regressions and policy documentation; the uncommitted PR 6 follow-up expands the Rust transport coverage. End-to-end results, baselines, and rollout decisions cannot be inferred from a commit named "boot successful". - -**Important differences from the original plan:** - -- **Accepted PR 3/PR 5 design:** `IWslCoreVm` is the service-facing backend contract. `WslCoreVm` remains the HCS implementation; OpenVMM is a sibling implementation, not a backend underneath a shared `WslCoreVm` facade. This explicitly replaces the original lower-level extraction in A3/B1/B2, rather than claiming that extraction happened. The dedicated factory was removed by `2caf8dba`; `LxssUserSessionImpl::_CreateVm()` is accepted as B3's centralized creation/selection point. A2's full lifecycle contract remains separate follow-up work. -- **Accepted PR 5 policy:** HCS is the default; explicit OpenVMM opt-in fails rather than silently falling back when unavailable or when initialization fails. Rollback is manual: disable the setting and shut down WSL. A live VM retains its recorded backend until shutdown. -- **Accepted PR 6 contract:** Keep gRPC over AF_UNIX, not ttrpc. Replace transparent reconciliation with fail-closed recovery after uncertain mutations; teardown and fresh-process recreation are required. -- **C2/C7 closeout:** Close these bullets for the accepted RPC-layer scope: bounded, fail-closed RPC behavior and ordinary debugger-output tracing macros. Backend cancellation/lifetime integration, recreation-path coverage, and broader service/process diagnostics remain follow-ups, not claims of completed end-to-end integration. -- Mixed admin/non-admin access is **not implemented**: `InitializeDrvFs` and `AddVirtioFsShare` reject elevation different from the VM creator. Pass-through disks are also explicitly unsupported. -- Memory remains capped at 4 GiB. GUI/GPU, debug shell, and DNS tunneling are disabled in this backend configuration; pmem and virtio-rng configuration are not wired through the new RPC builder. -- Console/dmesg capture is implemented, but it is not kernel-panic extraction or a saved-state/crash-artifact collection pipeline. - -### Source evidence - -S1-S10 paths and line numbers refer to the original audited tips; S11-S13 identify committed follow-ups; S14 identifies uncommitted RPC diagnostics. Source IDs identify implementation evidence, not successful runtime results. - -| ID | Evidence | -|---|---| -| S1 | `src\windows\service\exe\IWslCoreVm.h:8-88`; `src\windows\service\exe\WslCoreVm.h:43` (`WslCoreVm : IWslCoreVm`). Refactor commit `2caf8dba` removes the dedicated factory. | -| S2 | `src\windows\service\exe\LxssUserSession.cpp:2999-3028` (inline selection and failure cleanup), `:2211-2239` (backend-specific force termination); `src\windows\common\WslCoreConfig.h:298,388` (opt-in key/default); `CMakeLists.txt:44` (build gate defaults off). | -| S3 | `src\shared\inc\SocketChannel.h:620-749` (AF_UNIX I/O); `src\windows\service\exe\LxssCreateProcess.h:54,76-111`; `src\windows\service\exe\WslCoreInstance.cpp:38-50,244-247,439-442,550-580`; `src\windows\service\exe\OpenVmmWslCoreVm.cpp:38-101` (guest bridge). | -| S4 | `src\windows\wslopenvmm\src\af_unix.rs:14-45` (connect retries/timeouts); `src\windows\wslopenvmm\src\client.rs:43-79,363-383` (Tonic client, deadlines, HRESULT mapping); `src\windows\service\exe\OpenVmmWslCoreVm.cpp:459-462` (`transport=grpc`). | -| S5 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:117-187,207-256,540-586,984-1026,1335-1392` (launch, cleanup, teardown/quit, process wait and callbacks). | -| S6 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:258-350,464-538` (configuration overrides, 4-GiB cap, boot/device setup); `src\windows\wslopenvmm\src\client.rs:81-172` (configuration builder). | -| S7 | `src\windows\service\exe\VirtioFsShareRequest.cpp:8-68`; `src\windows\service\exe\OpenVmmWslCoreVm.cpp:653-768,1247-1263,1393-1406` (share requests, worker, elevation restriction); `:1035-1039` (pass-through rejection); `src\windows\wslopenvmm\src\client.rs:223-275` (share RPCs). | -| S8 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:515-521,807-875` (consomme NIC, DHCP, IPv6 enabled, port tracker); `src\windows\wslopenvmm\src\client.rs:313-349` (IPv4/IPv6 localhost port requests). | -| S9 | `src\windows\service\exe\OpenVmmWslCoreVm.cpp:342,386-430,554-577,984-1012` (dump-count override, collector/debug console, stdout/stderr log, process-exit trace); `src\windows\common\Dmesg.cpp:45-78,156-211` (pipe access and raw guest-log capture, not panic parsing). | -| S10 | `src\windows\wslopenvmm\src\client\tests.rs:86-110,133-207`: TCP-loopback mock server; port protocol/address-family assertions and share add/remove failure/retry assertions. No real VM or AF_UNIX connection/recovery test. | -| S11 | PR 3 commit `b956db18` on the refactor branch: contract comments in `src\windows\service\exe\IWslCoreVm.h` and `WslCoreVm.h`; `SimpleTests::VmBackendShutdownAndReconnect` in `test\windows\SimpleTests.cpp`. Execution is delegated to CI, with results not yet recorded here. | -| S12 | PR 5 commit `b8b146cf` on the backend branch: selection matrix in `doc\docs\technical-documentation\wslservice.exe.md`; parser, backend identity, failure/retry, live-configuration shutdown, and rollback cases in `test\windows\VmBackendTests.cpp`; invalid-boolean warning assertion in `UnitTests.cpp`; source registration, availability definition, and `configfile` linkage in `test\windows\CMakeLists.txt`. | -| S13 | PR 6 RPC follow-up committed as `3e729f2d`: `src\windows\wslopenvmm\src\rpc.rs`, `af_unix.rs`, `client.rs`, `lib.rs`, and `client\tests.rs`; private exports in `wslopenvmm.h`; revised contract in the local `src\windows\wslopenvmm\README.md`. Detailed coverage and remaining backend integration are in the PR 6 page. | -| S14 | Uncommitted C7 RPC diagnostics: epci2-style once-initialized debugger subscriber and stack-backed writer in `src\windows\wslopenvmm\src\diagnostics.rs`; initialization and ordinary tracing macros in `client.rs` and `rpc.rs`. Messages omit payloads and server error text. The accepted approach uses debugger output, not an ETW provider, custom event schemas, or request correlation metadata. | - -## Original plan, annotated by scope bullet - -Organize this as small, dependency-ordered PRs—not one PR per deliverable. The main sequence should be contracts → HCS-preserving abstraction → OpenVMM boot → filesystem/networking compatibility → diagnostics → rollout gates. Start WHP memory work in parallel; it should block removing the memory cap, not the initial capped-memory backend. - -The order below maps all 50 description bullets into proposed PRs. Some validation bullets intentionally span an early foundation PR and a later completion PR. - -Account for work you already have - -These are foundations to consume, not features to implement again. Merged PRs do not, by themselves, establish completion of their broader deliverables. - -| Area | Existing work | Planning implication | -| --- | --- | --- | -| Backend prototype | WSL #40629, open; your current WSL branch also contains backend and private AF_UNIX RPC work | Extract cohesive changes into the backend PRs below rather than starting over. | -| VirtioFS | OpenVMM #3821, WSL #41129, WSL #41151, all merged | Focus on OpenVMM integration and identity/elevation gaps, not rebuilding aggregate shares. | -| Networking | OpenVMM #2398, IPv6, merged; #4378, control/data-path separation, open | Consume existing IPv6 support and identify the remaining integration gaps. Treat the networking refactor as a dependency only where needed. | -| RPC configuration | OpenVMM #4420, open | Land the required network/filesystem RPC capabilities before their WSL consumers. | -| Crash artifacts | OpenVMM #3882, triple-fault .vmrs, merged | Extend and integrate the existing mechanism; distinguish triple faults from kernel panics and host-process crashes. | - -At the initial ADO lookup, all seven deliverables said Proposed. That state is not a reliable measure of implementation progress; the checklist below records local-stack evidence separately. - -Proposed PR order - -References:  A1  means the first Scope bullet in deliverable A. - -| Key | Deliverable | -| --- | --- | -| A | 62679114 — Architecture and compatibility matrix | -| B | 63428739 — Pluggable VM backends | -| C | 63428740 — OpenVMM backend | -| D | 63428744 — Filesystem and networking | -| E | 63428746 — Memory elasticity and nested virtualization | -| F | 63428745 — Crash diagnostics | -| G | 63428747 — Compatibility and regression validation | - -### PR 1 — Architecture, compatibility, and ownership contract - -Design/documentation PR. First; approve the relevant decisions before implementing their consumers. - -- [ ] **TODO - A1:** Define the supported WSL and WSLC scenario-compatibility matrix. No matrix is added by this stack. -- [ ] **Partial - A2:** Define `IWslVmBackend` responsibilities and lifecycle contract. `IWslCoreVm` and lifecycle implementations exist (refactor/backend; S1, S5), but the approved contract must reflect the actual interface and ownership model. -- [x] **Implemented - A3:** Define the boundary between service orchestration and HCS-specific behavior. The sibling HCS/OpenVMM design is explicitly accepted and documented in PR 3 below and the interface/class comments (S1, S11). This supersedes the originally proposed split within `WslCoreVm`. -- [ ] **Partial - A4:** Define backend selection, feature control, rollback, and configuration behavior. Compile-time and `.wslconfig` gates and the accepted selection/failure/manual-rollback matrix are documented (backend; S2, S12). The broader unsupported-setting compatibility contract remains outstanding. -- [ ] **Partial - A5:** Define the guest communication abstraction for HvSocket and vsock. Code implements callbacks and the guest bridge (refactor/backend; S3); the reviewed transport/lifecycle contract is not evidenced. -- [ ] **Partial - A6:** Record the initial VirtioFS-only and consomme-only constraints. These are enforced by configuration overrides (backend; S6), but a reviewed compatibility/limitations document is still needed. -- [ ] **TODO - A7:** Identify repository, component, and DRI ownership for every gap. No ownership table is added. -- [ ] **TODO - A8:** Resolve ownership overlap between scenarios 62917985 and 61024686. No recorded resolution is evidenced by the branch changes. -- [ ] **TODO - D3:** Design elevation/broker behavior for pass-through disk file opens. The current backend rejects non-VHD disks and does not implement a pass-through broker (backend; S7). -- [ ] **TODO - D6:** Define the migration path to converged WSL networking. Forcing consomme in configuration is not a migration plan (S6). -- [ ] **TODO - F5:** Define artifact retention, size, privacy, and upload behavior. Socket/pipe ACLs and local logging exist, but no artifact policy is added (S9). - -Keep this focused on decisions, not implementations. In particular, define when fallback is allowed; do not leave “safe fallback” to become an arbitrary retry after a partially created VM. - -### PR 2 — Backend comparison harness and baseline measurements - -WSL test/infrastructure PR. Start after PR 1; develop alongside the implementation. - -- [ ] **TODO - G1:** Create the end-to-end matrix for WSL and WSLC on HCS and OpenVMM. Mock RPC tests are not a backend matrix (S10). -- [ ] **TODO - G5, foundation:** Establish measurement tooling and HCS startup, memory, CPU, I/O, networking, and reliability baselines; collect OpenVMM results once available. No benchmark harness or baseline results are added. -- [ ] **TODO - G7, definition:** Agree preview/GA pass rates and regression thresholds before deciding whether results are acceptable. No threshold definitions are added. - -This is infrastructure, not a reason to defer feature-specific tests until the end. - -### PR 3 — Isolate the HCS backend behind the accepted service contract - -WSL PR. Depends on PR 1. - -- [x] **Implemented - B1 (accepted revised scope):** Isolate HCS-specific VM operations from service callers behind `IWslCoreVm`, retaining HCS ownership in `WslCoreVm` (S1, S11). -- [x] **Implemented - B2 (accepted revised scope):** Put existing HCS behavior behind the service-facing backend contract; use sibling HCS/OpenVMM implementations rather than a shared facade (S1, S11). -- [x] **Implemented - B6:** Preserve HCS initialization, networking, VirtioFS, shutdown, and error telemetry, with regression coverage (S11). - -Keep OpenVMM implementation out of this PR. Its review question should be: does the abstraction preserve HCS behavior? - -[PR 3 details: ownership, test coverage, CI sign-off, and build evidence](WSL-openvmm/PR-3.md). - -### PR 4 — Make guest control channels transport-neutral - -WSL PR. Depends on the agreed transport contract and PR 3. - -- [x] **Implemented - B5:** Abstract guest control channels away from the HvSocket-specific implementation. `ConnectToGuest` and connector callbacks are wired through instance/process/session creation; `SocketChannel` handles AF_UNIX separately from existing Windows I/O (refactor/backend; S3). - -Introduce and exercise the abstraction with existing behavior first. Do not conflate the host-side gRPC socket with the guest-control transport; they are separate contracts. - -### PR 5 — Backend selection, fail-fast behavior, and manual rollback - -WSL PR. Depends on PRs 3–4. - -- [x] **Implemented - B3 (accepted revised scope):** Centralize WSL VM creation/selection in `_CreateVm()` with `IWslCoreVm` callers; no separate factory is required (S1, S2, S12). -- [x] **Implemented - B4 (accepted policy):** Keep HCS as default, OpenVMM explicitly opt-in, failures explicit, and rollback manual after shutdown (S2, S12). -- [x] **Implemented - B7:** Add configuration-parsing and integration coverage for backend selection, failure handling, shutdown, and rollback (S12). - -[PR 5 details: accepted policy, test cases, CI requirements, and build evidence](WSL-openvmm/PR-5.md). - -### PR 6 — OpenVMM process supervision and gRPC client - -WSL PR. Depends on the contracts and shared abstractions. - -- [x] **Implemented - C1:** Implement process launch, lifetime, and termination handling. User-token launch, kill-on-close job, process registry/wait, cleanup, timeout-based forced termination, and exit callbacks are present (backend; S5). -- [x] **Implemented - C2 (accepted RPC-layer scope):** Bounded gRPC/AF_UNIX calls, fail-closed status handling, cancellation, and HRESULT mapping are implemented with client-owned per-handle synchronization (rpc; S13). Closed for this scope; backend lifecycle integration remains a separate follow-up. -- [x] **Implemented - C7 (accepted logging scope):** Existing backend traces, distinct RPC error categories, and ordinary tracing macros through the once-initialized debugger subscriber are implemented (S3, S5, S7, S9, S13, S14). No ETW provider, structured event schema, or correlation metadata is required for closeout. -- [ ] **Partial - G4, transport portion:** AF_UNIX startup, deadlines, cancellation races, fail-closed status, and silent/wrong-protocol peers have regression coverage (rpc; S13). Remaining: actual process crashes, lost-response resource state, and service shutdown races. - -**Backend follow-ups retained outside the C2/C7 closeout:** - -- [ ] Wire cancellation into process exit/termination, coordinate RPC-handle lifetime, and cover teardown followed by fresh-process recovery through service call sites. -- [ ] Integrate service/process diagnostics and define the backend-wide failure taxonomy. - -Your current AF_UNIX work belongs here. Distinguish establishing/re-establishing a connection from replaying a VM-management operation whose outcome is unknown. - -[PR 6 details: revised RPC contract, regression coverage, and remaining backend integration](WSL-openvmm/PR-6.md). - -### PR 7 — Configure, boot, and manage an OpenVMM VM - -WSL PR. Depends on PRs 4–6 and the required upstream RPC/device capabilities. - -- [ ] **Partial - C3:** Translate WSL VM settings into OpenVMM configuration. Kernel/initrd/modules, command line, CPU, capped memory, disks and NIC are translated (rpc/backend; S6). Several settings are forcibly disabled/overridden; complete or explicitly approve the supported-setting matrix. -- [ ] **Partial - C4:** Configure boot, memory, processors, serial, vsock, disks, pmem, and virtio-rng. Boot/CPU/memory/serial/virtio-console/vsock/SCSI configuration exists (rpc/backend; S6); pmem and virtio-rng are not configured by the new builder. Sending boot entropy is not virtio-rng support. -- [x] **Implemented - C5:** Implement start, stop, shutdown, terminate, and unexpected-exit handling. Create/resume, channel shutdown, teardown/quit, timed force termination, process-exit signaling and session callback routing are wired (rpc/backend; S2, S5). Runtime reliability coverage is tracked separately in G2/G4. -- [ ] **TODO - G2, initial slice:** Automate boot, distro launch, basic disk, vsock, console, and shutdown scenarios. The stack contains implementation and mock RPC tests, not real-VM scenario automation (S10). -- [ ] **Partial - G4, configuration portion:** Add malformed-configuration negative tests. PR 5 adds invalid backend-boolean parsing/warnings and early unsupported-system-distro failure/retry coverage (S12). Broader malformed VM/RPC configuration and post-allocation failure cases remain outstanding (S6, S10). - -This is the first usable, gated backend milestone, initially retaining the memory cap. Consume already-implemented boot/RPC functionality rather than duplicating it. - -### PR 8 — VirtioFS integration and mixed-elevation access - -WSL integration PR. Depends on PR 7 and upstream filesystem capabilities. - -- [x] **Implemented - D1:** Implement VirtioFS-based cross-OS filesystem access. Share request/response handling, guest listener/worker, canonical host paths, read-only options, and VPCI share RPCs are connected (rpc/backend; S7). This is creator-elevation access; D2 remains separate. -- [ ] **TODO - D2:** Support admin and non-admin Windows file access from the same VM. `AddVirtioFsShare` rejects `Admin != m_creatorElevated`; `InitializeDrvFs` explicitly rejects switching elevation context after creation (backend; S7). The guard is a limitation, not mixed-elevation support. - -Apply the broker/identity decisions from PR 1. If additional OpenVMM or DeviceHost mechanisms are required, land those as separate prerequisite PRs; do not bundle cross-repository implementation into this WSL PR. - -### PR 9 — Consommé networking integration and compatibility - -WSL integration PR. Depends on PR 7 and the required OpenVMM networking/RPC changes. - -- [x] **Implemented - D4:** Integrate initial consomme networking. NIC configuration, mini_init networking/DHCP setup, port tracker and localhost bind/unbind RPCs are wired (rpc/backend; S8). -- [ ] **Partial - D5:** Consume the required consomme IPv6 changes. Guest configuration enables IPv6 and RPCs handle `AF_INET6`/`::1`, with mock field assertions (rpc/backend; S8, S10). Confirm the consumed OpenVMM version satisfies the upstream dependency and exercise real IPv6 behavior. -- [ ] **TODO - D7:** Validate DNS, localhost, VPN, proxy, firewall, IPv6, and multi-distro behavior. Configuration checks and mock port serialization are not networking compatibility results; no such matrix/automation is added. - -PRs 8 and 9 can proceed independently. Existing IPv6 support is a starting point—not proof that the entire WSL networking matrix passes. - -### PR 10 — Complete the OpenVMM crash-artifact producer - -OpenVMM PR. Can proceed in parallel once the artifact contract is agreed. - -- [ ] **External - F1:** Complete the mechanism to produce a VM saved-state or crash artifact. Earlier evidence identified merged OpenVMM #3882 for triple faults; this WSL stack neither implements nor proves the complete producer contract. Track upstream completion and consumption separately. -- [ ] **External - F2:** Add the compatible compression writer for the selected format, or update the consumer. Neither change is present in this WSL stack; verify the selected upstream format and remaining consumer work. - -Build on merged #3882. If the chosen solution instead changes the consumer, place F2 in PR 11, rather than implementing both approaches. - -The previously observed OpenVMM `Add crash dump path option` commit belongs to this diagnostics work, not the network/filesystem RPC story in #4420. It is outside the WSL stack assessed here. - -### PR 11 — WSL/WSLC diagnostic collection and debugger integration - -WSL PR. Depends on PR 7; artifact collection additionally depends on PR 10. - -- [ ] **TODO - C6:** Add kernel debugger support. Debug console/early-console output is wired, but OpenVMM kernel-debugger configuration is not (backend; S6, S9). Do not count a debug console as a debugger. -- [ ] **Partial - F3:** Integrate artifact collection into WSL and WSLC diagnostics. WSL reuses `DmesgCollector`, adds user-accessible console pipes, and writes OpenVMM stdout/stderr locally (backend; S9). Saved-state/crash-artifact collection and WSLC integration are not added. -- [ ] **TODO - F4:** Extract kernel-panic details from dmesg collector output. The reused collector buffers/emits raw guest log lines; the stack adds pipe access, not panic parsing or attribution (S9). -- [ ] **TODO - F6:** Update log-collection scripts for OpenVMM logs and traces. No `diagnostics` scripts change; creating a local `.log` file is only a prerequisite. -- [ ] **Partial - F7:** Add telemetry for dump success/failure, parsing, and backend crash buckets. Process-exit code/VM-ID traces and guest logs exist (backend; S9), but dump outcome, parsing and crash-bucket telemetry are not implemented. - -Bring this forward alongside filesystem/networking work: actionable diagnostics are useful before broad stress testing, not just before release. - -### PR 12 — WHP memory contract and accounting design - -Design/documentation PR. Start alongside PR 1, despite its position in this implementation sequence. - -- [ ] **External - E1:** Confirm WHP deferred-commit and sparse-allocation requirements with the WHP owner. Owner agreement is not evidenced by WSL branch changes; attach the decision separately. -- [ ] **External - E5:** Define host-commit versus guest-visible memory accounting and telemetry. No accounting contract or new memory telemetry is present; the 4-GiB clamp is not accounting (S6). - -Owner agreement is a prerequisite, not something a code PR alone can accomplish. Explicitly determine whether host/WHP changes are required; ballooning alone should not be assumed to solve upfront host commit. - -### PR 13 — Virtio-balloon support - -OpenVMM PR. Depends on PR 12. - -- [ ] **External - E2:** Implement the virtio-balloon support required by WSL and WSLC. No balloon configuration/control integration is added in this WSL stack (S6). Track separate OpenVMM implementation and its WSL/WSLC consumption. - -Keep this independently reviewable from cold-discard and nested virtualization. Any WSL policy/configuration wiring should be a separate consuming PR if it requires code changes there. - -### PR 14 — Cold-discard support and memory-elasticity integration - -OpenVMM PR, followed by a WSL integration PR where needed. Depends on PRs 12–13. - -- [ ] **External - E3:** Implement qemu-style cold-discard hints or the approved equivalent. No new host memory-discard integration is present. Existing guest reclaim settings and disk trim commands are not evidence of this host-memory feature. -- [ ] **TODO - E4:** Validate grow, shrink, reclaim, pressure, suspend, and multi-VM behavior. No elasticity scenario coverage/results are added; the memory cap remains (S6). - -Do not remove the WSL memory cap merely because the device exists. Removal should follow demonstrated host-commit and reclaim behavior plus the memory stress/performance coverage below. - -### PR 15 — Remaining nested-virtualization support - -OpenVMM/WHP-owned implementation PRs. Independent of balloon/discard unless a concrete shared dependency emerges. - -- [ ] **External - E6:** Complete the remaining nested-virtualization work. The new WSL RPC configuration does not wire a nested-virtualization setting (S6); track OpenVMM/WHP completion and explicit WSL consumption separately. - -Keep this a separate workstream. The deliverable groups nesting with memory, but its description does not establish that they must form one linear code stack. - -### PR 16 — Complete compatibility automation and cross-feature stress - -Test PRs in the repository owning each harness. Depends on the applicable feature PRs. - -- [ ] **TODO - G2, completion:** Complete automated disk, VirtioFS, vsock, console, networking, boot, launch, and shutdown coverage. Two mock RPC tests do not exercise these real-VM scenarios (S10). -- [ ] **TODO - G3:** Add multi-distro, repeated attach/detach, restart, update, and hot-add stress. Share retry assertions are not repeated real-device or multi-VM stress. -- [ ] **TODO - G4, completion:** Add host-resource-pressure tests and complete cross-feature failure coverage. Only the narrow mock-RPC portion in PR 6 is present (S10). -- [ ] **TODO - E7:** Add memory-elasticity and nested-workload stress/performance coverage. No such harness/results are added. - -The feature PRs should already carry their focused tests. This layer covers interactions, longer-running workloads, and the full matrix. - -### PR 17 — Enforce performance and rollout gates - -WSL validation/release-infrastructure PR. Depends on representative results from the preceding work. - -- [ ] **TODO - G5, completion:** Establish the comparable OpenVMM baselines across startup, memory, CPU, I/O, networking, and reliability. Slow-operation logging does not supply comparative baseline results. -- [ ] **TODO - G6:** Use WSLC startup-time P95 measure 63134665 as a rollout signal. No measure integration is added. -- [ ] **TODO - G7, enforcement:** Make the agreed preview/GA thresholds enforceable gates. Compile-time and config opt-in gates are not health/performance release gates. - -Do not invent numerical thresholds from the work-item text; it specifies that they must be defined, not what their values are. - -## Stack boundaries and dependencies outside your seven items - -Use a short WSL foundation stack for PRs 3–7, then separate filesystem, networking, diagnostics, and memory workstreams. OpenVMM prerequisite PRs belong in OpenVMM stacks; connect them to WSL consumers through explicit dependency links and consumed versions—not one cross-repository branch chain. - -Three sibling deliverables need to remain visible in the dependency map: - -| Dependency | Where it matters | -| --- | --- | -| 63428742 — Guest channels and virtio devices | PRs 4, 7, and 8 require the selected mini_init transport, independent control/diagnostic channels, and appropriate VirtioFS/device support. This is a real dependency omitted from the seven-item list. | -| 62679000 — Productization and release pipeline | Required to consume supported, versioned OpenVMM artifacts and ship the result; avoid making prototype completion synonymous with release readiness. | -| 63428748 — Preview rollout and GA readiness | Owns rollout execution. PRs 5 and 17 should provide selection controls and gates without duplicating its rollout ownership. | - -Also align PRs 6 and 11 with 63459355 — Diagnosability. GPU support is explicitly non-blocking in the parent scenario and should not hold up this core sequence. From 35a64acf20ccb4b90c137dca52d5da18975a9b89 Mon Sep 17 00:00:00 2001 From: Daman Mulye Date: Tue, 22 Sep 2026 17:06:18 -0700 Subject: [PATCH 10/10] . --- src/windows/common/OpenVmmVirtualMachineBackend.cpp | 3 ++- test/windows/CMakeLists.txt | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/windows/common/OpenVmmVirtualMachineBackend.cpp b/src/windows/common/OpenVmmVirtualMachineBackend.cpp index 9a00db2cf9..832e7fbdf3 100644 --- a/src/windows/common/OpenVmmVirtualMachineBackend.cpp +++ b/src/windows/common/OpenVmmVirtualMachineBackend.cpp @@ -22,6 +22,7 @@ Module Name: namespace { +constexpr UINT64 c_mib = 1024 * 1024; constexpr UINT32 c_maximumDisks = 254; constexpr UINT32 c_rpcTimeoutMs = 30000; constexpr HRESULT c_notSupported = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); @@ -79,7 +80,7 @@ VmDescription wsl::windows::common::vm::openvmm::ValidateCreateRequest(const VmC description.Identity.VmId = Request.VmId; description.Backend = BackendKind::OpenVmm; description.Processor.Count = Request.Processor.Count; - description.Memory.SizeBytes = Request.Memory.SizeBytes; + description.Memory.SizeBytes = (Request.Memory.SizeBytes / c_mib) * c_mib; ValidateFeature(Request.Processor.NestedVirtualization, L"nested virtualization"); ValidateFeature(Request.Processor.PerfmonPmu, L"PMU"); ValidateFeature(Request.Processor.PerfmonLbr, L"LBR"); diff --git a/test/windows/CMakeLists.txt b/test/windows/CMakeLists.txt index f692570e7c..b30447e4ae 100644 --- a/test/windows/CMakeLists.txt +++ b/test/windows/CMakeLists.txt @@ -33,6 +33,9 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KERNEL_SOURCE_DIR}/bin/${TARGET_PLATFORM}/kernel" "$/kernel" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${WSL_OPENVMM_SOURCE_DIR}/bin/${TARGET_PLATFORM}/openvmm.exe" + "$/openvmm.exe" VERBATIM) target_include_directories(wsltests PRIVATE