Initial implementation for CreateContainer() (#13791)

* Implement CreateContainer method

* Build fixes

* Implement CreateContainer method

* Update src/windows/wslaservice/exe/WSLAContainer.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix nerdctl host networking parameter

* Prototype CreateContainer()

* Format

* Format

* Start writing tests

* Fix various issues

* Add new files

* Test cleanup

* Test cleanup

* Prepare for PR

* Fix ARM build

* Add copyright header

* Update cmakelists.txt

* Use ifdefs

* Format

* ifdef ARM64

* Install the test .vhd in the MSI

* Update the stdin test

* Format

* Update src/windows/wslaservice/exe/WSLAContainer.cpp

Co-authored-by: Pooja Trivedi <poojatrivedi@gmail.com>

* Update src/windows/wslaservice/inc/wslaservice.idl

Co-authored-by: Pooja Trivedi <poojatrivedi@gmail.com>

* Update test/windows/WSLATests.cpp

Co-authored-by: Pooja Trivedi <poojatrivedi@gmail.com>

* Update src/windows/common/WSLAContainerLauncher.cpp

Co-authored-by: Pooja Trivedi <poojatrivedi@gmail.com>

* Apply PR feedback

* Format

---------

Co-authored-by: Pooja Trivedi <trivedipooja@microsoft.com>
Co-authored-by: Pooja Trivedi <poojatrivedi@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Blue 2025-12-02 22:40:16 +00:00 committed by GitHub
parent 2ab516edf4
commit 2a41fe20e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 555 additions and 35 deletions

View File

@ -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()

View File

@ -40,6 +40,12 @@
<File Id="system.vhd" Source="${WSLG_SOURCE_DIR}/${TARGET_PLATFORM}/system.vhd"/>
<?endif?>
<!-- Temporary runtime VHD. TODO: Update once the final VHD is available. -->
<?if "${WSL_DEV_BINARY_PATH}" = "" AND "${TARGET_PLATFORM}" = "x64" ?>
<File Id="wslarootfs.vhd" Name="wslarootfs.vhd" Source="${WSLA_TEST_DISTRO_SOURCE_DIR}/wslatestrootfs.vhd"/>
<?endif?>
<!-- Installation folder -->
<RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss\MSI">
<RegistryValue Name="InstallLocation" Value="[INSTALLDIR]" Type="string" />

View File

@ -30,6 +30,10 @@ Abstract:
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;

View File

@ -34,6 +34,7 @@ set(SOURCES
SubProcess.cpp
svccomm.cpp
svccommio.cpp
WSLAContainerLauncher.cpp
VirtioNetworking.cpp
WSLAProcessLauncher.cpp
WslClient.cpp
@ -111,6 +112,7 @@ set(HEADERS
SubProcess.h
svccomm.hpp
svccommio.hpp
WSLAContainerLauncher.h
VirtioNetworking.h
WSLAProcessLauncher.h
WslClient.h

View File

@ -0,0 +1,74 @@
/*++
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;
using wsl::windows::common::RunningWSLAContainer;
using wsl::windows::common::WSLAContainerLauncher;
RunningWSLAContainer::RunningWSLAContainer(wil::com_ptr<IWSLAContainer>&& Container, std::vector<WSLA_PROCESS_FD>&& 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<IWSLAProcess> 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<std::string>& Arguments,
const std::vector<std::string>& 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, container networking mode, etc.
wil::com_ptr<IWSLAContainer> container;
THROW_IF_FAILED(Session.CreateContainer(&options, &container));
return RunningWSLAContainer{std::move(container), std::move(m_fds)};
}

View File

@ -0,0 +1,59 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
WSLAContainerLauncher.h
Abstract:
This file contains the definition for WSLAContainerLauncher.
--*/
#pragma once
#include "WSLAProcessLauncher.h"
namespace wsl::windows::common {
class RunningWSLAContainer
{
public:
NON_COPYABLE(RunningWSLAContainer);
DEFAULT_MOVABLE(RunningWSLAContainer);
RunningWSLAContainer(wil::com_ptr<IWSLAContainer>&& Container, std::vector<WSLA_PROCESS_FD>&& fds);
IWSLAContainer& Get();
WSLA_CONTAINER_STATE State();
ClientRunningWSLAProcess GetInitProcess();
private:
wil::com_ptr<IWSLAContainer> m_container;
std::vector<WSLA_PROCESS_FD> 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<std::string>& Arguments = {},
const std::vector<std::string>& 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

View File

@ -158,7 +158,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<HRESULT, int, std::optional<ClientRunningWSLAProcess>> WSLAProcessLauncher::LaunchNoThrow(IWSLASession& Session)

View File

@ -43,6 +43,9 @@ public:
};
RunningWSLAProcess(std::vector<WSLA_PROCESS_FD>&& fds);
NON_COPYABLE(RunningWSLAProcess);
DEFAULT_MOVABLE(RunningWSLAProcess);
ProcessResult WaitAndCaptureOutput(DWORD TimeoutMs = INFINITE, std::vector<std::unique_ptr<relay::OverlappedIOHandle>>&& ExtraHandles = {});
virtual wil::unique_handle GetStdHandle(int Index) = 0;
virtual wil::unique_event GetExitEvent() = 0;
@ -57,6 +60,9 @@ protected:
class ClientRunningWSLAProcess : public RunningWSLAProcess
{
public:
NON_COPYABLE(ClientRunningWSLAProcess);
DEFAULT_MOVABLE(ClientRunningWSLAProcess);
ClientRunningWSLAProcess(wil::com_ptr<IWSLAProcess>&& process, std::vector<WSLA_PROCESS_FD>&& fds);
wil::unique_handle GetStdHandle(int Index) override;
wil::unique_event GetExitEvent() override;
@ -68,7 +74,6 @@ protected:
private:
wil::com_ptr<IWSLAProcess> m_process;
};
class WSLAProcessLauncher
{
public:

View File

@ -1549,7 +1549,9 @@ int WslaShell(_In_ std::wstring_view commandLine)
settings.BootTimeoutMs = 30000;
settings.NetworkingMode = WSLANetworkingModeNAT;
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");
@ -1560,6 +1562,8 @@ int WslaShell(_In_ std::wstring_view commandLine)
parser.AddArgument(Integer(reinterpret_cast<int&>(settings.NetworkingMode)), L"--networking-mode");
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();
@ -1605,18 +1609,28 @@ int WslaShell(_In_ std::wstring_view commandLine)
wil::com_ptr<IWSLASession> 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())
{
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->OpenSessionByName(debugShell.c_str(), &session));
}
else
{
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<wil::com_ptr<IWSLAContainer>> container;
std::optional<wsl::windows::common::ClientRunningWSLAProcess> process;
// Get the terminal size.
HANDLE Stdout = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE Stdin = GetStdHandle(STD_INPUT_HANDLE);
@ -1631,7 +1645,36 @@ int WslaShell(_In_ std::wstring_view commandLine)
launcher.AddFd(WSLA_PROCESS_FD{.Fd = 2, .Type = WSLAFdTypeTerminalControl});
launcher.SetTtySize(Info.srWindow.Bottom - Info.srWindow.Top + 1, Info.srWindow.Right - Info.srWindow.Left + 1);
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<WSLA_PROCESS_FD> 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<DWORD>(fds.size());
container.emplace();
THROW_IF_FAILED(session->CreateContainer(&containerOptions, &container.value()));
wil::com_ptr<IWSLAProcess> initProcess;
THROW_IF_FAILED((*container)->GetInitProcess(&initProcess));
process.emplace(std::move(initProcess), std::move(fds));
}
// Configure console for interactive usage.
{
@ -1658,7 +1701,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]() {
@ -1674,7 +1717,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, [&]() {
@ -1683,12 +1726,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;

View File

@ -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)

View File

@ -143,7 +143,8 @@ static const std::map<HRESULT, LPCWSTR> 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

View File

@ -24,7 +24,7 @@ class ServiceRunningProcess : public common::RunningWSLAProcess
{
public:
NON_COPYABLE(ServiceRunningProcess);
NON_MOVABLE(ServiceRunningProcess);
DEFAULT_MOVABLE(ServiceRunningProcess);
ServiceRunningProcess(const Microsoft::WRL::ComPtr<WSLAProcess>& process, std::vector<WSLA_PROCESS_FD>&& fds);
wil::unique_handle GetStdHandle(int Index) override;

View File

@ -18,6 +18,14 @@ Abstract:
using wsl::windows::service::wsla::WSLAContainer;
constexpr const char* nerdctlPath = "/usr/bin/nerdctl";
// Constants for required default arguments for "nerdctl run..."
static std::vector<std::string> defaultNerdctlRunArgs{//"--pull=never", // TODO: Uncomment once PullImage() is implemented.
"--net=host", // 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,103 @@ try
return S_OK;
}
CATCH_RETURN();
Microsoft::WRL::ComPtr<WSLAContainer> 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++)
{
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<std::string> 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");
}
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++)
{
launcher.AddFd(containerOptions.InitProcessOptions.Fds[i]);
}
return wil::MakeOrThrow<WSLAContainer>(&parentVM, launcher.Launch(parentVM));
}
std::vector<std::string> WSLAContainer::PrepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options, std::vector<std::string>&& inputOptions)
{
std::vector<std::string> args{nerdctlPath};
args.push_back("run");
args.push_back("--name");
args.push_back(options.Name);
if (options.ShmSize > 0)
{
args.push_back(std::format("--shm-size={}m", options.ShmSize));
}
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.insert(args.end(), defaultNerdctlRunArgs.begin(), defaultNerdctlRunArgs.end());
args.insert(args.end(), inputOptions.begin(), inputOptions.end());
for (ULONG i = 0; i < options.InitProcessOptions.EnvironmentCount; i++)
{
THROW_HR_IF_MSG(
E_INVALIDARG,
options.InitProcessOptions.Environment[i][0] == L'-',
"Invalid environment string: %hs",
options.InitProcessOptions.Environment[i]);
args.insert(args.end(), {"-e", options.InitProcessOptions.Environment[i]});
}
if (options.InitProcessOptions.Executable != nullptr)
{
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 > 0)
{
args.push_back("--");
for (ULONG i = 0; i < options.InitProcessOptions.CommandLineCount; i++)
{
args.push_back(options.InitProcessOptions.CommandLine[i]);
}
}
return args;
}

View File

@ -14,7 +14,9 @@ Abstract:
#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 @@ public:
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<WSLAContainer> Create(const WSLA_CONTAINER_OPTIONS& Options, WSLAVirtualMachine& parentVM);
private:
ServiceRunningProcess m_containerProcess;
WSLAVirtualMachine* m_parentVM = nullptr;
static std::vector<std::string> PrepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options, std::vector<std::string>&& inputOptions);
};
} // namespace wsl::windows::service::wsla

View File

@ -44,6 +44,8 @@ void WSLAProcess::OnVmTerminated()
{
m_state = WslaProcessStateSignalled;
m_exitedCode = 9; // SIGKILL
m_exitEvent.SetEvent();
}
}
@ -74,6 +76,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;

View File

@ -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;
@ -80,14 +85,17 @@ 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.
RETURN_HR_IF_NULL(E_POINTER, containerOptions);
auto container = wil::MakeOrThrow<WSLAContainer>();
container.CopyTo(__uuidof(IWSLAContainer), (void**)Container);
std::lock_guard lock{m_lock};
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
// TODO: Log entrance into the function.
auto container = WSLAContainer::Create(*containerOptions, *m_virtualMachine.Get());
THROW_IF_FAILED(container.CopyTo(__uuidof(IWSLAContainer), (void**)Container));
return S_OK;
}

View File

@ -27,6 +27,7 @@ public:
~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;
@ -56,6 +57,8 @@ private:
Microsoft::WRL::ComPtr<WSLAVirtualMachine> m_virtualMachine;
std::wstring m_displayName;
std::mutex m_lock;
// TODO: Add container tracking here. Could reuse m_lock for that.
};
} // namespace wsl::windows::service::wsla

View File

@ -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<WSLASession>(*Settings, *this, *VmSettings);
@ -63,6 +62,24 @@ HRESULT wsl::windows::service::wsla::WSLAUserSessionImpl::CreateSession(
return S_OK;
}
HRESULT WSLAUserSessionImpl::OpenSessionByName(LPCWSTR DisplayName, IWSLASession** Session)
{
std::lock_guard lock(m_wslaSessionsLock);
// TODO: ACL check
// 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<WSLAUserSessionImpl>&& Session) :
m_session(std::move(Session))
{
@ -92,7 +109,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();

View File

@ -30,6 +30,7 @@ public:
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 @@ public:
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<WSLAUserSessionImpl> m_session;

View File

@ -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;
@ -119,7 +119,8 @@ struct WSLA_PROCESS_OPTIONS
struct WSLA_VOLUME
{
LPCSTR HostPath;
LPCSTR ContainerHostPath;
LPCSTR ContainerPath;
BOOL ReadOnly;
};
struct WSLA_PORT_MAPPING
@ -128,11 +129,17 @@ struct WSLA_PORT_MAPPING
USHORT ContainerPort;
};
enum WSLA_CONTAINER_FLAGS
{
WSLA_CONTAINER_FLAG_ENABLE_GPU = 1
} ;
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;
@ -315,6 +322,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()' ?
}

View File

@ -17,11 +17,14 @@ Abstract:
#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<IWSLASession> 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<IWSLAUserSession> 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<int, std::string>& expectedOutput, int expectedResult = 0)
{
auto result = process.WaitAndCaptureOutput();
if (result.Code != expectedResult)
{
LogError(
"Command 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();
@ -436,7 +474,7 @@ class WSLATests
auto [hresult, _, process] = launcher.LaunchNoThrow(*session);
VERIFY_ARE_EQUAL(hresult, expectedError);
return process;
return std::move(process);
};
{
@ -1123,4 +1161,112 @@ 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();
#ifdef _ARM64_
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))
{
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;
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";
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 works with the default entrypoint.
{
WSLAContainerLauncher launcher(
"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<OverlappedIOHandle> writeStdin(
new WriteHandle(process.GetStdHandle(0), {shellInput.begin(), shellInput.end()}));
std::vector<std::unique_ptr<OverlappedIOHandle>> extraHandles;
extraHandles.emplace_back(std::move(writeStdin));
auto result = process.WaitAndCaptureOutput(INFINITE, std::move(extraHandles));
VERIFY_ARE_EQUAL(result.Output[1], "bash\n");
*/
}
// 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, ""}});
}
#endif
}
};