Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1220fbe
Implement CreateContainer method
Nov 19, 2025
0920c52
Build fixes
OneBlue Nov 21, 2025
3d5fa8b
Implement CreateContainer method
Nov 19, 2025
3a8a669
Update src/windows/wslaservice/exe/WSLAContainer.cpp
ptrivedi Nov 21, 2025
b8c5b8c
Fix nerdctl host networking parameter
Nov 21, 2025
cff1cd2
Prototype CreateContainer()
OneBlue Nov 21, 2025
29971ac
Format
OneBlue Nov 22, 2025
9c18826
Merge branch 'user/ptrivedi/create-cont' of https://github.com/micros…
OneBlue Nov 22, 2025
cdf7c46
Format
OneBlue Nov 22, 2025
3b5c028
Start writing tests
OneBlue Nov 25, 2025
2a4f8d0
Fix various issues
OneBlue Nov 26, 2025
9dd34f2
Add new files
OneBlue Nov 26, 2025
292707e
Test cleanup
OneBlue Nov 26, 2025
0eda07f
Test cleanup
OneBlue Nov 26, 2025
e4822ba
Merge remote-tracking branch 'origin/feature/wsl-for-apps' into user/…
OneBlue Nov 26, 2025
987b1b7
Prepare for PR
OneBlue Nov 26, 2025
a4ed4b2
Fix ARM build
OneBlue Nov 26, 2025
ee948d5
Add copyright header
OneBlue Nov 26, 2025
08d5929
Update cmakelists.txt
OneBlue Nov 26, 2025
2c28e53
Use ifdefs
OneBlue Nov 27, 2025
52aa7f8
Format
OneBlue Nov 27, 2025
9c5ed51
ifdef ARM64
OneBlue Nov 27, 2025
8d9e9cb
Install the test .vhd in the MSI
OneBlue Dec 1, 2025
e786943
Merge branch 'feature/wsl-for-apps' into user/oneblue/create-container
OneBlue Dec 1, 2025
0760be6
Update the stdin test
OneBlue Dec 1, 2025
70ac182
Format
OneBlue Dec 1, 2025
dcc0f5f
Merge branch 'user/oneblue/create-container' of https://github.com/mi…
OneBlue Dec 1, 2025
6d2831f
Update src/windows/wslaservice/exe/WSLAContainer.cpp
OneBlue Dec 2, 2025
2c7c305
Update src/windows/wslaservice/inc/wslaservice.idl
OneBlue Dec 2, 2025
4acb3a4
Update test/windows/WSLATests.cpp
OneBlue Dec 2, 2025
61bca24
Update src/windows/common/WSLAContainerLauncher.cpp
OneBlue Dec 2, 2025
6fc68e8
Apply PR feedback
OneBlue Dec 2, 2025
912af10
Format
OneBlue Dec 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition DEFINED OFFICIAL_BUILD was changed to OFFICIAL_BUILD without checking if it's defined first. If OFFICIAL_BUILD is not defined, this will cause a CMake warning or error. The correct pattern should be if (NOT DEFINED OFFICIAL_BUILD OR NOT OFFICIAL_BUILD) or keep the original if (NOT DEFINED OFFICIAL_BUILD ...) form.

Copilot uses AI. Check for mistakes.
add_compile_definitions(WSLA_TEST_DISTRO_PATH="${WSLA_TEST_DISTRO_SOURCE_DIR}/wslatestrootfs.vhd")
endif()

Expand Down
6 changes: 6 additions & 0 deletions msipackage/package.wix.in
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
4 changes: 4 additions & 0 deletions src/shared/inc/defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/windows/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ set(SOURCES
SubProcess.cpp
svccomm.cpp
svccommio.cpp
WSLAContainerLauncher.cpp
VirtioNetworking.cpp
WSLAProcessLauncher.cpp
WslClient.cpp
Expand Down Expand Up @@ -111,6 +112,7 @@ set(HEADERS
SubProcess.h
svccomm.hpp
svccommio.hpp
WSLAContainerLauncher.h
VirtioNetworking.h
WSLAProcessLauncher.h
WslClient.h
Expand Down
74 changes: 74 additions & 0 deletions src/windows/common/WSLAContainerLauncher.cpp
Original file line number Diff line number Diff line change
@@ -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)};
}
59 changes: 59 additions & 0 deletions src/windows/common/WSLAContainerLauncher.h
Original file line number Diff line number Diff line change
@@ -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();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: It would be good to add a GetPid() either here or in ClientRunningWSLAProcess(). Otherwise, to get Container's entry process pid, we would be doing a RunningWSLAContainer->GetInitProcess()->Get()->GetPid(). Could do that in a followup PR, oc

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PID can be retrieved today RunningWSLAContainer.Get().GetPid().

We could add a convenience method to make it a bit easier though


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);
Comment on lines +50 to +51
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing implementation: The AddVolume and AddPort methods are declared in the header but never implemented. These methods should either be implemented or marked as TODO/future work if they're intentionally deferred.

Copilot uses AI. Check for mistakes.

RunningWSLAContainer Launch(IWSLASession& Session);

private:
std::string m_image;
std::string m_name;
};
} // namespace wsl::windows::common
2 changes: 1 addition & 1 deletion src/windows/common/WSLAProcessLauncher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Similarly, std::move() is unnecessary here. The compiler will automatically apply move semantics when returning a local variable.

Copilot uses AI. Check for mistakes.
}

std::tuple<HRESULT, int, std::optional<ClientRunningWSLAProcess>> WSLAProcessLauncher::LaunchNoThrow(IWSLASession& Session)
Expand Down
7 changes: 6 additions & 1 deletion src/windows/common/WSLAProcessLauncher.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class RunningWSLAProcess
};

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;
Expand All @@ -57,6 +60,9 @@ class RunningWSLAProcess
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;
Expand All @@ -68,7 +74,6 @@ class ClientRunningWSLAProcess : public RunningWSLAProcess
private:
wil::com_ptr<IWSLAProcess> m_process;
};

class WSLAProcessLauncher
{
public:
Expand Down
71 changes: 57 additions & 14 deletions src/windows/common/WslClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -1559,6 +1561,8 @@ 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(debugShell, L"--debug-shell");
parser.AddArgument(help, L"--help");
parser.Parse();

Expand Down Expand Up @@ -1594,18 +1598,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())
{
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<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);
Expand All @@ -1620,7 +1634,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.
{
Expand All @@ -1647,7 +1690,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]() {
Expand All @@ -1663,7 +1706,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, [&]() {
Expand All @@ -1672,12 +1715,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;
Expand Down
6 changes: 4 additions & 2 deletions src/windows/common/WslCoreFilesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Comment on lines +32 to +35
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Changed from WI_ASSERT to THROW_HR_IF(E_INVALIDARG, ...). This is a good improvement as it provides better error handling in release builds. However, note that this changes behavior: the assertion would only trigger in debug builds, while the exception will trigger in all builds. Ensure callers are prepared to handle this exception.

Copilot uses AI. Check for mistakes.

// Disable creation of sparse VHDs while data corruption is being debugged.
if (sparse)
Expand Down
3 changes: 2 additions & 1 deletion src/windows/common/wslutil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/windows/wslaservice/exe/ServiceProcessLauncher.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading