/*-----------------------------------------------------------------------------

   Copyright    (c)    1995-1997    Microsoft Corporation

   Module  Name :
       wamexec.cxx

   Abstract:
       This module executes a wam request

   Author:
       David Kaplan    ( DaveK )     11-Mar-1997
       Lei Jin                  (leijin)         24-Apr-1997    (WamInfo & WamDictator)

   Environment:
       User Mode - Win32

   Project:
       W3 services DLL

-----------------------------------------------------------------------------*/

#include <w3p.hxx>
#include "wamexec.hxx"
#include "wamreq.hxx"
#include <wmrgexp.h>
#include <ole2.h>
#include <imd.h>
#include <mb.hxx>
#include <issched.hxx>

#define dwMDDefaultTimeOut 2000

#define PERIODIC_RESTART_TIME_DEFAULT       0
#define PERIODIC_RESTART_REQUESTS_DEFAULT	0
#define SHUTDOWN_TIME_LIMIT_DEFAULT			600

/*-----------------------------------------------------------------------------
 Globals
-----------------------------------------------------------------------------*/
WAM_DICTATOR *   g_pWamDictator;      // global wam dictator
PFN_INTERLOCKED_COMPARE_EXCHANGE g_pfnInterlockedCompareExchange = NULL;
static VOID UnloadNTApis(VOID);
static VOID LoadNTApis(VOID);

// Default Package name, defined in wamreg.h, shared by wamreg.dll.
WCHAR   g_szIISInProcWAMCLSID[]     = W3_INPROC_WAM_CLSID;
WCHAR   g_szIISOOPPoolWAMCLSID[]    = W3_OOP_POOL_WAM_CLSID;
WCHAR   g_szIISOOPPoolPackageID[]   = W3_OOP_POOL_PACKAGE_ID;
// Sink function at wamreg.dll
// This function will get called when user change application configuration on the fly.
//
HRESULT W3SVC_WamRegSink(       LPCSTR  szAppPath,
                                const DWORD     dwCommand,
                                DWORD*  pdwResult
                        );


/*-----------------------------------------------------------------------------
CreateWamRequestInstance
    Similar to CoCreateInstance followed by QueryInterface

Arguments:
    pHttpRequest - pointer to the HTTP REQUEST object containing all information
                   about the current request.
    pExec        - Execution descriptor block
    pstrPath     - Fully qualified path to Isapi DLL
    pWamInfo     - pointer to wam-info which will process this request
        ppWamRequestOut -pointer to a pointer that contains the return WamRequest.

Return Value:
    HRESULT

-----------------------------------------------------------------------------*/
HRESULT
CreateWamRequestInstance
(
HTTP_REQUEST    *       pHttpRequest,
EXEC_DESCRIPTOR *       pExec,
const STR *             pstrPath,
CWamInfo *              pWamInfo,
WAM_REQUEST **          ppWamRequestOut
)
    {
    HRESULT hr = NOERROR;
    WAM_REQUEST *   pWamRequest =
        new WAM_REQUEST(
                  pHttpRequest
                , pExec
                ,  pWamInfo
                );

    *ppWamRequestOut = NULL;
     if( pWamRequest == NULL)
        {
        hr = E_OUTOFMEMORY;
        goto LExit;
        }

    // Init the wam req
    hr = pWamRequest->InitWamRequest( pstrPath );

    if ( FAILED( hr ) )
        {

        DBGPRINTF((
              DBG_CONTEXT
            , "CreateWamRequestInstance - "
              "failed to init wam request. "
              "pHttpRequest(%08x) "
              "pWamInfo(%08x) "
              "pstrPath(%s) "
              "hr(%d) "
              "\n"
            , pHttpRequest
            , pWamInfo
            , pstrPath
            , hr
        ));

        delete pWamRequest;
        pWamRequest = NULL;
        goto LExit;

        }

    pWamRequest->AddRef();

    *ppWamRequestOut = pWamRequest;

LExit:
    return hr;
    }

/*-----------------------------------------------------------------------------
WAM_DICTATOR::ProcessWamRequest
    This function finds a WamInfo and makes a call to the WamInfo to process a Http Server
    Extension Request.
    It uses the HTTP_REQUEST passed in as well as the EXEC_DESCRIPTOR.

Arguments:
    pHttpRequest - pointer to the HTTP REQUEST object containing all information
                   about the current request.
    pExec        - Execution descriptor block
    pstrPath     - Fully qualified path to Module (DLL or ComIsapi ProgId)
    pfHandled    - Indicates we handled this request
    pfFinished   - Indicates no further processing is required

Return Value:
    HRESULT
-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::ProcessWamRequest
(
HTTP_REQUEST    *       pHttpRequest,
EXEC_DESCRIPTOR *       pExec,
const STR *             pstrPath,
BOOL *                  pfHandled,
BOOL *                  pfFinished
)
    {

    HRESULT                 hr = NOERROR;
    CWamInfo*               pWamInfo = NULL;
    STR     *                       pstrAppPath = NULL;
    CLSID                   clsidWam;
    BOOL                    fRet = FALSE;

    DBG_ASSERT(pHttpRequest);
    DBG_ASSERT(pExec);

    if (m_fShutdownInProgress)
        {
        DBGPRINTF((DBG_CONTEXT, "Wam Dictator: Shut down in progress, stops serving requests.\n"));

        // Signal the child event to indicate that the request should terminate.
        if ( pExec->IsChild() ) {
            pExec->SetChildEvent();
        }

        return E_FAIL;
        }

    pstrAppPath = pExec->QueryAppPath();

    if (pstrAppPath == NULL)
        {
        hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
        goto LExit;
        }

    hr = GetWamInfo( pstrAppPath, pHttpRequest, &pWamInfo );
    if ( FAILED(hr) )
        {
        DBGPRINTF(( DBG_CONTEXT,
                   "WAM_DICTATOR::ProcessWamRequest: GetWamInfo failed, hr = %08x\n",
                   hr
                   ));

        //
        //  For Child execution just return the error and don't
        //  disconnect, otherwise a race ensues between SSI and the disconnect
        //  cleanup
        //

        if ( pExec->IsChild() )
            {
            SetLastError( hr );
            goto LExit;
            }

        //
        // Since WAM_DICTATOR is going to send some message to the browser, we need
        // to set pfHandled = TRUE here.
        //
        *pfHandled = TRUE;

        if (W3PlatformType == PtWindows95)
            {
            pHttpRequest->SetLogStatus( HT_SERVER_ERROR, hr);
            // Win95 platform, no event log, special message without mention EventLog.
            pHttpRequest->Disconnect(HT_SERVER_ERROR, IDS_WAM_FAILTOLOADONW95_ERROR, TRUE, pfFinished);
            }
        else
            {
            //
            // Log to Event Log first.
            //
            const CHAR      *pszEventLog[2];
            CHAR szErrorDescription[2048];
            CHAR * pszErrorDescription = NULL;
            HANDLE hMetabase = GetModuleHandle( "METADATA.DLL" );

            if(FormatMessage(
                    FORMAT_MESSAGE_ALLOCATE_BUFFER |
                    FORMAT_MESSAGE_FROM_HMODULE |
                    FORMAT_MESSAGE_FROM_SYSTEM |
                    FORMAT_MESSAGE_IGNORE_INSERTS,
                    hMetabase,
                    hr,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                    (LPTSTR) &pszErrorDescription,
                    sizeof(szErrorDescription) - 1,
                    NULL
                    ) && pszErrorDescription )
            {
                strcpy(szErrorDescription, pszErrorDescription);
                LocalFree(pszErrorDescription);

            } else {
                wsprintf(szErrorDescription, "%08x", hr);
            }

            pszEventLog[0] = pstrAppPath->QueryStr();
            pszEventLog[1] = szErrorDescription;
            // Event log
            g_pInetSvc->LogEvent(W3_EVENT_FAIL_LOADWAM,
                                                    2,
                                                    pszEventLog,
                                                    0);

            pHttpRequest->SetLogStatus( HT_SERVER_ERROR, hr);
            pHttpRequest->Disconnect(HT_SERVER_ERROR, IDS_WAM_FAILTOLOAD_ERROR, TRUE, pfFinished);
            }
        //
        // Since we already called Disconnect, WAM_DICTATOR should return TRUE to high level.
        //
        hr = NOERROR;
        goto LExit;
        }

    hr = pWamInfo->ProcessWamRequest(pHttpRequest, pExec, pstrPath, pfHandled,pfFinished);

LExit:
    return hr;
    } // WAM_DICTATOR::ProcessWamRequest()

/*-----------------------------------------------------------------------------*
WAM_DICTATOR::WAM_DICTATOR
    Constructor

Arguments:
    None

Return Value:
    None
-----------------------------------------------------------------------------*/

WAM_DICTATOR::WAM_DICTATOR
(
)
:
//m_pPackageCtl(NULL),
m_cRef(0),
m_pMetabase(NULL),
m_fCleanupInProgress(FALSE),
m_fShutdownInProgress(FALSE),
m_hW3Svc ( (HANDLE)NULL ),
m_dwScheduledId (0),
m_strRootAppPath("/LM/W3SVC"),
m_HashTable(LK_DFLT_MAXLOAD, LK_DFLT_INITSIZE, LK_DFLT_NUM_SUBTBLS),
m_pidInetInfo(0)
    {
    IF_DEBUG( WAM_ISA_CALLS )
        DBGPRINTF((DBG_CONTEXT, "WAM_DICTATOR::WAM_DICTATOR\n"));

    InitializeListHead(&m_DyingListHead);

    INITIALIZE_CRITICAL_SECTION(&m_csWamCreation);
    INITIALIZE_CRITICAL_SECTION(&m_csDyingList);

    m_PTable.Init();
    //
    // Init wamreq allocation cache
    //

    DBG_REQUIRE( WAM_REQUEST::InitClass() );

    }//WAM_DICTATOR::WAM_DICTATOR


/*-----------------------------------------------------------------------------*
WAM_DICTATOR::~WAM_DICTATOR
    Destructor

Arguments:
    None
                                                //      (only interesting if out of process)
Return Value:
    None

-----------------------------------------------------------------------------*/
WAM_DICTATOR::~WAM_DICTATOR
(
)
    {
    IF_DEBUG( WAM_ISA_CALLS )
        DBGPRINTF((DBG_CONTEXT, "WAM_DICTATOR::~WAM_DICTATOR\n"));

    DBG_ASSERT(m_cRef == 0);

    DeleteCriticalSection(&m_csWamCreation);
    DeleteCriticalSection(&m_csDyingList);

    m_PTable.UnInit();
    //
    // Uninit wamreq allocation cache
    //
    WAM_REQUEST::CleanupClass();
    }//WAM_DICTATOR::~WAMDICTATOR


/*-----------------------------------------------------------------------------
WAM_DICTATOR::InitWamDictator
    Initializes Wam dictator

Arguments:
    None

Return Value:
    HRESULT

-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::InitWamDictator
(
)
    {
    IF_DEBUG( WAM_ISA_CALLS )
        DBGPRINTF((DBG_CONTEXT, "WAM_DICTATOR::InitWamDictator\n"));

    HRESULT     hr = NOERROR;
    IMDCOM *    pMetabase;


    DBG_ASSERT(g_pInetSvc->QueryMDObject());

    m_pMetabase = new MB( (IMDCOM*) g_pInetSvc->QueryMDObject() );
    if (m_pMetabase == NULL)
        {
        hr = E_OUTOFMEMORY;
        goto LExit;
        }

    // Save the handle of the W3Svc process for use when copying tokens to out of proc Wam's
    // NOTE: this is actually a "pseudo-handle", which is good enough.  Note that pseudo-handles
    // do NOT need to be closed.  CloseHandle is a no-op on a pseudo-handle.
    m_hW3Svc = GetCurrentProcess();

    
    //
    // Get the process id for inetinfo. Under some circumstances com 
    // svcs my get hosed. This causes object activation to happen in 
    // process even if the object is registered to be launched in the
    // surrogate. In order to prevent inetinfo from AV'ing we want to 
    // prevent these applications from running.
    //
    m_pidInetInfo = GetCurrentProcessId();

    DBG_REQUIRE( m_ServerVariableMap.Initialize() );

    // Register the Sink function at WamReg.
    WamReg_RegisterSinkNotify(W3SVC_WamRegSink);

    LoadNTApis();

    // Make the reference count to 1.
    Reference();
LExit:
    return hr;
    }//WAM_DICTATOR::InitWamDictator

/*-----------------------------------------------------------------------------
WAM_DICTATOR::



-----------------------------------------------------------------------------*/
LK_PREDICATE
WAM_DICTATOR::DeleteInShutdown
(
CWamInfo*       pWamInfo,
void*           pvState
)
    {
    IF_DEBUG( WAM_ISA_CALLS )
        DBGPRINTF((DBG_CONTEXT, "WAM_DICTATOR::DeleteInShutdown\n"));

    DWORD dwPriorState;

    dwPriorState = pWamInfo->ChangeToState(WIS_SHUTDOWN);

    DBGPRINTF((DBG_CONTEXT, "Start shutdown, change state to WIS_SHUTDOWN\n"));
    DBG_REQUIRE(dwPriorState == WIS_RUNNING || dwPriorState == WIS_PAUSE || dwPriorState == WIS_CPUPAUSE);

    // remove from the hash table here. and clean up later.
    g_pWamDictator->InsertDyingList(pWamInfo, TRUE);

    return LKP_PERFORM;
    }//WAM_DICTATOR::DeleteInShutdown



/*-----------------------------------------------------------------------------
WAM_DICTATOR::StartShutdown
    Starts to shutdown Wam dictator

Arguments:
    None

Return Value:
    HRESULT
-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::StartShutdown
(
)
    {
    IF_DEBUG( WAM_ISA_CALLS )
        DBGPRINTF((DBG_CONTEXT, "WAM_DICTATOR::StartShutdown\n"));

    HRESULT         hr = NOERROR;
    CWamInfo*       pWamInfo;
    DWORD           dwErr;
    DWORD           dwPriorState;

    InterlockedExchange((LPLONG)&m_fShutdownInProgress, (LONG)TRUE);

    WamReg_UnRegisterSinkNotify();

    m_HashTable.DeleteIf(DeleteInShutdown, NULL);

    return hr;
    }

/*-----------------------------------------------------------------------------*
WAM_DICTATOR::UninitWamDictator
    Un-initializes Wam dictator

Arguments:
    None

Return Value:
    HRESULT
-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::UninitWamDictator
(
)
    {
    IF_DEBUG( WAM_ISA_CALLS )
        DBGPRINTF((DBG_CONTEXT, "WAM_DICTATOR::UninitWamDictator\n"));

    HRESULT     hr = NOERROR;

    // UNDONE leijin thinks we can get rid of this RemoveWorkItem code???
    ScheduledId_Lock();
    if (m_dwScheduledId != 0)
        {
        DBGPRINTF((DBG_CONTEXT, "remove work item %d\n", m_dwScheduledId));
        // Cancel the ScheduleWorkItem
        RemoveWorkItem(m_dwScheduledId);
        m_dwScheduledId = 0;
        }
    ScheduledId_UnLock();
    DBG_ASSERT(m_dwScheduledId == 0);

    // Clean up the dying list.
    CleanUpDyingList();

    // After the CleanUpDyingList() call, this should be an empty list.
    //
    DBG_ASSERT(IsListEmpty(&m_DyingListHead));

    //
    // Allow all other references to WAM_DICTATOR to drain away.
    // This loop is especially important in shutdown during stress scenario.
    // Unload Application while w3svc is still running, a busy application might
    // still have some outstanding HTTPREQUEST unfinished which hold the reference to
    // this WAM_DICTATOR.
    //
    while (m_cRef != 1)
        {
        DBGPRINTF((DBG_CONTEXT, "Still have out-standing reference(%d) to this WAM_DICTATOR\n",
                m_cRef));
        Sleep(20);
        }

    // Delete m_pMetabase
    // pMetabase should be there
    DBG_REQUIRE(m_pMetabase);
    delete m_pMetabase;
    m_pMetabase = NULL;

    UnloadNTApis();

    //
    // Dereference to balance the reference in InitWamDictator. after
    // this Dereference, ref count of WAM_DICTATOR should be 0.
    //
    Dereference();
    DBGPRINTF((DBG_CONTEXT, "Wam Dictator Exits.\n"));
    return hr;
    }//WAM_DICTATOR::UninitWamDictator

/*-----------------------------------------------------------------------------
WAM_DICTATOR::MDGetAppVariables

Giving a Metabase Path, find out the Application Path, WAM CLSID, and Context of WAM object of
the application that apply to the metabase path.

Argument:
szMetabasePath: [in]    A metabase path.
pfAllowApptoRun:        [out]   True, if application is enabled to run.
pclsidWam:              [out]   a pointer to the buffer for WAM CLSID
pfInProcess:            [out]   a pointer to DWORD for Context
pfEnableTryExcept   [out]   a pointer to DWORD for EnableTryExcept flag

Return:
HRESULT
-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::MDGetAppVariables
(
LPCSTR  szMetabasePath,
BOOL*   pfAllowAppToRun,
CLSID*  pclsidWam,
BOOL*   pfInProcess,
BOOL*   pfInPool,
BOOL*   pfEnableTryExcept,
DWORD   *pdwOOPCrashThreshold,
BOOL*   pfJobEnabled,
WCHAR*  wszPackageID,
DWORD*	pdwPeriodicRestartRequests,
DWORD*	pdwPeriodicRestartTime,
MULTISZ*pmszPeriodicRestartSchedule,
DWORD*  pdwShutdownTimeLimit
)
    {
    HRESULT         hr = NOERROR;
    METADATA_RECORD recMetaData;
    DWORD           dwRequiredLen;
    CHAR            szclsidWam[uSizeCLSIDStr];
    WCHAR           wszclsidWam[uSizeCLSIDStr];
    BOOL            fKeyOpen = FALSE;
    DWORD           dwAppMode = 0;
    BOOL            fEnableTryExcept = TRUE;
    DWORD           dwAppState = 0;

    DBG_ASSERT(szMetabasePath);
    DBG_ASSERT(pfAllowAppToRun);
    DBG_ASSERT(pclsidWam);
    DBG_ASSERT(pfInProcess);
    DBG_ASSERT(pfInPool);
    DBG_ASSERT(pfEnableTryExcept);
    DBG_ASSERT(pdwOOPCrashThreshold);
    DBG_ASSERT(pfJobEnabled);
    DBG_ASSERT(m_pMetabase);
    DBG_ASSERT(wszPackageID);
	DBG_ASSERT(pdwPeriodicRestartRequests);
	DBG_ASSERT(pdwPeriodicRestartTime);
	DBG_ASSERT(pmszPeriodicRestartSchedule);
	DBG_ASSERT(pdwShutdownTimeLimit);

    wszPackageID[0] = L'\0';
    // Open Key
    if (!m_pMetabase->Open(szMetabasePath, METADATA_PERMISSION_READ))
            {
            hr = HRESULT_FROM_WIN32(GetLastError());
            goto LErrExit;
            }
    fKeyOpen = TRUE;

    // if MD_APP_STATE exists, and is defined with a value APPSTATUS_PAUSE,
    // then, fAllowAppToRun is set to FALSE
    // otherwise, fAllowAppToRun is set to TRUE
    if (m_pMetabase->GetDword("", MD_APP_STATE, IIS_MD_UT_WAM, (DWORD *)&dwAppState, METADATA_INHERIT))
        {
        if (dwAppState == APPSTATUS_PAUSE)
            {
            *pfAllowAppToRun = FALSE;
            goto LErrExit;
            }
        }

    *pfAllowAppToRun = TRUE;
    if (!m_pMetabase->GetDword("", MD_APP_ISOLATED, IIS_MD_UT_WAM, &dwAppMode, METADATA_INHERIT))
        {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto LErrExit;
        }

    *pfInProcess = ( dwAppMode == eAppInProc) ? TRUE : FALSE;
    *pfInPool = ( dwAppMode == eAppInProc || dwAppMode == eAppOOPPool ) ? TRUE : FALSE;

    if (dwAppMode == eAppOOPIsolated)
        {
        DWORD dwRet = 0;
        CHAR  szPackageID[uSizeCLSIDStr];

        //
        // Disable job objects for IIS5.1, original code would read the MD_CPU_APP_ENABLED 
        // property from metabase
        //
        *pfJobEnabled = FALSE;

        dwRequiredLen= uSizeCLSIDStr;
        if (!m_pMetabase->GetString("", MD_APP_PACKAGE_ID, IIS_MD_UT_WAM, szPackageID, &dwRequiredLen, METADATA_INHERIT))
            {
            DBG_ASSERT(dwRequiredLen <= uSizeCLSIDStr);
            hr = HRESULT_FROM_WIN32(GetLastError());
            goto LErrExit;
            }

        dwRet = MultiByteToWideChar(CP_ACP, 0, szPackageID, -1, wszPackageID, uSizeCLSIDStr);
        DBG_ASSERT(dwRet != 0);
        }
    else if (dwAppMode == eAppOOPPool)
        {
        wcsncpy(wszPackageID, g_szIISOOPPoolPackageID, sizeof(g_szIISOOPPoolPackageID)/sizeof(WCHAR));
        // Always disable Job object for pool process.
        *pfJobEnabled = FALSE;
        }
    else
        {
        *pfJobEnabled = FALSE;
        }


    // If not present assume the default (TRUE) (For Debug builds set default to  FALSE)
    if (m_pMetabase->GetDword("", MD_ASP_EXCEPTIONCATCHENABLE, ASP_MD_UT_APP, (DWORD *)&fEnableTryExcept, METADATA_INHERIT))
        {
        *pfEnableTryExcept = fEnableTryExcept ? TRUE : FALSE;
        }
    else
        {
        #if DBG
            *pfEnableTryExcept = FALSE;
        #else
            *pfEnableTryExcept = TRUE;
        #endif
        }

    DWORD dwThreshold;

    if (dwAppMode == eAppInProc)
        {
        hr = CLSIDFromString((LPOLESTR) g_szIISInProcWAMCLSID, (LPCLSID)pclsidWam);
        }
    else if (dwAppMode == eAppOOPPool)
        {
        hr = CLSIDFromString((LPOLESTR) g_szIISOOPPoolWAMCLSID, (LPCLSID)pclsidWam);

        *pdwOOPCrashThreshold = 0XFFFFFFFF;
        }
    else
        {
        if (m_pMetabase->GetDword("", MD_APP_OOP_RECOVER_LIMIT, IIS_MD_UT_WAM, (DWORD *)&dwThreshold, METADATA_INHERIT))
            {
            // It used to be OOP_CRASH_LIMIT (range 1 - 5)
            // Now, the name is changed to RECOVER_LIMIT with range of (0-xxx).
            // 0 means no recovery, same as 1 in crash limit, 1 crash, and it's over, (0 recovery).
            // Add 1 because internally this threshold is implemented in crash_limit concept.
            // 0xFFFFFFFF is unlimited.

            // Changed to default to unlimited - Bug 240012. The idea of making this time
            // dependent seems much smarter than making it infinite. But no one was willing
            // to step up and decide what the right parameters were.
            if (dwThreshold != 0xFFFFFFFF)
                {
                dwThreshold++;
                }
            }
        else
            {
            dwThreshold = APP_OOP_RECOVER_LIMIT_DEFAULT;
            }

        *pdwOOPCrashThreshold = dwThreshold;

        dwRequiredLen= uSizeCLSIDStr;
        if (!m_pMetabase->GetString("", MD_APP_WAM_CLSID, IIS_MD_UT_WAM, szclsidWam, &dwRequiredLen, METADATA_INHERIT))
            {
            DBG_ASSERT(dwRequiredLen <= uSizeCLSIDStr);
            hr = HRESULT_FROM_WIN32(GetLastError());
            goto LErrExit;
            }

        MultiByteToWideChar(CP_ACP, 0, szclsidWam, -1, wszclsidWam, uSizeCLSIDStr);

        hr = CLSIDFromString((LPOLESTR)wszclsidWam, (LPCLSID)pclsidWam);
        }

	if ( dwAppMode != eAppInProc )
	{
		//
		// Get the application recycle settings
		//
		
        //
        // Disable wam recycling for IIS5.1, original code would read these properties 
        // from metabase
        //
        *pdwPeriodicRestartTime = PERIODIC_RESTART_TIME_DEFAULT;

        *pdwPeriodicRestartRequests = PERIODIC_RESTART_REQUESTS_DEFAULT;

		*pdwShutdownTimeLimit = SHUTDOWN_TIME_LIMIT_DEFAULT;

		pmszPeriodicRestartSchedule->Reset();
	}

    if (FAILED(hr))
        goto LErrExit;

LErrExit:

    if (fKeyOpen)
        {
        m_pMetabase->Close();
        }

    return hr;
    }//WAM_DICTATOR::MDGetAppVariables


/*-----------------------------------------------------------------------------
WAM_DICTATOR::GetWamInfo
    Returns the interface pointer for a wam, NULL if wam not found

Arguments:
    pstrPath            [in]  wam key: path to extension dll, prog id, etc.
    fInProc             [in]  run the wam in-proc?
    ppIWam              [out] interface pointer for the wam

Return Value:
    TRUE or FALSE
-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::GetWamInfo
(
const STR*      pstrAppPath,
const HTTP_REQUEST * pHttpRequest,
CWamInfo **     ppWamInfo
)
    {
    HRESULT     hr = NOERROR;
    INT         AppState;
    CWamInfo*   pWamInfo = NULL;

    DBG_ASSERT(pstrAppPath);

    AppState = FindWamInfo(pstrAppPath, &pWamInfo);
    if (-1 == AppState)
        {
        //
        // Lock the Creation process
        // to avoid multiple threads try to create the same WAMInfo,
        // (init WAM object, very expensive in out-proc cases)
        //
        CreateWam_Lock();

        //
        // Try to find it again, in case another thread already created it.
        // This check is better that creating another WamInfo.
        //
        AppState = FindWamInfo(pstrAppPath, &pWamInfo);
        if (-1 == AppState)
            {
            //
            // We are out of luck in our search for a valid Wam Info
            // Let us just create a new one and make it happen.
            // Note: Creation also adds the WamInfo to the hashtable
            //
            DBG_ASSERT(pHttpRequest != NULL);
            hr = CreateWamInfo( *pstrAppPath, &pWamInfo, pHttpRequest->QueryW3Instance());
            }
        CreateWam_UnLock();
        }

    if (m_fShutdownInProgress && pWamInfo != NULL)
        {
        hr = HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS);
        pWamInfo->Dereference();
        pWamInfo = NULL;
        }

    *ppWamInfo = pWamInfo;
    return hr;
    }//WAM_DICTATOR::GetWamInfo



/*-----------------------------------------------------------------------------

 WAM_DICTATOR::CreateWamInfo()

 Description:
   Creates a new CWamInfo object for the specified application root path.
   On successful creation, it adds the object to the hash table internal
     to the WAM_DICTATOR that contains the active WamInfos.
   Finally, it returns the object via the pointer supplied.

 Arguments:
   strAppRootPath - string containing the metabase path for for the
                    Application Root
   ppWamInfo      - pointer to pointer to CWamInfo object. On successful
                    return this contains the pointer to the new
                    created CWamInfo

 Returns:
    HRESULT
      NOERROR - on success
      and specific error codes on failure

 Note:
    This function should be called with the WAM_DICTATOR WamInfo lock held.
-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::CreateWamInfo
(
const STR & strAppRootPath,
CWamInfo ** ppWamInfo,
PW3_SERVER_INSTANCE pwsiInstance
)
    {
    HRESULT    hr = NOERROR;
    CLSID      clsidWam;
    BOOL       fInProcess;
    BOOL       fEnableTryExcept;
    CWamInfo * pWamInfo;
    DWORD      dwThreshold;    // OOP crash threshold
    BOOL       fAllowAppToRun;
    BOOL       fJobEnabled;
    BOOL       fInPool;
    WCHAR      wszPackageID[uSizeCLSIDStr];
	DWORD	   dwPeriodicRestartRequests;
	DWORD	   dwPeriodicRestartTime;
	DWORD	   dwShutdownTimeLimit;
	MULTISZ    mzPeriodicRestartSchedule;

    DBG_ASSERT( NULL != ppWamInfo );
    DBG_ASSERT( NULL == *ppWamInfo);

    // Get the latest Application path, CLSID, and inproc/out-of-proc flag
    hr = MDGetAppVariables(strAppRootPath.QueryStr(),
                           &fAllowAppToRun,
                           &clsidWam,
                           &fInProcess,
                           &fInPool,
                           &fEnableTryExcept,
                           &dwThreshold,
                           &fJobEnabled,
                           wszPackageID,
						   &dwPeriodicRestartRequests,
						   &dwPeriodicRestartTime,
						   &mzPeriodicRestartSchedule,
						   &dwShutdownTimeLimit
						   );

    if ( SUCCEEDED( hr) && fAllowAppToRun)
        {
        //
        // Create a New CWamInfo
        //
        if (fInProcess)
            {
            pWamInfo = new CWamInfo( strAppRootPath, 
                                     fInProcess,
                                     fInPool, 
                                     fEnableTryExcept, 
                                     clsidWam
                                     );
            }
        else
            {
            //
            // Check to see if we have scheduled restart times.  If so,
            // set the restart time to the earlier of the configured
            // restart time, or the earliest scheduled time.
            //

            if ( mzPeriodicRestartSchedule.QueryStringCount() )
            {
                SYSTEMTIME  st;
                DWORD       dwTimeOfDayInMinutes;
                DWORD       dwScheduleInMinutes;
                LPSTR       szHour;
                LPSTR       szMinute;

                GetLocalTime( &st );
                dwTimeOfDayInMinutes = st.wHour * 60 + st.wMinute;

                IF_DEBUG( WAM_ISA_CALLS )
                    DBGPRINTF((
                        DBG_CONTEXT,
                        "Current Time is %d.\r\n",
                        dwTimeOfDayInMinutes
                        ));

                szHour = mzPeriodicRestartSchedule.QueryStrA();

                DBG_ASSERT( szHour );

                while ( *szHour )
                {
                    IF_DEBUG( WAM_ISA_CALLS )
                        DBGPRINTF((
                            DBG_CONTEXT,
                            "Considering recycle at %s.\r\n",
                            szHour
                            ));
                    
                    szMinute = strchr( szHour, ':' );

                    if ( !szMinute )
                    {
                        //
                        // Parsing error - hour and minute not separated
                        // by a ':'
                        //

                        break;
                    }

                    szMinute++;

                    dwScheduleInMinutes = atol( szHour ) * 60;
                    dwScheduleInMinutes += atol( szMinute );

                    if ( dwScheduleInMinutes <= dwTimeOfDayInMinutes )
                    {
                        dwScheduleInMinutes += 24 * 60;
                    }

                    dwScheduleInMinutes -= dwTimeOfDayInMinutes;

                    IF_DEBUG( WAM_ISA_CALLS )
                        DBGPRINTF((
                            DBG_CONTEXT,
                            "Scheduled time is %d minutes from now.\r\n",
                            dwScheduleInMinutes
                            ));

                    //
                    // If the scheduled time is earlier than the current
                    // dwPeriodicRestartTime, then replace the existing
                    // value.
                    //

                    if ( dwScheduleInMinutes < dwPeriodicRestartTime ||
                         dwPeriodicRestartTime == 0 )
                    {
                        dwPeriodicRestartTime = dwScheduleInMinutes;
                    }

                    szHour += strlen( szHour ) + 1;
                }
            }

            if ( dwPeriodicRestartRequests || dwPeriodicRestartTime )
            {
                DBGPRINTF((
                    DBG_CONTEXT,
                    "Recycling will occur in %d minutes. or when %d requests have been served.\r\n",
                    dwPeriodicRestartTime,
                    dwPeriodicRestartRequests
                    ));
            }

            pWamInfo = new CWamInfoOutProc(strAppRootPath,
                                           fInProcess,
                                           fInPool, 
                                           fEnableTryExcept,
                                           clsidWam,
                                           dwThreshold,
                                           pwsiInstance,
                                           fJobEnabled,
										   dwPeriodicRestartRequests,
										   dwPeriodicRestartTime,
										   dwShutdownTimeLimit
                                           );
            }

        if (pWamInfo)
            {
			//
			// This is WAM_DICTATOR's reference on the CWamInfo.  In "normal"
			// operations, this will be the last reference.
			//

			pWamInfo->Reference();

            BOOL fInitialize = (fInProcess || !fJobEnabled || !pwsiInstance->AreProcsCPUStopped());
            if (fInitialize)
                {
                //
                //Init the CWamInfo.  this call will CoCreateInstance WAM object.
                //
                hr = pWamInfo->Init( wszPackageID, m_pidInetInfo );
                if (SUCCEEDED(hr))
                    {
                    pWamInfo->ChangeToState(WIS_RUNNING);
                    }
                else
                    {
                    pWamInfo->UnInit();
                    }
                }
            else
                {
                pWamInfo->ChangeToState(WIS_CPUPAUSE);
                }

            if (SUCCEEDED(hr))
                {

                if ( LK_SUCCESS != m_HashTable.InsertRecord(pWamInfo))
                    {
                    if (fInitialize)
                        {
                        pWamInfo->UnInit();
                        }
					pWamInfo->Dereference();	// should be last reference
                    //delete pWamInfo;
                    hr = E_FAIL;  // NYI: What is the right failure code?
                    }
                else
                    {

                    //
                    // Finally we are successful with a valid WAmInfo.
                    // Return this.  Will be balanced by a DeleteRecord
					// in CProcessTable::RemoveWamInfoFromProcessTable
                    //
                    pWamInfo->Reference();
                    *ppWamInfo = pWamInfo;
                    }
                }
            else
                {
                DBG_ASSERT( NOERROR != hr);
				pWamInfo->Dereference();
                //delete pWamInfo;
                }
            }
        else
            {
            // pWamInfo == NULL
            hr = E_OUTOFMEMORY;
            }
        }
    else
        {
        if (SUCCEEDED(hr))
            {
            hr = HRESULT_FROM_WIN32(ERROR_SERVER_DISABLED);
            *ppWamInfo = NULL;
            }
        //
        // NYI: Isn't pstrNewPath going away?
        // Should I bother deleting pstrNewPath ??
        //
        }

    //
    // Either we should be returning an error or send back the proper pointer
    //
    DBG_ASSERT( (hr != NOERROR) || (*ppWamInfo != NULL));

    return ( hr);
    } // WAM_DICTATOR::CreateWamInfo()


/*-----------------------------------------------------------------------------
WAM_DICTATOR::FindWamInfo
    Returns the interface pointer for a wam, NULL if wam not found

Arguments:
    pstrPath    - [in]  wam key: path to extension dll, prog id, etc.
    ppIWam      - [out] interface pointer for the wam

Return Value:
    TRUE or FALSE

-----------------------------------------------------------------------------*/
INT
WAM_DICTATOR::FindWamInfo
(
const STR*      pstrMetabasePath,
CWamInfo**      ppWamInfo
)
    {
    INT         AppState = -1;
    CWamInfo*   pWamInfo = NULL;
    char*       pszKey;

    DBG_ASSERT(pstrMetabasePath);
    DBG_ASSERT(ppWamInfo);

    pszKey = pstrMetabasePath->QueryStr();

    HashReadLock();
    m_HashTable.FindKey(pszKey, &pWamInfo);
    HashReadUnlock();

    if (pWamInfo)
        {
        AppState = pWamInfo->QueryState();

        DBG_ASSERT(AppState != WIS_START || AppState != WIS_END);
        }

    *ppWamInfo = pWamInfo;
    return AppState;
    }//WAM_DICTATOR::FindWamInfo



/*-----------------------------------------------------------------------------
WAM_DICTATOR::UnLoadWamInfo
    Unload an OOP Wam.

Arguments:
    pstrWamPath    - [in]  wam key: path to extension dll, prog id, etc.
    fCPUPause      - [in]  If true, CPU pauses the WAM. Kills the process
                           but keeps the info around.
    pfAppCpuUnloaded - [out] If nonNULL, set to TRUE iff an app was killed.

Return Value:

    HRESULT        - Error code.

-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::UnLoadWamInfo
(
STR     *pstrAppPath,
BOOL    fCPUPause,
BOOL    *pfAppCpuUnloaded
)
    {
    IF_DEBUG( WAM_ISA_CALLS )
        DBGPRINTF((DBG_CONTEXT, "WAM_DICTATOR::UnLoadWamInfo\n"));

    int         iAppState;
    CWamInfo*   pWamInfo = NULL;
    DWORD       eWISPrevState;
    DWORD       eWISState;
    HRESULT     hr = NOERROR;
    BOOL        fInProc;
    BOOL        fAppCpuUnloaded = FALSE;

    CreateWam_Lock();

    iAppState = FindWamInfo(pstrAppPath,
                            &pWamInfo);

    CreateWam_UnLock();

    if (iAppState != -1)
        {
        bool fRet = FALSE;

        CProcessEntry* pProcEntry = NULL;


        fInProc = pWamInfo->FInProcess();
        pProcEntry = pWamInfo->QueryProcessEntry();
        DBG_ASSERT(pProcEntry != NULL);

        pProcEntry->AddRef();

        while (pWamInfo != NULL)
            {
            eWISState = (fCPUPause == TRUE) ? WIS_CPUPAUSE : WIS_SHUTDOWN;
            eWISPrevState = pWamInfo->ChangeToState(WIS_RUNNING, eWISState);
            //
            // Terminate process and kill off current requests.
            //

            if (WIS_RUNNING == eWISPrevState)
                {
                DBG_ASSERT(pWamInfo->HWamProcess() != NULL);

                hr = ShutdownWamInfo(pWamInfo,
                                    HASH_TABLE_REF + FIND_KEY_REF
                                    );

                if (!fCPUPause)
                    {
                    LK_RETCODE  LkReturn;

                    LkReturn = m_HashTable.DeleteKey(
                                    pWamInfo->QueryApplicationPath().QueryStr());
                    DBG_ASSERT(LK_SUCCESS == LkReturn);
                    //
                    // Release FIND_KEY_REF
                    //
                    pWamInfo->Dereference();
                    if (LK_SUCCESS == LkReturn)
                        {
						pWamInfo->Dereference();
                        //delete pWamInfo;
                        pWamInfo = NULL;
                        }
                    }
                else
                    {
                    //
                    // Application is running. Kill It
                    //
                    DBG_ASSERT(!pWamInfo->FInProcess());
                    //
                    // Get rid of the shutting down flag
                    //
                    pWamInfo->ClearMembers();
                    //
                    // Release FIND_KEY_REF
                    //
                    pWamInfo->Dereference();

                    fAppCpuUnloaded = TRUE;

                    }
                }

            if (fInProc)
                {
                pWamInfo = NULL;
                }
            else
                {
                fRet = m_PTable.FindWamInfo(pProcEntry,&pWamInfo);
                DBG_ASSERT(fRet == TRUE);
                }
            }

        pProcEntry->Release();
        }

    if (pfAppCpuUnloaded != NULL) {
        *pfAppCpuUnloaded = fAppCpuUnloaded;
    }
    return hr;
    }

/*-----------------------------------------------------------------------------
WAM_DICTATOR::CPUResumeWamInfo
    Resumes a CPU Paused OOP Wam.

Arguments:
    pstrWamPath    - [in]  wam key: path to extension dll, prog id, etc.

Return Value:

-----------------------------------------------------------------------------*/
VOID
WAM_DICTATOR::CPUResumeWamInfo
(
STR   *pstrWamPath
)
    {
    int         iAppState;
    CWamInfo*   pWICurrentApplication = NULL;
    DWORD       eWISPrevState;
    CLSID      clsidWam;
    BOOL       fInProcess;
    BOOL       fInPool;
    BOOL       fEnableTryExcept;
    DWORD      dwThreshold;    // OOP crash threshold
    BOOL       fAllowAppToRun;
    BOOL       fJobEnabled;
    WCHAR      wszPackageID[uSizeCLSIDStr];
	DWORD	   dwPeriodicRestartRequests;
	DWORD	   dwPeriodicRestartTime;
	DWORD	   dwShutdownTimeLimit;
	MULTISZ    mzPeriodicRestartSchedule;
    HRESULT    hr;


    CreateWam_Lock();

    iAppState = FindWamInfo(pstrWamPath,
                            &pWICurrentApplication);

    CreateWam_UnLock();

    if (iAppState != -1)
        {
        // Application may not be loaded. This method is called for every
        // application defined under the site being paused.

        // Get the latest Application path, CLSID, and inproc/out-of-proc flag
        hr = MDGetAppVariables(pWICurrentApplication->QueryApplicationPath().QueryStr(),
                               &fAllowAppToRun,
                               &clsidWam,
                               &fInProcess,
                               &fInPool,
                               &fEnableTryExcept,
                               &dwThreshold,
                               &fJobEnabled,
                               wszPackageID,
							   &dwPeriodicRestartRequests,
							   &dwPeriodicRestartTime,
							   &mzPeriodicRestartSchedule,
							   &dwShutdownTimeLimit);


        if (iAppState == WIS_CPUPAUSE && SUCCEEDED(hr))
            {
            if (SUCCEEDED(pWICurrentApplication->Init(wszPackageID, m_pidInetInfo)))
                {
                pWICurrentApplication->ChangeToState(WIS_CPUPAUSE, WIS_RUNNING);
                }
            }

        //
        // FindWamInfo References this, so dereference it.
        //
        pWICurrentApplication->Dereference();
        }

    }//WAM_DICTATOR::CPUResumeWamInfo

/*-----------------------------------------------------------------------------
WAM_DICTATOR::ShutdownWamInfo
    This function currently can only call a blocking method of WAM, UnInitWam.
    In the future, this function might support a non-blocking method provided by WAM in
    case of IIS shutdown.

Arguments:
    pWamInfo            [in]    a pointer to WamInfo
    cIgnoreRef          [in]    The reference count that need to be ignored in StartShutdown.

Return Value:
    TRUE or FALSE

-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::ShutdownWamInfo
(
CWamInfo*       pWamInfo,
INT             cIgnoreRefs
)
    {
    IF_DEBUG( WAM_ISA_CALLS )
        DBGPRINTF((DBG_CONTEXT, "WAM_DICTATOR::ShutdownWamInfo\n"));

    HRESULT     hr = NOERROR;
    WCHAR       szPackageID[uSizeCLSIDStr];

    DBG_ASSERT(pWamInfo);

    LPCSTR          pszAppPath = (pWamInfo->QueryApplicationPath()).QueryStr();


    // Might create IPackageUtil * for later use.
    DBGPRINTF((DBG_CONTEXT, "Shutting down WamInfo %08p, Inproc(%d), AppRoot(%s).\n",
            pWamInfo,
            pWamInfo->FInProcess(),
            pszAppPath));


    hr = pWamInfo->StartShutdown(cIgnoreRefs);
    hr = pWamInfo->UnInit();

    if (FAILED(hr))
        {
        const CHAR      *pszEventLog[2];
        char szErr[20];

        pszEventLog[0] = pszAppPath;
        wsprintf(szErr, "0x%08X", hr);
        pszEventLog[1] = szErr;

        // Event login
        g_pInetSvc->LogEvent(W3_EVENT_FAIL_SHUTDOWN,
                        2,
                        pszEventLog,
                        0);
        }

    //delete pWamInfo;
    //pWamInfo = NULL;
    return hr;
    } // wAM_DICTATOR::ShutdownWamInfo


/*-----------------------------------------------------------------------------
WAM_DICTATOR::CPUUpdateWamInfo
Arguments:

Return Value:
    TRUE or FALSE

-----------------------------------------------------------------------------*/
void
WAM_DICTATOR::CPUUpdateWamInfo
(
STR     *pstrAppPath
)
    {
    CWamInfo*       pWamInfo = NULL;
    INT             AppState;

    DBG_ASSERT(pstrAppPath != NULL);

    // Find the WamInfo.
    AppState = FindWamInfo(pstrAppPath, &pWamInfo);
    if (-1 != AppState)
        {
        BOOL            fWasShutDown = FALSE;
        DWORD           eWISPrevState;

        if ( !pWamInfo->FInProcess() )
            {
            //
            // It's out of process and the job state has changed
            // Shut it down so new value will get picked up
            //

            //
            // change the state of the WamInfo
            //

            eWISPrevState = pWamInfo->ChangeToState(WIS_RUNNING, WIS_SHUTDOWN);

            //
            // Need to shutdown the Application
            // remove from the hash table here. and clean up later.
            if (WIS_RUNNING == eWISPrevState)
                {

                ShutdownWamInfo(pWamInfo, HASH_TABLE_REF + FIND_KEY_REF);

                // NoNeed to go through the Dying List.
                // But since we already have the Dying List, we might just used those functions.
                // this means we might release other old WamInfo in the Dying List as well.
                m_HashTable.DeleteKey(pstrAppPath->QueryStr());

                fWasShutDown = TRUE;

                }
            }
        pWamInfo->Dereference();
        if (fWasShutDown)
            {
			pWamInfo->Dereference();
            //delete pWamInfo;
            }
        }
    }


/*-----------------------------------------------------------------------------
HRESULT
WAM_DICTATOR::WamRegSink
Argument:
szAppPath               [in]  Application Path.
dwCommand               [in]  Delete, Change, Stop..etc.

Return:
HRESULT
-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::WamRegSink
(
LPCSTR  szAppPath,
const DWORD     dwCommand,
DWORD*  pdwResult
)
    {
    CWamInfo*       pWamInfo = NULL;
    INT             AppState;
    HRESULT         hrReturn = NOERROR;

    DBG_ASSERT(szAppPath != NULL);

    *pdwResult = APPSTATUS_UnLoaded;

    //
    // Set up the Application Path
    //
    STR*    pstrAppPath  = new STR(szAppPath);

    if (NULL == pstrAppPath)
        {
        return E_OUTOFMEMORY;
        }

    Reference();
    DBGPRINTF((DBG_CONTEXT,
                "Wam Dictator received a SinkNotify on MD Path %s, cmd = %d\n",
                szAppPath,
                dwCommand));

    if (dwCommand == APPCMD_UNLOAD)
        {
        hrReturn = UnLoadWamInfo(pstrAppPath, FALSE);
        *pdwResult = (SUCCEEDED(hrReturn)) ? APPSTATUS_UnLoaded : APPSTATUS_Error;
        }
    else
        {
        // Find the WamInfo.
        AppState = FindWamInfo(pstrAppPath, &pWamInfo);
        if (-1 != AppState)
            {
            LK_RETCODE      LkReturn;
            DWORD           eWISPrevState;

            if (dwCommand == APPCMD_DELETE ||
                 dwCommand == APPCMD_CHANGETOINPROC ||
                 dwCommand == APPCMD_CHANGETOOUTPROC)
                {
                eWISPrevState = pWamInfo->ChangeToState(WIS_RUNNING, WIS_SHUTDOWN);

                if (WIS_RUNNING == eWISPrevState)
                {
                    LkReturn = m_HashTable.DeleteKey(pstrAppPath->QueryStr());
                    DBG_ASSERT(LK_SUCCESS == LkReturn);

                    if (LK_SUCCESS == LkReturn)
                        {
                        DyingList_Lock();
                        InsertDyingList(pWamInfo, FALSE);
                        DyingList_UnLock();

                        ScheduledId_Lock();
                        if (m_dwScheduledId == 0)
                            {
                                // DebugBreak();
                            m_dwScheduledId = ScheduleWorkItem
                                            (
                                            WAM_DICTATOR::CleanupScheduled,
                                            NULL,
                                            0,
                                            FALSE
                                            );
                            DBGPRINTF((DBG_CONTEXT, "add schedule item %d\n", m_dwScheduledId));
                            DBG_ASSERT(m_dwScheduledId);
                            }
                        ScheduledId_UnLock();

                        *pdwResult = (SUCCEEDED(hrReturn)) ? APPSTATUS_UnLoaded : APPSTATUS_Error;
                        }
                    }
                else
                    {
                    //
                    // Previous state is not running state.  Leave it alone.
                    //
                    pWamInfo->Dereference();
                    }
                }
            else
                {
                // AppGetStatus
                if (WIS_RUNNING == pWamInfo->QueryState())
                    {
                    *pdwResult = APPSTATUS_Running;
                    }
                else
                    {
                    *pdwResult = APPSTATUS_Stopped;
                    }

                pWamInfo->Dereference();
                }
            }
        else
            {
            //
            // This Application is not loaded.
            *pdwResult = APPSTATUS_NotFoundInW3SVC;
            }
        }

    Dereference();
    return hrReturn;
    }//WAM_DICTATOR::WamRegSink


/*-----------------------------------------------------------------------------
WAM_DICTATOR::InsertDyingList
    This function insert an invalid WamInfo into DyingList.  No blocking.

Arguments:
    pWamInfo         [in]    a pointer to WamInfo
    fNeedReference [in]     if TRUE, then we Rereference the WamInfo.  Because we get the WamInfo by
                            Hashtable's Interator or LookUp.  When we get a WamInfo from Hash Table by
                            hash table's interator or LookUp call, the Hash table does a AddRef to
                            WamInfo.  Therefore, this parameter tells whether we need to balance that
                            Addref or not.
    Return Value:
    NOERROR.
-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::InsertDyingList
(
CWamInfo* pWamInfo,
BOOL    fNeedReference
)
    {
    DBG_ASSERT(pWamInfo);

    DyingList_Lock();
    //
    // Note: Delete Dereference WamInfo.  This Dereference() within Delete() balances
    // the AddRef() in Insert() call to the hash table.
    //
    InsertHeadList(&m_DyingListHead, &pWamInfo->ListEntry);
    //
    // Unfortunately, I can not move this Dereference() out of Critical Section.
    // If I move the Dereference() after the Critical Section, then, since the WamInfo is
    // already on the DyingList.  therefore, any other thread happens to do the CleanUpDyingList()
    // call before this Dereference() will have an unbalanced reference to the CWamInfo.
    //
    if (fNeedReference)
        {
        pWamInfo->Reference();
        }

    DyingList_UnLock();

    return NOERROR;
    }//WAM_DICTATOR::InsertDyingList


/*-----------------------------------------------------------------------------
WAM_DICTATOR::CleanUpDyingList
    This function clean up any remaining WamInfo on the dying list.

Arguments:

Return Value:
        HRESULT

-----------------------------------------------------------------------------*/
HRESULT
WAM_DICTATOR::CleanUpDyingList
(
VOID
)
    {
    IF_DEBUG( WAM_ISA_CALLS )
        DBGPRINTF((DBG_CONTEXT, "WAM_DICTATOR::CleanUpDyingList\n"));

    CWamInfo *      pWamInfo = NULL;
    HRESULT         hr = NOERROR;
    PLIST_ENTRY     pleTemp;
    BOOL            fNeedCleanupAction = FALSE;

    DyingList_Lock();
    if (!IsListEmpty(&m_DyingListHead))
        {
        if (!m_fCleanupInProgress)
            {
            m_fCleanupInProgress = TRUE;
            fNeedCleanupAction = TRUE;
            }
        }

    DBGPRINTF((DBG_CONTEXT, "Clean up dying list. (ScheduledId:%d).\n",
                m_dwScheduledId));

    DyingList_UnLock();

    if (!fNeedCleanupAction)
        {
        goto Egress;
        }

    // From now on, only one thread is working on the killing dying list.
    while (!IsListEmpty(&m_DyingListHead))
        {
        DyingList_Lock();
        pleTemp = RemoveHeadList(&m_DyingListHead);
        DyingList_UnLock();

        DBG_ASSERT(pleTemp);
        pWamInfo = CONTAINING_RECORD(
                    pleTemp,
                    CWamInfo,
                    ListEntry);

        ShutdownWamInfo(pWamInfo, DYING_LIST_REF);

        pWamInfo->Dereference();

		pWamInfo->Dereference();
        //delete pWamInfo;
        pWamInfo = NULL;
        }

    InterlockedExchange((LPLONG)&m_fCleanupInProgress, (LONG)FALSE);

    DBGPRINTF((DBG_CONTEXT, "CleanupDyingList done, ScheduledId(%d)\n", m_dwScheduledId));

Egress:
    IF_DEBUG( WAM_ISA_CALLS )
        DBGPRINTF((DBG_CONTEXT, "WAM_DICTATOR::CleanUpDyingList done\n"));

    m_dwScheduledId = 0;
    return hr;
    }//WAM_DICTATOR::CleanUpDyingList


/*-----------------------------------------------------------------------------
WAM_DICTATOR::StopAplicationsByInstance

Unload out-proc applications for a particular w3 server instance.

Arguments:
    W3_SERVER_INSTANCE *pInstance
        pointer to W3 Server Instance object.  Used to query the server instance
        metabase path.

Return Value:
    none

-----------------------------------------------------------------------------*/
VOID
WAM_DICTATOR::StopApplicationsByInstance
(
VOID *pContext //W3_SERVER_INSTANCE *pInstance
)
    {
    IF_DEBUG( WAM_ISA_CALLS )
        DBGPRINTF((DBG_CONTEXT, "WAM_DICTATOR::StopApplicationsByInstance\n"));

    BUFFER bufDataPaths;
    MB      mb( (IMDCOM*) g_pInetSvc->QueryMDObject() );
    LPSTR   pszCurrentPath;
    STR     strPath;
    W3_SERVER_INSTANCE *pInstance;

    DBG_ASSERT(pContext != NULL);
    DBG_ASSERT(g_pWamDictator != NULL);

    pInstance = (W3_SERVER_INSTANCE *)pContext;

    g_pWamDictator->Reference();
    if (!g_pWamDictator->FIsShutdown())
        {
        if ( mb.Open( pInstance->QueryMDPath(),
                      METADATA_PERMISSION_READ ) )
            {
            //
            // First find the OOP Applications
            //
            if (mb.GetDataPaths(NULL,
                                MD_APP_WAM_CLSID,
                                STRING_METADATA,
                                &bufDataPaths))
                {
                //
                // For each OOP Application
                //
                mb.Close();
                for (pszCurrentPath = (LPSTR)bufDataPaths.QueryPtr();
                     *pszCurrentPath != '\0';
                     pszCurrentPath += (strlen(pszCurrentPath) + 1))
                    {
                    strPath.Copy(pInstance->QueryMDPath());
                    strPath.Append(pszCurrentPath);
                    strPath.SetLen(strlen(strPath.QueryStr()) - 1);
                    g_pWamDictator->UnLoadWamInfo(&strPath, FALSE);
                    }
                }
            else
                {
                mb.Close();
                }
            }
        } // WamDictator is in shutdown
        g_pWamDictator->Dereference();

    }

BOOL
WAM_DICTATOR::DeleteWamInfoFromHashTable
(
CWamInfo *  pWamInfo
)
{
    LK_RETCODE      lkReturn;
    const CHAR *    szKey;

    DBG_ASSERT( pWamInfo );

    szKey = m_HashTable.ExtractKey( pWamInfo );

    if ( !szKey )
    {
        return FALSE;
    }

    lkReturn = m_HashTable.DeleteKey( szKey );

    return ( lkReturn == LK_SUCCESS );
}

/*-----------------------------------------------------------------------------
WAM_DICTATOR::DumpWamDictatorInfo

  Description:
    This function dumps the stats on all WAMs for diagnostics

  Arguments:
    pchBuffer - pointer to buffer that will contain the html results
    lpcchBuffer - pointer to DWORD containing the size of buffer on entry
               On return this contains the # of bytes written out to buffer

  Return:
    TRUE for success and FALSE for failure
    Look at GetLastError() for the error code.
-----------------------------------------------------------------------------*/
BOOL
WAM_DICTATOR::DumpWamDictatorInfo
(
OUT CHAR *     pchBuffer,
IN OUT LPDWORD lpcchBuffer
)
    {
    LIST_ENTRY  * pEntry;
    DWORD  iCount, cch;
    BOOL   fRet = TRUE;

    if ( lpcchBuffer == NULL )
        {
        SetLastError( ERROR_INVALID_PARAMETER);
        return ( FALSE);
        }


    if ( 200 < *lpcchBuffer )
        {
        // Print the header blob
        cch = wsprintf( pchBuffer,
                        " Wam Director Table (%x)<br>"
                        "<TABLE BORDER> <TR> "
                        "<TH> Wam Instance </TH> "
                        "<TH> Total Reqs </TH> "
                        "<TH> Current Reqs </TH> "
                        "<TH> Max Reqs </TH> "
                        " </TR>"
                        ,
                        this
                        );
        }
    else
        {
        cch = 200;
        }

    //
    // For now there is only ONE WAM. Later use a loop to iterate thru WAMs
    //

    iCount = 0;

    CWamInfoHash::CIterator  iter;
    LK_RETCODE              lkReturn = m_HashTable.InitializeIterator(&iter);
    WAM_STATISTICS_INFO     wsi;
    CWamInfo*               pWamInfo;
    DWORD                   dwErr;
    HRESULT                 hr;

    ZeroMemory( (PVOID ) &wsi, sizeof(wsi));

    while (LK_SUCCESS == lkReturn)
        {
        CWamInfoHash::Record *pRec  = iter.Record();

        pWamInfo = (CWamInfo *)pRec;

        hr = pWamInfo->GetStatistics( 0, &wsi);

        if (SUCCEEDED(hr))
            {
            DBGPRINTF(( DBG_CONTEXT, " Wam(%08x)::GetStatistics( %08x) => %08x\n",
                    pWamInfo->QueryIWam(), &wsi, hr));

            if ( (cch + 150 ) < *lpcchBuffer)
                {
                cch += wsprintf( pchBuffer + cch,
                                 " <TR> <TD> [%d] %s </TD> "
                                 " <TD> %4d </TD>"
                                 " <TD> %4d </TD>"
                                 " <TD> %4d </TD>"
                                 " </TR>"
                                 ,
                                 iCount,
                                 pWamInfo->QueryKey(),
                                 wsi.WamStats0.TotalWamRequests,
                                 wsi.WamStats0.CurrentWamRequests,
                                 wsi.WamStats0.MaxWamRequests
                                 );
                iCount++;
                }
            else
                {
                cch += 150;
                }
            }

        lkReturn = m_HashTable.IncrementIterator(&iter);
    } // while()

    DBG_ASSERT(lkReturn == LK_NO_MORE_ELEMENTS);

    lkReturn = m_HashTable.CloseIterator(&iter);

    DBG_REQUIRE(lkReturn == LK_SUCCESS);

    //
    // dump the final summary
    //
    if ( (cch + 100 ) < *lpcchBuffer)
        {
        cch += wsprintf( pchBuffer + cch,
                         " </TABLE>"
                         );
        }
    else
        {
        cch += 100;
        }

    if ( *lpcchBuffer < cch )
        {
        SetLastError( ERROR_INSUFFICIENT_BUFFER);
        fRet = FALSE;
        }

    *lpcchBuffer = cch;

    return (fRet);
} // WAM_DICTATOR::DumpWamDictatorInfo()


/*-----------------------------------------------------------------------------
Description:
    Thunk so that we can dll export DumpWamDictatorInfo.

-----------------------------------------------------------------------------*/
extern "C"
BOOL WamDictatorDumpInfo
(
OUT CHAR * pch,
IN OUT LPDWORD lpcchBuff
)
    {
    return ( g_pWamDictator->DumpWamDictatorInfo( pch, lpcchBuff));
    } // WamDictatorDumpInfo()

HRESULT W3SVC_WamRegSink
(
LPCSTR  szAppPath,
const DWORD     dwCommand,
DWORD*  pdwResult
)
    {
    return (g_pWamDictator->WamRegSink(szAppPath, dwCommand, pdwResult));
    } // W3SVCDictatorDumpInfo()


/*-----------------------------------------------------------------------------
  Thunks for Fake NT APIs
-----------------------------------------------------------------------------*/
CRITICAL_SECTION g_csNonNTAPIs;
LONG FakeInterlockedCompareExchange(
    LONG *Destination,
    LONG Exchange,
    LONG Comperand
   );

LONG
FakeInterlockedCompareExchange(
    LONG *Destination,
    LONG Exchange,
    LONG Comperand
   )
/*-----------------------------------------------------------------------------
Description:
  This function fakes the interlocked compare exchange operation for non NT
platforms
  See WAMLoadNTApis() for details

Returns:
   returns the old value at Destination
-----------------------------------------------------------------------------*/
{
    LONG oldValue;

    EnterCriticalSection( &g_csNonNTAPIs);

    oldValue = *Destination;

    if ( oldValue == Comperand ) {
        *Destination = Exchange;
    }

    LeaveCriticalSection( &g_csNonNTAPIs);

    return( oldValue);
} // FakeInterlockedCompareExchange()


static VOID
LoadNTApis(VOID)
/*-----------------------------------------------------------------------------
Description:
  This function loads the entry point for functions from
  Kernel32.dll. If the entry point is missing, the function
  pointer will point to a fake routine which does nothing. Otherwise,
  it will point to the real function.

  It dynamically loads the kernel32.dll to find the entry ponit and then
  unloads it after getting the address. For the resulting function
  pointer to work correctly one has to ensure that the kernel32.dll is
  linked with the dll/exe which links to this file.
-----------------------------------------------------------------------------*/
    {
    // Initialize the critical section for non NT API support, in case if we need this
    INITIALIZE_CRITICAL_SECTION( &g_csNonNTAPIs);

    if ( g_pfnInterlockedCompareExchange == NULL )
        {
        HINSTANCE tmpInstance;
        //
        // load kernel32 and get NT specific entry points
        //

        tmpInstance = LoadLibrary("kernel32.dll");
        if ( tmpInstance != NULL )
            {

            // For some reason the original function is _InterlockedCompareExchange!
            g_pfnInterlockedCompareExchange = (PFN_INTERLOCKED_COMPARE_EXCHANGE )
                GetProcAddress( tmpInstance, "InterlockedCompareExchange");

            if ( g_pfnInterlockedCompareExchange == NULL )
                {
                // the function is not available
                //  Just thunk it.
                g_pfnInterlockedCompareExchange = FakeInterlockedCompareExchange;
                }

            //
            // We can free this because we are statically linked to it
            //

            FreeLibrary(tmpInstance);
            }
        }

    return;
    } // WAMLoadNTApis()

static void
UnloadNTApis(VOID)
    {
    DeleteCriticalSection( &g_csNonNTAPIs);

    return;
    } // WAMUnloadNTApis()

VOID
RecycleCallback(
    VOID *  pvContext
    )
{
    CWamInfo *  pWamInfo = (CWamInfo*)pvContext;

    DBG_ASSERT( pWamInfo );

    pWamInfo->m_fRecycled = TRUE;

    if (!g_pWamDictator->m_PTable.RecycleWamInfo( pWamInfo ) )
    {
        DBGPRINTF((
            DBG_CONTEXT,
            "[RecycleCallback] failed to recycle CWamInfo 0x%08x.\r\n"
            ));
    }

	pWamInfo->Dereference();

    return;
}

/************************ End of File ***********************/