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
37namespace 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