Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/port/cpl_vsil_cache.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  VSI Virtual File System
4
 * Purpose:  Implementation of caching IO layer.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2011, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2011-2014, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "cpl_vsi_virtual.h"
16
17
#include <cstddef>
18
#include <cstring>
19
20
#include <algorithm>
21
#include <limits>
22
#include <map>
23
#include <memory>
24
#include <utility>
25
26
#include "cpl_conv.h"
27
#include "cpl_error.h"
28
#include "cpl_vsi.h"
29
#include "cpl_vsi_virtual.h"
30
#include "cpl_mem_cache.h"
31
#include "cpl_noncopyablevector.h"
32
33
//! @cond Doxygen_Suppress
34
35
/************************************************************************/
36
/* ==================================================================== */
37
/*                             VSICachedFile                            */
38
/* ==================================================================== */
39
/************************************************************************/
40
41
class VSICachedFile final : public VSIVirtualHandle
42
{
43
    CPL_DISALLOW_COPY_ASSIGN(VSICachedFile)
44
45
  public:
46
    VSICachedFile(VSIVirtualHandle *poBaseHandle, size_t nChunkSize,
47
                  size_t nCacheSize);
48
49
    ~VSICachedFile() override
50
1.13k
    {
51
1.13k
        VSICachedFile::Close();
52
1.13k
    }
53
54
    bool LoadBlocks(vsi_l_offset nStartBlock, size_t nBlockCount, void *pBuffer,
55
                    size_t nBufferSize);
56
57
    VSIVirtualHandleUniquePtr m_poBase{};
58
59
    vsi_l_offset m_nOffset = 0;
60
    vsi_l_offset m_nFileSize = 0;
61
62
    size_t m_nChunkSize = 0;
63
    lru11::Cache<vsi_l_offset, cpl::NonCopyableVector<GByte>>
64
        m_oCache;  // can only been initialized in constructor
65
66
    bool m_bEOF = false;
67
    bool m_bError = false;
68
69
    int Seek(vsi_l_offset nOffset, int nWhence) override;
70
    vsi_l_offset Tell() override;
71
    size_t Read(void *pBuffer, size_t nBytes) override;
72
    int ReadMultiRange(int nRanges, void **ppData,
73
                       const vsi_l_offset *panOffsets,
74
                       const size_t *panSizes) override;
75
76
    void AdviseRead(int nRanges, const vsi_l_offset *panOffsets,
77
                    const size_t *panSizes) override
78
0
    {
79
0
        m_poBase->AdviseRead(nRanges, panOffsets, panSizes);
80
0
    }
81
82
    size_t Write(const void *pBuffer, size_t nBytes) override;
83
    void ClearErr() override;
84
    int Eof() override;
85
    int Error() override;
86
    int Flush() override;
87
    int Close() override;
88
89
    void *GetNativeFileDescriptor() override
90
0
    {
91
0
        return m_poBase->GetNativeFileDescriptor();
92
0
    }
93
94
    bool HasPRead() const override
95
0
    {
96
0
        return m_poBase->HasPRead();
97
0
    }
98
99
    size_t PRead(void *pBuffer, size_t nSize,
100
                 vsi_l_offset nOffset) const override
101
0
    {
102
0
        return m_poBase->PRead(pBuffer, nSize, nOffset);
103
0
    }
104
};
105
106
/************************************************************************/
107
/*                            GetCacheMax()                             */
108
/************************************************************************/
109
110
static size_t GetCacheMax(size_t nCacheSize)
111
1.13k
{
112
1.13k
    if (nCacheSize)
113
13
    {
114
13
        return nCacheSize;
115
13
    }
116
117
1.11k
    const char *pszCacheSize = CPLGetConfigOption("VSI_CACHE_SIZE", "25000000");
118
1.11k
    GIntBig nMemorySize;
119
1.11k
    bool bUnitSpecified;
120
1.11k
    if (CPLParseMemorySize(pszCacheSize, &nMemorySize, &bUnitSpecified) !=
121
1.11k
        CE_None)
122
0
    {
123
0
        CPLError(
124
0
            CE_Failure, CPLE_IllegalArg,
125
0
            "Failed to parse value of VSI_CACHE_SIZE. Using default of 25MB");
126
0
        nMemorySize = 25000000;
127
0
    }
128
1.11k
    else if (static_cast<size_t>(nMemorySize) >
129
1.11k
             std::numeric_limits<size_t>::max() / 2)
130
0
    {
131
0
        nMemorySize =
132
0
            static_cast<GIntBig>(std::numeric_limits<size_t>::max() / 2);
133
0
    }
134
135
1.11k
    return static_cast<size_t>(nMemorySize);
136
1.13k
}
137
138
/************************************************************************/
139
/*                           VSICachedFile()                            */
140
/************************************************************************/
141
142
VSICachedFile::VSICachedFile(VSIVirtualHandle *poBaseHandle, size_t nChunkSize,
143
                             size_t nCacheSize)
144
1.13k
    : m_poBase(poBaseHandle),
145
1.13k
      m_nChunkSize(nChunkSize ? nChunkSize : VSI_CACHED_DEFAULT_CHUNK_SIZE),
146
1.13k
      m_oCache{cpl::div_round_up(GetCacheMax(nCacheSize), m_nChunkSize), 0}
147
1.13k
{
148
1.13k
    m_poBase->Seek(0, SEEK_END);
149
1.13k
    m_nFileSize = m_poBase->Tell();
150
1.13k
}
151
152
/************************************************************************/
153
/*                               Close()                                */
154
/************************************************************************/
155
156
int VSICachedFile::Close()
157
158
2.26k
{
159
2.26k
    m_oCache.clear();
160
2.26k
    m_poBase.reset();
161
162
2.26k
    return 0;
163
2.26k
}
164
165
/************************************************************************/
166
/*                                Seek()                                */
167
/************************************************************************/
168
169
int VSICachedFile::Seek(vsi_l_offset nReqOffset, int nWhence)
170
171
819
{
172
819
    m_bEOF = false;
173
174
819
    if (nWhence == SEEK_SET)
175
428
    {
176
        // Use offset directly.
177
428
    }
178
391
    else if (nWhence == SEEK_CUR)
179
0
    {
180
0
        nReqOffset += m_nOffset;
181
0
    }
182
391
    else if (nWhence == SEEK_END)
183
391
    {
184
391
        nReqOffset += m_nFileSize;
185
391
    }
186
187
819
    m_nOffset = nReqOffset;
188
189
819
    return 0;
190
819
}
191
192
/************************************************************************/
193
/*                                Tell()                                */
194
/************************************************************************/
195
196
vsi_l_offset VSICachedFile::Tell()
197
198
391
{
199
391
    return m_nOffset;
200
391
}
201
202
/************************************************************************/
203
/*                             LoadBlocks()                             */
204
/*                                                                      */
205
/*      Load the desired set of blocks.  Use pBuffer as a temporary     */
206
/*      buffer if it would be helpful.                                  */
207
/************************************************************************/
208
209
bool VSICachedFile::LoadBlocks(vsi_l_offset nStartBlock, size_t nBlockCount,
210
                               void *pBuffer, size_t nBufferSize)
211
212
1.05k
{
213
1.05k
    if (nBlockCount == 0)
214
0
        return true;
215
216
    /* -------------------------------------------------------------------- */
217
    /*      When we want to load only one block, we can directly load it    */
218
    /*      into the target buffer with no concern about intermediaries.    */
219
    /* -------------------------------------------------------------------- */
220
1.05k
    if (nBlockCount == 1)
221
912
    {
222
912
        if (m_poBase->Seek(static_cast<vsi_l_offset>(nStartBlock) *
223
912
                               m_nChunkSize,
224
912
                           SEEK_SET) != 0)
225
0
        {
226
0
            return false;
227
0
        }
228
229
912
        try
230
912
        {
231
912
            cpl::NonCopyableVector<GByte> oData(m_nChunkSize);
232
912
            const auto nDataRead =
233
912
                m_poBase->Read(oData.data(), 1, m_nChunkSize);
234
912
            if (nDataRead == 0)
235
912
                return false;
236
0
            if (nDataRead < m_nChunkSize && m_poBase->Error())
237
0
                m_bError = true;
238
0
            oData.resize(nDataRead);
239
240
0
            m_oCache.insert(nStartBlock, std::move(oData));
241
0
        }
242
912
        catch (const std::exception &)
243
912
        {
244
0
            CPLError(CE_Failure, CPLE_OutOfMemory,
245
0
                     "Out of memory situation in VSICachedFile::LoadBlocks()");
246
0
            return false;
247
0
        }
248
249
0
        return true;
250
912
    }
251
252
    /* -------------------------------------------------------------------- */
253
    /*      If the buffer is quite large but not quite large enough to      */
254
    /*      hold all the blocks we will take the pain of splitting the      */
255
    /*      io request in two in order to avoid allocating a large          */
256
    /*      temporary buffer.                                               */
257
    /* -------------------------------------------------------------------- */
258
147
    if (nBufferSize > m_nChunkSize * 20 &&
259
106
        nBufferSize < nBlockCount * m_nChunkSize)
260
29
    {
261
29
        if (!LoadBlocks(nStartBlock, 2, pBuffer, nBufferSize))
262
29
            return false;
263
264
0
        return LoadBlocks(nStartBlock + 2, nBlockCount - 2, pBuffer,
265
0
                          nBufferSize);
266
29
    }
267
268
118
    if (m_poBase->Seek(static_cast<vsi_l_offset>(nStartBlock) * m_nChunkSize,
269
118
                       SEEK_SET) != 0)
270
0
        return false;
271
272
    /* -------------------------------------------------------------------- */
273
    /*      Do we need to allocate our own buffer?                          */
274
    /* -------------------------------------------------------------------- */
275
118
    GByte *pabyWorkBuffer = static_cast<GByte *>(pBuffer);
276
277
118
    if (nBufferSize < m_nChunkSize * nBlockCount)
278
33
    {
279
33
        pabyWorkBuffer = static_cast<GByte *>(
280
33
            VSI_MALLOC_VERBOSE(m_nChunkSize * nBlockCount));
281
33
        if (pabyWorkBuffer == nullptr)
282
0
            return false;
283
33
    }
284
285
    /* -------------------------------------------------------------------- */
286
    /*      Read the whole request into the working buffer.                 */
287
    /* -------------------------------------------------------------------- */
288
289
118
    const size_t nToRead = nBlockCount * m_nChunkSize;
290
118
    const size_t nDataRead = m_poBase->Read(pabyWorkBuffer, 1, nToRead);
291
118
    if (nDataRead < nToRead && m_poBase->Error())
292
0
        m_bError = true;
293
294
118
    bool ret = true;
295
118
    if (nToRead > nDataRead + m_nChunkSize - 1)
296
118
    {
297
118
        size_t nNewBlockCount = cpl::div_round_up(nDataRead, m_nChunkSize);
298
118
        if (nNewBlockCount < nBlockCount)
299
118
        {
300
118
            nBlockCount = nNewBlockCount;
301
118
            ret = false;
302
118
        }
303
118
    }
304
305
118
    for (size_t i = 0; i < nBlockCount; i++)
306
0
    {
307
0
        const vsi_l_offset iBlock = nStartBlock + i;
308
309
0
        const auto nDataFilled = (nDataRead >= (i + 1) * m_nChunkSize)
310
0
                                     ? m_nChunkSize
311
0
                                     : nDataRead - i * m_nChunkSize;
312
0
        try
313
0
        {
314
0
            cpl::NonCopyableVector<GByte> oData(nDataFilled);
315
316
0
            memcpy(oData.data(), pabyWorkBuffer + i * m_nChunkSize,
317
0
                   nDataFilled);
318
319
0
            m_oCache.insert(iBlock, std::move(oData));
320
0
        }
321
0
        catch (const std::exception &)
322
0
        {
323
0
            CPLError(CE_Failure, CPLE_OutOfMemory,
324
0
                     "Out of memory situation in VSICachedFile::LoadBlocks()");
325
0
            ret = false;
326
0
            break;
327
0
        }
328
0
    }
329
330
118
    if (pabyWorkBuffer != pBuffer)
331
33
        CPLFree(pabyWorkBuffer);
332
333
118
    return ret;
334
118
}
335
336
/************************************************************************/
337
/*                                Read()                                */
338
/************************************************************************/
339
340
size_t VSICachedFile::Read(void *pBuffer, size_t nBytes)
341
342
809
{
343
809
    if (nBytes == 0)
344
294
        return 0;
345
515
    const size_t nRequestedBytes = nBytes;
346
347
    // nFileSize might be set wrongly to 0 by underlying layers, such as
348
    // /vsicurl_streaming/https://query.data.world/s/jgsghstpphjhicstradhy5kpjwrnfy
349
515
    if (m_nFileSize > 0 && m_nOffset >= m_nFileSize)
350
0
    {
351
0
        m_bEOF = true;
352
0
        return 0;
353
0
    }
354
355
    /* ==================================================================== */
356
    /*      Make sure the cache is loaded for the whole request region.     */
357
    /* ==================================================================== */
358
515
    const vsi_l_offset nStartBlock = m_nOffset / m_nChunkSize;
359
    // Calculate last block
360
515
    const vsi_l_offset nLastBlock = m_nFileSize / m_nChunkSize;
361
515
    vsi_l_offset nEndBlock = (m_nOffset + nRequestedBytes - 1) / m_nChunkSize;
362
363
    // if nLastBlock is not 0 consider the min value to avoid out-of-range reads
364
515
    if (nLastBlock != 0 && nEndBlock > nLastBlock)
365
0
    {
366
0
        nEndBlock = nLastBlock;
367
0
    }
368
369
515
    for (vsi_l_offset iBlock = nStartBlock; iBlock <= nEndBlock; iBlock++)
370
515
    {
371
515
        if (!m_oCache.contains(iBlock))
372
515
        {
373
515
            size_t nBlocksToLoad = 1;
374
15.0k
            while (iBlock + nBlocksToLoad <= nEndBlock &&
375
14.5k
                   !m_oCache.contains(iBlock + nBlocksToLoad))
376
14.5k
            {
377
14.5k
                nBlocksToLoad++;
378
14.5k
            }
379
380
515
            if (!LoadBlocks(iBlock, nBlocksToLoad, pBuffer, nRequestedBytes))
381
515
                break;
382
515
        }
383
515
    }
384
385
    /* ==================================================================== */
386
    /*      Copy data into the target buffer to the extent possible.        */
387
    /* ==================================================================== */
388
515
    size_t nAmountCopied = 0;
389
390
515
    while (nAmountCopied < nRequestedBytes)
391
515
    {
392
515
        const vsi_l_offset iBlock = (m_nOffset + nAmountCopied) / m_nChunkSize;
393
515
        const cpl::NonCopyableVector<GByte> *poData = m_oCache.getPtr(iBlock);
394
515
        if (poData == nullptr)
395
515
        {
396
            // We can reach that point when the amount to read exceeds
397
            // the cache size.
398
515
            LoadBlocks(iBlock, 1, static_cast<GByte *>(pBuffer) + nAmountCopied,
399
515
                       std::min(nRequestedBytes - nAmountCopied, m_nChunkSize));
400
515
            poData = m_oCache.getPtr(iBlock);
401
515
            if (poData == nullptr)
402
515
            {
403
515
                break;
404
515
            }
405
515
        }
406
407
0
        const vsi_l_offset nStartOffset =
408
0
            static_cast<vsi_l_offset>(iBlock) * m_nChunkSize;
409
0
        if (nStartOffset + poData->size() < nAmountCopied + m_nOffset)
410
0
            break;
411
0
        const size_t nThisCopy =
412
0
            std::min(nRequestedBytes - nAmountCopied,
413
0
                     static_cast<size_t>(((nStartOffset + poData->size()) -
414
0
                                          nAmountCopied - m_nOffset)));
415
0
        if (nThisCopy == 0)
416
0
            break;
417
418
0
        memcpy(static_cast<GByte *>(pBuffer) + nAmountCopied,
419
0
               poData->data() + (m_nOffset + nAmountCopied) - nStartOffset,
420
0
               nThisCopy);
421
422
0
        nAmountCopied += nThisCopy;
423
0
    }
424
425
515
    m_nOffset += nAmountCopied;
426
427
515
    const size_t nRet = nAmountCopied;
428
515
    if (nRet != nBytes && !m_bError)
429
515
        m_bEOF = true;
430
515
    return nRet;
431
515
}
432
433
/************************************************************************/
434
/*                           ReadMultiRange()                           */
435
/************************************************************************/
436
437
int VSICachedFile::ReadMultiRange(int const nRanges, void **const ppData,
438
                                  const vsi_l_offset *const panOffsets,
439
                                  const size_t *const panSizes)
440
0
{
441
    // If the base is /vsicurl/
442
0
    return m_poBase->ReadMultiRange(nRanges, ppData, panOffsets, panSizes);
443
0
}
444
445
/************************************************************************/
446
/*                               Write()                                */
447
/************************************************************************/
448
449
size_t VSICachedFile::Write(const void * /* pBuffer */, size_t /*nBytes */)
450
0
{
451
0
    return 0;
452
0
}
453
454
/************************************************************************/
455
/*                              ClearErr()                              */
456
/************************************************************************/
457
458
void VSICachedFile::ClearErr()
459
460
0
{
461
0
    m_poBase->ClearErr();
462
0
    m_bEOF = false;
463
0
    m_bError = false;
464
0
}
465
466
/************************************************************************/
467
/*                               Error()                                */
468
/************************************************************************/
469
470
int VSICachedFile::Error()
471
472
0
{
473
0
    return m_bError;
474
0
}
475
476
/************************************************************************/
477
/*                                Eof()                                 */
478
/************************************************************************/
479
480
int VSICachedFile::Eof()
481
482
0
{
483
0
    return m_bEOF;
484
0
}
485
486
/************************************************************************/
487
/*                               Flush()                                */
488
/************************************************************************/
489
490
int VSICachedFile::Flush()
491
492
0
{
493
0
    return 0;
494
0
}
495
496
/************************************************************************/
497
/*                      VSICachedFilesystemHandler                      */
498
/************************************************************************/
499
500
class VSICachedFilesystemHandler final : public VSIFilesystemHandler
501
{
502
    static bool AnalyzeFilename(const char *pszFilename,
503
                                std::string &osUnderlyingFilename,
504
                                size_t &nChunkSize, size_t &nCacheSize);
505
506
  public:
507
    VSIVirtualHandleUniquePtr Open(const char *pszFilename,
508
                                   const char *pszAccess, bool bSetError,
509
                                   CSLConstList papszOptions) override;
510
    int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
511
             int nFlags) override;
512
    char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
513
};
514
515
/************************************************************************/
516
/*                             ParseSize()                              */
517
/************************************************************************/
518
519
static bool ParseSize(const char *pszKey, const char *pszValue, size_t nMaxVal,
520
                      size_t &nOutVal)
521
1.61k
{
522
1.61k
    char *end = nullptr;
523
1.61k
    auto nVal = std::strtoull(pszValue, &end, 10);
524
1.61k
    if (!end || end == pszValue || nVal >= nMaxVal)
525
160
    {
526
160
        CPLError(
527
160
            CE_Failure, CPLE_IllegalArg,
528
160
            "Invalid value for %s: %s. Max supported value = " CPL_FRMT_GUIB,
529
160
            pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
530
160
        return false;
531
160
    }
532
1.45k
    if (*end != '\0')
533
823
    {
534
823
        if (strcmp(end, "KB") == 0)
535
310
        {
536
310
            if (nVal > nMaxVal / 1024)
537
111
            {
538
111
                CPLError(CE_Failure, CPLE_IllegalArg,
539
111
                         "Invalid value for %s: %s. Max supported value "
540
111
                         "= " CPL_FRMT_GUIB,
541
111
                         pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
542
111
                return false;
543
111
            }
544
199
            nVal *= 1024;
545
199
        }
546
513
        else if (strcmp(end, "MB") == 0)
547
311
        {
548
311
            if (nVal > nMaxVal / (1024 * 1024))
549
5
            {
550
5
                CPLError(CE_Failure, CPLE_IllegalArg,
551
5
                         "Invalid value for %s: %s. Max supported value "
552
5
                         "= " CPL_FRMT_GUIB,
553
5
                         pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
554
5
                return false;
555
5
            }
556
306
            nVal *= (1024 * 1024);
557
306
        }
558
202
        else
559
202
        {
560
202
            CPLError(CE_Failure, CPLE_IllegalArg, "Invalid value for %s: %s",
561
202
                     pszKey, pszValue);
562
202
            return false;
563
202
        }
564
823
    }
565
1.13k
    nOutVal = static_cast<size_t>(nVal);
566
1.13k
    return true;
567
1.45k
}
568
569
/************************************************************************/
570
/*                          AnalyzeFilename()                           */
571
/************************************************************************/
572
573
bool VSICachedFilesystemHandler::AnalyzeFilename(
574
    const char *pszFilename, std::string &osUnderlyingFilename,
575
    size_t &nChunkSize, size_t &nCacheSize)
576
4.08k
{
577
578
4.08k
    if (!STARTS_WITH(pszFilename, "/vsicached?"))
579
1
        return false;
580
581
4.08k
    const CPLStringList aosTokens(
582
4.08k
        CSLTokenizeString2(pszFilename + strlen("/vsicached?"), "&", 0));
583
584
4.08k
    osUnderlyingFilename.clear();
585
4.08k
    nChunkSize = 0;
586
4.08k
    nCacheSize = 0;
587
588
30.0k
    for (int i = 0; i < aosTokens.size(); ++i)
589
26.4k
    {
590
26.4k
        char *pszUnescaped =
591
26.4k
            CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
592
26.4k
        std::string osUnescaped(pszUnescaped);
593
26.4k
        CPLFree(pszUnescaped);
594
26.4k
        char *pszKey = nullptr;
595
26.4k
        const char *pszValue = CPLParseNameValue(osUnescaped.c_str(), &pszKey);
596
26.4k
        if (pszKey && pszValue)
597
11.9k
        {
598
11.9k
            if (strcmp(pszKey, "file") == 0)
599
3.19k
            {
600
3.19k
                osUnderlyingFilename = pszValue;
601
3.19k
            }
602
8.76k
            else if (strcmp(pszKey, "chunk_size") == 0)
603
480
            {
604
480
                if (!ParseSize(pszKey, pszValue, 1024 * 1024 * 1024,
605
480
                               nChunkSize))
606
52
                {
607
52
                    CPLFree(pszKey);
608
52
                    return false;
609
52
                }
610
480
            }
611
8.28k
            else if (strcmp(pszKey, "cache_size") == 0)
612
1.13k
            {
613
1.13k
                if (!ParseSize(pszKey, pszValue,
614
1.13k
                               std::numeric_limits<size_t>::max(), nCacheSize))
615
426
                {
616
426
                    CPLFree(pszKey);
617
426
                    return false;
618
426
                }
619
1.13k
            }
620
7.14k
            else
621
7.14k
            {
622
7.14k
                CPLError(CE_Warning, CPLE_NotSupported,
623
7.14k
                         "Unsupported option: %s", pszKey);
624
7.14k
            }
625
11.9k
        }
626
25.9k
        CPLFree(pszKey);
627
25.9k
    }
628
629
3.60k
    if (osUnderlyingFilename.empty())
630
1.29k
    {
631
1.29k
        CPLError(CE_Warning, CPLE_NotSupported, "Missing 'file' option");
632
1.29k
    }
633
634
3.60k
    return !osUnderlyingFilename.empty();
635
4.08k
}
636
637
/************************************************************************/
638
/*                                Open()                                */
639
/************************************************************************/
640
641
VSIVirtualHandleUniquePtr
642
VSICachedFilesystemHandler::Open(const char *pszFilename, const char *pszAccess,
643
                                 bool bSetError, CSLConstList papszOptions)
644
1.95k
{
645
1.95k
    std::string osUnderlyingFilename;
646
1.95k
    size_t nChunkSize = 0;
647
1.95k
    size_t nCacheSize = 0;
648
1.95k
    if (!AnalyzeFilename(pszFilename, osUnderlyingFilename, nChunkSize,
649
1.95k
                         nCacheSize))
650
604
        return nullptr;
651
1.35k
    if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "rb") != 0)
652
0
    {
653
0
        if (bSetError)
654
0
        {
655
0
            VSIError(VSIE_FileError,
656
0
                     "/vsicached? supports only 'r' and 'rb' access modes");
657
0
        }
658
0
        return nullptr;
659
0
    }
660
661
1.35k
    auto fp = VSIFilesystemHandler::OpenStatic(
662
1.35k
        osUnderlyingFilename.c_str(), pszAccess, bSetError, papszOptions);
663
1.35k
    if (!fp)
664
222
        return nullptr;
665
1.13k
    return VSIVirtualHandleUniquePtr(
666
1.13k
        VSICreateCachedFile(fp.release(), nChunkSize, nCacheSize));
667
1.35k
}
668
669
/************************************************************************/
670
/*                                Stat()                                */
671
/************************************************************************/
672
673
int VSICachedFilesystemHandler::Stat(const char *pszFilename,
674
                                     VSIStatBufL *pStatBuf, int nFlags)
675
2.12k
{
676
2.12k
    std::string osUnderlyingFilename;
677
2.12k
    size_t nChunkSize = 0;
678
2.12k
    size_t nCacheSize = 0;
679
2.12k
    if (!AnalyzeFilename(pszFilename, osUnderlyingFilename, nChunkSize,
680
2.12k
                         nCacheSize))
681
1.16k
        return -1;
682
960
    return VSIStatExL(osUnderlyingFilename.c_str(), pStatBuf, nFlags);
683
2.12k
}
684
685
/************************************************************************/
686
/*                             ReadDirEx()                              */
687
/************************************************************************/
688
689
char **VSICachedFilesystemHandler::ReadDirEx(const char *pszDirname,
690
                                             int nMaxFiles)
691
0
{
692
0
    std::string osUnderlyingFilename;
693
0
    size_t nChunkSize = 0;
694
0
    size_t nCacheSize = 0;
695
0
    if (!AnalyzeFilename(pszDirname, osUnderlyingFilename, nChunkSize,
696
0
                         nCacheSize))
697
0
        return nullptr;
698
0
    return VSIReadDirEx(osUnderlyingFilename.c_str(), nMaxFiles);
699
0
}
700
701
//! @endcond
702
703
/************************************************************************/
704
/*                        VSICreateCachedFile()                         */
705
/************************************************************************/
706
707
/** Wraps a file handle in another one, which has caching for read-operations.
708
 *
709
 * This takes a virtual file handle and returns a new handle that caches
710
 * read-operations on the input file handle. The cache is RAM based and
711
 * the content of the cache is discarded when the file handle is closed.
712
 * The cache is a least-recently used lists of blocks of 32KB each.
713
 *
714
 * @param poBaseHandle base handle
715
 * @param nChunkSize chunk size, in bytes. If 0, defaults to 32 KB
716
 * @param nCacheSize total size of the cache for the file, in bytes.
717
 *                   If 0, defaults to the value of the VSI_CACHE_SIZE
718
 *                   configuration option, which defaults to 25 MB.
719
 * @return a new handle
720
 */
721
VSIVirtualHandle *VSICreateCachedFile(VSIVirtualHandle *poBaseHandle,
722
                                      size_t nChunkSize, size_t nCacheSize)
723
724
1.13k
{
725
1.13k
    return new VSICachedFile(poBaseHandle, nChunkSize, nCacheSize);
726
1.13k
}
727
728
/************************************************************************/
729
/*                    VSIInstallCachedFileHandler()                     */
730
/************************************************************************/
731
732
/*!
733
 \brief Install /vsicached? file system handler
734
735
 \verbatim embed:rst
736
 See :ref:`/vsicached? documentation <vsicached>`
737
 \endverbatim
738
739
 @since GDAL 3.8.0
740
 */
741
void VSIInstallCachedFileHandler(void)
742
3
{
743
3
    VSIFileManager::InstallHandler(
744
3
        "/vsicached?", std::make_shared<VSICachedFilesystemHandler>());
745
3
}