mirror of
https://github.com/microsoft/WSL.git
synced 2025-12-10 17:47:59 -06:00
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:
parent
95fcc56983
commit
b5769b4f97
2
.gitignore
vendored
2
.gitignore
vendored
@ -43,7 +43,7 @@ bin/
|
||||
*.nupkg
|
||||
build/
|
||||
generated/
|
||||
Microsoft.WSL.PluginApi.nuspec
|
||||
*.nuspec
|
||||
test/linux/unit_tests/wsl_unit_tests
|
||||
*.dir/
|
||||
UserConfig.cmake
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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)
|
||||
|
||||
21
nuget/Microsoft.WSL.API.nuspec.in
Normal file
21
nuget/Microsoft.WSL.API.nuspec.in
Normal 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>
|
||||
@ -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);
|
||||
|
||||
@ -20,7 +20,8 @@ set(SOURCES
|
||||
util.cpp
|
||||
WslDistributionConfig.cpp
|
||||
wslinfo.cpp
|
||||
wslpath.cpp)
|
||||
wslpath.cpp
|
||||
LSWInit.cpp)
|
||||
|
||||
set(HEADERS
|
||||
../inc/lxwil.h
|
||||
|
||||
@ -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
690
src/linux/init/LSWInit.cpp
Normal 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;
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
/*++
|
||||
|
||||
@ -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 = {});
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 << "{";
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
7
src/windows/lswclient/CMakeLists.txt
Normal file
7
src/windows/lswclient/CMakeLists.txt
Normal 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)
|
||||
283
src/windows/lswclient/DllMain.cpp
Normal file
283
src/windows/lswclient/DllMain.cpp
Normal 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;
|
||||
}
|
||||
172
src/windows/lswclient/LSWApi.h
Normal file
172
src/windows/lswclient/LSWApi.h
Normal 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
|
||||
14
src/windows/lswclient/lswclient.def
Normal file
14
src/windows/lswclient/lswclient.def
Normal file
@ -0,0 +1,14 @@
|
||||
LIBRARY lswclient
|
||||
|
||||
EXPORTS
|
||||
WslGetVersion
|
||||
WslCreateVirtualMachine
|
||||
WslAttachDisk
|
||||
WslMount
|
||||
WslCreateLinuxProcess
|
||||
WslWaitForLinuxProcess
|
||||
WslSignalLinuxProcess
|
||||
WslReleaseVirtualMachine
|
||||
WslShutdownVirtualMachine
|
||||
WslLaunchInteractiveTerminal
|
||||
WslLaunchDebugShell
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
56
src/windows/service/exe/LSWUserSession.cpp
Normal file
56
src/windows/service/exe/LSWUserSession.cpp
Normal 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();
|
||||
45
src/windows/service/exe/LSWUserSession.h
Normal file
45
src/windows/service/exe/LSWUserSession.h
Normal 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
|
||||
71
src/windows/service/exe/LSWUserSessionFactory.cpp
Normal file
71
src/windows/service/exe/LSWUserSessionFactory.cpp
Normal 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;
|
||||
}
|
||||
26
src/windows/service/exe/LSWUserSessionFactory.h
Normal file
26
src/windows/service/exe/LSWUserSessionFactory.h
Normal 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
|
||||
659
src/windows/service/exe/LSWVirtualMachine.cpp
Normal file
659
src/windows/service/exe/LSWVirtualMachine.cpp
Normal 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;
|
||||
}
|
||||
81
src/windows/service/exe/LSWVirtualMachine.h
Normal file
81
src/windows/service/exe/LSWVirtualMachine.h
Normal 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
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
408
test/windows/LSWTests.cpp
Normal 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);
|
||||
}
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user