summaryrefslogtreecommitdiffstats
path: root/sot/source/sdstor/stgcache.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sot/source/sdstor/stgcache.cxx')
-rw-r--r--sot/source/sdstor/stgcache.cxx430
1 files changed, 430 insertions, 0 deletions
diff --git a/sot/source/sdstor/stgcache.cxx b/sot/source/sdstor/stgcache.cxx
new file mode 100644
index 0000000000..93f7d3090c
--- /dev/null
+++ b/sot/source/sdstor/stgcache.cxx
@@ -0,0 +1,430 @@
+/* -*- 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 <string.h>
+#include <o3tl/safeint.hxx>
+#include <osl/endian.h>
+#include <osl/diagnose.h>
+
+#include <sot/stg.hxx>
+#include "stgcache.hxx"
+
+#include <algorithm>
+
+////////////////////////////// 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( short nSize, sal_Int32 nPage )
+ : mnPage( nPage )
+ , mpData( new sal_uInt8[ nSize ] )
+ , mnSize( nSize )
+{
+ OSL_ENSURE( mnSize >= 512, "Unexpected page size is provided!" );
+ // We will write this data to a permanent file later
+ // best to clear if first.
+ memset( mpData.get(), 0, mnSize );
+}
+
+StgPage::~StgPage()
+{
+}
+
+rtl::Reference< StgPage > StgPage::Create( short nData, sal_Int32 nPage )
+{
+ return rtl::Reference< StgPage >( new StgPage( nData, nPage ) );
+}
+
+void StgCache::SetToPage ( const rtl::Reference< StgPage >& rPage, short nOff, sal_Int32 nVal )
+{
+ if( nOff >= 0 && ( o3tl::make_unsigned(nOff) < rPage->GetSize() / sizeof( sal_Int32 ) ) )
+ {
+#ifdef OSL_BIGENDIAN
+ nVal = OSL_SWAPDWORD(nVal);
+#endif
+ static_cast<sal_Int32*>(rPage->GetData())[ nOff ] = nVal;
+ SetDirty( rPage );
+ }
+}
+
+bool StgPage::IsPageGreater( const StgPage *pA, const StgPage *pB )
+{
+ return pA->mnPage < pB->mnPage;
+}
+
+//////////////////////////////// class StgCache
+
+// The disk cache holds the cached sectors. The sector type differ according
+// to their purpose.
+
+static sal_Int32 lcl_GetPageCount( sal_uInt64 nFileSize, short nPageSize )
+{
+// return (nFileSize >= 512) ? (nFileSize - 512) / nPageSize : 0;
+ // #i61980# real life: last page may be incomplete, return number of *started* pages
+ return (nFileSize >= 512) ? (nFileSize - 512 + nPageSize - 1) / nPageSize : 0;
+}
+
+StgCache::StgCache()
+ : m_nError( ERRCODE_NONE )
+ , m_nPages( 0 )
+ , m_nRef( 0 )
+ , m_nReplaceIdx( 0 )
+ , maLRUPages( 8 ) // entries in the LRU lookup
+ , m_nPageSize( 512 )
+ , m_pStorageStream( nullptr )
+ , m_pStrm( nullptr )
+ , m_bMyStream( false )
+ , m_bFile( false )
+{
+}
+
+StgCache::~StgCache()
+{
+ Clear();
+ SetStrm( nullptr, false );
+}
+
+void StgCache::SetPhysPageSize( short n )
+{
+ OSL_ENSURE( n >= 512, "Unexpected page size is provided!" );
+ if ( n >= 512 )
+ {
+ m_nPageSize = n;
+ sal_uInt64 nFileSize = m_pStrm->TellEnd();
+ m_nPages = lcl_GetPageCount( nFileSize, m_nPageSize );
+ }
+}
+
+// Create a new cache element
+
+rtl::Reference< StgPage > StgCache::Create( sal_Int32 nPg )
+{
+ rtl::Reference< StgPage > xElem( StgPage::Create( m_nPageSize, nPg ) );
+ maLRUPages[ m_nReplaceIdx++ % maLRUPages.size() ] = xElem;
+ return xElem;
+}
+
+// Delete the given element
+
+void StgCache::Erase( const rtl::Reference< StgPage > &xElem )
+{
+ OSL_ENSURE( xElem.is(), "The pointer should not be NULL!" );
+ if ( xElem.is() ) {
+ auto it = std::find_if(maLRUPages.begin(), maLRUPages.end(),
+ [xElem](const rtl::Reference<StgPage>& rxPage) { return rxPage.is() && rxPage->GetPage() == xElem->GetPage(); });
+ if (it != maLRUPages.end())
+ it->clear();
+ }
+}
+
+// remove all cache elements without flushing them
+
+void StgCache::Clear()
+{
+ maDirtyPages.clear();
+ for (auto& rxPage : maLRUPages)
+ rxPage.clear();
+}
+
+// Look for a cached page
+
+rtl::Reference< StgPage > StgCache::Find( sal_Int32 nPage )
+{
+ auto it = std::find_if(maLRUPages.begin(), maLRUPages.end(),
+ [nPage](const rtl::Reference<StgPage>& rxPage) { return rxPage.is() && rxPage->GetPage() == nPage; });
+ if (it != maLRUPages.end())
+ return *it;
+ IndexToStgPage::iterator it2 = maDirtyPages.find( nPage );
+ if ( it2 != maDirtyPages.end() )
+ return it2->second;
+ return rtl::Reference< StgPage >();
+}
+
+// Load a page into the cache
+
+rtl::Reference< StgPage > StgCache::Get( sal_Int32 nPage, bool bForce )
+{
+ rtl::Reference< StgPage > p = Find( nPage );
+ if( !p.is() )
+ {
+ p = Create( nPage );
+ if( !Read( nPage, p->GetData() ) && bForce )
+ {
+ Erase( p );
+ p.clear();
+ 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.
+
+rtl::Reference< StgPage > StgCache::Copy( sal_Int32 nNew, sal_Int32 nOld )
+{
+ rtl::Reference< StgPage > p = Find( nNew );
+ if( !p.is() )
+ p = Create( nNew );
+ if( nOld >= 0 )
+ {
+ // old page: we must have this data!
+ rtl::Reference< StgPage > q = Get( nOld, true );
+ if( q.is() )
+ {
+ OSL_ENSURE( p->GetSize() == q->GetSize(), "Unexpected page size!" );
+ memcpy( p->GetData(), q->GetData(), p->GetSize() );
+ }
+ }
+ SetDirty( p );
+
+ return p;
+}
+
+// Historically this wrote pages in a sorted, ascending order;
+// continue that tradition.
+bool StgCache::Commit()
+{
+ if ( Good() ) // otherwise Write does nothing
+ {
+ std::vector< StgPage * > aToWrite;
+ aToWrite.reserve(maDirtyPages.size());
+ for (const auto& rEntry : maDirtyPages)
+ aToWrite.push_back( rEntry.second.get() );
+
+ std::sort( aToWrite.begin(), aToWrite.end(), StgPage::IsPageGreater );
+ for (StgPage* pWr : aToWrite)
+ {
+ const rtl::Reference< StgPage > &pPage = pWr;
+ if ( !Write( pPage->GetPage(), pPage->GetData() ) )
+ return false;
+ }
+ }
+
+ maDirtyPages.clear();
+
+ m_pStrm->Flush();
+ SetError( m_pStrm->GetError() );
+
+ return true;
+}
+
+// Set a stream
+
+void StgCache::SetStrm( SvStream* p, bool bMy )
+{
+ if( m_pStorageStream )
+ {
+ m_pStorageStream->ReleaseRef();
+ m_pStorageStream = nullptr;
+ }
+
+ if( m_bMyStream )
+ delete m_pStrm;
+ m_pStrm = p;
+ m_bMyStream = bMy;
+}
+
+void StgCache::SetStrm( UCBStorageStream* pStgStream )
+{
+ if( m_pStorageStream )
+ m_pStorageStream->ReleaseRef();
+ m_pStorageStream = pStgStream;
+
+ if( m_bMyStream )
+ delete m_pStrm;
+
+ m_pStrm = nullptr;
+
+ if ( m_pStorageStream )
+ {
+ m_pStorageStream->AddFirstRef();
+ m_pStrm = m_pStorageStream->GetModifySvStream();
+ }
+
+ m_bMyStream = false;
+}
+
+void StgCache::SetDirty( const rtl::Reference< StgPage > &rPage )
+{
+ assert( m_pStrm && m_pStrm->IsWritable() );
+ maDirtyPages[ rPage->GetPage() ] = rPage;
+}
+
+// Open/close the disk file
+
+bool StgCache::Open( const OUString& rName, StreamMode nMode )
+{
+ // do not open in exclusive mode!
+ if( nMode & StreamMode::SHARE_DENYALL )
+ nMode = ( ( nMode & ~StreamMode::SHARE_DENYALL ) | StreamMode::SHARE_DENYWRITE );
+ SvFileStream* pFileStrm = new SvFileStream( rName, nMode );
+ // SvStream "feature" Write Open also successful if it does not work
+ bool bAccessDenied = false;
+ if( ( nMode & StreamMode::WRITE ) && !pFileStrm->IsWritable() )
+ {
+ pFileStrm->Close();
+ bAccessDenied = true;
+ }
+ SetStrm( pFileStrm, true );
+ if( pFileStrm->IsOpen() )
+ {
+ sal_uInt64 nFileSize = m_pStrm->TellEnd();
+ m_nPages = lcl_GetPageCount( nFileSize, m_nPageSize );
+ m_pStrm->Seek( 0 );
+ }
+ else
+ m_nPages = 0;
+ m_bFile = true;
+ SetError( bAccessDenied ? ERRCODE_IO_ACCESSDENIED : m_pStrm->GetError() );
+ return Good();
+}
+
+void StgCache::Close()
+{
+ if( m_bFile )
+ {
+ static_cast<SvFileStream*>(m_pStrm)->Close();
+ SetError( m_pStrm->GetError() );
+ }
+}
+
+// low level I/O
+
+bool StgCache::Read( sal_Int32 nPage, void* pBuf )
+{
+ sal_uInt32 nRead = 0, nBytes = m_nPageSize;
+ 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 > m_nPages )
+ SetError( SVSTREAM_READ_ERROR );
+ else if ( nPage < m_nPages )
+ {
+ sal_uInt32 nPos;
+ sal_Int32 nPg2;
+ // fixed address and size for the header
+ if( nPage == -1 )
+ {
+ nPos = 0;
+ nPg2 = 1;
+ nBytes = 512;
+ }
+ else
+ {
+ nPos = Page2Pos(nPage);
+ nPg2 = ((nPage + 1) > m_nPages) ? m_nPages - nPage : 1;
+ }
+
+ if (m_pStrm->Tell() != nPos)
+ m_pStrm->Seek(nPos);
+
+ if (nPg2 != 1)
+ SetError(SVSTREAM_READ_ERROR);
+ else
+ {
+ nRead = m_pStrm->ReadBytes(pBuf, nBytes);
+ SetError(m_pStrm->GetError());
+ }
+ }
+ }
+
+ if (!Good())
+ return false;
+
+ if (nRead != nBytes)
+ memset(static_cast<sal_uInt8*>(pBuf) + nRead, 0, nBytes - nRead);
+ return true;
+}
+
+bool StgCache::Write( sal_Int32 nPage, void const * pBuf )
+{
+ if( Good() )
+ {
+ sal_uInt32 nPos = Page2Pos( nPage );
+ sal_uInt32 nBytes = m_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 = 0;
+ nBytes = 512;
+ }
+ if( m_pStrm->Tell() != nPos )
+ {
+ m_pStrm->Seek(nPos);
+ }
+ size_t nRes = m_pStrm->WriteBytes( pBuf, nBytes );
+ if( nRes != nBytes )
+ SetError( SVSTREAM_WRITE_ERROR );
+ else
+ SetError( m_pStrm->GetError() );
+ }
+ return Good();
+}
+
+// set the file size in pages
+
+bool StgCache::SetSize( sal_Int32 n )
+{
+ // Add the file header
+ sal_Int32 nSize = n * m_nPageSize + 512;
+ m_pStrm->SetStreamSize( nSize );
+ SetError( m_pStrm->GetError() );
+ if( !m_nError )
+ m_nPages = n;
+ return Good();
+}
+
+void StgCache::SetError( ErrCode n )
+{
+ if( n && !m_nError )
+ m_nError = n;
+}
+
+void StgCache::ResetError()
+{
+ m_nError = ERRCODE_NONE;
+ m_pStrm->ResetError();
+}
+
+void StgCache::MoveError( StorageBase const & r )
+{
+ if( m_nError != ERRCODE_NONE )
+ {
+ r.SetError( m_nError );
+ ResetError();
+ }
+}
+
+// Utility functions
+
+sal_Int32 StgCache::Page2Pos( sal_Int32 nPage ) const
+{
+ if( nPage < 0 ) nPage = 0;
+ return( nPage * m_nPageSize ) + m_nPageSize;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */