Leaked source code of windows server 2003
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.

1580 lines
49 KiB

  1. //+---------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1992 - 1996.
  5. //
  6. // File: log.cxx
  7. //
  8. // Contents: Logging routines for the job scheduler service.
  9. //
  10. // Classes: None.
  11. //
  12. // Functions: OpenLogFile
  13. // CloseLogFile
  14. // LogTaskStatus
  15. // LogTaskError
  16. // LogServiceStatus
  17. // LogServiceError
  18. // ConstructStatusField
  19. // ConstructResultField
  20. // GetSchedulerResultCodeString
  21. // OverwriteRecordFragment
  22. // IntegerToString
  23. //
  24. // History: 1-Feb-96 MarkBl Created.
  25. // 24-Oct-96 AnirudhS Modified to handle DBCS.
  26. // 2-Feb-98 jschwart Modified to handle Unicode.
  27. // 26-Feb-01 JBenton Prefix bug 294880
  28. //
  29. //----------------------------------------------------------------------------
  30. #include "..\pch\headers.hxx"
  31. #pragma hdrstop
  32. #include <sddl.h>
  33. #include "globals.hxx"
  34. #include "svc_core.hxx"
  35. #include "..\inc\resource.h"
  36. #include "..\inc\common.hxx"
  37. #define CCH_INT 11
  38. #define ARRAY_LEN(a) (sizeof(a)/sizeof(a[0]))
  39. TCHAR * ConstructStatusField(DWORD, SYSTEMTIME *, ULONG *);
  40. TCHAR * ConstructResultField(DWORD, LPTSTR);
  41. TCHAR * GetSchedulerResultCodeString(DWORD, DWORD);
  42. TCHAR * IntegerToString(ULONG, TCHAR *);
  43. VOID OverwriteRecordFragment(VOID);
  44. VOID WriteLog(LPTSTR);
  45. BOOL GetDateTime(const SYSTEMTIME *, LPTSTR, LPTSTR);
  46. VOID LogServiceMessage(LPCTSTR, DWORD);
  47. //
  48. // Note, this buffer must be at least double the size of the ASCII string:
  49. // "[ ***** Most recent entry is above this line ***** ]\r\n\r\n"
  50. // to leave sufficient space for localization changes.
  51. //
  52. #define MOST_RECENT_ENTRY_MARKER_SIZE 128
  53. static TCHAR gtszMostRecentEntryMarker[MOST_RECENT_ENTRY_MARKER_SIZE + 1] = TEXT("");
  54. extern CStaticCritSec gcsLogCritSection;
  55. HANDLE ghLog = NULL;
  56. DWORD gdwMaxLogSizeKB = NULL;
  57. DWORD gcbMostRecentEntryMarkerSize;
  58. //+---------------------------------------------------------------------------
  59. //
  60. // Function: OpenLogFile
  61. //
  62. // Synopsis: Open the log file and position the global file pointer.
  63. //
  64. // Log file path/name can be specified in in the registry as:
  65. // HKEY_LOCAL_MACHINE\Software\Microsoft\JobScheduler\LogPath.
  66. // If this value is not specified, or we fail somehow fetching
  67. // it, default to the log file name "SCHEDLGU.TXT" in the tasks folder.
  68. //
  69. // The log file handle is cached as a global.
  70. //
  71. // Arguments: None.
  72. //
  73. // Returns: HRESULT status code.
  74. //
  75. // Notes: ** Important Note **
  76. //
  77. // This function *must* be called *once* prior to log usage.
  78. // This function should be called after g_hInstance has been
  79. // initialized.
  80. //
  81. //----------------------------------------------------------------------------
  82. HRESULT
  83. OpenLogFile(VOID)
  84. {
  85. TCHAR tszBuffer[MAX_PATH + 1] = TEXT("\0");
  86. DWORD cbBufferSize = sizeof(tszBuffer);
  87. DWORD dwMaxLogSizeKB = MAX_LOG_SIZE_DEFAULT;
  88. DWORD dwType;
  89. HKEY hKey;
  90. HRESULT hr = S_OK;
  91. #define tszLogPath TEXT("LogPath")
  92. #define tszMaxLogSizeKB TEXT("MaxLogSizeKB")
  93. #define tszMarkerSentinel TEXT("[ *****")
  94. #define MARKER_SENTINEL_LENGTH (ARRAY_LEN(tszMarkerSentinel) - 1)
  95. #define READ_BUFFER_SIZE 512
  96. //
  97. // Even though this function should be called only once at service start up,
  98. // protect it with a critsec just in case someone tries to start two or more
  99. // instances of the scheduler service simultaneously
  100. //
  101. EnterCriticalSection(&gcsLogCritSection);
  102. if (ghLog != NULL)
  103. {
  104. hr = E_FAIL;
  105. schAssert(!"ghLog not NULL on entry to OpenLogFile -- attempted to run more than one instance of service?");
  106. goto ErrorExit;
  107. }
  108. // Load the most recent entry marker string from the resource table.
  109. // Set the size of the marker to the end of the string. Otherwise,
  110. // the IsTextUnicode API (called by notepad) thinks this is Ansi.
  111. //
  112. gcbMostRecentEntryMarkerSize =
  113. LoadString(g_hInstance,
  114. IDS_MOSTRECENTLOGENTRYMARKER,
  115. gtszMostRecentEntryMarker,
  116. MOST_RECENT_ENTRY_MARKER_SIZE + 1);
  117. if (!gcbMostRecentEntryMarkerSize)
  118. {
  119. hr = HRESULT_FROM_WIN32(GetLastError());
  120. CHECK_HRESULT(hr);
  121. goto ErrorExit;
  122. }
  123. // Convert to size in bytes
  124. //
  125. gcbMostRecentEntryMarkerSize *= sizeof(TCHAR);
  126. // Read the log path and maximum size from the registry. Note that these
  127. // are stored in the service's key.
  128. //
  129. if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  130. SCH_AGENT_KEY,
  131. 0,
  132. KEY_READ | KEY_SET_VALUE,
  133. &hKey))
  134. {
  135. if (RegQueryValueEx(hKey,
  136. tszLogPath,
  137. NULL,
  138. &dwType,
  139. (UCHAR *)tszBuffer,
  140. &cbBufferSize) ||
  141. (dwType != REG_SZ && dwType != REG_EXPAND_SZ))
  142. {
  143. //
  144. // Value is missing; create it with default
  145. //
  146. RegSetValueEx(hKey,
  147. tszLogPath,
  148. NULL,
  149. REG_EXPAND_SZ,
  150. (const BYTE *)TSZ_LOG_NAME_DEFAULT,
  151. sizeof(TSZ_LOG_NAME_DEFAULT));
  152. // if this call fails, there isn't much we can do
  153. // but at least we've defaulted to a reasonable value
  154. // and can continue with logging
  155. }
  156. cbBufferSize = sizeof(dwMaxLogSizeKB);
  157. if (RegQueryValueEx(hKey,
  158. tszMaxLogSizeKB,
  159. NULL,
  160. &dwType,
  161. (UCHAR *)&dwMaxLogSizeKB,
  162. &cbBufferSize) || dwType != REG_DWORD)
  163. {
  164. //
  165. // Value is missing; create it with default
  166. //
  167. dwMaxLogSizeKB = MAX_LOG_SIZE_DEFAULT;
  168. RegSetValueEx(hKey,
  169. tszMaxLogSizeKB,
  170. NULL,
  171. REG_DWORD,
  172. (CONST BYTE *)&dwMaxLogSizeKB,
  173. cbBufferSize);
  174. // if this call fails, there isn't much we can do
  175. // but at least we've defaulted to a reasonable value
  176. // and can continue with logging
  177. }
  178. RegCloseKey(hKey);
  179. }
  180. // Default log path on error.
  181. //
  182. if (!tszBuffer[0])
  183. {
  184. StringCchCopy(tszBuffer, MAX_PATH + 1, TSZ_LOG_NAME_DEFAULT);
  185. }
  186. // Expand environment strings in the log path.
  187. //
  188. TCHAR tszFileName[MAX_PATH+1];
  189. DWORD cch = ExpandEnvironmentStrings(tszBuffer,
  190. tszFileName,
  191. ARRAY_LEN(tszFileName));
  192. if (cch == 0 || cch > ARRAY_LEN(tszFileName))
  193. {
  194. ERR_OUT("ExpandEnvironmentStrings", cch);
  195. hr = E_OUTOFMEMORY;
  196. goto ErrorExit;
  197. }
  198. //
  199. // Obtain just the folder path from the full log file path
  200. //
  201. TCHAR tszLogFolderPath[MAX_PATH + 1];
  202. StringCchCopy(tszLogFolderPath, MAX_PATH + 1, tszFileName);
  203. TCHAR* ptszLastSlash = _tcsrchr(tszLogFolderPath, TEXT('\\'));
  204. if (ptszLastSlash)
  205. *ptszLastSlash = TEXT('\0');
  206. DWORD cchLogFolderPath = _tcslen(tszLogFolderPath);
  207. //
  208. // Compare the log file path with the tasks folder path;
  209. // The memory allocated by GetTasksFolder() must be deleted below.
  210. //
  211. TCHAR* ptszTasksFolder = NULL;
  212. hr = GetTasksFolder(&ptszTasksFolder);
  213. if (FAILED(hr))
  214. {
  215. goto ErrorExit;
  216. }
  217. DWORD cchTasksFolder = _tcslen(ptszTasksFolder);
  218. if (cchLogFolderPath == cchTasksFolder && !_tcsnicmp(tszFileName, ptszTasksFolder, cchTasksFolder))
  219. {
  220. //
  221. // The log file is to be stored under the tasks folder;
  222. // if the tasks folder doesn't exist, create it and set security
  223. //
  224. hr = EnsureTasksFolderExists(ptszTasksFolder);
  225. if (FAILED(hr))
  226. {
  227. goto ErrorExit;
  228. }
  229. }
  230. else
  231. {
  232. //
  233. // The log file is to be stored under a different folder;
  234. // if the folder doesn't exist, create it and set security,
  235. // but do not enable our shell extension on this folder
  236. //
  237. hr = EnsureTasksFolderExists(tszLogFolderPath, FALSE);
  238. if (FAILED(hr))
  239. {
  240. goto ErrorExit;
  241. }
  242. }
  243. // create an appropriate ACL
  244. // note that it only takes effect if we're *creating* the file
  245. // we're basically adding read access for authenticated users on PRO
  246. //
  247. PSECURITY_DESCRIPTOR pSD = NULL;
  248. WCHAR* pwszSDDL = NULL;
  249. OSVERSIONINFOEX verInfo;
  250. verInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
  251. if (!GetVersionEx((LPOSVERSIONINFOW)&verInfo))
  252. return E_FAIL;
  253. if (verInfo.wProductType == VER_NT_WORKSTATION)
  254. {
  255. pwszSDDL =
  256. L"D:(A;;FA;;;CO)(A;;FR;;;AU)(A;;FA;;;BA)(A;;FA;;;SY)";
  257. //
  258. // generate SD to be used for tasks folder
  259. //
  260. if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(pwszSDDL, SDDL_REVISION_1, &pSD, NULL))
  261. {
  262. hr = HRESULT_FROM_WIN32(GetLastError());
  263. goto ErrorExit;
  264. }
  265. }
  266. SECURITY_ATTRIBUTES sa;
  267. sa.lpSecurityDescriptor = pSD;
  268. sa.bInheritHandle = FALSE;
  269. sa.nLength = sizeof(SECURITY_ATTRIBUTES);
  270. // Create the file if it doesn't exist, open it if it does.
  271. //
  272. HANDLE hLog = CreateFile(tszFileName,
  273. GENERIC_READ | GENERIC_WRITE,
  274. FILE_SHARE_READ,
  275. (verInfo.wProductType == VER_NT_WORKSTATION) ? &sa : NULL,
  276. OPEN_ALWAYS,
  277. FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
  278. NULL);
  279. if (hLog == INVALID_HANDLE_VALUE)
  280. {
  281. // We're in a fine mess, bail.
  282. //
  283. hr = HRESULT_FROM_WIN32(GetLastError());
  284. CHECK_HRESULT(hr);
  285. schDebugOut((DEB_ERROR, "Attempted to create file \"" FMT_TSTR "\"\n", tszFileName));
  286. goto ErrorExit;
  287. }
  288. TCHAR rgtBuffer[READ_BUFFER_SIZE / sizeof(TCHAR)];
  289. DWORD dwRead;
  290. DWORD iMarker = 0; // Scope the marker index such that the search may span multiple reads.
  291. DWORD dwLoops = 0; // Keep track of how many successful reads we've made.
  292. // Used to figure out whether to write the UNICODE
  293. // byte order mark (BOM) at the top of the file
  294. // Seek to the most recent entry marker. Do so by searching for the first
  295. // several distinguishable characters of the marker - a sentinel.
  296. //
  297. for (;;)
  298. {
  299. // Save away current file position for later file pointer adjustment.
  300. //
  301. LARGE_INTEGER liLogPos;
  302. liLogPos.QuadPart = 0;
  303. if ((liLogPos.LowPart = SetFilePointer(hLog,
  304. 0,
  305. &liLogPos.HighPart,
  306. FILE_CURRENT)) == -1)
  307. {
  308. break;
  309. }
  310. if (!ReadFile(hLog, rgtBuffer, READ_BUFFER_SIZE, &dwRead, NULL) ||
  311. !dwRead)
  312. {
  313. break;
  314. }
  315. // Convert to the number of characters (and chop off a stray byte
  316. // if it exists in the Unicode case)
  317. //
  318. dwRead /= sizeof(TCHAR);
  319. for (DWORD iBuffer = 0; iBuffer < dwRead; iBuffer++)
  320. {
  321. // If the first marker character is found, or the marker
  322. // comparison is continued from the previous read, evaluate
  323. // remaining marker string.
  324. //
  325. if (rgtBuffer[iBuffer] == TEXT('[') || iMarker)
  326. {
  327. for (; iMarker < MARKER_SENTINEL_LENGTH && dwRead - iBuffer;
  328. iMarker++, iBuffer++)
  329. {
  330. if (rgtBuffer[iBuffer] != tszMarkerSentinel[iMarker])
  331. {
  332. break;
  333. }
  334. }
  335. // If the marker is found, stop & re-position the file
  336. // pointer for future writes.
  337. //
  338. if (iMarker == MARKER_SENTINEL_LENGTH)
  339. {
  340. // Adjust file pointer accordingly.
  341. //
  342. liLogPos.QuadPart += iBuffer * sizeof(TCHAR);
  343. liLogPos.QuadPart -= MARKER_SENTINEL_LENGTH * sizeof(TCHAR);
  344. if (SetFilePointer(hLog,
  345. liLogPos.LowPart,
  346. &liLogPos.HighPart,
  347. FILE_BEGIN) != -1)
  348. {
  349. goto MarkerFound;
  350. }
  351. else
  352. {
  353. hr = HRESULT_FROM_WIN32(GetLastError());
  354. CHECK_HRESULT(hr);
  355. }
  356. }
  357. else if (iMarker < MARKER_SENTINEL_LENGTH && dwRead - iBuffer)
  358. {
  359. // Almost a match, but not quite - reset for continued
  360. // search.
  361. //
  362. iMarker = 0;
  363. }
  364. }
  365. }
  366. dwLoops++;
  367. }
  368. if (!dwLoops && !dwRead)
  369. {
  370. // We just created the file and it's empty, so write the Unicode BOM
  371. //
  372. DWORD cbWritten;
  373. WCHAR wcBOM = 0xFEFF;
  374. if (!WriteFile(hLog, &wcBOM, sizeof(WCHAR), &cbWritten, NULL) ||
  375. !cbWritten)
  376. {
  377. // If we can't write to the log, we've got problems
  378. //
  379. CloseHandle(hLog);
  380. hr = HRESULT_FROM_WIN32(GetLastError());
  381. CHECK_HRESULT(hr);
  382. goto ErrorExit;
  383. }
  384. }
  385. // Marker not found. Seek to file end.
  386. //
  387. if (SetFilePointer(hLog, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
  388. {
  389. // Another fine mess, bail.
  390. //
  391. CloseHandle(hLog);
  392. hr = HRESULT_FROM_WIN32(GetLastError());
  393. CHECK_HRESULT(hr);
  394. schAssert(!"Couldn't seek to log file end");
  395. goto ErrorExit;
  396. }
  397. MarkerFound:
  398. gdwMaxLogSizeKB = dwMaxLogSizeKB;
  399. ghLog = hLog;
  400. ErrorExit:
  401. if (pSD)
  402. LocalFree(pSD);
  403. if (ptszTasksFolder)
  404. {
  405. delete [] ptszTasksFolder;
  406. }
  407. LeaveCriticalSection(&gcsLogCritSection);
  408. return hr;
  409. }
  410. //+---------------------------------------------------------------------------
  411. //
  412. // Function: CloseLogFile
  413. //
  414. // Synopsis: Close log file and invalidate global handle.
  415. //
  416. // Arguments: None.
  417. //
  418. // Returns: None.
  419. //
  420. // Notes: ** Important Note **
  421. //
  422. // Presumably, this function is called on process closure.
  423. // Therefore, let the OS delete the critical section, *not* this
  424. // thread. Otherwise, the critical section can be deleted out
  425. // from under other threads currently accessing the log.
  426. //
  427. //----------------------------------------------------------------------------
  428. VOID
  429. CloseLogFile(VOID)
  430. {
  431. //
  432. // If OpenLogFile has not completed successfully, the critical section
  433. // won't have been initialized nor the global file handle set.
  434. //
  435. if (ghLog != NULL)
  436. {
  437. // Handle close gracefully in case another thread is accessing the log.
  438. // Do so by entering the log critical section, closing the log and
  439. // invalidating the global log handle (setting it to NULL).
  440. //
  441. EnterCriticalSection(&gcsLogCritSection);
  442. if (ghLog)
  443. {
  444. CloseHandle(ghLog);
  445. ghLog = NULL;
  446. }
  447. LeaveCriticalSection(&gcsLogCritSection);
  448. }
  449. }
  450. //+---------------------------------------------------------------------------
  451. //
  452. // Function: LogTaskStatus
  453. //
  454. // Purpose: Log successful task operations.
  455. //
  456. // Arguments: [ptszTaskName] - the task name.
  457. // [ptszTaskTarget] - the application/document name.
  458. // [uMsgID] - this would typically be either:
  459. // IDS_LOG_JOB_STATUS_STARTED or
  460. // IDS_LOG_JOB_STATUS_FINISHED
  461. // [dwExitCode] - if uMsgID is IDS_LOG_JOB_STATUS_FINISHED,
  462. // it is the task exit code; ignored otherwise.
  463. //
  464. //----------------------------------------------------------------------------
  465. VOID
  466. LogTaskStatus(
  467. LPCTSTR ptszTaskName,
  468. LPTSTR ptszTaskTarget,
  469. UINT uMsgID,
  470. DWORD dwExitCode)
  471. {
  472. TCHAR tszMsgFormat[SCH_BIGBUF_LEN];
  473. TCHAR * ptszStatusMsg = NULL;
  474. ULONG ccSize;
  475. //
  476. // Add the date & time as inserts to the format string.
  477. //
  478. TCHAR tszDate[SCH_MEDBUF_LEN];
  479. TCHAR tszTime[SCH_MEDBUF_LEN];
  480. if (!GetDateTime(NULL, tszDate, tszTime))
  481. {
  482. return;
  483. }
  484. TCHAR * ptszResultField = NULL;
  485. // Load the format string resource.
  486. //
  487. if (!LoadString(g_hInstance,
  488. uMsgID,
  489. tszMsgFormat,
  490. ARRAY_LEN(tszMsgFormat)))
  491. {
  492. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  493. return;
  494. }
  495. if (uMsgID == IDS_LOG_JOB_STATUS_FINISHED)
  496. {
  497. ptszResultField = ConstructResultField(dwExitCode, ptszTaskTarget);
  498. if (ptszResultField == NULL)
  499. {
  500. return;
  501. }
  502. }
  503. TCHAR * rgptszInserts[] = { (TCHAR *)ptszTaskName,
  504. (TCHAR *)ptszTaskTarget,
  505. tszDate,
  506. tszTime,
  507. ptszResultField };
  508. if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  509. FORMAT_MESSAGE_ALLOCATE_BUFFER |
  510. FORMAT_MESSAGE_ARGUMENT_ARRAY,
  511. tszMsgFormat,
  512. 0,
  513. 0,
  514. (TCHAR *)&ptszStatusMsg,
  515. 1,
  516. (va_list *) rgptszInserts))
  517. {
  518. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  519. if (ptszResultField != NULL)
  520. {
  521. LocalFree(ptszResultField);
  522. }
  523. return;
  524. }
  525. WriteLog(ptszStatusMsg);
  526. LocalFree(ptszStatusMsg);
  527. if (ptszResultField != NULL)
  528. {
  529. LocalFree(ptszResultField);
  530. }
  531. }
  532. //+---------------------------------------------------------------------------
  533. //
  534. // Function: LogTaskError
  535. //
  536. // Purpose: Log task warnings and errors.
  537. //
  538. // Arguments: [ptszTaskName] - the task name.
  539. // [ptszTaskTarget] - the application/document name.
  540. // [uSeverityMsgID] - this would typically be either:
  541. // IDS_LOG_SEVERITY_WARNING or
  542. // IDS_LOG_SEVERITY_ERROR
  543. // [uErrorClassMsgID] - this indicates the class of error, such
  544. // as "Unable to start task" or "Forced to
  545. // close"
  546. // [pst] - the time when the error occured; if NULL,
  547. // enters the current time.
  548. // [dwErrorCode] - if non-zero, then an error from the OS
  549. // that would be expanded by FormatMessage.
  550. // [uHelpHintMsgID] - if an error, then a suggestion as to a
  551. // possible remedy.
  552. //
  553. //----------------------------------------------------------------------------
  554. VOID
  555. LogTaskError(
  556. LPCTSTR ptszTaskName,
  557. LPCTSTR ptszTaskTarget,
  558. UINT uSeverityMsgID,
  559. UINT uErrorClassMsgID,
  560. LPSYSTEMTIME pst,
  561. DWORD dwErrCode,
  562. UINT uHelpHintMsgID)
  563. {
  564. TCHAR tszEmpty[] = TEXT("");
  565. //
  566. // Verify params:
  567. //
  568. if (ptszTaskName == NULL)
  569. {
  570. ptszTaskName = tszEmpty;
  571. }
  572. if (ptszTaskTarget == NULL)
  573. {
  574. ptszTaskTarget = tszEmpty;
  575. }
  576. TCHAR tszFormat[SCH_BUF_LEN];
  577. //
  578. // Compose the first part of the error log entry:
  579. // "<task name>" (<task target>) <time> ** [WARNING | ERROR] **
  580. //
  581. if (!LoadString(g_hInstance,
  582. uSeverityMsgID,
  583. tszFormat,
  584. SCH_BUF_LEN))
  585. {
  586. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  587. return;
  588. }
  589. //
  590. // Add the date & time as inserts to the format string.
  591. //
  592. TCHAR tszDate[SCH_MEDBUF_LEN];
  593. TCHAR tszTime[SCH_MEDBUF_LEN];
  594. if (!GetDateTime(pst, tszDate, tszTime))
  595. {
  596. return;
  597. }
  598. //
  599. // Obtain the error message string.
  600. //
  601. LPTSTR ptszErrMsg = ComposeErrorMsg(uErrorClassMsgID,
  602. dwErrCode,
  603. uHelpHintMsgID);
  604. if (ptszErrMsg == NULL)
  605. {
  606. return;
  607. }
  608. //
  609. // Glue the whole mess together.
  610. //
  611. TCHAR * rgptszInserts[] = { (TCHAR *)ptszTaskName,
  612. (TCHAR *)ptszTaskTarget,
  613. tszDate,
  614. tszTime,
  615. ptszErrMsg };
  616. TCHAR * ptszLogStr;
  617. if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  618. FORMAT_MESSAGE_ALLOCATE_BUFFER |
  619. FORMAT_MESSAGE_ARGUMENT_ARRAY,
  620. tszFormat,
  621. 0,
  622. 0,
  623. (TCHAR *)&ptszLogStr,
  624. 1,
  625. (va_list *) rgptszInserts))
  626. {
  627. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  628. LocalFree(ptszErrMsg);
  629. return;
  630. }
  631. WriteLog(ptszLogStr);
  632. LocalFree(ptszErrMsg);
  633. LocalFree(ptszLogStr);
  634. }
  635. //+---------------------------------------------------------------------------
  636. //
  637. // Function: LogServiceError
  638. //
  639. // Purpose: Log service failures.
  640. //
  641. // Arguments: [uErrorClassMsgID] - as above.
  642. // [dwErrCode] - as above.
  643. // [uHelpHintMsgID] - as above.
  644. //
  645. //----------------------------------------------------------------------------
  646. VOID
  647. LogServiceError(
  648. UINT uErrorClassMsgID,
  649. DWORD dwErrCode,
  650. UINT uHelpHintMsgID)
  651. {
  652. TCHAR tszSvcErrMsgFormat[SCH_MEDBUF_LEN];
  653. if (LoadString(g_hInstance,
  654. IDS_LOG_SERVICE_ERROR,
  655. tszSvcErrMsgFormat,
  656. SCH_MEDBUF_LEN) == 0)
  657. {
  658. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  659. //
  660. // Generic error message if things are really foobared.
  661. //
  662. StringCchCopy(tszSvcErrMsgFormat, SCH_MEDBUF_LEN,
  663. TEXT("\042Task Scheduler Service\042 ** FATAL ERROR **\n"));
  664. WriteLog(tszSvcErrMsgFormat);
  665. return;
  666. }
  667. //
  668. // Add the date & time as inserts to the format string.
  669. //
  670. TCHAR tszDate[SCH_MEDBUF_LEN];
  671. TCHAR tszTime[SCH_MEDBUF_LEN];
  672. if (!GetDateTime(NULL, tszDate, tszTime))
  673. {
  674. return;
  675. }
  676. LPTSTR ptszErrMsg = ComposeErrorMsg(uErrorClassMsgID,
  677. dwErrCode,
  678. uHelpHintMsgID);
  679. if (ptszErrMsg == NULL)
  680. {
  681. return;
  682. }
  683. TCHAR * rgptszInserts[] = {tszDate, tszTime, ptszErrMsg};
  684. TCHAR * ptszLogStr;
  685. if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  686. FORMAT_MESSAGE_ALLOCATE_BUFFER |
  687. FORMAT_MESSAGE_ARGUMENT_ARRAY,
  688. tszSvcErrMsgFormat,
  689. 0,
  690. 0,
  691. (TCHAR *)&ptszLogStr,
  692. 1,
  693. (va_list *) rgptszInserts))
  694. {
  695. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  696. LocalFree(ptszErrMsg);
  697. return;
  698. }
  699. WriteLog(ptszLogStr);
  700. LocalFree(ptszErrMsg);
  701. LocalFree(ptszLogStr);
  702. }
  703. //+---------------------------------------------------------------------------
  704. //
  705. // Function: LogServiceEvent
  706. //
  707. // Synopsis: Write the service event to the log file.
  708. //
  709. // Purpose: Note the starting, stopping, pausing, and continuing of the
  710. // service.
  711. //
  712. // Arguments: [uStrId] - a string identifying the event.
  713. //
  714. //----------------------------------------------------------------------------
  715. VOID
  716. LogServiceEvent(UINT uStrId)
  717. {
  718. TCHAR * ptszSvcMsg;
  719. ULONG cbMsgSize;
  720. SYSTEMTIME st;
  721. GetLocalTime(&st);
  722. ptszSvcMsg = ConstructStatusField(uStrId, &st, &cbMsgSize);
  723. if( NULL == ptszSvcMsg )
  724. {
  725. schDebugOut((DEB_ITRACE, "LogServiceEvent - ConstructStatusField(uStrId, &st, &cbMsgSize) failed!\n"));
  726. return;
  727. }
  728. LogServiceMessage(ptszSvcMsg, cbMsgSize);
  729. LocalFree(ptszSvcMsg);
  730. }
  731. //+---------------------------------------------------------------------------
  732. //
  733. // Function: LogMissedRuns
  734. //
  735. // Synopsis: Write details about missed runs to the log file.
  736. //
  737. // Arguments: [pstLastRun], [pstNow] - times between which runs were missed.
  738. //
  739. //----------------------------------------------------------------------------
  740. VOID
  741. LogMissedRuns(const SYSTEMTIME * pstLastRun, const SYSTEMTIME * pstNow)
  742. {
  743. TCHAR tszLastRunDate[SCH_MEDBUF_LEN];
  744. TCHAR tszLastRunTime[SCH_MEDBUF_LEN];
  745. TCHAR tszNowDate [SCH_MEDBUF_LEN];
  746. TCHAR tszNowTime [SCH_MEDBUF_LEN];
  747. if (!GetDateTime(pstLastRun, tszLastRunDate, tszLastRunTime) ||
  748. !GetDateTime(pstNow, tszNowDate, tszNowTime))
  749. {
  750. return;
  751. }
  752. TCHAR tszMsgFormat[SCH_BIGBUF_LEN];
  753. if (!LoadString(g_hInstance,
  754. IDS_LOG_RUNS_MISSED,
  755. tszMsgFormat,
  756. SCH_BIGBUF_LEN))
  757. {
  758. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  759. return;
  760. }
  761. TCHAR * rgptszInserts[] = { tszLastRunDate, tszLastRunTime,
  762. tszNowDate, tszNowTime };
  763. TCHAR * ptszLogStr;
  764. if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  765. FORMAT_MESSAGE_ALLOCATE_BUFFER |
  766. FORMAT_MESSAGE_ARGUMENT_ARRAY,
  767. tszMsgFormat,
  768. 0,
  769. 0,
  770. (TCHAR *)&ptszLogStr,
  771. 1,
  772. (va_list *) rgptszInserts))
  773. {
  774. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  775. return;
  776. }
  777. LogServiceMessage(ptszLogStr, (lstrlen(ptszLogStr) + 1) * sizeof(TCHAR));
  778. LocalFree(ptszLogStr);
  779. }
  780. //+---------------------------------------------------------------------------
  781. //
  782. // Function: LogServiceMessage
  783. //
  784. // Synopsis: Write a generic service message to the log file.
  785. //
  786. // Purpose: Used by LogServiceEvent and LogMissedRuns.
  787. //
  788. // Arguments: [ptszStrMsg] - a string message.
  789. // [cbStrMsg] - size of pszStrMsg in bytes (may be overestimated,
  790. // used only to calculate size of intermediate buffer.)
  791. //
  792. //----------------------------------------------------------------------------
  793. VOID
  794. LogServiceMessage(LPCTSTR ptszStrMsg, DWORD cbStrMsg)
  795. {
  796. size_t cchMsg = SCH_MEDBUF_LEN + (cbStrMsg / sizeof(TCHAR)) + 1;
  797. TCHAR * ptszMsg = (TCHAR *)LocalAlloc(LPTR, cchMsg * sizeof(TCHAR));
  798. if (ptszMsg == NULL)
  799. {
  800. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  801. return;
  802. }
  803. if (LoadString(g_hInstance,
  804. IDS_LOG_SERVICE_TITLE,
  805. ptszMsg,
  806. SCH_MEDBUF_LEN) == 0)
  807. {
  808. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  809. //
  810. // Generic error message if things are really foobared.
  811. //
  812. StringCchCopy(ptszMsg, cchMsg, TEXT("\042Task Scheduler Service\042 ** ERROR **\n"));
  813. }
  814. if (ptszStrMsg != NULL)
  815. {
  816. StringCchCat(ptszMsg, cchMsg, ptszStrMsg);
  817. }
  818. WriteLog(ptszMsg);
  819. LocalFree(ptszMsg);
  820. }
  821. //+---------------------------------------------------------------------------
  822. //
  823. // Function: WriteLog
  824. //
  825. // Synopsis: Write the string to the log file.
  826. //
  827. //----------------------------------------------------------------------------
  828. VOID
  829. WriteLog(LPTSTR ptsz)
  830. {
  831. LARGE_INTEGER liCurLogSize, liMaxLogSize;
  832. DWORD cbWritten;
  833. ULONG cbStringSize = lstrlen(ptsz) * sizeof(TCHAR);
  834. ULONG cbDataSize = cbStringSize;
  835. EnterCriticalSection(&gcsLogCritSection);
  836. schDebugOut((DEB_TRACE, "LOG:\n " FMT_TSTR "", ptsz));
  837. // Lose some time here by not caching this value, but not much.
  838. //
  839. cbDataSize += lstrlen(gtszMostRecentEntryMarker) * sizeof(TCHAR);
  840. // Get the current log size to see if there is room to write this.
  841. //
  842. liCurLogSize.QuadPart = 0;
  843. if ((liCurLogSize.LowPart = SetFilePointer(ghLog,
  844. 0,
  845. &liCurLogSize.HighPart,
  846. FILE_CURRENT)) == -1)
  847. {
  848. goto ErrorExit_A;
  849. }
  850. // Add current data size. Convert maximum size to bytes for comparison.
  851. //
  852. liCurLogSize.QuadPart += cbDataSize;
  853. liMaxLogSize.QuadPart = gdwMaxLogSizeKB * 1024;
  854. // Is there sufficient space to write the entry?
  855. //
  856. if (liCurLogSize.QuadPart > liMaxLogSize.QuadPart)
  857. {
  858. // No, adjust the end of file to eliminate the most recent entry
  859. // marker & wrap to beginning.
  860. //
  861. SetEndOfFile(ghLog); // Ignore return code.
  862. // skip the BOM
  863. //
  864. if (SetFilePointer(ghLog, sizeof(WCHAR), NULL, FILE_BEGIN) == -1)
  865. {
  866. // Seek failure
  867. //
  868. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  869. schAssert(!"Couldn't seek log file");
  870. goto ErrorExit_A;
  871. }
  872. }
  873. // Write the string.
  874. //
  875. if (!WriteFile(ghLog, ptsz, cbStringSize, &cbWritten, NULL))
  876. {
  877. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  878. goto ErrorExit_A;
  879. }
  880. // Write most recent entry marker.
  881. //
  882. // First, save the current file pointer position. This will be the
  883. // starting location of the next log write. Note: double-timing current
  884. // log size local since it is no longer used.
  885. //
  886. liCurLogSize.QuadPart = 0;
  887. if ((liCurLogSize.LowPart = SetFilePointer(ghLog,
  888. 0,
  889. &liCurLogSize.HighPart,
  890. FILE_CURRENT)) == -1)
  891. {
  892. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  893. goto ErrorExit_A;
  894. }
  895. if (!WriteFile(ghLog,
  896. gtszMostRecentEntryMarker,
  897. gcbMostRecentEntryMarkerSize,
  898. &cbWritten,
  899. NULL))
  900. {
  901. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  902. goto ErrorExit_A;
  903. }
  904. // If the log has wrapped, it's likely the write pointer is positioned
  905. // somewhere in the middle of the next record. If this is the case,
  906. // the remaining partial record must be overwritten with spaces.
  907. //
  908. OverwriteRecordFragment();
  909. // Restore log position for next write.
  910. //
  911. if (SetFilePointer(ghLog,
  912. liCurLogSize.LowPart,
  913. &liCurLogSize.HighPart,
  914. FILE_BEGIN) == -1)
  915. {
  916. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  917. }
  918. ErrorExit_A:
  919. LeaveCriticalSection(&gcsLogCritSection);
  920. }
  921. //+---------------------------------------------------------------------------
  922. //
  923. // Function: OverwriteRecordFragment
  924. //
  925. // Synopsis: If the log has wrapped, the last write most likely has
  926. // partially overwritten a record. This routine overwrites such
  927. // record fragments with spaces up to the next record.
  928. //
  929. // Fundamental assumption - how a record is designated: The start
  930. // of a new record is designated by the two characters \n". This
  931. // routine simply fills text with spaces up to but not including
  932. // this character sequence.
  933. //
  934. // Arguments: None.
  935. //
  936. // Returns: N/A
  937. //
  938. // Notes: Upon exit, the log file pointer is restored to its original
  939. // position on entry.
  940. //
  941. //----------------------------------------------------------------------------
  942. VOID
  943. OverwriteRecordFragment(VOID)
  944. {
  945. TCHAR rgtBuffer[READ_BUFFER_SIZE / sizeof(TCHAR)];
  946. LARGE_INTEGER liSavedLogPos, liLogPos;
  947. DWORD dwRead;
  948. // Save file pointer position during read for subsequent write.
  949. //
  950. liSavedLogPos.QuadPart = 0;
  951. if ((liSavedLogPos.LowPart = SetFilePointer(ghLog,
  952. 0,
  953. &liSavedLogPos.HighPart,
  954. FILE_CURRENT)) == -1)
  955. {
  956. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  957. return;
  958. }
  959. // From the previous write, the last character is a line feed.
  960. //
  961. TCHAR tchPrev = TEXT('\n');
  962. TCHAR tchCur;
  963. int cbOverwrite = 0;
  964. DWORD i = 0; // Index of chCur in rgbBuffer
  965. for (;;)
  966. {
  967. if (!ReadFile(ghLog, rgtBuffer, READ_BUFFER_SIZE, &dwRead, NULL) ||
  968. !dwRead)
  969. {
  970. break;
  971. }
  972. // Convert to the number of characters (and chop off a stray byte
  973. // if it exists in the Unicode case)
  974. //
  975. dwRead /= sizeof(TCHAR);
  976. for ( ; i < dwRead; i++, cbOverwrite += sizeof(TCHAR))
  977. {
  978. tchCur = rgtBuffer[i];
  979. if (tchPrev == TEXT('\n') && tchCur == TEXT('"') && cbOverwrite > 2 * sizeof(TCHAR))
  980. {
  981. break;
  982. }
  983. tchPrev = tchCur;
  984. }
  985. if (i < dwRead)
  986. {
  987. // We found the \n" character sequence. Don't
  988. // overwrite the \r\n" sequence of the next record.
  989. //
  990. cbOverwrite -= 2 * sizeof(TCHAR);
  991. break;
  992. }
  993. i = 0;
  994. }
  995. DWORD cbWritten;
  996. // Overwrite record fragment with spaces.
  997. //
  998. if (cbOverwrite > 0)
  999. {
  1000. // Adjust file pointer from read above.
  1001. //
  1002. if (SetFilePointer(ghLog,
  1003. liSavedLogPos.LowPart,
  1004. &liSavedLogPos.HighPart,
  1005. FILE_BEGIN) != -1)
  1006. {
  1007. for (UINT uCount = 0;
  1008. uCount < READ_BUFFER_SIZE / sizeof(TCHAR);
  1009. uCount++)
  1010. {
  1011. rgtBuffer[uCount] = TEXT(' ');
  1012. }
  1013. while (cbOverwrite > 0)
  1014. {
  1015. if (!WriteFile(ghLog,
  1016. rgtBuffer,
  1017. min(cbOverwrite, READ_BUFFER_SIZE),
  1018. &cbWritten,
  1019. NULL))
  1020. {
  1021. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1022. break;
  1023. }
  1024. cbOverwrite -= cbWritten;
  1025. }
  1026. }
  1027. else
  1028. {
  1029. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1030. }
  1031. }
  1032. }
  1033. //+---------------------------------------------------------------------------
  1034. //
  1035. // Function: ConstructStatusField
  1036. //
  1037. // Synopsis: Retrieve the status field with an optional status timestamp
  1038. // insert.
  1039. //
  1040. // Arguments: [dwStatusFieldMsgID] -- Status field format string msg id.
  1041. // [pstStatusTime] -- Optional status timestamp. If NULL,
  1042. // no timestamp is written.
  1043. // [pcbSize] -- Returned status field size (bytes).
  1044. //
  1045. // Returns: TCHAR * status field
  1046. // NULL on error
  1047. //
  1048. // Notes: FormatMessage allocates the return string. Use LocalFree() to
  1049. // deallocate.
  1050. //
  1051. //----------------------------------------------------------------------------
  1052. TCHAR *
  1053. ConstructStatusField(
  1054. DWORD dwStatusFieldMsgID,
  1055. SYSTEMTIME * pstStatusTime,
  1056. ULONG * pcbSize)
  1057. {
  1058. // Note: Insure string buffer sizes are at least double the size of the
  1059. // largest string they'll contain, for localization reasons.
  1060. //
  1061. TCHAR tszStatusFieldFormat[SCH_BIGBUF_LEN];
  1062. TCHAR tszDate[SCH_MEDBUF_LEN];
  1063. TCHAR tszTime[SCH_MEDBUF_LEN];
  1064. TCHAR * rgptszInserts[] = { tszDate, tszTime };
  1065. TCHAR * ptszStatusField = NULL;
  1066. // The status field may/may not contain a date & time. The first
  1067. // branch is taken for status fields containing them.
  1068. //
  1069. if (pstStatusTime != NULL)
  1070. {
  1071. // Add the date & time as inserts to the status field format string.
  1072. //
  1073. if (!GetDateTime(pstStatusTime, tszDate, tszTime))
  1074. {
  1075. return(NULL);
  1076. }
  1077. }
  1078. ULONG ccSize = 0;
  1079. // Load the status field format string resource.
  1080. //
  1081. if (LoadString(g_hInstance,
  1082. dwStatusFieldMsgID,
  1083. tszStatusFieldFormat,
  1084. SCH_BIGBUF_LEN))
  1085. {
  1086. if (!(ccSize = FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  1087. FORMAT_MESSAGE_ALLOCATE_BUFFER |
  1088. (pstStatusTime != NULL ? FORMAT_MESSAGE_ARGUMENT_ARRAY : 0),
  1089. tszStatusFieldFormat,
  1090. 0,
  1091. 0,
  1092. (TCHAR *)&ptszStatusField,
  1093. 1,
  1094. (pstStatusTime != NULL ? (va_list *) rgptszInserts : NULL))))
  1095. {
  1096. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1097. }
  1098. }
  1099. else
  1100. {
  1101. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1102. }
  1103. *pcbSize = ccSize * sizeof(TCHAR);
  1104. return(ptszStatusField);
  1105. }
  1106. //+---------------------------------------------------------------------------
  1107. //
  1108. // Function: ConstructResultField
  1109. //
  1110. // Synopsis: Retrieve the result field. Algorithm:
  1111. //
  1112. // The result code is the job's exit code. Utilize the following
  1113. // algorithm to obtain the exit code string:
  1114. //
  1115. // Attempt to fetch the the exit code string as a
  1116. // message binary specified in the Job Scheduler portion of
  1117. // the registry.
  1118. //
  1119. // If this fails, produce a default
  1120. // "message not found for exit code (n)" message.
  1121. //
  1122. // Insert the result string obtained above as an insert string
  1123. // to the result field format string.
  1124. //
  1125. // Arguments: [dwResultCode] -- Result field result code.
  1126. // [ptszJobExecutable] -- Binary name executed by the job.
  1127. //
  1128. // Returns: TCHAR * result field
  1129. // NULL on error
  1130. //
  1131. // Notes: FormatMessage allocates the return string. Use LocalFree() to
  1132. // deallocate.
  1133. //
  1134. //----------------------------------------------------------------------------
  1135. TCHAR *
  1136. ConstructResultField(
  1137. DWORD dwResultCode,
  1138. LPTSTR ptszJobExecutable)
  1139. {
  1140. // Note: Insure format string buffer size is at least double the size of
  1141. // the largest string it will contain, for localization reasons.
  1142. //
  1143. TCHAR tszResultFieldFormat[SCH_MEDBUF_LEN];
  1144. TCHAR tszResultCodeValue[CCH_INT + 1];
  1145. TCHAR * ptszResultField = NULL;
  1146. TCHAR * ptszResult;
  1147. IntegerToString(dwResultCode, tszResultCodeValue);
  1148. // Job exit code. Fetch the exit code string from the
  1149. // ExitCodeMessageFile associated with the job program.
  1150. //
  1151. if ((ptszResult = GetExitCodeString(dwResultCode,
  1152. tszResultCodeValue,
  1153. (TCHAR *)ptszJobExecutable)) == NULL)
  1154. {
  1155. // Produce a default "message not found" result string.
  1156. //
  1157. ptszResult = GetSchedulerResultCodeString(
  1158. IDS_LOG_EXIT_CODE_MSG_NOT_FOUND,
  1159. dwResultCode);
  1160. }
  1161. ULONG ccSize = 0;
  1162. // Load the result field format string resource.
  1163. //
  1164. if (ptszResult != NULL)
  1165. {
  1166. if (LoadString(g_hInstance,
  1167. IDS_LOG_JOB_RESULT_FINISHED,
  1168. tszResultFieldFormat,
  1169. SCH_MEDBUF_LEN))
  1170. {
  1171. TCHAR * rgtszInserts[] = { ptszResult, tszResultCodeValue };
  1172. if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  1173. FORMAT_MESSAGE_ALLOCATE_BUFFER |
  1174. FORMAT_MESSAGE_ARGUMENT_ARRAY,
  1175. tszResultFieldFormat,
  1176. 0,
  1177. 0,
  1178. (TCHAR *)&ptszResultField,
  1179. 1,
  1180. (va_list *) rgtszInserts))
  1181. {
  1182. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1183. }
  1184. LocalFree(ptszResult); // pszResultField now encapsulates
  1185. // this string.
  1186. }
  1187. else
  1188. {
  1189. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1190. }
  1191. }
  1192. return(ptszResultField);
  1193. }
  1194. //+---------------------------------------------------------------------------
  1195. //
  1196. // Function: GetSchedulerResultCodeString
  1197. //
  1198. // Synopsis: Fetch the result code from the schedule service process.
  1199. //
  1200. // Arguments: [dwResultMsgID] -- Result message string ID.
  1201. // [dwResultCode] -- result code.
  1202. //
  1203. // Returns: TCHAR * result code string
  1204. // NULL on error
  1205. //
  1206. // Notes: FormatMessage allocates the return string. Use LocalFree() to
  1207. // deallocate.
  1208. //
  1209. //----------------------------------------------------------------------------
  1210. TCHAR *
  1211. GetSchedulerResultCodeString(
  1212. DWORD dwResultMsgID,
  1213. DWORD dwResultCode)
  1214. {
  1215. TCHAR tszResultCodeValue[SCH_SMBUF_LEN];
  1216. TCHAR tszErrMsg[SCH_MEDBUF_LEN];
  1217. TCHAR * ptszErrMsg = NULL, * ptszResultCode = NULL;
  1218. DWORD ccLength;
  1219. TCHAR * rgtszInserts[] = { tszResultCodeValue, ptszErrMsg };
  1220. IntegerToString(dwResultCode, tszResultCodeValue);
  1221. TCHAR tszMsgBuf[MAX_PATH], * ptsz;
  1222. if (LoadString(g_hInstance, dwResultMsgID, tszMsgBuf, MAX_PATH) == 0)
  1223. {
  1224. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1225. return NULL;
  1226. }
  1227. if (dwResultCode != 0)
  1228. {
  1229. BOOL fDelete = FALSE;
  1230. //
  1231. // Try to obtain an error message from the system.
  1232. //
  1233. if (!(ccLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
  1234. FORMAT_MESSAGE_ALLOCATE_BUFFER,
  1235. NULL,
  1236. dwResultCode,
  1237. LOCALE_SYSTEM_DEFAULT,
  1238. (TCHAR *)&ptszErrMsg,
  1239. 1,
  1240. NULL)))
  1241. {
  1242. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1243. if (!LoadString(g_hInstance,
  1244. IDS_GENERIC_ERROR_MSG,
  1245. tszErrMsg,
  1246. SCH_MEDBUF_LEN))
  1247. {
  1248. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1249. return NULL;
  1250. }
  1251. ptszErrMsg = tszErrMsg;
  1252. }
  1253. else
  1254. {
  1255. fDelete = TRUE;
  1256. //
  1257. // Overwrite \r\n with a null characters.
  1258. //
  1259. ptsz = ptszErrMsg + ccLength - 2;
  1260. *ptsz++ = TEXT('\0');
  1261. *ptsz = TEXT('\0');
  1262. }
  1263. if (!(ccLength = FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  1264. FORMAT_MESSAGE_ALLOCATE_BUFFER |
  1265. FORMAT_MESSAGE_ARGUMENT_ARRAY,
  1266. tszMsgBuf,
  1267. 0,
  1268. 0,
  1269. (TCHAR *)&ptszResultCode,
  1270. 2,
  1271. (va_list *) rgtszInserts)))
  1272. {
  1273. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1274. if (fDelete) LocalFree(ptszErrMsg);
  1275. return NULL;
  1276. }
  1277. if (fDelete) LocalFree(ptszErrMsg);
  1278. }
  1279. else
  1280. {
  1281. //
  1282. // No result code. All of the info is encapsulated in dwResultMsgID,
  1283. // which has no inserts.
  1284. //
  1285. if (!(ccLength = FormatMessage(FORMAT_MESSAGE_FROM_STRING |
  1286. FORMAT_MESSAGE_ALLOCATE_BUFFER,
  1287. tszMsgBuf,
  1288. 0,
  1289. 0,
  1290. (TCHAR *)&ptszResultCode,
  1291. 1,
  1292. NULL)))
  1293. {
  1294. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1295. return NULL;
  1296. }
  1297. }
  1298. return(ptszResultCode);
  1299. }
  1300. //+---------------------------------------------------------------------------
  1301. //
  1302. // Function: IntegerToString
  1303. //
  1304. // Synopsis: Converts a 32 bit integer to a string.
  1305. //
  1306. // Arguments: [n] -- Converted int.
  1307. // [ptszBuf] -- Caller allocated buffer.
  1308. //
  1309. // Returns: Buffer ptr passed.
  1310. //
  1311. // Notes: None.
  1312. //
  1313. //----------------------------------------------------------------------------
  1314. TCHAR *
  1315. IntegerToString(ULONG n, TCHAR * tszBuf)
  1316. {
  1317. //
  1318. // Assemble hex representation into passed buffer, reversed,
  1319. // then reverse in-place into correct format
  1320. //
  1321. // This code deliberately eschews ultoa, since div and mod 16
  1322. // optimize so very nicely.
  1323. //
  1324. UINT ich = 0;
  1325. do
  1326. {
  1327. UINT nDigitValue = (UINT)(n % 16);
  1328. n /= 16;
  1329. if (nDigitValue > 9)
  1330. {
  1331. tszBuf[ich++] = (WCHAR)nDigitValue - 10 + TEXT('a');
  1332. }
  1333. else
  1334. {
  1335. tszBuf[ich++] = (WCHAR)nDigitValue + TEXT('0');
  1336. }
  1337. } while (n > 0);
  1338. tszBuf[ich] = TEXT('\0');
  1339. _tcsrev(tszBuf);
  1340. return(tszBuf);
  1341. }
  1342. //+---------------------------------------------------------------------------
  1343. //
  1344. // Function: GetDateTime
  1345. //
  1346. // Synopsis: Formats the date and time.
  1347. //
  1348. // Arguments: [pst] - The time to use; if NULL, then the current time
  1349. // is obtained.
  1350. // [ptszDate] - The date string buffer.
  1351. // [ptszTime] - The time string buffer.
  1352. //
  1353. // Returns: TRUE for success, FALSE for failure.
  1354. //
  1355. // Notes: Note that the buffers must be at least SCH_MEDBUF_LEN in size.
  1356. //
  1357. //----------------------------------------------------------------------------
  1358. BOOL
  1359. GetDateTime(const SYSTEMTIME * pst, LPTSTR ptszDate, LPTSTR ptszTime)
  1360. {
  1361. SYSTEMTIME st;
  1362. if (pst == NULL)
  1363. {
  1364. GetLocalTime(&st);
  1365. pst = &st;
  1366. }
  1367. if (!GetDateFormat(LOCALE_USER_DEFAULT,
  1368. LOCALE_NOUSEROVERRIDE,
  1369. pst,
  1370. NULL,
  1371. ptszDate,
  1372. SCH_MEDBUF_LEN))
  1373. {
  1374. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1375. return FALSE;
  1376. }
  1377. if (!GetTimeFormat(LOCALE_USER_DEFAULT,
  1378. LOCALE_NOUSEROVERRIDE,
  1379. pst,
  1380. NULL,
  1381. ptszTime,
  1382. SCH_MEDBUF_LEN))
  1383. {
  1384. CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
  1385. return FALSE;
  1386. }
  1387. return TRUE;
  1388. }