From 46db45c39aa791e77cd68ab5e8e7e7f167f79c52 Mon Sep 17 00:00:00 2001 From: Blue Date: Tue, 4 Nov 2025 13:09:20 -0800 Subject: [PATCH] Add a temporary flag to wsl.exe to create a WSLA shell (#13664) * Add a temporary flag to wsl.exe to create a WSLA shell * Improve some error paths * Add dependency --- src/shared/inc/CommandLine.h | 17 ++ src/windows/common/CMakeLists.txt | 3 +- src/windows/common/WslClient.cpp | 167 ++++++++++++++++++++ src/windows/wslaservice/inc/wslaservice.idl | 1 - 4 files changed, 186 insertions(+), 2 deletions(-) diff --git a/src/shared/inc/CommandLine.h b/src/shared/inc/CommandLine.h index dc98122..9966c5e 100644 --- a/src/shared/inc/CommandLine.h +++ b/src/shared/inc/CommandLine.h @@ -187,6 +187,23 @@ struct Handle } }; +struct Utf8String +{ + std::string& Value; + + int operator()(const TChar* Input) const + { + if (Input == nullptr) + { + return -1; + } + + Value = wsl::shared::string::WideToMultiByte(Input); + + return 1; + } +}; + #else struct UniqueFd diff --git a/src/windows/common/CMakeLists.txt b/src/windows/common/CMakeLists.txt index 150ee09..e451d5e 100644 --- a/src/windows/common/CMakeLists.txt +++ b/src/windows/common/CMakeLists.txt @@ -119,8 +119,9 @@ set(HEADERS wslutil.cpp ) +include_directories(${CMAKE_SOURCE_DIR}/src/windows/wslaclient) add_library(common STATIC ${SOURCES} ${HEADERS}) -add_dependencies(common wslserviceidl localization wslservicemc wslinstalleridl) +add_dependencies(common wslserviceidl localization wslservicemc wslinstalleridl wslaserviceproxystub) target_precompile_headers(common PRIVATE precomp.h) set_target_properties(common PROPERTIES FOLDER windows) diff --git a/src/windows/common/WslClient.cpp b/src/windows/common/WslClient.cpp index 26cfdfc..cbb9823 100644 --- a/src/windows/common/WslClient.cpp +++ b/src/windows/common/WslClient.cpp @@ -18,6 +18,8 @@ Abstract: #include "Distribution.h" #include "CommandLine.h" #include +#include "wslaservice.h" +#include "WSLAApi.h" #define BASH_PATH L"/bin/bash" @@ -1539,6 +1541,167 @@ int RunDebugShell() THROW_HR(HCS_E_CONNECTION_CLOSED); } +// Temporary debugging tool for WSLA +int WslaShell(_In_ std::wstring_view commandLine) +{ +#ifdef WSL_SYSTEM_DISTRO_PATH + + std::wstring vhd = TEXT(WSL_SYSTEM_DISTRO_PATH); + +#else + + std::wstring vhd = wsl::windows::common::wslutil::GetMsiPackagePath().value() + L"/system.vhd"; + +#endif + + VIRTUAL_MACHINE_SETTINGS settings{}; + settings.CpuCount = 4; + settings.DisplayName = L"WSLA"; + settings.MemoryMb = 1024; + settings.BootTimeoutMs = 30000; + settings.NetworkingMode = WslNetworkingModeNAT; + std::string shell = "/bin/bash"; + bool help = false; + + ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); + parser.AddArgument(vhd, L"--vhd"); + parser.AddArgument(Utf8String(shell), L"--shell"); + parser.AddArgument(reinterpret_cast(settings.EnableDnsTunneling), L"--dns-tunneling"); + parser.AddArgument(Integer(settings.MemoryMb), L"--memory"); + parser.AddArgument(Integer(settings.CpuCount), L"--cpu"); + parser.AddArgument(help, L"--help"); + + parser.Parse(); + + if (help) + { + const auto usage = std::format( + LR"({} --wsla [--vhd ] [--shell ] [--memory ] [--cpu ] [--dns-tunneling] [--help])", + WSL_BINARY_NAME); + + wprintf(L"%ls\n", usage.c_str()); + return 1; + } + + wil::com_ptr userSession; + THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLAUserSession), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&userSession))); + wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get()); + + wil::com_ptr virtualMachine; + THROW_IF_FAILED(userSession->CreateVirtualMachine(&settings, &virtualMachine)); + wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get()); + + wil::unique_cotaskmem_ansistring diskDevice; + ULONG Lun{}; + THROW_IF_FAILED(virtualMachine->AttachDisk(vhd.c_str(), true, &diskDevice, &Lun)); + + THROW_IF_FAILED(virtualMachine->Mount(diskDevice.get(), "/mnt", "ext4", "ro", WslMountFlagsChroot | WslMountFlagsWriteableOverlayFs)); + THROW_IF_FAILED(virtualMachine->Mount(nullptr, "/dev", "devtmpfs", "", 0)); + THROW_IF_FAILED(virtualMachine->Mount(nullptr, "/sys", "sysfs", "", 0)); + THROW_IF_FAILED(virtualMachine->Mount(nullptr, "/proc", "proc", "", 0)); + THROW_IF_FAILED(virtualMachine->Mount(nullptr, "/dev/pts", "devpts", "noatime,nosuid,noexec,gid=5,mode=620", 0)); + + std::vector shellCommandLine{shell.c_str()}; + std::vector env{"TERM=xterm-256color"}; + + std::vector fds(3); + fds[0].Fd = 0; + fds[0].Type = WslFdTypeTerminalInput; + fds[1].Fd = 1; + fds[1].Type = WslFdTypeTerminalOutput; + fds[2].Fd = 2; + fds[2].Type = WslFdTypeTerminalControl; + + WSLA_CREATE_PROCESS_OPTIONS processOptions{}; + processOptions.Executable = shell.c_str(); + processOptions.CommandLine = shellCommandLine.data(); + processOptions.CommandLineCount = static_cast(shellCommandLine.size()); + processOptions.Environment = env.data(); + processOptions.EnvironmentCount = static_cast(env.size()); + processOptions.CurrentDirectory = "/"; + + std::vector handles(fds.size()); + + WSLA_CREATE_PROCESS_RESULT result{}; + auto createProcessResult = + virtualMachine->CreateLinuxProcess(&processOptions, static_cast(fds.size()), fds.data(), handles.data(), &result); + + if (FAILED(createProcessResult)) + { + if (result.Errno != 0) + { + THROW_HR_WITH_USER_ERROR(E_FAIL, std::format(L"Failed to create process {}, errno = {}", shell.c_str(), result.Errno)); + } + else + { + THROW_HR(createProcessResult); + } + } + + // Configure console for interactive usage. + + HANDLE Stdout = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE Stdin = GetStdHandle(STD_INPUT_HANDLE); + { + DWORD OutputMode{}; + THROW_LAST_ERROR_IF(!::GetConsoleMode(Stdout, &OutputMode)); + + WI_SetAllFlags(OutputMode, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN); + THROW_IF_WIN32_BOOL_FALSE(SetConsoleMode(Stdout, OutputMode)); + } + + { + DWORD InputMode{}; + THROW_LAST_ERROR_IF(!::GetConsoleMode(Stdin, &InputMode)); + + WI_SetAllFlags(InputMode, (ENABLE_WINDOW_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT)); + WI_ClearAllFlags(InputMode, (ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)); + THROW_IF_WIN32_BOOL_FALSE(SetConsoleMode(Stdin, InputMode)); + } + + THROW_LAST_ERROR_IF(!::SetConsoleOutputCP(CP_UTF8)); + + { + // Create a thread to relay stdin to the pipe. + auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset); + + wsl::shared::SocketChannel controlChannel{wil::unique_socket(handles[2]), "TerminalControl", exitEvent.get()}; + + std::thread inputThread([&]() { + auto updateTerminal = [&controlChannel, &Stdout]() { + CONSOLE_SCREEN_BUFFER_INFOEX info{}; + info.cbSize = sizeof(info); + + THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfoEx(Stdout, &info)); + + WSLA_TERMINAL_CHANGED message{}; + message.Columns = info.srWindow.Right - info.srWindow.Left + 1; + message.Rows = info.srWindow.Bottom - info.srWindow.Top + 1; + + controlChannel.SendMessage(message); + }; + + wsl::windows::common::relay::StandardInputRelay(Stdin, UlongToHandle(handles[0]), updateTerminal, exitEvent.get()); + }); + + 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(UlongToHandle(handles[1]), Stdout); + } + + ULONG exitState{}; + int exitCode{}; + THROW_IF_FAILED(virtualMachine->WaitPid(result.Pid, 0, &exitState, &exitCode)); + + wprintf(L"%hs exited with: %i", shell.c_str(), exitCode); + + return exitCode; +} + int WslMain(_In_ std::wstring_view commandLine) { // Call the MSI package if we're in an MSIX context @@ -1772,6 +1935,10 @@ int WslMain(_In_ std::wstring_view commandLine) { return Uninstall(); } + else if (argument == L"--wsla") + { + return WslaShell(commandLine); + } else { if ((argument.size() > 0) && (argument[0] == L'-')) diff --git a/src/windows/wslaservice/inc/wslaservice.idl b/src/windows/wslaservice/inc/wslaservice.idl index 177a09e..3698719 100644 --- a/src/windows/wslaservice/inc/wslaservice.idl +++ b/src/windows/wslaservice/inc/wslaservice.idl @@ -99,7 +99,6 @@ struct _VIRTUAL_MACHINE_SETTINGS { BOOL EnableDebugShell; BOOL EnableEarlyBootDmesg; BOOL EnableGPU; - BOOL EnableDnsTunnelling; } VIRTUAL_MACHINE_SETTINGS;