// Copyright (c) 1996-1999 Microsoft Corporation //+------------------------------------------------------------------------- // // Microsoft Windows // // File: svcctrl.cxx // // Contents: Class for service control interface. // // Classes: // // Functions: // // // // History: 18-Nov-96 BillMo Created. // // Notes: // // Codework: // //-------------------------------------------------------------------------- #include "pch.cxx" #pragma hdrstop #include "trklib.hxx" #define THIS_FILE_NUMBER SVCCTRL_CXX_FILE_NO // This is static so that we can handle a PNP timing problem // (see the comment in CSvcCtrlInterface::ServiceHandler). BOOL CSvcCtrlInterface::_fStoppedOrStopping = TRUE; //+---------------------------------------------------------------------------- // // CSvcCtrlInterface::Initialize // // Register our service control handler with the control dispatcher, and set our state // to start-pending. // //+---------------------------------------------------------------------------- void CSvcCtrlInterface::Initialize(const TCHAR *ptszServiceName, IServiceHandler *pServiceHandler) { _fInitializeCalled = TRUE; _pServiceHandler = pServiceHandler; _fStoppedOrStopping = FALSE; _dwCheckPoint = 0; // Register with the control dispatcher. _ssh = RegisterServiceCtrlHandlerEx(ptszServiceName, ServiceHandler, this ); if (_ssh == 0) { TrkReportInternalError( THIS_FILE_NUMBER, __LINE__, HRESULT_FROM_WIN32(GetLastError()), ptszServiceName ); TrkRaiseLastError(); } // Go to the start-pending state. SetServiceStatus(SERVICE_START_PENDING, 0, NO_ERROR); } //+---------------------------------------------------------------------------- // // CSvcCtrlInterface::ServiceHandler // // This method is called by the control dispatcher. If we get a stop // or shutdown request, automatically send a stop-pending before calling // the service's handler. Interrogate is handled automatically in // this routine without bothering to call the service. // //+---------------------------------------------------------------------------- DWORD // static CSvcCtrlInterface::ServiceHandler(DWORD dwControl, DWORD dwEventType, PVOID EventData, PVOID pData) { // NOTE: In services.exe, this method is called on the one and only ServiceHandler // thread. So while we execute, no other service in this process can // receive notifications. Thus it is important that we do nothing // blocking or time-consuming here. DWORD dwRet = NO_ERROR; CSvcCtrlInterface *pThis = (CSvcCtrlInterface*)pData; #if DBG if( SERVICE_CONTROL_STOP == dwControl ) TrkLog(( TRKDBG_SVR|TRKDBG_WKS, TEXT("\n") )); TrkLog(( TRKDBG_SVR|TRKDBG_WKS, TEXT("ServiceHandler(%s)"), StringizeServiceControl(dwControl) )); #endif // On a stop or shutdown, flag it (e.g. so we don't try to accept new // requests from clients) and tell the SCM that we're stopping. switch (dwControl) { case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: pThis->SetServiceStatus(SERVICE_STOP_PENDING, 0, NO_ERROR); _fStoppedOrStopping = TRUE; break; } // Check for PNP timing issues. The problem is that during a stop // or shutdown, we unregister with PNP so that we don't get any more // notifications. This is fine, except that between now and the time // we do that unregister, more PNP notifications might get queued. So // when we get called to process those undesired notifications, we need // to ignore them here in the static function. // // As a quick fix, since only trkwks receives PNP notifications, we'll // just check to see if it's alive. A better fix (raided) is to have // a static function for each service, so that only the trkwks has to // deal with it. if( SERVICE_CONTROL_DEVICEEVENT == dwControl && _fStoppedOrStopping ) { TrkLog(( TRKDBG_WARNING, TEXT("Ignoring SERVICE_CONTROL_DEVICEEVENT; service is stopped") )); return dwRet; } // Call this service's service handler. As a final safety measure, // catch any exceptions (there should be none). We must be sure that we don't // kill this thread, since it's shared by everyone in services.exe. __try { dwRet = pThis->_pServiceHandler->ServiceHandler(dwControl, dwEventType, EventData, pData); switch (dwControl) { case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: break; case SERVICE_CONTROL_PAUSE: pThis->SetServiceStatus(SERVICE_PAUSED, pThis->_dwControlsAccepted, NO_ERROR); break; case SERVICE_CONTROL_CONTINUE: pThis->SetServiceStatus(SERVICE_RUNNING, pThis->_dwControlsAccepted, NO_ERROR); break; case SERVICE_CONTROL_INTERROGATE: pThis->SetServiceStatus(pThis->_dwState, pThis->_dwControlsAccepted, NO_ERROR); break; case SERVICE_CONTROL_DEVICEEVENT: break; } } __except( BREAK_THEN_RETURN( EXCEPTION_EXECUTE_HANDLER )) { TrkLog(( TRKDBG_ERROR, TEXT("Unexpected exception in CSvcCtrlInterface::ServiceHandler (%08x)"), GetExceptionCode() )); dwRet = ERROR_EXCEPTION_IN_SERVICE; } return dwRet; } //+---------------------------------------------------------------------------- // // CSvcCtrlInterface::SetServiceStatus // // Send a SetServiceStatus to the SCM. The checkpoint is automatically // maintained by this class. // //+---------------------------------------------------------------------------- void CSvcCtrlInterface::SetServiceStatus(DWORD dwState, DWORD dwControlsAccepted, DWORD dwWin32ExitCode) { SERVICE_STATUS ss; _dwState = dwState; _dwControlsAccepted = dwControlsAccepted; if( SERVICE_START_PENDING != dwState && SERVICE_STOP_PENDING != dwState ) { _dwCheckPoint = 0; } ss.dwServiceType = SERVICE_WIN32; // XX_SC ss.dwCurrentState = _dwState; ss.dwControlsAccepted = _dwControlsAccepted; ss.dwWin32ExitCode = dwWin32ExitCode; ss.dwServiceSpecificExitCode = 0; ss.dwCheckPoint = _dwCheckPoint++; ss.dwWaitHint = DEFAULT_WAIT_HINT; if (_ssh != 0) { if( !::SetServiceStatus(_ssh, &ss) ) { TrkLog(( TRKDBG_ERROR, TEXT("SetServiceStatus(%s) failed, gle=%lu"), (const TCHAR*)CDebugString(SServiceState(dwState)), GetLastError() )); } else { TrkLog(( TRKDBG_MISC, TEXT("SetServiceStatus(%s)"), (const TCHAR*)CDebugString(SServiceState(dwState)) )); } } } //+---------------------------------------------------------------------------- // // CSvcCtrlInterface::UpdateWaitHint // // Send a non-default wait hint to the SCM. // //+---------------------------------------------------------------------------- void CSvcCtrlInterface::UpdateWaitHint(DWORD dwMilliseconds) { SERVICE_STATUS ss; ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS; ss.dwCurrentState = _dwState; ss.dwControlsAccepted = _dwControlsAccepted; ss.dwWin32ExitCode = NO_ERROR; ss.dwServiceSpecificExitCode = 0; ss.dwCheckPoint = _dwCheckPoint++; ss.dwWaitHint = dwMilliseconds; if (_ssh != 0) ::SetServiceStatus(_ssh, &ss); } //+---------------------------------------------------------------------------- // // StringizeServiceControl (debug only) // //+---------------------------------------------------------------------------- #if DBG TCHAR * StringizeServiceControl( DWORD dwControl ) { switch( dwControl ) { case SERVICE_CONTROL_STOP: return TEXT("SERVICE_CONTROL_STOP"); case SERVICE_CONTROL_PAUSE: return TEXT("SERVICE_CONTROL_PAUSE"); case SERVICE_CONTROL_CONTINUE: return TEXT("SERVICE_CONTROL_CONTINUE"); case SERVICE_CONTROL_INTERROGATE: return TEXT("SERVICE_CONTROL_INTERROGATE"); case SERVICE_CONTROL_SHUTDOWN: return TEXT("SERVICE_CONTROL_SHUTDOWN"); case SERVICE_CONTROL_PARAMCHANGE: return TEXT("SERVICE_CONTROL_PARAMCHANGE"); case SERVICE_CONTROL_NETBINDADD: return TEXT("SERVICE_CONTROL_NETBINDADD"); case SERVICE_CONTROL_NETBINDREMOVE: return TEXT("SERVICE_CONTROL_NETBINDREMOVE"); case SERVICE_CONTROL_NETBINDENABLE: return TEXT("SERVICE_CONTROL_NETBINDENABLE"); case SERVICE_CONTROL_NETBINDDISABLE: return TEXT("SERVICE_CONTROL_NETBINDDISABLE"); case SERVICE_CONTROL_DEVICEEVENT: return TEXT("SERVICE_CONTROL_DEVICEEVENT"); default: return TEXT("Unknown"); } } #endif