Line data Source code
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 <osl/endian.h>
22 : #include <tools/solar.h>
23 :
24 : #include "sot/stg.hxx"
25 : #include "stgelem.hxx"
26 : #include "stgcache.hxx"
27 : #include "stgstrms.hxx"
28 : #include "stgdir.hxx"
29 : #include "stgio.hxx"
30 :
31 : //#define CHECK_DIRTY 1
32 : //#define READ_AFTER_WRITE 1
33 :
34 : ////////////////////////////// class StgPage
35 : // This class implements buffer functionality. The cache will always return
36 : // a page buffer, even if a read fails. It is up to the caller to determine
37 : // the correctness of the I/O.
38 :
39 0 : StgPage::StgPage( short nSize, sal_Int32 nPage )
40 : : mnRefCount( 0 )
41 : , mnPage( nPage )
42 0 : , mpData( new sal_uInt8[ nSize ] )
43 0 : , mnSize( nSize )
44 : {
45 : OSL_ENSURE( mnSize >= 512, "Unexpected page size is provided!" );
46 : // We will write this data to a permanent file later
47 : // best to clear if first.
48 0 : memset( mpData, 0, mnSize );
49 0 : }
50 :
51 0 : StgPage::~StgPage()
52 : {
53 0 : delete [] mpData;
54 0 : }
55 :
56 0 : rtl::Reference< StgPage > StgPage::Create( short nData, sal_Int32 nPage )
57 : {
58 0 : return rtl::Reference< StgPage >( new StgPage( nData, nPage ) );
59 : }
60 :
61 0 : void StgCache::SetToPage ( const rtl::Reference< StgPage > xPage, short nOff, sal_Int32 nVal )
62 : {
63 0 : if( ( nOff < (short) ( xPage->GetSize() / sizeof( sal_Int32 ) ) ) && nOff >= 0 )
64 : {
65 : #ifdef OSL_BIGENDIAN
66 : nVal = OSL_SWAPDWORD(nVal);
67 : #endif
68 0 : ((sal_Int32*) xPage->GetData() )[ nOff ] = nVal;
69 0 : SetDirty( xPage );
70 : }
71 0 : }
72 :
73 0 : bool StgPage::IsPageGreater( const StgPage *pA, const StgPage *pB )
74 : {
75 0 : return pA->mnPage < pB->mnPage;
76 : }
77 :
78 : //////////////////////////////// class StgCache
79 :
80 : // The disk cache holds the cached sectors. The sector type differ according
81 : // to their purpose.
82 :
83 0 : static sal_Int32 lcl_GetPageCount( sal_uLong nFileSize, short nPageSize )
84 : {
85 : // return (nFileSize >= 512) ? (nFileSize - 512) / nPageSize : 0;
86 : // #i61980# reallife: last page may be incomplete, return number of *started* pages
87 0 : return (nFileSize >= 512) ? (nFileSize - 512 + nPageSize - 1) / nPageSize : 0;
88 : }
89 :
90 0 : StgCache::StgCache()
91 : : nError( SVSTREAM_OK )
92 : , nPages( 0 )
93 : , nRef( 0 )
94 : , nReplaceIdx( 0 )
95 : , maLRUPages( 8 ) // entries in the LRU lookup
96 : , nPageSize( 512 )
97 : , pStorageStream( NULL )
98 : , pStrm( NULL )
99 : , bMyStream( false )
100 0 : , bFile( false )
101 : {
102 0 : }
103 :
104 0 : StgCache::~StgCache()
105 : {
106 0 : Clear();
107 0 : SetStrm( NULL, false );
108 0 : }
109 :
110 0 : void StgCache::SetPhysPageSize( short n )
111 : {
112 : OSL_ENSURE( n >= 512, "Unexpecte page size is provided!" );
113 0 : if ( n >= 512 )
114 : {
115 0 : nPageSize = n;
116 0 : sal_uLong nPos = pStrm->Tell();
117 0 : sal_uLong nFileSize = pStrm->Seek( STREAM_SEEK_TO_END );
118 0 : nPages = lcl_GetPageCount( nFileSize, nPageSize );
119 0 : pStrm->Seek( nPos );
120 : }
121 0 : }
122 :
123 : // Create a new cache element
124 :
125 0 : rtl::Reference< StgPage > StgCache::Create( sal_Int32 nPg )
126 : {
127 0 : rtl::Reference< StgPage > xElem( StgPage::Create( nPageSize, nPg ) );
128 0 : maLRUPages[ nReplaceIdx++ % maLRUPages.size() ] = xElem;
129 0 : return xElem;
130 : }
131 :
132 : // Delete the given element
133 :
134 0 : void StgCache::Erase( const rtl::Reference< StgPage > &xElem )
135 : {
136 : OSL_ENSURE( xElem.is(), "The pointer should not be NULL!" );
137 0 : if ( xElem.is() ) {
138 0 : for ( LRUList::iterator it = maLRUPages.begin(); it != maLRUPages.end(); ++it ) {
139 0 : if ( it->is() && (*it)->GetPage() == xElem->GetPage() ) {
140 0 : it->clear();
141 0 : break;
142 : }
143 : }
144 : }
145 0 : }
146 :
147 : // remove all cache elements without flushing them
148 :
149 0 : void StgCache::Clear()
150 : {
151 0 : maDirtyPages.clear();
152 0 : for ( LRUList::iterator it = maLRUPages.begin(); it != maLRUPages.end(); ++it )
153 0 : it->clear();
154 0 : }
155 :
156 : // Look for a cached page
157 :
158 0 : rtl::Reference< StgPage > StgCache::Find( sal_Int32 nPage )
159 : {
160 0 : for ( LRUList::iterator it = maLRUPages.begin(); it != maLRUPages.end(); ++it )
161 0 : if ( it->is() && (*it)->GetPage() == nPage )
162 0 : return *it;
163 0 : IndexToStgPage::iterator it2 = maDirtyPages.find( nPage );
164 0 : if ( it2 != maDirtyPages.end() )
165 0 : return it2->second;
166 0 : return rtl::Reference< StgPage >();
167 : }
168 :
169 : // Load a page into the cache
170 :
171 0 : rtl::Reference< StgPage > StgCache::Get( sal_Int32 nPage, bool bForce )
172 : {
173 0 : rtl::Reference< StgPage > p = Find( nPage );
174 0 : if( !p.is() )
175 : {
176 0 : p = Create( nPage );
177 0 : if( !Read( nPage, p->GetData(), 1 ) && bForce )
178 : {
179 0 : Erase( p );
180 0 : p.clear();
181 0 : SetError( SVSTREAM_READ_ERROR );
182 : }
183 : }
184 0 : return p;
185 : }
186 :
187 : // Copy an existing page into a new page. Use this routine
188 : // to duplicate an existing stream or to create new entries.
189 : // The new page is initially marked dirty. No owner is copied.
190 :
191 0 : rtl::Reference< StgPage > StgCache::Copy( sal_Int32 nNew, sal_Int32 nOld )
192 : {
193 0 : rtl::Reference< StgPage > p = Find( nNew );
194 0 : if( !p.is() )
195 0 : p = Create( nNew );
196 0 : if( nOld >= 0 )
197 : {
198 : // old page: we must have this data!
199 0 : rtl::Reference< StgPage > q = Get( nOld, true );
200 0 : if( q.is() )
201 : {
202 : OSL_ENSURE( p->GetSize() == q->GetSize(), "Unexpected page size!" );
203 0 : memcpy( p->GetData(), q->GetData(), p->GetSize() );
204 0 : }
205 : }
206 0 : SetDirty( p );
207 :
208 0 : return p;
209 : }
210 :
211 : // Historically this wrote pages in a sorted, ascending order;
212 : // continue that tradition.
213 0 : bool StgCache::Commit()
214 : {
215 0 : if ( Good() ) // otherwise Write does nothing
216 : {
217 0 : std::vector< StgPage * > aToWrite;
218 0 : for ( IndexToStgPage::iterator aIt = maDirtyPages.begin();
219 0 : aIt != maDirtyPages.end(); ++aIt )
220 0 : aToWrite.push_back( aIt->second.get() );
221 :
222 0 : std::sort( aToWrite.begin(), aToWrite.end(), StgPage::IsPageGreater );
223 0 : for ( std::vector< StgPage * >::iterator aWr = aToWrite.begin();
224 0 : aWr != aToWrite.end(); ++aWr)
225 : {
226 0 : const rtl::Reference< StgPage > &pPage = *aWr;
227 0 : if ( !Write( pPage->GetPage(), pPage->GetData(), 1 ) )
228 0 : return false;
229 0 : }
230 : }
231 :
232 0 : maDirtyPages.clear();
233 :
234 0 : pStrm->Flush();
235 0 : SetError( pStrm->GetError() );
236 :
237 0 : return true;
238 : }
239 :
240 : // Set a stream
241 :
242 0 : void StgCache::SetStrm( SvStream* p, bool bMy )
243 : {
244 0 : if( pStorageStream )
245 : {
246 0 : pStorageStream->ReleaseRef();
247 0 : pStorageStream = NULL;
248 : }
249 :
250 0 : if( bMyStream )
251 0 : delete pStrm;
252 0 : pStrm = p;
253 0 : bMyStream = bMy;
254 0 : }
255 :
256 0 : void StgCache::SetStrm( UCBStorageStream* pStgStream )
257 : {
258 0 : if( pStorageStream )
259 0 : pStorageStream->ReleaseRef();
260 0 : pStorageStream = pStgStream;
261 :
262 0 : if( bMyStream )
263 0 : delete pStrm;
264 :
265 0 : pStrm = NULL;
266 :
267 0 : if ( pStorageStream )
268 : {
269 0 : pStorageStream->AddRef();
270 0 : pStrm = pStorageStream->GetModifySvStream();
271 : }
272 :
273 0 : bMyStream = false;
274 0 : }
275 :
276 0 : void StgCache::SetDirty( const rtl::Reference< StgPage > &xPage )
277 : {
278 : assert( IsWritable() );
279 0 : maDirtyPages[ xPage->GetPage() ] = xPage;
280 0 : }
281 :
282 : // Open/close the disk file
283 :
284 0 : bool StgCache::Open( const OUString& rName, StreamMode nMode )
285 : {
286 : // do not open in exclusive mode!
287 0 : if( nMode & STREAM_SHARE_DENYALL )
288 0 : nMode = ( ( nMode & ~STREAM_SHARE_DENYALL ) | STREAM_SHARE_DENYWRITE );
289 0 : SvFileStream* pFileStrm = new SvFileStream( rName, nMode );
290 : // SvStream "Feature" Write Open auch erfolgreich, wenns nicht klappt
291 0 : bool bAccessDenied = false;
292 0 : if( ( nMode & STREAM_WRITE ) && !pFileStrm->IsWritable() )
293 : {
294 0 : pFileStrm->Close();
295 0 : bAccessDenied = true;
296 : }
297 0 : SetStrm( pFileStrm, true );
298 0 : if( pFileStrm->IsOpen() )
299 : {
300 0 : sal_uLong nFileSize = pStrm->Seek( STREAM_SEEK_TO_END );
301 0 : nPages = lcl_GetPageCount( nFileSize, nPageSize );
302 0 : pStrm->Seek( 0L );
303 : }
304 : else
305 0 : nPages = 0;
306 0 : bFile = true;
307 0 : SetError( bAccessDenied ? ERRCODE_IO_ACCESSDENIED : pStrm->GetError() );
308 0 : return Good();
309 : }
310 :
311 0 : void StgCache::Close()
312 : {
313 0 : if( bFile )
314 : {
315 0 : ((SvFileStream*) pStrm)->Close();
316 0 : SetError( pStrm->GetError() );
317 : }
318 0 : }
319 :
320 : // low level I/O
321 :
322 0 : bool StgCache::Read( sal_Int32 nPage, void* pBuf, sal_Int32 nPg )
323 : {
324 0 : if( Good() )
325 : {
326 : /* #i73846# real life: a storage may refer to a page one-behind the
327 : last valid page (see document attached to the issue). In that case
328 : (if nPage==nPages), just do nothing here and let the caller work on
329 : the empty zero-filled buffer. */
330 0 : if ( nPage > nPages )
331 0 : SetError( SVSTREAM_READ_ERROR );
332 0 : else if ( nPage < nPages )
333 : {
334 0 : sal_uLong nPos = Page2Pos( nPage );
335 0 : sal_Int32 nPg2 = ( ( nPage + nPg ) > nPages ) ? nPages - nPage : nPg;
336 0 : sal_uLong nBytes = nPg2 * nPageSize;
337 : // fixed address and size for the header
338 0 : if( nPage == -1 )
339 : {
340 0 : nPos = 0L, nBytes = 512;
341 0 : nPg2 = nPg;
342 : }
343 0 : if( pStrm->Tell() != nPos )
344 : {
345 0 : if( pStrm->Seek( nPos ) != nPos ) {
346 : #ifdef CHECK_DIRTY
347 : ErrorBox( NULL, WB_OK, OUString("SO2: Seek failed") ).Execute();
348 : #endif
349 : }
350 : }
351 0 : pStrm->Read( pBuf, nBytes );
352 0 : if ( nPg != nPg2 )
353 0 : SetError( SVSTREAM_READ_ERROR );
354 : else
355 0 : SetError( pStrm->GetError() );
356 : }
357 : }
358 0 : return Good();
359 : }
360 :
361 0 : bool StgCache::Write( sal_Int32 nPage, void* pBuf, sal_Int32 nPg )
362 : {
363 0 : if( Good() )
364 : {
365 0 : sal_uLong nPos = Page2Pos( nPage );
366 0 : sal_uLong nBytes = 0;
367 0 : if ( SAL_MAX_INT32 / nPg > nPageSize )
368 0 : nBytes = nPg * nPageSize;
369 :
370 : // fixed address and size for the header
371 : // nPageSize must be >= 512, otherwise the header can not be written here, we check it on import
372 0 : if( nPage == -1 )
373 0 : nPos = 0L, nBytes = 512;
374 0 : if( pStrm->Tell() != nPos )
375 : {
376 0 : if( pStrm->Seek( nPos ) != nPos ) {
377 : #ifdef CHECK_DIRTY
378 : ErrorBox( NULL, WB_OK, OUString("SO2: Seek failed") ).Execute();
379 : #endif
380 : }
381 : }
382 0 : sal_uLong nRes = pStrm->Write( pBuf, nBytes );
383 0 : if( nRes != nBytes )
384 0 : SetError( SVSTREAM_WRITE_ERROR );
385 : else
386 0 : SetError( pStrm->GetError() );
387 : #ifdef READ_AFTER_WRITE
388 : sal_uInt8 cBuf[ 512 ];
389 : pStrm->Flush();
390 : pStrm->Seek( nPos );
391 : bool bRes = ( pStrm->Read( cBuf, 512 ) == 512 );
392 : if( bRes )
393 : bRes = !memcmp( cBuf, pBuf, 512 );
394 : if( !bRes )
395 : {
396 : ErrorBox( NULL, WB_OK, OUString("SO2: Read after Write failed") ).Execute();
397 : pStrm->SetError( SVSTREAM_WRITE_ERROR );
398 : }
399 : #endif
400 : }
401 0 : return Good();
402 : }
403 :
404 : // set the file size in pages
405 :
406 0 : bool StgCache::SetSize( sal_Int32 n )
407 : {
408 : // Add the file header
409 0 : sal_Int32 nSize = n * nPageSize + 512;
410 0 : pStrm->SetStreamSize( nSize );
411 0 : SetError( pStrm->GetError() );
412 0 : if( !nError )
413 0 : nPages = n;
414 0 : return Good();
415 : }
416 :
417 0 : void StgCache::SetError( sal_uLong n )
418 : {
419 0 : if( n && !nError )
420 0 : nError = n;
421 0 : }
422 :
423 0 : void StgCache::ResetError()
424 : {
425 0 : nError = SVSTREAM_OK;
426 0 : pStrm->ResetError();
427 0 : }
428 :
429 0 : void StgCache::MoveError( StorageBase& r )
430 : {
431 0 : if( nError != SVSTREAM_OK )
432 : {
433 0 : r.SetError( nError );
434 0 : ResetError();
435 : }
436 0 : }
437 :
438 : // Utility functions
439 :
440 0 : sal_Int32 StgCache::Page2Pos( sal_Int32 nPage )
441 : {
442 0 : if( nPage < 0 ) nPage = 0;
443 0 : return( nPage * nPageSize ) + nPageSize;
444 : }
445 :
446 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|