From 1220fbe389adeb3b81d77833750e1516a84df225 Mon Sep 17 00:00:00 2001 From: Pooja Trivedi Date: Wed, 19 Nov 2025 10:53:58 -0500 Subject: [PATCH 01/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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 b7ff00b73b40c7ed7dc9f742b31eba0ac4ac68dd Mon Sep 17 00:00:00 2001 From: Blue Date: Mon, 1 Dec 2025 18:35:42 -0800 Subject: [PATCH 24/27] Refactor the session creation logic to match the new WSLA API --- src/windows/common/WslClient.cpp | 86 +++--- src/windows/wslaservice/exe/WSLASession.cpp | 144 +++++++++- src/windows/wslaservice/exe/WSLASession.h | 8 +- .../wslaservice/exe/WSLAUserSession.cpp | 9 +- src/windows/wslaservice/exe/WSLAUserSession.h | 4 +- .../wslaservice/exe/WSLAVirtualMachine.cpp | 74 +++--- .../wslaservice/exe/WSLAVirtualMachine.h | 20 +- src/windows/wslaservice/inc/wslaservice.idl | 40 ++- test/windows/WSLATests.cpp | 247 +++++------------- 9 files changed, 318 insertions(+), 314 deletions(-) diff --git a/src/windows/common/WslClient.cpp b/src/windows/common/WslClient.cpp index cc95ee14c..d0c33f894 100644 --- a/src/windows/common/WslClient.cpp +++ b/src/windows/common/WslClient.cpp @@ -1525,42 +1525,37 @@ int RunDebugShell() THROW_HR(HCS_E_CONNECTION_CLOSED); } +DEFINE_ENUM_FLAG_OPERATORS(WSLAFeatureFlags); + // Temporary debugging tool for WSLA int WslaShell(_In_ std::wstring_view commandLine) { -#ifdef WSLA_TEST_DISTRO_PATH + WSLA_SESSION_SETTINGS sessionSettings{}; + sessionSettings.DisplayName = L"WSLAShell"; + sessionSettings.CpuCount = 4; + sessionSettings.MemoryMb = 4096; + sessionSettings.NetworkingMode = WSLANetworkingModeNAT; + sessionSettings.BootTimeoutMs = 30 * 1000; + sessionSettings.MaximumStorageSizeMb = 4096; - std::wstring vhd = TEXT(WSLA_TEST_DISTRO_PATH); std::string shell = "/bin/sh"; - std::string fsType = "squashfs"; - -#else - - std::wstring vhd = wsl::windows::common::wslutil::GetMsiPackagePath().value() + L"/system.vhd"; - std::string shell = "/bin/bash"; - std::string fsType = "ext4"; -#endif - - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 1024; - settings.BootTimeoutMs = 30000; - settings.NetworkingMode = WSLANetworkingModeNAT; - std::wstring containerRootVhd; std::string containerImage; bool help = false; std::wstring debugShell; + std::wstring storagePath; + std::wstring rootVhdOverride; + std::string rootVhdTypeOverride; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); - parser.AddArgument(vhd, L"--vhd"); + parser.AddArgument(rootVhdOverride, L"--vhd"); parser.AddArgument(Utf8String(shell), L"--shell"); - parser.AddArgument(reinterpret_cast(settings.EnableDnsTunneling), L"--dns-tunneling"); - parser.AddArgument(Integer(settings.MemoryMb), L"--memory"); - parser.AddArgument(Integer(settings.CpuCount), L"--cpu"); - parser.AddArgument(Utf8String(fsType), L"--fstype"); - parser.AddArgument(containerRootVhd, L"--container-vhd"); + parser.AddArgument( + SetFlag(reinterpret_cast(sessionSettings.FeatureFlags)), L"--dns-tunneling"); + parser.AddArgument(Integer(sessionSettings.MemoryMb), L"--memory"); + parser.AddArgument(Integer(sessionSettings.CpuCount), L"--cpu"); + parser.AddArgument(Utf8String(rootVhdTypeOverride), L"--fstype"); + parser.AddArgument(storagePath, L"--storage"); parser.AddArgument(Utf8String(containerImage), L"--image"); parser.AddArgument(debugShell, L"--debug-shell"); parser.AddArgument(help, L"--help"); @@ -1576,28 +1571,30 @@ int WslaShell(_In_ std::wstring_view commandLine) return 1; } - if (!containerRootVhd.empty()) - { - settings.ContainerRootVhd = containerRootVhd.c_str(); - - if (!std::filesystem::exists(containerRootVhd)) - { - auto token = wil::open_current_access_token(); - auto tokenInfo = wil::get_token_information(token.get()); - wsl::core::filesystem::CreateVhd(containerRootVhd.c_str(), 5368709120 /* 5 GB */, tokenInfo->User.Sid, FALSE, FALSE); - settings.FormatContainerRootVhd = TRUE; - } - } - wil::com_ptr userSession; THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLAUserSession), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&userSession))); wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get()); wil::com_ptr virtualMachine; - WSLA_SESSION_SETTINGS sessionSettings{L"WSLA Test Session"}; wil::com_ptr session; - settings.RootVhd = vhd.c_str(); - settings.RootVhdType = fsType.c_str(); + if (!rootVhdOverride.empty()) + { + sessionSettings.RootVhdOverride = rootVhdOverride.c_str(); + + if (rootVhdTypeOverride.empty()) + { + wprintf(L"--fstype required when --vhd is passed\n"); + return 1; + } + + sessionSettings.RootVhdTypeOverride = rootVhdTypeOverride.c_str(); + } + + if (!storagePath.empty()) + { + storagePath = std::filesystem::weakly_canonical(storagePath).wstring(); + sessionSettings.StoragePath = storagePath.c_str(); + } if (!debugShell.empty()) { @@ -1605,17 +1602,10 @@ int WslaShell(_In_ std::wstring_view commandLine) } else { - THROW_IF_FAILED(userSession->CreateSession(&sessionSettings, &settings, &session)); + THROW_IF_FAILED(userSession->CreateSession(&sessionSettings, &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/wslaservice/exe/WSLASession.cpp b/src/windows/wslaservice/exe/WSLASession.cpp index 1e34394ca..a22cb2d7b 100644 --- a/src/windows/wslaservice/exe/WSLASession.cpp +++ b/src/windows/wslaservice/exe/WSLASession.cpp @@ -17,23 +17,77 @@ Module Name: #include "WSLAUserSession.h" #include "WSLAContainer.h" #include "ServiceProcessLauncher.h" +#include "WslCoreFilesystem.h" using wsl::windows::service::wsla::WSLASession; +using wsl::windows::service::wsla::WSLAVirtualMachine; -WSLASession::WSLASession(const WSLA_SESSION_SETTINGS& Settings, WSLAUserSessionImpl& userSessionImpl, const VIRTUAL_MACHINE_SETTINGS& VmSettings) : - m_sessionSettings(Settings), - m_userSession(&userSessionImpl), - m_virtualMachine(wil::MakeOrThrow(VmSettings, userSessionImpl.GetUserSid(), &userSessionImpl)), - m_displayName(Settings.DisplayName) +WSLASession::WSLASession(const WSLA_SESSION_SETTINGS& Settings, WSLAUserSessionImpl& userSessionImpl) : + m_sessionSettings(Settings), m_userSession(&userSessionImpl), m_displayName(Settings.DisplayName) { WSL_LOG("SessionCreated", TraceLoggingValue(m_displayName.c_str(), "DisplayName")); + m_virtualMachine = wil::MakeOrThrow(CreateVmSettings(Settings), userSessionImpl.GetUserSid(), &userSessionImpl); + if (Settings.TerminationCallback != nullptr) { m_virtualMachine->RegisterCallback(Settings.TerminationCallback); } m_virtualMachine->Start(); + + ConfigureStorage(Settings); + + // Launch the init script. + // TODO: Replace with something more robust once the final VHD is ready. + try + { + ServiceProcessLauncher launcher{"/bin/sh", {"/bin/sh", "-c", "/etc/lsw-init.sh"}}; + + auto result = launcher.Launch(*m_virtualMachine.Get()).WaitAndCaptureOutput(); + + THROW_HR_IF_MSG(E_FAIL, result.Code != 0, "Init script failed: %hs", launcher.FormatResult(result).c_str()); + } + catch (...) + { + // Ignore issues launching the init script with custom root VHD's, for convenience. + // TODO: Remove once the final VHD is ready. + if (Settings.RootVhdOverride == nullptr) + { + throw; + } + } +} + +WSLAVirtualMachine::Settings WSLASession::CreateVmSettings(const WSLA_SESSION_SETTINGS& Settings) +{ + WSLAVirtualMachine::Settings vmSettings{}; + vmSettings.CpuCount = Settings.CpuCount; + vmSettings.MemoryMb = Settings.MemoryMb; + vmSettings.NetworkingMode = Settings.NetworkingMode; + vmSettings.BootTimeoutMs = Settings.BootTimeoutMs; + vmSettings.FeatureFlags = static_cast(Settings.FeatureFlags); + vmSettings.DisplayName = Settings.DisplayName; + + if (Settings.RootVhdOverride != nullptr) + { + THROW_HR_IF(E_INVALIDARG, Settings.RootVhdTypeOverride == nullptr); + + vmSettings.RootVhd = Settings.RootVhdOverride; + vmSettings.RootVhdType = Settings.RootVhdTypeOverride; + } + else + { + vmSettings.RootVhd = std::filesystem::path(common::wslutil::GetMsiPackagePath().value()) / L"wslarootfs.vhd"; + vmSettings.RootVhdType = "squashfs"; + } + + if (Settings.DmesgOutput != 0) + { + vmSettings.DmesgHandle.reset(wsl::windows::common::wslutil::DuplicateHandleFromCallingProcess(ULongToHandle(Settings.DmesgOutput))); + } + + return vmSettings; } WSLASession::~WSLASession() @@ -54,6 +108,71 @@ WSLASession::~WSLASession() } } +void WSLASession::ConfigureStorage(const WSLA_SESSION_SETTINGS& Settings) +{ + if (Settings.StoragePath != nullptr) + { + std::filesystem::path storagePath{Settings.StoragePath}; + THROW_HR_IF_MSG(E_INVALIDARG, !storagePath.is_absolute(), "Storage path is not absolute: %ls", storagePath.c_str()); + + m_storageVhdPath = storagePath / "storage.vhdx"; + + std::string diskDevice; + std::optional diskLun{}; + bool vhdCreated = false; + + auto deleteVhdOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { + if (vhdCreated) + { + if (diskLun.has_value()) + { + m_virtualMachine->DetachDisk(diskLun.value()); + } + + LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(m_storageVhdPath.c_str())); + } + }); + + auto result = + wil::ResultFromException([&]() { diskDevice = m_virtualMachine->AttachDisk(m_storageVhdPath.c_str(), false).second; }); + + if (FAILED(result)) + { + THROW_HR_IF_MSG( + result, + result != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) && result != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), + "Failed to attach vhd: %ls", + m_storageVhdPath.c_str()); + + // If the VHD wasn'found, create it. + WSL_LOG("CreateStorageVhd", TraceLoggingValue(m_storageVhdPath.c_str(), "StorageVhdPath")); + + auto runAsUser = wil::CoImpersonateClient(); + + std::filesystem::create_directories(storagePath); + wsl::core::filesystem::CreateVhd( + m_storageVhdPath.c_str(), Settings.MaximumStorageSizeMb * _1MB, m_userSession->GetUserSid(), false, false); + vhdCreated = true; + + // Then attach the new disk. + std::tie(diskLun, diskDevice) = m_virtualMachine->AttachDisk(m_storageVhdPath.c_str(), false); + + // Then format it. + Ext4Format(diskDevice); + } + + // Mount the device to /root. + m_virtualMachine->Mount(diskDevice.c_str(), "/root", "ext4", "", 0); + + deleteVhdOnFailure.release(); + } + else + { + // If no storage path is specified, use a tmpfs for convenience. + m_virtualMachine->Mount("", "/root", "tmpfs", "", 0); + } +} + HRESULT WSLASession::GetDisplayName(LPWSTR* DisplayName) { *DisplayName = wil::make_unique_string(m_displayName.c_str()).release(); @@ -135,6 +254,15 @@ try } CATCH_RETURN(); +void WSLASession::Ext4Format(const std::string& Device) +{ + constexpr auto mkfsPath = "/usr/sbin/mkfs.ext4"; + ServiceProcessLauncher launcher(mkfsPath, {mkfsPath, Device}); + auto result = launcher.Launch(*m_virtualMachine.Get()).WaitAndCaptureOutput(); + + THROW_HR_IF_MSG(E_FAIL, result.Code != 0, "%hs", launcher.FormatResult(result).c_str()); +} + HRESULT WSLASession::FormatVirtualDisk(LPCWSTR Path) try { @@ -150,11 +278,7 @@ try auto detachDisk = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [this, lun]() { m_virtualMachine->DetachDisk(lun); }); // Format it to ext4. - constexpr auto mkfsPath = "/usr/sbin/mkfs.ext4"; - ServiceProcessLauncher launcher(mkfsPath, {mkfsPath, device}); - auto result = launcher.Launch(*m_virtualMachine.Get()).WaitAndCaptureOutput(); - - THROW_HR_IF_MSG(E_FAIL, result.Code != 0, "%hs", launcher.FormatResult(result).c_str()); + Ext4Format(device); return S_OK; } diff --git a/src/windows/wslaservice/exe/WSLASession.h b/src/windows/wslaservice/exe/WSLASession.h index 21eb62151..2573238aa 100644 --- a/src/windows/wslaservice/exe/WSLASession.h +++ b/src/windows/wslaservice/exe/WSLASession.h @@ -23,7 +23,7 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLASession : public Microsoft::WRL::RuntimeClass, IWSLASession, IFastRundown> { public: - WSLASession(const WSLA_SESSION_SETTINGS& Settings, WSLAUserSessionImpl& userSessionImpl, const VIRTUAL_MACHINE_SETTINGS& VmSettings); + WSLASession(const WSLA_SESSION_SETTINGS& Settings, WSLAUserSessionImpl& userSessionImpl); ~WSLASession(); IFACEMETHOD(GetDisplayName)(LPWSTR* DisplayName) override; @@ -52,10 +52,16 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLASession void OnUserSessionTerminating(); private: + static WSLAVirtualMachine::Settings CreateVmSettings(const WSLA_SESSION_SETTINGS& Settings); + + void ConfigureStorage(const WSLA_SESSION_SETTINGS& Settings); + void Ext4Format(const std::string& Device); + WSLA_SESSION_SETTINGS m_sessionSettings; // TODO: Revisit to see if we should have session settings as a member or not WSLAUserSessionImpl* m_userSession = nullptr; Microsoft::WRL::ComPtr m_virtualMachine; std::wstring m_displayName; + std::filesystem::path m_storageVhdPath; std::mutex m_lock; // TODO: Add container tracking here. Could reuse m_lock for that. diff --git a/src/windows/wslaservice/exe/WSLAUserSession.cpp b/src/windows/wslaservice/exe/WSLAUserSession.cpp index 20eddccb3..ac2cd3cd0 100644 --- a/src/windows/wslaservice/exe/WSLAUserSession.cpp +++ b/src/windows/wslaservice/exe/WSLAUserSession.cpp @@ -47,9 +47,9 @@ PSID WSLAUserSessionImpl::GetUserSid() const return m_tokenInfo->User.Sid; } -HRESULT WSLAUserSessionImpl::CreateSession(const WSLA_SESSION_SETTINGS* Settings, const VIRTUAL_MACHINE_SETTINGS* VmSettings, IWSLASession** WslaSession) +HRESULT WSLAUserSessionImpl::CreateSession(const WSLA_SESSION_SETTINGS* Settings, IWSLASession** WslaSession) { - auto session = wil::MakeOrThrow(*Settings, *this, *VmSettings); + auto session = wil::MakeOrThrow(*Settings, *this); std::lock_guard lock(m_wslaSessionsLock); auto it = m_sessions.emplace(session.Get()); @@ -94,14 +94,13 @@ HRESULT wsl::windows::service::wsla::WSLAUserSession::GetVersion(_Out_ WSLA_VERS return S_OK; } -HRESULT wsl::windows::service::wsla::WSLAUserSession::CreateSession( - const WSLA_SESSION_SETTINGS* Settings, const VIRTUAL_MACHINE_SETTINGS* VmSettings, IWSLASession** WslaSession) +HRESULT wsl::windows::service::wsla::WSLAUserSession::CreateSession(const WSLA_SESSION_SETTINGS* Settings, IWSLASession** WslaSession) try { auto session = m_session.lock(); RETURN_HR_IF(RPC_E_DISCONNECTED, !session); - return session->CreateSession(Settings, VmSettings, WslaSession); + return session->CreateSession(Settings, WslaSession); } CATCH_RETURN(); diff --git a/src/windows/wslaservice/exe/WSLAUserSession.h b/src/windows/wslaservice/exe/WSLAUserSession.h index d0fcca5d8..be8c9a5c0 100644 --- a/src/windows/wslaservice/exe/WSLAUserSession.h +++ b/src/windows/wslaservice/exe/WSLAUserSession.h @@ -29,7 +29,7 @@ class WSLAUserSessionImpl PSID GetUserSid() const; - HRESULT CreateSession(const WSLA_SESSION_SETTINGS* Settings, const VIRTUAL_MACHINE_SETTINGS* VmSettings, IWSLASession** WslaSession); + HRESULT CreateSession(const WSLA_SESSION_SETTINGS* Settings, IWSLASession** WslaSession); HRESULT OpenSessionByName(_In_ LPCWSTR DisplayName, _Out_ IWSLASession** Session); void OnSessionTerminated(WSLASession* Session); @@ -53,7 +53,7 @@ class DECLSPEC_UUID("a9b7a1b9-0671-405c-95f1-e0612cb4ce8f") WSLAUserSession WSLAUserSession& operator=(const WSLAUserSession&) = delete; IFACEMETHOD(GetVersion)(_Out_ WSLA_VERSION* Version) override; - IFACEMETHOD(CreateSession)(const WSLA_SESSION_SETTINGS* WslaSessionSettings, const VIRTUAL_MACHINE_SETTINGS* VmSettings, IWSLASession** WslaSession) override; + IFACEMETHOD(CreateSession)(const WSLA_SESSION_SETTINGS* WslaSessionSettings, 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; diff --git a/src/windows/wslaservice/exe/WSLAVirtualMachine.cpp b/src/windows/wslaservice/exe/WSLAVirtualMachine.cpp index 0c8265cfc..dc743ddf7 100644 --- a/src/windows/wslaservice/exe/WSLAVirtualMachine.cpp +++ b/src/windows/wslaservice/exe/WSLAVirtualMachine.cpp @@ -33,28 +33,14 @@ constexpr auto SAVED_STATE_FILE_EXTENSION = L".vmrs"; constexpr auto SAVED_STATE_FILE_PREFIX = L"saved-state-"; constexpr auto RECEIVE_TIMEOUT = 30 * 1000; -WSLAVirtualMachine::WSLAVirtualMachine(const VIRTUAL_MACHINE_SETTINGS& Settings, PSID UserSid, WSLAUserSessionImpl* Session) : - m_settings(Settings), m_userSid(UserSid) +WSLAVirtualMachine::WSLAVirtualMachine(WSLAVirtualMachine::Settings&& Settings, PSID UserSid, WSLAUserSessionImpl* Session) : + m_settings(std::move(Settings)), m_userSid(UserSid) { THROW_IF_FAILED(CoCreateGuid(&m_vmId)); m_vmIdString = wsl::shared::string::GuidToString(m_vmId, wsl::shared::string::GuidToStringFlags::Uppercase); m_userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation); m_crashDumpFolder = GetCrashDumpFolder(); - - if (Settings.EnableDebugShell) - { - m_debugShellPipe = wsl::windows::common::wslutil::GetDebugShellPipeName(m_userSid) + m_settings.DisplayName; - } -} - -HRESULT WSLAVirtualMachine::GetDebugShellPipe(LPWSTR* pipePath) -{ - RETURN_HR_IF(E_INVALIDARG, m_debugShellPipe.empty()); - - *pipePath = wil::make_unique_string(m_debugShellPipe.c_str()).release(); - - return S_OK; } void WSLAVirtualMachine::OnSessionTerminated() @@ -76,6 +62,11 @@ void WSLAVirtualMachine::OnSessionTerminated() WSLAVirtualMachine::~WSLAVirtualMachine() { WSL_LOG("WSLATerminateVmStart", TraceLoggingValue(m_running, "running")); + if (!m_computeSystem) + { + // If m_computeSystem is null, don't try to stop the VM since it never started. + return; + } m_initChannel.Close(); @@ -203,14 +194,14 @@ void WSLAVirtualMachine::Start() kernelCmdLine += L" hv_utils.timesync_implicit=1"; wil::unique_handle dmesgOutput; - if (m_settings.DmesgOutput != 0) + if (m_settings.DmesgHandle) { - dmesgOutput.reset(wsl::windows::common::wslutil::DuplicateHandleFromCallingProcess(ULongToHandle(m_settings.DmesgOutput))); + dmesgOutput = std::move(m_settings.DmesgHandle); } m_dmesgCollector = DmesgCollector::Create(m_vmId, m_vmExitEvent, true, false, L"", true, std::move(dmesgOutput)); - if (m_settings.EnableEarlyBootDmesg) + if (FeatureEnabled(WslaFeatureFlagsEarlyBootDmesg)) { kernelCmdLine += L" earlycon=uart8250,io,0x3f8,115200"; vmSettings.Devices.ComPorts["0"] = hcs::ComPort{m_dmesgCollector->EarlyConsoleName()}; @@ -304,6 +295,7 @@ void WSLAVirtualMachine::Start() WSL_LOG("CreateWSLAVirtualMachine", TraceLoggingValue(json.c_str(), "json")); m_computeSystem = hcs::CreateComputeSystem(m_vmIdString.c_str(), json.c_str()); + m_running = true; auto runtimeId = wsl::windows::common::hcs::GetRuntimeId(m_computeSystem.get()); WI_ASSERT(IsEqualGUID(m_vmId, runtimeId)); @@ -351,7 +343,7 @@ void WSLAVirtualMachine::Start() Mount(m_initChannel, device.c_str(), "", "ext4", "ro", WSLA_MOUNT::KernelModules); // Configure GPU if requested. - if (m_settings.EnableGPU) + if (FeatureEnabled(WslaFeatureFlagsGPU)) { hcs::ModifySettingRequest gpuRequest{}; gpuRequest.ResourcePath = L"VirtualMachine/ComputeTopology/Gpu"; @@ -372,32 +364,23 @@ void WSLAVirtualMachine::Start() void WSLAVirtualMachine::ConfigureMounts() { - auto [_, device] = AttachDisk(m_settings.RootVhd, true); + auto [_, device] = AttachDisk(m_settings.RootVhd.c_str(), true); - Mount(m_initChannel, device.c_str(), "/mnt", m_settings.RootVhdType, "ro", WSLAMountFlagsChroot | WSLAMountFlagsWriteableOverlayFs); + Mount(m_initChannel, device.c_str(), "/mnt", m_settings.RootVhdType.c_str(), "ro", WSLAMountFlagsChroot | WSLAMountFlagsWriteableOverlayFs); Mount(m_initChannel, nullptr, "/dev", "devtmpfs", "", 0); Mount(m_initChannel, nullptr, "/sys", "sysfs", "", 0); Mount(m_initChannel, nullptr, "/proc", "proc", "", 0); Mount(m_initChannel, nullptr, "/dev/pts", "devpts", "noatime,nosuid,noexec,gid=5,mode=620", 0); - if (m_settings.EnableGPU) // TODO: re-think how GPU settings should work at the session level API. + if (FeatureEnabled(WslaFeatureFlagsGPU)) // TODO: re-think how GPU settings should work at the session level API. { MountGpuLibraries("/usr/lib/wsl/lib", "/usr/lib/wsl/drivers", WSLAMountFlagsNone); } +} - if (m_settings.ContainerRootVhd) // TODO: re-think how container root settings should work at the session level API. - { - auto [_, containerRootDevice] = AttachDisk(m_settings.ContainerRootVhd, false); - - if (m_settings.FormatContainerRootVhd) - { - ServiceProcessLauncher formatProcessLauncher{"/usr/sbin/mkfs.ext4", {"/usr/sbin/mkfs.ext4", containerRootDevice}}; - auto formatProcess = formatProcessLauncher.Launch(*this); - THROW_HR_IF(E_FAIL, formatProcess.WaitAndCaptureOutput().Code != 0); - } - - Mount(m_initChannel, containerRootDevice.c_str(), "/root", "ext4", "rw", 0); - } +bool WSLAVirtualMachine::FeatureEnabled(WSLAFeatureFlags Value) const +{ + return static_cast(m_settings.FeatureFlags) & static_cast(Value); } void WSLAVirtualMachine::WatchForExitedProcesses(wsl::shared::SocketChannel& Channel) @@ -459,7 +442,7 @@ void WSLAVirtualMachine::ConfigureNetworking() std::vector cmd{"/gns", LX_INIT_GNS_SOCKET_ARG}; // If DNS tunnelling is enabled, use an additional for its channel. - if (m_settings.EnableDnsTunneling) + if (FeatureEnabled(WslaFeatureFlagsDnsTunneling)) { fds.emplace_back(WSLA_PROCESS_FD{.Fd = -1, .Type = WSLAFdType::WSLAFdTypeDefault}); THROW_IF_FAILED(wsl::core::networking::DnsResolver::LoadDnsResolverMethods()); @@ -605,7 +588,7 @@ std::pair WSLAVirtualMachine::AttachDisk(_In_ PCWSTR Path, _ auto result = wil::ResultFromException([&]() { std::lock_guard lock{m_lock}; - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_running); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_running); AttachedDisk disk{Path}; @@ -734,7 +717,7 @@ std::tuple WSLAVirtualMachine::For int32_t pid{}; int32_t ptyMaster{}; { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_running); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_running); WSLA_FORK message; message.ForkType = Type; @@ -912,6 +895,13 @@ Microsoft::WRL::ComPtr WSLAVirtualMachine::CreateLinuxProcess(_In_ return process; } +void WSLAVirtualMachine::Mount(LPCSTR Source, LPCSTR Target, LPCSTR Type, LPCSTR Options, ULONG Flags) +{ + std::lock_guard lock{m_lock}; + + Mount(m_initChannel, Source, Target, Type, Options, Flags); +} + void WSLAVirtualMachine::Mount(shared::SocketChannel& Channel, LPCSTR Source, LPCSTR Target, LPCSTR Type, LPCSTR Options, ULONG Flags) { static_assert(WSLAMountFlagsNone == WSLA_MOUNT::None); @@ -985,7 +975,7 @@ try { std::lock_guard lock(m_lock); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_running); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_running); WSLA_SHUTDOWN message{}; m_initChannel.SendMessage(message); @@ -1002,7 +992,7 @@ HRESULT WSLAVirtualMachine::Signal(_In_ LONG Pid, _In_ int Signal) try { std::lock_guard lock(m_lock); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_running); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_running); WSLA_SIGNAL message; message.Pid = Pid; @@ -1221,7 +1211,7 @@ CATCH_RETURN(); void WSLAVirtualMachine::MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_ LPCSTR DriversMountpoint, _In_ DWORD Flags) { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_CONFIG_VALUE), !m_settings.EnableGPU); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_CONFIG_VALUE), !FeatureEnabled(WslaFeatureFlagsGPU)); auto [channel, _, __] = Fork(WSLA_FORK::Thread); diff --git a/src/windows/wslaservice/exe/WSLAVirtualMachine.h b/src/windows/wslaservice/exe/WSLAVirtualMachine.h index a2ed05323..e9bd289be 100644 --- a/src/windows/wslaservice/exe/WSLAVirtualMachine.h +++ b/src/windows/wslaservice/exe/WSLAVirtualMachine.h @@ -41,9 +41,22 @@ class DECLSPEC_UUID("0CFC5DC1-B6A7-45FC-8034-3FA9ED73CE30") WSLAVirtualMachine wil::unique_socket Socket; }; + struct Settings + { + std::wstring DisplayName; + ULONGLONG MemoryMb{}; + ULONG CpuCount; + ULONG BootTimeoutMs{}; + WSLANetworkingMode NetworkingMode{}; + WSLAFeatureFlags FeatureFlags{}; + wil::unique_handle DmesgHandle; + std::filesystem::path RootVhd; + std::string RootVhdType; + }; + using TPrepareCommandLine = std::function&)>; - WSLAVirtualMachine(const VIRTUAL_MACHINE_SETTINGS& Settings, PSID Sid, WSLAUserSessionImpl* UserSession); + WSLAVirtualMachine(Settings&& Settings, PSID Sid, WSLAUserSessionImpl* UserSession); ~WSLAVirtualMachine(); @@ -54,7 +67,6 @@ class DECLSPEC_UUID("0CFC5DC1-B6A7-45FC-8034-3FA9ED73CE30") WSLAVirtualMachine IFACEMETHOD(WaitPid(_In_ LONG Pid, _In_ ULONGLONG TimeoutMs, _Out_ ULONG* State, _Out_ int* Code)) override; IFACEMETHOD(Signal(_In_ LONG Pid, _In_ int Signal)) override; IFACEMETHOD(Shutdown(ULONGLONG _In_ TimeoutMs)) override; - IFACEMETHOD(GetDebugShellPipe(_Out_ LPWSTR* pipePath)) override; IFACEMETHOD(MapPort(_In_ int Family, _In_ short WindowsPort, _In_ short LinuxPort, _In_ BOOL Remove)) override; IFACEMETHOD(Unmount(_In_ const char* Path)) override; IFACEMETHOD(MountWindowsFolder(_In_ LPCWSTR WindowsPath, _In_ LPCSTR LinuxPath, _In_ BOOL ReadOnly)) override; @@ -69,6 +81,7 @@ class DECLSPEC_UUID("0CFC5DC1-B6A7-45FC-8034-3FA9ED73CE30") WSLAVirtualMachine std::pair AttachDisk(_In_ PCWSTR Path, _In_ BOOL ReadOnly); void DetachDisk(_In_ ULONG Lun); + void Mount(_In_ LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags); private: static void Mount(wsl::shared::SocketChannel& Channel, LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags); @@ -80,6 +93,7 @@ class DECLSPEC_UUID("0CFC5DC1-B6A7-45FC-8034-3FA9ED73CE30") WSLAVirtualMachine void ConfigureMounts(); void OnExit(_In_ const HCS_EVENT* Event); void OnCrash(_In_ const HCS_EVENT* Event); + bool FeatureEnabled(WSLAFeatureFlags Flag) const; std::tuple Fork(enum WSLA_FORK::ForkType Type); std::tuple Fork( @@ -110,7 +124,7 @@ class DECLSPEC_UUID("0CFC5DC1-B6A7-45FC-8034-3FA9ED73CE30") WSLAVirtualMachine bool AccessGranted = false; }; - VIRTUAL_MACHINE_SETTINGS m_settings; + Settings m_settings; std::thread m_processExitThread; std::thread m_crashDumpCollectionThread; diff --git a/src/windows/wslaservice/inc/wslaservice.idl b/src/windows/wslaservice/inc/wslaservice.idl index 82dc52568..29e392417 100644 --- a/src/windows/wslaservice/inc/wslaservice.idl +++ b/src/windows/wslaservice/inc/wslaservice.idl @@ -206,7 +206,6 @@ interface IWSLAVirtualMachine : IUnknown HRESULT WaitPid([in] LONG Pid, [in] ULONGLONG TimeoutMs, [out] ULONG* State, [out] int* Code); HRESULT Signal([in] LONG Pid, [in] int Signal); HRESULT Shutdown([in] ULONGLONG TimeoutMs); - HRESULT GetDebugShellPipe([out] LPWSTR* pipePath); HRESULT MapPort([in] int Family, [in] short WindowsPort, [in] short LinuxPort, [in] BOOL Remove); HRESULT Unmount([in] LPCSTR Path); HRESULT MountWindowsFolder([in] LPCWSTR WindowsPath, [in] LPCSTR LinuxPath, [in] BOOL ReadOnly); @@ -219,32 +218,29 @@ typedef enum _WSLANetworkingMode WSLANetworkingModeNAT } WSLANetworkingMode; -typedef -struct _VIRTUAL_MACHINE_SETTINGS { // TODO: Delete once the new API is wired. - LPCWSTR DisplayName; - ULONGLONG MemoryMb; - ULONG CpuCount; - ULONG BootTimeoutMs; - ULONG DmesgOutput; - WSLANetworkingMode NetworkingMode; - BOOL EnableDnsTunneling; - BOOL EnableDebugShell; - BOOL EnableEarlyBootDmesg; - BOOL EnableGPU; - LPCWSTR RootVhd; // Temporary option to provide the root VHD. TODO: Remove once runtime VHD is available. - LPCSTR RootVhdType; // Temporary option to provide the root VHD. TODO: Remove once runtime VHD is available. - LPCWSTR ContainerRootVhd; - BOOL FormatContainerRootVhd; -} VIRTUAL_MACHINE_SETTINGS; - - +typedef enum _WSLAFeatureFlags +{ + WslaFeatureFlagsNone = 0, + WslaFeatureFlagsDnsTunneling = 1, + WslaFeatureFlagsEarlyBootDmesg = 2, + WslaFeatureFlagsGPU = 4, +} WSLAFeatureFlags; struct WSLA_SESSION_SETTINGS { LPCWSTR DisplayName; LPCWSTR StoragePath; + ULONGLONG MaximumStorageSizeMb; + ULONG CpuCount; + ULONG MemoryMb; + ULONG BootTimeoutMs; + WSLANetworkingMode NetworkingMode; [unique] ITerminationCallback* TerminationCallback; + ULONG FeatureFlags; + ULONG DmesgOutput; - // TODO: Termination callback, flags + // Below options are used for debugging purposes only. + [unique] LPCWSTR RootVhdOverride; + [unique] LPCSTR RootVhdTypeOverride; }; @@ -318,7 +314,7 @@ interface IWSLAUserSession : IUnknown HRESULT GetVersion([out] WSLA_VERSION* Version); // Session managment. - HRESULT CreateSession([in] const struct WSLA_SESSION_SETTINGS* Settings, [in] const VIRTUAL_MACHINE_SETTINGS* VmSettings, [out]IWSLASession** Session); + HRESULT CreateSession([in] const struct WSLA_SESSION_SETTINGS* Settings, [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); diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index b0a991811..2895da04b 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -29,6 +29,8 @@ using wsl::windows::common::WSLAProcessLauncher; using wsl::windows::common::relay::OverlappedIOHandle; using wsl::windows::common::relay::WriteHandle; +DEFINE_ENUM_FLAG_OPERATORS(WSLAFeatureFlags); + class WSLATests { WSL_TEST_CLASS(WSLATests) @@ -54,20 +56,25 @@ class WSLATests return true; } - wil::com_ptr CreateSession(VIRTUAL_MACHINE_SETTINGS& vmSettings, const WSLA_SESSION_SETTINGS& sessionSettings = {L"wsla-test"}) + static WSLA_SESSION_SETTINGS GetDefaultSessionSettings() { - if (vmSettings.RootVhdType == nullptr) - { - vmSettings.RootVhdType = "ext4"; - } + WSLA_SESSION_SETTINGS settings{}; + settings.DisplayName = L"wsla-test"; + settings.CpuCount = 4; + settings.MemoryMb = 2024; + settings.BootTimeoutMs = 30 * 1000; + return settings; + } + wil::com_ptr CreateSession(const WSLA_SESSION_SETTINGS& sessionSettings = GetDefaultSessionSettings()) + { wil::com_ptr userSession; VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(WSLAUserSession), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&userSession))); wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get()); wil::com_ptr session; - VERIFY_SUCCEEDED(userSession->CreateSession(&sessionSettings, &vmSettings, &session)); + VERIFY_SUCCEEDED(userSession->CreateSession(&sessionSettings, &session)); wsl::windows::common::security::ConfigureForCOMImpersonation(session.get()); return session; @@ -177,14 +184,9 @@ class WSLATests auto createVmWithDmesg = [this](bool earlyBootLogging) { auto [read, write] = CreateSubprocessPipe(false, false); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; + auto settings = GetDefaultSessionSettings(); settings.DmesgOutput = (ULONG) reinterpret_cast(write.get()); - settings.EnableEarlyBootDmesg = earlyBootLogging; - settings.RootVhd = testVhd.c_str(); + WI_SetFlagIf(settings.FeatureFlags, WslaFeatureFlagsEarlyBootDmesg, earlyBootLogging); std::vector dmesgContent; auto readDmesg = [read = read.get(), &dmesgContent]() mutable { @@ -223,7 +225,7 @@ class WSLATests write.reset(); - ExpectCommandResult(session.get(), {"/bin/bash", "-c", "echo DmesgTest > /dev/kmsg"}, 0); + ExpectCommandResult(session.get(), {"/bin/sh", "-c", "echo DmesgTest > /dev/kmsg"}, 0); VERIFY_ARE_EQUAL(session->Shutdown(30 * 1000), S_OK); detach.reset(); @@ -281,23 +283,16 @@ class WSLATests std::function m_callback; }; - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = testVhd.c_str(); - std::promise> promise; CallbackInstance callback{[&](WSLAVirtualMachineTerminationReason reason, LPCWSTR details) { promise.set_value(std::make_pair(reason, details)); }}; - WSLA_SESSION_SETTINGS sessionSettings{L"wsla-test"}; + WSLA_SESSION_SETTINGS sessionSettings = GetDefaultSessionSettings(); sessionSettings.TerminationCallback = &callback; - auto session = CreateSession(settings, sessionSettings); + auto session = CreateSession(sessionSettings); wil::com_ptr vm; VERIFY_SUCCEEDED(session->GetVirtualMachine(&vm)); @@ -313,14 +308,7 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = testVhd.c_str(); - - auto session = CreateSession(settings); + auto session = CreateSession(); WSLAProcessLauncher launcher("/bin/sh", {"/bin/sh"}, {"TERM=xterm-256color"}, ProcessFlags::None); launcher.AddFd(WSLA_PROCESS_FD{.Fd = 0, .Type = WSLAFdTypeTerminalInput}); @@ -354,7 +342,7 @@ class WSLATests }; // Expect the shell prompt to be displayed - validateTtyOutput("#"); + validateTtyOutput("/ #"); writeTty("echo OK\n"); validateTtyOutput(" echo OK\r\nOK"); @@ -368,20 +356,15 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; + auto settings = GetDefaultSessionSettings(); settings.NetworkingMode = WSLANetworkingModeNAT; - settings.RootVhd = testVhd.c_str(); auto session = CreateSession(settings); // Validate that eth0 has an ip address ExpectCommandResult( session.get(), - {"/bin/bash", + {"/bin/sh", "-c", "ip a show dev eth0 | grep -iF 'inet ' | grep -E '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}'"}, 0); @@ -393,21 +376,16 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; + auto settings = GetDefaultSessionSettings(); settings.NetworkingMode = WSLANetworkingModeNAT; - settings.EnableDnsTunneling = true; - settings.RootVhd = testVhd.c_str(); + WI_SetFlag(settings.FeatureFlags, WslaFeatureFlagsDnsTunneling); auto session = CreateSession(settings); // Validate that eth0 has an ip address ExpectCommandResult( session.get(), - {"/bin/bash", + {"/bin/sh", "-c", "ip a show dev eth0 | grep -iF 'inet ' | grep -E '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}'"}, 0); @@ -422,14 +400,7 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = testVhd.c_str(); - - auto session = CreateSession(settings); + auto session = CreateSession(); struct FileFd { @@ -519,7 +490,7 @@ class WSLATests {{0, WSLAFdTypeLinuxFileInput, "/proc/self/comm"}, {1, WSLAFdTypeLinuxFileInput, "/tmp/output"}, {2, WSLAFdTypeDefault, nullptr}}); auto result = process->WaitAndCaptureOutput(); - VERIFY_ARE_EQUAL(result.Output[2], "/bin/cat: write error: Bad file descriptor\n"); + VERIFY_ARE_EQUAL(result.Output[2], "cat: write error: Bad file descriptor\n"); VERIFY_ARE_EQUAL(result.Code, 1); } @@ -527,7 +498,7 @@ class WSLATests auto process = createProcess({"/bin/cat"}, {{0, WSLAFdTypeLinuxFileOutput, "/tmp/output"}, {2, WSLAFdTypeDefault, nullptr}}); auto result = process->WaitAndCaptureOutput(); - VERIFY_ARE_EQUAL(result.Output[2], "/bin/cat: standard output: Bad file descriptor\n"); + VERIFY_ARE_EQUAL(result.Output[2], "cat: read error: Bad file descriptor\n"); VERIFY_ARE_EQUAL(result.Code, 1); } } @@ -536,13 +507,10 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; + auto settings = GetDefaultSessionSettings(); + settings.RootVhdOverride = testVhd.c_str(); // socat is required to run this test case. + settings.RootVhdTypeOverride = "ext4"; settings.NetworkingMode = WSLANetworkingModeNAT; - settings.RootVhd = testVhd.c_str(); auto session = CreateSession(settings); @@ -582,7 +550,7 @@ class WSLATests auto listen = [&](short port, const char* content, bool ipv6) { auto cmd = std::format("echo -n '{}' | /usr/bin/socat -dd TCP{}-LISTEN:{},reuseaddr -", content, ipv6 ? "6" : "", port); - auto process = WSLAProcessLauncher("/bin/bash", {"/bin/bash", "-c", cmd}).Launch(*session); + auto process = WSLAProcessLauncher("/bin/sh", {"/bin/sh", "-c", cmd}).Launch(*session); waitForOutput(process.GetStdHandle(2).get(), "listening on"); return process; @@ -677,14 +645,7 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = testVhd.c_str(); - - auto session = CreateSession(settings); + auto session = CreateSession(); // Create a 'stuck' process auto process = WSLAProcessLauncher{"/bin/cat", {"/bin/cat"}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout}.Launch(*session); @@ -697,14 +658,7 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = testVhd.c_str(); - - auto session = CreateSession(settings); + auto session = CreateSession(); wil::com_ptr vm; VERIFY_SUCCEEDED(session->GetVirtualMachine(&vm)); @@ -713,7 +667,7 @@ class WSLATests auto expectMount = [&](const std::string& target, const std::optional& options) { auto cmd = std::format("set -o pipefail ; findmnt '{}' | tail -n 1", target); - auto result = ExpectCommandResult(session.get(), {"/bin/bash", "-c", cmd}, options.has_value() ? 0 : 1); + auto result = ExpectCommandResult(session.get(), {"/bin/sh", "-c", cmd}, options.has_value() ? 0 : 1); const auto& output = result.Output[1]; const auto& error = result.Output[2]; @@ -744,7 +698,7 @@ class WSLATests VERIFY_ARE_EQUAL(vm->MountWindowsFolder(testFolder.c_str(), "/win-path", false), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)); // Validate that folder is writeable from linux - ExpectCommandResult(session.get(), {"/bin/bash", "-c", "echo -n content > /win-path/file.txt && sync"}, 0); + ExpectCommandResult(session.get(), {"/bin/sh", "-c", "echo -n content > /win-path/file.txt && sync"}, 0); VERIFY_ARE_EQUAL(ReadFileContent(testFolder / "file.txt"), L"content"); VERIFY_SUCCEEDED(vm->UnmountWindowsFolder("/win-path")); @@ -757,7 +711,7 @@ class WSLATests expectMount("/win-path", "/win-path*9p*rw,relatime,aname=*,cache=5,access=client,msize=65536,trans=fd,rfd=*,wfd=*"); // Validate that folder is not writeable from linux - ExpectCommandResult(session.get(), {"/bin/bash", "-c", "echo -n content > /win-path/file.txt"}, 1); + ExpectCommandResult(session.get(), {"/bin/sh", "-c", "echo -n content > /win-path/file.txt"}, 1); VERIFY_SUCCEEDED(vm->UnmountWindowsFolder("/win-path")); expectMount("/win-path", {}); @@ -784,19 +738,12 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = testVhd.c_str(); - - auto session = CreateSession(settings); - auto result = ExpectCommandResult( - session.get(), {"/bin/bash", "-c", "echo /proc/self/fd/* && (readlink -v /proc/self/fd/* || true)"}, 0); + auto session = CreateSession(); + auto result = + ExpectCommandResult(session.get(), {"/bin/sh", "-c", "echo /proc/self/fd/* && (readlink -v /proc/self/fd/* || true)"}, 0); // Note: fd/0 is opened by readlink to read the actual content of /proc/self/fd. - if (!PathMatchSpecA(result.Output[1].c_str(), "/proc/self/fd/0 /proc/self/fd/1 /proc/self/fd/2\nsocket:[*]\nsocket:[*]\n")) + if (!PathMatchSpecA(result.Output[1].c_str(), "/proc/self/fd/0 /proc/self/fd/1 /proc/self/fd/2\n")) { LogInfo("Found additional fds: %hs", result.Output[1].c_str()); VERIFY_FAIL(); @@ -807,13 +754,8 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.EnableGPU = true; - settings.RootVhd = testVhd.c_str(); + auto settings = GetDefaultSessionSettings(); + WI_SetFlag(settings.FeatureFlags, WslaFeatureFlagsGPU); auto session = CreateSession(settings); @@ -821,10 +763,10 @@ class WSLATests VERIFY_SUCCEEDED(session->GetVirtualMachine(&vm)); // Validate that the GPU device is available. - ExpectCommandResult(session.get(), {"/bin/bash", "-c", "test -c /dev/dxg"}, 0); + ExpectCommandResult(session.get(), {"/bin/sh", "-c", "test -c /dev/dxg"}, 0); auto expectMount = [&](const std::string& target, const std::optional& options) { auto cmd = std::format("set -o pipefail ; findmnt '{}' | tail -n 1", target); - WSLAProcessLauncher launcher{"/bin/bash", {"/bin/bash", "-c", cmd}}; + WSLAProcessLauncher launcher{"/bin/sh", {"/bin/sh", "-c", cmd}}; auto result = launcher.Launch(*session).WaitAndCaptureOutput(); const auto& output = result.Output[1]; @@ -853,7 +795,7 @@ class WSLATests // Validate that trying to mount the shares without GPU support disabled fails. { - settings.EnableGPU = false; + WI_ClearFlag(settings.FeatureFlags, WslaFeatureFlagsGPU); session = CreateSession(settings); wil::com_ptr vm; @@ -869,49 +811,23 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = testVhd.c_str(); - - // Use the system distro vhd for modprobe & lsmod. - -#ifdef WSL_SYSTEM_DISTRO_PATH - - auto rootfs = std::filesystem::path(TEXT(WSL_SYSTEM_DISTRO_PATH)); - -#else - auto rootfs = std::filesystem::path(wsl::windows::common::wslutil::GetMsiPackagePath().value()) / L"system.vhd"; - -#endif - settings.RootVhd = rootfs.c_str(); - - auto session = CreateSession(settings); + auto session = CreateSession(); // Sanity check. - ExpectCommandResult(session.get(), {"/bin/bash", "-c", "lsmod | grep ^xsk_diag"}, 1); + ExpectCommandResult(session.get(), {"/bin/sh", "-c", "lsmod | grep ^xsk_diag"}, 1); // Validate that modules can be loaded. ExpectCommandResult(session.get(), {"/usr/sbin/modprobe", "xsk_diag"}, 0); // Validate that xsk_diag is now loaded. - ExpectCommandResult(session.get(), {"/bin/bash", "-c", "lsmod | grep ^xsk_diag"}, 0); + ExpectCommandResult(session.get(), {"/bin/sh", "-c", "lsmod | grep ^xsk_diag"}, 0); } TEST_METHOD(CreateRootNamespaceProcess) { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = testVhd.c_str(); - - auto session = CreateSession(settings); + auto session = CreateSession(); // Simple case { @@ -1031,14 +947,7 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = testVhd.c_str(); - - auto session = CreateSession(settings); + auto session = CreateSession(); int processId = 0; // Cache the existing crash dumps so we can check that a new one is created. @@ -1073,7 +982,7 @@ class WSLATests // Dumps files are named with the format: wsl-crash----.dmp // Check if a new file was added in crashDumpsDir matching the pattern and not in existingDumps. - std::string expectedPattern = std::format("wsl-crash-*-{}-_usr_bin_cat-11.dmp", processId); + std::string expectedPattern = std::format("wsl-crash-*-{}-_usr_bin_busybox-11.dmp", processId); auto dumpFile = wsl::shared::retry::RetryWithTimeout( [crashDumpsDir, expectedPattern, existingDumps]() { @@ -1108,14 +1017,7 @@ class WSLATests { WSL2_TEST_ONLY(); - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; - settings.RootVhd = testVhd.c_str(); - - auto session = CreateSession(settings); + auto session = CreateSession(); constexpr auto formatedVhd = L"test-format-vhd.vhdx"; @@ -1148,45 +1050,28 @@ class WSLATests #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 storagePath = std::filesystem::current_path() / "test-storage"; - 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, [&]() { + std::error_code error; - VIRTUAL_MACHINE_SETTINGS settings{}; - settings.CpuCount = 4; - settings.DisplayName = L"WSLA"; - settings.MemoryMb = 2048; - settings.BootTimeoutMs = 30 * 1000; + std::filesystem::remove_all(storagePath, error); + if (error) + { + LogError("Failed to cleanup storage path %ws: %s", storagePath.c_str(), error.message().c_str()); + } + }); auto installedVhdPath = std::filesystem::path(wsl::windows::common::wslutil::GetMsiPackagePath().value()) / L"wslarootfs.vhd"; -#ifdef WSL_DEV_INSTALL_PATH - - settings.RootVhd = TEXT(WSLA_TEST_DISTRO_PATH); - -#else - - settings.RootVhd = installedVhdPath.c_str(); - -#endif - - settings.RootVhdType = "squashfs"; + auto settings = GetDefaultSessionSettings(); settings.NetworkingMode = WSLANetworkingModeNAT; - settings.ContainerRootVhd = storageVhd.c_str(); - settings.FormatContainerRootVhd = true; + settings.StoragePath = storagePath.c_str(); + settings.MaximumStorageSizeMb = 1024; 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"}); @@ -1198,7 +1083,7 @@ class WSLATests // Validate that env is correctly wired. { - WSLAContainerLauncher launcher("debian:latest", "test-env", "/bin/bash", {"-c", "echo $testenv"}, {{"testenv=testvalue"}}); + WSLAContainerLauncher launcher("debian:latest", "test-env", "/bin/sh", {"-c", "echo $testenv"}, {{"testenv=testvalue"}}); auto container = launcher.Launch(*session); auto process = container.GetInitProcess(); From 7284d8e988e416314b6982282e20a6a3a5704ee5 Mon Sep 17 00:00:00 2001 From: Blue Date: Tue, 2 Dec 2025 15:10:58 -0800 Subject: [PATCH 25/27] Prepare for review --- src/windows/wslaservice/exe/WSLAContainer.cpp | 2 -- src/windows/wslaservice/exe/WSLASession.cpp | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/windows/wslaservice/exe/WSLAContainer.cpp b/src/windows/wslaservice/exe/WSLAContainer.cpp index a1062bc0c..5c241c820 100644 --- a/src/windows/wslaservice/exe/WSLAContainer.cpp +++ b/src/windows/wslaservice/exe/WSLAContainer.cpp @@ -161,7 +161,5 @@ 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 diff --git a/src/windows/wslaservice/exe/WSLASession.cpp b/src/windows/wslaservice/exe/WSLASession.cpp index cd417f063..df4134ac9 100644 --- a/src/windows/wslaservice/exe/WSLASession.cpp +++ b/src/windows/wslaservice/exe/WSLASession.cpp @@ -43,7 +43,6 @@ WSLASession::WSLASession(const WSLA_SESSION_SETTINGS& Settings, WSLAUserSessionI try { ServiceProcessLauncher launcher{"/bin/sh", {"/bin/sh", "-c", "/etc/lsw-init.sh"}}; - auto result = launcher.Launch(*m_virtualMachine.Get()).WaitAndCaptureOutput(); THROW_HR_IF_MSG(E_FAIL, result.Code != 0, "Init script failed: %hs", launcher.FormatResult(result).c_str()); @@ -153,7 +152,7 @@ void WSLASession::ConfigureStorage(const WSLA_SESSION_SETTINGS& Settings) "Failed to attach vhd: %ls", m_storageVhdPath.c_str()); - // If the VHD wasn'found, create it. + // If the VHD wasn't found, create it. WSL_LOG("CreateStorageVhd", TraceLoggingValue(m_storageVhdPath.c_str(), "StorageVhdPath")); auto runAsUser = wil::CoImpersonateClient(); From f8e2119aa63d886e7ffc4213bb59610e46a4d46c Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 3 Dec 2025 12:05:09 -0800 Subject: [PATCH 26/27] Apply PR feedback --- src/windows/common/WslClient.cpp | 3 +-- src/windows/wslaservice/exe/WSLASession.cpp | 3 ++- src/windows/wslaservice/exe/WSLAVirtualMachine.cpp | 9 +++------ src/windows/wslaservice/exe/WSLAVirtualMachine.h | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/windows/common/WslClient.cpp b/src/windows/common/WslClient.cpp index dc9278f11..f17e0a264 100644 --- a/src/windows/common/WslClient.cpp +++ b/src/windows/common/WslClient.cpp @@ -1591,14 +1591,13 @@ int WslaShell(_In_ std::wstring_view commandLine) if (!rootVhdOverride.empty()) { - sessionSettings.RootVhdOverride = rootVhdOverride.c_str(); - if (rootVhdTypeOverride.empty()) { wprintf(L"--fstype required when --vhd is passed\n"); return 1; } + sessionSettings.RootVhdOverride = rootVhdOverride.c_str(); sessionSettings.RootVhdTypeOverride = rootVhdTypeOverride.c_str(); } diff --git a/src/windows/wslaservice/exe/WSLASession.cpp b/src/windows/wslaservice/exe/WSLASession.cpp index df4134ac9..e7c3ef789 100644 --- a/src/windows/wslaservice/exe/WSLASession.cpp +++ b/src/windows/wslaservice/exe/WSLASession.cpp @@ -27,7 +27,7 @@ WSLASession::WSLASession(const WSLA_SESSION_SETTINGS& Settings, WSLAUserSessionI { WSL_LOG("SessionCreated", TraceLoggingValue(m_displayName.c_str(), "DisplayName")); - m_virtualMachine = wil::MakeOrThrow(CreateVmSettings(Settings), userSessionImpl.GetUserSid(), &userSessionImpl); + m_virtualMachine = wil::MakeOrThrow(CreateVmSettings(Settings), userSessionImpl.GetUserSid()); if (Settings.TerminationCallback != nullptr) { @@ -137,6 +137,7 @@ void WSLASession::ConfigureStorage(const WSLA_SESSION_SETTINGS& Settings) m_virtualMachine->DetachDisk(diskLun.value()); } + auto runAsUser = wil::CoImpersonateClient(); LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(m_storageVhdPath.c_str())); } }); diff --git a/src/windows/wslaservice/exe/WSLAVirtualMachine.cpp b/src/windows/wslaservice/exe/WSLAVirtualMachine.cpp index a581f47de..0abaad290 100644 --- a/src/windows/wslaservice/exe/WSLAVirtualMachine.cpp +++ b/src/windows/wslaservice/exe/WSLAVirtualMachine.cpp @@ -33,7 +33,7 @@ constexpr auto SAVED_STATE_FILE_EXTENSION = L".vmrs"; constexpr auto SAVED_STATE_FILE_PREFIX = L"saved-state-"; constexpr auto RECEIVE_TIMEOUT = 30 * 1000; -WSLAVirtualMachine::WSLAVirtualMachine(WSLAVirtualMachine::Settings&& Settings, PSID UserSid, WSLAUserSessionImpl* Session) : +WSLAVirtualMachine::WSLAVirtualMachine(WSLAVirtualMachine::Settings&& Settings, PSID UserSid) : m_settings(std::move(Settings)), m_userSid(UserSid) { THROW_IF_FAILED(CoCreateGuid(&m_vmId)); @@ -200,10 +200,7 @@ void WSLAVirtualMachine::Start() kernelCmdLine += L" hv_utils.timesync_implicit=1"; wil::unique_handle dmesgOutput; - if (m_settings.DmesgHandle) - { - dmesgOutput = std::move(m_settings.DmesgHandle); - } + dmesgOutput = std::move(m_settings.DmesgHandle); m_dmesgCollector = DmesgCollector::Create(m_vmId, m_vmExitEvent, true, false, L"", true, std::move(dmesgOutput)); @@ -301,7 +298,6 @@ void WSLAVirtualMachine::Start() WSL_LOG("CreateWSLAVirtualMachine", TraceLoggingValue(json.c_str(), "json")); m_computeSystem = hcs::CreateComputeSystem(m_vmIdString.c_str(), json.c_str()); - m_running = true; auto runtimeId = wsl::windows::common::hcs::GetRuntimeId(m_computeSystem.get()); WI_ASSERT(IsEqualGUID(m_vmId, runtimeId)); @@ -316,6 +312,7 @@ void WSLAVirtualMachine::Start() wsl::windows::common::hcs::RegisterCallback(m_computeSystem.get(), &s_OnExit, this); wsl::windows::common::hcs::StartComputeSystem(m_computeSystem.get(), json.c_str()); + m_running = true; // Create a socket listening for crash dumps. auto crashDumpSocket = wsl::windows::common::hvsocket::Listen(runtimeId, LX_INIT_UTILITY_VM_CRASH_DUMP_PORT); diff --git a/src/windows/wslaservice/exe/WSLAVirtualMachine.h b/src/windows/wslaservice/exe/WSLAVirtualMachine.h index ab24287ab..e436cd1f1 100644 --- a/src/windows/wslaservice/exe/WSLAVirtualMachine.h +++ b/src/windows/wslaservice/exe/WSLAVirtualMachine.h @@ -58,7 +58,7 @@ class DECLSPEC_UUID("0CFC5DC1-B6A7-45FC-8034-3FA9ED73CE30") WSLAVirtualMachine using TPrepareCommandLine = std::function&)>; - WSLAVirtualMachine(Settings&& Settings, PSID Sid, WSLAUserSessionImpl* UserSession); + WSLAVirtualMachine(Settings&& Settings, PSID Sid); ~WSLAVirtualMachine(); From c69c83b0ab95410225ec3272d94986cba540f554 Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 3 Dec 2025 12:58:34 -0800 Subject: [PATCH 27/27] Use early return --- src/windows/wslaservice/exe/WSLASession.cpp | 93 ++++++++++----------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/src/windows/wslaservice/exe/WSLASession.cpp b/src/windows/wslaservice/exe/WSLASession.cpp index e7c3ef789..acf20fcaa 100644 --- a/src/windows/wslaservice/exe/WSLASession.cpp +++ b/src/windows/wslaservice/exe/WSLASession.cpp @@ -118,68 +118,67 @@ WSLASession::~WSLASession() void WSLASession::ConfigureStorage(const WSLA_SESSION_SETTINGS& Settings) { - if (Settings.StoragePath != nullptr) + if (Settings.StoragePath == nullptr) { - std::filesystem::path storagePath{Settings.StoragePath}; - THROW_HR_IF_MSG(E_INVALIDARG, !storagePath.is_absolute(), "Storage path is not absolute: %ls", storagePath.c_str()); + // If no storage path is specified, use a tmpfs for convenience. + m_virtualMachine->Mount("", "/root", "tmpfs", "", 0); + return; + } - m_storageVhdPath = storagePath / "storage.vhdx"; + std::filesystem::path storagePath{Settings.StoragePath}; + THROW_HR_IF_MSG(E_INVALIDARG, !storagePath.is_absolute(), "Storage path is not absolute: %ls", storagePath.c_str()); - std::string diskDevice; - std::optional diskLun{}; - bool vhdCreated = false; + m_storageVhdPath = storagePath / "storage.vhdx"; - auto deleteVhdOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { - if (vhdCreated) - { - if (diskLun.has_value()) - { - m_virtualMachine->DetachDisk(diskLun.value()); - } + std::string diskDevice; + std::optional diskLun{}; + bool vhdCreated = false; - auto runAsUser = wil::CoImpersonateClient(); - LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(m_storageVhdPath.c_str())); + auto deleteVhdOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { + if (vhdCreated) + { + if (diskLun.has_value()) + { + m_virtualMachine->DetachDisk(diskLun.value()); } - }); - - auto result = - wil::ResultFromException([&]() { diskDevice = m_virtualMachine->AttachDisk(m_storageVhdPath.c_str(), false).second; }); - if (FAILED(result)) - { - THROW_HR_IF_MSG( - result, - result != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) && result != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), - "Failed to attach vhd: %ls", - m_storageVhdPath.c_str()); + auto runAsUser = wil::CoImpersonateClient(); + LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(m_storageVhdPath.c_str())); + } + }); - // If the VHD wasn't found, create it. - WSL_LOG("CreateStorageVhd", TraceLoggingValue(m_storageVhdPath.c_str(), "StorageVhdPath")); + auto result = + wil::ResultFromException([&]() { diskDevice = m_virtualMachine->AttachDisk(m_storageVhdPath.c_str(), false).second; }); - auto runAsUser = wil::CoImpersonateClient(); + if (FAILED(result)) + { + THROW_HR_IF_MSG( + result, + result != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) && result != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), + "Failed to attach vhd: %ls", + m_storageVhdPath.c_str()); - std::filesystem::create_directories(storagePath); - wsl::core::filesystem::CreateVhd( - m_storageVhdPath.c_str(), Settings.MaximumStorageSizeMb * _1MB, m_userSession->GetUserSid(), false, false); - vhdCreated = true; + // If the VHD wasn't found, create it. + WSL_LOG("CreateStorageVhd", TraceLoggingValue(m_storageVhdPath.c_str(), "StorageVhdPath")); - // Then attach the new disk. - std::tie(diskLun, diskDevice) = m_virtualMachine->AttachDisk(m_storageVhdPath.c_str(), false); + auto runAsUser = wil::CoImpersonateClient(); - // Then format it. - Ext4Format(diskDevice); - } + std::filesystem::create_directories(storagePath); + wsl::core::filesystem::CreateVhd( + m_storageVhdPath.c_str(), Settings.MaximumStorageSizeMb * _1MB, m_userSession->GetUserSid(), false, false); + vhdCreated = true; - // Mount the device to /root. - m_virtualMachine->Mount(diskDevice.c_str(), "/root", "ext4", "", 0); + // Then attach the new disk. + std::tie(diskLun, diskDevice) = m_virtualMachine->AttachDisk(m_storageVhdPath.c_str(), false); - deleteVhdOnFailure.release(); - } - else - { - // If no storage path is specified, use a tmpfs for convenience. - m_virtualMachine->Mount("", "/root", "tmpfs", "", 0); + // Then format it. + Ext4Format(diskDevice); } + + // Mount the device to /root. + m_virtualMachine->Mount(diskDevice.c_str(), "/root", "ext4", "", 0); + + deleteVhdOnFailure.release(); } HRESULT WSLASession::GetDisplayName(LPWSTR* DisplayName)