| 1 | // Copyright (C) Microsoft Corporation. All rights reserved. |
| 2 | |
| 3 | #include "precomp.h" |
| 4 | #include "ExecutionContext.h" |
| 5 | #include "wsleventschema.h" |
| 6 | |
| 7 | using wsl::windows::common::ClientExecutionContext; |
| 8 | using wsl::windows::common::COMServiceExecutionContext; |
| 9 | using wsl::windows::common::Context; |
| 10 | using wsl::windows::common::Error; |
| 11 | using wsl::windows::common::ExecutionContext; |
| 12 | using wsl::windows::common::ServiceExecutionContext; |
| 13 | |
| 14 | thread_local ExecutionContext* g_currentContext = nullptr; |
| 15 | static bool g_enabled = false; |
| 16 | bool g_runningInService = false; |
| 17 | bool g_useComErrors = false; |
| 18 | bool g_enableNotifications = false; |
| 19 | static HANDLE g_eventLog = nullptr; |
| 20 | |
| 21 | void wsl::windows::common::EnableContextualizedErrors(bool service, bool useComErrors, bool enableNotifications) |
| 22 | { |
| 23 | WI_ASSERT(!g_enabled); |
| 24 | g_enabled = true; |
| 25 | g_runningInService = service; |
| 26 | g_useComErrors = useComErrors; |
| 27 | g_enableNotifications = enableNotifications; |
| 28 | } |
| 29 | |
| 30 | ExecutionContext::ExecutionContext(Context context, FILE* warningsFile) noexcept : |
| 31 | m_warningsFile(warningsFile), m_parent(g_currentContext), m_context(context) |
| 32 | { |
| 33 | WI_ASSERT(g_currentContext == nullptr || g_currentContext->m_context < m_context); |
| 34 | |
| 35 | if (!g_enabled) |
| 36 | { |
| 37 | return; |
| 38 | } |
| 39 | |
| 40 | g_currentContext = this; |
| 41 | } |
| 42 | |
| 43 | ExecutionContext::~ExecutionContext() |
| 44 | { |
| 45 | g_currentContext = m_parent; |
| 46 | WI_ASSERT(!m_errorString.has_value()); |
| 47 | WI_ASSERT(!m_errorSource.has_value()); |
| 48 | } |
| 49 | |
| 50 | ExecutionContext* ExecutionContext::Current() |
| 51 | { |
| 52 | return g_currentContext; |
| 53 | } |
| 54 | |
| 55 | void ExecutionContext::SetErrorStringImpl(std::wstring&& string, std::wstring&& source) |
| 56 | { |
| 57 | WI_ASSERT(!m_errorString.has_value()); |
| 58 | m_errorString = std::move(string); |
| 59 | m_errorSource = std::move(source); |
| 60 | } |
| 61 | |
| 62 | bool ExecutionContext::CanCollectUserErrorMessage() |
| 63 | { |
| 64 | if (g_runningInService) |
| 65 | { |
| 66 | if (m_parent != nullptr) |
| 67 | { |
| 68 | return m_parent->CanCollectUserErrorMessage(); |
| 69 | } |
| 70 | else |
| 71 | { |
| 72 | // If we're running in a service and the root context isn't a service context, |
| 73 | // then error messages cannot be reported. |
| 74 | return false; |
| 75 | } |
| 76 | } |
| 77 | else |
| 78 | { |
| 79 | return true; |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | ULONGLONG ExecutionContext::CurrentContext() const noexcept |
| 84 | { |
| 85 | ULONGLONG errorContext = m_context; |
| 86 | for (const auto* e = m_parent; e != nullptr; e = e->m_parent) |
| 87 | { |
| 88 | errorContext |= static_cast<ULONGLONG>(e->m_context); |
| 89 | } |
| 90 | |
| 91 | return errorContext; |
| 92 | } |
| 93 | |
| 94 | void ExecutionContext::CollectErrorImpl(HRESULT result, ULONGLONG context, std::optional<std::wstring>&& message, std::optional<std::wstring>&& source) |
| 95 | { |
| 96 | WI_ASSERT(m_parent == nullptr); |
| 97 | |
| 98 | // Special case for an error being rethrown from a parent context. |
| 99 | if (m_error.has_value() && m_error->Code == result && (context & m_error->Context) == context) |
| 100 | { |
| 101 | // This error has the same HRESULT that the one we already have and comes from a less specific context, drop. |
| 102 | if (!m_error->Message.has_value() && message.has_value()) |
| 103 | { |
| 104 | /* This is for the scenario where a specialized error message is sent after catching and rethrowing an error. |
| 105 | * Example: |
| 106 | * try |
| 107 | * { |
| 108 | * something(); |
| 109 | * } |
| 110 | * catch (...) |
| 111 | * { |
| 112 | * THROW_HR_WITH_USER_ERROR(..., "Something failed: [...]"); |
| 113 | * } |
| 114 | */ |
| 115 | |
| 116 | m_error->Message = std::move(message); |
| 117 | m_error->Source = std::move(source); |
| 118 | } |
| 119 | |
| 120 | return; |
| 121 | } |
| 122 | |
| 123 | m_error.emplace(result, context, std::move(message), std::move(source)); |
| 124 | } |
| 125 | |
| 126 | void ExecutionContext::CollectError(HRESULT result) |
| 127 | { |
| 128 | if (g_currentContext == nullptr) |
| 129 | { |
| 130 | return; |
| 131 | } |
| 132 | |
| 133 | g_currentContext->CollectErrorImpl(result); |
| 134 | } |
| 135 | |
| 136 | void ExecutionContext::CollectErrorImpl(HRESULT result) |
| 137 | { |
| 138 | if (!g_runningInService && g_useComErrors && !m_errorString.has_value() && !m_errorSource.has_value()) |
| 139 | { |
| 140 | // If no error message has been reported, look for a COM error. |
| 141 | if (auto comError = common::wslutil::GetCOMErrorInfo()) |
| 142 | { |
| 143 | if (comError->Message) |
| 144 | { |
| 145 | m_errorString = comError->Message.get(); |
| 146 | } |
| 147 | |
| 148 | if (comError->Source) |
| 149 | { |
| 150 | m_errorSource = comError->Source.get(); |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | RootContext().CollectErrorImpl(result, CurrentContext(), std::move(m_errorString), std::move(m_errorSource)); |
| 156 | |
| 157 | m_errorString.reset(); |
| 158 | m_errorSource.reset(); |
| 159 | } |
| 160 | |
| 161 | void ExecutionContext::EmitUserWarning(const std::wstring& warning, const std::source_location& location) |
| 162 | { |
| 163 | WSL_LOG( |
| 164 | "UserWarning", |
| 165 | TraceLoggingValue(location.file_name(), "FileName"), |
| 166 | TraceLoggingValue(location.line(), "Line"), |
| 167 | TraceLoggingValue(warning.c_str(), "Content")); |
| 168 | |
| 169 | if (!g_enabled) |
| 170 | { |
| 171 | return; |
| 172 | } |
| 173 | |
| 174 | if (!CollectUserWarning(std::format(L"wsl: {}\n", warning)) && g_enableNotifications) |
| 175 | { |
| 176 | static std::atomic<bool> displayed = false; |
| 177 | if (!displayed.exchange(true)) |
| 178 | { |
| 179 | wsl::windows::common::notifications::DisplayWarningsNotification(); |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | if (g_eventLog) |
| 184 | { |
| 185 | auto* warningPtr = warning.c_str(); |
| 186 | LOG_IF_WIN32_BOOL_FALSE(ReportEventW(g_eventLog, EVENTLOG_WARNING_TYPE, 0, MSG_WARNING, nullptr, 1, 0, &warningPtr, nullptr)); |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | ExecutionContext& ExecutionContext::RootContext() |
| 191 | { |
| 192 | if (m_parent == nullptr) |
| 193 | { |
| 194 | return *this; |
| 195 | } |
| 196 | else |
| 197 | { |
| 198 | return m_parent->RootContext(); |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | const std::optional<wsl::windows::common::Error>& ExecutionContext::ReportedError() const noexcept |
| 203 | { |
| 204 | if (!m_error.has_value() && m_parent != nullptr) |
| 205 | { |
| 206 | return m_parent->ReportedError(); |
| 207 | } |
| 208 | |
| 209 | return m_error; |
| 210 | } |
| 211 | |
| 212 | bool ExecutionContext::ShouldCollectErrorMessage() |
| 213 | { |
| 214 | return g_currentContext != nullptr && g_currentContext->CanCollectUserErrorMessage(); |
| 215 | } |
| 216 | |
| 217 | bool ExecutionContext::CanCollectUserWarnings() const |
| 218 | { |
| 219 | return m_warningsFile != nullptr; |
| 220 | } |
| 221 | |
| 222 | bool ExecutionContext::CollectUserWarning(const std::wstring& warning) |
| 223 | { |
| 224 | if (m_warningsFile != nullptr) |
| 225 | { |
| 226 | fputws(warning.c_str(), m_warningsFile); |
| 227 | return true; |
| 228 | } |
| 229 | else if (m_parent != nullptr) |
| 230 | { |
| 231 | return m_parent->CollectUserWarning(warning); |
| 232 | } |
| 233 | else |
| 234 | { |
| 235 | return false; |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | ClientExecutionContext::~ClientExecutionContext() |
| 240 | { |
| 241 | if (m_outError.Message != nullptr) |
| 242 | { |
| 243 | CoTaskMemFree(m_outError.Message); |
| 244 | } |
| 245 | |
| 246 | if (m_outError.Warnings != nullptr) |
| 247 | { |
| 248 | CollectUserWarning(m_outError.Warnings); |
| 249 | CoTaskMemFree(m_outError.Warnings); |
| 250 | } |
| 251 | |
| 252 | if (m_interactiveWarningsThread.joinable()) |
| 253 | { |
| 254 | m_warningsPipeWrite.reset(); |
| 255 | m_interactiveWarningsThread.join(); |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | ClientExecutionContext::ClientExecutionContext(bool enableContextualizedErrors) : ExecutionContext(Service) |
| 260 | { |
| 261 | WI_SetFlagIf(m_outError.Flags, LxssExecutionContextFlagsEnableContextualizedErrors, enableContextualizedErrors); |
| 262 | WI_SetFlagIf(m_outError.Flags, LxssExecutionContextFlagsEnableUserWarnings, RootContext().CanCollectUserWarnings()); |
| 263 | } |
| 264 | |
| 265 | void ClientExecutionContext::CollectErrorImpl(HRESULT result) |
| 266 | { |
| 267 | const auto errorContext = CurrentContext() | m_outError.Context; |
| 268 | std::optional<std::wstring> message; |
| 269 | if (m_outError.Message != nullptr) |
| 270 | { |
| 271 | WI_ASSERT(WI_IsFlagSet(m_outError.Flags, LxssExecutionContextFlagsEnableContextualizedErrors)); |
| 272 | message = std::wstring(m_outError.Message); |
| 273 | } |
| 274 | |
| 275 | RootContext().CollectErrorImpl(result, errorContext, std::move(message), {}); |
| 276 | } |
| 277 | |
| 278 | void ClientExecutionContext::FlushWarnings() |
| 279 | { |
| 280 | if (m_interactiveWarningsThread.joinable()) |
| 281 | { |
| 282 | m_warningsPipeWrite.reset(); |
| 283 | m_interactiveWarningsThread.join(); |
| 284 | } |
| 285 | |
| 286 | if (m_outError.Warnings && CollectUserWarning(m_outError.Warnings)) |
| 287 | { |
| 288 | CoTaskMemFree(m_outError.Warnings); |
| 289 | m_outError.Warnings = nullptr; |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | void ClientExecutionContext::EnableInteractiveWarnings() |
| 294 | { |
| 295 | WI_ASSERT(!m_interactiveWarningsThread.joinable()); |
| 296 | |
| 297 | wil::unique_handle read; |
| 298 | THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&read, &m_warningsPipeWrite, nullptr, 0)); |
| 299 | |
| 300 | m_outError.WarningsPipe = HandleToULong(m_warningsPipeWrite.get()); |
| 301 | |
| 302 | m_interactiveWarningsThread = std::thread([read = std::move(read)]() { |
| 303 | try |
| 304 | { |
| 305 | wchar_t buffer[1024] = {0}; |
| 306 | |
| 307 | DWORD bytesRead{}; |
| 308 | while (ReadFile(read.get(), buffer, sizeof(buffer) - sizeof(wchar_t), &bytesRead, nullptr) && bytesRead > 0) |
| 309 | { |
| 310 | const auto endIndex = bytesRead / sizeof(wchar_t); |
| 311 | buffer[endIndex] = UNICODE_NULL; |
| 312 | |
| 313 | fwprintf(stderr, L"%ls", buffer); |
| 314 | } |
| 315 | } |
| 316 | CATCH_LOG(); |
| 317 | }); |
| 318 | } |
| 319 | |
| 320 | ServiceExecutionContext::ServiceExecutionContext(LXSS_ERROR_INFO* outError) noexcept : ExecutionContext(Empty) |
| 321 | { |
| 322 | if (outError != nullptr) |
| 323 | { |
| 324 | if (WI_IsFlagSet(outError->Flags, LxssExecutionContextFlagsEnableContextualizedErrors)) |
| 325 | { |
| 326 | m_outError = outError; |
| 327 | |
| 328 | if (WI_IsFlagSet(outError->Flags, LxssExecutionContextFlagsEnableUserWarnings)) |
| 329 | { |
| 330 | if (outError->WarningsPipe != 0) |
| 331 | { |
| 332 | m_warningsPipe.reset(wslutil::DuplicateHandleFromCallingProcess(ULongToHandle(outError->WarningsPipe))); |
| 333 | } |
| 334 | |
| 335 | if (!m_warningsPipe) |
| 336 | { |
| 337 | m_warningsString.emplace(); |
| 338 | } |
| 339 | } |
| 340 | } |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | bool ServiceExecutionContext::CanCollectUserErrorMessage() |
| 345 | { |
| 346 | return m_outError.has_value(); |
| 347 | } |
| 348 | |
| 349 | bool ServiceExecutionContext::CollectUserWarning(const std::wstring& warning) |
| 350 | { |
| 351 | if (m_warningsPipe) |
| 352 | { |
| 353 | LOG_IF_WIN32_BOOL_FALSE(WriteFile( |
| 354 | m_warningsPipe.get(), warning.c_str(), gsl::narrow_cast<DWORD>(warning.size() * sizeof(wchar_t)), nullptr, nullptr)); |
| 355 | |
| 356 | return true; |
| 357 | } |
| 358 | |
| 359 | if (m_warningsString.has_value()) |
| 360 | { |
| 361 | *m_warningsString += warning; |
| 362 | return true; |
| 363 | } |
| 364 | else |
| 365 | { |
| 366 | return false; |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | ServiceExecutionContext::~ServiceExecutionContext() |
| 371 | { |
| 372 | if (m_outError.has_value()) |
| 373 | { |
| 374 | if (m_error.has_value()) |
| 375 | { |
| 376 | m_outError.value()->Context = m_error->Context; |
| 377 | |
| 378 | if (m_error->Message.has_value()) |
| 379 | { |
| 380 | m_outError.value()->Message = wil::make_unique_string<wil::unique_cotaskmem_string>(m_error->Message->c_str()).release(); |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | if (m_warningsString.has_value()) |
| 385 | { |
| 386 | m_outError.value()->Warnings = wil::make_unique_string<wil::unique_cotaskmem_string>(m_warningsString->c_str()).release(); |
| 387 | } |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | COMServiceExecutionContext::COMServiceExecutionContext() : ExecutionContext(Empty) |
| 392 | { |
| 393 | } |
| 394 | |
| 395 | COMServiceExecutionContext::~COMServiceExecutionContext() |
| 396 | try |
| 397 | { |
| 398 | if (m_error.has_value()) |
| 399 | { |
| 400 | wil::com_ptr<ICreateErrorInfo> errorInfo; |
| 401 | THROW_IF_FAILED(CreateErrorInfo(&errorInfo)); |
| 402 | |
| 403 | if (m_error->Message.has_value()) |
| 404 | { |
| 405 | auto description = wil::make_bstr(m_error->Message->c_str()); |
| 406 | THROW_IF_FAILED(errorInfo->SetDescription(description.get())); |
| 407 | } |
| 408 | |
| 409 | if (m_error->Source.has_value()) |
| 410 | { |
| 411 | THROW_IF_FAILED(errorInfo->SetSource(wil::make_bstr(m_error->Source->c_str()).get())); |
| 412 | } |
| 413 | |
| 414 | if (m_error->Source.has_value() || m_error->Message.has_value()) |
| 415 | { |
| 416 | THROW_IF_FAILED(SetErrorInfo(0, errorInfo.query<IErrorInfo>().get())); |
| 417 | } |
| 418 | } |
| 419 | } |
| 420 | CATCH_LOG(); // Catch to avoid throwing from a destructor |
| 421 | |
| 422 | bool COMServiceExecutionContext::CanCollectUserErrorMessage() |
| 423 | { |
| 424 | return true; |
| 425 | } |
| 426 | |
| 427 | LXSS_ERROR_INFO* ClientExecutionContext::OutError() noexcept |
| 428 | { |
| 429 | return &m_outError; |
| 430 | } |
| 431 | |
| 432 | void wsl::windows::common::SetErrorMessage(std::string&& message, const std::source_location& source) |
| 433 | { |
| 434 | return SetErrorMessage(wsl::shared::string::MultiByteToWide(message), source); |
| 435 | } |
| 436 | |
| 437 | void wsl::windows::common::SetErrorMessage(std::wstring&& message, const std::source_location& source) |
| 438 | { |
| 439 | if (g_currentContext == nullptr || message.empty()) |
| 440 | { |
| 441 | return; // no context to save the error to or empty message, ignore |
| 442 | } |
| 443 | |
| 444 | g_currentContext->SetErrorStringImpl(std::move(message), std::format(L"{}", source)); |
| 445 | } |
| 446 | |
| 447 | void wsl::windows::common::SetEventLog(HANDLE eventLog) |
| 448 | { |
| 449 | WI_ASSERT(!g_eventLog); |
| 450 | |
| 451 | g_eventLog = eventLog; |
| 452 | } |