mirror of
https://github.com/microsoft/WSL.git
synced 2025-12-11 04:35:57 -06:00
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:
parent
5585e4210b
commit
c1a3e6174c
@ -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.
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
199
src/windows/common/WSLAProcessLauncher.cpp
Normal file
199
src/windows/common/WSLAProcessLauncher.cpp
Normal 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};
|
||||
}
|
||||
84
src/windows/common/WSLAProcessLauncher.h
Normal file
84
src/windows/common/WSLAProcessLauncher.h
Normal 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
|
||||
@ -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)
|
||||
|
||||
@ -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();
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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:
|
||||
};
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -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();
|
||||
@ -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
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user