xref: /trunk/main/sot/source/sdstor/stgcache.cxx (revision 297a844a)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_sot.hxx"
26 
27 #if defined(_MSC_VER) && (_MSC_VER<1200)
28 #include <tools/presys.h>
29 #endif
30 #include <hash_map>
31 #if defined(_MSC_VER) && (_MSC_VER<1200)
32 #include <tools/postsys.h>
33 #endif
34 #include <vos/macros.hxx>
35 
36 #include <string.h>
37 #include <osl/endian.h>
38 #include <tools/string.hxx>
39 
40 #include "sot/stg.hxx"
41 #include "stgelem.hxx"
42 #include "stgcache.hxx"
43 #include "stgstrms.hxx"
44 #include "stgdir.hxx"
45 #include "stgio.hxx"
46 
47 /*************************************************************************/
48 //-----------------------------------------------------------------------------
49 typedef std::hash_map
50 <
51 	sal_Int32,
52 	StgPage *,
53 	std::hash< sal_Int32 >,
54 	NAMESPACE_STD(equal_to)< sal_Int32 >
55 > UsrStgPagePtr_Impl;
56 #ifdef _MSC_VER
57 #pragma warning( disable: 4786 )
58 #endif
59 
60 //#define	CHECK_DIRTY 1
61 //#define	READ_AFTER_WRITE 1
62 
63 ////////////////////////////// class StgPage /////////////////////////////
64 // This class implements buffer functionality. The cache will always return
65 // a page buffer, even if a read fails. It is up to the caller to determine
66 // the correctness of the I/O.
67 
StgPage(StgCache * p,short n)68 StgPage::StgPage( StgCache* p, short n )
69 {
70     OSL_ENSURE( n >= 512, "Unexpected page size is provided!" );
71     pCache = p;
72     nData  = n;
73     bDirty = sal_False;
74     nPage  = 0;
75     pData  = new sal_uInt8[ nData ];
76     pNext1 =
77     pNext2 =
78     pLast1 =
79     pLast2 = NULL;
80 	pOwner = NULL;
81 }
82 
~StgPage()83 StgPage::~StgPage()
84 {
85     delete [] pData;
86 }
87 
SetPage(short nOff,sal_Int32 nVal)88 void StgPage::SetPage( short nOff, sal_Int32 nVal )
89 {
90     if( ( nOff < (short) ( nData / sizeof( sal_Int32 ) ) ) && nOff >= 0 )
91     {
92 #ifdef OSL_BIGENDIAN
93 	  nVal = SWAPLONG(nVal);
94 #endif
95         ((sal_Int32*) pData )[ nOff ] = nVal;
96         bDirty = sal_True;
97     }
98 }
99 
100 //////////////////////////////// class StgCache ////////////////////////////
101 
102 // The disk cache holds the cached sectors. The sector type differ according
103 // to their purpose.
104 
lcl_GetPageCount(sal_uLong nFileSize,short nPageSize)105 sal_Int32 lcl_GetPageCount( sal_uLong nFileSize, short nPageSize )
106 {
107 //    return (nFileSize >= 512) ? (nFileSize - 512) / nPageSize : 0;
108     // #i61980# reallife: last page may be incomplete, return number of *started* pages
109     return (nFileSize >= 512) ? (nFileSize - 512 + nPageSize - 1) / nPageSize : 0;
110 }
111 
StgCache()112 StgCache::StgCache()
113 {
114 	nRef = 0;
115 	pStrm = NULL;
116 	pCur = pElem1 = NULL;
117     nPageSize = 512;
118     nError = SVSTREAM_OK;
119 	bMyStream = sal_False;
120 	bFile = sal_False;
121 	pLRUCache = NULL;
122 	pStorageStream = NULL;
123 }
124 
~StgCache()125 StgCache::~StgCache()
126 {
127     Clear();
128 	SetStrm( NULL, sal_False );
129 	delete (UsrStgPagePtr_Impl*)pLRUCache;
130 }
131 
SetPhysPageSize(short n)132 void StgCache::SetPhysPageSize( short n )
133 {
134     OSL_ENSURE( n >= 512, "Unexpecte page size is provided!" );
135     if ( n >= 512 )
136     {
137         nPageSize = n;
138         sal_uLong nPos = pStrm->Tell();
139         sal_uLong nFileSize = pStrm->Seek( STREAM_SEEK_TO_END );
140         nPages = lcl_GetPageCount( nFileSize, nPageSize );
141         pStrm->Seek( nPos );
142     }
143 }
144 
145 // Create a new cache element
146 // pCur points to this element
147 
Create(sal_Int32 nPg)148 StgPage* StgCache::Create( sal_Int32 nPg )
149 {
150     StgPage* pElem = new StgPage( this, nPageSize );
151     pElem->nPage = nPg;
152     // For data security, clear the buffer contents
153     memset( pElem->pData, 0, pElem->nData );
154 
155 	// insert to LRU
156     if( pCur )
157     {
158         pElem->pNext1 = pCur;
159         pElem->pLast1 = pCur->pLast1;
160         pElem->pNext1->pLast1 =
161         pElem->pLast1->pNext1 = pElem;
162     }
163     else
164         pElem->pNext1 = pElem->pLast1 = pElem;
165 	if( !pLRUCache )
166 		pLRUCache = new UsrStgPagePtr_Impl();
167 	(*(UsrStgPagePtr_Impl*)pLRUCache)[pElem->nPage] = pElem;
168     pCur = pElem;
169 
170 	// insert to Sorted
171     if( !pElem1 )
172         pElem1 = pElem->pNext2 = pElem->pLast2 = pElem;
173     else
174     {
175         StgPage* p = pElem1;
176         do
177         {
178             if( pElem->nPage < p->nPage )
179                 break;
180             p = p->pNext2;
181         } while( p != pElem1 );
182         pElem->pNext2 = p;
183         pElem->pLast2 = p->pLast2;
184         pElem->pNext2->pLast2 =
185         pElem->pLast2->pNext2 = pElem;
186         if( p->nPage < pElem1->nPage )
187             pElem1 = pElem;
188     }
189     return pElem;
190 }
191 
192 // Delete the given element
193 
Erase(StgPage * pElem)194 void StgCache::Erase( StgPage* pElem )
195 {
196     OSL_ENSURE( pElem, "The pointer should not be NULL!" );
197     if ( pElem )
198     {
199         OSL_ENSURE( pElem->pNext1 && pElem->pLast1, "The pointers may not be NULL!" );
200         //remove from LRU
201         pElem->pNext1->pLast1 = pElem->pLast1;
202         pElem->pLast1->pNext1 = pElem->pNext1;
203         if( pCur == pElem )
204             pCur = ( pElem->pNext1 == pElem ) ? NULL : pElem->pNext1;
205         if( pLRUCache )
206             ((UsrStgPagePtr_Impl*)pLRUCache)->erase( pElem->nPage );
207         // remove from Sorted
208         pElem->pNext2->pLast2 = pElem->pLast2;
209         pElem->pLast2->pNext2 = pElem->pNext2;
210         if( pElem1 == pElem )
211             pElem1 = ( pElem->pNext2 == pElem ) ? NULL : pElem->pNext2;
212         delete pElem;
213     }
214 }
215 
216 // remove all cache elements without flushing them
217 
Clear()218 void StgCache::Clear()
219 {
220 	StgPage* pElem = pCur;
221 	if( pCur ) do
222 	{
223 		StgPage* pDelete = pElem;
224 		pElem = pElem->pNext1;
225 		delete pDelete;
226 	}
227     while( pCur != pElem );
228     pCur = NULL;
229 	pElem1 = NULL;
230 	delete (UsrStgPagePtr_Impl*)pLRUCache;
231 	pLRUCache = NULL;
232 }
233 
234 // Look for a cached page
235 
Find(sal_Int32 nPage)236 StgPage* StgCache::Find( sal_Int32 nPage )
237 {
238 	if( !pLRUCache )
239 		return NULL;
240 	UsrStgPagePtr_Impl::iterator aIt = ((UsrStgPagePtr_Impl*)pLRUCache)->find( nPage );
241 	if( aIt != ((UsrStgPagePtr_Impl*)pLRUCache)->end() )
242 	{
243 		// page found
244 	    StgPage* pFound = (*aIt).second;
245         OSL_ENSURE( pFound, "The pointer may not be NULL!" );
246 
247 		if( pFound != pCur )
248 		{
249             OSL_ENSURE( pFound->pNext1 && pFound->pLast1, "The pointers may not be NULL!" );
250 			// remove from LRU
251 			pFound->pNext1->pLast1 = pFound->pLast1;
252 			pFound->pLast1->pNext1 = pFound->pNext1;
253 			// insert to LRU
254 			pFound->pNext1 = pCur;
255 			pFound->pLast1 = pCur->pLast1;
256 			pFound->pNext1->pLast1 =
257 			pFound->pLast1->pNext1 = pFound;
258 		}
259 		return pFound;
260 	}
261     return NULL;
262 }
263 
264 // Load a page into the cache
265 
Get(sal_Int32 nPage,sal_Bool bForce)266 StgPage* StgCache::Get( sal_Int32 nPage, sal_Bool bForce )
267 {
268     StgPage* p = Find( nPage );
269     if( !p )
270     {
271         p = Create( nPage );
272         if( !Read( nPage, p->pData, 1 ) && bForce )
273 		{
274 			Erase( p );
275 			p = NULL;
276             SetError( SVSTREAM_READ_ERROR );
277 		}
278 	}
279     return p;
280 }
281 
282 // Copy an existing page into a new page. Use this routine
283 // to duplicate an existing stream or to create new entries.
284 // The new page is initially marked dirty. No owner is copied.
285 
Copy(sal_Int32 nNew,sal_Int32 nOld)286 StgPage* StgCache::Copy( sal_Int32 nNew, sal_Int32 nOld )
287 {
288     StgPage* p = Find( nNew );
289     if( !p )
290         p = Create( nNew );
291     if( nOld >= 0 )
292     {
293         // old page: we must have this data!
294         StgPage* q = Get( nOld, sal_True );
295         if( q )
296         {
297             OSL_ENSURE( p->nData == q->nData, "Unexpected page size!" );
298             memcpy( p->pData, q->pData, p->nData );
299         }
300     }
301     p->SetDirty();
302     return p;
303 }
304 
305 // Flush the cache whose owner is given. NULL flushes all.
306 
Commit(StgDirEntry *)307 sal_Bool StgCache::Commit( StgDirEntry* )
308 {
309     StgPage* p = pElem1;
310     if( p ) do
311     {
312         if( p->bDirty )
313         {
314             sal_Bool b = Write( p->nPage, p->pData, 1 );
315             if( !b )
316 				return sal_False;
317             p->bDirty = sal_False;
318         }
319         p = p->pNext2;
320     } while( p != pElem1 );
321 	pStrm->Flush();
322 	SetError( pStrm->GetError() );
323 #ifdef CHECK_DIRTY
324 	p = pElem1;
325 	if( p ) do
326 	{
327 	    if( p->bDirty )
328 	    {
329 			ErrorBox( NULL, WB_OK, String("SO2: Dirty Block in Ordered List") ).Execute();
330 	        sal_Bool b = Write( p->nPage, p->pData, 1 );
331 	        if( !b )
332 				return sal_False;
333 	        p->bDirty = sal_False;
334 	    }
335 	    p = p->pNext2;
336 	} while( p != pElem1 );
337 	p = pElem1;
338 	if( p ) do
339 	{
340 	    if( p->bDirty )
341 	    {
342 			ErrorBox( NULL, WB_OK, String("SO2: Dirty Block in LRU List") ).Execute();
343 	        sal_Bool b = Write( p->nPage, p->pData, 1 );
344 	        if( !b )
345 				return sal_False;
346 	        p->bDirty = sal_False;
347 	    }
348 	    p = p->pNext1;
349 	} while( p != pElem1 );
350 #endif
351 	return sal_True;
352 }
353 
Revert(StgDirEntry *)354 void StgCache::Revert( StgDirEntry* )
355 {}
356 
357 // Set a stream
358 
SetStrm(SvStream * p,sal_Bool bMy)359 void StgCache::SetStrm( SvStream* p, sal_Bool bMy )
360 {
361 	if( pStorageStream )
362 	{
363 		pStorageStream->ReleaseRef();
364 		pStorageStream = NULL;
365 	}
366 
367 	if( bMyStream )
368 		delete pStrm;
369 	pStrm = p;
370 	bMyStream = bMy;
371 }
372 
SetStrm(UCBStorageStream * pStgStream)373 void StgCache::SetStrm( UCBStorageStream* pStgStream )
374 {
375 	if( pStorageStream )
376 		pStorageStream->ReleaseRef();
377 	pStorageStream = pStgStream;
378 
379 	if( bMyStream )
380 		delete pStrm;
381 
382 	pStrm = NULL;
383 
384 	if ( pStorageStream )
385 	{
386 		pStorageStream->AddRef();
387 		pStrm = pStorageStream->GetModifySvStream();
388 	}
389 
390 	bMyStream = sal_False;
391 }
392 
393 // Open/close the disk file
394 
Open(const String & rName,StreamMode nMode)395 sal_Bool StgCache::Open( const String& rName, StreamMode nMode )
396 {
397 	// do not open in exclusive mode!
398 	if( nMode & STREAM_SHARE_DENYALL )
399 		nMode = ( ( nMode & ~STREAM_SHARE_DENYALL ) | STREAM_SHARE_DENYWRITE );
400 	SvFileStream* pFileStrm = new SvFileStream( rName, nMode );
401 	// SvStream "Feature" Write Open auch erfolgreich, wenns nicht klappt
402 	sal_Bool bAccessDenied = sal_False;
403 	if( ( nMode & STREAM_WRITE ) && !pFileStrm->IsWritable() )
404 	{
405 		pFileStrm->Close();
406 		bAccessDenied = sal_True;
407 	}
408 	SetStrm( pFileStrm, sal_True );
409 	if( pFileStrm->IsOpen() )
410 	{
411 	    sal_uLong nFileSize = pStrm->Seek( STREAM_SEEK_TO_END );
412         nPages = lcl_GetPageCount( nFileSize, nPageSize );
413 	    pStrm->Seek( 0L );
414 	}
415 	else
416 		nPages = 0;
417 	bFile = sal_True;
418     SetError( bAccessDenied ? ERRCODE_IO_ACCESSDENIED : pStrm->GetError() );
419     return Good();
420 }
421 
Close()422 void StgCache::Close()
423 {
424 	if( bFile )
425 	{
426 		((SvFileStream*) pStrm)->Close();
427 	    SetError( pStrm->GetError() );
428 	}
429 }
430 
431 // low level I/O
432 
Read(sal_Int32 nPage,void * pBuf,sal_Int32 nPg)433 sal_Bool StgCache::Read( sal_Int32 nPage, void* pBuf, sal_Int32 nPg )
434 {
435     if( Good() )
436     {
437         /*  #i73846# real life: a storage may refer to a page one-behind the
438             last valid page (see document attached to the issue). In that case
439             (if nPage==nPages), just do nothing here and let the caller work on
440             the empty zero-filled buffer. */
441         if ( nPage > nPages )
442 			SetError( SVSTREAM_READ_ERROR );
443         else if ( nPage < nPages )
444 		{
445 			sal_uLong nPos = Page2Pos( nPage );
446 			sal_Int32 nPg2 = ( ( nPage + nPg ) > nPages ) ? nPages - nPage : nPg;
447 			sal_uLong nBytes = nPg2 * nPageSize;
448 			// fixed address and size for the header
449 			if( nPage == -1 )
450 			{
451 				nPos = 0L, nBytes = 512;
452 				nPg2 = nPg;
453 			}
454 			if( pStrm->Tell() != nPos )
455 			{
456 				if( pStrm->Seek( nPos ) != nPos ) {
457 	#ifdef CHECK_DIRTY
458 					ErrorBox( NULL, WB_OK, String("SO2: Seek failed") ).Execute();
459 	#endif
460                 }
461 			}
462 			pStrm->Read( pBuf, nBytes );
463 			if ( nPg != nPg2 )
464 				SetError( SVSTREAM_READ_ERROR );
465 			else
466 				SetError( pStrm->GetError() );
467 		}
468     }
469     return Good();
470 }
471 
Write(sal_Int32 nPage,void * pBuf,sal_Int32 nPg)472 sal_Bool StgCache::Write( sal_Int32 nPage, void* pBuf, sal_Int32 nPg )
473 {
474     if( Good() )
475     {
476         sal_uLong nPos = Page2Pos( nPage );
477         sal_uLong nBytes = 0;
478         if ( SAL_MAX_INT32 / nPg > nPageSize )
479             nBytes = nPg * nPageSize;
480 
481         // fixed address and size for the header
482         // nPageSize must be >= 512, otherwise the header can not be written here, we check it on import
483         if( nPage == -1 )
484             nPos = 0L, nBytes = 512;
485         if( pStrm->Tell() != nPos )
486 		{
487 			if( pStrm->Seek( nPos ) != nPos ) {
488 #ifdef CHECK_DIRTY
489 				ErrorBox( NULL, WB_OK, String("SO2: Seek failed") ).Execute();
490 #endif
491             }
492 		}
493 		sal_uLong nRes = pStrm->Write( pBuf, nBytes );
494         if( nRes != nBytes )
495             SetError( SVSTREAM_WRITE_ERROR );
496         else
497             SetError( pStrm->GetError() );
498 #ifdef READ_AFTER_WRITE
499 		sal_uInt8 cBuf[ 512 ];
500 		pStrm->Flush();
501 		pStrm->Seek( nPos );
502 		sal_Bool bRes = ( pStrm->Read( cBuf, 512 ) == 512 );
503 		if( bRes )
504 			bRes = !memcmp( cBuf, pBuf, 512 );
505 		if( !bRes )
506 		{
507 			ErrorBox( NULL, WB_OK, String("SO2: Read after Write failed") ).Execute();
508 			pStrm->SetError( SVSTREAM_WRITE_ERROR );
509 		}
510 #endif
511     }
512     return Good();
513 }
514 
515 // set the file size in pages
516 
SetSize(sal_Int32 n)517 sal_Bool StgCache::SetSize( sal_Int32 n )
518 {
519     // Add the file header
520     sal_Int32 nSize = n * nPageSize + 512;
521     pStrm->SetStreamSize( nSize );
522     SetError( pStrm->GetError() );
523 	if( !nError )
524 	    nPages = n;
525     return Good();
526 }
527 
SetError(sal_uLong n)528 void StgCache::SetError( sal_uLong n )
529 {
530     if( n && !nError )
531         nError = n;
532 }
533 
ResetError()534 void StgCache::ResetError()
535 {
536     nError = SVSTREAM_OK;
537     pStrm->ResetError();
538 }
539 
MoveError(StorageBase & r)540 void StgCache::MoveError( StorageBase& r )
541 {
542 	if( nError != SVSTREAM_OK )
543 	{
544 		r.SetError( nError );
545 		ResetError();
546 	}
547 }
548 
549 // Utility functions
550 
Page2Pos(sal_Int32 nPage)551 sal_Int32 StgCache::Page2Pos( sal_Int32 nPage )
552 {
553     if( nPage < 0 ) nPage = 0;
554     return( nPage * nPageSize ) + nPageSize;
555 }
556 
Pos2Page(sal_Int32 nPos)557 sal_Int32 StgCache::Pos2Page( sal_Int32 nPos )
558 {
559     return ( ( nPos + nPageSize - 1 ) / nPageSize ) * nPageSize - 1;
560 }
561 
562