// 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