1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | // ============================================================ |
5 | // |
6 | // CDebugLog.cpp |
7 | // |
8 | |
9 | |
10 | // |
11 | // Implements the fusion-derived CDebugLog class |
12 | // |
13 | // ============================================================ |
14 | |
15 | #ifdef FEATURE_VERSIONING_LOG |
16 | |
17 | #include "cdebuglog.hpp" |
18 | #include "applicationcontext.hpp" |
19 | #include "assemblyname.hpp" |
20 | #include "variables.hpp" |
21 | #include "utils.hpp" |
22 | |
23 | #include "shlwapi.h" |
24 | #include "strsafe.h" |
25 | |
26 | #include "../dlls/mscorrc/fusres.h" |
27 | |
28 | #define MAX_DBG_STR_LEN 1024 |
29 | #define MAX_DATE_LEN 128 |
30 | |
31 | #define DEBUG_LOG_HTML_START L"<html><pre>\r\n" |
32 | #define DEBUG_LOG_HTML_META_LANGUAGE L"<meta http-equiv=\"Content-Type\" content=\"charset=unicode-1-1-utf-8\">" |
33 | #define DEBUG_LOG_MARK_OF_THE_WEB L"<!-- saved from url=(0015)assemblybinder: -->" |
34 | #define DEBUG_LOG_HTML_END L"\r\n</pre></html>" |
35 | #define DEBUG_LOG_NEW_LINE L"\r\n" |
36 | |
37 | namespace BINDER_SPACE |
38 | { |
39 | namespace |
40 | { |
41 | inline LPCWSTR LogCategoryToString(DWORD dwLogCategory) |
42 | { |
43 | switch (dwLogCategory) |
44 | { |
45 | case FUSION_BIND_LOG_CATEGORY_DEFAULT: |
46 | return L"default" ; |
47 | case FUSION_BIND_LOG_CATEGORY_NGEN: |
48 | return L"Native" ; |
49 | default: |
50 | return L"Unknown" ; |
51 | } |
52 | } |
53 | |
54 | HRESULT CreateFilePathHierarchy(LPCOLESTR pszName) |
55 | { |
56 | HRESULT hr=S_OK; |
57 | LPTSTR pszFileName; |
58 | PathString szPathString; |
59 | DWORD dw = 0; |
60 | |
61 | size_t pszNameLen = wcslen(pszName); |
62 | WCHAR * szPath = szPathString.OpenUnicodeBuffer(static_cast<COUNT_T>(pszNameLen)); |
63 | size_t cbSzPath = (sizeof(WCHAR)) * (pszNameLen + 1); // SString allocates extra byte for null |
64 | IF_FAIL_GO(StringCbCopy(szPath, cbSzPath, pszName)); |
65 | szPathString.CloseBuffer(static_cast<COUNT_T>(pszNameLen)); |
66 | |
67 | pszFileName = PathFindFileName(szPath); |
68 | |
69 | if (pszFileName <= szPath) |
70 | { |
71 | IF_FAIL_GO(E_INVALIDARG); |
72 | } |
73 | |
74 | *(pszFileName-1) = 0; |
75 | |
76 | dw = WszGetFileAttributes(szPath); |
77 | if (dw != INVALID_FILE_ATTRIBUTES) |
78 | { |
79 | return S_OK; |
80 | } |
81 | |
82 | hr = HRESULT_FROM_GetLastError(); |
83 | |
84 | switch (hr) |
85 | { |
86 | case __HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND): |
87 | { |
88 | hr = CreateFilePathHierarchy(szPath); |
89 | if (hr != S_OK) |
90 | return hr; |
91 | } |
92 | |
93 | case __HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND): |
94 | { |
95 | if (WszCreateDirectory(szPath, NULL)) |
96 | return S_OK; |
97 | else |
98 | { |
99 | hr = HRESULT_FROM_WIN32(GetLastError()); |
100 | if(hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) |
101 | hr = S_OK; |
102 | else |
103 | return hr; |
104 | } |
105 | } |
106 | |
107 | default: |
108 | break; |
109 | } |
110 | |
111 | Exit: |
112 | return hr; |
113 | } |
114 | |
115 | HRESULT WriteLog(HANDLE hLogFile, |
116 | LPCWSTR pwzInfo) |
117 | { |
118 | HRESULT hr = S_OK; |
119 | DWORD dwLen = 0; |
120 | DWORD dwWritten = 0; |
121 | CHAR szBuf[MAX_DBG_STR_LEN]; |
122 | |
123 | dwLen = WszWideCharToMultiByte(CP_UTF8, |
124 | 0, |
125 | pwzInfo, |
126 | -1, |
127 | szBuf, |
128 | MAX_DBG_STR_LEN, |
129 | NULL, |
130 | NULL); |
131 | |
132 | if (!dwLen) |
133 | { |
134 | IF_FAIL_GO(HRESULT_FROM_GetLastError()); |
135 | } |
136 | |
137 | // dwLen includes NULL terminator. We don't want to write this out. |
138 | if (dwLen > 1) { |
139 | dwLen--; |
140 | |
141 | if (!WriteFile(hLogFile, szBuf, dwLen, &dwWritten, NULL)) { |
142 | IF_FAIL_GO(HRESULT_FROM_GetLastError()); |
143 | } |
144 | } |
145 | |
146 | Exit: |
147 | return hr; |
148 | } |
149 | |
150 | HRESULT GetBindTimeInfo(PathString &info) |
151 | { |
152 | HRESULT hr = S_OK; |
153 | SYSTEMTIME systime; |
154 | |
155 | { |
156 | WCHAR wzDateBuffer[MAX_DATE_LEN]; |
157 | WCHAR wzTimeBuffer[MAX_DATE_LEN]; |
158 | |
159 | GetLocalTime(&systime); |
160 | |
161 | if (!WszGetDateFormat(LOCALE_USER_DEFAULT, |
162 | 0, |
163 | &systime, |
164 | NULL, |
165 | wzDateBuffer, |
166 | MAX_DATE_LEN)) |
167 | { |
168 | return HRESULT_FROM_GetLastError(); |
169 | } |
170 | |
171 | if (!WszGetTimeFormat(LOCALE_USER_DEFAULT, |
172 | 0, |
173 | &systime, |
174 | NULL, |
175 | wzTimeBuffer, |
176 | MAX_DATE_LEN)) |
177 | { |
178 | return HRESULT_FROM_GetLastError(); |
179 | } |
180 | |
181 | info.Printf(L"(%s @ %s)" , wzDateBuffer, wzTimeBuffer); |
182 | } |
183 | return hr; |
184 | } |
185 | |
186 | HRESULT GetHrResultInfo(PathString &info, HRESULT hrResult) |
187 | { |
188 | HRESULT hr = S_OK; |
189 | |
190 | // TODO: Get the result information in here. |
191 | info.Printf(L"%p." , hrResult); |
192 | |
193 | return hr; |
194 | } |
195 | |
196 | inline BOOL IsInvalidCharacter(WCHAR wcChar) |
197 | { |
198 | switch (wcChar) |
199 | { |
200 | case L':': |
201 | case L'/': |
202 | case L'\\': |
203 | case L'*': |
204 | case L'<': |
205 | case L'>': |
206 | case L'?': |
207 | case L'|': |
208 | case L'"': |
209 | return TRUE; |
210 | default: |
211 | return FALSE; |
212 | } |
213 | } |
214 | |
215 | inline void ReplaceInvalidFileCharacters(SString &assemblyName) |
216 | { |
217 | SString::Iterator pos = assemblyName.Begin(); |
218 | SString::Iterator end = assemblyName.End(); |
219 | |
220 | while (pos < end) |
221 | { |
222 | if (IsInvalidCharacter(pos[0])) |
223 | { |
224 | assemblyName.Replace(pos, L'_'); |
225 | } |
226 | |
227 | pos++; |
228 | } |
229 | } |
230 | }; |
231 | |
232 | CDebugLog::CDebugLog() |
233 | { |
234 | m_cRef = 1; |
235 | } |
236 | |
237 | CDebugLog::~CDebugLog() |
238 | { |
239 | // Nothing to do here |
240 | } |
241 | |
242 | /* static */ |
243 | HRESULT CDebugLog::Create(ApplicationContext *pApplicationContext, |
244 | AssemblyName *pAssemblyName, |
245 | SString &sCodeBase, |
246 | CDebugLog **ppCDebugLog) |
247 | { |
248 | HRESULT hr = S_OK; |
249 | BINDER_LOG_ENTER(L"CDebugLog::Create" ); |
250 | ReleaseHolder<CDebugLog> pDebugLog; |
251 | |
252 | // Validate input arguments |
253 | IF_FALSE_GO(pApplicationContext != NULL); |
254 | IF_FALSE_GO(ppCDebugLog != NULL); |
255 | |
256 | SAFE_NEW(pDebugLog, CDebugLog); |
257 | IF_FAIL_GO(pDebugLog->Init(pApplicationContext, pAssemblyName, sCodeBase)); |
258 | |
259 | *ppCDebugLog = pDebugLog.Extract(); |
260 | |
261 | Exit: |
262 | BINDER_LOG_LEAVE_HR(L"CDebugLog::Create" , hr); |
263 | return hr; |
264 | } |
265 | |
266 | ULONG CDebugLog::AddRef() |
267 | { |
268 | return InterlockedIncrement(&m_cRef); |
269 | } |
270 | |
271 | ULONG CDebugLog::Release() |
272 | { |
273 | ULONG ulRef; |
274 | |
275 | ulRef = InterlockedDecrement(&m_cRef); |
276 | |
277 | if (ulRef == 0) |
278 | { |
279 | delete this; |
280 | } |
281 | |
282 | return ulRef; |
283 | } |
284 | |
285 | HRESULT CDebugLog::SetResultCode(DWORD dwLogCategory, |
286 | HRESULT hrResult) |
287 | { |
288 | HRESULT hr = S_OK; |
289 | BINDER_LOG_ENTER(L"CDebugLog::SetResultCode" ); |
290 | |
291 | IF_FALSE_GO(dwLogCategory < FUSION_BIND_LOG_CATEGORY_MAX); |
292 | |
293 | m_HrResult[dwLogCategory] = hrResult; |
294 | |
295 | Exit: |
296 | BINDER_LOG_LEAVE_HR(L"CDebugLog::SetResultCode" , hr); |
297 | return hr; |
298 | } |
299 | |
300 | HRESULT CDebugLog::LogMessage(DWORD, |
301 | DWORD dwLogCategory, |
302 | SString &sDebugString) |
303 | { |
304 | HRESULT hr = S_OK; |
305 | BINDER_LOG_ENTER(L"CDebugLog::LogMessage" ); |
306 | |
307 | IF_FALSE_GO(dwLogCategory < FUSION_BIND_LOG_CATEGORY_MAX); |
308 | |
309 | m_content[dwLogCategory].AddTail(const_cast<const SString &>(sDebugString)); |
310 | |
311 | Exit: |
312 | BINDER_LOG_LEAVE_HR(L"CDebugLog::LogMessage" , hr); |
313 | return hr; |
314 | } |
315 | |
316 | HRESULT CDebugLog::Flush(DWORD, |
317 | DWORD dwLogCategory) |
318 | { |
319 | HRESULT hr = S_OK; |
320 | BINDER_LOG_ENTER(L"CDebugLog::Flush" ); |
321 | SmallStackSString sCategory(LogCategoryToString(dwLogCategory)); |
322 | PathString logFilePath; |
323 | ListNode<SString> *pListNode = NULL; |
324 | |
325 | IF_FALSE_GO(dwLogCategory < FUSION_BIND_LOG_CATEGORY_MAX); |
326 | |
327 | CombinePath(g_BinderVariables->logPath, sCategory, logFilePath); |
328 | CombinePath(logFilePath, m_applicationName, logFilePath); |
329 | CombinePath(logFilePath, m_logFileName, logFilePath); |
330 | |
331 | BINDER_LOG_STRING(L"logFilePath" , logFilePath); |
332 | |
333 | IF_FAIL_GO(CreateFilePathHierarchy(logFilePath.GetUnicode())); |
334 | |
335 | m_hLogFile = WszCreateFile(logFilePath.GetUnicode(), |
336 | GENERIC_READ | GENERIC_WRITE, |
337 | 0, |
338 | NULL, |
339 | CREATE_ALWAYS, |
340 | FILE_ATTRIBUTE_NORMAL, |
341 | NULL); |
342 | |
343 | if (m_hLogFile == INVALID_HANDLE_VALUE) |
344 | { |
345 | // Silently ignore unability to log. |
346 | BINDER_LOG(L"Unable to open binding log" ); |
347 | GO_WITH_HRESULT(S_OK); |
348 | } |
349 | |
350 | LogHeader(dwLogCategory); |
351 | |
352 | pListNode = static_cast<ListNode<SString> *>(m_content[dwLogCategory].GetHeadPosition()); |
353 | while (pListNode != NULL) |
354 | { |
355 | SString item = pListNode->GetItem(); |
356 | |
357 | IF_FAIL_GO(WriteLog(m_hLogFile, item.GetUnicode())); |
358 | IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_NEW_LINE)); |
359 | |
360 | pListNode = pListNode->GetNext(); |
361 | } |
362 | |
363 | LogFooter(dwLogCategory); |
364 | |
365 | // Ignore failure |
366 | CloseHandle(m_hLogFile.Extract()); |
367 | |
368 | Exit: |
369 | BINDER_LOG_LEAVE_HR(L"CDebugLog::Flush" , hr); |
370 | return hr; |
371 | } |
372 | |
373 | HRESULT CDebugLog::Init(ApplicationContext *pApplicationContext, |
374 | AssemblyName *pAssemblyName, |
375 | SString &sCodeBase) |
376 | { |
377 | HRESULT hr = S_OK; |
378 | BINDER_LOG_ENTER(L"CDebugLog::Init" ); |
379 | |
380 | m_applicationName.Set(pApplicationContext->GetApplicationName()); |
381 | ReplaceInvalidFileCharacters(m_applicationName); |
382 | |
383 | if (m_applicationName.IsEmpty()) |
384 | { |
385 | BINDER_LOG(L"empty application name" ); |
386 | m_applicationName.Set(L"unknown" ); |
387 | } |
388 | |
389 | if (pAssemblyName == NULL) |
390 | { |
391 | m_logFileName.Set(L"WhereRefBind!Host=(LocalMachine)!FileName=(" ); |
392 | |
393 | LPCWSTR pwzFileName = PathFindFileNameW(sCodeBase.GetUnicode()); |
394 | if (pwzFileName != NULL) |
395 | { |
396 | m_logFileName.Append(pwzFileName); |
397 | } |
398 | m_logFileName.Append(L").HTM" ); |
399 | } |
400 | else |
401 | { |
402 | PathString assemblyDisplayName; |
403 | |
404 | pAssemblyName->GetDisplayName(assemblyDisplayName, |
405 | AssemblyName::INCLUDE_VERSION | |
406 | AssemblyName::INCLUDE_ARCHITECTURE | |
407 | AssemblyName::INCLUDE_RETARGETABLE); |
408 | |
409 | ReplaceInvalidFileCharacters(assemblyDisplayName); |
410 | |
411 | m_logFileName.Set(assemblyDisplayName); |
412 | m_logFileName.Append(L".HTM" ); |
413 | } |
414 | |
415 | BINDER_LOG_LEAVE_HR(L"CDebugLog::Init" , hr); |
416 | return hr; |
417 | } |
418 | |
419 | HRESULT CDebugLog::LogHeader(DWORD dwLogCategory) |
420 | { |
421 | HRESULT hr = S_OK; |
422 | BINDER_LOG_ENTER(L"CDebugLog::LogHeader" ); |
423 | PathString info; |
424 | PathString temp; |
425 | PathString format; |
426 | |
427 | IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_HTML_META_LANGUAGE)); |
428 | IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_MARK_OF_THE_WEB)); |
429 | IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_HTML_START)); |
430 | |
431 | IF_FAIL_GO(GetBindTimeInfo(temp)); |
432 | IF_FAIL_GO(format.LoadResourceAndReturnHR(CCompRC::Debugging, ID_FUSLOG_BINDING_HEADER_BEGIN)); |
433 | info.Printf(format.GetUnicode(), temp.GetUnicode()); |
434 | IF_FAIL_GO(WriteLog(m_hLogFile, info.GetUnicode())); |
435 | IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_NEW_LINE DEBUG_LOG_NEW_LINE)); |
436 | |
437 | if (SUCCEEDED(m_HrResult[dwLogCategory])) |
438 | { |
439 | IF_FAIL_GO(temp. |
440 | LoadResourceAndReturnHR(CCompRC::Debugging, ID_FUSLOG_BINDING_HEADER_BIND_RESULT_SUCCESS)); |
441 | IF_FAIL_GO(WriteLog(m_hLogFile, temp.GetUnicode())); |
442 | } |
443 | else |
444 | { |
445 | IF_FAIL_GO(temp. |
446 | LoadResourceAndReturnHR(CCompRC::Debugging, ID_FUSLOG_BINDING_HEADER_BIND_RESULT_ERROR)); |
447 | IF_FAIL_GO(WriteLog(m_hLogFile, temp.GetUnicode())); |
448 | } |
449 | IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_NEW_LINE)); |
450 | |
451 | GetHrResultInfo(temp, m_HrResult[dwLogCategory]); |
452 | |
453 | IF_FAIL_GO(format.LoadResourceAndReturnHR(CCompRC::Debugging, ID_FUSLOG_BINDING_HEADER_BIND_RESULT)); |
454 | info.Printf(format.GetUnicode(), temp.GetUnicode()); |
455 | IF_FAIL_GO(WriteLog(m_hLogFile, info.GetUnicode())); |
456 | IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_NEW_LINE DEBUG_LOG_NEW_LINE)); |
457 | |
458 | // TODO: Assembly Manager info + Executable info. |
459 | |
460 | IF_FAIL_GO(info.LoadResourceAndReturnHR(CCompRC::Debugging, ID_FUSLOG_BINDING_HEADER_END)); |
461 | IF_FAIL_GO(WriteLog(m_hLogFile, info.GetUnicode())); |
462 | IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_NEW_LINE DEBUG_LOG_NEW_LINE)); |
463 | |
464 | Exit: |
465 | BINDER_LOG_LEAVE_HR(L"CDebugLog::LogHeader" , hr); |
466 | return hr; |
467 | } |
468 | |
469 | HRESULT CDebugLog::LogFooter(DWORD) |
470 | { |
471 | HRESULT hr = S_OK; |
472 | BINDER_LOG_ENTER(L"CDebugLog::LogFooter" ); |
473 | |
474 | IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_HTML_END)); |
475 | |
476 | Exit: |
477 | BINDER_LOG_LEAVE_HR(L"CDebugLog::LogFooter" , hr); |
478 | return hr; |
479 | } |
480 | }; |
481 | |
482 | #endif // FEATURE_VERSIONING_LOG |
483 | |