Coverage Report

Created: 2025-12-08 09:28

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
5.37M
    : mnPage( nPage )
37
5.37M
    , mpData( new sal_uInt8[ nSize ] )
38
5.37M
    , mnSize( nSize )
39
5.37M
{
40
5.37M
    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
5.37M
    memset( mpData.get(), 0, mnSize );
44
5.37M
}
45
46
StgPage::~StgPage()
47
5.37M
{
48
5.37M
}
49
50
rtl::Reference< StgPage > StgPage::Create( short nData, sal_Int32 nPage )
51
5.37M
{
52
5.37M
    return rtl::Reference< StgPage >( new StgPage( nData, nPage ) );
53
5.37M
}
54
55
void StgCache::SetToPage ( const rtl::Reference< StgPage >& rPage, short nOff, sal_Int32 nVal )
56
22.1M
{
57
22.1M
    if( nOff >= 0 && ( o3tl::make_unsigned(nOff) < rPage->GetSize() / sizeof( sal_Int32 ) ) )
58
22.1M
    {
59
#ifdef OSL_BIGENDIAN
60
        nVal = OSL_SWAPDWORD(nVal);
61
#endif
62
22.1M
        static_cast<sal_Int32*>(rPage->GetData())[ nOff ] = nVal;
63
22.1M
        SetDirty( rPage );
64
22.1M
    }
65
22.1M
}
66
67
bool StgPage::IsPageGreater( const StgPage *pA, const StgPage *pB )
68
3.13M
{
69
3.13M
    return pA->mnPage < pB->mnPage;
70
3.13M
}
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
206k
{
79
//    return (nFileSize >= 512) ? (nFileSize - 512) / nPageSize : 0;
80
    // #i61980# real life: last page may be incomplete, return number of *started* pages
81
206k
    return (nFileSize >= 512) ? (nFileSize - 512 + nPageSize - 1) / nPageSize : 0;
82
206k
}
83
84
StgCache::StgCache()
85
210k
   : m_nError( ERRCODE_NONE )
86
210k
   , m_nPages( 0 )
87
210k
   , m_nRef( 0 )
88
210k
   , m_nReplaceIdx( 0 )
89
210k
   , maLRUPages( 8 ) // entries in the LRU lookup
90
210k
   , m_nPageSize( 512 )
91
210k
   , m_pStorageStream( nullptr )
92
210k
   , m_pStrm( nullptr )
93
210k
   , m_bMyStream( false )
94
210k
   , m_bFile( false )
95
210k
{
96
210k
}
97
98
StgCache::~StgCache()
99
210k
{
100
210k
    Clear();
101
210k
    SetStrm( nullptr, false );
102
210k
}
103
104
void StgCache::SetPhysPageSize( short n )
105
206k
{
106
206k
    OSL_ENSURE( n >= 512, "Unexpected page size is provided!" );
107
206k
    if ( n >= 512 )
108
206k
    {
109
206k
        m_nPageSize = n;
110
206k
        sal_uInt64 nFileSize = m_pStrm->TellEnd();
111
206k
        m_nPages = lcl_GetPageCount( nFileSize, m_nPageSize );
112
206k
    }
113
206k
}
114
115
// Create a new cache element
116
117
rtl::Reference< StgPage > StgCache::Create( sal_Int32 nPg )
118
5.37M
{
119
5.37M
    rtl::Reference< StgPage > xElem( StgPage::Create( m_nPageSize, nPg ) );
120
5.37M
    maLRUPages[ m_nReplaceIdx++ % maLRUPages.size() ] = xElem;
121
5.37M
    return xElem;
122
5.37M
}
123
124
// Delete the given element
125
126
void StgCache::Erase( const rtl::Reference< StgPage > &xElem )
127
2.49M
{
128
2.49M
    OSL_ENSURE( xElem.is(), "The pointer should not be NULL!" );
129
2.49M
    if ( xElem.is() ) {
130
2.49M
        auto it = std::find_if(maLRUPages.begin(), maLRUPages.end(),
131
11.2M
            [xElem](const rtl::Reference<StgPage>& rxPage) { return rxPage.is() && rxPage->GetPage() == xElem->GetPage(); });
132
2.49M
        if (it != maLRUPages.end())
133
2.49M
            it->clear();
134
2.49M
    }
135
2.49M
}
136
137
// remove all cache elements without flushing them
138
139
void StgCache::Clear()
140
210k
{
141
210k
    maDirtyPages.clear();
142
210k
    for (auto& rxPage : maLRUPages)
143
1.68M
        rxPage.clear();
144
210k
}
145
146
// Look for a cached page
147
148
rtl::Reference< StgPage > StgCache::Find( sal_Int32 nPage )
149
100M
{
150
100M
    auto it = std::find_if(maLRUPages.begin(), maLRUPages.end(),
151
493M
        [nPage](const rtl::Reference<StgPage>& rxPage) { return rxPage.is() && rxPage->GetPage() == nPage; });
152
100M
    if (it != maLRUPages.end())
153
82.6M
        return *it;
154
18.1M
    IndexToStgPage::iterator it2 = maDirtyPages.find( nPage );
155
18.1M
    if ( it2 != maDirtyPages.end() )
156
5.95M
        return it2->second;
157
12.2M
    return rtl::Reference< StgPage >();
158
18.1M
}
159
160
// Load a page into the cache
161
162
rtl::Reference< StgPage > StgCache::Get( sal_Int32 nPage, bool bForce )
163
92.7M
{
164
92.7M
    rtl::Reference< StgPage > p = Find( nPage );
165
92.7M
    if( !p.is() )
166
4.68M
    {
167
4.68M
        p = Create( nPage );
168
4.68M
        if( !Read( nPage, p->GetData() ) && bForce )
169
2.49M
        {
170
2.49M
            Erase( p );
171
2.49M
            p.clear();
172
2.49M
            SetError( SVSTREAM_READ_ERROR );
173
2.49M
        }
174
4.68M
    }
175
92.7M
    return p;
176
92.7M
}
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
693k
{
184
693k
    rtl::Reference< StgPage > p = Find( nNew );
185
693k
    if( !p.is() )
186
690k
        p = Create( nNew );
187
693k
    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
693k
    SetDirty( p );
198
199
693k
    return p;
200
693k
}
201
202
// Historically this wrote pages in a sorted, ascending order;
203
// continue that tradition.
204
bool StgCache::Commit()
205
160k
{
206
160k
    if ( Good() ) // otherwise Write does nothing
207
160k
    {
208
160k
        std::vector< StgPage * > aToWrite;
209
160k
        aToWrite.reserve(maDirtyPages.size());
210
160k
        for (const auto& rEntry : maDirtyPages)
211
838k
            aToWrite.push_back( rEntry.second.get() );
212
213
160k
        std::sort( aToWrite.begin(), aToWrite.end(), StgPage::IsPageGreater );
214
160k
        for (StgPage* pWr : aToWrite)
215
838k
        {
216
838k
            const rtl::Reference< StgPage > pPage = pWr;
217
838k
            if ( !Write( pPage->GetPage(), pPage->GetData() ) )
218
0
                return false;
219
838k
        }
220
160k
    }
221
222
160k
    maDirtyPages.clear();
223
224
160k
    m_pStrm->Flush();
225
160k
    SetError( m_pStrm->GetError() );
226
227
160k
    return true;
228
160k
}
229
230
// Set a stream
231
232
void StgCache::SetStrm( SvStream* p, bool bMy )
233
421k
{
234
421k
    if( m_pStorageStream )
235
0
    {
236
0
        m_pStorageStream->ReleaseRef();
237
0
        m_pStorageStream = nullptr;
238
0
    }
239
240
421k
    if( m_bMyStream )
241
0
        delete m_pStrm;
242
421k
    m_pStrm = p;
243
421k
    m_bMyStream = bMy;
244
421k
}
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
25.6M
{
268
25.6M
    assert( m_pStrm && m_pStrm->IsWritable() );
269
25.6M
    maDirtyPages[ rPage->GetPage() ] = rPage;
270
25.6M
}
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
210k
{
303
210k
    if( m_bFile )
304
0
    {
305
0
        static_cast<SvFileStream*>(m_pStrm)->Close();
306
0
        SetError( m_pStrm->GetError() );
307
0
    }
308
210k
}
309
310
// low level I/O
311
312
bool StgCache::Read( sal_Int32 nPage, void* pBuf )
313
9.32M
{
314
9.32M
    sal_uInt32 nRead = 0, nBytes = m_nPageSize;
315
9.32M
    if( Good() )
316
6.83M
    {
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
6.83M
        if ( nPage > m_nPages )
322
28.3k
            SetError( SVSTREAM_READ_ERROR );
323
6.80M
        else if ( nPage < m_nPages )
324
6.76M
        {
325
6.76M
            sal_uInt32 nPos;
326
6.76M
            sal_Int32 nPg2;
327
            // fixed address and size for the header
328
6.76M
            if( nPage == -1 )
329
1.23k
            {
330
1.23k
                nPos = 0;
331
1.23k
                nPg2 = 1;
332
1.23k
                nBytes = 512;
333
1.23k
            }
334
6.76M
            else
335
6.76M
            {
336
6.76M
                nPos = Page2Pos(nPage);
337
6.76M
                nPg2 = ((nPage + 1) > m_nPages) ? m_nPages - nPage : 1;
338
6.76M
            }
339
340
6.76M
            if (m_pStrm->Tell() != nPos)
341
1.89M
                m_pStrm->Seek(nPos);
342
343
6.76M
            if (nPg2 != 1)
344
0
                SetError(SVSTREAM_READ_ERROR);
345
6.76M
            else
346
6.76M
            {
347
6.76M
                nRead = m_pStrm->ReadBytes(pBuf, nBytes);
348
6.76M
                SetError(m_pStrm->GetError());
349
6.76M
            }
350
6.76M
        }
351
6.83M
    }
352
353
9.32M
    if (!Good())
354
2.51M
        return false;
355
356
6.80M
    if (nRead != nBytes)
357
119k
        memset(static_cast<sal_uInt8*>(pBuf) + nRead, 0, nBytes - nRead);
358
6.80M
    return true;
359
9.32M
}
360
361
bool StgCache::Write( sal_Int32 nPage, void const * pBuf )
362
3.02M
{
363
3.02M
    if( Good() )
364
3.02M
    {
365
3.02M
        sal_uInt32 nPos = Page2Pos( nPage );
366
3.02M
        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
3.02M
        if( nPage == -1 )
371
0
        {
372
0
            nPos = 0;
373
0
            nBytes = 512;
374
0
        }
375
3.02M
        if( m_pStrm->Tell() != nPos )
376
288k
        {
377
288k
            m_pStrm->Seek(nPos);
378
288k
        }
379
3.02M
        size_t nRes = m_pStrm->WriteBytes( pBuf, nBytes );
380
3.02M
        if( nRes != nBytes )
381
0
            SetError( SVSTREAM_WRITE_ERROR );
382
3.02M
        else
383
3.02M
            SetError( m_pStrm->GetError() );
384
3.02M
    }
385
3.02M
    return Good();
386
3.02M
}
387
388
// set the file size in pages
389
390
bool StgCache::SetSize( sal_Int32 n )
391
338k
{
392
    // Add the file header
393
338k
    sal_Int32 nSize = n * m_nPageSize + 512;
394
338k
    m_pStrm->SetStreamSize( nSize );
395
338k
    SetError( m_pStrm->GetError() );
396
338k
    if( !m_nError )
397
338k
        m_nPages = n;
398
338k
    return Good();
399
338k
}
400
401
void StgCache::SetError( ErrCode n )
402
14.7M
{
403
14.7M
    if( n && !m_nError )
404
141k
        m_nError = n;
405
14.7M
}
406
407
void StgCache::ResetError()
408
556k
{
409
556k
    m_nError = ERRCODE_NONE;
410
556k
    m_pStrm->ResetError();
411
556k
}
412
413
void StgCache::MoveError( StorageBase const & r )
414
99.9M
{
415
99.9M
    if( m_nError != ERRCODE_NONE )
416
141k
    {
417
141k
        r.SetError( m_nError );
418
141k
        ResetError();
419
141k
    }
420
99.9M
}
421
422
// Utility functions
423
424
sal_Int32 StgCache::Page2Pos( sal_Int32 nPage ) const
425
9.78M
{
426
9.78M
    if( nPage < 0 ) nPage = 0;
427
9.78M
    return( nPage * m_nPageSize ) + m_nPageSize;
428
9.78M
}
429
430
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */