//+---------------------------------------------------------------------------- // // 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", ¶ms.dwProcessId)) // SEC:REVIEWED 2002-04-30 // this function does not involve an unbounded string copy { EnumWindows(CloseWindowEnumProc, (LPARAM) ¶ms); 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(); }