//+============================================================================
//
//  File:       SSMapStm.cxx
//
//  Purpose:    This file defines the CSSMappedStream class.
//              This class provdes a IMappedStream implementation
//              which maps an IStream from a Compound File.
//
//  History:
//
//      5/6/98  MikeHill
//              -   Use CoTaskMem rather than new/delete.
//
//+============================================================================

//  --------
//  Includes
//  --------

#include <pch.cxx>
#include "SSMapStm.hxx"

#include <privguid.h>       // IID_IMappedStream

#ifdef _MAC_NODOC
ASSERTDATA  // File-specific data for FnAssert
#endif


//+----------------------------------------------------------------------------
//
//  Method:     CSSMappedStream::Initialize
//
//  Synopsis:   Zero-out all of the member data.
//
//  Arguments:  None
//
//  Returns:    None
//
//
//+----------------------------------------------------------------------------

VOID
CSSMappedStream::Initialize()
{
    propDbg(( DEB_ITRACE, "CSSMappedStream::Initialize\n" ));

    _pstm = NULL;
    _pbMappedStream = NULL;
    _cbMappedStream = 0;
    _cbActualStreamSize = 0;
    _powner = NULL;
    _fLowMem = FALSE;
    _fDirty = FALSE;

#if DBGPROP
    _fChangePending = FALSE;
#endif

}   // CSSMappedStream::Initialize()


//+----------------------------------------------------------------------------
//
//  Member:     Constructor/Destructor
//
//  Synopsis:   Initialize/cleanup this object.
//
//+----------------------------------------------------------------------------

CSSMappedStream::CSSMappedStream( IStream *pstm )
{
    DfpAssert( NULL != pstm );

    // Initialize the member data.
    Initialize();

    // Keep a copy of the Stream that we're mapping.
    _pstm = pstm;
    _pstm->AddRef();
    _cRefs = 1;
}


CSSMappedStream::~CSSMappedStream( )
{
    // Just to be safe, free the mapping buffer (it should have
    // already been freed).

    DfpAssert( NULL == _pbMappedStream );
    CoTaskMemFree( _pbMappedStream );

    // If we've got the global reserved buffer locked,
    // free it now.

    if (_fLowMem)
    {
        g_ReservedMemory.UnlockMemory();
    }

    // Free the stream which we were mapping.

    if( NULL != _pstm )
        _pstm->Release();
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::QueryInterface, AddRef, Release
//
//  Synopsis:   IUnknown members
//
//--------------------------------------------------------------------

HRESULT CSSMappedStream::QueryInterface( REFIID riid, void **ppvObject)
{
    HRESULT hr = S_OK;

    // Validate the inputs

    VDATEREADPTRIN( &riid, IID );
    VDATEPTROUT( ppvObject, void* );

    //  -----------------
    //  Perform the Query
    //  -----------------

    *ppvObject = NULL;

    if (IsEqualIID(riid,IID_IMappedStream) || IsEqualIID(riid,IID_IUnknown))
    {
        *ppvObject = (IMappedStream *)this;
        CSSMappedStream::AddRef();
    }
    else
    {
        hr = E_NOINTERFACE;
    }

    return(hr);
}

ULONG   CSSMappedStream::AddRef(void)
{
    InterlockedIncrement(&_cRefs);
    return(_cRefs);
}

ULONG   CSSMappedStream::Release(void)
{
    LONG lRet;

    lRet = InterlockedDecrement(&_cRefs);

    if (lRet == 0)
    {
        delete this;
    }
    else
    if (lRet <0)
    {
        lRet = 0;
    }
    return(lRet);
}


//+----------------------------------------------------------------------------
//
//  Method:     CSSMappedStream::Open
//
//  Synopsis:   Open up the Stream which we're mapping, and
//              read it's data into a buffer.
//
//  Arguments:  [VOID*] powner
//                  The owner of this Stream.  We use this for the
//                  PrOnMappedStreamEvent call.
//              [HRESULT*] phr
//                  The return code.
//
//  Returns:    Nothing.
//
//+----------------------------------------------------------------------------


VOID
CSSMappedStream::Open( IN VOID     *powner,
                       OUT HRESULT *phr )
{
    HRESULT &hr = *phr;
    VOID *pv = NULL;
    DfpAssert(!_fLowMem);

    hr = S_OK;
    propITrace( "CSSMappedStream::Open" );

    // If given a pointer to the owner of this mapped stream,
    // save it.  This could be NULL (i.e., when called from
    // ReOpen).

    if( NULL != powner  )
        _powner = powner;

    // If we haven't already read the stream, read it now.

    if( NULL == _pbMappedStream )
    {
        STATSTG statstg;
        LARGE_INTEGER liSeek;

        DfpAssert( NULL != _pstm );
        DfpAssert( 0 == _cbMappedStream );
        DfpAssert( 0 == _cbActualStreamSize );

        // Get and validate the size of the Stream.

        *phr = _pstm->Stat( &statstg, STATFLAG_NONAME );
        if( FAILED(*phr) ) goto Exit;

        if( statstg.cbSize.HighPart != 0
            ||
            statstg.cbSize.LowPart > CBMAXPROPSETSTREAM )
        {
            *phr = STG_E_INVALIDHEADER;
            goto Exit;
        }
        _cbMappedStream = _cbActualStreamSize = statstg.cbSize.LowPart;

        // Allocate a buffer to hold the Stream.  If there isn't sufficient
        // memory in the system, lock and get the reserved buffer.  In the
        // end, 'pv' points to the appropriate buffer.

        pv = CoTaskMemAlloc( _cbActualStreamSize );

        if (pv == NULL)
        {
            pv = g_ReservedMemory.LockMemory();   // could wait until previous
                                                  // property call completes
            _fLowMem = TRUE;
        }
        _pbMappedStream = (BYTE*) pv;

        // Seek to the start of the Stream.

        liSeek.HighPart = 0;
        liSeek.LowPart = 0;
        *phr = _pstm->Seek( liSeek, STREAM_SEEK_SET, NULL );
        if( FAILED(*phr) ) goto Exit;

        // Read in the Stream.  But only if it is non-zero; some
        // stream implementations (namely the Mac StreamOnHGlobal imp)
        // don't allow 0-length reads.

        if( 0 != _cbActualStreamSize )
        {
            *phr = _pstm->Read(
                          _pbMappedStream,
                          _cbActualStreamSize,
                          &_cbMappedStream);
            if( FAILED(*phr) ) goto Exit;

            // Ensure that we got all the bytes we requested.

            if( _cbMappedStream != _cbActualStreamSize )
            {
                propDbg((DEBTRACE_ERROR,
                         "CSSMappedStream(%08X)::Open bytes-read (%lu) doesn't match bytes-requested (%lu)\n",
                         this, _cbMappedStream, _cbActualStreamSize ));
                *phr = STG_E_INVALIDHEADER;
                goto Exit;
            }
        }


#if BIGENDIAN==1
        // Notify our owner that we've read in new data.

        if( _powner != NULL && 0 != _cbMappedStream )
        {
            *phr = PrOnMappedStreamEvent( _powner, _pbMappedStream, _cbMappedStream );
            if( FAILED(*phr) ) goto Exit;
        }
#endif

    }   // if( NULL == _pbMappedStream )

    //  ----
    //  Exit
    //  ----

Exit:

    // If there was an error, free any memory we have.

    if( FAILED(*phr) )
    {
        propDbg((DEB_ERROR, "CSSMappedStream(%08X):Open exception returns %08X\n", this, *phr));

        if (_fLowMem)
            g_ReservedMemory.UnlockMemory();
        else
            CoTaskMemFree( pv );

        _pbMappedStream = NULL;
        _cbMappedStream = 0;
        _cbActualStreamSize = 0;
        _fLowMem = FALSE;
    }

    return;

}   // CSSMappedStream::Open


//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::Flush
//
//  Synopsis:   Write out the mapping buffer to the Stream,
//              and Commit it.
//
//  Arguments:  [LONG*] phr
//                  An HRESULT return code.
//
//  Returns:    None.
//
//--------------------------------------------------------------------

VOID CSSMappedStream::Flush(OUT LONG *phr)
{

    HRESULT &hr = *phr;
    propITrace( "CSSMappedStream::Flush" );

    // Write out any data we have cached to the Stream.
    hr = Write();

    // Commit the Stream.
    if( SUCCEEDED(hr) )
    {
        hr = _pstm->Commit(STGC_DEFAULT);
    }

    return;
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::Close
//
//  Synopsis:   Close the mapped stream by writing out
//              the mapping buffer and then freeing it.
//
//  Arguments:  [LONG*] phr
//                  An HRESULT error code.
//
//  Returns:    None.
//
//--------------------------------------------------------------------

VOID CSSMappedStream::Close(OUT LONG *phr)
{
    // Write the changes.  We don't need to Commit them,
    // they will be implicitely committed when the
    // Stream is Released.

    HRESULT &hr = *phr;
    propITrace( "CSSMappedStream::Close" );

    hr = Write();

    // Even if we fail the write, we must free the memory.
    // (PrClosePropertySet deletes everything whether or not
    // there was an error here, so we must free the memory.
    // There's no danger of this happenning due to out-of-
    // disk-space conditions, because the propset code
    // pre-allocates).

    CoTaskMemFree( _pbMappedStream );
    _pstm->Release();

    // Re-zero the member data.
    Initialize();

    return;
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::ReOpen
//
//  Synopsis:   Gets the caller a pointer to the already-opened
//              mapping buffer.  If it isn't already opened, then
//              it is opened here.
//
//  Arguments:  [VOID**] ppv
//                  Used to return the mapping buffer.
//              [LONG*] phr
//                  Used to return an HRESULT.
//
//  Returns:    None.
//
//--------------------------------------------------------------------

VOID CSSMappedStream::ReOpen(IN OUT VOID **ppv, OUT LONG *phr)
{
    *ppv = NULL;

    Open(NULL,  // Unspecified owner.
         phr);

    if( SUCCEEDED(*phr) )
        *ppv = _pbMappedStream;

    return;
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::Quiesce
//
//  Synopsis:   Unnecessary for this IMappedStream implementation.
//
//--------------------------------------------------------------------

VOID CSSMappedStream::Quiesce(VOID)
{
    DfpAssert(_pbMappedStream != NULL);
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::Map
//
//  Synopsis:   Used to get a pointer to the current mapping.
//
//  Arguments:  [BOOLEAN] fCreate
//                  Not used by this IMappedStream implementation.
//              [VOID**] ppv
//                  Used to return the mapping buffer.
//
//  Returns:    None.
//
//--------------------------------------------------------------------

VOID CSSMappedStream::Map(BOOLEAN fCreate, VOID **ppv)
{
    DfpAssert(_pbMappedStream != NULL);
    *ppv = _pbMappedStream;
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::Unmap
//
//  Synopsis:   Unnecessary for this IMappedStream implementation.
//
//--------------------------------------------------------------------

VOID CSSMappedStream::Unmap(BOOLEAN fFlush, VOID **ppv)
{
    *ppv = NULL;
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::Write
//
//  Synopsis:   Writes the mapping buffer out to the original
//              Stream.
//
//  Arguments:  None.
//
//  Returns:    [HRESULT]
//                  S_FALSE => Nothing needed to be written
//
//--------------------------------------------------------------------
#define STACK_BYTES 16

HRESULT CSSMappedStream::Write ()
{
    HRESULT hr;
    ULONG cbWritten;
    LARGE_INTEGER liSeek;
    BOOL fOwnerSignaled = FALSE;

    propITrace( "CSSMappedStream::Write" );

    // We can return right away if there's nothing to write.
    // (_pbMappedStream may be NULL in the error path of our
    // caller).

    if (!_fDirty || NULL == _pbMappedStream )
    {
        propDbg((DEB_PROP_INFO, "CPubStream(%08X):Flush returns with not-dirty\n", this));

        return S_FALSE;
    }

    DfpAssert( _pstm != NULL );

#if BIGENDIAN==1
    // Notify our owner that we're about to perform a Write.
    // Note that there are no goto Exit calls prior to this point, because
    // we making a corresponding call to PrOnMappedStreamEvent (for byte-swapping)
    // in the Exit.
    hr = PrOnMappedStreamEvent( _powner, _pbMappedStream, _cbMappedStream );
    if( S_OK != hr ) goto Exit;
    fOwnerSignaled = TRUE;
#endif

    // Seek to the start of the Stream.
    liSeek.HighPart = 0;
    liSeek.LowPart = 0;
    hr = _pstm->Seek( liSeek, STREAM_SEEK_SET, NULL );
    if( FAILED(hr) ) goto Exit;

    // Write out the mapping buffer.
    hr = _pstm->Write(_pbMappedStream, _cbMappedStream, &cbWritten);
    if( S_OK != hr ) goto Exit;
    if( cbWritten != _cbMappedStream )
    {
        propDbg((DEB_ERROR,
                 "CSSMappedStream(%08X)::Write bytes-written (%lu) doesn't match bytes-requested (%lu)\n",
                 this, cbWritten, _cbMappedStream ));
        hr = STG_E_INVALIDHEADER;
        goto Exit;
    }

    // If the buffer is shrinking, this is a good time to shrink the Stream.
    if (_cbMappedStream < _cbActualStreamSize)
    {
        ULARGE_INTEGER uli;
        uli.HighPart = 0;
        uli.LowPart = _cbMappedStream;

        hr = _pstm->SetSize(uli);
        if( S_OK == hr )
        {
            _cbActualStreamSize = _cbMappedStream;
        }
    }

    //
    // If we changed the buffer size and it is less than the
    // actual underlying stream, then we need to zero out the memory
    // above the currrent size.
    //
    if (_cbMappedStream < _cbActualStreamSize)
    {
        PBYTE           pTemp;
        HRESULT         hr;
        LARGE_INTEGER   li;
        DWORD           cbWrite = _cbActualStreamSize - _cbMappedStream;

        li.HighPart = 0;
        li.LowPart = _cbMappedStream;
        hr = _pstm->Seek(li,STREAM_SEEK_SET,NULL);
        if (SUCCEEDED(hr))
        {
            pTemp = reinterpret_cast<PBYTE>( CoTaskMemAlloc( cbWrite ));
            if (pTemp != NULL)
            {
                memset(pTemp,0,cbWrite);
                //
                // Successfully allocated memory for the write.  Write the
                // zeros out all at once.
                //
                hr = _pstm->Write(pTemp, cbWrite, NULL);
                if (FAILED(hr))
                {
                    propDbg((DEB_ERROR, "CSSMappedStream::Write "
                        "write failure\n",hr));
                    goto Exit;
                }
                CoTaskMemFree( pTemp );
            }
            else
            {
                //
                // We couldn't allocate memory.  So we will use a small
                // stack buffer instead.
                //
                BYTE   stackBuf[STACK_BYTES];
                memset(stackBuf, 0, STACK_BYTES);

                while (cbWrite >= STACK_BYTES)
                {
                    hr = _pstm->Write(stackBuf, STACK_BYTES, NULL);
                    if (FAILED(hr))
                    {
                        propDbg((DEB_ERROR, "CSSMappedStream::Write write failure\n",hr));
                        goto Exit;
                    }
                    cbWrite -= STACK_BYTES;
                }

                if (cbWrite < STACK_BYTES)
                {
                    hr = _pstm->Write(stackBuf, cbWrite, NULL);
                    if (FAILED(hr))
                    {
                        propDbg((DEB_ERROR, "CSSMappedStream::Write write failure\n",hr));
                        goto Exit;
                    }
                }
            }
        }
        else
        {
            propDbg((DEB_ERROR, "CSSMappedStream::Write seek failure\n",hr));
            goto Exit;
        }
    }
    //  ----
    //  Exit
    //  ----

Exit:

    // Notify our owner that we're done with the Write.  We do this
    // whether or not there was an error, because _pbMappedStream is
    // not modified, and therefore intact even in the error path.
    // This call allows the owner to correct the byte-order of the header.

#if BIGENDIAN==1
    if( fOwnerSignaled )
        DfpVerify( PrOnMappedStreamEvent( _powner, _pbMappedStream, _cbMappedStream ));
#endif

    if (hr == S_OK || hr == STG_E_REVERTED)
    {
        _fDirty = FALSE;
    }

    return hr;

}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::GetSize
//
//  Synopsis:   Returns the current size of the mapped stream.
//
//  Arguments:  [LONG*] phr
//                  Used to return an HRESULT.
//
//  Returns:    [ULONG]
//                  The current size.
//
//--------------------------------------------------------------------

ULONG CSSMappedStream::GetSize(OUT LONG *phr)
{
    HRESULT &hr = *phr;
    hr = S_OK;

    propITrace( "CSSMappedStream::GetSize" );

    // If necessary, open the Stream.

    if( NULL == _pbMappedStream )
    {
        Open(NULL,  // Unspecified owner
             phr);
    }

    if( SUCCEEDED(*phr) )
    {
        DfpAssert( NULL != _pbMappedStream );
    }

    // Return the size of the mapped stream.  If there was an
    // Open error, it will be zero, and *phr will be set.

    propDbg(( DEB_ITRACE, "CSSMappedStream::GetSize, size is %d\n", _cbMappedStream ));
    return _cbMappedStream;
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::SetSize
//
//  Synopsis:   Set the size of the mapped stream.
//
//  Arguments:  [ULONG] cb
//                  The new size.
//              [BOOLEAN] fPersistent
//                  If not set, then this change will not be stored -
//                  thus the mapping buffer must be set, but the
//                  Stream itself must not.  This was added so that
//                  CPropertySetStream could grow the buffer for internal
//                  processing, when the Stream itself is read-only.
//              [VOID**] ppv
//                  Used to return the new mapping buffer location.
//
//  Returns:    None.
//
//  Pre-Conditions:
//              cb is below the maximum property set size.
//
//--------------------------------------------------------------------

VOID
CSSMappedStream::SetSize(ULONG cb,
                         IN BOOLEAN fPersistent,
                         VOID **ppv, OUT LONG *phr)
{
    BYTE            *pv;

    HRESULT &hr = *phr;

    hr = S_OK;
    DfpAssert(cb != 0);
    DfpAssert(cb <= CBMAXPROPSETSTREAM);

    propITrace( "CSSMappedStream::SetSize" );
    propTraceParameters(( "cb=%lu, fPersistent=%s, ppv=%p", cb, fPersistent?"True":"False" ));

    //
    // if we are growing the data, we should grow the stream
    //
    if (fPersistent && cb > _cbActualStreamSize)
    {
        ULARGE_INTEGER  uli;
        uli.HighPart = 0;
        uli.LowPart = cb;

        //----------------
        // Need to Grow!
        //----------------

        propDbg(( DEB_ITRACE, "Growing from %d to %d\n", _cbActualStreamSize, cb ));
        *phr = _pstm->SetSize( uli );

        if (FAILED(*phr) )
            goto Exit;
        else
            _cbActualStreamSize = cb;
    }

    //
    // We only get here if we either (1) didn't want to grow the
    // underlying stream, or (2) we successfully grew the underlying stream.
    //

    //
    // Re-size the buffer to the size specified in cb.
    //
    if ( _fLowMem )
    {
        // If we want to grow the buffer In low-memory conditions,
        // no realloc is necessary, because
        // _pbMappedStream is already large enough for the largest
        // property set.

        *ppv = _pbMappedStream;
    }
    else if ( cb != _cbMappedStream )
    {

            // We must re-alloc the buffer.

            pv = reinterpret_cast<BYTE*>( CoTaskMemAlloc( cb ));

            if ((pv == NULL) )
            {
                // allocation failed: we need to try using a backup mechanism for
                // more memory.
                // copy the data to the global reserved chunk... we will wait until
                // someone else has released it.  it will be released on the way out
                // of the property code.

                _fLowMem = TRUE;
                pv = g_ReservedMemory.LockMemory();
                if ( NULL == pv)
                {
                    *phr = E_OUTOFMEMORY;
                    goto Exit;
                }
                else if( NULL != _pbMappedStream)
                {
                    memcpy( pv, _pbMappedStream, min(cb,_cbMappedStream) );
                }
                CoTaskMemFree( _pbMappedStream );
            }
            else
            {
                memcpy( pv, _pbMappedStream, min(cb,_cbMappedStream) );
                CoTaskMemFree( _pbMappedStream );
            }

            _pbMappedStream = pv;
            *ppv = pv;
    }
    _cbMappedStream = cb;

    //  ----
    //  Exit
    //  ----

Exit:

    propDbg((DbgFlag(*phr,DEB_TRACE), "CSSMappedStream(%08X):SetSize %s returns hr=%08X\n",
                                    this, *phr != S_OK ? "exception" : "", *phr));

}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::Lock
//
//  Synopsis:   Locking is not supported by this class.
//
//--------------------------------------------------------------------

NTSTATUS CSSMappedStream::Lock(BOOLEAN fExclusive)
{
    return(STATUS_SUCCESS);
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::Unlock
//
//  Synopsis:   Locking is not supported by this class.
//              However, this method still must check to
//              see if the reserved memory pool should be
//              freed for use by another property set.
//
//--------------------------------------------------------------------

NTSTATUS CSSMappedStream::Unlock(VOID)
{
    // if at the end of the properties set/get call we have the low
    // memory region locked, we flush to disk.
    HRESULT hr = S_OK;

    if (_fLowMem)
    {
        Flush(&hr);

        g_ReservedMemory.UnlockMemory();
        _pbMappedStream = NULL;
        _cbMappedStream = 0;
        _fLowMem = FALSE;
        propDbg((DEB_ERROR, "CPubStream(%08X):Unlock low-mem returns NTSTATUS=%08X\n",
            this, hr));
    }

    return(hr);
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::QueryTimeStamps
//
//  Synopsis:   Not used by this IMappedStream derivation.
//
//--------------------------------------------------------------------

VOID CSSMappedStream::QueryTimeStamps(STATPROPSETSTG *pspss, BOOLEAN fNonSimple) const
{
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::QueryModifyTime
//
//  Synopsis:   Not used by this IMappedStream derivation.
//
//  Notes:
//
//--------------------------------------------------------------------

BOOLEAN CSSMappedStream::QueryModifyTime(OUT LONGLONG *pll) const
{
    return(FALSE);
}

//+-------------------------------------------------------------------
//
//  Member:     Unused methods by this IMappedStream implementation:
//              QuerySecurity, IsWritable, GetHandle
//
//--------------------------------------------------------------------

BOOLEAN CSSMappedStream::QuerySecurity(OUT ULONG *pul) const
{
    return(FALSE);
}

BOOLEAN CSSMappedStream::IsWriteable() const
{
    return TRUE;
}

HANDLE CSSMappedStream::GetHandle(VOID) const
{
    return(INVALID_HANDLE_VALUE);
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::SetModified/IsModified
//
//--------------------------------------------------------------------

VOID CSSMappedStream::SetModified(OUT LONG *phr)
{
    _fDirty = TRUE;
    *phr = S_OK;
}

BOOLEAN CSSMappedStream::IsModified(VOID) const
{
    propDbg(( DEB_ITRACE, "CSSMappedStream::IsModified (%s)\n", _fDirty?"TRUE":"FALSE" ));
    return (BOOLEAN) _fDirty;
}

//+-------------------------------------------------------------------
//
//  Member:     CSSMappedStream::IsNtMappedStream/SetChangePending
//
//  Synopsis:   Debug routines.
//
//--------------------------------------------------------------------

#if DBGPROP
BOOLEAN CSSMappedStream::IsNtMappedStream(VOID) const
{
    return(FALSE);
}
#endif


#if DBGPROP
BOOLEAN CSSMappedStream::SetChangePending(BOOLEAN f)
{
    BOOL fOld = _fChangePending;
    _fChangePending = f;
    return((BOOLEAN)_fChangePending);
}
#endif