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.

574 lines
12 KiB

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright (c) 1998, Microsoft Corp. All rights reserved.
  4. //
  5. // FILE
  6. //
  7. // logfile.cpp
  8. //
  9. // SYNOPSIS
  10. //
  11. // Defines the class LogFile.
  12. //
  13. // MODIFICATION HISTORY
  14. //
  15. // 08/04/1998 Original version.
  16. // 09/09/1998 Fix missing backslash in updateSequence.
  17. // 09/22/1998 Check to see if directory has actually changed.
  18. // 03/23/1999 Retry if write with cached handle fails.
  19. // 03/26/1999 Added setEnabled.
  20. // 07/21/1999 Don't increment sequence number when opening file.
  21. //
  22. ///////////////////////////////////////////////////////////////////////////////
  23. #include <ias.h>
  24. #include <sdoias.h>
  25. #include <new>
  26. #include <logfile.h>
  27. ///////////////////////////////////////////////////////////////////////////////
  28. //
  29. // CLASS
  30. //
  31. // FileHandle
  32. //
  33. // DESCRIPTION
  34. //
  35. // Lightweight wrapper around a reference counted file handle.
  36. //
  37. ///////////////////////////////////////////////////////////////////////////////
  38. class FileHandle : NonCopyable
  39. {
  40. public:
  41. FileHandle(HANDLE h) throw ()
  42. : refCount(0), hFile(h)
  43. { }
  44. ~FileHandle() throw ()
  45. {
  46. CloseHandle(hFile);
  47. hFile = INVALID_HANDLE_VALUE;
  48. }
  49. void addRef() throw ()
  50. {
  51. InterlockedIncrement(&refCount);
  52. }
  53. void release() throw ()
  54. {
  55. if (!InterlockedDecrement(&refCount)) { delete this; }
  56. }
  57. BOOL write(const BYTE* buf, DWORD buflen) const throw ()
  58. {
  59. DWORD dwNumWritten;
  60. return WriteFile(hFile, buf, buflen, &dwNumWritten, NULL);
  61. }
  62. protected:
  63. long refCount;
  64. HANDLE hFile;
  65. };
  66. //////////
  67. // Determine the first day of the week for the current locale.
  68. //////////
  69. DWORD
  70. WINAPI
  71. GetFirstDayOfWeek() throw ()
  72. {
  73. WCHAR buffer[4];
  74. if (GetLocaleInfo(
  75. LOCALE_SYSTEM_DEFAULT,
  76. LOCALE_IFIRSTDAYOFWEEK,
  77. buffer,
  78. sizeof(buffer)/sizeof(WCHAR)
  79. ))
  80. {
  81. // The locale info calls Monday day zero, while SYSTEMTIME calls
  82. // Sunday day zero.
  83. return (1 + (DWORD)_wtoi(buffer)) % 7;
  84. }
  85. return 0;
  86. }
  87. //////////
  88. // Cached value for LOCALE_IFIRSTDAYOFWEEK.
  89. //////////
  90. const DWORD FIRST_DAY_OF_WEEK(GetFirstDayOfWeek());
  91. //////////
  92. // Determine the week of the month (numbered 1 to 5) for a given SYSTEMTIME.
  93. //////////
  94. DWORD
  95. WINAPI
  96. GetWeekOfMonth(
  97. const SYSTEMTIME* st
  98. ) throw ()
  99. {
  100. DWORD dom = st->wDay - 1;
  101. DWORD wom = 1 + dom / 7;
  102. if ((dom % 7) > (st->wDayOfWeek + 7 - FIRST_DAY_OF_WEEK) % 7)
  103. {
  104. ++wom;
  105. }
  106. return wom;
  107. }
  108. //////////
  109. // Determines the last sequence number used for a given filename format.
  110. //////////
  111. DWORD
  112. WINAPI
  113. GetLastSequence(
  114. PCWSTR szPath
  115. ) throw ()
  116. {
  117. // Make sure the path string is valid.
  118. if (szPath == NULL || wcslen(szPath) > MAX_PATH) { return 0; }
  119. // Does the sequence format specifier exist?
  120. PWCHAR specifier = wcsstr(szPath, L"%u");
  121. if (specifier == NULL) { return 0; }
  122. // Strip off just the filename portion.
  123. WCHAR format[MAX_PATH + 1];
  124. PWCHAR delim = wcsrchr(szPath, L'\\');
  125. wcscpy(format, (delim ? delim + 1 : szPath));
  126. // Replace the format specifier with a splat.
  127. WCHAR filename[MAX_PATH + 1];
  128. size_t prefixLength = specifier - szPath;
  129. memcpy(filename, szPath, prefixLength * sizeof(WCHAR));
  130. *(filename + prefixLength) = L'*';
  131. wcscpy(filename + prefixLength + 1, specifier + 2);
  132. // Find all files that match the pattern.
  133. WIN32_FIND_DATAW findData;
  134. HANDLE hFind = FindFirstFileW(filename, &findData);
  135. if (hFind == INVALID_HANDLE_VALUE) { return 0; }
  136. unsigned lastSequence = 0;
  137. do
  138. {
  139. // Read the sequence number out of the filename and see if it's the
  140. // highest we've seen.
  141. unsigned sequence;
  142. if (swscanf(findData.cFileName, format, &sequence) == 1 &&
  143. sequence > lastSequence)
  144. {
  145. lastSequence = sequence;
  146. }
  147. } while (FindNextFileW(hFind, &findData));
  148. FindClose(hFind);
  149. return (DWORD)lastSequence;
  150. }
  151. LogFile::LogFile() throw ()
  152. : enabled(FALSE),
  153. period(IAS_LOGGING_UNLIMITED_SIZE),
  154. seqNum(0),
  155. file(NULL)
  156. {
  157. dirPath[0] = L'\0';
  158. maxSize.QuadPart = (DWORDLONG)-1;
  159. }
  160. LogFile::~LogFile() throw ()
  161. {
  162. close();
  163. }
  164. void LogFile::setDirectory(PCWSTR szDirectory) throw ()
  165. {
  166. _ASSERT(szDirectory != NULL);
  167. _ASSERT(wcslen(szDirectory) < MAX_PATH - 12);
  168. WCHAR tmp[MAX_PATH];
  169. // Find the end of the string.
  170. size_t len = wcslen(szDirectory);
  171. // Does it end in a backlash ?
  172. if (len != 0 && szDirectory[len - 1] == L'\\')
  173. {
  174. // Make a copy since szDirectory is const.
  175. szDirectory = wcscpy(tmp, szDirectory);
  176. // Null out the backslash.
  177. tmp[len - 1] = L'\0';
  178. }
  179. Lock();
  180. // Has the directory changed ?
  181. if (wcscmp(szDirectory, dirPath) != 0)
  182. {
  183. // Close the old file.
  184. close();
  185. // Copy in the new path.
  186. wcscpy(dirPath, szDirectory);
  187. // Rescan the sequence number.
  188. updateSequence();
  189. }
  190. Unlock();
  191. }
  192. void LogFile::setEnabled(BOOL newVal) throw ()
  193. {
  194. Lock();
  195. if (!(enabled = newVal)) { close(); }
  196. Unlock();
  197. }
  198. void LogFile::setMaxSize(DWORDLONG newVal) throw ()
  199. {
  200. Lock();
  201. maxSize.QuadPart = newVal;
  202. Unlock();
  203. }
  204. void LogFile::setPeriod(LONG newVal) throw ()
  205. {
  206. Lock();
  207. if (period != newVal)
  208. {
  209. // New period == new filename.
  210. close();
  211. period = newVal;
  212. updateSequence();
  213. }
  214. Unlock();
  215. }
  216. void LogFile::close() throw ()
  217. {
  218. if (file)
  219. {
  220. file->release();
  221. file = NULL;
  222. }
  223. }
  224. BOOL LogFile::write(
  225. const SYSTEMTIME* st,
  226. const BYTE* buf,
  227. DWORD buflen,
  228. BOOL allowRetry
  229. ) throw ()
  230. {
  231. FileHandle *cached, *fh;
  232. BOOL retval;
  233. Lock();
  234. //////////
  235. // Quick exit if the logfile is disabled.
  236. //////////
  237. if (!enabled)
  238. {
  239. Unlock();
  240. return TRUE;
  241. }
  242. //////////
  243. // Save the cache value on entry.
  244. //////////
  245. cached = file;
  246. //////////
  247. // Do we have a valid handle?
  248. //////////
  249. if (file == NULL && !openFile(st)) { goto failure; }
  250. //////////
  251. // Have we reached the next period?
  252. //////////
  253. switch (period)
  254. {
  255. case IAS_LOGGING_UNLIMITED_SIZE:
  256. break;
  257. case IAS_LOGGING_DAILY:
  258. {
  259. if (st->wDay != whenOpened.wDay ||
  260. st->wMonth != whenOpened.wMonth ||
  261. st->wYear != whenOpened.wYear)
  262. {
  263. if (!openFile(st)) { goto failure; }
  264. }
  265. break;
  266. }
  267. case IAS_LOGGING_WEEKLY:
  268. {
  269. if (GetWeekOfMonth(st) != weekOpened ||
  270. st->wMonth != whenOpened.wMonth ||
  271. st->wYear != whenOpened.wYear)
  272. {
  273. if (!openFile(st)) { goto failure; }
  274. }
  275. break;
  276. }
  277. case IAS_LOGGING_MONTHLY:
  278. {
  279. if (st->wMonth != whenOpened.wMonth ||
  280. st->wYear != whenOpened.wYear)
  281. {
  282. if (!openFile(st)) { goto failure; }
  283. }
  284. break;
  285. }
  286. case IAS_LOGGING_WHEN_FILE_SIZE_REACHES:
  287. {
  288. while (currentSize.QuadPart + buflen > maxSize.QuadPart)
  289. {
  290. ++seqNum;
  291. if (!openFile(st)) { goto failure; }
  292. }
  293. break;
  294. }
  295. }
  296. //////////
  297. // All is well so update state, save the handle for use, and release
  298. // the lock.
  299. //////////
  300. currentSize.QuadPart += buflen;
  301. (fh = file)->addRef();
  302. Unlock();
  303. //////////
  304. // Now with the lock released, we can do the actual write.
  305. //////////
  306. retval = fh->write(buf, buflen);
  307. if (!retval)
  308. {
  309. // Prevent others from using the bad handle.
  310. invalidateHandle(fh);
  311. // If we used a cached handle and allowRetry, then try again.
  312. if (cached == fh && allowRetry)
  313. {
  314. // Set allowRetry to FALSE to prevent an infinite recursion.
  315. retval = write(st, buf, buflen, FALSE);
  316. }
  317. }
  318. fh->release();
  319. return retval;
  320. failure:
  321. Unlock();
  322. return FALSE;
  323. }
  324. //////////
  325. // Determine the next filename to use. fname must point to a buffer of at
  326. // least MAX_PATH + 1 characters.
  327. //////////
  328. void LogFile::getFileName(const SYSTEMTIME* st, PWSTR fname) throw ()
  329. {
  330. // Start off with the directory.
  331. wcscpy(fname, dirPath);
  332. PWCHAR next = fname + wcslen(fname);
  333. // Add a backslash
  334. *next++ = L'\\';
  335. switch (period)
  336. {
  337. case IAS_LOGGING_UNLIMITED_SIZE:
  338. {
  339. wcscpy(next, L"iaslog.log");
  340. break;
  341. }
  342. case IAS_LOGGING_WHEN_FILE_SIZE_REACHES:
  343. {
  344. swprintf(next, L"iaslog%lu.log", seqNum);
  345. break;
  346. }
  347. case IAS_LOGGING_DAILY:
  348. {
  349. swprintf(next, L"IN%02hu%02hu%02hu.log", st->wYear % 100,
  350. st->wMonth,
  351. st->wDay);
  352. break;
  353. }
  354. case IAS_LOGGING_WEEKLY:
  355. {
  356. swprintf(next, L"IN%02hu%02hu%02hu.log", st->wYear % 100,
  357. st->wMonth,
  358. GetWeekOfMonth(st));
  359. break;
  360. }
  361. case IAS_LOGGING_MONTHLY:
  362. {
  363. swprintf(next, L"IN%02hu%02hu.log", st->wYear % 100, st->wMonth);
  364. break;
  365. }
  366. }
  367. }
  368. void LogFile::invalidateHandle(FileHandle* handle) throw ()
  369. {
  370. Lock();
  371. // If this is the same handle we have cached, then release it.
  372. if (file == handle)
  373. {
  374. file->release();
  375. file = NULL;
  376. }
  377. Unlock();
  378. }
  379. BOOL LogFile::openFile(const SYSTEMTIME* st) throw ()
  380. {
  381. /////////
  382. // Close the current logfile (if any).
  383. /////////
  384. close();
  385. /////////
  386. // Get the name for the new file.
  387. /////////
  388. WCHAR fileName[MAX_PATH];
  389. getFileName(st, fileName);
  390. //////////
  391. // Open the file if it exists or else create a new one.
  392. //////////
  393. HANDLE hFile = CreateFileW(
  394. fileName,
  395. GENERIC_WRITE,
  396. FILE_SHARE_READ,
  397. NULL,
  398. OPEN_ALWAYS,
  399. FILE_FLAG_SEQUENTIAL_SCAN,
  400. NULL
  401. );
  402. if (hFile == INVALID_HANDLE_VALUE)
  403. {
  404. // We can only handle 'path not found' errors.
  405. if (GetLastError() != ERROR_PATH_NOT_FOUND) { return FALSE; }
  406. // If the path is just a drive letter, there's nothing we can do.
  407. size_t len = wcslen(dirPath);
  408. if (len != 0 && dirPath[len - 1] == L':') { return FALSE; }
  409. // Otherwise, let's try to create the directory.
  410. if (!CreateDirectoryW(dirPath, NULL)) { return FALSE; }
  411. // Then try again to create the file.
  412. hFile = CreateFileW(
  413. fileName,
  414. GENERIC_WRITE,
  415. FILE_SHARE_READ,
  416. NULL,
  417. OPEN_ALWAYS,
  418. FILE_FLAG_SEQUENTIAL_SCAN,
  419. NULL
  420. );
  421. if (hFile == INVALID_HANDLE_VALUE) { return FALSE; }
  422. }
  423. //////////
  424. // Create a FileHandle object.
  425. //////////
  426. file = new (std::nothrow) FileHandle(hFile);
  427. if (!file)
  428. {
  429. CloseHandle(hFile);
  430. SetLastError(ERROR_NOT_ENOUGH_MEMORY);
  431. return FALSE;
  432. }
  433. file->addRef();
  434. //////////
  435. // Get the size of the file.
  436. //////////
  437. currentSize.LowPart = GetFileSize(hFile, &currentSize.HighPart);
  438. if (currentSize.LowPart == 0xFFFFFFFF && GetLastError() != NO_ERROR)
  439. {
  440. close();
  441. return FALSE;
  442. }
  443. //////////
  444. // Start writing new information at the end of the file.
  445. //////////
  446. SetFilePointer(hFile, 0, NULL, FILE_END);
  447. //////////
  448. // Save the time when this was opened.
  449. //////////
  450. whenOpened = *st;
  451. weekOpened = GetWeekOfMonth(st);
  452. return TRUE;
  453. }
  454. //////////
  455. // Scans the logfile directory to determine the last seqNum used.
  456. //////////
  457. void LogFile::updateSequence() throw ()
  458. {
  459. if (period != IAS_LOGGING_WHEN_FILE_SIZE_REACHES)
  460. {
  461. seqNum = 0;
  462. }
  463. else
  464. {
  465. WCHAR format[MAX_PATH + 1];
  466. wcscpy(format, dirPath);
  467. wcscat(format, L"\\iaslog%u.log");
  468. seqNum = GetLastSequence(format);
  469. }
  470. }