Implement WSLAProcess (#13694)

* wsla: Prototype new process waitpid() model

* Save state

* Save state

* Save state

* Save state

* Save state

* Wire everything

* Format

* Save state

* Save state

* Redesign process launcher

* Port tests

* Port tests

* Move shell to new API

* Format

* Port more tests

* Add copyright header

* Prepare for PR

* Fix tests

* PR feedback

* Fix termination issue + add more negative tests

* Fix termination issue + add more negative tests

* Format
This commit is contained in:
Blue 2025-11-14 02:10:00 +00:00 committed by GitHub
parent 5585e4210b
commit c1a3e6174c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 1531 additions and 756 deletions

View File

@ -24,6 +24,7 @@ Abstract:
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/utsname.h>
#include <sys/signalfd.h>
#include <arpa/inet.h>
#include <pty.h>
@ -636,11 +637,116 @@ void HandleMessage(wsl::shared::SocketChannel& Channel, LX_MESSAGE_TYPE Type, co
}
}
void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const WSLA_WATCH_PROCESSES& Message, const gsl::span<gsl::byte>& Buffer)
{
// Create a signalfd to watch for SIGCHLD
sigset_t mask{};
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
THROW_LAST_ERROR_IF(UtilSaveBlockedSignals(mask) < 0);
wil::unique_fd signalFd = signalfd(-1, &mask, SFD_CLOEXEC);
THROW_LAST_ERROR_IF(signalFd.get() < 0);
Channel.SendResultMessage<uint32_t>(0);
// Poll for either a received signal or a new message on the channel.
pollfd polls[2]{};
polls[0].fd = signalFd.get();
polls[0].events = POLLIN;
polls[1].fd = Channel.Socket();
polls[1].events = POLLIN;
while (true)
{
auto result = poll(polls, COUNT_OF(polls), -1);
THROW_LAST_ERROR_IF(result < 0);
// TODO: Check for poll errors
if (polls[0].revents & POLLIN)
{
signalfd_siginfo sigInfo{};
auto bytes = TEMP_FAILURE_RETRY(read(signalFd.get(), &sigInfo, sizeof(signalfd_siginfo)));
THROW_LAST_ERROR_IF(bytes < 0);
if (bytes != sizeof(sigInfo))
{
LOG_ERROR("Unexpected read size: {} (expected {})", bytes, sizeof(sigInfo));
THROW_ERRNO(EINVAL);
}
if (sigInfo.ssi_signo != SIGCHLD)
{
LOG_ERROR("Received unexpected signal from signalfd: {}", sigInfo.ssi_signo);
THROW_LAST_ERROR_IF(EINVAL);
}
// We received a SIGCHLD. This means that one or more children processes have exited.
bool exitedProcess = false; // Sanity check
while (true)
{
int status{};
result = waitpid(-1, &status, WNOHANG);
if (result < 0 && errno != ECHILD)
{
THROW_LAST_ERROR();
}
if (result <= 0)
{
break;
}
exitedProcess = true;
WSLA_PROCESS_EXITED message{};
message.Pid = result;
if (WIFSIGNALED(status))
{
message.Signaled = true;
message.Code = WTERMSIG(status);
}
else if (WIFEXITED(status))
{
message.Code = WEXITSTATUS(status);
}
else
{
LOG_ERROR("Received SIGCHLD for process that was neither signaled nor exited. Pid: {}, Status: {}", result, status)
}
Channel.SendMessage(message);
}
if (!exitedProcess)
{
LOG_ERROR("Received SIGCHLD but no children have exited");
}
}
if (polls[1].revents & POLLIN)
{
auto [message, _] = Channel.ReceiveMessageOrClosed<MESSAGE_HEADER>();
if (message == nullptr)
{
break;
}
else
{
LOG_ERROR("Received unexpected message: {}", message->MessageType);
THROW_ERRNO(EINVAL);
}
}
}
}
void ProcessMessage(wsl::shared::SocketChannel& Channel, LX_MESSAGE_TYPE Type, const gsl::span<gsl::byte>& Buffer)
{
try
{
HandleMessage<WSLA_GET_DISK, WSLA_MOUNT, WSLA_EXEC, WSLA_FORK, WSLA_CONNECT, WSLA_WAITPID, WSLA_SIGNAL, WSLA_TTY_RELAY, WSLA_PORT_RELAY, WSLA_OPEN, WSLA_UNMOUNT, WSLA_DETACH, WSLA_ACCEPT>(
HandleMessage<WSLA_GET_DISK, WSLA_MOUNT, WSLA_EXEC, WSLA_FORK, WSLA_CONNECT, WSLA_WAITPID, WSLA_SIGNAL, WSLA_TTY_RELAY, WSLA_PORT_RELAY, WSLA_OPEN, WSLA_UNMOUNT, WSLA_DETACH, WSLA_ACCEPT, WSLA_WATCH_PROCESSES>(
Channel, Type, Buffer);
}
catch (...)
@ -756,6 +862,11 @@ int WSLAEntryPoint(int Argc, char* Argv[])
THROW_LAST_ERROR_IF(UtilSetSignalHandlers(g_SavedSignalActions, false) < 0);
sigset_t mask{};
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
THROW_LAST_ERROR_IF(UtilSaveBlockedSignals(mask) < 0);
//
// Ensure /dev/console is present and set as the controlling terminal.
// If opening /dev/console times out, stdout and stderr to the logging file descriptor.

View File

@ -58,6 +58,8 @@ public:
{
m_name = std::move(other.m_name);
m_socket = std::move(other.m_socket);
m_received_messages = other.m_received_messages;
m_sent_messages = other.m_sent_messages;
#ifdef WIN32
m_exitEvent = std::move(other.m_exitEvent);

View File

@ -22,6 +22,14 @@ Abstract:
#define _wcsicmp wcscasecmp
#endif
#define NON_COPYABLE(Type) \
Type(const Type&) = delete; \
Type& operator=(const Type&) = delete;
#define NON_MOVABLE(Type) \
Type(Type&&) = delete; \
Type& operator=(Type&&) = delete;
namespace wsl::shared {
inline constexpr std::uint32_t VersionMajor = WSL_PACKAGE_VERSION_MAJOR;

View File

@ -391,6 +391,8 @@ typedef enum _LX_MESSAGE_TYPE
LxMessageWSLAUnmount,
LxMessageWSLADetach,
LxMessageWSLATerminalChanged,
LxMessageWSLAWatchProcesses,
LxMessageWSLAProcessExited
} LX_MESSAGE_TYPE,
*PLX_MESSAGE_TYPE;
@ -500,6 +502,9 @@ inline auto ToString(LX_MESSAGE_TYPE messageType)
X(LxMessageWSLAUnmount)
X(LxMessageWSLADetach)
X(LxMessageWSLATerminalChanged)
X(LxMessageWSLAWatchProcesses)
X(LxMessageWSLAProcessExited)
default:
return "<unexpected LX_MESSAGE_TYPE>";
}
@ -1848,6 +1853,30 @@ struct WSLA_TERMINAL_CHANGED
PRETTY_PRINT(FIELD(Header), FIELD(Rows), FIELD(Columns));
};
struct WSLA_WATCH_PROCESSES
{
DECLARE_MESSAGE_CTOR(WSLA_WATCH_PROCESSES);
static inline auto Type = LxMessageWSLAWatchProcesses;
MESSAGE_HEADER Header;
PRETTY_PRINT(FIELD(Header));
};
struct WSLA_PROCESS_EXITED
{
DECLARE_MESSAGE_CTOR(WSLA_PROCESS_EXITED);
MESSAGE_HEADER Header;
static inline auto Type = LxMessageWSLAProcessExited;
uint32_t Pid;
uint32_t Code;
bool Signaled;
PRETTY_PRINT(FIELD(Header), FIELD(Pid), FIELD(Code), FIELD(Signaled));
};
typedef struct _LX_MINI_INIT_IMPORT_RESULT
{
static inline auto Type = LxMiniInitMessageImportResult;

View File

@ -34,6 +34,7 @@ set(SOURCES
SubProcess.cpp
svccomm.cpp
svccommio.cpp
WSLAProcessLauncher.cpp
WslClient.cpp
WslCoreConfig.cpp
WslCoreFilesystem.cpp
@ -105,6 +106,7 @@ set(HEADERS
SubProcess.h
svccomm.hpp
svccommio.hpp
WSLAProcessLauncher.h
WslClient.h
WslCoreConfig.h
WslCoreFilesystem.h

View File

@ -0,0 +1,199 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
WSLAProcessLauncher.cpp
Abstract:
WSLAProcessLauncher implementation.
--*/
#include <precomp.h>
#include "WSLAProcessLauncher.h"
#include "WSLAApi.h"
using wsl::windows::common::RunningWSLAProcess;
using wsl::windows::common::WSLAProcessLauncher;
WSLAProcessLauncher::WSLAProcessLauncher(
const std::string& Executable, const std::vector<std::string>& Arguments, const std::vector<std::string>& Environment, ProcessFlags Flags) :
m_executable(Executable), m_arguments(Arguments), m_environment(Environment)
{
// Add standard Fds.
if (WI_IsFlagSet(Flags, ProcessFlags::Stdin))
{
m_fds.emplace_back(WSLA_PROCESS_FD{.Fd = 0, .Type = WslFdTypeDefault, .Path = nullptr});
}
if (WI_IsFlagSet(Flags, ProcessFlags::Stdout))
{
m_fds.emplace_back(WSLA_PROCESS_FD{.Fd = 1, .Type = WslFdTypeDefault, .Path = nullptr});
}
if (WI_IsFlagSet(Flags, ProcessFlags::Stderr))
{
m_fds.emplace_back(WSLA_PROCESS_FD{.Fd = 2, .Type = WslFdTypeDefault, .Path = nullptr});
}
}
void WSLAProcessLauncher::AddFd(WSLA_PROCESS_FD Fd)
{
WI_ASSERT(std::ranges::find_if(m_fds, [&](const auto& e) { return e.Fd == Fd.Fd; }) == m_fds.end());
m_fds.push_back(Fd);
}
std::tuple<WSLA_PROCESS_OPTIONS, std::vector<const char*>, std::vector<const char*>> WSLAProcessLauncher::CreateProcessOptions()
{
std::vector<const char*> commandLine;
std::ranges::transform(m_arguments, std::back_inserter(commandLine), [](const std::string& e) { return e.c_str(); });
std::vector<const char*> environment;
std::ranges::transform(m_environment, std::back_inserter(environment), [](const std::string& e) { return e.c_str(); });
WSLA_PROCESS_OPTIONS options{};
options.Executable = m_executable.c_str();
options.CommandLine = commandLine.data();
options.CommandLineCount = static_cast<DWORD>(commandLine.size());
options.Fds = m_fds.data();
options.FdsCount = static_cast<DWORD>(m_fds.size());
options.Environment = environment.data();
options.EnvironmentCount = static_cast<DWORD>(environment.size());
return std::make_tuple(options, std::move(commandLine), std::move(environment));
}
RunningWSLAProcess WSLAProcessLauncher::Launch(IWSLASession& Session)
{
auto [hresult, error, process] = LaunchNoThrow(Session);
if (FAILED(hresult))
{
auto commandLine = wsl::shared::string::Join(m_arguments, ' ');
THROW_HR_MSG(hresult, "Failed to launch process: %hs (commandline: %hs). Errno = %i", m_executable.c_str(), commandLine.c_str(), error);
}
return process.value();
}
std::tuple<HRESULT, int, std::optional<RunningWSLAProcess>> WSLAProcessLauncher::LaunchNoThrow(IWSLASession& Session)
{
auto [options, commandLine, env] = CreateProcessOptions();
wil::com_ptr<IWSLAProcess> process;
int error = -1;
auto result = Session.CreateRootNamespaceProcess(&options, &process, &error);
if (FAILED(result))
{
return std::make_tuple(result, error, std::optional<RunningWSLAProcess>());
}
wsl::windows::common::security::ConfigureForCOMImpersonation(process.get());
return {S_OK, 0, RunningWSLAProcess{std::move(process), std::move(m_fds)}};
}
IWSLAProcess& RunningWSLAProcess::Get()
{
return *m_process.get();
}
RunningWSLAProcess::RunningWSLAProcess(wil::com_ptr<IWSLAProcess>&& process, std::vector<WSLA_PROCESS_FD>&& fds) :
m_process(std::move(process)), m_fds(std::move(fds))
{
}
RunningWSLAProcess::ProcessResult RunningWSLAProcess::WaitAndCaptureOutput(DWORD TimeoutMs, std::vector<std::unique_ptr<relay::OverlappedIOHandle>>&& ExtraHandles)
{
RunningWSLAProcess::ProcessResult result;
relay::MultiHandleWait io;
// Add a callback on IO for each std handle.
for (size_t i = 0; i < m_fds.size(); i++)
{
if (m_fds[i].Fd == 0 || m_fds[i].Type != WslFdTypeDefault)
{
continue; // Don't try to read from stdin or non hvsocket fds.
}
result.Output.emplace(m_fds[i].Fd, std::string{});
wil::unique_handle stdHandle;
THROW_IF_FAILED(m_process->GetStdHandle(m_fds[i].Fd, reinterpret_cast<ULONG*>(&stdHandle)));
auto ioCallback = [Index = m_fds[i].Fd, &result](const gsl::span<char>& Content) {
result.Output[Index].insert(result.Output[Index].end(), Content.begin(), Content.end());
};
io.AddHandle(std::make_unique<relay::ReadHandle>(std::move(stdHandle), std::move(ioCallback)));
}
for (auto& e : ExtraHandles)
{
io.AddHandle(std::move(e));
}
// Add a callback for when the process exits.
wil::unique_handle exitEvent;
THROW_IF_FAILED(m_process->GetExitEvent(reinterpret_cast<ULONG*>(&exitEvent)));
auto exitCallback = [&]() {
WSLA_PROCESS_STATE state{};
THROW_IF_FAILED(m_process->GetState(&state, &result.Code));
if (state == WslaProcessStateExited)
{
result.Signalled = false;
}
else if (state == WslaProcessStateSignalled)
{
result.Signalled = true;
}
else
{
THROW_HR_MSG(E_UNEXPECTED, "Unexpected process state: %i", state);
}
};
io.AddHandle(std::make_unique<relay::EventHandle>(std::move(exitEvent), std::move(exitCallback)));
io.Run(std::chrono::milliseconds(TimeoutMs));
return result;
}
wil::unique_handle RunningWSLAProcess::GetStdHandle(int Index)
{
wil::unique_handle handle;
THROW_IF_FAILED_MSG(m_process->GetStdHandle(Index, reinterpret_cast<ULONG*>(&handle)), "Failed to get handle: %i", Index);
return handle;
}
wil::unique_event RunningWSLAProcess::GetExitEvent()
{
wil::unique_event event;
THROW_IF_FAILED(m_process->GetExitEvent(reinterpret_cast<ULONG*>(&event)));
return event;
}
std::pair<int, bool> RunningWSLAProcess::GetExitState()
{
WSLA_PROCESS_STATE state{};
int code{};
THROW_IF_FAILED(m_process->GetState(&state, &code));
THROW_HR_IF_MSG(
HRESULT_FROM_WIN32(ERROR_INVALID_STATE),
state != WslaProcessStateSignalled && state != WslaProcessStateExited,
"Process is not exited. State: %i",
state);
return {code, state == WslaProcessStateSignalled};
}

View File

@ -0,0 +1,84 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
WSLAProcessLauncher.h
Abstract:
Helper class to launch and wait for WSLA processes.
This is designed to function both for VM level and container level processes.
This class is also designed to work both from client & server side.
--*/
#pragma once
#include "wslaservice.h"
#include <variant>
#include <vector>
#include <string>
namespace wsl::windows::common {
enum class ProcessFlags
{
None = 0,
Stdin = 1,
Stdout = 2,
Stderr = 4,
};
DEFINE_ENUM_FLAG_OPERATORS(ProcessFlags);
class RunningWSLAProcess
{
public:
struct ProcessResult
{
int Code;
bool Signalled;
std::map<int, std::string> Output;
};
RunningWSLAProcess(wil::com_ptr<IWSLAProcess>&& process, std::vector<WSLA_PROCESS_FD>&& fds);
ProcessResult WaitAndCaptureOutput(DWORD TimeoutMs = INFINITE, std::vector<std::unique_ptr<relay::OverlappedIOHandle>>&& ExtraHandles = {});
wil::unique_handle GetStdHandle(int Index);
wil::unique_event GetExitEvent();
IWSLAProcess& Get();
std::pair<int, bool> GetExitState();
private:
wil::com_ptr<IWSLAProcess> m_process;
std::vector<WSLA_PROCESS_FD> m_fds;
};
class WSLAProcessLauncher
{
public:
NON_COPYABLE(WSLAProcessLauncher);
NON_MOVABLE(WSLAProcessLauncher);
WSLAProcessLauncher(
const std::string& Executable,
const std::vector<std::string>& Arguments,
const std::vector<std::string>& Environment = {},
ProcessFlags Flags = ProcessFlags::Stdout | ProcessFlags::Stderr);
void AddFd(WSLA_PROCESS_FD Fd);
// TODO: Add overloads for IWSLAContainer once implemented.
RunningWSLAProcess Launch(IWSLASession& Session);
std::tuple<HRESULT, int, std::optional<RunningWSLAProcess>> LaunchNoThrow(IWSLASession& Session);
private:
std::tuple<WSLA_PROCESS_OPTIONS, std::vector<const char*>, std::vector<const char*>> CreateProcessOptions();
std::vector<WSLA_PROCESS_FD> m_fds;
std::string m_executable;
std::vector<std::string> m_arguments;
std::vector<std::string> m_environment;
};
} // namespace wsl::windows::common

View File

@ -20,6 +20,7 @@ Abstract:
#include <conio.h>
#include "wslaservice.h"
#include "WSLAApi.h"
#include "WSLAProcessLauncher.h"
#define BASH_PATH L"/bin/bash"
@ -28,9 +29,11 @@ using winrt::Windows::Management::Deployment::DeploymentOptions;
using wsl::shared::Localization;
using wsl::windows::common::ClientExecutionContext;
using wsl::windows::common::Context;
using wsl::windows::common::WSLAProcessLauncher;
using namespace wsl::windows::common;
using namespace wsl::shared;
using namespace wsl::windows::common::distribution;
using wsl::windows::common::ProcessFlags;
static bool g_promptBeforeExit = false;
@ -1542,7 +1545,6 @@ int WslaShell(_In_ std::wstring_view commandLine)
settings.NetworkingMode = WslNetworkingModeNAT;
std::string shell = "/bin/bash";
std::string fsType = "ext4";
bool newApi = false;
bool help = false;
ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
@ -1552,9 +1554,7 @@ int WslaShell(_In_ std::wstring_view commandLine)
parser.AddArgument(Integer(settings.MemoryMb), L"--memory");
parser.AddArgument(Integer(settings.CpuCount), L"--cpu");
parser.AddArgument(Utf8String(fsType), L"--fstype");
parser.AddArgument(newApi, L"--new-api");
parser.AddArgument(help, L"--help");
parser.Parse();
if (help)
@ -1572,17 +1572,10 @@ int WslaShell(_In_ std::wstring_view commandLine)
wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get());
wil::com_ptr<IWSLAVirtualMachine> virtualMachine;
if (newApi)
{
WSLA_SESSION_SETTINGS sessionSettings{L"my-display-name"};
wil::com_ptr<IWSLASession> session;
THROW_IF_FAILED(userSession->CreateSession(&sessionSettings, &settings, &session));
THROW_IF_FAILED(session->GetVirtualMachine(&virtualMachine));
}
else
{
THROW_IF_FAILED(userSession->CreateVirtualMachine(&settings, &virtualMachine));
}
WSLA_SESSION_SETTINGS sessionSettings{L"my-display-name"};
wil::com_ptr<IWSLASession> session;
THROW_IF_FAILED(userSession->CreateSession(&sessionSettings, &settings, &session));
THROW_IF_FAILED(session->GetVirtualMachine(&virtualMachine));
wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get());
@ -1596,42 +1589,12 @@ int WslaShell(_In_ std::wstring_view commandLine)
THROW_IF_FAILED(virtualMachine->Mount(nullptr, "/proc", "proc", "", 0));
THROW_IF_FAILED(virtualMachine->Mount(nullptr, "/dev/pts", "devpts", "noatime,nosuid,noexec,gid=5,mode=620", 0));
std::vector<const char*> shellCommandLine{shell.c_str()};
std::vector<const char*> env{"TERM=xterm-256color"};
wsl::windows::common::WSLAProcessLauncher launcher{shell, {shell}, {"TERM=xterm-256color"}, ProcessFlags::None};
launcher.AddFd(WSLA_PROCESS_FD{.Fd = 0, .Type = WslFdTypeTerminalInput});
launcher.AddFd(WSLA_PROCESS_FD{.Fd = 1, .Type = WslFdTypeTerminalOutput});
launcher.AddFd(WSLA_PROCESS_FD{.Fd = 2, .Type = WslFdTypeTerminalControl});
std::vector<WSLA_PROCESS_FD> fds(3);
fds[0].Fd = 0;
fds[0].Type = WslFdTypeTerminalInput;
fds[1].Fd = 1;
fds[1].Type = WslFdTypeTerminalOutput;
fds[2].Fd = 2;
fds[2].Type = WslFdTypeTerminalControl;
WSLA_CREATE_PROCESS_OPTIONS processOptions{};
processOptions.Executable = shell.c_str();
processOptions.CommandLine = shellCommandLine.data();
processOptions.CommandLineCount = static_cast<ULONG>(shellCommandLine.size());
processOptions.Environment = env.data();
processOptions.EnvironmentCount = static_cast<ULONG>(env.size());
processOptions.CurrentDirectory = "/";
std::vector<ULONG> handles(fds.size());
WSLA_CREATE_PROCESS_RESULT result{};
auto createProcessResult =
virtualMachine->CreateLinuxProcess(&processOptions, static_cast<ULONG>(fds.size()), fds.data(), handles.data(), &result);
if (FAILED(createProcessResult))
{
if (result.Errno != 0)
{
THROW_HR_WITH_USER_ERROR(E_FAIL, std::format(L"Failed to create process {}, errno = {}", shell.c_str(), result.Errno));
}
else
{
THROW_HR(createProcessResult);
}
}
auto process = launcher.Launch(*session);
// Configure console for interactive usage.
@ -1660,7 +1623,8 @@ int WslaShell(_In_ std::wstring_view commandLine)
// Create a thread to relay stdin to the pipe.
auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset);
wsl::shared::SocketChannel controlChannel{wil::unique_socket(handles[2]), "TerminalControl", exitEvent.get()};
wsl::shared::SocketChannel controlChannel{
wil::unique_socket{(SOCKET)process.GetStdHandle(2).release()}, "TerminalControl", exitEvent.get()};
std::thread inputThread([&]() {
auto updateTerminal = [&controlChannel, &Stdout]() {
@ -1676,7 +1640,7 @@ int WslaShell(_In_ std::wstring_view commandLine)
controlChannel.SendMessage(message);
};
wsl::windows::common::relay::StandardInputRelay(Stdin, UlongToHandle(handles[0]), 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, [&]() {
@ -1685,16 +1649,15 @@ int WslaShell(_In_ std::wstring_view commandLine)
});
// Relay the contents of the pipe to stdout.
wsl::windows::common::relay::InterruptableRelay(UlongToHandle(handles[1]), Stdout);
wsl::windows::common::relay::InterruptableRelay(process.GetStdHandle(1).get(), Stdout);
}
ULONG exitState{};
int exitCode{};
THROW_IF_FAILED(virtualMachine->WaitPid(result.Pid, 0, &exitState, &exitCode));
process.GetExitEvent().wait();
wprintf(L"%hs exited with: %i", shell.c_str(), exitCode);
auto [code, signalled] = process.GetExitState();
wprintf(L"%hs exited with: %i%hs", shell.c_str(), code, signalled ? " (signalled)" : "");
return exitCode;
return code;
}
int WslMain(_In_ std::wstring_view commandLine)

View File

@ -16,8 +16,14 @@ Abstract:
#include "relay.hpp"
#pragma hdrstop
using wsl::windows::common::relay::EventHandle;
using wsl::windows::common::relay::IOHandleStatus;
using wsl::windows::common::relay::MultiHandleWait;
using wsl::windows::common::relay::OverlappedIOHandle;
using wsl::windows::common::relay::ReadHandle;
using wsl::windows::common::relay::ScopedMultiRelay;
using wsl::windows::common::relay::ScopedRelay;
using wsl::windows::common::relay::WriteHandle;
namespace {
@ -923,4 +929,250 @@ try
}
}
}
CATCH_LOG()
CATCH_LOG()
void MultiHandleWait::AddHandle(std::unique_ptr<OverlappedIOHandle>&& handle)
{
m_handles.emplace_back(std::move(handle));
}
void MultiHandleWait::Run(std::optional<std::chrono::milliseconds> Timeout)
{
std::optional<std::chrono::steady_clock::time_point> deadline;
if (Timeout.has_value())
{
deadline = std::chrono::steady_clock::now() + Timeout.value();
}
// Run until all handles are completed.
while (!m_handles.empty())
{
// Schedule IO on each handle until all are either pending, or completed.
for (auto i = 0; i < m_handles.size(); i++)
{
while (m_handles[i]->GetState() == IOHandleStatus::Standby)
{
m_handles[i]->Schedule();
}
}
// Remove completed handles from m_handles.
std::erase_if(m_handles, [&](const auto& e) { return e->GetState() == IOHandleStatus::Completed; });
if (m_handles.empty())
{
break;
}
// Wait for the next operation to complete.
std::vector<HANDLE> waitHandles;
for (const auto& e : m_handles)
{
waitHandles.emplace_back(e->GetHandle());
}
DWORD waitTimeout = INFINITE;
if (deadline.has_value())
{
auto miliseconds =
std::chrono::duration_cast<std::chrono::milliseconds>(deadline.value() - std::chrono::steady_clock::now()).count();
waitTimeout = static_cast<DWORD>(std::max(0LL, miliseconds));
}
auto result = WaitForMultipleObjects(static_cast<DWORD>(waitHandles.size()), waitHandles.data(), false, waitTimeout);
if (result == WAIT_TIMEOUT)
{
THROW_WIN32(ERROR_TIMEOUT);
}
else if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + m_handles.size())
{
auto index = result - WAIT_OBJECT_0;
m_handles[index]->Collect();
}
else
{
THROW_LAST_ERROR_MSG("Timeout: %lu, Count: %llu", waitTimeout, waitHandles.size());
}
}
}
IOHandleStatus OverlappedIOHandle::GetState() const
{
return State;
}
EventHandle::EventHandle(wil::unique_handle&& Handle, std::function<void()>&& OnSignalled) :
Handle(std::move(Handle)), OnSignalled(std::move(OnSignalled))
{
}
void EventHandle::Schedule()
{
State = IOHandleStatus::Pending;
}
void EventHandle::Collect()
{
State = IOHandleStatus::Completed;
OnSignalled();
}
HANDLE EventHandle::GetHandle() const
{
return Handle.get();
}
ReadHandle::ReadHandle(wil::unique_handle&& MovedHandle, std::function<void(const gsl::span<char>& Buffer)>&& OnRead) :
Handle(std::move(MovedHandle)), OnRead(OnRead)
{
Overlapped.hEvent = Event.get();
}
ReadHandle::~ReadHandle()
{
if (State == IOHandleStatus::Pending)
{
DWORD bytesRead{};
LOG_IF_WIN32_BOOL_FALSE(CancelIoEx(Handle.get(), &Overlapped));
LOG_IF_WIN32_BOOL_FALSE(GetOverlappedResult(Handle.get(), &Overlapped, &bytesRead, true));
}
}
void ReadHandle::Schedule()
{
WI_ASSERT(State == IOHandleStatus::Standby);
Event.ResetEvent();
// Schedule the read.
DWORD bytesRead{};
if (ReadFile(Handle.get(), Buffer.data(), static_cast<DWORD>(Buffer.size()), &bytesRead, &Overlapped))
{
// Signal the read.
OnRead(gsl::make_span<char>(Buffer.data(), static_cast<size_t>(bytesRead)));
// ReadFile completed immediately, process the result right away.
if (bytesRead == 0)
{
State = IOHandleStatus::Completed;
return; // Handle is completely read, don't try again.
}
// Read was done synchronously, remain in 'standby' state.
}
else
{
auto error = GetLastError();
if (error == ERROR_HANDLE_EOF || error == ERROR_BROKEN_PIPE)
{
State = IOHandleStatus::Completed;
return;
}
THROW_LAST_ERROR_IF_MSG(error != ERROR_IO_PENDING, "Handle: 0x%p", (void*)Handle.get());
// The read is pending, update to 'Pending'
State = IOHandleStatus::Pending;
}
}
void ReadHandle::Collect()
{
WI_ASSERT(State == IOHandleStatus::Pending);
// Transition back to standby
State = IOHandleStatus::Standby;
// Complete the read.
DWORD bytesRead{};
if (!GetOverlappedResult(Handle.get(), &Overlapped, &bytesRead, false))
{
auto error = GetLastError();
THROW_WIN32_IF(error, error != ERROR_HANDLE_EOF && error != ERROR_BROKEN_PIPE);
// We received ERROR_HANDLE_EOF or ERROR_BROKEN_PIPE. Validate that this was indeed a zero byte read.
WI_ASSERT(bytesRead == 0);
}
// Signal the read.
OnRead(gsl::make_span<char>(Buffer.data(), static_cast<size_t>(bytesRead)));
// Transition to Complete if this was a zero byte read.
if (bytesRead == 0)
{
State = IOHandleStatus::Completed;
}
}
HANDLE ReadHandle::GetHandle() const
{
return Event.get();
}
WriteHandle::WriteHandle(wil::unique_handle&& MovedHandle, const std::vector<char>& Buffer) :
Handle(std::move(MovedHandle)), Buffer(Buffer)
{
Overlapped.hEvent = Event.get();
}
WriteHandle::~WriteHandle()
{
if (State == IOHandleStatus::Pending)
{
DWORD bytesRead{};
LOG_IF_WIN32_BOOL_FALSE(CancelIoEx(Handle.get(), &Overlapped));
LOG_IF_WIN32_BOOL_FALSE(GetOverlappedResult(Handle.get(), &Overlapped, &bytesRead, true));
}
}
void WriteHandle::Schedule()
{
WI_ASSERT(State == IOHandleStatus::Standby);
Event.ResetEvent();
// Schedule the write.
DWORD bytesWritten{};
if (WriteFile(Handle.get(), Buffer.data() + Offset, static_cast<DWORD>(Buffer.size() - Offset), &bytesWritten, &Overlapped))
{
Offset += bytesWritten;
if (Offset >= Buffer.size())
{
State = IOHandleStatus::Completed;
}
}
else
{
auto error = GetLastError();
THROW_LAST_ERROR_IF_MSG(error != ERROR_IO_PENDING, "Handle: 0x%p", (void*)Handle.get());
// The write is pending, update to 'Pending'
State = IOHandleStatus::Pending;
}
}
void WriteHandle::Collect()
{
WI_ASSERT(State == IOHandleStatus::Pending);
// Transition back to standby
State = IOHandleStatus::Standby;
// Complete the write.
DWORD bytesWritten{};
THROW_IF_WIN32_BOOL_FALSE(GetOverlappedResult(Handle.get(), &Overlapped, &bytesWritten, false));
Offset += bytesWritten;
if (Offset >= Buffer.size())
{
State = IOHandleStatus::Completed;
}
}
HANDLE WriteHandle::GetHandle() const
{
return Event.get();
}

View File

@ -152,4 +152,96 @@ private:
std::function<void()> m_onDestroy;
};
enum class IOHandleStatus
{
Standby,
Pending,
Completed
};
class OverlappedIOHandle
{
public:
NON_COPYABLE(OverlappedIOHandle)
NON_MOVABLE(OverlappedIOHandle)
OverlappedIOHandle() = default;
virtual ~OverlappedIOHandle() = default;
virtual void Schedule() = 0;
virtual void Collect() = 0;
virtual HANDLE GetHandle() const = 0;
IOHandleStatus GetState() const;
protected:
IOHandleStatus State = IOHandleStatus::Standby;
};
class EventHandle : public OverlappedIOHandle
{
public:
NON_COPYABLE(EventHandle)
NON_MOVABLE(EventHandle)
EventHandle(wil::unique_handle&& EventHandle, std::function<void()>&& OnSignalled);
void Schedule() override;
void Collect() override;
HANDLE GetHandle() const override;
private:
wil::unique_handle Handle;
std::function<void()> OnSignalled;
};
class ReadHandle : public OverlappedIOHandle
{
public:
NON_COPYABLE(ReadHandle);
NON_MOVABLE(ReadHandle);
ReadHandle(wil::unique_handle&& MovedHandle, std::function<void(const gsl::span<char>& Buffer)>&& OnRead);
~ReadHandle();
void Schedule() override;
void Collect() override;
HANDLE GetHandle() const override;
private:
wil::unique_handle Handle;
std::function<void(const gsl::span<char>& Buffer)> OnRead;
wil::unique_event Event{wil::EventOptions::ManualReset};
OVERLAPPED Overlapped{};
std::vector<char> Buffer = std::vector<char>(LX_RELAY_BUFFER_SIZE);
};
class WriteHandle : public OverlappedIOHandle
{
public:
NON_COPYABLE(WriteHandle);
NON_MOVABLE(WriteHandle);
WriteHandle(wil::unique_handle&& MovedHandle, const std::vector<char>& Buffer);
~WriteHandle();
void Schedule() override;
void Collect() override;
HANDLE GetHandle() const override;
private:
wil::unique_handle Handle;
wil::unique_event Event{wil::EventOptions::ManualReset};
OVERLAPPED Overlapped{};
const std::vector<char>& Buffer;
DWORD Offset = 0;
};
class MultiHandleWait
{
public:
MultiHandleWait() = default;
void AddHandle(std::unique_ptr<OverlappedIOHandle>&& handle);
void Run(std::optional<std::chrono::milliseconds> Timeout);
private:
std::vector<std::unique_ptr<OverlappedIOHandle>> m_handles;
};
} // namespace wsl::windows::common::relay

View File

@ -2460,6 +2460,8 @@ void WslCoreVm::RegisterCallbacks(_In_ const std::function<void(ULONG)>& DistroE
if (header->MessageType == LxMiniInitMessageChildExit)
{
const auto* exitMessage = gslhelpers::try_get_struct<LX_MINI_INIT_CHILD_EXIT_MESSAGE>(message);
WSL_LOG("ProcessExited", TraceLoggingValue(exitMessage->ChildPid, "pid"));
if (exitMessage)
{
exitCallback(exitMessage->ChildPid);

View File

@ -113,59 +113,6 @@ HRESULT WslMount(WslVirtualMachineHandle VirtualMachine, const WslMountSettings*
HRESULT WslCreateLinuxProcess(WslVirtualMachineHandle VirtualMachine, WslCreateProcessSettings* UserSettings, int32_t* Pid)
{
WSLA_CREATE_PROCESS_OPTIONS options{};
auto Count = [](const auto* Ptr) -> ULONG {
if (Ptr == nullptr)
{
return 0;
}
ULONG Result = 0;
while (*Ptr != nullptr)
{
Result++;
Ptr++;
}
return Result;
};
options.Executable = UserSettings->Executable;
options.CommandLine = UserSettings->Arguments;
options.CommandLineCount = Count(options.CommandLine);
options.Environment = UserSettings->Environment;
options.EnvironmentCount = Count(options.Environment);
options.CurrentDirectory = UserSettings->CurrentDirectory;
WSLA_CREATE_PROCESS_RESULT result{};
std::vector<WSLA_PROCESS_FD> inputFd(UserSettings->FdCount);
for (size_t i = 0; i < UserSettings->FdCount; i++)
{
inputFd[i] = {
UserSettings->FileDescriptors[i].Number,
UserSettings->FileDescriptors[i].Type,
(char*)UserSettings->FileDescriptors[i].Path};
}
std::vector<ULONG> fds(UserSettings->FdCount);
if (fds.empty())
{
fds.resize(1); // COM doesn't like null pointers.
}
RETURN_IF_FAILED(reinterpret_cast<IWSLAVirtualMachine*>(VirtualMachine)
->CreateLinuxProcess(&options, UserSettings->FdCount, inputFd.data(), fds.data(), &result));
for (size_t i = 0; i < UserSettings->FdCount; i++)
{
UserSettings->FileDescriptors[i].Handle = UlongToHandle(fds[i]);
}
*Pid = result.Pid;
return S_OK;
}

View File

@ -43,12 +43,12 @@ HRESULT WSLAContainer::GetInitProcess(IWSLAProcess** process)
return E_NOTIMPL;
}
HRESULT WSLAContainer::Exec(const WSLA_PROCESS_OPTIONS* Options, IWSLAProcess** Process)
HRESULT WSLAContainer::Exec(const WSLA_PROCESS_OPTIONS* Options, IWSLAProcess** Process, int* Errno)
try
{
auto process = wil::MakeOrThrow<WSLAProcess>();
// auto process = wil::MakeOrThrow<WSLAProcess>();
process.CopyTo(__uuidof(IWSLAProcess), (void**)Process);
// process.CopyTo(__uuidof(IWSLAProcess), (void**)Process);
return S_OK;
}

View File

@ -31,7 +31,7 @@ public:
IFACEMETHOD(Delete)() override;
IFACEMETHOD(GetState)(_Out_ WSLA_CONTAINER_STATE* State) override;
IFACEMETHOD(GetInitProcess)(_Out_ IWSLAProcess** process) override;
IFACEMETHOD(Exec)(_In_ const WSLA_PROCESS_OPTIONS* Options, _Out_ IWSLAProcess** Process) override;
IFACEMETHOD(Exec)(_In_ const WSLA_PROCESS_OPTIONS* Options, _Out_ IWSLAProcess** Process, _Out_ int* Errno) override;
private:
};

View File

@ -14,14 +14,49 @@ Abstract:
#include "precomp.h"
#include "WSLAProcess.h"
#include "WSLAVirtualMachine.h"
using wsl::windows::service::wsla::WSLAProcess;
HRESULT WSLAProcess::Signal(int Signal)
WSLAProcess::WSLAProcess(std::map<int, wil::unique_socket>&& handles, int pid, WSLAVirtualMachine* virtualMachine) :
m_handles(std::move(handles)), m_pid(pid), m_virtualMachine(virtualMachine)
{
return E_NOTIMPL;
}
WSLAProcess::~WSLAProcess()
{
std::lock_guard lock{m_mutex};
if (m_virtualMachine != nullptr)
{
m_virtualMachine->OnProcessReleased(m_pid);
}
}
void WSLAProcess::OnVmTerminated()
{
WI_ASSERT(m_virtualMachine != nullptr);
std::lock_guard lock{m_mutex};
m_virtualMachine = nullptr;
// Make sure that the process is in a terminated state, so users don't think that it might still be running.
if (m_state == WslaProcessStateRunning)
{
m_state = WslaProcessStateSignalled;
m_exitedCode = 9; // SIGKILL
}
}
HRESULT WSLAProcess::Signal(int Signal)
try
{
std::lock_guard lock{m_mutex};
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_state != WslaProcessStateRunning || m_virtualMachine == nullptr);
return m_virtualMachine->Signal(m_pid, Signal);
}
CATCH_RETURN();
HRESULT WSLAProcess::GetExitEvent(ULONG* Event)
try
{
@ -31,16 +66,78 @@ try
CATCH_RETURN();
HRESULT WSLAProcess::GetStdHandle(ULONG Index, ULONG* Handle)
try
{
return E_NOTIMPL;
std::lock_guard lock{m_mutex};
auto& socket = GetSocket(Index);
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !socket.is_valid());
*Handle = HandleToUlong(common::wslutil::DuplicateHandleToCallingProcess(reinterpret_cast<HANDLE>(socket.get())));
socket.reset();
return S_OK;
}
CATCH_RETURN();
wil::unique_socket& WSLAProcess::GetSocket(int Index)
{
std::lock_guard lock{m_mutex};
auto it = m_handles.find(Index);
THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), it == m_handles.end(), "Pid: %i, Fd: %i", m_pid, Index);
return it->second;
}
HRESULT WSLAProcess::GetPid(int* Pid)
try
{
return E_NOTIMPL;
// m_pid is immutable, so m_mutex doesn't need to be acquired.
// TODO: Container processes should return the container pid, and not the root namespace pid.
*Pid = m_pid;
return S_OK;
}
CATCH_RETURN();
int WSLAProcess::GetPid() const
{
// m_pid is immutable, so m_mutex doesn't need to be acquired.
return m_pid;
}
HRESULT WSLAProcess::GetState(WSLA_PROCESS_STATE* State, int* Code)
try
{
return E_NOTIMPL;
std::lock_guard lock{m_mutex};
*State = m_state;
*Code = m_exitedCode;
return S_OK;
}
CATCH_RETURN();
void WSLAProcess::OnTerminated(bool Signalled, int Code)
{
WI_ASSERT(m_virtualMachine != nullptr);
{
std::lock_guard lock{m_mutex};
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_state != WslaProcessStateRunning);
if (Signalled)
{
m_state = WslaProcessStateSignalled;
}
else
{
m_state = WslaProcessStateExited;
}
m_exitedCode = Code;
}
m_exitEvent.SetEvent();
}

View File

@ -17,11 +17,14 @@ Abstract:
namespace wsl::windows::service::wsla {
class WSLAVirtualMachine;
class DECLSPEC_UUID("AFBEA6D6-D8A4-4F81-8FED-F947EB74B33B") WSLAProcess
: public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IWSLAProcess, IFastRundown>
{
public:
WSLAProcess() = default; // TODO
WSLAProcess(std::map<int, wil::unique_socket>&& handles, int pid, WSLAVirtualMachine* virtualMachine);
~WSLAProcess();
WSLAProcess(const WSLAProcess&) = delete;
WSLAProcess& operator=(const WSLAProcess&) = delete;
@ -31,8 +34,18 @@ public:
IFACEMETHOD(GetPid)(_Out_ int* Pid) override;
IFACEMETHOD(GetState)(_Out_ WSLA_PROCESS_STATE* State, _Out_ int* Code) override;
void OnTerminated(bool Signalled, int Code);
void OnVmTerminated();
wil::unique_socket& GetSocket(int Index);
int GetPid() const;
private:
std::vector<wil::unique_handle> m_handles;
std::recursive_mutex m_mutex;
std::map<int, wil::unique_socket> m_handles;
int m_pid = -1;
int m_exitedCode = -1;
WSLA_PROCESS_STATE m_state = WslaProcessStateRunning;
wil::unique_event m_exitEvent{wil::EventOptions::ManualReset};
WSLAVirtualMachine* m_virtualMachine{};
};
} // namespace wsl::windows::service::wsla

View File

@ -22,10 +22,10 @@ using wsl::windows::service::wsla::WSLASession;
WSLASession::WSLASession(const WSLA_SESSION_SETTINGS& Settings, WSLAUserSessionImpl& userSessionImpl, const VIRTUAL_MACHINE_SETTINGS& VmSettings) :
m_sessionSettings(Settings),
m_userSession(userSessionImpl),
m_virtualMachine(VmSettings, userSessionImpl.GetUserSid(), &userSessionImpl),
m_virtualMachine(std::make_optional<WSLAVirtualMachine>(VmSettings, userSessionImpl.GetUserSid(), &userSessionImpl)),
m_displayName(Settings.DisplayName)
{
m_virtualMachine.Start();
m_virtualMachine->Start();
}
HRESULT WSLASession::GetDisplayName(LPWSTR* DisplayName)
@ -79,16 +79,42 @@ HRESULT WSLASession::ListContainers(WSLA_CONTAINER** Images, ULONG* Count)
HRESULT WSLASession::GetVirtualMachine(IWSLAVirtualMachine** VirtualMachine)
{
THROW_IF_FAILED(m_virtualMachine.QueryInterface(__uuidof(IWSLAVirtualMachine), (void**)VirtualMachine));
std::lock_guard lock{m_lock};
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine.has_value());
THROW_IF_FAILED(m_virtualMachine->QueryInterface(__uuidof(IWSLAVirtualMachine), (void**)VirtualMachine));
return S_OK;
}
HRESULT WSLASession::CreateRootNamespaceProcess(const WSLA_PROCESS_OPTIONS* Options, IWSLAProcess** VirtualMachine)
HRESULT WSLASession::CreateRootNamespaceProcess(const WSLA_PROCESS_OPTIONS* Options, IWSLAProcess** Process, int* Errno)
try
{
return E_NOTIMPL;
if (Errno != nullptr)
{
*Errno = -1; // Make sure not to return 0 if something fails.
}
std::lock_guard lock{m_lock};
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine.has_value());
return m_virtualMachine->CreateLinuxProcess(Options, Process, Errno);
}
CATCH_RETURN();
HRESULT WSLASession::FormatVirtualDisk(LPCWSTR Path)
{
return E_NOTIMPL;
}
HRESULT WSLASession::Shutdown(ULONG Timeout)
try
{
std::lock_guard lock{m_lock};
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine.has_value());
THROW_IF_FAILED(m_virtualMachine->Shutdown(Timeout));
m_virtualMachine.reset();
return S_OK;
}
CATCH_RETURN();

View File

@ -40,16 +40,19 @@ public:
// VM management.
IFACEMETHOD(GetVirtualMachine)(IWSLAVirtualMachine** VirtualMachine) override;
IFACEMETHOD(CreateRootNamespaceProcess)(_In_ const WSLA_PROCESS_OPTIONS* Options, _Out_ IWSLAProcess** VirtualMachine) override;
IFACEMETHOD(CreateRootNamespaceProcess)(_In_ const WSLA_PROCESS_OPTIONS* Options, _Out_ IWSLAProcess** VirtualMachine, _Out_ int* Errno) override;
// Disk management.
IFACEMETHOD(FormatVirtualDisk)(_In_ LPCWSTR Path) override;
IFACEMETHOD(Shutdown(_In_ ULONG)) override;
private:
WSLA_SESSION_SETTINGS m_sessionSettings; // TODO: Revisit to see if we should have session settings as a member or not
WSLAUserSessionImpl& m_userSession;
WSLAVirtualMachine m_virtualMachine;
std::optional<WSLAVirtualMachine> m_virtualMachine;
std::wstring m_displayName;
std::mutex m_lock;
};
} // namespace wsl::windows::service::wsla

View File

@ -21,6 +21,7 @@ Abstract:
using namespace wsl::windows::common;
using helpers::WindowsBuildNumbers;
using helpers::WindowsVersion;
using wsl::windows::service::wsla::WSLAProcess;
using wsl::windows::service::wsla::WSLAVirtualMachine;
WSLAVirtualMachine::WSLAVirtualMachine(const VIRTUAL_MACHINE_SETTINGS& Settings, PSID UserSid, WSLAUserSessionImpl* Session) :
@ -101,6 +102,18 @@ WSLAVirtualMachine::~WSLAVirtualMachine()
}
CATCH_LOG()
}
if (m_processExitThread.joinable())
{
m_processExitThread.join();
}
// Clear the state of all remaining processes now that the VM has exited.
// The WSLAProcess object reference will be released when the last COM reference is closed.
for (auto& e : m_trackedProcesses)
{
e->OnVmTerminated();
}
}
void WSLAVirtualMachine::Start()
@ -314,8 +327,60 @@ void WSLAVirtualMachine::Start()
wsl::windows::common::hcs::ModifyComputeSystem(m_computeSystem.get(), wsl::shared::ToJsonW(gpuRequest).c_str());
}
auto [_, __, childChannel] = Fork(WSLA_FORK::Thread);
WSLA_WATCH_PROCESSES watchMessage{};
childChannel.SendMessage(watchMessage);
THROW_HR_IF(E_FAIL, childChannel.ReceiveMessage<RESULT_MESSAGE<uint32_t>>().Result != 0);
m_processExitThread = std::thread(std::bind(&WSLAVirtualMachine::WatchForExitedProcesses, this, std::move(childChannel)));
}
void WSLAVirtualMachine::WatchForExitedProcesses(wsl::shared::SocketChannel& Channel)
try
{
// TODO: Terminate the VM if this thread exits unexpectedly.
while (true)
{
auto [message, _] = Channel.ReceiveMessageOrClosed<WSLA_PROCESS_EXITED>();
if (message == nullptr)
{
break; // Channel has been closed, exit
}
WSL_LOG(
"ProcessExited",
TraceLoggingValue(message->Pid, "Pid"),
TraceLoggingValue(message->Code, "Code"),
TraceLoggingValue(message->Signaled, "Signaled"));
// Signal the exited process, if it's been monitored.
{
std::lock_guard lock{m_lock};
bool found = false;
for (auto& e : m_trackedProcesses)
{
if (e->GetPid() == message->Pid)
{
WI_ASSERT(!found);
try
{
e->OnTerminated(message->Signaled, message->Code);
}
CATCH_LOG();
found = true;
}
}
}
}
}
CATCH_LOG();
void WSLAVirtualMachine::ConfigureNetworking()
{
if (m_settings.NetworkingMode == WslNetworkingModeNone)
@ -325,8 +390,6 @@ void WSLAVirtualMachine::ConfigureNetworking()
else if (m_settings.NetworkingMode == WslNetworkingModeNAT)
{
// Launch GNS
// WSLA-TODO: Using fd=4 here seems to hang gns. There's probably a hardcoded file descriptor somewhere that's causing
// so using 1000 for now.
std::vector<WSLA_PROCESS_FD> fds(1);
fds[0].Fd = -1;
fds[0].Type = WslFdType::WslFdTypeDefault;
@ -340,21 +403,27 @@ void WSLAVirtualMachine::ConfigureNetworking()
THROW_IF_FAILED(wsl::core::networking::DnsResolver::LoadDnsResolverMethods());
}
WSLA_CREATE_PROCESS_OPTIONS options{};
WSLA_PROCESS_OPTIONS options{};
options.Executable = "/init";
options.Fds = fds.data();
options.FdsCount = static_cast<DWORD>(fds.size());
// Because the file descriptors numbers aren't known in advance, the command line needs to be generated after the file
// descriptors are allocated.
std::string socketFdArg;
std::string dnsFdArg;
int gnsChannelFd = -1;
int dnsChannelFd = -1;
auto prepareCommandLine = [&](const auto& sockets) {
socketFdArg = std::to_string(sockets[0].Fd);
gnsChannelFd = sockets[0].Fd;
socketFdArg = std::to_string(gnsChannelFd);
cmd.emplace_back(socketFdArg.c_str());
if (sockets.size() > 1)
{
dnsFdArg = std::to_string(sockets[1].Fd);
dnsChannelFd = sockets[1].Fd;
dnsFdArg = std::to_string(dnsChannelFd);
cmd.emplace_back(LX_INIT_GNS_DNS_SOCKET_ARG);
cmd.emplace_back(dnsFdArg.c_str());
cmd.emplace_back(LX_INIT_GNS_DNS_TUNNELING_IP);
@ -365,10 +434,7 @@ void WSLAVirtualMachine::ConfigureNetworking()
options.CommandLineCount = static_cast<DWORD>(cmd.size());
};
WSLA_CREATE_PROCESS_RESULT result{};
auto sockets = CreateLinuxProcessImpl(&options, static_cast<DWORD>(fds.size()), fds.data(), &result, prepareCommandLine);
THROW_HR_IF(E_FAIL, result.Errno != 0);
auto process = CreateLinuxProcessImpl(options, nullptr, prepareCommandLine);
// TODO: refactor this to avoid using wsl config
static wsl::core::Config config(nullptr);
@ -382,9 +448,9 @@ void WSLAVirtualMachine::ConfigureNetworking()
m_networkEngine = std::make_unique<wsl::core::NatNetworking>(
m_computeSystem.get(),
wsl::core::NatNetworking::CreateNetwork(config),
std::move(sockets[0].Socket),
std::move(process->GetSocket(gnsChannelFd)),
config,
sockets.size() > 1 ? std::move(sockets[1].Socket) : wil::unique_socket{});
dnsChannelFd != -1 ? std::move(process->GetSocket(dnsChannelFd)) : wil::unique_socket{});
m_networkEngine->Initialize();
@ -606,8 +672,8 @@ WSLAVirtualMachine::ConnectedSocket WSLAVirtualMachine::ConnectSocket(wsl::share
WSLA_ACCEPT message{};
message.Fd = Fd;
const auto& response = Channel.Transaction(message);
ConnectedSocket socket;
ConnectedSocket socket;
socket.Socket = wsl::windows::common::hvsocket::Connect(m_vmId, response.Result);
// If the FD was unspecified, read the Linux file descriptor from the guest.
@ -640,58 +706,53 @@ void WSLAVirtualMachine::OpenLinuxFile(wsl::shared::SocketChannel& Channel, cons
THROW_HR_IF_MSG(E_FAIL, result != 0, "Failed to open %hs (flags: %u), %i", Path, Flags, result);
}
HRESULT WSLAVirtualMachine::CreateLinuxProcess(
_In_ const WSLA_CREATE_PROCESS_OPTIONS* Options, ULONG FdCount, WSLA_PROCESS_FD* Fds, _Out_ ULONG* Handles, _Out_ WSLA_CREATE_PROCESS_RESULT* Result)
HRESULT WSLAVirtualMachine::CreateLinuxProcess(_In_ const WSLA_PROCESS_OPTIONS* Options, _Out_ IWSLAProcess** Process, _Out_ int* Errno)
try
{
auto sockets = CreateLinuxProcessImpl(Options, FdCount, Fds, Result);
for (size_t i = 0; i < sockets.size(); i++)
{
if (sockets[i].Socket)
{
Handles[i] = HandleToUlong(
wsl::windows::common::wslutil::DuplicateHandleToCallingProcess(reinterpret_cast<HANDLE>(sockets[i].Socket.get())));
}
}
CreateLinuxProcessImpl(*Options, Errno).CopyTo(Process);
return S_OK;
}
CATCH_RETURN();
std::vector<WSLAVirtualMachine::ConnectedSocket> WSLAVirtualMachine::CreateLinuxProcessImpl(
_In_ const WSLA_CREATE_PROCESS_OPTIONS* Options,
_In_ ULONG FdCount,
_In_ WSLA_PROCESS_FD* Fds,
_Out_ WSLA_CREATE_PROCESS_RESULT* Result,
const TPrepareCommandLine& PrepareCommandLine)
Microsoft::WRL::ComPtr<WSLAProcess> WSLAVirtualMachine::CreateLinuxProcessImpl(
_In_ const WSLA_PROCESS_OPTIONS& Options, int* Errno, const TPrepareCommandLine& PrepareCommandLine)
{
auto setErrno = [Errno](int Error) {
if (Errno != nullptr)
{
*Errno = Error;
}
};
// Check if this is a tty or not
const WSLA_PROCESS_FD* ttyInput = nullptr;
const WSLA_PROCESS_FD* ttyOutput = nullptr;
const WSLA_PROCESS_FD* ttyControl = nullptr;
auto interactiveTty = ParseTtyInformation(Fds, FdCount, &ttyInput, &ttyOutput, &ttyControl);
auto interactiveTty = ParseTtyInformation(Options.Fds, Options.FdsCount, &ttyInput, &ttyOutput, &ttyControl);
auto [pid, _, childChannel] = Fork(WSLA_FORK::Process);
std::vector<ConnectedSocket> sockets(FdCount);
for (size_t i = 0; i < FdCount; i++)
std::vector<WSLAVirtualMachine::ConnectedSocket> sockets;
for (size_t i = 0; i < Options.FdsCount; i++)
{
if (Fds[i].Type == WslFdTypeDefault || Fds[i].Type == WslFdTypeTerminalInput || Fds[i].Type == WslFdTypeTerminalOutput ||
Fds[i].Type == WslFdTypeTerminalControl)
if (Options.Fds[i].Type == WslFdTypeDefault || Options.Fds[i].Type == WslFdTypeTerminalInput ||
Options.Fds[i].Type == WslFdTypeTerminalOutput || Options.Fds[i].Type == WslFdTypeTerminalControl)
{
THROW_HR_IF_MSG(E_INVALIDARG, Fds[i].Path != nullptr, "Fd[%zu] has a non-null path but flags: %i", i, Fds[i].Type);
sockets[i] = ConnectSocket(childChannel, static_cast<int32_t>(Fds[i].Fd));
THROW_HR_IF_MSG(
E_INVALIDARG, Options.Fds[i].Path != nullptr, "Fd[%zu] has a non-null path but flags: %i", i, Options.Fds[i].Type);
sockets.emplace_back(ConnectSocket(childChannel, static_cast<int32_t>(Options.Fds[i].Fd)));
}
else
{
THROW_HR_IF_MSG(
E_INVALIDARG,
WI_IsAnyFlagSet(Fds[i].Type, WslFdTypeTerminalInput | WslFdTypeTerminalOutput | WslFdTypeTerminalControl),
WI_IsAnyFlagSet(Options.Fds[i].Type, WslFdTypeTerminalInput | WslFdTypeTerminalOutput | WslFdTypeTerminalControl),
"Invalid flags: %i",
Fds[i].Type);
Options.Fds[i].Type);
THROW_HR_IF_MSG(E_INVALIDARG, Fds[i].Path == nullptr, "Fd[%zu] has a null path but flags: %i", i, Fds[i].Type);
OpenLinuxFile(childChannel, Fds[i].Path, Fds[i].Type, Fds[i].Fd);
THROW_HR_IF_MSG(
E_INVALIDARG, Options.Fds[i].Path == nullptr, "Fd[%zu] has a null path but flags: %i", i, Options.Fds[i].Type);
OpenLinuxFile(childChannel, Options.Fds[i].Path, Options.Fds[i].Type, Options.Fds[i].Fd);
}
}
@ -699,10 +760,10 @@ std::vector<WSLAVirtualMachine::ConnectedSocket> WSLAVirtualMachine::CreateLinux
wsl::shared::MessageWriter<WSLA_EXEC> Message;
Message.WriteString(Message->ExecutableIndex, Options->Executable);
Message.WriteString(Message->CurrentDirectoryIndex, Options->CurrentDirectory ? Options->CurrentDirectory : "/");
Message.WriteStringArray(Message->CommandLineIndex, Options->CommandLine, Options->CommandLineCount);
Message.WriteStringArray(Message->EnvironmentIndex, Options->Environment, Options->EnvironmentCount);
Message.WriteString(Message->ExecutableIndex, Options.Executable);
Message.WriteString(Message->CurrentDirectoryIndex, Options.CurrentDirectory ? Options.CurrentDirectory : "/");
Message.WriteStringArray(Message->CommandLineIndex, Options.CommandLine, Options.CommandLineCount);
Message.WriteStringArray(Message->EnvironmentIndex, Options.Environment, Options.EnvironmentCount);
// If this is an interactive tty, we need a relay process
if (interactiveTty)
@ -718,16 +779,16 @@ std::vector<WSLAVirtualMachine::ConnectedSocket> WSLAVirtualMachine::CreateLinux
auto result = ExpectClosedChannelOrError(childChannel);
if (result != 0)
{
Result->Errno = result;
THROW_HR(E_FAIL);
setErrno(result);
THROW_HR_MSG(E_FAIL, "errno: %i", result);
}
grandChildChannel.SendMessage<WSLA_EXEC>(Message.Span());
result = ExpectClosedChannelOrError(grandChildChannel);
if (result != 0)
{
Result->Errno = result;
THROW_HR(E_FAIL);
setErrno(result);
THROW_HR_MSG(E_FAIL, "errno: %i", result);
}
pid = grandChildPid;
@ -738,14 +799,27 @@ std::vector<WSLAVirtualMachine::ConnectedSocket> WSLAVirtualMachine::CreateLinux
auto result = ExpectClosedChannelOrError(childChannel);
if (result != 0)
{
Result->Errno = result;
THROW_HR(E_FAIL);
setErrno(result);
THROW_HR_MSG(E_FAIL, "errno: %i", result);
}
}
Result->Errno = 0;
Result->Pid = pid;
return sockets;
std::map<int, wil::unique_socket> socketMap;
for (auto& [fd, socket] : sockets)
{
socketMap.emplace(fd, std::move(socket));
}
auto process = wil::MakeOrThrow<WSLAProcess>(std::move(socketMap), pid, this);
{
std::lock_guard lock{m_lock};
m_trackedProcesses.emplace_back(process.Get());
}
setErrno(0);
return process;
}
int32_t WSLAVirtualMachine::MountImpl(shared::SocketChannel& Channel, LPCSTR Source, LPCSTR Target, LPCSTR Type, LPCSTR Options, ULONG Flags)
@ -874,7 +948,6 @@ bool WSLAVirtualMachine::ParseTtyInformation(
if (Fds[i].Type == WslFdTypeTerminalInput)
{
THROW_HR_IF_MSG(E_INVALIDARG, *TtyInput != nullptr, "Only one TtyInput fd can be passed. Index=%lu", i);
*TtyInput = &Fds[i];
}
else if (Fds[i].Type == WslFdTypeTerminalOutput)
@ -894,7 +967,9 @@ bool WSLAVirtualMachine::ParseTtyInformation(
}
THROW_HR_IF_MSG(
E_INVALIDARG, foundNonTtyFd && (*TtyOutput != nullptr || *TtyInput != nullptr), "Found mixed tty & non tty fds");
E_INVALIDARG,
foundNonTtyFd && (*TtyOutput != nullptr || *TtyInput != nullptr || *TtyControl != nullptr),
"Found mixed tty & non tty fds");
return !foundNonTtyFd && FdCount > 0;
}
@ -1107,4 +1182,12 @@ try
RETURN_IF_FAILED(Mount("none", LibrariesMountPoint, "overlay", options.c_str(), Flags));
return S_OK;
}
CATCH_RETURN();
CATCH_RETURN();
void WSLAVirtualMachine::OnProcessReleased(int Pid)
{
std::lock_guard lock{m_lock};
auto erased = std::erase_if(m_trackedProcesses, [Pid](const auto* e) { return e->GetPid() == Pid; });
WI_VERIFY(erased <= 1);
}

View File

@ -17,6 +17,7 @@ Abstract:
#include "hcs.hpp"
#include "Dmesg.h"
#include "WSLAApi.h"
#include "WSLAProcess.h"
namespace wsl::windows::service::wsla {
@ -28,6 +29,8 @@ class DECLSPEC_UUID("0CFC5DC1-B6A7-45FC-8034-3FA9ED73CE30") WSLAVirtualMachine
{
public:
WSLAVirtualMachine(const VIRTUAL_MACHINE_SETTINGS& Settings, PSID Sid, WSLAUserSessionImpl* UserSession);
// TODO: Clear processes on exit
~WSLAVirtualMachine();
void Start();
@ -35,8 +38,7 @@ public:
IFACEMETHOD(AttachDisk(_In_ PCWSTR Path, _In_ BOOL ReadOnly, _Out_ LPSTR* Device, _Out_ ULONG* Lun)) override;
IFACEMETHOD(Mount(_In_ LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags)) override;
IFACEMETHOD(CreateLinuxProcess(
_In_ const WSLA_CREATE_PROCESS_OPTIONS* Options, _In_ ULONG FdCount, _In_ WSLA_PROCESS_FD* Fd, _Out_ ULONG* Handles, _Out_ WSLA_CREATE_PROCESS_RESULT* Result)) override;
IFACEMETHOD(CreateLinuxProcess(_In_ const WSLA_PROCESS_OPTIONS* Options, _Out_ IWSLAProcess** Process, _Out_ int* Errno)) override;
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;
@ -49,6 +51,8 @@ public:
IFACEMETHOD(UnmountWindowsFolder(_In_ LPCSTR LinuxPath)) override;
IFACEMETHOD(MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_ LPCSTR DriversMountpoint, _In_ DWORD Flags)) override;
void OnProcessReleased(int Pid);
private:
struct ConnectedSocket
{
@ -74,15 +78,13 @@ private:
static void OpenLinuxFile(wsl::shared::SocketChannel& Channel, const char* Path, uint32_t Flags, int32_t Fd);
void LaunchPortRelay();
std::vector<ConnectedSocket> CreateLinuxProcessImpl(
_In_ const WSLA_CREATE_PROCESS_OPTIONS* Options,
_In_ ULONG FdCount,
_In_ WSLA_PROCESS_FD* Fd,
_Out_ WSLA_CREATE_PROCESS_RESULT* Result,
const TPrepareCommandLine& PrepareCommandLine = [](const auto&) {});
Microsoft::WRL::ComPtr<WSLAProcess> CreateLinuxProcessImpl(
_In_ const WSLA_PROCESS_OPTIONS& Options, int* Errno = nullptr, const TPrepareCommandLine& PrepareCommandLine = [](const auto&) {});
HRESULT MountWindowsFolderImpl(_In_ LPCWSTR WindowsPath, _In_ LPCSTR LinuxPath, _In_ BOOL ReadOnly, _In_ WslMountFlags Flags);
void WatchForExitedProcesses(wsl::shared::SocketChannel& Channel);
struct AttachedDisk
{
std::filesystem::path Path;
@ -91,6 +93,8 @@ private:
};
VIRTUAL_MACHINE_SETTINGS m_settings;
std::thread m_processExitThread;
GUID m_vmId{};
std::wstring m_vmIdString;
wsl::windows::common::helpers::WindowsVersion m_windowsVersion = wsl::windows::common::helpers::GetWindowsVersion();
@ -98,6 +102,7 @@ private:
bool m_running = false;
PSID m_userSid{};
std::wstring m_debugShellPipe;
std::vector<WSLAProcess*> m_trackedProcesses;
wsl::windows::common::hcs::unique_hcs_system m_computeSystem;
std::shared_ptr<DmesgCollector> m_dmesgCollector;

View File

@ -27,16 +27,6 @@ struct _WSL_VERSION {
ULONG Revision;
} WSL_VERSION;
typedef
struct _WSLA_CREATE_PROCESS_OPTIONS { // TODO: Delete once the new API is wired.
[string] LPCSTR Executable;
ULONG CommandLineCount;
[unique, size_is(CommandLineCount)] LPCSTR* CommandLine;
ULONG EnvironmentCount;
[unique, size_is(EnvironmentCount)] LPCSTR* Environment;
[unique] LPCSTR CurrentDirectory;
} WSLA_CREATE_PROCESS_OPTIONS;
typedef struct _WSLA_PROCESS_FD
{
LONG Fd;
@ -70,54 +60,6 @@ interface IProgressCallback : IUnknown
HRESULT OnProgress(ULONG Progress, ULONG Total);
};
// TODO: Delete once the new API is wired.
[
uuid(82A7ABC8-6B50-43FC-AB96-15FBBE7E8761),
pointer_default(unique),
object
]
interface IWSLAVirtualMachine : IUnknown
{
HRESULT AttachDisk([in] LPCWSTR Path, [in] BOOL ReadOnly, [out] LPSTR* Device, [out] ULONG* Lun);
HRESULT Mount([in, unique] LPCSTR Source, [in] LPCSTR Target, [in] LPCSTR Type, [in] LPCSTR Options, [in] ULONG Flags);
HRESULT CreateLinuxProcess([in] const WSLA_CREATE_PROCESS_OPTIONS* Options, [in] ULONG FdCount, [in, unique, size_is(FdCount)] WSLA_PROCESS_FD* Fds, [out, size_is(FdCount)] ULONG* Handles, [out] WSLA_CREATE_PROCESS_RESULT* Result);
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 RegisterCallback([in] ITerminationCallback* terminationCallback);
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 DetachDisk([in] ULONG Lun);
HRESULT MountWindowsFolder([in] LPCWSTR WindowsPath, [in] LPCSTR LinuxPath, [in] BOOL ReadOnly);
HRESULT UnmountWindowsFolder([in] LPCSTR LinuxPath);
HRESULT MountGpuLibraries([in] LPCSTR LibrariesMountPoint, [in] LPCSTR DriversMountpoint, [in] DWORD Flags);
}
typedef
struct _VIRTUAL_MACHINE_SETTINGS { // TODO: Delete once the new API is wired.
LPCWSTR DisplayName;
ULONGLONG MemoryMb;
ULONG CpuCount;
ULONG BootTimeoutMs;
ULONG DmesgOutput;
ULONG NetworkingMode;
BOOL EnableDnsTunneling;
BOOL EnableDebugShell;
BOOL EnableEarlyBootDmesg;
BOOL EnableGPU;
} VIRTUAL_MACHINE_SETTINGS;
struct WSLA_SESSION_SETTINGS {
LPCWSTR DisplayName;
LPCWSTR StoragePath;
// TODO: Termination callback, flags
};
struct WSLA_REGISTRY_AUTHENTICATION_INFORMATION
{
int Dummy; // Dummy value for the .idl file to compile.
@ -141,7 +83,7 @@ struct WSLA_PROCESS_OPTIONS
ULONG CommandLineCount;
[unique, size_is(EnvironmentCount)] LPCSTR* Environment;
ULONG EnvironmentCount;
[unique, size_is(EnvironmentCount)] WSLA_PROCESS_FD *Fds;
[unique, size_is(FdsCount)] WSLA_PROCESS_FD *Fds;
int FdsCount;
};
@ -215,6 +157,55 @@ interface IWSLAProcess : IUnknown
// Note: the SDK can offer a convenience Wait() method, but that doesn't need to be part of the service API.
}
// TODO: Delete once the new API is wired.
[
uuid(82A7ABC8-6B50-43FC-AB96-15FBBE7E8761),
pointer_default(unique),
object
]
interface IWSLAVirtualMachine : IUnknown
{
HRESULT AttachDisk([in] LPCWSTR Path, [in] BOOL ReadOnly, [out] LPSTR* Device, [out] ULONG* Lun);
HRESULT Mount([in, unique] LPCSTR Source, [in] LPCSTR Target, [in] LPCSTR Type, [in] LPCSTR Options, [in] ULONG Flags);
HRESULT CreateLinuxProcess([in] const struct WSLA_PROCESS_OPTIONS* Options, [out] IWSLAProcess** Process, [out] int* Errno);
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 RegisterCallback([in] ITerminationCallback* terminationCallback);
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 DetachDisk([in] ULONG Lun);
HRESULT MountWindowsFolder([in] LPCWSTR WindowsPath, [in] LPCSTR LinuxPath, [in] BOOL ReadOnly);
HRESULT UnmountWindowsFolder([in] LPCSTR LinuxPath);
HRESULT MountGpuLibraries([in] LPCSTR LibrariesMountPoint, [in] LPCSTR DriversMountpoint, [in] DWORD Flags);
}
typedef
struct _VIRTUAL_MACHINE_SETTINGS { // TODO: Delete once the new API is wired.
LPCWSTR DisplayName;
ULONGLONG MemoryMb;
ULONG CpuCount;
ULONG BootTimeoutMs;
ULONG DmesgOutput;
ULONG NetworkingMode;
BOOL EnableDnsTunneling;
BOOL EnableDebugShell;
BOOL EnableEarlyBootDmesg;
BOOL EnableGPU;
} VIRTUAL_MACHINE_SETTINGS;
struct WSLA_SESSION_SETTINGS {
LPCWSTR DisplayName;
LPCWSTR StoragePath;
// TODO: Termination callback, flags
};
[
uuid(7577FE8D-DE85-471E-B870-11669986F332),
pointer_default(unique),
@ -227,7 +218,7 @@ interface IWSLAContainer : IUnknown
HRESULT Delete(); // TODO: Look into lifetime logic.
HRESULT GetState([out] enum WSLA_CONTAINER_STATE* State);
HRESULT GetInitProcess([out] IWSLAProcess** Process);
HRESULT Exec([in] const struct WSLA_PROCESS_OPTIONS* Options, [out] IWSLAProcess** Process);
HRESULT Exec([in] const struct WSLA_PROCESS_OPTIONS* Options, [out] IWSLAProcess** Process, [out] int* Errno);
// Anonymous host port allocation (P1).
//HRESULT AllocateHostPort([in] LPCSTR Name, [in] USHORT ContainerPort, [out] USHORT* AllocatedHostPort);
@ -253,13 +244,16 @@ interface IWSLASession : IUnknown
HRESULT ListContainers([out, size_is(, *Count)] struct WSLA_CONTAINER** Images, [out] ULONG* Count);
// Create a process at the VM level. This is meant for debugging.
HRESULT CreateRootNamespaceProcess([in] const struct WSLA_PROCESS_OPTIONS* Options, [out] IWSLAProcess** Process);
HRESULT CreateRootNamespaceProcess([in] const struct WSLA_PROCESS_OPTIONS* Options, [out] IWSLAProcess** Process, [out] int* Errno);
// TODO: an OpenProcess() method can be added later if needed.
// Disk management.
HRESULT FormatVirtualDisk([in] LPCWSTR Path);
// Terminate the VM and containers.
HRESULT Shutdown([in] ULONG TimeoutMs);
// To be deleted.
HRESULT GetDisplayName([out] LPWSTR* DisplayName);
HRESULT GetVirtualMachine([out] IWSLAVirtualMachine **VirtualMachine);

File diff suppressed because it is too large Load Diff