1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
21 #include <o3tl/safeint.hxx>
22 #include <osl/endian.h>
23 #include <osl/diagnose.h>
25 #include <sot/stg.hxx>
26 #include "stgcache.hxx"
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
)
37 , mpData( new sal_uInt8
[ 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
);
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
) ) )
60 nVal
= OSL_SWAPDWORD(nVal
);
62 static_cast<sal_Int32
*>(rPage
->GetData())[ nOff
] = nVal
;
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
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;
85 : m_nError( ERRCODE_NONE
)
89 , maLRUPages( 8 ) // entries in the LRU lookup
91 , m_pStorageStream( nullptr )
93 , m_bMyStream( false )
101 SetStrm( nullptr, false );
104 void StgCache::SetPhysPageSize( short n
)
106 OSL_ENSURE( n
>= 512, "Unexpected page size is provided!" );
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
;
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!" );
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())
137 // remove all cache elements without flushing them
139 void StgCache::Clear()
141 maDirtyPages
.clear();
142 for (auto& rxPage
: maLRUPages
)
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())
154 IndexToStgPage::iterator it2
= maDirtyPages
.find( nPage
);
155 if ( it2
!= maDirtyPages
.end() )
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
);
168 if( !Read( nPage
, p
->GetData() ) && bForce
)
172 SetError( SVSTREAM_READ_ERROR
);
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
);
189 // old page: we must have this data!
190 rtl::Reference
< StgPage
> q
= Get( nOld
, true );
193 OSL_ENSURE( p
->GetSize() == q
->GetSize(), "Unexpected page size!" );
194 memcpy( p
->GetData(), q
->GetData(), p
->GetSize() );
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() ) )
222 maDirtyPages
.clear();
225 SetError( m_pStrm
->GetError() );
232 void StgCache::SetStrm( SvStream
* p
, bool bMy
)
234 if( m_pStorageStream
)
236 m_pStorageStream
->ReleaseRef();
237 m_pStorageStream
= nullptr;
246 void StgCache::SetStrm( UCBStorageStream
* pStgStream
)
248 if( m_pStorageStream
)
249 m_pStorageStream
->ReleaseRef();
250 m_pStorageStream
= pStgStream
;
257 if ( m_pStorageStream
)
259 m_pStorageStream
->AddFirstRef();
260 m_pStrm
= m_pStorageStream
->GetModifySvStream();
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() )
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
);
297 SetError( bAccessDenied
? ERRCODE_IO_ACCESSDENIED
: m_pStrm
->GetError() );
301 void StgCache::Close()
305 static_cast<SvFileStream
*>(m_pStrm
)->Close();
306 SetError( m_pStrm
->GetError() );
312 bool StgCache::Read( sal_Int32 nPage
, void* pBuf
)
314 sal_uInt32 nRead
= 0, nBytes
= m_nPageSize
;
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
)
327 // fixed address and size for the header
336 nPos
= Page2Pos(nPage
);
337 nPg2
= ((nPage
+ 1) > m_nPages
) ? m_nPages
- nPage
: 1;
340 if (m_pStrm
->Tell() != nPos
)
344 SetError(SVSTREAM_READ_ERROR
);
347 nRead
= m_pStrm
->ReadBytes(pBuf
, nBytes
);
348 SetError(m_pStrm
->GetError());
357 memset(static_cast<sal_uInt8
*>(pBuf
) + nRead
, 0, nBytes
- nRead
);
361 bool StgCache::Write( sal_Int32 nPage
, void const * pBuf
)
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
375 if( m_pStrm
->Tell() != nPos
)
379 size_t nRes
= m_pStrm
->WriteBytes( pBuf
, nBytes
);
381 SetError( SVSTREAM_WRITE_ERROR
);
383 SetError( m_pStrm
->GetError() );
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() );
401 void StgCache::SetError( ErrCode 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
);
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: */