/*++

Copyright (C) 1999-2001 Microsoft Corporation

Module Name:

    RESYNC.CPP

Abstract:


History:

    a-davj   04-Mar-97    Created.
    ivanbrug 01-Sep-2000  changed for svchost migration

--*/

#include "precomp.h"

#include <malloc.h>
#include <tchar.h>

#include "WinMgmt.h"

#include "resync.h"


// Timeout is a 64-bit value.  See documentation on SetWaitableTimer
// for why we are setting it this way.
#define             _SECOND     10000000
#define             RESYNC_TIMEOUT_INTERVAL 10 * _SECOND

DWORD               gdwADAPDelaySec = 0;
DWORD               gdwLodCtrDelaySec = 0;

BOOL                gfResyncInit = FALSE;
HANDLE              ghWaitableTimer = NULL;
BOOL                gfSpawnedResync = FALSE;


HANDLE              ghResyncThreadHandle = NULL;
HANDLE              ghResyncThreadEvent = NULL;
CRITICAL_SECTION*   g_pResyncCs = NULL;
DWORD               gdwResyncThreadId = 0;

// A global handle used to store the last dredger we
// kicked off!
HANDLE              ghChildProcessHandle = NULL;


void ResetResyncTimer( HANDLE hResyncTimer, BOOL bIsLoadCtr )
{
    DWORD dwErr = 0;
    __int64 qwDueTime  = bIsLoadCtr?(gdwLodCtrDelaySec * _SECOND):(gdwADAPDelaySec * _SECOND); // RESYNC_TIMEOUT_INTERVAL;

    // Convert it to relative time
    qwDueTime *= -1;

    // Copy the relative time into a LARGE_INTEGER.
    LARGE_INTEGER   li;

    li.LowPart  = (DWORD) ( qwDueTime & 0xFFFFFFFF );
    li.HighPart = (LONG)  ( qwDueTime >> 32 );

    if ( !SetWaitableTimer( hResyncTimer, &li, 0, NULL, NULL, FALSE ) )
    {
        dwErr = GetLastError();
    }

}

// This thread controls the actual shelling of a resync perf operation
DWORD WINAPI ResyncPerfThread( void* pVoid )
{
    RESYNCPERFDATASTRUCT*   pResyncPerfData = (RESYNCPERFDATASTRUCT*) pVoid;

    // We get the two handles, copy them and wait on them
    // The first handle is the terminate event, the second is the
    // timer on which to spin off the resync

    BOOL bIsLodCtr = pResyncPerfData->m_bIsLodCtr;
    HANDLE  aHandles[2];

    aHandles[0] = pResyncPerfData->m_hTerminate;
    HANDLE  hTimer = pResyncPerfData->m_hWaitableTimer;

    CRITICAL_SECTION*   pcs = pResyncPerfData->m_pcs;

	BOOL	bFullDredge = pResyncPerfData->m_fFullDredge;

    delete pResyncPerfData;
    pResyncPerfData = NULL;

    // Reset the spawned flag
    gfSpawnedResync = FALSE;

    // Okay.  Signal this event so the starting thread can get us going
    SetEvent( ghResyncThreadEvent );

    // Now, if ghChildProcessHandle is not NULL, then we've obviously kicked off a
    // dredge before.  See where the last one is at.  If it's not done, wait for
    // it to finish.  We will always check this at the start of this chunk of code,
    // since we are really the only location in which the process handle can ever get set,
    // and there really shouldn't be more than one thread ever, waiting to start another
    // dredge

    if ( NULL != ghChildProcessHandle )
    {

        aHandles[1] = ghChildProcessHandle;

        DWORD   dwWait = WaitForMultipleObjects( 2, aHandles, FALSE, INFINITE );

        // If abort was signalled, leave!
        if ( dwWait == WAIT_OBJECT_0 )
        {
            return 0;
        }

        // If the process handle was signalled, close the process, reset the timer
        // and we'll get ready to start the next dredge!
        if ( dwWait == WAIT_OBJECT_0 + 1 )
        {
            EnterCriticalSection( pcs );

            CloseHandle( ghChildProcessHandle );
            ghChildProcessHandle = NULL;
            ResetResyncTimer( hTimer, bIsLodCtr );

            LeaveCriticalSection( pcs );

        }

    }
    else
    {
        // If the Child Process Handle is NULL, we've never dredged before, so we'll
        // just reset the timer
        ResetResyncTimer( hTimer, bIsLodCtr );
    }

    BOOL    fHoldOff = TRUE;

    // Reset this handle to the timer now
    aHandles[1] = hTimer;

    while ( fHoldOff )
    {
        // Wait for either the terminate event or the timer
        DWORD   dwWait = WaitForMultipleObjects( 2, aHandles, FALSE, INFINITE );

        // This means the terminate was signaled
        if ( dwWait == WAIT_OBJECT_0 ) 
        {
            break;        
        } else
        // This means the timer was signaled
        if ( dwWait == WAIT_OBJECT_0 + 1 )
        {
            EnterCriticalSection( pcs );

            // Finally, if the current thread id != gdwResyncThreadId, this means another
            // resync perf thread got kicked off, inside of the critical section,
            // so we should just let it wait on the timer.  We don't really need to do
            // this, since the main thread will wait on this thread to complete before
            // it actually kicks off another thread.

            if ( GetCurrentThreadId() != gdwResyncThreadId )
            {
                // Used the following int 3 for debugging
                // _asm int 3;
                LeaveCriticalSection( pcs );
                break;
            }

            // Once we get through the critical section, check that the
            // timer is still signalled.  If it is not, this means that somebody
            // got control of the critical section and reset the timer

            if ( WaitForSingleObject( aHandles[1], 0 ) == WAIT_OBJECT_0 )
            {

                // Last quick sanity check on the abort event
                if ( WaitForSingleObject( aHandles[0], 0 ) == WAIT_OBJECT_0 )
                {
                    // Outa here!
                    LeaveCriticalSection( pcs );
                    break;
                }

                // Okay, we really will try to create the process now.
                gfSpawnedResync = TRUE;

                // We signalled to start the process, so make it so.
                PROCESS_INFORMATION pi;
                STARTUPINFO si;
                memset(&si, 0, sizeof(si));
                si.cb = sizeof(si);

                TCHAR * pWritebleBuffer = (TCHAR *)_alloca(sizeof(__T("WMIADAP.EXE /F"))+2);

                lstrcpy(pWritebleBuffer,(bFullDredge?__T("WMIADAP.EXE /F"):__T("WMIADAP.EXE")));

                BOOL bRes = CreateProcess(NULL, 
										  pWritebleBuffer,
										  NULL, 
										  NULL, 
										  FALSE, 
										  CREATE_NO_WINDOW,
										  NULL, 
										  NULL,  
										  &si, 
										  &pi);
                if(bRes)
                {
                    // Who cares about this one?
                    CloseHandle(pi.hThread);

                    // Clean up our old values
                    if ( NULL != ghChildProcessHandle )
                    {
                        CloseHandle( ghChildProcessHandle );
                        ghChildProcessHandle = NULL;
                    }

                    ghChildProcessHandle = pi.hProcess;
                }

                // We're done
                fHoldOff = FALSE;

            }   // Check that we're still signalled, or we will just have to go back to waiting

            LeaveCriticalSection( pcs );

        }   // IF timer was signalled

    }   // WHILE fHoldOff

    return 0;
}

// For the waitable timer
//#define _SECOND 10000000

// Create all the things we need
BOOL InitResync( void )
{
    if ( gfResyncInit )
        return gfResyncInit;

        
    if ( NULL == ghWaitableTimer )
    {
        ghWaitableTimer = CreateWaitableTimerW( NULL, TRUE, NULL );

        // We gotta big problem
        if ( NULL == ghWaitableTimer )
        {
            // Log an error here
            ERRORTRACE( ( LOG_WINMGMT, "Could not create a waitable timer for Resyncperf.\n" ) );
        }

    }

    if ( NULL == ghResyncThreadEvent )
    {
        ghResyncThreadEvent = CreateEvent( NULL, FALSE, FALSE, NULL );

        // We gotta big problem
        if ( NULL == ghResyncThreadEvent )
        {
            // Log an event here
            ERRORTRACE( ( LOG_WINMGMT, "Could not create a ResyncThreadEvent event for Resyncperf.\n" ) );
        }

    }

    // This critical section won't be freed or deleted because of
    // potential timing issues.  But since it's only one, I think
    // we can live with it.
    if ( NULL == g_pResyncCs )
    {
        g_pResyncCs = new CRITICAL_SECTION;

        // We gotta big problem
        if ( NULL == g_pResyncCs )
        {
            // Log an event here
            ERRORTRACE( ( LOG_WINMGMT, "Could not create a ResyncCs critical section for Resyncperf.\n" ) );
        }
        else
        {
            InitializeCriticalSection( g_pResyncCs );
        }

    }

    gfResyncInit = (    NULL    !=  ghWaitableTimer &&
                        NULL    !=  g_pResyncCs     &&
                        NULL    != ghResyncThreadEvent  );

    // Read the initialization information

    Registry reg;
    
    if ( Registry::no_error == reg.Open( HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\WBEM\\CIMOM" ) )
    {
        long lError = reg.GetDWORD( L"ADAPDelay", &gdwADAPDelaySec );

        if ( Registry::no_error == lError )
        {
            //This is what we want
        }
        else if ( ERROR_FILE_NOT_FOUND == reg.GetLastError() )
        {
            // Not set, so add it
            reg.SetDWORD( L"ADAPDelay", WMIADAP_DEFAULT_DELAY );
            gdwADAPDelaySec = WMIADAP_DEFAULT_DELAY;
        }
        else
        {
            // Error
            ERRORTRACE( ( LOG_WINMGMT, "ResyncPerf experienced an error while attempting to read the WMIADAPDelay value in the CIMOM subkey.  Continuing using a default value.\n" ) );
            gdwADAPDelaySec = WMIADAP_DEFAULT_DELAY;
        }

        lError = reg.GetDWORD( L"LodCtrDelay", &gdwLodCtrDelaySec );

        if ( Registry::no_error == lError )
        {
            //This is what we want
        }
        else if ( ERROR_FILE_NOT_FOUND == reg.GetLastError() )
        {
            // Not set, so add it
            reg.SetDWORD( L"LodCtrDelay", WMIADAP_DEFAULT_DELAY );
            gdwLodCtrDelaySec = WMIADAP_DEFAULT_DELAY_LODCTR;
        }
        else
        {
            // Error
            ERRORTRACE( ( LOG_WINMGMT, "ResyncPerf experienced an error while attempting to read the WMIADAPDelay value in the CIMOM subkey.  Continuing using a default value.\n" ) );
            gdwLodCtrDelaySec = WMIADAP_DEFAULT_DELAY_LODCTR;
        }        
    }
    else
    {
        // Error
        ERRORTRACE( ( LOG_WINMGMT, "ResyncPerf could not open the CIMOM subkey to read initialization data. Continuing using a default value.\n" ) );
        gdwADAPDelaySec = WMIADAP_DEFAULT_DELAY;
        gdwLodCtrDelaySec = WMIADAP_DEFAULT_DELAY_LODCTR;
    }

    return gfResyncInit;
}

// PLEASE NOTE - THIS FUNCTION IS NOT REENTRANT!  PLEASE DO NOT CALL IT ON MULTIPLE THREADS!
void ResyncPerf( HANDLE hTerminate, BOOL bIsLodCtr )
{

    // Assume that we should check the timer
    BOOL    fFirstTime = !gfResyncInit;

    if ( !InitResync() )
        return;

    
    EnterCriticalSection( g_pResyncCs );

    // Now, if this or the first time, or the spawned resyncflag is set to TRUE, then we need
    // to kick off another thread.  By checking gfSpawnedResync in a critical section, since
    // it only gets set in the same critical section, we ensure that we will resignal as needed
    // as well as only kick off a thread when we really need to.

    BOOL    fSpawnThread = ( fFirstTime || gfSpawnedResync );

    if ( !fSpawnThread )
    {
        // We are here because we don't appear to have spawned a resync.
        // This is either because we are servicing many lodctr requests
        // within our time delay, or a dredger was started and
        // a previous request request to dredge is waiting for
        // the process to complete.  If the child process handle
        // is not NULL, there is no real need to reset the
        // waitable timer

        if ( NULL == ghChildProcessHandle && ghResyncThreadHandle )
        {
            // Reset the timer here
            ResetResyncTimer( ghWaitableTimer , bIsLodCtr );
        }

    }

    LeaveCriticalSection( g_pResyncCs );


    if ( fSpawnThread )
    {
        HANDLE  ahHandle[2];

        if ( NULL != ghResyncThreadHandle )
        {
            ahHandle[0] = hTerminate;
            ahHandle[1] = ghResyncThreadHandle;

            // Wait for ten seconds on this handle.  If it is not signalled, something is
            // direly wrong.  We're probably not going to be able to kick off a dredge
            // so put some info to this effect in the error log.  The only time we should
            // have contention here, is when a lodctr event is signalled, just as the timer
            // becomes signalled.  The resync thread will wake up and start another dredge
            // this thread will wait for the other thread to complete before continuing.
            // We will kick off another resync thread, which will start another dredge,
            // but it will wait for the first dredge to continue.  This is a worst case
            // scenario, and arguably kicking off two dredges isn't that bad of a bailout

            DWORD   dwRet = WaitForMultipleObjects( 2, ahHandle, FALSE, 10000 );

            // We're done
            if ( dwRet == WAIT_OBJECT_0 )
            {
                return;
            }

            if ( dwRet != WAIT_OBJECT_0 + 1 )
            {
                ERRORTRACE( ( LOG_WINMGMT, "The wait for a termination event or ResyncThreadHandle timed out in Resyncperf.\n" ) );
                return;
            }

            CloseHandle( ghResyncThreadHandle );
            ghResyncThreadHandle = NULL;
        }

        EnterCriticalSection( g_pResyncCs );

        DWORD   dwThreadId = 0;

        RESYNCPERFDATASTRUCT*   pResyncData = new RESYNCPERFDATASTRUCT;

        // Boy are we low on memory!
        if ( NULL == pResyncData )
        {
            LeaveCriticalSection( g_pResyncCs );

            // Log an event here
            ERRORTRACE( ( LOG_WINMGMT, "Could not create a RESYNCPERFDATASTRUCT in Resyncperf.\n" ) );
            
            return;
        }

        // Store the data for the resync operation
        pResyncData->m_hTerminate = hTerminate;
        pResyncData->m_hWaitableTimer = ghWaitableTimer;
        pResyncData->m_pcs = g_pResyncCs;
		pResyncData->m_fFullDredge = fFirstTime;
		pResyncData->m_bIsLodCtr = bIsLodCtr;

        ghResyncThreadHandle =  CreateThread( NULL, 0, 
                                              (LPTHREAD_START_ROUTINE)ResyncPerfThread, (void*) pResyncData,
                                              0, &gdwResyncThreadId );

        LeaveCriticalSection( g_pResyncCs );


        if ( NULL == ghResyncThreadHandle )
        {
            LeaveCriticalSection( g_pResyncCs );

            // Log an event here
            ERRORTRACE( ( LOG_WINMGMT, "Could not create a ResyncPerfThread thread in Resyncperf.\n" ) );

            return;
        }
        else
        {
            // Wait for the resync thread event to be signalled by the thread we just started.
            // If it doesn't signal in 10 seconds, something is VERY wrong
            DWORD   dwWait = WaitForSingleObject( ghResyncThreadEvent, INFINITE );

            if ( dwWait != WAIT_OBJECT_0 )
            {
                // Log an event
                ERRORTRACE( ( LOG_WINMGMT, "The ResyncPerfThread thread never signaled the ghResyncThreadEvent in Resyncperf.\n" ) );

                return;
            }
        }

    }   // IF fSpawnThread

}

