LibreOffice Module sot (master) 1
stgcache.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include <string.h>
21#include <o3tl/safeint.hxx>
22#include <osl/endian.h>
23#include <osl/diagnose.h>
24
25#include <sot/stg.hxx>
26#include "stgcache.hxx"
27
28#include <algorithm>
29
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.
34
35StgPage::StgPage( short nSize, sal_Int32 nPage )
36 : mnPage( nPage )
37 , mpData( new sal_uInt8[ nSize ] )
38 , mnSize( nSize )
39{
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 );
44}
45
47{
48}
49
50rtl::Reference< StgPage > StgPage::Create( short nData, sal_Int32 nPage )
51{
52 return rtl::Reference< StgPage >( new StgPage( nData, nPage ) );
53}
54
55void StgCache::SetToPage ( const rtl::Reference< StgPage >& rPage, short nOff, sal_Int32 nVal )
56{
57 if( nOff >= 0 && ( o3tl::make_unsigned(nOff) < rPage->GetSize() / sizeof( sal_Int32 ) ) )
58 {
59#ifdef OSL_BIGENDIAN
60 nVal = OSL_SWAPDWORD(nVal);
61#endif
62 static_cast<sal_Int32*>(rPage->GetData())[ nOff ] = nVal;
63 SetDirty( rPage );
64 }
65}
66
67bool StgPage::IsPageGreater( const StgPage *pA, const StgPage *pB )
68{
69 return pA->mnPage < pB->mnPage;
70}
71
73
74// The disk cache holds the cached sectors. The sector type differ according
75// to their purpose.
76
77static sal_Int32 lcl_GetPageCount( sal_uInt64 nFileSize, short nPageSize )
78{
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;
82}
83
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 )
95{
96}
97
99{
100 Clear();
101 SetStrm( nullptr, false );
102}
103
105{
106 OSL_ENSURE( n >= 512, "Unexpected page size is provided!" );
107 if ( n >= 512 )
108 {
109 m_nPageSize = n;
110 sal_uInt64 nFileSize = m_pStrm->TellEnd();
111 m_nPages = lcl_GetPageCount( nFileSize, m_nPageSize );
112 }
113}
114
115// Create a new cache element
116
118{
120 maLRUPages[ m_nReplaceIdx++ % maLRUPages.size() ] = xElem;
121 return xElem;
122}
123
124// Delete the given element
125
127{
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();
134 }
135}
136
137// remove all cache elements without flushing them
138
140{
141 maDirtyPages.clear();
142 for (auto& rxPage : maLRUPages)
143 rxPage.clear();
144}
145
146// Look for a cached page
147
149{
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;
158}
159
160// Load a page into the cache
161
162rtl::Reference< StgPage > StgCache::Get( sal_Int32 nPage, bool bForce )
163{
165 if( !p.is() )
166 {
167 p = Create( nPage );
168 if( !Read( nPage, p->GetData() ) && bForce )
169 {
170 Erase( p );
171 p.clear();
173 }
174 }
175 return p;
176}
177
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.
181
182rtl::Reference< StgPage > StgCache::Copy( sal_Int32 nNew, sal_Int32 nOld )
183{
185 if( !p.is() )
186 p = Create( nNew );
187 if( nOld >= 0 )
188 {
189 // old page: we must have this data!
190 rtl::Reference< StgPage > q = Get( nOld, true );
191 if( q.is() )
192 {
193 OSL_ENSURE( p->GetSize() == q->GetSize(), "Unexpected page size!" );
194 memcpy( p->GetData(), q->GetData(), p->GetSize() );
195 }
196 }
197 SetDirty( p );
198
199 return p;
200}
201
202// Historically this wrote pages in a sorted, ascending order;
203// continue that tradition.
205{
206 if ( Good() ) // otherwise Write does nothing
207 {
208 std::vector< StgPage * > aToWrite;
209 aToWrite.reserve(maDirtyPages.size());
210 for (const auto& rEntry : maDirtyPages)
211 aToWrite.push_back( rEntry.second.get() );
212
213 std::sort( aToWrite.begin(), aToWrite.end(), StgPage::IsPageGreater );
214 for (StgPage* pWr : aToWrite)
215 {
216 const rtl::Reference< StgPage > &pPage = pWr;
217 if ( !Write( pPage->GetPage(), pPage->GetData() ) )
218 return false;
219 }
220 }
221
222 maDirtyPages.clear();
223
224 m_pStrm->Flush();
226
227 return true;
228}
229
230// Set a stream
231
232void StgCache::SetStrm( SvStream* p, bool bMy )
233{
234 if( m_pStorageStream )
235 {
237 m_pStorageStream = nullptr;
238 }
239
240 if( m_bMyStream )
241 delete m_pStrm;
242 m_pStrm = p;
243 m_bMyStream = bMy;
244}
245
247{
248 if( m_pStorageStream )
250 m_pStorageStream = pStgStream;
251
252 if( m_bMyStream )
253 delete m_pStrm;
254
255 m_pStrm = nullptr;
256
257 if ( m_pStorageStream )
258 {
261 }
262
263 m_bMyStream = false;
264}
265
267{
268 assert( m_pStrm && m_pStrm->IsWritable() );
269 maDirtyPages[ rPage->GetPage() ] = rPage;
270}
271
272// Open/close the disk file
273
274bool StgCache::Open( const OUString& rName, StreamMode nMode )
275{
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() )
283 {
284 pFileStrm->Close();
285 bAccessDenied = true;
286 }
287 SetStrm( pFileStrm, true );
288 if( pFileStrm->IsOpen() )
289 {
290 sal_uInt64 nFileSize = m_pStrm->TellEnd();
291 m_nPages = lcl_GetPageCount( nFileSize, m_nPageSize );
292 m_pStrm->Seek( 0 );
293 }
294 else
295 m_nPages = 0;
296 m_bFile = true;
297 SetError( bAccessDenied ? ERRCODE_IO_ACCESSDENIED : m_pStrm->GetError() );
298 return Good();
299}
300
302{
303 if( m_bFile )
304 {
305 static_cast<SvFileStream*>(m_pStrm)->Close();
307 }
308}
309
310// low level I/O
311
312bool StgCache::Read( sal_Int32 nPage, void* pBuf )
313{
314 sal_uInt32 nRead = 0, nBytes = m_nPageSize;
315 if( Good() )
316 {
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 )
323 else if ( nPage < m_nPages )
324 {
325 sal_uInt32 nPos;
326 sal_Int32 nPg2;
327 // fixed address and size for the header
328 if( nPage == -1 )
329 {
330 nPos = 0;
331 nPg2 = 1;
332 nBytes = 512;
333 }
334 else
335 {
336 nPos = Page2Pos(nPage);
337 nPg2 = ((nPage + 1) > m_nPages) ? m_nPages - nPage : 1;
338 }
339
340 if (m_pStrm->Tell() != nPos)
341 m_pStrm->Seek(nPos);
342
343 if (nPg2 != 1)
345 else
346 {
347 nRead = m_pStrm->ReadBytes(pBuf, nBytes);
349 }
350 }
351 }
352
353 if (!Good())
354 return false;
355
356 if (nRead != nBytes)
357 memset(static_cast<sal_uInt8*>(pBuf) + nRead, 0, nBytes - nRead);
358 return true;
359}
360
361bool StgCache::Write( sal_Int32 nPage, void const * pBuf )
362{
363 if( Good() )
364 {
365 sal_uInt32 nPos = Page2Pos( nPage );
366 sal_uInt32 nBytes = m_nPageSize;
367
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 )
371 {
372 nPos = 0;
373 nBytes = 512;
374 }
375 if( m_pStrm->Tell() != nPos )
376 {
377 m_pStrm->Seek(nPos);
378 }
379 size_t nRes = m_pStrm->WriteBytes( pBuf, nBytes );
380 if( nRes != nBytes )
382 else
384 }
385 return Good();
386}
387
388// set the file size in pages
389
390bool StgCache::SetSize( sal_Int32 n )
391{
392 // Add the file header
393 sal_Int32 nSize = n * m_nPageSize + 512;
394 m_pStrm->SetStreamSize( nSize );
396 if( !m_nError )
397 m_nPages = n;
398 return Good();
399}
400
402{
403 if( n && !m_nError )
404 m_nError = n;
405}
406
408{
411}
412
414{
415 if( m_nError != ERRCODE_NONE )
416 {
417 r.SetError( m_nError );
418 ResetError();
419 }
420}
421
422// Utility functions
423
424sal_Int32 StgCache::Page2Pos( sal_Int32 nPage ) const
425{
426 if( nPage < 0 ) nPage = 0;
427 return( nPage * m_nPageSize ) + m_nPageSize;
428}
429
430/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
StgCache()
Definition: stgcache.cxx:84
bool Open(const OUString &rName, StreamMode)
Definition: stgcache.cxx:274
short m_nPageSize
Definition: stgcache.hxx:52
void ResetError()
Definition: stgcache.cxx:407
UCBStorageStream * m_pStorageStream
Definition: stgcache.hxx:53
rtl::Reference< StgPage > Get(sal_Int32, bool)
Definition: stgcache.cxx:162
rtl::Reference< StgPage > Copy(sal_Int32, sal_Int32=STG_FREE)
Definition: stgcache.cxx:182
bool Write(sal_Int32 nPage, void const *pBuf)
Definition: stgcache.cxx:361
sal_Int32 m_nPages
Definition: stgcache.hxx:47
rtl::Reference< StgPage > Find(sal_Int32)
Definition: stgcache.cxx:148
void SetPhysPageSize(short)
Definition: stgcache.cxx:104
~StgCache()
Definition: stgcache.cxx:98
bool m_bMyStream
Definition: stgcache.hxx:58
void Close()
Definition: stgcache.cxx:301
void MoveError(StorageBase const &)
Definition: stgcache.cxx:413
IndexToStgPage maDirtyPages
Definition: stgcache.hxx:49
rtl::Reference< StgPage > Create(sal_Int32)
Definition: stgcache.cxx:117
void SetStrm(SvStream *, bool)
Definition: stgcache.cxx:232
void Erase(const rtl::Reference< StgPage > &)
Definition: stgcache.cxx:126
bool SetSize(sal_Int32 nPages)
Definition: stgcache.cxx:390
void SetToPage(const rtl::Reference< StgPage > &rPage, short nOff, sal_Int32 nVal)
Definition: stgcache.cxx:55
bool Good() const
Definition: stgcache.hxx:73
void Clear()
Definition: stgcache.cxx:139
LRUList maLRUPages
Definition: stgcache.hxx:51
SvStream * m_pStrm
Definition: stgcache.hxx:57
void SetDirty(const rtl::Reference< StgPage > &rPage)
Definition: stgcache.cxx:266
sal_Int32 Page2Pos(sal_Int32) const
Definition: stgcache.cxx:424
bool m_bFile
Definition: stgcache.hxx:60
bool Commit()
Definition: stgcache.cxx:204
ErrCode m_nError
Definition: stgcache.hxx:46
void SetError(ErrCode)
Definition: stgcache.cxx:401
bool Read(sal_Int32 nPage, void *pBuf)
Definition: stgcache.cxx:312
int m_nReplaceIdx
Definition: stgcache.hxx:50
const sal_Int32 mnPage
Definition: stgcache.hxx:98
static rtl::Reference< StgPage > Create(short nData, sal_Int32 nPage)
Definition: stgcache.cxx:50
virtual ~StgPage() override
Definition: stgcache.cxx:46
StgPage(short nData, sal_Int32 nPage)
Definition: stgcache.cxx:35
std::unique_ptr< sal_uInt8[]> mpData
Definition: stgcache.hxx:100
static bool IsPageGreater(const StgPage *pA, const StgPage *pB)
Definition: stgcache.cxx:67
short mnSize
Definition: stgcache.hxx:101
void SetError(ErrCode) const
Definition: stg.cxx:67
bool IsOpen() const
void ReleaseRef()
void AddFirstRef()
virtual void ResetError()
bool IsWritable() const
sal_uInt64 Tell() const
virtual sal_uInt64 TellEnd()
std::size_t WriteBytes(const void *pData, std::size_t nSize)
bool SetStreamSize(sal_uInt64 nSize)
sal_uInt64 Seek(sal_uInt64 nPos)
void Flush()
std::size_t ReadBytes(void *pData, std::size_t nSize)
ErrCode GetError() const
SvStream * GetModifySvStream()
sal_uInt32 mnSize
#define SVSTREAM_WRITE_ERROR
#define ERRCODE_IO_ACCESSDENIED
#define ERRCODE_NONE
#define SVSTREAM_READ_ERROR
oslInterlockedCount m_nRef
void * p
sal_Int64 n
sal_uInt16 nPos
sal_uInt16 m_nPageSize
constexpr std::enable_if_t< std::is_signed_v< T >, std::make_unsigned_t< T > > make_unsigned(T value)
static sal_Int32 lcl_GetPageCount(sal_uInt64 nFileSize, short nPageSize)
Definition: stgcache.cxx:77
StreamMode
unsigned char sal_uInt8