Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

437 lines
11 KiB

  1. //=======================================================================
  2. //
  3. // Copyright (c) 1998-2001 Microsoft Corporation. All Rights Reserved.
  4. //
  5. // File: FreeLog.cpp
  6. //
  7. // Owner: KenSh
  8. //
  9. // Description:
  10. //
  11. // Runtime logging for use in both checked and free builds.
  12. //
  13. //=======================================================================
  14. #include <windows.h>
  15. #include <tchar.h>
  16. #include <stdio.h>
  17. #include <malloc.h>
  18. #include "FreeLog.h"
  19. #include <MISTSAFE.h>
  20. #ifndef _countof
  21. #define _countof(ar) (sizeof(ar)/sizeof((ar)[0]))
  22. #endif
  23. // Unicode files start with the 2 bytes { FF FE }.
  24. // This is the little-endian version of those 2 bytes.
  25. #define UNICODE_FILE_HEADER 0xFEFF
  26. #define MUTEX_TIMEOUT 1000 // Don't wait more than 1 second to write to logfile
  27. #define MAX_MUTEX_WAITS 4 // Don't keep trying after this many failures
  28. #define LOG_FILE_BIG_SIZE 50000 // Don't bother trimming if file is smaller than this
  29. #define LOG_LINES_TRIM_FROM 1000 // Start trimming if more than this many lines
  30. #define LOG_LINES_TRIM_TO 750 // Trim til the log file is this many lines
  31. #define LOG_LEVEL_SUCCESS 0
  32. #define LOG_LEVEL_FAILURE 1
  33. #define MAX_MSG_LENGTH (MAX_PATH + 20)
  34. #define MAX_ERROR_LENGTH 128
  35. static const TCHAR c_szUnknownModuleName[] = _T("?");
  36. // Local functions
  37. void LogMessageExV(UINT nLevel, DWORD dwError, LPCSTR pszFormatA, va_list args);
  38. //============================================================================
  39. //
  40. // Private CFreeLogging class to keep track of log file resources
  41. //
  42. //============================================================================
  43. class CFreeLogging
  44. {
  45. public:
  46. CFreeLogging(LPCTSTR pszModuleName, LPCTSTR pszLogFileName);
  47. ~CFreeLogging();
  48. void WriteLine(LPCTSTR pszText, UINT nLevel, DWORD dwError);
  49. private:
  50. inline static HANDLE CreateMutex(LPCTSTR pszMutexName);
  51. inline HANDLE OpenLogFile(LPCTSTR pszFileName);
  52. inline void CloseLogFile();
  53. void TrimLogFile();
  54. BOOL AcquireMutex();
  55. void ReleaseMutex();
  56. private:
  57. HANDLE m_hFile;
  58. HANDLE m_hMutex;
  59. int m_cLinesWritten;
  60. int m_cFailedWaits;
  61. LPTSTR m_pszModuleName;
  62. };
  63. CFreeLogging* g_pFreeLogging;
  64. //============================================================================
  65. //
  66. // Public functions
  67. //
  68. //============================================================================
  69. void InitFreeLogging(LPCTSTR pszModuleName, LPCTSTR pszLogFileName)
  70. {
  71. if (g_pFreeLogging == NULL)
  72. {
  73. g_pFreeLogging = new CFreeLogging(pszModuleName, pszLogFileName);
  74. }
  75. }
  76. void TermFreeLogging()
  77. {
  78. delete g_pFreeLogging;
  79. g_pFreeLogging = NULL;
  80. }
  81. void LogMessage(LPCSTR pszFormatA, ...)
  82. {
  83. va_list arglist;
  84. va_start(arglist, pszFormatA);
  85. LogMessageExV(LOG_LEVEL_SUCCESS, 0, pszFormatA, arglist);
  86. va_end(arglist);
  87. }
  88. void LogError(DWORD dwError, LPCSTR pszFormatA, ...)
  89. {
  90. va_list arglist;
  91. va_start(arglist, pszFormatA);
  92. LogMessageExV(LOG_LEVEL_FAILURE, dwError, pszFormatA, arglist);
  93. va_end(arglist);
  94. }
  95. void LogMessageExV(UINT nLevel, DWORD dwError, LPCSTR pszFormatA, va_list args)
  96. {
  97. if (g_pFreeLogging != NULL)
  98. {
  99. char szBufA[MAX_MSG_LENGTH];
  100. size_t nRem=0;
  101. StringCchVPrintfExA(szBufA, _countof(szBufA), NULL, &nRem, MISTSAFE_STRING_FLAGS, pszFormatA, args);
  102. int cchA = _countof(szBufA) - nRem;
  103. #ifdef UNICODE
  104. WCHAR szBufW[MAX_MSG_LENGTH];
  105. MultiByteToWideChar(CP_ACP, 0, szBufA, cchA+1, szBufW, _countof(szBufW));
  106. g_pFreeLogging->WriteLine(szBufW, nLevel, dwError);
  107. #else
  108. g_pFreeLogging->WriteLine(szBufA, nLevel, dwError);
  109. #endif
  110. }
  111. }
  112. //============================================================================
  113. //
  114. // CFreeLogging implementation
  115. //
  116. //============================================================================
  117. CFreeLogging::CFreeLogging(LPCTSTR pszModuleName, LPCTSTR pszLogFileName)
  118. : m_cFailedWaits(0),
  119. m_cLinesWritten(0)
  120. {
  121. m_pszModuleName = _tcsdup(pszModuleName);
  122. if (m_pszModuleName == NULL)
  123. m_pszModuleName = (LPTSTR)c_szUnknownModuleName;
  124. m_hMutex = CreateMutex(pszLogFileName);
  125. m_hFile = OpenLogFile(pszLogFileName);
  126. }
  127. CFreeLogging::~CFreeLogging()
  128. {
  129. CloseLogFile();
  130. if (m_hMutex != NULL)
  131. CloseHandle(m_hMutex);
  132. if (m_pszModuleName != c_szUnknownModuleName)
  133. free(m_pszModuleName);
  134. }
  135. inline HANDLE CFreeLogging::CreateMutex(LPCTSTR pszMutexName)
  136. {
  137. // Create a mutex in the global namespace (works across TS sessions)
  138. HANDLE hMutex = ::CreateMutex(NULL, FALSE, pszMutexName);
  139. return hMutex;
  140. }
  141. inline HANDLE CFreeLogging::OpenLogFile(LPCTSTR pszLogFileName)
  142. {
  143. HANDLE hFile = INVALID_HANDLE_VALUE;
  144. TCHAR szPath[MAX_PATH+1];
  145. int cch = GetWindowsDirectory(szPath, _countof(szPath)-1);
  146. if(cch >0)
  147. {
  148. if (szPath[cch-1] != _T('\\'))
  149. szPath[cch++] = _T('\\');
  150. HRESULT hr = StringCchCopyEx(szPath + cch, _countof(szPath)-cch, pszLogFileName, NULL, NULL, MISTSAFE_STRING_FLAGS);
  151. if(FAILED(hr))
  152. return hFile;
  153. hFile = CreateFile(szPath, GENERIC_READ | GENERIC_WRITE,
  154. FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
  155. OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  156. }
  157. #ifdef UNICODE
  158. if (hFile != INVALID_HANDLE_VALUE)
  159. {
  160. if (AcquireMutex())
  161. {
  162. //
  163. // Check for the unicode header { FF FE }
  164. //
  165. WORD wHeader = 0;
  166. DWORD cbRead;
  167. (void)ReadFile(hFile, &wHeader, sizeof(wHeader), &cbRead, NULL);
  168. //
  169. // Write the header if there isn't one. This may be due to the
  170. // file being newly created, or to an ANSI-formatted file.
  171. //
  172. if (wHeader != UNICODE_FILE_HEADER)
  173. {
  174. SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
  175. DWORD cbWritten;
  176. wHeader = UNICODE_FILE_HEADER;
  177. WriteFile(hFile, &wHeader, sizeof(wHeader), &cbWritten, NULL);
  178. SetEndOfFile(hFile);
  179. }
  180. ReleaseMutex();
  181. }
  182. }
  183. #endif
  184. return hFile;
  185. }
  186. inline void CFreeLogging::CloseLogFile()
  187. {
  188. if (m_hFile != INVALID_HANDLE_VALUE)
  189. {
  190. // Trim old stuff from the log before closing the file
  191. TrimLogFile();
  192. CloseHandle(m_hFile);
  193. m_hFile = INVALID_HANDLE_VALUE;
  194. }
  195. }
  196. BOOL CFreeLogging::AcquireMutex()
  197. {
  198. // In rare case where mutex not created, we allow file operations
  199. // with no synchronization
  200. if (m_hMutex == NULL)
  201. return TRUE;
  202. // Don't keep waiting if we've been blocked in the past
  203. if (m_cFailedWaits >= MAX_MUTEX_WAITS)
  204. return FALSE;
  205. BOOL fResult = TRUE;
  206. if (WaitForSingleObject(m_hMutex, MUTEX_TIMEOUT) != WAIT_OBJECT_0)
  207. {
  208. fResult = FALSE;
  209. m_cFailedWaits++;
  210. }
  211. return fResult;
  212. }
  213. void CFreeLogging::ReleaseMutex()
  214. {
  215. if (m_hMutex != NULL) // Note: AcquireMutex succeeds even if m_hMutex is NULL
  216. {
  217. ::ReleaseMutex(m_hMutex);
  218. }
  219. }
  220. void CFreeLogging::WriteLine(LPCTSTR pszText, UINT nLevel, DWORD dwError)
  221. {
  222. if (m_hFile != INVALID_HANDLE_VALUE)
  223. {
  224. DWORD cbText = lstrlen(pszText) * sizeof(TCHAR);
  225. if (AcquireMutex())
  226. {
  227. DWORD cbWritten;
  228. SetFilePointer(m_hFile, 0, NULL, FILE_END);
  229. //
  230. // Write time/date/module as a prefix
  231. //
  232. // 2001-05-03 13:49:01 21:49:01 CDM Failed Loading module (Error 0x00000005: Access is denied.)
  233. //
  234. // NOTE: ISO 8601 format for date/time. Local time first, then GMT.
  235. //
  236. TCHAR szPrefix[60];
  237. SYSTEMTIME sysTime, gmtTime;
  238. GetLocalTime(&sysTime);
  239. GetSystemTime(&gmtTime);
  240. LPCTSTR pszStatus = (nLevel == LOG_LEVEL_SUCCESS) ? _T("Success") : _T("Error ");
  241. StringCchPrintfEx(szPrefix, _countof(szPrefix), NULL, NULL, MISTSAFE_STRING_FLAGS,
  242. _T("%04d-%02d-%02d %02d:%02d:%02d %02d:%02d:%02d %s %-13s "),
  243. sysTime.wYear, sysTime.wMonth, sysTime.wDay,
  244. sysTime.wHour, sysTime.wMinute, sysTime.wSecond,
  245. gmtTime.wHour, gmtTime.wMinute, gmtTime.wSecond,
  246. pszStatus, m_pszModuleName);
  247. WriteFile(m_hFile, szPrefix, lstrlen(szPrefix) * sizeof(TCHAR), &cbWritten, NULL);
  248. //
  249. // Write the message followed by error info (if any) and a newline
  250. //
  251. WriteFile(m_hFile, pszText, cbText, &cbWritten, NULL);
  252. if (nLevel != LOG_LEVEL_SUCCESS)
  253. {
  254. TCHAR szError[MAX_ERROR_LENGTH];
  255. HRESULT hr=S_OK;
  256. size_t nRem=0;
  257. // nRem contains the remaining characters in the buffer including the null terminator
  258. // To get the number of characters written in to the buffer we use
  259. // int cchErrorPrefix = _countof(szError) - nRem;
  260. StringCchPrintfEx(szError, _countof(szError), NULL, &nRem, MISTSAFE_STRING_FLAGS, _T(" (Error 0x%08X: "), dwError);
  261. // Get the number of characters written in to the buffer
  262. int cchErrorPrefix = _countof(szError) - nRem;
  263. int cchErrorText = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, 0,
  264. szError + cchErrorPrefix, _countof(szError) - cchErrorPrefix - 1, NULL);
  265. int cchError = cchErrorPrefix + cchErrorText;
  266. cchError -= 2; // backup past ": " or "\r\n"
  267. StringCchCopyEx(szError + cchError, _countof(szError)-cchError, _T(")"), NULL, NULL, MISTSAFE_STRING_FLAGS);
  268. WriteFile(m_hFile, szError, (cchError + 1) * sizeof(TCHAR), &cbWritten, NULL);
  269. }
  270. WriteFile(m_hFile, _T("\r\n"), 2 * sizeof(TCHAR), &cbWritten, NULL);
  271. //
  272. // If we've written a ton of stuff, trim now rather than waiting
  273. // for the module to unload. (This check is only for how much this
  274. // module has written, not how big the log file itself is.)
  275. //
  276. if (++m_cLinesWritten > LOG_LINES_TRIM_FROM)
  277. {
  278. TrimLogFile();
  279. m_cLinesWritten = LOG_LINES_TRIM_TO;
  280. }
  281. ReleaseMutex();
  282. }
  283. }
  284. }
  285. // Checks the size of the log file, and trims it if necessary.
  286. void CFreeLogging::TrimLogFile()
  287. {
  288. if (AcquireMutex())
  289. {
  290. DWORD cbFile = GetFileSize(m_hFile, NULL);
  291. if (cbFile > LOG_FILE_BIG_SIZE)
  292. {
  293. DWORD cbFileNew = cbFile;
  294. //
  295. // Create a memory-mapped file so we can use memmove
  296. //
  297. HANDLE hMapping = CreateFileMapping(m_hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
  298. if (hMapping != NULL)
  299. {
  300. LPTSTR pszFileStart = (LPTSTR)MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, 0);
  301. if (pszFileStart != NULL)
  302. {
  303. LPTSTR pszEnd = (LPTSTR)((LPBYTE)pszFileStart + cbFile);
  304. LPTSTR pszTextStart = pszFileStart;
  305. #ifdef UNICODE
  306. pszTextStart++; // skip the 2-byte header
  307. #endif
  308. //
  309. // Count newlines
  310. //
  311. int cLines = 0;
  312. for (LPTSTR pch = pszTextStart; pch < pszEnd; )
  313. {
  314. if (*pch == _T('\n'))
  315. cLines++;
  316. // REVIEW: in Ansi builds should we call CharNextExA?
  317. // If so, what code page is the log file in?
  318. pch++;
  319. }
  320. if (cLines > LOG_LINES_TRIM_FROM)
  321. {
  322. int cTrimLines = cLines - LOG_LINES_TRIM_TO;
  323. for (pch = pszTextStart; pch < pszEnd; )
  324. {
  325. if (*pch == _T('\n'))
  326. cTrimLines--;
  327. // REVIEW: in Ansi builds should we call CharNextExA?
  328. // If so, what code page is the log file in?
  329. pch++;
  330. if (cTrimLines <= 0)
  331. break;
  332. }
  333. // Move more recent data to beginning of file
  334. int cchMove = (int)(pszEnd - pch);
  335. memmove(pszTextStart, pch, cchMove * sizeof(TCHAR));
  336. cbFileNew = (cchMove * sizeof(TCHAR));
  337. #ifdef UNICODE
  338. cbFileNew += sizeof(WORD);
  339. #endif
  340. }
  341. UnmapViewOfFile(pszFileStart);
  342. }
  343. CloseHandle(hMapping);
  344. if (cbFileNew != cbFile)
  345. {
  346. // Truncate the file, now that we've moved data as needed
  347. SetFilePointer(m_hFile, cbFileNew, NULL, FILE_BEGIN);
  348. SetEndOfFile(m_hFile);
  349. }
  350. }
  351. }
  352. ReleaseMutex();
  353. }
  354. }