| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | svccomm.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains function definitions for the SvcComm helper class. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #include "precomp.h" |
| 16 | #include "svccomm.hpp" |
| 17 | #include "registry.hpp" |
| 18 | #include "relay.hpp" |
| 19 | |
| 20 | #pragma hdrstop |
| 21 | |
| 22 | // |
| 23 | // Macros to test exit status (defined in sys\wait.h). |
| 24 | // |
| 25 | |
| 26 | #define LXSS_WEXITSTATUS(_status) ((_status) >> 8) |
| 27 | #define LXSS_WSTATUS(_status) ((_status) & 0x7F) |
| 28 | #define LXSS_WIFEXITED(_status) (LXSS_WSTATUS((_status)) == 0) |
| 29 | |
| 30 | #define IS_VALID_HANDLE(_handle) ((_handle != NULL) && (_handle != INVALID_HANDLE_VALUE)) |
| 31 | |
| 32 | using wsl::windows::common::ClientExecutionContext; |
| 33 | namespace { |
| 34 | |
| 35 | BOOL GetNextCharacter(_In_ INPUT_RECORD* InputRecord, _Out_ PWCHAR NextCharacter); |
| 36 | BOOL IsActionableKey(_In_ PKEY_EVENT_RECORD KeyEvent); |
| 37 | void SpawnWslHost(_In_ HANDLE ServerPort, _In_ const GUID& DistroId, _In_opt_ LPCGUID VmId); |
| 38 | |
| 39 | struct CreateProcessArguments |
| 40 | { |
| 41 | CreateProcessArguments(LPCWSTR Filename, int Argc, LPCWSTR Argv[], ULONG LaunchFlags, LPCWSTR WorkingDirectory) |
| 42 | { |
| 43 | // Populate the current working directory. |
| 44 | // |
| 45 | // N.B. Failure to get the current working directory is non-fatal. |
| 46 | if (ARGUMENT_PRESENT(WorkingDirectory)) |
| 47 | { |
| 48 | // If a current working directory was provided, it must be a Linux-style path. |
| 49 | WI_ASSERT(*WorkingDirectory == L'/' || *WorkingDirectory == L'~'); |
| 50 | |
| 51 | CurrentWorkingDirectory = WorkingDirectory; |
| 52 | } |
| 53 | else |
| 54 | { |
| 55 | LOG_IF_FAILED(wil::GetCurrentDirectoryW(CurrentWorkingDirectory)); |
| 56 | } |
| 57 | |
| 58 | // Populate the command line and file name. |
| 59 | // |
| 60 | // N.B. The CommandLineStrings vector contains weak references to the |
| 61 | // strings in the CommandLine vector. |
| 62 | if (Argc > 0) |
| 63 | { |
| 64 | CommandLine.reserve(Argc); |
| 65 | std::transform(Argv, Argv + Argc, std::back_inserter(CommandLine), [](LPCWSTR Arg) { |
| 66 | return wsl::shared::string::WideToMultiByte(Arg); |
| 67 | }); |
| 68 | |
| 69 | CommandLineStrings.reserve(CommandLine.size()); |
| 70 | std::transform(CommandLine.cbegin(), CommandLine.cend(), std::back_inserter(CommandLineStrings), [](const std::string& string) { |
| 71 | return string.c_str(); |
| 72 | }); |
| 73 | } |
| 74 | |
| 75 | if (ARGUMENT_PRESENT(Filename)) |
| 76 | { |
| 77 | FilenameString = wsl::shared::string::WideToMultiByte(Filename); |
| 78 | } |
| 79 | |
| 80 | // Query the current NT %PATH% environment variable. |
| 81 | // |
| 82 | // N.B. Failure to query the path is non-fatal. |
| 83 | LOG_IF_FAILED(wil::ExpandEnvironmentStringsW(L"%PATH%", NtPath)); |
| 84 | |
| 85 | if (WI_IsFlagSet(LaunchFlags, LXSS_LAUNCH_FLAG_TRANSLATE_ENVIRONMENT)) |
| 86 | { |
| 87 | NtEnvironment.reset(GetEnvironmentStringsW()); |
| 88 | |
| 89 | // Calculate the size of the environment block. |
| 90 | for (PCWSTR Variable = NtEnvironment.get(); Variable[0] != '\0';) |
| 91 | { |
| 92 | const size_t Length = wcslen(Variable) + 1; |
| 93 | NtEnvironmentLength += Length; |
| 94 | Variable += Length; |
| 95 | } |
| 96 | |
| 97 | NtEnvironmentLength += 1; |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | std::vector<std::string> CommandLine{}; |
| 102 | std::vector<LPCSTR> CommandLineStrings{}; |
| 103 | std::wstring CurrentWorkingDirectory{}; |
| 104 | std::string FilenameString{}; |
| 105 | wsl::windows::common::helpers::unique_environment_strings NtEnvironment; |
| 106 | size_t NtEnvironmentLength{}; |
| 107 | std::wstring NtPath{}; |
| 108 | }; |
| 109 | |
| 110 | void InitializeInterop(_In_ HANDLE ServerPort, _In_ const GUID& DistroId) |
| 111 | { |
| 112 | // |
| 113 | // Create a thread to handle interop requests. |
| 114 | // |
| 115 | |
| 116 | wil::unique_handle WorkerThreadSeverPort{wsl::windows::common::wslutil::DuplicateHandle(ServerPort)}; |
| 117 | std::thread([WorkerThreadSeverPort = std::move(WorkerThreadSeverPort)]() mutable { |
| 118 | wsl::windows::common::wslutil::SetThreadDescription(L"Interop"); |
| 119 | wsl::windows::common::interop::WorkerThread(std::move(WorkerThreadSeverPort)); |
| 120 | }).detach(); |
| 121 | |
| 122 | // |
| 123 | // Spawn wslhost to handle interop requests from processes that have |
| 124 | // been backgrounded and their console window has been closed. |
| 125 | // |
| 126 | |
| 127 | SpawnWslHost(ServerPort, DistroId, nullptr); |
| 128 | } |
| 129 | |
| 130 | void SpawnWslHost(_In_ HANDLE ServerPort, _In_ const GUID& DistroId, _In_opt_ LPCGUID VmId) |
| 131 | { |
| 132 | wsl::windows::common::helpers::SetHandleInheritable(ServerPort); |
| 133 | const auto RegistrationComplete = wil::unique_event(wil::EventOptions::None); |
| 134 | const wil::unique_handle ParentProcess{wsl::windows::common::wslutil::DuplicateHandle(GetCurrentProcess(), std::nullopt, TRUE)}; |
| 135 | THROW_LAST_ERROR_IF(!ParentProcess); |
| 136 | |
| 137 | const wil::unique_handle Process{wsl::windows::common::helpers::LaunchInteropServer( |
| 138 | &DistroId, ServerPort, RegistrationComplete.get(), ParentProcess.get(), VmId)}; |
| 139 | |
| 140 | // Wait for either the child to exit, or the registration complete event to be set. |
| 141 | const HANDLE WaitHandles[] = {Process.get(), RegistrationComplete.get()}; |
| 142 | const DWORD WaitStatus = WaitForMultipleObjects(RTL_NUMBER_OF(WaitHandles), WaitHandles, FALSE, INFINITE); |
| 143 | LOG_HR_IF_MSG(E_FAIL, (WaitStatus == WAIT_OBJECT_0), "wslhost failed to register"); |
| 144 | } |
| 145 | } // namespace |
| 146 | |
| 147 | // |
| 148 | // Exported function definitions. |
| 149 | // |
| 150 | |
| 151 | wsl::windows::common::SvcComm::SvcComm() |
| 152 | { |
| 153 | // Ensure that the OS has support for running lifted WSL. This interface is always present on Windows 11 and later. |
| 154 | // |
| 155 | // Prior to Windows 11 there are two cases where the IWslSupport interface may not be present: |
| 156 | // 1. The machine has not installed the DCR that contains support for lifted WSL. |
| 157 | // 2. The WSL optional component which contains the interface is not installed. |
| 158 | if (!wsl::windows::common::helpers::IsWindows11OrAbove() && !wsl::windows::common::helpers::IsWslSupportInterfacePresent()) |
| 159 | { |
| 160 | THROW_HR(wsl::windows::common::helpers::IsWslOptionalComponentPresent() ? WSL_E_OS_NOT_SUPPORTED : WSL_E_WSL_OPTIONAL_COMPONENT_REQUIRED); |
| 161 | } |
| 162 | |
| 163 | auto retry_pred = []() { |
| 164 | const auto errorCode = wil::ResultFromCaughtException(); |
| 165 | |
| 166 | return errorCode == HRESULT_FROM_WIN32(ERROR_SERVICE_DOES_NOT_EXIST) || errorCode == REGDB_E_CLASSNOTREG; |
| 167 | }; |
| 168 | |
| 169 | wsl::shared::retry::RetryWithTimeout<void>( |
| 170 | [this]() { m_userSession = wil::CoCreateInstance<LxssUserSession, ILxssUserSession>(CLSCTX_LOCAL_SERVER); }, |
| 171 | std::chrono::seconds(1), |
| 172 | std::chrono::minutes(1), |
| 173 | retry_pred); |
| 174 | |
| 175 | // Query client security interface. |
| 176 | auto clientSecurity = m_userSession.query<IClientSecurity>(); |
| 177 | |
| 178 | // Get the current proxy blanket settings. |
| 179 | DWORD authnSvc, authzSvc, authnLvl, capabilities; |
| 180 | THROW_IF_FAILED(clientSecurity->QueryBlanket(m_userSession.get(), &authnSvc, &authzSvc, NULL, &authnLvl, NULL, NULL, &capabilities)); |
| 181 | |
| 182 | // Make sure that dynamic cloaking is used. |
| 183 | WI_ClearFlag(capabilities, EOAC_STATIC_CLOAKING); |
| 184 | WI_SetFlag(capabilities, EOAC_DYNAMIC_CLOAKING); |
| 185 | THROW_IF_FAILED(clientSecurity->SetBlanket( |
| 186 | m_userSession.get(), authnSvc, authzSvc, NULL, authnLvl, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, capabilities)); |
| 187 | } |
| 188 | |
| 189 | wsl::windows::common::SvcComm::~SvcComm() |
| 190 | { |
| 191 | } |
| 192 | |
| 193 | void wsl::windows::common::SvcComm::ConfigureDistribution(_In_opt_ LPCGUID DistroGuid, _In_ ULONG DefaultUid, _In_ ULONG Flags) const |
| 194 | { |
| 195 | ClientExecutionContext context; |
| 196 | THROW_IF_FAILED(m_userSession->ConfigureDistribution(DistroGuid, DefaultUid, Flags, context.OutError())); |
| 197 | } |
| 198 | |
| 199 | void wsl::windows::common::SvcComm::CreateInstance(_In_opt_ LPCGUID DistroGuid, _In_ ULONG Flags) |
| 200 | { |
| 201 | ClientExecutionContext context; |
| 202 | THROW_IF_FAILED(CreateInstanceNoThrow(DistroGuid, Flags, context.OutError())); |
| 203 | } |
| 204 | |
| 205 | HRESULT |
| 206 | wsl::windows::common::SvcComm::CreateInstanceNoThrow(_In_opt_ LPCGUID DistroGuid, _In_ ULONG Flags, _Out_ LXSS_ERROR_INFO* Error) const |
| 207 | { |
| 208 | return m_userSession->CreateInstance(DistroGuid, Flags, Error); |
| 209 | } |
| 210 | |
| 211 | std::vector<LXSS_ENUMERATE_INFO> wsl::windows::common::SvcComm::EnumerateDistributions() const |
| 212 | { |
| 213 | ExecutionContext enumerateDistroContext(Context::EnumerateDistros); |
| 214 | ClientExecutionContext context; |
| 215 | |
| 216 | wil::unique_cotaskmem_array_ptr<LXSS_ENUMERATE_INFO> Distributions; |
| 217 | THROW_IF_FAILED(m_userSession->EnumerateDistributions(Distributions.size_address<ULONG>(), &Distributions, context.OutError())); |
| 218 | |
| 219 | std::vector<LXSS_ENUMERATE_INFO> DistributionList; |
| 220 | for (size_t Index = 0; Index < Distributions.size(); Index += 1) |
| 221 | { |
| 222 | DistributionList.push_back(Distributions[Index]); |
| 223 | } |
| 224 | |
| 225 | return DistributionList; |
| 226 | } |
| 227 | |
| 228 | HRESULT |
| 229 | wsl::windows::common::SvcComm::ExportDistribution(_In_opt_ LPCGUID DistroGuid, _In_ HANDLE FileHandle, _In_ ULONG Flags) const |
| 230 | { |
| 231 | ClientExecutionContext context; |
| 232 | |
| 233 | // Create a pipe for reading errors from bsdtar. |
| 234 | wil::unique_handle stdErrRead; |
| 235 | wil::unique_handle stdErrWrite; |
| 236 | THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&stdErrRead, &stdErrWrite, nullptr, 0)); |
| 237 | |
| 238 | relay::ScopedRelay stdErrRelay( |
| 239 | std::move(stdErrRead), GetStdHandle(STD_ERROR_HANDLE), LX_RELAY_BUFFER_SIZE, [&stdErrWrite]() { stdErrWrite.reset(); }); |
| 240 | |
| 241 | HRESULT result = E_FAIL; |
| 242 | if (GetFileType(FileHandle) != FILE_TYPE_PIPE) |
| 243 | { |
| 244 | result = m_userSession->ExportDistribution(DistroGuid, FileHandle, stdErrWrite.get(), Flags, context.OutError()); |
| 245 | } |
| 246 | else |
| 247 | { |
| 248 | result = m_userSession->ExportDistributionPipe(DistroGuid, FileHandle, stdErrWrite.get(), Flags, context.OutError()); |
| 249 | } |
| 250 | |
| 251 | stdErrWrite.reset(); |
| 252 | stdErrRelay.Sync(); |
| 253 | |
| 254 | RETURN_HR(result); |
| 255 | } |
| 256 | |
| 257 | void wsl::windows::common::SvcComm::GetDistributionConfiguration( |
| 258 | _In_opt_ LPCGUID DistroGuid, |
| 259 | _Out_ LPWSTR* Name, |
| 260 | _Out_ ULONG* Version, |
| 261 | _Out_ ULONG* DefaultUid, |
| 262 | _Out_ ULONG* DefaultEnvironmentCount, |
| 263 | _Out_ LPSTR** DefaultEnvironment, |
| 264 | _Out_ ULONG* Flags) const |
| 265 | { |
| 266 | ClientExecutionContext context; |
| 267 | |
| 268 | THROW_IF_FAILED(m_userSession->GetDistributionConfiguration( |
| 269 | DistroGuid, Name, Version, DefaultUid, DefaultEnvironmentCount, DefaultEnvironment, Flags, context.OutError())); |
| 270 | } |
| 271 | |
| 272 | DWORD |
| 273 | wsl::windows::common::SvcComm::LaunchProcess( |
| 274 | _In_opt_ LPCGUID DistroGuid, |
| 275 | _In_opt_ LPCWSTR Filename, |
| 276 | _In_ int Argc, |
| 277 | _In_reads_(Argc) LPCWSTR Argv[], |
| 278 | _In_ ULONG LaunchFlags, |
| 279 | _In_opt_ PCWSTR Username, |
| 280 | _In_opt_ PCWSTR CurrentWorkingDirectory, |
| 281 | _In_ DWORD Timeout) const |
| 282 | { |
| 283 | ClientExecutionContext context; |
| 284 | |
| 285 | // |
| 286 | // Parse the input arguments. |
| 287 | // |
| 288 | |
| 289 | DWORD ExitCode = 1; |
| 290 | CreateProcessArguments Parsed(Filename, Argc, Argv, LaunchFlags, CurrentWorkingDirectory); |
| 291 | |
| 292 | // |
| 293 | // Create the process. |
| 294 | // |
| 295 | |
| 296 | ConsoleState Io; |
| 297 | Io.SetInteractiveMode(); |
| 298 | COORD WindowSize = Io.GetWindowSize(); |
| 299 | ULONG Flags = LXSS_CREATE_INSTANCE_FLAGS_ALLOW_FS_UPGRADE; |
| 300 | if (WI_IsFlagSet(LaunchFlags, LXSS_LAUNCH_FLAG_USE_SYSTEM_DISTRO)) |
| 301 | { |
| 302 | WI_SetFlag(Flags, LXSS_CREATE_INSTANCE_FLAGS_USE_SYSTEM_DISTRO); |
| 303 | } |
| 304 | |
| 305 | if (WI_IsFlagSet(LaunchFlags, LXSS_LAUNCH_FLAG_SHELL_LOGIN)) |
| 306 | { |
| 307 | WI_SetFlag(Flags, LXSS_CREATE_INSTANCE_FLAGS_SHELL_LOGIN); |
| 308 | } |
| 309 | |
| 310 | // This method is also used by Terminal. |
| 311 | // See: https://github.com/microsoft/terminal/blob/ec434e3fba2a6ef254123e31f5257c25b04f2547/src/tools/ConsoleBench/conhost.cpp#L159-L164 |
| 312 | HANDLE console = NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->Reserved2[0]; |
| 313 | |
| 314 | LXSS_STD_HANDLES StdHandles{}; |
| 315 | const HANDLE InputHandle = GetStdHandle(STD_INPUT_HANDLE); |
| 316 | const bool IsConsoleInput = wsl::windows::common::wslutil::IsConsoleHandle(InputHandle); |
| 317 | StdHandles.StdIn.HandleType = IsConsoleInput ? LxssHandleConsole : LxssHandleInput; |
| 318 | StdHandles.StdIn.Handle = IsConsoleInput ? LXSS_HANDLE_USE_CONSOLE : HandleToUlong(InputHandle); |
| 319 | const HANDLE OutputHandle = GetStdHandle(STD_OUTPUT_HANDLE); |
| 320 | const bool IsConsoleOutput = wsl::windows::common::wslutil::IsConsoleHandle(OutputHandle); |
| 321 | StdHandles.StdOut.HandleType = IsConsoleOutput ? LxssHandleConsole : LxssHandleOutput; |
| 322 | StdHandles.StdOut.Handle = IsConsoleOutput ? LXSS_HANDLE_USE_CONSOLE : HandleToUlong(OutputHandle); |
| 323 | const HANDLE ErrorHandle = GetStdHandle(STD_ERROR_HANDLE); |
| 324 | const bool IsConsoleError = wsl::windows::common::wslutil::IsConsoleHandle(ErrorHandle); |
| 325 | StdHandles.StdErr.HandleType = IsConsoleError ? LxssHandleConsole : LxssHandleOutput; |
| 326 | StdHandles.StdErr.Handle = IsConsoleError ? LXSS_HANDLE_USE_CONSOLE : HandleToUlong(ErrorHandle); |
| 327 | |
| 328 | GUID DistributionId; |
| 329 | GUID InstanceId; |
| 330 | wil::unique_handle ProcessHandle; |
| 331 | wil::unique_handle ServerPortHandle; |
| 332 | wil::unique_handle StdInSocket; |
| 333 | wil::unique_handle StdOutSocket; |
| 334 | wil::unique_handle StdErrSocket; |
| 335 | wil::unique_handle ControlSocket; |
| 336 | wil::unique_handle InteropSocket; |
| 337 | |
| 338 | if (GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_CHAR) |
| 339 | { |
| 340 | context.EnableInteractiveWarnings(); |
| 341 | } |
| 342 | |
| 343 | THROW_IF_FAILED(m_userSession->CreateLxProcess( |
| 344 | DistroGuid, |
| 345 | Parsed.FilenameString.empty() ? nullptr : Parsed.FilenameString.c_str(), |
| 346 | Argc, |
| 347 | Parsed.CommandLineStrings.data(), |
| 348 | Parsed.CurrentWorkingDirectory.empty() ? nullptr : Parsed.CurrentWorkingDirectory.c_str(), |
| 349 | Parsed.NtPath.empty() ? nullptr : Parsed.NtPath.c_str(), |
| 350 | Parsed.NtEnvironment.get(), |
| 351 | static_cast<ULONG>(Parsed.NtEnvironmentLength), |
| 352 | Username, |
| 353 | WindowSize.X, |
| 354 | WindowSize.Y, |
| 355 | HandleToUlong(console), |
| 356 | &StdHandles, |
| 357 | Flags, |
| 358 | &DistributionId, |
| 359 | &InstanceId, |
| 360 | &ProcessHandle, |
| 361 | &ServerPortHandle, |
| 362 | &StdInSocket, |
| 363 | &StdOutSocket, |
| 364 | &StdErrSocket, |
| 365 | &ControlSocket, |
| 366 | &InteropSocket, |
| 367 | context.OutError())); |
| 368 | |
| 369 | context.FlushWarnings(); |
| 370 | |
| 371 | WI_ASSERT((!ARGUMENT_PRESENT(DistroGuid)) || (IsEqualGUID(*DistroGuid, DistributionId))); |
| 372 | |
| 373 | // |
| 374 | // If a process handle was returned, this is a WSL process. Otherwise, the |
| 375 | // process is running in a utility VM. |
| 376 | // |
| 377 | |
| 378 | if (ProcessHandle) |
| 379 | { |
| 380 | // |
| 381 | // Mark the process handle as uninheritable. |
| 382 | // |
| 383 | |
| 384 | helpers::SetHandleInheritable(ProcessHandle.get(), false); |
| 385 | |
| 386 | // |
| 387 | // If the caller requested interop and a server port was created, start |
| 388 | // the interop worker thread and background wslhost process. |
| 389 | // |
| 390 | |
| 391 | if ((WI_IsFlagSet(LaunchFlags, LXSS_LAUNCH_FLAG_ENABLE_INTEROP)) && (ServerPortHandle)) |
| 392 | { |
| 393 | try |
| 394 | { |
| 395 | InitializeInterop(ServerPortHandle.get(), DistributionId); |
| 396 | } |
| 397 | CATCH_LOG() |
| 398 | } |
| 399 | |
| 400 | ServerPortHandle.reset(); |
| 401 | |
| 402 | // |
| 403 | // Wait for the launched process to exit and return the process exit |
| 404 | // code. |
| 405 | // |
| 406 | |
| 407 | LXBUS_IPC_LX_PROCESS_WAIT_FOR_TERMINATION_PARAMETERS Parameters{}; |
| 408 | Parameters.Input.TimeoutMs = Timeout; |
| 409 | THROW_IF_NTSTATUS_FAILED(LxBusClientWaitForLxProcess(ProcessHandle.get(), &Parameters)); |
| 410 | |
| 411 | if (LXSS_WIFEXITED(Parameters.Output.ExitStatus)) |
| 412 | { |
| 413 | Parameters.Output.ExitStatus = LXSS_WEXITSTATUS(Parameters.Output.ExitStatus); |
| 414 | } |
| 415 | |
| 416 | ExitCode = Parameters.Output.ExitStatus; |
| 417 | } |
| 418 | else |
| 419 | { |
| 420 | // |
| 421 | // Create stdin, stdout and stderr worker threads. |
| 422 | // |
| 423 | |
| 424 | std::thread StdOutWorker; |
| 425 | std::thread StdErrWorker; |
| 426 | auto ExitEvent = wil::unique_event(wil::EventOptions::ManualReset); |
| 427 | auto outWorkerExit = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&StdOutWorker, &StdErrWorker, &ExitEvent] { |
| 428 | ExitEvent.SetEvent(); |
| 429 | if (StdOutWorker.joinable()) |
| 430 | { |
| 431 | StdOutWorker.join(); |
| 432 | } |
| 433 | |
| 434 | if (StdErrWorker.joinable()) |
| 435 | { |
| 436 | StdErrWorker.join(); |
| 437 | } |
| 438 | }); |
| 439 | |
| 440 | // This channel needs to be a shared_ptr because closing it will cause the linux relay to exit so we should keep it open |
| 441 | // even after the stdin thread exits, but we can't keep give a simple reference to that thread because the main thread |
| 442 | // might return from this method before the stdin relay thread does. |
| 443 | |
| 444 | auto ControlChannel = std::make_shared<wsl::shared::SocketChannel>( |
| 445 | wil::unique_socket{reinterpret_cast<SOCKET>(ControlSocket.release())}, "Control"); |
| 446 | |
| 447 | auto StdIn = GetStdHandle(STD_INPUT_HANDLE); |
| 448 | if (IS_VALID_HANDLE(StdIn)) |
| 449 | { |
| 450 | std::thread([StdIn, StdInSocket = std::move(StdInSocket), ControlChannel = ControlChannel, ExitHandle = ExitEvent.get(), Io = &Io]() mutable { |
| 451 | auto updateTerminal = [&]() { |
| 452 | // |
| 453 | // Query the window size and send an update message via the |
| 454 | // control channel. |
| 455 | // |
| 456 | if (ControlChannel) |
| 457 | { |
| 458 | auto WindowSize = Io->GetWindowSize(); |
| 459 | |
| 460 | LX_INIT_WINDOW_SIZE_CHANGED WindowSizeMessage{}; |
| 461 | WindowSizeMessage.Header.MessageType = LxInitMessageWindowSizeChanged; |
| 462 | WindowSizeMessage.Header.MessageSize = sizeof(WindowSizeMessage); |
| 463 | WindowSizeMessage.Columns = WindowSize.X; |
| 464 | WindowSizeMessage.Rows = WindowSize.Y; |
| 465 | |
| 466 | try |
| 467 | { |
| 468 | ControlChannel->SendMessage(WindowSizeMessage); |
| 469 | } |
| 470 | CATCH_LOG(); |
| 471 | } |
| 472 | }; |
| 473 | |
| 474 | wsl::windows::common::relay::StandardInputRelay(StdIn, StdInSocket.get(), updateTerminal, ExitHandle); |
| 475 | }).detach(); |
| 476 | } |
| 477 | |
| 478 | auto StdOut = GetStdHandle(STD_OUTPUT_HANDLE); |
| 479 | StdOutWorker = relay::CreateThread(std::move(StdOutSocket), IS_VALID_HANDLE(StdOut) ? StdOut : nullptr); |
| 480 | auto StdErr = GetStdHandle(STD_ERROR_HANDLE); |
| 481 | StdErrWorker = relay::CreateThread(std::move(StdErrSocket), IS_VALID_HANDLE(StdErr) ? StdErr : nullptr); |
| 482 | |
| 483 | // |
| 484 | // Spawn wslhost to handle interop requests from processes that have |
| 485 | // been backgrounded and their console window has been closed. |
| 486 | // |
| 487 | |
| 488 | if (WI_IsFlagSet(LaunchFlags, LXSS_LAUNCH_FLAG_ENABLE_INTEROP)) |
| 489 | { |
| 490 | try |
| 491 | { |
| 492 | SpawnWslHost(InteropSocket.get(), DistributionId, &InstanceId); |
| 493 | } |
| 494 | CATCH_LOG() |
| 495 | } |
| 496 | |
| 497 | // |
| 498 | // Begin reading messages from the utility vm. |
| 499 | // |
| 500 | |
| 501 | wsl::shared::SocketChannel InteropChannel{ |
| 502 | wil::unique_socket{reinterpret_cast<SOCKET>(InteropSocket.release())}, "Interop"}; |
| 503 | ExitCode = interop::VmModeWorkerThread(InteropChannel, InstanceId); |
| 504 | } |
| 505 | |
| 506 | return ExitCode; |
| 507 | } |
| 508 | |
| 509 | GUID wsl::windows::common::SvcComm::GetDefaultDistribution() const |
| 510 | { |
| 511 | ClientExecutionContext context; |
| 512 | GUID DistroId; |
| 513 | THROW_IF_FAILED(m_userSession->GetDefaultDistribution(context.OutError(), &DistroId)); |
| 514 | |
| 515 | return DistroId; |
| 516 | } |
| 517 | |
| 518 | ULONG |
| 519 | wsl::windows::common::SvcComm::GetDistributionFlags(_In_opt_ LPCGUID DistroGuid) const |
| 520 | { |
| 521 | ClientExecutionContext context; |
| 522 | |
| 523 | wil::unique_cotaskmem_string Name; |
| 524 | ULONG Version; |
| 525 | ULONG Uid; |
| 526 | wil::unique_cotaskmem_array_ptr<wil::unique_cotaskmem_ansistring> Environment; |
| 527 | ULONG Flags; |
| 528 | THROW_IF_FAILED(m_userSession->GetDistributionConfiguration( |
| 529 | DistroGuid, &Name, &Version, &Uid, Environment.size_address<ULONG>(), &Environment, &Flags, context.OutError())); |
| 530 | |
| 531 | return Flags; |
| 532 | } |
| 533 | |
| 534 | GUID wsl::windows::common::SvcComm::GetDistributionId(_In_ LPCWSTR Name, _In_ ULONG Flags) const |
| 535 | { |
| 536 | ClientExecutionContext context; |
| 537 | |
| 538 | GUID DistroId; |
| 539 | THROW_IF_FAILED(m_userSession->GetDistributionId(Name, Flags, context.OutError(), &DistroId)); |
| 540 | |
| 541 | return DistroId; |
| 542 | } |
| 543 | |
| 544 | GUID wsl::windows::common::SvcComm::ImportDistributionInplace(_In_ LPCWSTR Name, _In_ LPCWSTR VhdPath) const |
| 545 | { |
| 546 | ClientExecutionContext context; |
| 547 | |
| 548 | GUID DistroGuid; |
| 549 | THROW_IF_FAILED(m_userSession->ImportDistributionInplace(Name, VhdPath, context.OutError(), &DistroGuid)); |
| 550 | |
| 551 | return DistroGuid; |
| 552 | } |
| 553 | |
| 554 | void wsl::windows::common::SvcComm::MoveDistribution(_In_ const GUID& DistroGuid, _In_ LPCWSTR Location) const |
| 555 | { |
| 556 | ClientExecutionContext context; |
| 557 | |
| 558 | THROW_IF_FAILED(m_userSession->MoveDistribution(&DistroGuid, Location, context.OutError())); |
| 559 | } |
| 560 | |
| 561 | std::pair<GUID, wil::unique_cotaskmem_string> wsl::windows::common::SvcComm::RegisterDistribution( |
| 562 | _In_ LPCWSTR Name, |
| 563 | _In_ ULONG Version, |
| 564 | _In_ HANDLE FileHandle, |
| 565 | _In_ LPCWSTR TargetDirectory, |
| 566 | _In_ ULONG Flags, |
| 567 | _In_ std::optional<uint64_t> VhdSize, |
| 568 | _In_opt_ LPCWSTR PackageFamilyName) const |
| 569 | { |
| 570 | ClientExecutionContext context; |
| 571 | |
| 572 | // Create a pipe for reading errors from bsdtar. |
| 573 | wil::unique_handle stdErrRead; |
| 574 | wil::unique_handle stdErrWrite; |
| 575 | THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&stdErrRead, &stdErrWrite, nullptr, 0)); |
| 576 | |
| 577 | relay::ScopedRelay stdErrRelay( |
| 578 | std::move(stdErrRead), GetStdHandle(STD_ERROR_HANDLE), LX_RELAY_BUFFER_SIZE, [&stdErrWrite]() { stdErrWrite.reset(); }); |
| 579 | |
| 580 | GUID DistroGuid{}; |
| 581 | HRESULT Result = E_FAIL; |
| 582 | wil::unique_cotaskmem_string installedName; |
| 583 | if (GetFileType(FileHandle) != FILE_TYPE_PIPE) |
| 584 | { |
| 585 | Result = m_userSession->RegisterDistribution( |
| 586 | Name, |
| 587 | Version, |
| 588 | FileHandle, |
| 589 | stdErrWrite.get(), |
| 590 | TargetDirectory, |
| 591 | Flags, |
| 592 | VhdSize.value_or(0), |
| 593 | PackageFamilyName, |
| 594 | &installedName, |
| 595 | context.OutError(), |
| 596 | &DistroGuid); |
| 597 | } |
| 598 | else |
| 599 | { |
| 600 | Result = m_userSession->RegisterDistributionPipe( |
| 601 | Name, |
| 602 | Version, |
| 603 | FileHandle, |
| 604 | stdErrWrite.get(), |
| 605 | TargetDirectory, |
| 606 | Flags, |
| 607 | VhdSize.value_or(0), |
| 608 | PackageFamilyName, |
| 609 | &installedName, |
| 610 | context.OutError(), |
| 611 | &DistroGuid); |
| 612 | } |
| 613 | |
| 614 | stdErrWrite.reset(); |
| 615 | stdErrRelay.Sync(); |
| 616 | |
| 617 | THROW_IF_FAILED(Result); |
| 618 | |
| 619 | return std::make_pair(DistroGuid, std::move(installedName)); |
| 620 | } |
| 621 | |
| 622 | void wsl::windows::common::SvcComm::SetDefaultDistribution(_In_ LPCGUID DistroGuid) const |
| 623 | { |
| 624 | ClientExecutionContext context; |
| 625 | THROW_IF_FAILED(m_userSession->SetDefaultDistribution(DistroGuid, context.OutError())); |
| 626 | } |
| 627 | |
| 628 | HRESULT |
| 629 | wsl::windows::common::SvcComm::SetSparse(_In_ LPCGUID DistroGuid, _In_ BOOL Sparse, _In_ BOOL AllowUnsafe) const |
| 630 | { |
| 631 | ClientExecutionContext context; |
| 632 | |
| 633 | RETURN_HR(m_userSession->SetSparse(DistroGuid, Sparse, AllowUnsafe, context.OutError())); |
| 634 | } |
| 635 | |
| 636 | HRESULT |
| 637 | wsl::windows::common::SvcComm::ResizeDistribution(_In_ LPCGUID DistroGuid, _In_ ULONG64 NewSize) const |
| 638 | { |
| 639 | ClientExecutionContext context; |
| 640 | |
| 641 | wil::unique_handle outputRead; |
| 642 | wil::unique_handle outputWrite; |
| 643 | THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&outputRead, &outputWrite, nullptr, 0)); |
| 644 | |
| 645 | relay::ScopedRelay outputRelay( |
| 646 | std::move(outputRead), GetStdHandle(STD_ERROR_HANDLE), LX_RELAY_BUFFER_SIZE, [&outputWrite]() { outputWrite.reset(); }); |
| 647 | |
| 648 | const auto result = m_userSession->ResizeDistribution(DistroGuid, outputWrite.get(), NewSize, context.OutError()); |
| 649 | |
| 650 | outputWrite.reset(); |
| 651 | outputRelay.Sync(); |
| 652 | |
| 653 | RETURN_HR(result); |
| 654 | } |
| 655 | |
| 656 | HRESULT |
| 657 | wsl::windows::common::SvcComm::CompactDistribution(_In_ LPCGUID DistroGuid) const |
| 658 | { |
| 659 | ClientExecutionContext context; |
| 660 | RETURN_HR(m_userSession->CompactDistribution(DistroGuid, context.OutError())); |
| 661 | } |
| 662 | |
| 663 | HRESULT |
| 664 | wsl::windows::common::SvcComm::SetVersion(_In_ LPCGUID DistroGuid, _In_ ULONG Version) const |
| 665 | { |
| 666 | ClientExecutionContext context; |
| 667 | |
| 668 | // Create a pipe for reading errors from bsdtar. |
| 669 | wil::unique_handle stdErrRead; |
| 670 | wil::unique_handle stdErrWrite; |
| 671 | THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&stdErrRead, &stdErrWrite, nullptr, 0)); |
| 672 | |
| 673 | relay::ScopedRelay stdErrRelay( |
| 674 | std::move(stdErrRead), GetStdHandle(STD_ERROR_HANDLE), LX_RELAY_BUFFER_SIZE, [&stdErrWrite]() { stdErrWrite.reset(); }); |
| 675 | |
| 676 | RETURN_HR(m_userSession->SetVersion(DistroGuid, Version, stdErrWrite.get(), context.OutError())); |
| 677 | } |
| 678 | |
| 679 | HRESULT |
| 680 | wsl::windows::common::SvcComm::AttachDisk(_In_ LPCWSTR Disk, _In_ ULONG Flags) const |
| 681 | { |
| 682 | ClientExecutionContext context; |
| 683 | |
| 684 | RETURN_HR(m_userSession->AttachDisk(Disk, Flags, context.OutError())); |
| 685 | } |
| 686 | |
| 687 | std::pair<int, int> wsl::windows::common::SvcComm::DetachDisk(_In_opt_ LPCWSTR Disk) const |
| 688 | { |
| 689 | ClientExecutionContext context; |
| 690 | |
| 691 | int Result = -1; |
| 692 | int Step = 0; |
| 693 | THROW_IF_FAILED(m_userSession->DetachDisk(Disk, &Result, &Step, context.OutError())); |
| 694 | |
| 695 | return std::make_pair(Result, Step); |
| 696 | } |
| 697 | |
| 698 | wsl::windows::common::SvcComm::MountResult wsl::windows::common::SvcComm::MountDisk( |
| 699 | _In_ LPCWSTR Disk, _In_ ULONG Flags, _In_ ULONG PartitionIndex, _In_opt_ LPCWSTR Name, _In_opt_ LPCWSTR Type, _In_opt_ LPCWSTR Options) const |
| 700 | { |
| 701 | ClientExecutionContext context; |
| 702 | |
| 703 | MountResult Result; |
| 704 | THROW_IF_FAILED(m_userSession->MountDisk( |
| 705 | Disk, Flags, PartitionIndex, Name, Type, Options, &Result.Result, &Result.Step, &Result.MountName, context.OutError())); |
| 706 | |
| 707 | return Result; |
| 708 | } |
| 709 | |
| 710 | void wsl::windows::common::SvcComm::Shutdown(_In_ bool Force) const |
| 711 | { |
| 712 | THROW_IF_FAILED(m_userSession->Shutdown(Force)); |
| 713 | } |
| 714 | |
| 715 | void wsl::windows::common::SvcComm::TerminateInstance(_In_opt_ LPCGUID DistroGuid) const |
| 716 | { |
| 717 | ClientExecutionContext context; |
| 718 | |
| 719 | // |
| 720 | // If there is an instance running, terminate it. |
| 721 | // |
| 722 | |
| 723 | THROW_IF_FAILED(m_userSession->TerminateDistribution(DistroGuid, context.OutError())); |
| 724 | } |
| 725 | |
| 726 | void wsl::windows::common::SvcComm::UnregisterDistribution(_In_ LPCGUID DistroGuid) const |
| 727 | { |
| 728 | ClientExecutionContext context; |
| 729 | THROW_IF_FAILED(m_userSession->UnregisterDistribution(DistroGuid, context.OutError())); |
| 730 | } |