LCOV - code coverage report
Current view: top level - port - cpl_vsil_cache.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 214 252 84.9 %
Date: 2026-02-06 10:43:15 Functions: 20 26 76.9 %

          Line data    Source code
       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          64 :     ~VSICachedFile() override
      50          32 :     {
      51          32 :         VSICachedFile::Close();
      52          64 :     }
      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           0 :     void AdviseRead(int nRanges, const vsi_l_offset *panOffsets,
      77             :                     const size_t *panSizes) override
      78             :     {
      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           0 :     void *GetNativeFileDescriptor() override
      90             :     {
      91           0 :         return m_poBase->GetNativeFileDescriptor();
      92             :     }
      93             : 
      94           0 :     bool HasPRead() const override
      95             :     {
      96           0 :         return m_poBase->HasPRead();
      97             :     }
      98             : 
      99           0 :     size_t PRead(void *pBuffer, size_t nSize,
     100             :                  vsi_l_offset nOffset) const override
     101             :     {
     102           0 :         return m_poBase->PRead(pBuffer, nSize, nOffset);
     103             :     }
     104             : };
     105             : 
     106             : /************************************************************************/
     107             : /*                            GetCacheMax()                             */
     108             : /************************************************************************/
     109             : 
     110          32 : static size_t GetCacheMax(size_t nCacheSize)
     111             : {
     112          32 :     if (nCacheSize)
     113             :     {
     114           0 :         return nCacheSize;
     115             :     }
     116             : 
     117          32 :     const char *pszCacheSize = CPLGetConfigOption("VSI_CACHE_SIZE", "25000000");
     118             :     GIntBig nMemorySize;
     119             :     bool bUnitSpecified;
     120          32 :     if (CPLParseMemorySize(pszCacheSize, &nMemorySize, &bUnitSpecified) !=
     121             :         CE_None)
     122             :     {
     123           0 :         CPLError(
     124             :             CE_Failure, CPLE_IllegalArg,
     125             :             "Failed to parse value of VSI_CACHE_SIZE. Using default of 25MB");
     126           0 :         nMemorySize = 25000000;
     127             :     }
     128          32 :     else if (static_cast<size_t>(nMemorySize) >
     129          32 :              std::numeric_limits<size_t>::max() / 2)
     130             :     {
     131           0 :         nMemorySize =
     132           0 :             static_cast<GIntBig>(std::numeric_limits<size_t>::max() / 2);
     133             :     }
     134             : 
     135          32 :     return static_cast<size_t>(nMemorySize);
     136             : }
     137             : 
     138             : /************************************************************************/
     139             : /*                           VSICachedFile()                            */
     140             : /************************************************************************/
     141             : 
     142          32 : VSICachedFile::VSICachedFile(VSIVirtualHandle *poBaseHandle, size_t nChunkSize,
     143          32 :                              size_t nCacheSize)
     144             :     : m_poBase(poBaseHandle),
     145          32 :       m_nChunkSize(nChunkSize ? nChunkSize : VSI_CACHED_DEFAULT_CHUNK_SIZE),
     146          64 :       m_oCache{cpl::div_round_up(GetCacheMax(nCacheSize), m_nChunkSize), 0}
     147             : {
     148          32 :     m_poBase->Seek(0, SEEK_END);
     149          32 :     m_nFileSize = m_poBase->Tell();
     150          32 : }
     151             : 
     152             : /************************************************************************/
     153             : /*                               Close()                                */
     154             : /************************************************************************/
     155             : 
     156          65 : int VSICachedFile::Close()
     157             : 
     158             : {
     159          65 :     m_oCache.clear();
     160          65 :     m_poBase.reset();
     161             : 
     162          65 :     return 0;
     163             : }
     164             : 
     165             : /************************************************************************/
     166             : /*                                Seek()                                */
     167             : /************************************************************************/
     168             : 
     169       10747 : int VSICachedFile::Seek(vsi_l_offset nReqOffset, int nWhence)
     170             : 
     171             : {
     172       10747 :     m_bEOF = false;
     173             : 
     174       10747 :     if (nWhence == SEEK_SET)
     175             :     {
     176             :         // Use offset directly.
     177             :     }
     178        7977 :     else if (nWhence == SEEK_CUR)
     179             :     {
     180        7937 :         nReqOffset += m_nOffset;
     181             :     }
     182          40 :     else if (nWhence == SEEK_END)
     183             :     {
     184          40 :         nReqOffset += m_nFileSize;
     185             :     }
     186             : 
     187       10747 :     m_nOffset = nReqOffset;
     188             : 
     189       10747 :     return 0;
     190             : }
     191             : 
     192             : /************************************************************************/
     193             : /*                                Tell()                                */
     194             : /************************************************************************/
     195             : 
     196        8012 : vsi_l_offset VSICachedFile::Tell()
     197             : 
     198             : {
     199        8012 :     return m_nOffset;
     200             : }
     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         482 : bool VSICachedFile::LoadBlocks(vsi_l_offset nStartBlock, size_t nBlockCount,
     210             :                                void *pBuffer, size_t nBufferSize)
     211             : 
     212             : {
     213         482 :     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         482 :     if (nBlockCount == 1)
     221             :     {
     222         455 :         if (m_poBase->Seek(static_cast<vsi_l_offset>(nStartBlock) *
     223         455 :                                m_nChunkSize,
     224         455 :                            SEEK_SET) != 0)
     225             :         {
     226           0 :             return false;
     227             :         }
     228             : 
     229             :         try
     230             :         {
     231         455 :             cpl::NonCopyableVector<GByte> oData(m_nChunkSize);
     232             :             const auto nDataRead =
     233         455 :                 m_poBase->Read(oData.data(), 1, m_nChunkSize);
     234         455 :             if (nDataRead == 0)
     235           6 :                 return false;
     236         449 :             if (nDataRead < m_nChunkSize && m_poBase->Error())
     237           0 :                 m_bError = true;
     238         449 :             oData.resize(nDataRead);
     239             : 
     240         449 :             m_oCache.insert(nStartBlock, std::move(oData));
     241             :         }
     242           0 :         catch (const std::exception &)
     243             :         {
     244           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
     245             :                      "Out of memory situation in VSICachedFile::LoadBlocks()");
     246           0 :             return false;
     247             :         }
     248             : 
     249         449 :         return true;
     250             :     }
     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          27 :     if (nBufferSize > m_nChunkSize * 20 &&
     259          15 :         nBufferSize < nBlockCount * m_nChunkSize)
     260             :     {
     261           0 :         if (!LoadBlocks(nStartBlock, 2, pBuffer, nBufferSize))
     262           0 :             return false;
     263             : 
     264           0 :         return LoadBlocks(nStartBlock + 2, nBlockCount - 2, pBuffer,
     265           0 :                           nBufferSize);
     266             :     }
     267             : 
     268          27 :     if (m_poBase->Seek(static_cast<vsi_l_offset>(nStartBlock) * m_nChunkSize,
     269          27 :                        SEEK_SET) != 0)
     270           0 :         return false;
     271             : 
     272             :     /* -------------------------------------------------------------------- */
     273             :     /*      Do we need to allocate our own buffer?                          */
     274             :     /* -------------------------------------------------------------------- */
     275          27 :     GByte *pabyWorkBuffer = static_cast<GByte *>(pBuffer);
     276             : 
     277          27 :     if (nBufferSize < m_nChunkSize * nBlockCount)
     278             :     {
     279             :         pabyWorkBuffer = static_cast<GByte *>(
     280           4 :             VSI_MALLOC_VERBOSE(m_nChunkSize * nBlockCount));
     281           4 :         if (pabyWorkBuffer == nullptr)
     282           0 :             return false;
     283             :     }
     284             : 
     285             :     /* -------------------------------------------------------------------- */
     286             :     /*      Read the whole request into the working buffer.                 */
     287             :     /* -------------------------------------------------------------------- */
     288             : 
     289          27 :     const size_t nToRead = nBlockCount * m_nChunkSize;
     290          27 :     const size_t nDataRead = m_poBase->Read(pabyWorkBuffer, 1, nToRead);
     291          27 :     if (nDataRead < nToRead && m_poBase->Error())
     292           0 :         m_bError = true;
     293             : 
     294          27 :     bool ret = true;
     295          27 :     if (nToRead > nDataRead + m_nChunkSize - 1)
     296             :     {
     297           5 :         size_t nNewBlockCount = cpl::div_round_up(nDataRead, m_nChunkSize);
     298           5 :         if (nNewBlockCount < nBlockCount)
     299             :         {
     300           5 :             nBlockCount = nNewBlockCount;
     301           5 :             ret = false;
     302             :         }
     303             :     }
     304             : 
     305         552 :     for (size_t i = 0; i < nBlockCount; i++)
     306             :     {
     307         525 :         const vsi_l_offset iBlock = nStartBlock + i;
     308             : 
     309        1050 :         const auto nDataFilled = (nDataRead >= (i + 1) * m_nChunkSize)
     310         525 :                                      ? m_nChunkSize
     311           4 :                                      : nDataRead - i * m_nChunkSize;
     312             :         try
     313             :         {
     314        1050 :             cpl::NonCopyableVector<GByte> oData(nDataFilled);
     315             : 
     316         525 :             memcpy(oData.data(), pabyWorkBuffer + i * m_nChunkSize,
     317             :                    nDataFilled);
     318             : 
     319         525 :             m_oCache.insert(iBlock, std::move(oData));
     320             :         }
     321           0 :         catch (const std::exception &)
     322             :         {
     323           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
     324             :                      "Out of memory situation in VSICachedFile::LoadBlocks()");
     325           0 :             ret = false;
     326           0 :             break;
     327             :         }
     328             :     }
     329             : 
     330          27 :     if (pabyWorkBuffer != pBuffer)
     331           4 :         CPLFree(pabyWorkBuffer);
     332             : 
     333          27 :     return ret;
     334             : }
     335             : 
     336             : /************************************************************************/
     337             : /*                                Read()                                */
     338             : /************************************************************************/
     339             : 
     340       11571 : size_t VSICachedFile::Read(void *pBuffer, size_t nBytes)
     341             : 
     342             : {
     343       11571 :     if (nBytes == 0)
     344           0 :         return 0;
     345       11571 :     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       11571 :     if (m_nFileSize > 0 && m_nOffset >= m_nFileSize)
     350             :     {
     351           7 :         m_bEOF = true;
     352           7 :         return 0;
     353             :     }
     354             : 
     355             :     /* ==================================================================== */
     356             :     /*      Make sure the cache is loaded for the whole request region.     */
     357             :     /* ==================================================================== */
     358       11564 :     const vsi_l_offset nStartBlock = m_nOffset / m_nChunkSize;
     359             :     // Calculate last block
     360       11564 :     const vsi_l_offset nLastBlock = m_nFileSize / m_nChunkSize;
     361       11564 :     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       11564 :     if (nLastBlock != 0 && nEndBlock > nLastBlock)
     365             :     {
     366           7 :         nEndBlock = nLastBlock;
     367             :     }
     368             : 
     369       23701 :     for (vsi_l_offset iBlock = nStartBlock; iBlock <= nEndBlock; iBlock++)
     370             :     {
     371       12143 :         if (!m_oCache.contains(iBlock))
     372             :         {
     373         433 :             size_t nBlocksToLoad = 1;
     374        1477 :             while (iBlock + nBlocksToLoad <= nEndBlock &&
     375        1477 :                    !m_oCache.contains(iBlock + nBlocksToLoad))
     376             :             {
     377         518 :                 nBlocksToLoad++;
     378             :             }
     379             : 
     380         433 :             if (!LoadBlocks(iBlock, nBlocksToLoad, pBuffer, nRequestedBytes))
     381           6 :                 break;
     382             :         }
     383             :     }
     384             : 
     385             :     /* ==================================================================== */
     386             :     /*      Copy data into the target buffer to the extent possible.        */
     387             :     /* ==================================================================== */
     388       11564 :     size_t nAmountCopied = 0;
     389             : 
     390       23803 :     while (nAmountCopied < nRequestedBytes)
     391             :     {
     392       12255 :         const vsi_l_offset iBlock = (m_nOffset + nAmountCopied) / m_nChunkSize;
     393       12255 :         const cpl::NonCopyableVector<GByte> *poData = m_oCache.getPtr(iBlock);
     394       12255 :         if (poData == nullptr)
     395             :         {
     396             :             // We can reach that point when the amount to read exceeds
     397             :             // the cache size.
     398          49 :             LoadBlocks(iBlock, 1, static_cast<GByte *>(pBuffer) + nAmountCopied,
     399          49 :                        std::min(nRequestedBytes - nAmountCopied, m_nChunkSize));
     400          49 :             poData = m_oCache.getPtr(iBlock);
     401          49 :             if (poData == nullptr)
     402             :             {
     403          16 :                 break;
     404             :             }
     405             :         }
     406             : 
     407       12250 :         const vsi_l_offset nStartOffset =
     408       12250 :             static_cast<vsi_l_offset>(iBlock) * m_nChunkSize;
     409       12250 :         if (nStartOffset + poData->size() < nAmountCopied + m_nOffset)
     410           1 :             break;
     411             :         const size_t nThisCopy =
     412       24498 :             std::min(nRequestedBytes - nAmountCopied,
     413       12249 :                      static_cast<size_t>(((nStartOffset + poData->size()) -
     414       12249 :                                           nAmountCopied - m_nOffset)));
     415       12249 :         if (nThisCopy == 0)
     416          10 :             break;
     417             : 
     418       12239 :         memcpy(static_cast<GByte *>(pBuffer) + nAmountCopied,
     419       12239 :                poData->data() + (m_nOffset + nAmountCopied) - nStartOffset,
     420             :                nThisCopy);
     421             : 
     422       12239 :         nAmountCopied += nThisCopy;
     423             :     }
     424             : 
     425       11564 :     m_nOffset += nAmountCopied;
     426             : 
     427       11564 :     const size_t nRet = nAmountCopied;
     428       11564 :     if (nRet != nBytes && !m_bError)
     429          16 :         m_bEOF = true;
     430       11564 :     return nRet;
     431             : }
     432             : 
     433             : /************************************************************************/
     434             : /*                           ReadMultiRange()                           */
     435             : /************************************************************************/
     436             : 
     437           0 : int VSICachedFile::ReadMultiRange(int const nRanges, void **const ppData,
     438             :                                   const vsi_l_offset *const panOffsets,
     439             :                                   const size_t *const panSizes)
     440             : {
     441             :     // If the base is /vsicurl/
     442           0 :     return m_poBase->ReadMultiRange(nRanges, ppData, panOffsets, panSizes);
     443             : }
     444             : 
     445             : /************************************************************************/
     446             : /*                               Write()                                */
     447             : /************************************************************************/
     448             : 
     449           0 : size_t VSICachedFile::Write(const void * /* pBuffer */, size_t /*nBytes */)
     450             : {
     451           0 :     return 0;
     452             : }
     453             : 
     454             : /************************************************************************/
     455             : /*                              ClearErr()                              */
     456             : /************************************************************************/
     457             : 
     458           1 : void VSICachedFile::ClearErr()
     459             : 
     460             : {
     461           1 :     m_poBase->ClearErr();
     462           1 :     m_bEOF = false;
     463           1 :     m_bError = false;
     464           1 : }
     465             : 
     466             : /************************************************************************/
     467             : /*                               Error()                                */
     468             : /************************************************************************/
     469             : 
     470           2 : int VSICachedFile::Error()
     471             : 
     472             : {
     473           2 :     return m_bError;
     474             : }
     475             : 
     476             : /************************************************************************/
     477             : /*                                Eof()                                 */
     478             : /************************************************************************/
     479             : 
     480           2 : int VSICachedFile::Eof()
     481             : 
     482             : {
     483           2 :     return m_bEOF;
     484             : }
     485             : 
     486             : /************************************************************************/
     487             : /*                               Flush()                                */
     488             : /************************************************************************/
     489             : 
     490           2 : int VSICachedFile::Flush()
     491             : 
     492             : {
     493           2 :     return 0;
     494             : }
     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          17 : static bool ParseSize(const char *pszKey, const char *pszValue, size_t nMaxVal,
     520             :                       size_t &nOutVal)
     521             : {
     522          17 :     char *end = nullptr;
     523          17 :     auto nVal = std::strtoull(pszValue, &end, 10);
     524          17 :     if (!end || end == pszValue || nVal >= nMaxVal)
     525             :     {
     526           6 :         CPLError(
     527             :             CE_Failure, CPLE_IllegalArg,
     528             :             "Invalid value for %s: %s. Max supported value = " CPL_FRMT_GUIB,
     529             :             pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
     530           6 :         return false;
     531             :     }
     532          11 :     if (*end != '\0')
     533             :     {
     534          10 :         if (strcmp(end, "KB") == 0)
     535             :         {
     536           4 :             if (nVal > nMaxVal / 1024)
     537             :             {
     538           2 :                 CPLError(CE_Failure, CPLE_IllegalArg,
     539             :                          "Invalid value for %s: %s. Max supported value "
     540             :                          "= " CPL_FRMT_GUIB,
     541             :                          pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
     542           2 :                 return false;
     543             :             }
     544           2 :             nVal *= 1024;
     545             :         }
     546           6 :         else if (strcmp(end, "MB") == 0)
     547             :         {
     548           4 :             if (nVal > nMaxVal / (1024 * 1024))
     549             :             {
     550           2 :                 CPLError(CE_Failure, CPLE_IllegalArg,
     551             :                          "Invalid value for %s: %s. Max supported value "
     552             :                          "= " CPL_FRMT_GUIB,
     553             :                          pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
     554           2 :                 return false;
     555             :             }
     556           2 :             nVal *= (1024 * 1024);
     557             :         }
     558             :         else
     559             :         {
     560           2 :             CPLError(CE_Failure, CPLE_IllegalArg, "Invalid value for %s: %s",
     561             :                      pszKey, pszValue);
     562           2 :             return false;
     563             :         }
     564             :     }
     565           5 :     nOutVal = static_cast<size_t>(nVal);
     566           5 :     return true;
     567             : }
     568             : 
     569             : /************************************************************************/
     570             : /*                          AnalyzeFilename()                           */
     571             : /************************************************************************/
     572             : 
     573          26 : bool VSICachedFilesystemHandler::AnalyzeFilename(
     574             :     const char *pszFilename, std::string &osUnderlyingFilename,
     575             :     size_t &nChunkSize, size_t &nCacheSize)
     576             : {
     577             : 
     578          26 :     if (!STARTS_WITH(pszFilename, "/vsicached?"))
     579           0 :         return false;
     580             : 
     581             :     const CPLStringList aosTokens(
     582          52 :         CSLTokenizeString2(pszFilename + strlen("/vsicached?"), "&", 0));
     583             : 
     584          26 :     osUnderlyingFilename.clear();
     585          26 :     nChunkSize = 0;
     586          26 :     nCacheSize = 0;
     587             : 
     588          42 :     for (int i = 0; i < aosTokens.size(); ++i)
     589             :     {
     590             :         char *pszUnescaped =
     591          28 :             CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
     592          28 :         std::string osUnescaped(pszUnescaped);
     593          28 :         CPLFree(pszUnescaped);
     594          28 :         char *pszKey = nullptr;
     595          28 :         const char *pszValue = CPLParseNameValue(osUnescaped.c_str(), &pszKey);
     596          28 :         if (pszKey && pszValue)
     597             :         {
     598          28 :             if (strcmp(pszKey, "file") == 0)
     599             :             {
     600          10 :                 osUnderlyingFilename = pszValue;
     601             :             }
     602          18 :             else if (strcmp(pszKey, "chunk_size") == 0)
     603             :             {
     604          11 :                 if (!ParseSize(pszKey, pszValue, 1024 * 1024 * 1024,
     605             :                                nChunkSize))
     606             :                 {
     607           6 :                     CPLFree(pszKey);
     608           6 :                     return false;
     609             :                 }
     610             :             }
     611           7 :             else if (strcmp(pszKey, "cache_size") == 0)
     612             :             {
     613           6 :                 if (!ParseSize(pszKey, pszValue,
     614             :                                std::numeric_limits<size_t>::max(), nCacheSize))
     615             :                 {
     616           6 :                     CPLFree(pszKey);
     617           6 :                     return false;
     618             :                 }
     619             :             }
     620             :             else
     621             :             {
     622           1 :                 CPLError(CE_Warning, CPLE_NotSupported,
     623             :                          "Unsupported option: %s", pszKey);
     624             :             }
     625             :         }
     626          16 :         CPLFree(pszKey);
     627             :     }
     628             : 
     629          14 :     if (osUnderlyingFilename.empty())
     630             :     {
     631           4 :         CPLError(CE_Warning, CPLE_NotSupported, "Missing 'file' option");
     632             :     }
     633             : 
     634          14 :     return !osUnderlyingFilename.empty();
     635             : }
     636             : 
     637             : /************************************************************************/
     638             : /*                                Open()                                */
     639             : /************************************************************************/
     640             : 
     641             : VSIVirtualHandleUniquePtr
     642          22 : VSICachedFilesystemHandler::Open(const char *pszFilename, const char *pszAccess,
     643             :                                  bool bSetError, CSLConstList papszOptions)
     644             : {
     645          44 :     std::string osUnderlyingFilename;
     646          22 :     size_t nChunkSize = 0;
     647          22 :     size_t nCacheSize = 0;
     648          22 :     if (!AnalyzeFilename(pszFilename, osUnderlyingFilename, nChunkSize,
     649             :                          nCacheSize))
     650          14 :         return nullptr;
     651           8 :     if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "rb") != 0)
     652             :     {
     653           1 :         if (bSetError)
     654             :         {
     655           0 :             VSIError(VSIE_FileError,
     656             :                      "/vsicached? supports only 'r' and 'rb' access modes");
     657             :         }
     658           1 :         return nullptr;
     659             :     }
     660             : 
     661             :     auto fp = VSIFilesystemHandler::OpenStatic(
     662          14 :         osUnderlyingFilename.c_str(), pszAccess, bSetError, papszOptions);
     663           7 :     if (!fp)
     664           1 :         return nullptr;
     665             :     return VSIVirtualHandleUniquePtr(
     666           6 :         VSICreateCachedFile(fp.release(), nChunkSize, nCacheSize));
     667             : }
     668             : 
     669             : /************************************************************************/
     670             : /*                                Stat()                                */
     671             : /************************************************************************/
     672             : 
     673           2 : int VSICachedFilesystemHandler::Stat(const char *pszFilename,
     674             :                                      VSIStatBufL *pStatBuf, int nFlags)
     675             : {
     676           4 :     std::string osUnderlyingFilename;
     677           2 :     size_t nChunkSize = 0;
     678           2 :     size_t nCacheSize = 0;
     679           2 :     if (!AnalyzeFilename(pszFilename, osUnderlyingFilename, nChunkSize,
     680             :                          nCacheSize))
     681           1 :         return -1;
     682           1 :     return VSIStatExL(osUnderlyingFilename.c_str(), pStatBuf, nFlags);
     683             : }
     684             : 
     685             : /************************************************************************/
     686             : /*                             ReadDirEx()                              */
     687             : /************************************************************************/
     688             : 
     689           2 : char **VSICachedFilesystemHandler::ReadDirEx(const char *pszDirname,
     690             :                                              int nMaxFiles)
     691             : {
     692           4 :     std::string osUnderlyingFilename;
     693           2 :     size_t nChunkSize = 0;
     694           2 :     size_t nCacheSize = 0;
     695           2 :     if (!AnalyzeFilename(pszDirname, osUnderlyingFilename, nChunkSize,
     696             :                          nCacheSize))
     697           1 :         return nullptr;
     698           1 :     return VSIReadDirEx(osUnderlyingFilename.c_str(), nMaxFiles);
     699             : }
     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          32 : VSIVirtualHandle *VSICreateCachedFile(VSIVirtualHandle *poBaseHandle,
     722             :                                       size_t nChunkSize, size_t nCacheSize)
     723             : 
     724             : {
     725          32 :     return new VSICachedFile(poBaseHandle, nChunkSize, nCacheSize);
     726             : }
     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        1789 : void VSIInstallCachedFileHandler(void)
     742             : {
     743        1789 :     VSIFileManager::InstallHandler(
     744        3578 :         "/vsicached?", std::make_shared<VSICachedFilesystemHandler>());
     745        1789 : }

Generated by: LCOV version 1.14