| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | Common.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This contains common used definitions used for testing. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | // includes |
| 16 | |
| 17 | #include "precomp.h" |
| 18 | #include "Common.h" |
| 19 | #include "LxssDynamicFunction.h" |
| 20 | #include "WslCoreNetworkEndpointSettings.h" |
| 21 | #include <tlhelp32.h> |
| 22 | #include <werapi.h> |
| 23 | #include <Dbghelp.h> |
| 24 | #include <winsafer.h> |
| 25 | |
| 26 | using namespace WEX::Logging; |
| 27 | using namespace WEX::Common; |
| 28 | using namespace WEX::TestExecution; |
| 29 | |
| 30 | MODULE_SETUP(ModuleSetup); |
| 31 | MODULE_CLEANUP(ModuleCleanup); |
| 32 | |
| 33 | // Defines |
| 34 | #define LXSS_LOGS_DIRECTORY L"logs" |
| 35 | #define LXSS_TEST_DIRECTORY L"\\data\\test" |
| 36 | #define LXSS_TEST_LOG_SEPARATOR_CHAR L"&" |
| 37 | #define LXSS_DEFAULT_TIMEOUT (15 * 1000) |
| 38 | |
| 39 | // |
| 40 | // The instance test timeout should roughly be the maximum time to start an |
| 41 | // instance. |
| 42 | // |
| 43 | |
| 44 | #define LXSS_INSTANCE_TEST_TIMEOUT (3 * 1000) |
| 45 | |
| 46 | // |
| 47 | // The watchdog timeout is set to 3 hours. |
| 48 | // |
| 49 | |
| 50 | #define LXSS_WATCHDOG_TIMEOUT (3 * 60 * 60 * 1000) |
| 51 | #define LXSS_WATCHDOG_TIMEOUT_WINDOW 1000 |
| 52 | |
| 53 | // |
| 54 | // Global variables |
| 55 | // |
| 56 | |
| 57 | static HANDLE g_OriginalStdout; |
| 58 | static HANDLE g_OriginalStderr; |
| 59 | static BOOL g_RelogEverything = TRUE; |
| 60 | static bool g_LogDmesgAfterEachTest = false; |
| 61 | static PTP_TIMER g_WatchdogTimer; |
| 62 | |
| 63 | static BOOL g_VmMode; |
| 64 | static std::wstring g_originalConfig; |
| 65 | static std::wstring g_originalDefaultDistro; |
| 66 | std::wstring g_dumpFolder; |
| 67 | std::optional<std::wstring> g_dumpToolPath; |
| 68 | static bool g_enableWerReport = false; |
| 69 | std::wstring g_pipelineBuildId; |
| 70 | std::wstring g_testDistroPath; |
| 71 | std::wstring g_testDataPath; |
| 72 | bool g_fastTestRun = false; // True when test.bat was invoked with -f |
| 73 | static wil::unique_mta_usage_cookie g_mtaCookie; |
| 74 | |
| 75 | std::pair<wil::unique_handle, wil::unique_handle> CreateSubprocessPipe(bool inheritRead, bool inheritWrite, DWORD bufferSize, _In_opt_ SECURITY_ATTRIBUTES* sa) |
| 76 | { |
| 77 | wil::unique_handle read; |
| 78 | wil::unique_handle write; |
| 79 | THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&read, &write, sa, bufferSize)); |
| 80 | |
| 81 | if (inheritWrite) |
| 82 | { |
| 83 | THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(write.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)); |
| 84 | } |
| 85 | |
| 86 | if (inheritRead) |
| 87 | { |
| 88 | THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(read.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)); |
| 89 | } |
| 90 | |
| 91 | return {std::move(read), std::move(write)}; |
| 92 | } |
| 93 | |
| 94 | // LxsstuLaunchWsl |
| 95 | |
| 96 | DWORD |
| 97 | LxsstuLaunchWsl(_In_opt_ LPCWSTR Arguments, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE StandardOutput, _In_opt_ HANDLE StandardError, _In_opt_ HANDLE Token, _In_ DWORD Flags) |
| 98 | { |
| 99 | // Launch wsl.exe to handle the operation. |
| 100 | auto CommandLine = LxssGenerateWslCommandLine(Arguments); |
| 101 | |
| 102 | return LxsstuRunCommand(CommandLine.data(), StandardInput, StandardOutput, StandardError, Token, Flags); |
| 103 | } |
| 104 | |
| 105 | DWORD |
| 106 | LxsstuLaunchWsl(_In_opt_ const std::wstring& Arguments, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE StandardOutput, _In_opt_ HANDLE StandardError, _In_opt_ HANDLE Token) |
| 107 | { |
| 108 | return LxsstuLaunchWsl(Arguments.data(), StandardInput, StandardOutput, StandardError, Token); |
| 109 | } |
| 110 | |
| 111 | // LxsstuLaunchWslAndCaptureOutput |
| 112 | |
| 113 | std::pair<std::wstring, std::wstring> LxsstuLaunchWslAndCaptureOutput( |
| 114 | _In_ LPCWSTR Cmd, _In_ int ExpectedExitCode, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE Token, _In_ DWORD Flags, _In_ LPCWSTR EntryPoint) |
| 115 | |
| 116 | /*++ |
| 117 | |
| 118 | Routine Description: |
| 119 | |
| 120 | Run a WSL command and capture its output. |
| 121 | |
| 122 | Arguments: |
| 123 | |
| 124 | Cmd - The command line to run. |
| 125 | |
| 126 | ExpectedExitCode - The expected exit code from the child process. |
| 127 | |
| 128 | StandardInput - Handle to the process's standard input |
| 129 | |
| 130 | Return Value: |
| 131 | |
| 132 | A pair of strings with stdout and stderr output. |
| 133 | |
| 134 | --*/ |
| 135 | |
| 136 | { |
| 137 | |
| 138 | auto CommandLine = LxssGenerateWslCommandLine(Cmd, EntryPoint); |
| 139 | return LxsstuLaunchCommandAndCaptureOutput(CommandLine.data(), ExpectedExitCode, StandardInput, Token, Flags); |
| 140 | } |
| 141 | |
| 142 | // LxssGenerateWslCommandLine |
| 143 | |
| 144 | std::wstring LxssGenerateWslCommandLine(_In_opt_ LPCWSTR Arguments, _In_ LPCWSTR EntryPoint) |
| 145 | { |
| 146 | std::wstring CommandLine; |
| 147 | THROW_IF_FAILED(wil::GetSystemDirectoryW(CommandLine)); |
| 148 | |
| 149 | CommandLine += L"\\"; |
| 150 | CommandLine += EntryPoint; |
| 151 | if (ARGUMENT_PRESENT(Arguments)) |
| 152 | { |
| 153 | CommandLine += L" "; |
| 154 | CommandLine += Arguments; |
| 155 | } |
| 156 | |
| 157 | return CommandLine; |
| 158 | } |
| 159 | |
| 160 | // LxsstuLaunchWslAndCaptureOutput |
| 161 | |
| 162 | std::pair<std::wstring, std::wstring> LxsstuLaunchWslAndCaptureOutput( |
| 163 | _In_ const std::wstring& Cmd, _In_ int ExpectedExitCode, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE Token, _In_ DWORD Flags, _In_ LPCWSTR EntryPoint) |
| 164 | |
| 165 | /*++ |
| 166 | |
| 167 | Routine Description: |
| 168 | |
| 169 | Run a wsl command and return its output. |
| 170 | |
| 171 | Arguments: |
| 172 | |
| 173 | Cmd - Supplies the wsl command to run. |
| 174 | |
| 175 | ExpectedExitCode - The expected exit code from the child process. |
| 176 | |
| 177 | StandardInput - Handle to the process's standard input |
| 178 | |
| 179 | Return Value: |
| 180 | |
| 181 | The command's stdout and stderr output. |
| 182 | |
| 183 | --*/ |
| 184 | |
| 185 | { |
| 186 | return LxsstuLaunchWslAndCaptureOutput(Cmd.data(), ExpectedExitCode, StandardInput, Token, Flags, EntryPoint); |
| 187 | } |
| 188 | |
| 189 | std::pair<std::wstring, std::wstring> LxsstuLaunchCommandAndCaptureOutput(_In_ LPWSTR Cmd, _In_ LPCSTR StandardInput, _In_opt_ HANDLE Token, _In_ DWORD Flags) |
| 190 | { |
| 191 | const auto inputSize = static_cast<DWORD>(strlen(StandardInput)); |
| 192 | auto [read, write] = CreateSubprocessPipe(true, false, inputSize); |
| 193 | THROW_IF_WIN32_BOOL_FALSE(WriteFile(write.get(), StandardInput, inputSize, nullptr, nullptr)); |
| 194 | write.reset(); |
| 195 | |
| 196 | return LxsstuLaunchCommandAndCaptureOutput(Cmd, 0, read.get(), Token, Flags); |
| 197 | } |
| 198 | |
| 199 | // LxsstuLaunchCommandAndCaptureOutputWithResult |
| 200 | |
| 201 | std::tuple<std::wstring, std::wstring, int> LxsstuLaunchCommandAndCaptureOutputWithResult( |
| 202 | _In_ LPWSTR Cmd, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE Token, _In_ DWORD Flags) |
| 203 | |
| 204 | /*++ |
| 205 | |
| 206 | Routine Description: |
| 207 | |
| 208 | Run a command and capture its output. |
| 209 | |
| 210 | Arguments: |
| 211 | |
| 212 | Cmd - The command line to run. |
| 213 | |
| 214 | Return Value: |
| 215 | |
| 216 | A pair of strings with stdout and stderr output. |
| 217 | |
| 218 | --*/ |
| 219 | |
| 220 | { |
| 221 | |
| 222 | wsl::windows::common::SubProcess process(nullptr, Cmd); |
| 223 | process.SetStdHandles(StandardInput, nullptr, nullptr); |
| 224 | process.SetToken(Token); |
| 225 | process.SetFlags(Flags); |
| 226 | |
| 227 | auto result = process.RunAndCaptureOutput(); |
| 228 | |
| 229 | return {result.Stdout, result.Stderr, result.ExitCode}; |
| 230 | } |
| 231 | |
| 232 | // LxsstuLaunchCommandAndCaptureOutput |
| 233 | |
| 234 | std::pair<std::wstring, std::wstring> LxsstuLaunchCommandAndCaptureOutput( |
| 235 | _In_ LPWSTR Cmd, _In_ int ExpectedExitCode, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE Token, _In_ DWORD Flags) |
| 236 | |
| 237 | /*++ |
| 238 | |
| 239 | Routine Description: |
| 240 | |
| 241 | Run a command and capture its output. |
| 242 | |
| 243 | Arguments: |
| 244 | |
| 245 | Cmd - The command line to run. |
| 246 | |
| 247 | Return Value: |
| 248 | |
| 249 | A pair of strings with stdout and stderr output. |
| 250 | |
| 251 | --*/ |
| 252 | |
| 253 | { |
| 254 | auto [Out, Err, ExitCode] = LxsstuLaunchCommandAndCaptureOutputWithResult(Cmd, StandardInput, Token, Flags); |
| 255 | if (ExitCode != ExpectedExitCode) |
| 256 | { |
| 257 | THROW_HR_MSG( |
| 258 | E_UNEXPECTED, |
| 259 | "Command \"%ls\" " |
| 260 | "returned unexpected exit code (%lu != %i). " |
| 261 | "Stdout: '%ls' " |
| 262 | "Stderr: '%ls'", |
| 263 | Cmd, |
| 264 | ExitCode, |
| 265 | ExpectedExitCode, |
| 266 | Out.c_str(), |
| 267 | Err.c_str()); |
| 268 | } |
| 269 | |
| 270 | return std::make_pair(Out, Err); |
| 271 | } |
| 272 | |
| 273 | // LxsstuRunCommand |
| 274 | |
| 275 | DWORD |
| 276 | LxsstuRunCommand(_In_ LPWSTR Command, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE StandardOutput, _In_opt_ HANDLE StandardError, _In_opt_ HANDLE Token, _In_ DWORD Flags) |
| 277 | { |
| 278 | const auto Process = LxsstuStartProcess(Command, StandardInput, StandardOutput, StandardError, Token, Flags); |
| 279 | return wsl::windows::common::SubProcess::GetExitCode(Process.get()); |
| 280 | } |
| 281 | |
| 282 | // LxsstuStartProcess |
| 283 | |
| 284 | wil::unique_handle LxsstuStartProcess( |
| 285 | _In_ LPWSTR Command, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE StandardOutput, _In_opt_ HANDLE StandardError, _In_opt_ HANDLE Token, _In_ DWORD Flags) |
| 286 | { |
| 287 | wsl::windows::common::SubProcess process(nullptr, Command); |
| 288 | |
| 289 | process.SetStdHandles( |
| 290 | ARGUMENT_PRESENT(StandardInput) ? StandardInput : GetStdHandle(STD_INPUT_HANDLE), |
| 291 | ARGUMENT_PRESENT(StandardOutput) ? StandardOutput : GetStdHandle(STD_OUTPUT_HANDLE), |
| 292 | ARGUMENT_PRESENT(StandardError) ? StandardError : GetStdHandle(STD_ERROR_HANDLE)); |
| 293 | |
| 294 | process.SetToken(Token); |
| 295 | process.SetFlags(Flags); |
| 296 | |
| 297 | return process.Start(); |
| 298 | } |
| 299 | |
| 300 | // FileFromHandle |
| 301 | |
| 302 | wil::unique_file FileFromHandle(_Inout_ wil::unique_handle& Handle, _In_ const char* Mode) |
| 303 | |
| 304 | /*++ |
| 305 | |
| 306 | Routine Description: |
| 307 | |
| 308 | Create a FILE from a handle. |
| 309 | |
| 310 | Arguments: |
| 311 | Handle - The handle to create the FILE from. |
| 312 | |
| 313 | Mode - The mode to create the FILE with. |
| 314 | |
| 315 | Return Value: |
| 316 | |
| 317 | The created FILE. |
| 318 | |
| 319 | --*/ |
| 320 | |
| 321 | { |
| 322 | |
| 323 | using UniqueFd = wil::unique_any<int, decltype(_close), _close, wil::details::pointer_access_all, int, int, -1>; |
| 324 | |
| 325 | UniqueFd Fd(_open_osfhandle(reinterpret_cast<intptr_t>(Handle.get()), 0)); |
| 326 | if (Fd.get() < 0) |
| 327 | { |
| 328 | THROW_LAST_ERROR_MSG("_open_osfhandle failed"); |
| 329 | } |
| 330 | |
| 331 | Handle.release(); |
| 332 | |
| 333 | wil::unique_file File(_fdopen(Fd.get(), Mode)); |
| 334 | VERIFY_IS_NOT_NULL(File.get()); |
| 335 | Fd.release(); |
| 336 | |
| 337 | return File; |
| 338 | } |
| 339 | |
| 340 | // LxsstuInitialize |
| 341 | |
| 342 | BOOL LxsstuInitialize(__in BOOLEAN RunInstanceTests) |
| 343 | { |
| 344 | wil::unique_hkey Key; |
| 345 | LRESULT Result; |
| 346 | BOOL Success; |
| 347 | DWORD Value; |
| 348 | |
| 349 | Success = FALSE; |
| 350 | |
| 351 | THROW_IF_FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)); |
| 352 | |
| 353 | // |
| 354 | // Don't fail if CoInitializeSecurity has already been called. |
| 355 | // |
| 356 | |
| 357 | const auto Hr = CoInitializeSecurity( |
| 358 | nullptr, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_STATIC_CLOAKING, 0); |
| 359 | |
| 360 | THROW_HR_IF(Hr, FAILED(Hr) && Hr != RPC_E_TOO_LATE); |
| 361 | |
| 362 | WSADATA Data; |
| 363 | THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &Data)); |
| 364 | |
| 365 | VERIFY_IS_TRUE(SetEnvironmentVariableW(L"WSL_UTF8", L"1")); |
| 366 | |
| 367 | if (LxsstuVmMode() == FALSE) |
| 368 | { |
| 369 | Result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, LXSS_REGISTRY_PATH, 0, KEY_ALL_ACCESS, &Key); |
| 370 | |
| 371 | if (Result != ERROR_SUCCESS) |
| 372 | { |
| 373 | LogError("RegOpenKeyEx %s failed with %Id", LXSS_REGISTRY_PATH, Result); |
| 374 | |
| 375 | goto InitializeEnd; |
| 376 | } |
| 377 | |
| 378 | // |
| 379 | // Set the error level to critical so the driver will not break into kd |
| 380 | // while the test is running. |
| 381 | // |
| 382 | |
| 383 | Value = LxErrorLevel_Critical; |
| 384 | Result = RegSetValueEx(Key.get(), LX_QUERY_REGISTRY_ERROR_LEVEL_SUBKEY, 0, REG_DWORD, (const PBYTE)&Value, sizeof(DWORD)); |
| 385 | |
| 386 | if (Result != ERROR_SUCCESS) |
| 387 | { |
| 388 | LogError("RegSetValueEx %s failed with %Id", LX_QUERY_REGISTRY_ERROR_LEVEL_SUBKEY, Result); |
| 389 | |
| 390 | goto InitializeEnd; |
| 391 | } |
| 392 | |
| 393 | // |
| 394 | // Disable breaking on syscall failures. |
| 395 | // |
| 396 | |
| 397 | Value = FALSE; |
| 398 | Result = RegSetValueEx(Key.get(), LX_QUERY_REGISTRY_BREAK_ON_SYSCALL_FAILURE_SUBKEY, 0, REG_DWORD, (const PBYTE)&Value, sizeof(DWORD)); |
| 399 | |
| 400 | if (Result != ERROR_SUCCESS) |
| 401 | { |
| 402 | LogError("RegSetValueEx %s failed with %Id", LX_QUERY_REGISTRY_BREAK_ON_SYSCALL_FAILURE_SUBKEY, Result); |
| 403 | |
| 404 | goto InitializeEnd; |
| 405 | } |
| 406 | |
| 407 | // |
| 408 | // Enable lxbus root access. |
| 409 | // |
| 410 | |
| 411 | Value = TRUE; |
| 412 | Result = RegSetValueEx(Key.get(), LX_QUERY_REGISTRY_ROOT_LXBUS_ACCESS, 0, REG_DWORD, (const PBYTE)&Value, sizeof(DWORD)); |
| 413 | |
| 414 | if (Result != ERROR_SUCCESS) |
| 415 | { |
| 416 | LogError("RegSetValueEx %s failed with %Id", LX_QUERY_REGISTRY_ROOT_LXBUS_ACCESS, Result); |
| 417 | |
| 418 | goto InitializeEnd; |
| 419 | } |
| 420 | |
| 421 | // |
| 422 | // Enable mounting DrvFs with case=force. |
| 423 | // |
| 424 | |
| 425 | Value = TRUE; |
| 426 | Result = RegSetValueEx(Key.get(), LX_QUERY_REGISTRY_DRVFS_ALLOW_FORCE_CASE_SENSITIVITY, 0, REG_DWORD, (const PBYTE)&Value, sizeof(DWORD)); |
| 427 | |
| 428 | if (Result != ERROR_SUCCESS) |
| 429 | { |
| 430 | LogError("RegSetValueEx %s failed with %Id", LX_QUERY_REGISTRY_DRVFS_ALLOW_FORCE_CASE_SENSITIVITY, Result); |
| 431 | |
| 432 | goto InitializeEnd; |
| 433 | } |
| 434 | } |
| 435 | else |
| 436 | { |
| 437 | const auto LogDirectory = LxsstuGetTestDirectory() + L"\\log"; |
| 438 | wil::CreateDirectoryDeep(LogDirectory.c_str()); |
| 439 | } |
| 440 | |
| 441 | // |
| 442 | // Run the instance tests. |
| 443 | // |
| 444 | |
| 445 | if (RunInstanceTests != FALSE) |
| 446 | { |
| 447 | VERIFY_NO_THROW(LxsstuInstanceTests()); |
| 448 | } |
| 449 | |
| 450 | Success = TRUE; |
| 451 | |
| 452 | InitializeEnd: |
| 453 | |
| 454 | return Success; |
| 455 | } |
| 456 | |
| 457 | // LxxstuVmMode |
| 458 | |
| 459 | BOOL LxsstuVmMode(VOID) |
| 460 | |
| 461 | /*++ |
| 462 | |
| 463 | Routine Description: |
| 464 | |
| 465 | Queries if the tests are being run in VM mode. |
| 466 | |
| 467 | Arguments: |
| 468 | |
| 469 | None. |
| 470 | |
| 471 | Return Value: |
| 472 | |
| 473 | TRUE if the tests are running in VM mode, FALSE otherwise. |
| 474 | |
| 475 | --*/ |
| 476 | |
| 477 | { |
| 478 | return g_VmMode; |
| 479 | } |
| 480 | |
| 481 | // LxsstuLaunchPowershellAndCaptureOutput |
| 482 | |
| 483 | std::pair<std::wstring, std::wstring> LxsstuLaunchPowershellAndCaptureOutput(_In_ const std::wstring& Cmd, _In_ int ExpectedExitCode) |
| 484 | |
| 485 | /*++ |
| 486 | |
| 487 | Routine Description: |
| 488 | |
| 489 | Run a powershell command and return its output. |
| 490 | |
| 491 | Arguments: |
| 492 | |
| 493 | Cmd - Supplies the powershell command to run. |
| 494 | |
| 495 | ExpectedExitCode - The expected exit code from the child process. |
| 496 | |
| 497 | Return Value: |
| 498 | s |
| 499 | The command's stdout and stderr output. |
| 500 | |
| 501 | --*/ |
| 502 | |
| 503 | { |
| 504 | auto CommandLine = L"Powershell -NoProfile -Command \"" + Cmd + L"\""; |
| 505 | LogInfo("Running the command: %ls\n", CommandLine.c_str()); |
| 506 | return LxsstuLaunchCommandAndCaptureOutput(CommandLine.data(), ExpectedExitCode); |
| 507 | } |
| 508 | |
| 509 | // LxsstuUninitialize |
| 510 | |
| 511 | VOID LxsstuUninitialize(__in BOOLEAN RunInstanceTests) |
| 512 | { |
| 513 | |
| 514 | wil::unique_hkey Key; |
| 515 | LRESULT Result; |
| 516 | |
| 517 | // |
| 518 | // Run the instance tests again to make sure that the instance can be |
| 519 | // started and stopped (i.e. no leaked fs references). |
| 520 | // |
| 521 | |
| 522 | if (RunInstanceTests != FALSE) |
| 523 | { |
| 524 | VERIFY_NO_THROW(LxsstuInstanceTests()); |
| 525 | } |
| 526 | |
| 527 | if (LxsstuVmMode() == FALSE) |
| 528 | { |
| 529 | |
| 530 | // |
| 531 | // Delete registry subkeys that were set by the test framework. |
| 532 | // |
| 533 | |
| 534 | Result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, LXSS_REGISTRY_PATH, 0, KEY_ALL_ACCESS, &Key); |
| 535 | |
| 536 | if (Result != ERROR_SUCCESS) |
| 537 | { |
| 538 | LogInfo("RegOpenKeyEx failed with %Id", Result); |
| 539 | } |
| 540 | else |
| 541 | { |
| 542 | auto DeleteKey = [&](LPCWSTR KeyName) { |
| 543 | Result = RegDeleteKeyValue(Key.get(), nullptr, KeyName); |
| 544 | if (Result != ERROR_SUCCESS) |
| 545 | { |
| 546 | LogInfo("RegDeleteKeyValue %s failed with %Id", KeyName, Result); |
| 547 | } |
| 548 | }; |
| 549 | |
| 550 | DeleteKey(LX_QUERY_REGISTRY_ERROR_LEVEL_SUBKEY); |
| 551 | DeleteKey(LX_QUERY_REGISTRY_BREAK_ON_SYSCALL_FAILURE_SUBKEY); |
| 552 | DeleteKey(LX_QUERY_REGISTRY_ROOT_LXBUS_ACCESS); |
| 553 | DeleteKey(LX_QUERY_REGISTRY_DRVFS_ALLOW_FORCE_CASE_SENSITIVITY); |
| 554 | } |
| 555 | } |
| 556 | |
| 557 | VERIFY_IS_TRUE(SetEnvironmentVariableW(L"WSL_UTF8", nullptr)); |
| 558 | |
| 559 | WSACleanup(); |
| 560 | |
| 561 | // |
| 562 | // Clear the winrt cache in case LookupLiftedPackage() is called again after another CoInitialize(). |
| 563 | // |
| 564 | |
| 565 | winrt::clear_factory_cache(); |
| 566 | |
| 567 | CoUninitialize(); |
| 568 | |
| 569 | return; |
| 570 | } |
| 571 | |
| 572 | // LxssLogKernelOutput |
| 573 | |
| 574 | void LxssLogKernelOutput(VOID) |
| 575 | |
| 576 | /*++ |
| 577 | |
| 578 | Routine Description: |
| 579 | |
| 580 | Write the kernel output in the test logs. |
| 581 | |
| 582 | Arguments: |
| 583 | None. |
| 584 | |
| 585 | Return Value: |
| 586 | |
| 587 | None. |
| 588 | |
| 589 | --*/ |
| 590 | |
| 591 | { |
| 592 | if (!g_LogDmesgAfterEachTest) |
| 593 | { |
| 594 | return; |
| 595 | } |
| 596 | |
| 597 | // |
| 598 | // dmesg -c isn't implemented on WSL1 |
| 599 | // |
| 600 | |
| 601 | const auto cmd = LxsstuVmMode() ? L"dmesg -c" : L"dmesg"; |
| 602 | const auto Output = LxsstuLaunchWslAndCaptureOutput(cmd); |
| 603 | LogInfo("Kernel logs: '%ls'", Output.first.c_str()); |
| 604 | } |
| 605 | |
| 606 | // LxsstuGetTestDirectory |
| 607 | |
| 608 | std::wstring LxsstuGetTestDirectory(VOID) |
| 609 | |
| 610 | /*++ |
| 611 | |
| 612 | Description: |
| 613 | |
| 614 | This routine gets the test directory. |
| 615 | |
| 616 | Parameters: |
| 617 | |
| 618 | None. |
| 619 | |
| 620 | Return: |
| 621 | |
| 622 | The test directory. |
| 623 | |
| 624 | --*/ |
| 625 | |
| 626 | { |
| 627 | |
| 628 | std::wstring TestDirectory = LxsstuGetLxssDirectory(); |
| 629 | TestDirectory += L"\\" LXSS_ROOTFS_DIRECTORY LXSS_TEST_DIRECTORY; |
| 630 | return TestDirectory; |
| 631 | } |
| 632 | |
| 633 | // LxsstuGetLxssDirectory |
| 634 | |
| 635 | std::wstring LxsstuGetLxssDirectory(VOID) |
| 636 | |
| 637 | /*++ |
| 638 | |
| 639 | Description: |
| 640 | |
| 641 | This routine gets the lxss directory. |
| 642 | |
| 643 | Parameters: |
| 644 | |
| 645 | None. |
| 646 | |
| 647 | Return: |
| 648 | |
| 649 | The lxss directory. |
| 650 | |
| 651 | --*/ |
| 652 | |
| 653 | { |
| 654 | |
| 655 | const wil::unique_hkey LxssKey = wsl::windows::common::registry::OpenLxssUserKey(); |
| 656 | const std::wstring Default = wsl::windows::common::registry::ReadString(LxssKey.get(), nullptr, L"DefaultDistribution", nullptr); |
| 657 | |
| 658 | std::wstring BasePath = wsl::windows::common::registry::ReadString(LxssKey.get(), Default.c_str(), L"BasePath", nullptr); |
| 659 | |
| 660 | return BasePath; |
| 661 | } |
| 662 | |
| 663 | void CaptureLiveDump() |
| 664 | { |
| 665 | auto PrivilegeState = wsl::windows::common::security::AcquirePrivilege(SE_DEBUG_NAME); |
| 666 | |
| 667 | const std::wstring targetFile = g_dumpFolder + L"\\livedump.dmp"; |
| 668 | LogInfo("Writing livedump in: %ls", targetFile.c_str()); |
| 669 | |
| 670 | wsl::windows::common::SubProcess dumpProcess{nullptr, std::format(L"{} \"{}\"", g_dumpToolPath->c_str(), targetFile.c_str()).c_str()}; |
| 671 | const auto exitCode = dumpProcess.Run(); |
| 672 | if (exitCode != 0) |
| 673 | { |
| 674 | LogError("Failed to capture livedump. ExitCode=%lu", exitCode); |
| 675 | return; |
| 676 | } |
| 677 | |
| 678 | LogInfo("Dump size: %llu", std::filesystem::file_size(targetFile)); |
| 679 | |
| 680 | // Try to compress the dump. |
| 681 | std::wstring command = L"Powershell.exe -NoProfile -ExecutionPolicy Bypass -Command \"Compress-Archive -Force -Path '" + |
| 682 | targetFile + L"' -DestinationPath '" + targetFile + L".zip'\""; |
| 683 | if (LxsstuRunCommand(command.data()) != 0) |
| 684 | { |
| 685 | // Note: powershell will fail to create the .zip if the dump is bigger than 2GB with: |
| 686 | // Exception calling "Write" with "3" argument(s): "Stream was too long." |
| 687 | LogError("Failed to compress live dump"); |
| 688 | } |
| 689 | else |
| 690 | { |
| 691 | THROW_IF_WIN32_BOOL_FALSE(DeleteFile(targetFile.c_str())); |
| 692 | } |
| 693 | } |
| 694 | |
| 695 | DEFINE_ENUM_FLAG_OPERATORS(MINIDUMP_TYPE); |
| 696 | |
| 697 | DWORD FindThreadInProcess(DWORD Pid) |
| 698 | { |
| 699 | const wil::unique_handle Threads{CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)}; |
| 700 | |
| 701 | THREADENTRY32 ThreadInfo{}; |
| 702 | ThreadInfo.dwSize = sizeof(ThreadInfo); |
| 703 | for (auto result = Thread32First(Threads.get(), &ThreadInfo); result; result = Thread32Next(Threads.get(), &ThreadInfo)) |
| 704 | { |
| 705 | if (ThreadInfo.th32OwnerProcessID == Pid) |
| 706 | { |
| 707 | return ThreadInfo.th32ThreadID; |
| 708 | } |
| 709 | } |
| 710 | |
| 711 | THROW_HR(HRESULT_FROM_WIN32(STATUS_NOT_FOUND)); |
| 712 | } |
| 713 | |
| 714 | PVOID GetModuleAddressInProcess(HANDLE Process, const std::wstring& Module) |
| 715 | { |
| 716 | // From: https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodulesex |
| 717 | // Do not call CloseHandle on any of the handles returned by this function. The information comes from a snapshot, so there are no resources to be freed. |
| 718 | |
| 719 | std::vector<HMODULE> Modules; |
| 720 | DWORD RequiredSize{}; |
| 721 | bool Result{}; |
| 722 | do |
| 723 | { |
| 724 | Modules.resize(RequiredSize / sizeof(HMODULE)); |
| 725 | Result = EnumProcessModulesEx(Process, Modules.data(), static_cast<DWORD>(Modules.size() * sizeof(HMODULE)), &RequiredSize, LIST_MODULES_ALL); |
| 726 | } while (Result && RequiredSize / sizeof(HMODULE) > Modules.size()); |
| 727 | |
| 728 | for (const auto& e : Modules) |
| 729 | { |
| 730 | std::filesystem::path modulePath = wil::GetModuleFileNameExW<std::wstring>(Process, e); |
| 731 | |
| 732 | if (wsl::windows::common::string::IsPathComponentEqual(modulePath.filename().native(), Module)) |
| 733 | { |
| 734 | MODULEINFO Info{}; |
| 735 | THROW_IF_WIN32_BOOL_FALSE(GetModuleInformation(Process, e, &Info, sizeof(Info))); |
| 736 | |
| 737 | return Info.lpBaseOfDll; |
| 738 | } |
| 739 | } |
| 740 | |
| 741 | THROW_HR(HRESULT_FROM_WIN32(STATUS_NOT_FOUND)); |
| 742 | } |
| 743 | |
| 744 | void CreateCrashReport(HANDLE Process, LPCWSTR ProcessName, DWORD Pid, std::wstring const& EventName) |
| 745 | { |
| 746 | using unique_hreport = wil::unique_any<HREPORT, decltype(WerReportCloseHandle), WerReportCloseHandle>; |
| 747 | |
| 748 | auto setProperty = [](LPWSTR Target, const std::wstring& Value, size_t BufferSize) { |
| 749 | wcsncpy(Target, Value.c_str(), std::min(BufferSize - 1, Value.size())); |
| 750 | }; |
| 751 | |
| 752 | WER_REPORT_INFORMATION Info{}; |
| 753 | Info.dwSize = sizeof(Info); |
| 754 | Info.hProcess = Process; |
| 755 | |
| 756 | setProperty(Info.wzDescription, EventName, ARRAYSIZE(Info.wzDescription)); |
| 757 | setProperty(Info.wzApplicationName, ProcessName, ARRAYSIZE(Info.wzApplicationName)); |
| 758 | setProperty(Info.wzApplicationPath, wil::GetModuleFileNameExW<std::wstring>(Process, nullptr), ARRAYSIZE(Info.wzApplicationPath)); |
| 759 | |
| 760 | unique_hreport Report; |
| 761 | THROW_IF_FAILED(WerReportCreate(EventName.c_str(), WerReportApplicationCrash, &Info, &Report)); |
| 762 | |
| 763 | const std::wstring DumpPath = g_dumpFolder + L"\\" + ProcessName + L"." + std::to_wstring(Pid) + L".hdmp"; |
| 764 | wil::unique_hfile DumpFile{CreateFileW(DumpPath.c_str(), GENERIC_ALL, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)}; |
| 765 | THROW_LAST_ERROR_IF(!DumpFile); |
| 766 | |
| 767 | std::optional<MINIDUMP_EXCEPTION_INFORMATION> ExceptionInfo; |
| 768 | EXCEPTION_RECORD Record{}; |
| 769 | EXCEPTION_POINTERS Pointers{}; |
| 770 | |
| 771 | // To get access to the dumps in AzureWatson, the exception address needs to point to a module |
| 772 | // that we own. To do that, load the main module and point the exception to its entrypoint. |
| 773 | try |
| 774 | { |
| 775 | Record.ExceptionAddress = GetModuleAddressInProcess(Process, ProcessName); |
| 776 | Record.ExceptionCode = EXCEPTION_BREAKPOINT; |
| 777 | Pointers.ExceptionRecord = &Record; |
| 778 | |
| 779 | ExceptionInfo.emplace(); |
| 780 | ExceptionInfo->ExceptionPointers = &Pointers; |
| 781 | ExceptionInfo->ThreadId = FindThreadInProcess(Pid); |
| 782 | } |
| 783 | catch (...) |
| 784 | { |
| 785 | LogError("Failed to find module address / thread id for %ls, 0x%x", ProcessName, wil::ResultFromCaughtException()); |
| 786 | } |
| 787 | |
| 788 | THROW_IF_WIN32_BOOL_FALSE(MiniDumpWriteDump( |
| 789 | Process, |
| 790 | Pid, |
| 791 | DumpFile.get(), |
| 792 | MiniDumpWithDataSegs | MiniDumpWithProcessThreadData | MiniDumpWithHandleData | MiniDumpWithPrivateReadWriteMemory | |
| 793 | MiniDumpWithUnloadedModules | MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo | MiniDumpWithTokenInformation | |
| 794 | MiniDumpWithPrivateWriteCopyMemory | MiniDumpWithCodeSegs, |
| 795 | ExceptionInfo.has_value() ? &ExceptionInfo.value() : nullptr, |
| 796 | nullptr, |
| 797 | nullptr)); |
| 798 | |
| 799 | DumpFile.reset(); |
| 800 | |
| 801 | THROW_IF_FAILED(WerReportAddFile(Report.get(), DumpPath.c_str(), WerFileTypeHeapdump, 0)); |
| 802 | |
| 803 | WER_SUBMIT_RESULT SubmitResult{}; |
| 804 | const auto Result = WerReportSubmit( |
| 805 | Report.get(), |
| 806 | WerConsentApproved, |
| 807 | WER_SUBMIT_ADD_REGISTERED_DATA | WER_SUBMIT_NO_CLOSE_UI | WER_SUBMIT_BYPASS_DATA_THROTTLING | WER_SUBMIT_REPORT_MACHINE_ID | WER_SUBMIT_QUEUE, |
| 808 | &SubmitResult); |
| 809 | |
| 810 | LogInfo("WerReportSubmit() returned 0x%x, SubmitResult = %i, EventName = %ls", Result, SubmitResult, EventName.c_str()); |
| 811 | } |
| 812 | |
| 813 | void CreateProcessCrashReport(DWORD Pid, LPCWSTR ImageName, LPCWSTR EventName) |
| 814 | { |
| 815 | try |
| 816 | { |
| 817 | LogInfo("Opening process %ls, Pid %lu", ImageName, Pid); |
| 818 | const wil::unique_handle Process(OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid)); |
| 819 | THROW_LAST_ERROR_IF_NULL(Process); |
| 820 | |
| 821 | CreateCrashReport(Process.get(), ImageName, Pid, EventName); |
| 822 | } |
| 823 | catch (...) |
| 824 | { |
| 825 | LogError("Failed to create crash report for process %ls (%lu), %lu", ImageName, Pid, wil::ResultFromCaughtException()); |
| 826 | } |
| 827 | } |
| 828 | |
| 829 | void CreateWerReports() |
| 830 | { |
| 831 | static const std::set<std::wstring, wsl::shared::string::CaseInsensitiveCompare> WslProcesses{ |
| 832 | L"wsl.exe", |
| 833 | L"wslhost.exe", |
| 834 | L"wslrelay.exe", |
| 835 | L"wslservice.exe", |
| 836 | L"wslg.exe", |
| 837 | L"vmcompute.exe", |
| 838 | L"vmwp.exe", |
| 839 | L"wslcsession.exe", |
| 840 | L"wslc.exe"}; |
| 841 | |
| 842 | auto PrivilegeState = wsl::windows::common::security::AcquirePrivilege(SE_DEBUG_NAME); |
| 843 | const std::wstring EventName = L"WslTestHang-" + g_pipelineBuildId; |
| 844 | |
| 845 | LogInfo("Dumps here: https://azurewatson.microsoft.com/?EventType=%s", EventName.c_str()); |
| 846 | |
| 847 | // Start by capturing the test process, since collect dmesg changes the state of the UVM. |
| 848 | try |
| 849 | { |
| 850 | CreateProcessCrashReport(GetCurrentProcessId(), L"te.processhost.exe", EventName.c_str()); |
| 851 | } |
| 852 | CATCH_LOG(); |
| 853 | |
| 854 | PROCESSENTRY32 PE32; |
| 855 | PE32.dwSize = sizeof(PE32); |
| 856 | const wil::unique_handle ProcessSnapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); |
| 857 | THROW_LAST_ERROR_IF(ProcessSnapshot.get() == INVALID_HANDLE_VALUE); |
| 858 | |
| 859 | try |
| 860 | { |
| 861 | if (Process32First(ProcessSnapshot.get(), &PE32)) |
| 862 | { |
| 863 | do |
| 864 | { |
| 865 | if (WslProcesses.find(std::wstring(PE32.szExeFile)) == WslProcesses.end()) |
| 866 | { |
| 867 | continue; |
| 868 | } |
| 869 | |
| 870 | try |
| 871 | { |
| 872 | CreateProcessCrashReport(PE32.th32ProcessID, PE32.szExeFile, EventName.c_str()); |
| 873 | } |
| 874 | CATCH_LOG(); |
| 875 | |
| 876 | } while (Process32Next(ProcessSnapshot.get(), &PE32)); |
| 877 | } |
| 878 | THROW_LAST_ERROR_IF(GetLastError() != ERROR_NO_MORE_FILES); |
| 879 | } |
| 880 | CATCH_LOG(); |
| 881 | |
| 882 | // Also capture an HNS dump. Since the process name is svchost.exe, find its pid from its service. |
| 883 | const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT)}; |
| 884 | THROW_LAST_ERROR_IF_NULL(manager); |
| 885 | |
| 886 | const wil::unique_schandle service{OpenService(manager.get(), L"HNS", SERVICE_QUERY_STATUS)}; |
| 887 | THROW_LAST_ERROR_IF_NULL(service); |
| 888 | |
| 889 | auto [_, pid] = GetServiceState(service.get()); |
| 890 | CreateProcessCrashReport(pid, L"svchost.exe", EventName.c_str()); |
| 891 | } |
| 892 | |
| 893 | void DumpGuestProcesses() |
| 894 | { |
| 895 | constexpr auto dumpScript = |
| 896 | R"( |
| 897 | set -ue |
| 898 | |
| 899 | dmesg |
| 900 | |
| 901 | # Try to install gdb |
| 902 | tdnf install -y gdb || true |
| 903 | |
| 904 | declare -a pids_to_dump |
| 905 | |
| 906 | for proc in /proc/[0-9]*; do |
| 907 | read -a stats < "$proc/stat" # Skip kernel threads to make the output easier to read |
| 908 | flags=${stats[8]} |
| 909 | |
| 910 | if (( ("$flags" & 0x00200000) == 0x00200000 )); then |
| 911 | continue |
| 912 | fi |
| 913 | |
| 914 | pid=$(basename "$proc") |
| 915 | |
| 916 | pids_to_dump+=("$pid") |
| 917 | parent=$(ps -o ppid= -p "$pid") |
| 918 | |
| 919 | echo -e "\nProcess: $pid (parent: $parent) " |
| 920 | echo -en "cmd: " |
| 921 | cat "/proc/$pid/cmdline" || true |
| 922 | echo -e "\nstat: " |
| 923 | cat "/proc/$pid/stat" || true |
| 924 | |
| 925 | for tid in $(ls "/proc/$pid/task" || true); do |
| 926 | echo -n "tid: $tid - " |
| 927 | cat "/proc/$pid/task/$tid/comm" || true |
| 928 | cat "/proc/$pid/task/$tid/stack" || true |
| 929 | done |
| 930 | |
| 931 | echo "fds: " |
| 932 | ls -la "/proc/$pid/fd" || true |
| 933 | done |
| 934 | |
| 935 | for pid in "${pids_to_dump[@]}" ; do |
| 936 | name=$(ps -p "$pid" -o comm=) |
| 937 | if [[ "$name" =~ ^(bash|login)$ ]]; then |
| 938 | echo "Skipping dump for process: $name" |
| 939 | continue |
| 940 | fi |
| 941 | |
| 942 | echo "Dumping process: $name ($pid) " |
| 943 | if gcore -a -o core "$pid" ; then |
| 944 | if ! /wsl-capture-crash 0 "$name" "$pid" 0 < "core.$pid" ; then |
| 945 | echo "Failed to dump process $pid" |
| 946 | fi |
| 947 | |
| 948 | rm "core.$pid" |
| 949 | fi |
| 950 | done |
| 951 | |
| 952 | echo "hvsockets: " |
| 953 | ss -lap --vsock |
| 954 | |
| 955 | echo "meminfo: " |
| 956 | cat /proc/meminfo |
| 957 | |
| 958 | poweroff -f |
| 959 | )"; |
| 960 | |
| 961 | const std::wstring filePath = g_dumpFolder + L"\\guest-state.txt"; |
| 962 | LogInfo("Dumping guest processes in: %ls", filePath.c_str()); |
| 963 | |
| 964 | const wil::unique_hfile outputFile{CreateFileW( |
| 965 | filePath.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)}; |
| 966 | |
| 967 | THROW_LAST_ERROR_IF(!outputFile); |
| 968 | THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(outputFile.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)); |
| 969 | |
| 970 | auto [readPipe, writePipe] = CreateSubprocessPipe(true, false); |
| 971 | |
| 972 | auto cmd = LxssGenerateWslCommandLine(L"--debug-shell"); |
| 973 | const auto process = LxsstuStartProcess(cmd.data(), readPipe.get(), outputFile.get()); |
| 974 | |
| 975 | THROW_IF_WIN32_BOOL_FALSE(WriteFile(writePipe.get(), dumpScript, static_cast<DWORD>(strlen(dumpScript)), nullptr, nullptr)); |
| 976 | writePipe.reset(); |
| 977 | |
| 978 | // Wait up to 5 minutes for that process |
| 979 | const auto result = WaitForSingleObject(process.get(), 60 * 1000 * 5); |
| 980 | if (result != WAIT_TIMEOUT) |
| 981 | { |
| 982 | LogError("Unexpected status waiting for the debug shell, %lu", result); |
| 983 | } |
| 984 | } |
| 985 | |
| 986 | // LxsstuWatchdogTimer |
| 987 | |
| 988 | VOID __stdcall LxsstuWatchdogTimer(_Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_opt_ PVOID ThreadpoolTimerContext, _Inout_ PTP_TIMER Timer) |
| 989 | |
| 990 | /*++ |
| 991 | |
| 992 | Routine Description: |
| 993 | |
| 994 | Runs when the watch dog timer has fired to crash the process. |
| 995 | |
| 996 | Arguments: |
| 997 | |
| 998 | Instance - Not used. |
| 999 | |
| 1000 | ThreadpoolTimerContext - Not used. |
| 1001 | |
| 1002 | Timer - Not used. |
| 1003 | |
| 1004 | Return Value: |
| 1005 | |
| 1006 | None. |
| 1007 | |
| 1008 | --*/ |
| 1009 | |
| 1010 | { |
| 1011 | |
| 1012 | UNREFERENCED_PARAMETER(Instance); |
| 1013 | UNREFERENCED_PARAMETER(ThreadpoolTimerContext); |
| 1014 | UNREFERENCED_PARAMETER(Timer); |
| 1015 | |
| 1016 | try |
| 1017 | { |
| 1018 | if (g_enableWerReport) |
| 1019 | { |
| 1020 | CreateWerReports(); |
| 1021 | } |
| 1022 | else |
| 1023 | { |
| 1024 | LogError("Wer reporting disabled, skipping"); |
| 1025 | } |
| 1026 | } |
| 1027 | catch (...) |
| 1028 | { |
| 1029 | LogError("Failed to create WER report, 0x%x", wil::ResultFromCaughtException()); |
| 1030 | } |
| 1031 | |
| 1032 | if (LxsstuVmMode()) |
| 1033 | { |
| 1034 | try |
| 1035 | { |
| 1036 | DumpGuestProcesses(); |
| 1037 | } |
| 1038 | catch (...) |
| 1039 | { |
| 1040 | LogError("Failed to dump guest processes, 0x%x", wil::ResultFromCaughtException()); |
| 1041 | } |
| 1042 | } |
| 1043 | |
| 1044 | try |
| 1045 | { |
| 1046 | if (g_enableWerReport && g_dumpToolPath.has_value()) |
| 1047 | { |
| 1048 | CaptureLiveDump(); |
| 1049 | } |
| 1050 | } |
| 1051 | catch (...) |
| 1052 | { |
| 1053 | LogError("Failed to capture livedump, 0x%x", wil::ResultFromCaughtException()); |
| 1054 | } |
| 1055 | |
| 1056 | __fastfail(FAST_FAIL_FATAL_APP_EXIT); |
| 1057 | return; |
| 1058 | } |
| 1059 | |
| 1060 | // LxsstuInstanceTests |
| 1061 | |
| 1062 | VOID LxsstuInstanceTests(VOID) |
| 1063 | |
| 1064 | /*++ |
| 1065 | |
| 1066 | Routine Description: |
| 1067 | |
| 1068 | Runs the instance unit tests. |
| 1069 | |
| 1070 | Arguments: |
| 1071 | |
| 1072 | None. |
| 1073 | |
| 1074 | Return Value: |
| 1075 | |
| 1076 | None. |
| 1077 | |
| 1078 | --*/ |
| 1079 | |
| 1080 | { |
| 1081 | |
| 1082 | ULONG Iteration; |
| 1083 | ULONG NumberOfIterations; |
| 1084 | ULONG SleepDuration; |
| 1085 | unsigned int Seed; |
| 1086 | |
| 1087 | // |
| 1088 | // Start and stop an instance multiple times, sleeping a random duration |
| 1089 | // between the start and stop. |
| 1090 | // |
| 1091 | |
| 1092 | NumberOfIterations = 5; |
| 1093 | Seed = GetTickCount(); |
| 1094 | srand(Seed); |
| 1095 | LogInfo("Starting instance tests, Seed = %u", Seed); |
| 1096 | for (Iteration = 0; Iteration < NumberOfIterations; Iteration++) |
| 1097 | { |
| 1098 | LogInfo("Create instance - Iteration %u of %u", (Iteration + 1), NumberOfIterations); |
| 1099 | |
| 1100 | VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"/bin/true"), 0u); |
| 1101 | SleepDuration = rand() % LXSS_INSTANCE_TEST_TIMEOUT; |
| 1102 | LogInfo("Sleeping %u milliseconds before destroying instance...", SleepDuration); |
| 1103 | |
| 1104 | SleepEx(SleepDuration, FALSE); |
| 1105 | TerminateDistribution(); |
| 1106 | } |
| 1107 | |
| 1108 | LogPass("Instance tests passed"); |
| 1109 | |
| 1110 | return; |
| 1111 | } |
| 1112 | |
| 1113 | // LxxsSplitString |
| 1114 | |
| 1115 | std::vector<std::wstring> LxssSplitString(_In_ const std::wstring& String, _In_ const std::wstring& Delim) |
| 1116 | |
| 1117 | /*++ |
| 1118 | |
| 1119 | Routine Description: |
| 1120 | |
| 1121 | Split a string by a delimiter. |
| 1122 | |
| 1123 | Arguments: |
| 1124 | |
| 1125 | String - Supplies the string to split. |
| 1126 | |
| 1127 | Delim - The delimiter to split the string on. |
| 1128 | |
| 1129 | Return Value: |
| 1130 | |
| 1131 | A vector of split string. |
| 1132 | |
| 1133 | --*/ |
| 1134 | |
| 1135 | { |
| 1136 | std::vector<std::wstring> output; |
| 1137 | |
| 1138 | std::wistringstream input(String); |
| 1139 | std::wstring entry; |
| 1140 | |
| 1141 | std::string::size_type index = 0; |
| 1142 | std::string::size_type previous_index = 0; |
| 1143 | |
| 1144 | while ((index = String.find(Delim, previous_index)) != std::string::npos) |
| 1145 | { |
| 1146 | output.emplace_back(String.substr(previous_index, index - previous_index)); |
| 1147 | previous_index = index + Delim.size(); |
| 1148 | } |
| 1149 | |
| 1150 | auto remaining = String.substr(previous_index); |
| 1151 | if (remaining != Delim && !remaining.empty()) |
| 1152 | { |
| 1153 | output.emplace_back(std::move(remaining)); |
| 1154 | } |
| 1155 | |
| 1156 | return output; |
| 1157 | } |
| 1158 | |
| 1159 | // WslKeepAlive class definitions |
| 1160 | |
| 1161 | WslKeepAlive::WslKeepAlive(HANDLE Token) : m_token(Token) |
| 1162 | { |
| 1163 | Set(); |
| 1164 | } |
| 1165 | |
| 1166 | WslKeepAlive::~WslKeepAlive() |
| 1167 | { |
| 1168 | Reset(); |
| 1169 | } |
| 1170 | |
| 1171 | void WslKeepAlive::Set() |
| 1172 | { |
| 1173 | std::tie(m_read, m_write) = CreateSubprocessPipe(true, false); |
| 1174 | |
| 1175 | m_running.emplace(); |
| 1176 | m_thread = std::thread(std::bind(&WslKeepAlive::Run, this)); |
| 1177 | m_running->get_future().wait(); |
| 1178 | } |
| 1179 | |
| 1180 | void WslKeepAlive::Run() |
| 1181 | { |
| 1182 | try |
| 1183 | { |
| 1184 | // Create a pipe to read wsl's output |
| 1185 | wil::unique_handle read; |
| 1186 | wil::unique_handle write; |
| 1187 | SECURITY_ATTRIBUTES attributes = {0}; |
| 1188 | attributes.nLength = sizeof(attributes); |
| 1189 | attributes.bInheritHandle = true; |
| 1190 | THROW_LAST_ERROR_IF(!CreatePipe(&read, &write, &attributes, sizeof(attributes))); |
| 1191 | |
| 1192 | // Start a process that outputs 'running', then waits |
| 1193 | const std::wstring expectedOutput = L"running"; |
| 1194 | std::wstring cmd = L"wsl.exe echo -n " + expectedOutput + L" && read -n 1 "; |
| 1195 | const auto process = LxsstuStartProcess(cmd.data(), m_read.get(), write.get(), nullptr, m_token); |
| 1196 | write.reset(); |
| 1197 | |
| 1198 | // Wait until we read 'running' |
| 1199 | std::string buffer(expectedOutput.size(), '\0'); |
| 1200 | DWORD bytesRead = 0; |
| 1201 | VERIFY_IS_TRUE(ReadFile(read.get(), buffer.data(), static_cast<DWORD>(expectedOutput.size()), &bytesRead, nullptr)); |
| 1202 | |
| 1203 | VERIFY_ARE_EQUAL(buffer, wsl::shared::string::WideToMultiByte(expectedOutput)); |
| 1204 | |
| 1205 | m_running->set_value(); |
| 1206 | |
| 1207 | WaitForSingleObject(process.get(), INFINITE); |
| 1208 | } |
| 1209 | catch (...) |
| 1210 | { |
| 1211 | LogError("Caught exception in WslKeepAlive::Run"); |
| 1212 | m_running->set_exception(std::current_exception()); |
| 1213 | } |
| 1214 | } |
| 1215 | |
| 1216 | void WslKeepAlive::Reset() |
| 1217 | { |
| 1218 | if (m_thread.joinable()) |
| 1219 | { |
| 1220 | const char c = 'k'; |
| 1221 | THROW_LAST_ERROR_IF(!WriteFile(m_write.get(), &c, sizeof(c), nullptr, nullptr)); |
| 1222 | m_write.reset(); |
| 1223 | m_thread.join(); |
| 1224 | } |
| 1225 | } |
| 1226 | |
| 1227 | std::pair<DWORD, DWORD> GetServiceState(SC_HANDLE service) |
| 1228 | { |
| 1229 | DWORD dwBytesNeeded{}; |
| 1230 | SERVICE_STATUS_PROCESS status{}; |
| 1231 | if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&status, sizeof(status), &dwBytesNeeded)) |
| 1232 | { |
| 1233 | LogError("QueryServiceStatusEx() failed, %lu", GetLastError()); |
| 1234 | VERIFY_FAIL(); |
| 1235 | } |
| 1236 | |
| 1237 | return std::make_pair(status.dwCurrentState, status.dwProcessId); |
| 1238 | } |
| 1239 | |
| 1240 | void WaitForServiceState(SC_HANDLE service, DWORD state, DWORD previousPid) |
| 1241 | { |
| 1242 | DWORD currentState{}; |
| 1243 | DWORD pid{}; |
| 1244 | auto pred = [&]() { |
| 1245 | std::tie(currentState, pid) = GetServiceState(service); |
| 1246 | if (pid != previousPid && state == SERVICE_STOPPED) |
| 1247 | { |
| 1248 | return; |
| 1249 | } |
| 1250 | |
| 1251 | THROW_HR_IF(E_ABORT, currentState != state && currentState != SERVICE_STOPPED); |
| 1252 | }; |
| 1253 | |
| 1254 | try |
| 1255 | { |
| 1256 | wsl::shared::retry::RetryWithTimeout<void>(pred, std::chrono::milliseconds(100), std::chrono::minutes(2), [&]() { |
| 1257 | return wil::ResultFromCaughtException() == E_ABORT; |
| 1258 | }); |
| 1259 | } |
| 1260 | catch (...) |
| 1261 | { |
| 1262 | LogError("Timed waiting for service to reach state: %lu. Current state: %lu, error: 0x%x", state, currentState, wil::ResultFromCaughtException()); |
| 1263 | } |
| 1264 | } |
| 1265 | |
| 1266 | void StopService(SC_HANDLE service) |
| 1267 | { |
| 1268 | // Some services don't accept SERVICE_CONTROL_STOP when starting. |
| 1269 | // Wait for them to be running before stopping them |
| 1270 | auto [state, pid] = GetServiceState(service); |
| 1271 | if (state == SERVICE_START_PENDING) |
| 1272 | { |
| 1273 | WaitForServiceState(service, SERVICE_RUNNING, pid); |
| 1274 | } |
| 1275 | |
| 1276 | SERVICE_STATUS status{}; |
| 1277 | if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) |
| 1278 | { |
| 1279 | const auto error = GetLastError(); |
| 1280 | if (error != ERROR_SERVICE_NOT_ACTIVE) |
| 1281 | { |
| 1282 | LogError("Unexpected error code: 0x%x", error); |
| 1283 | VERIFY_FAIL(); |
| 1284 | } |
| 1285 | return; // Service is not running |
| 1286 | } |
| 1287 | |
| 1288 | WaitForServiceState(service, SERVICE_STOPPED, pid); |
| 1289 | } |
| 1290 | |
| 1291 | void RestartWslService() |
| 1292 | /*++ |
| 1293 | |
| 1294 | Routine Description: |
| 1295 | |
| 1296 | Restart the WSL service. |
| 1297 | |
| 1298 | Arguments: |
| 1299 | |
| 1300 | None. |
| 1301 | |
| 1302 | Return Value: |
| 1303 | |
| 1304 | None. |
| 1305 | |
| 1306 | --*/ |
| 1307 | { |
| 1308 | LogInfo("Restarting WSLService"); |
| 1309 | const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT)}; |
| 1310 | VERIFY_IS_NOT_NULL(manager); |
| 1311 | |
| 1312 | const wil::unique_schandle service{OpenService(manager.get(), L"wslservice", SERVICE_STOP | SERVICE_QUERY_STATUS | SERVICE_START)}; |
| 1313 | VERIFY_IS_NOT_NULL(service); |
| 1314 | |
| 1315 | StopService(service.get()); |
| 1316 | if (!StartService(service.get(), 0, nullptr)) |
| 1317 | { |
| 1318 | VERIFY_ARE_EQUAL(GetLastError(), ERROR_SERVICE_ALREADY_RUNNING); |
| 1319 | } |
| 1320 | } |
| 1321 | |
| 1322 | void StopWslService() |
| 1323 | { |
| 1324 | LogInfo("Stopping WSLService"); |
| 1325 | const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT)}; |
| 1326 | VERIFY_IS_NOT_NULL(manager); |
| 1327 | |
| 1328 | const wil::unique_schandle service{OpenService(manager.get(), L"wslservice", SERVICE_STOP | SERVICE_QUERY_STATUS)}; |
| 1329 | VERIFY_IS_NOT_NULL(service); |
| 1330 | StopService(service.get()); |
| 1331 | } |
| 1332 | |
| 1333 | wil::unique_handle GetNonElevatedToken(TOKEN_TYPE Type) |
| 1334 | { |
| 1335 | auto token = wil::open_current_access_token(TOKEN_ALL_ACCESS); |
| 1336 | |
| 1337 | if (Type != TokenPrimary) |
| 1338 | { |
| 1339 | // N.B. Using the Safer API to create a non-elevated primary token break drvfs, so skipping this for primary tokens. |
| 1340 | SAFER_LEVEL_HANDLE saferLevel = nullptr; |
| 1341 | auto closeSaferLevel = wil::scope_exit([&]() { SaferCloseLevel(saferLevel); }); |
| 1342 | |
| 1343 | THROW_IF_WIN32_BOOL_FALSE(SaferCreateLevel(SAFER_SCOPEID_MACHINE, SAFER_LEVELID_NORMALUSER, SAFER_LEVEL_OPEN, &saferLevel, nullptr)); |
| 1344 | |
| 1345 | wil::unique_handle restrictedToken; |
| 1346 | THROW_IF_WIN32_BOOL_FALSE(SaferComputeTokenFromLevel(saferLevel, token.get(), &restrictedToken, 0, nullptr)); |
| 1347 | |
| 1348 | token = std::move(restrictedToken); |
| 1349 | } |
| 1350 | |
| 1351 | wil::unique_handle nonElevatedToken; |
| 1352 | THROW_IF_WIN32_BOOL_FALSE(DuplicateTokenEx(token.get(), TOKEN_ALL_ACCESS, nullptr, SecurityImpersonation, Type, &nonElevatedToken)); |
| 1353 | |
| 1354 | wil::unique_sid mediumIntegritySid; |
| 1355 | THROW_LAST_ERROR_IF(!ConvertStringSidToSidA("S-1-16-8192", &mediumIntegritySid)); |
| 1356 | |
| 1357 | TOKEN_MANDATORY_LABEL label = {0}; |
| 1358 | label.Label.Attributes = SE_GROUP_INTEGRITY; |
| 1359 | label.Label.Sid = mediumIntegritySid.get(); |
| 1360 | THROW_IF_WIN32_BOOL_FALSE(SetTokenInformation(nonElevatedToken.get(), TokenIntegrityLevel, &label, sizeof(label))); |
| 1361 | |
| 1362 | return nonElevatedToken; |
| 1363 | } |
| 1364 | |
| 1365 | WslConfigChange::WslConfigChange(const std::wstring& Content) |
| 1366 | { |
| 1367 | m_originalContent = Update(Content); |
| 1368 | } |
| 1369 | |
| 1370 | WslConfigChange::WslConfigChange(WslConfigChange&& other) : m_originalContent(std::move(other.m_originalContent)) |
| 1371 | { |
| 1372 | } |
| 1373 | |
| 1374 | std::wstring WslConfigChange::Update(const std::wstring& Content) |
| 1375 | { |
| 1376 | auto previous = LxssWriteWslConfig(Content); |
| 1377 | |
| 1378 | if (previous != Content) |
| 1379 | { |
| 1380 | RestartWslService(); |
| 1381 | } |
| 1382 | |
| 1383 | return previous; |
| 1384 | } |
| 1385 | |
| 1386 | WslConfigChange::~WslConfigChange() |
| 1387 | { |
| 1388 | if (m_originalContent) |
| 1389 | { |
| 1390 | Update(m_originalContent.value()); |
| 1391 | } |
| 1392 | } |
| 1393 | |
| 1394 | HostFileChange::HostFileChange(const std::filesystem::path& Path, const std::string& NewContent) : m_path(Path) |
| 1395 | { |
| 1396 | if (std::filesystem::exists(m_path)) |
| 1397 | { |
| 1398 | std::ifstream file(m_path, std::ios::binary); |
| 1399 | THROW_HR_IF(E_FAIL, !file.is_open()); |
| 1400 | std::stringstream buffer; |
| 1401 | buffer << file.rdbuf(); |
| 1402 | m_originalContent = buffer.str(); |
| 1403 | } |
| 1404 | |
| 1405 | Update(NewContent); |
| 1406 | } |
| 1407 | |
| 1408 | HostFileChange::~HostFileChange() |
| 1409 | try |
| 1410 | { |
| 1411 | if (m_originalContent.has_value()) |
| 1412 | { |
| 1413 | std::filesystem::create_directories(m_path.parent_path()); |
| 1414 | std::ofstream file(m_path, std::ios::binary | std::ios::trunc); |
| 1415 | if (file.is_open()) |
| 1416 | { |
| 1417 | file.write(m_originalContent->data(), static_cast<std::streamsize>(m_originalContent->size())); |
| 1418 | } |
| 1419 | } |
| 1420 | else |
| 1421 | { |
| 1422 | std::filesystem::remove(m_path); |
| 1423 | } |
| 1424 | } |
| 1425 | CATCH_LOG() |
| 1426 | |
| 1427 | void HostFileChange::Update(const std::string& NewContent) const |
| 1428 | { |
| 1429 | std::filesystem::create_directories(m_path.parent_path()); |
| 1430 | std::ofstream file(m_path, std::ios::binary | std::ios::trunc); |
| 1431 | THROW_HR_IF(E_FAIL, !file.is_open()); |
| 1432 | file.write(NewContent.data(), static_cast<std::streamsize>(NewContent.size())); |
| 1433 | THROW_HR_IF(E_FAIL, !file.good()); |
| 1434 | } |
| 1435 | |
| 1436 | std::wstring ReadFileContent(const std::string& Path) |
| 1437 | { |
| 1438 | std::ifstream configRead(Path); |
| 1439 | return std::wstring{std::istreambuf_iterator<char>(configRead), {}}; |
| 1440 | } |
| 1441 | |
| 1442 | std::wstring ReadFileContent(const std::wstring& Path) |
| 1443 | { |
| 1444 | std::wifstream configRead(Path); |
| 1445 | return std::wstring{std::istreambuf_iterator<wchar_t>(configRead), {}}; |
| 1446 | } |
| 1447 | |
| 1448 | // writes global WSL 2 config settings at %userprofile%/.wslconfig |
| 1449 | std::wstring LxssWriteWslConfig(const std::wstring& Content) |
| 1450 | { |
| 1451 | auto path = getenv("userprofile") + std::string("\\.wslconfig"); |
| 1452 | |
| 1453 | auto previousContent = ReadFileContent(path); |
| 1454 | |
| 1455 | std::wofstream config(path); |
| 1456 | VERIFY_IS_TRUE(config.good()); |
| 1457 | config << Content; |
| 1458 | |
| 1459 | return previousContent; |
| 1460 | } |
| 1461 | |
| 1462 | // writes distro specific settings /etc/wsl.conf |
| 1463 | std::string LxssWriteWslDistroConfig(const std::string& Content, LPCWSTR DistributionName) |
| 1464 | { |
| 1465 | std::string path = std::format("\\\\wsl.localhost\\{}\\etc\\wsl.conf", DistributionName); |
| 1466 | |
| 1467 | std::ifstream distroConfigRead(path); |
| 1468 | auto previousContent = std::string{std::istreambuf_iterator<char>(distroConfigRead), {}}; |
| 1469 | distroConfigRead.close(); |
| 1470 | |
| 1471 | std::ofstream distroConfig(path, std::ios_base::binary); |
| 1472 | VERIFY_IS_TRUE(distroConfig.good()); |
| 1473 | distroConfig.write(Content.c_str(), Content.size()); |
| 1474 | |
| 1475 | return previousContent; |
| 1476 | } |
| 1477 | |
| 1478 | // generates a sample global WSL config for the tests |
| 1479 | std::wstring LxssGenerateTestConfig(TestConfigDefaults Default) |
| 1480 | { |
| 1481 | WEX::Common::String kernelLogsArg; |
| 1482 | WEX::TestExecution::RuntimeParameters::TryGetValue(L"KernelLogs", kernelLogsArg); |
| 1483 | |
| 1484 | std::wstring kernelLogs; |
| 1485 | if (kernelLogsArg.IsEmpty()) |
| 1486 | { |
| 1487 | kernelLogs = wil::GetCurrentDirectoryW().get() + std::wstring(L"\\kernelLogs.txt"); |
| 1488 | } |
| 1489 | else |
| 1490 | { |
| 1491 | kernelLogs = kernelLogsArg; |
| 1492 | } |
| 1493 | |
| 1494 | auto boolOptionToString = [](LPCWSTR optionName, std::optional<bool> condition, bool defaultValue) { |
| 1495 | std::wstring value{optionName}; |
| 1496 | value += L"="; |
| 1497 | value += condition.value_or(defaultValue) ? L"true" : L"false"; |
| 1498 | value += L"\n"; |
| 1499 | return value; |
| 1500 | }; |
| 1501 | |
| 1502 | auto networkingModeToString = [](std::optional<wsl::core::NetworkingMode> mode) { |
| 1503 | if (mode.has_value()) |
| 1504 | { |
| 1505 | std::wstring value = L"networkingMode="; |
| 1506 | value += wsl::shared::string::MultiByteToWide(wsl::core::ToString(mode.value())); |
| 1507 | value += L"\n"; |
| 1508 | return value; |
| 1509 | } |
| 1510 | |
| 1511 | return std::wstring{}; |
| 1512 | }; |
| 1513 | |
| 1514 | auto drvFsModeToString = [](std::optional<DrvFsMode> mode) { |
| 1515 | std::wstring value; |
| 1516 | switch (mode.value_or(DrvFsMode::Plan9)) |
| 1517 | { |
| 1518 | case DrvFsMode::Plan9: |
| 1519 | value = L"virtio9p=false"; |
| 1520 | break; |
| 1521 | case DrvFsMode::Virtio9p: |
| 1522 | value = L"virtio9p=true"; |
| 1523 | break; |
| 1524 | case DrvFsMode::VirtioFs: |
| 1525 | value = L"virtiofs=true"; |
| 1526 | break; |
| 1527 | } |
| 1528 | |
| 1529 | value += L"\n"; |
| 1530 | return value; |
| 1531 | }; |
| 1532 | |
| 1533 | std::wstring newConfig = |
| 1534 | L"[wsl2]\n" |
| 1535 | L"crashDumpFolder=" + |
| 1536 | EscapePath(Default.CrashDumpFolder.value_or(g_dumpFolder + L"\\linux-crashes")) + L"\nmaxCrashDumpCount=" + |
| 1537 | std::to_wstring(Default.crashDumpCount) + L"\nvmIdleTimeout=" + std::to_wstring(Default.vmIdleTimeout.value_or(2000)) + |
| 1538 | L"\n" |
| 1539 | L"mountDeviceTimeout=120000\n" |
| 1540 | L"kernelBootTimeout=120000\n" |
| 1541 | L"debugConsoleLogFile=" + |
| 1542 | EscapePath(Default.debugConsoleLogFile.value_or(kernelLogs)) + |
| 1543 | L"\n" |
| 1544 | L"telemetry=false\n" + |
| 1545 | boolOptionToString(L"safeMode", Default.safeMode, false) + boolOptionToString(L"guiApplications", Default.guiApplications, true) + |
| 1546 | boolOptionToString(L"earlyBootLogging", Default.earlyBootLogging, false) + |
| 1547 | networkingModeToString(Default.networkingMode) + drvFsModeToString(Default.drvFsMode); |
| 1548 | |
| 1549 | if (Default.kernel.has_value()) |
| 1550 | { |
| 1551 | newConfig += L"kernel=" + EscapePath(Default.kernel.value()) + L"\n"; |
| 1552 | } |
| 1553 | |
| 1554 | if (Default.kernelCommandLine.has_value()) |
| 1555 | { |
| 1556 | newConfig += L"kernelCommandLine=" + Default.kernelCommandLine.value() + L"\n"; |
| 1557 | } |
| 1558 | |
| 1559 | if (Default.kernelModules.has_value()) |
| 1560 | { |
| 1561 | newConfig += L"kernelModules=" + EscapePath(Default.kernelModules.value()) + L"\n"; |
| 1562 | } |
| 1563 | |
| 1564 | if (Default.loadKernelModules.has_value()) |
| 1565 | { |
| 1566 | newConfig += L"loadKernelModules=" + Default.loadKernelModules.value() + L"\n"; |
| 1567 | } |
| 1568 | |
| 1569 | if (Default.loadDefaultKernelModules.has_value()) |
| 1570 | { |
| 1571 | newConfig += |
| 1572 | L"loadDefaultKernelModules=" + std::wstring(Default.loadDefaultKernelModules.value() ? L"true" : L"false") + L"\n"; |
| 1573 | } |
| 1574 | |
| 1575 | if (Default.systemDistro.has_value()) |
| 1576 | { |
| 1577 | newConfig += L"systemDistro=" + EscapePath(Default.systemDistro.value()) + L"\n"; |
| 1578 | } |
| 1579 | |
| 1580 | switch (Default.networkingMode.value_or(wsl::core::NetworkingMode::Nat)) |
| 1581 | { |
| 1582 | case wsl::core::NetworkingMode::Nat: |
| 1583 | { |
| 1584 | if (Default.dnsProxy.has_value()) |
| 1585 | { |
| 1586 | newConfig += boolOptionToString(L"dnsProxy", Default.dnsProxy, false); |
| 1587 | } |
| 1588 | |
| 1589 | if (Default.firewall.has_value()) |
| 1590 | { |
| 1591 | newConfig += L"[experimental]\nfirewall="; |
| 1592 | newConfig += *Default.firewall ? L"true" : L"false"; |
| 1593 | newConfig += L"\n[wsl2]\n"; |
| 1594 | } |
| 1595 | |
| 1596 | break; |
| 1597 | } |
| 1598 | case wsl::core::NetworkingMode::Bridged: |
| 1599 | { |
| 1600 | VERIFY_IS_TRUE(Default.vmSwitch.has_value()); |
| 1601 | |
| 1602 | newConfig += L"vmSwitch=" + *Default.vmSwitch; |
| 1603 | |
| 1604 | if (Default.macAddress.has_value()) |
| 1605 | { |
| 1606 | newConfig += L"\nmacAddress=" + *Default.macAddress; |
| 1607 | } |
| 1608 | |
| 1609 | newConfig += L"\nipv6=" + std::wstring(Default.ipv6 ? L"true" : L"false"); |
| 1610 | newConfig += L"\n"; |
| 1611 | |
| 1612 | break; |
| 1613 | } |
| 1614 | } |
| 1615 | |
| 1616 | if (Default.dnsTunneling.has_value()) |
| 1617 | { |
| 1618 | newConfig += L"\n[experimental]\n"; |
| 1619 | newConfig += boolOptionToString(L"dnsTunneling", Default.dnsTunneling, false); |
| 1620 | newConfig += L"[wsl2]\n"; |
| 1621 | } |
| 1622 | |
| 1623 | if (Default.dnsTunnelingIpAddress.has_value()) |
| 1624 | { |
| 1625 | newConfig += L"\n[experimental]\n"; |
| 1626 | newConfig += L"dnsTunnelingIpAddress=" + Default.dnsTunnelingIpAddress.value() + L"\n"; |
| 1627 | newConfig += L"[wsl2]\n"; |
| 1628 | } |
| 1629 | |
| 1630 | // always add this regardless if it has value, want to have it disabled by default for tests |
| 1631 | newConfig += L"\n[experimental]\n"; |
| 1632 | newConfig += boolOptionToString(L"autoProxy", Default.autoProxy, false); |
| 1633 | newConfig += L"[wsl2]\n"; |
| 1634 | |
| 1635 | if (Default.sparse.has_value()) |
| 1636 | { |
| 1637 | std::wstring value = Default.sparse.value() ? L"true" : L"false"; |
| 1638 | newConfig += L"[experimental]\nsparseVhd=" + value + L"\n[wsl2]"; |
| 1639 | } |
| 1640 | |
| 1641 | if (Default.hostAddressLoopback.has_value()) |
| 1642 | { |
| 1643 | newConfig += L"\n[experimental]\n"; |
| 1644 | newConfig += boolOptionToString(L"hostAddressLoopback", Default.hostAddressLoopback, false); |
| 1645 | newConfig += L"[wsl2]\n"; |
| 1646 | } |
| 1647 | |
| 1648 | if (Default.virtioFsAggregateShares.has_value()) |
| 1649 | { |
| 1650 | newConfig += L"\n[experimental]\n"; |
| 1651 | newConfig += boolOptionToString(L"virtioFsAggregateShares", Default.virtioFsAggregateShares, true); |
| 1652 | newConfig += L"[wsl2]\n"; |
| 1653 | } |
| 1654 | |
| 1655 | // TODO: Remove once SetVersion() truncated archive error is root caused. |
| 1656 | newConfig += L"\n[experimental]\nSetVersionDebug=true\n[wsl2]\n"; |
| 1657 | |
| 1658 | if (Default.isolateDistroCgroup.has_value()) |
| 1659 | { |
| 1660 | newConfig += boolOptionToString(L"isolateDistroCgroup", Default.isolateDistroCgroup, true); |
| 1661 | } |
| 1662 | |
| 1663 | return newConfig; |
| 1664 | } |
| 1665 | |
| 1666 | std::wstring EscapePath(std::wstring_view Path) |
| 1667 | { |
| 1668 | std::wstring escaped; |
| 1669 | for (const auto e : Path) |
| 1670 | { |
| 1671 | escaped += e; |
| 1672 | |
| 1673 | if (e == L'\\') |
| 1674 | { |
| 1675 | escaped += e; |
| 1676 | } |
| 1677 | } |
| 1678 | |
| 1679 | return escaped; |
| 1680 | } |
| 1681 | |
| 1682 | NTSTATUS |
| 1683 | LxsstuParseLinuxLogFiles(__in PCWSTR LogFileName, __out PBOOL TestPassed) |
| 1684 | |
| 1685 | /*++ |
| 1686 | |
| 1687 | Routine Description: |
| 1688 | |
| 1689 | Parses the output of the linux test and relogs the output. |
| 1690 | |
| 1691 | Arguments: |
| 1692 | |
| 1693 | LogFileName - Supplies a string containing the log files for the test |
| 1694 | separated by LXSS_TEST_LOG_SEPARATOR_CHAR. |
| 1695 | |
| 1696 | TestPassed - Supplies a buffer to receive a boolean value specifying if the |
| 1697 | tests completed without errors. |
| 1698 | |
| 1699 | Return Value: |
| 1700 | |
| 1701 | NTSTATUS |
| 1702 | |
| 1703 | --*/ |
| 1704 | |
| 1705 | { |
| 1706 | |
| 1707 | HANDLE LinuxLogFile; |
| 1708 | WCHAR LinuxLogPath[MAX_PATH]; |
| 1709 | WCHAR LocalLogFileBuffer[MAX_PATH]; |
| 1710 | PWCHAR LogFileToken; |
| 1711 | DWORD PrintStatus; |
| 1712 | NTSTATUS Status; |
| 1713 | std::wstring TestDirectory; |
| 1714 | LXSS_TEST_LAUNCHER_TEST TestRecord; |
| 1715 | PWCHAR TokenState; |
| 1716 | |
| 1717 | LinuxLogFile = INVALID_HANDLE_VALUE; |
| 1718 | Status = STATUS_UNSUCCESSFUL; |
| 1719 | *TestPassed = FALSE; |
| 1720 | RtlZeroMemory(&TestRecord, sizeof(TestRecord)); |
| 1721 | |
| 1722 | // |
| 1723 | // Make a copy of the log file name so wcstok can modify it. |
| 1724 | // |
| 1725 | |
| 1726 | PrintStatus = swprintf_s(LocalLogFileBuffer, RTL_NUMBER_OF(LocalLogFileBuffer), L"%s", LogFileName); |
| 1727 | |
| 1728 | if (PrintStatus == -1) |
| 1729 | { |
| 1730 | Status = STATUS_UNSUCCESSFUL; |
| 1731 | LogError("Increase LocalLogFileBuffer buffer"); |
| 1732 | goto ErrorExit; |
| 1733 | } |
| 1734 | |
| 1735 | // |
| 1736 | // Get the test directory. |
| 1737 | // |
| 1738 | |
| 1739 | TestDirectory = LxsstuGetTestDirectory(); |
| 1740 | |
| 1741 | // |
| 1742 | // Parse the logs for the test and determine how many passes / errors there |
| 1743 | // were. |
| 1744 | // |
| 1745 | |
| 1746 | LogFileToken = wcstok(LocalLogFileBuffer, LXSS_TEST_LOG_SEPARATOR_CHAR, &TokenState); |
| 1747 | |
| 1748 | while (LogFileToken != NULL) |
| 1749 | { |
| 1750 | LogInfo("LOGFILE: %s", LogFileToken); |
| 1751 | PrintStatus = swprintf_s(LinuxLogPath, RTL_NUMBER_OF(LinuxLogPath), L"%s\\log\\%s", TestDirectory.c_str(), LogFileToken); |
| 1752 | |
| 1753 | if (PrintStatus == -1) |
| 1754 | { |
| 1755 | Status = STATUS_UNSUCCESSFUL; |
| 1756 | LogError("Increase LinuxLogPath buffer"); |
| 1757 | goto ErrorExit; |
| 1758 | } |
| 1759 | |
| 1760 | // |
| 1761 | // For VM Mode, copy the output file out of the ext4 volume so it can |
| 1762 | // be read. |
| 1763 | // |
| 1764 | |
| 1765 | if (LxsstuVmMode()) |
| 1766 | { |
| 1767 | std::wstring Command = std::format(L"/bin/cp /data/test/log/{} $(wslpath '{}')", LogFileToken, LinuxLogPath); |
| 1768 | VERIFY_NO_THROW(LxsstuRunTest(Command.c_str())); |
| 1769 | } |
| 1770 | |
| 1771 | LinuxLogFile = |
| 1772 | CreateFileW(LinuxLogPath, GENERIC_READ, (FILE_SHARE_READ | FILE_SHARE_WRITE), NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
| 1773 | |
| 1774 | if (LinuxLogFile == INVALID_HANDLE_VALUE) |
| 1775 | { |
| 1776 | Status = STATUS_UNSUCCESSFUL; |
| 1777 | LogError("Could not open {:%s:} after running test, LastError %#x", LinuxLogPath, GetLastError()); |
| 1778 | |
| 1779 | goto ErrorExit; |
| 1780 | } |
| 1781 | |
| 1782 | Status = LxsstuParseLogFile(LinuxLogFile, &TestRecord); |
| 1783 | if (!NT_SUCCESS(Status)) |
| 1784 | { |
| 1785 | goto ErrorExit; |
| 1786 | } |
| 1787 | |
| 1788 | if (TestRecord.NumberOfErrors > 0) |
| 1789 | { |
| 1790 | LogError("LOG FILE SUMMARY: %s - PASSED: %u ERRORS: %u", LogFileToken, TestRecord.NumberOfPasses, TestRecord.NumberOfErrors); |
| 1791 | } |
| 1792 | else if (TestRecord.NumberOfPasses > 0) |
| 1793 | { |
| 1794 | LogPass("LOG FILE SUMMARY: %s - PASSED: %u ERRORS: %u", LogFileToken, TestRecord.NumberOfPasses, TestRecord.NumberOfErrors); |
| 1795 | } |
| 1796 | else |
| 1797 | { |
| 1798 | LogError("LOG FILE SUMMARY: %s - log had no passes or errors, ensure test was actually run", LogFileToken); |
| 1799 | } |
| 1800 | |
| 1801 | CloseHandle(LinuxLogFile); |
| 1802 | LinuxLogFile = INVALID_HANDLE_VALUE; |
| 1803 | LogFileToken = wcstok(NULL, LXSS_TEST_LOG_SEPARATOR_CHAR, &TokenState); |
| 1804 | } |
| 1805 | |
| 1806 | Status = STATUS_SUCCESS; |
| 1807 | |
| 1808 | ErrorExit: |
| 1809 | if (LinuxLogFile != INVALID_HANDLE_VALUE) |
| 1810 | { |
| 1811 | CloseHandle(LinuxLogFile); |
| 1812 | } |
| 1813 | |
| 1814 | if ((TestRecord.NumberOfErrors == 0) && (TestRecord.NumberOfPasses > 0)) |
| 1815 | { |
| 1816 | *TestPassed = TRUE; |
| 1817 | } |
| 1818 | |
| 1819 | return Status; |
| 1820 | } |
| 1821 | |
| 1822 | NTSTATUS |
| 1823 | LxsstuParseLogFile(__in HANDLE FileHandle, __in PLXSS_TEST_LAUNCHER_TEST TestRecord) |
| 1824 | |
| 1825 | /*++ |
| 1826 | |
| 1827 | Routine Description: |
| 1828 | |
| 1829 | Parses a single log file. |
| 1830 | |
| 1831 | Arguments: |
| 1832 | |
| 1833 | TestName - Name of the test. |
| 1834 | |
| 1835 | LogFileName - string containing the log files for the test separated by |
| 1836 | LXSS_TEST_LOG_SEPARATOR_CHAR. |
| 1837 | |
| 1838 | Return Value: |
| 1839 | |
| 1840 | NTSTATUS |
| 1841 | |
| 1842 | --*/ |
| 1843 | |
| 1844 | { |
| 1845 | |
| 1846 | PBYTE Buffer; |
| 1847 | DWORD BytesRead; |
| 1848 | DWORD FileSize; |
| 1849 | DWORD FileSizeHigh; |
| 1850 | PCHAR Message; |
| 1851 | LXSS_TEST_LAUNCHER_MESSAGE_TYPE MessageType; |
| 1852 | NTSTATUS Status; |
| 1853 | PCHAR Token; |
| 1854 | |
| 1855 | Buffer = NULL; |
| 1856 | Status = STATUS_UNSUCCESSFUL; |
| 1857 | |
| 1858 | FileSize = GetFileSize(FileHandle, &FileSizeHigh); |
| 1859 | Buffer = (PBYTE)ALLOC(FileSize + 1); |
| 1860 | if (Buffer == NULL) |
| 1861 | { |
| 1862 | goto ErrorExit; |
| 1863 | } |
| 1864 | |
| 1865 | Buffer[FileSize] = '\0'; |
| 1866 | |
| 1867 | do |
| 1868 | { |
| 1869 | RtlZeroMemory(Buffer, FileSize); |
| 1870 | if (ReadFile(FileHandle, Buffer, FileSize, &BytesRead, NULL) == FALSE) |
| 1871 | { |
| 1872 | |
| 1873 | Status = STATUS_UNSUCCESSFUL; |
| 1874 | LogError("ReadFile failed, LastError %#x", GetLastError()); |
| 1875 | goto ErrorExit; |
| 1876 | } |
| 1877 | |
| 1878 | if (BytesRead == 0) |
| 1879 | { |
| 1880 | break; |
| 1881 | } |
| 1882 | |
| 1883 | // |
| 1884 | // Parse the log line-by-line. |
| 1885 | // |
| 1886 | |
| 1887 | Token = strtok((PCHAR)Buffer, "\n"); |
| 1888 | while (Token != NULL) |
| 1889 | { |
| 1890 | |
| 1891 | // |
| 1892 | // A well-formed message begins with a timestamp and then is either |
| 1893 | // a start, info, error, or pass message. For example: |
| 1894 | // [12:30:05.432] ERROR: Something went wrong! |
| 1895 | // |
| 1896 | // Anything that does not fit this format is re-logged an an "info" |
| 1897 | // message. |
| 1898 | // |
| 1899 | |
| 1900 | MessageType = LogInfoMessage; |
| 1901 | if (Token[0] == '[') |
| 1902 | { |
| 1903 | Message = strchr(Token, ' '); |
| 1904 | if ((Message == NULL) || (strlen(Message) < 2)) |
| 1905 | { |
| 1906 | break; |
| 1907 | } |
| 1908 | |
| 1909 | switch (Message[1]) |
| 1910 | { |
| 1911 | case 'E': |
| 1912 | case 'R': |
| 1913 | MessageType = LogErrorMessage; |
| 1914 | break; |
| 1915 | |
| 1916 | case 'P': |
| 1917 | MessageType = LogPassMessage; |
| 1918 | break; |
| 1919 | } |
| 1920 | } |
| 1921 | |
| 1922 | switch (MessageType) |
| 1923 | { |
| 1924 | case LogInfoMessage: |
| 1925 | if (g_RelogEverything != FALSE) |
| 1926 | { |
| 1927 | LogInfo("%S", Token); |
| 1928 | } |
| 1929 | |
| 1930 | break; |
| 1931 | |
| 1932 | case LogErrorMessage: |
| 1933 | TestRecord->NumberOfErrors += 1; |
| 1934 | if (g_RelogEverything != FALSE) |
| 1935 | { |
| 1936 | LogError("%S", Token); |
| 1937 | } |
| 1938 | |
| 1939 | break; |
| 1940 | |
| 1941 | case LogPassMessage: |
| 1942 | TestRecord->NumberOfPasses += 1; |
| 1943 | if (g_RelogEverything != FALSE) |
| 1944 | { |
| 1945 | LogPass("%S", Token); |
| 1946 | } |
| 1947 | |
| 1948 | break; |
| 1949 | |
| 1950 | DEFAULT_UNREACHABLE; |
| 1951 | } |
| 1952 | |
| 1953 | Token = strtok(NULL, "\n"); |
| 1954 | } |
| 1955 | } while (BytesRead > 0); |
| 1956 | |
| 1957 | Status = STATUS_SUCCESS; |
| 1958 | |
| 1959 | ErrorExit: |
| 1960 | if (Buffer != NULL) |
| 1961 | { |
| 1962 | FREE(Buffer); |
| 1963 | } |
| 1964 | |
| 1965 | return Status; |
| 1966 | } |
| 1967 | |
| 1968 | VOID LxsstuRunTest(_In_ PCWSTR CommandLine, _In_opt_ PCWSTR LogFileName, _In_opt_ PCWSTR Username) noexcept(false) |
| 1969 | |
| 1970 | /*++ |
| 1971 | |
| 1972 | Routine Description: |
| 1973 | |
| 1974 | Run an individual test. |
| 1975 | |
| 1976 | Arguments: |
| 1977 | |
| 1978 | CommandLine - Command line path and arguments to pass |
| 1979 | LogFileName - Name of the linux log file |
| 1980 | Username - User to run the test as, if one is not supplied the test |
| 1981 | will be run as root |
| 1982 | |
| 1983 | Return Value: |
| 1984 | |
| 1985 | None. |
| 1986 | |
| 1987 | --*/ |
| 1988 | |
| 1989 | { |
| 1990 | |
| 1991 | BOOL TestPassed; |
| 1992 | std::wstring LaunchArguments{}; |
| 1993 | |
| 1994 | if (ARGUMENT_PRESENT(Username)) |
| 1995 | { |
| 1996 | LaunchArguments += WSL_USER_ARG L" "; |
| 1997 | LaunchArguments += Username; |
| 1998 | LaunchArguments += L" "; |
| 1999 | } |
| 2000 | |
| 2001 | LaunchArguments += CommandLine; |
| 2002 | DWORD ExitCode = LxsstuLaunchWsl(LaunchArguments.c_str()); |
| 2003 | LogInfo("Test process exited with: %lu", ExitCode); |
| 2004 | |
| 2005 | // |
| 2006 | // Parse the contents of the linux log(s) files and relog. |
| 2007 | // |
| 2008 | |
| 2009 | if (ARGUMENT_PRESENT(LogFileName)) |
| 2010 | { |
| 2011 | THROW_IF_NTSTATUS_FAILED(LxsstuParseLinuxLogFiles(LogFileName, &TestPassed)); |
| 2012 | |
| 2013 | VERIFY_IS_TRUE(TestPassed); |
| 2014 | } |
| 2015 | |
| 2016 | VERIFY_ARE_EQUAL(0, ExitCode); |
| 2017 | |
| 2018 | return; |
| 2019 | } |
| 2020 | |
| 2021 | bool ModuleSetup(VOID) |
| 2022 | |
| 2023 | /*++ |
| 2024 | |
| 2025 | Routine Description: |
| 2026 | |
| 2027 | Configures the machine to run tests |
| 2028 | |
| 2029 | Arguments: |
| 2030 | |
| 2031 | Return Value: |
| 2032 | |
| 2033 | None. |
| 2034 | |
| 2035 | --*/ |
| 2036 | |
| 2037 | { |
| 2038 | wsl::windows::common::wslutil::InitializeWil(); |
| 2039 | |
| 2040 | THROW_IF_FAILED(CoIncrementMTAUsage(&g_mtaCookie)); |
| 2041 | |
| 2042 | // Assign a job object to the current process to ensure that we don't leak processes on failure. |
| 2043 | // N.B. When the job object is closed, all processes associated with the job will be terminated. |
| 2044 | // Because of that, we're purposefully leaking this job object so we don't kill the test process on cleanup. |
| 2045 | auto job = CreateJobObjectW(nullptr, nullptr); |
| 2046 | THROW_LAST_ERROR_IF(!job); |
| 2047 | |
| 2048 | JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo{}; |
| 2049 | jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; |
| 2050 | THROW_IF_WIN32_BOOL_FALSE(SetInformationJobObject(job, JobObjectExtendedLimitInformation, &jobInfo, sizeof(jobInfo))); |
| 2051 | THROW_IF_WIN32_BOOL_FALSE(AssignProcessToJobObject(job, GetCurrentProcess())); |
| 2052 | |
| 2053 | // Don't crash for unknown exceptions (makes debugging testpasses harder) |
| 2054 | #ifndef _DEBUG |
| 2055 | wil::g_fResultFailFastUnknownExceptions = false; |
| 2056 | #endif |
| 2057 | |
| 2058 | WslTraceLoggingInitialize(LxssTelemetryProvider, true); |
| 2059 | wsl::windows::common::EnableContextualizedErrors(false); |
| 2060 | |
| 2061 | auto getOptionalTestParam = [](LPCWSTR Name) -> std::optional<std::wstring> { |
| 2062 | WEX::Common::String Value; |
| 2063 | |
| 2064 | WEX::TestExecution::RuntimeParameters::TryGetValue(Name, Value); |
| 2065 | |
| 2066 | return Value.IsEmpty() ? std::optional<std::wstring>() : static_cast<LPCWSTR>(Value); |
| 2067 | }; |
| 2068 | |
| 2069 | auto getTestParam = [&](LPCWSTR Name) -> std::wstring { |
| 2070 | auto value = getOptionalTestParam(Name); |
| 2071 | if (!value.has_value()) |
| 2072 | { |
| 2073 | const std::wstring error = L"Missing TE argument: " + std::wstring(Name); |
| 2074 | VERIFY_FAIL(error.c_str()); |
| 2075 | } |
| 2076 | |
| 2077 | return value.value(); |
| 2078 | }; |
| 2079 | |
| 2080 | try |
| 2081 | { |
| 2082 | const auto buildString = wsl::windows::common::registry::ReadString( |
| 2083 | HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", L"BuildLabEx"); |
| 2084 | |
| 2085 | LogInfo("OS build string: %ls", buildString.c_str()); |
| 2086 | } |
| 2087 | CATCH_LOG(); |
| 2088 | |
| 2089 | try |
| 2090 | { |
| 2091 | const auto userKey = wsl::windows::common::registry::OpenLxssUserKey(); |
| 2092 | g_originalDefaultDistro = wsl::windows::common::registry::ReadString(userKey.get(), nullptr, L"DefaultDistribution", L""); |
| 2093 | } |
| 2094 | CATCH_LOG(); |
| 2095 | |
| 2096 | g_originalConfig = LxssWriteWslConfig(LxssGenerateTestConfig()); |
| 2097 | |
| 2098 | const auto redirectStdout = getOptionalTestParam(L"RedirectStdout"); |
| 2099 | const auto redirectStderr = getOptionalTestParam(L"RedirectStderr"); |
| 2100 | |
| 2101 | if (redirectStdout.has_value()) |
| 2102 | { |
| 2103 | g_OriginalStdout = LxssRedirectOutput(STD_OUTPUT_HANDLE, redirectStdout.value()); |
| 2104 | } |
| 2105 | |
| 2106 | if (redirectStderr.has_value()) |
| 2107 | { |
| 2108 | g_OriginalStderr = LxssRedirectOutput(STD_ERROR_HANDLE, redirectStderr.value()); |
| 2109 | } |
| 2110 | |
| 2111 | g_dumpFolder = getOptionalTestParam(L"DumpFolder").value_or(L"."); |
| 2112 | g_dumpToolPath = getOptionalTestParam(L"DumpTool"); |
| 2113 | g_pipelineBuildId = getOptionalTestParam(L"PipelineBuildId").value_or(L""); |
| 2114 | |
| 2115 | if (!g_pipelineBuildId.empty()) |
| 2116 | { |
| 2117 | LogInfo("Pipeline build id: %ls", g_pipelineBuildId.c_str()); |
| 2118 | } |
| 2119 | |
| 2120 | WEX::TestExecution::RuntimeParameters::TryGetValue(L"WerReport", g_enableWerReport); |
| 2121 | WEX::TestExecution::RuntimeParameters::TryGetValue(L"LogDmesg", g_LogDmesgAfterEachTest); |
| 2122 | |
| 2123 | g_WatchdogTimer = CreateThreadpoolTimer(LxsstuWatchdogTimer, nullptr, nullptr); |
| 2124 | VERIFY_IS_NOT_NULL(g_WatchdogTimer); |
| 2125 | |
| 2126 | ULARGE_INTEGER fileTimeConvert{}; |
| 2127 | fileTimeConvert.QuadPart = LXSS_WATCHDOG_TIMEOUT; |
| 2128 | fileTimeConvert.QuadPart *= (-1 * 1000 * 10i64); // fileTime is unsigned- took out -1; check if this causes errors later |
| 2129 | FILETIME DueTime{}; |
| 2130 | DueTime.dwLowDateTime = fileTimeConvert.LowPart; |
| 2131 | DueTime.dwHighDateTime = fileTimeConvert.HighPart; |
| 2132 | SetThreadpoolTimer(g_WatchdogTimer, &DueTime, 0, LXSS_WATCHDOG_TIMEOUT_WINDOW); |
| 2133 | |
| 2134 | const auto version = getTestParam(L"Version"); |
| 2135 | if (version == L"1") |
| 2136 | { |
| 2137 | g_VmMode = false; |
| 2138 | } |
| 2139 | else if (version == L"2") |
| 2140 | { |
| 2141 | g_VmMode = true; |
| 2142 | } |
| 2143 | else |
| 2144 | { |
| 2145 | LogError("Unexpected version: %ls", version.c_str()); |
| 2146 | VERIFY_FAIL(); |
| 2147 | } |
| 2148 | |
| 2149 | g_testDistroPath = getTestParam(L"DistroPath"); |
| 2150 | |
| 2151 | g_testDataPath = getTestParam(L"TestDataPath"); |
| 2152 | |
| 2153 | const auto setupScript = getOptionalTestParam(L"SetupScript"); |
| 2154 | if (!setupScript.has_value()) |
| 2155 | { |
| 2156 | // If no setup script is present, mark test_distro as the default distro here for convenience. |
| 2157 | VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--set-default " LXSS_DISTRO_NAME_TEST_L), 0L); |
| 2158 | g_fastTestRun = true; |
| 2159 | |
| 2160 | return true; |
| 2161 | } |
| 2162 | |
| 2163 | std::wstring Cmd = |
| 2164 | L"Powershell \ |
| 2165 | -NoProfile \ |
| 2166 | -ExecutionPolicy Bypass \ |
| 2167 | -Command \"" + |
| 2168 | setupScript.value() + L" -Version '" + getTestParam(L"Version") + L"'" + L" -DistroPath " + g_testDistroPath + |
| 2169 | L" -DistroName " + LXSS_DISTRO_NAME_TEST_L + L" -Package '" + getTestParam(L"Package") + L"'" + L" -UnitTestsPath " + |
| 2170 | getOptionalTestParam(L"UnitTestsPath").value_or(L"$null"); |
| 2171 | |
| 2172 | if (getOptionalTestParam(L"AllowUnsigned") == L"1") |
| 2173 | { |
| 2174 | Cmd += L" -AllowUnsigned"; |
| 2175 | } |
| 2176 | |
| 2177 | Cmd += +L"\""; |
| 2178 | |
| 2179 | LogInfo("Running test setup command: %ls", Cmd.c_str()); |
| 2180 | |
| 2181 | const auto ExitCode = LxsstuRunCommand(Cmd.data()); |
| 2182 | if (ExitCode != 0) |
| 2183 | { |
| 2184 | THROW_HR_MSG(E_FAIL, "Test setup returned non-zero exit code %lu", ExitCode); |
| 2185 | } |
| 2186 | |
| 2187 | return true; |
| 2188 | } |
| 2189 | |
| 2190 | bool ModuleCleanup(VOID) |
| 2191 | |
| 2192 | /*++ |
| 2193 | |
| 2194 | Routine Description: |
| 2195 | |
| 2196 | Called after the tests cases have been executed. |
| 2197 | Reverts WSL version upgrades, if any. |
| 2198 | |
| 2199 | Arguments: |
| 2200 | None. |
| 2201 | |
| 2202 | Return Value: |
| 2203 | |
| 2204 | None. |
| 2205 | |
| 2206 | --*/ |
| 2207 | |
| 2208 | { |
| 2209 | LogInfo("Exiting UnitTests module"); |
| 2210 | |
| 2211 | auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { |
| 2212 | WslTraceLoggingUninitialize(); |
| 2213 | g_mtaCookie.reset(); |
| 2214 | }); |
| 2215 | |
| 2216 | // |
| 2217 | // Release the watchdog timer. |
| 2218 | // |
| 2219 | |
| 2220 | if (g_WatchdogTimer != NULL) |
| 2221 | { |
| 2222 | SetThreadpoolTimer(g_WatchdogTimer, nullptr, 0, 0); |
| 2223 | WaitForThreadpoolTimerCallbacks(g_WatchdogTimer, true); |
| 2224 | CloseThreadpoolTimer(g_WatchdogTimer); |
| 2225 | } |
| 2226 | |
| 2227 | // Save the Appx & defender logs in the dump folder |
| 2228 | if (!g_pipelineBuildId.empty()) |
| 2229 | { |
| 2230 | auto commandLine = std::format(L"Get-AppPackageLog -All > \"{}\\appx-logs.txt\"", g_dumpFolder); |
| 2231 | LxsstuLaunchPowershellAndCaptureOutput(commandLine.data()); |
| 2232 | |
| 2233 | commandLine = std::format(L"Get-MpThreatDetection > \"{}\\Get-MpThreatDetection.txt\"", g_dumpFolder); |
| 2234 | LxsstuLaunchPowershellAndCaptureOutput(commandLine.data()); |
| 2235 | |
| 2236 | commandLine = std::format(L"Get-MpThreat > \"{}\\Get-MpThreat.txt\"", g_dumpFolder); |
| 2237 | LxsstuLaunchPowershellAndCaptureOutput(commandLine.data()); |
| 2238 | |
| 2239 | commandLine = std::format(L"Get-MpPreference > \"{}\\Get-MpPreference.txt\"", g_dumpFolder); |
| 2240 | LxsstuLaunchPowershellAndCaptureOutput(commandLine.data()); |
| 2241 | } |
| 2242 | |
| 2243 | if (!g_originalConfig.empty()) |
| 2244 | { |
| 2245 | LogInfo("Restoring .wslconfig"); |
| 2246 | LxssWriteWslConfig(g_originalConfig); |
| 2247 | } |
| 2248 | |
| 2249 | if (!g_originalDefaultDistro.empty()) |
| 2250 | { |
| 2251 | // Edge case: If the previous default distro was the test distro, it might have been deleted during the testpass. |
| 2252 | // Validate the distro exists before restoring. |
| 2253 | |
| 2254 | const auto userKey = wsl::windows::common::registry::OpenLxssUserKey(); |
| 2255 | |
| 2256 | try |
| 2257 | { |
| 2258 | wsl::windows::common::registry::OpenKey(userKey.get(), g_originalDefaultDistro.c_str(), KEY_READ); |
| 2259 | } |
| 2260 | catch (...) |
| 2261 | { |
| 2262 | LogInfo("Previous default distro doesn't exist anymore: '%ls', skipping restore", g_originalDefaultDistro.c_str()); |
| 2263 | return true; |
| 2264 | } |
| 2265 | |
| 2266 | LogInfo("Restoring default distro: '%ls", g_originalDefaultDistro.c_str()); |
| 2267 | |
| 2268 | wsl::windows::common::registry::WriteString(userKey.get(), nullptr, L"DefaultDistribution", g_originalDefaultDistro.c_str()); |
| 2269 | } |
| 2270 | |
| 2271 | return true; |
| 2272 | } |
| 2273 | |
| 2274 | HANDLE |
| 2275 | LxssRedirectOutput(_In_ DWORD Stream, _In_ const std::wstring& File) |
| 2276 | |
| 2277 | /*++ |
| 2278 | |
| 2279 | Routine Description: |
| 2280 | |
| 2281 | Redirect a standard stream to a file |
| 2282 | |
| 2283 | Arguments: |
| 2284 | Stream - The stream to redirect |
| 2285 | |
| 2286 | File - The file to redirect the output to |
| 2287 | |
| 2288 | Return Value: |
| 2289 | |
| 2290 | None. |
| 2291 | |
| 2292 | --*/ |
| 2293 | |
| 2294 | { |
| 2295 | const HANDLE OriginalHandle = GetStdHandle(Stream); |
| 2296 | |
| 2297 | SECURITY_ATTRIBUTES Attributes = {0}; |
| 2298 | Attributes.nLength = sizeof(Attributes); |
| 2299 | Attributes.bInheritHandle = true; |
| 2300 | |
| 2301 | const auto Handle = |
| 2302 | CreateFileW(File.c_str(), FILE_APPEND_DATA, FILE_SHARE_READ, &Attributes, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); |
| 2303 | |
| 2304 | VERIFY_IS_NOT_NULL(Handle); |
| 2305 | |
| 2306 | VERIFY_IS_TRUE(SetStdHandle(Stream, Handle)); |
| 2307 | |
| 2308 | return OriginalHandle; |
| 2309 | } |
| 2310 | |
| 2311 | void CreateUser(_In_ const std::wstring& Username, _Out_ PULONG Uid, _Out_ PULONG Gid) |
| 2312 | { |
| 2313 | // |
| 2314 | // Create the user account. |
| 2315 | // |
| 2316 | // N.B. The user may already exist if the test was run previously. |
| 2317 | // |
| 2318 | |
| 2319 | std::wstring CreateUser{L"/usr/sbin/adduser --quiet --force-badname --disabled-password --gecos \"\" "}; |
| 2320 | CreateUser += Username.c_str(); |
| 2321 | LxsstuLaunchWsl(CreateUser.c_str()); |
| 2322 | |
| 2323 | // |
| 2324 | // Create an unnamed pipe to read the output of the launched commands. |
| 2325 | // |
| 2326 | |
| 2327 | wil::unique_handle ReadPipe; |
| 2328 | wil::unique_handle WritePipe; |
| 2329 | THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&ReadPipe, &WritePipe, NULL, 0)); |
| 2330 | |
| 2331 | // |
| 2332 | // Mark the write end of the pipe as inheritable. |
| 2333 | // |
| 2334 | |
| 2335 | THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(WritePipe.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)); |
| 2336 | |
| 2337 | // |
| 2338 | // Query the UID. |
| 2339 | // |
| 2340 | |
| 2341 | std::wstring QueryUid{L"/usr/bin/id -u "}; |
| 2342 | QueryUid += Username.c_str(); |
| 2343 | THROW_HR_IF(E_UNEXPECTED, (LxsstuLaunchWsl(QueryUid.c_str(), nullptr, WritePipe.get()) != 0)); |
| 2344 | |
| 2345 | CHAR Buffer[64]; |
| 2346 | DWORD BytesRead; |
| 2347 | THROW_IF_WIN32_BOOL_FALSE(ReadFile(ReadPipe.get(), Buffer, (sizeof(Buffer) - 1), &BytesRead, NULL)); |
| 2348 | Buffer[BytesRead] = ANSI_NULL; |
| 2349 | const ULONG UidLocal = std::stoul(Buffer, nullptr, 10); |
| 2350 | |
| 2351 | // |
| 2352 | // Query the GID. |
| 2353 | // |
| 2354 | |
| 2355 | std::wstring QueryGid{L"/usr/bin/id -g "}; |
| 2356 | QueryGid += Username.c_str(); |
| 2357 | THROW_HR_IF(E_UNEXPECTED, (LxsstuLaunchWsl(QueryGid.c_str(), nullptr, WritePipe.get()) != 0)); |
| 2358 | |
| 2359 | THROW_IF_WIN32_BOOL_FALSE(ReadFile(ReadPipe.get(), Buffer, (sizeof(Buffer) - 1), &BytesRead, NULL)); |
| 2360 | Buffer[BytesRead] = ANSI_NULL; |
| 2361 | const ULONG GidLocal = std::stoul(Buffer, nullptr, 10); |
| 2362 | |
| 2363 | // |
| 2364 | // Return the queried values to the user. |
| 2365 | // |
| 2366 | |
| 2367 | *Uid = UidLocal; |
| 2368 | *Gid = GidLocal; |
| 2369 | } |
| 2370 | |
| 2371 | std::pair<HANDLE, HANDLE> UseOriginalStdHandles(VOID) |
| 2372 | |
| 2373 | /*++ |
| 2374 | |
| 2375 | Routine Description: |
| 2376 | |
| 2377 | Restores the original stdout & stderr handles, if any. |
| 2378 | |
| 2379 | Arguments: |
| 2380 | None. |
| 2381 | |
| 2382 | Return Value: |
| 2383 | |
| 2384 | A pair of the previous stdout & stderr handles. |
| 2385 | |
| 2386 | --*/ |
| 2387 | |
| 2388 | { |
| 2389 | HANDLE PreviousStdout = GetStdHandle(STD_OUTPUT_HANDLE); |
| 2390 | HANDLE PreviousStderr = GetStdHandle(STD_ERROR_HANDLE); |
| 2391 | |
| 2392 | if (g_OriginalStdout != nullptr) |
| 2393 | { |
| 2394 | VERIFY_IS_TRUE(SetStdHandle(STD_OUTPUT_HANDLE, g_OriginalStdout)); |
| 2395 | } |
| 2396 | |
| 2397 | if (g_OriginalStderr != nullptr) |
| 2398 | { |
| 2399 | VERIFY_IS_TRUE(SetStdHandle(STD_ERROR_HANDLE, g_OriginalStderr)); |
| 2400 | } |
| 2401 | |
| 2402 | return {PreviousStdout, PreviousStderr}; |
| 2403 | } |
| 2404 | |
| 2405 | void RestoreTestStdHandles(_In_ const std::pair<HANDLE, HANDLE>& handles) |
| 2406 | |
| 2407 | /*++ |
| 2408 | |
| 2409 | Routine Description: |
| 2410 | |
| 2411 | Assign stdout & stderr handles. |
| 2412 | |
| 2413 | Arguments: |
| 2414 | None. |
| 2415 | |
| 2416 | Return Value: |
| 2417 | |
| 2418 | None. |
| 2419 | |
| 2420 | --*/ |
| 2421 | |
| 2422 | { |
| 2423 | VERIFY_IS_TRUE(SetStdHandle(STD_OUTPUT_HANDLE, handles.first)); |
| 2424 | VERIFY_IS_TRUE(SetStdHandle(STD_ERROR_HANDLE, handles.second)); |
| 2425 | } |
| 2426 | |
| 2427 | bool TryLoadDnsResolverMethods() noexcept |
| 2428 | { |
| 2429 | constexpr auto c_dnsModuleName = L"dnsapi.dll"; |
| 2430 | const wil::shared_hmodule dnsModule{LoadLibraryEx(c_dnsModuleName, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)}; |
| 2431 | if (!dnsModule) |
| 2432 | { |
| 2433 | return false; |
| 2434 | } |
| 2435 | |
| 2436 | try |
| 2437 | { |
| 2438 | // attempt to find the functions for the DNS tunneling OS APIs. |
| 2439 | static LxssDynamicFunction<decltype(DnsQueryRaw)> dnsQueryRaw{dnsModule, "DnsQueryRaw"}; |
| 2440 | static LxssDynamicFunction<decltype(DnsCancelQueryRaw)> dnsCancelQueryRaw{dnsModule, "DnsCancelQueryRaw"}; |
| 2441 | static LxssDynamicFunction<decltype(DnsQueryRawResultFree)> dnsQueryRawResultFree{dnsModule, "DnsQueryRawResultFree"}; |
| 2442 | |
| 2443 | // Make a dummy call to the DNS APIs to verify if they are working. The APIs are going to be present |
| 2444 | // on older OS versions, where they can be turned on/off using a KIR. If the KIR is turned off, the APIs |
| 2445 | // will be unusable and will return ERROR_CALL_NOT_IMPLEMENTED. |
| 2446 | THROW_HR_IF(E_NOTIMPL, dnsQueryRaw(nullptr, nullptr) == ERROR_CALL_NOT_IMPLEMENTED); |
| 2447 | } |
| 2448 | catch (...) |
| 2449 | { |
| 2450 | return false; |
| 2451 | } |
| 2452 | return true; |
| 2453 | } |
| 2454 | |
| 2455 | bool AreExperimentalNetworkingFeaturesSupported() |
| 2456 | { |
| 2457 | constexpr auto NETWORKING_EXPERIMENTAL_FLOOR_BUILD = 25885; |
| 2458 | constexpr auto GALLIUM_FLOOR_BUILD = 25846; |
| 2459 | const auto build = wsl::windows::common::helpers::GetWindowsVersion(); |
| 2460 | return ((build.BuildNumber < GALLIUM_FLOOR_BUILD) || (build.BuildNumber >= GALLIUM_FLOOR_BUILD && build.BuildNumber >= NETWORKING_EXPERIMENTAL_FLOOR_BUILD)); |
| 2461 | } |
| 2462 | |
| 2463 | bool IsHyperVFirewallSupported() noexcept |
| 2464 | { |
| 2465 | try |
| 2466 | { |
| 2467 | // Query for the Hyper-V Firewall profile object. If this object is successfully queried, then |
| 2468 | // the OS has the necessary Hyper-V firewall support. |
| 2469 | LxsstuLaunchPowershellAndCaptureOutput(L"Get-NetFirewallHyperVProfile"); |
| 2470 | } |
| 2471 | catch (...) |
| 2472 | { |
| 2473 | return false; |
| 2474 | } |
| 2475 | return true; |
| 2476 | } |
| 2477 | |
| 2478 | std::optional<GUID> GetDistributionId(LPCWSTR Name) |
| 2479 | { |
| 2480 | // Get the GUID of the test distro |
| 2481 | wsl::windows::common::SvcComm service; |
| 2482 | for (const auto& e : service.EnumerateDistributions()) |
| 2483 | { |
| 2484 | if (wsl::shared::string::IsEqual(e.DistroName, Name)) |
| 2485 | { |
| 2486 | return e.DistroGuid; |
| 2487 | } |
| 2488 | } |
| 2489 | |
| 2490 | return {}; |
| 2491 | } |
| 2492 | |
| 2493 | wil::unique_hkey OpenDistributionKey(LPCWSTR Name) |
| 2494 | { |
| 2495 | const auto id = GetDistributionId(Name); |
| 2496 | if (!id.has_value()) |
| 2497 | { |
| 2498 | return {}; |
| 2499 | } |
| 2500 | |
| 2501 | const auto idString = wsl::shared::string::GuidToString<wchar_t>(id.value()); |
| 2502 | |
| 2503 | const auto userKey = wsl::windows::common::registry::OpenLxssUserKey(); |
| 2504 | return wsl::windows::common::registry::OpenKey(userKey.get(), idString.c_str(), KEY_ALL_ACCESS); |
| 2505 | } |
| 2506 | |
| 2507 | bool WslShutdown() |
| 2508 | { |
| 2509 | return VERIFY_ARE_EQUAL(0u, LxsstuLaunchWsl(WSL_SHUTDOWN_ARG)); |
| 2510 | } |
| 2511 | |
| 2512 | void TerminateDistribution(LPCWSTR DistributionName) |
| 2513 | { |
| 2514 | VERIFY_ARE_EQUAL(0u, LxsstuLaunchWsl(std::format(L"{} {}", WSL_TERMINATE_ARG, DistributionName))); |
| 2515 | } |
| 2516 | |
| 2517 | void ValidateOutput(LPCWSTR CommandLine, const std::wstring& ExpectedOutput, const std::wstring& ExpectedWarnings, int ExitCode) |
| 2518 | { |
| 2519 | auto [output, warnings] = LxsstuLaunchWslAndCaptureOutput(CommandLine, ExitCode); |
| 2520 | |
| 2521 | VERIFY_ARE_EQUAL(ExpectedOutput, output); |
| 2522 | VERIFY_ARE_EQUAL(ExpectedWarnings, warnings); |
| 2523 | } |
| 2524 | |
| 2525 | // Trim helper method |
| 2526 | |
| 2527 | void Trim(std::wstring& string) |
| 2528 | { |
| 2529 | // Remove any extra chars (lf, spaces, ...) |
| 2530 | std::erase_if(string, [](auto c) { return !isalnum(c); }); |
| 2531 | } |
| 2532 | |
| 2533 | static std::optional<std::wstring> CaptureEnvValue(const std::wstring& Name) |
| 2534 | { |
| 2535 | std::wstring value; |
| 2536 | HRESULT hr = wil::GetEnvironmentVariableW(Name.c_str(), value); |
| 2537 | if (hr == HRESULT_FROM_WIN32(ERROR_ENVVAR_NOT_FOUND)) |
| 2538 | { |
| 2539 | return std::nullopt; |
| 2540 | } |
| 2541 | THROW_IF_FAILED(hr); |
| 2542 | return value; |
| 2543 | } |
| 2544 | |
| 2545 | ScopedEnvVariable::ScopedEnvVariable(const std::wstring& Name) : m_name(Name), m_originalValue(CaptureEnvValue(Name)) |
| 2546 | { |
| 2547 | VERIFY_IS_TRUE(SetEnvironmentVariableW(Name.c_str(), nullptr)); |
| 2548 | } |
| 2549 | |
| 2550 | ScopedEnvVariable::ScopedEnvVariable(const std::wstring& Name, const std::wstring& Value) : |
| 2551 | m_name(Name), m_originalValue(CaptureEnvValue(Name)) |
| 2552 | { |
| 2553 | VERIFY_IS_TRUE(SetEnvironmentVariableW(Name.c_str(), Value.c_str())); |
| 2554 | } |
| 2555 | |
| 2556 | ScopedEnvVariable::~ScopedEnvVariable() |
| 2557 | { |
| 2558 | VERIFY_IS_TRUE(SetEnvironmentVariableW(m_name.c_str(), m_originalValue.has_value() ? m_originalValue->c_str() : nullptr)); |
| 2559 | } |
| 2560 | |
| 2561 | void ScopedEnvVariable::Set(const std::wstring& Value) |
| 2562 | { |
| 2563 | VERIFY_IS_TRUE(SetEnvironmentVariableW(m_name.c_str(), Value.c_str())); |
| 2564 | } |
| 2565 | |
| 2566 | void ScopedEnvVariable::Clear() |
| 2567 | { |
| 2568 | VERIFY_IS_TRUE(SetEnvironmentVariableW(m_name.c_str(), nullptr)); |
| 2569 | } |
| 2570 | |
| 2571 | UniqueWebServer::UniqueWebServer(LPCWSTR Endpoint, LPCWSTR Content, UINT StatusCode) |
| 2572 | { |
| 2573 | auto cmd = std::format( |
| 2574 | LR"(Powershell.exe -NoProfile -ExecutionPolicy Bypass -Command " |
| 2575 | $ErrorActionPreference = 'Stop' |
| 2576 | $server = New-Object System.Net.HttpListener |
| 2577 | $server.Prefixes.Add('{}') |
| 2578 | $server.Start() |
| 2579 | while ($true) |
| 2580 | {{ |
| 2581 | $context = $server.GetContext() |
| 2582 | $context.Response.StatusCode = {} |
| 2583 | $content = [Text.Encoding]::UTF8.GetBytes('{}') |
| 2584 | $context.Response.OutputStream.Write($content , 0, $content.length) |
| 2585 | $context.Response.close() |
| 2586 | }}")", |
| 2587 | Endpoint, |
| 2588 | StatusCode, |
| 2589 | Content); |
| 2590 | |
| 2591 | m_process = LxsstuStartProcess(cmd.data()); |
| 2592 | } |
| 2593 | |
| 2594 | UniqueWebServer::UniqueWebServer(LPCWSTR Endpoint, const std::filesystem::path& File) |
| 2595 | { |
| 2596 | auto cmd = std::format( |
| 2597 | LR"(Powershell.exe -NoProfile -ExecutionPolicy Bypass -Command " |
| 2598 | $ErrorActionPreference = 'Stop' |
| 2599 | $server = New-Object System.Net.HttpListener |
| 2600 | $server.Prefixes.Add('{}') |
| 2601 | $server.Start() |
| 2602 | while ($true) |
| 2603 | {{ |
| 2604 | $context = $server.GetContext() |
| 2605 | $context.Response.StatusCode |
| 2606 | $content = [System.IO.File]::ReadAllBytes('{}') |
| 2607 | $context.Response.ContentLength64 = $content.length |
| 2608 | $context.Response.ContentType = 'application/octet-stream' |
| 2609 | $context.Response.OutputStream.Write($content, 0, $content.length) |
| 2610 | $context.Response.close() |
| 2611 | }}")", |
| 2612 | Endpoint, |
| 2613 | File.wstring()); |
| 2614 | |
| 2615 | m_process = LxsstuStartProcess(cmd.data()); |
| 2616 | } |
| 2617 | |
| 2618 | UniqueWebServer::~UniqueWebServer() |
| 2619 | { |
| 2620 | if (!TerminateProcess(m_process.get(), 0)) |
| 2621 | { |
| 2622 | LogError("TerminateProcess failed, %lu", GetLastError()); |
| 2623 | } |
| 2624 | } |
| 2625 | |
| 2626 | DistroFileChange::DistroFileChange(LPCWSTR Path, bool exists) : m_path(Path) |
| 2627 | { |
| 2628 | if (exists) |
| 2629 | { |
| 2630 | m_originalContent = LxsstuLaunchWslAndCaptureOutput(std::format(L"cat '{}'", m_path)).first; |
| 2631 | } |
| 2632 | } |
| 2633 | |
| 2634 | DistroFileChange::~DistroFileChange() |
| 2635 | { |
| 2636 | if (m_originalContent.has_value()) |
| 2637 | { |
| 2638 | SetContent(m_originalContent->c_str()); |
| 2639 | } |
| 2640 | else |
| 2641 | { |
| 2642 | Delete(); |
| 2643 | } |
| 2644 | } |
| 2645 | |
| 2646 | void DistroFileChange::SetContent(LPCWSTR Content) |
| 2647 | { |
| 2648 | const auto cmd = LxssGenerateWslCommandLine(std::format(L" -u root cat > '{}'", m_path).c_str()); |
| 2649 | wsl::windows::common::SubProcess process(nullptr, cmd.c_str()); |
| 2650 | |
| 2651 | auto [read, write] = CreateSubprocessPipe(true, false); |
| 2652 | |
| 2653 | process.SetStdHandles(read.get(), nullptr, nullptr); |
| 2654 | const auto processHandle = process.Start(); |
| 2655 | |
| 2656 | const auto utf8content = wsl::shared::string::WideToMultiByte(Content); |
| 2657 | auto index = 0; |
| 2658 | |
| 2659 | while (index < utf8content.size()) |
| 2660 | { |
| 2661 | DWORD written{}; |
| 2662 | |
| 2663 | VERIFY_IS_TRUE(WriteFile(write.get(), utf8content.data() + index, static_cast<DWORD>(utf8content.size() - index), &written, nullptr)); |
| 2664 | |
| 2665 | index += written; |
| 2666 | } |
| 2667 | |
| 2668 | write.reset(); |
| 2669 | |
| 2670 | VERIFY_ARE_EQUAL(wsl::windows::common::SubProcess::GetExitCode(processHandle.get()), 0L); |
| 2671 | } |
| 2672 | |
| 2673 | void DistroFileChange::Delete() |
| 2674 | { |
| 2675 | VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-u root rm -f '{}'", m_path).c_str()), 0L); |
| 2676 | } |
| 2677 | |
| 2678 | std::string ReadToString(SOCKET Handle) |
| 2679 | { |
| 2680 | std::string output; |
| 2681 | DWORD offset = 0; |
| 2682 | while (true) // TODO: timeout |
| 2683 | { |
| 2684 | constexpr auto bufferSize = 512; |
| 2685 | |
| 2686 | output.resize(output.size() + bufferSize); |
| 2687 | int bytesRead = 0; |
| 2688 | |
| 2689 | if ((bytesRead = recv(Handle, &output[offset], bufferSize, 0)) < 0) |
| 2690 | { |
| 2691 | LogError("recv failed with %lu", GetLastError()); |
| 2692 | VERIFY_FAIL(); |
| 2693 | } |
| 2694 | |
| 2695 | if (bytesRead == 0) |
| 2696 | { |
| 2697 | output.resize(offset); |
| 2698 | break; |
| 2699 | } |
| 2700 | |
| 2701 | output.resize(offset + bytesRead); |
| 2702 | offset += bytesRead; |
| 2703 | } |
| 2704 | |
| 2705 | return output; |
| 2706 | } |
| 2707 | |
| 2708 | std::pair<wil::unique_socket, wil::unique_socket> MakeSocketPair() |
| 2709 | { |
| 2710 | wil::unique_socket listenSocket{WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED)}; |
| 2711 | THROW_LAST_ERROR_IF(!listenSocket); |
| 2712 | |
| 2713 | sockaddr_in bindAddr{}; |
| 2714 | bindAddr.sin_family = AF_INET; |
| 2715 | bindAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| 2716 | bindAddr.sin_port = 0; |
| 2717 | THROW_LAST_ERROR_IF(bind(listenSocket.get(), reinterpret_cast<sockaddr*>(&bindAddr), sizeof(bindAddr)) == SOCKET_ERROR); |
| 2718 | THROW_LAST_ERROR_IF(listen(listenSocket.get(), 1) == SOCKET_ERROR); |
| 2719 | |
| 2720 | sockaddr_in boundAddr{}; |
| 2721 | int boundAddrLen = sizeof(boundAddr); |
| 2722 | THROW_LAST_ERROR_IF(getsockname(listenSocket.get(), reinterpret_cast<sockaddr*>(&boundAddr), &boundAddrLen) == SOCKET_ERROR); |
| 2723 | |
| 2724 | wil::unique_socket clientSocket{WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED)}; |
| 2725 | THROW_LAST_ERROR_IF(!clientSocket); |
| 2726 | THROW_LAST_ERROR_IF(connect(clientSocket.get(), reinterpret_cast<sockaddr*>(&boundAddr), sizeof(boundAddr)) == SOCKET_ERROR); |
| 2727 | |
| 2728 | wil::unique_socket serverSocket{accept(listenSocket.get(), nullptr, nullptr)}; |
| 2729 | THROW_LAST_ERROR_IF(!serverSocket); |
| 2730 | |
| 2731 | return {std::move(clientSocket), std::move(serverSocket)}; |
| 2732 | } |
| 2733 | |
| 2734 | std::string ReadToString(HANDLE Handle) |
| 2735 | { |
| 2736 | std::string output; |
| 2737 | DWORD offset = 0; |
| 2738 | constexpr DWORD bufferSize = 4096; |
| 2739 | |
| 2740 | while (true) |
| 2741 | { |
| 2742 | output.resize(offset + bufferSize); |
| 2743 | DWORD bytesRead = 0; |
| 2744 | if (!ReadFile(Handle, output.data() + offset, bufferSize, &bytesRead, nullptr)) |
| 2745 | { |
| 2746 | VERIFY_ARE_EQUAL(GetLastError(), ERROR_BROKEN_PIPE); |
| 2747 | } |
| 2748 | |
| 2749 | offset += bytesRead; |
| 2750 | output.resize(offset); |
| 2751 | if (bytesRead == 0) |
| 2752 | { |
| 2753 | break; |
| 2754 | } |
| 2755 | } |
| 2756 | |
| 2757 | return output; |
| 2758 | } |
| 2759 | |
| 2760 | void VerifyPatternMatch(const std::string& Content, const std::string& Pattern) |
| 2761 | { |
| 2762 | if (!PathMatchSpecA(Content.c_str(), Pattern.c_str())) |
| 2763 | { |
| 2764 | std::wstring message = std::format(L"Output: '{}' didn't match pattern: '{}'", Content, Pattern); |
| 2765 | VERIFY_FAIL(message.c_str()); |
| 2766 | } |
| 2767 | } |
| 2768 | |
| 2769 | std::string EscapeString(const std::string& Input) |
| 2770 | { |
| 2771 | std::string Output; |
| 2772 | |
| 2773 | for (const auto& e : Input) |
| 2774 | { |
| 2775 | if (e == '\n') |
| 2776 | { |
| 2777 | Output += "\\n"; |
| 2778 | } |
| 2779 | else if (e == '\r') |
| 2780 | { |
| 2781 | Output += "\\r"; |
| 2782 | } |
| 2783 | else if (e == '\0') |
| 2784 | { |
| 2785 | Output += "\\0"; |
| 2786 | } |
| 2787 | else if (e == '\t') |
| 2788 | { |
| 2789 | Output += "\\t"; |
| 2790 | } |
| 2791 | else if (e == '\x1b') // ESC character - start of VT sequence |
| 2792 | { |
| 2793 | Output += "\\x1b"; |
| 2794 | } |
| 2795 | else |
| 2796 | { |
| 2797 | Output += e; |
| 2798 | } |
| 2799 | } |
| 2800 | |
| 2801 | return Output; |
| 2802 | } |
| 2803 | |
| 2804 | PartialHandleRead::PartialHandleRead(HANDLE Handle) : m_handle(Handle) |
| 2805 | { |
| 2806 | m_thread = std::thread(std::bind(&PartialHandleRead::Run, this)); |
| 2807 | } |
| 2808 | |
| 2809 | PartialHandleRead::~PartialHandleRead() |
| 2810 | { |
| 2811 | Stop(); |
| 2812 | } |
| 2813 | |
| 2814 | void PartialHandleRead::Stop() |
| 2815 | { |
| 2816 | m_exitEvent.SetEvent(); |
| 2817 | if (m_thread.joinable()) |
| 2818 | { |
| 2819 | m_thread.join(); |
| 2820 | } |
| 2821 | } |
| 2822 | |
| 2823 | std::string PartialHandleRead::ReadBytes(size_t Length) |
| 2824 | { |
| 2825 | wsl::shared::retry::RetryWithTimeout<void>( |
| 2826 | [&]() { |
| 2827 | std::lock_guard lock{m_mutex}; |
| 2828 | |
| 2829 | THROW_HR_IF(E_ABORT, m_data.size() < Length); |
| 2830 | }, |
| 2831 | std::chrono::milliseconds(100), |
| 2832 | std::chrono::seconds(60)); |
| 2833 | |
| 2834 | std::lock_guard lock{m_mutex}; |
| 2835 | |
| 2836 | return m_data.substr(0, Length); |
| 2837 | } |
| 2838 | |
| 2839 | std::string PartialHandleRead::ConsumeBytes(size_t Length) |
| 2840 | { |
| 2841 | wsl::shared::retry::RetryWithTimeout<void>( |
| 2842 | [&]() { |
| 2843 | std::lock_guard lock{m_mutex}; |
| 2844 | |
| 2845 | THROW_HR_IF(E_ABORT, m_data.size() < Length); |
| 2846 | }, |
| 2847 | std::chrono::milliseconds(100), |
| 2848 | std::chrono::seconds(60)); |
| 2849 | |
| 2850 | std::lock_guard lock{m_mutex}; |
| 2851 | std::string result = m_data.substr(0, Length); |
| 2852 | m_data.erase(0, Length); |
| 2853 | return result; |
| 2854 | } |
| 2855 | |
| 2856 | std::string PartialHandleRead::GetData() const |
| 2857 | { |
| 2858 | std::lock_guard lock{m_mutex}; |
| 2859 | return m_data; |
| 2860 | } |
| 2861 | |
| 2862 | void PartialHandleRead::Expect(const std::string& Expected) |
| 2863 | { |
| 2864 | auto content = ReadBytes(Expected.size()); |
| 2865 | |
| 2866 | VERIFY_ARE_EQUAL(content, Expected); |
| 2867 | } |
| 2868 | |
| 2869 | void PartialHandleRead::ExpectConsume(const std::string& Expected) |
| 2870 | { |
| 2871 | auto content = ConsumeBytes(Expected.size()); |
| 2872 | |
| 2873 | if (content != Expected) |
| 2874 | { |
| 2875 | VERIFY_FAIL(std::format( |
| 2876 | L"Expected: '{}' but got: '{}'", |
| 2877 | wsl::shared::string::MultiByteToWide(EscapeString(Expected)), |
| 2878 | wsl::shared::string::MultiByteToWide(EscapeString(content))) |
| 2879 | .c_str()); |
| 2880 | } |
| 2881 | } |
| 2882 | |
| 2883 | void PartialHandleRead::ExpectClosed(DWORD Timeout) |
| 2884 | { |
| 2885 | VERIFY_ARE_EQUAL(WaitForSingleObject(m_thread.native_handle(), Timeout), WAIT_OBJECT_0); |
| 2886 | } |
| 2887 | |
| 2888 | void PartialHandleRead::Run() |
| 2889 | try |
| 2890 | { |
| 2891 | std::vector<gsl::byte> buffer(4096); |
| 2892 | |
| 2893 | while (!m_exitEvent.is_signaled()) |
| 2894 | { |
| 2895 | auto bytesRead = wsl::windows::common::relay::InterruptableRead(m_handle, gsl::make_span(buffer), {m_exitEvent.get()}); |
| 2896 | if (bytesRead == 0) |
| 2897 | { |
| 2898 | break; |
| 2899 | } |
| 2900 | |
| 2901 | std::lock_guard lock{m_mutex}; |
| 2902 | m_data.append(reinterpret_cast<char*>(buffer.data()), bytesRead); |
| 2903 | } |
| 2904 | } |
| 2905 | CATCH_LOG(); |
| 2906 | |
| 2907 | class ReadHandleWithTargetValue : public wsl::windows::common::io::ReadHandle |
| 2908 | { |
| 2909 | public: |
| 2910 | NON_COPYABLE(ReadHandleWithTargetValue); |
| 2911 | NON_MOVABLE(ReadHandleWithTargetValue); |
| 2912 | |
| 2913 | ReadHandleWithTargetValue(wsl::windows::common::io::HandleWrapper&& MovedHandle, std::string_view targetValue) : |
| 2914 | ReadHandle(std::move(MovedHandle), [this](const auto& buffer) { m_readBuffer.append(buffer.data(), buffer.size()); }), |
| 2915 | m_targetValue(targetValue) |
| 2916 | { |
| 2917 | } |
| 2918 | |
| 2919 | void Schedule() override |
| 2920 | { |
| 2921 | ReadHandle::Schedule(); |
| 2922 | CheckIfTargetFound(); |
| 2923 | } |
| 2924 | |
| 2925 | void Collect() override |
| 2926 | { |
| 2927 | ReadHandle::Collect(); |
| 2928 | CheckIfTargetFound(); |
| 2929 | } |
| 2930 | |
| 2931 | private: |
| 2932 | void CheckIfTargetFound() |
| 2933 | { |
| 2934 | using namespace wsl::windows::common::io; |
| 2935 | |
| 2936 | if (State == IOHandleStatus::Standby || State == IOHandleStatus::Completed) |
| 2937 | { |
| 2938 | bool targetFound = (m_readBuffer.find(m_targetValue) != std::string::npos); |
| 2939 | |
| 2940 | if (State == IOHandleStatus::Standby) |
| 2941 | { |
| 2942 | if (targetFound) |
| 2943 | { |
| 2944 | State = IOHandleStatus::Completed; |
| 2945 | } |
| 2946 | } |
| 2947 | else |
| 2948 | { |
| 2949 | THROW_WIN32_IF(ERROR_NOT_FOUND, !targetFound); |
| 2950 | } |
| 2951 | } |
| 2952 | } |
| 2953 | |
| 2954 | std::string m_readBuffer; |
| 2955 | std::string m_targetValue; |
| 2956 | }; |
| 2957 | |
| 2958 | void WaitForOutput(wsl::windows::common::io::HandleWrapper handle, std::string_view targetValue, std::chrono::milliseconds timeout) |
| 2959 | { |
| 2960 | wsl::windows::common::io::MultiHandleWait io; |
| 2961 | io.AddHandle(std::make_unique<ReadHandleWithTargetValue>(std::move(handle), targetValue)); |
| 2962 | io.Run(timeout); |
| 2963 | } |
| 2964 | |
| 2965 | std::filesystem::path GetTestImagePath(std::string_view imageName) |
| 2966 | { |
| 2967 | std::filesystem::path result = std::filesystem::path{g_testDataPath}; |
| 2968 | |
| 2969 | if (imageName == "debian:latest") |
| 2970 | { |
| 2971 | result /= L"debian-latest.tar"; |
| 2972 | } |
| 2973 | else if (imageName == "python:3.12-alpine") |
| 2974 | { |
| 2975 | result /= L"python-3_12-alpine.tar"; |
| 2976 | } |
| 2977 | else if (imageName == "alpine:latest") |
| 2978 | { |
| 2979 | result /= L"alpine-latest.tar"; |
| 2980 | } |
| 2981 | else if (imageName == "hello-world:latest") |
| 2982 | { |
| 2983 | result /= L"HelloWorldSaved.tar"; |
| 2984 | } |
| 2985 | else if (imageName == "wslc-registry:latest") |
| 2986 | { |
| 2987 | result /= L"wslc-registry.tar"; |
| 2988 | } |
| 2989 | else |
| 2990 | { |
| 2991 | THROW_HR_MSG(E_INVALIDARG, "Unknown test image: %hs", imageName.data()); |
| 2992 | } |
| 2993 | |
| 2994 | return result; |
| 2995 | } |
| 2996 | |
| 2997 | void LoadTestImage(IWSLCSession& session, std::string_view imageName) |
| 2998 | { |
| 2999 | std::filesystem::path imagePath = GetTestImagePath(imageName); |
| 3000 | wil::unique_hfile imageFile{ |
| 3001 | CreateFileW(imagePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)}; |
| 3002 | THROW_LAST_ERROR_IF(!imageFile); |
| 3003 | |
| 3004 | LARGE_INTEGER fileSize{}; |
| 3005 | THROW_LAST_ERROR_IF(!GetFileSizeEx(imageFile.get(), &fileSize)); |
| 3006 | |
| 3007 | THROW_IF_FAILED(session.LoadImage(wsl::windows::common::wslutil::ToCOMInputHandle(imageFile.get()), fileSize.QuadPart, nullptr, nullptr)); |
| 3008 | } |
| 3009 | |
| 3010 | void ExpectHttpResponse(LPCWSTR Url, std::optional<int> expectedCode, bool retry) |
| 3011 | { |
| 3012 | const winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter; |
| 3013 | filter.CacheControl().WriteBehavior(winrt::Windows::Web::Http::Filters::HttpCacheWriteBehavior::NoCache); |
| 3014 | |
| 3015 | const winrt::Windows::Web::Http::HttpClient client(filter); |
| 3016 | |
| 3017 | const auto sendRequest = [&]() { |
| 3018 | try |
| 3019 | { |
| 3020 | LogInfo("Sending request to: %ls", Url); |
| 3021 | auto response = client.GetAsync(winrt::Windows::Foundation::Uri(Url)).get(); |
| 3022 | auto content = response.Content().ReadAsStringAsync().get(); |
| 3023 | |
| 3024 | if (expectedCode.has_value()) |
| 3025 | { |
| 3026 | VERIFY_ARE_EQUAL(static_cast<int>(response.StatusCode()), expectedCode.value()); |
| 3027 | } |
| 3028 | else |
| 3029 | { |
| 3030 | LogError("Unexpected reply for: %ls", Url); |
| 3031 | VERIFY_FAIL(); |
| 3032 | } |
| 3033 | } |
| 3034 | catch (...) |
| 3035 | { |
| 3036 | auto result = wil::ResultFromCaughtException(); |
| 3037 | |
| 3038 | if (!expectedCode.has_value()) |
| 3039 | { |
| 3040 | // We currently reset the connection if connect() fails inside |
| 3041 | // the VM. Consider failing the Windows connect() instead. |
| 3042 | VERIFY_ARE_EQUAL(result, HRESULT_FROM_WIN32(WININET_E_INVALID_SERVER_RESPONSE)); |
| 3043 | return; |
| 3044 | } |
| 3045 | |
| 3046 | // Throw so RetryWithTimeout can decide whether to retry. |
| 3047 | THROW_HR(result); |
| 3048 | } |
| 3049 | }; |
| 3050 | |
| 3051 | if (retry) |
| 3052 | { |
| 3053 | wsl::shared::retry::RetryWithTimeout<void>(sendRequest, std::chrono::milliseconds(500), std::chrono::seconds(30), [&]() { |
| 3054 | return wil::ResultFromCaughtException() == HRESULT_FROM_WIN32(WININET_E_INVALID_SERVER_RESPONSE); |
| 3055 | }); |
| 3056 | } |
| 3057 | else |
| 3058 | { |
| 3059 | sendRequest(); |
| 3060 | } |
| 3061 | } |
| 3062 | |
| 3063 | std::optional<std::wstring> GetHostAdapterIpv4() |
| 3064 | { |
| 3065 | auto endpoint = wsl::core::networking::GetHostEndpointSettings(); |
| 3066 | if (!endpoint || endpoint->PreferredIpAddress.AddressString.empty()) |
| 3067 | { |
| 3068 | return {}; |
| 3069 | } |
| 3070 | |
| 3071 | return endpoint->PreferredIpAddress.AddressString; |
| 3072 | } |
| 3073 | |
| 3074 | void SetPathAccess(const std::filesystem::path& path, DWORD Permissions, ACCESS_MODE Mode) |
| 3075 | { |
| 3076 | auto [everyoneSid, everyoneSidBuffer] = wsl::windows::common::security::CreateSid(SECURITY_WORLD_SID_AUTHORITY, SECURITY_WORLD_RID); |
| 3077 | |
| 3078 | EXPLICIT_ACCESSW ea{}; |
| 3079 | ea.grfAccessPermissions = Permissions; |
| 3080 | ea.grfAccessMode = Mode; |
| 3081 | ea.grfInheritance = NO_INHERITANCE; |
| 3082 | ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; |
| 3083 | ea.Trustee.ptstrName = static_cast<LPWSTR>(everyoneSid); |
| 3084 | |
| 3085 | PACL acl = nullptr; |
| 3086 | wil::unique_hlocal descriptor; |
| 3087 | THROW_IF_WIN32_ERROR( |
| 3088 | GetNamedSecurityInfoW(path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &acl, nullptr, &descriptor)); |
| 3089 | |
| 3090 | wsl::windows::common::security::unique_acl newAcl; |
| 3091 | THROW_IF_WIN32_ERROR(SetEntriesInAclW(1, &ea, acl, &newAcl)); |
| 3092 | |
| 3093 | THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW( |
| 3094 | const_cast<LPWSTR>(path.c_str()), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, newAcl.get(), nullptr)); |
| 3095 | } |
| 3096 | |
| 3097 | void WriteSocket(SOCKET Socket, const void* data, size_t size) |
| 3098 | { |
| 3099 | while (size > 0) |
| 3100 | { |
| 3101 | auto result = send(Socket, static_cast<const char*>(data), gsl::narrow_cast<int>(size), 0); |
| 3102 | VERIFY_IS_TRUE(result > 0); |
| 3103 | |
| 3104 | size -= result; |
| 3105 | data = static_cast<const char*>(data) + result; |
| 3106 | } |
| 3107 | } |
| 3108 | |
| 3109 | void ValidateCOMErrorMessage(const std::optional<std::wstring>& Expected, const std::source_location& Source) |
| 3110 | { |
| 3111 | auto comError = wsl::windows::common::wslutil::GetCOMErrorInfo(); |
| 3112 | |
| 3113 | if (comError.has_value()) |
| 3114 | { |
| 3115 | if (!Expected.has_value()) |
| 3116 | { |
| 3117 | LogError("Unexpected COM error: '%ls'. Source: %hs", comError->Message.get(), std::format("{}", Source).c_str()); |
| 3118 | VERIFY_FAIL(); |
| 3119 | } |
| 3120 | |
| 3121 | VERIFY_ARE_EQUAL(Expected.value(), comError->Message.get()); |
| 3122 | } |
| 3123 | else |
| 3124 | { |
| 3125 | if (Expected.has_value()) |
| 3126 | { |
| 3127 | LogError("Expected COM error: '%ls' but none was set. Source: %hs", Expected->c_str(), std::format("{}", Source).c_str()); |
| 3128 | VERIFY_FAIL(); |
| 3129 | } |
| 3130 | } |
| 3131 | } |
| 3132 | |
| 3133 | void ValidateCOMErrorMessageContains(const std::wstring& ExpectedSubstring) |
| 3134 | { |
| 3135 | auto comError = wsl::windows::common::wslutil::GetCOMErrorInfo(); |
| 3136 | |
| 3137 | if (comError.has_value()) |
| 3138 | { |
| 3139 | if (!comError->Message) |
| 3140 | { |
| 3141 | LogError("Expected COM error containing: '%ls', but COM error message was null", ExpectedSubstring.c_str()); |
| 3142 | VERIFY_FAIL(); |
| 3143 | } |
| 3144 | |
| 3145 | if (wcsstr(comError->Message.get(), ExpectedSubstring.c_str()) == nullptr) |
| 3146 | { |
| 3147 | LogError("Expected COM error containing: '%ls', but got: '%ls'", ExpectedSubstring.c_str(), comError->Message.get()); |
| 3148 | VERIFY_FAIL(); |
| 3149 | } |
| 3150 | } |
| 3151 | else |
| 3152 | { |
| 3153 | LogError("Expected COM error containing: '%ls' but none was set", ExpectedSubstring.c_str()); |
| 3154 | VERIFY_FAIL(); |
| 3155 | } |
| 3156 | } |
| 3157 | |
| 3158 | std::wstring FormatErrorMessage(std::wstring_view message, std::wstring_view errorCode) |
| 3159 | { |
| 3160 | return std::format( |
| 3161 | L"{}\r\nError code: {}\r\n" |
| 3162 | L"If this error was unexpected, please consider searching for existing issues or filing a new issue at " |
| 3163 | L"https://github.com/microsoft/WSL/issues.\r\n", |
| 3164 | message, |
| 3165 | errorCode); |
| 3166 | } |