/*++

   Copyright    (c)    1995-1997    Microsoft Corporation

   Module  Name :
       wamobj.cxx

   Abstract:
       This module implements the WAM (web application manager) object

   Author:
       David Kaplan    ( DaveK )     26-Feb-1997

   Environment:
       User Mode - Win32

   Project:
       Wam DLL

--*/

/************************************************************
 *     Include Headers
 ************************************************************/
#include <isapip.hxx>
#include "setable.hxx"

# include "isapi.hxx"
# include "WamW3.hxx"

#include "wamobj.hxx"
#include "iwr.h"
#include "iwr_i.c"
# include "timer.h"

#include <irtlmisc.h>

#include <ooptoken.h>

// UNDONE where do these belong?
extern    PSE_TABLE    g_psextensions;


class CComContextHelper
/*++

Class description:

    Stack based helper class to enable calls to CoInitialize inside
    ISAPI. 
    
    Replaces member functions PrepareCom/UnprepareCom in WAM_EXEC_INFO. 
    These had to be replaced because WAM_EXEC_INFO will persist beyond
    the initial ISAPI call in the async case and keeping the call context
    in member data would leak and overrelease under certain conditions.


Public Interface:

    PrepareCom          : For OOP get the call context and use our
                          private interface to enable coinit calls.
    UnprepareCom        : Release the call context.

--*/
{
public:
    
    CComContextHelper( BOOL fInProcess )
        : m_fInProcess( fInProcess ),
          m_pComContext( NULL ),
          m_pComInitsCookie( NULL )
    {
    }

    ~CComContextHelper( void )
    {
        UnprepareCom();
    }

    HRESULT PrepareCom()
    /*++
    Routine Description:

        Prepare com before call into ISAPI. For OOP get the call context
        and use our private interface to enable coinit calls.

    --*/
    {
        HRESULT hr = NOERROR;

        // Never call twice.
        DBG_ASSERT( NULL == m_pComInitsCookie );
        DBG_ASSERT( NULL == m_pComContext );
   
        if( !m_fInProcess )
        {    
            // Save COM Call Context in MTS case       
            hr = CoGetCallContext( IID_IComDispatchInfo, (void **)&m_pComContext );
            if( SUCCEEDED(hr) ) 
            {
                hr = m_pComContext->EnableComInits( &m_pComInitsCookie );
            }
        }
        return hr;
    }

    void UnprepareCom()
    /*++
    Routine Description:

        Restores com state after call into ISAPI. Release call context.

    --*/
    {
        // Restore COM Call Context
        if( m_pComContext ) 
        {
            DBG_ASSERT( !m_fInProcess );
            DBG_ASSERT( m_pComInitsCookie );

            m_pComContext->DisableComInits( m_pComInitsCookie );
            m_pComContext->Release();

            m_pComContext = NULL;
            m_pComInitsCookie = NULL;
        }
    }

private:
    //NO-OP
    CComContextHelper() {}
    CComContextHelper( const CComContextHelper & ref ) {}

private:

    BOOL                    m_fInProcess;
    IComDispatchInfo *      m_pComContext;
    void *                  m_pComInitsCookie;

};


/*---------------------------------------------------------------------*
WAM::InitWam

Routine Description:
    Initializes this WAM.

Arguments:
    See below

Return Value:
    HRESULT

*/
STDMETHODIMP
WAM::InitWam
(
BOOL    fInProcess,         // are we in-proc or out-of-proc?
BOOL    fInPool,            // !Isolated
BOOL    fEnableTryExcept,   // catch exceptions in ISAPI calls?
int     pt,             // PLATFORM_TYPE - are we running on Win95?
DWORD   *pPID           // Process Id of the process the wam was created in
)
{
    HRESULT    hr = NOERROR;


    IF_DEBUG( INIT_CLEAN ) {
        DBGPRINTF(( DBG_CONTEXT,
                    "\n ********** WAM(%08x)::InitWam() *****\n",
                    this));
    }

    INITIALIZE_CRITICAL_SECTION( &m_csWamExecInfoList );
    InitializeListHead( &m_WamExecInfoListHead );

    m_fInProcess = fInProcess;
    m_fInPool = fInPool;

    DBG_ASSERT( pt != PtInvalid );
    m_pt = (PLATFORM_TYPE) pt;

    // Get the process id of the current process so we can return it to w3svc
    *pPID = GetCurrentProcessId();

    // Acquire a reference to the SE_TABLE object
    DBG_REQUIRE( g_psextensions->AddRefWam() > 0);

    IF_DEBUG( INIT_CLEAN) {
        DBGPRINTF(( DBG_CONTEXT, "\n************Leaving WAM::InitWam ...\n" ));
        }

    DBG_ASSERT( SUCCEEDED(hr));

    //
    // UNDONE: See the DoGlobalInitializations() for details.
    //
    hr = DoGlobalInitializations( fInProcess, fEnableTryExcept);

    return (hr);
} // WAM::InitWam()




/*---------------------------------------------------------------------*
WAM::StartShutdown

Routine Description:
    Phase 1 of shutdown process.
    Set the shutdown flag on the WAM object
    Loop for small duration checking if all WAM_EXEC_INFO's have drained.
    At the end of loop initiate the first phase of TerminateExtension()

    NOTE: TerminateExtension() - currently have been tested only for the
    MUST_UNLOAD option => there is no two-phase operation there.
    So, we will rely on calling it once only.

Arguments:
    None

Return Value:
    HRESULT

*/
STDMETHODIMP
WAM::StartShutdown(
)
{
    DWORD i;

    IF_DEBUG( WAM ) {
        DBGPRINTF(( DBG_CONTEXT,
                    "WAM(%08x)::StartShutdown() %d Active Requests\n",
                    this, QueryWamStats().QueryCurrentWamRequests() ));
    }

    // Set the Shutting down flag to true now
    DBG_REQUIRE( FALSE ==
                 InterlockedExchange((LPLONG)&m_fShuttingDown, (LONG)TRUE)
                 );

    for ( i = 0;
          ( (i < 10) &&
            (QueryWamStats().QueryCurrentWamRequests())
            );
          i++ )
    {
# ifndef SHUTOFF_AFTER_BETA
        DBGPRINTF(  (
            DBG_CONTEXT,
            "[%d] WAM(%08x) has %d requests waiting for cleanup\n",
            GetCurrentThreadId(),
            this, QueryWamStats().QueryCurrentWamRequests()
            ) );
# endif // SHUTOFF_AFTER_BETA

        Sleep( 200 );  // sleep for a while before restarting the check again
    } // for

    // Release the reference to the SE_TABLE object
    g_psextensions->ReleaseRefWam();

    return NOERROR;
} // WAM::StartShutdown()




/*---------------------------------------------------------------------*
WAM::UninitWam

Routine Description:
    Phase 2 of shutdown process.

Arguments:
    None

Return Value:
    HRESULT
        NOERROR on success
        E_FAIL if there are any pending items to be deleted still

*/
STDMETHODIMP
WAM::UninitWam()
{
    //
    //  If there are any pending requests being processed, wait for them
    //   to be drained off from this WAM
    //

    IF_DEBUG( WAM ) {
        DBGPRINTF(( DBG_CONTEXT,
                    "WAM(%08x)::UninitWam() %d Active Requests\n",
                    this, QueryWamStats().QueryCurrentWamRequests() ));
    }

    if ( QueryWamStats().QueryCurrentWamRequests() != 0 )
    {
        IF_DEBUG( ERROR) {
            DBGPRINTF(( DBG_CONTEXT,
                        "WAM(%08x)::UninitWam() Error - "
                        " Failed with active requests! (%d active)\n",
                        this,
                        QueryWamStats().QueryCurrentWamRequests() ));
        }

        //
        // Enumerate and dump information on all requests that are hanging
        //  and the associated ISAPI DLLs
        //
# ifndef SHUTOFF_AFTER_BETA
        while ( QueryWamStats().QueryCurrentWamRequests() > 0) {

            DBGPRINTF(
                ( DBG_CONTEXT,
                  "\n\n[Thd %d]WAM(%08x) has %d requests waiting ... \n",
                  GetCurrentThreadId(),
                  this, QueryWamStats().QueryCurrentWamRequests()
                ) );

            g_psextensions->PrintRequestCounts();

            // sleep for a while before restarting the check again
            Sleep( 1000 );
        } // while

# endif // SHUTOFF_AFTER_BETA


        // return failure, since we failed to shutdown gracefully!
        // NYI: Should I ignore the fact that some long-hanging connections
        //  are okay?
        // Shouldn't I be forcing the exit now?
        DBG_ASSERT( QueryWamStats().QueryCurrentWamRequests() == 0);
        // return ( E_FAIL);
    }

    DeleteCriticalSection( &m_csWamExecInfoList );

    return NOERROR;
} // WAM::UninitWam()


/*-----------------------------------------------------------------------------*
WAM::ProcessAsyncIO
    Completes an async i/o process for a given wam request.

Arguments:

Return Value:
    HRESULT

*/
STDMETHODIMP
WAM::ProcessAsyncIO
(
#ifdef _WIN64
UINT64    pWamExecInfoIn,  // WAM_EXEC_INFO *
#else
DWORD_PTR pWamExecInfoIn,  // WAM_EXEC_INFO *
#endif
DWORD   dwStatus,
DWORD   cbWritten
)
{
    return ProcessAsyncIOImpl( pWamExecInfoIn,
                               dwStatus,
                               cbWritten
                               );

} // WAM::ProcessAsyncIO

STDMETHODIMP
WAM::ProcessAsyncReadOop
(
#ifdef _WIN64
    UINT64          pWamExecInfoIn,
#else
    DWORD_PTR       pWamExecInfoIn,
#endif
    DWORD           dwStatus,
    DWORD           cbRead,
    unsigned char * lpDataRead
)
/*++
    Routine Description:
        
        Handle callback for out of process AsyncRead

    Arguments:

        pWamExecInfoIn  - The smuggled pointer to the WAM_EXEC_INFO
        dwStatus        - IO status
        cbRead          - Number of bytes read
        lpDataRead      - Marshalled data read

    Return Value:

    Notes:

        The initial design for AsyncRead was inadequate when the isapi is 
        running out of process. The problem was that the data buffer was
        marshalled over to inetinfo and back during the AsyncRead call.
        Since the completion will happen on another thread the ISAPI could
        get the completion before the data was completely marshalled back
        or the address in inetinfo could be invalidated before the read was
        complete. The solution is to add a separate path for oop async reads
        and marshall the data on the io completion and copy it into the
        client's buffer then.

--*/
{
    DBG_ASSERT( lpDataRead != NULL );
    DBG_ASSERT( !m_fInProcess );

    return ProcessAsyncIOImpl( pWamExecInfoIn,
                               dwStatus,
                               cbRead,
                               lpDataRead
                               );
}

HRESULT         
WAM::ProcessAsyncIOImpl
(
#ifdef _WIN64
    UINT64          pWamExecInfoIn,
#else
    DWORD_PTR       pWamExecInfoIn,
#endif
    DWORD           dwStatus,
    DWORD           cb,
    LPBYTE          lpDataRead // = NULL
)
{
    HRESULT             hrRet;
    CComContextHelper   callContext( m_fInProcess );

    WAM_EXEC_INFO * pWamExecInfo =
        reinterpret_cast<WAM_EXEC_INFO *>(pWamExecInfoIn);

    //
    //  NOTE we assert because we believe the pointer can never be null
    //  AND we fail gracefully if it is null because there is a long
    //  code path across many threads between setting the pointer
    //  and here, and you never know ...
    //

    DBG_ASSERT ( pWamExecInfo != NULL );

    if ( pWamExecInfo == NULL ) {

        return HRESULT_FROM_WIN32( E_POINTER );
    }

    //
    // Note: The AddRef/Release calls may not be necessary anymore.
    // 
    // Make sure that UnprepareCom has valid WamInfo
    //

    pWamExecInfo->AddRef();

    callContext.PrepareCom();

    if( lpDataRead == NULL )
    {
        // Doing an out of process async read

        hrRet = HresultFromBool(
                            pWamExecInfo->ProcessAsyncIO(
                            dwStatus,
                            cb
                            ) );
    }
    else
    {
        // All other async io completions

        hrRet = HresultFromBool(
                            pWamExecInfo->ProcessAsyncReadOop(
                            dwStatus,
                            cb,
                            lpDataRead
                            ) );
    }

    callContext.UnprepareCom();
	
    //
    // Balance AddRef() above
    //

    pWamExecInfo->Release();

    return hrRet;
}


/*-----------------------------------------------------------------------------*
WAM::ProcessRequest
    Processes a WAM request.

Arguments:
    pIWamRequest - pointer to IWamRequest interface
    cbWrcStrings - Count of bytes for wamreq core strings
    pfHandled    - Indicates we handled this request
    pfFinished   - Indicates no further processing is required

Return Value:
    HRESULT

*/
STDMETHODIMP
WAM::ProcessRequest
(
IWamRequest *       pIWamRequest,
DWORD               cbWrcStrings,
OOP_CORE_STATE *    pOopCoreState,
BOOL *              pfHandled
)
{
    HRESULT             hrRet = NOERROR;    // this function's return value
    HRESULT                             hr;
    int                 iretInvokeExt;      // return value from InvokeExtension
    BOOL                fFreeContext = TRUE;// do we need to free up wamex-info?
    WAM_EXEC_INFO *     pWamExecInfo = NULL;
    BOOL                fImpersonated = FALSE;
    BYTE *              pbEntityBody = NULL;
    DWORD               cbEntityBody = 0;
    CComContextHelper   callContext( m_fInProcess );


    DBG_ASSERT( pIWamRequest );
    IF_DEBUG( WAM) {
        DBGPRINTF(( DBG_CONTEXT,
                    "WAM(%08x)::ProcessRequest(%08x, %08x, ... )\n",
                    this, pIWamRequest ));
    }


    if ( m_fShuttingDown ) {

        IF_DEBUG( WAM) {

            DBGPRINTF((
                DBG_CONTEXT
                , "WAM(%08x) shutting down.  "
                  "Request(%08x) will be aborted.\n"
                , this
                , pIWamRequest
            ));
        }

        // UNDONE something besides E_FAIL?
        return E_FAIL;

    }


    // create, init the wamexec-info and add it to list
    pWamExecInfo = new WAM_EXEC_INFO( this );
    if( NULL == pWamExecInfo )
        {
        hrRet = E_OUTOFMEMORY;
        goto LError;
        }

    //
    //  Update the statistics counters
    //

    m_WamStats.IncrWamRequests();


    if ( FAILED( hrRet = pWamExecInfo->InitWamExecInfo( pIWamRequest,
                                                        cbWrcStrings,
                                                        pOopCoreState
                                                        ) ) )
    {
         goto LError;
    }

    if( !m_fInProcess )
    {
        //
        // This test shouldn't really be necessary. In the case where com plus 
        // activates us in process even if we are marked to run in the
        // surrogate, we will fail the app creation.
        //
        DBG_ASSERT( CWamOopTokenInfo::HasInstance() );
        if( CWamOopTokenInfo::HasInstance() )
        {
            hrRet = CWamOopTokenInfo::QueryInstance()->ModifyTokenForOop
                        (
                        WRC_GET_FIX.m_hUserToken 
                        );

            if( FAILED(hrRet) )
            {
                goto LError;
            }
        }
    }

    this->InsertIntoList( &pWamExecInfo->_ListEntry);


    DBG_WAMREQ_REFCOUNTS(( "WAM::ProcessRequest right after wrc construction ...", pWamExecInfo ));

    if ( !FWin95() )
    {
        if ( !ImpersonateLoggedOnUser( WRC_GET_FIX.m_hUserToken ) )
        {
            IF_DEBUG( ERROR ) {
                DBGPRINTF((DBG_CONTEXT,
                           "WAM(%08x) ImpersonateLoggedOnUser(%08x)"
                           "failed[err %d]\n",
                           this, WRC_GET_FIX.m_hUserToken, GetLastError()));
            }

            hrRet = HresultFromGetLastError();
            goto LError;
        }
        fImpersonated = TRUE;
    }

    pWamExecInfo->_psExtension = NULL;

    if ( ! g_psextensions->GetExtension(  WRC_GET_SZ( WRC_I_ISADLLPATH ),
                                          WRC_GET_FIX.m_hUserToken,
                                          WRC_GET_FIX.m_fAnonymous,
                                          WRC_GET_FIX.m_fCacheISAPIApps,
                                          &(pWamExecInfo->_psExtension ) ) )
        {

        hrRet = HresultFromGetLastError( );
        goto LError;

        }

    // Add a reference to ensure that wamexec is valid until we hit the
    // cleanup code. Don't put any goto LError after this or the wamexec
    // will leak.
    pWamExecInfo->AddRef();

    // Invoke the server extension
    callContext.PrepareCom();
    iretInvokeExt = InvokeExtension( pWamExecInfo->_psExtension,
                                     WRC_GET_SZ( WRC_I_ISADLLPATH ),
                                     pWamExecInfo );
    callContext.UnprepareCom();

    if ( fImpersonated ) {
       ::RevertToSelf( );
       fImpersonated = FALSE;
    }

    pWamExecInfo->_dwFlags &= ~SE_PRIV_FLAG_IN_CALLBACK;

    switch ( iretInvokeExt )
    {

    case HSE_STATUS_PENDING: {

        IF_DEBUG( WAM_EXEC ) {

            DBGPRINTF((
                DBG_CONTEXT
                , "WAM(%08x)::ProcessRequest case HSE_STATUS_PENDING\n"
                , this
            ));
        }

        IF_DEBUG( WAM_REFCOUNTS ) {

            DBG_WAMREQ_REFCOUNTS((
                "WAM::ProcessRequest case HSE_STATUS_PENDING ",
                pWamExecInfo
            ));
        }

        //
        //  Figure out whether this mainline thread or callback thread
        //  hit its cleanup code first
        //
        //  This protects us against isapis that disobey the async rules.
        //  The isapi should be in one of two modes:
        //
        //  1. It return HSE_STATUS_PENDING in the mainline thread and 
        //  always calls HSE_DONE_WITH_SESSION.
        //
        //  2. It returns any other status code from the mainline and
        //  NEVER calls HSE_DONE_WITH_SESSION.
        //
        //  Unfortunately isapi writers frequently do bad things to good
        //  servers.
        //
        //  NOTE return value of INTERLOCKED_COMPARE_EXCHANGE
        //  is initial value of the destination
        //

        LONG FirstThread = INTERLOCKED_COMPARE_EXCHANGE(
                                (LONG *) &pWamExecInfo->_FirstThread
                                , (LONG) FT_MAINLINE
                                , (LONG) FT_NULL
                            );

        if( FirstThread == (LONG) FT_CALLBACK ) {

            //
            //  If we made it here, then we need to do cleanup, meaning:
            //  - SSF HSE_REQ_DONE_WITH_SESSION callback has already run
            //  - we set fFreeContext TRUE to trigger cleanup below
            //

            fFreeContext = TRUE;

            IF_DEBUG( WAM_EXEC ) {

                DBGPRINTF((
                    DBG_CONTEXT
                    , "\tSession done.\n"
                ));
            }

        } else {

            DBG_ASSERT( FirstThread == (LONG) FT_NULL );

            //
            //  If we made it here, then we can't cleanup yet, meaning:
            //  - SSF HSE_REQ_DONE_WITH_SESSION callback will cleanup
            //    when it runs
            //  - we set fFreeContext FALSE to avoid cleanup below
            //
            fFreeContext = FALSE;

            IF_DEBUG( WAM_EXEC ) {

                DBGPRINTF((
                    DBG_CONTEXT
                    , "\tSession NOT done.\n"
                ));
            }
        }

        //
        //  If fFreeContext is FALSE at this point, we may not use
        //  pWamExecInfo from this point on - callback thread can
        //  invalidate it at any time.
        //

        break;
    } // case HSE_STATUS_PENDING


    case HSE_STATUS_ERROR:
    case HSE_STATUS_SUCCESS:

        //
        //  in error and success cases we no-op
        //

        break;

    case HSE_STATUS_SUCCESS_AND_KEEP_CONN:

        //
        //  remember that ISA asked us to set keep-conn
        //

        pWamExecInfo->_dwIsaKeepConn = KEEPCONN_TRUE;
        break;

    default:

        break;
    } // switch()

    // Release the ref held by this call
    pWamExecInfo->Release();

    if ( fFreeContext ) {

        DBG_ASSERT( pWamExecInfo != NULL);

        DBG_WAMREQ_REFCOUNTS(( "WAM::ProcessRequest fFreeContext ...",
                            pWamExecInfo));



        pWamExecInfo->CleanupAndRelease( TRUE );

        pWamExecInfo = NULL;

    } // if ( fFreeContext);

    *pfHandled = TRUE;
    DBG_ASSERT( hrRet == NOERROR );


LExit:

    if ( fImpersonated ) {

        ::RevertToSelf( );
        fImpersonated = FALSE;
    }

    return hrRet;


LError:

    //
    // release pWamExecInfo on failure case
    // NOTE we separate this from normal exit code because wamexecinfo
    // must hang around in many non-error cases (status-pending, async i/o)
    //

    if ( pWamExecInfo != NULL) {

        pWamExecInfo->CleanupAndRelease( FALSE );

        pWamExecInfo = NULL;
    }


    goto LExit;

} // WAM::ProcessRequest()



STDMETHODIMP
WAM::GetStatistics(
                   /*[in]*/     DWORD       dwLevel,
                   /*[out, switch_is(Level)]*/
                   LPWAM_STATISTICS_INFO pWamStatsInfo
                  )
/*++
  Description:
    Obtains the statistics for the given instance of WAM for specified level.

  Arguments:
    dwLevel - specifies the information level. (Currently level 0 is supported)
    pWamStatsInfo - pointer to the WAM STATISTICS INFO object that will
                  receive the statistics.

  Returns:
    HRESULT - NOERROR on success and E_FAIL on failure.
--*/
{
    HRESULT hr = NOERROR;

    DBG_ASSERT( pWamStatsInfo != NULL);

    IF_DEBUG( API_ENTRY) {
        DBGPRINTF(( DBG_CONTEXT, "WAM(%08x)::GetStatistics(%d, %08x)\n",
                    this, dwLevel, pWamStatsInfo));
    }

    switch ( dwLevel) {
    case 0: {
        // copy values out of the statistics structure
        m_WamStats.CopyToStatsBuffer( &pWamStatsInfo->WamStats0);
        break;
    }

    default:
        DBG_ASSERT( FALSE);
        hr = E_FAIL;
        break;

    } // switch()

    return (hr);

} // WAM::GetStatistics()



/*++
WAM::InvokeExtension

Routine Description:

    Invokes a server extension.
    NOTE without this cover function, we get a compile error
        error C2712: Cannot use __try in functions that require object unwinding

Arguments:

    psExt            -    pointer to server extension
    szISADllPath    -    Fully qualified path to Module (DLL name)
    pWamExecInfo    -   ptr to wamexec info

Return Value:

    DWORD    -    HSE_STATUS_ code

--*/
DWORD
WAM::InvokeExtension
(
IN PHSE         psExt,
const char *    szISADllPath,
WAM_EXEC_INFO * pWamExecInfo
)
    {

    DWORD               ret;

    //
    //  Protect the call to the server extension so we don't hose the
    //  server
    //

    __try
        {
        ret = psExt->ExecuteRequest( pWamExecInfo );
        }
    __except ( g_fEnableTryExcept ?
                WAMExceptionFilter( GetExceptionInformation(),
                                    WAM_EVENT_EXTENSION_EXCEPTION,
                                    pWamExecInfo ) :
                EXCEPTION_CONTINUE_SEARCH )
        {

        //
        // exception caused us to to leave HSE_APPDLL::ExecuteRequest
        // with unbalanced AddRef()
        //

        pWamExecInfo->Release();

        ret = HSE_STATUS_ERROR;
        }

    return ret;
    }    // WAM::InvokeExtension


/*++
WAM::HseReleaseExtension

Routine Description:

    Releases a server extension.

Arguments:

    psExt  - pointer to server extension.

Return Value:

    None

--*/
VOID
WAM::HseReleaseExtension
(
IN PHSE psExt
)
    {
    g_psextensions->ReleaseExtension( psExt);
    } // HseReleaseExtension()





/************************************************************
 *    Member Functions of WAM_STATISTICS
 ************************************************************/


WAM_STATISTICS::WAM_STATISTICS( VOID)
/*++
     Initializes statistics information for server.
--*/
{
    INITIALIZE_CRITICAL_SECTION( & m_csStatsLock);
    ClearStatistics();

} // WAM_STATISTICS::WAM_STATISTICS();


VOID
WAM_STATISTICS::ClearStatistics( VOID)
/*++

    Clears the counters used for statistics information

--*/
{
    LockStatistics();

    memset( &m_WamStats, 0, sizeof(WAM_STATISTICS_0) );
    m_WamStats.TimeOfLastClear       = GetCurrentTimeInSeconds();

    UnlockStatistics();

} // WAM_STATISTICS::ClearStatistics()



DWORD
WAM_STATISTICS::CopyToStatsBuffer( PWAM_STATISTICS_0 pStat0)
/*++
    Description:
        copies the statistics data from the server statistcs structure
        to the WAM_STATISTICS_0 structure for RPC access.

    Arugments:
        pStat0  pointer to WAM_STATISTICS_0 object which contains the
                data on successful return

    Returns:
        Win32 error codes. NO_ERROR on success.

--*/
{

    DBG_ASSERT( pStat0 != NULL);

    LockStatistics();

    CopyMemory( pStat0, &m_WamStats, sizeof(WAM_STATISTICS_0) );

    UnlockStatistics();

    return ( NO_ERROR);

} // WAM_STATISTICS::CopyToStatsBuffer()

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