Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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: */