master
cpp 452 lines 12.2 KB
Raw
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 }