Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

1060 lines
28 KiB

/*++
Copyright (c) 1992 Microsoft Corporation
Module Name:
atmain.c
Abstract:
This is the main routine for the NT LAN Manager Schedule service.
Author:
Vladimir Z. Vulovic (vladimv) 06 - November - 1992
Environment:
User Mode - Win32
Revision History:
06-Nov-1992 vladimv
Created
--*/
#define ATDATA_ALLOCATE
#include "at.h"
#undef ATDATA_ALLOCATE
#include <atnames.h> // AT_INTERFACE_NAME
#include <winuser.h> // for winuserp.h
#include <wingdi.h>
#include <..\..\..\..\windows\inc\winuserp.h> // for STARTF_DESKTOPINHERIT
typedef enum _AT_ERROR_CONDITION {
AtErrorRegisterControlHandler = 0,
AtErrorCreateEvent,
AtErrorNotifyServiceController,
AtErrorStartRpcServer,
AtErrorCreateSecurityObject,
AtErrorMakeDataFromRegistry
} AT_ERROR_CONDITION, *PAT_ERROR_CONDITION;
DBGSTATIC NET_API_STATUS AtUpdateStatus( VOID)
/*++
Routine Description:
This function updates the Schedule service status with the Service
Controller.
Arguments:
None.
Return Value:
NET_API_STATUS - NERR_Success or reason for failure.
--*/
{
NET_API_STATUS status = NERR_Success;
if (AtGlobalServiceStatusHandle == (SERVICE_STATUS_HANDLE) NULL) {
AtLog(( AT_DEBUG_MAIN, "Cannot call SetServiceStatus, no status handle.\n"));
return( ERROR_INVALID_HANDLE);
}
if ( ! SetServiceStatus( AtGlobalServiceStatusHandle, &AtGlobalServiceStatus)) {
status = GetLastError();
AtLog(( AT_DEBUG_MAIN, "SetServiceStatus error %lu\n", status));
}
return( status);
}
DBGSTATIC VOID AtShutdown(
IN NET_API_STATUS ErrorCode,
IN DWORD initState
)
/*++
Routine Description:
This function shuts down the Schedule service.
Arguments:
ErrorCode - Supplies the error code of the failure
initState - Supplies a flag to indicate how far we got with initializing
the Schedule service before an error occured, thus the amount of
clean up needed.
Return Value:
None.
Note:
BUGBUG There is a possible race condition between main thread shutting
BUGBUG down & some of the rpc-api thread trying to complete their work.
--*/
{
//
// Service stop still pending. Update checkpoint counter and the
// status with the Service Controller.
//
(AtGlobalServiceStatus.dwCheckPoint)++;
(VOID) AtUpdateStatus();
if ( initState & AT_RPC_SERVER_STARTED) {
//
// Stop the RPC server
//
NetpStopRpcServer( atsvc_ServerIfHandle);
}
if ( initState & AT_SECURITY_OBJECT_CREATED) {
//
// Destroy schedule security object
//
AtDeleteSecurityObject();
}
//
// Service stop still pending. Update checkpoint counter and the
// status with the Service Controller.
//
(AtGlobalServiceStatus.dwCheckPoint)++;
(VOID) AtUpdateStatus();
if ( initState & AT_EVENT_CREATED) {
//
// Close handle to the event. Note that we do not need to signal
// the event. We assume AtShutdown() will be called from the main
// schedule service thread and only when it is safe - i.e. when
// main service thread is outside its main loop. This avoids some
// ugly competition between main thread & shutdown.
//
CloseHandle( AtGlobalEvent);
}
//
// We are done with cleaning up. Tell Service Controller that we are
// stopped.
//
AtGlobalServiceStatus.dwCurrentState = SERVICE_STOPPED;
AtGlobalServiceStatus.dwControlsAccepted = 0;
SET_SERVICE_EXITCODE(
ErrorCode,
AtGlobalServiceStatus.dwWin32ExitCode,
AtGlobalServiceStatus.dwServiceSpecificExitCode
);
AtGlobalServiceStatus.dwCheckPoint = 0;
AtGlobalServiceStatus.dwWaitHint = 0;
(VOID) AtUpdateStatus();
}
DBGSTATIC VOID AtHandleError(
IN AT_ERROR_CONDITION failingCondition,
IN NET_API_STATUS status,
IN DWORD initState
)
/*++
Routine Description:
This function handles a Schedule service error condition. If the error
condition is fatal, it shuts down the Schedule service.
Arguments:
failingCondition - Supplies a value which indicates what the failure is.
status - Supplies the status code for the failure.
initState - Supplies a flag to indicate how far we got with initializing
the Schedule service before an error occured, thus the amount of
clean up needed.
Return Value:
None.
--*/
{
#ifdef AT_DEBUG
PCHAR string;
switch (failingCondition) {
case AtErrorRegisterControlHandler:
string = "Cannot register control handler";
break;
case AtErrorCreateEvent:
string = "Cannot create event";
break;
case AtErrorNotifyServiceController:
string = "SetServiceStatus error";
break;
case AtErrorStartRpcServer:
string = "Cannot start RPC server";
break;
case AtErrorCreateSecurityObject:
string = "Error in creating security object";
break;
case AtErrorMakeDataFromRegistry:
string = "Severe error while initializing from registry";
break;
default:
string = "Unknown error condition %lu\n";
NetpAssert(FALSE);
}
NetpKdPrint((
"[Job] %s " FORMAT_API_STATUS "\n",
string,
status
));
#endif // AT_DEBUG
AtShutdown( status, initState);
}
VOID AtControlHandler( IN DWORD Opcode)
/*++
Routine Description:
This is the service control handler of the Schedule service.
Arguments:
Opcode - Supplies a value which specifies the action for the Schedule
service to perform.
Return Value:
None.
Comments:
Note that AtGlobalCriticalSection is used to protect changes both to
AtGlobalTasks & AtGlobalServiceStatus.dwCurrentState.
--*/
{
AtLog(( AT_DEBUG_MAIN, "In Control Handler\n"));
switch (Opcode) {
case SERVICE_CONTROL_PAUSE:
// Take critical section and set status so that no new requests
// can be submitted. Current requests will still be honored.
// Then release critical seciton.
EnterCriticalSection( &AtGlobalCriticalSection);
AtGlobalServiceStatus.dwCurrentState = SERVICE_PAUSED;
LeaveCriticalSection( &AtGlobalCriticalSection);
break;
case SERVICE_CONTROL_CONTINUE:
// Take critical section and set status so that new requests
// can be submitted. Then release critical seciton.
EnterCriticalSection( &AtGlobalCriticalSection);
AtGlobalServiceStatus.dwCurrentState = SERVICE_RUNNING;
LeaveCriticalSection( &AtGlobalCriticalSection);
break;
case SERVICE_CONTROL_STOP:
if (AtGlobalServiceStatus.dwCurrentState != SERVICE_STOP_PENDING) {
AtLog(( AT_DEBUG_MAIN, "Stopping schedule service...\n"));
AtGlobalServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
AtGlobalServiceStatus.dwCheckPoint = 1;
AtGlobalServiceStatus.dwWaitHint = AT_WAIT_HINT_TIME;
//
// Send the status response.
//
(VOID) AtUpdateStatus();
EnterCriticalSection( &AtGlobalCriticalSection);
AtGlobalTasks |= AT_SERVICE_SHUTDOWN;
LeaveCriticalSection( &AtGlobalCriticalSection);
if (! SetEvent( AtGlobalEvent)) {
AtLog(( AT_DEBUG_CRITICAL, "Error setting the event 0x%x\n", GetLastError()));
AtAssert( FALSE);
}
return;
}
break;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
AtLog(( AT_DEBUG_CRITICAL, "Unknown schedule service opcode 0x%x\n", Opcode));
break;
}
//
// Send the status response.
//
(VOID) AtUpdateStatus();
}
DBGSTATIC NET_API_STATUS AtInitialize(
IN PAT_TIME pTime,
OUT LPDWORD pInitState
)
/*++
Routine Description:
This function initializes the Schedule service.
Arguments:
pInitState - Returns a flag to indicate how far we got with initializing
the Schedule service before an error occured.
Return Value:
NET_API_STATUS - NERR_Success or reason for failure.
--*/
{
NET_API_STATUS status;
//
// Initialize all the status fields so that subsequent calls to
// SetServiceStatus need to only update fields that changed.
//
AtGlobalServiceStatus.dwServiceType = SERVICE_WIN32;
AtGlobalServiceStatus.dwCurrentState = SERVICE_START_PENDING;
AtGlobalServiceStatus.dwControlsAccepted = 0;
AtGlobalServiceStatus.dwCheckPoint = 1;
AtGlobalServiceStatus.dwWaitHint = AT_WAIT_HINT_TIME;
SET_SERVICE_EXITCODE(
NO_ERROR,
AtGlobalServiceStatus.dwWin32ExitCode,
AtGlobalServiceStatus.dwServiceSpecificExitCode
);
//
// Initialize schedule service to receive service requests by registering
// the control handler.
//
if ((AtGlobalServiceStatusHandle = RegisterServiceCtrlHandler(
SERVICE_SCHEDULE,
AtControlHandler
)) == (SERVICE_STATUS_HANDLE) NULL) {
status = GetLastError();
AtHandleError( AtErrorRegisterControlHandler, status, *pInitState);
return( status);
}
//
// Read the registry path & build queue of things to do.
//
status = AtMakeDataFromRegistry( pTime);
if ( status != NERR_Success) {
AtHandleError( AtErrorMakeDataFromRegistry, status, *pInitState);
return( status);
}
(*pInitState) |= AT_QUEUES_CREATED;
AtGlobalPermitServerOperators = AtPermitServerOperators();
//
// Create the only event used by schedule service.
//
if ((AtGlobalEvent =
CreateEvent(
NULL, // Event attributes
FALSE, // Event will be automaticallly reset
FALSE,
NULL // Initial state not signalled
)) == NULL) {
status = GetLastError();
AtHandleError( AtErrorCreateEvent, status, *pInitState);
return( status);
}
(*pInitState) |= AT_EVENT_CREATED;
//
// Notify the Service Controller for the first time that we are alive
// and we are start pending
//
if ((status = AtUpdateStatus()) != NERR_Success) {
AtHandleError( AtErrorNotifyServiceController, status, *pInitState);
return( status);
}
AtSetEnvironment( &AtGlobalStartupInfo);
//
// Create Schedule service security object
//
if ((status = AtCreateSecurityObject()) != NERR_Success) {
AtHandleError( AtErrorCreateSecurityObject, status, *pInitState);
return( status);
}
(*pInitState) |= AT_SECURITY_OBJECT_CREATED;
//
// Initialize the schedule service to receive RPC requests.
// Note that interface name is defined in atsvc.idl file.
//
if ((status = NetpStartRpcServer(
AT_INTERFACE_NAME,
atsvc_ServerIfHandle
)) != NERR_Success) {
AtHandleError( AtErrorStartRpcServer, status, *pInitState);
return( status);
}
(*pInitState) |= AT_RPC_SERVER_STARTED;
//
// We are done with starting the Schedule service. Tell Service
// Controller our new status.
//
AtGlobalServiceStatus.dwCurrentState = SERVICE_RUNNING;
AtGlobalServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_PAUSE_CONTINUE;
AtGlobalServiceStatus.dwCheckPoint = 0;
AtGlobalServiceStatus.dwWaitHint = 0;
if ((status = AtUpdateStatus()) != NERR_Success) {
AtHandleError( AtErrorNotifyServiceController, status, *pInitState);
return( status);
}
AtLog(( AT_DEBUG_MAIN, "Successful Initialization\n"));
return( NERR_Success);
}
DBGSTATIC DWORD AtCalculateTimeout(
LARGE_INTEGER Runtime,
LARGE_INTEGER TimeLargeInteger
)
/*++
Routine Description:
Given the current time and the next time to run, this routine returns
the number of miliseconds to sleep before the next runttime.
Arguments:
Runtime - next time to run a job
TimeLargeInteger - current time
Return Value:
Number of miliseconds to sleep before the next runtime.
--*/
{
DWORD Remainder;
Runtime.QuadPart = Runtime.QuadPart - TimeLargeInteger.QuadPart;
Runtime = RtlExtendedLargeIntegerDivide(
Runtime,
NT_TICKS_IN_WINDOWS_TICK,
&Remainder
);
if ( Runtime.HighPart != 0 || Runtime.LowPart > MAX_BUSY_TIMEOUT) {
return( MAX_BUSY_TIMEOUT);
} else {
return( Runtime.LowPart);
}
}
DBGSTATIC BOOL AtBatOrCmd( IN OUT PWCHAR Command)
/*++
Returns true if we have .BAT or .CMD file.
--*/
{
WCHAR NameBuffer[ MAX_PATH];
PWCHAR Temp;
DWORD Length;
DWORD Index;
PWCHAR BatOrCmd[] = { L".bat", L".cmd"};
for ( Temp = Command; *Temp != 0; Temp++) {
if ( *Temp == L' ' || *Temp == L'\t') {
*Temp = 0;
break;
}
}
for ( Index = 0; Index < sizeof(BatOrCmd)/sizeof(BatOrCmd[0]); Index++) {
Temp = NULL;
Length = SearchPathW(
NULL,
Command,
BatOrCmd[ Index],
sizeof( NameBuffer) / sizeof( WCHAR),
NameBuffer,
&Temp
);
if ( Length == 0 || Length >= MAX_PATH || Temp == NULL) {
continue;
}
#ifdef AT_DEBUG
Temp = wcschr( Temp, L'.');
if ( Temp == NULL || _wcsicmp( Temp, BatOrCmd[ Index])) {
AtLog(( AT_DEBUG_CRITICAL, "BatOrCmd: Command=%ws Extension=%ws\n",
Command, BatOrCmd[ Index]));
}
#endif
return( TRUE);
}
return( FALSE);
}
VOID AtReportEvent(
IN WORD EventType,
IN DWORD MessageId,
IN WORD StringCount,
IN LPWSTR * StringArray,
IN DWORD RawDataBufferLength OPTIONAL,
IN LPVOID RawDataBuffer
)
/*++
Routine Description:
Writes an error message and ascii string to the error log. Also,
writes the data in the data buf if there are any.
Arguments:
EventType - warning, error, ...
MessageId - Message ID
StringCount - number of strings to use in the string array
StringArray - array of insertion strings
RawDataBufferLength - size of data to be printed from the auxiliary data
buffer. May be zero, in which case the actual value depends on
"RawDataBuffer". It is 0 if "RawDataBuffer" is NULL, else it is
"sizeof( wchar) * wcslen( RawDataBuffer)".
RawDataBuffer - data buffer containing secondary error code & some
other useful info. Must be NULL terminated string or NULL if
"RawDataBufferLength" is zero.
Return Value:
None.
--*/
{
HANDLE logHandle;
logHandle = RegisterEventSource( NULL, SCHEDULE_EVENTLOG_NAME);
// If the event log cannot be opened, just return.
if ( logHandle == NULL) {
#ifdef AT_DEBUG
AtLog(( AT_DEBUG_CRITICAL, "ReportEvent: RegisterEventSource() failed with error %d",
GetLastError()));
#endif
return;
}
//
// Use default for RawDataBufferLength if caller requested us so.
//
if ( RawDataBufferLength == 0 && RawDataBuffer != NULL) {
RawDataBufferLength = sizeof( TCHAR) * wcslen( (LPWSTR)RawDataBuffer);
}
if ( !ReportEvent(
logHandle,
EventType,
0, // event category
MessageId, // event id
NULL, // user SID. We're local system - uninteresting
StringCount, // number of strings
RawDataBufferLength, // raw data size
StringArray, // string array
RawDataBuffer // raw data buffer
)) {
#ifdef AT_DEBUG
AtLog(( AT_DEBUG_CRITICAL, "ReportEvent: ReportEvent() failed with error %d",
GetLastError()));
#endif // NOT_YET
}
DeregisterEventSource( logHandle);
}
DBGSTATIC VOID AtCreateProcess( PAT_RECORD pRecord)
/*++
Routine Description:
Creates a process for a given record.
Arguments:
pRecord - pointer to AT_RECORD
--*/
#define CMD_EXE L"cmd /c "
{
PROCESS_INFORMATION ProcessInformation;
BOOL success;
BOOL JobInteractive;
LPWSTR StringArray[ 2];
WCHAR ErrorCodeString[ 25];
WCHAR Command[ sizeof( CMD_EXE) + MAXIMUM_COMMAND_LENGTH];
wcscpy( Command, pRecord->Command);
if ( AtBatOrCmd( Command)) {
wcscpy( Command, CMD_EXE);
wcscat( Command, pRecord->Command);
} else {
wcscpy( Command, pRecord->Command);
}
if ( pRecord->Flags & JOB_NONINTERACTIVE) {
JobInteractive = FALSE;
} else {
//
// Job is to be executed interactively only if the global system flag
// allows that.
//
JobInteractive = AtSystemInteractive();
if ( !JobInteractive) {
StringArray[ 0] = pRecord->Command;
AtReportEvent( EVENTLOG_WARNING_TYPE, EVENT_COMMAND_NOT_INTERACTIVE,
1, StringArray, 0, NULL);
}
}
//
// If the process is to be interactive, set the appropriate flags.
//
if ( JobInteractive) {
AtGlobalStartupInfo.dwFlags |= STARTF_DESKTOPINHERIT;
AtGlobalStartupInfo.lpDesktop = INTERACTIVE_DESKTOP;
}
else {
AtGlobalStartupInfo.dwFlags &= (~STARTF_DESKTOPINHERIT);
AtGlobalStartupInfo.lpDesktop = NULL;
}
success = CreateProcess(
NULL, // image name is imbedded in the command line
Command, // command line
NULL, // pSecAttrProcess
NULL, // pSecAttrThread
FALSE, // this process will not inherit our handles
CREATE_NEW_CONSOLE | CREATE_SEPARATE_WOW_VDM,
// CREATE_NO_WINDOW, // the only choice that works
NULL, // pEnvironment
NULL, // pCurrentDirectory
&AtGlobalStartupInfo,
&ProcessInformation
);
if ( success == FALSE) {
DWORD winError;
winError = GetLastError();
AtLog(( AT_DEBUG_CRITICAL, "CreateProcess( %ws) fails with winError = %d\n",
pRecord->Command, winError));
pRecord->Flags |= JOB_EXEC_ERROR;
StringArray[ 0] = pRecord->Command;
wcscpy( ErrorCodeString, L"%%"); // tell event viewer to expand this error
ultow( winError, ErrorCodeString + 2, 10);
StringArray[ 1] = ErrorCodeString;
AtReportEvent( EVENTLOG_ERROR_TYPE, EVENT_COMMAND_START_FAILED,
2, StringArray, 0, NULL);
} else {
AtLog(( AT_DEBUG_MAIN,
"CreateProcess( %ws) succeeded\t"
"ProcessInformation=%x %x %x %x JobId=%x JobDay=%x Runtime=%x:%x\n",
pRecord->Command,
ProcessInformation.hProcess,
ProcessInformation.hThread,
ProcessInformation.dwProcessId,
ProcessInformation.dwThreadId,
pRecord->JobId,
pRecord->JobDay,
pRecord->Runtime.HighPart,
pRecord->Runtime.LowPart
));
CloseHandle( ProcessInformation.hThread);
CloseHandle( ProcessInformation.hProcess);
pRecord->Flags &= ~JOB_EXEC_ERROR;
}
ASSERT( pRecord->Debug++ < 20);
if ( pRecord->JobDay != JOB_INVALID_DAY) {
ASSERT( (pRecord->Flags & JOB_RUN_PERIODICALLY) == 0 &&
( pRecord->DaysOfWeek != 0 || pRecord->DaysOfMonth != 0));
if ( pRecord->Flags & JOB_CLEAR_WEEKDAY) {
pRecord->DaysOfWeek &= ~( 1<< pRecord->JobDay);
} else {
pRecord->DaysOfMonth &= ~( 1<< ( pRecord->JobDay - 1));
}
pRecord->JobDay = JOB_INVALID_DAY;
} else {
ASSERT( (pRecord->Flags & JOB_RUN_PERIODICALLY) != 0 ||
( pRecord->DaysOfWeek == 0 && pRecord->DaysOfMonth == 0));
}
}
DBGSTATIC VOID AtUpdateRecords( IN PAT_TIME pTime)
/*++
Routine Description:
Reshufles runtime queue to reflect the time change.
Arguments:
pTime - pointer to the new system time
Return Value:
None.
--*/
{
PLIST_ENTRY pListEntry;
PAT_RECORD pRecord;
AtLog(( AT_DEBUG_MAIN, "Update runtime queue.\n"));
//
// If we want to simulate OS/2 behavior, here we can insert a block
// which runs all records whose Runtimes are earlier than the new
// Jan.01,70 system time.
//
if ( IsListEmpty( &AtGlobalJobIdListHead) == TRUE) {
return; // do not bother waking up the master if queue is empty
}
for ( pListEntry = AtGlobalJobIdListHead.Flink;
pListEntry != &AtGlobalJobIdListHead;
pListEntry = pListEntry->Flink) {
pRecord = CONTAINING_RECORD(
pListEntry,
AT_RECORD,
JobIdList
);
AtCalculateRuntime( pRecord, pTime);
AtRemoveRecord( pRecord, RUNTIME_QUEUE);
AtInsertRecord( pRecord, RUNTIME_QUEUE);
}
}
DBGSTATIC BOOL AtTimeChanged(
IN PAT_TIME pGetupTime,
IN OUT PAT_TIME pSleepTime
)
/*++
Routine Description:
Detects if there was a change in system time since we went to sleep.
If there was a sustem time it revises SleepTime to match GetupTime.
Arguments:
pGetupTime - points to AT_TIME structure for the Getup moment
pSleepTime - points to AT_TIME structure for the Sleep moment
Return Value:
TRUE - if there was a time change
FALSE - otherwise
--*/
{
//
// DateDelta says how long we slept in units that CAN be adjusted
// by user.
//
LARGE_INTEGER DateDelta;
//
// BootDelta says how long we slept in units that CANNOT be adjusted
// by user.
//
LARGE_INTEGER BootDelta;
#ifdef AT_DEBUG
PCHAR Format;
#endif
DateDelta.QuadPart = pGetupTime->LargeInteger.QuadPart -
pSleepTime->LargeInteger.QuadPart;
BootDelta = RtlEnlargedUnsignedMultiply(
pGetupTime->TickCount - pSleepTime->TickCount,
NT_TICKS_IN_WINDOWS_TICK
);
if ( DateDelta.QuadPart < 0) {
#ifdef AT_DEBUG
Format = "TimeChanged: negative DateDelta: Getup=%x:%x|%x Sleep=%x:%x|%x\n";
#endif
goto time_changed;
}
if ( DateDelta.QuadPart >= BootDelta.QuadPart ) {
DateDelta.QuadPart = DateDelta.QuadPart - BootDelta.QuadPart;
} else {
DateDelta.QuadPart = BootDelta.QuadPart - DateDelta.QuadPart;
}
if ( DateDelta.HighPart != 0 ||
DateDelta.LowPart > ONE_MINUTE_IN_NT_TICKS) {
#ifdef AT_DEBUG
Format = "TimeChanged: large DateDelta: Getup=%x:%x!%x Sleep=%x:%x!%x\n";
#endif
goto time_changed;
}
return( FALSE);
time_changed:
AtLog(( AT_DEBUG_MAIN, Format,
pGetupTime->LargeInteger.HighPart, pGetupTime->LargeInteger.LowPart, pGetupTime->TickCount,
pSleepTime->LargeInteger.HighPart, pSleepTime->LargeInteger.LowPart, pSleepTime->TickCount));
pSleepTime->LargeInteger.QuadPart = pGetupTime->LargeInteger.QuadPart - BootDelta.QuadPart;
AtTimeGetCurrents( pSleepTime);
AtLog(( AT_DEBUG_MAIN, "TimeChanged: revised Sleep=%x:%x|%x\n",
pSleepTime->LargeInteger.HighPart, pSleepTime->LargeInteger.LowPart, pSleepTime->TickCount));
return( TRUE);
}
VOID SCHEDULE_main(
DWORD argc,
LPWSTR * argv
)
/*++
Routine Description:
This is the main routine of the Schedule Service which registers
itself as an RPC server and notifies the Service Controller of the
Schedule service control entry point.
After the Schedule Service is started, this thread is used to spawn
processes corresponding to records waiting in the queue.
Arguments:
argc - Supplies the number of strings specified in argv
argv - Supplies string arguments that are specified in the
StartService API call.
Return Value:
None.
--*/
{
DWORD initState = 0;
UNREFERENCED_PARAMETER( argc);
UNREFERENCED_PARAMETER( argv);
#ifdef AT_DEBUG
AtDebugCreate();
#endif
#ifdef NOT_YET
if ( !AllocConsole()) {
AT_DEBUG_PRINT(
AT_DEBUG_MAIN,(
"[Job] AllocConsole() returns WinError = %d\n",
GetLastError()
));
}
#endif // NOT_YET
//
// Perhaps one should use something that builds in current day & time
// to be even more random.
//
AtGlobalSeed = GetTickCount();
InitializeCriticalSection( &AtGlobalCriticalSection);
//
// Initialize the scheduler.
//
AtTimeGet( &AtGlobalGetupTime); // initialize AtGlobalGetupTime
if ( AtInitialize( &AtGlobalGetupTime, &initState) != NERR_Success) {
return;
}
//
// We enter and leave loop while holding the critical section.
//
EnterCriticalSection( &AtGlobalCriticalSection);
for ( ; ; ) {
DWORD timeout; // in milliseconds
PAT_RECORD pRecord;
if ( AtGlobalTasks & AT_SERVICE_SHUTDOWN) {
AtLog(( AT_DEBUG_MAIN, "Service shutdown.\n"));
break;
}
AtLogRuntimeList( "Before executions");
timeout = MAX_LAZY_TIMEOUT;
while ( IsListEmpty( &AtGlobalRuntimeListHead) == FALSE) {
pRecord = CONTAINING_RECORD(
AtGlobalRuntimeListHead.Flink,
AT_RECORD,
RuntimeList
);
if (pRecord->Runtime.QuadPart > AtGlobalGetupTime.LargeInteger.QuadPart) {
timeout = AtCalculateTimeout( pRecord->Runtime, AtGlobalGetupTime.LargeInteger);
break; // nothing to do for now
}
AtCreateProcess( pRecord);
if ( pRecord->DaysOfWeek == 0 && pRecord->DaysOfMonth == 0) {
AtDeleteKey( pRecord);
AtRemoveRecord( pRecord, BOTH_QUEUES);
(VOID)LocalFree( pRecord);
} else {
AtCalculateRuntime( pRecord, &AtGlobalGetupTime);
AtRemoveRecord( pRecord, RUNTIME_QUEUE);
AtInsertRecord( pRecord, RUNTIME_QUEUE);
}
}
AtGlobalSleepTime = AtGlobalGetupTime;
AtLogRuntimeList( "After executions");
LeaveCriticalSection( &AtGlobalCriticalSection);
AtLogTimeout( timeout);
(VOID)WaitForSingleObject( AtGlobalEvent, timeout);
EnterCriticalSection( &AtGlobalCriticalSection);
AtTimeGet( &AtGlobalGetupTime);
if ( AtTimeChanged( &AtGlobalGetupTime, &AtGlobalSleepTime) == TRUE) {
//
// SleepTime has been revised and is now expressed in GetupTime
// units. Reorder the queue with respect to revised SleepTime
// and run whatever needs to be run.
// Above has the same effect as if a time change occured
// JUST BEFORE the last time main thread ordered the runtime
// queue and went to sleep.
//
AtUpdateRecords( &AtGlobalSleepTime);
}
}
LeaveCriticalSection( &AtGlobalCriticalSection);
AtShutdown( NERR_Success, initState);
#ifdef AT_DEBUG
AtDebugDelete();
#endif
}