Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sot / source / sdstor / stgcache.cxx
blob93f7d3090c939923ad3a7c3d95b172280a071b50
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 .
20 #include <string.h>
21 #include <o3tl/safeint.hxx>
22 #include <osl/endian.h>
23 #include <osl/diagnose.h>
25 #include <sot/stg.hxx>
26 #include "stgcache.hxx"
28 #include <algorithm>
30 ////////////////////////////// class StgPage
31 // This class implements buffer functionality. The cache will always return
32 // a page buffer, even if a read fails. It is up to the caller to determine
33 // the correctness of the I/O.
35 StgPage::StgPage( short nSize, sal_Int32 nPage )
36 : mnPage( nPage )
37 , mpData( new sal_uInt8[ nSize ] )
38 , mnSize( nSize )
40 OSL_ENSURE( mnSize >= 512, "Unexpected page size is provided!" );
41 // We will write this data to a permanent file later
42 // best to clear if first.
43 memset( mpData.get(), 0, mnSize );
46 StgPage::~StgPage()
50 rtl::Reference< StgPage > StgPage::Create( short nData, sal_Int32 nPage )
52 return rtl::Reference< StgPage >( new StgPage( nData, nPage ) );
55 void StgCache::SetToPage ( const rtl::Reference< StgPage >& rPage, short nOff, sal_Int32 nVal )
57 if( nOff >= 0 && ( o3tl::make_unsigned(nOff) < rPage->GetSize() / sizeof( sal_Int32 ) ) )
59 #ifdef OSL_BIGENDIAN
60 nVal = OSL_SWAPDWORD(nVal);
61 #endif
62 static_cast<sal_Int32*>(rPage->GetData())[ nOff ] = nVal;
63 SetDirty( rPage );
67 bool StgPage::IsPageGreater( const StgPage *pA, const StgPage *pB )
69 return pA->mnPage < pB->mnPage;
72 //////////////////////////////// class StgCache
74 // The disk cache holds the cached sectors. The sector type differ according
75 // to their purpose.
77 static sal_Int32 lcl_GetPageCount( sal_uInt64 nFileSize, short nPageSize )
79 // return (nFileSize >= 512) ? (nFileSize - 512) / nPageSize : 0;
80 // #i61980# real life: last page may be incomplete, return number of *started* pages
81 return (nFileSize >= 512) ? (nFileSize - 512 + nPageSize - 1) / nPageSize : 0;
84 StgCache::StgCache()
85 : m_nError( ERRCODE_NONE )
86 , m_nPages( 0 )
87 , m_nRef( 0 )
88 , m_nReplaceIdx( 0 )
89 , maLRUPages( 8 ) // entries in the LRU lookup
90 , m_nPageSize( 512 )
91 , m_pStorageStream( nullptr )
92 , m_pStrm( nullptr )
93 , m_bMyStream( false )
94 , m_bFile( false )
98 StgCache::~StgCache()
100 Clear();
101 SetStrm( nullptr, false );
104 void StgCache::SetPhysPageSize( short n )
106 OSL_ENSURE( n >= 512, "Unexpected page size is provided!" );
107 if ( n >= 512 )
109 m_nPageSize = n;
110 sal_uInt64 nFileSize = m_pStrm->TellEnd();
111 m_nPages = lcl_GetPageCount( nFileSize, m_nPageSize );
115 // Create a new cache element
117 rtl::Reference< StgPage > StgCache::Create( sal_Int32 nPg )
119 rtl::Reference< StgPage > xElem( StgPage::Create( m_nPageSize, nPg ) );
120 maLRUPages[ m_nReplaceIdx++ % maLRUPages.size() ] = xElem;
121 return xElem;
124 // Delete the given element
126 void StgCache::Erase( const rtl::Reference< StgPage > &xElem )
128 OSL_ENSURE( xElem.is(), "The pointer should not be NULL!" );
129 if ( xElem.is() ) {
130 auto it = std::find_if(maLRUPages.begin(), maLRUPages.end(),
131 [xElem](const rtl::Reference<StgPage>& rxPage) { return rxPage.is() && rxPage->GetPage() == xElem->GetPage(); });
132 if (it != maLRUPages.end())
133 it->clear();
137 // remove all cache elements without flushing them
139 void StgCache::Clear()
141 maDirtyPages.clear();
142 for (auto& rxPage : maLRUPages)
143 rxPage.clear();
146 // Look for a cached page
148 rtl::Reference< StgPage > StgCache::Find( sal_Int32 nPage )
150 auto it = std::find_if(maLRUPages.begin(), maLRUPages.end(),
151 [nPage](const rtl::Reference<StgPage>& rxPage) { return rxPage.is() && rxPage->GetPage() == nPage; });
152 if (it != maLRUPages.end())
153 return *it;
154 IndexToStgPage::iterator it2 = maDirtyPages.find( nPage );
155 if ( it2 != maDirtyPages.end() )
156 return it2->second;
157 return rtl::Reference< StgPage >();
160 // Load a page into the cache
162 rtl::Reference< StgPage > StgCache::Get( sal_Int32 nPage, bool bForce )
164 rtl::Reference< StgPage > p = Find( nPage );
165 if( !p.is() )
167 p = Create( nPage );
168 if( !Read( nPage, p->GetData() ) && bForce )
170 Erase( p );
171 p.clear();
172 SetError( SVSTREAM_READ_ERROR );
175 return p;
178 // Copy an existing page into a new page. Use this routine
179 // to duplicate an existing stream or to create new entries.
180 // The new page is initially marked dirty. No owner is copied.
182 rtl::Reference< StgPage > StgCache::Copy( sal_Int32 nNew, sal_Int32 nOld )
184 rtl::Reference< StgPage > p = Find( nNew );
185 if( !p.is() )
186 p = Create( nNew );
187 if( nOld >= 0 )
189 // old page: we must have this data!
190 rtl::Reference< StgPage > q = Get( nOld, true );
191 if( q.is() )
193 OSL_ENSURE( p->GetSize() == q->GetSize(), "Unexpected page size!" );
194 memcpy( p->GetData(), q->GetData(), p->GetSize() );
197 SetDirty( p );
199 return p;
202 // Historically this wrote pages in a sorted, ascending order;
203 // continue that tradition.
204 bool StgCache::Commit()
206 if ( Good() ) // otherwise Write does nothing
208 std::vector< StgPage * > aToWrite;
209 aToWrite.reserve(maDirtyPages.size());
210 for (const auto& rEntry : maDirtyPages)
211 aToWrite.push_back( rEntry.second.get() );
213 std::sort( aToWrite.begin(), aToWrite.end(), StgPage::IsPageGreater );
214 for (StgPage* pWr : aToWrite)
216 const rtl::Reference< StgPage > &pPage = pWr;
217 if ( !Write( pPage->GetPage(), pPage->GetData() ) )
218 return false;
222 maDirtyPages.clear();
224 m_pStrm->Flush();
225 SetError( m_pStrm->GetError() );
227 return true;
230 // Set a stream
232 void StgCache::SetStrm( SvStream* p, bool bMy )
234 if( m_pStorageStream )
236 m_pStorageStream->ReleaseRef();
237 m_pStorageStream = nullptr;
240 if( m_bMyStream )
241 delete m_pStrm;
242 m_pStrm = p;
243 m_bMyStream = bMy;
246 void StgCache::SetStrm( UCBStorageStream* pStgStream )
248 if( m_pStorageStream )
249 m_pStorageStream->ReleaseRef();
250 m_pStorageStream = pStgStream;
252 if( m_bMyStream )
253 delete m_pStrm;
255 m_pStrm = nullptr;
257 if ( m_pStorageStream )
259 m_pStorageStream->AddFirstRef();
260 m_pStrm = m_pStorageStream->GetModifySvStream();
263 m_bMyStream = false;
266 void StgCache::SetDirty( const rtl::Reference< StgPage > &rPage )
268 assert( m_pStrm && m_pStrm->IsWritable() );
269 maDirtyPages[ rPage->GetPage() ] = rPage;
272 // Open/close the disk file
274 bool StgCache::Open( const OUString& rName, StreamMode nMode )
276 // do not open in exclusive mode!
277 if( nMode & StreamMode::SHARE_DENYALL )
278 nMode = ( ( nMode & ~StreamMode::SHARE_DENYALL ) | StreamMode::SHARE_DENYWRITE );
279 SvFileStream* pFileStrm = new SvFileStream( rName, nMode );
280 // SvStream "feature" Write Open also successful if it does not work
281 bool bAccessDenied = false;
282 if( ( nMode & StreamMode::WRITE ) && !pFileStrm->IsWritable() )
284 pFileStrm->Close();
285 bAccessDenied = true;
287 SetStrm( pFileStrm, true );
288 if( pFileStrm->IsOpen() )
290 sal_uInt64 nFileSize = m_pStrm->TellEnd();
291 m_nPages = lcl_GetPageCount( nFileSize, m_nPageSize );
292 m_pStrm->Seek( 0 );
294 else
295 m_nPages = 0;
296 m_bFile = true;
297 SetError( bAccessDenied ? ERRCODE_IO_ACCESSDENIED : m_pStrm->GetError() );
298 return Good();
301 void StgCache::Close()
303 if( m_bFile )
305 static_cast<SvFileStream*>(m_pStrm)->Close();
306 SetError( m_pStrm->GetError() );
310 // low level I/O
312 bool StgCache::Read( sal_Int32 nPage, void* pBuf )
314 sal_uInt32 nRead = 0, nBytes = m_nPageSize;
315 if( Good() )
317 /* #i73846# real life: a storage may refer to a page one-behind the
318 last valid page (see document attached to the issue). In that case
319 (if nPage==nPages), just do nothing here and let the caller work on
320 the empty zero-filled buffer. */
321 if ( nPage > m_nPages )
322 SetError( SVSTREAM_READ_ERROR );
323 else if ( nPage < m_nPages )
325 sal_uInt32 nPos;
326 sal_Int32 nPg2;
327 // fixed address and size for the header
328 if( nPage == -1 )
330 nPos = 0;
331 nPg2 = 1;
332 nBytes = 512;
334 else
336 nPos = Page2Pos(nPage);
337 nPg2 = ((nPage + 1) > m_nPages) ? m_nPages - nPage : 1;
340 if (m_pStrm->Tell() != nPos)
341 m_pStrm->Seek(nPos);
343 if (nPg2 != 1)
344 SetError(SVSTREAM_READ_ERROR);
345 else
347 nRead = m_pStrm->ReadBytes(pBuf, nBytes);
348 SetError(m_pStrm->GetError());
353 if (!Good())
354 return false;
356 if (nRead != nBytes)
357 memset(static_cast<sal_uInt8*>(pBuf) + nRead, 0, nBytes - nRead);
358 return true;
361 bool StgCache::Write( sal_Int32 nPage, void const * pBuf )
363 if( Good() )
365 sal_uInt32 nPos = Page2Pos( nPage );
366 sal_uInt32 nBytes = m_nPageSize;
368 // fixed address and size for the header
369 // nPageSize must be >= 512, otherwise the header can not be written here, we check it on import
370 if( nPage == -1 )
372 nPos = 0;
373 nBytes = 512;
375 if( m_pStrm->Tell() != nPos )
377 m_pStrm->Seek(nPos);
379 size_t nRes = m_pStrm->WriteBytes( pBuf, nBytes );
380 if( nRes != nBytes )
381 SetError( SVSTREAM_WRITE_ERROR );
382 else
383 SetError( m_pStrm->GetError() );
385 return Good();
388 // set the file size in pages
390 bool StgCache::SetSize( sal_Int32 n )
392 // Add the file header
393 sal_Int32 nSize = n * m_nPageSize + 512;
394 m_pStrm->SetStreamSize( nSize );
395 SetError( m_pStrm->GetError() );
396 if( !m_nError )
397 m_nPages = n;
398 return Good();
401 void StgCache::SetError( ErrCode n )
403 if( n && !m_nError )
404 m_nError = n;
407 void StgCache::ResetError()
409 m_nError = ERRCODE_NONE;
410 m_pStrm->ResetError();
413 void StgCache::MoveError( StorageBase const & r )
415 if( m_nError != ERRCODE_NONE )
417 r.SetError( m_nError );
418 ResetError();
422 // Utility functions
424 sal_Int32 StgCache::Page2Pos( sal_Int32 nPage ) const
426 if( nPage < 0 ) nPage = 0;
427 return( nPage * m_nPageSize ) + m_nPageSize;
430 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */