/************************************************************** * * 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 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_sot.hxx" #include // memcpy() #include #include #include #include #include "sot/stg.hxx" #include "stgelem.hxx" #include "stgcache.hxx" #include "stgstrms.hxx" #include "stgdir.hxx" #include "stgio.hxx" #define __HUGE ///////////////////////////// class StgFAT /////////////////////////////// // The FAT class performs FAT operations on an underlying storage stream. // This stream is either the master FAT stream (m == sal_True ) or a normal // storage stream, which then holds the FAT for small data allocations. StgFAT::StgFAT( StgStrm& r, sal_Bool m ) : rStrm( r ) { bPhys = m; nPageSize = rStrm.GetIo().GetPhysPageSize(); nEntries = nPageSize >> 2; nOffset = 0; nMaxPage = 0; nLimit = 0; } // Retrieve the physical page for a given byte offset. StgPage* StgFAT::GetPhysPage( sal_Int32 nByteOff ) { StgPage* pPg = NULL; // Position within the underlying stream // use the Pos2Page() method of the stream if( rStrm.Pos2Page( nByteOff ) ) { nOffset = rStrm.GetOffset(); sal_Int32 nPhysPage = rStrm.GetPage(); // get the physical page (must be present) pPg = rStrm.GetIo().Get( nPhysPage, sal_True ); } return pPg; } // Get the follow page for a certain FAT page. sal_Int32 StgFAT::GetNextPage( sal_Int32 nPg ) { if( nPg >= 0 ) { StgPage* pPg = GetPhysPage( nPg << 2 ); nPg = pPg ? pPg->GetPage( 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 = rStrm.GetSize() >> 2; sal_Bool bFound = sal_False; StgPage* pPg = NULL; short nEntry = 0; for( sal_Int32 i = 0; i < nPages; i++, nEntry++ ) { if( !( nEntry % nEntries ) ) { // load the next page for that stream nEntry = 0; pPg = GetPhysPage( i << 2 ); if( !pPg ) return STG_EOF; } sal_Int32 nCur = pPg->GetPage( 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 >= nLimit ) ) ) break; } else if( nTmpLen ) { if( nTmpLen > nPgs && nTmpLen < nMaxLen ) // block > requested size nMaxLen = nTmpLen, nMaxStart = nTmpStart, bFound = sal_True; else if( nTmpLen >= nMinLen ) { // block < requested size nMinLen = nTmpLen, nMinStart = nTmpStart; bFound = sal_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. sal_Bool StgFAT::MakeChain( sal_Int32 nStart, sal_Int32 nPgs ) { sal_Int32 nPos = nStart << 2; StgPage* pPg = GetPhysPage( nPos ); if( !pPg || !nPgs ) return sal_False; while( --nPgs ) { if( nOffset >= nPageSize ) { pPg = GetPhysPage( nPos ); if( !pPg ) return sal_False; } pPg->SetPage( nOffset >> 2, ++nStart ); nOffset += 4; nPos += 4; } if( nOffset >= nPageSize ) { pPg = GetPhysPage( nPos ); if( !pPg ) return sal_False; } pPg->SetPage( nOffset >> 2, STG_EOF ); return sal_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 = 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 ) > nMaxPage ) nMaxPage = nBegin + nAlloc; if( !MakeChain( nBegin, nAlloc ) ) return STG_EOF; if( nOrig == STG_EOF ) nOrig = nBegin; else { // Patch the chain StgPage* pPg = GetPhysPage( nLast << 2 ); if( !pPg ) return STG_EOF; pPg->SetPage( nOffset >> 2, nBegin ); } nLast = nBegin + nAlloc - 1; nPgs -= nAlloc; } if( nPgs && !nPasses ) { // we need new, fresh space, so allocate and retry if( !rStrm.SetSize( ( nPages + nPgs ) << 2 ) ) return STG_EOF; if( !bPhys && !InitNew( nPages ) ) return sal_False; nPages = rStrm.GetSize() >> 2; nPasses++; } else break; } // now we should have a chain for the complete block if( nBegin == STG_EOF || nPgs ) { 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 sal_Bool StgFAT::InitNew( sal_Int32 nPage1 ) { sal_Int32 n = ( ( rStrm.GetSize() >> 2 ) - nPage1 ) / nEntries; if ( n > 0 ) { while( n-- ) { StgPage* pPg = NULL; // Position within the underlying stream // use the Pos2Page() method of the stream rStrm.Pos2Page( nPage1 << 2 ); // Initialize the page pPg = rStrm.GetIo().Copy( rStrm.GetPage(), STG_FREE ); if ( !pPg ) return sal_False; for( short i = 0; i < nEntries; i++ ) pPg->SetPage( i, STG_FREE ); nPage1++; } } return sal_True; } // Release a chain sal_Bool StgFAT::FreePages( sal_Int32 nStart, sal_Bool bAll ) { while( nStart >= 0 ) { StgPage* pPg = GetPhysPage( nStart << 2 ); if( !pPg ) return sal_False; nStart = pPg->GetPage( nOffset >> 2 ); // The first released page is either set to EOF or FREE pPg->SetPage( nOffset >> 2, bAll ? STG_FREE : STG_EOF ); bAll = sal_True; } return sal_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 ) : rIo( r ) { pFat = NULL; nStart = nPage = STG_EOF; nOffset = 0; pEntry = NULL; nPos = nSize = 0; nPageSize = rIo.GetPhysPageSize(); } StgStrm::~StgStrm() { delete pFat; } // Attach the stream to the given entry. void StgStrm::SetEntry( StgDirEntry& r ) { r.aEntry.SetLeaf( STG_DATA, nStart ); r.aEntry.SetSize( nSize ); pEntry = &r; r.SetDirty(); } // Compute page number and offset for the given byte position. // If the position is behind the size, set the stream right // behind the EOF. sal_Bool StgStrm::Pos2Page( sal_Int32 nBytePos ) { if ( !pFat ) return sal_False; sal_Int32 nRel, nBgn; // Values < 0 seek to the end if( nBytePos < 0 || nBytePos >= nSize ) nBytePos = nSize; // Adjust the position back to offset 0 nPos -= nOffset; sal_Int32 nMask = ~( nPageSize - 1 ); sal_Int32 nOld = nPos & nMask; sal_Int32 nNew = nBytePos & nMask; nOffset = (short) ( nBytePos & ~nMask ); nPos = nBytePos; if( nOld == nNew ) return sal_True; if( nNew > nOld ) { // the new position is behind the current, so an incremental // positioning is OK. Set the page relative position nRel = nNew - nOld; nBgn = nPage; } else { // the new position is before the current, so we have to scan // the entire chain. nRel = nNew; nBgn = nStart; } // now, traverse the FAT chain. nRel /= nPageSize; sal_Int32 nLast = STG_EOF; while( nRel && nBgn >= 0 ) { nLast = nBgn; nBgn = pFat->GetNextPage( nBgn ); nRel--; } // special case: seek to 1st byte of new, unallocated page // (in case the file size is a multiple of the page size) if( nBytePos == nSize && nBgn == STG_EOF && !nRel && !nOffset ) nBgn = nLast, nOffset = nPageSize; if( nBgn < 0 && nBgn != STG_EOF ) { rIo.SetError( SVSTREAM_FILEFORMAT_ERROR ); nBgn = STG_EOF; nOffset = nPageSize; } nPage = nBgn; return sal_Bool( nRel == 0 && nPage >= 0 ); } // Retrieve the physical page for a given byte offset. StgPage* StgStrm::GetPhysPage( sal_Int32 nBytePos, sal_Bool bForce ) { if( !Pos2Page( nBytePos ) ) return NULL; return rIo.Get( nPage, bForce ); } // Copy an entire stream. Both streams are allocated in the FAT. // The target stream is this stream. sal_Bool StgStrm::Copy( sal_Int32 nFrom, sal_Int32 nBytes ) { if ( !pFat ) return sal_False; sal_Int32 nTo = nStart; sal_Int32 nPgs = ( nBytes + nPageSize - 1 ) / nPageSize; while( nPgs-- ) { if( nTo < 0 ) { rIo.SetError( SVSTREAM_FILEFORMAT_ERROR ); return sal_False; } rIo.Copy( nTo, nFrom ); if( nFrom >= 0 ) { nFrom = pFat->GetNextPage( nFrom ); if( nFrom < 0 ) { rIo.SetError( SVSTREAM_FILEFORMAT_ERROR ); return sal_False; } } nTo = pFat->GetNextPage( nTo ); } return sal_True; } sal_Bool StgStrm::SetSize( sal_Int32 nBytes ) { if ( nBytes < 0 || !pFat ) return sal_False; // round up to page size sal_Int32 nOld = ( ( nSize + nPageSize - 1 ) / nPageSize ) * nPageSize; sal_Int32 nNew = ( ( nBytes + nPageSize - 1 ) / nPageSize ) * nPageSize; if( nNew > nOld ) { if( !Pos2Page( nSize ) ) return sal_False; sal_Int32 nBgn = pFat->AllocPages( nPage, ( nNew - nOld ) / nPageSize ); if( nBgn == STG_EOF ) return sal_False; if( nStart == STG_EOF ) nStart = nPage = nBgn; } else if( nNew < nOld ) { sal_Bool bAll = sal_Bool( nBytes == 0 ); if( !Pos2Page( nBytes ) || !pFat->FreePages( nPage, bAll ) ) return sal_False; if( bAll ) nStart = nPage = STG_EOF; } if( pEntry ) { // change the dir entry? if( !nSize || !nBytes ) pEntry->aEntry.SetLeaf( STG_DATA, nStart ); pEntry->aEntry.SetSize( nBytes ); pEntry->SetDirty(); } nSize = nBytes; pFat->SetLimit( GetPages() ); return sal_True; } // Return the # of allocated pages sal_Int32 StgStrm::GetPages() { return ( nSize + nPageSize - 1 ) / nPageSize; } //////////////////////////// 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 ) : StgStrm( r ) { pFat = new StgFAT( *this, sal_True ); nSize = rIo.aHdr.GetFATSize() * nPageSize; } sal_Bool StgFATStrm::Pos2Page( sal_Int32 nBytePos ) { // Values < 0 seek to the end if( nBytePos < 0 || nBytePos >= nSize ) nBytePos = nSize ? nSize - 1 : 0; nPage = nBytePos / nPageSize; nOffset = (short) ( nBytePos % nPageSize ); nPos = nBytePos; nPage = GetPage( (short) nPage, sal_False ); return sal_Bool( nPage >= 0 ); } // Retrieve the physical page for a given byte offset. // Since Pos2Page() already has computed the physical offset, // use the byte offset directly. StgPage* StgFATStrm::GetPhysPage( sal_Int32 nBytePos, sal_Bool bForce ) { OSL_ENSURE( nBytePos >= 0, "The value may not be negative!" ); return rIo.Get( nBytePos / ( nPageSize >> 2 ), bForce ); } // Get the page number entry for the given page offset. sal_Int32 StgFATStrm::GetPage( short nOff, sal_Bool bMake, sal_uInt16 *pnMasterAlloc ) { OSL_ENSURE( nOff >= 0, "The offset may not be negative!" ); if( pnMasterAlloc ) *pnMasterAlloc = 0; if( nOff < rIo.aHdr.GetFAT1Size() ) return rIo.aHdr.GetFATPage( nOff ); sal_Int32 nMaxPage = nSize >> 2; nOff = nOff - rIo.aHdr.GetFAT1Size(); // Anzahl der Masterpages, durch die wir iterieren muessen sal_uInt16 nMasterCount = ( nPageSize >> 2 ) - 1; sal_uInt16 nBlocks = nOff / nMasterCount; // Offset in letzter Masterpage nOff = nOff % nMasterCount; StgPage* pOldPage = 0; StgPage* pMaster = 0; sal_Int32 nFAT = rIo.aHdr.GetFATChain(); for( sal_uInt16 nCount = 0; nCount <= nBlocks; nCount++ ) { if( nFAT == STG_EOF || nFAT == STG_FREE ) { if( bMake ) { // create a new master page nFAT = nMaxPage++; pMaster = rIo.Copy( nFAT, STG_FREE ); if ( pMaster ) { for( short k = 0; k < ( nPageSize >> 2 ); k++ ) pMaster->SetPage( k, STG_FREE ); // Verkettung herstellen if( !pOldPage ) rIo.aHdr.SetFATChain( nFAT ); else pOldPage->SetPage( nMasterCount, nFAT ); if( nMaxPage >= rIo.GetPhysPages() ) if( !rIo.SetSize( nMaxPage ) ) return STG_EOF; // mark the page as used // Platz fuer Masterpage schaffen if( !pnMasterAlloc ) // Selbst Platz schaffen { if( !Pos2Page( nFAT << 2 ) ) return STG_EOF; StgPage* pPg = rIo.Get( nPage, sal_True ); if( !pPg ) return STG_EOF; pPg->SetPage( nOffset >> 2, STG_MASTER ); } else (*pnMasterAlloc)++; rIo.aHdr.SetMasters( nCount + 1 ); pOldPage = pMaster; } } } else { pMaster = rIo.Get( nFAT, sal_True ); if ( pMaster ) { nFAT = pMaster->GetPage( nMasterCount ); pOldPage = pMaster; } } } if( pMaster ) return pMaster->GetPage( nOff ); rIo.SetError( SVSTREAM_GENERALERROR ); return STG_EOF; } // Set the page number entry for the given page offset. sal_Bool StgFATStrm::SetPage( short nOff, sal_Int32 nNewPage ) { OSL_ENSURE( nOff >= 0, "The offset may not be negative!" ); sal_Bool bRes = sal_True; if( nOff < rIo.aHdr.GetFAT1Size() ) rIo.aHdr.SetFATPage( nOff, nNewPage ); else { nOff = nOff - rIo.aHdr.GetFAT1Size(); // Anzahl der Masterpages, durch die wir iterieren muessen sal_uInt16 nMasterCount = ( nPageSize >> 2 ) - 1; sal_uInt16 nBlocks = nOff / nMasterCount; // Offset in letzter Masterpage nOff = nOff % nMasterCount; StgPage* pMaster = 0; sal_Int32 nFAT = rIo.aHdr.GetFATChain(); for( sal_uInt16 nCount = 0; nCount <= nBlocks; nCount++ ) { if( nFAT == STG_EOF || nFAT == STG_FREE ) { pMaster = 0; break; } pMaster = rIo.Get( nFAT, sal_True ); if ( pMaster ) nFAT = pMaster->GetPage( nMasterCount ); } if( pMaster ) pMaster->SetPage( nOff, nNewPage ); else { rIo.SetError( SVSTREAM_GENERALERROR ); bRes = sal_False; } } // lock the page against access if( bRes ) { Pos2Page( nNewPage << 2 ); StgPage* pPg = rIo.Get( nPage, sal_True ); if( pPg ) pPg->SetPage( nOffset >> 2, STG_FAT ); else bRes = sal_False; } return bRes; } sal_Bool StgFATStrm::SetSize( sal_Int32 nBytes ) { if ( nBytes < 0 ) return sal_False; // Set the number of entries to a multiple of the page size short nOld = (short) ( ( nSize + ( nPageSize - 1 ) ) / nPageSize ); short nNew = (short) ( ( nBytes + ( nPageSize - 1 ) ) / 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, sal_True, &nMasterAlloc ); if( nPg == STG_EOF ) return sal_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( pFat, "The pointer is always initializer here!" ); sal_Int32 nNewPage = 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 = nSize >> 2; // if a MegaMasterPage was created avoid taking // the same Page nNewPage += nMasterAlloc; // adjust the file size if necessary if( nNewPage >= rIo.GetPhysPages() ) if( !rIo.SetSize( nNewPage + 1 ) ) return sal_False; } // Set up the page with empty entries StgPage* pPg = rIo.Copy( nNewPage, STG_FREE ); if ( !pPg ) return sal_False; for( short j = 0; j < ( nPageSize >> 2 ); j++ ) pPg->SetPage( j, STG_FREE ); // store the page number into the master FAT // Set the size before so the correct FAT can be found nSize = ( nOld + 1 ) * nPageSize; SetPage( nOld, nNewPage ); // MegaMasterPages were created, mark it them as used sal_uInt32 nMax = rIo.aHdr.GetMasters( ); sal_uInt32 nFAT = rIo.aHdr.GetFATChain(); if( nMasterAlloc ) for( sal_uInt16 nCount = 0; nCount < nMax; nCount++ ) { if( !Pos2Page( nFAT << 2 ) ) return sal_False; if( nMax - nCount <= nMasterAlloc ) { StgPage* piPg = rIo.Get( nPage, sal_True ); if( !piPg ) return sal_False; piPg->SetPage( nOffset >> 2, STG_MASTER ); } StgPage* pPage = rIo.Get( nFAT, sal_True ); if( !pPage ) return sal_False; nFAT = pPage->GetPage( (nPageSize >> 2 ) - 1 ); } nOld++; // We have used up 4 bytes for the STG_FAT entry nBytes += 4; nNew = (short) ( ( nBytes + ( nPageSize - 1 ) ) / nPageSize ); } } nSize = nNew * nPageSize; rIo.aHdr.SetFATSize( nNew ); return sal_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 ) { pEntry = &p; Init( p.aEntry.GetLeaf( STG_DATA ), p.aEntry.GetSize() ); } void StgDataStrm::Init( sal_Int32 nBgn, sal_Int32 nLen ) { if ( rIo.pFAT ) pFat = new StgFAT( *rIo.pFAT, sal_True ); OSL_ENSURE( pFat, "The pointer should not be empty!" ); nStart = nPage = nBgn; nSize = nLen; nIncr = 1; nOffset = 0; if( nLen < 0 && pFat ) { // determine the actual size of the stream by scanning // the FAT chain and counting the # of pages allocated nSize = 0; // there may be files with double page numbers or loops of page // references. This is not allowed. To be able to track this and // to exit with an error, track already scanned PageNumbers here // and use them to see if an already counted page is re-visited std::set< sal_Int32 > nUsedPageNumbers; while(nBgn >= 0) { if(nUsedPageNumbers.find(nBgn) == nUsedPageNumbers.end()) { nUsedPageNumbers.insert(nBgn); nSize += nPageSize; nBgn = pFat->GetNextPage(nBgn); } else { rIo.SetError(ERRCODE_IO_WRONGFORMAT); nBgn = -1; } } } } // Set the size of a physical stream. sal_Bool StgDataStrm::SetSize( sal_Int32 nBytes ) { if ( !pFat ) return sal_False; nBytes = ( ( nBytes + nIncr - 1 ) / nIncr ) * nIncr; sal_Int32 nOldSz = nSize; if( ( nOldSz != nBytes ) ) { if( !StgStrm::SetSize( nBytes ) ) return sal_False; sal_Int32 nMaxPage = pFat->GetMaxPage(); if( nMaxPage > rIo.GetPhysPages() ) if( !rIo.SetSize( nMaxPage ) ) return sal_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( ( nSize - 1 ) / nPageSize - ( nOldSz - 1 ) / nPageSize == 1 ) { Pos2Page( nBytes ); if( nPage >= 0 ) rIo.Copy( nPage, STG_FREE ); } } return sal_True; } // Get the address of the data byte at a specified offset. // If bForce = sal_True, a read of non-existent data causes // a read fault. void* StgDataStrm::GetPtr( sal_Int32 Pos, sal_Bool bForce, sal_Bool bDirty ) { if( Pos2Page( Pos ) ) { StgPage* pPg = rIo.Get( nPage, bForce ); if( pPg ) { pPg->SetOwner( pEntry ); if( bDirty ) pPg->SetDirty(); return ((sal_uInt8 *)pPg->GetData()) + nOffset; } } return NULL; } // 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; if( ( nPos + n ) > nSize ) n = nSize - nPos; sal_Int32 nDone = 0; while( n ) { short nBytes = nPageSize - nOffset; short nRes; StgPage* pPg; if( (sal_Int32) nBytes > n ) nBytes = (short) n; if( nBytes ) { void *p = (sal_uInt8 *) pBuf + nDone; if( nBytes == nPageSize ) { pPg = rIo.Find( nPage ); if( pPg ) { // data is present, so use the cached data pPg->SetOwner( pEntry ); memcpy( p, pPg->GetData(), nBytes ); nRes = nBytes; } else // do a direct (unbuffered) read nRes = (short) rIo.Read( nPage, p, 1 ) * nPageSize; } else { // partial block read thru the cache. pPg = rIo.Get( nPage, sal_False ); if( !pPg ) break; pPg->SetOwner( pEntry ); memcpy( p, (sal_uInt8*)pPg->GetData() + nOffset, nBytes ); nRes = nBytes; } nDone += nRes; nPos += nRes; n -= nRes; nOffset = nOffset + nRes; if( nRes != nBytes ) break; // read error or EOF } // Switch to next page if necessary if( nOffset >= nPageSize && !Pos2Page( nPos ) ) break; } return nDone; } sal_Int32 StgDataStrm::Write( const void* pBuf, sal_Int32 n ) { if ( n < 0 ) return 0; sal_Int32 nDone = 0; if( ( nPos + n ) > nSize ) { sal_Int32 nOld = nPos; if( !SetSize( nPos + n ) ) return 0; Pos2Page( nOld ); } while( n ) { short nBytes = nPageSize - nOffset; short nRes; StgPage* pPg; if( (sal_Int32) nBytes > n ) nBytes = (short) n; if( nBytes ) { const void *p = (const sal_uInt8 *) pBuf + nDone; if( nBytes == nPageSize ) { pPg = rIo.Find( nPage ); if( pPg ) { // data is present, so use the cached data pPg->SetOwner( pEntry ); memcpy( pPg->GetData(), p, nBytes ); pPg->SetDirty(); nRes = nBytes; } else // do a direct (unbuffered) write nRes = (short) rIo.Write( nPage, (void*) p, 1 ) * nPageSize; } else { // partial block read thru the cache. pPg = rIo.Get( nPage, sal_False ); if( !pPg ) break; pPg->SetOwner( pEntry ); memcpy( (sal_uInt8*)pPg->GetData() + nOffset, p, nBytes ); pPg->SetDirty(); nRes = nBytes; } nDone += nRes; nPos += nRes; n -= nRes; nOffset = nOffset + nRes; if( nRes != nBytes ) break; // read error } // Switch to next page if necessary if( nOffset >= nPageSize && !Pos2Page( nPos ) ) 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, sal_Int32 nLen ) : StgStrm( r ) { Init( nBgn, nLen ); } StgSmallStrm::StgSmallStrm( StgIo& r, StgDirEntry& p ) : StgStrm( r ) { pEntry = &p; Init( p.aEntry.GetLeaf( STG_DATA ), p.aEntry.GetSize() ); } void StgSmallStrm::Init( sal_Int32 nBgn, sal_Int32 nLen ) { if ( rIo.pDataFAT ) pFat = new StgFAT( *rIo.pDataFAT, sal_False ); pData = rIo.pDataStrm; OSL_ENSURE( pFat && pData, "The pointers should not be empty!" ); nPageSize = rIo.GetDataPageSize(); nStart = nPage = nBgn; 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. if( ( nPos + n ) > nSize ) n = nSize - nPos; short nDone = 0; while( n ) { short nBytes = nPageSize - nOffset; if( (sal_Int32) nBytes > n ) nBytes = (short) n; if( nBytes ) { if( !pData || !pData->Pos2Page( nPage * nPageSize + nOffset ) ) break; // all reading thru the stream short nRes = (short) pData->Read( (sal_uInt8*)pBuf + nDone, nBytes ); nDone = nDone + nRes; nPos += nRes; n -= nRes; nOffset = nOffset + nRes; // read problem? if( nRes != nBytes ) break; } // Switch to next page if necessary if( nOffset >= nPageSize && !Pos2Page( nPos ) ) 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. short nDone = 0; if( ( nPos + n ) > nSize ) { sal_Int32 nOld = nPos; if( !SetSize( nPos + n ) ) return sal_False; Pos2Page( nOld ); } while( n ) { short nBytes = nPageSize - nOffset; if( (sal_Int32) nBytes > n ) nBytes = (short) n; if( nBytes ) { // all writing goes thru the stream sal_Int32 nDataPos = nPage * nPageSize + nOffset; if ( !pData || ( pData->GetSize() < ( nDataPos + nBytes ) && !pData->SetSize( nDataPos + nBytes ) ) ) break; if( !pData->Pos2Page( nDataPos ) ) break; short nRes = (short) pData->Write( (sal_uInt8*)pBuf + nDone, nBytes ); nDone = nDone + nRes; nPos += nRes; n -= nRes; nOffset = nOffset + nRes; // write problem? if( nRes != nBytes ) break; } // Switch to next page if necessary if( nOffset >= nPageSize && !Pos2Page( nPos ) ) break; } return nDone; } /////////////////////////// class StgTmpStrm ///////////////////////////// // The temporary stream uses a memory stream if < 32K, otherwise a // temporary file. #define THRESHOLD 32768L StgTmpStrm::StgTmpStrm( sal_uLong nInitSize ) : SvMemoryStream( nInitSize > THRESHOLD ? 16 : ( nInitSize ? nInitSize : 16 ), 4096 ) { pStrm = NULL; // this calls FlushData, so all members should be set by this time SetBufferSize( 0 ); if( nInitSize > THRESHOLD ) SetSize( nInitSize ); } sal_Bool StgTmpStrm::Copy( StgTmpStrm& rSrc ) { sal_uLong n = rSrc.GetSize(); sal_uLong nCur = rSrc.Tell(); SetSize( n ); if( GetError() == SVSTREAM_OK ) { sal_uInt8* p = new sal_uInt8[ 4096 ]; rSrc.Seek( 0L ); Seek( 0L ); while( n ) { sal_uLong nn = n; if( nn > 4096 ) nn = 4096; if( rSrc.Read( p, nn ) != nn ) break; if( Write( p, nn ) != nn ) break; n -= nn; } delete [] p; rSrc.Seek( nCur ); Seek( nCur ); return sal_Bool( n == 0 ); } else return sal_False; } StgTmpStrm::~StgTmpStrm() { if( pStrm ) { pStrm->Close(); osl::File::remove( aName ); delete pStrm; } } sal_uLong StgTmpStrm::GetSize() const { sal_uLong n; if( pStrm ) { sal_uLong old = pStrm->Tell(); n = pStrm->Seek( STREAM_SEEK_TO_END ); pStrm->Seek( old ); } else n = nEndOfData; return n; } void StgTmpStrm::SetSize( sal_uLong n ) { if( pStrm ) pStrm->SetStreamSize( n ); else { if( n > THRESHOLD ) { aName = TempFile::CreateTempName(); SvFileStream* s = new SvFileStream( aName, STREAM_READWRITE ); sal_uLong nCur = Tell(); sal_uLong i = nEndOfData; if( i ) { sal_uInt8* p = new sal_uInt8[ 4096 ]; Seek( 0L ); while( i ) { sal_uLong nb = ( i > 4096 ) ? 4096 : i; if( Read( p, nb ) == nb && s->Write( p, nb ) == nb ) i -= nb; else break; } delete [] p; } 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( n - 1 ); s->Write( &i, 1 ); s->Flush(); if( s->GetError() != SVSTREAM_OK ) i = 1; } Seek( nCur ); s->Seek( nCur ); if( i ) { SetError( s->GetError() ); delete s; return; } pStrm = s; // Shrink the memory to 16 bytes, which seems to be the minimum ReAllocateMemory( - ( (long) nEndOfData - 16 ) ); } else { if( n > nEndOfData ) { sal_uLong nCur = Tell(); Seek( nEndOfData - 1 ); *this << (sal_uInt8) 0; Seek( nCur ); } else nEndOfData = n; } } } sal_uLong StgTmpStrm::GetData( void* pData, sal_uLong n ) { if( pStrm ) { n = pStrm->Read( pData, n ); SetError( pStrm->GetError() ); return n; } else return SvMemoryStream::GetData( (sal_Char *)pData, n ); } sal_uLong StgTmpStrm::PutData( const void* pData, sal_uLong n ) { sal_uInt32 nCur = Tell(); sal_uInt32 nNew = nCur + n; if( nNew > THRESHOLD && !pStrm ) { SetSize( nNew ); if( GetError() != SVSTREAM_OK ) return 0; } if( pStrm ) { nNew = pStrm->Write( pData, n ); SetError( pStrm->GetError() ); } else nNew = SvMemoryStream::PutData( (sal_Char*)pData, n ); return nNew; } sal_uLong StgTmpStrm::SeekPos( sal_uLong n ) { if( n == STREAM_SEEK_TO_END ) n = GetSize(); if( n && n > THRESHOLD && !pStrm ) { SetSize( n ); if( GetError() != SVSTREAM_OK ) return Tell(); else return n; } else if( pStrm ) { n = pStrm->Seek( n ); SetError( pStrm->GetError() ); return n; } else return SvMemoryStream::SeekPos( n ); } void StgTmpStrm::FlushData() { if( pStrm ) { pStrm->Flush(); SetError( pStrm->GetError() ); } else SvMemoryStream::FlushData(); }