From 4ac05678eb94b63a660bd27ff251b5a7319e4cd6 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Mon, 16 Mar 2026 17:39:09 -0700 Subject: [PATCH 1/8] checkpoint --- src/windows/WslcSDK/WslcsdkPrivate.h | 22 ++++++++++++++++- src/windows/WslcSDK/wslcsdk.cpp | 35 +++++++++++++++++++++------- src/windows/WslcSDK/wslcsdk.def | 2 +- src/windows/WslcSDK/wslcsdk.h | 10 ++++---- 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/windows/WslcSDK/WslcsdkPrivate.h b/src/windows/WslcSDK/WslcsdkPrivate.h index cab81c615..67b17a3c8 100644 --- a/src/windows/WslcSDK/WslcsdkPrivate.h +++ b/src/windows/WslcSDK/WslcsdkPrivate.h @@ -53,11 +53,15 @@ typedef struct WslcContainerProcessOptionsInternal PCSTR const* environment; uint32_t environmentCount; PCSTR currentDirectory; + WslcStdIOCallback stdOutCallback; + PVOID stdOutCallbackContext; + WslcStdIOCallback stdErrCallback; + PVOID stdErrCallbackContext; } WslcContainerProcessOptionsInternal; static_assert( sizeof(WslcContainerProcessOptionsInternal) == WSLC_CONTAINER_PROCESS_OPTIONS_SIZE, - "WSLC_CONTAINER_PROCESS_OPTIONS_INTERNAL must be 48 bytes"); + "WSLC_CONTAINER_PROCESS_OPTIONS_INTERNAL must be 72 bytes"); static_assert( __alignof(WslcContainerProcessOptionsInternal) == WSLC_CONTAINER_PROCESS_OPTIONS_ALIGNMENT, "WSLC_CONTAINER_PROCESS_OPTIONS_INTERNAL must be 8-byte aligned"); @@ -103,9 +107,24 @@ struct WslcSessionImpl WslcSessionImpl* GetInternalType(WslcSession handle); +// Holds IO callback objects. +struct IOCallbackLifetime +{ + IOCallbackLifetime(); + ~IOCallbackLifetime(); + + void Cancel(); + + static bool HasIOCallback(const WslcContainerProcessOptionsInternal& options); + +private: + std::thread m_thread; +}; + struct WslcContainerImpl { wil::com_ptr container; + std::shared_ptr ioCallbacks; }; WslcContainerImpl* GetInternalType(WslcContainer handle); @@ -113,6 +132,7 @@ WslcContainerImpl* GetInternalType(WslcContainer handle); struct WslcProcessImpl { wil::com_ptr process; + std::shared_ptr ioCallbacks; }; WslcProcessImpl* GetInternalType(WslcProcess handle); diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 45c097e8c..219fa045a 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -535,6 +535,12 @@ try if (SUCCEEDED(errorInfoWrapper.CaptureResult(internalSession->session->CreateContainer(&containerOptions, &result->container)))) { wsl::windows::common::security::ConfigureForCOMImpersonation(result->container.get()); + + if (HasIOCallback(internalContainerSettings->initProcessOptions)) + { + result->container->GetInitProcess(); + } + *container = reinterpret_cast(result.release()); } @@ -914,19 +920,30 @@ try } CATCH_RETURN(); -STDAPI WslcSetProcessSettingsIoCallback( - _In_ WslcProcessSettings* processSettings, _In_ WslcProcessIoHandle ioHandle, _In_opt_ WslcStdIOCallback stdIOCallback, _In_opt_ PVOID context) +STDAPI WslcSetProcessSettingsIOCallback( + _In_ WslcProcessSettings* processSettings, _In_ WslcProcessIOHandle ioHandle, _In_opt_ WslcStdIOCallback stdIOCallback, _In_opt_ PVOID context) try { - UNREFERENCED_PARAMETER(processSettings); - UNREFERENCED_PARAMETER(ioHandle); - UNREFERENCED_PARAMETER(stdIOCallback); - UNREFERENCED_PARAMETER(context); - return E_NOTIMPL; + auto internalType = CheckAndGetInternalType(processSettings); + RETURN_HR_IF(E_INVALIDARG, ioHandle != WSLC_PROCESS_IO_HANDLE_STDOUT && ioHandle != WSLC_PROCESS_IO_HANDLE_STDERR); + RETURN_HR_IF(E_INVALIDARG, stdIOCallback == nullptr && context != nullptr); + + if (ioHandle == WSLC_PROCESS_IO_HANDLE_STDOUT) + { + internalType->stdOutCallback = stdIOCallback; + internalType->stdOutCallbackContext = context; + } + else if (ioHandle == WSLC_PROCESS_IO_HANDLE_STDERR) + { + internalType->stdErrCallback = stdIOCallback; + internalType->stdErrCallbackContext = context; + } + + return S_OK; } CATCH_RETURN(); -STDAPI WslcGetProcessIOHandle(_In_ WslcProcess process, _In_ WslcProcessIoHandle ioHandle, _Out_ HANDLE* handle) +STDAPI WslcGetProcessIOHandle(_In_ WslcProcess process, _In_ WslcProcessIOHandle ioHandle, _Out_ HANDLE* handle) try { auto internalType = CheckAndGetInternalType(process); @@ -938,7 +955,7 @@ try ULONG ulongHandle = 0; HRESULT hr = internalType->process->GetStdHandle( - static_cast(static_cast>(ioHandle)), &ulongHandle); + static_cast(static_cast>(ioHandle)), &ulongHandle); if (SUCCEEDED_LOG(hr)) { diff --git a/src/windows/WslcSDK/wslcsdk.def b/src/windows/WslcSDK/wslcsdk.def index 5868ca805..f62fac23e 100644 --- a/src/windows/WslcSDK/wslcsdk.def +++ b/src/windows/WslcSDK/wslcsdk.def @@ -46,7 +46,7 @@ WslcDeleteContainer WslcGetContainerID WslcGetContainerInitProcess -WslcSetProcessSettingsIoCallback +WslcSetProcessSettingsIOCallback WslcSetProcessSettingsCurrentDirectory WslcSetProcessSettingsCmdLine WslcSetProcessSettingsEnvVariables diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index ce5d7abf1..74d49ea65 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -43,7 +43,7 @@ typedef struct WslcContainerSettings DECLARE_HANDLE(WslcContainer); // Process values -#define WSLC_CONTAINER_PROCESS_OPTIONS_SIZE 40 +#define WSLC_CONTAINER_PROCESS_OPTIONS_SIZE 72 #define WSLC_CONTAINER_PROCESS_OPTIONS_ALIGNMENT 8 typedef struct WslcProcessSettings { @@ -287,7 +287,7 @@ STDAPI WslcSetProcessSettingsEnvVariables(_In_ WslcProcessSettings* processSetti // - The buffer is not null-terminated; it is a raw byte sequence. // typedef __callback void(CALLBACK* WslcStdIOCallback)(_In_reads_bytes_(dataSize) const BYTE* data, _In_ uint32_t dataSize, _In_opt_ PVOID context); -typedef enum WslcProcessIoHandle +typedef enum WslcProcessIOHandle { WSLC_PROCESS_IO_HANDLE_STDIN = 0, WSLC_PROCESS_IO_HANDLE_STDOUT = 1, @@ -295,8 +295,8 @@ typedef enum WslcProcessIoHandle } WslcProcessIoHandle; // Pass in Null for WslcStdIOCallback to clear the callback for the given handle -STDAPI WslcSetProcessSettingsIoCallback( - _In_ WslcProcessSettings* processSettings, _In_ WslcProcessIoHandle ioHandle, _In_opt_ WslcStdIOCallback stdIOCallback, _In_opt_ PVOID context); +STDAPI WslcSetProcessSettingsIOCallback( + _In_ WslcProcessSettings* processSettings, _In_ WslcProcessIOHandle ioHandle, _In_opt_ WslcStdIOCallback stdIOCallback, _In_opt_ PVOID context); // PROCESS MANAGEMENT @@ -318,7 +318,7 @@ STDAPI WslcGetProcessExitCode(_In_ WslcProcess process, _Out_ PINT32 exitCode); STDAPI WslcSignalProcess(_In_ WslcProcess process, _In_ WslcSignal signal); -STDAPI WslcGetProcessIOHandle(_In_ WslcProcess process, _In_ WslcProcessIoHandle ioHandle, _Out_ HANDLE* handle); +STDAPI WslcGetProcessIOHandle(_In_ WslcProcess process, _In_ WslcProcessIOHandle ioHandle, _Out_ HANDLE* handle); STDAPI WslcReleaseProcess(_In_ WslcProcess process); From 5dc7be052c5b8ac5c45dffe725f10b6490d02f07 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 17 Mar 2026 10:53:01 -0700 Subject: [PATCH 2/8] SDK IO callbacks and supporting work --- src/windows/WslcSDK/CMakeLists.txt | 2 + src/windows/WslcSDK/IOCallback.cpp | 79 ++++++++++++++++++++++++++++ src/windows/WslcSDK/IOCallback.h | 37 +++++++++++++ src/windows/WslcSDK/WslcsdkPrivate.h | 33 +++++------- src/windows/WslcSDK/wslcsdk.cpp | 47 +++++++++++------ src/windows/common/relay.cpp | 12 +++++ src/windows/common/relay.hpp | 6 ++- test/windows/WslcSdkTests.cpp | 2 +- 8 files changed, 178 insertions(+), 40 deletions(-) create mode 100644 src/windows/WslcSDK/IOCallback.cpp create mode 100644 src/windows/WslcSDK/IOCallback.h diff --git a/src/windows/WslcSDK/CMakeLists.txt b/src/windows/WslcSDK/CMakeLists.txt index 94e779985..98f445800 100644 --- a/src/windows/WslcSDK/CMakeLists.txt +++ b/src/windows/WslcSDK/CMakeLists.txt @@ -1,10 +1,12 @@ set(SOURCES + IOCallback.cpp ProgressCallback.cpp TerminationCallback.cpp wslcsdk.cpp WslcsdkPrivate.cpp ) set(HEADERS + IOCallback.h ProgressCallback.h TerminationCallback.h wslcsdk.h diff --git a/src/windows/WslcSDK/IOCallback.cpp b/src/windows/WslcSDK/IOCallback.cpp new file mode 100644 index 000000000..14c058398 --- /dev/null +++ b/src/windows/WslcSDK/IOCallback.cpp @@ -0,0 +1,79 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + IOCallback.cpp + +Abstract: + + Holds IO callback objects. + +--*/ +#include "precomp.h" +#include "WslcsdkPrivate.h" + +IOCallback::IOCallback(IWSLAProcess* process, const WslcContainerProcessIOCallbackOptions& options) +{ + if (options.stdOutCallback) + { + auto localCallback = options.stdOutCallback; + auto localContext = options.stdOutCallbackContext; + m_io.AddHandle(std::make_unique( + GetIOHandle(process, WSLC_PROCESS_IO_HANDLE_STDOUT), + [localCallback, localContext](const auto& buffer) { localCallback(reinterpret_cast(buffer.data()), static_cast(buffer.size()), localContext); })); + } + + if (options.stdErrCallback) + { + auto localCallback = options.stdErrCallback; + auto localContext = options.stdErrCallbackContext; + m_io.AddHandle(std::make_unique( + GetIOHandle(process, WSLC_PROCESS_IO_HANDLE_STDERR), [localCallback, localContext](const auto& buffer) { + localCallback(reinterpret_cast(buffer.data()), static_cast(buffer.size()), localContext); + })); + } + + m_thread = std::thread([this]() { + try + { + m_io.Run({}); + } + catch (...){} + }); +} + +IOCallback::~IOCallback() +{ + Cancel(); + if (m_thread.joinable()) + { + m_thread.join(); + } +} + +void IOCallback::Cancel() +{ + m_io.Cancel(); +} + +bool IOCallback::HasIOCallback(const WslcContainerProcessOptionsInternal* options) +{ + return options && HasIOCallback(options->ioCallbacks); +} + +bool IOCallback::HasIOCallback(const WslcContainerProcessIOCallbackOptions& options) +{ + return options.stdOutCallback || options.stdErrCallback; +} + +wil::unique_handle IOCallback::GetIOHandle(IWSLAProcess* process, WslcProcessIOHandle ioHandle) +{ + ULONG ulongHandle = 0; + + THROW_IF_FAILED(process->GetStdHandle( + static_cast(static_cast>(ioHandle)), &ulongHandle)); + + return wil::unique_handle{ULongToHandle(ulongHandle)}; +} diff --git a/src/windows/WslcSDK/IOCallback.h b/src/windows/WslcSDK/IOCallback.h new file mode 100644 index 000000000..6c05e79d1 --- /dev/null +++ b/src/windows/WslcSDK/IOCallback.h @@ -0,0 +1,37 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + IOCallback.h + +Abstract: + + Holds IO callback objects. + +--*/ +#pragma once +#include "wslaservice.h" +#include "wslrelay.h" +#include + +struct WslcContainerProcessIOCallbackOptions; +struct WslcContainerProcessOptionsInternal; + +struct IOCallback +{ + IOCallback(IWSLAProcess* process, const WslcContainerProcessIOCallbackOptions& options); + ~IOCallback(); + + void Cancel(); + + static bool HasIOCallback(const WslcContainerProcessOptionsInternal* options); + static bool HasIOCallback(const WslcContainerProcessIOCallbackOptions& options); + + static wil::unique_handle GetIOHandle(IWSLAProcess* process, WslcProcessIOHandle ioHandle); + +private: + std::thread m_thread; + wsl::windows::common::relay::MultiHandleWait m_io; +}; diff --git a/src/windows/WslcSDK/WslcsdkPrivate.h b/src/windows/WslcSDK/WslcsdkPrivate.h index 67b17a3c8..c0be865d5 100644 --- a/src/windows/WslcSDK/WslcsdkPrivate.h +++ b/src/windows/WslcSDK/WslcsdkPrivate.h @@ -15,6 +15,7 @@ Module Name: #include #include "wslcsdk.h" #include "wslaservice.h" +#include "IOCallback.h" #include #include // COM helpers // #include // handle wrappers @@ -45,6 +46,14 @@ static_assert(std::is_trivial_v, "WSLC_SESSION_OPTIO WslcSessionOptionsInternal* GetInternalType(WslcSessionSettings* settings); +struct WslcContainerProcessIOCallbackOptions +{ + WslcStdIOCallback stdOutCallback; + PVOID stdOutCallbackContext; + WslcStdIOCallback stdErrCallback; + PVOID stdErrCallbackContext; +}; + // PROCESS DEFINITIONS typedef struct WslcContainerProcessOptionsInternal { @@ -53,10 +62,7 @@ typedef struct WslcContainerProcessOptionsInternal PCSTR const* environment; uint32_t environmentCount; PCSTR currentDirectory; - WslcStdIOCallback stdOutCallback; - PVOID stdOutCallbackContext; - WslcStdIOCallback stdErrCallback; - PVOID stdErrCallbackContext; + WslcContainerProcessIOCallbackOptions ioCallbacks; } WslcContainerProcessOptionsInternal; static_assert( @@ -107,24 +113,11 @@ struct WslcSessionImpl WslcSessionImpl* GetInternalType(WslcSession handle); -// Holds IO callback objects. -struct IOCallbackLifetime -{ - IOCallbackLifetime(); - ~IOCallbackLifetime(); - - void Cancel(); - - static bool HasIOCallback(const WslcContainerProcessOptionsInternal& options); - -private: - std::thread m_thread; -}; - struct WslcContainerImpl { wil::com_ptr container; - std::shared_ptr ioCallbacks; + WslcContainerProcessIOCallbackOptions ioCallbackOptions{}; + std::atomic> ioCallbacks; }; WslcContainerImpl* GetInternalType(WslcContainer handle); @@ -132,7 +125,7 @@ WslcContainerImpl* GetInternalType(WslcContainer handle); struct WslcProcessImpl { wil::com_ptr process; - std::shared_ptr ioCallbacks; + std::shared_ptr ioCallbacks; }; WslcProcessImpl* GetInternalType(WslcProcess handle); diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 219fa045a..60b35094c 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -536,9 +536,9 @@ try { wsl::windows::common::security::ConfigureForCOMImpersonation(result->container.get()); - if (HasIOCallback(internalContainerSettings->initProcessOptions)) + if (IOCallback::HasIOCallback(internalContainerSettings->initProcessOptions)) { - result->container->GetInitProcess(); + result->ioCallbackOptions = internalContainerSettings->initProcessOptions->ioCallbacks; } *container = reinterpret_cast(result.release()); @@ -555,7 +555,18 @@ try auto internalType = CheckAndGetInternalType(container); RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalType->container); - return errorInfoWrapper.CaptureResult(internalType->container->Start(ConvertFlags(flags), nullptr)); + if (SUCCEEDED(errorInfoWrapper.CaptureResult(internalType->container->Start(ConvertFlags(flags), nullptr)))) + { + if (IOCallback::HasIOCallback(internalType->ioCallbackOptions)) + { + wil::com_ptr process; + RETURN_IF_FAILED(internalType->container->GetInitProcess(&process)); + wsl::windows::common::security::ConfigureForCOMImpersonation(process.get()); + internalType->ioCallbacks = std::make_shared(process.get(), internalType->ioCallbackOptions); + } + } + + return errorInfoWrapper; } CATCH_RETURN(); @@ -686,6 +697,12 @@ try if (SUCCEEDED(errorInfoWrapper.CaptureResult(internalContainer->container->Exec(&runtimeOptions, nullptr, &result->process)))) { wsl::windows::common::security::ConfigureForCOMImpersonation(result->process.get()); + + if (IOCallback::HasIOCallback(internalProcessSettings)) + { + result->ioCallbacks = std::make_shared(result->process.get(), internalProcessSettings->ioCallbacks); + } + *newProcess = reinterpret_cast(result.release()); } @@ -730,6 +747,9 @@ try RETURN_IF_FAILED(internalType->container->GetInitProcess(&result->process)); wsl::windows::common::security::ConfigureForCOMImpersonation(result->process.get()); + + result->ioCallbacks = internalType->ioCallbacks.load(); + *initProcess = reinterpret_cast(result.release()); return S_OK; @@ -930,13 +950,13 @@ try if (ioHandle == WSLC_PROCESS_IO_HANDLE_STDOUT) { - internalType->stdOutCallback = stdIOCallback; - internalType->stdOutCallbackContext = context; + internalType->ioCallbacks.stdOutCallback = stdIOCallback; + internalType->ioCallbacks.stdOutCallbackContext = context; } else if (ioHandle == WSLC_PROCESS_IO_HANDLE_STDERR) { - internalType->stdErrCallback = stdIOCallback; - internalType->stdErrCallbackContext = context; + internalType->ioCallbacks.stdErrCallback = stdIOCallback; + internalType->ioCallbacks.stdErrCallbackContext = context; } return S_OK; @@ -952,17 +972,10 @@ try *handle = nullptr; - ULONG ulongHandle = 0; - - HRESULT hr = internalType->process->GetStdHandle( - static_cast(static_cast>(ioHandle)), &ulongHandle); + auto result = IOCallback::GetIOHandle(internalType->process.get(), ioHandle); + *handle = result.release(); - if (SUCCEEDED_LOG(hr)) - { - *handle = ULongToHandle(ulongHandle); - } - - return hr; + return S_OK; } CATCH_RETURN(); diff --git a/src/windows/common/relay.cpp b/src/windows/common/relay.cpp index 564510c33..6003c26cb 100644 --- a/src/windows/common/relay.cpp +++ b/src/windows/common/relay.cpp @@ -961,6 +961,18 @@ try } CATCH_LOG() +MultiHandleWait::MultiHandleWait(MultiHandleWait&& other) noexcept +{ + this->operator=(std::move(other)); +} + +MultiHandleWait& MultiHandleWait::operator=(MultiHandleWait&& other) noexcept +{ + this->m_handles = std::move(other.m_handles); + this->m_cancel = other.m_cancel.load(); + return *this; +} + void MultiHandleWait::AddHandle(std::unique_ptr&& handle, Flags flags) { m_handles.emplace_back(flags, std::move(handle)); diff --git a/src/windows/common/relay.hpp b/src/windows/common/relay.hpp index dd4664c1f..cd87cac08 100644 --- a/src/windows/common/relay.hpp +++ b/src/windows/common/relay.hpp @@ -498,7 +498,6 @@ class MultiHandleWait { public: NON_COPYABLE(MultiHandleWait); - DEFAULT_MOVABLE(MultiHandleWait); enum Flags { @@ -509,13 +508,16 @@ class MultiHandleWait MultiHandleWait() = default; + MultiHandleWait(MultiHandleWait&&) noexcept; + MultiHandleWait& operator=(MultiHandleWait&&) noexcept; + void AddHandle(std::unique_ptr&& handle, Flags flags = Flags::None); bool Run(std::optional Timeout); void Cancel(); private: std::vector>> m_handles; - bool m_cancel = false; + std::atomic_bool m_cancel = false; }; DEFINE_ENUM_FLAG_OPERATORS(MultiHandleWait::Flags); diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index c50fccfcd..487a9db89 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -1432,7 +1432,7 @@ class WslcSdkTests WslcProcessSettings procSettings; VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); - VERIFY_ARE_EQUAL(WslcSetProcessSettingsIoCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDOUT, nullptr, nullptr), E_NOTIMPL); + VERIFY_ARE_EQUAL(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDOUT, nullptr, nullptr), E_NOTIMPL); } TEST_METHOD(SessionCreateVhdNotImplemented) From 2f165d120172333c1f8d1fbe63239e8767949f5b Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 17 Mar 2026 11:54:33 -0700 Subject: [PATCH 3/8] Tests --- src/windows/WslcSDK/IOCallback.cpp | 12 +- src/windows/WslcSDK/wslcsdk.cpp | 7 +- test/windows/WslcSdkTests.cpp | 272 ++++++++++++++++++++++++++++- 3 files changed, 276 insertions(+), 15 deletions(-) diff --git a/src/windows/WslcSDK/IOCallback.cpp b/src/windows/WslcSDK/IOCallback.cpp index 14c058398..587735bac 100644 --- a/src/windows/WslcSDK/IOCallback.cpp +++ b/src/windows/WslcSDK/IOCallback.cpp @@ -21,8 +21,9 @@ IOCallback::IOCallback(IWSLAProcess* process, const WslcContainerProcessIOCallba auto localCallback = options.stdOutCallback; auto localContext = options.stdOutCallbackContext; m_io.AddHandle(std::make_unique( - GetIOHandle(process, WSLC_PROCESS_IO_HANDLE_STDOUT), - [localCallback, localContext](const auto& buffer) { localCallback(reinterpret_cast(buffer.data()), static_cast(buffer.size()), localContext); })); + GetIOHandle(process, WSLC_PROCESS_IO_HANDLE_STDOUT), [localCallback, localContext](const auto& buffer) { + localCallback(reinterpret_cast(buffer.data()), static_cast(buffer.size()), localContext); + })); } if (options.stdErrCallback) @@ -40,7 +41,9 @@ IOCallback::IOCallback(IWSLAProcess* process, const WslcContainerProcessIOCallba { m_io.Run({}); } - catch (...){} + catch (...) + { + } }); } @@ -72,8 +75,7 @@ wil::unique_handle IOCallback::GetIOHandle(IWSLAProcess* process, WslcProcessIOH { ULONG ulongHandle = 0; - THROW_IF_FAILED(process->GetStdHandle( - static_cast(static_cast>(ioHandle)), &ulongHandle)); + THROW_IF_FAILED(process->GetStdHandle(static_cast(static_cast>(ioHandle)), &ulongHandle)); return wil::unique_handle{ULongToHandle(ulongHandle)}; } diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 60b35094c..9be8ee6b5 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -555,9 +555,14 @@ try auto internalType = CheckAndGetInternalType(container); RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalType->container); + bool hasIOCallback = IOCallback::HasIOCallback(internalType->ioCallbackOptions); + // If callbacks were provided, ATTACH must be used. + // TODO: Consider if we should just override flags when callbacks were provided instead. + RETURN_HR_IF(E_INVALIDARG, WI_IsFlagClear(flags, WSLC_CONTAINER_START_FLAG_ATTACH) && hasIOCallback); + if (SUCCEEDED(errorInfoWrapper.CaptureResult(internalType->container->Start(ConvertFlags(flags), nullptr)))) { - if (IOCallback::HasIOCallback(internalType->ioCallbackOptions)) + if (hasIOCallback) { wil::com_ptr process; RETURN_IF_FAILED(internalType->container->GetInitProcess(&process)); diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index 487a9db89..fac7ba125 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -1386,6 +1386,269 @@ class WslcSdkTests VERIFY_ARE_EQUAL(WslcGetVersion(nullptr), E_POINTER); } + // ----------------------------------------------------------------------- + // WslcSetProcessSettingsIOCallback tests + // ----------------------------------------------------------------------- + + TEST_METHOD(ProcessIoCallbackUnit) + { + WSL2_TEST_ONLY(); + + auto noopCb = [](const BYTE*, uint32_t, PVOID) {}; + + // Negative: null processSettings must fail. + VERIFY_ARE_EQUAL(WslcSetProcessSettingsIOCallback(nullptr, WSLC_PROCESS_IO_HANDLE_STDOUT, noopCb, nullptr), E_POINTER); + + // Negative: STDIN is not a valid output handle. + { + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + VERIFY_ARE_EQUAL(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDIN, noopCb, nullptr), E_INVALIDARG); + } + + // Negative: out-of-range handle value must fail. + { + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + VERIFY_ARE_EQUAL(WslcSetProcessSettingsIOCallback(&procSettings, static_cast(99), noopCb, nullptr), E_INVALIDARG); + } + + // Negative: null callback with non-null context must fail. + { + int ctx = 0; + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + VERIFY_ARE_EQUAL(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDOUT, nullptr, &ctx), E_INVALIDARG); + } + + // Positive: clear STDOUT (null callback, null context) must succeed. + { + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + VERIFY_SUCCEEDED(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDOUT, nullptr, nullptr)); + } + + // Positive: clear STDERR (null callback, null context) must succeed. + { + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + VERIFY_SUCCEEDED(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDERR, nullptr, nullptr)); + } + + // Positive: set a valid STDOUT callback must succeed. + { + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + VERIFY_SUCCEEDED(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDOUT, noopCb, nullptr)); + } + + // Positive: set a valid STDERR callback must succeed. + { + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + VERIFY_SUCCEEDED(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDERR, noopCb, nullptr)); + } + + // Negative: StartContainer without ATTACH fails when callbacks are registered. + // The ATTACH flag is required so the IOCallback can claim the init process pipe handles. + { + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + const char* argv[] = {"/bin/sleep", "1"}; + VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&procSettings, argv, ARRAYSIZE(argv))); + VERIFY_SUCCEEDED(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDOUT, noopCb, nullptr)); + + WslcContainerSettings containerSettings; + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings)); + VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings, &procSettings)); + + UniqueContainer container; + VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &container, nullptr)); + VERIFY_ARE_EQUAL(WslcStartContainer(container.get(), WSLC_CONTAINER_START_FLAG_NONE, nullptr), E_INVALIDARG); + } + } + + TEST_METHOD(ProcessIoCallbackInitProcess) + { + WSL2_TEST_ONLY(); + + std::string stdoutData; + std::string stderrData; + + auto appendToContextString = [](const BYTE* data, uint32_t size, PVOID ctx) { + static_cast(ctx)->append(reinterpret_cast(data), size); + }; + + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + const char* argv[] = {"/bin/sh", "-c", "echo STDOUT && echo STDERR >&2"}; + VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&procSettings, argv, ARRAYSIZE(argv))); + VERIFY_SUCCEEDED(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDOUT, appendToContextString, &stdoutData)); + VERIFY_SUCCEEDED(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDERR, appendToContextString, &stderrData)); + + WslcContainerSettings containerSettings; + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings)); + VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings, &procSettings)); + + UniqueContainer container; + VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &container, nullptr)); + VERIFY_SUCCEEDED(WslcStartContainer(container.get(), WSLC_CONTAINER_START_FLAG_ATTACH, nullptr)); + + UniqueProcess process; + VERIFY_SUCCEEDED(WslcGetContainerInitProcess(container.get(), &process)); + + HANDLE exitEvent = nullptr; + VERIFY_SUCCEEDED(WslcGetProcessExitEvent(process.get(), &exitEvent)); + VERIFY_ARE_EQUAL(WaitForSingleObject(exitEvent, 30 * 1000), static_cast(WAIT_OBJECT_0)); + + // Release the process handle first, then the container handle. + // Releasing the container destroys the WslcContainerImpl which joins the IOCallback + // thread, guaranteeing all bytes have been delivered before the assertions below. + process.reset(); + container.reset(); + + VERIFY_ARE_EQUAL(stdoutData, "STDOUT\n"); + VERIFY_ARE_EQUAL(stderrData, "STDERR\n"); + } + + TEST_METHOD(ProcessIoCallbackExecProcess) + { + WSL2_TEST_ONLY(); + + // Start a long-running container so we can exec into it. + WslcProcessSettings initProcSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&initProcSettings)); + const char* initArgv[] = {"/bin/sleep", "99"}; + VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&initProcSettings, initArgv, ARRAYSIZE(initArgv))); + + WslcContainerSettings containerSettings; + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings)); + VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings, &initProcSettings)); + + UniqueContainer container; + VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &container, nullptr)); + VERIFY_SUCCEEDED(WslcStartContainer(container.get(), WSLC_CONTAINER_START_FLAG_NONE, nullptr)); + + std::string stdoutData; + std::string stderrData; + + auto stdoutCb = [](const BYTE* data, uint32_t size, PVOID ctx) { + static_cast(ctx)->append(reinterpret_cast(data), size); + }; + auto stderrCb = [](const BYTE* data, uint32_t size, PVOID ctx) { + static_cast(ctx)->append(reinterpret_cast(data), size); + }; + + WslcProcessSettings execProcSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&execProcSettings)); + const char* execArgv[] = {"/bin/sh", "-c", "echo EXEC_OUT && echo EXEC_ERR >&2"}; + VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&execProcSettings, execArgv, ARRAYSIZE(execArgv))); + VERIFY_SUCCEEDED(WslcSetProcessSettingsIOCallback(&execProcSettings, WSLC_PROCESS_IO_HANDLE_STDOUT, stdoutCb, &stdoutData)); + VERIFY_SUCCEEDED(WslcSetProcessSettingsIOCallback(&execProcSettings, WSLC_PROCESS_IO_HANDLE_STDERR, stderrCb, &stderrData)); + + UniqueProcess execProcess; + VERIFY_SUCCEEDED(WslcCreateContainerProcess(container.get(), &execProcSettings, &execProcess, nullptr)); + + HANDLE exitEvent = nullptr; + VERIFY_SUCCEEDED(WslcGetProcessExitEvent(execProcess.get(), &exitEvent)); + VERIFY_ARE_EQUAL(WaitForSingleObject(exitEvent, 30 * 1000), static_cast(WAIT_OBJECT_0)); + + // Releasing the exec process handle destroys WslcProcessImpl and joins its IOCallback + // thread, ensuring all bytes are delivered before assertions. + execProcess.reset(); + + VERIFY_ARE_EQUAL(stdoutData, "EXEC_OUT\n"); + VERIFY_ARE_EQUAL(stderrData, "EXEC_ERR\n"); + } + + TEST_METHOD(ProcessIoCallbackHandleExclusion) + { + WSL2_TEST_ONLY(); + + // Register a stdout callback only — no stderr callback. + // The IOCallback object will claim stdout's pipe handle via GetStdHandle (ownership + // transfer); calling WslcGetProcessIOHandle for stdout afterwards must therefore fail + // with ERROR_INVALID_STATE. The stderr handle (no callback) must remain obtainable. + auto noopCb = [](const BYTE*, uint32_t, PVOID) {}; + + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + const char* argv[] = {"/bin/sleep", "99"}; + VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&procSettings, argv, ARRAYSIZE(argv))); + VERIFY_SUCCEEDED(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDOUT, noopCb, nullptr)); + + WslcContainerSettings containerSettings; + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings)); + VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings, &procSettings)); + + UniqueContainer container; + VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &container, nullptr)); + VERIFY_SUCCEEDED(WslcStartContainer(container.get(), WSLC_CONTAINER_START_FLAG_ATTACH, nullptr)); + + UniqueProcess process; + VERIFY_SUCCEEDED(WslcGetContainerInitProcess(container.get(), &process)); + + // stdout handle was consumed by the IOCallback — must not be obtainable. + { + HANDLE h = nullptr; + VERIFY_ARE_EQUAL( + WslcGetProcessIOHandle(process.get(), WSLC_PROCESS_IO_HANDLE_STDOUT, &h), + HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + + // stderr handle was NOT consumed — must still be obtainable. + { + HANDLE h = nullptr; + VERIFY_SUCCEEDED(WslcGetProcessIOHandle(process.get(), WSLC_PROCESS_IO_HANDLE_STDERR, &h)); + VERIFY_IS_NOT_NULL(h); + CloseHandle(h); + } + } + + TEST_METHOD(ProcessIoCallbackLargeOutput) + { + WSL2_TEST_ONLY(); + + // Generate ~1 MiB of stdout via: dd if=/dev/zero bs=1024 count=1024 | base64 + // 1,048,576 zero bytes → base64 output is 1,398,104 bytes (ceil(1048576/3)*4 + newlines). + // We verify the accumulated size is at least 1,398,000 bytes to allow minor variance. + static constexpr size_t c_expectedMinBytes = 1'398'000; + + std::string stdoutData; + stdoutData.reserve(c_expectedMinBytes + 4096); + + auto stdoutCb = [](const BYTE* data, uint32_t size, PVOID ctx) { + static_cast(ctx)->append(reinterpret_cast(data), size); + }; + + WslcProcessSettings procSettings; + VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); + const char* argv[] = {"/bin/sh", "-c", "dd if=/dev/zero bs=1024 count=1024 2>/dev/null | base64"}; + VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&procSettings, argv, ARRAYSIZE(argv))); + VERIFY_SUCCEEDED(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDOUT, stdoutCb, &stdoutData)); + + WslcContainerSettings containerSettings; + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings)); + VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings, &procSettings)); + + UniqueContainer container; + VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &container, nullptr)); + VERIFY_SUCCEEDED(WslcStartContainer(container.get(), WSLC_CONTAINER_START_FLAG_ATTACH, nullptr)); + + UniqueProcess process; + VERIFY_SUCCEEDED(WslcGetContainerInitProcess(container.get(), &process)); + + HANDLE exitEvent = nullptr; + VERIFY_SUCCEEDED(WslcGetProcessExitEvent(process.get(), &exitEvent)); + VERIFY_ARE_EQUAL(WaitForSingleObject(exitEvent, 60 * 1000), static_cast(WAIT_OBJECT_0)); + + // Join the IOCallback thread before inspecting the accumulator. + process.reset(); + container.reset(); + + VERIFY_IS_TRUE(stdoutData.size() >= c_expectedMinBytes); + } + // ----------------------------------------------------------------------- // Stub tests for unimplemented (E_NOTIMPL) functions. // Each of these confirms the current state of the SDK; once the underlying @@ -1426,15 +1689,6 @@ class WslcSdkTests VERIFY_SUCCEEDED(WslcDeleteContainer(container.get(), WSLC_DELETE_CONTAINER_FLAG_NONE, nullptr)); } - TEST_METHOD(ProcessIoCallbackNotImplemented) - { - WSL2_TEST_ONLY(); - - WslcProcessSettings procSettings; - VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings)); - VERIFY_ARE_EQUAL(WslcSetProcessSettingsIOCallback(&procSettings, WSLC_PROCESS_IO_HANDLE_STDOUT, nullptr, nullptr), E_NOTIMPL); - } - TEST_METHOD(SessionCreateVhdNotImplemented) { WSL2_TEST_ONLY(); From 22ca2171e11713ed6a3302e7a87f5d526abf4c3e Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 17 Mar 2026 11:55:08 -0700 Subject: [PATCH 4/8] Format --- test/windows/WslcSdkTests.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index fac7ba125..a3444177b 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -1591,9 +1591,7 @@ class WslcSdkTests // stdout handle was consumed by the IOCallback — must not be obtainable. { HANDLE h = nullptr; - VERIFY_ARE_EQUAL( - WslcGetProcessIOHandle(process.get(), WSLC_PROCESS_IO_HANDLE_STDOUT, &h), - HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + VERIFY_ARE_EQUAL(WslcGetProcessIOHandle(process.get(), WSLC_PROCESS_IO_HANDLE_STDOUT, &h), HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); } // stderr handle was NOT consumed — must still be obtainable. From e1cc982911dc04916ce03fbfa476d9618ce03a64 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 17 Mar 2026 14:52:21 -0700 Subject: [PATCH 5/8] o -> O --- src/windows/WslcSDK/wslcsdk.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index 74d49ea65..18462e3ee 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -292,7 +292,7 @@ typedef enum WslcProcessIOHandle WSLC_PROCESS_IO_HANDLE_STDIN = 0, WSLC_PROCESS_IO_HANDLE_STDOUT = 1, WSLC_PROCESS_IO_HANDLE_STDERR = 2 -} WslcProcessIoHandle; +} WslcProcessIOHandle; // Pass in Null for WslcStdIOCallback to clear the callback for the given handle STDAPI WslcSetProcessSettingsIOCallback( From 750998da373e5639744953e59ef2822311dc9b99 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 17 Mar 2026 17:59:45 -0700 Subject: [PATCH 6/8] PR feedback: cancel via event, consume output for callbacks that were not provided --- src/windows/WslcSDK/IOCallback.cpp | 44 ++++++++++++++++-------------- src/windows/WslcSDK/IOCallback.h | 3 +- src/windows/WslcSDK/wslcsdk.h | 3 +- src/windows/common/relay.cpp | 12 -------- src/windows/common/relay.hpp | 6 ++-- test/windows/WslcSdkTests.cpp | 6 ++-- 6 files changed, 31 insertions(+), 43 deletions(-) diff --git a/src/windows/WslcSDK/IOCallback.cpp b/src/windows/WslcSDK/IOCallback.cpp index 587735bac..77aad3fdf 100644 --- a/src/windows/WslcSDK/IOCallback.cpp +++ b/src/windows/WslcSDK/IOCallback.cpp @@ -16,34 +16,36 @@ Module Name: IOCallback::IOCallback(IWSLAProcess* process, const WslcContainerProcessIOCallbackOptions& options) { - if (options.stdOutCallback) - { - auto localCallback = options.stdOutCallback; - auto localContext = options.stdOutCallbackContext; - m_io.AddHandle(std::make_unique( - GetIOHandle(process, WSLC_PROCESS_IO_HANDLE_STDOUT), [localCallback, localContext](const auto& buffer) { - localCallback(reinterpret_cast(buffer.data()), static_cast(buffer.size()), localContext); - })); - } + using namespace wsl::windows::common::relay; - if (options.stdErrCallback) + auto addIOCallback = [&](WslcProcessIOHandle ioHandle, WslcStdIOCallback callback, PVOID context) { - auto localCallback = options.stdErrCallback; - auto localContext = options.stdErrCallbackContext; - m_io.AddHandle(std::make_unique( - GetIOHandle(process, WSLC_PROCESS_IO_HANDLE_STDERR), [localCallback, localContext](const auto& buffer) { - localCallback(reinterpret_cast(buffer.data()), static_cast(buffer.size()), localContext); - })); - } + std::function& Buffer)> function; + if (callback) + { + function = [callback, context](const gsl::span& buffer) { + callback(reinterpret_cast(buffer.data()), static_cast(buffer.size()), context); + }; + } + else + { + function = [](const gsl::span&) {}; + } + + m_io.AddHandle(std::make_unique(GetIOHandle(process, ioHandle), std::move(function))); + }; + + addIOCallback(WSLC_PROCESS_IO_HANDLE_STDOUT, options.stdOutCallback, options.stdOutCallbackContext); + addIOCallback(WSLC_PROCESS_IO_HANDLE_STDERR, options.stdErrCallback, options.stdErrCallbackContext); + + m_io.AddHandle(std::make_unique(m_cancelEvent.get()), MultiHandleWait::CancelOnCompleted); m_thread = std::thread([this]() { try { m_io.Run({}); } - catch (...) - { - } + CATCH_LOG(); }); } @@ -58,7 +60,7 @@ IOCallback::~IOCallback() void IOCallback::Cancel() { - m_io.Cancel(); + m_cancelEvent.SetEvent(); } bool IOCallback::HasIOCallback(const WslcContainerProcessOptionsInternal* options) diff --git a/src/windows/WslcSDK/IOCallback.h b/src/windows/WslcSDK/IOCallback.h index 6c05e79d1..51a854a0f 100644 --- a/src/windows/WslcSDK/IOCallback.h +++ b/src/windows/WslcSDK/IOCallback.h @@ -13,7 +13,7 @@ Module Name: --*/ #pragma once #include "wslaservice.h" -#include "wslrelay.h" +#include "relay.hpp" #include struct WslcContainerProcessIOCallbackOptions; @@ -34,4 +34,5 @@ struct IOCallback private: std::thread m_thread; wsl::windows::common::relay::MultiHandleWait m_io; + wil::unique_event m_cancelEvent{wil::EventOptions::ManualReset}; }; diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index 18462e3ee..258aeb559 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -294,7 +294,8 @@ typedef enum WslcProcessIOHandle WSLC_PROCESS_IO_HANDLE_STDERR = 2 } WslcProcessIOHandle; -// Pass in Null for WslcStdIOCallback to clear the callback for the given handle +// Only WSLC_PROCESS_IO_HANDLE_STDOUT and WSLC_PROCESS_IO_HANDLE_STDERR are supported for callbacks. +// Pass in Null for WslcStdIOCallback to clear the callback for the given handle. STDAPI WslcSetProcessSettingsIOCallback( _In_ WslcProcessSettings* processSettings, _In_ WslcProcessIOHandle ioHandle, _In_opt_ WslcStdIOCallback stdIOCallback, _In_opt_ PVOID context); diff --git a/src/windows/common/relay.cpp b/src/windows/common/relay.cpp index 6003c26cb..564510c33 100644 --- a/src/windows/common/relay.cpp +++ b/src/windows/common/relay.cpp @@ -961,18 +961,6 @@ try } CATCH_LOG() -MultiHandleWait::MultiHandleWait(MultiHandleWait&& other) noexcept -{ - this->operator=(std::move(other)); -} - -MultiHandleWait& MultiHandleWait::operator=(MultiHandleWait&& other) noexcept -{ - this->m_handles = std::move(other.m_handles); - this->m_cancel = other.m_cancel.load(); - return *this; -} - void MultiHandleWait::AddHandle(std::unique_ptr&& handle, Flags flags) { m_handles.emplace_back(flags, std::move(handle)); diff --git a/src/windows/common/relay.hpp b/src/windows/common/relay.hpp index cd87cac08..dd4664c1f 100644 --- a/src/windows/common/relay.hpp +++ b/src/windows/common/relay.hpp @@ -498,6 +498,7 @@ class MultiHandleWait { public: NON_COPYABLE(MultiHandleWait); + DEFAULT_MOVABLE(MultiHandleWait); enum Flags { @@ -508,16 +509,13 @@ class MultiHandleWait MultiHandleWait() = default; - MultiHandleWait(MultiHandleWait&&) noexcept; - MultiHandleWait& operator=(MultiHandleWait&&) noexcept; - void AddHandle(std::unique_ptr&& handle, Flags flags = Flags::None); bool Run(std::optional Timeout); void Cancel(); private: std::vector>> m_handles; - std::atomic_bool m_cancel = false; + bool m_cancel = false; }; DEFINE_ENUM_FLAG_OPERATORS(MultiHandleWait::Flags); diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index a3444177b..184baabf7 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -1594,12 +1594,10 @@ class WslcSdkTests VERIFY_ARE_EQUAL(WslcGetProcessIOHandle(process.get(), WSLC_PROCESS_IO_HANDLE_STDOUT, &h), HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); } - // stderr handle was NOT consumed — must still be obtainable. + // stderr handle was also consumed in order to drain it despite not being given a callback. { HANDLE h = nullptr; - VERIFY_SUCCEEDED(WslcGetProcessIOHandle(process.get(), WSLC_PROCESS_IO_HANDLE_STDERR, &h)); - VERIFY_IS_NOT_NULL(h); - CloseHandle(h); + VERIFY_ARE_EQUAL(WslcGetProcessIOHandle(process.get(), WSLC_PROCESS_IO_HANDLE_STDERR, &h), HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); } } From 488ffb727a16e7e1fb552070c8eab5a3bfaf2666 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 17 Mar 2026 18:00:10 -0700 Subject: [PATCH 7/8] format --- src/windows/WslcSDK/IOCallback.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/windows/WslcSDK/IOCallback.cpp b/src/windows/WslcSDK/IOCallback.cpp index 77aad3fdf..ffd805bca 100644 --- a/src/windows/WslcSDK/IOCallback.cpp +++ b/src/windows/WslcSDK/IOCallback.cpp @@ -18,14 +18,13 @@ IOCallback::IOCallback(IWSLAProcess* process, const WslcContainerProcessIOCallba { using namespace wsl::windows::common::relay; - auto addIOCallback = [&](WslcProcessIOHandle ioHandle, WslcStdIOCallback callback, PVOID context) - { + auto addIOCallback = [&](WslcProcessIOHandle ioHandle, WslcStdIOCallback callback, PVOID context) { std::function& Buffer)> function; if (callback) { function = [callback, context](const gsl::span& buffer) { callback(reinterpret_cast(buffer.data()), static_cast(buffer.size()), context); - }; + }; } else { From fbb244bcadccb40bd75b0eadf100b711fb8662de Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 19 Mar 2026 09:16:17 -0700 Subject: [PATCH 8/8] Empty commit to force a new merge build