blob: 1468c4452e0bdc59d5cb9d960094c77d1b4399d0 [file] [log] [blame]
/**************************************************************
*
* 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"
#if defined(_MSC_VER) && (_MSC_VER<1200)
#include <tools/presys.h>
#endif
#include <hash_map>
#if defined(_MSC_VER) && (_MSC_VER<1200)
#include <tools/postsys.h>
#endif
#include <vos/macros.hxx>
#include <string.h>
#include <osl/endian.h>
#include <tools/string.hxx>
#include "sot/stg.hxx"
#include "stgelem.hxx"
#include "stgcache.hxx"
#include "stgstrms.hxx"
#include "stgdir.hxx"
#include "stgio.hxx"
/*************************************************************************/
//-----------------------------------------------------------------------------
typedef std::hash_map
<
sal_Int32,
StgPage *,
std::hash< sal_Int32 >,
NAMESPACE_STD(equal_to)< sal_Int32 >
> UsrStgPagePtr_Impl;
#ifdef _MSC_VER
#pragma warning( disable: 4786 )
#endif
//#define CHECK_DIRTY 1
//#define READ_AFTER_WRITE 1
////////////////////////////// class StgPage /////////////////////////////
// This class implements buffer functionality. The cache will always return
// a page buffer, even if a read fails. It is up to the caller to determine
// the correctness of the I/O.
StgPage::StgPage( StgCache* p, short n )
{
OSL_ENSURE( n >= 512, "Unexpected page size is provided!" );
pCache = p;
nData = n;
bDirty = sal_False;
nPage = 0;
pData = new sal_uInt8[ nData ];
pNext1 =
pNext2 =
pLast1 =
pLast2 = NULL;
pOwner = NULL;
}
StgPage::~StgPage()
{
delete [] pData;
}
void StgPage::SetPage( short nOff, sal_Int32 nVal )
{
if( ( nOff < (short) ( nData / sizeof( sal_Int32 ) ) ) && nOff >= 0 )
{
#ifdef OSL_BIGENDIAN
nVal = SWAPLONG(nVal);
#endif
((sal_Int32*) pData )[ nOff ] = nVal;
bDirty = sal_True;
}
}
//////////////////////////////// class StgCache ////////////////////////////
// The disk cache holds the cached sectors. The sector type differ according
// to their purpose.
sal_Int32 lcl_GetPageCount( sal_uLong nFileSize, short nPageSize )
{
// return (nFileSize >= 512) ? (nFileSize - 512) / nPageSize : 0;
// #i61980# reallife: last page may be incomplete, return number of *started* pages
return (nFileSize >= 512) ? (nFileSize - 512 + nPageSize - 1) / nPageSize : 0;
}
StgCache::StgCache()
{
nRef = 0;
pStrm = NULL;
pCur = pElem1 = NULL;
nPageSize = 512;
nError = SVSTREAM_OK;
bMyStream = sal_False;
bFile = sal_False;
pLRUCache = NULL;
pStorageStream = NULL;
}
StgCache::~StgCache()
{
Clear();
SetStrm( NULL, sal_False );
delete (UsrStgPagePtr_Impl*)pLRUCache;
}
void StgCache::SetPhysPageSize( short n )
{
OSL_ENSURE( n >= 512, "Unexpecte page size is provided!" );
if ( n >= 512 )
{
nPageSize = n;
sal_uLong nPos = pStrm->Tell();
sal_uLong nFileSize = pStrm->Seek( STREAM_SEEK_TO_END );
nPages = lcl_GetPageCount( nFileSize, nPageSize );
pStrm->Seek( nPos );
}
}
// Create a new cache element
// pCur points to this element
StgPage* StgCache::Create( sal_Int32 nPg )
{
StgPage* pElem = new StgPage( this, nPageSize );
pElem->nPage = nPg;
// For data security, clear the buffer contents
memset( pElem->pData, 0, pElem->nData );
// insert to LRU
if( pCur )
{
pElem->pNext1 = pCur;
pElem->pLast1 = pCur->pLast1;
pElem->pNext1->pLast1 =
pElem->pLast1->pNext1 = pElem;
}
else
pElem->pNext1 = pElem->pLast1 = pElem;
if( !pLRUCache )
pLRUCache = new UsrStgPagePtr_Impl();
(*(UsrStgPagePtr_Impl*)pLRUCache)[pElem->nPage] = pElem;
pCur = pElem;
// insert to Sorted
if( !pElem1 )
pElem1 = pElem->pNext2 = pElem->pLast2 = pElem;
else
{
StgPage* p = pElem1;
do
{
if( pElem->nPage < p->nPage )
break;
p = p->pNext2;
} while( p != pElem1 );
pElem->pNext2 = p;
pElem->pLast2 = p->pLast2;
pElem->pNext2->pLast2 =
pElem->pLast2->pNext2 = pElem;
if( p->nPage < pElem1->nPage )
pElem1 = pElem;
}
return pElem;
}
// Delete the given element
void StgCache::Erase( StgPage* pElem )
{
OSL_ENSURE( pElem, "The pointer should not be NULL!" );
if ( pElem )
{
OSL_ENSURE( pElem->pNext1 && pElem->pLast1, "The pointers may not be NULL!" );
//remove from LRU
pElem->pNext1->pLast1 = pElem->pLast1;
pElem->pLast1->pNext1 = pElem->pNext1;
if( pCur == pElem )
pCur = ( pElem->pNext1 == pElem ) ? NULL : pElem->pNext1;
if( pLRUCache )
((UsrStgPagePtr_Impl*)pLRUCache)->erase( pElem->nPage );
// remove from Sorted
pElem->pNext2->pLast2 = pElem->pLast2;
pElem->pLast2->pNext2 = pElem->pNext2;
if( pElem1 == pElem )
pElem1 = ( pElem->pNext2 == pElem ) ? NULL : pElem->pNext2;
delete pElem;
}
}
// remove all cache elements without flushing them
void StgCache::Clear()
{
StgPage* pElem = pCur;
if( pCur ) do
{
StgPage* pDelete = pElem;
pElem = pElem->pNext1;
delete pDelete;
}
while( pCur != pElem );
pCur = NULL;
pElem1 = NULL;
delete (UsrStgPagePtr_Impl*)pLRUCache;
pLRUCache = NULL;
}
// Look for a cached page
StgPage* StgCache::Find( sal_Int32 nPage )
{
if( !pLRUCache )
return NULL;
UsrStgPagePtr_Impl::iterator aIt = ((UsrStgPagePtr_Impl*)pLRUCache)->find( nPage );
if( aIt != ((UsrStgPagePtr_Impl*)pLRUCache)->end() )
{
// page found
StgPage* pFound = (*aIt).second;
OSL_ENSURE( pFound, "The pointer may not be NULL!" );
if( pFound != pCur )
{
OSL_ENSURE( pFound->pNext1 && pFound->pLast1, "The pointers may not be NULL!" );
// remove from LRU
pFound->pNext1->pLast1 = pFound->pLast1;
pFound->pLast1->pNext1 = pFound->pNext1;
// insert to LRU
pFound->pNext1 = pCur;
pFound->pLast1 = pCur->pLast1;
pFound->pNext1->pLast1 =
pFound->pLast1->pNext1 = pFound;
}
return pFound;
}
return NULL;
}
// Load a page into the cache
StgPage* StgCache::Get( sal_Int32 nPage, sal_Bool bForce )
{
StgPage* p = Find( nPage );
if( !p )
{
p = Create( nPage );
if( !Read( nPage, p->pData, 1 ) && bForce )
{
Erase( p );
p = NULL;
SetError( SVSTREAM_READ_ERROR );
}
}
return p;
}
// Copy an existing page into a new page. Use this routine
// to duplicate an existing stream or to create new entries.
// The new page is initially marked dirty. No owner is copied.
StgPage* StgCache::Copy( sal_Int32 nNew, sal_Int32 nOld )
{
StgPage* p = Find( nNew );
if( !p )
p = Create( nNew );
if( nOld >= 0 )
{
// old page: we must have this data!
StgPage* q = Get( nOld, sal_True );
if( q )
{
OSL_ENSURE( p->nData == q->nData, "Unexpected page size!" );
memcpy( p->pData, q->pData, p->nData );
}
}
p->SetDirty();
return p;
}
// Flush the cache whose owner is given. NULL flushes all.
sal_Bool StgCache::Commit( StgDirEntry* )
{
StgPage* p = pElem1;
if( p ) do
{
if( p->bDirty )
{
sal_Bool b = Write( p->nPage, p->pData, 1 );
if( !b )
return sal_False;
p->bDirty = sal_False;
}
p = p->pNext2;
} while( p != pElem1 );
pStrm->Flush();
SetError( pStrm->GetError() );
#ifdef CHECK_DIRTY
p = pElem1;
if( p ) do
{
if( p->bDirty )
{
ErrorBox( NULL, WB_OK, String("SO2: Dirty Block in Ordered List") ).Execute();
sal_Bool b = Write( p->nPage, p->pData, 1 );
if( !b )
return sal_False;
p->bDirty = sal_False;
}
p = p->pNext2;
} while( p != pElem1 );
p = pElem1;
if( p ) do
{
if( p->bDirty )
{
ErrorBox( NULL, WB_OK, String("SO2: Dirty Block in LRU List") ).Execute();
sal_Bool b = Write( p->nPage, p->pData, 1 );
if( !b )
return sal_False;
p->bDirty = sal_False;
}
p = p->pNext1;
} while( p != pElem1 );
#endif
return sal_True;
}
void StgCache::Revert( StgDirEntry* )
{}
// Set a stream
void StgCache::SetStrm( SvStream* p, sal_Bool bMy )
{
if( pStorageStream )
{
pStorageStream->ReleaseRef();
pStorageStream = NULL;
}
if( bMyStream )
delete pStrm;
pStrm = p;
bMyStream = bMy;
}
void StgCache::SetStrm( UCBStorageStream* pStgStream )
{
if( pStorageStream )
pStorageStream->ReleaseRef();
pStorageStream = pStgStream;
if( bMyStream )
delete pStrm;
pStrm = NULL;
if ( pStorageStream )
{
pStorageStream->AddRef();
pStrm = pStorageStream->GetModifySvStream();
}
bMyStream = sal_False;
}
// Open/close the disk file
sal_Bool StgCache::Open( const String& rName, StreamMode nMode )
{
// do not open in exclusive mode!
if( nMode & STREAM_SHARE_DENYALL )
nMode = ( ( nMode & ~STREAM_SHARE_DENYALL ) | STREAM_SHARE_DENYWRITE );
SvFileStream* pFileStrm = new SvFileStream( rName, nMode );
// SvStream "Feature" Write Open auch erfolgreich, wenns nicht klappt
sal_Bool bAccessDenied = sal_False;
if( ( nMode & STREAM_WRITE ) && !pFileStrm->IsWritable() )
{
pFileStrm->Close();
bAccessDenied = sal_True;
}
SetStrm( pFileStrm, sal_True );
if( pFileStrm->IsOpen() )
{
sal_uLong nFileSize = pStrm->Seek( STREAM_SEEK_TO_END );
nPages = lcl_GetPageCount( nFileSize, nPageSize );
pStrm->Seek( 0L );
}
else
nPages = 0;
bFile = sal_True;
SetError( bAccessDenied ? ERRCODE_IO_ACCESSDENIED : pStrm->GetError() );
return Good();
}
void StgCache::Close()
{
if( bFile )
{
((SvFileStream*) pStrm)->Close();
SetError( pStrm->GetError() );
}
}
// low level I/O
sal_Bool StgCache::Read( sal_Int32 nPage, void* pBuf, sal_Int32 nPg )
{
if( Good() )
{
/* #i73846# real life: a storage may refer to a page one-behind the
last valid page (see document attached to the issue). In that case
(if nPage==nPages), just do nothing here and let the caller work on
the empty zero-filled buffer. */
if ( nPage > nPages )
SetError( SVSTREAM_READ_ERROR );
else if ( nPage < nPages )
{
sal_uLong nPos = Page2Pos( nPage );
sal_Int32 nPg2 = ( ( nPage + nPg ) > nPages ) ? nPages - nPage : nPg;
sal_uLong nBytes = nPg2 * nPageSize;
// fixed address and size for the header
if( nPage == -1 )
{
nPos = 0L, nBytes = 512;
nPg2 = nPg;
}
if( pStrm->Tell() != nPos )
{
if( pStrm->Seek( nPos ) != nPos ) {
#ifdef CHECK_DIRTY
ErrorBox( NULL, WB_OK, String("SO2: Seek failed") ).Execute();
#endif
}
}
pStrm->Read( pBuf, nBytes );
if ( nPg != nPg2 )
SetError( SVSTREAM_READ_ERROR );
else
SetError( pStrm->GetError() );
}
}
return Good();
}
sal_Bool StgCache::Write( sal_Int32 nPage, void* pBuf, sal_Int32 nPg )
{
if( Good() )
{
sal_uLong nPos = Page2Pos( nPage );
sal_uLong nBytes = 0;
if ( SAL_MAX_INT32 / nPg > nPageSize )
nBytes = nPg * nPageSize;
// fixed address and size for the header
// nPageSize must be >= 512, otherwise the header can not be written here, we check it on import
if( nPage == -1 )
nPos = 0L, nBytes = 512;
if( pStrm->Tell() != nPos )
{
if( pStrm->Seek( nPos ) != nPos ) {
#ifdef CHECK_DIRTY
ErrorBox( NULL, WB_OK, String("SO2: Seek failed") ).Execute();
#endif
}
}
sal_uLong nRes = pStrm->Write( pBuf, nBytes );
if( nRes != nBytes )
SetError( SVSTREAM_WRITE_ERROR );
else
SetError( pStrm->GetError() );
#ifdef READ_AFTER_WRITE
sal_uInt8 cBuf[ 512 ];
pStrm->Flush();
pStrm->Seek( nPos );
sal_Bool bRes = ( pStrm->Read( cBuf, 512 ) == 512 );
if( bRes )
bRes = !memcmp( cBuf, pBuf, 512 );
if( !bRes )
{
ErrorBox( NULL, WB_OK, String("SO2: Read after Write failed") ).Execute();
pStrm->SetError( SVSTREAM_WRITE_ERROR );
}
#endif
}
return Good();
}
// set the file size in pages
sal_Bool StgCache::SetSize( sal_Int32 n )
{
// Add the file header
sal_Int32 nSize = n * nPageSize + 512;
pStrm->SetStreamSize( nSize );
SetError( pStrm->GetError() );
if( !nError )
nPages = n;
return Good();
}
void StgCache::SetError( sal_uLong n )
{
if( n && !nError )
nError = n;
}
void StgCache::ResetError()
{
nError = SVSTREAM_OK;
pStrm->ResetError();
}
void StgCache::MoveError( StorageBase& r )
{
if( nError != SVSTREAM_OK )
{
r.SetError( nError );
ResetError();
}
}
// Utility functions
sal_Int32 StgCache::Page2Pos( sal_Int32 nPage )
{
if( nPage < 0 ) nPage = 0;
return( nPage * nPageSize ) + nPageSize;
}
sal_Int32 StgCache::Pos2Page( sal_Int32 nPos )
{
return ( ( nPos + nPageSize - 1 ) / nPageSize ) * nPageSize - 1;
}