/src/libreoffice/sot/source/sdstor/stgcache.cxx
Line | Count | Source |
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 | | |
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. |
34 | | |
35 | | StgPage::StgPage( short nSize, sal_Int32 nPage ) |
36 | 3.50M | : mnPage( nPage ) |
37 | 3.50M | , mpData( new sal_uInt8[ nSize ] ) |
38 | 3.50M | , mnSize( nSize ) |
39 | 3.50M | { |
40 | 3.50M | 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 | 3.50M | memset( mpData.get(), 0, mnSize ); |
44 | 3.50M | } |
45 | | |
46 | | StgPage::~StgPage() |
47 | 3.50M | { |
48 | 3.50M | } |
49 | | |
50 | | rtl::Reference< StgPage > StgPage::Create( short nData, sal_Int32 nPage ) |
51 | 3.50M | { |
52 | 3.50M | return rtl::Reference< StgPage >( new StgPage( nData, nPage ) ); |
53 | 3.50M | } |
54 | | |
55 | | void StgCache::SetToPage ( const rtl::Reference< StgPage >& rPage, short nOff, sal_Int32 nVal ) |
56 | 6.06M | { |
57 | 6.06M | if( nOff >= 0 && ( o3tl::make_unsigned(nOff) < rPage->GetSize() / sizeof( sal_Int32 ) ) ) |
58 | 6.06M | { |
59 | | #ifdef OSL_BIGENDIAN |
60 | | nVal = OSL_SWAPDWORD(nVal); |
61 | | #endif |
62 | 6.06M | static_cast<sal_Int32*>(rPage->GetData())[ nOff ] = nVal; |
63 | 6.06M | SetDirty( rPage ); |
64 | 6.06M | } |
65 | 6.06M | } |
66 | | |
67 | | bool StgPage::IsPageGreater( const StgPage *pA, const StgPage *pB ) |
68 | 673k | { |
69 | 673k | return pA->mnPage < pB->mnPage; |
70 | 673k | } |
71 | | |
72 | | //////////////////////////////// class StgCache |
73 | | |
74 | | // The disk cache holds the cached sectors. The sector type differ according |
75 | | // to their purpose. |
76 | | |
77 | | static sal_Int32 lcl_GetPageCount( sal_uInt64 nFileSize, short nPageSize ) |
78 | 91.9k | { |
79 | | // return (nFileSize >= 512) ? (nFileSize - 512) / nPageSize : 0; |
80 | | // #i61980# real life: last page may be incomplete, return number of *started* pages |
81 | 91.9k | return (nFileSize >= 512) ? (nFileSize - 512 + nPageSize - 1) / nPageSize : 0; |
82 | 91.9k | } |
83 | | |
84 | | StgCache::StgCache() |
85 | 95.1k | : m_nError( ERRCODE_NONE ) |
86 | 95.1k | , m_nPages( 0 ) |
87 | 95.1k | , m_nRef( 0 ) |
88 | 95.1k | , m_nReplaceIdx( 0 ) |
89 | 95.1k | , maLRUPages( 8 ) // entries in the LRU lookup |
90 | 95.1k | , m_nPageSize( 512 ) |
91 | 95.1k | , m_pStorageStream( nullptr ) |
92 | 95.1k | , m_pStrm( nullptr ) |
93 | 95.1k | , m_bMyStream( false ) |
94 | 95.1k | , m_bFile( false ) |
95 | 95.1k | { |
96 | 95.1k | } |
97 | | |
98 | | StgCache::~StgCache() |
99 | 95.1k | { |
100 | 95.1k | Clear(); |
101 | 95.1k | SetStrm( nullptr, false ); |
102 | 95.1k | } |
103 | | |
104 | | void StgCache::SetPhysPageSize( short n ) |
105 | 91.9k | { |
106 | 91.9k | OSL_ENSURE( n >= 512, "Unexpected page size is provided!" ); |
107 | 91.9k | if ( n >= 512 ) |
108 | 91.9k | { |
109 | 91.9k | m_nPageSize = n; |
110 | 91.9k | sal_uInt64 nFileSize = m_pStrm->TellEnd(); |
111 | 91.9k | m_nPages = lcl_GetPageCount( nFileSize, m_nPageSize ); |
112 | 91.9k | } |
113 | 91.9k | } |
114 | | |
115 | | // Create a new cache element |
116 | | |
117 | | rtl::Reference< StgPage > StgCache::Create( sal_Int32 nPg ) |
118 | 3.50M | { |
119 | 3.50M | rtl::Reference< StgPage > xElem( StgPage::Create( m_nPageSize, nPg ) ); |
120 | 3.50M | maLRUPages[ m_nReplaceIdx++ % maLRUPages.size() ] = xElem; |
121 | 3.50M | return xElem; |
122 | 3.50M | } |
123 | | |
124 | | // Delete the given element |
125 | | |
126 | | void StgCache::Erase( const rtl::Reference< StgPage > &xElem ) |
127 | 2.16M | { |
128 | 2.16M | OSL_ENSURE( xElem.is(), "The pointer should not be NULL!" ); |
129 | 2.16M | if ( xElem.is() ) { |
130 | 2.16M | auto it = std::find_if(maLRUPages.begin(), maLRUPages.end(), |
131 | 9.72M | [xElem](const rtl::Reference<StgPage>& rxPage) { return rxPage.is() && rxPage->GetPage() == xElem->GetPage(); }); |
132 | 2.16M | if (it != maLRUPages.end()) |
133 | 2.16M | it->clear(); |
134 | 2.16M | } |
135 | 2.16M | } |
136 | | |
137 | | // remove all cache elements without flushing them |
138 | | |
139 | | void StgCache::Clear() |
140 | 95.1k | { |
141 | 95.1k | maDirtyPages.clear(); |
142 | 95.1k | for (auto& rxPage : maLRUPages) |
143 | 761k | rxPage.clear(); |
144 | 95.1k | } |
145 | | |
146 | | // Look for a cached page |
147 | | |
148 | | rtl::Reference< StgPage > StgCache::Find( sal_Int32 nPage ) |
149 | 65.0M | { |
150 | 65.0M | auto it = std::find_if(maLRUPages.begin(), maLRUPages.end(), |
151 | 299M | [nPage](const rtl::Reference<StgPage>& rxPage) { return rxPage.is() && rxPage->GetPage() == nPage; }); |
152 | 65.0M | if (it != maLRUPages.end()) |
153 | 58.6M | return *it; |
154 | 6.42M | IndexToStgPage::iterator it2 = maDirtyPages.find( nPage ); |
155 | 6.42M | if ( it2 != maDirtyPages.end() ) |
156 | 670k | return it2->second; |
157 | 5.75M | return rtl::Reference< StgPage >(); |
158 | 6.42M | } |
159 | | |
160 | | // Load a page into the cache |
161 | | |
162 | | rtl::Reference< StgPage > StgCache::Get( sal_Int32 nPage, bool bForce ) |
163 | 62.2M | { |
164 | 62.2M | rtl::Reference< StgPage > p = Find( nPage ); |
165 | 62.2M | if( !p.is() ) |
166 | 3.32M | { |
167 | 3.32M | p = Create( nPage ); |
168 | 3.32M | if( !Read( nPage, p->GetData() ) && bForce ) |
169 | 2.16M | { |
170 | 2.16M | Erase( p ); |
171 | 2.16M | p.clear(); |
172 | 2.16M | SetError( SVSTREAM_READ_ERROR ); |
173 | 2.16M | } |
174 | 3.32M | } |
175 | 62.2M | return p; |
176 | 62.2M | } |
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 | | |
182 | | rtl::Reference< StgPage > StgCache::Copy( sal_Int32 nNew, sal_Int32 nOld ) |
183 | 180k | { |
184 | 180k | rtl::Reference< StgPage > p = Find( nNew ); |
185 | 180k | if( !p.is() ) |
186 | 179k | p = Create( nNew ); |
187 | 180k | if( nOld >= 0 ) |
188 | 0 | { |
189 | | // old page: we must have this data! |
190 | 0 | rtl::Reference< StgPage > q = Get( nOld, true ); |
191 | 0 | if( q.is() ) |
192 | 0 | { |
193 | 0 | OSL_ENSURE( p->GetSize() == q->GetSize(), "Unexpected page size!" ); |
194 | 0 | memcpy( p->GetData(), q->GetData(), p->GetSize() ); |
195 | 0 | } |
196 | 0 | } |
197 | 180k | SetDirty( p ); |
198 | | |
199 | 180k | return p; |
200 | 180k | } |
201 | | |
202 | | // Historically this wrote pages in a sorted, ascending order; |
203 | | // continue that tradition. |
204 | | bool StgCache::Commit() |
205 | 55.1k | { |
206 | 55.1k | if ( Good() ) // otherwise Write does nothing |
207 | 55.1k | { |
208 | 55.1k | std::vector< StgPage * > aToWrite; |
209 | 55.1k | aToWrite.reserve(maDirtyPages.size()); |
210 | 55.1k | for (const auto& rEntry : maDirtyPages) |
211 | 220k | aToWrite.push_back( rEntry.second.get() ); |
212 | | |
213 | 55.1k | std::sort( aToWrite.begin(), aToWrite.end(), StgPage::IsPageGreater ); |
214 | 55.1k | for (StgPage* pWr : aToWrite) |
215 | 220k | { |
216 | 220k | const rtl::Reference< StgPage > pPage = pWr; |
217 | 220k | if ( !Write( pPage->GetPage(), pPage->GetData() ) ) |
218 | 0 | return false; |
219 | 220k | } |
220 | 55.1k | } |
221 | | |
222 | 55.1k | maDirtyPages.clear(); |
223 | | |
224 | 55.1k | m_pStrm->Flush(); |
225 | 55.1k | SetError( m_pStrm->GetError() ); |
226 | | |
227 | 55.1k | return true; |
228 | 55.1k | } |
229 | | |
230 | | // Set a stream |
231 | | |
232 | | void StgCache::SetStrm( SvStream* p, bool bMy ) |
233 | 190k | { |
234 | 190k | if( m_pStorageStream ) |
235 | 0 | { |
236 | 0 | m_pStorageStream->ReleaseRef(); |
237 | 0 | m_pStorageStream = nullptr; |
238 | 0 | } |
239 | | |
240 | 190k | if( m_bMyStream ) |
241 | 0 | delete m_pStrm; |
242 | 190k | m_pStrm = p; |
243 | 190k | m_bMyStream = bMy; |
244 | 190k | } |
245 | | |
246 | | void StgCache::SetStrm( UCBStorageStream* pStgStream ) |
247 | 0 | { |
248 | 0 | if( m_pStorageStream ) |
249 | 0 | m_pStorageStream->ReleaseRef(); |
250 | 0 | m_pStorageStream = pStgStream; |
251 | |
|
252 | 0 | if( m_bMyStream ) |
253 | 0 | delete m_pStrm; |
254 | |
|
255 | 0 | m_pStrm = nullptr; |
256 | |
|
257 | 0 | if ( m_pStorageStream ) |
258 | 0 | { |
259 | 0 | m_pStorageStream->AddFirstRef(); |
260 | 0 | m_pStrm = m_pStorageStream->GetModifySvStream(); |
261 | 0 | } |
262 | |
|
263 | 0 | m_bMyStream = false; |
264 | 0 | } |
265 | | |
266 | | void StgCache::SetDirty( const rtl::Reference< StgPage > &rPage ) |
267 | 6.88M | { |
268 | 6.88M | assert( m_pStrm && m_pStrm->IsWritable() ); |
269 | 6.88M | maDirtyPages[ rPage->GetPage() ] = rPage; |
270 | 6.88M | } |
271 | | |
272 | | // Open/close the disk file |
273 | | |
274 | | bool StgCache::Open( const OUString& rName, StreamMode nMode ) |
275 | 0 | { |
276 | | // do not open in exclusive mode! |
277 | 0 | if( nMode & StreamMode::SHARE_DENYALL ) |
278 | 0 | nMode = ( ( nMode & ~StreamMode::SHARE_DENYALL ) | StreamMode::SHARE_DENYWRITE ); |
279 | 0 | SvFileStream* pFileStrm = new SvFileStream( rName, nMode ); |
280 | | // SvStream "feature" Write Open also successful if it does not work |
281 | 0 | bool bAccessDenied = false; |
282 | 0 | if( ( nMode & StreamMode::WRITE ) && !pFileStrm->IsWritable() ) |
283 | 0 | { |
284 | 0 | pFileStrm->Close(); |
285 | 0 | bAccessDenied = true; |
286 | 0 | } |
287 | 0 | SetStrm( pFileStrm, true ); |
288 | 0 | if( pFileStrm->IsOpen() ) |
289 | 0 | { |
290 | 0 | sal_uInt64 nFileSize = m_pStrm->TellEnd(); |
291 | 0 | m_nPages = lcl_GetPageCount( nFileSize, m_nPageSize ); |
292 | 0 | m_pStrm->Seek( 0 ); |
293 | 0 | } |
294 | 0 | else |
295 | 0 | m_nPages = 0; |
296 | 0 | m_bFile = true; |
297 | 0 | SetError( bAccessDenied ? ERRCODE_IO_ACCESSDENIED : m_pStrm->GetError() ); |
298 | 0 | return Good(); |
299 | 0 | } |
300 | | |
301 | | void StgCache::Close() |
302 | 95.1k | { |
303 | 95.1k | if( m_bFile ) |
304 | 0 | { |
305 | 0 | static_cast<SvFileStream*>(m_pStrm)->Close(); |
306 | 0 | SetError( m_pStrm->GetError() ); |
307 | 0 | } |
308 | 95.1k | } |
309 | | |
310 | | // low level I/O |
311 | | |
312 | | bool StgCache::Read( sal_Int32 nPage, void* pBuf ) |
313 | 5.14M | { |
314 | 5.14M | sal_uInt32 nRead = 0, nBytes = m_nPageSize; |
315 | 5.14M | if( Good() ) |
316 | 2.99M | { |
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 | 2.99M | if ( nPage > m_nPages ) |
322 | 24.5k | SetError( SVSTREAM_READ_ERROR ); |
323 | 2.96M | else if ( nPage < m_nPages ) |
324 | 2.93M | { |
325 | 2.93M | sal_uInt32 nPos; |
326 | 2.93M | sal_Int32 nPg2; |
327 | | // fixed address and size for the header |
328 | 2.93M | if( nPage == -1 ) |
329 | 1.07k | { |
330 | 1.07k | nPos = 0; |
331 | 1.07k | nPg2 = 1; |
332 | 1.07k | nBytes = 512; |
333 | 1.07k | } |
334 | 2.92M | else |
335 | 2.92M | { |
336 | 2.92M | nPos = Page2Pos(nPage); |
337 | 2.92M | nPg2 = ((nPage + 1) > m_nPages) ? m_nPages - nPage : 1; |
338 | 2.92M | } |
339 | | |
340 | 2.93M | if (m_pStrm->Tell() != nPos) |
341 | 1.02M | m_pStrm->Seek(nPos); |
342 | | |
343 | 2.93M | if (nPg2 != 1) |
344 | 0 | SetError(SVSTREAM_READ_ERROR); |
345 | 2.93M | else |
346 | 2.93M | { |
347 | 2.93M | nRead = m_pStrm->ReadBytes(pBuf, nBytes); |
348 | 2.93M | SetError(m_pStrm->GetError()); |
349 | 2.93M | } |
350 | 2.93M | } |
351 | 2.99M | } |
352 | | |
353 | 5.14M | if (!Good()) |
354 | 2.18M | return false; |
355 | | |
356 | 2.96M | if (nRead != nBytes) |
357 | 93.2k | memset(static_cast<sal_uInt8*>(pBuf) + nRead, 0, nBytes - nRead); |
358 | 2.96M | return true; |
359 | 5.14M | } |
360 | | |
361 | | bool StgCache::Write( sal_Int32 nPage, void const * pBuf ) |
362 | 651k | { |
363 | 651k | if( Good() ) |
364 | 651k | { |
365 | 651k | sal_uInt32 nPos = Page2Pos( nPage ); |
366 | 651k | 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 | 651k | if( nPage == -1 ) |
371 | 0 | { |
372 | 0 | nPos = 0; |
373 | 0 | nBytes = 512; |
374 | 0 | } |
375 | 651k | if( m_pStrm->Tell() != nPos ) |
376 | 78.4k | { |
377 | 78.4k | m_pStrm->Seek(nPos); |
378 | 78.4k | } |
379 | 651k | size_t nRes = m_pStrm->WriteBytes( pBuf, nBytes ); |
380 | 651k | if( nRes != nBytes ) |
381 | 0 | SetError( SVSTREAM_WRITE_ERROR ); |
382 | 651k | else |
383 | 651k | SetError( m_pStrm->GetError() ); |
384 | 651k | } |
385 | 651k | return Good(); |
386 | 651k | } |
387 | | |
388 | | // set the file size in pages |
389 | | |
390 | | bool StgCache::SetSize( sal_Int32 n ) |
391 | 72.0k | { |
392 | | // Add the file header |
393 | 72.0k | sal_Int32 nSize = n * m_nPageSize + 512; |
394 | 72.0k | m_pStrm->SetStreamSize( nSize ); |
395 | 72.0k | SetError( m_pStrm->GetError() ); |
396 | 72.0k | if( !m_nError ) |
397 | 72.0k | m_nPages = n; |
398 | 72.0k | return Good(); |
399 | 72.0k | } |
400 | | |
401 | | void StgCache::SetError( ErrCode n ) |
402 | 6.38M | { |
403 | 6.38M | if( n && !m_nError ) |
404 | 103k | m_nError = n; |
405 | 6.38M | } |
406 | | |
407 | | void StgCache::ResetError() |
408 | 286k | { |
409 | 286k | m_nError = ERRCODE_NONE; |
410 | 286k | m_pStrm->ResetError(); |
411 | 286k | } |
412 | | |
413 | | void StgCache::MoveError( StorageBase const & r ) |
414 | 69.0M | { |
415 | 69.0M | if( m_nError != ERRCODE_NONE ) |
416 | 103k | { |
417 | 103k | r.SetError( m_nError ); |
418 | 103k | ResetError(); |
419 | 103k | } |
420 | 69.0M | } |
421 | | |
422 | | // Utility functions |
423 | | |
424 | | sal_Int32 StgCache::Page2Pos( sal_Int32 nPage ) const |
425 | 3.58M | { |
426 | 3.58M | if( nPage < 0 ) nPage = 0; |
427 | 3.58M | return( nPage * m_nPageSize ) + m_nPageSize; |
428 | 3.58M | } |
429 | | |
430 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |