/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <algorithm>

#include <string.h>
#include <sal/log.hxx>
#include <o3tl/safeint.hxx>
#include <osl/file.hxx>
#include <unotools/tempfile.hxx>

#include "stgelem.hxx"
#include "stgcache.hxx"
#include "stgstrms.hxx"
#include "stgdir.hxx"
#include "stgio.hxx"
#include <memory>

///////////////////////////// class StgFAT

// The FAT class performs FAT operations on an underlying storage stream.
// This stream is either the master FAT stream (m == true ) or a normal
// storage stream, which then holds the FAT for small data allocations.

StgFAT::StgFAT( StgStrm& r, bool m ) : m_rStrm( r )
{
    m_bPhys   = m;
    m_nPageSize = m_rStrm.GetIo().GetPhysPageSize();
    m_nEntries  = m_nPageSize >> 2;
    m_nOffset   = 0;
    m_nMaxPage  = 0;
    m_nLimit    = 0;
}

// Retrieve the physical page for a given byte offset.

rtl::Reference< StgPage > StgFAT::GetPhysPage( sal_Int32 nByteOff )
{
    rtl::Reference< StgPage > pPg;
    // Position within the underlying stream
    // use the Pos2Page() method of the stream
    if( m_rStrm.Pos2Page( nByteOff ) )
    {
        m_nOffset = m_rStrm.GetOffset();
        sal_Int32 nPhysPage = m_rStrm.GetPage();
        // get the physical page (must be present)
        pPg = m_rStrm.GetIo().Get( nPhysPage, true );
    }
    return pPg;
}

// Get the follow page for a certain FAT page.

sal_Int32 StgFAT::GetNextPage( sal_Int32 nPg )
{
    if (nPg >= 0)
    {
        if (nPg > (SAL_MAX_INT32 >> 2))
            return STG_EOF;
        rtl::Reference< StgPage > pPg = GetPhysPage( nPg << 2 );
        nPg = pPg.is() ? StgCache::GetFromPage( pPg, m_nOffset >> 2 ) : STG_EOF;
    }
    return nPg;
}

// Find the best fit block for the given size. Return
// the starting block and its size or STG_EOF and 0.
// nLastPage is a stopper which tells the current
// underlying stream size. It is treated as a recommendation
// to abort the search to inhibit excessive file growth.

sal_Int32 StgFAT::FindBlock( sal_Int32& nPgs )
{
    sal_Int32 nMinStart = STG_EOF, nMinLen = 0;
    sal_Int32 nMaxStart = STG_EOF, nMaxLen = 0x7FFFFFFFL;
    sal_Int32 nTmpStart = STG_EOF, nTmpLen = 0;
    sal_Int32 nPages    = m_rStrm.GetSize() >> 2;
    bool bFound     = false;
    rtl::Reference< StgPage > pPg;
    short nEntry = 0;
    for( sal_Int32 i = 0; i < nPages; i++, nEntry++ )
    {
        if( !( nEntry % m_nEntries ) )
        {
            // load the next page for that stream
            nEntry = 0;
            pPg = GetPhysPage( i << 2 );
            if( !pPg.is() )
                return STG_EOF;
        }
        sal_Int32 nCur = StgCache::GetFromPage( pPg, nEntry );
        if( nCur == STG_FREE )
        {
            // count the size of this area
            if( nTmpLen )
                nTmpLen++;
            else
            {
                nTmpStart = i;
                nTmpLen   = 1;
            }
            if( nTmpLen == nPgs
             // If we already did find a block, stop when reaching the limit
             || ( bFound && ( nEntry >= m_nLimit ) ) )
                break;
        }
        else if( nTmpLen )
        {
            if( nTmpLen > nPgs && nTmpLen < nMaxLen )
            {
                // block > requested size
                nMaxLen = nTmpLen;
                nMaxStart = nTmpStart;
                bFound = true;
            }
            else if( nTmpLen >= nMinLen )
            {
                // block < requested size
                nMinLen = nTmpLen;
                nMinStart = nTmpStart;
                bFound = true;
                if( nTmpLen == nPgs )
                    break;
            }
            nTmpStart = STG_EOF;
            nTmpLen   = 0;
        }
    }
    // Determine which block to use.
    if( nTmpLen )
    {
        if( nTmpLen > nPgs  && nTmpLen < nMaxLen )
        {
            // block > requested size
            nMaxLen = nTmpLen;
            nMaxStart = nTmpStart;
        }
        else if( nTmpLen >= nMinLen )
        {
            // block < requested size
            nMinLen = nTmpLen;
            nMinStart = nTmpStart;
        }
    }
    if( nMinStart != STG_EOF && nMaxStart != STG_EOF )
    {
        // two areas found; return the best fit area
        sal_Int32 nMinDiff = nPgs - nMinLen;
        sal_Int32 nMaxDiff = nMaxLen - nPgs;
        if( nMinDiff > nMaxDiff )
            nMinStart = STG_EOF;
    }
    if( nMinStart != STG_EOF )
    {
        nPgs = nMinLen; return nMinStart;
    }
    else
    {
        return nMaxStart;
    }
}

// Set up the consecutive chain for a given block.

bool StgFAT::MakeChain( sal_Int32 nStart, sal_Int32 nPgs )
{
    sal_Int32 nPos = nStart << 2;
    rtl::Reference< StgPage > pPg = GetPhysPage( nPos );
    if( !pPg.is() || !nPgs )
        return false;
    while( --nPgs )
    {
        if( m_nOffset >= m_nPageSize )
        {
            pPg = GetPhysPage( nPos );
            if( !pPg.is() )
                return false;
        }
        m_rStrm.GetIo().SetToPage( pPg, m_nOffset >> 2, ++nStart );
        m_nOffset += 4;
        nPos += 4;
    }
    if( m_nOffset >= m_nPageSize )
    {
        pPg = GetPhysPage( nPos );
        if( !pPg.is() )
            return false;
    }
    m_rStrm.GetIo().SetToPage( pPg, m_nOffset >> 2, STG_EOF );
    return true;
}

// Allocate a block of data from the given page number on.
// It the page number is != STG_EOF, chain the block.

sal_Int32 StgFAT::AllocPages( sal_Int32 nBgn, sal_Int32 nPgs )
{
    sal_Int32 nOrig = nBgn;
    sal_Int32 nLast = nBgn;
    sal_Int32 nBegin = STG_EOF;
    sal_Int32 nAlloc;
    sal_Int32 nPages = m_rStrm.GetSize() >> 2;
    short nPasses = 0;
    // allow for two passes
    while( nPasses < 2 )
    {
        // try to satisfy the request from the pool of free pages
        while( nPgs )
        {
            nAlloc = nPgs;
            nBegin = FindBlock( nAlloc );
            // no more blocks left in present alloc chain
            if( nBegin == STG_EOF )
                break;
            if( ( nBegin + nAlloc ) > m_nMaxPage )
                m_nMaxPage = nBegin + nAlloc;
            if( !MakeChain( nBegin, nAlloc ) )
                return STG_EOF;
            if( nOrig == STG_EOF )
                nOrig = nBegin;
            else
            {
                // Patch the chain
                rtl::Reference< StgPage > pPg = GetPhysPage( nLast << 2 );
                if( !pPg.is() )
                    return STG_EOF;
                m_rStrm.GetIo().SetToPage( pPg, m_nOffset >> 2, nBegin );
            }
            nLast = nBegin + nAlloc - 1;
            nPgs -= nAlloc;
        }
        if( nPgs && !nPasses )
        {
            // we need new, fresh space, so allocate and retry
            if( !m_rStrm.SetSize( ( nPages + nPgs ) << 2 ) )
                return STG_EOF;
            if( !m_bPhys && !InitNew( nPages ) )
                return 0;
                    // FIXME: this was originally "FALSE", whether or not that
                    // makes sense (or should be STG_EOF instead, say?)
            nPages = m_rStrm.GetSize() >> 2;
            nPasses++;
        }
        else
            break;
    }
    // now we should have a chain for the complete block
    if( nBegin == STG_EOF || nPgs )
    {
        m_rStrm.GetIo().SetError( SVSTREAM_FILEFORMAT_ERROR );
        return STG_EOF; // bad structure
    }
    return nOrig;
}

// Initialize newly allocated pages for a standard FAT stream
// It can be assumed that the stream size is always on
// a page boundary

bool StgFAT::InitNew( sal_Int32 nPage1 )
{
    sal_Int32 n = ( ( m_rStrm.GetSize() >> 2 ) - nPage1 ) / m_nEntries;
    if ( n > 0 )
    {
        while( n-- )
        {
            rtl::Reference< StgPage > pPg;
            // Position within the underlying stream
            // use the Pos2Page() method of the stream
            m_rStrm.Pos2Page( nPage1 << 2 );
            // Initialize the page
            pPg = m_rStrm.GetIo().Copy( m_rStrm.GetPage() );
            if ( !pPg.is() )
                return false;
            for( short i = 0; i < m_nEntries; i++ )
                m_rStrm.GetIo().SetToPage( pPg, i, STG_FREE );
            nPage1++;
        }
    }
    return true;
}

// Release a chain

bool StgFAT::FreePages( sal_Int32 nStart, bool bAll )
{
    while( nStart >= 0 )
    {
        rtl::Reference< StgPage > pPg = GetPhysPage( nStart << 2 );
        if( !pPg.is() )
            return false;
        nStart = StgCache::GetFromPage( pPg, m_nOffset >> 2 );
        // The first released page is either set to EOF or FREE
        m_rStrm.GetIo().SetToPage( pPg, m_nOffset >> 2, bAll ? STG_FREE : STG_EOF );
        bAll = true;
    }
    return true;
}

///////////////////////////// class StgStrm

// The base stream class provides basic functionality for seeking
// and accessing the data on a physical basis. It uses the built-in
// FAT class for the page allocations.

StgStrm::StgStrm( StgIo& r )
    : m_nPos(0),
      m_bBytePosValid(true),
      m_rIo(r),
      m_pEntry(nullptr),
      m_nStart(STG_EOF),
      m_nSize(0),
      m_nPage(STG_EOF),
      m_nOffset(0),
      m_nPageSize(m_rIo.GetPhysPageSize())
{
}

StgStrm::~StgStrm()
{
}

// Attach the stream to the given entry.

void StgStrm::SetEntry( StgDirEntry& r )
{
    r.m_aEntry.SetLeaf( STG_DATA, m_nStart );
    r.m_aEntry.SetSize( m_nSize );
    m_pEntry = &r;
    r.SetDirty();
}

/*
 * The page chain, is basically a singly linked list of slots each
 * point to the next page. Instead of traversing the file structure
 * for this each time build a simple flat in-memory vector list
 * of pages.
 */
sal_Int32 StgStrm::scanBuildPageChainCache()
{
    if (m_nSize > 0)
    {
        m_aPagesCache.reserve(m_nSize/m_nPageSize);
        m_aUsedPageNumbers.reserve(m_nSize/m_nPageSize);
    }

    bool bError = false;
    sal_Int32 nBgn = m_nStart;
    sal_Int32 nOptSize = 0;

    // Track already scanned PageNumbers here and use them to
    // see if an  already counted page is re-visited
    while( nBgn >= 0 && !bError )
    {
        m_aPagesCache.push_back(nBgn);
        nBgn = m_pFat->GetNextPage( nBgn );

        //returned second is false if it already exists
        if (!m_aUsedPageNumbers.insert(nBgn).second)
        {
            SAL_WARN ("sot", "Error: page number " << nBgn << " already in chain for stream");
            bError = true;
        }

        nOptSize += m_nPageSize;
    }
    if (bError)
    {
        SAL_WARN("sot", "returning wrong format error");
        m_rIo.SetError( ERRCODE_IO_WRONGFORMAT );
        m_aPagesCache.clear();
        m_aUsedPageNumbers.clear();
    }
    return nOptSize;
}

// Compute page number and offset for the given byte position.
// If the position is behind the size, set the stream right
// behind the EOF.
bool StgStrm::Pos2Page( sal_Int32 nBytePos )
{
    if ( !m_pFat )
        return false;

    // Values < 0 seek to the end
    if( nBytePos < 0 || nBytePos >= m_nSize )
        nBytePos = m_nSize;
    // Adjust the position back to offset 0
    m_nPos -= m_nOffset;
    sal_Int32 nMask = ~( m_nPageSize - 1 );
    sal_Int32 nOld = m_nPos & nMask;
    sal_Int32 nNew = nBytePos & nMask;
    m_nOffset = static_cast<short>( nBytePos & ~nMask );
    m_nPos = nBytePos;
    if (nOld == nNew)
        return m_bBytePosValid;

    // See fdo#47644 for a .doc with a vast amount of pages where seeking around the
    // document takes a colossal amount of time

    // Please Note: we build the pagescache incrementally as we go if necessary,
    // so that a corrupted FAT doesn't poison the stream state for earlier reads
    size_t nIdx = nNew / m_nPageSize;
    if( nIdx >= m_aPagesCache.size() )
    {
        // Extend the FAT cache ! ...
        size_t nToAdd = nIdx + 1;

        if (m_aPagesCache.empty())
        {
            m_aPagesCache.push_back( m_nStart );
            assert(m_aUsedPageNumbers.empty());
            m_aUsedPageNumbers.insert(m_nStart);
        }

        nToAdd -= m_aPagesCache.size();

        sal_Int32 nBgn = m_aPagesCache.back();

        // Start adding pages while we can
        while (nToAdd > 0 && nBgn >= 0)
        {
            sal_Int32 nOldBgn = nBgn;
            nBgn = m_pFat->GetNextPage(nOldBgn);
            if( nBgn >= 0 )
            {
                //returned second is false if it already exists
                if (!m_aUsedPageNumbers.insert(nBgn).second)
                {
                    SAL_WARN ("sot", "Error: page number " << nBgn << " already in chain for stream");
                    break;
                }

                //very much the normal case
                m_aPagesCache.push_back(nBgn);
                --nToAdd;
            }
        }
    }

    if ( nIdx > m_aPagesCache.size() )
    {
        SAL_WARN("sot", "seek to index " << nIdx <<
                 " beyond page cache size " << m_aPagesCache.size());
        // fdo#84229 - handle seek to end and back as eg. XclImpStream expects
        m_nPage = STG_EOF;
        m_nOffset = 0;
        // Intriguingly in the past we didn't reset nPos to match the real
        // length of the stream thus:
        //   nIdx = m_aPagesCache.size();
        //   nPos = nPageSize * nIdx;
        // so retain this behavior for now.
        m_bBytePosValid = false;
        return false;
    }

    // special case: seek to 1st byte of new, unallocated page
    // (in case the file size is a multiple of the page size)
    if( nBytePos == m_nSize && !m_nOffset && nIdx > 0 && nIdx == m_aPagesCache.size() )
    {
        nIdx--;
        m_nOffset = m_nPageSize;
    }
    else if ( nIdx == m_aPagesCache.size() )
    {
        m_nPage = STG_EOF;
        m_bBytePosValid = false;
        return false;
    }

    m_nPage = m_aPagesCache[ nIdx ];

    m_bBytePosValid = m_nPage >= 0;
    return m_bBytePosValid;
}

// Copy an entire stream. Both streams are allocated in the FAT.
// The target stream is this stream.

bool StgStrm::Copy( sal_Int32 nFrom, sal_Int32 nBytes )
{
    if ( !m_pFat )
        return false;

    m_aPagesCache.clear();
    m_aUsedPageNumbers.clear();

    sal_Int32 nTo = m_nStart;
    sal_Int32 nPgs = ( nBytes + m_nPageSize - 1 ) / m_nPageSize;
    while( nPgs-- )
    {
        if( nTo < 0 )
        {
            m_rIo.SetError( SVSTREAM_FILEFORMAT_ERROR );
            return false;
        }
        m_rIo.Copy( nTo, nFrom );
        if( nFrom >= 0 )
        {
            nFrom = m_pFat->GetNextPage( nFrom );
            if( nFrom < 0 )
            {
                m_rIo.SetError( SVSTREAM_FILEFORMAT_ERROR );
                return false;
            }
        }
        nTo = m_pFat->GetNextPage( nTo );
    }
    return true;
}

bool StgStrm::SetSize( sal_Int32 nBytes )
{
    if ( nBytes < 0 || !m_pFat )
        return false;

    m_aPagesCache.clear();
    m_aUsedPageNumbers.clear();

    // round up to page size
    sal_Int32 nOld = ( ( m_nSize + m_nPageSize - 1 ) / m_nPageSize ) * m_nPageSize;
    sal_Int32 nNew = ( ( nBytes + m_nPageSize - 1 ) / m_nPageSize ) * m_nPageSize;
    if( nNew > nOld )
    {
        if( !Pos2Page( m_nSize ) )
            return false;
        sal_Int32 nBgn = m_pFat->AllocPages( m_nPage, ( nNew - nOld ) / m_nPageSize );
        if( nBgn == STG_EOF )
            return false;
        if( m_nStart == STG_EOF )
            m_nStart = m_nPage = nBgn;
    }
    else if( nNew < nOld )
    {
        bool bAll = ( nBytes == 0 );
        if( !Pos2Page( nBytes ) || !m_pFat->FreePages( m_nPage, bAll ) )
            return false;
        if( bAll )
            m_nStart = m_nPage = STG_EOF;
    }
    if( m_pEntry )
    {
        // change the dir entry?
        if( !m_nSize || !nBytes )
            m_pEntry->m_aEntry.SetLeaf( STG_DATA, m_nStart );
        m_pEntry->m_aEntry.SetSize( nBytes );
        m_pEntry->SetDirty();
    }
    m_nSize = nBytes;
    m_pFat->SetLimit( GetPages() );
    return true;
}

// Return the # of allocated pages


//////////////////////////// class StgFATStrm

// The FAT stream class provides physical access to the master FAT.
// Since this access is implemented as a StgStrm, we can use the
// FAT allocator.

StgFATStrm::StgFATStrm(StgIo& r, sal_Int32 nFatStrmSize) : StgStrm( r )
{
    m_pFat.reset( new StgFAT( *this, true ) );
    m_nSize = nFatStrmSize;
}

bool StgFATStrm::Pos2Page( sal_Int32 nBytePos )
{
    // Values < 0 seek to the end
    if( nBytePos < 0 || nBytePos >= m_nSize  )
        nBytePos = m_nSize ? m_nSize - 1 : 0;
    m_nPage   = nBytePos / m_nPageSize;
    m_nOffset = static_cast<short>( nBytePos % m_nPageSize );
    m_nPage   = GetPage(m_nPage, false);
    bool bValid = m_nPage >= 0;
    SetPos(nBytePos, bValid);
    return bValid;
}

// Get the page number entry for the given page offset.

sal_Int32 StgFATStrm::GetPage(sal_Int32 nOff, bool bMake, sal_uInt16 *pnMasterAlloc)
{
    OSL_ENSURE( nOff >= 0, "The offset may not be negative!" );
    if( pnMasterAlloc ) *pnMasterAlloc = 0;
    if( nOff < StgHeader::GetFAT1Size() )
        return m_rIo.m_aHdr.GetFATPage( nOff );
    sal_Int32 nMaxPage = m_nSize >> 2;
    nOff = nOff - StgHeader::GetFAT1Size();
    // number of master pages that we need to iterate through
    sal_uInt16 nMasterCount =  ( m_nPageSize >> 2 ) - 1;
    sal_uInt16 nBlocks = nOff / nMasterCount;
    // offset in the last master page
    nOff = nOff % nMasterCount;

    rtl::Reference< StgPage > pOldPage;
    rtl::Reference< StgPage > pMaster;
    sal_Int32 nFAT = m_rIo.m_aHdr.GetFATChain();
    for( sal_uInt16 nCount = 0; nCount <= nBlocks; nCount++ )
    {
        if( nFAT == STG_EOF || nFAT == STG_FREE )
        {
            if( bMake )
            {
                m_aPagesCache.clear();
                m_aUsedPageNumbers.clear();

                // create a new master page
                nFAT = nMaxPage++;
                pMaster = m_rIo.Copy( nFAT );
                if ( pMaster.is() )
                {
                    for( short k = 0; k < static_cast<short>( m_nPageSize >> 2 ); k++ )
                        m_rIo.SetToPage( pMaster, k, STG_FREE );
                    // chaining
                    if( !pOldPage.is() )
                        m_rIo.m_aHdr.SetFATChain( nFAT );
                    else
                        m_rIo.SetToPage( pOldPage, nMasterCount, nFAT );
                    if( nMaxPage >= m_rIo.GetPhysPages() )
                        if( !m_rIo.SetSize( nMaxPage ) )
                            return STG_EOF;
                    // mark the page as used
                    // make space for Masterpage
                    if( !pnMasterAlloc ) // create space oneself
                    {
                        if( !Pos2Page( nFAT << 2 ) )
                            return STG_EOF;
                        rtl::Reference< StgPage > pPg = m_rIo.Get( m_nPage, true );
                        if( !pPg.is() )
                            return STG_EOF;
                        m_rIo.SetToPage( pPg, m_nOffset >> 2, STG_MASTER );
                    }
                    else
                        (*pnMasterAlloc)++;
                    m_rIo.m_aHdr.SetMasters( nCount + 1 );
                    pOldPage = pMaster;
                }
            }
        }
        else
        {
            pMaster = m_rIo.Get( nFAT, true );
            if ( pMaster.is() )
            {
                nFAT = StgCache::GetFromPage( pMaster, nMasterCount );
                pOldPage = pMaster;
            }
        }
    }
    if( pMaster.is() )
        return StgCache::GetFromPage( pMaster, nOff );
    m_rIo.SetError( SVSTREAM_GENERALERROR );
    return STG_EOF;
}


// Set the page number entry for the given page offset.

bool StgFATStrm::SetPage( short nOff, sal_Int32 nNewPage )
{
    OSL_ENSURE( nOff >= 0, "The offset may not be negative!" );
    m_aPagesCache.clear();
    m_aUsedPageNumbers.clear();

    bool bRes = true;
    if( nOff < StgHeader::GetFAT1Size() )
        m_rIo.m_aHdr.SetFATPage( nOff, nNewPage );
    else
    {
        nOff = nOff - StgHeader::GetFAT1Size();
        // number of master pages that we need to iterate through
        sal_uInt16 nMasterCount =  ( m_nPageSize >> 2 ) - 1;
        sal_uInt16 nBlocks = nOff / nMasterCount;
        // offset in the last master page
        nOff = nOff % nMasterCount;

        rtl::Reference< StgPage > pMaster;
        sal_Int32 nFAT = m_rIo.m_aHdr.GetFATChain();
        for( sal_uInt16 nCount = 0; nCount <= nBlocks; nCount++ )
        {
            if( nFAT == STG_EOF || nFAT == STG_FREE )
            {
                pMaster = nullptr;
                break;
            }
            pMaster = m_rIo.Get( nFAT, true );
            if ( pMaster.is() )
                nFAT = StgCache::GetFromPage( pMaster, nMasterCount );
        }
        if( pMaster.is() )
            m_rIo.SetToPage( pMaster, nOff, nNewPage );
        else
        {
            m_rIo.SetError( SVSTREAM_GENERALERROR );
            bRes = false;
        }
    }

    // lock the page against access
    if( bRes )
    {
        Pos2Page( nNewPage << 2 );
        rtl::Reference< StgPage > pPg = m_rIo.Get( m_nPage, true );
        if( pPg.is() )
            m_rIo.SetToPage( pPg, m_nOffset >> 2, STG_FAT );
        else
            bRes = false;
    }
    return bRes;
}

bool StgFATStrm::SetSize( sal_Int32 nBytes )
{
    if ( nBytes < 0 )
        return false;

    m_aPagesCache.clear();
    m_aUsedPageNumbers.clear();

    // Set the number of entries to a multiple of the page size
    short nOld = static_cast<short>( ( m_nSize + ( m_nPageSize - 1 ) ) / m_nPageSize );
    short nNew = static_cast<short>(
        ( nBytes + ( m_nPageSize - 1 ) ) / m_nPageSize ) ;
    if( nNew < nOld )
    {
        // release master pages
        for( short i = nNew; i < nOld; i++ )
            SetPage( i, STG_FREE );
    }
    else
    {
        while( nOld < nNew )
        {
            // allocate master pages
            // find a free master page slot
            sal_Int32 nPg = 0;
            sal_uInt16 nMasterAlloc = 0;
            nPg = GetPage( nOld, true, &nMasterAlloc );
            if( nPg == STG_EOF )
                return false;
            // 4 Bytes have been used for Allocation of each MegaMasterPage
            nBytes += nMasterAlloc << 2;

            // find a free page using the FAT allocator
            sal_Int32 n = 1;
            OSL_ENSURE( m_pFat, "The pointer is always initializer here!" );
            sal_Int32 nNewPage = m_pFat->FindBlock( n );
            if( nNewPage == STG_EOF )
            {
                // no free pages found; create a new page
                // Since all pages are allocated, extend
                // the file size for the next page!
                nNewPage = m_nSize >> 2;
                // if a MegaMasterPage was created avoid taking
                // the same Page
                nNewPage += nMasterAlloc;
                // adjust the file size if necessary
                if( nNewPage >= m_rIo.GetPhysPages() )
                    if( !m_rIo.SetSize( nNewPage + 1 ) )
                        return false;
            }
            // Set up the page with empty entries
            rtl::Reference< StgPage > pPg = m_rIo.Copy( nNewPage );
            if ( !pPg.is() )
                return false;
            for( short j = 0; j < static_cast<short>( m_nPageSize >> 2 ); j++ )
                m_rIo.SetToPage( pPg, j, STG_FREE );

            // store the page number into the master FAT
            // Set the size before so the correct FAT can be found
            m_nSize = ( nOld + 1 ) * m_nPageSize;
            SetPage( nOld, nNewPage );

            // MegaMasterPages were created, mark it them as used

            sal_uInt32 nMax = m_rIo.m_aHdr.GetMasters( );
            sal_uInt32 nFAT = m_rIo.m_aHdr.GetFATChain();
            if( nMasterAlloc )
                for( sal_uInt32 nCount = 0; nCount < nMax; nCount++ )
                {
                    if( !Pos2Page( nFAT << 2 ) )
                        return false;
                    if( nMax - nCount <= nMasterAlloc )
                    {
                        rtl::Reference< StgPage > piPg = m_rIo.Get( m_nPage, true );
                        if( !piPg.is() )
                            return false;
                        m_rIo.SetToPage( piPg, m_nOffset >> 2, STG_MASTER );
                    }
                    rtl::Reference< StgPage > pPage = m_rIo.Get( nFAT, true );
                    if( !pPage.is() ) return false;
                    nFAT = StgCache::GetFromPage( pPage, (m_nPageSize >> 2 ) - 1 );
                }

            nOld++;
            // We have used up 4 bytes for the STG_FAT entry
            nBytes += 4;
            nNew = static_cast<short>(
                ( nBytes + ( m_nPageSize - 1 ) ) / m_nPageSize );
        }
    }
    m_nSize = nNew * m_nPageSize;
    m_rIo.m_aHdr.SetFATSize( nNew );
    return true;
}

/////////////////////////// class StgDataStrm

// This class is a normal physical stream which can be initialized
// either with an existing dir entry or an existing FAT chain.
// The stream has a size increment which normally is 1, but which can be
// set to any value is you want the size to be incremented by certain values.

StgDataStrm::StgDataStrm( StgIo& r, sal_Int32 nBgn, sal_Int32 nLen ) : StgStrm( r )
{
    Init( nBgn, nLen );
}

StgDataStrm::StgDataStrm( StgIo& r, StgDirEntry& p ) : StgStrm( r )
{
    m_pEntry = &p;
    Init( p.m_aEntry.GetLeaf( STG_DATA ),
          p.m_aEntry.GetSize() );
}

void StgDataStrm::Init( sal_Int32 nBgn, sal_Int32 nLen )
{
    if ( m_rIo.m_pFAT )
        m_pFat.reset( new StgFAT( *m_rIo.m_pFAT, true ) );

    OSL_ENSURE( m_pFat, "The pointer should not be empty!" );

    m_nStart = m_nPage = nBgn;
    m_nSize  = nLen;
    m_nIncr  = 1;
    m_nOffset = 0;
    if( nLen < 0 && m_pFat )
    {
        // determine the actual size of the stream by scanning
        // the FAT chain and counting the # of pages allocated
        m_nSize = scanBuildPageChainCache();
    }
}

// Set the size of a physical stream.

bool StgDataStrm::SetSize( sal_Int32 nBytes )
{
    if ( !m_pFat )
        return false;

    nBytes = ( ( nBytes + m_nIncr - 1 ) / m_nIncr ) * m_nIncr;
    sal_Int32 nOldSz = m_nSize;
    if( nOldSz != nBytes )
    {
        if( !StgStrm::SetSize( nBytes ) )
            return false;
        sal_Int32 nMaxPage = m_pFat->GetMaxPage();
        if( nMaxPage > m_rIo.GetPhysPages() )
            if( !m_rIo.SetSize( nMaxPage ) )
                return false;
        // If we only allocated one page or less, create this
        // page in the cache for faster throughput. The current
        // position is the former EOF point.
        if( ( m_nSize - 1 )  / m_nPageSize - ( nOldSz - 1 ) / m_nPageSize == 1 )
        {
            Pos2Page( nBytes );
            if( m_nPage >= 0 )
                m_rIo.Copy( m_nPage );
        }
    }
    return true;
}

// Get the address of the data byte at a specified offset.
// If bForce = true, a read of non-existent data causes
// a read fault.

void* StgDataStrm::GetPtr( sal_Int32 Pos, bool bDirty )
{
    if( Pos2Page( Pos ) )
    {
        rtl::Reference< StgPage > pPg = m_rIo.Get( m_nPage, true/*bForce*/ );
        if (pPg.is() && m_nOffset < pPg->GetSize())
        {
            if( bDirty )
                m_rIo.SetDirty( pPg );
            return static_cast<sal_uInt8 *>(pPg->GetData()) + m_nOffset;
        }
    }
    return nullptr;
}

// This could easily be adapted to a better algorithm by determining
// the amount of consecutable blocks before doing a read. The result
// is the number of bytes read. No error is generated on EOF.

sal_Int32 StgDataStrm::Read( void* pBuf, sal_Int32 n )
{
    if ( n < 0 )
        return 0;

    const auto nAvailable = m_nSize - GetPos();
    if (n > nAvailable)
        n = nAvailable;
    sal_Int32 nDone = 0;
    while( n )
    {
        short nBytes = m_nPageSize - m_nOffset;
        rtl::Reference< StgPage > pPg;
        if( static_cast<sal_Int32>(nBytes) > n )
            nBytes = static_cast<short>(n);
        if( nBytes )
        {
            short nRes;
            void *p = static_cast<sal_uInt8 *>(pBuf) + nDone;
            if( nBytes == m_nPageSize )
            {
                pPg = m_rIo.Find( m_nPage );
                if( pPg.is() )
                {
                    // data is present, so use the cached data
                    memcpy( p, pPg->GetData(), nBytes );
                    nRes = nBytes;
                }
                else
                    // do a direct (unbuffered) read
                    nRes = static_cast<short>(m_rIo.Read( m_nPage, p )) * m_nPageSize;
            }
            else
            {
                // partial block read through the cache.
                pPg = m_rIo.Get( m_nPage, false );
                if( !pPg.is() )
                    break;
                memcpy( p, static_cast<sal_uInt8*>(pPg->GetData()) + m_nOffset, nBytes );
                nRes = nBytes;
            }
            nDone += nRes;
            SetPos(GetPos() + nRes, true);
            n -= nRes;
            m_nOffset = m_nOffset + nRes;
            if( nRes != nBytes )
                break;  // read error or EOF
        }
        // Switch to next page if necessary
        if (m_nOffset >= m_nPageSize && !Pos2Page(GetPos()))
            break;
    }
    return nDone;
}

sal_Int32 StgDataStrm::Write( const void* pBuf, sal_Int32 n )
{
    if ( n < 0 )
        return 0;

    sal_Int32 nDone = 0;
    if( ( GetPos() + n ) > m_nSize )
    {
        sal_Int32 nOld = GetPos();
        if( !SetSize( nOld + n ) )
            return 0;
        Pos2Page( nOld );
    }
    while( n )
    {
        short nBytes = m_nPageSize - m_nOffset;
        rtl::Reference< StgPage > pPg;
        if( static_cast<sal_Int32>(nBytes) > n )
            nBytes = static_cast<short>(n);
        if( nBytes )
        {
            short nRes;
            const void *p = static_cast<const sal_uInt8 *>(pBuf) + nDone;
            if( nBytes == m_nPageSize )
            {
                pPg = m_rIo.Find( m_nPage );
                if( pPg.is() )
                {
                    // data is present, so use the cached data
                    memcpy( pPg->GetData(), p, nBytes );
                    m_rIo.SetDirty( pPg );
                    nRes = nBytes;
                }
                else
                    // do a direct (unbuffered) write
                    nRes = static_cast<short>(m_rIo.Write( m_nPage, p )) * m_nPageSize;
            }
            else
            {
                // partial block read through the cache.
                pPg = m_rIo.Get( m_nPage, false );
                if( !pPg.is() )
                    break;
                memcpy( static_cast<sal_uInt8*>(pPg->GetData()) + m_nOffset, p, nBytes );
                m_rIo.SetDirty( pPg );
                nRes = nBytes;
            }
            nDone += nRes;
            SetPos(GetPos() + nRes, true);
            n -= nRes;
            m_nOffset = m_nOffset + nRes;
            if( nRes != nBytes )
                break;  // read error
        }
        // Switch to next page if necessary
        if( m_nOffset >= m_nPageSize && !Pos2Page(GetPos()) )
            break;
    }
    return nDone;
}

//////////////////////////// class StgSmallStream

// The small stream class provides access to streams with a size < 4096 bytes.
// This stream is a StgStream containing small pages. The FAT for this stream
// is also a StgStream. The start of the FAT is in the header at DataRootPage,
// the stream itself is pointed to by the root entry (it holds start & size).

StgSmallStrm::StgSmallStrm( StgIo& r, sal_Int32 nBgn ) : StgStrm( r )
{
    Init( nBgn, 0 );
}

StgSmallStrm::StgSmallStrm( StgIo& r, StgDirEntry& p ) : StgStrm( r )
{
    m_pEntry = &p;
    Init( p.m_aEntry.GetLeaf( STG_DATA ),
          p.m_aEntry.GetSize() );
}

void StgSmallStrm::Init( sal_Int32 nBgn, sal_Int32 nLen )
{
    if ( m_rIo.m_pDataFAT )
        m_pFat.reset( new StgFAT( *m_rIo.m_pDataFAT, false ) );
    m_pData = m_rIo.m_pDataStrm;
    OSL_ENSURE( m_pFat && m_pData, "The pointers should not be empty!" );

    m_nPageSize = m_rIo.GetDataPageSize();
    m_nStart =
    m_nPage  = nBgn;
    m_nSize  = nLen;
}

// This could easily be adapted to a better algorithm by determining
// the amount of consecutable blocks before doing a read. The result
// is the number of bytes read. No error is generated on EOF.

sal_Int32 StgSmallStrm::Read( void* pBuf, sal_Int32 n )
{
    // We can safely assume that reads are not huge, since the
    // small stream is likely to be < 64 KBytes.
    sal_Int32 nBytePos = GetPos();
    if( ( nBytePos + n ) > m_nSize )
        n = m_nSize - nBytePos;
    sal_Int32 nDone = 0;
    while( n )
    {
        short nBytes = m_nPageSize - m_nOffset;
        if( static_cast<sal_Int32>(nBytes) > n )
            nBytes = static_cast<short>(n);
        if( nBytes )
        {
            if (!m_pData)
                break;
            sal_Int32 nPos;
            if (o3tl::checked_multiply<sal_Int32>(m_nPage, m_nPageSize, nPos))
                break;
            if (!m_pData->Pos2Page(nPos + m_nOffset))
                break;
            // all reading through the stream
            short nRes = static_cast<short>(m_pData->Read( static_cast<sal_uInt8*>(pBuf) + nDone, nBytes ));
            nDone += nRes;
            SetPos(GetPos() + nRes, true);
            n -= nRes;
            m_nOffset = m_nOffset + nRes;
            // read problem?
            if( nRes != nBytes )
                break;
        }
        // Switch to next page if necessary
        if (m_nOffset >= m_nPageSize && !Pos2Page(GetPos()))
            break;
    }
    return nDone;
}

sal_Int32 StgSmallStrm::Write( const void* pBuf, sal_Int32 n )
{
    // you can safely assume that reads are not huge, since the
    // small stream is likely to be < 64 KBytes.
    sal_Int32 nDone = 0;
    sal_Int32 nOldPos = GetPos();
    if( ( nOldPos + n ) > m_nSize )
    {
        if (!SetSize(nOldPos + n))
            return 0;
        Pos2Page(nOldPos);
    }
    while( n )
    {
        short nBytes = m_nPageSize - m_nOffset;
        if( static_cast<sal_Int32>(nBytes) > n )
            nBytes = static_cast<short>(n);
        if( nBytes )
        {
            // all writing goes through the stream
            sal_Int32 nDataPos = m_nPage * m_nPageSize + m_nOffset;
            if ( !m_pData
              || ( m_pData->GetSize() < ( nDataPos + nBytes )
                && !m_pData->SetSize( nDataPos + nBytes ) ) )
                break;
            if( !m_pData->Pos2Page( nDataPos ) )
                break;
            short nRes = static_cast<short>(m_pData->Write( static_cast<sal_uInt8 const *>(pBuf) + nDone, nBytes ));
            nDone += nRes;
            SetPos(GetPos() + nRes, true);
            n -= nRes;
            m_nOffset = m_nOffset + nRes;
            // write problem?
            if( nRes != nBytes )
                break;
        }
        // Switch to next page if necessary
        if( m_nOffset >= m_nPageSize && !Pos2Page(GetPos()) )
            break;
    }
    return nDone;
}

/////////////////////////// class StgTmpStrm

// The temporary stream uses a memory stream if < 32K, otherwise a
// temporary file.

#define THRESHOLD 32768L

StgTmpStrm::StgTmpStrm( sal_uInt64 nInitSize )
          : SvMemoryStream( nInitSize > THRESHOLD
                              ? 16
                            : ( nInitSize ? nInitSize : 16 ), 4096 )
{
    m_pStrm = nullptr;
    // this calls FlushData, so all members should be set by this time
    SetBufferSize( 0 );
    if( nInitSize > THRESHOLD )
        SetSize( nInitSize );
}

bool StgTmpStrm::Copy( StgTmpStrm& rSrc )
{
    sal_uInt64 n = rSrc.GetSize();
    const sal_uInt64 nCur = rSrc.Tell();
    SetSize( n );
    if( GetError() == ERRCODE_NONE )
    {
        std::unique_ptr<sal_uInt8[]> p(new sal_uInt8[ 4096 ]);
        rSrc.Seek( 0 );
        Seek( 0 );
        while( n )
        {
            const sal_uInt64 nn = std::min<sal_uInt64>(n, 4096);
            if (rSrc.ReadBytes( p.get(), nn ) != nn)
                break;
            if (WriteBytes( p.get(), nn ) != nn)
                break;
            n -= nn;
        }
        p.reset();
        rSrc.Seek( nCur );
        Seek( nCur );
        return n == 0;
    }
    else
        return false;
}

StgTmpStrm::~StgTmpStrm()
{
    if( m_pStrm )
    {
        m_pStrm->Close();
        osl::File::remove( m_aName );
        delete m_pStrm;
    }
}

sal_uInt64 StgTmpStrm::GetSize() const
{
    sal_uInt64 n;
    if( m_pStrm )
    {
        n = m_pStrm->TellEnd();
    }
    else
        n = nEndOfData;
    return n;
}

void StgTmpStrm::SetSize(sal_uInt64 n)
{
    if( m_pStrm )
        m_pStrm->SetStreamSize( n );
    else
    {
        if( n > THRESHOLD )
        {
            m_aName = utl::CreateTempURL();
            std::unique_ptr<SvFileStream> s(new SvFileStream( m_aName, StreamMode::READWRITE ));
            const sal_uInt64 nCur = Tell();
            sal_uInt64 i = nEndOfData;
            std::unique_ptr<sal_uInt8[]> p(new sal_uInt8[ 4096 ]);
            if( i )
            {
                Seek( 0 );
                while( i )
                {
                    const sal_uInt64 nb = std::min<sal_uInt64>(i, 4096);
                    if (ReadBytes(p.get(), nb) == nb
                        && s->WriteBytes(p.get(), nb) == nb)
                        i -= nb;
                    else
                        break;
                }
            }
            if( !i && n > nEndOfData )
            {
                // We have to write one byte at the end of the file
                // if the file is bigger than the memstream to see
                // if it fits on disk
                s->Seek(nEndOfData);
                memset(p.get(), 0x00, 4096);
                i = n - nEndOfData;
                while (i)
                {
                    const sal_uInt64 nb = std::min<sal_uInt64>(i, 4096);
                    if (s->WriteBytes(p.get(), nb) == nb)
                        i -= nb;
                    else
                        break; // error
                }
                s->Flush();
                if( s->GetError() != ERRCODE_NONE )
                    i = 1;
            }
            Seek( nCur );
            s->Seek( nCur );
            if( i )
            {
                SetError( s->GetError() );
                return;
            }
            m_pStrm = s.release();
            // Shrink the memory to 16 bytes, which seems to be the minimum
            ReAllocateMemory( - ( static_cast<tools::Long>(nEndOfData) - 16 ) );
        }
        else
        {
            if( n > nEndOfData )
            {
                SvMemoryStream::SetSize(n);
            }
            else
                nEndOfData = n;
        }
    }
}

std::size_t StgTmpStrm::GetData( void* pData, std::size_t n )
{
    if( m_pStrm )
    {
        n = m_pStrm->ReadBytes( pData, n );
        SetError( m_pStrm->GetError() );
        return n;
    }
    else
        return SvMemoryStream::GetData( pData, n );
}

std::size_t StgTmpStrm::PutData( const void* pData, std::size_t n )
{
    sal_uInt32 nCur = Tell();
    sal_uInt32 nNew = nCur + n;
    if( nNew > THRESHOLD && !m_pStrm )
    {
        SetSize( nNew );
        if( GetError() != ERRCODE_NONE )
            return 0;
    }
    if( m_pStrm )
    {
        nNew = m_pStrm->WriteBytes( pData, n );
        SetError( m_pStrm->GetError() );
    }
    else
        nNew = SvMemoryStream::PutData( pData, n );
    return nNew;
}

sal_uInt64 StgTmpStrm::SeekPos(sal_uInt64 n)
{
    // check if a truncated STREAM_SEEK_TO_END was passed
    assert(n != SAL_MAX_UINT32);
    if( n == STREAM_SEEK_TO_END )
        n = GetSize();
    if( n > THRESHOLD && !m_pStrm )
    {
        SetSize( n );
        if( GetError() != ERRCODE_NONE )
            return Tell();
        else
            return n;
    }
    else if( m_pStrm )
    {
        n = m_pStrm->Seek( n );
        SetError( m_pStrm->GetError() );
        return n;
    }
    else
        return SvMemoryStream::SeekPos( n );
}

void StgTmpStrm::FlushData()
{
    if( m_pStrm )
    {
        m_pStrm->Flush();
        SetError( m_pStrm->GetError() );
    }
    else
        SvMemoryStream::FlushData();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */