@samitouri / QOSAMI-WSL / commits / bd4e4362

wslc service com api support for emit warning message as optional com callback (#40558)

yao-msft committed Jun 3, 2026 at 11:21 UTC bd4e436239ead160713dd3e861b95a3158bf8838
27 files changed +784 -253
localization/strings/en-US/Resources.resw
+34
@@ -3113,4 +3113,38 @@ On first run, creates the file with all settings commented out at their defaults
3113 <value>Warning: Settings file at {} is empty or has invalid structure. Expected a YAML mapping.</value>
3114 <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
3115 </data>
3116 + <data name="MessageWslcFailedToRecoverContainer" xml:space="preserve">
3117 + <value>Failed to recover container '{}'</value>
3118 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
3119 + </data>
3120 + <data name="MessageWslcFailedToRecoverNetwork" xml:space="preserve">
3121 + <value>Failed to recover network '{}'</value>
3122 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
3123 + </data>
3124 + <data name="MessageWslcFailedToRecoverVolume" xml:space="preserve">
3125 + <value>Failed to recover volume '{}'</value>
3126 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
3127 + </data>
3128 + <data name="MessageWslcSwapInitFailed" xml:space="preserve">
3129 + <value>Failed to initialize swap</value>
3130 + </data>
3131 + <data name="MessageWslcContainerTimestampRecoveryFailed" xml:space="preserve">
3132 + <value>Failed to restore timestamp for container '{}'</value>
3133 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
3134 + </data>
3135 + <data name="MessageWslcImportProgressParseFailed" xml:space="preserve">
3136 + <value>Failed to parse image import progress</value>
3137 + </data>
3138 + <data name="MessageWslcVolumeUnmountFailed" xml:space="preserve">
3139 + <value>Failed to unmount volume '{}': {}</value>
3140 + <comment>{FixedPlaceholder="{}"}{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
3141 + </data>
3142 + <data name="MessageWslcContainerStopAfterPluginRejectionFailed" xml:space="preserve">
3143 + <value>Failed to stop container '{}' after plugin rejection</value>
3144 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
3145 + </data>
3146 + <data name="MessageWslcVolumeReleaseFailed" xml:space="preserve">
3147 + <value>Failed to release host resources for volume '{}'</value>
3148 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
3149 + </data>
3150 </root>
msipackage/package.wix.in
+8
@@ -322,6 +322,14 @@
322 </RegistryKey>
323 </RegistryKey>
324
325 + <!-- IWarningCallback-->
326 + <RegistryKey Root="HKCR" Key="Interface\{8153ED5D-8ABB-408B-ADBE-C0F3B13E07C3}">
327 + <RegistryValue Value="IWarningCallback" Type="string" />
328 + <RegistryKey Key="ProxyStubClsid32">
329 + <RegistryValue Value="{4EA0C6DD-E9FF-48E7-994E-13A31D10DC60}" Type="string" />
330 + </RegistryKey>
331 + </RegistryKey>
332 +
333 <!-- IWSLCSession-->
334 <RegistryKey Root="HKCR" Key="Interface\{EF0661E4-6364-40EA-B433-E2FDF11F3519}">
335 <RegistryValue Value="IWSLCSession" Type="string" />
src/windows/WslcSDK/wslcsdk.cpp
+9 -7
@@ -444,7 +444,8 @@ try
444 WI_SetFlag(runtimeSettings.FeatureFlags, WslcFeatureFlagsVirtioFs);
445 WI_SetFlag(runtimeSettings.FeatureFlags, WslcFeatureFlagsDnsTunneling);
446
447 - if (SUCCEEDED(errorInfoWrapper.CaptureResult(sessionManager->CreateSession(&runtimeSettings, WSLCSessionFlagsNone, &result->session))))
447 + if (SUCCEEDED(errorInfoWrapper.CaptureResult(
448 + sessionManager->CreateSession(&runtimeSettings, WSLCSessionFlagsNone, nullptr, &result->session))))
449 {
450 wsl::windows::common::security::ConfigureForCOMImpersonation(result->session.get());
451 *session = reinterpret_cast<WslcSession>(result.release());
@@ -831,7 +832,7 @@ try
832 // containerOptions.StopSignal;
833 // containerOptions.ShmSize;
834
834 - if (SUCCEEDED(errorInfoWrapper.CaptureResult(internalSession->session->CreateContainer(&containerOptions, &result->container))))
835 + if (SUCCEEDED(errorInfoWrapper.CaptureResult(internalSession->session->CreateContainer(&containerOptions, nullptr, &result->container))))
836 {
837 wsl::windows::common::security::ConfigureForCOMImpersonation(result->container.get());
838
@@ -859,7 +860,7 @@ try
860 // TODO: Consider if we should just override flags when callbacks were provided instead.
861 RETURN_HR_IF(E_INVALIDARG, WI_IsFlagClear(flags, WSLC_CONTAINER_START_FLAG_ATTACH) && hasIOCallback);
862
862 - if (SUCCEEDED(errorInfoWrapper.CaptureResult(internalType->container->Start(ConvertFlags(flags), nullptr))))
863 + if (SUCCEEDED(errorInfoWrapper.CaptureResult(internalType->container->Start(ConvertFlags(flags), nullptr, nullptr))))
864 {
865 if (hasIOCallback)
866 {
@@ -1331,7 +1332,7 @@ try
1332
1333 auto progressCallback = ProgressCallback::CreateIf(options);
1334
1334 - return errorInfoWrapper.CaptureResult(internalType->session->PullImage(options->uri, options->registryAuth, progressCallback.get()));
1335 + return errorInfoWrapper.CaptureResult(internalType->session->PullImage(options->uri, options->registryAuth, progressCallback.get(), nullptr));
1336 }
1337 CATCH_RETURN();
1338
@@ -1341,7 +1342,7 @@ static HRESULT WslcImportSessionImageImpl(
1342 auto progressCallback = ProgressCallback::CreateIf(options);
1343
1344 return errorInfoWrapper.CaptureResult(internalSession->session->ImportImage(
1344 - ToCOMInputHandle(imageFile.Handle()), imageName, progressCallback.get(), imageFile.Length()));
1345 + ToCOMInputHandle(imageFile.Handle()), imageName, progressCallback.get(), imageFile.Length(), nullptr));
1346 }
1347
1348 STDAPI WslcImportSessionImage(
@@ -1379,7 +1380,7 @@ static HRESULT WslcLoadSessionImageImpl(
1380 auto progressCallback = ProgressCallback::CreateIf(options);
1381
1382 return errorInfoWrapper.CaptureResult(
1382 - internalSession->session->LoadImage(ToCOMInputHandle(imageFile.Handle()), progressCallback.get(), imageFile.Length()));
1383 + internalSession->session->LoadImage(ToCOMInputHandle(imageFile.Handle()), progressCallback.get(), imageFile.Length(), nullptr));
1384 }
1385
1386 STDAPI WslcLoadSessionImage(
@@ -1458,7 +1459,8 @@ try
1459
1460 auto progressCallback = ProgressCallback::CreateIf(options);
1461
1461 - return errorInfoWrapper.CaptureResult(internalType->session->PushImage(options->image, options->registryAuth, progressCallback.get()));
1462 + return errorInfoWrapper.CaptureResult(
1463 + internalType->session->PushImage(options->image, options->registryAuth, progressCallback.get(), nullptr));
1464 }
1465 CATCH_RETURN();
1466
src/windows/common/WSLCContainerLauncher.cpp
+10 -9
@@ -250,20 +250,21 @@ void wsl::windows::common::WSLCContainerLauncher::AddAdditionalNetwork(const std
250 m_additionalNetworks.push_back(Name);
251 }
252
253 -std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::LaunchNoThrow(IWSLCSession& Session, WSLCContainerStartFlags Flags)
253 +std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::LaunchNoThrow(
254 + IWSLCSession& Session, WSLCContainerStartFlags Flags, IWarningCallback* WarningCallback)
255 {
255 - auto [result, container] = CreateNoThrow(Session);
256 + auto [result, container] = CreateNoThrow(Session, WarningCallback);
257 if (FAILED(result))
258 {
259 return std::make_pair(result, std::optional<RunningWSLCContainer>{});
260 }
261
261 - result = container.value().Get().Start(Flags, nullptr);
262 + result = container.value().Get().Start(Flags, nullptr, WarningCallback);
263
264 return std::make_pair(result, std::move(container));
265 }
266
266 -std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::CreateNoThrow(IWSLCSession& Session)
267 +std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::CreateNoThrow(IWSLCSession& Session, IWarningCallback* WarningCallback)
268 {
269 WSLCContainerOptions options{};
270 options.Image = m_image.c_str();
@@ -373,7 +374,7 @@ std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::C
374
375 // TODO: Support volumes, ports, flags, container networking mode, etc.
376 wil::com_ptr<IWSLCContainer> container;
376 - auto result = Session.CreateContainer(&options, &container);
377 + auto result = Session.CreateContainer(&options, WarningCallback, &container);
378 if (FAILED(result))
379 {
380 return std::pair<HRESULT, std::optional<RunningWSLCContainer>>(result, std::optional<RunningWSLCContainer>{});
@@ -382,17 +383,17 @@ std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::C
383 return std::make_pair(S_OK, std::move(RunningWSLCContainer{std::move(container), m_flags}));
384 }
385
385 -RunningWSLCContainer WSLCContainerLauncher::Create(IWSLCSession& Session)
386 +RunningWSLCContainer WSLCContainerLauncher::Create(IWSLCSession& Session, IWarningCallback* WarningCallback)
387 {
387 - auto [result, container] = CreateNoThrow(Session);
388 + auto [result, container] = CreateNoThrow(Session, WarningCallback);
389 THROW_IF_FAILED(result);
390
391 return std::move(container.value());
392 }
393
393 -RunningWSLCContainer WSLCContainerLauncher::Launch(IWSLCSession& Session, WSLCContainerStartFlags Flags)
394 +RunningWSLCContainer WSLCContainerLauncher::Launch(IWSLCSession& Session, WSLCContainerStartFlags Flags, IWarningCallback* WarningCallback)
395 {
395 - auto [result, container] = LaunchNoThrow(Session, Flags);
396 + auto [result, container] = LaunchNoThrow(Session, Flags, WarningCallback);
397 THROW_IF_FAILED(result);
398
399 return std::move(container.value());
src/windows/common/WSLCContainerLauncher.h
+5 -4
@@ -64,11 +64,12 @@ public:
64 void AddTmpfs(const std::string& ContainerPath, const std::string& Options);
65 void AddAdditionalNetwork(const std::string& Name);
66
67 - std::pair<HRESULT, std::optional<RunningWSLCContainer>> CreateNoThrow(IWSLCSession& Session);
68 - RunningWSLCContainer Create(IWSLCSession& Session);
67 + std::pair<HRESULT, std::optional<RunningWSLCContainer>> CreateNoThrow(IWSLCSession& Session, IWarningCallback* WarningCallback = nullptr);
68 + RunningWSLCContainer Create(IWSLCSession& Session, IWarningCallback* WarningCallback = nullptr);
69
70 - RunningWSLCContainer Launch(IWSLCSession& Session, WSLCContainerStartFlags Flags = WSLCContainerStartFlagsAttach);
71 - std::pair<HRESULT, std::optional<RunningWSLCContainer>> LaunchNoThrow(IWSLCSession& Session, WSLCContainerStartFlags Flags = WSLCContainerStartFlagsAttach);
70 + RunningWSLCContainer Launch(IWSLCSession& Session, WSLCContainerStartFlags Flags = WSLCContainerStartFlagsAttach, IWarningCallback* WarningCallback = nullptr);
71 + std::pair<HRESULT, std::optional<RunningWSLCContainer>> LaunchNoThrow(
72 + IWSLCSession& Session, WSLCContainerStartFlags Flags = WSLCContainerStartFlagsAttach, IWarningCallback* WarningCallback = nullptr);
73
74 void SetName(std::string&& Name);
75 void SetEntrypoint(std::vector<std::string>&& entrypoint);
src/windows/inc/docker_schema.h
+2 -1
@@ -38,9 +38,10 @@ struct ErrorResponse
38 struct ImageLoadResult
39 {
40 std::optional<std::string> stream;
41 + std::optional<std::string> status;
42 std::optional<ErrorResponse> errorDetail;
43
43 - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ImageLoadResult, stream, errorDetail);
44 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ImageLoadResult, stream, status, errorDetail);
45 };
46
47 struct EmptyRequest
src/windows/service/exe/WSLCSessionManager.cpp
+12 -9
@@ -164,7 +164,8 @@ try
164 }
165 CATCH_LOG()
166
167 -void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, IWSLCSession** WslcSession)
167 +void WSLCSessionManagerImpl::CreateSession(
168 + _In_ const WSLCSessionSettings* Settings, _In_ WSLCSessionFlags Flags, _In_opt_ IWarningCallback* WarningCallback, _Out_ IWSLCSession** WslcSession)
169 {
170 auto tokenInfo = GetCallingProcessTokenInfo();
171 const auto callerToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
@@ -269,10 +270,10 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings,
270 auto factory = wslutil::CreateComServerAsUser<IWSLCSessionFactory>(__uuidof(WSLCSessionFactory), userToken.get());
271 AddSessionProcessToJobObject(factory.get());
272
272 - auto sessionSettings = CreateSessionSettings(sessionId, creatorPid, Settings, resolvedDisplayName.c_str());
273 + const auto sessionSettings = CreateSessionSettings(sessionId, creatorPid, Settings, resolvedDisplayName.c_str());
274 wil::com_ptr<IWSLCSession> session;
275 wil::com_ptr<IWSLCSessionReference> serviceRef;
275 - THROW_IF_FAILED(factory->CreateSession(&sessionSettings, vm.Get(), notifier.Get(), &session, &serviceRef));
276 + THROW_IF_FAILED(factory->CreateSession(&sessionSettings, vm.Get(), notifier.Get(), WarningCallback, &session, &serviceRef));
277
278 // Track the session via its service ref, along with metadata and security info.
279 m_sessions.push_back(SessionEntry{
@@ -402,14 +403,15 @@ void WSLCSessionManagerImpl::ListSessions(_Out_ WSLCSessionListEntry** Sessions,
403 *SessionsCount = static_cast<ULONG>(sessionInfo.size());
404 }
405
405 -void WSLCSessionManagerImpl::EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession)
406 +void WSLCSessionManagerImpl::EnterSession(
407 + _In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, _In_opt_ IWarningCallback* WarningCallback, _Out_ IWSLCSession** WslcSession)
408 {
409 THROW_HR_IF(E_POINTER, DisplayName == nullptr || StoragePath == nullptr);
410 THROW_HR_IF(E_INVALIDARG, DisplayName[0] == L'\0' || StoragePath[0] == L'\0');
411
412 const auto callerToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
413 auto sessionSettings = SessionSettings::Custom(callerToken.get(), DisplayName, StoragePath, WSLCSessionStorageFlagsNoCreate);
412 - CreateSession(&sessionSettings.Settings, WSLCSessionFlagsNone, WslcSession);
414 + CreateSession(&sessionSettings.Settings, WSLCSessionFlagsNone, WarningCallback, WslcSession);
415 }
416
417 WSLCSessionInitSettings WSLCSessionManagerImpl::CreateSessionSettings(
@@ -551,20 +553,21 @@ try
553 }
554 CATCH_RETURN();
555
554 -HRESULT WSLCSessionManager::CreateSession(const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWSLCSession** WslcSession)
556 +HRESULT WSLCSessionManager::CreateSession(
557 + const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWarningCallback* WarningCallback, IWSLCSession** WslcSession)
558 try
559 {
560 COMServiceExecutionContext context;
561
559 - return CallImpl(&WSLCSessionManagerImpl::CreateSession, WslcSessionSettings, Flags, WslcSession);
562 + return CallImpl(&WSLCSessionManagerImpl::CreateSession, WslcSessionSettings, Flags, WarningCallback, WslcSession);
563 }
564 CATCH_RETURN();
565
563 -HRESULT WSLCSessionManager::EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession)
566 +HRESULT WSLCSessionManager::EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWarningCallback* WarningCallback, IWSLCSession** WslcSession)
567 {
568 COMServiceExecutionContext context;
569
567 - return CallImpl(&WSLCSessionManagerImpl::EnterSession, DisplayName, StoragePath, WslcSession);
570 + return CallImpl(&WSLCSessionManagerImpl::EnterSession, DisplayName, StoragePath, WarningCallback, WslcSession);
571 }
572
573 HRESULT WSLCSessionManager::ListSessions(_Out_ WSLCSessionListEntry** Sessions, _Out_ ULONG* SessionsCount)
src/windows/service/exe/WSLCSessionManager.h
+9 -4
@@ -79,8 +79,12 @@ public:
79 WSLCSessionManagerImpl();
80 ~WSLCSessionManagerImpl();
81
82 - void CreateSession(const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWSLCSession** WslcSession);
83 - void EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession);
82 + void CreateSession(
83 + _In_ const WSLCSessionSettings* WslcSessionSettings,
84 + _In_ WSLCSessionFlags Flags,
85 + _In_opt_ IWarningCallback* WarningCallback,
86 + _Out_ IWSLCSession** WslcSession);
87 + void EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, _In_opt_ IWarningCallback* WarningCallback, _Out_ IWSLCSession** WslcSession);
88 void ListSessions(_Out_ WSLCSessionListEntry** Sessions, _Out_ ULONG* SessionsCount);
89 void OpenSession(_In_ ULONG Id, _Out_ IWSLCSession** Session);
90 void OpenSessionByName(_In_ LPCWSTR DisplayName, _Out_ IWSLCSession** Session);
@@ -198,8 +202,9 @@ public:
202
203 IFACEMETHOD(GetVersion)(_Out_ WSLCVersion* Version) override;
204 IFACEMETHOD(IsClientVersionSupported)(_In_ const WSLCVersion* ClientVersion, _Out_ BOOL* IsSupported) override;
201 - IFACEMETHOD(CreateSession)(const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWSLCSession** WslcSession) override;
202 - IFACEMETHOD(EnterSession)(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession) override;
205 + IFACEMETHOD(CreateSession)(
206 + const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWarningCallback* WarningCallback, IWSLCSession** WslcSession) override;
207 + IFACEMETHOD(EnterSession)(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWarningCallback* WarningCallback, IWSLCSession** WslcSession) override;
208 IFACEMETHOD(ListSessions)(_Out_ WSLCSessionListEntry** Sessions, _Out_ ULONG* SessionsCount) override;
209 IFACEMETHOD(OpenSession)(_In_ ULONG Id, _Out_ IWSLCSession** Session) override;
210 IFACEMETHOD(OpenSessionByName)(_In_ LPCWSTR DisplayName, _Out_ IWSLCSession** Session) override;
src/windows/service/inc/wslc.idl
+23 -11
@@ -117,6 +117,16 @@ interface IProgressCallback : IUnknown
117 HRESULT OnProgress(LPCSTR Status, LPCSTR Id, ULONGLONG Current, ULONGLONG Total);
118 };
119
120 +[
121 + uuid(8153ED5D-8ABB-408B-ADBE-C0F3B13E07C3),
122 + pointer_default(unique),
123 + object
124 +]
125 +interface IWarningCallback : IUnknown
126 +{
127 + HRESULT OnWarning([in, string] LPCWSTR Message);
128 +};
129 +
130 [
131 uuid(F3E6D5B2-1D40-4E8B-9C39-7A45D1C0F8A2),
132 pointer_default(unique),
@@ -556,7 +566,7 @@ interface IWSLCContainer : IUnknown
566 {
567 HRESULT Attach([in, unique] LPCSTR DetachKeys, [out] WSLCHandle* StdIn, [out] WSLCHandle* StdOut, [out] WSLCHandle* StdErr);
568 HRESULT Stop([in] WSLCSignal Signal, [in] LONG TimeoutSeconds);
559 - HRESULT Start([in] WSLCContainerStartFlags Flags, [in, unique] LPCSTR DetachKeys);
569 + HRESULT Start([in] WSLCContainerStartFlags Flags, [in, unique] LPCSTR DetachKeys, [in, unique] IWarningCallback* WarningCallback);
570 HRESULT Delete([in] WSLCDeleteFlags Flags);
571 HRESULT Export([in] WSLCHandle TarHandle);
572 HRESULT GetState([out] WSLCContainerState* State);
@@ -724,10 +734,10 @@ interface IWSLCSession : IUnknown
734 HRESULT GetState([out] WSLCSessionState* State);
735
736 // Image management.
727 - HRESULT PullImage([in] LPCSTR Image, [in, unique] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback);
737 + HRESULT PullImage([in] LPCSTR Image, [in, unique] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback);
738 HRESULT BuildImage([in] const WSLCBuildImageOptions* Options, [in, unique] IProgressCallback* ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent);
729 - HRESULT LoadImage([in] WSLCHandle ImageHandle, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength);
730 - HRESULT ImportImage([in] WSLCHandle ImageHandle, [in] LPCSTR ImageName, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength);
739 + HRESULT LoadImage([in] WSLCHandle ImageHandle, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback);
740 + HRESULT ImportImage([in] WSLCHandle ImageHandle, [in] LPCSTR ImageName, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback);
741 HRESULT SaveImage([in] WSLCHandle OutputHandle, [in] LPCSTR ImageNameOrID, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent);
742 HRESULT ListImages([in, unique] const WSLCListImagesOptions* Options, [out, size_is(, *Count)] WSLCImageInformation** Images, [out] ULONG* Count);
743 HRESULT DeleteImage([in] const WSLCDeleteImageOptions* Options, [out, size_is(, *Count)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* Count);
@@ -736,7 +746,7 @@ interface IWSLCSession : IUnknown
746 HRESULT PruneImages([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *DeletedImagesCount)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* DeletedImagesCount, [out] ULONGLONG* SpaceReclaimed);
747
748 // Container management.
739 - HRESULT CreateContainer([in] const WSLCContainerOptions* Options, [out] IWSLCContainer** Container);
749 + HRESULT CreateContainer([in] const WSLCContainerOptions* Options, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCContainer** Container);
750 HRESULT OpenContainer([in, ref] LPCSTR Id, [out] IWSLCContainer** Container);
751 HRESULT ListContainers([in, unique] const WSLCListContainersOptions* Options,[out, size_is(, *Count)] WSLCContainerEntry** Containers,[out] ULONG* Count, [out, size_is(, *PortsCount)] WSLCContainerPortMapping** Ports, [out] ULONG* PortsCount);
752 HRESULT PruneContainers([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out] WSLCPruneContainersResults* Result);
@@ -766,7 +776,8 @@ interface IWSLCSession : IUnknown
776 HRESULT Initialize(
777 [in] const WSLCSessionInitSettings* Settings,
778 [in] IWSLCVirtualMachine* Vm,
769 - [in] IWSLCPluginNotifier* PluginNotifier);
779 + [in] IWSLCPluginNotifier* PluginNotifier,
780 + [in, unique] IWarningCallback* WarningCallback);
781
782 // Volume management.
783 HRESULT CreateVolume([in] const WSLCVolumeOptions* Options, [out] WSLCVolumeInformation* VolumeInfo);
@@ -775,11 +786,11 @@ interface IWSLCSession : IUnknown
786 HRESULT InspectVolume([in] LPCSTR Name, [out] LPSTR* Output);
787
788 HRESULT Authenticate([in] LPCSTR ServerAddress, [in] LPCSTR Username, [in] LPCSTR Password, [out] LPSTR* IdentityToken);
778 - HRESULT PushImage([in] LPCSTR Image, [in] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback);
779 - HRESULT PruneVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *VolumesCount)] WSLCVolumeName** Volumes, [out] ULONG* VolumesCount, [out] ULONGLONG* SpaceReclaimed);
789 + HRESULT PushImage([in] LPCSTR Image, [in] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback);
790 + HRESULT PruneVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [in, unique] IWarningCallback* WarningCallback, [out, size_is(, *VolumesCount)] WSLCVolumeName** Volumes, [out] ULONG* VolumesCount, [out] ULONGLONG* SpaceReclaimed);
791
792 // Network management.
782 - HRESULT CreateNetwork([in] const WSLCNetworkOptions* Options);
793 + HRESULT CreateNetwork([in] const WSLCNetworkOptions* Options, [in, unique] IWarningCallback* WarningCallback);
794 HRESULT DeleteNetwork([in] LPCSTR Name);
795 HRESULT ListNetworks([out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count);
796 HRESULT InspectNetwork([in] LPCSTR Name, [out] LPSTR* Output);
@@ -821,6 +832,7 @@ interface IWSLCSessionFactory : IUnknown
832 [in] const WSLCSessionInitSettings* Settings,
833 [in] IWSLCVirtualMachine* Vm,
834 [in] IWSLCPluginNotifier* PluginNotifier,
835 + [in, unique] IWarningCallback* WarningCallback,
836 [out] IWSLCSession** Session,
837 [out] IWSLCSessionReference** ServiceRef);
838
@@ -857,8 +869,8 @@ interface IWSLCSessionManager : IUnknown
869 HRESULT IsClientVersionSupported([in] const WSLCVersion* ClientVersion, [out] BOOL* IsSupported);
870
871 // Session management.
860 - HRESULT CreateSession([in, unique] const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, [out] IWSLCSession** Session);
861 - HRESULT EnterSession([in, ref] LPCWSTR DisplayName, [in, ref] LPCWSTR StoragePath, [out] IWSLCSession** Session);
872 + HRESULT CreateSession([in, unique] const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session);
873 + HRESULT EnterSession([in, ref] LPCWSTR DisplayName, [in, ref] LPCWSTR StoragePath, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session);
874 HRESULT ListSessions([out, size_is(, *SessionsCount)] WSLCSessionListEntry** Sessions, [out] ULONG* SessionsCount);
875 HRESULT OpenSession([in] ULONG Id, [out] IWSLCSession** Session);
876 HRESULT OpenSessionByName([in, unique] LPCWSTR DisplayName, [out] IWSLCSession** Session);
src/windows/wslc/services/ContainerService.cpp
+12 -7
@@ -17,6 +17,7 @@ Abstract:
17 #include "ConsoleService.h"
18 #include "ImageService.h"
19 #include "ImageProgressCallback.h"
20 +#include "WarningCallback.h"
21 #include <wslutil.h>
22 #include <WSLCProcessLauncher.h>
23 #include <CommandLine.h>
@@ -38,7 +39,8 @@ static void SetContainerArguments(WSLCProcessOptions& options, std::vector<const
39 options.CommandLine = {.Values = argsStorage.data(), .Count = static_cast<ULONG>(argsStorage.size())};
40 }
41
41 -static wsl::windows::common::RunningWSLCContainer CreateInternal(Session& session, const std::string& image, const ContainerOptions& options)
42 +static wsl::windows::common::RunningWSLCContainer CreateInternal(
43 + Session& session, const std::string& image, const ContainerOptions& options, IWarningCallback* warningCallback = nullptr)
44 {
45 auto processFlags = WSLCProcessFlagsNone;
46 WI_SetFlagIf(processFlags, WSLCProcessFlagsStdin, options.Interactive);
@@ -160,7 +162,7 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(Session& sessio
162 containerLauncher.AddLabel(key, value);
163 }
164
163 - auto [result, runningContainer] = containerLauncher.CreateNoThrow(*session.Get());
165 + auto [result, runningContainer] = containerLauncher.CreateNoThrow(*session.Get(), warningCallback);
166 if (result == WSLC_E_IMAGE_NOT_FOUND)
167 {
168 {
@@ -170,7 +172,7 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(Session& sessio
172 ImageService imageService;
173 imageService.Pull(session, image, &callback);
174 }
173 - return containerLauncher.Create(*session.Get());
175 + return containerLauncher.Create(*session.Get(), warningCallback);
176 }
177
178 THROW_IF_FAILED(result);
@@ -346,9 +348,10 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO
348 // container isn't created when the caller-requested path can't be written. The file is
349 // removed automatically if we don't reach Commit() below.
350 CidFile cidFile(runOptions.CidFile);
351 + auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
352
353 // Create the container
351 - auto runningContainer = CreateInternal(session, image, runOptions);
354 + auto runningContainer = CreateInternal(session, image, runOptions, warningCallback.Get());
355 auto& container = runningContainer.Get();
356
357 WSLCContainerId containerId{};
@@ -357,7 +360,7 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO
360 // Start the created container
361 WSLCContainerStartFlags startFlags{};
362 WI_SetFlagIf(startFlags, WSLCContainerStartFlagsAttach, !runOptions.Detach);
360 - THROW_IF_FAILED(container.Start(startFlags, nullptr)); // TODO: Error message, detach keys
363 + THROW_IF_FAILED(container.Start(startFlags, nullptr, warningCallback.Get())); // TODO: Error message, detach keys
364
365 // Disable auto-delete only after successful start
366 runningContainer.SetDeleteOnClose(false);
@@ -377,7 +380,8 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO
380 CreateContainerResult ContainerService::Create(Session& session, const std::string& image, ContainerOptions runOptions)
381 {
382 CidFile cidFile(runOptions.CidFile);
380 - auto runningContainer = CreateInternal(session, image, runOptions);
383 + auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
384 + auto runningContainer = CreateInternal(session, image, runOptions, warningCallback.Get());
385 runningContainer.SetDeleteOnClose(false);
386 auto& container = runningContainer.Get();
387 WSLCContainerId id{};
@@ -391,7 +395,8 @@ int ContainerService::Start(Session& session, const std::string& id, bool attach
395 wil::com_ptr<IWSLCContainer> container;
396 THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
397 WSLCContainerStartFlags flags = attach ? WSLCContainerStartFlagsAttach : WSLCContainerStartFlagsNone;
394 - THROW_IF_FAILED_EXCEPT(container->Start(flags, nullptr), WSLC_E_CONTAINER_IS_RUNNING);
398 + auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
399 + THROW_IF_FAILED_EXCEPT(container->Start(flags, nullptr, warningCallback.Get()), WSLC_E_CONTAINER_IS_RUNNING);
400
401 if (!attach)
402 {
src/windows/wslc/services/ImageService.cpp
+13 -4
@@ -14,6 +14,7 @@ Abstract:
14 #include "ImageService.h"
15 #include "RegistryService.h"
16 #include "SessionService.h"
17 +#include "WarningCallback.h"
18 #include <wslutil.h>
19 #include <HandleConsoleProgressBar.h>
20 #include <relay.hpp>
@@ -227,14 +228,20 @@ std::vector<ImageInformation> ImageService::List(
228 void ImageService::Load(wsl::windows::wslc::models::Session& session, const std::wstring& input)
229 {
230 auto source = OpenImageInput(input);
230 - THROW_IF_FAILED(session.Get()->LoadImage(ToCOMInputHandle(source.Handle.Get()), nullptr, source.ContentLength));
231 + auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
232 + THROW_IF_FAILED(session.Get()->LoadImage(ToCOMInputHandle(source.Handle.Get()), nullptr, source.ContentLength, warningCallback.Get()));
233 }
234
235 void ImageService::Import(wsl::windows::wslc::models::Session& session, const std::wstring& input, const std::string& imageName)
236 {
237 auto source = OpenImageInput(input);
238 + auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
239 THROW_IF_FAILED(session.Get()->ImportImage(
237 - ToCOMInputHandle(source.Handle.Get()), imageName.empty() ? nullptr : imageName.c_str(), nullptr, source.ContentLength));
240 + ToCOMInputHandle(source.Handle.Get()),
241 + imageName.empty() ? nullptr : imageName.c_str(),
242 + nullptr,
243 + source.ContentLength,
244 + warningCallback.Get()));
245 }
246
247 void ImageService::Delete(wsl::windows::wslc::models::Session& session, const std::string& image, bool force, bool noPrune)
@@ -260,7 +267,8 @@ void ImageService::Pull(wsl::windows::wslc::models::Session& session, const std:
267 {
268 auto server = GetServerFromImage(image);
269 auto auth = RegistryService::Get(server);
263 - THROW_IF_FAILED(session.Get()->PullImage(image.c_str(), auth.c_str(), callback));
270 + auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
271 + THROW_IF_FAILED(session.Get()->PullImage(image.c_str(), auth.c_str(), callback, warningCallback.Get()));
272 }
273
274 void ImageService::Tag(wsl::windows::wslc::models::Session& session, const std::string& sourceImage, const std::string& targetImage)
@@ -291,7 +299,8 @@ void ImageService::Push(wsl::windows::wslc::models::Session& session, const std:
299 {
300 auto server = GetServerFromImage(image);
301 auto auth = RegistryService::Get(server);
294 - THROW_IF_FAILED(session.Get()->PushImage(image.c_str(), auth.c_str(), callback));
302 + auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
303 + THROW_IF_FAILED(session.Get()->PushImage(image.c_str(), auth.c_str(), callback, warningCallback.Get()));
304 }
305
306 void ImageService::Save(wsl::windows::wslc::models::Session& session, const std::string& image, const std::wstring& output, HANDLE cancelEvent)
src/windows/wslc/services/SessionService.cpp
+5 -2
@@ -15,6 +15,7 @@ Abstract:
15 #include "precomp.h"
16 #include "SessionService.h"
17 #include "ConsoleService.h"
18 +#include "WarningCallback.h"
19 #include <wslc.h>
20 #include <WSLCProcessLauncher.h>
21
@@ -113,7 +114,8 @@ Session SessionService::CreateDefaultSession()
114
115 // Null Settings = default session with server-determined name and settings.
116 wil::com_ptr<IWSLCSession> session;
116 - THROW_IF_FAILED(sessionManager->CreateSession(nullptr, WSLCSessionFlagsNone, &session));
117 + auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
118 + THROW_IF_FAILED(sessionManager->CreateSession(nullptr, WSLCSessionFlagsNone, warningCallback.Get(), &session));
119 wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
120 return Session(std::move(session));
121 }
@@ -128,7 +130,8 @@ int SessionService::Enter(const std::wstring& storagePath, const std::wstring& d
130 wsl::windows::common::security::ConfigureForCOMImpersonation(sessionManager.get());
131
132 wil::com_ptr<IWSLCSession> session;
131 - THROW_IF_FAILED(sessionManager->EnterSession(displayName.c_str(), storagePath.c_str(), &session));
133 + auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
134 + THROW_IF_FAILED(sessionManager->EnterSession(displayName.c_str(), storagePath.c_str(), warningCallback.Get(), &session));
135 wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
136 wsl::windows::common::wslutil::PrintMessage(Localization::MessageWslcCreatedSession(displayName), stderr);
137
src/windows/wslc/services/WarningCallback.h new
+24
@@ -0,0 +1,24 @@
1 +// Copyright (C) Microsoft Corporation. All rights reserved.
2 +
3 +#pragma once
4 +
5 +#include <wslc.h>
6 +#include <wslutil.h>
7 +
8 +namespace wsl::windows::wslc::services {
9 +
10 +class DECLSPEC_UUID("A7E3F8B2-4D19-4C6A-9E5B-8F2A1D3C7E90") WarningCallback
11 + : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IWarningCallback, IFastRundown>
12 +{
13 +public:
14 + HRESULT OnWarning(LPCWSTR Message) override
15 + {
16 + WI_ASSERT(Message);
17 + // The message already includes the "wsl: " prefix and a trailing newline (added by
18 + // EmitUserWarning), so write it directly. This matches how wsl.exe surfaces its warnings.
19 + fputws(Message, stderr);
20 + return S_OK;
21 + }
22 +};
23 +
24 +} // namespace wsl::windows::wslc::services
src/windows/wslcsession/WSLCContainer.cpp
+47 -22
@@ -19,6 +19,7 @@ Abstract:
19
20 #include "precomp.h"
21 #include "WSLCContainer.h"
22 +#include "WSLCExecutionContext.h"
23 #include "WSLCProcess.h"
24 #include "WSLCProcessIO.h"
25 #include "WSLCVolumes.h"
@@ -44,6 +45,7 @@ using wsl::windows::service::wslc::WSLCContainer;
45 using wsl::windows::service::wslc::WSLCContainerImpl;
46 using wsl::windows::service::wslc::WSLCContainerMetadata;
47 using wsl::windows::service::wslc::WSLCContainerMetadataV1;
48 +using wsl::windows::service::wslc::WSLCExecutionContext;
49 using wsl::windows::service::wslc::WSLCPortMapping;
50 using wsl::windows::service::wslc::WSLCSession;
51 using wsl::windows::service::wslc::WSLCVirtualMachine;
@@ -282,10 +284,17 @@ void UnmountVolumes(std::vector<WSLCVolumeMount>& volumes, WSLCVirtualMachine& p
284 {
285 if (volume.Mounted)
286 {
285 - if (SUCCEEDED(LOG_IF_FAILED(parentVM.UnmountWindowsFolder(volume.ParentVMPath.c_str()))))
287 + auto result = parentVM.UnmountWindowsFolder(volume.ParentVMPath.c_str());
288 + if (SUCCEEDED(result))
289 {
290 volume.Mounted = false;
291 }
292 + else
293 + {
294 + LOG_HR(result);
295 + EMIT_USER_WARNING(wsl::shared::Localization::MessageWslcVolumeUnmountFailed(
296 + volume.HostPath, wsl::windows::common::wslutil::GetErrorString(result)));
297 + }
298 }
299 }
300 }
@@ -500,7 +509,7 @@ WSLCContainerImpl::WSLCContainerImpl(
509 m_mountedVolumes(std::move(volumes)),
510 m_mappedPorts(std::move(ports)),
511 m_labels(std::move(labels)),
503 - m_comWrapper(wil::MakeOrThrow<WSLCContainer>(this, std::move(onDeleted))),
512 + m_comWrapper(wil::MakeOrThrow<WSLCContainer>(this, wslcSession, std::move(onDeleted))),
513 m_dockerClient(DockerClient),
514 m_eventTracker(EventTracker),
515 m_ioRelay(Relay),
@@ -760,7 +769,12 @@ void WSLCContainerImpl::Start(WSLCContainerStartFlags Flags, LPCSTR DetachKeys)
769 {
770 m_dockerClient.StopContainer(m_id.c_str(), {}, {});
771 }
763 - CATCH_LOG();
772 + catch (...)
773 + {
774 + LOG_CAUGHT_EXCEPTION();
775 + EMIT_USER_WARNING(wsl::shared::Localization::MessageWslcContainerStopAfterPluginRejectionFailed(
776 + wsl::shared::string::MultiByteToWide(m_id)));
777 + }
778
779 if (comError.has_value() && comError->Message)
780 {
@@ -1582,6 +1596,12 @@ std::unique_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
1596 // Send the request to docker.
1597 auto result = DockerClient.CreateContainer(request, containerName);
1598
1599 + // Surface any warnings returned by Docker (e.g., deprecated features, configuration issues).
1600 + for (const auto& warning : result.Warnings)
1601 + {
1602 + EMIT_USER_WARNING(wsl::shared::string::MultiByteToWide(warning));
1603 + }
1604 +
1605 // Clean up the Docker container if anything below fails.
1606 // N.B. The container ID is captured by value since it is moved into the WSLCContainerImpl constructor below.
1607 auto deleteOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&DockerClient, containerId = result.Id]() {
@@ -1753,7 +1773,12 @@ std::unique_ptr<WSLCContainerImpl> WSLCContainerImpl::Open(
1773 container->m_stateChangedAt = ParseDockerTimestamp(timestamp);
1774 }
1775 }
1756 - CATCH_LOG();
1776 + catch (...)
1777 + {
1778 + LOG_CAUGHT_EXCEPTION();
1779 + EMIT_USER_WARNING(wsl::shared::Localization::MessageWslcContainerTimestampRecoveryFailed(
1780 + wsl::shared::string::MultiByteToWide(dockerContainer.Id)));
1781 + }
1782
1783 return container;
1784 }
@@ -2020,14 +2045,14 @@ __requires_lock_held(m_lock) void WSLCContainerImpl::Transition(WSLCContainerSta
2045 m_stateChangedAt = stateChangedAt.value_or(static_cast<std::uint64_t>(std::time(nullptr)));
2046 }
2047
2023 -WSLCContainer::WSLCContainer(WSLCContainerImpl* impl, std::function<void(const WSLCContainerImpl*)>&& OnDeleted) :
2024 - COMImplClass<WSLCContainerImpl>(impl), m_onDeleted(std::move(OnDeleted))
2048 +WSLCContainer::WSLCContainer(WSLCContainerImpl* impl, WSLCSession& session, std::function<void(const WSLCContainerImpl*)>&& OnDeleted) :
2049 + COMImplClass<WSLCContainerImpl>(impl), m_session(session), m_onDeleted(std::move(OnDeleted))
2050 {
2051 }
2052
2053 HRESULT WSLCContainer::Attach(LPCSTR DetachKeys, WSLCHandle* Stdin, WSLCHandle* Stdout, WSLCHandle* Stderr)
2054 {
2030 - COMServiceExecutionContext context;
2055 + WSLCExecutionContext context(&m_session);
2056
2057 *Stdin = {};
2058 *Stdout = {};
@@ -2038,7 +2063,7 @@ HRESULT WSLCContainer::Attach(LPCSTR DetachKeys, WSLCHandle* Stdin, WSLCHandle*
2063
2064 HRESULT WSLCContainer::GetState(WSLCContainerState* Result)
2065 {
2041 - COMServiceExecutionContext context;
2066 + WSLCExecutionContext context(&m_session);
2067 RETURN_HR_IF_NULL(E_POINTER, Result);
2068
2069 *Result = WslcContainerStateInvalid;
@@ -2065,7 +2090,7 @@ HRESULT WSLCContainer::GetState(WSLCContainerState* Result)
2090
2091 HRESULT WSLCContainer::GetInitProcess(IWSLCProcess** Process)
2092 {
2068 - COMServiceExecutionContext context;
2093 + WSLCExecutionContext context(&m_session);
2094
2095 *Process = nullptr;
2096
@@ -2091,7 +2116,7 @@ HRESULT WSLCContainer::GetInitProcess(IWSLCProcess** Process)
2116
2117 HRESULT WSLCContainer::Exec(const WSLCProcessOptions* Options, LPCSTR DetachKeys, IWSLCProcess** Process)
2118 {
2094 - COMServiceExecutionContext context;
2119 + WSLCExecutionContext context(&m_session);
2120
2121 *Process = nullptr;
2122 return CallImpl(&WSLCContainerImpl::Exec, Options, DetachKeys, Process);
@@ -2099,22 +2124,22 @@ HRESULT WSLCContainer::Exec(const WSLCProcessOptions* Options, LPCSTR DetachKeys
2124
2125 HRESULT WSLCContainer::Stop(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds)
2126 {
2102 - COMServiceExecutionContext context;
2127 + WSLCExecutionContext context(&m_session);
2128
2129 return CallImpl(&WSLCContainerImpl::Stop, Signal, TimeoutSeconds, false);
2130 }
2131
2132 HRESULT WSLCContainer::Kill(_In_ WSLCSignal Signal)
2133 {
2109 - COMServiceExecutionContext context;
2134 + WSLCExecutionContext context(&m_session);
2135
2136 return CallImpl(&WSLCContainerImpl::Stop, Signal, {}, true);
2137 }
2138
2114 -HRESULT WSLCContainer::Start(WSLCContainerStartFlags Flags, LPCSTR DetachKeys)
2139 +HRESULT WSLCContainer::Start(WSLCContainerStartFlags Flags, LPCSTR DetachKeys, IWarningCallback* WarningCallback)
2140 try
2141 {
2117 - COMServiceExecutionContext context;
2142 + WSLCExecutionContext context(&m_session, WarningCallback);
2143
2144 THROW_HR_IF_MSG(E_INVALIDARG, WI_IsAnyFlagSet(Flags, ~WSLCContainerStartFlagsValid), "Invalid flags: 0x%x", Flags);
2145
@@ -2124,7 +2149,7 @@ CATCH_RETURN();
2149
2150 HRESULT WSLCContainer::Inspect(LPSTR* Output)
2151 {
2127 - COMServiceExecutionContext context;
2152 + WSLCExecutionContext context(&m_session);
2153
2154 *Output = nullptr;
2155
@@ -2134,7 +2159,7 @@ HRESULT WSLCContainer::Inspect(LPSTR* Output)
2159 HRESULT WSLCContainer::Stats(LPSTR* Output)
2160 try
2161 {
2137 - COMServiceExecutionContext context;
2162 + WSLCExecutionContext context(&m_session);
2163
2164 RETURN_HR_IF(E_POINTER, Output == nullptr);
2165
@@ -2146,7 +2171,7 @@ CATCH_RETURN();
2171 HRESULT WSLCContainer::Delete(WSLCDeleteFlags Flags)
2172 try
2173 {
2149 - COMServiceExecutionContext context;
2174 + WSLCExecutionContext context(&m_session);
2175
2176 THROW_HR_IF_MSG(E_INVALIDARG, WI_IsAnyFlagSet(Flags, ~WSLCDeleteFlagsValid), "Invalid flags: 0x%x", Flags);
2177
@@ -2177,7 +2202,7 @@ CATCH_LOG();
2202
2203 HRESULT WSLCContainer::Export(WSLCHandle TarHandle)
2204 {
2180 - COMServiceExecutionContext context;
2205 + WSLCExecutionContext context(&m_session);
2206
2207 return CallImpl(&WSLCContainerImpl::Export, TarHandle);
2208 }
@@ -2185,7 +2210,7 @@ HRESULT WSLCContainer::Export(WSLCHandle TarHandle)
2210 HRESULT WSLCContainer::Logs(WSLCLogsFlags Flags, WSLCHandle* Stdout, WSLCHandle* Stderr, ULONGLONG Since, ULONGLONG Until, ULONGLONG Tail)
2211 try
2212 {
2188 - COMServiceExecutionContext context;
2213 + WSLCExecutionContext context(&m_session);
2214 RETURN_HR_IF(E_POINTER, Stdout == nullptr || Stderr == nullptr);
2215
2216 THROW_HR_IF_MSG(E_INVALIDARG, WI_IsAnyFlagSet(Flags, ~WSLCLogsFlagsValid), "Invalid flags: 0x%x", Flags);
@@ -2200,7 +2225,7 @@ CATCH_RETURN();
2225 HRESULT WSLCContainer::GetId(WSLCContainerId Id)
2226 try
2227 {
2203 - COMServiceExecutionContext context;
2228 + WSLCExecutionContext context(&m_session);
2229
2230 const auto hr = wil::ResultFromException([&] {
2231 auto [lock, impl] = LockImpl();
@@ -2225,7 +2250,7 @@ CATCH_RETURN();
2250 HRESULT WSLCContainer::GetName(LPSTR* Name)
2251 try
2252 {
2228 - COMServiceExecutionContext context;
2253 + WSLCExecutionContext context(&m_session);
2254
2255 RETURN_HR_IF_NULL(E_POINTER, Name);
2256 *Name = nullptr;
@@ -2288,7 +2313,7 @@ void WSLCContainerImpl::GetLabels(WSLCLabelInformation** Labels, ULONG* Count) c
2313 HRESULT WSLCContainer::GetLabels(WSLCLabelInformation** Labels, ULONG* Count)
2314 try
2315 {
2291 - COMServiceExecutionContext context;
2316 + WSLCExecutionContext context(&m_session);
2317
2318 RETURN_HR_IF(E_POINTER, Labels == nullptr || Count == nullptr);
2319
src/windows/wslcsession/WSLCContainer.h
+3 -2
@@ -220,7 +220,7 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLCContainer
220 {
221
222 public:
223 - WSLCContainer(WSLCContainerImpl* impl, std::function<void(const WSLCContainerImpl*)>&& OnDeleted);
223 + WSLCContainer(WSLCContainerImpl* impl, WSLCSession& session, std::function<void(const WSLCContainerImpl*)>&& OnDeleted);
224
225 IFACEMETHOD(Attach)(_In_opt_ LPCSTR DetachKeys, _Out_ WSLCHandle* Stdin, _Out_ WSLCHandle* Stdout, _Out_ WSLCHandle* Stderr) override;
226 IFACEMETHOD(Stop)(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds) override;
@@ -230,7 +230,7 @@ public:
230 IFACEMETHOD(GetState)(_Out_ WSLCContainerState* State) override;
231 IFACEMETHOD(GetInitProcess)(_Out_ IWSLCProcess** process) override;
232 IFACEMETHOD(Exec)(_In_ const WSLCProcessOptions* Options, _In_opt_ LPCSTR DetachKeys, _Out_ IWSLCProcess** Process) override;
233 - IFACEMETHOD(Start)(WSLCContainerStartFlags Flags, _In_opt_ LPCSTR DetachKeys) override;
233 + IFACEMETHOD(Start)(WSLCContainerStartFlags Flags, _In_opt_ LPCSTR DetachKeys, _In_opt_ IWarningCallback* WarningCallback) override;
234 IFACEMETHOD(Inspect)(_Out_ LPSTR* Output) override;
235 IFACEMETHOD(Logs)(_In_ WSLCLogsFlags Flags, _Out_ WSLCHandle* Stdout, _Out_ WSLCHandle* Stderr, _In_ ULONGLONG Since, _In_ ULONGLONG Until, _In_ ULONGLONG Tail) override;
236 IFACEMETHOD(GetId)(_Out_ WSLCContainerId Id) override;
@@ -245,6 +245,7 @@ public:
245 void CacheState(const std::string& id, const std::string& name, WSLCContainerState state, const Microsoft::WRL::ComPtr<IWSLCProcess>& initProcess) noexcept;
246
247 private:
248 + WSLCSession& m_session;
249 std::function<void(const WSLCContainerImpl*)> m_onDeleted;
250
251 // Cached read-only properties populated by CacheState() so they remain
src/windows/wslcsession/WSLCExecutionContext.h new
+55
@@ -0,0 +1,55 @@
1 +// Copyright (C) Microsoft Corporation. All rights reserved.
2 +
3 +#pragma once
4 +
5 +#include "ExecutionContext.h"
6 +#include "WSLCSession.h"
7 +
8 +namespace wsl::windows::service::wslc {
9 +
10 +// Extends COMServiceExecutionContext with a WSLCSession pointer for lazy COM callback
11 +// registration when warnings are emitted. This enables EMIT_USER_WARNING to stream
12 +// warnings back to the CLI via IWarningCallback, with proper cancellation support
13 +// during session termination via RegisterUserCOMCallback/CoCancelCall.
14 +class WSLCExecutionContext : public wsl::windows::common::COMServiceExecutionContext
15 +{
16 +public:
17 + NON_COPYABLE(WSLCExecutionContext);
18 + NON_MOVABLE(WSLCExecutionContext);
19 +
20 + WSLCExecutionContext(WSLCSession* session, IWarningCallback* warningCallback = nullptr) :
21 + m_session(session), m_warningCallback(warningCallback)
22 + {
23 + }
24 +
25 + ~WSLCExecutionContext() override = default;
26 +
27 +protected:
28 + bool CollectUserWarning(const std::wstring& warning) override
29 + {
30 + if (m_warningCallback != nullptr)
31 + {
32 + std::unique_ptr<UserCOMCallback> comCallback;
33 + if (m_session != nullptr)
34 + {
35 + comCallback = std::make_unique<UserCOMCallback>(m_session->RegisterUserCOMCallback());
36 + }
37 +
38 + auto hr = m_warningCallback->OnWarning(warning.c_str());
39 + if (SUCCEEDED(hr) || hr == RPC_E_CALL_CANCELED || hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))
40 + {
41 + return true;
42 + }
43 +
44 + LOG_HR(hr);
45 + }
46 +
47 + return COMServiceExecutionContext::CollectUserWarning(warning);
48 + }
49 +
50 +private:
51 + WSLCSession* m_session = nullptr;
52 + IWarningCallback* m_warningCallback = nullptr;
53 +};
54 +
55 +} // namespace wsl::windows::service::wslc
src/windows/wslcsession/WSLCSession.cpp
+95 -48
@@ -14,6 +14,7 @@ Abstract:
14
15 #include "precomp.h"
16 #include "WSLCSession.h"
17 +#include "WSLCExecutionContext.h"
18 #include "WSLCContainer.h"
19 #include "WSLCNetworkMetadata.h"
20 #include "ContainerNameGenerator.h"
@@ -26,6 +27,7 @@ using io::MultiHandleWait;
27 using wsl::shared::Localization;
28 using wsl::windows::service::wslc::UserCOMCallback;
29 using wsl::windows::service::wslc::UserHandle;
30 +using wsl::windows::service::wslc::WSLCExecutionContext;
31 using wsl::windows::service::wslc::WSLCSession;
32 using wsl::windows::service::wslc::WSLCVirtualMachine;
33
@@ -257,12 +259,17 @@ try
259 }
260 CATCH_RETURN();
261
260 -HRESULT WSLCSession::Initialize(_In_ const WSLCSessionInitSettings* Settings, _In_ IWSLCVirtualMachine* Vm, _In_ IWSLCPluginNotifier* PluginNotifier)
262 +HRESULT WSLCSession::Initialize(
263 + _In_ const WSLCSessionInitSettings* Settings, _In_ IWSLCVirtualMachine* Vm, _In_ IWSLCPluginNotifier* PluginNotifier, _In_opt_ IWarningCallback* WarningCallback)
264 try
265 {
266 RETURN_HR_IF(E_POINTER, Settings == nullptr || Vm == nullptr);
267 RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED), m_virtualMachine.has_value());
268
269 + // Set up a warning context for the duration of initialization so that non-fatal
270 + // failures (e.g., container/volume/network recovery) are streamed to the CLI.
271 + WSLCExecutionContext warningContext(this, WarningCallback);
272 +
273 // N.B. No locking is required because Initialize() is always called before the session is returned to the caller.
274 m_id = Settings->SessionId;
275 m_displayName = Settings->DisplayName ? Settings->DisplayName : L"";
@@ -419,7 +426,11 @@ void WSLCSession::ConfigureStorage(const WSLCSessionInitSettings& Settings, PSID
426 ServiceProcessLauncher launcher("/bin/sh", {"/bin/sh", "-c", cmd});
427 launcher.Launch(*m_virtualMachine);
428 }
422 - CATCH_LOG()
429 + catch (...)
430 + {
431 + LOG_CAUGHT_EXCEPTION();
432 + EMIT_USER_WARNING(Localization::MessageWslcSwapInitFailed());
433 + }
434 }
435
436 deleteVhdOnFailure.release();
@@ -590,6 +601,7 @@ void WSLCSession::StreamImageOperation(DockerHTTPClient::HTTPRequestContext& req
601 OperationName,
602 reportedError->c_str(),
603 parsed.errorDetail->message.c_str());
604 + EMIT_USER_WARNING(wsl::shared::string::MultiByteToWide(*reportedError));
605 }
606
607 reportedError = parsed.errorDetail->message;
@@ -660,10 +672,10 @@ try
672 }
673 CATCH_LOG()
674
663 -HRESULT WSLCSession::PullImage(LPCSTR Image, LPCSTR RegistryAuthenticationInformation, IProgressCallback* ProgressCallback)
675 +HRESULT WSLCSession::PullImage(LPCSTR Image, LPCSTR RegistryAuthenticationInformation, IProgressCallback* ProgressCallback, IWarningCallback* WarningCallback)
676 try
677 {
666 - COMServiceExecutionContext context;
678 + WSLCExecutionContext context(this, WarningCallback);
679
680 RETURN_HR_IF_NULL(E_POINTER, Image);
681
@@ -697,7 +709,7 @@ CATCH_RETURN();
709 HRESULT WSLCSession::BuildImage(const WSLCBuildImageOptions* Options, IProgressCallback* ProgressCallback, HANDLE CancelEvent)
710 try
711 {
700 - COMServiceExecutionContext context;
712 + WSLCExecutionContext context(this);
713
714 RETURN_HR_IF_NULL(E_POINTER, Options);
715 RETURN_HR_IF_NULL(E_POINTER, Options->ContextPath);
@@ -1030,12 +1042,12 @@ try
1042 }
1043 CATCH_RETURN();
1044
1033 -HRESULT WSLCSession::LoadImage(const WSLCHandle ImageHandle, IProgressCallback* ProgressCallback, ULONGLONG ContentSize)
1045 +HRESULT WSLCSession::LoadImage(const WSLCHandle ImageHandle, IProgressCallback* ProgressCallback, ULONGLONG ContentSize, IWarningCallback* WarningCallback)
1046 try
1047 {
1048 UNREFERENCED_PARAMETER(ProgressCallback);
1049
1038 - COMServiceExecutionContext context;
1050 + WSLCExecutionContext context(this, WarningCallback);
1051
1052 auto lock = m_lock.lock_shared();
1053
@@ -1049,12 +1061,12 @@ try
1061 }
1062 CATCH_RETURN();
1063
1052 -HRESULT WSLCSession::ImportImage(const WSLCHandle ImageHandle, LPCSTR ImageName, IProgressCallback* ProgressCallback, ULONGLONG ContentSize)
1064 +HRESULT WSLCSession::ImportImage(const WSLCHandle ImageHandle, LPCSTR ImageName, IProgressCallback* ProgressCallback, ULONGLONG ContentSize, IWarningCallback* WarningCallback)
1065 try
1066 {
1067 UNREFERENCED_PARAMETER(ProgressCallback);
1068
1057 - COMServiceExecutionContext context;
1069 + WSLCExecutionContext context(this, WarningCallback);
1070
1071 RETURN_HR_IF_NULL(E_POINTER, ImageName);
1072 RETURN_HR_IF(E_INVALIDARG, strlen(ImageName) > WSLC_MAX_IMAGE_NAME_LENGTH);
@@ -1122,6 +1134,7 @@ void WSLCSession::ImportImageImpl(DockerHTTPClient::HTTPRequestContext& Request,
1134 "Overriding previous error message '%hs' with new message '%hs'",
1135 errorMessage->c_str(),
1136 parsed.errorDetail->message.c_str());
1137 + EMIT_USER_WARNING(wsl::shared::string::MultiByteToWide(*errorMessage));
1138 }
1139
1140 errorMessage = std::move(parsed.errorDetail->message);
@@ -1130,9 +1143,14 @@ void WSLCSession::ImportImageImpl(DockerHTTPClient::HTTPRequestContext& Request,
1143 {
1144 WSL_LOG("ImageImportProgress", TraceLoggingValue(parsed.stream->c_str(), "Content"));
1145 }
1146 + else if (parsed.status.has_value())
1147 + {
1148 + WSL_LOG("ImageImportProgress", TraceLoggingValue(parsed.status->c_str(), "Status"));
1149 + }
1150 else
1151 {
1152 LOG_HR_MSG(E_UNEXPECTED, "Failed to parse import progress: %.*hs", static_cast<int>(buffer.size()), buffer.data());
1153 + EMIT_USER_WARNING(Localization::MessageWslcImportProgressParseFailed());
1154 }
1155 };
1156
@@ -1168,7 +1186,7 @@ try
1186 {
1187 UNREFERENCED_PARAMETER(ProgressCallback);
1188
1171 - COMServiceExecutionContext context;
1189 + WSLCExecutionContext context(this);
1190
1191 RETURN_HR_IF_NULL(E_POINTER, ImageNameOrID);
1192 RETURN_HR_IF(E_INVALIDARG, strlen(ImageNameOrID) > WSLC_MAX_IMAGE_NAME_LENGTH);
@@ -1224,7 +1242,7 @@ void WSLCSession::SaveImageImpl(std::pair<uint32_t, wil::unique_socket>& SocketC
1242 HRESULT WSLCSession::ListImages(const WSLCListImagesOptions* Options, WSLCImageInformation** Images, ULONG* Count)
1243 try
1244 {
1227 - COMServiceExecutionContext context;
1245 + WSLCExecutionContext context(this);
1246
1247 RETURN_HR_IF_NULL(E_POINTER, Images);
1248 RETURN_HR_IF_NULL(E_POINTER, Count);
@@ -1343,7 +1361,7 @@ CATCH_RETURN();
1361 HRESULT WSLCSession::DeleteImage(const WSLCDeleteImageOptions* Options, WSLCDeletedImageInformation** DeletedImages, ULONG* Count)
1362 try
1363 {
1346 - COMServiceExecutionContext context;
1364 + WSLCExecutionContext context(this);
1365
1366 RETURN_HR_IF_NULL(E_POINTER, Options);
1367 RETURN_HR_IF_NULL(E_POINTER, Options->Image);
@@ -1424,7 +1442,7 @@ CATCH_RETURN();
1442 HRESULT WSLCSession::TagImage(const WSLCTagImageOptions* Options)
1443 try
1444 {
1427 - COMServiceExecutionContext context;
1445 + WSLCExecutionContext context(this);
1446
1447 RETURN_HR_IF_NULL(E_POINTER, Options);
1448 RETURN_HR_IF_NULL(E_POINTER, Options->Image);
@@ -1459,10 +1477,10 @@ try
1477 }
1478 CATCH_RETURN();
1479
1462 -HRESULT WSLCSession::PushImage(LPCSTR Image, LPCSTR RegistryAuthenticationInformation, IProgressCallback* ProgressCallback)
1480 +HRESULT WSLCSession::PushImage(LPCSTR Image, LPCSTR RegistryAuthenticationInformation, IProgressCallback* ProgressCallback, IWarningCallback* WarningCallback)
1481 try
1482 {
1465 - COMServiceExecutionContext context;
1483 + WSLCExecutionContext context(this, WarningCallback);
1484
1485 RETURN_HR_IF_NULL(E_POINTER, Image);
1486 RETURN_HR_IF_NULL(E_POINTER, RegistryAuthenticationInformation);
@@ -1483,7 +1501,7 @@ CATCH_RETURN();
1501 HRESULT WSLCSession::InspectImage(_In_ LPCSTR ImageNameOrId, _Out_ LPSTR* Output)
1502 try
1503 {
1486 - COMServiceExecutionContext context;
1504 + WSLCExecutionContext context(this);
1505
1506 RETURN_HR_IF_NULL(E_POINTER, ImageNameOrId);
1507 RETURN_HR_IF(E_INVALIDARG, strlen(ImageNameOrId) > WSLC_MAX_IMAGE_NAME_LENGTH);
@@ -1530,7 +1548,7 @@ std::string WSLCSession::InspectImageLockHeld(const std::string& NameOrId)
1548 HRESULT WSLCSession::Authenticate(_In_ LPCSTR ServerAddress, _In_ LPCSTR Username, _In_ LPCSTR Password, _Out_ LPSTR* IdentityToken)
1549 try
1550 {
1533 - COMServiceExecutionContext context;
1551 + WSLCExecutionContext context(this);
1552
1553 RETURN_HR_IF_NULL(E_POINTER, ServerAddress);
1554 RETURN_HR_IF_NULL(E_POINTER, Username);
@@ -1560,7 +1578,7 @@ HRESULT WSLCSession::PruneImages(
1578 const WSLCFilter* Filters, ULONG FiltersCount, WSLCDeletedImageInformation** DeletedImages, ULONG* DeletedImagesCount, ULONGLONG* SpaceReclaimed)
1579 try
1580 {
1563 - COMServiceExecutionContext context;
1581 + WSLCExecutionContext context(this);
1582
1583 RETURN_HR_IF_NULL(E_POINTER, DeletedImages);
1584 RETURN_HR_IF_NULL(E_POINTER, DeletedImagesCount);
@@ -1614,10 +1632,10 @@ try
1632 }
1633 CATCH_RETURN();
1634
1617 -HRESULT WSLCSession::CreateContainer(const WSLCContainerOptions* containerOptions, IWSLCContainer** Container)
1635 +HRESULT WSLCSession::CreateContainer(const WSLCContainerOptions* containerOptions, IWarningCallback* WarningCallback, IWSLCContainer** Container)
1636 try
1637 {
1620 - COMServiceExecutionContext context;
1638 + WSLCExecutionContext context(this, WarningCallback);
1639
1640 RETURN_HR_IF_NULL(E_POINTER, containerOptions);
1641
@@ -1711,7 +1729,7 @@ CATCH_RETURN();
1729 HRESULT WSLCSession::OpenContainer(LPCSTR Id, IWSLCContainer** Container)
1730 try
1731 {
1714 - COMServiceExecutionContext context;
1732 + WSLCExecutionContext context(this);
1733
1734 ValidateName(Id, WSLC_MAX_CONTAINER_NAME_LENGTH);
1735
@@ -1759,7 +1777,7 @@ HRESULT WSLCSession::ListContainers(
1777 const WSLCListContainersOptions* Options, WSLCContainerEntry** Containers, ULONG* Count, WSLCContainerPortMapping** Ports, ULONG* PortsCount)
1778 try
1779 {
1762 - COMServiceExecutionContext context;
1780 + WSLCExecutionContext context(this);
1781
1782 RETURN_HR_IF_NULL(E_POINTER, Containers);
1783 RETURN_HR_IF_NULL(E_POINTER, Count);
@@ -1861,7 +1879,7 @@ CATCH_RETURN();
1879 HRESULT WSLCSession::PruneContainers(_In_opt_ const WSLCFilter* Filters, _In_ ULONG FiltersCount, _Out_ WSLCPruneContainersResults* Result)
1880 try
1881 {
1864 - COMServiceExecutionContext context;
1882 + WSLCExecutionContext context(this);
1883
1884 RETURN_HR_IF_NULL(E_POINTER, Result);
1885 ZeroMemory(Result, sizeof(*Result));
@@ -1926,7 +1944,7 @@ CATCH_RETURN();
1944 HRESULT WSLCSession::CreateRootNamespaceProcess(LPCSTR Executable, const WSLCProcessOptions* Options, IWSLCProcess** Process, int* Errno)
1945 try
1946 {
1929 - COMServiceExecutionContext context;
1947 + WSLCExecutionContext context(this);
1948
1949 if (Errno != nullptr)
1950 {
@@ -1955,7 +1973,7 @@ void WSLCSession::Ext4Format(const std::string& Device)
1973 HRESULT WSLCSession::FormatVirtualDisk(LPCWSTR Path)
1974 try
1975 {
1958 - COMServiceExecutionContext context;
1976 + WSLCExecutionContext context(this);
1977
1978 THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessagePathNotAbsolute(Path), !std::filesystem::path(Path).is_absolute());
1979
@@ -1978,7 +1996,7 @@ CATCH_RETURN();
1996 HRESULT WSLCSession::CreateVolume(const WSLCVolumeOptions* Options, WSLCVolumeInformation* VolumeInfo)
1997 try
1998 {
1981 - COMServiceExecutionContext context;
1999 + WSLCExecutionContext context(this);
2000
2001 RETURN_HR_IF_NULL(E_POINTER, Options);
2002 RETURN_HR_IF_NULL(E_POINTER, VolumeInfo);
@@ -2003,7 +2021,7 @@ CATCH_RETURN();
2021 HRESULT WSLCSession::DeleteVolume(LPCSTR Name)
2022 try
2023 {
2006 - COMServiceExecutionContext context;
2024 + WSLCExecutionContext context(this);
2025
2026 RETURN_HR_IF_NULL(E_POINTER, Name);
2027
@@ -2018,7 +2036,7 @@ CATCH_RETURN();
2036 HRESULT WSLCSession::ListVolumes(const WSLCFilter* Filters, ULONG FiltersCount, WSLCVolumeInformation** Volumes, ULONG* Count)
2037 try
2038 {
2021 - COMServiceExecutionContext context;
2039 + WSLCExecutionContext context(this);
2040
2041 RETURN_HR_IF_NULL(E_POINTER, Volumes);
2042 RETURN_HR_IF_NULL(E_POINTER, Count);
@@ -2050,7 +2068,7 @@ CATCH_RETURN();
2068 HRESULT WSLCSession::InspectVolume(LPCSTR Name, LPSTR* Output)
2069 try
2070 {
2053 - COMServiceExecutionContext context;
2071 + WSLCExecutionContext context(this);
2072
2073 RETURN_HR_IF_NULL(E_POINTER, Name);
2074 RETURN_HR_IF_NULL(E_POINTER, Output);
@@ -2070,10 +2088,11 @@ try
2088 }
2089 CATCH_RETURN();
2090
2073 -HRESULT WSLCSession::PruneVolumes(const WSLCFilter* Filters, ULONG FiltersCount, WSLCVolumeName** Volumes, ULONG* VolumesCount, ULONGLONG* SpaceReclaimed)
2091 +HRESULT WSLCSession::PruneVolumes(
2092 + const WSLCFilter* Filters, ULONG FiltersCount, IWarningCallback* WarningCallback, WSLCVolumeName** Volumes, ULONG* VolumesCount, ULONGLONG* SpaceReclaimed)
2093 try
2094 {
2076 - COMServiceExecutionContext context;
2095 + WSLCExecutionContext context(this, WarningCallback);
2096
2097 RETURN_HR_IF_NULL(E_POINTER, Volumes);
2098 RETURN_HR_IF_NULL(E_POINTER, VolumesCount);
@@ -2144,10 +2163,10 @@ int WSLCSession::StopProcess(ServiceRunningProcess& Process, DWORD TerminateTime
2163 }
2164 // Network management.
2165
2147 -HRESULT WSLCSession::CreateNetwork(const WSLCNetworkOptions* Options)
2166 +HRESULT WSLCSession::CreateNetwork(const WSLCNetworkOptions* Options, IWarningCallback* WarningCallback)
2167 try
2168 {
2150 - COMServiceExecutionContext context;
2169 + WSLCExecutionContext context(this, WarningCallback);
2170
2171 RETURN_HR_IF_NULL(E_POINTER, Options);
2172 RETURN_HR_IF_NULL(E_POINTER, Options->Name);
@@ -2198,9 +2217,10 @@ try
2217 ipam.Config.emplace().push_back(std::move(ipamConfig));
2218 }
2219
2220 + docker_schema::CreateNetworkResponse createResult;
2221 try
2222 {
2203 - m_dockerClient->CreateNetwork(request);
2223 + createResult = m_dockerClient->CreateNetwork(request);
2224 }
2225 catch (const DockerHTTPException& e)
2226 {
@@ -2209,6 +2229,11 @@ try
2229 THROW_DOCKER_USER_ERROR_MSG(e, "Failed to create network '%hs'", name.c_str());
2230 }
2231
2232 + if (!createResult.Warning.empty())
2233 + {
2234 + EMIT_USER_WARNING(wsl::shared::string::MultiByteToWide(createResult.Warning));
2235 + }
2236 +
2237 auto removeNetworkCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [this, &name]() { m_dockerClient->RemoveNetwork(name); });
2238
2239 // Inspect the newly created network to cache full properties (IPAM, Scope, etc.)
@@ -2253,7 +2278,7 @@ CATCH_RETURN();
2278 HRESULT WSLCSession::DeleteNetwork(LPCSTR Name)
2279 try
2280 {
2256 - COMServiceExecutionContext context;
2281 + WSLCExecutionContext context(this);
2282
2283 RETURN_HR_IF_NULL(E_POINTER, Name);
2284 std::string name = Name;
@@ -2291,7 +2316,7 @@ CATCH_RETURN();
2316 HRESULT WSLCSession::ListNetworks(WSLCNetworkInformation** Networks, ULONG* Count)
2317 try
2318 {
2294 - COMServiceExecutionContext context;
2319 + WSLCExecutionContext context(this);
2320
2321 RETURN_HR_IF_NULL(E_POINTER, Networks);
2322 RETURN_HR_IF_NULL(E_POINTER, Count);
@@ -2328,7 +2353,7 @@ CATCH_RETURN();
2353 HRESULT WSLCSession::InspectNetwork(LPCSTR Name, LPSTR* Output)
2354 try
2355 {
2331 - COMServiceExecutionContext context;
2356 + WSLCExecutionContext context(this);
2357
2358 RETURN_HR_IF_NULL(E_POINTER, Name);
2359 RETURN_HR_IF_NULL(E_POINTER, Output);
@@ -2528,7 +2553,7 @@ CATCH_RETURN();
2553 HRESULT WSLCSession::MountWindowsFolder(LPCWSTR WindowsPath, LPCSTR LinuxPath, BOOL ReadOnly)
2554 try
2555 {
2531 - COMServiceExecutionContext context;
2556 + WSLCExecutionContext context(this);
2557
2558 auto lock = m_lock.lock_shared();
2559 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
@@ -2540,7 +2565,7 @@ CATCH_RETURN();
2565 HRESULT WSLCSession::UnmountWindowsFolder(LPCSTR LinuxPath)
2566 try
2567 {
2543 - COMServiceExecutionContext context;
2568 + WSLCExecutionContext context(this);
2569
2570 auto lock = m_lock.lock_shared();
2571 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
@@ -2552,7 +2577,7 @@ CATCH_RETURN();
2577 HRESULT WSLCSession::MapVmPort(int Family, unsigned short WindowsPort, unsigned short LinuxPort)
2578 try
2579 {
2555 - COMServiceExecutionContext context;
2580 + WSLCExecutionContext context(this);
2581
2582 auto lock = m_lock.lock_shared();
2583 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
@@ -2598,7 +2623,7 @@ CATCH_RETURN();
2623 HRESULT WSLCSession::UnmapVmPort(int Family, unsigned short WindowsPort, unsigned short LinuxPort)
2624 try
2625 {
2601 - COMServiceExecutionContext context;
2626 + WSLCExecutionContext context(this);
2627
2628 auto lock = m_lock.lock_shared();
2629 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
@@ -2705,8 +2730,18 @@ UserCOMCallback WSLCSession::RegisterUserCOMCallback()
2730
2731 THROW_IF_FAILED(CoEnableCallCancellation(nullptr));
2732
2708 - auto [_, inserted] = m_userCOMCallbackThreads.insert(GetCurrentThreadId());
2709 - WI_VERIFY(inserted);
2733 + auto threadId = GetCurrentThreadId();
2734 + auto it = m_userCOMCallbackThreads.find(threadId);
2735 + WI_VERIFY(it == m_userCOMCallbackThreads.end() || it->second > 0);
2736 +
2737 + if (it == m_userCOMCallbackThreads.end())
2738 + {
2739 + m_userCOMCallbackThreads.insert({threadId, 1});
2740 + }
2741 + else
2742 + {
2743 + it->second++;
2744 + }
2745
2746 return UserCOMCallback{*this};
2747 }
@@ -2716,14 +2751,21 @@ void WSLCSession::UnregisterUserCOMCallback(DWORD ThreadId)
2751 std::lock_guard lock(m_userCOMCallbacksLock);
2752
2753 auto it = m_userCOMCallbackThreads.find(ThreadId);
2719 - WI_VERIFY(it != m_userCOMCallbackThreads.end());
2754 + WI_VERIFY(it != m_userCOMCallbackThreads.end() && it->second > 0);
2755
2721 - m_userCOMCallbackThreads.erase(it);
2756 + if (it->second > 1)
2757 + {
2758 + it->second--;
2759 + }
2760 + else
2761 + {
2762 + m_userCOMCallbackThreads.erase(it);
2763 + }
2764 }
2765
2766 void WSLCSession::CancelUserCOMCallbacks()
2767 {
2726 - for (auto threadId : m_userCOMCallbackThreads)
2768 + for (auto threadId : std::views::keys(m_userCOMCallbackThreads))
2769 {
2770 LOG_IF_FAILED(CoCancelCall(threadId, 0));
2771 }
@@ -2771,8 +2813,9 @@ void WSLCSession::RecoverExistingContainers()
2813 }
2814 catch (...)
2815 {
2774 - // Log but don't fail the session startup if a single container fails to recover.
2816 LOG_CAUGHT_EXCEPTION_MSG("Failed to recover container: %hs", dockerContainer.Id.c_str());
2817 + EMIT_USER_WARNING(
2818 + Localization::MessageWslcFailedToRecoverContainer(wsl::shared::string::MultiByteToWide(dockerContainer.Id)));
2819 }
2820 }
2821
@@ -2821,7 +2864,11 @@ void WSLCSession::RecoverExistingNetworks()
2864 auto [_, inserted] = m_networks.insert({network.Name, std::move(entry)});
2865 WI_VERIFY(inserted);
2866 }
2824 - CATCH_LOG_MSG("Failed to recover network: %hs", network.Name.c_str());
2867 + catch (...)
2868 + {
2869 + LOG_CAUGHT_EXCEPTION_MSG("Failed to recover network: %hs", network.Name.c_str());
2870 + EMIT_USER_WARNING(Localization::MessageWslcFailedToRecoverNetwork(wsl::shared::string::MultiByteToWide(network.Name)));
2871 + }
2872 }
2873
2874 WSL_LOG(
src/windows/wslcsession/WSLCSession.h
+26 -8
@@ -85,21 +85,38 @@ public:
85
86 // IWSLCSession - initialization methods
87 IFACEMETHOD(GetProcessHandle)(_Out_ HANDLE* ProcessHandle) override;
88 - IFACEMETHOD(Initialize)(_In_ const WSLCSessionInitSettings* Settings, _In_ IWSLCVirtualMachine* Vm, _In_ IWSLCPluginNotifier* PluginNotifier) override;
88 + IFACEMETHOD(Initialize)(
89 + _In_ const WSLCSessionInitSettings* Settings,
90 + _In_ IWSLCVirtualMachine* Vm,
91 + _In_ IWSLCPluginNotifier* PluginNotifier,
92 + _In_opt_ IWarningCallback* WarningCallback) override;
93
94 IFACEMETHOD(GetId)(_Out_ ULONG* Id) override;
95 IFACEMETHOD(GetState)(_Out_ WSLCSessionState* State) override;
96
97 // Image management.
94 - IFACEMETHOD(PullImage)(_In_ LPCSTR Image, _In_opt_ LPCSTR RegistryAuthenticationInformation, _In_opt_ IProgressCallback* ProgressCallback) override;
98 + IFACEMETHOD(PullImage)(
99 + _In_ LPCSTR Image,
100 + _In_opt_ LPCSTR RegistryAuthenticationInformation,
101 + _In_opt_ IProgressCallback* ProgressCallback,
102 + _In_opt_ IWarningCallback* WarningCallback) override;
103 IFACEMETHOD(BuildImage)(_In_ const WSLCBuildImageOptions* Options, _In_opt_ IProgressCallback* ProgressCallback, _In_opt_ HANDLE CancelEvent) override;
96 - IFACEMETHOD(LoadImage)(_In_ const WSLCHandle ImageHandle, _In_ IProgressCallback* ProgressCallback, _In_ ULONGLONG ContentLength) override;
97 - IFACEMETHOD(ImportImage)(_In_ const WSLCHandle ImageHandle, _In_ LPCSTR ImageName, _In_ IProgressCallback* ProgressCallback, _In_ ULONGLONG ContentLength) override;
104 + IFACEMETHOD(LoadImage)(_In_ const WSLCHandle ImageHandle, _In_ IProgressCallback* ProgressCallback, _In_ ULONGLONG ContentLength, _In_opt_ IWarningCallback* WarningCallback) override;
105 + IFACEMETHOD(ImportImage)(
106 + _In_ const WSLCHandle ImageHandle,
107 + _In_ LPCSTR ImageName,
108 + _In_ IProgressCallback* ProgressCallback,
109 + _In_ ULONGLONG ContentLength,
110 + _In_opt_ IWarningCallback* WarningCallback) override;
111 IFACEMETHOD(SaveImage)(_In_ WSLCHandle OutputHandle, _In_ LPCSTR ImageNameOrID, _In_ IProgressCallback* ProgressCallback, _In_opt_ HANDLE CancelEvent) override;
112 IFACEMETHOD(ListImages)(_In_opt_ const WSLCListImagesOptions* Options, _Out_ WSLCImageInformation** Images, _Out_ ULONG* Count) override;
113 IFACEMETHOD(DeleteImage)(_In_ const WSLCDeleteImageOptions* Options, _Out_ WSLCDeletedImageInformation** DeletedImages, _Out_ ULONG* Count) override;
114 IFACEMETHOD(TagImage)(_In_ const WSLCTagImageOptions* Options) override;
102 - IFACEMETHOD(PushImage)(_In_ LPCSTR Image, _In_ LPCSTR RegistryAuthenticationInformation, _In_opt_ IProgressCallback* ProgressCallback) override;
115 + IFACEMETHOD(PushImage)(
116 + _In_ LPCSTR Image,
117 + _In_ LPCSTR RegistryAuthenticationInformation,
118 + _In_opt_ IProgressCallback* ProgressCallback,
119 + _In_opt_ IWarningCallback* WarningCallback) override;
120 IFACEMETHOD(InspectImage)(_In_ LPCSTR ImageNameOrId, _Out_ LPSTR* Output) override;
121 IFACEMETHOD(Authenticate)(_In_ LPCSTR ServerAddress, _In_ LPCSTR Username, _In_ LPCSTR Password, _Out_ LPSTR* IdentityToken) override;
122 IFACEMETHOD(PruneImages)(
@@ -110,7 +127,7 @@ public:
127 _Out_ ULONGLONG* SpaceReclaimed) override;
128
129 // Container management.
113 - IFACEMETHOD(CreateContainer)(_In_ const WSLCContainerOptions* Options, _Out_ IWSLCContainer** Container) override;
130 + IFACEMETHOD(CreateContainer)(_In_ const WSLCContainerOptions* Options, _In_opt_ IWarningCallback* WarningCallback, _Out_ IWSLCContainer** Container) override;
131 IFACEMETHOD(OpenContainer)(_In_ LPCSTR Id, _In_ IWSLCContainer** Container) override;
132 IFACEMETHOD(ListContainers)(
133 _In_opt_ const WSLCListContainersOptions* Options,
@@ -137,12 +154,13 @@ public:
154 IFACEMETHOD(PruneVolumes)
155 (_In_reads_opt_(FiltersCount) const WSLCFilter* Filters,
156 _In_ ULONG FiltersCount,
157 + _In_opt_ IWarningCallback* WarningCallback,
158 _Out_ WSLCVolumeName** Volumes,
159 _Out_ ULONG* VolumesCount,
160 _Out_ ULONGLONG* SpaceReclaimed) override;
161
162 // Network management.
145 - IFACEMETHOD(CreateNetwork)(_In_ const WSLCNetworkOptions* Options) override;
163 + IFACEMETHOD(CreateNetwork)(_In_ const WSLCNetworkOptions* Options, _In_opt_ IWarningCallback* WarningCallback) override;
164 IFACEMETHOD(DeleteNetwork)(_In_ LPCSTR Name) override;
165 IFACEMETHOD(ListNetworks)(_Out_ WSLCNetworkInformation** Networks, _Out_ ULONG* Count) override;
166 IFACEMETHOD(InspectNetwork)(_In_ LPCSTR Name, _Out_ LPSTR* Output) override;
@@ -247,7 +265,7 @@ private:
265
266 // Threads currently inside an outgoing COM callback (e.g. IProgressCallback::OnProgress).
267 std::recursive_mutex m_userCOMCallbacksLock;
250 - __guarded_by(m_userCOMCallbacksLock) std::set<DWORD> m_userCOMCallbackThreads;
268 + __guarded_by(m_userCOMCallbacksLock) std::map<DWORD, int> m_userCOMCallbackThreads;
269
270 // Used for testing only.
271 std::mutex m_allocatedPortsLock;
src/windows/wslcsession/WSLCSessionFactory.cpp
+2 -1
@@ -33,6 +33,7 @@ HRESULT wslc::WSLCSessionFactory::CreateSession(
33 _In_ const WSLCSessionInitSettings* Settings,
34 _In_ IWSLCVirtualMachine* Vm,
35 _In_ IWSLCPluginNotifier* PluginNotifier,
36 + _In_opt_ IWarningCallback* WarningCallback,
37 _Out_ IWSLCSession** Session,
38 _Out_ IWSLCSessionReference** ServiceRef)
39 try
@@ -48,7 +49,7 @@ try
49 session->SetDestructionCallback(std::move(m_destructionCallback));
50
51 // Initialize the session with the VM.
51 - RETURN_IF_FAILED(session->Initialize(Settings, Vm, PluginNotifier));
52 + RETURN_IF_FAILED(session->Initialize(Settings, Vm, PluginNotifier, WarningCallback));
53
54 // Create the service session ref. It extracts metadata and a weak reference from the session.
55 auto serviceRef = Microsoft::WRL::Make<wslc::WSLCSessionReference>(session.Get());
src/windows/wslcsession/WSLCSessionFactory.h
+1
@@ -47,6 +47,7 @@ public:
47 (_In_ const WSLCSessionInitSettings* Settings,
48 _In_ IWSLCVirtualMachine* Vm,
49 _In_ IWSLCPluginNotifier* PluginNotifier,
50 + _In_opt_ IWarningCallback* WarningCallback,
51 _Out_ IWSLCSession** Session,
52 _Out_ IWSLCSessionReference** ServiceRef) override;
53
src/windows/wslcsession/WSLCVolumes.cpp
+11 -2
@@ -41,7 +41,12 @@ WSLCVolumes::WSLCVolumes(
41 {
42 OpenVolumeExclusiveLockHeld(volume);
43 }
44 - CATCH_LOG_MSG("Failed to recover volume: %hs", volume.Name.c_str());
44 + catch (...)
45 + {
46 + LOG_CAUGHT_EXCEPTION_MSG("Failed to recover volume: %hs", volume.Name.c_str());
47 + EMIT_USER_WARNING(
48 + wsl::shared::Localization::MessageWslcFailedToRecoverVolume(wsl::shared::string::MultiByteToWide(volume.Name)));
49 + }
50 }
51 }
52
@@ -240,7 +245,11 @@ WSLCVolumes::PruneVolumesResult WSLCVolumes::PruneVolumes(const std::map<std::st
245 {
246 it->second->OnDeleted();
247 }
243 - CATCH_LOG_MSG("Failed to release host resources for pruned volume: %hs", name.c_str());
248 + catch (...)
249 + {
250 + LOG_CAUGHT_EXCEPTION_MSG("Failed to release host resources for pruned volume: %hs", name.c_str());
251 + EMIT_USER_WARNING(wsl::shared::Localization::MessageWslcVolumeReleaseFailed(wsl::shared::string::MultiByteToWide(name)));
252 + }
253
254 m_volumes.erase(it);
255 m_expectedEvents.emplace_back(name, VolumeEvent::Destroy);
test/windows/Common.cpp
+1 -1
@@ -2914,7 +2914,7 @@ void LoadTestImage(IWSLCSession& session, std::string_view imageName)
2914 LARGE_INTEGER fileSize{};
2915 THROW_LAST_ERROR_IF(!GetFileSizeEx(imageFile.get(), &fileSize));
2916
2917 - THROW_IF_FAILED(session.LoadImage(wsl::windows::common::wslutil::ToCOMInputHandle(imageFile.get()), nullptr, fileSize.QuadPart));
2917 + THROW_IF_FAILED(session.LoadImage(wsl::windows::common::wslutil::ToCOMInputHandle(imageFile.get()), nullptr, fileSize.QuadPart, nullptr));
2918 }
2919
2920 void ExpectHttpResponse(LPCWSTR Url, std::optional<int> expectedCode, bool retry)
test/windows/PluginTests.cpp
+4 -4
@@ -617,7 +617,7 @@ class PluginTests
617
618 auto manager = OpenWslcSessionManager();
619 wil::com_ptr<IWSLCSession> session;
620 - VERIFY_SUCCEEDED(manager->CreateSession(&settings, WSLCSessionFlagsNone, &session));
620 + VERIFY_SUCCEEDED(manager->CreateSession(&settings, WSLCSessionFlagsNone, nullptr, &session));
621 wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
622
623 WSLCSessionState state{};
@@ -704,7 +704,7 @@ class PluginTests
704 VERIFY_SUCCEEDED(session->TagImage(&tagOptions));
705
706 auto emptyAuth = wsl::windows::common::wslutil::BuildRegistryAuthHeader("", "");
707 - VERIFY_SUCCEEDED(session->PushImage(registryImage.c_str(), emptyAuth.c_str(), nullptr));
707 + VERIFY_SUCCEEDED(session->PushImage(registryImage.c_str(), emptyAuth.c_str(), nullptr, nullptr));
708
709 // Delete the local tagged copy so PullImage actually downloads it.
710 WSLCDeleteImageOptions deleteOpts{.Image = registryImage.c_str(), .Flags = WSLCDeleteImageFlagsNone};
@@ -712,7 +712,7 @@ class PluginTests
712 VERIFY_SUCCEEDED(session->DeleteImage(&deleteOpts, deletedImages.addressof(), deletedImages.size_address<ULONG>()));
713
714 // Pull the image back — this should trigger the ImageCreated plugin callback.
715 - VERIFY_SUCCEEDED(session->PullImage(registryImage.c_str(), nullptr, nullptr));
715 + VERIFY_SUCCEEDED(session->PullImage(registryImage.c_str(), nullptr, nullptr, nullptr));
716 }
717
718 constexpr auto ExpectedOutput =
@@ -739,7 +739,7 @@ class PluginTests
739
740 auto manager = OpenWslcSessionManager();
741 wil::com_ptr<IWSLCSession> session;
742 - const auto hr = manager->CreateSession(&settings, WSLCSessionFlagsNone, &session);
742 + const auto hr = manager->CreateSession(&settings, WSLCSessionFlagsNone, nullptr, &session);
743 ValidateCOMErrorMessageContains(L"A fatal error was returned by plugin 'TestPlugin'");
744 VERIFY_ARE_EQUAL(hr, HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED));
745
test/windows/WSLCTests.cpp
+255 -102
@@ -153,7 +153,7 @@ class WSLCTests
153
154 wil::com_ptr<IWSLCSession> session;
155
156 - VERIFY_SUCCEEDED(sessionManager->CreateSession(&sessionSettings, Flags, &session));
156 + VERIFY_SUCCEEDED(sessionManager->CreateSession(&sessionSettings, Flags, nullptr, &session));
157 wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
158
159 WSLCSessionState state{};
@@ -214,7 +214,7 @@ class WSLCTests
214 auto cleanup = wil::scope_exit_log(
215 WI_DIAGNOSTICS_INFO, [&]() { LOG_IF_FAILED(DeleteImageNoThrow(registryImage, WSLCDeleteImageFlagsNone).first); });
216
217 - VERIFY_SUCCEEDED(m_defaultSession->PushImage(registryImage.c_str(), registryAuth.c_str(), nullptr));
217 + VERIFY_SUCCEEDED(m_defaultSession->PushImage(registryImage.c_str(), registryAuth.c_str(), nullptr, nullptr));
218
219 return registryImage;
220 }
@@ -416,7 +416,7 @@ class WSLCTests
416 {
417 auto settings = GetDefaultSessionSettings(nullptr);
418 wil::com_ptr<IWSLCSession> session;
419 - VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, &session), WSLC_E_INVALID_SESSION_NAME);
419 + VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, nullptr, &session), WSLC_E_INVALID_SESSION_NAME);
420 }
421
422 // Reject DisplayName at exact boundary (no room for null terminator).
@@ -424,7 +424,7 @@ class WSLCTests
424 std::wstring boundaryName(std::size(WSLCSessionListEntry{}.DisplayName), L'x');
425 auto settings = GetDefaultSessionSettings(boundaryName.c_str());
426 wil::com_ptr<IWSLCSession> session;
427 - VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, &session), WSLC_E_INVALID_SESSION_NAME);
427 + VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, nullptr, &session), WSLC_E_INVALID_SESSION_NAME);
428 }
429
430 // Reject too long DisplayName.
@@ -432,7 +432,7 @@ class WSLCTests
432 std::wstring longName(std::size(WSLCSessionListEntry{}.DisplayName) + 1, L'x');
433 auto settings = GetDefaultSessionSettings(longName.c_str());
434 wil::com_ptr<IWSLCSession> session;
435 - VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, &session), WSLC_E_INVALID_SESSION_NAME);
435 + VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, nullptr, &session), WSLC_E_INVALID_SESSION_NAME);
436 }
437
438 // Validate that creating a session on a non-existing storage fails if WSLCSessionStorageFlagsNoCreate is set.
@@ -441,7 +441,7 @@ class WSLCTests
441 settings.StoragePath = L"C:\\does-not-exist";
442 settings.StorageFlags = WSLCSessionStorageFlagsNoCreate;
443 wil::com_ptr<IWSLCSession> session;
444 - VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, &session), HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND));
444 + VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, nullptr, &session), HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND));
445 }
446
447 // Reject invalid storage flags.
@@ -449,7 +449,7 @@ class WSLCTests
449 auto settings = GetDefaultSessionSettings(L"invalid-storage-flags");
450 settings.StorageFlags = static_cast<WSLCSessionStorageFlags>(0x2);
451 wil::com_ptr<IWSLCSession> session;
452 - VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, &session), E_INVALIDARG);
452 + VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, nullptr, &session), E_INVALIDARG);
453 }
454 }
455
@@ -592,7 +592,7 @@ class WSLCTests
592 auto image = PushImageToRegistry("hello-world:latest", registryAddress, BuildRegistryAuthHeader("", ""));
593 ExpectImagePresent(*m_defaultSession, image.c_str(), false);
594
595 - VERIFY_SUCCEEDED(m_defaultSession->PullImage(image.c_str(), nullptr, nullptr));
595 + VERIFY_SUCCEEDED(m_defaultSession->PullImage(image.c_str(), nullptr, nullptr, nullptr));
596 auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(DeleteImageNoThrow(image, WSLCDeleteImageFlagsForce).first); });
597
598 // Verify that the image is in the list of images.
@@ -611,7 +611,7 @@ class WSLCTests
611 L"pull access denied for does-not, repository does not exist or may require 'docker login': denied: requested "
612 L"access to the resource is denied";
613
614 - VERIFY_ARE_EQUAL(m_defaultSession->PullImage("does-not:exist", nullptr, nullptr), WSLC_E_IMAGE_NOT_FOUND);
614 + VERIFY_ARE_EQUAL(m_defaultSession->PullImage("does-not:exist", nullptr, nullptr, nullptr), WSLC_E_IMAGE_NOT_FOUND);
615 ValidateCOMErrorMessage(expectedError.c_str());
616 }
617
@@ -623,7 +623,7 @@ class WSLCTests
623 ResetTestSession(); // Reopen the test session since the session was terminated.
624 });
625
626 - VERIFY_ARE_EQUAL(m_defaultSession->PullImage("hello-world:linux", nullptr, nullptr), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
626 + VERIFY_ARE_EQUAL(m_defaultSession->PullImage("hello-world:linux", nullptr, nullptr, nullptr), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
627 }
628 }
629
@@ -638,7 +638,7 @@ class WSLCTests
638 auto registryImage = PushImageToRegistry(sourceImage, registryAddress, auth);
639 ExpectImagePresent(*m_defaultSession, registryImage.c_str(), false);
640
641 - VERIFY_SUCCEEDED(m_defaultSession->PullImage(registryImage.c_str(), nullptr, nullptr));
641 + VERIFY_SUCCEEDED(m_defaultSession->PullImage(registryImage.c_str(), nullptr, nullptr, nullptr));
642
643 auto cleanup =
644 wil::scope_exit([&]() { LOG_IF_FAILED(DeleteImageNoThrow(registryImage, WSLCDeleteImageFlagsForce).first); });
@@ -656,7 +656,7 @@ class WSLCTests
656 SKIP_TEST_UNSTABLE();
657
658 auto validatePull = [&](const std::string& Image, const std::optional<std::string>& ExpectedTag = {}) {
659 - VERIFY_SUCCEEDED(m_defaultSession->PullImage(Image.c_str(), nullptr, nullptr));
659 + VERIFY_SUCCEEDED(m_defaultSession->PullImage(Image.c_str(), nullptr, nullptr, nullptr));
660
661 auto cleanup = wil::scope_exit(
662 [&]() { LOG_IF_FAILED(DeleteImageNoThrow(ExpectedTag.value_or(Image), WSLCDeleteImageFlagsForce).first); });
@@ -705,7 +705,7 @@ class WSLCTests
705 settings.MemoryMb = 1024;
706 auto session = CreateSession(settings);
707
708 - VERIFY_ARE_EQUAL(session->PullImage("pytorch/pytorch", nullptr, nullptr), E_FAIL);
708 + VERIFY_ARE_EQUAL(session->PullImage("pytorch/pytorch", nullptr, nullptr, nullptr), E_FAIL);
709
710 ValidateCOMErrorMessageContains(L"no space left on device");
711 }
@@ -717,13 +717,13 @@ class WSLCTests
717
718 // Validate that pushing a non-existent image fails.
719 {
720 - VERIFY_ARE_EQUAL(m_defaultSession->PushImage("does-not-exist:latest", emptyAuth.c_str(), nullptr), E_FAIL);
720 + VERIFY_ARE_EQUAL(m_defaultSession->PushImage("does-not-exist:latest", emptyAuth.c_str(), nullptr, nullptr), E_FAIL);
721 ValidateCOMErrorMessage(L"An image does not exist locally with the tag: does-not-exist");
722 }
723
724 // Validate passing empty auth string returns an appropriate error.
725 {
726 - VERIFY_ARE_EQUAL(m_defaultSession->PushImage("does-not-exist:latest", "", nullptr), E_INVALIDARG);
726 + VERIFY_ARE_EQUAL(m_defaultSession->PushImage("does-not-exist:latest", "", nullptr, nullptr), E_INVALIDARG);
727 }
728
729 // Validate that PushImage() returns the appropriate error if the session is terminated.
@@ -731,7 +731,7 @@ class WSLCTests
731 VERIFY_SUCCEEDED(m_defaultSession->Terminate());
732 auto cleanup = wil::scope_exit([&]() { ResetTestSession(); });
733
734 - VERIFY_ARE_EQUAL(m_defaultSession->PushImage("hello-world:latest", emptyAuth.c_str(), nullptr), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
734 + VERIFY_ARE_EQUAL(m_defaultSession->PushImage("hello-world:latest", emptyAuth.c_str(), nullptr, nullptr), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
735 }
736 }
737
@@ -753,11 +753,11 @@ class WSLCTests
753 auto image = PushImageToRegistry("hello-world:latest", registryAddress, xRegistryAuth);
754
755 // Pulling without credentials should fail.
756 - VERIFY_ARE_EQUAL(m_defaultSession->PullImage(image.c_str(), nullptr, nullptr), E_FAIL);
756 + VERIFY_ARE_EQUAL(m_defaultSession->PullImage(image.c_str(), nullptr, nullptr, nullptr), E_FAIL);
757 ValidateCOMErrorMessageContains(L"no basic auth credentials");
758
759 // Pulling with credentials should succeed.
760 - VERIFY_SUCCEEDED(m_defaultSession->PullImage(image.c_str(), xRegistryAuth.c_str(), nullptr));
760 + VERIFY_SUCCEEDED(m_defaultSession->PullImage(image.c_str(), xRegistryAuth.c_str(), nullptr, nullptr));
761 ExpectImagePresent(*m_defaultSession, image.c_str());
762 }
763
@@ -1116,7 +1116,7 @@ class WSLCTests
1116 LARGE_INTEGER fileSize{};
1117 VERIFY_IS_TRUE(GetFileSizeEx(imageTarFileHandle.get(), &fileSize));
1118
1119 - VERIFY_SUCCEEDED(m_defaultSession->LoadImage(ToCOMInputHandle(imageTarFileHandle.get()), nullptr, fileSize.QuadPart));
1119 + VERIFY_SUCCEEDED(m_defaultSession->LoadImage(ToCOMInputHandle(imageTarFileHandle.get()), nullptr, fileSize.QuadPart, nullptr));
1120
1121 // Verify that the image is in the list of images.
1122 ExpectImagePresent(*m_defaultSession, "hello-world:latest");
@@ -1137,7 +1137,8 @@ class WSLCTests
1137 auto currentExecutableHandle = wil::open_file(wil::GetModuleFileNameW<std::wstring>().c_str());
1138 VERIFY_IS_TRUE(GetFileSizeEx(currentExecutableHandle.get(), &fileSize));
1139
1140 - VERIFY_ARE_EQUAL(m_defaultSession->LoadImage(ToCOMInputHandle(currentExecutableHandle.get()), nullptr, fileSize.QuadPart), E_FAIL);
1140 + VERIFY_ARE_EQUAL(
1141 + m_defaultSession->LoadImage(ToCOMInputHandle(currentExecutableHandle.get()), nullptr, fileSize.QuadPart, nullptr), E_FAIL);
1142
1143 ValidateCOMErrorMessage(L"archive/tar: invalid tar header");
1144 }
@@ -1150,7 +1151,7 @@ class WSLCTests
1151
1152 std::promise<HRESULT> loadResult;
1153 std::thread operationThread([&]() {
1153 - loadResult.set_value(m_defaultSession->LoadImage(ToCOMInputHandle(pipeRead.get()), nullptr, 1024 * 1024));
1154 + loadResult.set_value(m_defaultSession->LoadImage(ToCOMInputHandle(pipeRead.get()), nullptr, 1024 * 1024, nullptr));
1155 });
1156
1157 auto threadCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { operationThread.join(); });
@@ -1174,7 +1175,7 @@ class WSLCTests
1175 std::promise<HRESULT> terminateResult;
1176 wil::unique_event testCompleted{wil::EventOptions::ManualReset};
1177 std::thread operationThread([&]() {
1177 - terminateResult.set_value(m_defaultSession->LoadImage(ToCOMInputHandle(pipeRead.get()), nullptr, 1024 * 1024));
1178 + terminateResult.set_value(m_defaultSession->LoadImage(ToCOMInputHandle(pipeRead.get()), nullptr, 1024 * 1024, nullptr));
1179 WI_ASSERT(testCompleted.is_signaled());
1180 });
1181
@@ -1211,7 +1212,7 @@ class WSLCTests
1212 VERIFY_IS_TRUE(GetFileSizeEx(imageTarFileHandle.get(), &fileSize));
1213
1214 VERIFY_SUCCEEDED(m_defaultSession->ImportImage(
1214 - ToCOMInputHandle(imageTarFileHandle.get()), "my-hello-world:test", nullptr, fileSize.QuadPart));
1215 + ToCOMInputHandle(imageTarFileHandle.get()), "my-hello-world:test", nullptr, fileSize.QuadPart, nullptr));
1216
1217 ExpectImagePresent(*m_defaultSession, "my-hello-world:test");
1218
@@ -1229,7 +1230,7 @@ class WSLCTests
1230 // Validate that ImportImage fails if no tag is passed
1231 {
1232 VERIFY_ARE_EQUAL(
1232 - m_defaultSession->ImportImage(ToCOMInputHandle(imageTarFileHandle.get()), "my-hello-world", nullptr, fileSize.QuadPart),
1233 + m_defaultSession->ImportImage(ToCOMInputHandle(imageTarFileHandle.get()), "my-hello-world", nullptr, fileSize.QuadPart, nullptr),
1234 E_INVALIDARG);
1235 }
1236
@@ -1241,7 +1242,7 @@ class WSLCTests
1242
1243 VERIFY_ARE_EQUAL(
1244 m_defaultSession->ImportImage(
1244 - ToCOMInputHandle(currentExecutableHandle.get()), "invalid-image:test", nullptr, fileSize.QuadPart),
1245 + ToCOMInputHandle(currentExecutableHandle.get()), "invalid-image:test", nullptr, fileSize.QuadPart, nullptr),
1246 E_FAIL);
1247
1248 ValidateCOMErrorMessage(L"archive/tar: invalid tar header");
@@ -1255,7 +1256,8 @@ class WSLCTests
1256
1257 std::promise<HRESULT> importResult;
1258 std::thread operationThread([&]() {
1258 - importResult.set_value(m_defaultSession->ImportImage(ToCOMInputHandle(pipeRead.get()), "broken-read:eof", nullptr, 1024 * 1024));
1259 + importResult.set_value(
1260 + m_defaultSession->ImportImage(ToCOMInputHandle(pipeRead.get()), "broken-read:eof", nullptr, 1024 * 1024, nullptr));
1261 });
1262
1263 auto threadCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { operationThread.join(); });
@@ -1279,8 +1281,8 @@ class WSLCTests
1281 std::promise<HRESULT> terminateResult;
1282 wil::unique_event testCompleted{wil::EventOptions::ManualReset};
1283 std::thread operationThread([&]() {
1282 - terminateResult.set_value(
1283 - m_defaultSession->ImportImage(ToCOMInputHandle(pipeRead.get()), "session-terminate:test", nullptr, 1024 * 1024));
1284 + terminateResult.set_value(m_defaultSession->ImportImage(
1285 + ToCOMInputHandle(pipeRead.get()), "session-terminate:test", nullptr, 1024 * 1024, nullptr));
1286 WI_ASSERT(testCompleted.is_signaled());
1287 });
1288
@@ -2057,7 +2059,7 @@ class WSLCTests
2059 auto volumeCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
2060 wil::unique_cotaskmem_array_ptr<WSLCVolumeName> deleted;
2061 ULONGLONG spaceReclaimed = 0;
2060 - LOG_IF_FAILED(m_defaultSession->PruneVolumes(nullptr, 0, deleted.addressof(), deleted.size_address<ULONG>(), &spaceReclaimed));
2062 + LOG_IF_FAILED(m_defaultSession->PruneVolumes(nullptr, 0, nullptr, deleted.addressof(), deleted.size_address<ULONG>(), &spaceReclaimed));
2063 });
2064
2065 VERIFY_ARE_EQUAL(ListVolumes(anonymousVolumeFilters).size(), 1u);
@@ -2411,7 +2413,7 @@ class WSLCTests
2413 LARGE_INTEGER fileSize{};
2414 VERIFY_IS_TRUE(GetFileSizeEx(imageTarFileHandle.get(), &fileSize));
2415 // Load the image from a saved tar
2414 - VERIFY_SUCCEEDED(m_defaultSession->LoadImage(ToCOMInputHandle(imageTarFileHandle.get()), nullptr, fileSize.QuadPart));
2416 + VERIFY_SUCCEEDED(m_defaultSession->LoadImage(ToCOMInputHandle(imageTarFileHandle.get()), nullptr, fileSize.QuadPart, nullptr));
2417 // Verify that the image is in the list of images.
2418 ExpectImagePresent(*m_defaultSession, "hello-world:latest");
2419 WSLCContainerLauncher launcher("hello-world:latest", "wslc-hello-world-container");
@@ -2446,7 +2448,7 @@ class WSLCTests
2448 LARGE_INTEGER fileSize{};
2449 VERIFY_IS_TRUE(GetFileSizeEx(imageTarFileHandle.get(), &fileSize));
2450 // Load the image from a saved tar
2449 - VERIFY_SUCCEEDED(m_defaultSession->LoadImage(ToCOMInputHandle(imageTarFileHandle.get()), nullptr, fileSize.QuadPart));
2451 + VERIFY_SUCCEEDED(m_defaultSession->LoadImage(ToCOMInputHandle(imageTarFileHandle.get()), nullptr, fileSize.QuadPart, nullptr));
2452 // Verify that the image is in the list of images.
2453 ExpectImagePresent(*m_defaultSession, "hello-world:latest");
2454 WSLCContainerLauncher launcher("hello-world:latest", "wslc-hello-world-container");
@@ -2504,7 +2506,7 @@ class WSLCTests
2506
2507 wil::unique_event testCompleted{wil::EventOptions::ManualReset};
2508 std::thread operationThread([&]() {
2507 - result.set_value(m_defaultSession->ImportImage(ToCOMInputHandle(pipeRead.get()), "dummy:latest", nullptr, 1024 * 1024));
2509 + result.set_value(m_defaultSession->ImportImage(ToCOMInputHandle(pipeRead.get()), "dummy:latest", nullptr, 1024 * 1024, nullptr));
2510
2511 WI_ASSERT(testCompleted.is_signaled()); // Sanity check.
2512 });
@@ -2551,7 +2553,7 @@ class WSLCTests
2553 VERIFY_IS_FALSE(INVALID_HANDLE_VALUE == imageTarFileHandle.get());
2554 LARGE_INTEGER fileSize{};
2555 VERIFY_IS_TRUE(GetFileSizeEx(imageTarFileHandle.get(), &fileSize));
2554 - VERIFY_SUCCEEDED(m_defaultSession->LoadImage(ToCOMInputHandle(imageTarFileHandle.get()), nullptr, fileSize.QuadPart));
2556 + VERIFY_SUCCEEDED(m_defaultSession->LoadImage(ToCOMInputHandle(imageTarFileHandle.get()), nullptr, fileSize.QuadPart, nullptr));
2557 // Verify that the image is in the list of images.
2558 ExpectImagePresent(*m_defaultSession, "hello-world:latest");
2559 WSLCContainerLauncher launcher("hello-world:latest", "wslc-hello-world-container");
@@ -2584,7 +2586,7 @@ class WSLCTests
2586 });
2587
2588 VERIFY_SUCCEEDED(m_defaultSession->ImportImage(
2587 - ToCOMInputHandle(containerTarFileHandle.get()), "test-imported-container:latest", nullptr, fileSize.QuadPart));
2589 + ToCOMInputHandle(containerTarFileHandle.get()), "test-imported-container:latest", nullptr, fileSize.QuadPart, nullptr));
2590
2591 // Verify that the image is in the list of images.
2592 ExpectImagePresent(*m_defaultSession, "test-imported-container:latest");
@@ -4415,7 +4417,7 @@ class WSLCTests
4417 wil::unique_cotaskmem_array_ptr<WSLCVolumeName> deleted;
4418 ULONGLONG spaceReclaimed = 0;
4419 VERIFY_SUCCEEDED(m_defaultSession->PruneVolumes(
4418 - filtersPtr, filtersCount, deleted.addressof(), deleted.size_address<ULONG>(), &spaceReclaimed));
4420 + filtersPtr, filtersCount, nullptr, deleted.addressof(), deleted.size_address<ULONG>(), &spaceReclaimed));
4421
4422 std::vector<std::string> names;
4423 for (const auto& n : deleted)
@@ -4555,7 +4557,8 @@ class WSLCTests
4557
4558 VERIFY_ARE_EQUAL(
4559 E_POINTER,
4558 - m_defaultSession->PruneVolumes(filters, ARRAYSIZE(filters), deleted.addressof(), deleted.size_address<ULONG>(), &spaceReclaimed));
4560 + m_defaultSession->PruneVolumes(
4561 + filters, ARRAYSIZE(filters), nullptr, deleted.addressof(), deleted.size_address<ULONG>(), &spaceReclaimed));
4562 }
4563
4564 // Filter with null Value rejected.
@@ -4567,7 +4570,8 @@ class WSLCTests
4570
4571 VERIFY_ARE_EQUAL(
4572 E_POINTER,
4570 - m_defaultSession->PruneVolumes(filters, ARRAYSIZE(filters), deleted.addressof(), deleted.size_address<ULONG>(), &spaceReclaimed));
4573 + m_defaultSession->PruneVolumes(
4574 + filters, ARRAYSIZE(filters), nullptr, deleted.addressof(), deleted.size_address<ULONG>(), &spaceReclaimed));
4575 }
4576 }
4577
@@ -4587,7 +4591,7 @@ class WSLCTests
4591 options.Driver = "bridge";
4592 options.DriverOpts = nullptr;
4593 options.DriverOptsCount = 0;
4590 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options));
4594 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));
4595
4596 auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
4597
@@ -4599,7 +4603,7 @@ class WSLCTests
4603 VERIFY_IS_TRUE(strlen(networks[0].Id) > 0);
4604
4605 // Duplicate name should fail.
4602 - VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), m_defaultSession->CreateNetwork(&options));
4606 + VERIFY_ARE_EQUAL(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), m_defaultSession->CreateNetwork(&options, nullptr));
4607
4608 cleanup.release();
4609 VERIFY_SUCCEEDED(m_defaultSession->DeleteNetwork(networkName.c_str()));
@@ -4628,7 +4632,7 @@ class WSLCTests
4632
4633 auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
4634
4631 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options));
4635 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));
4636
4637 wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
4638 VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
@@ -4652,7 +4656,7 @@ class WSLCTests
4656
4657 auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
4658
4655 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options));
4659 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));
4660
4661 wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
4662 VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
@@ -4681,7 +4685,7 @@ class WSLCTests
4685
4686 auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
4687
4684 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options));
4688 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));
4689
4690 wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
4691 VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
@@ -4699,7 +4703,7 @@ class WSLCTests
4703 for (const char* driver : {"overlay", "Bridge", ""})
4704 {
4705 options.Driver = driver;
4702 - VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options));
4706 + VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options, nullptr));
4707 ValidateCOMErrorMessageContains(L"Unsupported network driver:");
4708 }
4709 }
@@ -4712,15 +4716,15 @@ class WSLCTests
4716 options.DriverOptsCount = 0;
4717
4718 options.Name = "bridge";
4715 - VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options));
4719 + VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options, nullptr));
4720 ValidateCOMErrorMessageContains(L"bridge");
4721
4722 options.Name = "host";
4719 - VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options));
4723 + VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options, nullptr));
4724 ValidateCOMErrorMessageContains(L"host");
4725
4726 options.Name = "none";
4723 - VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options));
4727 + VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options, nullptr));
4728 ValidateCOMErrorMessageContains(L"none");
4729 }
4730
@@ -4732,7 +4736,7 @@ class WSLCTests
4736 options.DriverOpts = nullptr;
4737 options.DriverOptsCount = 0;
4738
4735 - VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options));
4739 + VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options, nullptr));
4740 ValidateCOMErrorMessageContains(L"invalid name!");
4741 }
4742
@@ -4750,7 +4754,7 @@ class WSLCTests
4754 options.DriverOpts = opts;
4755 options.DriverOptsCount = ARRAYSIZE(opts);
4756
4753 - VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options));
4757 + VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options, nullptr));
4758 ValidateCOMErrorMessageContains(L"invalid subnet");
4759
4760 wil::unique_cotaskmem_ansistring output;
@@ -4771,7 +4775,7 @@ class WSLCTests
4775 options.DriverOpts = opts;
4776 options.DriverOptsCount = ARRAYSIZE(opts);
4777
4774 - VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options));
4778 + VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options, nullptr));
4779 ValidateCOMErrorMessageContains(L"invalid gateway");
4780
4781 wil::unique_cotaskmem_ansistring output;
@@ -4794,7 +4798,7 @@ class WSLCTests
4798
4799 auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
4800
4797 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options));
4801 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));
4802
4803 wil::unique_cotaskmem_ansistring output;
4804 VERIFY_SUCCEEDED(m_defaultSession->InspectNetwork(networkName.c_str(), &output));
@@ -4818,7 +4822,7 @@ class WSLCTests
4822 options.Driver = "bridge";
4823 options.DriverOpts = nullptr;
4824 options.DriverOptsCount = 0;
4821 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options));
4825 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));
4826
4827 auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
4828
@@ -4854,7 +4858,7 @@ class WSLCTests
4858 optionsA.Driver = "bridge";
4859 optionsA.DriverOpts = nullptr;
4860 optionsA.DriverOptsCount = 0;
4857 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&optionsA));
4861 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&optionsA, nullptr));
4862
4863 WSLCDriverOption subnetOpt[] = {{"Subnet", "172.29.0.0/16"}};
4864 WSLCNetworkOptions optionsB{};
@@ -4862,7 +4866,7 @@ class WSLCTests
4866 optionsB.Driver = "bridge";
4867 optionsB.DriverOpts = subnetOpt;
4868 optionsB.DriverOptsCount = ARRAYSIZE(subnetOpt);
4865 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&optionsB));
4869 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&optionsB, nullptr));
4870
4871 WSLCDriverOption internalOpt[] = {{"Internal", "true"}};
4872 WSLCNetworkOptions optionsC{};
@@ -4870,7 +4874,7 @@ class WSLCTests
4874 optionsC.Driver = "bridge";
4875 optionsC.DriverOpts = internalOpt;
4876 optionsC.DriverOptsCount = ARRAYSIZE(internalOpt);
4873 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&optionsC));
4877 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&optionsC, nullptr));
4878
4879 wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
4880 VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
@@ -4894,7 +4898,7 @@ class WSLCTests
4898 options.Driver = "bridge";
4899 options.DriverOpts = nullptr;
4900 options.DriverOptsCount = 0;
4897 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options));
4901 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));
4902
4903 wil::unique_cotaskmem_ansistring output;
4904 VERIFY_SUCCEEDED(m_defaultSession->InspectNetwork(networkName.c_str(), &output));
@@ -4922,7 +4926,7 @@ class WSLCTests
4926 options.Driver = "bridge";
4927 options.DriverOpts = subnetOpt;
4928 options.DriverOptsCount = ARRAYSIZE(subnetOpt);
4925 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options));
4929 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));
4930
4931 wil::unique_cotaskmem_ansistring output;
4932 VERIFY_SUCCEEDED(m_defaultSession->InspectNetwork(networkName.c_str(), &output));
@@ -5285,7 +5289,7 @@ class WSLCTests
5289 options.InitProcessOptions.CommandLine = {.Values = nullptr, .Count = 0};
5290
5291 wil::com_ptr<IWSLCContainer> container;
5288 - auto hr = m_defaultSession->CreateContainer(&options, &container);
5292 + auto hr = m_defaultSession->CreateContainer(&options, nullptr, &container);
5293 VERIFY_ARE_EQUAL(hr, E_INVALIDARG);
5294 }
5295
@@ -5297,7 +5301,7 @@ class WSLCTests
5301 options.InitProcessOptions.CommandLine = {.Values = nullptr, .Count = 0};
5302
5303 wil::com_ptr<IWSLCContainer> container;
5300 - VERIFY_SUCCEEDED(m_defaultSession->CreateContainer(&options, &container));
5304 + VERIFY_SUCCEEDED(m_defaultSession->CreateContainer(&options, nullptr, &container));
5305 VERIFY_SUCCEEDED(container->Delete(WSLCDeleteFlagsNone));
5306 }
5307 }
@@ -5313,14 +5317,14 @@ class WSLCTests
5317
5318 {
5319 // Validate that the container can be restarted.
5316 - VERIFY_ARE_EQUAL(container.Get().Start(WSLCContainerStartFlagsAttach, nullptr), S_OK);
5320 + VERIFY_ARE_EQUAL(container.Get().Start(WSLCContainerStartFlagsAttach, nullptr, nullptr), S_OK);
5321 auto restartedProcess = container.GetInitProcess();
5322 ValidateProcessOutput(restartedProcess, {{1, "OK\n"}});
5323 }
5324
5325 {
5326 // Validate that the container can be restarted without the attach flag.
5323 - VERIFY_ARE_EQUAL(container.Get().Start(WSLCContainerStartFlagsNone, nullptr), S_OK);
5327 + VERIFY_ARE_EQUAL(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr), S_OK);
5328 auto restartedProcess = container.GetInitProcess();
5329 VERIFY_ARE_EQUAL(restartedProcess.Wait(), 0);
5330
@@ -5342,21 +5346,21 @@ class WSLCTests
5346 VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGKILL, 0));
5347 VERIFY_ARE_EQUAL(container.State(), WslcContainerStateExited);
5348
5345 - VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr));
5349 + VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr));
5350 VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
5351
5352 auto initProcess = container.GetInitProcess();
5353 initProcess.Get().Signal(WSLCSignalSIGKILL);
5354 VERIFY_ARE_EQUAL(initProcess.Wait(), WSLCSignalSIGKILL + 128);
5355
5352 - VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr));
5356 + VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr));
5357 VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
5358
5359 VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGKILL, 0));
5360 VERIFY_SUCCEEDED(container.Get().Delete(WSLCDeleteFlagsNone));
5361
5362 // Validate that deleted containers can't be started.
5359 - VERIFY_ARE_EQUAL(container.Get().Start(WSLCContainerStartFlagsNone, nullptr), RPC_E_DISCONNECTED);
5363 + VERIFY_ARE_EQUAL(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr), RPC_E_DISCONNECTED);
5364 }
5365
5366 // Validate restart behavior for a container with the autorm flag set
@@ -5369,14 +5373,14 @@ class WSLCTests
5373 VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGKILL, 0));
5374
5375 // Validate that deleted containers can't be started.
5372 - VERIFY_ARE_EQUAL(container.Get().Start(WSLCContainerStartFlagsNone, nullptr), RPC_E_DISCONNECTED);
5376 + VERIFY_ARE_EQUAL(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr), RPC_E_DISCONNECTED);
5377 }
5378
5379 // Validate that invalid start flags are rejected.
5380 {
5381 WSLCContainerLauncher launcher("debian:latest", "test-stop-start-invalid-flags", {"echo", "OK"});
5382 auto container = launcher.Create(*m_defaultSession);
5379 - VERIFY_ARE_EQUAL(container.Get().Start(static_cast<WSLCContainerStartFlags>(0x2), nullptr), E_INVALIDARG);
5383 + VERIFY_ARE_EQUAL(container.Get().Start(static_cast<WSLCContainerStartFlags>(0x2), nullptr, nullptr), E_INVALIDARG);
5384 }
5385 }
5386
@@ -5578,7 +5582,7 @@ class WSLCTests
5582 ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", id));
5583
5584 // Verify that the container is in running state.
5581 - VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr));
5585 + VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr));
5586 VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
5587
5588 VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGTERM, 0));
@@ -5604,7 +5608,7 @@ class WSLCTests
5608 VERIFY_ARE_EQUAL(container.Get().Kill(WSLCSignalNone), WSLC_E_CONTAINER_NOT_RUNNING);
5609 ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", id));
5610
5607 - VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr));
5611 + VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr));
5612 VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
5613 VERIFY_SUCCEEDED(container.Get().Kill(WSLCSignalNone));
5614
@@ -5627,7 +5631,7 @@ class WSLCTests
5631
5632 auto container = launcher.Create(*m_defaultSession);
5633
5630 - VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr));
5634 + VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr));
5635 VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
5636 VERIFY_SUCCEEDED(container.Get().Kill(WSLCSignalSIGTERM));
5637
@@ -5707,11 +5711,11 @@ class WSLCTests
5711 VERIFY_SUCCEEDED(result);
5712
5713 VERIFY_ARE_EQUAL(container->State(), WslcContainerStateCreated);
5710 - VERIFY_SUCCEEDED(container->Get().Start(WSLCContainerStartFlagsNone, nullptr));
5714 + VERIFY_SUCCEEDED(container->Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr));
5715
5716 // Verify that Start() can't be called again on a running container.
5717 auto id = container->Id();
5714 - VERIFY_ARE_EQUAL(container->Get().Start(WSLCContainerStartFlagsNone, nullptr), WSLC_E_CONTAINER_IS_RUNNING);
5718 + VERIFY_ARE_EQUAL(container->Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr), WSLC_E_CONTAINER_IS_RUNNING);
5719 ValidateCOMErrorMessage(std::format(L"Container '{}' is running.", id));
5720
5721 VERIFY_ARE_EQUAL(container->State(), WslcContainerStateRunning);
@@ -6000,7 +6004,7 @@ class WSLCTests
6004 networkOptions.DriverOpts = opts;
6005 networkOptions.DriverOptsCount = ARRAYSIZE(opts);
6006
6003 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions));
6007 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions, nullptr));
6008
6009 auto networkCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6010
@@ -6035,7 +6039,7 @@ class WSLCTests
6039 options.ContainerNetwork.NetworksCount = 1;
6040
6041 wil::com_ptr<IWSLCContainer> container;
6038 - auto hr = m_defaultSession->CreateContainer(&options, &container);
6042 + auto hr = m_defaultSession->CreateContainer(&options, nullptr, &container);
6043 VERIFY_ARE_EQUAL(E_INVALIDARG, hr);
6044 ValidateCOMErrorMessageContains(L"Network name");
6045 }
@@ -6051,7 +6055,7 @@ class WSLCTests
6055 WSLCNetworkOptions networkOptions{};
6056 networkOptions.Name = networkName.c_str();
6057 networkOptions.Driver = "bridge";
6054 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions));
6058 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions, nullptr));
6059 auto networkCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6060
6061 LPCSTR args[] = {"sleep", "99999"};
@@ -6070,7 +6074,7 @@ class WSLCTests
6074 options.ContainerNetwork.NetworksCount = 1;
6075
6076 wil::com_ptr<IWSLCContainer> container;
6073 - auto hr = m_defaultSession->CreateContainer(&options, &container);
6077 + auto hr = m_defaultSession->CreateContainer(&options, nullptr, &container);
6078 VERIFY_ARE_EQUAL(E_NOTIMPL, hr);
6079 ValidateCOMErrorMessage(L"Endpoint settings are not yet supported (network 'custom-net-settings').");
6080 }
@@ -6101,7 +6105,7 @@ class WSLCTests
6105 networkOptions.DriverOpts = opts;
6106 networkOptions.DriverOptsCount = ARRAYSIZE(opts);
6107
6104 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions));
6108 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions, nullptr));
6109
6110 auto networkCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6111
@@ -6132,7 +6136,7 @@ class WSLCTests
6136 networkOptions.DriverOpts = opts;
6137 networkOptions.DriverOptsCount = ARRAYSIZE(opts);
6138
6135 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions));
6139 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions, nullptr));
6140
6141 auto networkCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6142
@@ -6158,7 +6162,7 @@ class WSLCTests
6162 networkOptions.DriverOpts = opts;
6163 networkOptions.DriverOptsCount = ARRAYSIZE(opts);
6164
6161 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions));
6165 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions, nullptr));
6166
6167 auto networkCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6168
@@ -6188,7 +6192,7 @@ class WSLCTests
6192 networkOptions.DriverOpts = opts;
6193 networkOptions.DriverOptsCount = ARRAYSIZE(opts);
6194
6191 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions));
6195 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions, nullptr));
6196
6197 auto networkCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6198
@@ -6207,7 +6211,7 @@ class WSLCTests
6211 auto recoveredContainer = OpenContainer(m_defaultSession.get(), containerName);
6212
6213 VERIFY_ARE_EQUAL(recoveredContainer.State(), WslcContainerStateCreated);
6210 - VERIFY_SUCCEEDED(recoveredContainer.Get().Start(WSLCContainerStartFlagsAttach, nullptr));
6214 + VERIFY_SUCCEEDED(recoveredContainer.Get().Start(WSLCContainerStartFlagsAttach, nullptr, nullptr));
6215 VERIFY_ARE_EQUAL(recoveredContainer.State(), WslcContainerStateRunning);
6216
6217 VERIFY_ARE_EQUAL(recoveredContainer.Inspect().HostConfig.NetworkMode, networkName);
@@ -6227,7 +6231,7 @@ class WSLCTests
6231 primaryNetOpts.Driver = "bridge";
6232 primaryNetOpts.DriverOpts = primaryOpts;
6233 primaryNetOpts.DriverOptsCount = ARRAYSIZE(primaryOpts);
6230 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&primaryNetOpts));
6234 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&primaryNetOpts, nullptr));
6235 auto primaryCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(primaryNetwork.c_str())); });
6236
6237 WSLCDriverOption additionalOpts[] = {{"Subnet", "172.41.0.0/16"}};
@@ -6236,7 +6240,7 @@ class WSLCTests
6240 additionalNetOpts.Driver = "bridge";
6241 additionalNetOpts.DriverOpts = additionalOpts;
6242 additionalNetOpts.DriverOptsCount = ARRAYSIZE(additionalOpts);
6239 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&additionalNetOpts));
6243 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&additionalNetOpts, nullptr));
6244 auto additionalCleanup =
6245 wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(additionalNetwork.c_str())); });
6246
@@ -6266,7 +6270,7 @@ class WSLCTests
6270 netOpts.Driver = "bridge";
6271 netOpts.DriverOpts = opts;
6272 netOpts.DriverOptsCount = ARRAYSIZE(opts);
6269 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&netOpts));
6273 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&netOpts, nullptr));
6274 auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(primaryNetwork.c_str())); });
6275
6276 WSLCContainerLauncher launcher("debian:latest", "test-multi-net-dup-reject", {"sleep", "99999"}, {}, std::string(primaryNetwork));
@@ -6292,7 +6296,7 @@ class WSLCTests
6296 netOpts.Driver = "bridge";
6297 netOpts.DriverOpts = opts;
6298 netOpts.DriverOptsCount = ARRAYSIZE(opts);
6295 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&netOpts));
6299 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&netOpts, nullptr));
6300 auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(primaryNetwork.c_str())); });
6301
6302 WSLCContainerLauncher launcher("debian:latest", "test-multi-net-notfound-reject", {"sleep", "99999"}, {}, std::string(primaryNetwork));
@@ -6366,7 +6370,7 @@ class WSLCTests
6370 primaryNetOpts.Driver = "bridge";
6371 primaryNetOpts.DriverOpts = primaryOpts;
6372 primaryNetOpts.DriverOptsCount = ARRAYSIZE(primaryOpts);
6369 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&primaryNetOpts));
6373 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&primaryNetOpts, nullptr));
6374 auto primaryCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(primaryNetwork.c_str())); });
6375
6376 WSLCDriverOption additionalOpts[] = {{"Subnet", "172.51.0.0/16"}};
@@ -6375,7 +6379,7 @@ class WSLCTests
6379 additionalNetOpts.Driver = "bridge";
6380 additionalNetOpts.DriverOpts = additionalOpts;
6381 additionalNetOpts.DriverOptsCount = ARRAYSIZE(additionalOpts);
6378 - VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&additionalNetOpts));
6382 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&additionalNetOpts, nullptr));
6383 auto additionalCleanup =
6384 wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(additionalNetwork.c_str())); });
6385
@@ -7056,7 +7060,7 @@ class WSLCTests
7060 options.ContainerNetwork.NetworkMode = containerNetworkType.c_str();
7061
7062 wil::com_ptr<IWSLCContainer> container;
7059 - VERIFY_ARE_EQUAL(session.CreateContainer(&options, &container), E_INVALIDARG);
7063 + VERIFY_ARE_EQUAL(session.CreateContainer(&options, nullptr, &container), E_INVALIDARG);
7064 }
7065
7066 // TODO: Update once UDP is supported.
@@ -7381,7 +7385,7 @@ class WSLCTests
7385 container.SetDeleteOnClose(false);
7386
7387 auto openedContainer = OpenContainer(m_defaultSession.get(), "test-volumes-8");
7384 - VERIFY_SUCCEEDED(openedContainer.Get().Start(WSLCContainerStartFlagsAttach, nullptr));
7388 + VERIFY_SUCCEEDED(openedContainer.Get().Start(WSLCContainerStartFlagsAttach, nullptr, nullptr));
7389 validateInspect(openedContainer);
7390
7391 ValidateContainerOutput(openedContainer, {{1, "OK"}});
@@ -7889,7 +7893,7 @@ class WSLCTests
7893 container.SetDeleteOnClose(false);
7894
7895 VERIFY_ARE_EQUAL(container.State(), WslcContainerStateCreated);
7892 - VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsAttach, nullptr));
7896 + VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsAttach, nullptr, nullptr));
7897
7898 auto initProcess = container.GetInitProcess();
7899 WaitForOutput(initProcess.GetStdHandle(1), "Serving HTTP on 0.0.0.0 port 8000");
@@ -8015,7 +8019,7 @@ class WSLCTests
8019 auto settings = GetDefaultSessionSettings(L"session-1");
8020
8021 wil::com_ptr<IWSLCSession> session;
8018 - VERIFY_ARE_EQUAL(manager->CreateSession(&settings, WSLCSessionFlagsPersistent, &session), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
8022 + VERIFY_ARE_EQUAL(manager->CreateSession(&settings, WSLCSessionFlagsPersistent, nullptr, &session), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
8023
8024 VERIFY_SUCCEEDED(session1Copy->Terminate());
8025 WSLCSessionState state{};
@@ -8288,7 +8292,7 @@ class WSLCTests
8292 options.LabelsCount = 1;
8293
8294 wil::com_ptr<IWSLCContainer> container;
8291 - auto hr = m_defaultSession->CreateContainer(&options, &container);
8295 + auto hr = m_defaultSession->CreateContainer(&options, nullptr, &container);
8296 VERIFY_ARE_EQUAL(hr, E_INVALIDARG);
8297 }
8298
@@ -8303,7 +8307,7 @@ class WSLCTests
8307 options.LabelsCount = 1;
8308
8309 wil::com_ptr<IWSLCContainer> container;
8306 - auto hr = m_defaultSession->CreateContainer(&options, &container);
8310 + auto hr = m_defaultSession->CreateContainer(&options, nullptr, &container);
8311 VERIFY_ARE_EQUAL(hr, E_INVALIDARG);
8312 }
8313
@@ -8320,7 +8324,7 @@ class WSLCTests
8324 options.LabelsCount = static_cast<ULONG>(labels.size());
8325
8326 wil::com_ptr<IWSLCContainer> container;
8323 - auto hr = m_defaultSession->CreateContainer(&options, &container);
8327 + auto hr = m_defaultSession->CreateContainer(&options, nullptr, &container);
8328 VERIFY_ARE_EQUAL(hr, HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
8329 }
8330
@@ -8425,7 +8429,7 @@ class WSLCTests
8429 options.UlimitsCount = 1;
8430
8431 wil::com_ptr<IWSLCContainer> container;
8428 - auto hr = m_defaultSession->CreateContainer(&options, &container);
8432 + auto hr = m_defaultSession->CreateContainer(&options, nullptr, &container);
8433 VERIFY_ARE_EQUAL(hr, E_INVALIDARG);
8434 }
8435 }
@@ -8447,7 +8451,7 @@ class WSLCTests
8451 ValidateCOMErrorMessage(std::format(L"Container '{}' is not running.", id));
8452
8453 // Start the container.
8450 - VERIFY_SUCCEEDED(container->Get().Start(WSLCContainerStartFlagsAttach, nullptr));
8454 + VERIFY_SUCCEEDED(container->Get().Start(WSLCContainerStartFlagsAttach, nullptr, nullptr));
8455
8456 // Verify that trying to attach with null handles fails.
8457 VERIFY_ARE_EQUAL(container->Get().Attach(nullptr, nullptr, nullptr, nullptr), HRESULT_FROM_WIN32(RPC_X_NULL_REF_POINTER));
@@ -8736,7 +8740,7 @@ class WSLCTests
8740 expectInvalidArg(longName);
8741
8742 auto expectInvalidPull = [&](const char* name) {
8739 - VERIFY_ARE_EQUAL(m_defaultSession->PullImage(name, nullptr, nullptr), E_INVALIDARG);
8743 + VERIFY_ARE_EQUAL(m_defaultSession->PullImage(name, nullptr, nullptr, nullptr), E_INVALIDARG);
8744
8745 auto comError = wsl::windows::common::wslutil::GetCOMErrorInfo();
8746 VERIFY_IS_TRUE(comError.has_value());
@@ -8869,7 +8873,7 @@ class WSLCTests
8873 auto container = OpenContainer(m_defaultSession.get(), "test-auto-remove");
8874 auto id = container.Id();
8875
8872 - VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr));
8876 + VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr));
8877 VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGKILL, 0));
8878
8879 // verifyContainerDeleted("test-auto-remove");
@@ -8987,11 +8991,11 @@ class WSLCTests
8991 VERIFY_ARE_EQUAL(container2.State(), WslcContainerStateCreated);
8992
8993 // Start container — should succeed.
8990 - VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr));
8994 + VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr));
8995 VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
8996
8997 // Start container 2 — should fail because the host port is already reserved by container 1.
8994 - VERIFY_ARE_EQUAL(container2.Get().Start(WSLCContainerStartFlagsNone, nullptr), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
8998 + VERIFY_ARE_EQUAL(container2.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
8999 VERIFY_ARE_EQUAL(container2.State(), WslcContainerStateCreated);
9000 }
9001
@@ -9022,7 +9026,7 @@ class WSLCTests
9026 VERIFY_ARE_EQUAL(getMountCount(), baselineMountCount);
9027
9028 // Start the container — volume should now be mounted.
9025 - VERIFY_SUCCEEDED(container->Get().Start(WSLCContainerStartFlagsNone, nullptr));
9029 + VERIFY_SUCCEEDED(container->Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr));
9030 VERIFY_ARE_EQUAL(container->State(), WslcContainerStateRunning);
9031 VERIFY_ARE_EQUAL(getMountCount(), baselineMountCount + 1);
9032
@@ -9192,7 +9196,7 @@ class WSLCTests
9196 WSLCContainerLauncher launcher("debian:latest", "test-detach", {"sleep", "9999999"}, {}, {}, WSLCProcessFlagsStdin | WSLCProcessFlagsTty);
9197
9198 auto container = launcher.Create(*m_defaultSession);
9195 - VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsAttach, DetachKeys));
9199 + VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsAttach, DetachKeys, nullptr));
9200
9201 auto initProcess = container.GetInitProcess();
9202
@@ -9243,9 +9247,9 @@ class WSLCTests
9247 WSLCContainerLauncher launcher("debian:latest", "test-detach", {"cat"}, {}, {}, WSLCProcessFlagsStdin | WSLCProcessFlagsTty);
9248 auto container = launcher.Create(*m_defaultSession);
9249
9246 - VERIFY_ARE_EQUAL(container.Get().Start(WSLCContainerStartFlagsAttach, "invalid"), E_INVALIDARG);
9250 + VERIFY_ARE_EQUAL(container.Get().Start(WSLCContainerStartFlagsAttach, "invalid", nullptr), E_INVALIDARG);
9251
9248 - VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr));
9252 + VERIFY_SUCCEEDED(container.Get().Start(WSLCContainerStartFlagsNone, nullptr, nullptr));
9253
9254 COMOutputHandle unusedHandle{};
9255 VERIFY_ARE_EQUAL(container.Get().Attach("invalid", &unusedHandle, &unusedHandle, &unusedHandle), E_INVALIDARG);
@@ -9678,4 +9682,153 @@ class WSLCTests
9682
9683 VERIFY_IS_FALSE(IsVmRunning(c_sessionName));
9684 }
9685 +
9686 + // Helper: COM callback that captures all warnings received.
9687 + class CapturingWarningCallback
9688 + : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IWarningCallback, IFastRundown>
9689 + {
9690 + public:
9691 + HRESULT OnWarning(LPCWSTR Message) override
9692 + {
9693 + std::lock_guard lock(m_lock);
9694 + m_warnings.emplace_back(Message);
9695 + return S_OK;
9696 + }
9697 +
9698 + std::vector<std::wstring> GetWarnings()
9699 + {
9700 + std::lock_guard lock(m_lock);
9701 + return m_warnings;
9702 + }
9703 +
9704 + private:
9705 + std::mutex m_lock;
9706 + std::vector<std::wstring> m_warnings;
9707 + };
9708 +
9709 + WSLC_TEST_METHOD(WarningCallbackContainerRecovery)
9710 + {
9711 + SKIP_TEST_SERVER();
9712 +
9713 + constexpr auto c_sessionName = L"warning-container-recovery";
9714 + auto storagePath = (std::filesystem::current_path() / "test-warning-container-recovery").wstring();
9715 + auto cleanupDir = wil::scope_exit([&]() {
9716 + std::error_code ec;
9717 + std::filesystem::remove_all(storagePath, ec);
9718 + });
9719 +
9720 + // Phase 1: Create a session and inject a container with a corrupt WSLC metadata label via docker CLI.
9721 + {
9722 + auto settings = GetDefaultSessionSettings(c_sessionName, false, WSLCNetworkingModeVirtioProxy);
9723 + settings.StoragePath = storagePath.c_str();
9724 + auto session = CreateSession(settings);
9725 +
9726 + // Load a base image so docker create works.
9727 + LoadTestImage(*session, "hello-world:latest");
9728 +
9729 + // Create a container with an invalid WSLC metadata label.
9730 + // RecoverExistingContainers will fail to parse this on the next session.
9731 + auto result = ExpectCommandResult(
9732 + session.get(),
9733 + {"/usr/bin/docker", "create", "--label", "wslc.container.metadata=INVALID_JSON", "hello-world:latest"},
9734 + 0);
9735 +
9736 + // Capture the container ID from docker create output (stdout, trimmed).
9737 + auto containerId = result.Output[1];
9738 + containerId.erase(containerId.find_last_not_of(" \n\r") + 1);
9739 +
9740 + VERIFY_SUCCEEDED(session->Terminate());
9741 +
9742 + // Phase 2: Create a new session pointing to the same storage with a warning callback.
9743 + auto warningCallback = Microsoft::WRL::Make<CapturingWarningCallback>();
9744 +
9745 + auto settings2 = GetDefaultSessionSettings(c_sessionName, false, WSLCNetworkingModeVirtioProxy);
9746 + settings2.StoragePath = storagePath.c_str();
9747 +
9748 + const auto sessionManager2 = OpenSessionManager();
9749 + wil::com_ptr<IWSLCSession> session2;
9750 + VERIFY_SUCCEEDED(sessionManager2->CreateSession(&settings2, WSLCSessionFlagsNone, warningCallback.Get(), &session2));
9751 + wsl::windows::common::security::ConfigureForCOMImpersonation(session2.get());
9752 +
9753 + // Verify the warning matches the expected localized message for the corrupt container.
9754 + auto warnings = warningCallback->GetWarnings();
9755 + auto expectedWarning = std::format(
9756 + L"wsl: {}\n",
9757 + wsl::shared::Localization::MessageWslcFailedToRecoverContainer(wsl::shared::string::MultiByteToWide(containerId)));
9758 +
9759 + VERIFY_IS_TRUE(std::ranges::any_of(warnings, [&](const auto& w) { return w == expectedWarning; }));
9760 +
9761 + VERIFY_SUCCEEDED(session2->Terminate());
9762 + }
9763 + }
9764 +
9765 + WSLC_TEST_METHOD(WarningCallbackVolumeRecovery)
9766 + {
9767 + SKIP_TEST_SERVER();
9768 +
9769 + constexpr auto c_sessionName = L"warning-volume-recovery";
9770 + auto storagePath = (std::filesystem::current_path() / "test-warning-volume-recovery").wstring();
9771 + auto cleanupDir = wil::scope_exit([&]() {
9772 + std::error_code ec;
9773 + std::filesystem::remove_all(storagePath, ec);
9774 + });
9775 +
9776 + std::string vhdHostPath;
9777 +
9778 + // Phase 1: Create a session with a VHD volume, then get the VHD path.
9779 + {
9780 + auto settings = GetDefaultSessionSettings(c_sessionName, false, WSLCNetworkingModeVirtioProxy);
9781 + settings.StoragePath = storagePath.c_str();
9782 + auto session = CreateSession(settings);
9783 +
9784 + // Create a VHD volume.
9785 + WSLCDriverOption driverOpts[] = {{"SizeBytes", "10485760"}}; // 10MB
9786 + WSLCVolumeOptions volumeOptions{};
9787 + volumeOptions.Name = "wslc-test-warning-recovery";
9788 + volumeOptions.Driver = "vhd";
9789 + volumeOptions.DriverOpts = driverOpts;
9790 + volumeOptions.DriverOptsCount = ARRAYSIZE(driverOpts);
9791 +
9792 + WSLCVolumeInformation volInfo{};
9793 + VERIFY_SUCCEEDED(session->CreateVolume(&volumeOptions, &volInfo));
9794 +
9795 + // Inspect the volume to get the host VHD path.
9796 + wil::unique_cotaskmem_ansistring inspectOutput;
9797 + VERIFY_SUCCEEDED(session->InspectVolume("wslc-test-warning-recovery", &inspectOutput));
9798 + auto inspect = wsl::shared::FromJson<wsl::windows::common::wslc_schema::InspectVolume>(inspectOutput.get());
9799 + VERIFY_IS_TRUE(inspect.Status.has_value());
9800 + VERIFY_IS_TRUE(inspect.Status->contains("HostPath"));
9801 + vhdHostPath = inspect.Status->at("HostPath");
9802 + VERIFY_IS_FALSE(vhdHostPath.empty());
9803 +
9804 + VERIFY_SUCCEEDED(session->Terminate());
9805 + }
9806 +
9807 + // Phase 2: Delete the VHD file, then restart with a warning callback.
9808 + VERIFY_IS_TRUE(DeleteFileA(vhdHostPath.c_str()));
9809 +
9810 + {
9811 + auto warningCallback = Microsoft::WRL::Make<CapturingWarningCallback>();
9812 +
9813 + auto settings = GetDefaultSessionSettings(c_sessionName, false, WSLCNetworkingModeVirtioProxy);
9814 + settings.StoragePath = storagePath.c_str();
9815 +
9816 + const auto sessionManager = OpenSessionManager();
9817 + wil::com_ptr<IWSLCSession> session;
9818 + VERIFY_SUCCEEDED(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, warningCallback.Get(), &session));
9819 + wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
9820 +
9821 + // Verify the warning matches the expected localized message for the missing volume.
9822 + auto warnings = warningCallback->GetWarnings();
9823 + auto expectedWarning =
9824 + std::format(L"wsl: {}\n", wsl::shared::Localization::MessageWslcFailedToRecoverVolume(L"wslc-test-warning-recovery"));
9825 +
9826 + VERIFY_IS_TRUE(std::ranges::any_of(warnings, [&](const auto& w) { return w == expectedWarning; }));
9827 +
9828 + // Clean up the orphaned volume from Docker's metadata.
9829 + LOG_IF_FAILED(session->DeleteVolume("wslc-test-warning-recovery"));
9830 +
9831 + VERIFY_SUCCEEDED(session->Terminate());
9832 + }
9833 + }
9834 };
test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp
+4 -4
@@ -183,7 +183,7 @@ class WSLCE2EGlobalTests
183 settings.MaximumStorageSizeMb = 4096;
184
185 wil::com_ptr<IWSLCSession> session;
186 - HRESULT hr = sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, &session);
186 + HRESULT hr = sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, nullptr, &session);
187 VERIFY_ARE_EQUAL(hr, WSLC_E_SESSION_RESERVED);
188 }
189
@@ -202,7 +202,7 @@ class WSLCE2EGlobalTests
202 settings.MaximumStorageSizeMb = 4096;
203
204 wil::com_ptr<IWSLCSession> session;
205 - HRESULT hr = sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, &session);
205 + HRESULT hr = sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, nullptr, &session);
206 VERIFY_ARE_EQUAL(hr, WSLC_E_SESSION_RESERVED);
207 }
208
@@ -227,10 +227,10 @@ class WSLCE2EGlobalTests
227 settings.MaximumStorageSizeMb = 4096;
228
229 wil::com_ptr<IWSLCSession> session;
230 - VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, &session), WSLC_E_SESSION_RESERVED);
230 + VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, nullptr, &session), WSLC_E_SESSION_RESERVED);
231
232 settings.DisplayName = L"Wslc-Cli-Admin";
233 - VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, &session), WSLC_E_SESSION_RESERVED);
233 + VERIFY_ARE_EQUAL(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, nullptr, &session), WSLC_E_SESSION_RESERVED);
234 }
235 }
236
test/windows/wslc/e2e/WSLCE2EHelpers.cpp
+1 -1
@@ -52,7 +52,7 @@ namespace {
52 {
53 const auto sessionManager = OpenSessionManager();
54 wil::com_ptr<IWSLCSession> session;
55 - VERIFY_SUCCEEDED(sessionManager->CreateSession(&sessionSettings, Flags, &session));
55 + VERIFY_SUCCEEDED(sessionManager->CreateSession(&sessionSettings, Flags, nullptr, &session));
56 wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
57
58 WSLCSessionState state{};
test/windows/wslc/e2e/WSLCE2EWarningTests.cpp new
+113
@@ -0,0 +1,113 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + WSLCE2EWarningTests.cpp
8 +
9 +Abstract:
10 +
11 + End-to-end tests validating that warnings emitted by the WSLC COM service are
12 + surfaced on the wslc.exe CLI's stderr via the IWarningCallback integration.
13 +--*/
14 +
15 +#include "precomp.h"
16 +#include "windows/Common.h"
17 +#include "WSLCExecutor.h"
18 +#include "WSLCE2EHelpers.h"
19 +#include <WSLCProcessLauncher.h>
20 +
21 +namespace WSLCE2ETests {
22 +using namespace wsl::shared;
23 +
24 +class WSLCE2EWarningTests
25 +{
26 + WSLC_TEST_CLASS(WSLCE2EWarningTests)
27 +
28 + const TestImage AlpineImage = AlpineTestImage();
29 + WSADATA m_wsaData{};
30 +
31 + TEST_CLASS_SETUP(ClassSetup)
32 + {
33 + THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &m_wsaData));
34 + EnsureImageIsLoaded(AlpineImage);
35 + return true;
36 + }
37 +
38 + TEST_CLASS_CLEANUP(ClassCleanup)
39 + {
40 + EnsureImageIsDeleted(AlpineImage);
41 + WSACleanup();
42 + return true;
43 + }
44 +
45 + static std::string RunDockerInSession(IWSLCSession& session, std::vector<std::string>&& args)
46 + {
47 + wsl::windows::common::WSLCProcessLauncher launcher("/usr/bin/docker", args);
48 + auto result = launcher.Launch(session).WaitAndCaptureOutput();
49 + VERIFY_ARE_EQUAL(0, result.Code);
50 +
51 + // Trim trailing whitespace from the captured stdout (fd 1).
52 + auto output = result.Output[1];
53 + output.erase(output.find_last_not_of(" \n\r") + 1);
54 + return output;
55 + }
56 +
57 + // Best-effort removal of a container from docker.
58 + static void RemoveContainerNoThrow(const std::string& containerId)
59 + try
60 + {
61 + wil::com_ptr<IWSLCSessionManager> sessionManager;
62 + THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLCSessionManager), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&sessionManager)));
63 + wsl::windows::common::security::ConfigureForCOMImpersonation(sessionManager.get());
64 +
65 + wil::com_ptr<IWSLCSession> session;
66 + THROW_IF_FAILED(sessionManager->OpenSessionByName(nullptr, &session));
67 + wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
68 +
69 + wsl::windows::common::WSLCProcessLauncher launcher("/usr/bin/docker", {"/usr/bin/docker", "rm", "-f", containerId});
70 + launcher.Launch(*session).WaitAndCaptureOutput();
71 + }
72 + CATCH_LOG()
73 +
74 + // Injects a container with corrupt WSLC metadata into the default session's storage,
75 + // then verifies that running the wslc.exe CLI surfaces the COM service's recovery
76 + // warning on stderr.
77 + WSLC_TEST_METHOD(WSLCE2E_Warning_ContainerRecoveryPrintedOnStderr)
78 + {
79 + std::string corruptContainerId;
80 +
81 + // Inject a container whose WSLC metadata label is not valid JSON. RecoverExistingContainers
82 + // will fail to parse it the next time the session is created.
83 + {
84 + auto session = OpenDefaultElevatedSession();
85 + corruptContainerId = RunDockerInSession(
86 + *session,
87 + {"/usr/bin/docker",
88 + "create",
89 + "--label",
90 + "wslc.container.metadata=INVALID_JSON",
91 + string::WideToMultiByte(AlpineImage.NameAndTag())});
92 + VERIFY_IS_FALSE(corruptContainerId.empty());
93 + }
94 +
95 + // cleanup: remove the corrupt container
96 + auto cleanup = wil::scope_exit([&]() { RemoveContainerNoThrow(corruptContainerId); });
97 +
98 + // Terminate the default session so the next wslc command recreates it and runs recovery.
99 + EnsureSessionIsTerminated();
100 +
101 + // Run the CLI: recovery of the corrupt container fails and the warning is printed on stderr.
102 + auto result = RunWslc(L"container list");
103 + VERIFY_IS_TRUE(result.ExitCode.has_value());
104 + VERIFY_ARE_EQUAL(0u, result.ExitCode.value());
105 + VERIFY_IS_TRUE(result.Stderr.has_value());
106 +
107 + const auto expectedStderr = std::format(
108 + L"wsl: {}\r\n", wsl::shared::Localization::MessageWslcFailedToRecoverContainer(string::MultiByteToWide(corruptContainerId)));
109 + VERIFY_ARE_EQUAL(expectedStderr, result.Stderr.value());
110 + }
111 +};
112 +
113 +} // namespace WSLCE2ETests