@samitouri / QOSAMI-WSL / commits / 95b0da42

WSLc Container Open SDK support (#41199)

Adds `WslcOpenContainer` and `WslcSetContainerInitProcessIOCallbacks` to the C SDK. Open uses the same `nameOrId` parameter as the runtime API to find the existing container. Setting the IO callbacks is done currently through the init process settings during create, so the other function allows an opened container to attach callbacks to the init process when it is started. The equivalent change to the WinRT surface was to add `Session::OpenContainer` that also accepts the init process output mode setting.

JohnMcPMS committed Jul 31, 2026 at 10:05 UTC 95b0da42fa270c4d75f57c920b4b0d059b2fa9ad
16 files changed +430 -10
src/windows/WslcSDK/winrt/Container.cpp
+29 -3
@@ -34,6 +34,28 @@ Container::Container(WslcSession session, winrt::Microsoft::WSL::Containers::Con
34 THROW_MSG_IF_FAILED(hr, errorMessage);
35 }
36
37 +Container::Container(WslcContainer handle, winrt::Microsoft::WSL::Containers::ProcessOutputMode initProcessOutputMode) :
38 + m_opened(true)
39 +{
40 + m_container.reset(handle);
41 +
42 + m_initProcess = winrt::make_self<implementation::Process>(initProcessOutputMode);
43 +
44 + if (initProcessOutputMode == ProcessOutputMode::Event)
45 + {
46 + auto callbacks = m_initProcess->GetEventCallbacks();
47 + winrt::check_hresult(WslcSetContainerInitProcessIOCallbacks(m_container.get(), &callbacks, m_initProcess.get()));
48 + }
49 +
50 + // Best-effort: attach the init process handle if one is available. The container may not
51 + // have been started yet, or may not have an init process at all.
52 + WslcProcess initHandle{};
53 + if (SUCCEEDED(WslcGetContainerInitProcess(m_container.get(), &initHandle)) && initHandle)
54 + {
55 + m_initProcess->AttachHandle(initHandle);
56 + }
57 +}
58 +
59 void Container::Start()
60 {
61 auto startFlags = WSLC_CONTAINER_START_FLAG_NONE;
@@ -51,9 +73,13 @@ void Container::Start()
73
74 if (m_initProcess)
75 {
54 - WslcProcess initHandle;
55 - winrt::check_hresult(WslcGetContainerInitProcess(ToHandle(), &initHandle));
56 - m_initProcess->AttachHandle(initHandle);
76 + WslcProcess initHandle{};
77 + hr = WslcGetContainerInitProcess(ToHandle(), &initHandle);
78 + THROW_HR_IF(hr, !m_opened && FAILED(hr));
79 + if (SUCCEEDED(hr) && initHandle)
80 + {
81 + m_initProcess->AttachHandle(initHandle);
82 + }
83 }
84 }
85
src/windows/WslcSDK/winrt/Container.h
+3
@@ -22,6 +22,8 @@ struct Container : ContainerT<Container>
22 {
23 Container() = default;
24 Container(WslcSession session, winrt::Microsoft::WSL::Containers::ContainerSettings const& settings);
25 + // Package-internal: wraps an already-opened container handle (from WslcOpenContainer).
26 + explicit Container(WslcContainer handle, winrt::Microsoft::WSL::Containers::ProcessOutputMode initProcessOutputMode);
27
28 void Start();
29 void Stop(winrt::Microsoft::WSL::Containers::Signal const& signal, winrt::Windows::Foundation::TimeSpan timeout);
@@ -41,6 +43,7 @@ private:
43 void EnsureNotClosed() const;
44
45 winrt::com_ptr<implementation::Process> m_initProcess;
46 + bool m_opened = false;
47
48 // Releasing the container handle will end the processes and disconnect the callbacks.
49 // Keep this at the end so that it is released first, ensuring the init process' events aren't destroyed while they may still be signaled.
src/windows/WslcSDK/winrt/Process.cpp
+17 -5
@@ -34,6 +34,13 @@ Process::Process(winrt::Microsoft::WSL::Containers::ProcessSettings const& setti
34 }
35 }
36
37 +Process::Process(winrt::Microsoft::WSL::Containers::ProcessOutputMode outputMode) : m_outputMode(outputMode)
38 +{
39 + // No ProcessSettings; used for opened-container paths where output mode is set independently.
40 + // For Event mode, callers are responsible for registering callbacks via
41 + // WslcSetContainerInitProcessIOCallbacks using the OutputCallback/ExitCallback statics.
42 +}
43 +
44 void Process::ApplyCallbacksToSettings()
45 {
46 // Callbacks are only used with OutputMode::Event.
@@ -45,11 +52,7 @@ void Process::ApplyCallbacksToSettings()
52
53 auto settingsPtr = GetStructPointer(m_settings);
54
48 - WslcProcessCallbacks callbacks{};
49 - callbacks.onExit = ExitCallback;
50 - callbacks.onStdOut = OutputCallback;
51 - callbacks.onStdErr = OutputCallback;
52 -
55 + WslcProcessCallbacks callbacks = GetEventCallbacks();
56 winrt::check_hresult(WslcSetProcessSettingsCallbacks(settingsPtr, &callbacks, this));
57 }
58
@@ -97,6 +100,15 @@ void Process::AttachHandle(WslcProcess handle)
100 StartWaitingForExit();
101 }
102
103 +WslcProcessCallbacks Process::GetEventCallbacks() const noexcept
104 +{
105 + WslcProcessCallbacks callbacks{};
106 + callbacks.onStdOut = OutputCallback;
107 + callbacks.onStdErr = OutputCallback;
108 + callbacks.onExit = ExitCallback;
109 + return callbacks;
110 +}
111 +
112 ProcessOutputMode Process::OutputMode()
113 {
114 return m_outputMode;
src/windows/WslcSDK/winrt/Process.h
+6
@@ -22,6 +22,9 @@ struct Process : ProcessT<Process>
22 Process() = default;
23 Process(winrt::Microsoft::WSL::Containers::ProcessSettings const& settings); // For the init process
24 Process(winrt::Microsoft::WSL::Containers::Container const& container, winrt::Microsoft::WSL::Containers::ProcessSettings const& settings);
25 + // Package-internal: creates a Process with a fixed output mode but no ProcessSettings struct.
26 + // Used for containers opened via OpenContainer (SetInitProcessOutputMode and InitProcess lazy-fetch paths).
27 + explicit Process(winrt::Microsoft::WSL::Containers::ProcessOutputMode outputMode);
28
29 void Start();
30 uint32_t Pid();
@@ -43,6 +46,9 @@ struct Process : ProcessT<Process>
46 WslcProcess ToHandle();
47 ProcessOutputMode OutputMode();
48 void AttachHandle(WslcProcess handle);
49 + // Returns the WslcProcessCallbacks struct pointing to the static callbacks with 'this' as context.
50 + // Only meaningful when OutputMode is Event; the callbacks fire into m_outputReceivedEvent etc.
51 + WslcProcessCallbacks GetEventCallbacks() const noexcept;
52
53 private:
54 void EnsureStarted() const;
src/windows/WslcSDK/winrt/Session.cpp
+17
@@ -94,6 +94,23 @@ winrt::Microsoft::WSL::Containers::Container Session::CreateContainer(winrt::Mic
94 return winrt::make<implementation::Container>(ToHandle(), containerSettings);
95 }
96
97 +winrt::Microsoft::WSL::Containers::Container Session::OpenContainer(hstring const& nameOrId, winrt::Microsoft::WSL::Containers::ProcessOutputMode const& initProcessOutputMode)
98 +{
99 + EnsureStarted();
100 +
101 + if (nameOrId.empty())
102 + {
103 + throw winrt::hresult_invalid_argument(L"nameOrId cannot be empty");
104 + }
105 +
106 + wil::unique_any<WslcContainer, decltype(&WslcReleaseContainer), &WslcReleaseContainer> containerHandle;
107 + wil::unique_cotaskmem_string errorMessage;
108 + auto hr = WslcOpenContainer(ToHandle(), winrt::to_string(nameOrId).c_str(), containerHandle.put(), errorMessage.put());
109 + THROW_MSG_IF_FAILED(hr, errorMessage);
110 +
111 + return winrt::make<implementation::Container>(containerHandle.release(), initProcessOutputMode);
112 +}
113 +
114 void Session::PullImage(winrt::Microsoft::WSL::Containers::PullImageOptions const& options)
115 {
116 if (!options)
src/windows/WslcSDK/winrt/Session.h
+2
@@ -24,6 +24,8 @@ struct Session : SessionT<Session>
24 void Start();
25 void Terminate();
26 winrt::Microsoft::WSL::Containers::Container CreateContainer(winrt::Microsoft::WSL::Containers::ContainerSettings const& containerSettings);
27 + winrt::Microsoft::WSL::Containers::Container OpenContainer(
28 + hstring const& nameOrId, winrt::Microsoft::WSL::Containers::ProcessOutputMode const& initProcessOutputMode);
29 void PullImage(winrt::Microsoft::WSL::Containers::PullImageOptions const& options);
30 winrt::Windows::Foundation::IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::ImageProgress> PullImageAsync(
31 winrt::Microsoft::WSL::Containers::PullImageOptions options);
src/windows/WslcSDK/winrt/wslcsdk.idl
+1
@@ -69,6 +69,7 @@ namespace Microsoft.WSL.Containers
69 void Terminate();
70
71 Container CreateContainer(ContainerSettings containerSettings);
72 + Container OpenContainer(String nameOrId, ProcessOutputMode initProcessOutputMode);
73
74 void PullImage(PullImageOptions options);
75 Windows.Foundation.IAsyncActionWithProgress<ImageProgress> PullImageAsync(PullImageOptions options);
src/windows/WslcSDK/wslcsdk.cpp
+37
@@ -908,6 +908,43 @@ try
908 }
909 CATCH_RETURN();
910
911 +STDAPI WslcOpenContainer(_In_ WslcSession session, _In_z_ PCSTR nameOrId, _Out_ WslcContainer* container, _Outptr_opt_result_z_ PWSTR* errorMessage)
912 +try
913 +{
914 + RETURN_HR_IF_NULL(E_POINTER, container);
915 + *container = nullptr;
916 + RETURN_HR_IF_NULL(E_POINTER, nameOrId);
917 + ErrorInfoWrapper errorInfoWrapper{errorMessage};
918 + auto internalSession = CheckAndGetInternalType(session);
919 + RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalSession->session);
920 +
921 + auto result = std::make_unique<WslcContainerImpl>();
922 +
923 + if (SUCCEEDED(errorInfoWrapper.CaptureResult(internalSession->session->OpenContainer(nameOrId, &result->container))))
924 + {
925 + wsl::windows::common::security::ConfigureForCOMImpersonation(result->container.get());
926 + *container = reinterpret_cast<WslcContainer>(result.release());
927 + }
928 +
929 + return errorInfoWrapper;
930 +}
931 +CATCH_RETURN();
932 +
933 +STDAPI WslcSetContainerInitProcessIOCallbacks(_In_ WslcContainer container, _In_ const WslcProcessCallbacks* callbacks, _In_opt_ PVOID context)
934 +try
935 +{
936 + RETURN_HR_IF_NULL(E_POINTER, callbacks);
937 + auto internalType = CheckAndGetInternalType(container);
938 +
939 + internalType->ioCallbackOptions.onStdOut = callbacks->onStdOut;
940 + internalType->ioCallbackOptions.onStdErr = callbacks->onStdErr;
941 + internalType->ioCallbackOptions.onExit = callbacks->onExit;
942 + internalType->ioCallbackOptions.callbackContext = context;
943 +
944 + return S_OK;
945 +}
946 +CATCH_RETURN();
947 +
948 STDAPI WslcStartContainer(_In_ WslcContainer container, _In_ WslcContainerStartFlags flags, _Outptr_opt_result_z_ PWSTR* errorMessage)
949 try
950 {
src/windows/WslcSDK/wslcsdk.def
+2
@@ -10,6 +10,7 @@ WslcInitProcessSettings
10
11 WslcCreateSession
12 WslcCreateContainer
13 +WslcOpenContainer
14
15 WslcReleaseSession
16 WslcReleaseContainer
@@ -52,6 +53,7 @@ WslcSetContainerSettingsPortMappings
53
54 WslcCreateContainerProcess
55 WslcStartContainer
56 +WslcSetContainerInitProcessIOCallbacks
57 WslcInspectContainer
58 WslcGetContainerState
59 WslcStopContainer
src/windows/WslcSDK/wslcsdk.h
+11
@@ -229,6 +229,12 @@ STDAPI WslcInitContainerSettings(_In_ PCSTR imageName, _Out_ WslcContainerSettin
229
230 STDAPI WslcCreateContainer(_In_ WslcSession session, _In_ const WslcContainerSettings* containerSettings, _Out_ WslcContainer* container, _Outptr_opt_result_z_ PWSTR* errorMessage);
231
232 +// Opens an existing container by name, full ID, or partial ID prefix.
233 +// The returned WslcContainer handle is owned by the caller; release it with WslcReleaseContainer.
234 +// Returns WSLC_E_CONTAINER_NOT_FOUND if no matching container exists, or
235 +// WSLC_E_CONTAINER_PREFIX_AMBIGUOUS if the given prefix matches more than one container.
236 +STDAPI WslcOpenContainer(_In_ WslcSession session, _In_z_ PCSTR nameOrId, _Out_ WslcContainer* container, _Outptr_opt_result_z_ PWSTR* errorMessage);
237 +
238 STDAPI WslcStartContainer(_In_ WslcContainer container, _In_ WslcContainerStartFlags flags, _Outptr_opt_result_z_ PWSTR* errorMessage);
239
240 // OPTIONAL CONTAINER SETTINGS
@@ -400,6 +406,11 @@ typedef struct WslcProcessCallbacks
406
407 STDAPI WslcSetProcessSettingsCallbacks(_In_ WslcProcessSettings* processSettings, _In_ const WslcProcessCallbacks* callbacks, _In_opt_ PVOID context);
408
409 +// Sets IO callbacks for the init process of a container.
410 +// Must be called before WslcStartContainer (with WSLC_CONTAINER_START_FLAG_ATTACH).
411 +// Has no effect on a container that is already running.
412 +STDAPI WslcSetContainerInitProcessIOCallbacks(_In_ WslcContainer container, _In_ const WslcProcessCallbacks* callbacks, _In_opt_ PVOID context);
413 +
414 // PROCESS MANAGEMENT
415
416 STDAPI WslcGetProcessPid(_In_ WslcProcess process, _Out_ uint32_t* pid);
src/windows/service/exe/WSLCSessionManager.cpp
+2 -2
@@ -581,11 +581,11 @@ try
581 TraceLoggingValue(ClientVersion->Minor, "Minor"),
582 TraceLoggingValue(ClientVersion->Revision, "Revision"));
583
584 - constexpr std::tuple<uint32_t, uint32_t, uint32_t> c_minClientVersion{2, 9, 0};
584 + constexpr std::tuple<uint32_t, uint32_t, uint32_t> c_minClientVersion{2, 9, 5};
585
586 const std::tuple<uint32_t, uint32_t, uint32_t> clientVersion{ClientVersion->Major, ClientVersion->Minor, ClientVersion->Revision};
587
588 - // For now set 2.9.0 as the floor version. Also support if the client version exactly matches ours to cover builds before 2.9.0.
588 + // Support anything at or above the minimum and when the client version exactly matches ours to cover dev builds before the break is released.
589 *IsSupported = (clientVersion >= c_minClientVersion || wsl::shared::PackageVersion == clientVersion);
590
591 return S_OK;
src/windows/service/inc/WSLCCompat.idl
+1
@@ -325,6 +325,7 @@ interface IWSLCCompatSession : IUnknown
325
326 // Container management.
327 HRESULT CreateContainer([in] const WSLCCompatContainerOptions* Options, [in, unique] IWSLCCompatWarningCallback* WarningCallback, [out] IWSLCCompatContainer** Container);
328 + HRESULT OpenContainer([in] LPCSTR NameOrId, [out] IWSLCCompatContainer** Container);
329
330 // Terminate the VM and containers.
331 HRESULT Terminate();
src/windows/wslcsession/WSLCSession.cpp
+15
@@ -3391,6 +3391,21 @@ try
3391 }
3392 CATCH_RETURN();
3393
3394 +HRESULT WSLCSession::OpenContainer(LPCSTR NameOrId, IWSLCCompatContainer** Container)
3395 +try
3396 +{
3397 + RETURN_HR_IF_NULL(E_POINTER, NameOrId);
3398 + RETURN_HR_IF_NULL(E_POINTER, Container);
3399 + *Container = nullptr;
3400 +
3401 + Microsoft::WRL::ComPtr<IWSLCContainer> container;
3402 + RETURN_IF_FAILED(OpenContainer(NameOrId, &container));
3403 + RETURN_HR_IF_NULL(E_UNEXPECTED, container);
3404 +
3405 + return container.CopyTo(Container);
3406 +}
3407 +CATCH_RETURN();
3408 +
3409 HRESULT WSLCSession::CreateVolume(const WSLCCompatVolumeOptions* Options, WSLCCompatVolumeInformation* VolumeInfo)
3410 try
3411 {
src/windows/wslcsession/WSLCSession.h
+1
@@ -236,6 +236,7 @@ public:
236 _In_ const WSLCCompatContainerOptions* Options,
237 _In_opt_ IWSLCCompatWarningCallback* WarningCallback,
238 _Out_ IWSLCCompatContainer** Container) override;
239 + IFACEMETHOD(OpenContainer)(_In_ LPCSTR NameOrId, _Out_ IWSLCCompatContainer** Container) override;
240 IFACEMETHOD(CreateVolume)(_In_ const WSLCCompatVolumeOptions* Options, _Out_ WSLCCompatVolumeInformation* VolumeInfo) override;
241 IFACEMETHOD(RegisterCrashDumpCallback)(_In_ IWSLCCompatCrashDumpCallback* Callback, _Out_ IUnknown** Subscription) override;
242
test/windows/WslcSdkTests.cpp
+143
@@ -56,6 +56,18 @@ void CloseContainer(WslcContainer container)
56
57 using UniqueContainer = wil::unique_any<WslcContainer, decltype(CloseContainer), CloseContainer>;
58
59 +// Release-only handle: does not stop or delete the container.
60 +// Used when a second handle to the same container is needed (e.g., OpenContainer tests).
61 +void ReleaseContainerHandle(WslcContainer container)
62 +{
63 + if (container)
64 + {
65 + THROW_IF_FAILED(WslcReleaseContainer(container));
66 + }
67 +}
68 +
69 +using UniqueContainerReleaseOnly = wil::unique_any<WslcContainer, decltype(ReleaseContainerHandle), ReleaseContainerHandle>;
70 +
71 void CloseProcess(WslcProcess process)
72 {
73 if (process)
@@ -2914,4 +2926,135 @@ class WslcSdkTests
2926 VERIFY_ARE_EQUAL(state, WSLC_CONTAINER_STATE_EXITED);
2927 }
2928 }
2929 +
2930 + WSLC_TEST_METHOD(OpenContainer)
2931 + {
2932 + constexpr auto c_containerName = "wslc-sdk-open-test";
2933 +
2934 + WslcContainerSettings containerSettings;
2935 + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings));
2936 + VERIFY_SUCCEEDED(WslcSetContainerSettingsName(&containerSettings, c_containerName));
2937 +
2938 + UniqueContainer created;
2939 + VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &created, nullptr));
2940 +
2941 + CHAR createdId[WSLC_CONTAINER_ID_BUFFER_SIZE]{};
2942 + VERIFY_SUCCEEDED(WslcGetContainerID(created.get(), createdId));
2943 +
2944 + // Positive: open by name — IDs must match.
2945 + {
2946 + UniqueContainerReleaseOnly opened;
2947 + VERIFY_SUCCEEDED(WslcOpenContainer(m_defaultSession, c_containerName, &opened, nullptr));
2948 + VERIFY_IS_NOT_NULL(opened.get());
2949 +
2950 + CHAR openedId[WSLC_CONTAINER_ID_BUFFER_SIZE]{};
2951 + VERIFY_SUCCEEDED(WslcGetContainerID(opened.get(), openedId));
2952 +
2953 + VERIFY_ARE_EQUAL(strcmp(createdId, openedId), 0);
2954 + }
2955 +
2956 + // Positive: open by full 64-character ID.
2957 + {
2958 + UniqueContainerReleaseOnly opened;
2959 + VERIFY_SUCCEEDED(WslcOpenContainer(m_defaultSession, createdId, &opened, nullptr));
2960 + VERIFY_IS_NOT_NULL(opened.get());
2961 + }
2962 +
2963 + // Positive: open by partial ID prefix (12 hex chars — standard short ID).
2964 + {
2965 + createdId[12] = '\0';
2966 +
2967 + UniqueContainerReleaseOnly opened;
2968 + VERIFY_SUCCEEDED(WslcOpenContainer(m_defaultSession, createdId, &opened, nullptr));
2969 + VERIFY_IS_NOT_NULL(opened.get());
2970 + }
2971 +
2972 + // Negative: non-existent name must fail with WSLC_E_CONTAINER_NOT_FOUND.
2973 + {
2974 + UniqueContainerReleaseOnly opened;
2975 + VERIFY_ARE_EQUAL(WslcOpenContainer(m_defaultSession, "no-such-container", &opened, nullptr), WSLC_E_CONTAINER_NOT_FOUND);
2976 + VERIFY_IS_NULL(opened.get());
2977 + }
2978 +
2979 + // Negative: null session must fail with E_POINTER.
2980 + {
2981 + UniqueContainerReleaseOnly opened;
2982 + VERIFY_ARE_EQUAL(WslcOpenContainer(nullptr, c_containerName, &opened, nullptr), E_POINTER);
2983 + }
2984 +
2985 + // Negative: null nameOrId must fail with E_POINTER.
2986 + {
2987 + UniqueContainerReleaseOnly opened;
2988 + VERIFY_ARE_EQUAL(WslcOpenContainer(m_defaultSession, nullptr, &opened, nullptr), E_POINTER);
2989 + }
2990 +
2991 + // Negative: null container output pointer must fail with E_POINTER.
2992 + VERIFY_ARE_EQUAL(WslcOpenContainer(m_defaultSession, c_containerName, nullptr, nullptr), E_POINTER);
2993 + }
2994 +
2995 + WSLC_TEST_METHOD(OpenContainerIOCallbacks)
2996 + {
2997 + // Verify that IO callbacks installed via WslcSetContainerInitProcessIOCallbacks fire when
2998 + // the opened container is started with WSLC_CONTAINER_START_FLAG_ATTACH.
2999 + constexpr auto c_containerName = "wslc-sdk-opencb-test";
3000 +
3001 + WslcProcessSettings procSettings;
3002 + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings));
3003 + PCSTR argv[] = {"/bin/sh", "-c", "echo STDOUT && echo STDERR >&2"};
3004 + VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&procSettings, argv, ARRAYSIZE(argv)));
3005 +
3006 + WslcContainerSettings containerSettings;
3007 + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings));
3008 + VERIFY_SUCCEEDED(WslcSetContainerSettingsName(&containerSettings, c_containerName));
3009 + VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings, &procSettings));
3010 +
3011 + UniqueContainer created;
3012 + VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &created, nullptr));
3013 +
3014 + UniqueContainerReleaseOnly opened;
3015 + VERIFY_SUCCEEDED(WslcOpenContainer(m_defaultSession, c_containerName, &opened, nullptr));
3016 +
3017 + struct Context
3018 + {
3019 + std::string stdoutData;
3020 + std::string stderrData;
3021 + wil::unique_event exitEvent{wil::EventOptions::ManualReset};
3022 + } ctx;
3023 +
3024 + auto ioCb = [](WslcProcessIOHandle ioHandle, const BYTE* data, uint32_t size, PVOID c) {
3025 + auto& target = (ioHandle == WSLC_PROCESS_IO_HANDLE_STDOUT) ? static_cast<Context*>(c)->stdoutData
3026 + : static_cast<Context*>(c)->stderrData;
3027 + target.append(reinterpret_cast<const char*>(data), size);
3028 + };
3029 +
3030 + auto exitCb = [](INT32, PVOID c) { static_cast<Context*>(c)->exitEvent.SetEvent(); };
3031 +
3032 + WslcProcessCallbacks callbacks{};
3033 + callbacks.onStdOut = ioCb;
3034 + callbacks.onStdErr = ioCb;
3035 + callbacks.onExit = exitCb;
3036 + VERIFY_SUCCEEDED(WslcSetContainerInitProcessIOCallbacks(opened.get(), &callbacks, &ctx));
3037 +
3038 + VERIFY_SUCCEEDED(WslcStartContainer(opened.get(), WSLC_CONTAINER_START_FLAG_ATTACH, nullptr));
3039 +
3040 + VERIFY_ARE_EQUAL(WaitForSingleObject(ctx.exitEvent.get(), 30 * 1000), static_cast<DWORD>(WAIT_OBJECT_0));
3041 +
3042 + // Release the opened handle first to flush all pending IO callbacks before asserting output.
3043 + opened.reset();
3044 +
3045 + VERIFY_ARE_EQUAL(ctx.stdoutData, "STDOUT\n");
3046 + VERIFY_ARE_EQUAL(ctx.stderrData, "STDERR\n");
3047 + }
3048 +
3049 + WSLC_TEST_METHOD(SetContainerInitProcessIOCallbacksNullCallbacks)
3050 + {
3051 + WslcContainerSettings containerSettings;
3052 + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings));
3053 +
3054 + UniqueContainer container;
3055 + VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &container, nullptr));
3056 +
3057 + // Null callbacks pointer must fail with E_POINTER.
3058 + VERIFY_ARE_EQUAL(WslcSetContainerInitProcessIOCallbacks(container.get(), nullptr, nullptr), E_POINTER);
3059 + }
3060 };
test/windows/WslcSdkWinRTTests.cpp
+143
@@ -1853,4 +1853,147 @@ class WslcSdkWinRtTests
1853
1854 gpuSession.Terminate();
1855 }
1856 +
1857 + WSLC_TEST_METHOD(OpenContainer)
1858 + {
1859 + constexpr auto c_containerName = L"wslc-winrt-open-test";
1860 +
1861 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1862 + containerSettings.Name(c_containerName);
1863 +
1864 + auto container = m_defaultSession.CreateContainer(containerSettings);
1865 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1866 +
1867 + const auto createdId = container.Id();
1868 +
1869 + // Positive: open by name — IDs must match.
1870 + {
1871 + auto opened = m_defaultSession.OpenContainer(c_containerName, WSLCSDK::ProcessOutputMode::Discard);
1872 + VERIFY_ARE_EQUAL(opened.Id(), createdId);
1873 + }
1874 +
1875 + // Positive: open by full 64-character ID.
1876 + {
1877 + auto opened = m_defaultSession.OpenContainer(createdId, WSLCSDK::ProcessOutputMode::Discard);
1878 + VERIFY_ARE_EQUAL(opened.Id(), createdId);
1879 + }
1880 +
1881 + // Positive: open by partial ID prefix (12 hex chars — standard short ID).
1882 + {
1883 + const auto shortId = winrt::hstring(std::wstring(createdId).substr(0, 12));
1884 + auto opened = m_defaultSession.OpenContainer(shortId, WSLCSDK::ProcessOutputMode::Discard);
1885 + VERIFY_ARE_EQUAL(opened.Id(), createdId);
1886 + }
1887 +
1888 + // Negative: non-existent name must throw WSLC_E_CONTAINER_NOT_FOUND.
1889 + VERIFY_THROWS_HR(m_defaultSession.OpenContainer(L"no-such-container", WSLCSDK::ProcessOutputMode::Discard), WSLC_E_CONTAINER_NOT_FOUND);
1890 +
1891 + // Negative: empty nameOrId must throw E_INVALIDARG.
1892 + VERIFY_THROWS_HR(m_defaultSession.OpenContainer(L"", WSLCSDK::ProcessOutputMode::Discard), E_INVALIDARG);
1893 + }
1894 +
1895 + WSLC_TEST_METHOD(OpenContainerInitProcess)
1896 + {
1897 + // Verify InitProcess() behaviour for an opened container:
1898 + // - Before Start(), InitProcess() succeeds but the underlying process handle is not yet
1899 + // attached, so methods that require a running process (Pid, State) throw.
1900 + // - After Start(), the handle is attached and the process is fully usable.
1901 + constexpr auto c_containerName = L"wslc-winrt-open-initproc-test";
1902 +
1903 + auto procSettings = WSLCSDK::ProcessSettings();
1904 + procSettings.CommandLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"5"}));
1905 +
1906 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1907 + containerSettings.InitProcess(procSettings);
1908 + containerSettings.Name(c_containerName);
1909 +
1910 + auto container = m_defaultSession.CreateContainer(containerSettings);
1911 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1912 +
1913 + auto opened = m_defaultSession.OpenContainer(c_containerName, WSLCSDK::ProcessOutputMode::Stream);
1914 +
1915 + // InitProcess() must succeed even before Start() (m_initProcess is always created for opened containers).
1916 + auto initProcess = opened.InitProcess();
1917 +
1918 + // The process handle has not been attached yet — Pid() must throw.
1919 + VERIFY_THROWS_HR(std::ignore = initProcess.Pid(), E_ILLEGAL_METHOD_CALL);
1920 +
1921 + // Start the opened container; the init process handle will be attached inside Start().
1922 + opened.Start();
1923 +
1924 + // Now the init process handle is attached — Pid() must succeed and return a non-zero PID.
1925 + VERIFY_IS_GREATER_THAN(initProcess.Pid(), 0u);
1926 +
1927 + opened.Stop(WSLCSDK::Signal::SIGKILL, TimeSpan::zero());
1928 + }
1929 +
1930 + WSLC_TEST_METHOD(OpenContainerAndStartStreamMode)
1931 + {
1932 + // Verify that opening a container with ProcessOutputMode::Stream allows reading output
1933 + // from the init process via GetOutputStream after Start().
1934 + constexpr auto c_containerName = L"wslc-winrt-open-stream-test";
1935 +
1936 + auto procSettings = WSLCSDK::ProcessSettings();
1937 + procSettings.CommandLine(
1938 + winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"echo STDOUT && echo STDERR >&2"}));
1939 +
1940 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1941 + containerSettings.InitProcess(procSettings);
1942 + containerSettings.Name(c_containerName);
1943 +
1944 + auto container = m_defaultSession.CreateContainer(containerSettings);
1945 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1946 +
1947 + auto opened = m_defaultSession.OpenContainer(c_containerName, WSLCSDK::ProcessOutputMode::Stream);
1948 +
1949 + StartContainerAndWaitForInitProcessExit(opened);
1950 + auto output = GetProcessOutput(opened.InitProcess());
1951 +
1952 + VERIFY_ARE_EQUAL(output.ExitCode, 0u);
1953 + VERIFY_ARE_EQUAL(output.StandardOutput, L"STDOUT\n");
1954 + VERIFY_ARE_EQUAL(output.StandardError, L"STDERR\n");
1955 + }
1956 +
1957 + WSLC_TEST_METHOD(OpenContainerAndStartEventMode)
1958 + {
1959 + // Verify that opening a container with ProcessOutputMode::Event delivers output via
1960 + // OutputReceived / ErrorReceived events, and that Exited fires with the correct exit code.
1961 + constexpr auto c_containerName = L"wslc-winrt-open-event-test";
1962 +
1963 + auto procSettings = WSLCSDK::ProcessSettings();
1964 + procSettings.CommandLine(
1965 + winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"echo EVENT_OUT && echo EVENT_ERR >&2"}));
1966 +
1967 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1968 + containerSettings.InitProcess(procSettings);
1969 + containerSettings.Name(c_containerName);
1970 +
1971 + auto container = m_defaultSession.CreateContainer(containerSettings);
1972 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1973 +
1974 + auto opened = m_defaultSession.OpenContainer(c_containerName, WSLCSDK::ProcessOutputMode::Event);
1975 + auto initProcess = opened.InitProcess();
1976 +
1977 + std::string stdoutAccum, stderrAccum;
1978 + std::promise<int32_t> exitPromise;
1979 +
1980 + auto stdoutRevoker = initProcess.OutputReceived(winrt::auto_revoke, [&](winrt::array_view<const uint8_t> data) {
1981 + stdoutAccum.append(reinterpret_cast<const char*>(data.data()), data.size());
1982 + });
1983 +
1984 + auto stderrRevoker = initProcess.ErrorReceived(winrt::auto_revoke, [&](winrt::array_view<const uint8_t> data) {
1985 + stderrAccum.append(reinterpret_cast<const char*>(data.data()), data.size());
1986 + });
1987 +
1988 + auto exitRevoker = initProcess.Exited(winrt::auto_revoke, [&](int32_t exitCode) { exitPromise.set_value(exitCode); });
1989 +
1990 + opened.Start();
1991 +
1992 + auto exitFuture = exitPromise.get_future();
1993 + VERIFY_ARE_EQUAL(exitFuture.wait_for(30s), std::future_status::ready);
1994 + VERIFY_ARE_EQUAL(exitFuture.get(), 0);
1995 +
1996 + VERIFY_ARE_EQUAL(stdoutAccum, "EVENT_OUT\n");
1997 + VERIFY_ARE_EQUAL(stderrAccum, "EVENT_ERR\n");
1998 + }
1999 };