1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <sot/stg.hxx>
22 #include "stgelem.hxx"
23 #include "stgstrms.hxx"
27 #include <osl/diagnose.h>
28 #include <sal/log.hxx>
32 //////////////////////////// class StgDirEntry
34 // This class holds the dir entry data and maintains dirty flags for both
35 // the entry and the data.
37 // Transacted mode for streams: On the first write, a temp stream pTmpStrm
38 // is created and operated on. A commit moves pTmpStrm to pCurStrm, which
39 // is used for subsequent reads. A new write creates a new copy of pTmpStrm
40 // based on pCurStrm. Reverting throws away pTmpStrm.
41 // Transacted mode for storages: A copy of the dir ents is kept in aSave.
42 // Committing means copying aEntry to aSave. Reverting means to copy aSave
43 // to aEntry, delete newly created entries and to reactivate removed entries.
45 // Problem of implementation: No hierarchical commits. Therefore only
46 // overall transaction-oriented or direct.
48 StgDirEntry::StgDirEntry( const void* pBuffer
, sal_uInt32 nBufferLen
, sal_uInt64 nUnderlyingStreamSize
, bool * pbOk
)
50 *pbOk
= m_aEntry
.Load( pBuffer
, nBufferLen
, nUnderlyingStreamSize
);
55 StgDirEntry::StgDirEntry( const StgEntry
& r
) : m_aEntry( r
)
60 // Helper for all ctors
62 void StgDirEntry::InitMembers()
73 m_nMode
= StreamMode::READ
;
82 StgDirEntry::~StgDirEntry()
90 // Comparison function
92 sal_Int32
StgDirEntry::Compare( const StgAvlNode
* p
) const
94 sal_Int32 nResult
= -1;
97 const StgDirEntry
* pEntry
= static_cast<const StgDirEntry
*>(p
);
98 nResult
= m_aEntry
.Compare( pEntry
->m_aEntry
);
103 // Enumerate the entry numbers.
104 // n is incremented to show the total # of entries.
105 // These number are later used as page numbers when storing
106 // the TOC tree into the TOC stream. Remember that aSave is
107 // stored, not aEntry.
109 void StgDirEntry::Enum( sal_Int32
& n
)
111 sal_Int32 nLeft
= STG_FREE
, nRight
= STG_FREE
, nDown
= STG_FREE
;
115 static_cast<StgDirEntry
*>(m_pLeft
)->Enum( n
);
116 nLeft
= static_cast<StgDirEntry
*>(m_pLeft
)->m_nEntry
;
120 static_cast<StgDirEntry
*>(m_pRight
)->Enum( n
);
121 nRight
= static_cast<StgDirEntry
*>(m_pRight
)->m_nEntry
;
125 m_pDown
->Enum( n
); nDown
= m_pDown
->m_nEntry
;
127 m_aSave
.SetLeaf( STG_LEFT
, nLeft
);
128 m_aSave
.SetLeaf( STG_RIGHT
, nRight
);
129 m_aSave
.SetLeaf( STG_CHILD
, nDown
);
132 // Delete all temporary entries before writing the TOC stream.
133 // Until now Deltemp is never called with bForce True
135 void StgDirEntry::DelTemp( bool bForce
)
138 static_cast<StgDirEntry
*>(m_pLeft
)->DelTemp( false );
140 static_cast<StgDirEntry
*>(m_pRight
)->DelTemp( false );
143 // If the storage is dead, of course all elements are dead, too
144 if( m_bInvalid
&& m_aEntry
.GetType() == STG_STORAGE
)
146 m_pDown
->DelTemp( bForce
);
148 if( !( bForce
|| m_bInvalid
) || ( m_aEntry
.GetType() == STG_ROOT
) )
154 // this deletes the element if refcnt == 0!
155 bool bDel
= m_nRefCnt
== 0;
156 StgAvlNode::Remove( reinterpret_cast<StgAvlNode
**>(&m_pUp
->m_pDown
), this, bDel
);
159 m_pLeft
= m_pRight
= m_pDown
= nullptr;
160 m_bInvalid
= m_bZombie
= true;
165 // Save the tree into the given dir stream
167 bool StgDirEntry::Store( StgDirStrm
& rStrm
)
169 void* pEntry
= rStrm
.GetEntry( m_nEntry
, true );
172 // Do not store the current (maybe not committed) entry
173 m_aSave
.Store( pEntry
);
175 if( !static_cast<StgDirEntry
*>(m_pLeft
)->Store( rStrm
) )
178 if( !static_cast<StgDirEntry
*>(m_pRight
)->Store( rStrm
) )
180 if( m_pDown
&& !m_pDown
->Store( rStrm
) )
185 bool StgDirEntry::StoreStream( StgIo
& rIo
)
187 if( m_aEntry
.GetType() == STG_STREAM
|| m_aEntry
.GetType() == STG_ROOT
)
191 // Delete the stream if needed
196 m_pStgStrm
= nullptr;
199 m_pStgStrm
->SetSize( 0 );
201 // or write the data stream
202 else if( !Tmp2Strm() )
208 // Save all dirty streams
210 bool StgDirEntry::StoreStreams( StgIo
& rIo
)
212 if( !StoreStream( rIo
) )
215 if( !static_cast<StgDirEntry
*>(m_pLeft
)->StoreStreams( rIo
) )
218 if( !static_cast<StgDirEntry
*>(m_pRight
)->StoreStreams( rIo
) )
221 if( !m_pDown
->StoreStreams( rIo
) )
226 // Revert all directory entries after failure to write the TOC stream
228 void StgDirEntry::RevertAll()
232 static_cast<StgDirEntry
*>(m_pLeft
)->RevertAll();
234 static_cast<StgDirEntry
*>(m_pRight
)->RevertAll();
236 m_pDown
->RevertAll();
239 // Look if any element of the tree is dirty
241 bool StgDirEntry::IsDirty()
243 if( m_bDirty
|| m_bInvalid
)
245 if( m_pLeft
&& static_cast<StgDirEntry
*>(m_pLeft
)->IsDirty() )
247 if( m_pRight
&& static_cast<StgDirEntry
*>(m_pRight
)->IsDirty() )
249 if( m_pDown
&& m_pDown
->IsDirty() )
256 void StgDirEntry::OpenStream( StgIo
& rIo
)
258 sal_Int32 nThreshold
= static_cast<sal_uInt16
>(rIo
.m_aHdr
.GetThreshold());
260 if( m_aEntry
.GetSize() < nThreshold
)
261 m_pStgStrm
= new StgSmallStrm( rIo
, *this );
263 m_pStgStrm
= new StgDataStrm( rIo
, *this );
264 if( m_bInvalid
&& m_aEntry
.GetSize() )
266 // This entry has invalid data, so delete that data
268 // bRemoved = bInvalid = false;
273 // Close the open stream without committing. If the entry is marked as
274 // temporary, delete it.
275 // Do not delete pCurStrm here!
276 // (TLX:??? At least pStgStrm must be deleted.)
278 void StgDirEntry::Close()
281 m_pTmpStrm
= nullptr;
283 m_bInvalid
= m_bTemp
;
286 // Get the current stream size
288 sal_Int32
StgDirEntry::GetSize() const
292 n
= m_pTmpStrm
->GetSize();
293 else if( m_pCurStrm
)
294 n
= m_pCurStrm
->GetSize();
295 else n
= m_aEntry
.GetSize();
299 // Set the stream size. This means also creating a temp stream.
301 bool StgDirEntry::SetSize( sal_Int32 nNewSize
)
304 !( m_nMode
& StreamMode::WRITE
) ||
305 (!m_bDirect
&& !m_pTmpStrm
&& !Strm2Tmp())
311 if( nNewSize
< m_nPos
)
315 m_pTmpStrm
->SetSize( nNewSize
);
316 m_pStgStrm
->GetIo().SetError( m_pTmpStrm
->GetError() );
317 return m_pTmpStrm
->GetError() == ERRCODE_NONE
;
321 OSL_ENSURE( m_pStgStrm
, "The pointer may not be NULL!" );
326 StgIo
& rIo
= m_pStgStrm
->GetIo();
327 sal_Int32 nThreshold
= rIo
.m_aHdr
.GetThreshold();
328 // ensure the correct storage stream!
329 StgStrm
* pOld
= nullptr;
330 sal_uInt16 nOldSize
= 0;
331 if( nNewSize
>= nThreshold
&& m_pStgStrm
->IsSmallStrm() )
334 nOldSize
= static_cast<sal_uInt16
>(pOld
->GetSize());
335 m_pStgStrm
= new StgDataStrm( rIo
, STG_EOF
, 0 );
337 else if( nNewSize
< nThreshold
&& !m_pStgStrm
->IsSmallStrm() )
340 nOldSize
= static_cast<sal_uInt16
>(nNewSize
);
341 m_pStgStrm
= new StgSmallStrm( rIo
, STG_EOF
);
343 // now set the new size
344 if( m_pStgStrm
->SetSize( nNewSize
) )
346 // did we create a new stream?
349 // if so, we probably need to copy the old data
352 std::unique_ptr
<sal_uInt8
[]> pBuf(new sal_uInt8
[ nOldSize
]);
354 m_pStgStrm
->Pos2Page( 0 );
355 if( pOld
->Read( pBuf
.get(), nOldSize
)
356 && m_pStgStrm
->Write( pBuf
.get(), nOldSize
) )
365 m_pStgStrm
->Pos2Page( m_nPos
);
366 m_pStgStrm
->SetEntry( *this );
370 m_pStgStrm
->SetSize( 0 );
377 m_pStgStrm
->Pos2Page( m_nPos
);
385 // Seek. On negative values, seek to EOF.
387 sal_Int32
StgDirEntry::Seek( sal_Int32 nNew
)
392 nNew
= m_pTmpStrm
->GetSize();
393 nNew
= m_pTmpStrm
->Seek( nNew
);
395 else if( m_pCurStrm
)
398 nNew
= m_pCurStrm
->GetSize();
399 nNew
= m_pCurStrm
->Seek( nNew
);
403 OSL_ENSURE( m_pStgStrm
, "The pointer may not be NULL!" );
407 sal_Int32 nSize
= m_aEntry
.GetSize();
412 // try to enlarge, readonly streams do not allow this
415 if ( !( m_nMode
& StreamMode::WRITE
) || !SetSize( nNew
) )
422 m_pStgStrm
->Pos2Page( nNew
);
423 nNew
= m_pStgStrm
->GetPos();
432 sal_Int32
StgDirEntry::Read( void* p
, sal_Int32 nLen
)
437 nLen
= m_pTmpStrm
->ReadBytes( p
, nLen
);
438 else if( m_pCurStrm
)
439 nLen
= m_pCurStrm
->ReadBytes( p
, nLen
);
442 OSL_ENSURE( m_pStgStrm
, "The pointer may not be NULL!" );
446 nLen
= m_pStgStrm
->Read( p
, nLen
);
455 sal_Int32
StgDirEntry::Write( const void* p
, sal_Int32 nLen
)
457 if( nLen
<= 0 || !( m_nMode
& StreamMode::WRITE
) )
460 // Was this stream committed internally and reopened in direct mode?
461 if( m_bDirect
&& ( m_pCurStrm
|| m_pTmpStrm
) && !Tmp2Strm() )
463 // Is this stream opened in transacted mode? Do we have to make a copy?
464 if( !m_bDirect
&& !m_pTmpStrm
&& !Strm2Tmp() )
467 OSL_ENSURE( m_pStgStrm
, "The pointer may not be NULL!" );
473 nLen
= m_pTmpStrm
->WriteBytes( p
, nLen
);
474 m_pStgStrm
->GetIo().SetError( m_pTmpStrm
->GetError() );
478 sal_Int32 nNew
= m_nPos
+ nLen
;
479 if( nNew
> m_pStgStrm
->GetSize() )
481 if( !SetSize( nNew
) )
483 m_pStgStrm
->Pos2Page( m_nPos
);
485 nLen
= m_pStgStrm
->Write( p
, nLen
);
491 void StgDirEntry::Copy( BaseStorageStream
& rDest
)
493 sal_Int32 n
= GetSize();
494 if( !(rDest
.SetSize( n
) && n
) )
497 sal_uInt64 Pos
= rDest
.Tell();
498 sal_uInt8 aTempBytes
[ 4096 ];
499 void* p
= static_cast<void*>( aTempBytes
);
507 if( Read( p
, nn
) != nn
)
509 if( sal::static_int_cast
<sal_Int32
>(rDest
.Write( p
, nn
)) != nn
)
513 rDest
.Seek( Pos
); // ?! Seems to be undocumented !
518 bool StgDirEntry::Commit()
520 // OSL_ENSURE( nMode & StreamMode::WRITE, "Trying to commit readonly stream!" );
524 if( m_aEntry
.GetType() == STG_STREAM
)
529 m_pCurStrm
= m_pTmpStrm
;
530 m_pTmpStrm
= nullptr;
533 // Delete the stream if needed
535 m_pStgStrm
->SetSize( 0 );
537 else if( m_aEntry
.GetType() == STG_STORAGE
&& m_bDirect
&& bRes
)
539 StgIterator
aIter( *this );
540 for( StgDirEntry
* p
= aIter
.First(); p
&& bRes
; p
= aIter
.Next() )
546 // Copy the stg stream to the temp stream
548 bool StgDirEntry::Strm2Tmp()
555 // It was already committed once
556 m_pTmpStrm
= new StgTmpStrm
;
557 if( m_pTmpStrm
->GetError() == ERRCODE_NONE
&& m_pTmpStrm
->Copy( *m_pCurStrm
) )
559 n
= 1; // indicates error
563 n
= m_aEntry
.GetSize();
564 m_pTmpStrm
= new StgTmpStrm( n
);
565 if( m_pTmpStrm
->GetError() == ERRCODE_NONE
)
569 OSL_ENSURE( m_pStgStrm
, "The pointer may not be NULL!" );
573 sal_uInt8 aTempBytes
[ 4096 ];
574 void* p
= static_cast<void*>( aTempBytes
);
575 m_pStgStrm
->Pos2Page( 0 );
581 if( static_cast<sal_uInt64
>(m_pStgStrm
->Read( p
, nn
)) != nn
)
583 if (m_pTmpStrm
->WriteBytes( p
, nn
) != nn
)
587 m_pStgStrm
->Pos2Page( m_nPos
);
588 m_pTmpStrm
->Seek( m_nPos
);
597 OSL_ENSURE( m_pStgStrm
, "The pointer may not be NULL!" );
599 m_pStgStrm
->GetIo().SetError( m_pTmpStrm
->GetError() );
602 m_pTmpStrm
= nullptr;
609 // Copy the temp stream to the stg stream during the final commit
611 bool StgDirEntry::Tmp2Strm()
613 // We did commit once, but have not written since then
616 m_pTmpStrm
= m_pCurStrm
;
617 m_pCurStrm
= nullptr;
621 OSL_ENSURE( m_pStgStrm
, "The pointer may not be NULL!" );
624 sal_uInt64 n
= m_pTmpStrm
->GetSize();
625 std::unique_ptr
<StgStrm
> pNewStrm
;
626 StgIo
& rIo
= m_pStgStrm
->GetIo();
627 sal_uInt64 nThreshold
= rIo
.m_aHdr
.GetThreshold();
629 pNewStrm
.reset(new StgSmallStrm( rIo
, STG_EOF
));
631 pNewStrm
.reset(new StgDataStrm( rIo
, STG_EOF
));
632 if( pNewStrm
->SetSize( n
) )
635 m_pTmpStrm
->Seek( 0 );
641 if (m_pTmpStrm
->ReadBytes( p
, nn
) != nn
)
643 if( static_cast<sal_uInt64
>(pNewStrm
->Write( p
, nn
)) != nn
)
649 m_pTmpStrm
->Seek( m_nPos
);
650 m_pStgStrm
->GetIo().SetError( m_pTmpStrm
->GetError() );
655 m_pStgStrm
->SetSize( 0 );
657 m_pStgStrm
= pNewStrm
.release();
658 m_pStgStrm
->SetEntry(*this);
659 m_pStgStrm
->Pos2Page(m_nPos
);
662 m_pTmpStrm
= m_pCurStrm
= nullptr;
670 // Invalidate all open entries by setting the RefCount to 0. If the bDel
671 // flag is set, also set the invalid flag to indicate deletion during the
672 // next dir stream flush.
674 void StgDirEntry::Invalidate( bool bDel
)
678 m_bRemoved
= m_bInvalid
= true;
679 switch( m_aEntry
.GetType() )
684 StgIterator
aIter( *this );
685 for( StgDirEntry
* p
= aIter
.First(); p
; p
= aIter
.Next() )
686 p
->Invalidate( bDel
);
694 ///////////////////////////// class StgDirStrm
696 // This specialized stream is the maintenance stream for the directory tree.
698 StgDirStrm::StgDirStrm( StgIo
& r
)
699 : StgDataStrm( r
, r
.m_aHdr
.GetTOCStart(), -1 )
704 if( m_nStart
== STG_EOF
)
708 static constexpr OUStringLiteral sRootEntry
= u
"Root Entry";
709 aRoot
.SetName( sRootEntry
);
710 aRoot
.SetType( STG_ROOT
);
711 m_pRoot
= new StgDirEntry( std::move(aRoot
) );
716 // temporarily use this instance as owner, so
717 // the TOC pages can be removed.
718 m_pEntry
= reinterpret_cast<StgDirEntry
*>(this); // just for a bit pattern
719 SetupEntry( 0, m_pRoot
);
724 StgDirStrm::~StgDirStrm()
729 // Recursively parse the directory tree during reading the TOC stream
731 void StgDirStrm::SetupEntry( sal_Int32 n
, StgDirEntry
* pUpper
)
733 void* p
= ( n
== STG_FREE
) ? nullptr : GetEntry( n
, false );
737 SvStream
*pUnderlyingStream
= m_rIo
.GetStrm();
738 sal_uInt64 nUnderlyingStreamSize
= pUnderlyingStream
->TellEnd();
741 std::unique_ptr
<StgDirEntry
> pCur(new StgDirEntry( p
, STGENTRY_SIZE
, nUnderlyingStreamSize
, &bOk
));
745 m_rIo
.SetError( SVSTREAM_GENERALERROR
);
752 pCur
->m_aEntry
.SetType( STG_ROOT
);
754 sal_Int32 nLeft
= pCur
->m_aEntry
.GetLeaf( STG_LEFT
);
755 sal_Int32 nRight
= pCur
->m_aEntry
.GetLeaf( STG_RIGHT
);
757 sal_Int32 nLeaf
= STG_FREE
;
758 if( pCur
->m_aEntry
.GetType() == STG_STORAGE
|| pCur
->m_aEntry
.GetType() == STG_ROOT
)
760 nLeaf
= pCur
->m_aEntry
.GetLeaf( STG_CHILD
);
761 if (nLeaf
!= STG_FREE
&& nLeaf
== n
)
763 m_rIo
.SetError( SVSTREAM_GENERALERROR
);
768 if( !(nLeaf
!= 0 && nLeft
!= 0 && nRight
!= 0) )
772 StgDirEntry
*pUp
= pUpper
;
775 if (pUp
->m_aEntry
.GetLeaf(STG_CHILD
) == nLeaf
)
777 SAL_WARN("sot", "Leaf node of upper StgDirEntry is same as current StgDirEntry's leaf node. Circular entry chain, discarding link");
783 if( StgAvlNode::Insert
784 ( reinterpret_cast<StgAvlNode
**>( pUpper
? &pUpper
->m_pDown
: &m_pRoot
), pCur
.get() ) )
786 pCur
->m_pUp
= pUpper
;
790 // bnc#682484: There are some really broken docs out there
791 // that contain duplicate entries in 'Directory' section
792 // so don't set the error flag here and just skip those
793 // (was: rIo.SetError( SVSTREAM_CANNOT_MAKE );)
796 SetupEntry( nLeft
, pUpper
);
797 SetupEntry( nRight
, pUpper
);
798 SetupEntry( nLeaf
, pCur
.release() );
801 // Extend or shrink the directory stream.
803 bool StgDirStrm::SetSize( sal_Int32 nBytes
)
805 // Always allocate full pages
809 nBytes
= ( ( nBytes
+ m_nPageSize
- 1 ) / m_nPageSize
) * m_nPageSize
;
810 return StgStrm::SetSize( nBytes
);
813 // Save the TOC stream into a new substream after saving all data streams
815 bool StgDirStrm::Store()
817 if( !m_pRoot
|| !m_pRoot
->IsDirty() )
819 if( !m_pRoot
->StoreStreams( m_rIo
) )
821 // After writing all streams, the data FAT stream has changed,
822 // so we have to commit the root again
824 // We want a completely new stream, so fake an empty stream
825 sal_Int32 nOldStart
= m_nStart
; // save for later deletion
826 sal_Int32 nOldSize
= m_nSize
;
827 m_nStart
= m_nPage
= STG_EOF
;
831 // Delete all temporary entries
832 m_pRoot
->DelTemp( false );
833 // set the entry numbers
836 if( !SetSize( n
* STGENTRY_SIZE
) )
838 m_nStart
= nOldStart
; m_nSize
= nOldSize
;
839 m_pRoot
->RevertAll();
842 // set up the cache elements for the new stream
843 if( !Copy( STG_FREE
, m_nSize
) )
845 m_pRoot
->RevertAll();
848 // Write the data to the new stream
849 if( !m_pRoot
->Store( *this ) )
851 m_pRoot
->RevertAll();
854 // fill any remaining entries with empty data
855 sal_Int32 ne
= m_nSize
/ STGENTRY_SIZE
;
860 void* p
= GetEntry( n
++, true );
863 m_pRoot
->RevertAll();
868 // Now we can release the old stream
869 m_pFat
->FreePages( nOldStart
, true );
870 m_rIo
.m_aHdr
.SetTOCStart( m_nStart
);
876 void* StgDirStrm::GetEntry( sal_Int32 n
, bool bDirty
)
878 return n
< 0 || n
>= m_nSize
/ STGENTRY_SIZE
879 ? nullptr : GetPtr( n
* STGENTRY_SIZE
, bDirty
);
884 StgDirEntry
* StgDirStrm::Find( StgDirEntry
& rStg
, const OUString
& rName
)
890 aEntry
.SetName( rName
);
891 // Look in the directory attached to the entry
892 StgDirEntry
aTest( std::move(aEntry
) );
893 return static_cast<StgDirEntry
*>( rStg
.m_pDown
->Find( &aTest
) );
899 // Create a new entry.
901 StgDirEntry
* StgDirStrm::Create( StgDirEntry
& rStg
, const OUString
& rName
, StgEntryType eType
)
905 aEntry
.SetType( eType
);
906 aEntry
.SetName( rName
);
907 StgDirEntry
* pRes
= Find( rStg
, rName
);
910 if( !pRes
->m_bInvalid
)
912 m_rIo
.SetError( SVSTREAM_CANNOT_MAKE
);
917 pRes
->m_bTemp
= false;
918 pRes
->m_bDirty
= true;
923 std::unique_ptr
<StgDirEntry
> pNewRes(new StgDirEntry( std::move(aEntry
) ));
924 if( StgAvlNode::Insert( reinterpret_cast<StgAvlNode
**>(&rStg
.m_pDown
), pNewRes
.get() ) )
926 pNewRes
->m_pUp
= &rStg
;
927 pNewRes
->m_bDirty
= true;
931 m_rIo
.SetError( SVSTREAM_CANNOT_MAKE
);
934 return pNewRes
.release();
938 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */