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

//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1996.
//
// File: log.cxx
//
// Contents: Logging routines for the job scheduler service.
//
// Classes: None.
//
// Functions: OpenLogFile
// CloseLogFile
// LogTaskStatus
// LogTaskError
// LogServiceStatus
// LogServiceError
// ConstructStatusField
// ConstructResultField
// GetSchedulerResultCodeString
// OverwriteRecordFragment
// IntegerToString
//
// History: 1-Feb-96 MarkBl Created.
// 24-Oct-96 AnirudhS Modified to handle DBCS.
// 2-Feb-98 jschwart Modified to handle Unicode.
// 26-Feb-01 JBenton Prefix bug 294880
//
//----------------------------------------------------------------------------
#include "..\pch\headers.hxx"
#pragma hdrstop
#include <sddl.h>
#include "globals.hxx"
#include "svc_core.hxx"
#include "..\inc\resource.h"
#include "..\inc\common.hxx"
#define CCH_INT 11
#define ARRAY_LEN(a) (sizeof(a)/sizeof(a[0]))
TCHAR * ConstructStatusField(DWORD, SYSTEMTIME *, ULONG *);
TCHAR * ConstructResultField(DWORD, LPTSTR);
TCHAR * GetSchedulerResultCodeString(DWORD, DWORD);
TCHAR * IntegerToString(ULONG, TCHAR *);
VOID OverwriteRecordFragment(VOID);
VOID WriteLog(LPTSTR);
BOOL GetDateTime(const SYSTEMTIME *, LPTSTR, LPTSTR);
VOID LogServiceMessage(LPCTSTR, DWORD);
//
// Note, this buffer must be at least double the size of the ASCII string:
// "[ ***** Most recent entry is above this line ***** ]\r\n\r\n"
// to leave sufficient space for localization changes.
//
#define MOST_RECENT_ENTRY_MARKER_SIZE 128
static TCHAR gtszMostRecentEntryMarker[MOST_RECENT_ENTRY_MARKER_SIZE + 1] = TEXT("");
extern CStaticCritSec gcsLogCritSection;
HANDLE ghLog = NULL;
DWORD gdwMaxLogSizeKB = NULL;
DWORD gcbMostRecentEntryMarkerSize;
//+---------------------------------------------------------------------------
//
// Function: OpenLogFile
//
// Synopsis: Open the log file and position the global file pointer.
//
// Log file path/name can be specified in in the registry as:
// HKEY_LOCAL_MACHINE\Software\Microsoft\JobScheduler\LogPath.
// If this value is not specified, or we fail somehow fetching
// it, default to the log file name "SCHEDLGU.TXT" in the tasks folder.
//
// The log file handle is cached as a global.
//
// Arguments: None.
//
// Returns: HRESULT status code.
//
// Notes: ** Important Note **
//
// This function *must* be called *once* prior to log usage.
// This function should be called after g_hInstance has been
// initialized.
//
//----------------------------------------------------------------------------
HRESULT
OpenLogFile(VOID)
{
TCHAR tszBuffer[MAX_PATH + 1] = TEXT("\0");
DWORD cbBufferSize = sizeof(tszBuffer);
DWORD dwMaxLogSizeKB = MAX_LOG_SIZE_DEFAULT;
DWORD dwType;
HKEY hKey;
HRESULT hr = S_OK;
#define tszLogPath TEXT("LogPath")
#define tszMaxLogSizeKB TEXT("MaxLogSizeKB")
#define tszMarkerSentinel TEXT("[ *****")
#define MARKER_SENTINEL_LENGTH (ARRAY_LEN(tszMarkerSentinel) - 1)
#define READ_BUFFER_SIZE 512
//
// Even though this function should be called only once at service start up,
// protect it with a critsec just in case someone tries to start two or more
// instances of the scheduler service simultaneously
//
EnterCriticalSection(&gcsLogCritSection);
if (ghLog != NULL)
{
hr = E_FAIL;
schAssert(!"ghLog not NULL on entry to OpenLogFile -- attempted to run more than one instance of service?");
goto ErrorExit;
}
// Load the most recent entry marker string from the resource table.
// Set the size of the marker to the end of the string. Otherwise,
// the IsTextUnicode API (called by notepad) thinks this is Ansi.
//
gcbMostRecentEntryMarkerSize =
LoadString(g_hInstance,
IDS_MOSTRECENTLOGENTRYMARKER,
gtszMostRecentEntryMarker,
MOST_RECENT_ENTRY_MARKER_SIZE + 1);
if (!gcbMostRecentEntryMarkerSize)
{
hr = HRESULT_FROM_WIN32(GetLastError());
CHECK_HRESULT(hr);
goto ErrorExit;
}
// Convert to size in bytes
//
gcbMostRecentEntryMarkerSize *= sizeof(TCHAR);
// Read the log path and maximum size from the registry. Note that these
// are stored in the service's key.
//
if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE,
SCH_AGENT_KEY,
0,
KEY_READ | KEY_SET_VALUE,
&hKey))
{
if (RegQueryValueEx(hKey,
tszLogPath,
NULL,
&dwType,
(UCHAR *)tszBuffer,
&cbBufferSize) ||
(dwType != REG_SZ && dwType != REG_EXPAND_SZ))
{
//
// Value is missing; create it with default
//
RegSetValueEx(hKey,
tszLogPath,
NULL,
REG_EXPAND_SZ,
(const BYTE *)TSZ_LOG_NAME_DEFAULT,
sizeof(TSZ_LOG_NAME_DEFAULT));
// if this call fails, there isn't much we can do
// but at least we've defaulted to a reasonable value
// and can continue with logging
}
cbBufferSize = sizeof(dwMaxLogSizeKB);
if (RegQueryValueEx(hKey,
tszMaxLogSizeKB,
NULL,
&dwType,
(UCHAR *)&dwMaxLogSizeKB,
&cbBufferSize) || dwType != REG_DWORD)
{
//
// Value is missing; create it with default
//
dwMaxLogSizeKB = MAX_LOG_SIZE_DEFAULT;
RegSetValueEx(hKey,
tszMaxLogSizeKB,
NULL,
REG_DWORD,
(CONST BYTE *)&dwMaxLogSizeKB,
cbBufferSize);
// if this call fails, there isn't much we can do
// but at least we've defaulted to a reasonable value
// and can continue with logging
}
RegCloseKey(hKey);
}
// Default log path on error.
//
if (!tszBuffer[0])
{
StringCchCopy(tszBuffer, MAX_PATH + 1, TSZ_LOG_NAME_DEFAULT);
}
// Expand environment strings in the log path.
//
TCHAR tszFileName[MAX_PATH+1];
DWORD cch = ExpandEnvironmentStrings(tszBuffer,
tszFileName,
ARRAY_LEN(tszFileName));
if (cch == 0 || cch > ARRAY_LEN(tszFileName))
{
ERR_OUT("ExpandEnvironmentStrings", cch);
hr = E_OUTOFMEMORY;
goto ErrorExit;
}
//
// Obtain just the folder path from the full log file path
//
TCHAR tszLogFolderPath[MAX_PATH + 1];
StringCchCopy(tszLogFolderPath, MAX_PATH + 1, tszFileName);
TCHAR* ptszLastSlash = _tcsrchr(tszLogFolderPath, TEXT('\\'));
if (ptszLastSlash)
*ptszLastSlash = TEXT('\0');
DWORD cchLogFolderPath = _tcslen(tszLogFolderPath);
//
// Compare the log file path with the tasks folder path;
// The memory allocated by GetTasksFolder() must be deleted below.
//
TCHAR* ptszTasksFolder = NULL;
hr = GetTasksFolder(&ptszTasksFolder);
if (FAILED(hr))
{
goto ErrorExit;
}
DWORD cchTasksFolder = _tcslen(ptszTasksFolder);
if (cchLogFolderPath == cchTasksFolder && !_tcsnicmp(tszFileName, ptszTasksFolder, cchTasksFolder))
{
//
// The log file is to be stored under the tasks folder;
// if the tasks folder doesn't exist, create it and set security
//
hr = EnsureTasksFolderExists(ptszTasksFolder);
if (FAILED(hr))
{
goto ErrorExit;
}
}
else
{
//
// The log file is to be stored under a different folder;
// if the folder doesn't exist, create it and set security,
// but do not enable our shell extension on this folder
//
hr = EnsureTasksFolderExists(tszLogFolderPath, FALSE);
if (FAILED(hr))
{
goto ErrorExit;
}
}
// create an appropriate ACL
// note that it only takes effect if we're *creating* the file
// we're basically adding read access for authenticated users on PRO
//
PSECURITY_DESCRIPTOR pSD = NULL;
WCHAR* pwszSDDL = NULL;
OSVERSIONINFOEX verInfo;
verInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
if (!GetVersionEx((LPOSVERSIONINFOW)&verInfo))
return E_FAIL;
if (verInfo.wProductType == VER_NT_WORKSTATION)
{
pwszSDDL =
L"D:(A;;FA;;;CO)(A;;FR;;;AU)(A;;FA;;;BA)(A;;FA;;;SY)";
//
// generate SD to be used for tasks folder
//
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(pwszSDDL, SDDL_REVISION_1, &pSD, NULL))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
}
SECURITY_ATTRIBUTES sa;
sa.lpSecurityDescriptor = pSD;
sa.bInheritHandle = FALSE;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
// Create the file if it doesn't exist, open it if it does.
//
HANDLE hLog = CreateFile(tszFileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
(verInfo.wProductType == VER_NT_WORKSTATION) ? &sa : NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
NULL);
if (hLog == INVALID_HANDLE_VALUE)
{
// We're in a fine mess, bail.
//
hr = HRESULT_FROM_WIN32(GetLastError());
CHECK_HRESULT(hr);
schDebugOut((DEB_ERROR, "Attempted to create file \"" FMT_TSTR "\"\n", tszFileName));
goto ErrorExit;
}
TCHAR rgtBuffer[READ_BUFFER_SIZE / sizeof(TCHAR)];
DWORD dwRead;
DWORD iMarker = 0; // Scope the marker index such that the search may span multiple reads.
DWORD dwLoops = 0; // Keep track of how many successful reads we've made.
// Used to figure out whether to write the UNICODE
// byte order mark (BOM) at the top of the file
// Seek to the most recent entry marker. Do so by searching for the first
// several distinguishable characters of the marker - a sentinel.
//
for (;;)
{
// Save away current file position for later file pointer adjustment.
//
LARGE_INTEGER liLogPos;
liLogPos.QuadPart = 0;
if ((liLogPos.LowPart = SetFilePointer(hLog,
0,
&liLogPos.HighPart,
FILE_CURRENT)) == -1)
{
break;
}
if (!ReadFile(hLog, rgtBuffer, READ_BUFFER_SIZE, &dwRead, NULL) ||
!dwRead)
{
break;
}
// Convert to the number of characters (and chop off a stray byte
// if it exists in the Unicode case)
//
dwRead /= sizeof(TCHAR);
for (DWORD iBuffer = 0; iBuffer < dwRead; iBuffer++)
{
// If the first marker character is found, or the marker
// comparison is continued from the previous read, evaluate
// remaining marker string.
//
if (rgtBuffer[iBuffer] == TEXT('[') || iMarker)
{
for (; iMarker < MARKER_SENTINEL_LENGTH && dwRead - iBuffer;
iMarker++, iBuffer++)
{
if (rgtBuffer[iBuffer] != tszMarkerSentinel[iMarker])
{
break;
}
}
// If the marker is found, stop & re-position the file
// pointer for future writes.
//
if (iMarker == MARKER_SENTINEL_LENGTH)
{
// Adjust file pointer accordingly.
//
liLogPos.QuadPart += iBuffer * sizeof(TCHAR);
liLogPos.QuadPart -= MARKER_SENTINEL_LENGTH * sizeof(TCHAR);
if (SetFilePointer(hLog,
liLogPos.LowPart,
&liLogPos.HighPart,
FILE_BEGIN) != -1)
{
goto MarkerFound;
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
CHECK_HRESULT(hr);
}
}
else if (iMarker < MARKER_SENTINEL_LENGTH && dwRead - iBuffer)
{
// Almost a match, but not quite - reset for continued
// search.
//
iMarker = 0;
}
}
}
dwLoops++;
}
if (!dwLoops && !dwRead)
{
// We just created the file and it's empty, so write the Unicode BOM
//
DWORD cbWritten;
WCHAR wcBOM = 0xFEFF;
if (!WriteFile(hLog, &wcBOM, sizeof(WCHAR), &cbWritten, NULL) ||
!cbWritten)
{
// If we can't write to the log, we've got problems
//
CloseHandle(hLog);
hr = HRESULT_FROM_WIN32(GetLastError());
CHECK_HRESULT(hr);
goto ErrorExit;
}
}
// Marker not found. Seek to file end.
//
if (SetFilePointer(hLog, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
{
// Another fine mess, bail.
//
CloseHandle(hLog);
hr = HRESULT_FROM_WIN32(GetLastError());
CHECK_HRESULT(hr);
schAssert(!"Couldn't seek to log file end");
goto ErrorExit;
}
MarkerFound:
gdwMaxLogSizeKB = dwMaxLogSizeKB;
ghLog = hLog;
ErrorExit:
if (pSD)
LocalFree(pSD);
if (ptszTasksFolder)
{
delete [] ptszTasksFolder;
}
LeaveCriticalSection(&gcsLogCritSection);
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: CloseLogFile
//
// Synopsis: Close log file and invalidate global handle.
//
// Arguments: None.
//
// Returns: None.
//
// Notes: ** Important Note **
//
// Presumably, this function is called on process closure.
// Therefore, let the OS delete the critical section, *not* this
// thread. Otherwise, the critical section can be deleted out
// from under other threads currently accessing the log.
//
//----------------------------------------------------------------------------
VOID
CloseLogFile(VOID)
{
//
// If OpenLogFile has not completed successfully, the critical section
// won't have been initialized nor the global file handle set.
//
if (ghLog != NULL)
{
// Handle close gracefully in case another thread is accessing the log.
// Do so by entering the log critical section, closing the log and
// invalidating the global log handle (setting it to NULL).
//
EnterCriticalSection(&gcsLogCritSection);
if (ghLog)
{
CloseHandle(ghLog);
ghLog = NULL;
}
LeaveCriticalSection(&gcsLogCritSection);
}
}
//+---------------------------------------------------------------------------
//
// Function: LogTaskStatus
//
// Purpose: Log successful task operations.
//
// Arguments: [ptszTaskName] - the task name.
// [ptszTaskTarget] - the application/document name.
// [uMsgID] - this would typically be either:
// IDS_LOG_JOB_STATUS_STARTED or
// IDS_LOG_JOB_STATUS_FINISHED
// [dwExitCode] - if uMsgID is IDS_LOG_JOB_STATUS_FINISHED,
// it is the task exit code; ignored otherwise.
//
//----------------------------------------------------------------------------
VOID
LogTaskStatus(
LPCTSTR ptszTaskName,
LPTSTR ptszTaskTarget,
UINT uMsgID,
DWORD dwExitCode)
{
TCHAR tszMsgFormat[SCH_BIGBUF_LEN];
TCHAR * ptszStatusMsg = NULL;
ULONG ccSize;
//
// Add the date & time as inserts to the format string.
//
TCHAR tszDate[SCH_MEDBUF_LEN];
TCHAR tszTime[SCH_MEDBUF_LEN];
if (!GetDateTime(NULL, tszDate, tszTime))
{
return;
}
TCHAR * ptszResultField = NULL;
// Load the format string resource.
//
if (!LoadString(g_hInstance,
uMsgID,
tszMsgFormat,
ARRAY_LEN(tszMsgFormat)))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
return;
}
if (uMsgID == IDS_LOG_JOB_STATUS_FINISHED)
{
ptszResultField = ConstructResultField(dwExitCode, ptszTaskTarget);
if (ptszResultField == NULL)
{
return;
}
}
TCHAR * rgptszInserts[] = { (TCHAR *)ptszTaskName,
(TCHAR *)ptszTaskTarget,
tszDate,
tszTime,
ptszResultField };
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_ARGUMENT_ARRAY,
tszMsgFormat,
0,
0,
(TCHAR *)&ptszStatusMsg,
1,
(va_list *) rgptszInserts))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
if (ptszResultField != NULL)
{
LocalFree(ptszResultField);
}
return;
}
WriteLog(ptszStatusMsg);
LocalFree(ptszStatusMsg);
if (ptszResultField != NULL)
{
LocalFree(ptszResultField);
}
}
//+---------------------------------------------------------------------------
//
// Function: LogTaskError
//
// Purpose: Log task warnings and errors.
//
// Arguments: [ptszTaskName] - the task name.
// [ptszTaskTarget] - the application/document name.
// [uSeverityMsgID] - this would typically be either:
// IDS_LOG_SEVERITY_WARNING or
// IDS_LOG_SEVERITY_ERROR
// [uErrorClassMsgID] - this indicates the class of error, such
// as "Unable to start task" or "Forced to
// close"
// [pst] - the time when the error occured; if NULL,
// enters the current time.
// [dwErrorCode] - if non-zero, then an error from the OS
// that would be expanded by FormatMessage.
// [uHelpHintMsgID] - if an error, then a suggestion as to a
// possible remedy.
//
//----------------------------------------------------------------------------
VOID
LogTaskError(
LPCTSTR ptszTaskName,
LPCTSTR ptszTaskTarget,
UINT uSeverityMsgID,
UINT uErrorClassMsgID,
LPSYSTEMTIME pst,
DWORD dwErrCode,
UINT uHelpHintMsgID)
{
TCHAR tszEmpty[] = TEXT("");
//
// Verify params:
//
if (ptszTaskName == NULL)
{
ptszTaskName = tszEmpty;
}
if (ptszTaskTarget == NULL)
{
ptszTaskTarget = tszEmpty;
}
TCHAR tszFormat[SCH_BUF_LEN];
//
// Compose the first part of the error log entry:
// "<task name>" (<task target>) <time> ** [WARNING | ERROR] **
//
if (!LoadString(g_hInstance,
uSeverityMsgID,
tszFormat,
SCH_BUF_LEN))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
return;
}
//
// Add the date & time as inserts to the format string.
//
TCHAR tszDate[SCH_MEDBUF_LEN];
TCHAR tszTime[SCH_MEDBUF_LEN];
if (!GetDateTime(pst, tszDate, tszTime))
{
return;
}
//
// Obtain the error message string.
//
LPTSTR ptszErrMsg = ComposeErrorMsg(uErrorClassMsgID,
dwErrCode,
uHelpHintMsgID);
if (ptszErrMsg == NULL)
{
return;
}
//
// Glue the whole mess together.
//
TCHAR * rgptszInserts[] = { (TCHAR *)ptszTaskName,
(TCHAR *)ptszTaskTarget,
tszDate,
tszTime,
ptszErrMsg };
TCHAR * ptszLogStr;
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_ARGUMENT_ARRAY,
tszFormat,
0,
0,
(TCHAR *)&ptszLogStr,
1,
(va_list *) rgptszInserts))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
LocalFree(ptszErrMsg);
return;
}
WriteLog(ptszLogStr);
LocalFree(ptszErrMsg);
LocalFree(ptszLogStr);
}
//+---------------------------------------------------------------------------
//
// Function: LogServiceError
//
// Purpose: Log service failures.
//
// Arguments: [uErrorClassMsgID] - as above.
// [dwErrCode] - as above.
// [uHelpHintMsgID] - as above.
//
//----------------------------------------------------------------------------
VOID
LogServiceError(
UINT uErrorClassMsgID,
DWORD dwErrCode,
UINT uHelpHintMsgID)
{
TCHAR tszSvcErrMsgFormat[SCH_MEDBUF_LEN];
if (LoadString(g_hInstance,
IDS_LOG_SERVICE_ERROR,
tszSvcErrMsgFormat,
SCH_MEDBUF_LEN) == 0)
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
//
// Generic error message if things are really foobared.
//
StringCchCopy(tszSvcErrMsgFormat, SCH_MEDBUF_LEN,
TEXT("\042Task Scheduler Service\042 ** FATAL ERROR **\n"));
WriteLog(tszSvcErrMsgFormat);
return;
}
//
// Add the date & time as inserts to the format string.
//
TCHAR tszDate[SCH_MEDBUF_LEN];
TCHAR tszTime[SCH_MEDBUF_LEN];
if (!GetDateTime(NULL, tszDate, tszTime))
{
return;
}
LPTSTR ptszErrMsg = ComposeErrorMsg(uErrorClassMsgID,
dwErrCode,
uHelpHintMsgID);
if (ptszErrMsg == NULL)
{
return;
}
TCHAR * rgptszInserts[] = {tszDate, tszTime, ptszErrMsg};
TCHAR * ptszLogStr;
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_ARGUMENT_ARRAY,
tszSvcErrMsgFormat,
0,
0,
(TCHAR *)&ptszLogStr,
1,
(va_list *) rgptszInserts))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
LocalFree(ptszErrMsg);
return;
}
WriteLog(ptszLogStr);
LocalFree(ptszErrMsg);
LocalFree(ptszLogStr);
}
//+---------------------------------------------------------------------------
//
// Function: LogServiceEvent
//
// Synopsis: Write the service event to the log file.
//
// Purpose: Note the starting, stopping, pausing, and continuing of the
// service.
//
// Arguments: [uStrId] - a string identifying the event.
//
//----------------------------------------------------------------------------
VOID
LogServiceEvent(UINT uStrId)
{
TCHAR * ptszSvcMsg;
ULONG cbMsgSize;
SYSTEMTIME st;
GetLocalTime(&st);
ptszSvcMsg = ConstructStatusField(uStrId, &st, &cbMsgSize);
if( NULL == ptszSvcMsg )
{
schDebugOut((DEB_ITRACE, "LogServiceEvent - ConstructStatusField(uStrId, &st, &cbMsgSize) failed!\n"));
return;
}
LogServiceMessage(ptszSvcMsg, cbMsgSize);
LocalFree(ptszSvcMsg);
}
//+---------------------------------------------------------------------------
//
// Function: LogMissedRuns
//
// Synopsis: Write details about missed runs to the log file.
//
// Arguments: [pstLastRun], [pstNow] - times between which runs were missed.
//
//----------------------------------------------------------------------------
VOID
LogMissedRuns(const SYSTEMTIME * pstLastRun, const SYSTEMTIME * pstNow)
{
TCHAR tszLastRunDate[SCH_MEDBUF_LEN];
TCHAR tszLastRunTime[SCH_MEDBUF_LEN];
TCHAR tszNowDate [SCH_MEDBUF_LEN];
TCHAR tszNowTime [SCH_MEDBUF_LEN];
if (!GetDateTime(pstLastRun, tszLastRunDate, tszLastRunTime) ||
!GetDateTime(pstNow, tszNowDate, tszNowTime))
{
return;
}
TCHAR tszMsgFormat[SCH_BIGBUF_LEN];
if (!LoadString(g_hInstance,
IDS_LOG_RUNS_MISSED,
tszMsgFormat,
SCH_BIGBUF_LEN))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
return;
}
TCHAR * rgptszInserts[] = { tszLastRunDate, tszLastRunTime,
tszNowDate, tszNowTime };
TCHAR * ptszLogStr;
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_ARGUMENT_ARRAY,
tszMsgFormat,
0,
0,
(TCHAR *)&ptszLogStr,
1,
(va_list *) rgptszInserts))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
return;
}
LogServiceMessage(ptszLogStr, (lstrlen(ptszLogStr) + 1) * sizeof(TCHAR));
LocalFree(ptszLogStr);
}
//+---------------------------------------------------------------------------
//
// Function: LogServiceMessage
//
// Synopsis: Write a generic service message to the log file.
//
// Purpose: Used by LogServiceEvent and LogMissedRuns.
//
// Arguments: [ptszStrMsg] - a string message.
// [cbStrMsg] - size of pszStrMsg in bytes (may be overestimated,
// used only to calculate size of intermediate buffer.)
//
//----------------------------------------------------------------------------
VOID
LogServiceMessage(LPCTSTR ptszStrMsg, DWORD cbStrMsg)
{
size_t cchMsg = SCH_MEDBUF_LEN + (cbStrMsg / sizeof(TCHAR)) + 1;
TCHAR * ptszMsg = (TCHAR *)LocalAlloc(LPTR, cchMsg * sizeof(TCHAR));
if (ptszMsg == NULL)
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
return;
}
if (LoadString(g_hInstance,
IDS_LOG_SERVICE_TITLE,
ptszMsg,
SCH_MEDBUF_LEN) == 0)
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
//
// Generic error message if things are really foobared.
//
StringCchCopy(ptszMsg, cchMsg, TEXT("\042Task Scheduler Service\042 ** ERROR **\n"));
}
if (ptszStrMsg != NULL)
{
StringCchCat(ptszMsg, cchMsg, ptszStrMsg);
}
WriteLog(ptszMsg);
LocalFree(ptszMsg);
}
//+---------------------------------------------------------------------------
//
// Function: WriteLog
//
// Synopsis: Write the string to the log file.
//
//----------------------------------------------------------------------------
VOID
WriteLog(LPTSTR ptsz)
{
LARGE_INTEGER liCurLogSize, liMaxLogSize;
DWORD cbWritten;
ULONG cbStringSize = lstrlen(ptsz) * sizeof(TCHAR);
ULONG cbDataSize = cbStringSize;
EnterCriticalSection(&gcsLogCritSection);
schDebugOut((DEB_TRACE, "LOG:\n " FMT_TSTR "", ptsz));
// Lose some time here by not caching this value, but not much.
//
cbDataSize += lstrlen(gtszMostRecentEntryMarker) * sizeof(TCHAR);
// Get the current log size to see if there is room to write this.
//
liCurLogSize.QuadPart = 0;
if ((liCurLogSize.LowPart = SetFilePointer(ghLog,
0,
&liCurLogSize.HighPart,
FILE_CURRENT)) == -1)
{
goto ErrorExit_A;
}
// Add current data size. Convert maximum size to bytes for comparison.
//
liCurLogSize.QuadPart += cbDataSize;
liMaxLogSize.QuadPart = gdwMaxLogSizeKB * 1024;
// Is there sufficient space to write the entry?
//
if (liCurLogSize.QuadPart > liMaxLogSize.QuadPart)
{
// No, adjust the end of file to eliminate the most recent entry
// marker & wrap to beginning.
//
SetEndOfFile(ghLog); // Ignore return code.
// skip the BOM
//
if (SetFilePointer(ghLog, sizeof(WCHAR), NULL, FILE_BEGIN) == -1)
{
// Seek failure
//
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
schAssert(!"Couldn't seek log file");
goto ErrorExit_A;
}
}
// Write the string.
//
if (!WriteFile(ghLog, ptsz, cbStringSize, &cbWritten, NULL))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
goto ErrorExit_A;
}
// Write most recent entry marker.
//
// First, save the current file pointer position. This will be the
// starting location of the next log write. Note: double-timing current
// log size local since it is no longer used.
//
liCurLogSize.QuadPart = 0;
if ((liCurLogSize.LowPart = SetFilePointer(ghLog,
0,
&liCurLogSize.HighPart,
FILE_CURRENT)) == -1)
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
goto ErrorExit_A;
}
if (!WriteFile(ghLog,
gtszMostRecentEntryMarker,
gcbMostRecentEntryMarkerSize,
&cbWritten,
NULL))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
goto ErrorExit_A;
}
// If the log has wrapped, it's likely the write pointer is positioned
// somewhere in the middle of the next record. If this is the case,
// the remaining partial record must be overwritten with spaces.
//
OverwriteRecordFragment();
// Restore log position for next write.
//
if (SetFilePointer(ghLog,
liCurLogSize.LowPart,
&liCurLogSize.HighPart,
FILE_BEGIN) == -1)
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
}
ErrorExit_A:
LeaveCriticalSection(&gcsLogCritSection);
}
//+---------------------------------------------------------------------------
//
// Function: OverwriteRecordFragment
//
// Synopsis: If the log has wrapped, the last write most likely has
// partially overwritten a record. This routine overwrites such
// record fragments with spaces up to the next record.
//
// Fundamental assumption - how a record is designated: The start
// of a new record is designated by the two characters \n". This
// routine simply fills text with spaces up to but not including
// this character sequence.
//
// Arguments: None.
//
// Returns: N/A
//
// Notes: Upon exit, the log file pointer is restored to its original
// position on entry.
//
//----------------------------------------------------------------------------
VOID
OverwriteRecordFragment(VOID)
{
TCHAR rgtBuffer[READ_BUFFER_SIZE / sizeof(TCHAR)];
LARGE_INTEGER liSavedLogPos, liLogPos;
DWORD dwRead;
// Save file pointer position during read for subsequent write.
//
liSavedLogPos.QuadPart = 0;
if ((liSavedLogPos.LowPart = SetFilePointer(ghLog,
0,
&liSavedLogPos.HighPart,
FILE_CURRENT)) == -1)
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
return;
}
// From the previous write, the last character is a line feed.
//
TCHAR tchPrev = TEXT('\n');
TCHAR tchCur;
int cbOverwrite = 0;
DWORD i = 0; // Index of chCur in rgbBuffer
for (;;)
{
if (!ReadFile(ghLog, rgtBuffer, READ_BUFFER_SIZE, &dwRead, NULL) ||
!dwRead)
{
break;
}
// Convert to the number of characters (and chop off a stray byte
// if it exists in the Unicode case)
//
dwRead /= sizeof(TCHAR);
for ( ; i < dwRead; i++, cbOverwrite += sizeof(TCHAR))
{
tchCur = rgtBuffer[i];
if (tchPrev == TEXT('\n') && tchCur == TEXT('"') && cbOverwrite > 2 * sizeof(TCHAR))
{
break;
}
tchPrev = tchCur;
}
if (i < dwRead)
{
// We found the \n" character sequence. Don't
// overwrite the \r\n" sequence of the next record.
//
cbOverwrite -= 2 * sizeof(TCHAR);
break;
}
i = 0;
}
DWORD cbWritten;
// Overwrite record fragment with spaces.
//
if (cbOverwrite > 0)
{
// Adjust file pointer from read above.
//
if (SetFilePointer(ghLog,
liSavedLogPos.LowPart,
&liSavedLogPos.HighPart,
FILE_BEGIN) != -1)
{
for (UINT uCount = 0;
uCount < READ_BUFFER_SIZE / sizeof(TCHAR);
uCount++)
{
rgtBuffer[uCount] = TEXT(' ');
}
while (cbOverwrite > 0)
{
if (!WriteFile(ghLog,
rgtBuffer,
min(cbOverwrite, READ_BUFFER_SIZE),
&cbWritten,
NULL))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
break;
}
cbOverwrite -= cbWritten;
}
}
else
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
}
}
}
//+---------------------------------------------------------------------------
//
// Function: ConstructStatusField
//
// Synopsis: Retrieve the status field with an optional status timestamp
// insert.
//
// Arguments: [dwStatusFieldMsgID] -- Status field format string msg id.
// [pstStatusTime] -- Optional status timestamp. If NULL,
// no timestamp is written.
// [pcbSize] -- Returned status field size (bytes).
//
// Returns: TCHAR * status field
// NULL on error
//
// Notes: FormatMessage allocates the return string. Use LocalFree() to
// deallocate.
//
//----------------------------------------------------------------------------
TCHAR *
ConstructStatusField(
DWORD dwStatusFieldMsgID,
SYSTEMTIME * pstStatusTime,
ULONG * pcbSize)
{
// Note: Insure string buffer sizes are at least double the size of the
// largest string they'll contain, for localization reasons.
//
TCHAR tszStatusFieldFormat[SCH_BIGBUF_LEN];
TCHAR tszDate[SCH_MEDBUF_LEN];
TCHAR tszTime[SCH_MEDBUF_LEN];
TCHAR * rgptszInserts[] = { tszDate, tszTime };
TCHAR * ptszStatusField = NULL;
// The status field may/may not contain a date & time. The first
// branch is taken for status fields containing them.
//
if (pstStatusTime != NULL)
{
// Add the date & time as inserts to the status field format string.
//
if (!GetDateTime(pstStatusTime, tszDate, tszTime))
{
return(NULL);
}
}
ULONG ccSize = 0;
// Load the status field format string resource.
//
if (LoadString(g_hInstance,
dwStatusFieldMsgID,
tszStatusFieldFormat,
SCH_BIGBUF_LEN))
{
if (!(ccSize = FormatMessage(FORMAT_MESSAGE_FROM_STRING |
FORMAT_MESSAGE_ALLOCATE_BUFFER |
(pstStatusTime != NULL ? FORMAT_MESSAGE_ARGUMENT_ARRAY : 0),
tszStatusFieldFormat,
0,
0,
(TCHAR *)&ptszStatusField,
1,
(pstStatusTime != NULL ? (va_list *) rgptszInserts : NULL))))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
}
}
else
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
}
*pcbSize = ccSize * sizeof(TCHAR);
return(ptszStatusField);
}
//+---------------------------------------------------------------------------
//
// Function: ConstructResultField
//
// Synopsis: Retrieve the result field. Algorithm:
//
// The result code is the job's exit code. Utilize the following
// algorithm to obtain the exit code string:
//
// Attempt to fetch the the exit code string as a
// message binary specified in the Job Scheduler portion of
// the registry.
//
// If this fails, produce a default
// "message not found for exit code (n)" message.
//
// Insert the result string obtained above as an insert string
// to the result field format string.
//
// Arguments: [dwResultCode] -- Result field result code.
// [ptszJobExecutable] -- Binary name executed by the job.
//
// Returns: TCHAR * result field
// NULL on error
//
// Notes: FormatMessage allocates the return string. Use LocalFree() to
// deallocate.
//
//----------------------------------------------------------------------------
TCHAR *
ConstructResultField(
DWORD dwResultCode,
LPTSTR ptszJobExecutable)
{
// Note: Insure format string buffer size is at least double the size of
// the largest string it will contain, for localization reasons.
//
TCHAR tszResultFieldFormat[SCH_MEDBUF_LEN];
TCHAR tszResultCodeValue[CCH_INT + 1];
TCHAR * ptszResultField = NULL;
TCHAR * ptszResult;
IntegerToString(dwResultCode, tszResultCodeValue);
// Job exit code. Fetch the exit code string from the
// ExitCodeMessageFile associated with the job program.
//
if ((ptszResult = GetExitCodeString(dwResultCode,
tszResultCodeValue,
(TCHAR *)ptszJobExecutable)) == NULL)
{
// Produce a default "message not found" result string.
//
ptszResult = GetSchedulerResultCodeString(
IDS_LOG_EXIT_CODE_MSG_NOT_FOUND,
dwResultCode);
}
ULONG ccSize = 0;
// Load the result field format string resource.
//
if (ptszResult != NULL)
{
if (LoadString(g_hInstance,
IDS_LOG_JOB_RESULT_FINISHED,
tszResultFieldFormat,
SCH_MEDBUF_LEN))
{
TCHAR * rgtszInserts[] = { ptszResult, tszResultCodeValue };
if (!FormatMessage(FORMAT_MESSAGE_FROM_STRING |
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_ARGUMENT_ARRAY,
tszResultFieldFormat,
0,
0,
(TCHAR *)&ptszResultField,
1,
(va_list *) rgtszInserts))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
}
LocalFree(ptszResult); // pszResultField now encapsulates
// this string.
}
else
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
}
}
return(ptszResultField);
}
//+---------------------------------------------------------------------------
//
// Function: GetSchedulerResultCodeString
//
// Synopsis: Fetch the result code from the schedule service process.
//
// Arguments: [dwResultMsgID] -- Result message string ID.
// [dwResultCode] -- result code.
//
// Returns: TCHAR * result code string
// NULL on error
//
// Notes: FormatMessage allocates the return string. Use LocalFree() to
// deallocate.
//
//----------------------------------------------------------------------------
TCHAR *
GetSchedulerResultCodeString(
DWORD dwResultMsgID,
DWORD dwResultCode)
{
TCHAR tszResultCodeValue[SCH_SMBUF_LEN];
TCHAR tszErrMsg[SCH_MEDBUF_LEN];
TCHAR * ptszErrMsg = NULL, * ptszResultCode = NULL;
DWORD ccLength;
TCHAR * rgtszInserts[] = { tszResultCodeValue, ptszErrMsg };
IntegerToString(dwResultCode, tszResultCodeValue);
TCHAR tszMsgBuf[MAX_PATH], * ptsz;
if (LoadString(g_hInstance, dwResultMsgID, tszMsgBuf, MAX_PATH) == 0)
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
return NULL;
}
if (dwResultCode != 0)
{
BOOL fDelete = FALSE;
//
// Try to obtain an error message from the system.
//
if (!(ccLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL,
dwResultCode,
LOCALE_SYSTEM_DEFAULT,
(TCHAR *)&ptszErrMsg,
1,
NULL)))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
if (!LoadString(g_hInstance,
IDS_GENERIC_ERROR_MSG,
tszErrMsg,
SCH_MEDBUF_LEN))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
return NULL;
}
ptszErrMsg = tszErrMsg;
}
else
{
fDelete = TRUE;
//
// Overwrite \r\n with a null characters.
//
ptsz = ptszErrMsg + ccLength - 2;
*ptsz++ = TEXT('\0');
*ptsz = TEXT('\0');
}
if (!(ccLength = FormatMessage(FORMAT_MESSAGE_FROM_STRING |
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_ARGUMENT_ARRAY,
tszMsgBuf,
0,
0,
(TCHAR *)&ptszResultCode,
2,
(va_list *) rgtszInserts)))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
if (fDelete) LocalFree(ptszErrMsg);
return NULL;
}
if (fDelete) LocalFree(ptszErrMsg);
}
else
{
//
// No result code. All of the info is encapsulated in dwResultMsgID,
// which has no inserts.
//
if (!(ccLength = FormatMessage(FORMAT_MESSAGE_FROM_STRING |
FORMAT_MESSAGE_ALLOCATE_BUFFER,
tszMsgBuf,
0,
0,
(TCHAR *)&ptszResultCode,
1,
NULL)))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
return NULL;
}
}
return(ptszResultCode);
}
//+---------------------------------------------------------------------------
//
// Function: IntegerToString
//
// Synopsis: Converts a 32 bit integer to a string.
//
// Arguments: [n] -- Converted int.
// [ptszBuf] -- Caller allocated buffer.
//
// Returns: Buffer ptr passed.
//
// Notes: None.
//
//----------------------------------------------------------------------------
TCHAR *
IntegerToString(ULONG n, TCHAR * tszBuf)
{
//
// Assemble hex representation into passed buffer, reversed,
// then reverse in-place into correct format
//
// This code deliberately eschews ultoa, since div and mod 16
// optimize so very nicely.
//
UINT ich = 0;
do
{
UINT nDigitValue = (UINT)(n % 16);
n /= 16;
if (nDigitValue > 9)
{
tszBuf[ich++] = (WCHAR)nDigitValue - 10 + TEXT('a');
}
else
{
tszBuf[ich++] = (WCHAR)nDigitValue + TEXT('0');
}
} while (n > 0);
tszBuf[ich] = TEXT('\0');
_tcsrev(tszBuf);
return(tszBuf);
}
//+---------------------------------------------------------------------------
//
// Function: GetDateTime
//
// Synopsis: Formats the date and time.
//
// Arguments: [pst] - The time to use; if NULL, then the current time
// is obtained.
// [ptszDate] - The date string buffer.
// [ptszTime] - The time string buffer.
//
// Returns: TRUE for success, FALSE for failure.
//
// Notes: Note that the buffers must be at least SCH_MEDBUF_LEN in size.
//
//----------------------------------------------------------------------------
BOOL
GetDateTime(const SYSTEMTIME * pst, LPTSTR ptszDate, LPTSTR ptszTime)
{
SYSTEMTIME st;
if (pst == NULL)
{
GetLocalTime(&st);
pst = &st;
}
if (!GetDateFormat(LOCALE_USER_DEFAULT,
LOCALE_NOUSEROVERRIDE,
pst,
NULL,
ptszDate,
SCH_MEDBUF_LEN))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
return FALSE;
}
if (!GetTimeFormat(LOCALE_USER_DEFAULT,
LOCALE_NOUSEROVERRIDE,
pst,
NULL,
ptszTime,
SCH_MEDBUF_LEN))
{
CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError()));
return FALSE;
}
return TRUE;
}