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.
 
 
 
 
 
 

1784 lines
57 KiB

//+----------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1993.
//
// File: procssr.cxx
//
// Contents: CJobProcessor class implementation.
//
// Classes: CJobProcessor
//
// Functions: None.
//
// History: 25-Oct-95 MarkBl Created
// 11/16/00 Dgrube remove (dwRet >= WAIT_OBJECT_0) &&
// from "else if ((dwRet >= WAIT_OBJECT_0) && (dwRet < WAIT_ABANDONED_0))"
// since dwRet is a DWORD. It would never occur and
// is causing compile errors.
//
//-----------------------------------------------------------------------------
//
#include "..\pch\headers.hxx"
#pragma hdrstop
#include "globals.hxx"
#include "svc_core.hxx"
#include "..\inc\resource.h"
// something to differentiate returns from RunDll32 from our returns
#define ERROR_LEVEL_OFFSET 100
// Parameters to CloseWindowEnumProc and ThreadWindowEnumProc
struct ENUMPROCPARMS
{
DWORD dwProcessId; // IN - pid of the process being closed
BOOL fWindowFound; // OUT - whether WM_CLOSE was sent to any window
};
BOOL CALLBACK CloseWindowEnumProc(HWND, LPARAM);
BOOL CALLBACK ThreadWindowEnumProc(HWND, LPARAM);
BOOL CloseWindowForProcess(CRun* pRun);
//
// Notes on the use of CRun's m_dwMaxRunTime and m_ftKill fields:
//
// m_ftKill is the time when the job processor thread monitoring the
// job should try to kill the job, if it hasn't already terminated.
// It is an absolute time. It is computed when the job is launched, based
// on a combination of (1) the duration-end of triggers that have
// TASK_TRIGGER_FLAG_KILL_AT_DURATION_END set and (2) the MaxRunTime set on
// the job itself.
// (1) can be predicted before the job runs, so it is calculated in
// GetTriggerRunTimes() and stored in m_ftKill.
// (2) is a relative time, so in many cases its end time cannot be
// predicted until the job runs. It is temporarily stored in m_dwMaxRunTime
// when the CRun object is created; but it is converted to an absolute time
// and combined with m_ftKill when the job is launched in RunJobs().
//
// Once the job is launched, m_ftKill remains the same for the lifetime of
// the CRun object, even if the job is killed because of
// TASK_FLAG_KILL_ON_IDLE_END and restarted because of
// TASK_FLAG_RESTART_ON_IDLE_RESUME.
//
// m_dwMaxRunTime is the remaining number of milliseconds that the
// CJobProcessor::PerformTask() thread will wait for the job to terminate.
// It is initialized to (m_ftKill - current time) in CJobProcessor::
// SubmitJobs() and repeatedly adjusted downwards each time the job processor
// thread wakes up. If the job is killed and restarted, m_dwMaxRunTime is
// recomputed from the original m_ftKill and the new current time.
//
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::~CJobProcessor
//
// Synopsis: Job processor destructor. This object is reference counted,
// ala class CTask inheritance. As a result, we are guaranteed
// all of this is safe to do, as no outstanding references
// remain.
//
// Arguments: N/A
//
// Notes: None.
//
//----------------------------------------------------------------------------
CJobProcessor::~CJobProcessor()
{
TRACE3(CJobProcessor, ~CJobProcessor);
if (_rgHandles != NULL)
{
//
// Close the processor notification event handle & delete the handle
// array.
//
if (_rgHandles)
{
if (_rgHandles[0])
CloseHandle(_rgHandles[0]);
delete _rgHandles;
_rgHandles = NULL;
}
}
DeleteCriticalSection(&_csProcessorCritSection);
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::Initialize
//
// Synopsis: Perform the initialization steps that would have otherwise
// been performed in the constructor. This method enables return
// of a status code if initialization should fail.
//
// Arguments: None.
//
// Notes: None.
//
//----------------------------------------------------------------------------
HRESULT
CJobProcessor::Initialize(void)
{
TRACE3(CJobProcessor, Initialize);
HRESULT hr;
schAssert(_rgHandles == NULL);
//
// Create the handle array with the processor notification event handle
// as the sole element.
//
_rgHandles = new HANDLE[1];
if (_rgHandles == NULL)
{
CHECK_HRESULT(E_OUTOFMEMORY);
return(E_OUTOFMEMORY);
}
//
// Create the processor notification event and assign its handle to the
// handle array.
//
_rgHandles[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
if (_rgHandles[0] == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
CHECK_HRESULT(hr);
return(hr);
}
//
// Request a thread to service this object.
//
hr = RequestService(this);
if (SUCCEEDED(hr))
{
this->InService();
}
return(hr);
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::IsIdle
//
// Synopsis: This member is called during job processor pool garbage
// collection to determine if this processor can be removed
// from the pool. This method is problematic, but this is OK,
// since the worst that can happen is this processor may
// be removed from the pool prematurely, requiring use of a
// additional, redundant job processor object.
//
// Arguments: None.
//
// Notes: None.
//
//----------------------------------------------------------------------------
BOOL
CJobProcessor::IsIdle(void)
{
TRACE3(CJobProcessor, IsIdle);
if ((_RequestQueue.GetCount() + _ProcessingQueue.GetCount()) == 0)
{
return(this->GetReferenceCount() == 1 ? TRUE : FALSE);
}
return(FALSE);
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::Next
//
// Synopsis: Return the next processor this object refers to. The returned
// object is AddRef()'d to reflect the new reference.
//
// Arguments: None.
//
// Notes: The processor pool is locked to ensure this thread is the
// sole thread accessing the pool throughout this operation.
//
//----------------------------------------------------------------------------
CJobProcessor *
CJobProcessor::Next(void)
{
TRACE3(CJobProcessor, Next);
gpJobProcessorMgr->LockProcessorPool();
CJobProcessor * pjpNext = (CJobProcessor *)CDLink::Next();
if (pjpNext != NULL)
{
pjpNext->AddRef();
}
gpJobProcessorMgr->UnlockProcessorPool();
return(pjpNext);
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::Prev
//
// Synopsis: Return the previous processor this object refers to. The
// returned object is AddRef()'d to reflect the new reference.
//
// Arguments: None.
//
// Notes: The processor pool is locked to ensure this thread is the
// sole thread accessing the pool throughout this operation.
//
//----------------------------------------------------------------------------
CJobProcessor *
CJobProcessor::Prev(void)
{
TRACE3(CJobProcessor, Prev);
gpJobProcessorMgr->LockProcessorPool();
CJobProcessor * pjpPrev = (CJobProcessor *)CDLink::Prev();
if (pjpPrev != NULL)
{
pjpPrev->AddRef();
}
gpJobProcessorMgr->UnlockProcessorPool();
return(pjpPrev);
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::PerformTask
//
// Synopsis: This is the function performed by the worker thread on the
// job processor. The processor thread enters a wait on the array
// of handles in the private data member, _rgHandles. The first
// array element is a handle to the processor notification event.
// This event is signals this thread that new jobs have been sub-
// mitted to this processor. The remaining n-1 handles in the
// array are job process handles signaled on job completion.
// When a job completes, the persisted job object is updated with
// the job's exit status code, completion time, etc.
//
// It's possible the wait for one or more jobs may time out. If
// the processor notification event wait times out, the wait is
// re-entered. If a job times out, its handle is removed from
// wait handle array and the job's job info object removed from
// the processing queue.
//
// Arguments: None.
//
// Notes: None.
//
//----------------------------------------------------------------------------
void
CJobProcessor::PerformTask(void)
{
#define CLOSE_WAIT_TIME (3 * 60 * 1000) // 3 mins (milliseconds).
#define WAIT_TIME_DEFAULT (10 * 3 * 60 * 1000) // 30 mins (milliseconds).
TRACE3(CJobProcessor, PerformTask);
CRun * pRun;
DWORD dwObjectIndex;
//
// Initialize this thread's keep-awake count.
//
InitThreadWakeCount();
for (;;)
{
//
// Wait for job completion, timeout, or processor notification.
//
// NB : ProcessQueue count + 1 since there is no processing queue
// entry for the first handle, the new submission event
// handle.
//
// There will never be a discrepancy between the processing
// queue count and the actual number of handles in _rgHandles
// since this thread exclusively updates the processing queue.
//
DWORD dwTimeoutInterval = WAIT_TIME_DEFAULT;
DWORD cHandles = _ProcessingQueue.GetCount() + 1;
if (cHandles > 1)
{
//
// There are jobs to process.
//
// Scan job info objects in the processor queue for the minimum
// value of the job's maximum run time. This will be the wait
// time on WaitForMultipleObjects.
//
for (pRun = _ProcessingQueue.GetFirstElement();
pRun != NULL;
pRun = pRun->Next())
{
schDebugOut((DEB_USER3,
"PerformTask(0x%x) Job " FMT_TSTR " remaining time %u ms\n",
this,
pRun->GetName(),
pRun->GetMaxRunTime()));
dwTimeoutInterval = min(dwTimeoutInterval,
pRun->GetMaxRunTime());
}
}
schDebugOut((DEB_USER3,
"PerformTask(0x%x) Processor entering wait; p queue cnt(%d); " \
"wait time %u ms\n",
this,
cHandles - 1,
dwTimeoutInterval));
DWORD dwWaitTime = GetTickCount();
DWORD dwRet = WaitForMultipleObjects(cHandles,
_rgHandles,
FALSE,
dwTimeoutInterval);
//
// Serialize processor data structure access.
//
EnterCriticalSection(&_csProcessorCritSection);
//
// (Note that GetTickCount() wrap is automatically taken care of
// by 2's-complement subtraction.)
//
dwWaitTime = GetTickCount() - dwWaitTime;
schDebugOut((DEB_USER3,
"PerformTask(0x%x) Processor awake after %u ms\n",
this,
dwWaitTime));
//
// Decrement each job's max run time by the amount of time waited.
// Skip jobs with zeroed max run time values.
//
for (pRun = _ProcessingQueue.GetFirstElement();
pRun != NULL;
pRun = pRun->Next())
{
//
// NB : Jobs with infinite run times do not expire. Therefore, do
// not decrease the max run time value.
//
if (pRun->GetMaxRunTime() != 0 &&
pRun->GetMaxRunTime() != INFINITE)
{
if (pRun->GetMaxRunTime() > dwWaitTime)
{
pRun->SetMaxRunTime(pRun->GetMaxRunTime() - dwWaitTime);
}
else
{
pRun->SetMaxRunTime(0);
}
}
}
if (dwRet == WAIT_FAILED)
{
//
// Wait attempt failed. Shutdown the processor & bail out.
//
// BUGBUG : Should probably log this.
//
schDebugOut((DEB_ERROR,
"PerformTask(0x%x) Wait failure(0x%x) - processor " \
"shutdown initiated\n",
this,
HRESULT_FROM_WIN32(GetLastError())));
this->_Shutdown();
LeaveCriticalSection(&_csProcessorCritSection);
break;
}
if (dwRet == WAIT_TIMEOUT)
{
if (!_ProcessingQueue.GetCount() && !_RequestQueue.GetCount())
{
//
// Shutdown this processor. The wait has expired and no jobs
// are in service, nor are there new requests queued.
//
schDebugOut((DEB_TRACE,
"PerformTask(0x%x) Processor idle - shutdown " \
"initiated\n",
this));
this->_Shutdown();
LeaveCriticalSection(&_csProcessorCritSection);
break;
}
//
// One or more jobs timed out (those with max run time values of
// zero). Close associated event handle, overwrite event handle
// array entry, then remove and destroy the associated job info
// object from the processor queue.
//
schDebugOut((DEB_USER3,
"PerformTask(0x%x) Wait timeout\n",
this));
CRun * pRunNext;
DWORD i;
for (pRun = _ProcessingQueue.GetFirstElement(), i = 1;
pRun != NULL;
pRun = pRunNext, i++)
{
pRunNext = pRun->Next();
if (pRun->GetMaxRunTime() != 0)
{
continue;
}
//
// Post a WM_CLOSE message to the job if this is the
// first attempt at closure. If WM_CLOSE was issued
// previously and the job is still running, resort to
// TerminateProcess.
//
if (!(pRun->IsFlagSet(RUN_STATUS_CLOSE_PENDING)))
{
pRun->SetFlag(RUN_STATUS_TIMED_OUT);
schDebugOut((DEB_ITRACE,
"PerformTask(0x%x) Forced closure; issuing " \
"WM_CLOSE to job " FMT_TSTR "\n",
this,
pRun->GetName()));
//
// Log job closure, post WM_CLOSE, then re-enter the
// wait for closure.
//
SYSTEMTIME stFinished;
GetLocalTime(&stFinished);
g_pSched->JobPostProcessing(pRun, stFinished);
/*******
// Attach to the correct desktop prior to enumerating
// the windows
//
HWINSTA hwinstaSave = NULL;
HDESK hdeskSave = NULL;
HWINSTA hwinsta = NULL;
DWORD dwTreadId = GetCurrentThreadId( );
if( NULL == dwTreadId )
{
schDebugOut((DEB_ERROR,
"CJobProcessor::PerformTask, GetCurrentThreadId " ));
}
else
{
hdeskSave = GetThreadDesktop( dwTreadId );
}
if( NULL == hdeskSave )
{
schDebugOut((DEB_ERROR,
"CJobProcessor::PerformTask, GetThreadDesktop " ));
}
else
{
hwinstaSave = GetProcessWindowStation( );
}
if( NULL == hwinstaSave )
{
schDebugOut((DEB_ERROR,
"CJobProcessor::PerformTask, GetProcessWindowStation " ));
}
hwinsta = OpenWindowStation(
pRun->GetStation( ),
TRUE,
MAXIMUM_ALLOWED );
if( NULL == hwinsta )
{
schDebugOut((DEB_ERROR,
"CJobProcessor::PerformTask, OpenWindowStation " ));
}
else if( !SetProcessWindowStation( hwinsta ) )
{
schDebugOut((DEB_ERROR,
"CJobProcessor::PerformTask, SetProcessWindowStation " ));
}
HDESK hDesk = OpenDesktop(
pRun->GetDesktop(),
0, //No hooks allowed
TRUE, //No inheritance
MAXIMUM_ALLOWED
);
if( !SetThreadDesktop( hDesk ) )
{
schDebugOut((DEB_ERROR,
"CJobProcessor::PerformTask, OpenDesktop failed, " \
"status = 0x%lx\n",
GetLastError()));
}
else
{
// Success enumerate windows else SetMaxRunTime to 0
// and ultimately kill the process (not very graceful)
//
EnumWindows(CloseWindowEnumProc, (LPARAM) &Parms);
}
********************/
pRun->SetFlag(RUN_STATUS_CLOSE_PENDING);
BOOL fFoundWindow = CloseWindowForProcess(pRun);
if (fFoundWindow)
{
pRun->SetMaxRunTime(CLOSE_WAIT_TIME);
}
else
{
schDebugOut((DEB_ITRACE, "PerformTask: no windows found\n"));
//
// If WM_CLOSE was not sent to any windows, there is no
// point waiting for the job to terminate.
// DCR: It would be polite, and perhaps help the app to
// avoid data loss (depending on the app), to send some other
// notification, such as a CTRL_C_EVENT. See bug 65251.
//
pRun->SetMaxRunTime(0);
}
}
else
{
schDebugOut((DEB_ITRACE,
"PerformTask(0x%x) 2nd forced closure; issuing " \
"TerminateProcess on job " FMT_TSTR "\n",
this,
pRun->GetName()));
DWORD dwExitCode = 0;
GetExitCodeProcess(pRun->GetHandle(), &dwExitCode);
if (dwExitCode == STILL_ACTIVE)
{
TerminateProcess(pRun->GetHandle(), (UINT)-1);
}
if (i < _ProcessingQueue.GetCount()) // Ignore last
// entry.
{
CopyMemory(&_rgHandles[i],
&_rgHandles[i + 1],
sizeof(HANDLE) *
(_ProcessingQueue.GetCount() - i));
}
i--; // Reflect overwritten array entry.
//
// Remove CRun object from the processing queue
// and destroy it.
//
_ProcessingQueue.RemoveElement(pRun);
if (pRun->IsFlagSet(TASK_FLAG_SYSTEM_REQUIRED))
{
//
// This thread is monitoring one less system-
// required job
//
WrapSetThreadExecutionState(FALSE,
"processor - forced close of task");
}
if (pRun->IsFlagSet(RUN_STATUS_RESTART_ON_IDLE_RESUME)
&& pRun->GetWait() > 0)
{
//
// Ask the main thread to move it back into the
// idle wait queue
//
pRun->ClearFlag(JOB_INTERNAL_FLAG_MASK);
pRun->SetMaxRunTime(INFINITE);
g_pSched->SubmitIdleRun(pRun);
//
// Note that we changed (reduced) pRun's MaxRunTime
// when we killed it. However we didn't mess with
// the kill time. The MaxRunTime will be recomputed
// based on the same kill time as before when this
// run is next submitted to a processor.
//
}
else
{
delete pRun;
}
}
}
}
else if (dwRet < WAIT_ABANDONED_0)
{
//
// One or more jobs completed.
//
dwObjectIndex = dwRet - WAIT_OBJECT_0;
if (dwObjectIndex == 0)
{
//
// Processor notification event signaled. Either new jobs
// have been submitted or the service is stopping.
//
if (IsServiceStopping())
{
//
// Service stop. Shutdown the processor.
//
schDebugOut((DEB_TRACE,
"PerformTask(0x%x) Service stop - processor " \
"shutdown initiated\n",
this));
this->_Shutdown();
LeaveCriticalSection(&_csProcessorCritSection);
break;
}
ResetEvent(_rgHandles[0]);
//
// Move jobs from request to processing queue.
//
_ProcessRequests();
//
// Unblock the thread that called SubmitJobs().
// (We happen to know it's the thread in the main service
// loop so we can use the global event. A cleaner model
// would be to either pass the handle of the event to
// SubmitJobs, or use an event private to SubmitJobs and
// PerformTask.)
//
g_pSched->Unblock();
}
else if (dwObjectIndex < cHandles)
{
//
// A job has finished (or closed).
// Find the CRun object associated with the handle.
//
if ((pRun = _ProcessingQueue.FindJob(
_rgHandles[dwObjectIndex])) != NULL)
{
pRun->ClearFlag(RUN_STATUS_RUNNING);
if (!(pRun->GetFlags() & RUN_STATUS_CLOSE_PENDING))
{
schDebugOut((DEB_USER3,
"PerformTask(0x%x) Job " FMT_TSTR " completed\n",
this,
pRun->GetName()));
//
// The job finished on its own. Log completion
// status. Fetch job completion time for pending log
// entry.
//
pRun->SetFlag(RUN_STATUS_FINISHED);
SYSTEMTIME stFinished;
GetLocalTime(&stFinished);
//
// Standard job post processing.
//
g_pSched->JobPostProcessing(pRun, stFinished);
}
else
{
// (NOTE: This may not be necessary - this info
// is not used yet.)
//
pRun->SetFlag(RUN_STATUS_CLOSED);
}
//
// Fix up handle array to reflect processed entry.
//
if (dwObjectIndex < _ProcessingQueue.GetCount())
{
CopyMemory(&_rgHandles[dwObjectIndex],
&_rgHandles[dwObjectIndex + 1],
sizeof(HANDLE) *
(_ProcessingQueue.GetCount() - dwObjectIndex));
}
//
// Remove CRun object from the processing queue and
// destroy it.
//
_ProcessingQueue.RemoveElement(pRun);
if (pRun->IsFlagSet(TASK_FLAG_SYSTEM_REQUIRED))
{
//
// This thread is monitoring one less system-
// required job
//
WrapSetThreadExecutionState(FALSE,
"processor - last task exited");
}
if (pRun->IsFlagSet(RUN_STATUS_CLOSE_PENDING) &&
pRun->IsFlagSet(RUN_STATUS_RESTART_ON_IDLE_RESUME) &&
pRun->GetWait() > 0)
{
//
// Ask the main thread to move it back into the
// idle wait queue
//
pRun->ClearFlag(JOB_INTERNAL_FLAG_MASK);
pRun->SetMaxRunTime(INFINITE);
g_pSched->SubmitIdleRun(pRun);
//
// Note that we changed (reduced) pRun's MaxRunTime
// when we killed it. However we didn't mess with
// the kill time. The MaxRunTime will be reset to
// match the same kill time as before when this run
// is next submitted to a processor.
//
}
else
{
delete pRun;
}
}
}
else
{
//
// Index out of range. This should never happen.
//
schDebugOut((DEB_ERROR,
"PerformTask(0x%x) Wait array index (%d) out of " \
"range! Handle count(%d)\n",
this,
dwObjectIndex,
cHandles));
schAssert(0);
LeaveCriticalSection(&_csProcessorCritSection);
continue;
}
}
else
{
//
// Clueless how we got here. Just continue the wait.
//
schAssert(!"How did this branch get evaluated?");
LeaveCriticalSection(&_csProcessorCritSection);
continue;
}
LeaveCriticalSection(&_csProcessorCritSection);
}
}
//+---------------------------------------------------------------------------
//
// Function: CloseWindowForProcess
//
// Synopsis: launches separate process to issue WM_CLOSE to main window
//
// Arguments: CRun for the job of interest
//
// Return value: true if a window was found.
//
// Notes:
//
//----------------------------------------------------------------------------
BOOL CloseWindowForProcess(CRun* pRun)
{
// processID == 0 means we can't find / don't have a proc id
if (pRun->GetProcessId() == 0)
return FALSE;
BOOL fWindowClosed = false;
// okay - it might not be the default desktop, but is the best guess we have
// if we guess wrong, no harm done - the PID is unique & we won't close the wrong proc
BOOL fIsDefaultWinsta = (NULL == pRun->GetStation()) || (NULL == pRun->GetDesktop());
// create process in proper winsta
// process is rundll32 to invoke CloseProc
WCHAR dllPath[MAX_PATH +2];
WCHAR runDllPath[MAX_PATH +2];
BOOL bLaunched = false;
HANDLE hProc = INVALID_HANDLE_VALUE;
if (ExpandEnvironmentStringsW(L"%windir%\\system32\\rundll32.exe", runDllPath, MAX_PATH +1) &&
GetModuleFileNameW(g_hInstance, dllPath, MAX_PATH +1))
{
WCHAR templitt[] = L"%s %s,CloseProc %u";
size_t cchCommandLine = wcslen(runDllPath) + wcslen(dllPath) + wcslen(templitt) + 20;
WCHAR* pCommandLine = new WCHAR[cchCommandLine];
size_t cchDesktop;
WCHAR* pDesktop = NULL;
if (fIsDefaultWinsta)
pDesktop = L"WinSta0\\Default";
else
{
cchDesktop = wcslen(pRun->GetStation()) + wcslen(pRun->GetDesktop()) + 5;
pDesktop = new WCHAR[cchDesktop];
}
if (pCommandLine && pDesktop)
{
StringCchPrintf(pCommandLine, cchCommandLine, templitt, runDllPath, dllPath, pRun->GetProcessId());
if (!fIsDefaultWinsta)
{
StringCchCopy(pDesktop, cchDesktop, pRun->GetStation());
StringCchCat(pDesktop, cchDesktop, L"\\");
StringCchCat(pDesktop, cchDesktop, pRun->GetDesktop());
}
// else - pDesktop was init'd above
PROCESS_INFORMATION procInfo;
STARTUPINFO startInfo;
SecureZeroMemory(&startInfo, sizeof(startInfo));
startInfo.lpDesktop = pDesktop;
startInfo.cb = sizeof(STARTUPINFO);
bLaunched = CreateProcessW(NULL, pCommandLine, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS,
NULL, NULL, &startInfo, &procInfo);
hProc = procInfo.hProcess;
}
// if fIsDefaultWinsta, then pDesktop is pointing at static memory
if (pDesktop && !fIsDefaultWinsta) delete[] pDesktop;
if (pCommandLine) delete[] pCommandLine;
}
if (bLaunched)
{
if (WaitForSingleObject(hProc, 60000) == WAIT_OBJECT_0)
{
DWORD exitCode;
if (GetExitCodeProcess(hProc, &exitCode))
fWindowClosed = exitCode - ERROR_LEVEL_OFFSET;
}
CloseHandle(hProc);
}
return fWindowClosed;
}
//+---------------------------------------------------------------------------
//
// Function: CloseProcEx
//
// Synopsis: Entry point used with RunDll32
// closes down window via WM_CLOSE
//
// Arguments: [hwnd] -- ignored
// [hinst] -- uninteresting
// [nCmdShow] -- boring
// [lpszCmdLine] -- command line from invocation
//
// Notes: command line should be proc id.
// CloseProc in main is a straight passthrough to this one.
//
//----------------------------------------------------------------------------
extern "C" void CALLBACK CloseProcEx(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
{
BOOL fFoundWindow = FALSE;
ENUMPROCPARMS params;
params.fWindowFound = 0;
if (lpszCmdLine && strlen(lpszCmdLine))
{
if (sscanf(lpszCmdLine, "%u", &params.dwProcessId)) // SEC:REVIEWED 2002-04-30
// this function does not involve an unbounded string copy
{
EnumWindows(CloseWindowEnumProc, (LPARAM) &params);
fFoundWindow = params.fWindowFound;
}
}
ExitProcess(fFoundWindow +ERROR_LEVEL_OFFSET);
}
//+---------------------------------------------------------------------------
//
// Function: CloseWindowEnumProc
//
// Synopsis:
//
// Arguments: [hWnd] --
// [lParam] --
//
// Notes:
//
//----------------------------------------------------------------------------
BOOL CALLBACK
CloseWindowEnumProc(HWND hWnd, LPARAM lParam)
{
DWORD dwProcessId, dwThreadId;
ENUMPROCPARMS * pParms = (ENUMPROCPARMS *) lParam;
dwThreadId = GetWindowThreadProcessId(hWnd, &dwProcessId);
if (dwProcessId == pParms->dwProcessId)
{
//
// Enumerate and close each owned, non-child window. This will close
// open dialogs along with the main window(s).
//
DWORD dwErr;
if (!EnumThreadWindows(dwThreadId, ThreadWindowEnumProc, lParam))
dwErr = GetLastError();
// some processes, e.g. cmd.exe don't get enumerated by EnumThreadWindows
// if so, we'll try one close message to the top window.
if ((pParms->fWindowFound == false) && IsWindow(hWnd))
{
PostMessage(hWnd, WM_CLOSE, 0, 0);
pParms->fWindowFound = true;
}
return FALSE;
}
return(TRUE);
}
//+---------------------------------------------------------------------------
//
// Function: ThreadWindowEnumProc
//
// Synopsis: Enumeration procedure.
//
// Arguments: [hWnd] -- The window handle.
// [lParam] -- The process ID.
//
//----------------------------------------------------------------------------
BOOL CALLBACK
ThreadWindowEnumProc(HWND hWnd, LPARAM lParam)
{
DWORD dwProcessId;
ENUMPROCPARMS * pParms = (ENUMPROCPARMS *) lParam;
GetWindowThreadProcessId(hWnd, &dwProcessId);
if (dwProcessId == pParms->dwProcessId)
{
//
// Close any dialogs.
//
//
// The most common dialog we are likely to see at this point is a
// "save changes" dialog. First try to send no to close that dialog
// and then try a cancel.
//
if( !PostMessage(hWnd, WM_COMMAND, 0, MAKEWPARAM(IDNO, 0)) )
{
schDebugOut((DEB_ERROR,
"CJobProcessor::PerformTask - ThreadWindowEnumProc, PMsg1, " \
"status = 0x%lx\n",
GetLastError()));
}
if( !PostMessage(hWnd, WM_COMMAND, 0, MAKEWPARAM(IDCANCEL, 0)) )
{
schDebugOut((DEB_ERROR,
"CJobProcessor::PerformTask - ThreadWindowEnumProc, PMsg2, " \
"status = 0x%lx\n",
GetLastError()));
}
//
// Close any non-child windows.
//
if( !PostMessage(hWnd, WM_CLOSE, 0, 0) )
{
schDebugOut((DEB_ERROR,
"CJobProcessor::PerformTask - ThreadWindowEnumProc, PMsg3, " \
"status = 0x%lx\n",
GetLastError()));
}
//
// Tell the calling function that we found a matching window.
//
pParms->fWindowFound = TRUE;
}
return TRUE;
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::SubmitJobs
//
// Synopsis: This method is used to submit new jobs to this processor.
//
// Each processor can handle a maximum of (MAXIMUM_WAIT_OBJECTS
// - 1) jobs (from the WaitForMultipleObjects constraint of
// at most MAXIMUM_WAIT_OBJECTS). Subject to processor capacity,
// all, or a subset of the jobs passed may be taken.
//
// Arguments: [pRunList] -- Submitted job linked list object. Jobs taken are
// transferred from this list to a private one.
//
// Returns: S_OK -- No submissions taken (as a result of a normal
// condition, such as the job processor already full,
// or the job processor shutting down).
// S_SCHED_JOBS_ACCEPTED -- Some submissions taken.
// On return, GetFirstJob() will return NULL if all
// submissions were taken.
// S_FALSE -- The service is shutting down. Call Shutdown()
// on this processor immediately after this return
// code. Submissions were likely taken, but they will
// not execute.
// HRESULT -- On error.
//
// Notes: None.
//
//----------------------------------------------------------------------------
HRESULT
CJobProcessor::SubmitJobs(CRunList * pRunList)
{
TRACE3(CJobProcessor, SubmitJobs);
schAssert(pRunList != NULL);
HRESULT hr = S_OK;
BOOL fJobsAccepted = FALSE;
schDebugOut((DEB_USER3,
"SubmitJobs(0x%x) pRunList(0x%x)\n",
this,
pRunList));
//
// Serialize processor data structure access.
//
EnterCriticalSection(&_csProcessorCritSection);
FILETIME ftNow = GetLocalTimeAsFileTime();
schDebugOut((DEB_USER3, "SubmitJobs: Time now = %lx %lx\n",
ftNow.dwLowDateTime, ftNow.dwHighDateTime));
//
// Add as many jobs as this processor will allow to the request queue.
// See synopsis for details.
//
// NB : Adding one to the request/processing queue sum to reflect the new
// processor notification event handle. For this handle array entry,
// there is no processing queue entry.
//
CRun * pRun = pRunList->GetFirstJob();
//
// First, check if this processor is in the process of shutting down.
// The data member, _rgHandles, is utilized as a flag to indicate this.
// If it is NULL, this processor has shutdown and will take no more jobs.
//
if (_rgHandles != NULL)
{
while ( !pRun->IsNull() &&
(MAXIMUM_WAIT_OBJECTS - (this->_RequestQueue.GetCount() +
this->_ProcessingQueue.GetCount() +
1) ))
{
CRun * pRunNext = pRun->Next();
pRun->UnLink();
schDebugOut((DEB_USER3,
"SubmitJobs: pRun(%#lx) (" FMT_TSTR ") KillTime = %lx %lx\n",
pRun, pRun->GetName(), pRun->GetKillTime().dwLowDateTime,
pRun->GetKillTime().dwHighDateTime));
//
// Compute the max run time (the time we will wait for
// this job to complete) based on the kill time
//
DWORDLONG MaxRunTime;
if (FTto64(pRun->GetKillTime()) < FTto64(ftNow))
{
MaxRunTime = 0;
}
else
{
MaxRunTime = (FTto64(pRun->GetKillTime()) - FTto64(ftNow)) /
FILETIMES_PER_MILLISECOND;
MaxRunTime = min(MaxRunTime, MAXULONG);
}
pRun->SetMaxRunTime((DWORD) MaxRunTime);
schDebugOut((DEB_USER3, "SubmitJobs: MaxRunTime = %lu\n", MaxRunTime));
_RequestQueue.AddElement(pRun);
fJobsAccepted = TRUE;
pRun = pRunNext;
}
//
// Is there a thread servicing this object? If not, request one.
//
if (!this->IsInService())
{
//
// NB : A RequestService() return code of S_FALSE indicates the
// service is shutting down. Simply propagate this return
// code. It will then be the caller's responsibility to
// shut down this processor.
//
hr = RequestService(this);
if (SUCCEEDED(hr) && hr != S_FALSE)
{
this->InService();
}
}
//
// Set the processor notification event.
//
schDebugOut((DEB_USER3,
"CJobProcessor::SubmitJobs(0x%x) Signalling processor thread\n"));
//
// A NOP if RequestService() failed above.
//
SetEvent(_rgHandles[0]);
}
LeaveCriticalSection(&_csProcessorCritSection);
if (hr == S_OK && fJobsAccepted)
{
hr = S_SCHED_JOBS_ACCEPTED;
}
return(hr);
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::KillJob
//
// Synopsis: Kill all instances of the job indicated, if in service by
// this processor.
//
// Arguments: [ptszJobName] -- Job name.
//
// Returns: None.
//
// Notes: None.
//
//----------------------------------------------------------------------------
void
CJobProcessor::KillJob(LPTSTR ptszJobName)
{
TRACE(CJobProcessor, KillJob);
BOOL fContractInitiated = FALSE;
//
// Serialize processor data structure access.
//
EnterCriticalSection(&_csProcessorCritSection);
//
// Is the job serviced by this processor?
// Find associated job info object(s) in the processing queue.
//
// NB : Rarely, but it is possible there may be more than one instance.
//
CRun * pRun;
for (pRun = _ProcessingQueue.GetFirstElement(); pRun != NULL;
pRun = pRun->Next())
{
//
// The abort flag check addresses the case where more than one user
// simultaneously aborts the same job.
//
if (!lstrcmpi(ptszJobName, pRun->GetName()) &&
!(pRun->GetFlags() & RUN_STATUS_ABORTED))
{
//
// Set flags for immediate timeout and closure.
//
pRun->SetMaxRunTime(0);
pRun->SetFlag(RUN_STATUS_ABORTED);
fContractInitiated = TRUE;
}
}
if (fContractInitiated)
{
//
// This logic will induce the PerformTask thread to respond as
// follows:
// - The wait will unblock and the next wait time re-calculated;
// this value will be zero since the min value is taken.
// - The wait is re-entered and immediately times out.
// - Jobs with max run times of zero are closed in the
// WAIT_TIMEOUT condition. As a result, the jobs are killed.
//
SetEvent(_rgHandles[0]);
}
LeaveCriticalSection(&_csProcessorCritSection);
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::KillIfFlagSet
//
// Synopsis: Kill all jobs that have the passed in flag set, if in service
// by this processor.
//
// Arguments: [dwFlag] - Job flag value, one of TASK_FLAG_KILL_ON_IDLE_END
// or TASK_FLAG_KILL_IF_GOING_ON_BATTERIES.
// Returns: None.
//
// Notes: None.
//
//----------------------------------------------------------------------------
void
CJobProcessor::KillIfFlagSet(DWORD dwFlag)
{
TRACE(CJobProcessor, KillIfFlagSet);
BOOL fContractInitiated = FALSE;
//
// Serialize processor data structure access.
//
EnterCriticalSection(&_csProcessorCritSection);
//
// Is the job serviced by this processor?
// Find associated job info object(s) in the processing queue.
//
CRun * pRun;
for (pRun = _ProcessingQueue.GetFirstElement(); pRun != NULL;
pRun = pRun->Next())
{
//
// The abort flag check addresses the case where more than one user
// simultaneously aborts the same job.
//
if ((pRun->GetFlags() & dwFlag) &&
!(pRun->GetFlags() & RUN_STATUS_ABORTED))
{
//
// Set flags for immediate timeout and closure.
//
pRun->SetMaxRunTime(0);
pRun->SetFlag(RUN_STATUS_ABORTED);
if (dwFlag == TASK_FLAG_KILL_ON_IDLE_END &&
pRun->IsFlagSet(TASK_FLAG_RESTART_ON_IDLE_RESUME) &&
! pRun->IsIdleTriggered())
{
//
// Note that this is the only case in which we set
// RUN_STATUS_RESTART_ON_IDLE_RESUME. If a job is terminated
// because a user explicitly terminated it, for example, we
// don't want to restart it on idle resume.
//
pRun->SetFlag(RUN_STATUS_RESTART_ON_IDLE_RESUME);
}
fContractInitiated = TRUE;
}
}
if (fContractInitiated)
{
//
// This logic will induce the PerformTask thread to respond as
// follows:
// - The wait will unblock and the next wait time re-calculated;
// this value will be zero since the min value is taken.
// - The wait is re-entered and immediately times out.
// - Jobs with max run times of zero are closed in the
// WAIT_TIMEOUT condition. As a result, the jobs are killed.
//
SetEvent(_rgHandles[0]);
}
LeaveCriticalSection(&_csProcessorCritSection);
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::Shutdown
//
// Synopsis: Effect processor shutdown. Do so by signalling the
// PerformTask thread. The thread will check the global service
// status flag. If the service is stopped (actually, in the
// process of stopping), the thread will execute the processor
// shutdown code & relinquish itself.
//
// Arguments: None.
//
// Returns: None.
//
// Notes: None.
//
//----------------------------------------------------------------------------
void
CJobProcessor::Shutdown(void)
{
TRACE3(CJobProcessor, Shutdown);
EnterCriticalSection(&_csProcessorCritSection);
if (_rgHandles != NULL)
{
SetEvent(_rgHandles[0]);
}
LeaveCriticalSection(&_csProcessorCritSection);
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::_EmptyJobQueue
//
// Synopsis: Empty respective job queue and log, per job, the reason why.
//
// Arguments: [JobQueue] -- Reference to CJobQueue instance.
// [dwMsgId] -- Why each job was abandoned. A value of zero
// indicates no reason; nothing is logged.
//
// Notes: Must be in the processor critical section for the duration
// of this method!
//
//----------------------------------------------------------------------------
void
CJobProcessor::_EmptyJobQueue(CJobQueue & JobQueue, DWORD dwMsgId)
{
TRACE3(CJobProcessor, _EmptyJobQueue);
CRun * pRun;
for (pRun = JobQueue.RemoveElement(); pRun != NULL;
pRun = JobQueue.RemoveElement())
{
if (!dwMsgId)
{
//
// BUGBUG : Log job info + reason why the job was abandoned.
// Should logging be per job? Per incident w/ job list?
//
}
delete pRun;
}
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::_ProcessRequests
//
// Synopsis: Transfer submitted jobs from the request queue to the
// processing queue and rebuild the wait handle array.
//
// Arguments: None.
//
// Notes: Must be in the processor critical section for the duration
// of this method!
//
//----------------------------------------------------------------------------
void
CJobProcessor::_ProcessRequests(void)
{
TRACE3(CJobProcessor, _ProcessRequests);
if (_RequestQueue.GetCount())
{
//
// Sum request, processing queue counts.
//
DWORD cJobs = _RequestQueue.GetCount() +
_ProcessingQueue.GetCount() + 1;
schDebugOut((DEB_USER3,
"CJobProcessor::_ProcessRequests(0x%x) Total job count(%d) = " \
"request(%d) + processing(%d) + 1\n",
this,
cJobs,
_RequestQueue.GetCount(),
_ProcessingQueue.GetCount()));
//
// Logic in SubmitJobs should prevent this from becoming false.
//
schAssert(cJobs <= MAXIMUM_WAIT_OBJECTS);
HANDLE * rgHandles = new HANDLE[cJobs];
if (rgHandles == NULL)
{
//
// Leave request, processing queues as-is.
//
LogServiceError(IDS_FATAL_ERROR,
ERROR_NOT_ENOUGH_MEMORY,
IDS_HELP_HINT_CLOSE_APPS);
ERR_OUT("JobProcessor: ProcessRequests", E_OUTOFMEMORY);
return;
}
//
// Copy existing handles.
//
CopyMemory(rgHandles,
_rgHandles,
sizeof(HANDLE) * (_ProcessingQueue.GetCount() + 1));
//
// Copy new job handles from request queue and transfer request
// queue contents to the tail of the processing queue.
//
for (DWORD i = _ProcessingQueue.GetCount() + 1; i < cJobs; i++)
{
CRun * pRun = _RequestQueue.RemoveElement();
Win4Assert( pRun != NULL );
rgHandles[i] = pRun->GetHandle();
_ProcessingQueue.AddElement(pRun);
if (pRun->IsFlagSet(TASK_FLAG_SYSTEM_REQUIRED))
{
//
// Increment the count of running system_required jobs
// handled by this thread. If this is the first such
// job, tell the system not to sleep until further notice.
//
WrapSetThreadExecutionState(TRUE, "processor - new job");
}
}
delete _rgHandles;
_rgHandles = rgHandles;
}
}
//+---------------------------------------------------------------------------
//
// Member: CJobProcessor::_Shutdown
//
// Synopsis: Take no more requests and dump whatever jobs remain in the
// request & processing queues.
//
// Arguments: None.
//
// Notes: Must be in the processor critical section for the duration
// of this method!
//
//----------------------------------------------------------------------------
void
CJobProcessor::_Shutdown(void)
{
TRACE3(CJobProcessor, _Shutdown);
//
// Utilizing the handle array member as a flag to indicate that this
// processor will take no more new jobs. Set this member to NULL on
// shutdown.
//
// First close the processor notification event handle & delete the
// array.
//
// No need to keep the machine awake for this thread any more
if (pfnSetThreadExecutionState != NULL)
{
schDebugOut((DEB_USER5, "RESETTING sys-required state: processor shutdown\n"));
(pfnSetThreadExecutionState)(ES_CONTINUOUS);
}
CloseHandle(_rgHandles[0]);
_rgHandles[0] = NULL;
delete _rgHandles;
_rgHandles = NULL;
//
// Now, empty request & processing queues.
//
// BUGBUG : Log job abandoned message.
//
this->_EmptyJobQueue(_RequestQueue);
this->_EmptyJobQueue(_ProcessingQueue);
}
//+---------------------------------------------------------------------------
//
// Member: CSchedWorker::JobPostProcessing
//
// Synopsis: Set the exit code, current status, and NextRunTime on the
// job object and log the run exit.
//
// Arguments: [pRun] -- Job run information object.
// [stFinished] -- Job finish time (local time). For logging.
//
// Notes: None.
//
//----------------------------------------------------------------------------
void
CSchedWorker::JobPostProcessing(
CRun * pRun,
SYSTEMTIME & stFinished)
{
TRACE3(CSchedWorker, JobPostProcessing);
schDebugOut((DEB_ITRACE,
"JobPostProcessing pRun(0x%x) flags(0x%x)\n",
pRun,
pRun->GetFlags()));
DWORD dwExitCode;
CJob * pJob = NULL;
//
// Instantiate the job so that the exit status can be saved.
//
// Note: if any of the variable length properties or the triggers are
// needed, then a full activation will be necessary.
//
// Important: the running instance count must be protected by the
// critical section here, where it is decremented, and in RunJobs, where
// it is incremented. These are the only sections of code that change
// the running instance count on the file object.
//
EnterCriticalSection(&m_SvcCriticalSection);
HRESULT hr = ActivateWithRetry(pRun->GetName(), &pJob, FALSE);
if (FAILED(hr))
{
//
// The job object may have been deleted. We can't supply LogTaskError
// with the name of the run target, since that's on the job object,
// which we just failed to load.
//
LogTaskError(pRun->GetName(),
NULL,
IDS_LOG_SEVERITY_WARNING,
IDS_LOG_JOB_WARNING_CANNOT_LOAD,
NULL,
(DWORD)hr);
ERR_OUT("JobPostProcessing Activate", hr);
LeaveCriticalSection(&m_SvcCriticalSection);
return;
}
//
// Isolate the executable name.
//
TCHAR tszExeName[MAX_PATH + 1];
GetExeNameFromCmdLine(pJob->GetCommand(), MAX_PATH + 1, tszExeName);
if (pRun->GetFlags() & RUN_STATUS_FINISHED)
{
//
// Only check the exit code if the job completed normally, that is,
// it wasn't timed-out or aborted.
//
if (!GetExitCodeProcess(pRun->GetHandle(), &dwExitCode))
{
LogTaskError(pRun->GetName(),
tszExeName,
IDS_LOG_SEVERITY_WARNING,
IDS_CANT_GET_EXITCODE,
NULL,
GetLastError());
ERR_OUT("GetExitCodeProcess", GetLastError());
}
}
else
{
//
// BUGBUG : What is written on the job when not complete?
//
}
//
// PostRunUpdate updates the flags and instance count, so always call it.
//
pJob->PostRunUpdate(dwExitCode, pRun->GetFlags() & RUN_STATUS_FINISHED);
//
// If the last run and the delete flag is set, delete the job object.
//
if (pJob->IsFlagSet(JOB_I_FLAG_NO_MORE_RUNS) &&
pJob->IsFlagSet(TASK_FLAG_DELETE_WHEN_DONE))
{
hr = pJob->Delete();
if (FAILED(hr))
{
LogTaskError(pRun->GetName(),
tszExeName,
IDS_LOG_SEVERITY_WARNING,
IDS_CANT_DELETE_JOB,
NULL,
GetLastError());
ERR_OUT("JobPostProcessing, delete-when-done", hr);
}
}
else
{
//
// Write the updated status to the job object. If there are sharing
// violations, retry two times.
//
hr = pJob->SaveWithRetry(NULL,
FALSE,
SAVEP_RUNNING_INSTANCE_COUNT |
SAVEP_PRESERVE_NET_SCHEDULE);
if (FAILED(hr))
{
LogTaskError(pRun->GetName(),
tszExeName,
IDS_LOG_SEVERITY_WARNING,
IDS_CANT_UPDATE_JOB,
NULL,
GetLastError());
ERR_OUT("JobPostProcessing, Saving run-completion-status", hr);
}
}
LeaveCriticalSection(&m_SvcCriticalSection);
if (pRun->GetFlags() & RUN_STATUS_FINISHED)
{
// Log job finish time & result.
//
LogTaskStatus(pRun->GetName(),
tszExeName,
IDS_LOG_JOB_STATUS_FINISHED,
dwExitCode);
}
else if (pRun->GetFlags() & RUN_STATUS_ABORTED)
{
// Log job closure on abort warning.
//
LogTaskError(pRun->GetName(),
tszExeName,
IDS_LOG_SEVERITY_WARNING,
IDS_LOG_JOB_WARNING_ABORTED,
&stFinished);
}
else if (pRun->GetFlags() & RUN_STATUS_TIMED_OUT)
{
// Log job closure on timeout warning.
//
LogTaskError(pRun->GetName(),
tszExeName,
IDS_LOG_SEVERITY_WARNING,
IDS_LOG_JOB_WARNING_TIMEOUT,
&stFinished,
0,
IDS_HELP_HINT_TIMEOUT);
}
pJob->Release();
}