Coverage Report

Created: 2025-06-13 06:29

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