diff options
Diffstat (limited to 'sot/source/sdstor/stgdir.cxx')
-rw-r--r-- | sot/source/sdstor/stgdir.cxx | 938 |
1 files changed, 938 insertions, 0 deletions
diff --git a/sot/source/sdstor/stgdir.cxx b/sot/source/sdstor/stgdir.cxx new file mode 100644 index 000000000..69ca094f3 --- /dev/null +++ b/sot/source/sdstor/stgdir.cxx @@ -0,0 +1,938 @@ +/* -*- 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 <sot/stg.hxx> +#include "stgelem.hxx" +#include "stgstrms.hxx" +#include "stgdir.hxx" +#include "stgio.hxx" + +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include <memory> + +//////////////////////////// class StgDirEntry + +// This class holds the dir entry data and maintains dirty flags for both +// the entry and the data. + +// Transacted mode for streams: On the first write, a temp stream pTmpStrm +// is created and operated on. A commit moves pTmpStrm to pCurStrm, which +// is used for subsequent reads. A new write creates a new copy of pTmpStrm +// based on pCurStrm. Reverting throws away pTmpStrm. +// Transacted mode for storages: A copy of the dir ents is kept in aSave. +// Committing means copying aEntry to aSave. Reverting means to copy aSave +// to aEntry, delete newly created entries and to reactivate removed entries. + +// Problem of implementation: No hierarchical commits. Therefore only +// overall transaction-oriented or direct. + +StgDirEntry::StgDirEntry( const void* pBuffer, sal_uInt32 nBufferLen, sal_uInt64 nUnderlyingStreamSize, bool * pbOk ) +{ + *pbOk = m_aEntry.Load( pBuffer, nBufferLen, nUnderlyingStreamSize ); + + InitMembers(); +} + +StgDirEntry::StgDirEntry( const StgEntry& r ) : m_aEntry( r ) +{ + InitMembers(); +} + +// Helper for all ctors + +void StgDirEntry::InitMembers() +{ + m_aSave = m_aEntry; + m_pUp = + m_pDown = nullptr; + m_pStgStrm = nullptr; + m_pCurStrm = + m_pTmpStrm = nullptr; + m_nPos = + m_nEntry = + m_nRefCnt = 0; + m_nMode = StreamMode::READ; + m_bDirect = true; + m_bInvalid = + m_bRemoved = + m_bTemp = + m_bDirty = + m_bZombie = false; +} + +StgDirEntry::~StgDirEntry() +{ + Close(); + delete m_pCurStrm; + delete m_pStgStrm; + delete m_pDown; +} + +// Comparison function + +sal_Int32 StgDirEntry::Compare( const StgAvlNode* p ) const +{ + sal_Int32 nResult = -1; + if ( p ) + { + const StgDirEntry* pEntry = static_cast<const StgDirEntry*>(p); + nResult = m_aEntry.Compare( pEntry->m_aEntry ); + } + return nResult; +} + +// Enumerate the entry numbers. +// n is incremented to show the total # of entries. +// These number are later used as page numbers when storing +// the TOC tree into the TOC stream. Remember that aSave is +// stored, not aEntry. + +void StgDirEntry::Enum( sal_Int32& n ) +{ + sal_Int32 nLeft = STG_FREE, nRight = STG_FREE, nDown = STG_FREE; + m_nEntry = n++; + if( m_pLeft ) + { + static_cast<StgDirEntry*>(m_pLeft)->Enum( n ); + nLeft = static_cast<StgDirEntry*>(m_pLeft)->m_nEntry; + } + if( m_pRight ) + { + static_cast<StgDirEntry*>(m_pRight)->Enum( n ); + nRight = static_cast<StgDirEntry*>(m_pRight)->m_nEntry; + } + if( m_pDown ) + { + m_pDown->Enum( n ); nDown = m_pDown->m_nEntry; + } + m_aSave.SetLeaf( STG_LEFT, nLeft ); + m_aSave.SetLeaf( STG_RIGHT, nRight ); + m_aSave.SetLeaf( STG_CHILD, nDown ); +} + +// Delete all temporary entries before writing the TOC stream. +// Until now Deltemp is never called with bForce True + +void StgDirEntry::DelTemp( bool bForce ) +{ + if( m_pLeft ) + static_cast<StgDirEntry*>(m_pLeft)->DelTemp( false ); + if( m_pRight ) + static_cast<StgDirEntry*>(m_pRight)->DelTemp( false ); + if( m_pDown ) + { + // If the storage is dead, of course all elements are dead, too + if( m_bInvalid && m_aEntry.GetType() == STG_STORAGE ) + bForce = true; + m_pDown->DelTemp( bForce ); + } + if( !( bForce || m_bInvalid ) || ( m_aEntry.GetType() == STG_ROOT ) ) + return; + + Close(); + if( m_pUp ) + { + // this deletes the element if refcnt == 0! + bool bDel = m_nRefCnt == 0; + StgAvlNode::Remove( reinterpret_cast<StgAvlNode**>(&m_pUp->m_pDown), this, bDel ); + if( !bDel ) + { + m_pLeft = m_pRight = m_pDown = nullptr; + m_bInvalid = m_bZombie = true; + } + } +} + +// Save the tree into the given dir stream + +bool StgDirEntry::Store( StgDirStrm& rStrm ) +{ + void* pEntry = rStrm.GetEntry( m_nEntry, true ); + if( !pEntry ) + return false; + // Do not store the current (maybe not committed) entry + m_aSave.Store( pEntry ); + if( m_pLeft ) + if( !static_cast<StgDirEntry*>(m_pLeft)->Store( rStrm ) ) + return false; + if( m_pRight ) + if( !static_cast<StgDirEntry*>(m_pRight)->Store( rStrm ) ) + return false; + if( m_pDown && !m_pDown->Store( rStrm ) ) + return false; + return true; +} + +bool StgDirEntry::StoreStream( StgIo& rIo ) +{ + if( m_aEntry.GetType() == STG_STREAM || m_aEntry.GetType() == STG_ROOT ) + { + if( m_bInvalid ) + { + // Delete the stream if needed + if( !m_pStgStrm ) + { + OpenStream( rIo ); + delete m_pStgStrm; + m_pStgStrm = nullptr; + } + else + m_pStgStrm->SetSize( 0 ); + } + // or write the data stream + else if( !Tmp2Strm() ) + return false; + } + return true; +} + +// Save all dirty streams + +bool StgDirEntry::StoreStreams( StgIo& rIo ) +{ + if( !StoreStream( rIo ) ) + return false; + if( m_pLeft ) + if( !static_cast<StgDirEntry*>(m_pLeft)->StoreStreams( rIo ) ) + return false; + if( m_pRight ) + if( !static_cast<StgDirEntry*>(m_pRight)->StoreStreams( rIo ) ) + return false; + if( m_pDown ) + if( !m_pDown->StoreStreams( rIo ) ) + return false; + return true; +} + +// Revert all directory entries after failure to write the TOC stream + +void StgDirEntry::RevertAll() +{ + m_aEntry = m_aSave; + if( m_pLeft ) + static_cast<StgDirEntry*>(m_pLeft)->RevertAll(); + if( m_pRight ) + static_cast<StgDirEntry*>(m_pRight)->RevertAll(); + if( m_pDown ) + m_pDown->RevertAll(); +} + +// Look if any element of the tree is dirty + +bool StgDirEntry::IsDirty() +{ + if( m_bDirty || m_bInvalid ) + return true; + if( m_pLeft && static_cast<StgDirEntry*>(m_pLeft)->IsDirty() ) + return true; + if( m_pRight && static_cast<StgDirEntry*>(m_pRight)->IsDirty() ) + return true; + if( m_pDown && m_pDown->IsDirty() ) + return true; + return false; +} + +// Set up a stream. + +void StgDirEntry::OpenStream( StgIo& rIo ) +{ + sal_Int32 nThreshold = static_cast<sal_uInt16>(rIo.m_aHdr.GetThreshold()); + delete m_pStgStrm; + if( m_aEntry.GetSize() < nThreshold ) + m_pStgStrm = new StgSmallStrm( rIo, *this ); + else + m_pStgStrm = new StgDataStrm( rIo, *this ); + if( m_bInvalid && m_aEntry.GetSize() ) + { + // This entry has invalid data, so delete that data + SetSize( 0 ); +// bRemoved = bInvalid = false; + } + m_nPos = 0; +} + +// Close the open stream without committing. If the entry is marked as +// temporary, delete it. +// Do not delete pCurStrm here! +// (TLX:??? At least pStgStrm must be deleted.) + +void StgDirEntry::Close() +{ + delete m_pTmpStrm; + m_pTmpStrm = nullptr; +// nRefCnt = 0; + m_bInvalid = m_bTemp; +} + +// Get the current stream size + +sal_Int32 StgDirEntry::GetSize() const +{ + sal_Int32 n; + if( m_pTmpStrm ) + n = m_pTmpStrm->GetSize(); + else if( m_pCurStrm ) + n = m_pCurStrm->GetSize(); + else n = m_aEntry.GetSize(); + return n; +} + +// Set the stream size. This means also creating a temp stream. + +bool StgDirEntry::SetSize( sal_Int32 nNewSize ) +{ + if ( + !( m_nMode & StreamMode::WRITE ) || + (!m_bDirect && !m_pTmpStrm && !Strm2Tmp()) + ) + { + return false; + } + + if( nNewSize < m_nPos ) + m_nPos = nNewSize; + if( m_pTmpStrm ) + { + m_pTmpStrm->SetSize( nNewSize ); + m_pStgStrm->GetIo().SetError( m_pTmpStrm->GetError() ); + return m_pTmpStrm->GetError() == ERRCODE_NONE; + } + else + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return false; + + bool bRes = false; + StgIo& rIo = m_pStgStrm->GetIo(); + sal_Int32 nThreshold = rIo.m_aHdr.GetThreshold(); + // ensure the correct storage stream! + StgStrm* pOld = nullptr; + sal_uInt16 nOldSize = 0; + if( nNewSize >= nThreshold && m_pStgStrm->IsSmallStrm() ) + { + pOld = m_pStgStrm; + nOldSize = static_cast<sal_uInt16>(pOld->GetSize()); + m_pStgStrm = new StgDataStrm( rIo, STG_EOF, 0 ); + } + else if( nNewSize < nThreshold && !m_pStgStrm->IsSmallStrm() ) + { + pOld = m_pStgStrm; + nOldSize = static_cast<sal_uInt16>(nNewSize); + m_pStgStrm = new StgSmallStrm( rIo, STG_EOF ); + } + // now set the new size + if( m_pStgStrm->SetSize( nNewSize ) ) + { + // did we create a new stream? + if( pOld ) + { + // if so, we probably need to copy the old data + if( nOldSize ) + { + std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[ nOldSize ]); + pOld->Pos2Page( 0 ); + m_pStgStrm->Pos2Page( 0 ); + if( pOld->Read( pBuf.get(), nOldSize ) + && m_pStgStrm->Write( pBuf.get(), nOldSize ) ) + bRes = true; + } + else + bRes = true; + if( bRes ) + { + pOld->SetSize( 0 ); + delete pOld; + m_pStgStrm->Pos2Page( m_nPos ); + m_pStgStrm->SetEntry( *this ); + } + else + { + m_pStgStrm->SetSize( 0 ); + delete m_pStgStrm; + m_pStgStrm = pOld; + } + } + else + { + m_pStgStrm->Pos2Page( m_nPos ); + bRes = true; + } + } + return bRes; + } +} + +// Seek. On negative values, seek to EOF. + +sal_Int32 StgDirEntry::Seek( sal_Int32 nNew ) +{ + if( m_pTmpStrm ) + { + if( nNew < 0 ) + nNew = m_pTmpStrm->GetSize(); + nNew = m_pTmpStrm->Seek( nNew ); + } + else if( m_pCurStrm ) + { + if( nNew < 0 ) + nNew = m_pCurStrm->GetSize(); + nNew = m_pCurStrm->Seek( nNew ); + } + else + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return m_nPos; + + sal_Int32 nSize = m_aEntry.GetSize(); + + if( nNew < 0 ) + nNew = nSize; + + // try to enlarge, readonly streams do not allow this + if( nNew > nSize ) + { + if ( !( m_nMode & StreamMode::WRITE ) || !SetSize( nNew ) ) + { + return m_nPos; + } + else + return Seek( nNew ); + } + m_pStgStrm->Pos2Page( nNew ); + nNew = m_pStgStrm->GetPos(); + } + + m_nPos = nNew; + return m_nPos; +} + +// Read + +sal_Int32 StgDirEntry::Read( void* p, sal_Int32 nLen ) +{ + if( nLen <= 0 ) + return 0; + if( m_pTmpStrm ) + nLen = m_pTmpStrm->ReadBytes( p, nLen ); + else if( m_pCurStrm ) + nLen = m_pCurStrm->ReadBytes( p, nLen ); + else + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return 0; + + nLen = m_pStgStrm->Read( p, nLen ); + } + + m_nPos += nLen; + return nLen; +} + +// Write + +sal_Int32 StgDirEntry::Write( const void* p, sal_Int32 nLen ) +{ + if( nLen <= 0 || !( m_nMode & StreamMode::WRITE ) ) + return 0; + + // Was this stream committed internally and reopened in direct mode? + if( m_bDirect && ( m_pCurStrm || m_pTmpStrm ) && !Tmp2Strm() ) + return 0; + // Is this stream opened in transacted mode? Do we have to make a copy? + if( !m_bDirect && !m_pTmpStrm && !Strm2Tmp() ) + return 0; + + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return 0; + + if( m_pTmpStrm ) + { + nLen = m_pTmpStrm->WriteBytes( p, nLen ); + m_pStgStrm->GetIo().SetError( m_pTmpStrm->GetError() ); + } + else + { + sal_Int32 nNew = m_nPos + nLen; + if( nNew > m_pStgStrm->GetSize() ) + { + if( !SetSize( nNew ) ) + return 0; + m_pStgStrm->Pos2Page( m_nPos ); + } + nLen = m_pStgStrm->Write( p, nLen ); + } + m_nPos += nLen; + return nLen; +} + +void StgDirEntry::Copy( BaseStorageStream& rDest ) +{ + sal_Int32 n = GetSize(); + if( !(rDest.SetSize( n ) && n) ) + return; + + sal_uInt64 Pos = rDest.Tell(); + sal_uInt8 aTempBytes[ 4096 ]; + void* p = static_cast<void*>( aTempBytes ); + Seek( 0 ); + rDest.Seek( 0 ); + while( n ) + { + sal_Int32 nn = n; + if( nn > 4096 ) + nn = 4096; + if( Read( p, nn ) != nn ) + break; + if( sal::static_int_cast<sal_Int32>(rDest.Write( p, nn )) != nn ) + break; + n -= nn; + } + rDest.Seek( Pos ); // ?! Seems to be undocumented ! +} + +// Commit this entry + +bool StgDirEntry::Commit() +{ + // OSL_ENSURE( nMode & StreamMode::WRITE, "Trying to commit readonly stream!" ); + + m_aSave = m_aEntry; + bool bRes = true; + if( m_aEntry.GetType() == STG_STREAM ) + { + if( m_pTmpStrm ) + { + delete m_pCurStrm; + m_pCurStrm = m_pTmpStrm; + m_pTmpStrm = nullptr; + } + if( m_bRemoved ) + // Delete the stream if needed + if( m_pStgStrm ) + m_pStgStrm->SetSize( 0 ); + } + else if( m_aEntry.GetType() == STG_STORAGE && m_bDirect && bRes ) + { + StgIterator aIter( *this ); + for( StgDirEntry* p = aIter.First(); p && bRes; p = aIter.Next() ) + bRes = p->Commit(); + } + return bRes; +} + +// Copy the stg stream to the temp stream + +bool StgDirEntry::Strm2Tmp() +{ + if( !m_pTmpStrm ) + { + sal_uInt64 n = 0; + if( m_pCurStrm ) + { + // It was already committed once + m_pTmpStrm = new StgTmpStrm; + if( m_pTmpStrm->GetError() == ERRCODE_NONE && m_pTmpStrm->Copy( *m_pCurStrm ) ) + return true; + n = 1; // indicates error + } + else + { + n = m_aEntry.GetSize(); + m_pTmpStrm = new StgTmpStrm( n ); + if( m_pTmpStrm->GetError() == ERRCODE_NONE ) + { + if( n ) + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return false; + + sal_uInt8 aTempBytes[ 4096 ]; + void* p = static_cast<void*>( aTempBytes ); + m_pStgStrm->Pos2Page( 0 ); + while( n ) + { + sal_uInt64 nn = n; + if( nn > 4096 ) + nn = 4096; + if( static_cast<sal_uInt64>(m_pStgStrm->Read( p, nn )) != nn ) + break; + if (m_pTmpStrm->WriteBytes( p, nn ) != nn) + break; + n -= nn; + } + m_pStgStrm->Pos2Page( m_nPos ); + m_pTmpStrm->Seek( m_nPos ); + } + } + else + n = 1; + } + + if( n ) + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( m_pStgStrm ) + m_pStgStrm->GetIo().SetError( m_pTmpStrm->GetError() ); + + delete m_pTmpStrm; + m_pTmpStrm = nullptr; + return false; + } + } + return true; +} + +// Copy the temp stream to the stg stream during the final commit + +bool StgDirEntry::Tmp2Strm() +{ + // We did commit once, but have not written since then + if( !m_pTmpStrm ) + { + m_pTmpStrm = m_pCurStrm; + m_pCurStrm = nullptr; + } + if( m_pTmpStrm ) + { + OSL_ENSURE( m_pStgStrm, "The pointer may not be NULL!" ); + if ( !m_pStgStrm ) + return false; + sal_uInt64 n = m_pTmpStrm->GetSize(); + std::unique_ptr<StgStrm> pNewStrm; + StgIo& rIo = m_pStgStrm->GetIo(); + sal_uInt64 nThreshold = rIo.m_aHdr.GetThreshold(); + if( n < nThreshold ) + pNewStrm.reset(new StgSmallStrm( rIo, STG_EOF )); + else + pNewStrm.reset(new StgDataStrm( rIo, STG_EOF )); + if( pNewStrm->SetSize( n ) ) + { + sal_uInt8 p[ 4096 ]; + m_pTmpStrm->Seek( 0 ); + while( n ) + { + sal_uInt64 nn = n; + if( nn > 4096 ) + nn = 4096; + if (m_pTmpStrm->ReadBytes( p, nn ) != nn) + break; + if( static_cast<sal_uInt64>(pNewStrm->Write( p, nn )) != nn ) + break; + n -= nn; + } + if( n ) + { + m_pTmpStrm->Seek( m_nPos ); + m_pStgStrm->GetIo().SetError( m_pTmpStrm->GetError() ); + return false; + } + else + { + m_pStgStrm->SetSize( 0 ); + delete m_pStgStrm; + m_pStgStrm = pNewStrm.release(); + m_pStgStrm->SetEntry(*this); + m_pStgStrm->Pos2Page(m_nPos); + delete m_pTmpStrm; + delete m_pCurStrm; + m_pTmpStrm = m_pCurStrm = nullptr; + m_aSave = m_aEntry; + } + } + } + return true; +} + +// Invalidate all open entries by setting the RefCount to 0. If the bDel +// flag is set, also set the invalid flag to indicate deletion during the +// next dir stream flush. + +void StgDirEntry::Invalidate( bool bDel ) +{ +// nRefCnt = 0; + if( bDel ) + m_bRemoved = m_bInvalid = true; + switch( m_aEntry.GetType() ) + { + case STG_STORAGE: + case STG_ROOT: + { + StgIterator aIter( *this ); + for( StgDirEntry* p = aIter.First(); p; p = aIter.Next() ) + p->Invalidate( bDel ); + break; + } + default: + break; + } +} + +///////////////////////////// class StgDirStrm + +// This specialized stream is the maintenance stream for the directory tree. + +StgDirStrm::StgDirStrm( StgIo& r ) + : StgDataStrm( r, r.m_aHdr.GetTOCStart(), -1 ) + , m_pRoot( nullptr ) +{ + if( r.GetError() ) + return; + if( m_nStart == STG_EOF ) + { + StgEntry aRoot; + aRoot.Init(); + static constexpr OUStringLiteral sRootEntry = u"Root Entry"; + aRoot.SetName( sRootEntry ); + aRoot.SetType( STG_ROOT ); + m_pRoot = new StgDirEntry( std::move(aRoot) ); + m_pRoot->SetDirty(); + } + else + { + // temporarily use this instance as owner, so + // the TOC pages can be removed. + m_pEntry = reinterpret_cast<StgDirEntry*>(this); // just for a bit pattern + SetupEntry( 0, m_pRoot ); + m_pEntry = nullptr; + } +} + +StgDirStrm::~StgDirStrm() +{ + delete m_pRoot; +} + +// Recursively parse the directory tree during reading the TOC stream + +void StgDirStrm::SetupEntry( sal_Int32 n, StgDirEntry* pUpper ) +{ + void* p = ( n == STG_FREE ) ? nullptr : GetEntry( n, false ); + if( !p ) + return; + + SvStream *pUnderlyingStream = m_rIo.GetStrm(); + sal_uInt64 nUnderlyingStreamSize = pUnderlyingStream->TellEnd(); + + bool bOk(false); + std::unique_ptr<StgDirEntry> pCur(new StgDirEntry( p, STGENTRY_SIZE, nUnderlyingStreamSize, &bOk )); + + if( !bOk ) + { + m_rIo.SetError( SVSTREAM_GENERALERROR ); + // an error occurred + return; + } + + // better it is + if( !pUpper ) + pCur->m_aEntry.SetType( STG_ROOT ); + + sal_Int32 nLeft = pCur->m_aEntry.GetLeaf( STG_LEFT ); + sal_Int32 nRight = pCur->m_aEntry.GetLeaf( STG_RIGHT ); + // substorage? + sal_Int32 nLeaf = STG_FREE; + if( pCur->m_aEntry.GetType() == STG_STORAGE || pCur->m_aEntry.GetType() == STG_ROOT ) + { + nLeaf = pCur->m_aEntry.GetLeaf( STG_CHILD ); + if (nLeaf != STG_FREE && nLeaf == n) + { + m_rIo.SetError( SVSTREAM_GENERALERROR ); + return; + } + } + + if( !(nLeaf != 0 && nLeft != 0 && nRight != 0) ) + return; + + //fdo#41642 + StgDirEntry *pUp = pUpper; + while (pUp) + { + if (pUp->m_aEntry.GetLeaf(STG_CHILD) == nLeaf) + { + SAL_WARN("sot", "Leaf node of upper StgDirEntry is same as current StgDirEntry's leaf node. Circular entry chain, discarding link"); + return; + } + pUp = pUp->m_pUp; + } + + if( StgAvlNode::Insert + ( reinterpret_cast<StgAvlNode**>( pUpper ? &pUpper->m_pDown : &m_pRoot ), pCur.get() ) ) + { + pCur->m_pUp = pUpper; + } + else + { + // bnc#682484: There are some really broken docs out there + // that contain duplicate entries in 'Directory' section + // so don't set the error flag here and just skip those + // (was: rIo.SetError( SVSTREAM_CANNOT_MAKE );) + return; + } + SetupEntry( nLeft, pUpper ); + SetupEntry( nRight, pUpper ); + SetupEntry( nLeaf, pCur.release() ); +} + +// Extend or shrink the directory stream. + +bool StgDirStrm::SetSize( sal_Int32 nBytes ) +{ + // Always allocate full pages + if ( nBytes < 0 ) + nBytes = 0; + + nBytes = ( ( nBytes + m_nPageSize - 1 ) / m_nPageSize ) * m_nPageSize; + return StgStrm::SetSize( nBytes ); +} + +// Save the TOC stream into a new substream after saving all data streams + +bool StgDirStrm::Store() +{ + if( !m_pRoot || !m_pRoot->IsDirty() ) + return true; + if( !m_pRoot->StoreStreams( m_rIo ) ) + return false; + // After writing all streams, the data FAT stream has changed, + // so we have to commit the root again + m_pRoot->Commit(); + // We want a completely new stream, so fake an empty stream + sal_Int32 nOldStart = m_nStart; // save for later deletion + sal_Int32 nOldSize = m_nSize; + m_nStart = m_nPage = STG_EOF; + m_nSize = 0; + SetPos(0, true); + m_nOffset = 0; + // Delete all temporary entries + m_pRoot->DelTemp( false ); + // set the entry numbers + sal_Int32 n = 0; + m_pRoot->Enum( n ); + if( !SetSize( n * STGENTRY_SIZE ) ) + { + m_nStart = nOldStart; m_nSize = nOldSize; + m_pRoot->RevertAll(); + return false; + } + // set up the cache elements for the new stream + if( !Copy( STG_FREE, m_nSize ) ) + { + m_pRoot->RevertAll(); + return false; + } + // Write the data to the new stream + if( !m_pRoot->Store( *this ) ) + { + m_pRoot->RevertAll(); + return false; + } + // fill any remaining entries with empty data + sal_Int32 ne = m_nSize / STGENTRY_SIZE; + StgEntry aEmpty; + aEmpty.Init(); + while( n < ne ) + { + void* p = GetEntry( n++, true ); + if( !p ) + { + m_pRoot->RevertAll(); + return false; + } + aEmpty.Store( p ); + } + // Now we can release the old stream + m_pFat->FreePages( nOldStart, true ); + m_rIo.m_aHdr.SetTOCStart( m_nStart ); + return true; +} + +// Get a dir entry. + +void* StgDirStrm::GetEntry( sal_Int32 n, bool bDirty ) +{ + return n < 0 || n >= m_nSize / STGENTRY_SIZE + ? nullptr : GetPtr( n * STGENTRY_SIZE, bDirty ); +} + +// Find a dir entry. + +StgDirEntry* StgDirStrm::Find( StgDirEntry& rStg, const OUString& rName ) +{ + if( rStg.m_pDown ) + { + StgEntry aEntry; + aEntry.Init(); + aEntry.SetName( rName ); + // Look in the directory attached to the entry + StgDirEntry aTest( std::move(aEntry) ); + return static_cast<StgDirEntry*>( rStg.m_pDown->Find( &aTest ) ); + } + else + return nullptr; +} + +// Create a new entry. + +StgDirEntry* StgDirStrm::Create( StgDirEntry& rStg, const OUString& rName, StgEntryType eType ) +{ + StgEntry aEntry; + aEntry.Init(); + aEntry.SetType( eType ); + aEntry.SetName( rName ); + StgDirEntry* pRes = Find( rStg, rName ); + if( pRes ) + { + if( !pRes->m_bInvalid ) + { + m_rIo.SetError( SVSTREAM_CANNOT_MAKE ); + return nullptr; + } + pRes->m_bInvalid = + pRes->m_bRemoved = + pRes->m_bTemp = false; + pRes->m_bDirty = true; + return pRes; + } + else + { + std::unique_ptr<StgDirEntry> pNewRes(new StgDirEntry( std::move(aEntry) )); + if( StgAvlNode::Insert( reinterpret_cast<StgAvlNode**>(&rStg.m_pDown), pNewRes.get() ) ) + { + pNewRes->m_pUp = &rStg; + pNewRes->m_bDirty = true; + } + else + { + m_rIo.SetError( SVSTREAM_CANNOT_MAKE ); + pNewRes.reset(); + } + return pNewRes.release(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |