Merge User/oneblue/prototype lsw to a feature/wsl-for-apps (#13278)

* Save state

* Save state

* Save state

* Get the VM booting

* VM booting

* Disk mounting

* CreateLinuxProcess

* Move to a proper API

* Implement env

* Progress on fd

* Redesign fork model

* Add process wait & signal

* Include nuget package

* Format

* Format

* Format

* Cleanup

* Format

* Format

* Format

* Fix nuspec

* Implement VM termination

* Add lsw dll

* Implement termination callbacks

* Save state

* Various fixes in API header

* Save state

* Test coverage

* Don't block all signals by default

* Writeable overlay

* Add struct keyword

* rename WslCreateVirualMachine -> WslCreateVirtualMachine

* rename Environmnent -> Environment

* rename HandleToUlong -> HandleToULong

* ensure correct amount of memory is used to create the LSW VM

* Adjust LSWVirtualMachine::AttachDisk so it does not require caller to have elevated permission

* Add missing struct keyword

* PR feedback

* PR review

---------

Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>
This commit is contained in:
Blue 2025-07-24 12:47:49 -07:00 committed by Blue
parent 95fcc56983
commit b5769b4f97
42 changed files with 3149 additions and 30 deletions

2
.gitignore vendored
View File

@ -43,7 +43,7 @@ bin/
*.nupkg
build/
generated/
Microsoft.WSL.PluginApi.nuspec
*.nuspec
test/linux/unit_tests/wsl_unit_tests
*.dir/
UserConfig.cmake

View File

@ -15,6 +15,7 @@ parameters:
type: object
default:
- Microsoft.WSL.PluginApi.nuspec
- Microsoft.WSL.Api.nuspec
- name: traceLoggingConfig
type: string
@ -376,6 +377,7 @@ stages:
Move-Item -Path "bin\$arch\release\wsltests.dll" -Destination "$(ob_outputDirectory)\testbin\$arch\release\wsltests.dll"
Move-Item -Path "bin\$arch\release\testplugin.dll" -Destination "$(ob_outputDirectory)\testbin\$arch\release\testplugin.dll"
Move-Item -Path "bin\$arch\release\lswclient.dll" -Destination "$(ob_outputDirectory)\testbin\$arch\release\lswclient.dll"
Move-Item -Path "packages\Microsoft.Taef.$taefVersion\build\Binaries\$arch" -Destination "$(ob_outputDirectory)\testbin\$arch\release\taef"
}

View File

@ -140,6 +140,7 @@ endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/${TARGET_PLATFORM})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
@ -421,6 +422,7 @@ add_subdirectory(src/windows/wslg)
add_subdirectory(src/windows/wslhost)
add_subdirectory(src/windows/wslrelay)
add_subdirectory(src/windows/wslinstall)
add_subdirectory(src/windows/lswclient)
if (WSL_BUILD_WSL_SETTINGS)
add_subdirectory(src/windows/libwsl)

View File

@ -131,6 +131,27 @@
</RegistryKey>
</RegistryKey>
<RegistryKey Root="HKCR" Key="Interface\{82A7ABC8-6B50-43FC-AB96-15FBBE7E8760}">
<RegistryValue Value="ILSWUserSession" Type="string" />
<RegistryKey Key="ProxyStubClsid32">
<RegistryValue Value="{4EA0C6DD-E9FF-48E7-994E-13A31D10DC60}" Type="string" />
</RegistryKey>
</RegistryKey>
<RegistryKey Root="HKCR" Key="Interface\{82A7ABC8-6B50-43FC-AB96-15FBBE7E8761}">
<RegistryValue Value="ILSWVirtualMachine" Type="string" />
<RegistryKey Key="ProxyStubClsid32">
<RegistryValue Value="{4EA0C6DD-E9FF-48E7-994E-13A31D10DC60}" Type="string" />
</RegistryKey>
</RegistryKey>
<RegistryKey Root="HKCR" Key="Interface\{7BC4E198-6531-4FA6-ADE2-5EF3D2A04DFE}">
<RegistryValue Value="ITerminationCallback" Type="string" />
<RegistryKey Key="ProxyStubClsid32">
<RegistryValue Value="{4EA0C6DD-E9FF-48E7-994E-13A31D10DC60}" Type="string" />
</RegistryKey>
</RegistryKey>
<RegistryKey Root="HKCR" Key="CLSID\{4EA0C6DD-E9FF-48E7-994E-13A31D10DC60}">
<RegistryValue Value="PSFactoryBuffer" Type="string" />
</RegistryKey>
@ -154,6 +175,18 @@
<RegistryValue Value="LxssUserSession" Type="string" />
</RegistryKey>
<!-- LSWUserSession -->
<RegistryKey Root="HKCR" Key="CLSID\{a9b7a1b9-0671-405c-95f1-e0612cb4ce8f}">
<RegistryValue Name="AppId" Value="{370121D2-AA7E-4608-A86D-0BBAB9DA1A60}" Type="string" />
<RegistryValue Value="LSWUserSession" Type="string" />
</RegistryKey>
<!-- LSWVirtualMachine -->
<RegistryKey Root="HKCR" Key="CLSID\{0CFC5DC1-B6A7-45FC-8034-3FA9ED73CE30}">
<RegistryValue Name="AppId" Value="{370121D2-AA7E-4608-A86D-0BBAB9DA1A60}" Type="string" />
<RegistryValue Value="LSWVirtualMachine" Type="string" />
</RegistryKey>
<!-- Notification server -->
<RegistryKey Root="HKCR" Key="CLSID\{2B9C59C3-98F1-45C8-B87B-12AE3C7927E8}\LocalServer32">
<RegistryValue Value='"[INSTALLDIR]wslhost.exe"' Type="string" />

View File

@ -1,4 +1,4 @@
set(NUGET_PACKAGES Microsoft.WSL.PluginApi.nuspec)
set(NUGET_PACKAGES Microsoft.WSL.PluginApi.nuspec Microsoft.WSL.Api.nuspec)
# generate vars with native paths since nuget won't accept unix path separators
cmake_path(NATIVE_PATH CMAKE_SOURCE_DIR CMAKE_SOURCE_DIR_NATIVE)

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>Microsoft.WSL.Api</id>
<version>${WSL_NUGET_PACKAGE_VERSION}</version>
<authors>Microsoft</authors>
<projectUrl>https://github.com/microsoft/WSL</projectUrl>
<description>WSL API Interface</description>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<tags>WSL</tags>
<language>en-us</language>
<license type="expression">MIT</license>
</metadata>
<files>
<file src="${CMAKE_SOURCE_DIR_NATIVE}\src\windows\lswclient\LSWApi.h" target="include"/>
<file src="${CMAKE_SOURCE_DIR_NATIVE}\bin\x64\Release\lswclient.lib" target="lib\x64"/>
<file src="${CMAKE_SOURCE_DIR_NATIVE}\bin\x64\Release\lswclient.dll" target="lib\x64"/>
<file src="${CMAKE_SOURCE_DIR_NATIVE}\bin\arm64\Release\lswclient.lib" target="lib\arm64"/>
<file src="${CMAKE_SOURCE_DIR_NATIVE}\bin\arm64\Release\lswclient.dll" target="lib\arm64"/>
</files>
</package>

View File

@ -373,6 +373,11 @@ public:
return fd;
}
int* addressof() noexcept
{
return &m_Fd;
}
friend void swap(unique_fd& fd1, unique_fd& fd2)
{
std::swap(fd1.m_Fd, fd2.m_Fd);

View File

@ -20,7 +20,8 @@ set(SOURCES
util.cpp
WslDistributionConfig.cpp
wslinfo.cpp
wslpath.cpp)
wslpath.cpp
LSWInit.cpp)
set(HEADERS
../inc/lxwil.h

View File

@ -331,6 +331,7 @@ void GnsEngine::ProcessDNSChange(Interface& interface, const wsl::shared::hns::D
content.str().c_str(),
interface.Name().c_str());
THROW_LAST_ERROR_IF(UtilMkdirPath("/etc", 0755) < 0);
std::wofstream resolvConf;
resolvConf.exceptions(std::ofstream::badbit | std::ofstream::failbit);
resolvConf.open("/etc/resolv.conf", std::ofstream::trunc);

690
src/linux/init/LSWInit.cpp Normal file
View File

@ -0,0 +1,690 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LSWInit.cpp
Abstract:
TODO
--*/
#include "util.h"
#include "SocketChannel.h"
#include "message.h"
#include <utmp.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/epoll.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pty.h>
#include "mountutilcpp.h"
#include <filesystem>
extern int InitializeLogging(bool SetStderr, wil::LogFunction* ExceptionCallback) noexcept;
extern std::set<pid_t> ListInitChildProcesses();
extern std::vector<unsigned int> ListScsiDisks();
extern int DetachScsiDisk(unsigned int Lun);
extern std::string GetLunDeviceName(unsigned int Lun);
void ProcessMessages(wsl::shared::SocketChannel& Channel);
int MountInit(const char* Target);
extern int EnableInterface(int Socket, const char* Name);
extern int g_LogFd;
void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const LSW_GET_DISK& Message, const gsl::span<gsl::byte>& Buffer)
{
wsl::shared::MessageWriter<LSW_GET_DISK_RESULT> writer;
try
{
auto deviceName = GetLunDeviceName(Message.ScsiLun);
writer->Result = 0;
writer.WriteString("/dev/" + deviceName);
}
catch (...)
{
writer->Result = wil::ResultFromCaughtException();
}
Channel.SendMessage<LSW_GET_DISK::TResponse>(writer.Span());
}
void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const LSW_CONNECT& Message, const gsl::span<gsl::byte>& Buffer)
{
sockaddr_vm SocketAddress{};
wil::unique_fd ListenSocket{UtilListenVsockAnyPort(&SocketAddress, 1, false)};
THROW_LAST_ERROR_IF(!ListenSocket);
Channel.SendResultMessage<uint32_t>(SocketAddress.svm_port);
wil::unique_fd Socket{UtilAcceptVsock(ListenSocket.get(), SocketAddress, SESSION_LEADER_ACCEPT_TIMEOUT_MS)};
THROW_LAST_ERROR_IF(!Socket);
THROW_LAST_ERROR_IF(dup2(Socket.get(), Message.Fd) < 0);
}
void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const LSW_TTY_RELAY& Message, const gsl::span<gsl::byte>&)
{
THROW_LAST_ERROR_IF(fcntl(Message.TtyMaster, F_SETFL, O_NONBLOCK) < 0);
pollfd pollDescriptors[2];
pollDescriptors[0].fd = Message.TtyInput;
pollDescriptors[0].events = POLLIN;
pollDescriptors[1].fd = Message.TtyMaster;
pollDescriptors[1].events = POLLIN;
std::vector<gsl::byte> pendingStdin;
std::vector<gsl::byte> buffer;
Channel.Close();
while (true)
{
int bytesWritten = 0;
auto result = poll(pollDescriptors, COUNT_OF(pollDescriptors), pendingStdin.empty() ? -1 : 100);
if (!pendingStdin.empty())
{
bytesWritten = write(Message.TtyMaster, pendingStdin.data(), pendingStdin.size());
if (bytesWritten < 0)
{
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
LOG_ERROR("delayed stdin write failed {}", errno);
}
}
else
{
if (bytesWritten <= pendingStdin.size()) // Partial or complete write
{
pendingStdin.erase(pendingStdin.begin(), pendingStdin.begin() + bytesWritten);
}
else
{
LOG_ERROR("Unexpected write result {}, pending={}", bytesWritten, pendingStdin.size());
}
}
}
if (result < 0)
{
LOG_ERROR("poll failed {}", errno);
break;
}
// Relay stdin.
if (pollDescriptors[0].revents & (POLLIN | POLLHUP | POLLERR) && pendingStdin.empty())
{
auto bytesRead = UtilReadBuffer(pollDescriptors[0].fd, buffer);
if (bytesRead < 0)
{
LOG_ERROR("read failed {}", errno);
break;
}
else if (bytesRead == 0)
{ // Stdin has been closed.
pollDescriptors[0].fd = -1;
CLOSE(Message.TtyMaster);
}
else
{
bytesWritten = write(Message.TtyMaster, buffer.data(), bytesRead);
if (bytesWritten < 0)
{
//
// If writing on stdin's pipe would block, mark the write as pending an continue.
// This is required blocking on the write() could lead to a deadlock if the child process
// is blocking trying to write on stderr / stdout while the relay tries to write stdin.
//
if (errno == EWOULDBLOCK || errno == EAGAIN)
{
assert(pendingStdin.empty());
pendingStdin.assign(buffer.begin(), buffer.begin() + bytesRead);
}
else
{
LOG_ERROR("write failed {}", errno);
break;
}
}
}
}
// Relay stdout & stderr
if (pollDescriptors[1].revents & (POLLIN | POLLHUP | POLLERR))
{
auto bytesRead = UtilReadBuffer(pollDescriptors[1].fd, buffer);
if (bytesRead <= 0)
{
if (bytesRead < 0 && errno != EIO)
{
LOG_ERROR("read failed {} {}", bytesRead, errno);
}
// The tty has been closed, stop relaying.
CLOSE(pollDescriptors[1].fd);
pollDescriptors[1].fd = -1;
break;
}
bytesWritten = UtilWriteBuffer(Message.TtyOutput, buffer.data(), bytesRead);
if (bytesWritten < 0)
{
LOG_ERROR("write failed {}", errno);
CLOSE(pollDescriptors[1].fd);
pollDescriptors[1].fd = -1;
}
}
}
// Shutdown sockets and tty
UtilSocketShutdown(Message.TtyInput, SHUT_WR);
UtilSocketShutdown(Message.TtyOutput, SHUT_WR);
}
void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const LSW_FORK& Message, const gsl::span<gsl::byte>& Buffer)
{
sockaddr_vm SocketAddress{};
wil::unique_fd ListenSocket{UtilListenVsockAnyPort(&SocketAddress, 1, true)};
THROW_LAST_ERROR_IF(!ListenSocket);
LSW_FORK_RESULT Response{};
Response.Header.MessageSize = sizeof(Response);
Response.Header.MessageType = LSW_FORK_RESULT::Type;
Response.Port = SocketAddress.svm_port;
std::promise<pid_t> childPid;
{
auto childLogic = [ListenSocket = std::move(ListenSocket), &SocketAddress, &Channel, &Message, &childPid]() mutable {
// Close parent channel
if (Message.ForkType == LSW_FORK::Process || Message.ForkType == LSW_FORK::Pty)
{
Channel.Close();
}
childPid.set_value(getpid());
wil::unique_fd ProcessSocket{UtilAcceptVsock(ListenSocket.get(), SocketAddress, SESSION_LEADER_ACCEPT_TIMEOUT_MS)};
THROW_LAST_ERROR_IF(!ProcessSocket);
ListenSocket.reset();
auto subChannel = wsl::shared::SocketChannel{std::move(ProcessSocket), "ForkedChannel"};
ProcessMessages(subChannel);
};
if (Message.ForkType == LSW_FORK::Thread)
{
std::thread thread{std::move(childLogic)};
thread.detach();
Response.Pid = childPid.get_future().get();
}
else if (Message.ForkType == LSW_FORK::Process)
{
Response.Pid = UtilCreateChildProcess("CreateChildProcess", std::move(childLogic));
}
else if (Message.ForkType == LSW_FORK::Pty)
{
THROW_LAST_ERROR_IF(prctl(PR_SET_CHILD_SUBREAPER, 1) < 0);
winsize ttySize{};
ttySize.ws_col = Message.TtyColumns;
ttySize.ws_row = Message.TtyRows;
wil::unique_fd ttyMaster;
auto result = forkpty(ttyMaster.addressof(), nullptr, nullptr, &ttySize);
THROW_ERRNO_IF(errno, result < 0);
if (result == 0) // Child
{
sigset_t SignalMask;
sigemptyset(&SignalMask);
try
{
childLogic();
}
CATCH_LOG();
exit(0);
}
Response.PtyMasterFd = ttyMaster.release();
Response.Pid = result;
}
else
{
LOG_ERROR("Unexpected fork type: {}", Message.Type);
THROW_ERRNO(EINVAL);
}
}
ListenSocket.reset();
Channel.SendMessage(Response);
}
void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const LSW_MOUNT& Message, const gsl::span<gsl::byte>& Buffer)
{
LSW_MOUNT_RESULT response{};
response.Header.MessageType = LSW_MOUNT_RESULT::Type;
response.Header.MessageSize = sizeof(response);
try
{
auto readField = [&](unsigned int index) -> const char* {
if (index > 0)
{
return wsl::shared::string::FromSpan(Buffer, index);
}
return "";
};
mountutil::ParsedOptions options;
if (Message.OptionsIndex > 0)
{
options = mountutil::MountParseFlags(wsl::shared::string::FromSpan(Buffer, Message.OptionsIndex));
}
const char* source = readField(Message.SourceIndex);
const char* target = readField(Message.DestinationIndex);
THROW_LAST_ERROR_IF(UtilMount(source, target, readField(Message.TypeIndex), options.MountFlags, options.StringOptions.c_str()) < 0);
std::optional<std::string> overlayTarget;
if (WI_IsFlagSet(Message.Flags, LSW_MOUNT::OverlayFs))
{
overlayTarget.emplace(target + std::string("-rw"));
THROW_LAST_ERROR_IF(UtilMountOverlayFs(overlayTarget->c_str(), target));
target = overlayTarget->c_str();
THROW_LAST_ERROR_IF(MountInit((overlayTarget.value() + "/wsl-init").c_str()) < 0); // Required to call /gns later
// If it exists, mount /etc/resolv.conf
if (std::filesystem::exists("/etc/resolv.conf"))
{
THROW_LAST_ERROR_IF(UtilMountFile("/etc/resolv.conf", (overlayTarget.value() + "/etc/resolv.conf").c_str()) < 0);
}
}
if (WI_IsFlagSet(Message.Flags, LSW_MOUNT::Chroot))
{
THROW_LAST_ERROR_IF(chdir(target));
THROW_LAST_ERROR_IF(chroot("."));
}
response.Result = 0;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
response.Result = wil::ResultFromCaughtException();
}
Channel.SendMessage<LSW_MOUNT_RESULT>(response);
}
void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const LSW_EXEC& Message, const gsl::span<gsl::byte>& Buffer)
{
auto Executable = wsl::shared::string::FromSpan(Buffer, Message.ExecutableIndex);
auto ArgumentArray = wsl::shared::string::ArrayFromSpan(Buffer, Message.CommandLineIndex);
ArgumentArray.push_back(nullptr);
auto EnvironmentArray = wsl::shared::string::ArrayFromSpan(Buffer, Message.EnvironmentIndex);
EnvironmentArray.push_back(nullptr);
execve(Executable, (char* const*)(ArgumentArray.data()), (char* const*)(EnvironmentArray.data()));
// Only reached if exec() fails
Channel.SendResultMessage<int32_t>(errno);
}
void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const LSW_WAITPID& Message, const gsl::span<gsl::byte>& Buffer)
{
LSW_WAITPID_RESULT response{};
response.State = LSWProcessStateUnknown;
auto sendResponse = wil::scope_exit([&]() { Channel.SendMessage(response); });
wil::unique_fd process = syscall(SYS_pidfd_open, Message.Pid, 0);
if (!process)
{
LOG_ERROR("pidfd_open({}) failed, {}", Message.Pid, errno);
response.Errno = errno;
return;
}
pollfd pollResult{};
pollResult.fd = process.get();
pollResult.events = POLLIN | POLLERR;
int result = poll(&pollResult, 1, Message.TimeoutMs);
if (result < 0)
{
LOG_ERROR("poll failed {}", errno);
response.Errno = errno;
return;
}
else if (result == 0) // Timed out
{
response.State = LSWProcessStateRunning;
response.Errno = 0;
return;
}
if (WI_IsFlagSet(pollResult.revents, POLLIN))
{
siginfo_t childState{};
auto result = waitid(P_PIDFD, process.get(), &childState, WEXITED);
if (result < 0)
{
LOG_ERROR("waitid({}) failed, {}", process.get(), errno);
response.Errno = errno;
return;
}
response.Code = childState.si_status;
response.Errno = 0;
response.State = childState.si_code == CLD_EXITED ? LSWProcessStateExited : LSWProcessStateSignaled;
return;
}
LOG_ERROR("Poll returned an unexpected error state on fd: {} for pid: {}", process.get(), Message.Pid);
}
void HandleMessageImpl(wsl::shared::SocketChannel& Channel, const LSW_SIGNAL& Message, const gsl::span<gsl::byte>& Buffer)
{
auto result = kill(Message.Pid, Message.Signal);
Channel.SendResultMessage(result < 0 ? errno : 0);
}
template <typename TMessage, typename... Args>
void HandleMessage(wsl::shared::SocketChannel& Channel, LX_MESSAGE_TYPE Type, const gsl::span<gsl::byte>& Buffer)
{
if (TMessage::Type == Type)
{
if (Buffer.size() < sizeof(TMessage))
{
LOG_ERROR("Received message {}, but size is too small: {}. Expected {}", Type, Buffer.size(), sizeof(TMessage));
THROW_ERRNO(EINVAL);
}
const auto Message = gslhelpers::try_get_struct<TMessage>(Buffer);
HandleMessageImpl(Channel, *Message, Buffer);
return;
}
else
{
if constexpr (sizeof...(Args) > 0)
{
HandleMessage<Args...>(Channel, Type, Buffer);
}
else
{
LOG_ERROR("Received unknown message type: {}", Type);
THROW_ERRNO(EINVAL);
}
}
}
void ProcessMessage(wsl::shared::SocketChannel& Channel, LX_MESSAGE_TYPE Type, const gsl::span<gsl::byte>& Buffer)
{
try
{
HandleMessage<LSW_GET_DISK, LSW_MOUNT, LSW_EXEC, LSW_FORK, LSW_CONNECT, LSW_WAITPID, LSW_SIGNAL, LSW_TTY_RELAY>(Channel, Type, Buffer);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
// TODO: error message
}
}
void ProcessMessages(wsl::shared::SocketChannel& Channel)
{
while (Channel.Connected())
{
auto [Message, Range] = Channel.ReceiveMessageOrClosed<MESSAGE_HEADER>();
if (Message == nullptr || Message->MessageType == LxMessageLswShutdown)
{
break;
}
ProcessMessage(Channel, Message->MessageType, Range);
}
LOG_INFO("Process {} exiting", getpid());
}
int LswEntryPoint(int Argc, char* Argv[])
{
//
// Mount devtmpfs.
//
int Result = UtilMount(nullptr, "/dev", "devtmpfs", 0, nullptr);
if (Result < 0)
{
FATAL_ERROR("Failed to mount /dev");
}
if (UtilMount(nullptr, "/proc", "proc", 0, nullptr) < 0)
{
return -1;
}
if (UtilMount(nullptr, "/sys", "sysfs", 0, nullptr) < 0)
{
return -1;
}
//
// Open kmesg for logging and ensure that the file descriptor is not set to one of the standard file descriptors.
//
// N.B. This is to work around a rare race condition where init is launched without /dev/console set as the controlling terminal.
//
InitializeLogging(false);
if (g_LogFd <= STDERR_FILENO)
{
LOG_ERROR("/init was started without /dev/console");
if (dup2(g_LogFd, 3) < 0)
{
LOG_ERROR("dup2 failed {}", errno);
}
close(g_LogFd);
g_LogFd = 3;
}
rlimit Limit{};
Limit.rlim_cur = 0x4000000;
Limit.rlim_max = 0x4000000;
if (setrlimit(RLIMIT_MEMLOCK, &Limit) < 0)
{
LOG_ERROR("setrlimit(RLIMIT_MEMLOCK) failed {}", errno);
return -1;
}
//
// Enable logging when processes receive fatal signals.
//
if (WriteToFile("/proc/sys/kernel/print-fatal-signals", "1\n") < 0)
{
return -1;
}
if (WriteToFile("/proc/sys/kernel/print-fatal-signals", "0\n") < 0)
{
return -1;
}
//
// Disable rate limiting of user writes to dmesg.
//
if (WriteToFile("/proc/sys/kernel/printk_devkmsg", "on\n") < 0)
{
return -1;
}
THROW_LAST_ERROR_IF(UtilSetSignalHandlers(g_SavedSignalActions, false) < 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.
//
wil::unique_fd ConsoleFd{};
try
{
wsl::shared::retry::RetryWithTimeout<void>(
[&]() {
ConsoleFd = open("/dev/console", O_RDWR);
THROW_LAST_ERROR_IF(!ConsoleFd);
},
c_defaultRetryPeriod,
c_defaultRetryTimeout);
THROW_LAST_ERROR_IF(login_tty(ConsoleFd.get()) < 0);
}
catch (...)
{
if (dup2(g_LogFd, STDOUT_FILENO) < 0)
{
LOG_ERROR("dup2 failed {}", errno);
}
if (dup2(g_LogFd, STDERR_FILENO) < 0)
{
LOG_ERROR("dup2 failed {}", errno);
}
}
//
// Open /dev/null for stdin.
//
{
wil::unique_fd Fd{TEMP_FAILURE_RETRY(open("/dev/null", O_RDONLY))};
if (!Fd)
{
LOG_ERROR("open({}) failed {}", "/dev/null", errno);
return -1;
}
if (Fd.get() == STDIN_FILENO)
{
Fd.release();
}
else
{
if (TEMP_FAILURE_RETRY(dup2(Fd.get(), STDIN_FILENO)) < 0)
{
LOG_ERROR("dup2 failed {}", errno);
return -1;
}
}
}
//
// Enable the loopback interface.
//
wil::unique_fd Fd{socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)};
if (!Fd)
{
LOG_ERROR("socket failed {}", errno);
return -1;
}
if (EnableInterface(Fd.get(), "lo") < 0)
{
return -1;
}
//
// Establish the message channel with the service via hvsocket.
//
wsl::shared::SocketChannel channel = {UtilConnectVsock(LX_INIT_UTILITY_VM_INIT_PORT, true), "mini_init"};
if (channel.Socket() < 0)
{
FATAL_ERROR("Failed to connect to host hvsocket");
}
try
{
ProcessMessages(channel);
}
CATCH_LOG();
LOG_INFO("Init exiting");
try
{
auto children = ListInitChildProcesses();
while (!children.empty())
{
// send SIGKILL to all running processes.
for (auto pid : children)
{
if (kill(pid, SIGKILL) < 0)
{
LOG_ERROR("Failed to send SIGKILL to {}: {}", pid, errno);
}
}
// Wait for processes to actually exit.
while (!children.empty())
{
auto Result = waitpid(-1, nullptr, 0);
THROW_ERRNO_IF(errno, Result <= 0);
LOG_INFO("Process {} exited", Result);
children.erase(Result);
}
children = ListInitChildProcesses();
}
}
CATCH_LOG();
sync();
try
{
for (auto disk : ListScsiDisks())
{
if (DetachScsiDisk(disk) < 0)
{
LOG_ERROR("Failed to detach disk: {}", disk);
}
}
}
CATCH_LOG();
reboot(RB_POWER_OFF);
return 0;
}

View File

@ -3875,6 +3875,7 @@ Return Value:
int WslEntryPoint(int Argc, char* Argv[]);
extern int LswEntryPoint(int Argc, char* Argv[]);
int main(int Argc, char* Argv[])
{
std::vector<gsl::byte> Buffer;
@ -3893,6 +3894,16 @@ int main(int Argc, char* Argv[])
// Determine which entrypoint should be used.
//
if (getenv(LSW_ROOT_INIT_ENV))
{
if (unsetenv(LSW_ROOT_INIT_ENV))
{
LOG_ERROR("unsetenv failed {}", errno);
}
return LswEntryPoint(Argc, Argv);
}
if (getpid() != 1 || !getenv(WSL_ROOT_INIT_ENV))
{
return WslEntryPoint(Argc, Argv);

View File

@ -1663,6 +1663,19 @@ Return Value:
return 0;
}
int UtilMountFile(const char* Source, const char* Destination)
try
{
wil::unique_fd Fd{open(Destination, (O_CREAT | O_WRONLY), 0755)};
THROW_LAST_ERROR_IF(!Fd);
THROW_LAST_ERROR_IF(mount(Source, Destination, nullptr, (MS_RDONLY | MS_BIND), nullptr) < 0);
THROW_LAST_ERROR_IF(mount(nullptr, Destination, nullptr, (MS_RDONLY | MS_REMOUNT | MS_BIND), nullptr) < 0);
return 0;
}
CATCH_RETURN_ERRNO();
int UtilMount(const char* Source, const char* Target, const char* Type, unsigned long MountFlags, const char* Options, std::optional<std::chrono::seconds> TimeoutSeconds)
/*++

View File

@ -245,6 +245,8 @@ int UtilMkdir(const char* Path, mode_t Mode);
int UtilMkdirPath(const char* Path, mode_t Mode, bool SkipLast = false);
int UtilMountFile(const char* Source, const char* Destination);
int UtilMount(const char* Source, const char* Target, const char* Type, unsigned long MountFlags, const char* Options, std::optional<std::chrono::seconds> TimeoutSeconds = {});
int UtilMountOverlayFs(const char* Target, const char* Lower, unsigned long MountFlags = 0, std::optional<std::chrono::seconds> TimeoutSeconds = {});

View File

@ -279,6 +279,15 @@ public:
return Transaction<TSentMessage>(gslhelpers::struct_as_writeable_bytes(message), responseSpan, timeout);
}
template <typename TSentMessage>
TSentMessage::TResponse& Transaction()
{
TSentMessage message{};
message.Header.MessageSize = sizeof(message);
message.Header.MessageType = TSentMessage::Type;
return Transaction<TSentMessage>(message);
}
void Close()
{
m_socket.reset();
@ -289,6 +298,11 @@ public:
return m_socket.get();
}
bool Connected() const
{
return m_socket.get() >= 0;
}
void IgnoreSequenceNumbers()
{
m_ignore_sequence = true;

View File

@ -187,6 +187,8 @@ Abstract:
#define WSL_ROOT_INIT_ENV "WSL_ROOT_INIT"
#define LSW_ROOT_INIT_ENV "LSW_ROOT_INIT"
#define WSL_SOCKET_LOG_ENV "WSL_SOCKET_LOG"
#define WSL_ENABLE_CRASH_DUMP_ENV "WSL_ENABLE_CRASH_DUMP"
@ -272,6 +274,13 @@ Abstract:
#define INIT_NETLINK_FD_ARG "--netlink-fd"
#define INIT_PORT_TRACKER_LOCALHOST_RELAY "--localhost-relay"
#define DECLARE_MESSAGE_CTOR(Name) \
Name() \
{ \
Header.MessageSize = sizeof(Name); \
Header.MessageType = Name::Type; \
}
//
// The types of messages that can be sent to init and mini init.
//
@ -357,7 +366,21 @@ typedef enum _LX_MESSAGE_TYPE
LxMessageResultBool,
LxMessageResultInt32,
LxMessageResultUint32,
LxMessageResultUint8
LxMessageResultUint8,
LxMessageLSWMount,
LxMessageLSWMountResult,
LxMessageLSWError,
LxMessageLswGetDisk,
LxMessageLswGetDiskResult,
LxMessageLswExec,
LxMessageLswFork,
LxMessageLswForkResult,
LxMessageLswConnect,
LxMessageLswWaitPid,
LxMessageLswWaitPidResponse,
LxMessageLswSignal,
LxMessageLswShutdown,
LxMessageLswRelayTty
} LX_MESSAGE_TYPE,
*PLX_MESSAGE_TYPE;
@ -446,7 +469,19 @@ inline auto ToString(LX_MESSAGE_TYPE messageType)
X(LxMessageResultUint32)
X(LxMiniInitTelemetryMessage)
X(LxMessageResultUint8)
X(LxMessageLSWMount)
X(LxMessageLSWMountResult)
X(LxMessageLswGetDisk)
X(LxMessageLswGetDiskResult)
X(LxMessageLswExec)
X(LxMessageLswFork)
X(LxMessageLswForkResult)
X(LxMessageLswConnect)
X(LxMessageLswWaitPid)
X(LxMessageLswWaitPidResponse)
X(LxMessageLswSignal)
X(LxMessageLswShutdown)
X(LxMessageLswRelayTty)
default:
return "<unexpected LX_MESSAGE_TYPE>";
}
@ -1465,6 +1500,221 @@ typedef struct _LX_MINI_INIT_CREATE_INSTANCE_RESULT
PRETTY_PRINT(FIELD(Header), FIELD(Result), FIELD(FailureStep), FIELD(Pid), FIELD(ConnectPort), STRING_FIELD(WarningsOffset));
} LX_MINI_INIT_CREATE_INSTANCE_RESULT, *P_LX_MINI_INIT_CREATE_INSTANCE_RESULT;
struct LSW_ERROR
{
static inline auto Type = LxMessageLSWError;
MESSAGE_HEADER Header;
int Errno;
PRETTY_PRINT(FIELD(Header), FIELD(Errno));
};
struct LSW_GET_DISK_RESULT
{
static inline auto Type = LxMessageLswGetDiskResult;
DECLARE_MESSAGE_CTOR(LSW_GET_DISK_RESULT);
MESSAGE_HEADER Header;
unsigned int Result;
char Buffer[];
PRETTY_PRINT(FIELD(Header), FIELD(Result), FIELD(Buffer));
};
struct LSW_GET_DISK
{
static inline auto Type = LxMessageLswGetDisk;
using TResponse = LSW_GET_DISK_RESULT;
DECLARE_MESSAGE_CTOR(LSW_GET_DISK);
MESSAGE_HEADER Header;
unsigned int ScsiLun;
PRETTY_PRINT(FIELD(Header), FIELD(ScsiLun));
};
struct LSW_MOUNT_RESULT
{
static inline auto Type = LxMessageLSWMountResult;
MESSAGE_HEADER Header;
int Result;
PRETTY_PRINT(FIELD(Header), FIELD(Result));
};
struct LSW_MOUNT
{
static inline auto Type = LxMessageLSWMount;
using TResponse = LSW_MOUNT_RESULT;
DECLARE_MESSAGE_CTOR(LSW_MOUNT);
MESSAGE_HEADER Header;
unsigned int SourceIndex;
unsigned int DestinationIndex;
unsigned int TypeIndex;
unsigned int OptionsIndex;
unsigned int Flags;
enum ForkType : uint8_t
{
None,
Chroot = 1,
OverlayFs = 2,
};
char Buffer[];
PRETTY_PRINT(FIELD(Header), STRING_FIELD(SourceIndex), STRING_FIELD(DestinationIndex), STRING_FIELD(TypeIndex), STRING_FIELD(OptionsIndex));
};
struct LSW_EXEC
{
static inline auto Type = LxMessageLswExec;
using TResponse = RESULT_MESSAGE<uint32_t>;
DECLARE_MESSAGE_CTOR(LSW_EXEC);
MESSAGE_HEADER Header;
unsigned int ExecutableIndex = 0;
unsigned int CommandLineIndex = 0;
unsigned int EnvironmentIndex = 0;
unsigned int CurrentDirectoryIndex = 0;
unsigned int FdIndex = 0;
char Buffer[];
PRETTY_PRINT(
FIELD(Header),
STRING_FIELD(ExecutableIndex),
STRING_FIELD(CurrentDirectoryIndex),
FIELD(FdIndex),
STRING_ARRAY_FIELD(CommandLineIndex),
STRING_ARRAY_FIELD(EnvironmentIndex));
};
struct LSW_FORK_RESULT
{
static inline auto Type = LxMessageLswForkResult;
using TResponse = RESULT_MESSAGE<uint32_t>;
DECLARE_MESSAGE_CTOR(LSW_FORK_RESULT)
MESSAGE_HEADER Header;
uint32_t Port = 0;
int32_t Pid = -1;
int32_t PtyMasterFd = -1;
PRETTY_PRINT(FIELD(Header), FIELD(Pid), FIELD(Port), FIELD(PtyMasterFd));
};
struct LSW_FORK
{
static inline auto Type = LxMessageLswFork;
using TResponse = LSW_FORK_RESULT;
enum ForkType : uint8_t
{
Invalid,
Process,
Thread,
Pty
};
DECLARE_MESSAGE_CTOR(LSW_FORK);
MESSAGE_HEADER Header;
ForkType ForkType = Invalid;
uint16_t TtyColumns = 0;
uint16_t TtyRows = 0;
PRETTY_PRINT(FIELD(Header), FIELD(ForkType), FIELD(TtyColumns), FIELD(TtyRows));
};
struct LSW_TTY_RELAY
{
static inline auto Type = LxMessageLswRelayTty;
using TResponse = LSW_FORK_RESULT;
DECLARE_MESSAGE_CTOR(LSW_TTY_RELAY);
MESSAGE_HEADER Header;
int32_t TtyMaster;
int32_t TtyInput;
int32_t TtyOutput;
PRETTY_PRINT(FIELD(Header), FIELD(TtyMaster), FIELD(TtyInput), FIELD(TtyOutput));
};
struct LSW_CONNECT
{
static inline auto Type = LxMessageLswConnect;
using TResponse = RESULT_MESSAGE<uint32_t>;
DECLARE_MESSAGE_CTOR(LSW_CONNECT);
MESSAGE_HEADER Header;
int32_t Fd = -1; // TODO: multiple at once
PRETTY_PRINT(FIELD(Header), FIELD(Fd));
};
enum LSWProcessState
{
LSWProcessStateUnknown,
LSWProcessStateRunning,
LSWProcessStateExited,
LSWProcessStateSignaled
};
struct LSW_WAITPID_RESULT
{
static inline auto Type = LxMessageLswWaitPidResponse;
DECLARE_MESSAGE_CTOR(LSW_WAITPID_RESULT);
MESSAGE_HEADER Header;
LSWProcessState State = LSWProcessStateUnknown;
int32_t Code = -1;
int32_t Errno = -1;
PRETTY_PRINT(FIELD(Header), FIELD(State), FIELD(Code), FIELD(Errno));
};
struct LSW_WAITPID
{
static inline auto Type = LxMessageLswWaitPid;
using TResponse = LSW_WAITPID_RESULT;
DECLARE_MESSAGE_CTOR(LSW_WAITPID);
MESSAGE_HEADER Header;
int32_t Pid = -1;
uint64_t TimeoutMs = 0;
PRETTY_PRINT(FIELD(Header), FIELD(Pid), FIELD(TimeoutMs));
};
struct LSW_SIGNAL
{
static inline auto Type = LxMessageLswSignal;
using TResponse = RESULT_MESSAGE<int32_t>;
DECLARE_MESSAGE_CTOR(LSW_SIGNAL);
MESSAGE_HEADER Header;
int32_t Pid = -1;
int32_t Signal = -1;
PRETTY_PRINT(FIELD(Header), FIELD(Pid), FIELD(Signal));
};
struct LSW_SHUTDOWN
{
static inline auto Type = LxMessageLswShutdown;
using TResponse = RESULT_MESSAGE<int32_t>;
DECLARE_MESSAGE_CTOR(LSW_SHUTDOWN);
MESSAGE_HEADER Header;
PRETTY_PRINT(FIELD(Header));
};
typedef struct _LX_MINI_INIT_IMPORT_RESULT
{
static inline auto Type = LxMiniInitMessageImportResult;

View File

@ -93,6 +93,13 @@ public:
gsl::copy(Span, InsertBuffer(Span.size()));
}
template <typename T>
gsl::span<T> InsertArray(unsigned int& Index, unsigned int& SizeInMessage, unsigned int ArraySize)
{
SizeInMessage = ArraySize;
return InsertBuffer(Index, ArraySize * sizeof(T));
}
gsl::span<std::byte> InsertBuffer(unsigned int& Index, size_t BufferSize, unsigned int& Size)
{
Size = BufferSize;
@ -140,6 +147,32 @@ public:
WriteString(Index, wsl::shared::string::WideToMultiByte(String));
}
// TODO: This design doesn't allow empty strings
void WriteStringArray(unsigned int& Index, const char** String, size_t Count)
{
size_t totalSize = 1; // 1 char for the additional \0
for (size_t i = 0; i < Count; i++)
{
auto size = strlen(String[i]);
assert(size > 0);
totalSize += size + 1;
}
auto span = InsertBuffer(Index, totalSize);
auto it = span.begin();
for (size_t i = 0; i < Count; i++)
{
auto size = strlen(String[i]) + 1;
it = std::copy(reinterpret_cast<const std::byte*>(String[i]), reinterpret_cast<const std::byte*>(String[i] + size), it);
}
*it = static_cast<std::byte>('\0');
it++;
assert(it == span.end());
}
gsl::span<std::byte> Span()
{
// In case the structure is padded,

View File

@ -31,6 +31,8 @@ Abstract:
#define STRING_FIELD(Name) #Name, (Name <= 0 ? "<empty>" : ((char*)(this)) + Name)
#define STRING_ARRAY_FIELD(Name) #Name, (StringArray((char*)(this), Name))
#define PRETTY_PRINT(...) \
void PrettyPrintImpl(std::stringstream& Out) const \
{ \
@ -44,6 +46,12 @@ Abstract:
return Out.str(); \
}
struct StringArray
{
const char* MessageHead = nullptr;
unsigned int Index = 0;
};
template <typename T>
inline void PrettyPrint(std::stringstream& Out, const T& Value)
{
@ -71,6 +79,26 @@ inline void PrettyPrint(std::stringstream& Out, const T& Value)
// N.B. Enum can be specialized by creating an overload for this method.
Out << std::to_string(Value);
}
else if constexpr (std::is_same_v<T, StringArray>)
{
if (Value.Index <= 0)
{
Out << "<empty>";
return;
}
const auto* it = Value.MessageHead + Value.Index;
while (*it != '\0')
{
if (it != Value.MessageHead + Value.Index)
{
Out << ",";
}
Out << it;
it += strlen(it) + 1;
}
}
else
{
Out << "{";

View File

@ -159,6 +159,27 @@ inline const char* FromSpan(gsl::span<gsl::byte> Span, size_t Offset = 0)
return String.data();
}
inline std::vector<const char*> ArrayFromSpan(gsl::span<gsl::byte> Span, size_t Offset = 0)
{
THROW_INVALID_ARG_IF(Span.size() < Offset);
Span = Span.subspan(Offset);
auto StringSpan = gsl::make_span<char>(reinterpret_cast<char*>(Span.data()), Span.size());
std::vector<const char*> Result;
auto it = StringSpan.begin();
while (*it != '\0')
{
Result.push_back(&*it);
it += strlen(&*it) + 1;
}
return Result;
}
constexpr auto c_defaultHostName = "localhost";
inline std::string CleanHostname(const std::string_view Hostname)

View File

@ -18,8 +18,6 @@ Abstract:
#include "hvsocket.hpp"
#pragma hdrstop
#define CONNECT_TIMEOUT (30 * 1000)
namespace {
void InitializeSocketAddress(_In_ const GUID& VmId, _In_ unsigned long Port, _Out_ PSOCKADDR_HV Address)
{
@ -47,7 +45,7 @@ wil::unique_socket wsl::windows::common::hvsocket::Accept(_In_ SOCKET ListenSock
return Socket;
}
wil::unique_socket wsl::windows::common::hvsocket::Connect(_In_ const GUID& VmId, _In_ unsigned long Port, _In_opt_ HANDLE ExitHandle)
wil::unique_socket wsl::windows::common::hvsocket::Connect(_In_ const GUID& VmId, _In_ unsigned long Port, _In_opt_ HANDLE ExitHandle, _In_opt_ ULONG Timeout)
{
OVERLAPPED Overlapped{};
const wil::unique_event OverlappedEvent(wil::EventOptions::ManualReset);
@ -74,9 +72,10 @@ wil::unique_socket wsl::windows::common::hvsocket::Connect(_In_ const GUID& VmId
socket::GetResult(Socket.get(), Overlapped, INFINITE, ExitHandle);
}
ULONG Timeout = CONNECT_TIMEOUT;
THROW_LAST_ERROR_IF(
setsockopt(Socket.get(), HV_PROTOCOL_RAW, HVSOCKET_CONNECT_TIMEOUT, reinterpret_cast<char*>(&Timeout), sizeof(Timeout)) == SOCKET_ERROR);
THROW_LAST_ERROR_IF_MSG(
setsockopt(Socket.get(), HV_PROTOCOL_RAW, HVSOCKET_CONNECT_TIMEOUT, reinterpret_cast<char*>(&Timeout), sizeof(Timeout)) == SOCKET_ERROR,
"Timeout: %lu",
Timeout);
SOCKADDR_HV Addr;
InitializeWildcardSocketAddress(&Addr);

View File

@ -19,9 +19,10 @@ Abstract:
namespace wsl::windows::common::hvsocket {
constexpr ULONG c_connectTimeoutMs = 30 * 1000;
wil::unique_socket Accept(_In_ SOCKET ListenSocket, _In_ int Timeout, _In_opt_ HANDLE ExitHandle = nullptr);
wil::unique_socket Connect(_In_ const GUID& VmId, _In_ unsigned long Port, _In_opt_ HANDLE ExitHandle = nullptr);
wil::unique_socket Connect(_In_ const GUID& VmId, _In_ unsigned long Port, _In_opt_ HANDLE ExitHandle = nullptr, ULONG Timeout = c_connectTimeoutMs);
wil::unique_socket Create();

View File

@ -108,7 +108,7 @@ wsl::windows::common::relay::InterruptableRead(
return 0;
}
THROW_LAST_ERROR_IF(lastError != ERROR_IO_PENDING);
THROW_LAST_ERROR_IF_MSG(lastError != ERROR_IO_PENDING, "Handle: 0x%p", (void*)InputHandle);
auto cancelRead = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
CancelIoEx(InputHandle, Overlapped);

View File

@ -135,7 +135,9 @@ static const std::map<HRESULT, LPCWSTR> g_commonErrors{
X_WIN32(ERROR_INVALID_SECURITY_DESCR),
X(VM_E_INVALID_STATE),
X_WIN32(STATUS_SHUTDOWN_IN_PROGRESS),
X(WININET_E_TIMEOUT)};
X(WININET_E_TIMEOUT),
X(WSAEADDRNOTAVAIL),
X_WIN32(ERROR_BAD_IMPERSONATION_LEVEL)};
#undef X

View File

@ -21,7 +21,8 @@ enum RelayMode
DebugConsole,
DebugConsoleRelay,
PortRelay,
KdRelay
KdRelay,
InteractiveConsoleRelay
};
LPCWSTR const binary_name = L"wslrelay.exe";
@ -32,4 +33,6 @@ LPCWSTR const pipe_option = L"--pipe";
LPCWSTR const exit_event_option = L"--exit-event";
LPCWSTR const port_option = L"--port";
LPCWSTR const disable_telemetry_option = L"--disable-telemetry";
LPCWSTR const input_option = L"--input";
LPCWSTR const output_option = L"--output";
} // namespace wslrelay

View File

@ -0,0 +1,7 @@
set(SOURCES DllMain.cpp)
set(HEADERS LSWApi.h)
add_library(lswclient SHARED ${SOURCES} ${HEADERS} lswclient.def)
set_target_properties(lswclient PROPERTIES EXCLUDE_FROM_ALL FALSE)
target_link_libraries(lswclient ${COMMON_LINK_LIBRARIES} legacy_stdio_definitions common)
target_precompile_headers(lswclient REUSE_FROM common)

View File

@ -0,0 +1,283 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
DllMain.cpp
Abstract:
This file the entrypoint for the LSW client library.
--*/
#include "precomp.h"
#include "wslservice.h"
#include "LSWApi.h"
#include "wslrelay.h"
class DECLSPEC_UUID("7BC4E198-6531-4FA6-ADE2-5EF3D2A04DFF") CallbackInstance
: public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, ITerminationCallback, IFastRundown>
{
public:
CallbackInstance(VirtualMachineTerminationCallback callback, void* context) : m_callback(callback), m_context(context)
{
}
HRESULT OnTermination(ULONG Reason, LPCWSTR Details) override
{
return m_callback(m_context, static_cast<VirtualMachineTerminationReason>(Reason), Details);
}
private:
VirtualMachineTerminationCallback m_callback = nullptr;
void* m_context = nullptr;
};
HRESULT WslGetVersion(WSL_VERSION_INFORMATION* Version)
try
{
wil::com_ptr<ILSWUserSession> session;
THROW_IF_FAILED(CoCreateInstance(__uuidof(LSWUserSession), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&session)));
static_assert(sizeof(WSL_VERSION_INFORMATION) == sizeof(WSL_VERSION));
return session->GetVersion(reinterpret_cast<WSL_VERSION*>(Version));
}
CATCH_RETURN();
HRESULT WslCreateVirtualMachine(const VirtualMachineSettings* UserSettings, LSWVirtualMachineHandle* VirtualMachine)
try
{
wil::com_ptr<ILSWUserSession> session;
THROW_IF_FAILED(CoCreateInstance(__uuidof(LSWUserSession), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&session)));
wil::com_ptr<ILSWVirtualMachine> virtualMachineInstance;
VIRTUAL_MACHINE_SETTINGS settings{};
settings.DisplayName = UserSettings->DisplayName;
settings.MemoryMb = UserSettings->Memory.MemoryMb;
settings.CpuCount = UserSettings->CPU.CpuCount;
settings.BootTimeoutMs = UserSettings->Options.BootTimeoutMs;
settings.DmesgOutput = HandleToULong(UserSettings->Options.Dmesg);
settings.EnableDebugShell = UserSettings->Options.EnableDebugShell;
settings.NetworkingMode = UserSettings->Networking.Mode;
settings.EnableDnsTunneling = UserSettings->Networking.DnsTunneling;
THROW_IF_FAILED(session->CreateVirtualMachine(&settings, &virtualMachineInstance));
wil::com_ptr_nothrow<IClientSecurity> clientSecurity;
THROW_IF_FAILED(virtualMachineInstance->QueryInterface(IID_PPV_ARGS(&clientSecurity)));
// Get the current proxy blanket settings.
DWORD authnSvc, authzSvc, authnLvl, capabilites;
THROW_IF_FAILED(clientSecurity->QueryBlanket(virtualMachineInstance.get(), &authnSvc, &authzSvc, NULL, &authnLvl, NULL, NULL, &capabilites));
// Make sure that dynamic cloaking is used.
WI_ClearFlag(capabilites, EOAC_STATIC_CLOAKING);
WI_SetFlag(capabilites, EOAC_DYNAMIC_CLOAKING);
THROW_IF_FAILED(clientSecurity->SetBlanket(
virtualMachineInstance.get(), authnSvc, authzSvc, NULL, authnLvl, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, capabilites));
// Register termination callback, if specified
if (UserSettings->Options.TerminationCallback != nullptr)
{
auto callbackInstance =
wil::MakeOrThrow<CallbackInstance>(UserSettings->Options.TerminationCallback, UserSettings->Options.TerminationContext);
THROW_IF_FAILED(virtualMachineInstance->RegisterCallback(callbackInstance.Get()));
// Callback instance is now owned by the service.
}
*reinterpret_cast<ILSWVirtualMachine**>(VirtualMachine) = virtualMachineInstance.detach();
return S_OK;
}
CATCH_RETURN();
HRESULT WslAttachDisk(LSWVirtualMachineHandle VirtualMachine, const DiskAttachSettings* Settings, AttachedDiskInformation* AttachedDisk)
{
wil::unique_cotaskmem_ansistring device;
RETURN_IF_FAILED(reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)->AttachDisk(Settings->WindowsPath, Settings->ReadOnly, &device));
// TODO: wire LUN
auto deviceSize = strlen(device.get());
WI_VERIFY(deviceSize < sizeof(AttachedDiskInformation::Device));
strncpy(AttachedDisk->Device, device.get(), sizeof(AttachedDiskInformation::Device));
return S_OK;
}
HRESULT WslMount(LSWVirtualMachineHandle VirtualMachine, const MountSettings* Settings)
{
return reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)
->Mount(Settings->Device, Settings->Target, Settings->Type, Settings->Options, Settings->Flags);
}
HRESULT WslCreateLinuxProcess(LSWVirtualMachineHandle VirtualMachine, CreateProcessSettings* UserSettings, int32_t* Pid)
{
LSW_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;
LSW_CREATE_PROCESS_RESULT result{};
std::vector<LSW_PROCESS_FD> inputFd(UserSettings->FdCount);
for (size_t i = 0; i < UserSettings->FdCount; i++)
{
inputFd[i] = {UserSettings->FileDescriptors[i].Number, UserSettings->FileDescriptors[i].Type};
}
std::vector<HANDLE> fds(UserSettings->FdCount);
if (fds.empty())
{
fds.resize(1); // COM doesn't like null pointers.
}
RETURN_IF_FAILED(reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)
->CreateLinuxProcess(&options, UserSettings->FdCount, inputFd.data(), fds.data(), &result));
for (size_t i = 0; i < UserSettings->FdCount; i++)
{
UserSettings->FileDescriptors[i].Handle = fds[i];
}
*Pid = result.Pid;
return S_OK;
}
HRESULT WslWaitForLinuxProcess(LSWVirtualMachineHandle VirtualMachine, int32_t Pid, uint64_t TimeoutMs, WaitResult* Result)
{
static_assert(ProcessStateUnknown == LSWProcessStateUnknown);
static_assert(ProcessStateRunning == LSWProcessStateRunning);
static_assert(ProcessStateExited == LSWProcessStateExited);
static_assert(ProcessStateSignaled == LSWProcessStateSignaled);
static_assert(sizeof(ProcessState) == sizeof(LSWProcessState));
static_assert(sizeof(ProcessState) == sizeof(ULONG));
return reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)
->WaitPid(Pid, TimeoutMs, reinterpret_cast<ULONG*>(&Result->State), &Result->Code);
}
HRESULT WslSignalLinuxProcess(LSWVirtualMachineHandle VirtualMachine, int32_t Pid, int32_t Signal)
{
return reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)->Signal(Pid, Signal);
}
HRESULT WslShutdownVirtualMachine(LSWVirtualMachineHandle VirtualMachine, uint64_t TimeoutMs)
{
return reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)->Shutdown(TimeoutMs);
}
void WslReleaseVirtualMachine(LSWVirtualMachineHandle VirtualMachine)
{
reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)->Release();
}
HRESULT WslLaunchInteractiveTerminal(HANDLE Input, HANDLE Output, HANDLE* Process)
try
{
wsl::windows::common::helpers::SetHandleInheritable(Input);
wsl::windows::common::helpers::SetHandleInheritable(Output);
auto basePath = wsl::windows::common::wslutil::GetMsiPackagePath();
THROW_HR_IF(E_UNEXPECTED, !basePath.has_value());
auto commandLine = std::format(
L"{}/wslrelay.exe --mode {} --input {} --output {}",
basePath.value(),
static_cast<int>(wslrelay::RelayMode::InteractiveConsoleRelay),
HandleToULong(Input),
HandleToULong(Output));
WSL_LOG("LaunchWslRelay", TraceLoggingValue(commandLine.c_str(), "cmd"));
wsl::windows::common::SubProcess process{nullptr, commandLine.c_str()};
process.InheritHandle(Input);
process.InheritHandle(Output);
process.SetFlags(CREATE_NEW_CONSOLE);
process.SetShowWindow(SW_SHOW);
*Process = process.Start().release();
return S_OK;
}
CATCH_RETURN();
HRESULT WslLaunchDebugShell(LSWVirtualMachineHandle VirtualMachine, HANDLE* Process)
try
{
wil::unique_cotaskmem_string pipePath;
THROW_IF_FAILED(reinterpret_cast<ILSWVirtualMachine*>(VirtualMachine)->GetDebugShellPipe(&pipePath));
wil::unique_hfile pipe{CreateFileW(pipePath.get(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr)};
THROW_LAST_ERROR_IF(!pipe);
wsl::windows::common::helpers::SetHandleInheritable(pipe.get());
auto basePath = wsl::windows::common::wslutil::GetMsiPackagePath();
THROW_HR_IF(E_UNEXPECTED, !basePath.has_value());
auto commandLine = std::format(
L"\"{}\\wslrelay.exe\" --mode {} --input {} --output {}",
basePath.value(),
static_cast<int>(wslrelay::RelayMode::InteractiveConsoleRelay),
HandleToULong(pipe.get()),
HandleToULong(pipe.get()));
WSL_LOG("LaunchDebugShellRelay", TraceLoggingValue(commandLine.c_str(), "cmd"));
wsl::windows::common::SubProcess process{nullptr, commandLine.c_str()};
process.InheritHandle(pipe.get());
process.SetFlags(0);
process.SetShowWindow(SW_SHOW);
*Process = process.Start().release();
return S_OK;
}
CATCH_RETURN();
EXTERN_C BOOL STDAPICALLTYPE DllMain(_In_ HINSTANCE Instance, _In_ DWORD Reason, _In_opt_ LPVOID Reserved)
{
wil::DLLMain(Instance, Reason, Reserved);
switch (Reason)
{
case DLL_PROCESS_ATTACH:
WslTraceLoggingInitialize(LxssTelemetryProvider, false);
break;
case DLL_PROCESS_DETACH:
WslTraceLoggingUninitialize();
break;
}
return TRUE;
}

View File

@ -0,0 +1,172 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LSWApi.h
Abstract:
TODO
--*/
#pragma once
#include <windows.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
struct WSL_VERSION_INFORMATION
{
uint32_t Major;
uint32_t Minor;
uint32_t Revision;
};
HRESULT WslGetVersion(struct WSL_VERSION_INFORMATION* Version);
struct Memory
{
uint64_t MemoryMb;
};
struct CPU
{
uint32_t CpuCount;
};
enum VirtualMachineTerminationReason
{
VirtualMachineTerminationReasonUnknown,
VirtualMachineTerminationReasonShutdown,
VirtualMachineTerminationReasonCrashed,
};
typedef HRESULT (*VirtualMachineTerminationCallback)(void*, enum VirtualMachineTerminationReason, LPCWSTR);
struct Options
{
uint32_t BootTimeoutMs;
HANDLE Dmesg;
VirtualMachineTerminationCallback TerminationCallback;
void* TerminationContext;
bool EnableDebugShell;
};
enum NetworkingMode
{
NetworkingModeNone,
NetworkingModeNAT
};
struct Networking
{
enum NetworkingMode Mode;
bool DnsTunneling; // Not implemented yet.
};
struct VirtualMachineSettings
{
LPCWSTR DisplayName; // Not implemented yet
struct Memory Memory; // Not implemented yet
struct CPU CPU; // Not implemented yet
struct Options Options;
struct Networking Networking;
};
typedef void* LSWVirtualMachineHandle;
HRESULT WslCreateVirtualMachine(const struct VirtualMachineSettings* Settings, LSWVirtualMachineHandle* VirtualMachine);
struct DiskAttachSettings
{
LPCWSTR WindowsPath;
bool ReadOnly;
};
struct AttachedDiskInformation
{
ULONG ScsiLun;
char Device[10];
};
HRESULT WslAttachDisk(LSWVirtualMachineHandle VirtualMachine, const struct DiskAttachSettings* Settings, struct AttachedDiskInformation* AttachedDisk);
enum MountFlags
{
MountFlagsNone = 0,
MountFlagsChroot = 1,
MountFlagsWriteableOverlayFs = 2,
};
struct MountSettings
{
const char* Device;
const char* Target;
const char* Type;
const char* Options;
uint32_t Flags;
};
HRESULT WslMount(LSWVirtualMachineHandle VirtualMachine, const struct MountSettings* Settings);
enum FileDescriptorType
{
Default,
TerminalInput,
TerminalOutput
};
struct ProcessFileDescriptorSettings
{
int32_t Number;
enum FileDescriptorType Type; // Not implemented yet
HANDLE Handle;
};
struct CreateProcessSettings
{
const char* Executable;
char const** Arguments;
char const** Environment;
const char* CurrentDirectory;
uint32_t FdCount;
struct ProcessFileDescriptorSettings* FileDescriptors;
};
HRESULT WslCreateLinuxProcess(LSWVirtualMachineHandle VirtualMachine, struct CreateProcessSettings* Settings, int32_t* Pid);
enum ProcessState
{
ProcessStateUnknown,
ProcessStateRunning,
ProcessStateExited,
ProcessStateSignaled
};
struct WaitResult
{
enum ProcessState State;
int32_t Code; // Signal number or exit code
};
HRESULT WslLaunchInteractiveTerminal(HANDLE Input, HANDLE Output, HANDLE* Process);
HRESULT WslWaitForLinuxProcess(LSWVirtualMachineHandle VirtualMachine, int32_t Pid, uint64_t TimeoutMs, struct WaitResult* Result);
HRESULT WslSignalLinuxProcess(LSWVirtualMachineHandle VirtualMachine, int32_t Pid, int32_t Signal);
HRESULT WslShutdownVirtualMachine(LSWVirtualMachineHandle VirtualMachine, uint64_t TimeoutMs);
void WslReleaseVirtualMachine(LSWVirtualMachineHandle VirtualMachine);
HRESULT WslLaunchDebugShell(LSWVirtualMachineHandle VirtualMachine, HANDLE* Process); // Used for development, might remove
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,14 @@
LIBRARY lswclient
EXPORTS
WslGetVersion
WslCreateVirtualMachine
WslAttachDisk
WslMount
WslCreateLinuxProcess
WslWaitForLinuxProcess
WslSignalLinuxProcess
WslReleaseVirtualMachine
WslShutdownVirtualMachine
WslLaunchInteractiveTerminal
WslLaunchDebugShell

View File

@ -34,6 +34,9 @@ set(SOURCES
WslCoreTcpIpStateTracking.cpp
WslCoreVm.cpp
VirtioNetworking.cpp
LSWUserSession.cpp
LSWUserSessionFactory.cpp
LSWVirtualMachine.cpp
main.rc
${CMAKE_CURRENT_BINARY_DIR}/../mc/${TARGET_PLATFORM}/${CMAKE_BUILD_TYPE}/wsleventschema.rc
application.manifest)
@ -75,7 +78,12 @@ set(HEADERS
WslCoreNetworkEndpointSettings.h
DnsResolver.h
WslCoreTcpIpStateTracking.h
WslCoreVm.h)
WslCoreVm.h
LSWUserSession.h
LSWUserSessionFactory.h
LSWVirtualMachine.h)
include_directories(${CMAKE_SOURCE_DIR}/src/windows/lswclient)
add_executable(wslservice ${SOURCES} ${HEADERS})
add_dependencies(wslservice wslserviceidl wslservicemc)

View File

@ -15,8 +15,13 @@ Abstract:
#include "precomp.h"
#include "Dmesg.h"
DmesgCollector::DmesgCollector(GUID VmId, const wil::unique_event& ExitEvent, bool EnableTelemetry, bool EnableDebugConsole, const std::wstring& Com1PipeName) :
m_com1PipeName(Com1PipeName), m_runtimeId(VmId), m_debugConsole(EnableDebugConsole), m_telemetry(EnableTelemetry)
DmesgCollector::DmesgCollector(
GUID VmId, const wil::unique_event& ExitEvent, bool EnableTelemetry, bool EnableDebugConsole, const std::wstring& Com1PipeName, wil::unique_handle&& OutputHandle) :
m_com1PipeName(Com1PipeName),
m_runtimeId(VmId),
m_debugConsole(EnableDebugConsole),
m_telemetry(EnableTelemetry),
m_outputHandle(std::move(OutputHandle))
{
m_exitEvent.reset(wsl::windows::common::helpers::DuplicateHandle(ExitEvent.get()));
m_overlappedEvent.create(wil::EventOptions::ManualReset);
@ -40,10 +45,16 @@ DmesgCollector::~DmesgCollector()
}
std::shared_ptr<DmesgCollector> DmesgCollector::Create(
GUID VmId, const wil::unique_event& ExitEvent, bool EnableTelemetry, bool EnableDebugConsole, const std::wstring& Com1PipeName, bool EnableEarlyBootConsole)
GUID VmId,
const wil::unique_event& ExitEvent,
bool EnableTelemetry,
bool EnableDebugConsole,
const std::wstring& Com1PipeName,
bool EnableEarlyBootConsole,
wil::unique_handle&& OutputHandle)
{
auto dmesgCollector =
std::shared_ptr<DmesgCollector>(new DmesgCollector(VmId, ExitEvent, EnableTelemetry, EnableDebugConsole, Com1PipeName));
auto dmesgCollector = std::shared_ptr<DmesgCollector>(
new DmesgCollector(VmId, ExitEvent, EnableTelemetry, EnableDebugConsole, Com1PipeName, std::move(OutputHandle)));
if (FAILED(dmesgCollector->Start(EnableEarlyBootConsole)))
{
@ -61,13 +72,13 @@ std::pair<std::wstring, std::thread> DmesgCollector::StartDmesgThread(InputSourc
THROW_LAST_ERROR_IF(!pipe);
auto workerThread = std::thread([Self = shared_from_this(), Source, Pipe = std::move(pipe)]() {
auto workerThread = std::thread([this, Source, Pipe = std::move(pipe)]() {
try
{
wsl::windows::common::wslutil::SetThreadDescription(L"Dmesg");
// When the pipe connects, start reading data.
wsl::windows::common::helpers::ConnectPipe(Pipe.get(), INFINITE, Self->m_exitEvents);
wsl::windows::common::helpers::ConnectPipe(Pipe.get(), INFINITE, m_exitEvents);
std::vector<char> buffer(LX_RELAY_BUFFER_SIZE);
const auto allBuffer = gsl::make_span(buffer);
@ -78,7 +89,7 @@ std::pair<std::wstring, std::thread> DmesgCollector::StartDmesgThread(InputSourc
{
overlappedEvent.ResetEvent();
const auto bytesRead = wsl::windows::common::relay::InterruptableRead(
Pipe.get(), gslhelpers::convert_span<gsl::byte>(allBuffer), Self->m_exitEvents, &overlapped);
Pipe.get(), gslhelpers::convert_span<gsl::byte>(allBuffer), m_exitEvents, &overlapped);
if (bytesRead == 0)
{
@ -86,7 +97,7 @@ std::pair<std::wstring, std::thread> DmesgCollector::StartDmesgThread(InputSourc
}
auto validBuffer = allBuffer.subspan(0, bytesRead);
Self->ProcessInput(Source, validBuffer);
ProcessInput(Source, validBuffer);
}
}
CATCH_LOG()
@ -142,6 +153,16 @@ void DmesgCollector::ProcessInput(InputSource Source, const gsl::span<char>& Inp
{
WriteToCom1(Input);
}
if (m_outputHandle != nullptr)
{
m_overlappedEvent.ResetEvent();
if (wsl::windows::common::relay::InterruptableWrite(
m_outputHandle.get(), gslhelpers::convert_span<gsl::byte>(Input), m_exitEvents, &m_overlapped) == 0)
{
m_outputHandle = nullptr;
}
}
}
void DmesgCollector::WriteToCom1(const gsl::span<char>& Input)

View File

@ -34,7 +34,13 @@ public:
}
static std::shared_ptr<DmesgCollector> Create(
GUID VmId, const wil::unique_event& ExitEvent, bool EnableTelemetry, bool EnableDebugConsole, const std::wstring& Com1PipeName, bool EnableEarlyBootConsole);
GUID VmId,
const wil::unique_event& ExitEvent,
bool EnableTelemetry,
bool EnableDebugConsole,
const std::wstring& Com1PipeName,
bool EnableEarlyBootConsole,
wil::unique_handle&& OutputHandle);
private:
enum InputSource
@ -43,7 +49,13 @@ private:
DmesgCollectorConsole
};
DmesgCollector(GUID VmId, const wil::unique_event& ExitEvent, bool EnableTelemetry, bool EnableDebugConsole, const std::wstring& Com1PipeName);
DmesgCollector(
GUID VmId,
const wil::unique_event& ExitEvent,
bool EnableTelemetry,
bool EnableDebugConsole,
const std::wstring& Com1PipeName,
wil::unique_handle&& OutputHandle = {});
HRESULT Start(bool EnableEarlyBootConsole);
std::pair<std::wstring, std::thread> StartDmesgThread(InputSource Source);
@ -70,4 +82,5 @@ private:
bool m_waitForConnection;
std::thread m_earlyConsoleWorker;
std::thread m_virtioWorker;
wil::unique_handle m_outputHandle = nullptr;
};

View File

@ -0,0 +1,56 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LSWUserSession.cpp
Abstract:
TODO
--*/
#include "LSWUserSession.h"
#include "LSWVirtualMachine.h"
using wsl::windows::service::lsw::LSWUserSessionImpl;
wsl::windows::service::lsw::LSWUserSessionImpl::LSWUserSessionImpl(HANDLE Token, wil::unique_tokeninfo_ptr<TOKEN_USER>&& TokenInfo) :
m_tokenInfo(std::move(TokenInfo))
{
}
PSID wsl::windows::service::lsw::LSWUserSessionImpl::GetUserSid() const
{
return m_tokenInfo->User.Sid;
}
wsl::windows::service::lsw::LSWUserSession::LSWUserSession(std::weak_ptr<LSWUserSessionImpl>&& Session) :
m_session(std::move(Session))
{
}
HRESULT wsl::windows::service::lsw::LSWUserSession::GetVersion(_Out_ WSL_VERSION* Version)
{
Version->Major = WSL_PACKAGE_VERSION_MAJOR;
Version->Minor = WSL_PACKAGE_VERSION_MINOR;
Version->Revision = WSL_PACKAGE_VERSION_REVISION;
return S_OK;
}
HRESULT wsl::windows::service::lsw::LSWUserSession::CreateVirtualMachine(const VIRTUAL_MACHINE_SETTINGS* Settings, ILSWVirtualMachine** VirtualMachine)
try
{
auto session = m_session.lock();
RETURN_HR_IF(RPC_E_DISCONNECTED, !session);
auto vm = wil::MakeOrThrow<LSWVirtualMachine>(*Settings, session->GetUserSid());
THROW_IF_FAILED(vm.CopyTo(__uuidof(ILSWVirtualMachine), (void**)VirtualMachine));
vm->Start();
return S_OK;
}
CATCH_RETURN();

View File

@ -0,0 +1,45 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LSWUserSession.h
Abstract:
TODO
--*/
#pragma once
namespace wsl::windows::service::lsw {
class LSWUserSessionImpl
{
public:
LSWUserSessionImpl(HANDLE Token, wil::unique_tokeninfo_ptr<TOKEN_USER>&& TokenInfo);
LSWUserSessionImpl(LSWUserSessionImpl&&) = default;
LSWUserSessionImpl& operator=(LSWUserSessionImpl&&) = default;
PSID GetUserSid() const;
private:
wil::unique_tokeninfo_ptr<TOKEN_USER> m_tokenInfo;
};
class DECLSPEC_UUID("a9b7a1b9-0671-405c-95f1-e0612cb4ce8f") LSWUserSession
: public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, ILSWUserSession, IFastRundown>
{
public:
LSWUserSession(std::weak_ptr<LSWUserSessionImpl>&& Session);
LSWUserSession(const LSWUserSession&) = delete;
LSWUserSession& operator=(const LSWUserSession&) = delete;
IFACEMETHOD(GetVersion)(_Out_ WSL_VERSION* Version) override;
IFACEMETHOD(CreateVirtualMachine)(const VIRTUAL_MACHINE_SETTINGS* Settings, ILSWVirtualMachine** VirtualMachine) override;
private:
std::weak_ptr<LSWUserSessionImpl> m_session;
};
} // namespace wsl::windows::service::lsw

View File

@ -0,0 +1,71 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LSWUserSessionFactory.cpp
Abstract:
TODO
--*/
#include "precomp.h"
#include "LSWUserSessionFactory.h"
#include "LSWUserSession.h"
using wsl::windows::service::lsw::LSWUserSessionFactory;
CoCreatableClassWithFactory(LSWUserSession, LSWUserSessionFactory);
HRESULT LSWUserSessionFactory::CreateInstance(_In_ IUnknown* pUnkOuter, _In_ REFIID riid, _Out_ void** ppCreated)
{
RETURN_HR_IF_NULL(E_POINTER, ppCreated);
*ppCreated = nullptr;
RETURN_HR_IF(CLASS_E_NOAGGREGATION, pUnkOuter != nullptr);
WSL_LOG("LSWUserSessionFactory", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
try
{
const wil::unique_handle userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
// Get the session ID and SID of the client process.
DWORD sessionId{};
DWORD length = 0;
THROW_IF_WIN32_BOOL_FALSE(::GetTokenInformation(userToken.get(), TokenSessionId, &sessionId, sizeof(sessionId), &length));
auto tokenInfo = wil::get_token_information<TOKEN_USER>(userToken.get());
static std::mutex mutex;
static std::vector<std::shared_ptr<LSWUserSessionImpl>> sessions;
std::lock_guard lock{mutex};
auto session = std::find_if(
sessions.begin(), sessions.end(), [&tokenInfo](auto it) { return EqualSid(it->GetUserSid(), &tokenInfo->User.Sid); });
if (session == sessions.end())
{
session = sessions.insert(sessions.end(), std::make_shared<LSWUserSessionImpl>(userToken.get(), std::move(tokenInfo)));
}
auto comInstance = wil::MakeOrThrow<LSWUserSession>(std::weak_ptr<LSWUserSessionImpl>(*session));
THROW_IF_FAILED(comInstance.CopyTo(riid, ppCreated));
}
catch (...)
{
const auto result = wil::ResultFromCaughtException();
// Note: S_FALSE will cause COM to retry if the service is stopping.
return result == CO_E_SERVER_STOPPING ? S_FALSE : result;
}
WSL_LOG("LSWUserSessionFactory", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
return S_OK;
}

View File

@ -0,0 +1,26 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LSWUserSessionFactory.h
Abstract:
TODO
--*/
#pragma once
#include <wil/resource.h>
namespace wsl::windows::service::lsw {
class LSWUserSessionFactory : public Microsoft::WRL::ClassFactory<>
{
public:
LSWUserSessionFactory() = default;
STDMETHODIMP CreateInstance(_In_ IUnknown* pUnkOuter, _In_ REFIID riid, _Out_ void** ppCreated) override;
};
} // namespace wsl::windows::service::lsw

View File

@ -0,0 +1,659 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LSWVirtualMachine.cpp
Abstract:
TODO
--*/
#include "LSWVirtualMachine.h"
#include "hcs_schema.h"
#include "LSWApi.h"
#include "NatNetworking.h"
using namespace wsl::windows::common;
using helpers::WindowsBuildNumbers;
using helpers::WindowsVersion;
using wsl::windows::service::lsw::LSWVirtualMachine;
#define VIRTIO_SERIAL_CONSOLE_COBALT_RELEASE_UBR 40 // TODO: factor
LSWVirtualMachine::LSWVirtualMachine(const VIRTUAL_MACHINE_SETTINGS& Settings, PSID UserSid) :
m_settings(Settings), m_userSid(UserSid)
{
THROW_IF_FAILED(CoCreateGuid(&m_vmId));
if (Settings.EnableDebugShell)
{
m_debugShellPipe = wsl::windows::common::wslutil::GetDebugShellPipeName(m_userSid) + m_settings.DisplayName;
}
}
HRESULT LSWVirtualMachine::GetDebugShellPipe(LPWSTR* pipePath)
{
RETURN_HR_IF(E_INVALIDARG, m_debugShellPipe.empty());
*pipePath = wil::make_unique_string<wil::unique_cotaskmem_string>(m_debugShellPipe.c_str()).release();
return S_OK;
}
LSWVirtualMachine::~LSWVirtualMachine()
{
WSL_LOG("LswTerminateVmStart", TraceLoggingValue(m_running, "running"));
m_vmTerminatingEvent.SetEvent();
m_initChannel.Close();
bool forceTerminate = false;
// Wait up to 5 seconds for the VM to terminate.
if (!m_vmExitEvent.wait(5000))
{
forceTerminate = true;
try
{
wsl::windows::common::hcs::TerminateComputeSystem(m_computeSystem.get());
}
CATCH_LOG()
}
WSL_LOG("LswTerminateVm", TraceLoggingValue(forceTerminate, "forced"), TraceLoggingValue(m_running, "running"));
m_computeSystem.reset();
for (const auto& e : m_attachedDisks)
{
try
{
wsl::windows::common::hcs::RevokeVmAccess(m_vmIdString.c_str(), e.second.Path.c_str());
}
CATCH_LOG()
}
}
void LSWVirtualMachine::Start()
{
hcs::ComputeSystem systemSettings{};
systemSettings.Owner = L"WSL";
systemSettings.ShouldTerminateOnLastHandleClosed = true;
systemSettings.SchemaVersion.Major = 2;
systemSettings.SchemaVersion.Minor = 3;
hcs::VirtualMachine vmSettings{};
vmSettings.StopOnReset = true;
vmSettings.Chipset.UseUtc = true;
// Ensure the 2MB granularity enforced by HCS.
vmSettings.ComputeTopology.Memory.SizeInMB = m_settings.MemoryMb & ~0x1;
vmSettings.ComputeTopology.Memory.AllowOvercommit = true;
vmSettings.ComputeTopology.Memory.EnableDeferredCommit = true;
vmSettings.ComputeTopology.Memory.EnableColdDiscardHint = true;
// Configure backing page size, fault cluster shift size, and cold discard hint size to favor density (lower vmmem usage).
//
// N.B. Cold discard hint size should be a multiple of the fault cluster shift size.
//
// N.B. This is only done on builds that have the fix for the VID deadlock on partition teardown.
if ((m_windowsVersion.BuildNumber >= WindowsBuildNumbers::Germanium) ||
(m_windowsVersion.BuildNumber >= WindowsBuildNumbers::Cobalt && m_windowsVersion.UpdateBuildRevision >= 2360) ||
(m_windowsVersion.BuildNumber >= WindowsBuildNumbers::Iron && m_windowsVersion.UpdateBuildRevision >= 1970) ||
(m_windowsVersion.BuildNumber >= WindowsBuildNumbers::Vibranium_22H2 && m_windowsVersion.UpdateBuildRevision >= 3393))
{
vmSettings.ComputeTopology.Memory.BackingPageSize = hcs::MemoryBackingPageSize::Small;
vmSettings.ComputeTopology.Memory.FaultClusterSizeShift = 4; // 64k
vmSettings.ComputeTopology.Memory.DirectMapFaultClusterSizeShift = 4; // 64k
m_coldDiscardShiftSize = 5; // 128k
}
else
{
m_coldDiscardShiftSize = 9; // 2MB
}
// Configure the number of processors.
vmSettings.ComputeTopology.Processor.Count = 4; // TODO
// Set the vmmem suffix which will change the process name in task manager.
// if (IsVmemmSuffixSupported()) // TODO: impl
{
vmSettings.ComputeTopology.Memory.HostingProcessNameSuffix = m_settings.DisplayName;
}
// TODO
/*
if (m_vmConfig.EnableHardwarePerformanceCounters)
{
HV_X64_HYPERVISOR_HARDWARE_FEATURES hardwareFeatures{};
__cpuid(reinterpret_cast<int*>(&hardwareFeatures), HvCpuIdFunctionMsHvHardwareFeatures);
vmSettings.ComputeTopology.Processor.EnablePerfmonPmu = hardwareFeatures.ChildPerfmonPmuSupported != 0;
vmSettings.ComputeTopology.Processor.EnablePerfmonLbr = hardwareFeatures.ChildPerfmonLbrSupported != 0;
}*/
// Initialize kernel command line.
std::wstring kernelCmdLine = L"initrd=\\" LXSS_VM_MODE_INITRD_NAME L" " TEXT(LSW_ROOT_INIT_ENV) L"=1 panic=-1";
// Set number of processors.
kernelCmdLine += std::format(L" nr_cpus={}", m_settings.CpuCount);
// Enable timesync workaround to sync on resume from sleep in modern standby.
kernelCmdLine += L" hv_utils.timesync_implicit=1";
// TODO: check for virtio serial support
wil::unique_handle dmesgOutput;
if (m_settings.DmesgOutput != 0)
{
dmesgOutput.reset(wsl::windows::common::wslutil::DuplicateHandleFromCallingProcess(ULongToHandle(m_settings.DmesgOutput)));
}
m_dmesgCollector = DmesgCollector::Create(m_vmId, m_vmExitEvent, true, false, L"", true, std::move(dmesgOutput));
if (false) // early boot logging
{
kernelCmdLine += L" earlycon=uart8250,io,0x3f8,115200";
vmSettings.Devices.ComPorts["0"] = hcs::ComPort{m_dmesgCollector->EarlyConsoleName()};
}
vmSettings.Devices.VirtioSerial.emplace();
// TODO: support early boot logging
// The primary "console" will be a virtio serial device.
if (true)
{
kernelCmdLine += L" console=hvc0 debug";
hcs::VirtioSerialPort virtioPort{};
virtioPort.Name = L"hvc0";
virtioPort.NamedPipe = m_dmesgCollector->VirtioConsoleName();
virtioPort.ConsoleSupport = true;
vmSettings.Devices.VirtioSerial->Ports["0"] = std::move(virtioPort);
}
if (!m_debugShellPipe.empty())
{
hcs::VirtioSerialPort virtioPort;
virtioPort.Name = L"hvc1";
virtioPort.NamedPipe = m_debugShellPipe;
virtioPort.ConsoleSupport = true;
vmSettings.Devices.VirtioSerial->Ports["1"] = std::move(virtioPort);
}
// Set up boot params.
//
// N.B. Linux kernel direct boot is not yet supported on ARM64.
auto basePath = wslutil::GetBasePath();
#ifdef WSL_KERNEL_PATH
auto kernelPath = std::filesystem::path(WSL_KERNEL_PATH);
#else
auto kernelPath = std::filesystem::path(basePath) / L"tools" / LXSS_VM_MODE_KERNEL_NAME;
#endif
if constexpr (!wsl::shared::Arm64)
{
vmSettings.Chipset.LinuxKernelDirect.emplace();
vmSettings.Chipset.LinuxKernelDirect->KernelFilePath = kernelPath.wstring();
vmSettings.Chipset.LinuxKernelDirect->InitRdPath = (basePath / L"tools" / LXSS_VM_MODE_INITRD_NAME).c_str();
vmSettings.Chipset.LinuxKernelDirect->KernelCmdLine = kernelCmdLine;
}
else
{
// TODO
THROW_HR(E_NOTIMPL);
auto bootThis = hcs::UefiBootEntry{};
bootThis.DeviceType = hcs::UefiBootDevice::VmbFs;
// bootThis.VmbFsRootPath = m_rootFsPath.c_str();
bootThis.DevicePath = L"\\" LXSS_VM_MODE_KERNEL_NAME;
bootThis.OptionalData = kernelCmdLine;
hcs::Uefi uefiSettings{};
uefiSettings.BootThis = std::move(bootThis);
vmSettings.Chipset.Uefi = std::move(uefiSettings);
}
// Initialize other devices.
vmSettings.Devices.Scsi["0"] = hcs::Scsi{};
hcs::HvSocket hvSocketConfig{};
// Construct a security descriptor that allows system and the current user.
wil::unique_hlocal_string userSidString;
THROW_LAST_ERROR_IF(!ConvertSidToStringSidW(m_userSid, &userSidString));
std::wstring securityDescriptor{L"D:P(A;;FA;;;SY)(A;;FA;;;"};
securityDescriptor += userSidString.get();
securityDescriptor += L")";
hvSocketConfig.HvSocketConfig.DefaultBindSecurityDescriptor = securityDescriptor;
hvSocketConfig.HvSocketConfig.DefaultConnectSecurityDescriptor = securityDescriptor;
vmSettings.Devices.HvSocket = std::move(hvSocketConfig);
systemSettings.VirtualMachine = std::move(vmSettings);
auto json = wsl::shared::ToJsonW(systemSettings);
WSL_LOG("CreateLSWVirtualMachine", TraceLoggingValue(json.c_str(), "json"));
m_vmIdString = wsl::shared::string::GuidToString<wchar_t>(m_vmId, wsl::shared::string::GuidToStringFlags::Uppercase);
m_computeSystem = hcs::CreateComputeSystem(m_vmIdString.c_str(), json.c_str());
auto runtimeId = wsl::windows::common::hcs::GetRuntimeId(m_computeSystem.get());
WI_ASSERT(IsEqualGUID(m_vmId, runtimeId));
wsl::windows::common::hcs::RegisterCallback(m_computeSystem.get(), &s_OnExit, this);
wsl::windows::common::hcs::StartComputeSystem(m_computeSystem.get(), json.c_str());
// Create a socket listening for connections from mini_init.
auto listenSocket = wsl::windows::common::hvsocket::Listen(runtimeId, LX_INIT_UTILITY_VM_INIT_PORT);
auto socket = wsl::windows::common::hvsocket::Accept(listenSocket.get(), m_settings.BootTimeoutMs, m_vmTerminatingEvent.get());
m_initChannel = wsl::shared::SocketChannel{std::move(socket), "mini_init", m_vmTerminatingEvent.get()};
ConfigureNetworking();
}
void LSWVirtualMachine::ConfigureNetworking()
{
if (m_settings.NetworkingMode == NetworkingModeNone)
{
return;
}
else if (m_settings.NetworkingMode == NetworkingModeNAT)
{
// Launch GNS
LSW_PROCESS_FD fds[2];
fds[0].Fd = 3;
fds[0].Type = FileDescriptorType::Default;
std::vector<const char*> cmd{"/gns", LX_INIT_GNS_SOCKET_ARG, "3"};
LSW_CREATE_PROCESS_OPTIONS options{};
options.Executable = "/init";
options.CommandLine = cmd.data();
options.CommandLineCount = static_cast<ULONG>(cmd.size());
std::vector<HANDLE> socketHandles(2);
LSW_CREATE_PROCESS_RESULT result{};
THROW_IF_FAILED(CreateLinuxProcess(&options, 1, fds, socketHandles.data(), &result));
wil::unique_socket gnsSocket{(SOCKET)socketHandles[0]};
// TODO: refactor this to avoid using wsl config
static wsl::core::Config config(nullptr);
// TODO: DNS Tunneling support
m_networkEngine = std::make_unique<wsl::core::NatNetworking>(
m_computeSystem.get(), wsl::core::NatNetworking::CreateNetwork(config), std::move(gnsSocket), config, wil::unique_socket{});
m_networkEngine->Initialize();
}
else
{
THROW_HR_MSG(E_INVALIDARG, "Invalid networking mode: %lu", m_settings.NetworkingMode);
}
}
void CALLBACK LSWVirtualMachine::s_OnExit(_In_ HCS_EVENT* Event, _In_opt_ void* Context)
{
if (Event->Type == HcsEventSystemExited || Event->Type == HcsEventSystemCrashInitiated || Event->Type == HcsEventSystemCrashReport)
{
reinterpret_cast<LSWVirtualMachine*>(Context)->OnExit(Event);
}
}
void LSWVirtualMachine::OnExit(_In_ const HCS_EVENT* Event)
{
WSL_LOG(
"LSWVmExited", TraceLoggingValue(Event->EventData, "details"), TraceLoggingValue(static_cast<int>(Event->Type), "type"));
m_vmExitEvent.SetEvent();
std::lock_guard lock(m_lock);
if (m_terminationCallback)
{
// TODO: parse json and give a better error.
VirtualMachineTerminationReason reason = VirtualMachineTerminationReasonUnknown;
if (Event->Type == HcsEventSystemExited)
{
reason = VirtualMachineTerminationReasonShutdown;
}
else if (Event->Type == HcsEventSystemCrashInitiated || Event->Type == HcsEventSystemCrashReport)
{
reason = VirtualMachineTerminationReasonCrashed;
}
LOG_IF_FAILED(m_terminationCallback->OnTermination(static_cast<ULONG>(reason), Event->EventData));
}
}
HRESULT LSWVirtualMachine::AttachDisk(_In_ PCWSTR Path, _In_ BOOL ReadOnly, _Out_ LPSTR* Device)
try
{
*Device = nullptr;
auto result = wil::ResultFromException([&]() {
std::lock_guard lock{m_lock};
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_running);
{
const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
auto runAsUser = wil::impersonate_token(userToken.get());
wsl::windows::common::hcs::GrantVmAccess(m_vmIdString.c_str(), Path);
}
ULONG lun = 0;
while (m_attachedDisks.find(lun) != m_attachedDisks.end())
{
lun++;
}
bool vhdAdded = false;
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
if (vhdAdded)
{
wsl::windows::common::hcs::RemoveScsiDisk(m_computeSystem.get(), lun);
}
wsl::windows::common::hcs::RevokeVmAccess(m_vmIdString.c_str(), Path);
});
wsl::windows::common::hcs::AddVhd(m_computeSystem.get(), Path, lun, ReadOnly);
vhdAdded = true;
LSW_GET_DISK message{};
message.Header.MessageSize = sizeof(message);
message.Header.MessageType = LSW_GET_DISK::Type;
message.ScsiLun = lun;
const auto& response = m_initChannel.Transaction(message);
THROW_HR_IF_MSG(E_FAIL, response.Result != 0, "Failed to attach disk, init returned: %lu", response.Result);
cleanup.release();
m_attachedDisks.emplace(lun, AttachedDisk{Path, response.Buffer});
*Device = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(response.Buffer).release();
});
WSL_LOG(
"LSWAttachDisk",
TraceLoggingValue(Path, "Path"),
TraceLoggingValue(ReadOnly, "ReadOnly"),
TraceLoggingValue(*Device == nullptr ? "<null>" : *Device, "Device"),
TraceLoggingValue(result, "Result"));
return result;
}
CATCH_RETURN();
HRESULT LSWVirtualMachine::Mount(_In_ LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags)
try
{
static_assert(MountFlagsNone == LSW_MOUNT::None);
static_assert(MountFlagsChroot == LSW_MOUNT::Chroot);
static_assert(MountFlagsWriteableOverlayFs == LSW_MOUNT::OverlayFs);
wsl::shared::MessageWriter<LSW_MOUNT> message;
auto optionalAdd = [&](auto value, unsigned int& index) {
if (value != nullptr)
{
message.WriteString(index, value);
}
};
optionalAdd(Source, message->SourceIndex);
optionalAdd(Target, message->DestinationIndex);
optionalAdd(Type, message->TypeIndex);
optionalAdd(Options, message->OptionsIndex);
message->Flags = Flags;
std::lock_guard lock{m_lock};
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_running);
const auto& response = m_initChannel.Transaction<LSW_MOUNT>(message.Span());
WSL_LOG(
"LSWMount",
TraceLoggingValue(Source == nullptr ? "<null>" : Source, "Source"),
TraceLoggingValue(Target == nullptr ? "<null>" : Target, "Target"),
TraceLoggingValue(Type == nullptr ? "<null>" : Type, "Type"),
TraceLoggingValue(Options == nullptr ? "<null>" : Options, "Options"),
TraceLoggingValue(Flags, "Flags"),
TraceLoggingValue(response.Result, "Result"));
// TODO: better error
THROW_HR_IF(E_FAIL, response.Result != 0);
return S_OK;
}
CATCH_RETURN();
std::tuple<int32_t, int32_t, wsl::shared::SocketChannel> LSWVirtualMachine::Fork(enum LSW_FORK::ForkType Type)
{
std::lock_guard lock{m_lock};
return Fork(m_initChannel, Type);
}
std::tuple<int32_t, int32_t, wsl::shared::SocketChannel> LSWVirtualMachine::Fork(wsl::shared::SocketChannel& Channel, enum LSW_FORK::ForkType Type)
{
uint32_t port{};
int32_t pid{};
int32_t ptyMaster{};
{
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_running);
LSW_FORK message;
message.ForkType = Type;
message.TtyColumns = 80;
message.TtyRows = 80;
const auto& response = Channel.Transaction(message);
port = response.Port;
pid = response.Pid;
ptyMaster = response.PtyMasterFd;
}
THROW_HR_IF_MSG(E_FAIL, pid <= 0, "fork() returned %i", pid);
auto socket = wsl::windows::common::hvsocket::Connect(m_vmId, port, m_vmExitEvent.get(), m_settings.BootTimeoutMs);
// TODO: pid in channel name
return std::make_tuple(pid, ptyMaster, wsl::shared::SocketChannel{std::move(socket), "ForkedChannel"});
}
wil::unique_socket LSWVirtualMachine::ConnectSocket(wsl::shared::SocketChannel& Channel, int32_t Fd)
{
LSW_CONNECT message{};
message.Header.MessageSize = sizeof(message);
message.Header.MessageType = LSW_CONNECT::Type;
message.Fd = Fd;
const auto& response = Channel.Transaction(message);
return wsl::windows::common::hvsocket::Connect(m_vmId, response.Result);
}
HRESULT LSWVirtualMachine::CreateLinuxProcess(
_In_ const LSW_CREATE_PROCESS_OPTIONS* Options, ULONG FdCount, LSW_PROCESS_FD* Fds, HANDLE* Handles, _Out_ LSW_CREATE_PROCESS_RESULT* Result)
try
{
// Check if this is a tty or not
const LSW_PROCESS_FD* ttyInput = nullptr;
const LSW_PROCESS_FD* ttyOutput = nullptr;
auto interactiveTty = ParseTtyInformation(Fds, FdCount, &ttyInput, &ttyOutput);
auto [pid, _, childChannel] = Fork(LSW_FORK::Process);
std::vector<wil::unique_socket> sockets(FdCount);
for (size_t i = 0; i < FdCount; i++)
{
sockets[i] = ConnectSocket(childChannel, static_cast<int32_t>(Fds[i].Fd));
}
wsl::shared::MessageWriter<LSW_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);
// If this is an interactive tty, we need a relay process
if (interactiveTty)
{
auto [grandChildPid, ptyMaster, grandChildChannel] = Fork(childChannel, LSW_FORK::Pty);
LSW_TTY_RELAY relayMessage;
relayMessage.TtyMaster = ptyMaster;
relayMessage.TtyInput = ttyInput->Fd;
relayMessage.TtyOutput = ttyOutput->Fd;
childChannel.SendMessage(relayMessage);
auto result = ExpectClosedChannelOrError(childChannel);
if (result != 0)
{
Result->Errno = result;
return E_FAIL;
}
grandChildChannel.SendMessage<LSW_EXEC>(Message.Span());
result = ExpectClosedChannelOrError(grandChildChannel);
if (result != 0)
{
Result->Errno = result;
return E_FAIL;
}
pid = grandChildPid;
}
else
{
childChannel.SendMessage<LSW_EXEC>(Message.Span());
auto result = ExpectClosedChannelOrError(childChannel);
if (result != 0)
{
Result->Errno = result;
return E_FAIL;
}
}
Result->Errno = 0;
Result->Pid = pid;
for (size_t i = 0; i < sockets.size(); i++)
{
Handles[i] = (HANDLE)sockets[i].release();
}
return S_OK;
}
CATCH_RETURN();
int32_t LSWVirtualMachine::ExpectClosedChannelOrError(wsl::shared::SocketChannel& Channel)
{
auto [response, span] = Channel.ReceiveMessageOrClosed<RESULT_MESSAGE<int32_t>>();
if (response != nullptr)
{
return response->Result;
}
else
{
return 0;
}
}
HRESULT LSWVirtualMachine::WaitPid(LONG Pid, ULONGLONG TimeoutMs, ULONG* State, int* Code)
try
{
auto [pid, _, subChannel] = Fork(LSW_FORK::Thread);
LSW_WAITPID message{};
message.Pid = Pid;
message.TimeoutMs = TimeoutMs;
const auto& response = subChannel.Transaction(message);
THROW_HR_IF(E_FAIL, response.State == LSWProcessStateUnknown);
*State = response.State;
*Code = response.Code;
return S_OK;
}
CATCH_RETURN();
HRESULT LSWVirtualMachine::Shutdown(ULONGLONG TimeoutMs)
try
{
std::lock_guard lock(m_lock);
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_running);
LSW_SHUTDOWN message{};
m_initChannel.SendMessage(message);
auto response = m_initChannel.ReceiveMessageOrClosed<MESSAGE_HEADER>(static_cast<wsl::shared::TTimeout>(TimeoutMs));
RETURN_HR_IF(E_UNEXPECTED, response.first != nullptr);
m_running = false;
return S_OK;
}
CATCH_RETURN();
HRESULT LSWVirtualMachine::Signal(_In_ LONG Pid, _In_ int Signal)
try
{
std::lock_guard lock(m_lock);
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_running);
LSW_SIGNAL message;
message.Pid = Pid;
message.Signal = Signal;
const auto& response = m_initChannel.Transaction(message);
RETURN_HR_IF(E_FAIL, response.Result != 0);
return S_OK;
}
CATCH_RETURN();
HRESULT LSWVirtualMachine::RegisterCallback(ITerminationCallback* callback)
try
{
std::lock_guard lock(m_lock);
THROW_HR_IF(E_INVALIDARG, m_terminationCallback);
// N.B. this calls AddRef() on the callback
m_terminationCallback = callback;
return S_OK;
}
CATCH_RETURN();
bool LSWVirtualMachine::ParseTtyInformation(const LSW_PROCESS_FD* Fds, ULONG FdCount, const LSW_PROCESS_FD** TtyInput, const LSW_PROCESS_FD** TtyOutput)
{
bool foundNonTtyFd = false;
for (ULONG i = 0; i < FdCount; i++)
{
if (Fds[i].Type == TerminalInput)
{
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 == TerminalOutput)
{
THROW_HR_IF_MSG(E_INVALIDARG, *TtyOutput != nullptr, "Only one TtyOutput fd can be passed. Index=%lu", i);
*TtyOutput = &Fds[i];
}
else
{
foundNonTtyFd = true;
}
}
THROW_HR_IF_MSG(
E_INVALIDARG, foundNonTtyFd && (*TtyOutput != nullptr || *TtyInput != nullptr), "Found mixed tty & non tty fds");
return !foundNonTtyFd && FdCount > 0;
}

View File

@ -0,0 +1,81 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LSWVirtualMachine.h
Abstract:
TODO
--*/
#pragma once
#include "wslservice.h"
#include "INetworkingEngine.h"
#include "hcs.hpp"
#include "Dmesg.h"
namespace wsl::windows::service::lsw {
class DECLSPEC_UUID("0CFC5DC1-B6A7-45FC-8034-3FA9ED73CE30") LSWVirtualMachine
: public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, ILSWVirtualMachine, IFastRundown>
{
public:
LSWVirtualMachine(const VIRTUAL_MACHINE_SETTINGS& Settings, PSID Sid);
~LSWVirtualMachine();
void Start();
IFACEMETHOD(AttachDisk(_In_ PCWSTR Path, _In_ BOOL ReadOnly, _Out_ LPSTR* Device)) override;
IFACEMETHOD(Mount(_In_ LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags)) override;
IFACEMETHOD(CreateLinuxProcess(
_In_ const LSW_CREATE_PROCESS_OPTIONS* Options, _In_ ULONG FdCount, _In_ LSW_PROCESS_FD* Fd, _Out_ HANDLE* Handles, _Out_ LSW_CREATE_PROCESS_RESULT* Result)) 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;
IFACEMETHOD(RegisterCallback(_In_ ITerminationCallback* callback)) override;
IFACEMETHOD(GetDebugShellPipe(_Out_ LPWSTR* pipePath)) override;
private:
static void CALLBACK s_OnExit(_In_ HCS_EVENT* Event, _In_opt_ void* Context);
static bool ParseTtyInformation(const LSW_PROCESS_FD* Fds, ULONG FdCount, const LSW_PROCESS_FD** TtyInput, const LSW_PROCESS_FD** TtyOutput);
void ConfigureNetworking();
void OnExit(_In_ const HCS_EVENT* Event);
std::tuple<int32_t, int32_t, wsl::shared::SocketChannel> Fork(enum LSW_FORK::ForkType Type);
std::tuple<int32_t, int32_t, wsl::shared::SocketChannel> Fork(wsl::shared::SocketChannel& Channel, enum LSW_FORK::ForkType Type);
int32_t ExpectClosedChannelOrError(wsl::shared::SocketChannel& Channel);
wil::unique_socket ConnectSocket(wsl::shared::SocketChannel& Channel, int32_t Fd);
struct AttachedDisk
{
std::filesystem::path Path;
std::string Device;
};
VIRTUAL_MACHINE_SETTINGS m_settings;
GUID m_vmId{};
std::wstring m_vmIdString;
wsl::windows::common::helpers::WindowsVersion m_windowsVersion = wsl::windows::common::helpers::GetWindowsVersion();
int m_coldDiscardShiftSize{};
bool m_running = false;
PSID m_userSid{};
std::wstring m_debugShellPipe;
wsl::windows::common::hcs::unique_hcs_system m_computeSystem;
std::shared_ptr<DmesgCollector> m_dmesgCollector;
wil::unique_event m_vmExitEvent{wil::EventOptions::ManualReset};
wil::unique_event m_vmTerminatingEvent{wil::EventOptions::ManualReset};
wil::com_ptr<ITerminationCallback> m_terminationCallback;
std::unique_ptr<wsl::core::INetworkingEngine> m_networkEngine;
wsl::shared::SocketChannel m_initChannel;
std::map<ULONG, AttachedDisk> m_attachedDisks;
std::mutex m_lock;
};
} // namespace wsl::windows::service::lsw

View File

@ -18,6 +18,7 @@ Abstract:
#include "WslCoreFilesystem.h"
#include "LxssIpTables.h"
#include "LxssUserSessionFactory.h"
#include "LSWUserSessionFactory.h"
#include <ctime>
using namespace wsl::windows::common::registry;
@ -30,6 +31,7 @@ wil::unique_event g_networkingReady{wil::EventOptions::ManualReset};
// Declare the LxssUserSession COM class.
CoCreatableClassWrlCreatorMapInclude(LxssUserSession);
CoCreatableClassWrlCreatorMapInclude(LSWUserSession);
struct WslServiceSecurityPolicy
{

View File

@ -297,7 +297,7 @@ void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken
{
bool enableTelemetry = TraceLoggingProviderEnabled(g_hTraceLoggingProvider, WINEVENT_LEVEL_INFO, 0);
m_dmesgCollector = DmesgCollector::Create(
VmId, m_vmExitEvent, enableTelemetry, m_vmConfig.EnableDebugConsole, m_comPipe0, m_vmConfig.EnableEarlyBootLogging);
VmId, m_vmExitEvent, enableTelemetry, m_vmConfig.EnableDebugConsole, m_comPipe0, m_vmConfig.EnableEarlyBootLogging, {});
WSL_LOG("DMESG collector created");

View File

@ -160,6 +160,8 @@ cpp_quote("const GUID CLSID_LxssUserSessionInBox = {0x4f476546, 0xb412, 0x4579,
cpp_quote("#ifdef __cplusplus")
cpp_quote("class DECLSPEC_UUID(\"a9b7a1b9-0671-405c-95f1-e0612cb4ce7e\") LxssUserSession;")
cpp_quote("class DECLSPEC_UUID(\"4f476546-b412-4579-b64c-123df331e3d6\") LxssUserSessionInBox;")
cpp_quote("class DECLSPEC_UUID(\"a9b7a1b9-0671-405c-95f1-e0612cb4ce8f\") LSWUserSession;")
cpp_quote("#endif")
[
@ -395,3 +397,88 @@ cpp_quote("#define WSL_E_DISK_CORRUPTED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_IT
cpp_quote("#define WSL_E_DISTRIBUTION_NAME_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x30) /* 0x80040330 */")
cpp_quote("#define WSL_E_INVALID_JSON MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x31) /* 0x80040331 */")
cpp_quote("#define WSL_E_VM_CRASHED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x32) /* 0x80040332 */")
typedef
struct _WSL_VERSION {
ULONG Major;
ULONG Minor;
ULONG Revision;
} WSL_VERSION;
typedef [system_handle(sh_socket)] HANDLE HVSOCKET_HANDLE;
typedef
struct _LSW_CREATE_PROCESS_OPTIONS {
[string] LPCSTR Executable;
ULONG CommandLineCount;
[unique, size_is(CommandLineCount)] LPCSTR* CommandLine;
ULONG EnvironmentCount;
[unique, size_is(EnvironmentCount)] LPCSTR* Environment;
[unique] LPCSTR CurrentDirectory;
} LSW_CREATE_PROCESS_OPTIONS;
typedef struct _LSW_PROCESS_FD
{
LONG Fd;
int Type;
} LSW_PROCESS_FD;
typedef
struct _LSW_CREATE_PROCESS_RESULT {
int Errno;
int Pid;
} LSW_CREATE_PROCESS_RESULT;
[
uuid(7BC4E198-6531-4FA6-ADE2-5EF3D2A04DFE),
pointer_default(unique),
object
]
interface ITerminationCallback : IUnknown
{
HRESULT OnTermination(ULONG Reason, LPCWSTR Details);
};
[
uuid(82A7ABC8-6B50-43FC-AB96-15FBBE7E8761),
pointer_default(unique),
object
]
interface ILSWVirtualMachine : IUnknown
{
HRESULT AttachDisk([in] LPCWSTR Path, [in] BOOL ReadOnly, [out] LPSTR* Device);
HRESULT Mount([in, unique] LPCSTR Source, [in] LPCSTR Target, [in] LPCSTR Type, [in] LPCSTR Options, [in] ULONG Flags);
HRESULT CreateLinuxProcess([in] const LSW_CREATE_PROCESS_OPTIONS* Options, [in] ULONG FdCount, [in, unique, size_is(FdCount)] LSW_PROCESS_FD* Fds, [out, size_is(FdCount)] HVSOCKET_HANDLE* Handles, [out] LSW_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);
}
typedef
struct _VIRTUAL_MACHINE_SETTINGS {
LPCWSTR DisplayName;
ULONGLONG MemoryMb;
ULONG CpuCount;
ULONG BootTimeoutMs;
ULONG DmesgOutput;
ULONG NetworkingMode;
BOOL EnableDnsTunneling;
BOOL EnableDebugShell;
} VIRTUAL_MACHINE_SETTINGS;
[
uuid(82A7ABC8-6B50-43FC-AB96-15FBBE7E8760),
pointer_default(unique),
object
]
interface ILSWUserSession : IUnknown
{
HRESULT GetVersion([out] WSL_VERSION* Error);
HRESULT CreateVirtualMachine([in] const VIRTUAL_MACHINE_SETTINGS* Settings, [out]ILSWVirtualMachine** VirtualMachine);
}

View File

@ -38,6 +38,8 @@ try
wslrelay::RelayMode mode{wslrelay::RelayMode::Invalid};
wil::unique_handle pipe{};
wil::unique_handle exitEvent{};
wil::unique_handle terminalInputHandle{};
wil::unique_handle terminalOutputHandle{};
int port{};
GUID vmId{};
bool disableTelemetry = !wsl::shared::OfficialBuild;
@ -50,6 +52,8 @@ try
parser.AddArgument(Handle{exitEvent}, wslrelay::exit_event_option);
parser.AddArgument(Integer{port}, wslrelay::port_option);
parser.AddArgument(disableTelemetry, wslrelay::disable_telemetry_option);
parser.AddArgument(Handle{terminalInputHandle}, wslrelay::input_option);
parser.AddArgument(Handle{terminalOutputHandle}, wslrelay::output_option);
parser.Parse();
// Initialize logging.
@ -125,6 +129,32 @@ try
break;
}
case wslrelay::RelayMode::InteractiveConsoleRelay:
{
AllocConsole();
THROW_HR_IF(E_INVALIDARG, !terminalInputHandle || !terminalOutputHandle);
// Create a thread to relay stdin to the pipe.
wsl::windows::common::SvcCommIo Io;
auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset);
std::thread inputThread([&]() {
wsl::windows::common::RelayStandardInput(GetStdHandle(STD_INPUT_HANDLE), terminalInputHandle.get(), {}, exitEvent.get(), &Io);
});
auto joinThread = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
exitEvent.SetEvent();
inputThread.join();
});
// Relay the contents of the pipe to stdout.
wsl::windows::common::relay::InterruptableRelay(terminalOutputHandle.get(), GetStdHandle(STD_OUTPUT_HANDLE));
// TODO: watch process exit code.
break;
}
default:
THROW_HR(E_INVALIDARG);
}

View File

@ -8,7 +8,8 @@ set(SOURCES
Common.cpp
PluginTests.cpp
PolicyTests.cpp
InstallerTests.cpp)
InstallerTests.cpp
LSWTests.cpp)
set(HEADERS
Common.h
@ -17,11 +18,14 @@ set(HEADERS
add_compile_definitions(INLINE_TEST_METHOD_MARKUP)
include_directories(${CMAKE_SOURCE_DIR}/src/windows/lswclient)
add_library(wsltests SHARED ${SOURCES} ${HEADERS})
target_link_directories(wsltests PRIVATE ${BIN})
target_precompile_headers(wsltests REUSE_FROM common)
target_link_libraries(wsltests
common
lswclient
${TAEF_LINK_LIBRARIES}
${COMMON_LINK_LIBRARIES}
VirtDisk.lib
@ -29,5 +33,5 @@ target_link_libraries(wsltests
Dbghelp.lib
sfc.lib)
add_dependencies(wsltests wslserviceidl)
add_dependencies(wsltests wslserviceidl lswclient)
add_subdirectory(testplugin)

408
test/windows/LSWTests.cpp Normal file
View File

@ -0,0 +1,408 @@
/*++
Copyright (c) Microsoft. All rights reserved.
Module Name:
LSWTests.cpp
Abstract:
This file contains test cases for the LSW API.
--*/
#include "precomp.h"
#include "Common.h"
#include "LSWApi.h"
using namespace wsl::windows::common::registry;
class LSWTests
{
WSL_TEST_CLASS(LSWTests)
wil::unique_couninitialize_call coinit = wil::CoInitializeEx();
WSADATA Data;
TEST_CLASS_SETUP(TestClassSetup)
{
THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &Data));
return true;
}
TEST_CLASS_CLEANUP(TestClassCleanup)
{
return true;
}
TEST_METHOD(GetVersion)
{
auto coinit = wil::CoInitializeEx();
WSL_VERSION_INFORMATION version{};
VERIFY_SUCCEEDED(WslGetVersion(&version));
VERIFY_ARE_EQUAL(version.Major, WSL_PACKAGE_VERSION_MAJOR);
VERIFY_ARE_EQUAL(version.Minor, WSL_PACKAGE_VERSION_MINOR);
VERIFY_ARE_EQUAL(version.Revision, WSL_PACKAGE_VERSION_REVISION);
}
int RunCommand(LSWVirtualMachineHandle vm, const std::vector<const char*>& command)
{
auto copiedCommand = command;
if (copiedCommand.back() != nullptr)
{
copiedCommand.push_back(nullptr);
}
std::vector<ProcessFileDescriptorSettings> fds(3);
fds[0].Number = 0;
fds[1].Number = 1;
fds[2].Number = 2;
CreateProcessSettings createProcessSettings{};
createProcessSettings.Executable = copiedCommand[0];
createProcessSettings.Arguments = copiedCommand.data();
createProcessSettings.FileDescriptors = fds.data();
createProcessSettings.FdCount = 3;
int pid = -1;
VERIFY_SUCCEEDED(WslCreateLinuxProcess(vm, &createProcessSettings, &pid));
WaitResult result{};
VERIFY_SUCCEEDED(WslWaitForLinuxProcess(vm, pid, 1000, &result));
VERIFY_ARE_EQUAL(result.State, ProcessStateExited);
return result.Code;
}
LSWVirtualMachineHandle CreateVm(const VirtualMachineSettings* settings)
{
LSWVirtualMachineHandle vm{};
VERIFY_SUCCEEDED(WslCreateVirtualMachine(settings, (LSWVirtualMachineHandle*)&vm));
#ifdef WSL_SYSTEM_DISTRO_PATH
std::wstring systemdDistroDiskPath = TEXT(WSL_SYSTEM_DISTRO_PATH);
#else
auto systemdDistroDiskPath = std::format(L"{}/system.vhd", wsl::windows::common::wslutil::GetMsiPackagePath().value());
#endif
DiskAttachSettings attachSettings{systemdDistroDiskPath.c_str(), true};
AttachedDiskInformation attachedDisk;
VERIFY_SUCCEEDED(WslAttachDisk(vm, &attachSettings, &attachedDisk));
MountSettings mountSettings{attachedDisk.Device, "/mnt", "ext4", "ro", MountFlagsChroot | MountFlagsWriteableOverlayFs};
VERIFY_SUCCEEDED(WslMount(vm, &mountSettings));
MountSettings devmountSettings{nullptr, "/dev", "devtmpfs", "", false};
VERIFY_SUCCEEDED(WslMount(vm, &devmountSettings));
MountSettings sysmountSettings{nullptr, "/sys", "sysfs", "", false};
VERIFY_SUCCEEDED(WslMount(vm, &sysmountSettings));
MountSettings procmountSettings{nullptr, "/proc", "proc", "", false};
VERIFY_SUCCEEDED(WslMount(vm, &procmountSettings));
MountSettings ptsMountSettings{nullptr, "/dev/pts", "devpts", "noatime,nosuid,noexec,gid=5,mode=620", false};
VERIFY_SUCCEEDED(WslMount(vm, &ptsMountSettings));
return vm;
}
TEST_METHOD(CustomDmesgOutput)
{
auto [read, write] = CreateSubprocessPipe(false, false);
VirtualMachineSettings settings{};
settings.CPU.CpuCount = 4;
settings.DisplayName = L"LSW";
settings.Memory.MemoryMb = 1024;
settings.Options.BootTimeoutMs = 30000;
settings.Options.Dmesg = write.get();
std::vector<char> dmesgContent;
auto readDmesg = [&]() {
DWORD Offset = 0;
constexpr auto bufferSize = 1024;
while (true)
{
dmesgContent.resize(Offset + bufferSize);
DWORD Read{};
if (!ReadFile(read.get(), &dmesgContent[Offset], bufferSize, &Read, nullptr))
{
LogInfo("ReadFile() failed: %lu", GetLastError());
}
if (Read == 0)
{
break;
}
Offset += Read;
}
};
std::thread thread(readDmesg);
auto vm = CreateVm(&settings);
write.reset();
auto detach = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
WslReleaseVirtualMachine(vm);
if (thread.joinable())
{
thread.join();
}
});
std::vector<const char*> cmd = {"/bin/bash", "-c", "echo DmesgTest > /dev/kmsg"};
VERIFY_ARE_EQUAL(RunCommand(vm, cmd), 0);
VERIFY_ARE_EQUAL(WslShutdownVirtualMachine(vm, 30 * 1000), S_OK);
detach.reset();
auto contentString = std::string(dmesgContent.begin(), dmesgContent.end());
VERIFY_ARE_NOT_EQUAL(contentString.find("Run /init as init process"), std::string::npos);
VERIFY_ARE_NOT_EQUAL(contentString.find("DmesgTest"), std::string::npos);
}
TEST_METHOD(TerminationCallback)
{
std::promise<std::pair<VirtualMachineTerminationReason, std::wstring>> callbackInfo;
auto callback = [](void* context, VirtualMachineTerminationReason reason, LPCWSTR details) -> HRESULT {
auto* future = reinterpret_cast<std::promise<std::pair<VirtualMachineTerminationReason, std::wstring>>*>(context);
future->set_value(std::make_pair(reason, details));
return S_OK;
};
VirtualMachineSettings settings{};
settings.CPU.CpuCount = 4;
settings.DisplayName = L"LSW";
settings.Memory.MemoryMb = 1024;
settings.Options.BootTimeoutMs = 30000;
settings.Options.TerminationCallback = callback;
settings.Options.TerminationContext = &callbackInfo;
auto vm = CreateVm(&settings);
VERIFY_SUCCEEDED(WslShutdownVirtualMachine(vm, 30 * 1000));
auto future = callbackInfo.get_future();
auto result = future.wait_for(std::chrono::seconds(10));
auto [reason, details] = future.get();
VERIFY_ARE_EQUAL(reason, VirtualMachineTerminationReasonShutdown);
VERIFY_ARE_NOT_EQUAL(details, L"");
WslReleaseVirtualMachine(vm);
}
TEST_METHOD(CreateVmSmokeTest)
{
VirtualMachineSettings settings{};
settings.CPU.CpuCount = 4;
settings.DisplayName = L"LSW";
settings.Memory.MemoryMb = 1024;
settings.Options.BootTimeoutMs = 30000;
auto vm = CreateVm(&settings);
// Create a process and wait for it to exit
{
std::vector<const char*> commandLine{"/bin/sh", "-c", "echo $bar", nullptr};
std::vector<ProcessFileDescriptorSettings> fds(3);
fds[0].Number = 0;
fds[1].Number = 1;
fds[2].Number = 2;
std::vector<const char*> env{"bar=foo", nullptr};
CreateProcessSettings createProcessSettings{};
createProcessSettings.Executable = "/bin/sh";
createProcessSettings.Arguments = commandLine.data();
createProcessSettings.FileDescriptors = fds.data();
createProcessSettings.Environment = env.data();
createProcessSettings.FdCount = 3;
int pid = -1;
VERIFY_SUCCEEDED(WslCreateLinuxProcess(vm, &createProcessSettings, &pid));
LogInfo("pid: %lu", pid);
std::vector<char> buffer(100);
DWORD bytes{};
if (!ReadFile(createProcessSettings.FileDescriptors[1].Handle, buffer.data(), (DWORD)buffer.size(), &bytes, nullptr))
{
LogError("ReadFile: %lu, handle: 0x%x", GetLastError(), createProcessSettings.FileDescriptors[1].Handle);
VERIFY_FAIL();
}
VERIFY_ARE_EQUAL(buffer.data(), std::string("foo\n"));
WaitResult result{};
VERIFY_SUCCEEDED(WslWaitForLinuxProcess(vm, pid, 1000, &result));
VERIFY_ARE_EQUAL(result.State, ProcessStateExited);
VERIFY_ARE_EQUAL(result.Code, 0);
}
// Create a 'stuck' process and kill it
{
std::vector<const char*> commandLine{"/usr/bin/sleep", "100000", nullptr};
std::vector<ProcessFileDescriptorSettings> fds(3);
fds[0].Number = 0;
fds[1].Number = 1;
fds[2].Number = 2;
CreateProcessSettings createProcessSettings{};
createProcessSettings.Executable = commandLine[0];
createProcessSettings.Arguments = commandLine.data();
createProcessSettings.FileDescriptors = fds.data();
createProcessSettings.Environment = nullptr;
createProcessSettings.FdCount = 3;
int pid = -1;
VERIFY_SUCCEEDED(WslCreateLinuxProcess(vm, &createProcessSettings, &pid));
// Verify that the process is in a running state
WaitResult result{};
VERIFY_SUCCEEDED(WslWaitForLinuxProcess(vm, pid, 1000, &result));
VERIFY_ARE_EQUAL(result.State, ProcessStateRunning);
// Verify that it can be killed.
VERIFY_SUCCEEDED(WslSignalLinuxProcess(vm, pid, 9));
// Verify that the process is in a running state
VERIFY_SUCCEEDED(WslWaitForLinuxProcess(vm, pid, 1000, &result));
VERIFY_ARE_EQUAL(result.State, ProcessStateSignaled);
VERIFY_ARE_EQUAL(result.Code, 9);
}
// Test various error paths
{
std::vector<const char*> commandLine{"dummy", "100000", nullptr};
std::vector<ProcessFileDescriptorSettings> fds(3);
fds[0].Number = 0;
fds[1].Number = 1;
fds[2].Number = 2;
CreateProcessSettings createProcessSettings{};
createProcessSettings.Executable = commandLine[0];
createProcessSettings.Arguments = commandLine.data();
createProcessSettings.FileDescriptors = fds.data();
createProcessSettings.Environment = nullptr;
createProcessSettings.FdCount = 3;
int pid = -1;
VERIFY_ARE_EQUAL(WslCreateLinuxProcess(vm, &createProcessSettings, &pid), E_FAIL);
WaitResult result{};
VERIFY_ARE_EQUAL(WslWaitForLinuxProcess(vm, 1234, 1000, &result), E_FAIL);
VERIFY_ARE_EQUAL(result.State, ProcessStateUnknown);
}
WslReleaseVirtualMachine(vm);
}
TEST_METHOD(InteractiveShell)
{
VirtualMachineSettings settings{};
settings.CPU.CpuCount = 4;
settings.DisplayName = L"LSW";
settings.Memory.MemoryMb = 2048;
settings.Options.BootTimeoutMs = 30 * 1000;
settings.Options.EnableDebugShell = true;
settings.Networking.Mode = NetworkingModeNone;
auto vm = CreateVm(&settings);
std::vector<const char*> commandLine{"/bin/sh", nullptr};
std::vector<ProcessFileDescriptorSettings> fds(2);
fds[0].Number = 0;
fds[0].Type = TerminalInput;
fds[1].Number = 1;
fds[1].Type = TerminalOutput;
CreateProcessSettings createProcessSettings{};
createProcessSettings.Executable = "/bin/sh";
createProcessSettings.Arguments = commandLine.data();
createProcessSettings.FileDescriptors = fds.data();
createProcessSettings.FdCount = static_cast<ULONG>(fds.size());
int pid = -1;
VERIFY_SUCCEEDED(WslCreateLinuxProcess(vm, &createProcessSettings, &pid));
auto validateTtyOutput = [&](const std::string& expected) {
std::string buffer(expected.size(), '\0');
DWORD offset = 0;
while (offset < buffer.size())
{
DWORD bytesRead{};
VERIFY_IS_TRUE(ReadFile(
createProcessSettings.FileDescriptors[1].Handle, buffer.data() + offset, static_cast<DWORD>(buffer.size() - offset), &bytesRead, nullptr));
offset += bytesRead;
}
buffer.resize(offset);
VERIFY_ARE_EQUAL(buffer, expected);
};
auto writeTty = [&](const std::string& content) {
VERIFY_IS_TRUE(WriteFile(
createProcessSettings.FileDescriptors[0].Handle, content.data(), static_cast<DWORD>(content.size()), nullptr, nullptr));
};
// Expect the shell prompt to be displayed
validateTtyOutput("sh-5.1#");
writeTty("echo OK\n");
validateTtyOutput(" echo OK\r\nOK");
// Validate that the interactive process successfully starts
wil::unique_handle process;
VERIFY_SUCCEEDED(WslLaunchInteractiveTerminal(
createProcessSettings.FileDescriptors[0].Handle, createProcessSettings.FileDescriptors[1].Handle, &process));
// Exit the shell
writeTty("exit\n");
VERIFY_ARE_EQUAL(WaitForSingleObject(process.get(), 30 * 1000), WAIT_OBJECT_0);
}
TEST_METHOD(NATNetworking)
{
VirtualMachineSettings settings{};
settings.CPU.CpuCount = 4;
settings.DisplayName = L"LSW";
settings.Memory.MemoryMb = 2048;
settings.Options.BootTimeoutMs = 30 * 1000;
settings.Networking.Mode = NetworkingModeNAT;
auto vm = CreateVm(&settings);
// Validate that eth0 has an ip address
VERIFY_ARE_EQUAL(
RunCommand(
vm,
{"/bin/bash",
"-c",
"ip a show dev eth0 | grep -iF 'inet ' | grep -E '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}'"}),
0);
// Verify that /etc/resolv.conf is configured
VERIFY_ARE_EQUAL(RunCommand(vm, {"/bin/grep", "-iF", "nameserver", "/etc/resolv.conf"}), 0);
}
};