Coverage Report

Created: 2026-10-28 07:09

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