| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | interop.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains interop function definitions. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #include "precomp.h" |
| 16 | #include "interop.hpp" |
| 17 | #include "HandleIO.h" |
| 18 | #include "helpers.hpp" |
| 19 | #include "socket.hpp" |
| 20 | #include "hvsocket.hpp" |
| 21 | #include "relay.hpp" |
| 22 | #include "LxssServerPort.h" |
| 23 | #include "LxssMessagePort.h" |
| 24 | #include <gsl/algorithm> |
| 25 | #include <gslhelpers.h> |
| 26 | #include "WslTelemetry.h" |
| 27 | |
| 28 | namespace { |
| 29 | |
| 30 | std::wstring BuildEnvironment(gsl::span<gsl::byte> EnvironmentData); |
| 31 | |
| 32 | std::string FormatCommandLine(gsl::span<gsl::byte> CommandLineData, USHORT CommandLineCount); |
| 33 | |
| 34 | struct CreateProcessResult; |
| 35 | DWORD ProcessInteropMessages(_In_ HANDLE CommunicationChannel, _Inout_ CreateProcessResult* Result); |
| 36 | |
| 37 | struct CreateProcessParsed |
| 38 | { |
| 39 | CreateProcessParsed(_In_ const gsl::span<gsl::byte>& Common) |
| 40 | { |
| 41 | // Validate the message size and get spans to the various buffers. Note |
| 42 | // that the spans will be larger than the actual data since the message |
| 43 | // does not specify the size of each data element; the length is encoded |
| 44 | // via NULL termination. |
| 45 | |
| 46 | const auto* Params = gslhelpers::try_get_struct<LX_INIT_CREATE_NT_PROCESS_COMMON>(Common); |
| 47 | THROW_HR_IF(E_INVALIDARG, !Params || (Common.size() < (Params->CommandLineOffset))); |
| 48 | |
| 49 | // Parse the application name, command line, and current working directory |
| 50 | // and convert to unicode. |
| 51 | |
| 52 | ApplicationName = wsl::shared::string::MultiByteToWide(wsl::shared::string::FromSpan(Common, Params->FilenameOffset)); |
| 53 | const auto FormattedCommandLine = FormatCommandLine(Common.subspan(Params->CommandLineOffset), Params->CommandLineCount); |
| 54 | CommandLineBuffer = wsl::shared::string::MultiByteToWide(FormattedCommandLine); |
| 55 | const auto* Cwd = wsl::shared::string::FromSpan(Common, Params->CurrentWorkingDirectoryOffset); |
| 56 | if (strlen(Cwd) > 0) |
| 57 | { |
| 58 | CwdBuffer = wsl::shared::string::MultiByteToWide(Cwd); |
| 59 | } |
| 60 | |
| 61 | // Construct an environment if one was provided. |
| 62 | |
| 63 | if (Params->EnvironmentOffset > 0) |
| 64 | { |
| 65 | THROW_HR_IF(E_INVALIDARG, Common.size() < Params->EnvironmentOffset); |
| 66 | |
| 67 | EnvironmentBuffer = BuildEnvironment(Common.subspan(Params->EnvironmentOffset)); |
| 68 | } |
| 69 | |
| 70 | Rows = Params->Rows; |
| 71 | Columns = Params->Columns; |
| 72 | CreatePseudoconsole = Params->CreatePseudoconsole; |
| 73 | } |
| 74 | |
| 75 | LPWSTR CommandLine() |
| 76 | { |
| 77 | return CommandLineBuffer.data(); |
| 78 | } |
| 79 | |
| 80 | LPCWSTR Cwd() const |
| 81 | { |
| 82 | return CwdBuffer.empty() ? nullptr : CwdBuffer.c_str(); |
| 83 | } |
| 84 | |
| 85 | LPVOID Environment() |
| 86 | { |
| 87 | return EnvironmentBuffer.empty() ? nullptr : EnvironmentBuffer.data(); |
| 88 | } |
| 89 | |
| 90 | std::wstring ApplicationName{}; |
| 91 | std::wstring CommandLineBuffer{}; |
| 92 | std::wstring EnvironmentBuffer{}; |
| 93 | std::wstring CwdBuffer{}; |
| 94 | DWORD Rows{}; |
| 95 | DWORD Columns{}; |
| 96 | bool CreatePseudoconsole{}; |
| 97 | }; |
| 98 | |
| 99 | struct CreateProcessResult |
| 100 | { |
| 101 | wil::unique_handle Process{}; |
| 102 | int Status{}; |
| 103 | unsigned int Flags{}; |
| 104 | wsl::windows::common::helpers::unique_pseudo_console PseudoConsole{}; |
| 105 | }; |
| 106 | |
| 107 | struct CreateProcessVmModeContext |
| 108 | { |
| 109 | GUID VmId{}; |
| 110 | std::vector<gsl::byte> Buffer{}; |
| 111 | }; |
| 112 | |
| 113 | std::wstring BuildEnvironment(gsl::span<gsl::byte> EnvironmentData) |
| 114 | { |
| 115 | std::map<std::wstring, std::wstring> Environment; |
| 116 | |
| 117 | // Construct a map of the current environment strings. |
| 118 | const wsl::windows::common::helpers::unique_environment_strings EnvironmentStrings(GetEnvironmentStrings()); |
| 119 | PCZZWSTR CurrentEnvironment = EnvironmentStrings.get(); |
| 120 | while (*CurrentEnvironment) |
| 121 | { |
| 122 | const PCWSTR Divider = wcschr(CurrentEnvironment, '='); |
| 123 | THROW_HR_IF_NULL(E_UNEXPECTED, Divider); |
| 124 | std::wstring Key(CurrentEnvironment, Divider); |
| 125 | Environment[Key] = Divider + 1; |
| 126 | CurrentEnvironment += wcslen(CurrentEnvironment) + 1; |
| 127 | } |
| 128 | |
| 129 | // Update the map with the Linux environment data. |
| 130 | for (;;) |
| 131 | { |
| 132 | std::string_view Variable = wsl::shared::string::FromSpan(EnvironmentData); |
| 133 | if (Variable.empty()) |
| 134 | { |
| 135 | break; |
| 136 | } |
| 137 | |
| 138 | const size_t Divider = Variable.find('='); |
| 139 | THROW_HR_IF(E_UNEXPECTED, Divider == Variable.npos); |
| 140 | std::wstring Key = wsl::shared::string::MultiByteToWide(std::string{Variable.substr(0, Divider)}); |
| 141 | auto Value = Variable.substr(Divider + 1); |
| 142 | if (Value.empty()) |
| 143 | { |
| 144 | Environment.erase(Key); |
| 145 | } |
| 146 | else |
| 147 | { |
| 148 | Environment[Key] = wsl::shared::string::MultiByteToWide(std::string{Value}); |
| 149 | } |
| 150 | |
| 151 | EnvironmentData = EnvironmentData.subspan(Variable.size() + 1); |
| 152 | } |
| 153 | |
| 154 | // Construct a new environment block. |
| 155 | std::wstring Block; |
| 156 | for (const auto& Variable : Environment) |
| 157 | { |
| 158 | Block += Variable.first; |
| 159 | Block += L'='; |
| 160 | Block += Variable.second; |
| 161 | Block += L'\0'; |
| 162 | } |
| 163 | |
| 164 | return Block; |
| 165 | } |
| 166 | |
| 167 | HRESULT GetProcessImageSubSystem(_In_ HANDLE Process, _Out_ ULONG* ImageSubsystem) |
| 168 | { |
| 169 | *ImageSubsystem = IMAGE_SUBSYSTEM_UNKNOWN; |
| 170 | |
| 171 | PROCESS_BASIC_INFORMATION ProcessBasicInfo{}; |
| 172 | RETURN_IF_NTSTATUS_FAILED(NtQueryInformationProcess(Process, ProcessBasicInformation, &ProcessBasicInfo, sizeof(ProcessBasicInfo), NULL)); |
| 173 | |
| 174 | // Terminal uses a similar method to read the PEB. |
| 175 | // See: https://github.com/microsoft/terminal/blob/ec434e3fba2a6ef254123e31f5257c25b04f2547/src/tools/ConsoleBench/conhost.cpp#L159-L164 |
| 176 | PEB* dummyPeb = nullptr; |
| 177 | const auto offset = ((char*)&dummyPeb->Reserved9[24]) - (char*)dummyPeb; |
| 178 | |
| 179 | SIZE_T BytesRead = 0; |
| 180 | RETURN_IF_WIN32_BOOL_FALSE(ReadProcessMemory( |
| 181 | Process, ((char*)ProcessBasicInfo.PebBaseAddress) + offset, ImageSubsystem, sizeof(*ImageSubsystem), &BytesRead)); |
| 182 | |
| 183 | if (BytesRead < sizeof(*ImageSubsystem)) |
| 184 | { |
| 185 | *ImageSubsystem = IMAGE_SUBSYSTEM_UNKNOWN; |
| 186 | return E_UNEXPECTED; |
| 187 | } |
| 188 | |
| 189 | return S_OK; |
| 190 | } |
| 191 | |
| 192 | CreateProcessResult CreateProcess(_In_ CreateProcessParsed* Parsed, _In_ HANDLE StdIn, _In_ HANDLE StdOut, _In_ HANDLE StdErr) |
| 193 | { |
| 194 | wsl::windows::common::helpers::SetHandleInheritable(StdIn); |
| 195 | wsl::windows::common::helpers::SetHandleInheritable(StdOut); |
| 196 | wsl::windows::common::helpers::SetHandleInheritable(StdErr); |
| 197 | |
| 198 | // N.B. Passing StartupFlags = 0 so that the cursor feedback is set to its default behavior. |
| 199 | // See: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa |
| 200 | wsl::windows::common::SubProcess process(Parsed->ApplicationName.c_str(), Parsed->CommandLine(), CREATE_UNICODE_ENVIRONMENT, 0); |
| 201 | |
| 202 | CreateProcessResult Result{}; |
| 203 | if (Parsed->CreatePseudoconsole) |
| 204 | { |
| 205 | const COORD Size{static_cast<SHORT>(Parsed->Columns), static_cast<SHORT>(Parsed->Rows)}; |
| 206 | THROW_IF_FAILED(CreatePseudoConsole(Size, StdIn, StdOut, PSEUDOCONSOLE_INHERIT_CURSOR, &Result.PseudoConsole)); |
| 207 | |
| 208 | process.SetPseudoConsole(Result.PseudoConsole.get()); |
| 209 | } |
| 210 | else |
| 211 | { |
| 212 | // In the case where this is a console process, don't create a new console window. |
| 213 | // This is useful for wslg.exe, when a console program is created through interop, |
| 214 | // we don't want to create a new console window. |
| 215 | // N.B. CREATE_NO_WINDOW only applies to console executables, so GUI applications |
| 216 | // are not affected by this flag. |
| 217 | process.SetFlags(CREATE_NO_WINDOW); |
| 218 | process.SetStdHandles(StdIn, StdOut, StdErr); |
| 219 | } |
| 220 | |
| 221 | // Set the breakaway override flag to ensure that processes created via interop are not packaged. |
| 222 | process.SetDesktopAppPolicy(PROCESS_CREATION_DESKTOP_APP_BREAKAWAY_OVERRIDE); |
| 223 | process.SetEnvironment(Parsed->Environment()); |
| 224 | process.SetWorkingDirectory(Parsed->Cwd()); |
| 225 | |
| 226 | try |
| 227 | { |
| 228 | Result.Process = process.Start(); |
| 229 | // Check if the process that was launched is a graphical application. |
| 230 | // Non-graphical applications should be terminated when the file |
| 231 | // descriptor that represents the process is closed. |
| 232 | ULONG ImageSubsystem = IMAGE_SUBSYSTEM_UNKNOWN; |
| 233 | LOG_IF_FAILED(GetProcessImageSubSystem(Result.Process.get(), &ImageSubsystem)); |
| 234 | const bool IsGuiApp = (ImageSubsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI); |
| 235 | WI_SetFlagIf(Result.Flags, LX_INIT_CREATE_PROCESS_RESULT_FLAG_GUI_APPLICATION, IsGuiApp); |
| 236 | } |
| 237 | catch (...) |
| 238 | { |
| 239 | const DWORD LastError = wil::ResultFromCaughtException(); |
| 240 | switch (LastError) |
| 241 | { |
| 242 | case ERROR_FILE_NOT_FOUND: |
| 243 | Result.Status = -LX_ENOENT; |
| 244 | break; |
| 245 | |
| 246 | case ERROR_ELEVATION_REQUIRED: |
| 247 | Result.Status = -LX_EACCES; |
| 248 | break; |
| 249 | |
| 250 | default: |
| 251 | Result.Status = -LX_EINVAL; |
| 252 | LOG_IF_WIN32_ERROR_MSG(LastError, "CreateProcessW"); |
| 253 | break; |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | return Result; |
| 258 | } |
| 259 | |
| 260 | void CreateProcessVmMode(_In_ const GUID& VmId, _In_ const gsl::span<gsl::byte>& Buffer) |
| 261 | { |
| 262 | // Create a worker thread to service the interop request. |
| 263 | // |
| 264 | // N.B. The worker thread takes ownership of the arguments. |
| 265 | auto Arguments = std::make_unique<CreateProcessVmModeContext>(); |
| 266 | Arguments->VmId = VmId; |
| 267 | Arguments->Buffer.resize(Buffer.size()); |
| 268 | gsl::copy(Buffer, gsl::make_span(Arguments->Buffer)); |
| 269 | std::thread([Arguments = std::move(Arguments)]() { |
| 270 | try |
| 271 | { |
| 272 | wsl::windows::common::wslutil::SetThreadDescription(L"Interop"); |
| 273 | auto Message = gsl::make_span(Arguments->Buffer); |
| 274 | auto* Params = gslhelpers::try_get_struct<LX_INIT_CREATE_NT_PROCESS_UTILITY_VM>(Message); |
| 275 | THROW_HR_IF(E_INVALIDARG, !Params || (Params->Header.MessageType != LxInitMessageCreateProcessUtilityVm)); |
| 276 | |
| 277 | // Parse the message. |
| 278 | CreateProcessParsed Parsed(Message.subspan(offsetof(LX_INIT_CREATE_NT_PROCESS_UTILITY_VM, Common))); |
| 279 | |
| 280 | // Establish connections on the specified port. |
| 281 | static_assert(LX_INIT_CREATE_NT_PROCESS_SOCKETS == 4); |
| 282 | |
| 283 | wil::unique_socket Sockets[LX_INIT_CREATE_NT_PROCESS_SOCKETS]; |
| 284 | for (ULONG Index = 0; Index < RTL_NUMBER_OF(Sockets); Index += 1) |
| 285 | { |
| 286 | Sockets[Index] = wsl::windows::common::hvsocket::Connect(Arguments->VmId, Params->Port); |
| 287 | } |
| 288 | |
| 289 | // Clean up relay threads. |
| 290 | // |
| 291 | // N.B. This must be declared before the stdin / stdout / stderr handles so they go out of scope first. |
| 292 | std::vector<std::thread> Relays; |
| 293 | auto CancelIo = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&Relays] { |
| 294 | for (auto& Relay : Relays) |
| 295 | { |
| 296 | if (Relay.joinable()) |
| 297 | { |
| 298 | Relay.join(); |
| 299 | } |
| 300 | } |
| 301 | }); |
| 302 | |
| 303 | // If a pseudoconsole is not being used, create hvsocket / pipe relays. |
| 304 | wil::unique_handle StdIn{reinterpret_cast<HANDLE>(Sockets[0].release())}; |
| 305 | wil::unique_handle StdOut{reinterpret_cast<HANDLE>(Sockets[1].release())}; |
| 306 | wil::unique_handle StdErr{reinterpret_cast<HANDLE>(Sockets[2].release())}; |
| 307 | if (Parsed.CreatePseudoconsole == FALSE) |
| 308 | { |
| 309 | auto Pipe = wsl::windows::common::wslutil::OpenAnonymousPipe(0, false, true); |
| 310 | Relays.push_back(wsl::windows::common::relay::CreateThread(std::move(StdIn), wil::unique_handle{Pipe.second.release()})); |
| 311 | StdIn.reset(Pipe.first.release()); |
| 312 | |
| 313 | Pipe = wsl::windows::common::wslutil::OpenAnonymousPipe(0, true, false); |
| 314 | Relays.push_back(wsl::windows::common::relay::CreateThread(wil::unique_handle{Pipe.first.release()}, std::move(StdOut))); |
| 315 | StdOut.reset(Pipe.second.release()); |
| 316 | |
| 317 | Pipe = wsl::windows::common::wslutil::OpenAnonymousPipe(0, true, false); |
| 318 | Relays.push_back(wsl::windows::common::relay::CreateThread(wil::unique_handle{Pipe.first.release()}, std::move(StdErr))); |
| 319 | StdErr.reset(Pipe.second.release()); |
| 320 | } |
| 321 | |
| 322 | // Launch the process and write the status via the control channel. |
| 323 | auto Result = CreateProcess(&Parsed, StdIn.get(), StdOut.get(), StdErr.get()); |
| 324 | LX_INIT_CREATE_PROCESS_RESPONSE Response{}; |
| 325 | Response.Header.MessageType = LxInitMessageCreateProcessResponse; |
| 326 | Response.Header.MessageSize = sizeof(Response); |
| 327 | Response.Flags = Result.Flags; |
| 328 | Response.Result = Result.Status; |
| 329 | wsl::windows::common::socket::Send(Sockets[3].get(), gslhelpers::struct_as_bytes(Response)); |
| 330 | if (Result.Status == 0) |
| 331 | { |
| 332 | // Process messages from the binfmt interpreter and wait for the process to exit. |
| 333 | LX_INIT_PROCESS_EXIT_STATUS ExitStatus{}; |
| 334 | ExitStatus.Header.MessageType = LxInitMessageExitStatus; |
| 335 | ExitStatus.Header.MessageSize = sizeof(ExitStatus); |
| 336 | ExitStatus.ExitCode = ProcessInteropMessages(reinterpret_cast<HANDLE>(Sockets[3].get()), &Result); |
| 337 | |
| 338 | // Write the exit status to the binfmt interpreter. |
| 339 | wsl::windows::common::socket::Send(Sockets[3].get(), gslhelpers::struct_as_bytes(ExitStatus)); |
| 340 | } |
| 341 | } |
| 342 | CATCH_LOG() |
| 343 | }).detach(); |
| 344 | } |
| 345 | |
| 346 | std::string FormatCommandLine(gsl::span<gsl::byte> CommandLineData, USHORT CommandLineCount) |
| 347 | { |
| 348 | // Concatenate all of the command line arguments into a single string for |
| 349 | // the CreateProcess api. |
| 350 | // |
| 351 | // N.B. Any empty arguments or arguments that contain whitespace must be |
| 352 | // encapsulated in quotes. Quotes must also be escaped according to |
| 353 | // standard command-line parsing rules: |
| 354 | // https://msdn.microsoft.com/en-us/library/17w5ykft.aspx. |
| 355 | // |
| 356 | // This logic is largely taken from AppendQuotedForWindows in |
| 357 | // hcsdiag.cpp. In the future it would make sense to merge this |
| 358 | // functionality. |
| 359 | std::string CommandLine; |
| 360 | for (USHORT Index = 0; Index < CommandLineCount; Index += 1) |
| 361 | { |
| 362 | std::string_view Buffer = wsl::shared::string::FromSpan(CommandLineData); |
| 363 | if (!Buffer.empty() && Buffer.find_first_of(" \t\r\n\"") == Buffer.npos) |
| 364 | { |
| 365 | CommandLine.append(Buffer); |
| 366 | } |
| 367 | else |
| 368 | { |
| 369 | CommandLine += '"'; |
| 370 | size_t BackslashCount = 0; |
| 371 | for (const char Ch : Buffer) |
| 372 | { |
| 373 | switch (Ch) |
| 374 | { |
| 375 | case '"': |
| 376 | CommandLine.append(((BackslashCount * 2) + 1), '\\'); |
| 377 | BackslashCount = 0; |
| 378 | CommandLine += '"'; |
| 379 | break; |
| 380 | |
| 381 | case '\\': |
| 382 | BackslashCount += 1; |
| 383 | break; |
| 384 | |
| 385 | default: |
| 386 | CommandLine.append(BackslashCount, '\\'); |
| 387 | BackslashCount = 0; |
| 388 | CommandLine += Ch; |
| 389 | break; |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | CommandLine.append(BackslashCount * 2, '\\'); |
| 394 | CommandLine += '"'; |
| 395 | } |
| 396 | |
| 397 | // Add a space between command line arguments. |
| 398 | if (Index < CommandLineCount - 1) |
| 399 | { |
| 400 | CommandLine += ' '; |
| 401 | } |
| 402 | |
| 403 | CommandLineData = CommandLineData.subspan(Buffer.size() + 1); |
| 404 | } |
| 405 | |
| 406 | return CommandLine; |
| 407 | } |
| 408 | |
| 409 | DWORD |
| 410 | ProcessInteropMessages(_In_ HANDLE MessageHandle, _Inout_ CreateProcessResult* Result) |
| 411 | { |
| 412 | namespace io = wsl::windows::common::io; |
| 413 | |
| 414 | DWORD exitCode = 1; |
| 415 | std::vector<char> pending; |
| 416 | |
| 417 | static_assert(sizeof(LX_INIT_WINDOW_SIZE_CHANGED) % alignof(LX_INIT_WINDOW_SIZE_CHANGED) == 0); |
| 418 | |
| 419 | auto processExit = [&] { |
| 420 | THROW_IF_WIN32_BOOL_FALSE(GetExitCodeProcess(Result->Process.get(), &exitCode)); |
| 421 | |
| 422 | // Close the pseudoconsole, this causes all pending data to be flushed. |
| 423 | Result->PseudoConsole.reset(); |
| 424 | }; |
| 425 | |
| 426 | io::MultiHandleWait wait; |
| 427 | wait.AddHandle( |
| 428 | std::make_unique<io::ReadHandle>( |
| 429 | io::HandleWrapper{MessageHandle}, |
| 430 | [&](const gsl::span<char>& input) { |
| 431 | if (input.empty()) |
| 432 | { |
| 433 | const DWORD waitStatus = WaitForSingleObject(Result->Process.get(), 0); |
| 434 | if (waitStatus == WAIT_OBJECT_0) |
| 435 | { |
| 436 | processExit(); |
| 437 | } |
| 438 | else |
| 439 | { |
| 440 | THROW_HR_IF(E_UNEXPECTED, waitStatus != WAIT_TIMEOUT); |
| 441 | if (WI_IsFlagClear(Result->Flags, LX_INIT_CREATE_PROCESS_RESULT_FLAG_GUI_APPLICATION)) |
| 442 | { |
| 443 | THROW_IF_WIN32_BOOL_FALSE(TerminateProcess(Result->Process.get(), 1)); |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | return; |
| 448 | } |
| 449 | |
| 450 | std::vector<char> stitchedInput; |
| 451 | auto remaining = input; |
| 452 | if (!pending.empty()) |
| 453 | { |
| 454 | stitchedInput.reserve(pending.size() + input.size()); |
| 455 | stitchedInput.insert(stitchedInput.end(), pending.begin(), pending.end()); |
| 456 | stitchedInput.insert(stitchedInput.end(), input.begin(), input.end()); |
| 457 | pending.clear(); |
| 458 | remaining = gsl::make_span(stitchedInput); |
| 459 | } |
| 460 | |
| 461 | while (remaining.size() >= sizeof(LX_INIT_WINDOW_SIZE_CHANGED)) |
| 462 | { |
| 463 | const auto* message = gslhelpers::get_struct<const LX_INIT_WINDOW_SIZE_CHANGED>(remaining); |
| 464 | THROW_HR_IF( |
| 465 | E_UNEXPECTED, |
| 466 | (message->Header.MessageType != LxInitMessageWindowSizeChanged) || (message->Header.MessageSize != sizeof(*message))); |
| 467 | |
| 468 | const COORD size{static_cast<SHORT>(message->Columns), static_cast<SHORT>(message->Rows)}; |
| 469 | THROW_IF_FAILED(ResizePseudoConsole(Result->PseudoConsole.get(), size)); |
| 470 | remaining = remaining.subspan(sizeof(*message)); |
| 471 | } |
| 472 | |
| 473 | pending.assign(remaining.begin(), remaining.end()); |
| 474 | }), |
| 475 | io::MultiHandleWait::CancelOnCompleted); |
| 476 | |
| 477 | wait.AddHandle(std::make_unique<io::EventHandle>(io::HandleWrapper{Result->Process.get()}, processExit), io::MultiHandleWait::CancelOnCompleted); |
| 478 | |
| 479 | wait.Run(std::nullopt); |
| 480 | return exitCode; |
| 481 | } |
| 482 | |
| 483 | } // namespace |
| 484 | |
| 485 | void wsl::windows::common::interop::WorkerThread(_In_ wil::unique_handle&& ServerPortHandle) |
| 486 | { |
| 487 | // This thread waits for connections and processes create process messages. |
| 488 | // |
| 489 | // N.B. This thread lives until the main thread of the process exits. |
| 490 | // |
| 491 | // TODO_LX: Wait for connection blocks in the driver until the server port |
| 492 | // is closed. The wait for connection ioctl should be moved to |
| 493 | // async so this thread can wait on the server port and a second |
| 494 | // event indicating that the thread should exit. |
| 495 | |
| 496 | LxssServerPort ServerPort(std::move(ServerPortHandle)); |
| 497 | for (;;) |
| 498 | { |
| 499 | try |
| 500 | { |
| 501 | // Wait for a client to connect, break out of the loop on disconnect. |
| 502 | std::unique_ptr<LxssMessagePort> MessagePort; |
| 503 | if (!NT_SUCCESS(ServerPort.WaitForConnectionNoThrow(&MessagePort))) |
| 504 | { |
| 505 | break; |
| 506 | } |
| 507 | |
| 508 | std::thread([MessagePort = std::move(MessagePort)]() mutable { |
| 509 | try |
| 510 | { |
| 511 | // Read the create process request from the client. |
| 512 | auto CreateProcessMessage = MessagePort->Receive(); |
| 513 | const auto Message = gsl::make_span(CreateProcessMessage); |
| 514 | const auto* Params = gslhelpers::try_get_struct<LX_INIT_CREATE_NT_PROCESS>(Message); |
| 515 | THROW_HR_IF(E_INVALIDARG, !Params || (Params->Header.MessageType != LxInitMessageCreateProcess)); |
| 516 | |
| 517 | // Parse the message. |
| 518 | CreateProcessParsed Parsed(Message.subspan(offsetof(LX_INIT_CREATE_NT_PROCESS, Common))); |
| 519 | |
| 520 | // Unmarshal the handles to be used as stdin / stdout / stederr and mark |
| 521 | // them as inheritable. |
| 522 | static_assert(LX_INIT_STD_FD_COUNT == 3); |
| 523 | |
| 524 | wil::unique_handle StdHandles[LX_INIT_STD_FD_COUNT]; |
| 525 | for (ULONG Index = 0; Index < LX_INIT_STD_FD_COUNT; Index += 1) |
| 526 | { |
| 527 | StdHandles[Index] = MessagePort->UnmarshalVfsFile(Params->StdFdIds[Index]); |
| 528 | } |
| 529 | |
| 530 | // Create the signal pipe to handle resize requests. |
| 531 | auto SignalPipe = wsl::windows::common::wslutil::OpenAnonymousPipe(0, true, true); |
| 532 | |
| 533 | // Launch the process. |
| 534 | auto Result = CreateProcess(&Parsed, StdHandles[0].get(), StdHandles[1].get(), StdHandles[2].get()); |
| 535 | |
| 536 | // Send a response back to the init daemon. |
| 537 | LX_INIT_CREATE_PROCESS_RESPONSE Response{}; |
| 538 | Response.Header.MessageType = LxInitMessageCreateProcessResponse; |
| 539 | Response.Header.MessageSize = sizeof(Response); |
| 540 | Response.Flags = Result.Flags; |
| 541 | Response.Result = Result.Status; |
| 542 | if (Result.Status == 0) |
| 543 | { |
| 544 | // Marshal the write end of the signal pipe. |
| 545 | const LXBUS_IPC_MESSAGE_MARSHAL_HANDLE_DATA HandleData{HandleToUlong(SignalPipe.second.get()), LxBusIpcMarshalHandleTypeOutput}; |
| 546 | Response.SignalPipeId = MessagePort->MarshalHandle(&HandleData); |
| 547 | SignalPipe.second.reset(); |
| 548 | |
| 549 | // Write the response to the binfmt interpreter. |
| 550 | MessagePort->Send(&Response, sizeof(Response)); |
| 551 | |
| 552 | // Process messages from the binfmt interpreter and wait for the |
| 553 | // process to exit. |
| 554 | LX_INIT_PROCESS_EXIT_STATUS ExitStatus{}; |
| 555 | ExitStatus.Header.MessageType = LxInitMessageExitStatus; |
| 556 | ExitStatus.Header.MessageSize = sizeof(ExitStatus); |
| 557 | ExitStatus.ExitCode = ProcessInteropMessages(SignalPipe.first.get(), &Result); |
| 558 | |
| 559 | // Write the exit status to the binfmt interpreter. |
| 560 | MessagePort->Send(&ExitStatus, sizeof(ExitStatus)); |
| 561 | } |
| 562 | else |
| 563 | { |
| 564 | MessagePort->Send(&Response, sizeof(Response)); |
| 565 | } |
| 566 | } |
| 567 | CATCH_LOG() |
| 568 | }).detach(); |
| 569 | } |
| 570 | CATCH_LOG() |
| 571 | } |
| 572 | } |
| 573 | |
| 574 | DWORD |
| 575 | wsl::windows::common::interop::VmModeWorkerThread(_In_ wsl::shared::SocketChannel& Channel, _In_ const GUID& VmId, _In_ bool IgnoreExit) |
| 576 | { |
| 577 | std::vector<gsl::byte> Buffer; |
| 578 | |
| 579 | for (;;) |
| 580 | { |
| 581 | auto [Message, Span] = Channel.ReceiveMessageOrClosed<MESSAGE_HEADER>(); |
| 582 | if (Message == nullptr) |
| 583 | { |
| 584 | break; |
| 585 | } |
| 586 | |
| 587 | switch (Message->MessageType) |
| 588 | { |
| 589 | case LxInitMessageExitStatus: |
| 590 | { |
| 591 | const auto* ExitStatusMessage = gslhelpers::try_get_struct<LX_INIT_PROCESS_EXIT_STATUS>(Span); |
| 592 | THROW_HR_IF(E_INVALIDARG, !ExitStatusMessage); |
| 593 | |
| 594 | Channel.SendMessage<LX_INIT_PROCESS_EXIT_STATUS>(Span); |
| 595 | |
| 596 | if (!IgnoreExit) |
| 597 | { |
| 598 | return ExitStatusMessage->ExitCode; |
| 599 | } |
| 600 | |
| 601 | break; |
| 602 | } |
| 603 | |
| 604 | case LxInitMessageCreateProcessUtilityVm: |
| 605 | THROW_HR_IF(E_INVALIDARG, (Span.size() < sizeof(LX_INIT_CREATE_PROCESS_UTILITY_VM))); |
| 606 | |
| 607 | CreateProcessVmMode(VmId, Span); |
| 608 | break; |
| 609 | |
| 610 | default: |
| 611 | THROW_HR_MSG(E_UNEXPECTED, "Unexpected message %d", Message->MessageType); |
| 612 | } |
| 613 | } |
| 614 | |
| 615 | return 1; |
| 616 | } |