From 1220fbe389adeb3b81d77833750e1516a84df225 Mon Sep 17 00:00:00 2001 From: Pooja Trivedi Date: Wed, 19 Nov 2025 10:53:58 -0500 Subject: [PATCH 01/29] Implement CreateContainer method --- src/windows/common/WSLAProcessLauncher.cpp | 2 +- src/windows/common/WSLAProcessLauncher.h | 4 + .../wslaservice/exe/ServiceProcessLauncher.h | 5 +- src/windows/wslaservice/exe/WSLAContainer.cpp | 77 ++++++++++++++++++- src/windows/wslaservice/exe/WSLAContainer.h | 12 +++ src/windows/wslaservice/exe/WSLASession.cpp | 10 +-- src/windows/wslaservice/exe/WSLASession.h | 3 + src/windows/wslaservice/inc/wslaservice.idl | 10 ++- test/windows/WSLATests.cpp | 2 +- 9 files changed, 114 insertions(+), 11 deletions(-) diff --git a/src/windows/common/WSLAProcessLauncher.cpp b/src/windows/common/WSLAProcessLauncher.cpp index 6e2b4cd2d..18ec9aa3b 100644 --- a/src/windows/common/WSLAProcessLauncher.cpp +++ b/src/windows/common/WSLAProcessLauncher.cpp @@ -150,7 +150,7 @@ ClientRunningWSLAProcess WSLAProcessLauncher::Launch(IWSLASession& Session) THROW_HR_MSG(hresult, "Failed to launch process: %hs (commandline: %hs). Errno = %i", m_executable.c_str(), commandLine.c_str(), error); } - return process.value(); + return std::move(process.value()); } std::tuple> WSLAProcessLauncher::LaunchNoThrow(IWSLASession& Session) diff --git a/src/windows/common/WSLAProcessLauncher.h b/src/windows/common/WSLAProcessLauncher.h index 97fd2ac7e..bd49105c0 100644 --- a/src/windows/common/WSLAProcessLauncher.h +++ b/src/windows/common/WSLAProcessLauncher.h @@ -43,6 +43,10 @@ class RunningWSLAProcess }; RunningWSLAProcess(std::vector&& fds); + NON_COPYABLE(RunningWSLAProcess); + RunningWSLAProcess(RunningWSLAProcess&&) = default; + RunningWSLAProcess& operator=(RunningWSLAProcess&&) = default; + ProcessResult WaitAndCaptureOutput(DWORD TimeoutMs = INFINITE, std::vector>&& ExtraHandles = {}); virtual wil::unique_handle GetStdHandle(int Index) = 0; virtual wil::unique_event GetExitEvent() = 0; diff --git a/src/windows/wslaservice/exe/ServiceProcessLauncher.h b/src/windows/wslaservice/exe/ServiceProcessLauncher.h index e9b61e040..3e14e6a1a 100644 --- a/src/windows/wslaservice/exe/ServiceProcessLauncher.h +++ b/src/windows/wslaservice/exe/ServiceProcessLauncher.h @@ -24,7 +24,10 @@ class ServiceRunningProcess : public common::RunningWSLAProcess { public: NON_COPYABLE(ServiceRunningProcess); - NON_MOVABLE(ServiceRunningProcess); + // NON_MOVABLE(ServiceRunningProcess); + + ServiceRunningProcess(ServiceRunningProcess&&) = default; + ServiceRunningProcess& operator=(ServiceRunningProcess&&) = default; ServiceRunningProcess(const Microsoft::WRL::ComPtr& process, std::vector&& fds); wil::unique_handle GetStdHandle(int Index) override; diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index 4e7a68b59..ca92c8046 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -18,6 +18,14 @@ Module Name: using wsl::windows::service::wsla::WSLAContainer; +const std::string nerdctlPath = "/usr/bin/nerdctl"; + +// Constants for required default arguments for "nerdctl run..." +static std::vector defaultNerdctlRunArgs{ + "--pull=never", + "--host=net", // TODO: default for now, change later + "--ulimit nofile=65536:65536"}; + HRESULT WSLAContainer::Start() { return E_NOTIMPL; @@ -38,10 +46,12 @@ HRESULT WSLAContainer::GetState(WSLA_CONTAINER_STATE* State) return E_NOTIMPL; } -HRESULT WSLAContainer::GetInitProcess(IWSLAProcess** process) +HRESULT WSLAContainer::GetInitProcess(IWSLAProcess** Process) +try { - return E_NOTIMPL; + return m_containerProcess.Get().QueryInterface(__uuidof(IWSLAProcess), (void**)Process); } +CATCH_RETURN(); HRESULT WSLAContainer::Exec(const WSLA_PROCESS_OPTIONS* Options, IWSLAProcess** Process, int* Errno) try @@ -53,3 +63,66 @@ try return S_OK; } CATCH_RETURN(); + +Microsoft::WRL::ComPtr WSLAContainer::Create(const WSLA_CONTAINER_OPTIONS& containerOptions, WSLAVirtualMachine& parentVM) +{ + auto args = WSLAContainer::prepareNerdctlRunCommand(containerOptions); + + ServiceProcessLauncher launcher(nerdctlPath, args); + return wil::MakeOrThrow(&parentVM, launcher.Launch(parentVM)); +} + +std::vector WSLAContainer::prepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options) +{ + std::vector args; + + args.push_back("run"); + args.insert(args.end(), defaultNerdctlRunArgs.begin(), defaultNerdctlRunArgs.end()); + args.push_back("--name"); + args.push_back(options.Name); + if (options.ShmSize > 0) + { + args.push_back("--shm-size=" + std::to_string(options.ShmSize) + 'm'); + } + if (options.Flags & WSLA_CONTAINER_FLAG_ENABLE_GPU) + { + args.push_back("--gpus"); + // TODO: Parse GPU device list from WSLA_CONTAINER_OPTIONS. For now, just enable all GPUs. + args.push_back("all"); + // args.push_back(options.GPUOptions.GPUDevices); + } + + args.insert(args.end(), {"--ulimit", "nofile=65536:65536"}); + + for (ULONG i = 0; i < options.InitProcessOptions->CommandLineCount; i++) + { + args.push_back(options.InitProcessOptions->CommandLine[i]); + } + for (ULONG i = 0; i < options.InitProcessOptions->EnvironmentCount; i++) + { + args.push_back(options.InitProcessOptions->Environment[i]); + } + for (ULONG i = 0; i < options.VolumesCount; i++) + { + std::string mountContainerPath; + mountContainerPath = std::string(options.Volumes[i].HostPath) + ":" + std::string(options.Volumes[i].ContainerPath); + if (options.Volumes[i].ReadOnly) + { + mountContainerPath += ":ro"; + } + args.insert(args.end(), {"-v", mountContainerPath}); + } + + args.push_back(options.Image); + + if (options.InitProcessOptions->CommandLineCount) + { + args.push_back("--"); + } + for (ULONG i = 0; i < options.InitProcessOptions->CommandLineCount; i++) + { + args.push_back(options.InitProcessOptions->CommandLine[i]); + } + + return args; +} \ No newline at end of file diff --git a/src/windows/wslaservice/exe/WSLAContainer.h b/src/windows/wslaservice/exe/WSLAContainer.h index f9bed4c84..4e9529bb6 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.h +++ b/src/windows/wslaservice/exe/WSLAContainer.h @@ -14,7 +14,9 @@ Module Name: #pragma once +#include "ServiceProcessLauncher.h" #include "wslaservice.h" +#include "WSLAVirtualMachine.h" namespace wsl::windows::service::wsla { @@ -23,6 +25,10 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLAContainer { public: WSLAContainer() = default; // TODO + WSLAContainer(WSLAVirtualMachine* parentVM, ServiceRunningProcess&& containerProcess) : + m_parentVM(parentVM), m_containerProcess(std::move(containerProcess)) + { + } WSLAContainer(const WSLAContainer&) = delete; WSLAContainer& operator=(const WSLAContainer&) = delete; @@ -33,6 +39,12 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLAContainer IFACEMETHOD(GetInitProcess)(_Out_ IWSLAProcess** process) override; IFACEMETHOD(Exec)(_In_ const WSLA_PROCESS_OPTIONS* Options, _Out_ IWSLAProcess** Process, _Out_ int* Errno) override; + static Microsoft::WRL::ComPtr Create(const WSLA_CONTAINER_OPTIONS& Options, WSLAVirtualMachine& parentVM); + private: + ServiceRunningProcess m_containerProcess; + WSLAVirtualMachine* m_parentVM = nullptr; + + static std::vector prepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options); }; } // namespace wsl::windows::service::wsla \ No newline at end of file diff --git a/src/windows/wslaservice/exe/WSLASession.cpp b/src/windows/wslaservice/exe/WSLASession.cpp index 525eb3f4d..6f3ca2190 100644 --- a/src/windows/wslaservice/exe/WSLASession.cpp +++ b/src/windows/wslaservice/exe/WSLASession.cpp @@ -80,13 +80,13 @@ HRESULT WSLASession::DeleteImage(LPCWSTR Image) return E_NOTIMPL; } -HRESULT WSLASession::CreateContainer(const WSLA_CONTAINER_OPTIONS* Options, IWSLAContainer** Container) +HRESULT WSLASession::CreateContainer(const WSLA_CONTAINER_OPTIONS* containerOptions, IWSLAContainer** Container) try { - // Basic instanciation for testing. - // TODO: Implement. - - auto container = wil::MakeOrThrow(); + RETURN_HR_IF_NULL(E_POINTER, containerOptions); + // TODO: Log entrance into the function. + m_containerId++; + auto container = WSLAContainer::Create(*containerOptions, *m_virtualMachine); container.CopyTo(__uuidof(IWSLAContainer), (void**)Container); return S_OK; diff --git a/src/windows/wslaservice/exe/WSLASession.h b/src/windows/wslaservice/exe/WSLASession.h index 843b88d50..5dc879623 100644 --- a/src/windows/wslaservice/exe/WSLASession.h +++ b/src/windows/wslaservice/exe/WSLASession.h @@ -56,6 +56,9 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLASession Microsoft::WRL::ComPtr m_virtualMachine; std::wstring m_displayName; std::mutex m_lock; + + std::atomic_int m_containerId = 1; + // TODO: Add container tracking here. Could reuse m_lock for that. }; } // namespace wsl::windows::service::wsla \ No newline at end of file diff --git a/src/windows/wslaservice/inc/wslaservice.idl b/src/windows/wslaservice/inc/wslaservice.idl index de9235cf0..5aa958fa6 100644 --- a/src/windows/wslaservice/inc/wslaservice.idl +++ b/src/windows/wslaservice/inc/wslaservice.idl @@ -117,7 +117,8 @@ struct WSLA_PROCESS_OPTIONS struct WSLA_VOLUME { LPCSTR HostPath; - LPCSTR ContainerHostPath; + LPCSTR ContainerPath; + BOOL ReadOnly; }; struct WSLA_PORT_MAPPING @@ -126,6 +127,13 @@ struct WSLA_PORT_MAPPING USHORT ContainerPort; }; +enum WSLA_CONTAINER_FLAGS +{ + WSLA_CONTAINER_FLAG_ENABLE_GPU = 0x0, + WSLA_CONTAINER_FLAG_USE_HOST_NETWORKING = 0x1 +} ; + + struct WSLA_CONTAINER_OPTIONS { LPCSTR Image; diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index f7cb14635..ece716b50 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -411,7 +411,7 @@ class WSLATests auto [hresult, _, process] = launcher.LaunchNoThrow(*session); VERIFY_ARE_EQUAL(hresult, expectedError); - return process; + return std::move(process); }; { From 0920c523eae4ffa5cf337f7ec3d58ba31a309825 Mon Sep 17 00:00:00 2001 From: Blue Date: Fri, 21 Nov 2025 12:59:37 -0800 Subject: [PATCH 02/29] Build fixes --- src/windows/wslaservice/exe/WSLASession.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/windows/wslaservice/exe/WSLASession.cpp b/src/windows/wslaservice/exe/WSLASession.cpp index 6f3ca2190..e00937fd7 100644 --- a/src/windows/wslaservice/exe/WSLASession.cpp +++ b/src/windows/wslaservice/exe/WSLASession.cpp @@ -84,10 +84,15 @@ HRESULT WSLASession::CreateContainer(const WSLA_CONTAINER_OPTIONS* containerOpti try { RETURN_HR_IF_NULL(E_POINTER, containerOptions); + + std::lock_guard lock{m_lock}; + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine); + + // TODO: Log entrance into the function. m_containerId++; - auto container = WSLAContainer::Create(*containerOptions, *m_virtualMachine); - container.CopyTo(__uuidof(IWSLAContainer), (void**)Container); + auto container = WSLAContainer::Create(*containerOptions, *m_virtualMachine.Get()); + THROW_IF_FAILED(container.CopyTo(__uuidof(IWSLAContainer), (void**)Container)); return S_OK; } From 3d5fa8becf11b13a74d8e3d8a0b2fed4ffe38f6c Mon Sep 17 00:00:00 2001 From: Pooja Trivedi Date: Wed, 19 Nov 2025 10:53:58 -0500 Subject: [PATCH 03/29] Implement CreateContainer method --- src/windows/common/WSLAProcessLauncher.cpp | 2 +- src/windows/common/WSLAProcessLauncher.h | 4 + .../wslaservice/exe/ServiceProcessLauncher.h | 5 +- src/windows/wslaservice/exe/WSLAContainer.cpp | 76 ++++++++++++++++++- src/windows/wslaservice/exe/WSLAContainer.h | 12 +++ src/windows/wslaservice/exe/WSLASession.cpp | 10 +-- src/windows/wslaservice/exe/WSLASession.h | 3 + src/windows/wslaservice/inc/wslaservice.idl | 10 ++- test/windows/WSLATests.cpp | 2 +- 9 files changed, 113 insertions(+), 11 deletions(-) diff --git a/src/windows/common/WSLAProcessLauncher.cpp b/src/windows/common/WSLAProcessLauncher.cpp index 6e2b4cd2d..18ec9aa3b 100644 --- a/src/windows/common/WSLAProcessLauncher.cpp +++ b/src/windows/common/WSLAProcessLauncher.cpp @@ -150,7 +150,7 @@ ClientRunningWSLAProcess WSLAProcessLauncher::Launch(IWSLASession& Session) THROW_HR_MSG(hresult, "Failed to launch process: %hs (commandline: %hs). Errno = %i", m_executable.c_str(), commandLine.c_str(), error); } - return process.value(); + return std::move(process.value()); } std::tuple> WSLAProcessLauncher::LaunchNoThrow(IWSLASession& Session) diff --git a/src/windows/common/WSLAProcessLauncher.h b/src/windows/common/WSLAProcessLauncher.h index 97fd2ac7e..bd49105c0 100644 --- a/src/windows/common/WSLAProcessLauncher.h +++ b/src/windows/common/WSLAProcessLauncher.h @@ -43,6 +43,10 @@ class RunningWSLAProcess }; RunningWSLAProcess(std::vector&& fds); + NON_COPYABLE(RunningWSLAProcess); + RunningWSLAProcess(RunningWSLAProcess&&) = default; + RunningWSLAProcess& operator=(RunningWSLAProcess&&) = default; + ProcessResult WaitAndCaptureOutput(DWORD TimeoutMs = INFINITE, std::vector>&& ExtraHandles = {}); virtual wil::unique_handle GetStdHandle(int Index) = 0; virtual wil::unique_event GetExitEvent() = 0; diff --git a/src/windows/wslaservice/exe/ServiceProcessLauncher.h b/src/windows/wslaservice/exe/ServiceProcessLauncher.h index e9b61e040..3e14e6a1a 100644 --- a/src/windows/wslaservice/exe/ServiceProcessLauncher.h +++ b/src/windows/wslaservice/exe/ServiceProcessLauncher.h @@ -24,7 +24,10 @@ class ServiceRunningProcess : public common::RunningWSLAProcess { public: NON_COPYABLE(ServiceRunningProcess); - NON_MOVABLE(ServiceRunningProcess); + // NON_MOVABLE(ServiceRunningProcess); + + ServiceRunningProcess(ServiceRunningProcess&&) = default; + ServiceRunningProcess& operator=(ServiceRunningProcess&&) = default; ServiceRunningProcess(const Microsoft::WRL::ComPtr& process, std::vector&& fds); wil::unique_handle GetStdHandle(int Index) override; diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index 4e7a68b59..550341d13 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -18,6 +18,14 @@ Module Name: using wsl::windows::service::wsla::WSLAContainer; +const std::string nerdctlPath = "/usr/bin/nerdctl"; + +// Constants for required default arguments for "nerdctl run..." +static std::vector defaultNerdctlRunArgs{ + "--pull=never", + "--host=net", // TODO: default for now, change later + "--ulimit nofile=65536:65536"}; + HRESULT WSLAContainer::Start() { return E_NOTIMPL; @@ -38,10 +46,12 @@ HRESULT WSLAContainer::GetState(WSLA_CONTAINER_STATE* State) return E_NOTIMPL; } -HRESULT WSLAContainer::GetInitProcess(IWSLAProcess** process) +HRESULT WSLAContainer::GetInitProcess(IWSLAProcess** Process) +try { - return E_NOTIMPL; + return m_containerProcess.Get().QueryInterface(__uuidof(IWSLAProcess), (void**)Process); } +CATCH_RETURN(); HRESULT WSLAContainer::Exec(const WSLA_PROCESS_OPTIONS* Options, IWSLAProcess** Process, int* Errno) try @@ -53,3 +63,65 @@ try return S_OK; } CATCH_RETURN(); + +Microsoft::WRL::ComPtr WSLAContainer::Create(const WSLA_CONTAINER_OPTIONS& containerOptions, WSLAVirtualMachine& parentVM) +{ + auto args = WSLAContainer::prepareNerdctlRunCommand(containerOptions); + + ServiceProcessLauncher launcher(nerdctlPath, args); + return wil::MakeOrThrow(&parentVM, launcher.Launch(parentVM)); +} + +std::vector WSLAContainer::prepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options) +{ + std::vector args; + + args.push_back("run"); + args.insert(args.end(), defaultNerdctlRunArgs.begin(), defaultNerdctlRunArgs.end()); + args.push_back("--name"); + args.push_back(options.Name); + if (options.ShmSize > 0) + { + args.push_back("--shm-size=" + std::to_string(options.ShmSize) + 'm'); + } + if (options.Flags & WSLA_CONTAINER_FLAG_ENABLE_GPU) + { + args.push_back("--gpus"); + // TODO: Parse GPU device list from WSLA_CONTAINER_OPTIONS. For now, just enable all GPUs. + args.push_back("all"); + // args.push_back(options.GPUOptions.GPUDevices); + } + + args.insert(args.end(), {"--ulimit", "nofile=65536:65536"}); + + // TODO: need to worry about env variables with dashes in them? + for (ULONG i = 0; i < options.InitProcessOptions->EnvironmentCount; i++) + { + args.insert(args.end(), {"-e", options.InitProcessOptions->Environment[i]}); + } + for (ULONG i = 0; i < options.VolumesCount; i++) + { + std::string mountContainerPath; + mountContainerPath = std::string(options.Volumes[i].HostPath) + ":" + std::string(options.Volumes[i].ContainerPath); + if (options.Volumes[i].ReadOnly) + { + mountContainerPath += ":ro"; + } + args.insert(args.end(), {"-v", mountContainerPath}); + } + + args.push_back(options.Image); + + if (options.InitProcessOptions->CommandLineCount) + { + args.push_back("--"); + } + for (ULONG i = 0; i < options.InitProcessOptions->CommandLineCount; i++) + { + args.push_back(options.InitProcessOptions->CommandLine[i]); + } + + // TODO: Implement --entrypoint override if specified in WSLA_CONTAINER_OPTIONS. + + return args; +} \ No newline at end of file diff --git a/src/windows/wslaservice/exe/WSLAContainer.h b/src/windows/wslaservice/exe/WSLAContainer.h index f9bed4c84..4e9529bb6 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.h +++ b/src/windows/wslaservice/exe/WSLAContainer.h @@ -14,7 +14,9 @@ Module Name: #pragma once +#include "ServiceProcessLauncher.h" #include "wslaservice.h" +#include "WSLAVirtualMachine.h" namespace wsl::windows::service::wsla { @@ -23,6 +25,10 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLAContainer { public: WSLAContainer() = default; // TODO + WSLAContainer(WSLAVirtualMachine* parentVM, ServiceRunningProcess&& containerProcess) : + m_parentVM(parentVM), m_containerProcess(std::move(containerProcess)) + { + } WSLAContainer(const WSLAContainer&) = delete; WSLAContainer& operator=(const WSLAContainer&) = delete; @@ -33,6 +39,12 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLAContainer IFACEMETHOD(GetInitProcess)(_Out_ IWSLAProcess** process) override; IFACEMETHOD(Exec)(_In_ const WSLA_PROCESS_OPTIONS* Options, _Out_ IWSLAProcess** Process, _Out_ int* Errno) override; + static Microsoft::WRL::ComPtr Create(const WSLA_CONTAINER_OPTIONS& Options, WSLAVirtualMachine& parentVM); + private: + ServiceRunningProcess m_containerProcess; + WSLAVirtualMachine* m_parentVM = nullptr; + + static std::vector prepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options); }; } // namespace wsl::windows::service::wsla \ No newline at end of file diff --git a/src/windows/wslaservice/exe/WSLASession.cpp b/src/windows/wslaservice/exe/WSLASession.cpp index 525eb3f4d..6f3ca2190 100644 --- a/src/windows/wslaservice/exe/WSLASession.cpp +++ b/src/windows/wslaservice/exe/WSLASession.cpp @@ -80,13 +80,13 @@ HRESULT WSLASession::DeleteImage(LPCWSTR Image) return E_NOTIMPL; } -HRESULT WSLASession::CreateContainer(const WSLA_CONTAINER_OPTIONS* Options, IWSLAContainer** Container) +HRESULT WSLASession::CreateContainer(const WSLA_CONTAINER_OPTIONS* containerOptions, IWSLAContainer** Container) try { - // Basic instanciation for testing. - // TODO: Implement. - - auto container = wil::MakeOrThrow(); + RETURN_HR_IF_NULL(E_POINTER, containerOptions); + // TODO: Log entrance into the function. + m_containerId++; + auto container = WSLAContainer::Create(*containerOptions, *m_virtualMachine); container.CopyTo(__uuidof(IWSLAContainer), (void**)Container); return S_OK; diff --git a/src/windows/wslaservice/exe/WSLASession.h b/src/windows/wslaservice/exe/WSLASession.h index 843b88d50..5dc879623 100644 --- a/src/windows/wslaservice/exe/WSLASession.h +++ b/src/windows/wslaservice/exe/WSLASession.h @@ -56,6 +56,9 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLASession Microsoft::WRL::ComPtr m_virtualMachine; std::wstring m_displayName; std::mutex m_lock; + + std::atomic_int m_containerId = 1; + // TODO: Add container tracking here. Could reuse m_lock for that. }; } // namespace wsl::windows::service::wsla \ No newline at end of file diff --git a/src/windows/wslaservice/inc/wslaservice.idl b/src/windows/wslaservice/inc/wslaservice.idl index de9235cf0..5aa958fa6 100644 --- a/src/windows/wslaservice/inc/wslaservice.idl +++ b/src/windows/wslaservice/inc/wslaservice.idl @@ -117,7 +117,8 @@ struct WSLA_PROCESS_OPTIONS struct WSLA_VOLUME { LPCSTR HostPath; - LPCSTR ContainerHostPath; + LPCSTR ContainerPath; + BOOL ReadOnly; }; struct WSLA_PORT_MAPPING @@ -126,6 +127,13 @@ struct WSLA_PORT_MAPPING USHORT ContainerPort; }; +enum WSLA_CONTAINER_FLAGS +{ + WSLA_CONTAINER_FLAG_ENABLE_GPU = 0x0, + WSLA_CONTAINER_FLAG_USE_HOST_NETWORKING = 0x1 +} ; + + struct WSLA_CONTAINER_OPTIONS { LPCSTR Image; diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index f7cb14635..ece716b50 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -411,7 +411,7 @@ class WSLATests auto [hresult, _, process] = launcher.LaunchNoThrow(*session); VERIFY_ARE_EQUAL(hresult, expectedError); - return process; + return std::move(process); }; { From 3a8a66934b5c41b05b020c1d5ceefe489a41717d Mon Sep 17 00:00:00 2001 From: Pooja Trivedi Date: Fri, 21 Nov 2025 16:25:28 -0500 Subject: [PATCH 04/29] Update src/windows/wslaservice/exe/WSLAContainer.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/windows/wslaservice/exe/WSLAContainer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index 550341d13..97c5181e7 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -92,7 +92,7 @@ std::vector WSLAContainer::prepareNerdctlRunCommand(const WSLA_CONT // args.push_back(options.GPUOptions.GPUDevices); } - args.insert(args.end(), {"--ulimit", "nofile=65536:65536"}); + // Removed redundant --ulimit nofile=65536:65536 (already in defaultNerdctlRunArgs) // TODO: need to worry about env variables with dashes in them? for (ULONG i = 0; i < options.InitProcessOptions->EnvironmentCount; i++) From b8c5b8ca63d1ff9c1977229068f716d00fb0bb49 Mon Sep 17 00:00:00 2001 From: Pooja Trivedi Date: Fri, 21 Nov 2025 16:32:56 -0500 Subject: [PATCH 05/29] Fix nerdctl host networking parameter --- src/windows/wslaservice/exe/WSLAContainer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index 97c5181e7..ab669088b 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -23,7 +23,7 @@ const std::string nerdctlPath = "/usr/bin/nerdctl"; // Constants for required default arguments for "nerdctl run..." static std::vector defaultNerdctlRunArgs{ "--pull=never", - "--host=net", // TODO: default for now, change later + "--net=host", // TODO: default for now, change later "--ulimit nofile=65536:65536"}; HRESULT WSLAContainer::Start() From cff1cd25b4de2f951428c5ee869c5938a21a1405 Mon Sep 17 00:00:00 2001 From: Blue Date: Fri, 21 Nov 2025 15:59:30 -0800 Subject: [PATCH 06/29] Prototype CreateContainer() --- src/windows/common/WslClient.cpp | 49 +++++++++++++++---- src/windows/common/WslCoreFilesystem.cpp | 6 ++- src/windows/wslaservice/exe/WSLAContainer.cpp | 35 ++++++------- src/windows/wslaservice/inc/wslaservice.idl | 2 +- 4 files changed, 60 insertions(+), 32 deletions(-) diff --git a/src/windows/common/WslClient.cpp b/src/windows/common/WslClient.cpp index 6b40f927f..4e94a8c89 100644 --- a/src/windows/common/WslClient.cpp +++ b/src/windows/common/WslClient.cpp @@ -1549,6 +1549,7 @@ int WslaShell(_In_ std::wstring_view commandLine) settings.BootTimeoutMs = 30000; settings.NetworkingMode = WSLANetworkingModeNAT; std::wstring containerRootVhd; + std::string containerImage; bool help = false; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); @@ -1559,6 +1560,7 @@ int WslaShell(_In_ std::wstring_view commandLine) parser.AddArgument(Integer(settings.CpuCount), L"--cpu"); parser.AddArgument(Utf8String(fsType), L"--fstype"); parser.AddArgument(containerRootVhd, L"--container-vhd"); + parser.AddArgument(Utf8String(containerImage), L"--image"); parser.AddArgument(help, L"--help"); parser.Parse(); @@ -1606,12 +1608,39 @@ int WslaShell(_In_ std::wstring_view commandLine) THROW_HR_IF(E_FAIL, initProcess.WaitAndCaptureOutput().Code != 0); } - wsl::windows::common::WSLAProcessLauncher launcher{shell, {shell}, {"TERM=xterm-256color"}, ProcessFlags::None}; - launcher.AddFd(WSLA_PROCESS_FD{.Fd = 0, .Type = WSLAFdTypeTerminalInput}); - launcher.AddFd(WSLA_PROCESS_FD{.Fd = 1, .Type = WSLAFdTypeTerminalOutput}); - launcher.AddFd(WSLA_PROCESS_FD{.Fd = 2, .Type = WSLAFdTypeTerminalControl}); + std::optional> container; + std::optional process; - auto process = launcher.Launch(*session); + if (containerImage.empty()) + { + wsl::windows::common::WSLAProcessLauncher launcher{shell, {shell}, {"TERM=xterm-256color"}, ProcessFlags::None}; + launcher.AddFd(WSLA_PROCESS_FD{.Fd = 0, .Type = WSLAFdTypeTerminalInput}); + launcher.AddFd(WSLA_PROCESS_FD{.Fd = 1, .Type = WSLAFdTypeTerminalOutput}); + launcher.AddFd(WSLA_PROCESS_FD{.Fd = 2, .Type = WSLAFdTypeTerminalControl}); + + process = launcher.Launch(*session); + } + else + { + std::vector fds{ + WSLA_PROCESS_FD{.Fd = 0, .Type = WSLAFdTypeTerminalInput}, + WSLA_PROCESS_FD{.Fd = 1, .Type = WSLAFdTypeTerminalOutput}, + WSLA_PROCESS_FD{.Fd = 2, .Type = WSLAFdTypeTerminalControl}, + }; + + WSLA_CONTAINER_OPTIONS containerOptions{}; + containerOptions.Image = containerImage.c_str(); + containerOptions.Name = "test-container"; + containerOptions.InitProcessOptions.Fds = fds.data(); + containerOptions.InitProcessOptions.FdsCount = static_cast(fds.size()); + + container.emplace(); + THROW_IF_FAILED(session->CreateContainer(&containerOptions, &container.value())); + + wil::com_ptr initProcess; + THROW_IF_FAILED((*container)->GetInitProcess(&initProcess)); + process.emplace(std::move(initProcess), std::move(fds)); + } // Configure console for interactive usage. @@ -1641,7 +1670,7 @@ int WslaShell(_In_ std::wstring_view commandLine) auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset); wsl::shared::SocketChannel controlChannel{ - wil::unique_socket{(SOCKET)process.GetStdHandle(2).release()}, "TerminalControl", exitEvent.get()}; + wil::unique_socket{(SOCKET)process->GetStdHandle(2).release()}, "TerminalControl", exitEvent.get()}; std::thread inputThread([&]() { auto updateTerminal = [&controlChannel, &Stdout]() { @@ -1657,7 +1686,7 @@ int WslaShell(_In_ std::wstring_view commandLine) controlChannel.SendMessage(message); }; - wsl::windows::common::relay::StandardInputRelay(Stdin, process.GetStdHandle(0).get(), updateTerminal, exitEvent.get()); + wsl::windows::common::relay::StandardInputRelay(Stdin, process->GetStdHandle(0).get(), updateTerminal, exitEvent.get()); }); auto joinThread = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { @@ -1666,12 +1695,12 @@ int WslaShell(_In_ std::wstring_view commandLine) }); // Relay the contents of the pipe to stdout. - wsl::windows::common::relay::InterruptableRelay(process.GetStdHandle(1).get(), Stdout); + wsl::windows::common::relay::InterruptableRelay(process->GetStdHandle(1).get(), Stdout); } - process.GetExitEvent().wait(); + process->GetExitEvent().wait(); - auto [code, signalled] = process.GetExitState(); + auto [code, signalled] = process->GetExitState(); wprintf(L"%hs exited with: %i%hs", shell.c_str(), code, signalled ? " (signalled)" : ""); return code; diff --git a/src/windows/common/WslCoreFilesystem.cpp b/src/windows/common/WslCoreFilesystem.cpp index 6fd79b31e..92cbe0165 100644 --- a/src/windows/common/WslCoreFilesystem.cpp +++ b/src/windows/common/WslCoreFilesystem.cpp @@ -29,8 +29,10 @@ wil::unique_hfile wsl::core::filesystem::CreateFile( void wsl::core::filesystem::CreateVhd(_In_ LPCWSTR target, _In_ ULONGLONG maximumSize, _In_ PSID userSid, _In_ BOOL sparse, _In_ BOOL fixed) { - WI_ASSERT(wsl::windows::common::string::IsPathComponentEqual( - std::filesystem::path{target}.extension().native(), windows::common::wslutil::c_vhdxFileExtension)); + THROW_HR_IF( + E_INVALIDARG, + !wsl::windows::common::string::IsPathComponentEqual( + std::filesystem::path{target}.extension().native(), windows::common::wslutil::c_vhdxFileExtension)); // Disable creation of sparse VHDs while data corruption is being debugged. if (sparse) diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index ca92c8046..6be65e6ff 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -22,9 +22,11 @@ const std::string nerdctlPath = "/usr/bin/nerdctl"; // Constants for required default arguments for "nerdctl run..." static std::vector defaultNerdctlRunArgs{ - "--pull=never", - "--host=net", // TODO: default for now, change later - "--ulimit nofile=65536:65536"}; + //"--pull=never", // TODO: Uncomment once PullImage() is implemented. + "-it", // TODO: only enable if fds allow for a tty. + "--net=host", // TODO: default for now, change later + "--ulimit", + "nofile=65536:65536"}; HRESULT WSLAContainer::Start() { @@ -68,16 +70,19 @@ Microsoft::WRL::ComPtr WSLAContainer::Create(const WSLA_CONTAINER { auto args = WSLAContainer::prepareNerdctlRunCommand(containerOptions); - ServiceProcessLauncher launcher(nerdctlPath, args); + ServiceProcessLauncher launcher(nerdctlPath, args, {}, common::ProcessFlags::None); + for (size_t i = 0; i < containerOptions.InitProcessOptions.FdsCount; i++) + { + launcher.AddFd(containerOptions.InitProcessOptions.Fds[i]); + } + return wil::MakeOrThrow(&parentVM, launcher.Launch(parentVM)); } std::vector WSLAContainer::prepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options) { - std::vector args; - + std::vector args{nerdctlPath}; args.push_back("run"); - args.insert(args.end(), defaultNerdctlRunArgs.begin(), defaultNerdctlRunArgs.end()); args.push_back("--name"); args.push_back(options.Name); if (options.ShmSize > 0) @@ -92,16 +97,8 @@ std::vector WSLAContainer::prepareNerdctlRunCommand(const WSLA_CONT // args.push_back(options.GPUOptions.GPUDevices); } - args.insert(args.end(), {"--ulimit", "nofile=65536:65536"}); + args.insert(args.end(), defaultNerdctlRunArgs.begin(), defaultNerdctlRunArgs.end()); - for (ULONG i = 0; i < options.InitProcessOptions->CommandLineCount; i++) - { - args.push_back(options.InitProcessOptions->CommandLine[i]); - } - for (ULONG i = 0; i < options.InitProcessOptions->EnvironmentCount; i++) - { - args.push_back(options.InitProcessOptions->Environment[i]); - } for (ULONG i = 0; i < options.VolumesCount; i++) { std::string mountContainerPath; @@ -115,13 +112,13 @@ std::vector WSLAContainer::prepareNerdctlRunCommand(const WSLA_CONT args.push_back(options.Image); - if (options.InitProcessOptions->CommandLineCount) + if (options.InitProcessOptions.CommandLineCount) { args.push_back("--"); } - for (ULONG i = 0; i < options.InitProcessOptions->CommandLineCount; i++) + for (ULONG i = 0; i < options.InitProcessOptions.CommandLineCount; i++) { - args.push_back(options.InitProcessOptions->CommandLine[i]); + args.push_back(options.InitProcessOptions.CommandLine[i]); } return args; diff --git a/src/windows/wslaservice/inc/wslaservice.idl b/src/windows/wslaservice/inc/wslaservice.idl index 5aa958fa6..16aea877a 100644 --- a/src/windows/wslaservice/inc/wslaservice.idl +++ b/src/windows/wslaservice/inc/wslaservice.idl @@ -138,7 +138,7 @@ struct WSLA_CONTAINER_OPTIONS { LPCSTR Image; LPCSTR Name; - struct WSLA_PROCESS_OPTIONS* InitProcessOptions; + struct WSLA_PROCESS_OPTIONS InitProcessOptions; [unique, size_is(VolumesCount)] struct WSLA_VOLUME* Volumes; ULONG VolumesCount; [unique, size_is(PortsCount)] struct WSLA_PORT_MAPPING* Ports; From 29971ace13ab64b0a1b11d5aa2a6e66168551501 Mon Sep 17 00:00:00 2001 From: Blue Date: Fri, 21 Nov 2025 16:01:34 -0800 Subject: [PATCH 07/29] Format --- src/windows/wslaservice/exe/WSLAContainer.cpp | 11 +++++------ src/windows/wslaservice/exe/WSLASession.cpp | 1 - 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index 6be65e6ff..c32dc0282 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -21,12 +21,11 @@ using wsl::windows::service::wsla::WSLAContainer; const std::string nerdctlPath = "/usr/bin/nerdctl"; // Constants for required default arguments for "nerdctl run..." -static std::vector defaultNerdctlRunArgs{ - //"--pull=never", // TODO: Uncomment once PullImage() is implemented. - "-it", // TODO: only enable if fds allow for a tty. - "--net=host", // TODO: default for now, change later - "--ulimit", - "nofile=65536:65536"}; +static std::vector defaultNerdctlRunArgs{ //"--pull=never", // TODO: Uncomment once PullImage() is implemented. + "-it", // TODO: only enable if fds allow for a tty. + "--net=host", // TODO: default for now, change later + "--ulimit", + "nofile=65536:65536"}; HRESULT WSLAContainer::Start() { diff --git a/src/windows/wslaservice/exe/WSLASession.cpp b/src/windows/wslaservice/exe/WSLASession.cpp index e00937fd7..3104dfaeb 100644 --- a/src/windows/wslaservice/exe/WSLASession.cpp +++ b/src/windows/wslaservice/exe/WSLASession.cpp @@ -88,7 +88,6 @@ try std::lock_guard lock{m_lock}; THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine); - // TODO: Log entrance into the function. m_containerId++; auto container = WSLAContainer::Create(*containerOptions, *m_virtualMachine.Get()); From cdf7c461247ae178a102657a5a996305c4f03c5f Mon Sep 17 00:00:00 2001 From: Blue Date: Fri, 21 Nov 2025 16:08:34 -0800 Subject: [PATCH 08/29] Format --- src/windows/wslaservice/exe/WSLAContainer.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index 4efb5aaa6..18d63f40c 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -21,12 +21,11 @@ using wsl::windows::service::wsla::WSLAContainer; const std::string nerdctlPath = "/usr/bin/nerdctl"; // Constants for required default arguments for "nerdctl run..." -static std::vector defaultNerdctlRunArgs{ - //"--pull=never", // TODO: Uncomment once PullImage() is implemented. - "-it", // TODO: only enable if fds allow for a tty. - "--net=host", // TODO: default for now, change later - "--ulimit", - "nofile=65536:65536"}; +static std::vector defaultNerdctlRunArgs{ //"--pull=never", // TODO: Uncomment once PullImage() is implemented. + "-it", // TODO: only enable if fds allow for a tty. + "--net=host", // TODO: default for now, change later + "--ulimit", + "nofile=65536:65536"}; HRESULT WSLAContainer::Start() { From 3b5c028af5e1d8bc6260c464534e34142b8f8055 Mon Sep 17 00:00:00 2001 From: Blue Date: Mon, 24 Nov 2025 18:29:39 -0800 Subject: [PATCH 09/29] Start writing tests --- src/shared/inc/defs.h | 4 + src/windows/common/CMakeLists.txt | 2 + src/windows/common/WSLAProcessLauncher.h | 7 +- .../wslaservice/exe/ServiceProcessLauncher.h | 5 +- src/windows/wslaservice/exe/WSLAContainer.cpp | 69 +++++++--- src/windows/wslaservice/exe/WSLAContainer.h | 2 +- src/windows/wslaservice/exe/WSLAProcess.cpp | 5 + src/windows/wslaservice/inc/wslaservice.idl | 5 +- test/windows/WSLATests.cpp | 118 +++++++++++++++++- 9 files changed, 188 insertions(+), 29 deletions(-) diff --git a/src/shared/inc/defs.h b/src/shared/inc/defs.h index cdeda12bd..b46853339 100644 --- a/src/shared/inc/defs.h +++ b/src/shared/inc/defs.h @@ -30,6 +30,10 @@ Module Name: Type(Type&&) = delete; \ Type& operator=(Type&&) = delete; +#define DEFAULT_MOVABLE(Type) \ + Type(Type&&) = default; \ + Type& operator=(Type&&) = default; + namespace wsl::shared { inline constexpr std::uint32_t VersionMajor = WSL_PACKAGE_VERSION_MAJOR; diff --git a/src/windows/common/CMakeLists.txt b/src/windows/common/CMakeLists.txt index 4424603e9..ba15ee5d1 100644 --- a/src/windows/common/CMakeLists.txt +++ b/src/windows/common/CMakeLists.txt @@ -34,6 +34,7 @@ set(SOURCES SubProcess.cpp svccomm.cpp svccommio.cpp + WSLAContainerLauncher.cpp WSLAProcessLauncher.cpp WslClient.cpp WslCoreConfig.cpp @@ -106,6 +107,7 @@ set(HEADERS SubProcess.h svccomm.hpp svccommio.hpp + WSLAContainerLauncher.h WSLAProcessLauncher.h WslClient.h WslCoreConfig.h diff --git a/src/windows/common/WSLAProcessLauncher.h b/src/windows/common/WSLAProcessLauncher.h index bd49105c0..1bdcade7f 100644 --- a/src/windows/common/WSLAProcessLauncher.h +++ b/src/windows/common/WSLAProcessLauncher.h @@ -44,8 +44,7 @@ class RunningWSLAProcess RunningWSLAProcess(std::vector&& fds); NON_COPYABLE(RunningWSLAProcess); - RunningWSLAProcess(RunningWSLAProcess&&) = default; - RunningWSLAProcess& operator=(RunningWSLAProcess&&) = default; + DEFAULT_MOVABLE(RunningWSLAProcess); ProcessResult WaitAndCaptureOutput(DWORD TimeoutMs = INFINITE, std::vector>&& ExtraHandles = {}); virtual wil::unique_handle GetStdHandle(int Index) = 0; @@ -61,6 +60,9 @@ class RunningWSLAProcess class ClientRunningWSLAProcess : public RunningWSLAProcess { public: + NON_COPYABLE(ClientRunningWSLAProcess); + DEFAULT_MOVABLE(ClientRunningWSLAProcess); + ClientRunningWSLAProcess(wil::com_ptr&& process, std::vector&& fds); wil::unique_handle GetStdHandle(int Index) override; wil::unique_event GetExitEvent() override; @@ -72,7 +74,6 @@ class ClientRunningWSLAProcess : public RunningWSLAProcess private: wil::com_ptr m_process; }; - class WSLAProcessLauncher { public: diff --git a/src/windows/wslaservice/exe/ServiceProcessLauncher.h b/src/windows/wslaservice/exe/ServiceProcessLauncher.h index 3e14e6a1a..88b4b0693 100644 --- a/src/windows/wslaservice/exe/ServiceProcessLauncher.h +++ b/src/windows/wslaservice/exe/ServiceProcessLauncher.h @@ -24,10 +24,7 @@ class ServiceRunningProcess : public common::RunningWSLAProcess { public: NON_COPYABLE(ServiceRunningProcess); - // NON_MOVABLE(ServiceRunningProcess); - - ServiceRunningProcess(ServiceRunningProcess&&) = default; - ServiceRunningProcess& operator=(ServiceRunningProcess&&) = default; + DEFAULT_MOVABLE(ServiceRunningProcess); ServiceRunningProcess(const Microsoft::WRL::ComPtr& process, std::vector&& fds); wil::unique_handle GetStdHandle(int Index) override; diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index 18d63f40c..df81ea3d0 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -21,8 +21,7 @@ using wsl::windows::service::wsla::WSLAContainer; const std::string nerdctlPath = "/usr/bin/nerdctl"; // Constants for required default arguments for "nerdctl run..." -static std::vector defaultNerdctlRunArgs{ //"--pull=never", // TODO: Uncomment once PullImage() is implemented. - "-it", // TODO: only enable if fds allow for a tty. +static std::vector defaultNerdctlRunArgs{//"--pull=never", // TODO: Uncomment once PullImage() is implemented. "--net=host", // TODO: default for now, change later "--ulimit", "nofile=65536:65536"}; @@ -67,7 +66,35 @@ CATCH_RETURN(); Microsoft::WRL::ComPtr WSLAContainer::Create(const WSLA_CONTAINER_OPTIONS& containerOptions, WSLAVirtualMachine& parentVM) { - auto args = WSLAContainer::prepareNerdctlRunCommand(containerOptions); + + bool hasStdin = false; + bool hasTty = false; + for (size_t i = 0; i < containerOptions.InitProcessOptions.FdsCount; i++) + { + if (containerOptions.InitProcessOptions.Fds[i].Fd == 0) + { + hasStdin = true; + } + + if (containerOptions.InitProcessOptions.Fds[i].Type == WSLAFdTypeTerminalInput || + containerOptions.InitProcessOptions.Fds[i].Type == WSLAFdTypeTerminalOutput) + { + hasTty = true; + } + } + + std::vector inputOptions; + if (hasStdin) + { + inputOptions.push_back("-i"); + } + + if (hasTty) + { + inputOptions.push_back("-t"); + } + + auto args = PrepareNerdctlRunCommand(containerOptions, std::move(inputOptions)); ServiceProcessLauncher launcher(nerdctlPath, args, {}, common::ProcessFlags::None); for (size_t i = 0; i < containerOptions.InitProcessOptions.FdsCount; i++) @@ -78,7 +105,7 @@ Microsoft::WRL::ComPtr WSLAContainer::Create(const WSLA_CONTAINER return wil::MakeOrThrow(&parentVM, launcher.Launch(parentVM)); } -std::vector WSLAContainer::prepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options) +std::vector WSLAContainer::PrepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options, std::vector&& inputOptions) { std::vector args{nerdctlPath}; args.push_back("run"); @@ -97,32 +124,40 @@ std::vector WSLAContainer::prepareNerdctlRunCommand(const WSLA_CONT } args.insert(args.end(), defaultNerdctlRunArgs.begin(), defaultNerdctlRunArgs.end()); + args.insert(args.end(), inputOptions.begin(), inputOptions.end()); // TODO: need to worry about env variables with dashes in them? for (ULONG i = 0; i < options.InitProcessOptions.EnvironmentCount; i++) { + THROW_HR_IF_MSG( + E_INVALIDARG, + options.InitProcessOptions.Environment[i][0] == L'-', + "Invlaid environment string: %hs", + options.InitProcessOptions.Environment[i]); + args.insert(args.end(), {"-e", options.InitProcessOptions.Environment[i]}); } - for (ULONG i = 0; i < options.VolumesCount; i++) + + if (options.InitProcessOptions.Executable != nullptr) { - std::string mountContainerPath; - mountContainerPath = std::string(options.Volumes[i].HostPath) + ":" + std::string(options.Volumes[i].ContainerPath); - if (options.Volumes[i].ReadOnly) - { - mountContainerPath += ":ro"; - } - args.insert(args.end(), {"-v", mountContainerPath}); + args.push_back("--entrypoint"); + args.push_back(options.InitProcessOptions.Executable); } + // TODO: + // - Implement volume mounts + // - Implement port mapping + args.push_back(options.Image); - if (options.InitProcessOptions.CommandLineCount) + if (options.InitProcessOptions.CommandLineCount > 0) { args.push_back("--"); - } - for (ULONG i = 0; i < options.InitProcessOptions.CommandLineCount; i++) - { - args.push_back(options.InitProcessOptions.CommandLine[i]); + + for (ULONG i = 0; i < options.InitProcessOptions.CommandLineCount; i++) + { + args.push_back(options.InitProcessOptions.CommandLine[i]); + } } // TODO: Implement --entrypoint override if specified in WSLA_CONTAINER_OPTIONS. diff --git a/src/windows/wslaservice/exe/WSLAContainer.h b/src/windows/wslaservice/exe/WSLAContainer.h index 4e9529bb6..d84eaac8f 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.h +++ b/src/windows/wslaservice/exe/WSLAContainer.h @@ -45,6 +45,6 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLAContainer ServiceRunningProcess m_containerProcess; WSLAVirtualMachine* m_parentVM = nullptr; - static std::vector prepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options); + static std::vector PrepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options, std::vector&& inputOptions); }; } // namespace wsl::windows::service::wsla \ No newline at end of file diff --git a/src/windows/wslaservice/exe/WSLAProcess.cpp b/src/windows/wslaservice/exe/WSLAProcess.cpp index 8eba6e7bf..30c1c24c0 100644 --- a/src/windows/wslaservice/exe/WSLAProcess.cpp +++ b/src/windows/wslaservice/exe/WSLAProcess.cpp @@ -74,6 +74,11 @@ try RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !socket.is_valid()); *Handle = HandleToUlong(common::wslutil::DuplicateHandleToCallingProcess(socket.get())); + WSL_LOG( + "GetStdHandle", + TraceLoggingValue(Index, "fd"), + TraceLoggingValue(socket.get(), "handle"), + TraceLoggingValue(*Handle, "remoteHandle")); socket.reset(); return S_OK; diff --git a/src/windows/wslaservice/inc/wslaservice.idl b/src/windows/wslaservice/inc/wslaservice.idl index 16aea877a..ef94c0427 100644 --- a/src/windows/wslaservice/inc/wslaservice.idl +++ b/src/windows/wslaservice/inc/wslaservice.idl @@ -104,7 +104,7 @@ struct WSLA_IMAGE_INFORMATION struct WSLA_PROCESS_OPTIONS { - LPCSTR Executable; + [unique] LPCSTR Executable; [unique] LPCSTR CurrentDirectory; [size_is(CommandLineCount)] LPCSTR* CommandLine; ULONG CommandLineCount; @@ -129,8 +129,7 @@ struct WSLA_PORT_MAPPING enum WSLA_CONTAINER_FLAGS { - WSLA_CONTAINER_FLAG_ENABLE_GPU = 0x0, - WSLA_CONTAINER_FLAG_USE_HOST_NETWORKING = 0x1 + WSLA_CONTAINER_FLAG_ENABLE_GPU = 1 } ; diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index ece716b50..3b06dddef 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -17,11 +17,14 @@ Module Name: #include "WSLAApi.h" #include "wslaservice.h" #include "WSLAProcessLauncher.h" +#include "WSLAContainerLauncher.h" #include "WslCoreFilesystem.h" using namespace wsl::windows::common::registry; using wsl::windows::common::ProcessFlags; +using wsl::windows::common::RunningWSLAContainer; using wsl::windows::common::RunningWSLAProcess; +using wsl::windows::common::WSLAContainerLauncher; using wsl::windows::common::WSLAProcessLauncher; using wsl::windows::common::relay::OverlappedIOHandle; using wsl::windows::common::relay::WriteHandle; @@ -53,7 +56,10 @@ class WSLATests wil::com_ptr CreateSession(VIRTUAL_MACHINE_SETTINGS& vmSettings, const WSLA_SESSION_SETTINGS& sessionSettings = {L"wsla-test"}) { - vmSettings.RootVhdType = "ext4"; + if (vmSettings.RootVhdType == nullptr) + { + vmSettings.RootVhdType = "ext4"; + } wil::com_ptr userSession; VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(WSLAUserSession), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&userSession))); @@ -132,6 +138,38 @@ class WSLATests return result; } + void ValidateProcessOutput(RunningWSLAProcess& process, const std::map& expectedOutput, int expectedResult = 0) + { + auto result = process.WaitAndCaptureOutput(); + + if (result.Code != expectedResult) + { + LogError( + "Comman didn't return expected code (%i). ExitCode: %i, Stdout: '%hs', Stderr: '%hs'", + expectedResult, + result.Code, + result.Output[1].c_str(), + result.Output[2].c_str()); + + return; + } + + for (const auto& [fd, expected] : expectedOutput) + { + auto it = result.Output.find(fd); + if (it == result.Output.end()) + { + LogError("Expected output on fd %i, but none found.", fd); + return; + } + + if (it->second != expected) + { + LogError("Unexpected output on fd %i. Expected: '%hs', Actual: '%hs'", fd, expected.c_str(), it->second.c_str()); + } + } + } + TEST_METHOD(CustomDmesgOutput) { WSL2_TEST_ONLY(); @@ -1098,4 +1136,82 @@ class WSLATests VERIFY_ARE_EQUAL(session->FormatVirtualDisk(L"DoesNotExist.vhdx"), E_INVALIDARG); VERIFY_ARE_EQUAL(session->FormatVirtualDisk(L"C:\\DoesNotExist.vhdx"), HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); } + + TEST_METHOD(CreateContainer) + { + WSL2_TEST_ONLY(); + + auto storageVhd = std::filesystem::current_path() / "storage.vhdx"; + + // Create a 1G temporary VHD. + if (!std::filesystem::exists(storageVhd)) + { + wsl::core::filesystem::CreateVhd(storageVhd.native().c_str(), 1024 * 1024 * 1024, nullptr, true, false); + } + + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(storageVhd.c_str())); }); + + VIRTUAL_MACHINE_SETTINGS settings{}; + settings.CpuCount = 4; + settings.DisplayName = L"WSLA"; + settings.MemoryMb = 2048; + settings.BootTimeoutMs = 30 * 1000; + settings.RootVhd = TEXT(WSLA_TEST_DISTRO_PATH); + settings.RootVhdType = "squashfs"; + settings.NetworkingMode = WSLANetworkingModeNAT; + settings.ContainerRootVhd = storageVhd.c_str(); + settings.FormatContainerRootVhd = true; + + auto session = CreateSession(settings); + + // TODO: Remove once the proper rootfs VHD is available. + ExpectCommandResult(session.get(), {"/etc/lsw-init.sh"}, 0); + + // Test a simple container start. + { + WSLAContainerLauncher launcher("debian:latest", "test-simple", "echo", {"OK"}); + auto container = launcher.Launch(*session); + auto process = container.GetInitProcess(); + + ValidateProcessOutput(process, {{1, "OK\n"}}); + } + + // Validate that env is correctly wired. + { + WSLAContainerLauncher launcher("debian:latest", "test-env", "/bin/bash", {"-c", "echo $testenv"}, {{"testenv=testvalue"}}); + auto container = launcher.Launch(*session); + auto process = container.GetInitProcess(); + + ValidateProcessOutput(process, {{1, "testvalue\n"}}); + } + + // Validate that starting containers work with the default entrypoint. + + // TODO: This is hanging. Need wsladbg to see why cat seems to be stuck. + /* + { + WSLAContainerLauncher launcher( + "debian:latest", "test-default-entrypoint", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | + ProcessFlags::Stderr); auto container = launcher.Launch(*session); auto process = container.GetInitProcess(); + + std::string shellInput = "echo $SHELL\n exit"; + std::unique_ptr writeStdin( + new WriteHandle(process.GetStdHandle(0), {shellInput.begin(), shellInput.end()})); + std::vector> extraHandles; + extraHandles.emplace_back(std::move(writeStdin)); + + auto result = process.WaitAndCaptureOutput(INFINITE, std::move(extraHandles)); + + VERIFY_ARE_EQUAL(result.Output[1], "foo"); + }*/ + + // Validate that stdin is empty if ProcessFlags::Stdin is not passed. + { + WSLAContainerLauncher launcher("debian:latest", "test-stdin", "/bin/cat"); + auto container = launcher.Launch(*session); + auto process = container.GetInitProcess(); + + ValidateProcessOutput(process, {{1, ""}}); + } + } }; \ No newline at end of file From 2a4f8d0bdda1c673e5b01979f0e2ad171b5aba7b Mon Sep 17 00:00:00 2001 From: Blue Date: Tue, 25 Nov 2025 16:57:45 -0800 Subject: [PATCH 10/29] Fix various issues --- src/windows/common/WslClient.cpp | 26 +++++++++++----- src/windows/common/wslutil.cpp | 3 +- src/windows/wslaservice/exe/WSLAProcess.cpp | 2 ++ src/windows/wslaservice/exe/WSLASession.cpp | 5 +++ src/windows/wslaservice/exe/WSLASession.h | 1 + .../wslaservice/exe/WSLAUserSession.cpp | 31 +++++++++++++++++-- src/windows/wslaservice/exe/WSLAUserSession.h | 2 ++ .../exe/WSLAUserSessionFactory.cpp | 6 +++- src/windows/wslaservice/inc/wslaservice.idl | 1 + test/windows/WSLATests.cpp | 11 +++---- 10 files changed, 70 insertions(+), 18 deletions(-) diff --git a/src/windows/common/WslClient.cpp b/src/windows/common/WslClient.cpp index 4e94a8c89..dedeb2de5 100644 --- a/src/windows/common/WslClient.cpp +++ b/src/windows/common/WslClient.cpp @@ -1551,6 +1551,7 @@ int WslaShell(_In_ std::wstring_view commandLine) std::wstring containerRootVhd; std::string containerImage; bool help = false; + std::wstring debugShell; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); parser.AddArgument(vhd, L"--vhd"); @@ -1561,6 +1562,7 @@ int WslaShell(_In_ std::wstring_view commandLine) parser.AddArgument(Utf8String(fsType), L"--fstype"); parser.AddArgument(containerRootVhd, L"--container-vhd"); parser.AddArgument(Utf8String(containerImage), L"--image"); + parser.AddArgument(debugShell, L"--debug-shell"); parser.AddArgument(help, L"--help"); parser.Parse(); @@ -1596,16 +1598,24 @@ int WslaShell(_In_ std::wstring_view commandLine) wil::com_ptr session; settings.RootVhd = vhd.c_str(); settings.RootVhdType = fsType.c_str(); - THROW_IF_FAILED(userSession->CreateSession(&sessionSettings, &settings, &session)); - THROW_IF_FAILED(session->GetVirtualMachine(&virtualMachine)); - wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get()); - - if (!containerRootVhd.empty()) + if (!debugShell.empty()) + { + THROW_IF_FAILED(userSession->OpenSessionByName(debugShell.c_str(), &session)); + } + else { - wsl::windows::common::WSLAProcessLauncher initProcessLauncher{shell, {shell, "/etc/lsw-init.sh"}}; - auto initProcess = initProcessLauncher.Launch(*session); - THROW_HR_IF(E_FAIL, initProcess.WaitAndCaptureOutput().Code != 0); + THROW_IF_FAILED(userSession->CreateSession(&sessionSettings, &settings, &session)); + THROW_IF_FAILED(session->GetVirtualMachine(&virtualMachine)); + + wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get()); + + if (!containerRootVhd.empty()) + { + wsl::windows::common::WSLAProcessLauncher initProcessLauncher{shell, {shell, "/etc/lsw-init.sh"}}; + auto initProcess = initProcessLauncher.Launch(*session); + THROW_HR_IF(E_FAIL, initProcess.WaitAndCaptureOutput().Code != 0); + } } std::optional> container; diff --git a/src/windows/common/wslutil.cpp b/src/windows/common/wslutil.cpp index fd0f10c06..43da515e4 100644 --- a/src/windows/common/wslutil.cpp +++ b/src/windows/common/wslutil.cpp @@ -143,7 +143,8 @@ static const std::map g_commonErrors{ X_WIN32(ERROR_OPERATION_ABORTED), X_WIN32(WSAECONNREFUSED), X_WIN32(ERROR_BAD_PATHNAME), - X(WININET_E_TIMEOUT)}; + X(WININET_E_TIMEOUT), + X_WIN32(ERROR_INVALID_SID)}; #undef X diff --git a/src/windows/wslaservice/exe/WSLAProcess.cpp b/src/windows/wslaservice/exe/WSLAProcess.cpp index 30c1c24c0..95269a36f 100644 --- a/src/windows/wslaservice/exe/WSLAProcess.cpp +++ b/src/windows/wslaservice/exe/WSLAProcess.cpp @@ -44,6 +44,8 @@ void WSLAProcess::OnVmTerminated() { m_state = WslaProcessStateSignalled; m_exitedCode = 9; // SIGKILL + + m_exitEvent.SetEvent(); } } diff --git a/src/windows/wslaservice/exe/WSLASession.cpp b/src/windows/wslaservice/exe/WSLASession.cpp index 3104dfaeb..bbfceff8a 100644 --- a/src/windows/wslaservice/exe/WSLASession.cpp +++ b/src/windows/wslaservice/exe/WSLASession.cpp @@ -60,6 +60,11 @@ HRESULT WSLASession::GetDisplayName(LPWSTR* DisplayName) return S_OK; } +const std::wstring& WSLASession::DisplayName() const +{ + return m_displayName; +} + HRESULT WSLASession::PullImage(LPCWSTR Image, const WSLA_REGISTRY_AUTHENTICATION_INFORMATION* RegistryInformation, IProgressCallback* ProgressCallback) { return E_NOTIMPL; diff --git a/src/windows/wslaservice/exe/WSLASession.h b/src/windows/wslaservice/exe/WSLASession.h index 5dc879623..362a70e78 100644 --- a/src/windows/wslaservice/exe/WSLASession.h +++ b/src/windows/wslaservice/exe/WSLASession.h @@ -27,6 +27,7 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLASession ~WSLASession(); IFACEMETHOD(GetDisplayName)(LPWSTR* DisplayName) override; + const std::wstring& DisplayName() const; // Image management. IFACEMETHOD(PullImage)(_In_ LPCWSTR Image, _In_ const WSLA_REGISTRY_AUTHENTICATION_INFORMATION* RegistryInformation, _In_ IProgressCallback* ProgressCallback) override; diff --git a/src/windows/wslaservice/exe/WSLAUserSession.cpp b/src/windows/wslaservice/exe/WSLAUserSession.cpp index f7d59418d..95daaf9bc 100644 --- a/src/windows/wslaservice/exe/WSLAUserSession.cpp +++ b/src/windows/wslaservice/exe/WSLAUserSession.cpp @@ -47,8 +47,7 @@ PSID WSLAUserSessionImpl::GetUserSid() const return m_tokenInfo->User.Sid; } -HRESULT wsl::windows::service::wsla::WSLAUserSessionImpl::CreateSession( - const WSLA_SESSION_SETTINGS* Settings, const VIRTUAL_MACHINE_SETTINGS* VmSettings, IWSLASession** WslaSession) +HRESULT WSLAUserSessionImpl::CreateSession(const WSLA_SESSION_SETTINGS* Settings, const VIRTUAL_MACHINE_SETTINGS* VmSettings, IWSLASession** WslaSession) { auto session = wil::MakeOrThrow(*Settings, *this, *VmSettings); @@ -63,6 +62,23 @@ HRESULT wsl::windows::service::wsla::WSLAUserSessionImpl::CreateSession( return S_OK; } +HRESULT WSLAUserSessionImpl::OpenSessionByName(LPCWSTR DisplayName, IWSLASession** Session) +{ + std::lock_guard lock(m_wslaSessionsLock); + + // TODO: Check for duplicate on session creation. + for (auto& e : m_sessions) + { + if (e->DisplayName() == DisplayName) + { + THROW_IF_FAILED(e->QueryInterface(__uuidof(IWSLASession), (void**)Session)); + return S_OK; + } + } + + return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); +} + wsl::windows::service::wsla::WSLAUserSession::WSLAUserSession(std::weak_ptr&& Session) : m_session(std::move(Session)) { @@ -92,7 +108,18 @@ HRESULT wsl::windows::service::wsla::WSLAUserSession::ListSessions(WSLA_SESSION_ { return E_NOTIMPL; } + HRESULT wsl::windows::service::wsla::WSLAUserSession::OpenSession(ULONG Id, IWSLASession** Session) { return E_NOTIMPL; } + +HRESULT wsl::windows::service::wsla::WSLAUserSession::OpenSessionByName(LPCWSTR DisplayName, IWSLASession** Session) +try +{ + auto session = m_session.lock(); + RETURN_HR_IF(RPC_E_DISCONNECTED, !session); + + return session->OpenSessionByName(DisplayName, Session); +} +CATCH_RETURN(); \ No newline at end of file diff --git a/src/windows/wslaservice/exe/WSLAUserSession.h b/src/windows/wslaservice/exe/WSLAUserSession.h index a7b6635ce..d0fcca5d8 100644 --- a/src/windows/wslaservice/exe/WSLAUserSession.h +++ b/src/windows/wslaservice/exe/WSLAUserSession.h @@ -30,6 +30,7 @@ class WSLAUserSessionImpl PSID GetUserSid() const; HRESULT CreateSession(const WSLA_SESSION_SETTINGS* Settings, const VIRTUAL_MACHINE_SETTINGS* VmSettings, IWSLASession** WslaSession); + HRESULT OpenSessionByName(_In_ LPCWSTR DisplayName, _Out_ IWSLASession** Session); void OnSessionTerminated(WSLASession* Session); @@ -55,6 +56,7 @@ class DECLSPEC_UUID("a9b7a1b9-0671-405c-95f1-e0612cb4ce8f") WSLAUserSession IFACEMETHOD(CreateSession)(const WSLA_SESSION_SETTINGS* WslaSessionSettings, const VIRTUAL_MACHINE_SETTINGS* VmSettings, IWSLASession** WslaSession) override; IFACEMETHOD(ListSessions)(_Out_ WSLA_SESSION_INFORMATION** Sessions, _Out_ ULONG* SessionsCount) override; IFACEMETHOD(OpenSession)(_In_ ULONG Id, _Out_ IWSLASession** Session) override; + IFACEMETHOD(OpenSessionByName)(_In_ LPCWSTR DisplayName, _Out_ IWSLASession** Session) override; private: std::weak_ptr m_session; diff --git a/src/windows/wslaservice/exe/WSLAUserSessionFactory.cpp b/src/windows/wslaservice/exe/WSLAUserSessionFactory.cpp index e13effec7..0a28ede8e 100644 --- a/src/windows/wslaservice/exe/WSLAUserSessionFactory.cpp +++ b/src/windows/wslaservice/exe/WSLAUserSessionFactory.cpp @@ -50,11 +50,15 @@ HRESULT WSLAUserSessionFactory::CreateInstance(_In_ IUnknown* pUnkOuter, _In_ RE THROW_HR_IF(CO_E_SERVER_STOPPING, !g_sessions.has_value()); auto session = std::find_if(g_sessions->begin(), g_sessions->end(), [&tokenInfo](auto it) { - return EqualSid(it->GetUserSid(), &tokenInfo->User.Sid); + return EqualSid(it->GetUserSid(), tokenInfo->User.Sid); }); if (session == g_sessions->end()) { + wil::unique_hlocal_string sid; + THROW_IF_WIN32_BOOL_FALSE(ConvertSidToStringSid(tokenInfo->User.Sid, &sid)); + WSL_LOG("WSLAUserSession created", TraceLoggingValue(sid.get(), "sid")); + session = g_sessions->insert(g_sessions->end(), std::make_shared(userToken.get(), std::move(tokenInfo))); } diff --git a/src/windows/wslaservice/inc/wslaservice.idl b/src/windows/wslaservice/inc/wslaservice.idl index ef94c0427..a3736c6d9 100644 --- a/src/windows/wslaservice/inc/wslaservice.idl +++ b/src/windows/wslaservice/inc/wslaservice.idl @@ -319,6 +319,7 @@ interface IWSLAUserSession : IUnknown HRESULT CreateSession([in] const struct WSLA_SESSION_SETTINGS* Settings, [in] const VIRTUAL_MACHINE_SETTINGS* VmSettings, [out]IWSLASession** Session); HRESULT ListSessions([out, size_is(, *SessionsCount)] struct WSLA_SESSION_INFORMATION** Sessions, [out] ULONG* SessionsCount); HRESULT OpenSession([in] ULONG Id, [out]IWSLASession** Session); + HRESULT OpenSessionByName([in] LPCWSTR DisplayName, [out]IWSLASession** Session); // TODO: Do we need 'TerminateSession()' ? } \ No newline at end of file diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index 3b06dddef..b0e2a46e7 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -1167,7 +1167,7 @@ class WSLATests // TODO: Remove once the proper rootfs VHD is available. ExpectCommandResult(session.get(), {"/etc/lsw-init.sh"}, 0); - // Test a simple container start. + /*// Test a simple container start. { WSLAContainerLauncher launcher("debian:latest", "test-simple", "echo", {"OK"}); auto container = launcher.Launch(*session); @@ -1178,16 +1178,15 @@ class WSLATests // Validate that env is correctly wired. { - WSLAContainerLauncher launcher("debian:latest", "test-env", "/bin/bash", {"-c", "echo $testenv"}, {{"testenv=testvalue"}}); - auto container = launcher.Launch(*session); - auto process = container.GetInitProcess(); + WSLAContainerLauncher launcher("debian:latest", "test-env", "/bin/bash", {"-c", "echo $testenv"}, + {{"testenv=testvalue"}}); auto container = launcher.Launch(*session); auto process = container.GetInitProcess(); ValidateProcessOutput(process, {{1, "testvalue\n"}}); - } + }*/ // Validate that starting containers work with the default entrypoint. - // TODO: This is hanging. Need wsladbg to see why cat seems to be stuck. + // TODO: This is hanging. nerdctl run seems to hang with -i is passed outside of a TTY context. /* { WSLAContainerLauncher launcher( From 9dd34f230dda59e78789992f7fc34db9898fc266 Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 26 Nov 2025 11:36:46 -0800 Subject: [PATCH 11/29] Add new files --- src/windows/common/WSLAContainerLauncher.cpp | 61 ++++++++++++++++++++ src/windows/common/WSLAContainerLauncher.h | 44 ++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/windows/common/WSLAContainerLauncher.cpp create mode 100644 src/windows/common/WSLAContainerLauncher.h diff --git a/src/windows/common/WSLAContainerLauncher.cpp b/src/windows/common/WSLAContainerLauncher.cpp new file mode 100644 index 000000000..6f8ce2753 --- /dev/null +++ b/src/windows/common/WSLAContainerLauncher.cpp @@ -0,0 +1,61 @@ +#include "WSLAContainerLauncher.h" + +using wsl::windows::common::ClientRunningWSLAProcess; +using wsl::windows::common::RunningWSLAContainer; +using wsl::windows::common::WSLAContainerLauncher; + +RunningWSLAContainer::RunningWSLAContainer(wil::com_ptr&& Container, std::vector&& fds) : + m_container(std::move(Container)), m_fds(std::move(fds)) +{ +} + +IWSLAContainer& RunningWSLAContainer::Get() +{ + return *m_container; +} + +WSLA_CONTAINER_STATE RunningWSLAContainer::State() +{ + WSLA_CONTAINER_STATE state{}; + THROW_IF_FAILED(m_container->GetState(&state)); + return state; +} + +ClientRunningWSLAProcess RunningWSLAContainer::GetInitProcess() +{ + wil::com_ptr process; + THROW_IF_FAILED(m_container->GetInitProcess(&process)); + + return ClientRunningWSLAProcess{std::move(process), std::move(m_fds)}; +} + +WSLAContainerLauncher::WSLAContainerLauncher( + const std::string& Image, + const std::string& Name, + const std::string& EntryPoint, + const std::vector& Arguments, + const std::vector& Environment, + ProcessFlags Flags) : + WSLAProcessLauncher(EntryPoint, Arguments, Environment, Flags), m_image(Image), m_name(Name) +{ +} + +RunningWSLAContainer WSLAContainerLauncher::Launch(IWSLASession& Session) +{ + WSLA_CONTAINER_OPTIONS options{}; + options.Image = m_image.c_str(); + options.Name = m_name.c_str(); + auto [processOptions, commandLinePtrs, environmentPtrs] = CreateProcessOptions(); + options.InitProcessOptions = processOptions; + + if (m_executable.empty()) + { + options.InitProcessOptions.Executable = nullptr; + } + + // TODO: Support volumes, ports, flags, shm size, etc. + wil::com_ptr container; + THROW_IF_FAILED(Session.CreateContainer(&options, &container)); + + return RunningWSLAContainer{std::move(container), std::move(m_fds)}; +} \ No newline at end of file diff --git a/src/windows/common/WSLAContainerLauncher.h b/src/windows/common/WSLAContainerLauncher.h new file mode 100644 index 000000000..11301afbe --- /dev/null +++ b/src/windows/common/WSLAContainerLauncher.h @@ -0,0 +1,44 @@ +#include "WSLAprocessLauncher.h" + +namespace wsl::windows::common { + +class RunningWSLAContainer +{ +public: + NON_COPYABLE(RunningWSLAContainer); + DEFAULT_MOVABLE(RunningWSLAContainer); + RunningWSLAContainer(wil::com_ptr&& Container, std::vector&& fds); + IWSLAContainer& Get(); + + WSLA_CONTAINER_STATE State(); + ClientRunningWSLAProcess GetInitProcess(); + +private: + wil::com_ptr m_container; + std::vector m_fds; +}; + +class WSLAContainerLauncher : public WSLAProcessLauncher +{ +public: + NON_COPYABLE(WSLAContainerLauncher); + NON_MOVABLE(WSLAContainerLauncher); + + WSLAContainerLauncher( + const std::string& Image, + const std::string& Name, + const std::string& EntryPoint = "", + const std::vector& Arguments = {}, + const std::vector& Environment = {}, + ProcessFlags Flags = ProcessFlags::Stdout | ProcessFlags::Stderr); + + void AddVolume(const std::string& HostPath, const std::string& ContainerPath, bool ReadOnly); + void AddPort(uint16_t WindowsPort, uint16_t ContainerPort, int Family); + + RunningWSLAContainer Launch(IWSLASession& Session); + +private: + std::string m_image; + std::string m_name; +}; +} // namespace wsl::windows::common \ No newline at end of file From 292707e3acbad4fd50c87aaf8d84996d27949377 Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 26 Nov 2025 12:29:11 -0800 Subject: [PATCH 12/29] Test cleanup --- src/windows/wslaservice/exe/WSLAContainer.cpp | 2 -- src/windows/wslaservice/exe/WSLAUserSession.cpp | 1 + test/windows/WSLATests.cpp | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index df81ea3d0..384ea4cdd 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -120,13 +120,11 @@ std::vector WSLAContainer::PrepareNerdctlRunCommand(const WSLA_CONT args.push_back("--gpus"); // TODO: Parse GPU device list from WSLA_CONTAINER_OPTIONS. For now, just enable all GPUs. args.push_back("all"); - // args.push_back(options.GPUOptions.GPUDevices); } args.insert(args.end(), defaultNerdctlRunArgs.begin(), defaultNerdctlRunArgs.end()); args.insert(args.end(), inputOptions.begin(), inputOptions.end()); - // TODO: need to worry about env variables with dashes in them? for (ULONG i = 0; i < options.InitProcessOptions.EnvironmentCount; i++) { THROW_HR_IF_MSG( diff --git a/src/windows/wslaservice/exe/WSLAUserSession.cpp b/src/windows/wslaservice/exe/WSLAUserSession.cpp index 95daaf9bc..20eddccb3 100644 --- a/src/windows/wslaservice/exe/WSLAUserSession.cpp +++ b/src/windows/wslaservice/exe/WSLAUserSession.cpp @@ -66,6 +66,7 @@ HRESULT WSLAUserSessionImpl::OpenSessionByName(LPCWSTR DisplayName, IWSLASession { std::lock_guard lock(m_wslaSessionsLock); + // TODO: ACL check // TODO: Check for duplicate on session creation. for (auto& e : m_sessions) { diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index b0e2a46e7..f7b88c26c 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -1167,7 +1167,7 @@ class WSLATests // TODO: Remove once the proper rootfs VHD is available. ExpectCommandResult(session.get(), {"/etc/lsw-init.sh"}, 0); - /*// Test a simple container start. + // Test a simple container start. { WSLAContainerLauncher launcher("debian:latest", "test-simple", "echo", {"OK"}); auto container = launcher.Launch(*session); @@ -1182,7 +1182,7 @@ class WSLATests {{"testenv=testvalue"}}); auto container = launcher.Launch(*session); auto process = container.GetInitProcess(); ValidateProcessOutput(process, {{1, "testvalue\n"}}); - }*/ + } // Validate that starting containers work with the default entrypoint. From 0eda07f0efbcaae169ee2cd61b0d922432205172 Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 26 Nov 2025 12:31:42 -0800 Subject: [PATCH 13/29] Test cleanup --- test/windows/WSLATests.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index f7b88c26c..59125bf35 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -1178,8 +1178,9 @@ class WSLATests // Validate that env is correctly wired. { - WSLAContainerLauncher launcher("debian:latest", "test-env", "/bin/bash", {"-c", "echo $testenv"}, - {{"testenv=testvalue"}}); auto container = launcher.Launch(*session); auto process = container.GetInitProcess(); + WSLAContainerLauncher launcher("debian:latest", "test-env", "/bin/bash", {"-c", "echo $testenv"}, {{"testenv=testvalue"}}); + auto container = launcher.Launch(*session); + auto process = container.GetInitProcess(); ValidateProcessOutput(process, {{1, "testvalue\n"}}); } From 987b1b73f4f7d69fbf7e7a9e79542a0068bdbe16 Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 26 Nov 2025 14:52:37 -0800 Subject: [PATCH 14/29] Prepare for PR --- src/windows/wslaservice/exe/WSLAContainer.cpp | 4 +++- src/windows/wslaservice/exe/WSLAContainer.h | 1 - src/windows/wslaservice/exe/WSLASession.cpp | 1 - src/windows/wslaservice/exe/WSLASession.h | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index 49297d5d0..51e640418 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -18,7 +18,7 @@ Module Name: using wsl::windows::service::wsla::WSLAContainer; -const std::string nerdctlPath = "/usr/bin/nerdctl"; +constexpr const char* nerdctlPath = "/usr/bin/nerdctl"; // Constants for required default arguments for "nerdctl run..." static std::vector defaultNerdctlRunArgs{//"--pull=never", // TODO: Uncomment once PullImage() is implemented. @@ -66,6 +66,8 @@ CATCH_RETURN(); Microsoft::WRL::ComPtr WSLAContainer::Create(const WSLA_CONTAINER_OPTIONS& containerOptions, WSLAVirtualMachine& parentVM) { + // TODO: Switch to nerdctl create, and call nerdctl start in Start(). + bool hasStdin = false; bool hasTty = false; for (size_t i = 0; i < containerOptions.InitProcessOptions.FdsCount; i++) diff --git a/src/windows/wslaservice/exe/WSLAContainer.h b/src/windows/wslaservice/exe/WSLAContainer.h index f627da616..d84eaac8f 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.h +++ b/src/windows/wslaservice/exe/WSLAContainer.h @@ -44,7 +44,6 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLAContainer private: ServiceRunningProcess m_containerProcess; WSLAVirtualMachine* m_parentVM = nullptr; - std::string m_id; static std::vector PrepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options, std::vector&& inputOptions); }; diff --git a/src/windows/wslaservice/exe/WSLASession.cpp b/src/windows/wslaservice/exe/WSLASession.cpp index bbfceff8a..1e34394ca 100644 --- a/src/windows/wslaservice/exe/WSLASession.cpp +++ b/src/windows/wslaservice/exe/WSLASession.cpp @@ -94,7 +94,6 @@ try THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine); // TODO: Log entrance into the function. - m_containerId++; auto container = WSLAContainer::Create(*containerOptions, *m_virtualMachine.Get()); THROW_IF_FAILED(container.CopyTo(__uuidof(IWSLAContainer), (void**)Container)); diff --git a/src/windows/wslaservice/exe/WSLASession.h b/src/windows/wslaservice/exe/WSLASession.h index 362a70e78..21eb62151 100644 --- a/src/windows/wslaservice/exe/WSLASession.h +++ b/src/windows/wslaservice/exe/WSLASession.h @@ -58,7 +58,6 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLASession std::wstring m_displayName; std::mutex m_lock; - std::atomic_int m_containerId = 1; // TODO: Add container tracking here. Could reuse m_lock for that. }; From a4ed4b28c6c1032c9aad69428a964d6831c54192 Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 26 Nov 2025 15:12:32 -0800 Subject: [PATCH 15/29] Fix ARM build --- test/windows/WSLATests.cpp | 116 ++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 54 deletions(-) diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index 59125bf35..590a76af3 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -1141,77 +1141,85 @@ class WSLATests { WSL2_TEST_ONLY(); - auto storageVhd = std::filesystem::current_path() / "storage.vhdx"; - - // Create a 1G temporary VHD. - if (!std::filesystem::exists(storageVhd)) + if constexpr (wsl::shared::Arm64) { - wsl::core::filesystem::CreateVhd(storageVhd.native().c_str(), 1024 * 1024 * 1024, nullptr, true, false); + LogSkipped("Skipping CreateContainer test case for ARM64"); } + else + { + auto storageVhd = std::filesystem::current_path() / "storage.vhdx"; - auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(storageVhd.c_str())); }); + // Create a 1G temporary VHD. + if (!std::filesystem::exists(storageVhd)) + { + wsl::core::filesystem::CreateVhd(storageVhd.native().c_str(), 1024 * 1024 * 1024, nullptr, true, false); + } - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = TEXT(WSLA_TEST_DISTRO_PATH); - settings.RootVhdType = "squashfs"; - settings.NetworkingMode = WSLANetworkingModeNAT; - settings.ContainerRootVhd = storageVhd.c_str(); - settings.FormatContainerRootVhd = true; + auto cleanup = + wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(storageVhd.c_str())); }); - auto session = CreateSession(settings); + VIRTUAL_MACHINE_SETTINGS settings{}; + settings.CpuCount = 4; + settings.DisplayName = L"WSLA"; + settings.MemoryMb = 2048; + settings.BootTimeoutMs = 30 * 1000; + settings.RootVhd = TEXT(WSLA_TEST_DISTRO_PATH); + settings.RootVhdType = "squashfs"; + settings.NetworkingMode = WSLANetworkingModeNAT; + settings.ContainerRootVhd = storageVhd.c_str(); + settings.FormatContainerRootVhd = true; + + auto session = CreateSession(settings); - // TODO: Remove once the proper rootfs VHD is available. - ExpectCommandResult(session.get(), {"/etc/lsw-init.sh"}, 0); + // TODO: Remove once the proper rootfs VHD is available. + ExpectCommandResult(session.get(), {"/etc/lsw-init.sh"}, 0); - // Test a simple container start. - { - WSLAContainerLauncher launcher("debian:latest", "test-simple", "echo", {"OK"}); - auto container = launcher.Launch(*session); - auto process = container.GetInitProcess(); + // Test a simple container start. + { + WSLAContainerLauncher launcher("debian:latest", "test-simple", "echo", {"OK"}); + auto container = launcher.Launch(*session); + auto process = container.GetInitProcess(); - ValidateProcessOutput(process, {{1, "OK\n"}}); - } + ValidateProcessOutput(process, {{1, "OK\n"}}); + } - // Validate that env is correctly wired. - { - WSLAContainerLauncher launcher("debian:latest", "test-env", "/bin/bash", {"-c", "echo $testenv"}, {{"testenv=testvalue"}}); - auto container = launcher.Launch(*session); - auto process = container.GetInitProcess(); + // Validate that env is correctly wired. + { + WSLAContainerLauncher launcher("debian:latest", "test-env", "/bin/bash", {"-c", "echo $testenv"}, {{"testenv=testvalue"}}); + auto container = launcher.Launch(*session); + auto process = container.GetInitProcess(); - ValidateProcessOutput(process, {{1, "testvalue\n"}}); - } + ValidateProcessOutput(process, {{1, "testvalue\n"}}); + } - // Validate that starting containers work with the default entrypoint. + // Validate that starting containers work with the default entrypoint. - // TODO: This is hanging. nerdctl run seems to hang with -i is passed outside of a TTY context. - /* - { - WSLAContainerLauncher launcher( - "debian:latest", "test-default-entrypoint", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | - ProcessFlags::Stderr); auto container = launcher.Launch(*session); auto process = container.GetInitProcess(); + // TODO: This is hanging. nerdctl run seems to hang with -i is passed outside of a TTY context. + /* + { + WSLAContainerLauncher launcher( + "debian:latest", "test-default-entrypoint", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | + ProcessFlags::Stderr); auto container = launcher.Launch(*session); auto process = container.GetInitProcess(); - std::string shellInput = "echo $SHELL\n exit"; - std::unique_ptr writeStdin( - new WriteHandle(process.GetStdHandle(0), {shellInput.begin(), shellInput.end()})); - std::vector> extraHandles; - extraHandles.emplace_back(std::move(writeStdin)); + std::string shellInput = "echo $SHELL\n exit"; + std::unique_ptr writeStdin( + new WriteHandle(process.GetStdHandle(0), {shellInput.begin(), shellInput.end()})); + std::vector> extraHandles; + extraHandles.emplace_back(std::move(writeStdin)); - auto result = process.WaitAndCaptureOutput(INFINITE, std::move(extraHandles)); + auto result = process.WaitAndCaptureOutput(INFINITE, std::move(extraHandles)); - VERIFY_ARE_EQUAL(result.Output[1], "foo"); - }*/ + VERIFY_ARE_EQUAL(result.Output[1], "foo"); + }*/ - // Validate that stdin is empty if ProcessFlags::Stdin is not passed. - { - WSLAContainerLauncher launcher("debian:latest", "test-stdin", "/bin/cat"); - auto container = launcher.Launch(*session); - auto process = container.GetInitProcess(); + // Validate that stdin is empty if ProcessFlags::Stdin is not passed. + { + WSLAContainerLauncher launcher("debian:latest", "test-stdin", "/bin/cat"); + auto container = launcher.Launch(*session); + auto process = container.GetInitProcess(); - ValidateProcessOutput(process, {{1, ""}}); + ValidateProcessOutput(process, {{1, ""}}); + } } } }; \ No newline at end of file From ee948d514aa44bd4c0652ccd9fc0c74db16a2d81 Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 26 Nov 2025 15:28:41 -0800 Subject: [PATCH 16/29] Add copyright header --- src/windows/common/WSLAContainerLauncher.cpp | 13 +++++++++++++ src/windows/common/WSLAContainerLauncher.h | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/windows/common/WSLAContainerLauncher.cpp b/src/windows/common/WSLAContainerLauncher.cpp index 6f8ce2753..bfaae6190 100644 --- a/src/windows/common/WSLAContainerLauncher.cpp +++ b/src/windows/common/WSLAContainerLauncher.cpp @@ -1,3 +1,16 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + WSLAContainerLauncher.cpp + +Abstract: + + This file contains the implementation for WSLAContainerLauncher. + +--*/ #include "WSLAContainerLauncher.h" using wsl::windows::common::ClientRunningWSLAProcess; diff --git a/src/windows/common/WSLAContainerLauncher.h b/src/windows/common/WSLAContainerLauncher.h index 11301afbe..b50d3ae70 100644 --- a/src/windows/common/WSLAContainerLauncher.h +++ b/src/windows/common/WSLAContainerLauncher.h @@ -1,3 +1,16 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + WSLAContainerLauncher.h + +Abstract: + + This file contains the definition for WSLAContainerLauncher. + +--*/ #include "WSLAprocessLauncher.h" namespace wsl::windows::common { From 08d5929e068837b2d542e84d7aba0d24377f7b34 Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 26 Nov 2025 15:42:58 -0800 Subject: [PATCH 17/29] Update cmakelists.txt --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d5f7651e8..3c778c403 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,7 +381,7 @@ if (DEFINED WSL_DEV_BINARY_PATH) # Development shortcut to make the package smal WSL_GPU_LIB_PATH="${WSL_DEV_BINARY_PATH}/lib") endif() -if (NOT DEFINED OFFICIAL_BUILD AND ${TARGET_PLATFORM} STREQUAL "x64") +if (NOT OFFICIAL_BUILD AND ${TARGET_PLATFORM} STREQUAL "x64") add_compile_definitions(WSLA_TEST_DISTRO_PATH="${WSLA_TEST_DISTRO_SOURCE_DIR}/wslatestrootfs.vhd") endif() From 2c28e536b75b81e255c2789c1d634904ddbf212a Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 26 Nov 2025 16:06:11 -0800 Subject: [PATCH 18/29] Use ifdefs --- test/windows/WSLATests.cpp | 126 +++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 62 deletions(-) diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index 590a76af3..c98970e8e 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -1141,85 +1141,87 @@ class WSLATests { WSL2_TEST_ONLY(); - if constexpr (wsl::shared::Arm64) +#ifdef _AMD64_ + + LogSkipped("Skipping CreateContainer test case for ARM64"); + return; + +#else + + auto storageVhd = std::filesystem::current_path() / "storage.vhdx"; + + // Create a 1G temporary VHD. + if (!std::filesystem::exists(storageVhd)) { - LogSkipped("Skipping CreateContainer test case for ARM64"); + wsl::core::filesystem::CreateVhd(storageVhd.native().c_str(), 1024 * 1024 * 1024, nullptr, true, false); } - else - { - auto storageVhd = std::filesystem::current_path() / "storage.vhdx"; - // Create a 1G temporary VHD. - if (!std::filesystem::exists(storageVhd)) - { - wsl::core::filesystem::CreateVhd(storageVhd.native().c_str(), 1024 * 1024 * 1024, nullptr, true, false); - } - - auto cleanup = - wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(storageVhd.c_str())); }); + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(storageVhd.c_str())); }); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = TEXT(WSLA_TEST_DISTRO_PATH); - settings.RootVhdType = "squashfs"; - settings.NetworkingMode = WSLANetworkingModeNAT; - settings.ContainerRootVhd = storageVhd.c_str(); - settings.FormatContainerRootVhd = true; + VIRTUAL_MACHINE_SETTINGS settings{}; + settings.CpuCount = 4; + settings.DisplayName = L"WSLA"; + settings.MemoryMb = 2048; + settings.BootTimeoutMs = 30 * 1000; + settings.RootVhd = TEXT(WSLA_TEST_DISTRO_PATH); + settings.RootVhdType = "squashfs"; + settings.NetworkingMode = WSLANetworkingModeNAT; + settings.ContainerRootVhd = storageVhd.c_str(); + settings.FormatContainerRootVhd = true; - auto session = CreateSession(settings); + auto session = CreateSession(settings); - // TODO: Remove once the proper rootfs VHD is available. - ExpectCommandResult(session.get(), {"/etc/lsw-init.sh"}, 0); + // TODO: Remove once the proper rootfs VHD is available. + ExpectCommandResult(session.get(), {"/etc/lsw-init.sh"}, 0); - // Test a simple container start. - { - WSLAContainerLauncher launcher("debian:latest", "test-simple", "echo", {"OK"}); - auto container = launcher.Launch(*session); - auto process = container.GetInitProcess(); + // Test a simple container start. + { + WSLAContainerLauncher launcher("debian:latest", "test-simple", "echo", {"OK"}); + auto container = launcher.Launch(*session); + auto process = container.GetInitProcess(); - ValidateProcessOutput(process, {{1, "OK\n"}}); - } + ValidateProcessOutput(process, {{1, "OK\n"}}); + } - // Validate that env is correctly wired. - { - WSLAContainerLauncher launcher("debian:latest", "test-env", "/bin/bash", {"-c", "echo $testenv"}, {{"testenv=testvalue"}}); - auto container = launcher.Launch(*session); - auto process = container.GetInitProcess(); + // Validate that env is correctly wired. + { + WSLAContainerLauncher launcher("debian:latest", "test-env", "/bin/bash", {"-c", "echo $testenv"}, {{"testenv=testvalue"}}); + auto container = launcher.Launch(*session); + auto process = container.GetInitProcess(); - ValidateProcessOutput(process, {{1, "testvalue\n"}}); - } + ValidateProcessOutput(process, {{1, "testvalue\n"}}); + } - // Validate that starting containers work with the default entrypoint. + // Validate that starting containers work with the default entrypoint. - // TODO: This is hanging. nerdctl run seems to hang with -i is passed outside of a TTY context. - /* - { - WSLAContainerLauncher launcher( - "debian:latest", "test-default-entrypoint", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | - ProcessFlags::Stderr); auto container = launcher.Launch(*session); auto process = container.GetInitProcess(); + // TODO: This is hanging. nerdctl run seems to hang with -i is passed outside of a TTY context. + /* + { + WSLAContainerLauncher launcher( + "debian:latest", "test-default-entrypoint", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | + ProcessFlags::Stderr); auto container = launcher.Launch(*session); auto process = container.GetInitProcess(); - std::string shellInput = "echo $SHELL\n exit"; - std::unique_ptr writeStdin( - new WriteHandle(process.GetStdHandle(0), {shellInput.begin(), shellInput.end()})); - std::vector> extraHandles; - extraHandles.emplace_back(std::move(writeStdin)); + std::string shellInput = "echo $SHELL\n exit"; + std::unique_ptr writeStdin( + new WriteHandle(process.GetStdHandle(0), {shellInput.begin(), shellInput.end()})); + std::vector> extraHandles; + extraHandles.emplace_back(std::move(writeStdin)); - auto result = process.WaitAndCaptureOutput(INFINITE, std::move(extraHandles)); + auto result = process.WaitAndCaptureOutput(INFINITE, std::move(extraHandles)); - VERIFY_ARE_EQUAL(result.Output[1], "foo"); - }*/ + VERIFY_ARE_EQUAL(result.Output[1], "foo"); + }*/ - // Validate that stdin is empty if ProcessFlags::Stdin is not passed. - { - WSLAContainerLauncher launcher("debian:latest", "test-stdin", "/bin/cat"); - auto container = launcher.Launch(*session); - auto process = container.GetInitProcess(); + // Validate that stdin is empty if ProcessFlags::Stdin is not passed. + { + WSLAContainerLauncher launcher("debian:latest", "test-stdin", "/bin/cat"); + auto container = launcher.Launch(*session); + auto process = container.GetInitProcess(); - ValidateProcessOutput(process, {{1, ""}}); - } + ValidateProcessOutput(process, {{1, ""}}); } + +#endif + } }; \ No newline at end of file From 52aa7f87597305decd9277f3e7d958348b9928e0 Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 26 Nov 2025 16:06:24 -0800 Subject: [PATCH 19/29] Format --- test/windows/WSLATests.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index c98970e8e..97017c7f8 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -1222,6 +1222,5 @@ class WSLATests } #endif - } }; \ No newline at end of file From 9c5ed515021effa37609b1fc7108cc292f00b79a Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 26 Nov 2025 16:07:53 -0800 Subject: [PATCH 20/29] ifdef ARM64 --- test/windows/WSLATests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index 97017c7f8..6cc0da23e 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -1141,7 +1141,7 @@ class WSLATests { WSL2_TEST_ONLY(); -#ifdef _AMD64_ +#ifdef _ARM64_ LogSkipped("Skipping CreateContainer test case for ARM64"); return; From 8d9e9cb2d1311644029dc54a493c4fc2046b94fb Mon Sep 17 00:00:00 2001 From: Blue Date: Mon, 1 Dec 2025 11:49:35 -0800 Subject: [PATCH 21/29] Install the test .vhd in the MSI --- msipackage/package.wix.in | 6 ++++++ test/windows/WSLATests.cpp | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/msipackage/package.wix.in b/msipackage/package.wix.in index f51bc8f3a..3944afebc 100644 --- a/msipackage/package.wix.in +++ b/msipackage/package.wix.in @@ -40,6 +40,12 @@ + + + + + + diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index 6cc0da23e..e2ba65b0e 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -1163,7 +1163,20 @@ class WSLATests settings.DisplayName = L"WSLA"; settings.MemoryMb = 2048; settings.BootTimeoutMs = 30 * 1000; + + auto installedVhdPath = + std::filesystem::path(wsl::windows::common::wslutil::GetMsiPackagePath().value()) / L"wslrootfs.vhd"; + +#ifdef WSLA_TEST_DISTRO_PATH + settings.RootVhd = TEXT(WSLA_TEST_DISTRO_PATH); + +#else + + settings.RootVhd = installedVhdPath.c_str(); + +#endif + settings.RootVhdType = "squashfs"; settings.NetworkingMode = WSLANetworkingModeNAT; settings.ContainerRootVhd = storageVhd.c_str(); From 0760be6becf81310990faed97bdf830a774fb8e1 Mon Sep 17 00:00:00 2001 From: Blue Date: Mon, 1 Dec 2025 14:19:57 -0800 Subject: [PATCH 22/29] Update the stdin test --- msipackage/package.wix.in | 2 +- src/windows/wslaservice/exe/WSLAContainer.cpp | 2 ++ test/windows/WSLATests.cpp | 30 ++++++++++++------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/msipackage/package.wix.in b/msipackage/package.wix.in index 3944afebc..eb599c58c 100644 --- a/msipackage/package.wix.in +++ b/msipackage/package.wix.in @@ -43,7 +43,7 @@ - + diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index 51e640418..d088d19a2 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -87,6 +87,8 @@ Microsoft::WRL::ComPtr WSLAContainer::Create(const WSLA_CONTAINER std::vector inputOptions; if (hasStdin) { + // For now return a proper error if the caller tries to pass stdin without a TTY to prevent hangs. + THROW_WIN32_IF(ERROR_NOT_SUPPORTED, hasTty == false); inputOptions.push_back("-i"); } diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index e2ba65b0e..074af326b 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -1165,9 +1165,9 @@ class WSLATests settings.BootTimeoutMs = 30 * 1000; auto installedVhdPath = - std::filesystem::path(wsl::windows::common::wslutil::GetMsiPackagePath().value()) / L"wslrootfs.vhd"; + std::filesystem::path(wsl::windows::common::wslutil::GetMsiPackagePath().value()) / L"wslarootfs.vhd"; -#ifdef WSLA_TEST_DISTRO_PATH +#ifdef WSL_DEV_INSTALL_PATH settings.RootVhd = TEXT(WSLA_TEST_DISTRO_PATH); @@ -1205,14 +1205,23 @@ class WSLATests ValidateProcessOutput(process, {{1, "testvalue\n"}}); } - // Validate that starting containers work with the default entrypoint. - - // TODO: This is hanging. nerdctl run seems to hang with -i is passed outside of a TTY context. - /* + // Validate that starting containers works with the default entrypoint. { WSLAContainerLauncher launcher( - "debian:latest", "test-default-entrypoint", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | - ProcessFlags::Stderr); auto container = launcher.Launch(*session); auto process = container.GetInitProcess(); + "debian:latest", "test-default-entrypoint", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | ProcessFlags::Stderr); + + // For now, validate that trying to use stdin without a tty returns the appropriate error. + auto result = wil::ResultFromException([&]() { + auto container = launcher.Launch(*session); + }); + + VERIFY_ARE_EQUAL(result, HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + + // This is hanging. nerdctl run seems to hang with -i is passed outside of a TTY context. + // TODO: Restore the test case once this is fixed. + + /* + auto process = container.GetInitProcess(); std::string shellInput = "echo $SHELL\n exit"; std::unique_ptr writeStdin( @@ -1222,8 +1231,9 @@ class WSLATests auto result = process.WaitAndCaptureOutput(INFINITE, std::move(extraHandles)); - VERIFY_ARE_EQUAL(result.Output[1], "foo"); - }*/ + VERIFY_ARE_EQUAL(result.Output[1], "bash\n"); + */ + } // Validate that stdin is empty if ProcessFlags::Stdin is not passed. { From 70ac182edb128d45692f0b55e84380a107a3f837 Mon Sep 17 00:00:00 2001 From: Blue Date: Mon, 1 Dec 2025 14:20:09 -0800 Subject: [PATCH 23/29] Format --- test/windows/WSLATests.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index 074af326b..b0a991811 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -1211,9 +1211,7 @@ class WSLATests "debian:latest", "test-default-entrypoint", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | ProcessFlags::Stderr); // For now, validate that trying to use stdin without a tty returns the appropriate error. - auto result = wil::ResultFromException([&]() { - auto container = launcher.Launch(*session); - }); + auto result = wil::ResultFromException([&]() { auto container = launcher.Launch(*session); }); VERIFY_ARE_EQUAL(result, HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); From 6d2831f4a7252a58223e2af341f1ee1a24e75666 Mon Sep 17 00:00:00 2001 From: Blue Date: Tue, 2 Dec 2025 11:18:02 -0800 Subject: [PATCH 24/29] Update src/windows/wslaservice/exe/WSLAContainer.cpp Co-authored-by: Pooja Trivedi --- src/windows/wslaservice/exe/WSLAContainer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index d088d19a2..a71ccc03c 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -161,7 +161,6 @@ std::vector WSLAContainer::PrepareNerdctlRunCommand(const WSLA_CONT } } - // TODO: Implement --entrypoint override if specified in WSLA_CONTAINER_OPTIONS. return args; } \ No newline at end of file From 2c7c3053402780a633970ed7e556dcd23b472994 Mon Sep 17 00:00:00 2001 From: Blue Date: Tue, 2 Dec 2025 11:18:42 -0800 Subject: [PATCH 25/29] Update src/windows/wslaservice/inc/wslaservice.idl Co-authored-by: Pooja Trivedi --- src/windows/wslaservice/inc/wslaservice.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/wslaservice/inc/wslaservice.idl b/src/windows/wslaservice/inc/wslaservice.idl index 82dc52568..b43057ff6 100644 --- a/src/windows/wslaservice/inc/wslaservice.idl +++ b/src/windows/wslaservice/inc/wslaservice.idl @@ -321,7 +321,7 @@ interface IWSLAUserSession : IUnknown HRESULT CreateSession([in] const struct WSLA_SESSION_SETTINGS* Settings, [in] const VIRTUAL_MACHINE_SETTINGS* VmSettings, [out]IWSLASession** Session); HRESULT ListSessions([out, size_is(, *SessionsCount)] struct WSLA_SESSION_INFORMATION** Sessions, [out] ULONG* SessionsCount); HRESULT OpenSession([in] ULONG Id, [out]IWSLASession** Session); - HRESULT OpenSessionByName([in] LPCWSTR DisplayName, [out]IWSLASession** Session); + HRESULT OpenSessionByName([in] LPCWSTR DisplayName, [out] IWSLASession** Session); // TODO: Do we need 'TerminateSession()' ? } \ No newline at end of file From 4acb3a424c8a4d941cedc4c579ec87e9baaa43e5 Mon Sep 17 00:00:00 2001 From: Blue Date: Tue, 2 Dec 2025 11:21:10 -0800 Subject: [PATCH 26/29] Update test/windows/WSLATests.cpp Co-authored-by: Pooja Trivedi --- test/windows/WSLATests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index b0a991811..61fdb8d5a 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -145,7 +145,7 @@ class WSLATests if (result.Code != expectedResult) { LogError( - "Comman didn't return expected code (%i). ExitCode: %i, Stdout: '%hs', Stderr: '%hs'", + "Command didn't return expected code (%i). ExitCode: %i, Stdout: '%hs', Stderr: '%hs'", expectedResult, result.Code, result.Output[1].c_str(), From 61bca24c7b95e29ce6008920ba467da74c0bf940 Mon Sep 17 00:00:00 2001 From: Blue Date: Tue, 2 Dec 2025 11:22:09 -0800 Subject: [PATCH 27/29] Update src/windows/common/WSLAContainerLauncher.cpp Co-authored-by: Pooja Trivedi --- src/windows/common/WSLAContainerLauncher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/common/WSLAContainerLauncher.cpp b/src/windows/common/WSLAContainerLauncher.cpp index bfaae6190..950fb647e 100644 --- a/src/windows/common/WSLAContainerLauncher.cpp +++ b/src/windows/common/WSLAContainerLauncher.cpp @@ -66,7 +66,7 @@ RunningWSLAContainer WSLAContainerLauncher::Launch(IWSLASession& Session) options.InitProcessOptions.Executable = nullptr; } - // TODO: Support volumes, ports, flags, shm size, etc. + // TODO: Support volumes, ports, flags, shm size, container networking mode, etc. wil::com_ptr container; THROW_IF_FAILED(Session.CreateContainer(&options, &container)); From 6fc68e8999363dabcda6be60564e19057b6323f4 Mon Sep 17 00:00:00 2001 From: Blue Date: Tue, 2 Dec 2025 11:45:49 -0800 Subject: [PATCH 28/29] Apply PR feedback --- src/windows/common/WSLAContainerLauncher.h | 4 +++- src/windows/wslaservice/exe/WSLAContainer.cpp | 14 +++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/windows/common/WSLAContainerLauncher.h b/src/windows/common/WSLAContainerLauncher.h index b50d3ae70..9a4447eb4 100644 --- a/src/windows/common/WSLAContainerLauncher.h +++ b/src/windows/common/WSLAContainerLauncher.h @@ -11,7 +11,9 @@ Module Name: This file contains the definition for WSLAContainerLauncher. --*/ -#include "WSLAprocessLauncher.h" + +#pragma once +#include "WSLAProcessLauncher.h" namespace wsl::windows::common { diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index a71ccc03c..87ace6b0b 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -21,10 +21,11 @@ using wsl::windows::service::wsla::WSLAContainer; constexpr const char* nerdctlPath = "/usr/bin/nerdctl"; // Constants for required default arguments for "nerdctl run..." -static std::vector defaultNerdctlRunArgs{//"--pull=never", // TODO: Uncomment once PullImage() is implemented. - "--net=host", // TODO: default for now, change later - "--ulimit", - "nofile=65536:65536"}; +static std::vector defaultNerdctlRunArgs{ + //"--pull=never", // TODO: Uncomment once PullImage() is implemented. + "--net=host", // TODO: default for now, change later + "--ulimit", + "nofile=65536:65536"}; HRESULT WSLAContainer::Start() { @@ -116,7 +117,7 @@ std::vector WSLAContainer::PrepareNerdctlRunCommand(const WSLA_CONT args.push_back(options.Name); if (options.ShmSize > 0) { - args.push_back("--shm-size=" + std::to_string(options.ShmSize) + 'm'); + args.push_back(std::format("--shm-size={}m", options.ShmSize)); } if (options.Flags & WSLA_CONTAINER_FLAG_ENABLE_GPU) { @@ -133,7 +134,7 @@ std::vector WSLAContainer::PrepareNerdctlRunCommand(const WSLA_CONT THROW_HR_IF_MSG( E_INVALIDARG, options.InitProcessOptions.Environment[i][0] == L'-', - "Invlaid environment string: %hs", + "Invalid environment string: %hs", options.InitProcessOptions.Environment[i]); args.insert(args.end(), {"-e", options.InitProcessOptions.Environment[i]}); @@ -161,6 +162,5 @@ std::vector WSLAContainer::PrepareNerdctlRunCommand(const WSLA_CONT } } - return args; } \ No newline at end of file From 912af10f0868f8f43fa40c11d8095d709ca305c6 Mon Sep 17 00:00:00 2001 From: Blue Date: Tue, 2 Dec 2025 11:47:25 -0800 Subject: [PATCH 29/29] Format --- src/windows/wslaservice/exe/WSLAContainer.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index 87ace6b0b..5c241c820 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -21,11 +21,10 @@ using wsl::windows::service::wsla::WSLAContainer; constexpr const char* nerdctlPath = "/usr/bin/nerdctl"; // Constants for required default arguments for "nerdctl run..." -static std::vector defaultNerdctlRunArgs{ - //"--pull=never", // TODO: Uncomment once PullImage() is implemented. - "--net=host", // TODO: default for now, change later - "--ulimit", - "nofile=65536:65536"}; +static std::vector defaultNerdctlRunArgs{//"--pull=never", // TODO: Uncomment once PullImage() is implemented. + "--net=host", // TODO: default for now, change later + "--ulimit", + "nofile=65536:65536"}; HRESULT WSLAContainer::Start() {