Add a new API to query the earliest supported client version#40067
Add a new API to query the earliest supported client version#40067OneBlue wants to merge 5 commits intofeature/wsl-for-appsfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new WSLC COM API intended to let clients query the minimum (earliest) supported client version, and adds SDK-side gating that fails with a WSLC-specific HRESULT when the SDK is older than what the runtime requires.
Changes:
- Adds
GetMinimumSupportedClientVersionto the session manager COM surface and implements it in the service. - Adds a new WSLC-specific HRESULT (
WSLC_E_SDK_UPDATED_NEEDED) and SDK logic to enforce a minimum supported client version. - Refactors
GetVersionimplementation inWSLCSessionManager(moves it out of the Impl forwarding path).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| src/windows/WslcSDK/wslcsdk.h | Adds WSLC HRESULT macro definitions for SDK consumers. |
| src/windows/WslcSDK/wslcsdk.cpp | Calls new COM method and throws a WSLC-specific error if SDK version is too old. |
| src/windows/service/inc/wslc.idl | Extends COM interface with a new method and adds an exported HRESULT constant. |
| src/windows/service/exe/WSLCSessionManager.h / .cpp | Implements the new minimum-supported-version query on the service side. |
| // WSLC specific error codes | ||
| #define WSLC_E_BASE (0x0600) | ||
| #define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */ | ||
| #define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */ | ||
| #define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */ | ||
| #define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */ | ||
| #define WSLC_E_SDK_UPDATED_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */ |
There was a problem hiding this comment.
WSLC error codes are now defined in this public header, but internal SDK code also includes the MIDL-generated wslc.h (via WslcsdkPrivate.h), which already defines these macros via cpp_quote. This will cause macro redefinition warnings (often /WX) when building the SDK. Consider wrapping these #defines with #ifndef guards or moving the shared HRESULT definitions to a single header included by both.
| // WSLC specific error codes | |
| #define WSLC_E_BASE (0x0600) | |
| #define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */ | |
| #define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */ | |
| #define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */ | |
| #define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */ | |
| #define WSLC_E_SDK_UPDATED_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */ | |
| // WSLC specific error codes | |
| #ifndef WSLC_E_BASE | |
| #define WSLC_E_BASE (0x0600) | |
| #endif | |
| #ifndef WSLC_E_IMAGE_NOT_FOUND | |
| #define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */ | |
| #endif | |
| #ifndef WSLC_E_CONTAINER_PREFIX_AMBIGUOUS | |
| #define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */ | |
| #endif | |
| #ifndef WSLC_E_CONTAINER_NOT_FOUND | |
| #define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */ | |
| #endif | |
| #ifndef WSLC_E_VOLUME_NOT_FOUND | |
| #define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */ | |
| #endif | |
| #ifndef WSLC_E_SDK_UPDATED_NEEDED | |
| #define WSLC_E_SDK_UPDATED_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */ | |
| #endif |
| #define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */ | ||
| #define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */ | ||
| #define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */ | ||
| #define WSLC_E_SDK_UPDATED_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */ |
There was a problem hiding this comment.
The new HRESULT name WSLC_E_SDK_UPDATED_NEEDED appears to have a typo/awkward tense ("UPDATED" vs "UPDATE"). Since this constant is part of the public API surface (IDL + SDK header), it’s best to correct the name now (e.g., WSLC_E_SDK_UPDATE_NEEDED/WSLC_E_SDK_UPDATE_REQUIRED) before it becomes a compatibility burden.
| #define WSLC_E_SDK_UPDATED_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */ | |
| #define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */ |
| decltype(wsl::shared::PackageVersion) requiredClientVersion{ | ||
| minimumClientVersion.Major, minimumClientVersion.Minor, minimumClientVersion.Revision}; | ||
|
|
||
| THROW_HR_IF_MSG( |
There was a problem hiding this comment.
This throw would go into CreateSessionManager and instead we would return the HR so that CanRun succeeds with a missing component rather than failing. Really with this API I think the SDK stuff would be refactored in several ways.
|
I spoke with @OneBlue, I think we should consider COM interface versioning a bit more. The problem with this solution is it required a checked-in version, and versions are controlled by git tags, so there is a chicken and egg problem. |
His original idea for a No matter how we do this, we would only want to break older SDK usage if it were a security issue. We can do that anyway, just less gracefully, without anything new (ex. making all ISession1 functions fail; must use ISession2 instead). The question becomes if it is worth building up the pre-check if it is extremely rare to happen and then no clients actually bother. A very specific HRESULT and documentation is probably good enough without the extra work from everyone. |
|
Hey @OneBlue 👋 — Following up on this draft PR. CI is green, but there are 3 unresolved review threads:
Is this still being actively developed? Let us know if the design direction has been settled. |
Did a bit of research on this, I think we should do both COM interface versioning and still have a method to allow clients to check for compatibility. COM versioning will allow us to add more methods in new interfaces without breaking ABI, and if one day we decide to stop supporting old client, we'll have an "escape" to do that without causing the clients to fail with confusing errors. Now that we have the 2.8.0 tag in the feature branch, the chicken-egg issue is also gone, we can just default to 2.8.0 as the lowest version. How does that sound @benhillis ? |
|
I would still propose that we swap who is responsible for determining version support from the current PR. We still have a similar "predict the release version number" problem if we return the minimum supported version (when it is the running version). If the runtime API is Regardless of the API shape, any developer caught by surprise at the release of a new WSL version that makes their SDK version unsupported will need to make and deliver a new build. I'm sure that we will have documented this and it will be discoverable quickly. |
…oneblue/client-version-check
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Comments suppressed due to low confidence (1)
src/windows/WslcSDK/wslcsdk.cpp:1576
- This treats any non-success (including WSLC_E_SDK_UPDATE_NEEDED) as requiring the Windows Update flow. If the SDK is too old for the installed runtime, running the installer path won’t fix the issue and may prompt for elevation unnecessarily. Consider special-casing WSLC_E_SDK_UPDATE_NEEDED to return that error immediately instead of invoking Windows Update.
if (!SUCCEEDED(runtimeResult))
{
std::function<void(uint32_t)> callback;
if (progressCallback)
{
| return result; | ||
| } | ||
|
|
||
| THROW_HR_IF(runtimeResult, runtimeResult != REGDB_E_CLASSNOTREG && runtimeResult != WSLC_E_SDK_UPDATE_NEEDED); |
There was a problem hiding this comment.
WslcInstallWithDependencies allows WSLC_E_SDK_UPDATE_NEEDED to proceed into the installation path. That error means the SDK is too old for the installed runtime, so attempting to install/repair WSL via Windows Update won’t resolve it and may trigger unnecessary elevation prompts. Consider returning WSLC_E_SDK_UPDATE_NEEDED early instead.
| THROW_HR_IF(runtimeResult, runtimeResult != REGDB_E_CLASSNOTREG && runtimeResult != WSLC_E_SDK_UPDATE_NEEDED); | |
| THROW_HR_IF(runtimeResult, runtimeResult != REGDB_E_CLASSNOTREG && runtimeResult != WSLC_E_SDK_UPDATE_NEEDED); | |
| RETURN_HR_IF(WSLC_E_SDK_UPDATE_NEEDED, runtimeResult == WSLC_E_SDK_UPDATE_NEEDED); |
| HRESULT WSLCSessionManager::GetVersion(_Out_ WSLCVersion* Version) | ||
| { | ||
| return CallImpl(&WSLCSessionManagerImpl::GetVersion, Version); | ||
| Version->Major = WSL_PACKAGE_VERSION_MAJOR; | ||
| Version->Minor = WSL_PACKAGE_VERSION_MINOR; | ||
| Version->Revision = WSL_PACKAGE_VERSION_REVISION; |
There was a problem hiding this comment.
GetVersion dereferences Version without validating it. Add an explicit null check (returning E_POINTER) before writing to the out-parameter to avoid an AV if a caller passes nullptr.
| } | ||
|
|
||
| HRESULT WSLCSessionManager::GetMinimumSupportedClientVersion(_Out_ WSLCVersion* Version) | ||
| { |
There was a problem hiding this comment.
GetMinimumSupportedClientVersion writes to Version without validating it. Add an explicit null check (E_POINTER) to match the rest of the COM surface’s parameter validation and avoid crashing on nullptr.
| { | |
| { | |
| RETURN_HR_IF(E_POINTER, Version == nullptr); |
| constexpr std::tuple<uint32_t, uint32_t, uint32_t> c_minClientVersion{2, 9, 0}; | ||
|
|
||
| // If the current version is below the minimum version, return the current version for convenience. | ||
| // TODO: Remove once 2.9.0 is published. |
There was a problem hiding this comment.
Presumably the removal here would be replaced with a 2.8.* as appropriate to the actual release version of WSLC.
While this pattern can work, I still think it better that we have the code where we control the update make the actual decision on whether the client should be supported. Returning the version to the unchanging client code means we couldn't do things like "2.9.0 introduced a security issue that was fixed in 2.9.2, but 2.8.0 didn't have it and should be allowed to continue on".
| // The WSL runtime package, at an appropriate version to provide support for WSLC. | ||
| WSLC_COMPONENT_FLAG_WSL_PACKAGE = 2, | ||
| // Set if the WSLC SDK itself needs to be updated. | ||
| WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE = 4, |
There was a problem hiding this comment.
Adding this makes me want to change the API name back to WslcCanRun and change the name of the enum/parameter to be more aligned with "reasons that we cannot run".
|
The |
Summary of the Pull Request
This change adds a new COM API to check the earliest supported client version, and SDK logic to fail with a specific error code if the SDK is too old
PR Checklist
Detailed Description of the Pull Request / Additional comments
Validation Steps Performed