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: 2025-09-10 17:48:50 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 nSize, size_t nMemb) 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 nSize, size_t nMemb) 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       10733 : int VSICachedFile::Seek(vsi_l_offset nReqOffset, int nWhence)
     170             : 
     171             : {
     172       10733 :     m_bEOF = false;
     173             : 
     174       10733 :     if (nWhence == SEEK_SET)
     175             :     {
     176             :         // Use offset directly.
     177             :     }
     178        7971 :     else if (nWhence == SEEK_CUR)
     179             :     {
     180        7937 :         nReqOffset += m_nOffset;
     181             :     }
     182          34 :     else if (nWhence == SEEK_END)
     183             :     {
     184          34 :         nReqOffset += m_nFileSize;
     185             :     }
     186             : 
     187       10733 :     m_nOffset = nReqOffset;
     188             : 
     189       10733 :     return 0;
     190             : }
     191             : 
     192             : /************************************************************************/
     193             : /*                                Tell()                                */
     194             : /************************************************************************/
     195             : 
     196        8002 : vsi_l_offset VSICachedFile::Tell()
     197             : 
     198             : {
     199        8002 :     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       11567 : size_t VSICachedFile::Read(void *pBuffer, size_t nSize, size_t nCount)
     341             : 
     342             : {
     343       11567 :     if (nSize == 0 || nCount == 0)
     344           0 :         return 0;
     345       11567 :     const size_t nRequestedBytes = nSize * nCount;
     346             : 
     347             :     // nFileSize might be set wrongly to 0 by underlying layers, such as
     348             :     // /vsicurl_streaming/https://query.data.world/s/jgsghstpphjhicstradhy5kpjwrnfy
     349       11567 :     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       11560 :     const vsi_l_offset nStartBlock = m_nOffset / m_nChunkSize;
     359             :     // Calculate last block
     360       11560 :     const vsi_l_offset nLastBlock = m_nFileSize / m_nChunkSize;
     361       11560 :     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       11560 :     if (nLastBlock != 0 && nEndBlock > nLastBlock)
     365             :     {
     366           7 :         nEndBlock = nLastBlock;
     367             :     }
     368             : 
     369       23693 :     for (vsi_l_offset iBlock = nStartBlock; iBlock <= nEndBlock; iBlock++)
     370             :     {
     371       12139 :         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       11560 :     size_t nAmountCopied = 0;
     389             : 
     390       23795 :     while (nAmountCopied < nRequestedBytes)
     391             :     {
     392       12251 :         const vsi_l_offset iBlock = (m_nOffset + nAmountCopied) / m_nChunkSize;
     393       12251 :         const cpl::NonCopyableVector<GByte> *poData = m_oCache.getPtr(iBlock);
     394       12251 :         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       12246 :         const vsi_l_offset nStartOffset =
     408       12246 :             static_cast<vsi_l_offset>(iBlock) * m_nChunkSize;
     409       12246 :         if (nStartOffset + poData->size() < nAmountCopied + m_nOffset)
     410           1 :             break;
     411             :         const size_t nThisCopy =
     412       24490 :             std::min(nRequestedBytes - nAmountCopied,
     413       12245 :                      static_cast<size_t>(((nStartOffset + poData->size()) -
     414       12245 :                                           nAmountCopied - m_nOffset)));
     415       12245 :         if (nThisCopy == 0)
     416          10 :             break;
     417             : 
     418       12235 :         memcpy(static_cast<GByte *>(pBuffer) + nAmountCopied,
     419       12235 :                poData->data() + (m_nOffset + nAmountCopied) - nStartOffset,
     420             :                nThisCopy);
     421             : 
     422       12235 :         nAmountCopied += nThisCopy;
     423             :     }
     424             : 
     425       11560 :     m_nOffset += nAmountCopied;
     426             : 
     427       11560 :     const size_t nRet = nAmountCopied / nSize;
     428       11560 :     if (nRet != nCount && !m_bError)
     429          16 :         m_bEOF = true;
     430       11560 :     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 /*nSize */,
     450             :                             size_t /* nCount */)
     451             : {
     452           0 :     return 0;
     453             : }
     454             : 
     455             : /************************************************************************/
     456             : /*                             ClearErr()                               */
     457             : /************************************************************************/
     458             : 
     459           1 : void VSICachedFile::ClearErr()
     460             : 
     461             : {
     462           1 :     m_poBase->ClearErr();
     463           1 :     m_bEOF = false;
     464           1 :     m_bError = false;
     465           1 : }
     466             : 
     467             : /************************************************************************/
     468             : /*                              Error()                                 */
     469             : /************************************************************************/
     470             : 
     471           2 : int VSICachedFile::Error()
     472             : 
     473             : {
     474           2 :     return m_bError;
     475             : }
     476             : 
     477             : /************************************************************************/
     478             : /*                                Eof()                                 */
     479             : /************************************************************************/
     480             : 
     481           2 : int VSICachedFile::Eof()
     482             : 
     483             : {
     484           2 :     return m_bEOF;
     485             : }
     486             : 
     487             : /************************************************************************/
     488             : /*                               Flush()                                */
     489             : /************************************************************************/
     490             : 
     491           2 : int VSICachedFile::Flush()
     492             : 
     493             : {
     494           2 :     return 0;
     495             : }
     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          17 : static bool ParseSize(const char *pszKey, const char *pszValue, size_t nMaxVal,
     521             :                       size_t &nOutVal)
     522             : {
     523          17 :     char *end = nullptr;
     524          17 :     auto nVal = std::strtoull(pszValue, &end, 10);
     525          17 :     if (!end || end == pszValue || nVal >= nMaxVal)
     526             :     {
     527           6 :         CPLError(
     528             :             CE_Failure, CPLE_IllegalArg,
     529             :             "Invalid value for %s: %s. Max supported value = " CPL_FRMT_GUIB,
     530             :             pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
     531           6 :         return false;
     532             :     }
     533          11 :     if (*end != '\0')
     534             :     {
     535          10 :         if (strcmp(end, "KB") == 0)
     536             :         {
     537           4 :             if (nVal > nMaxVal / 1024)
     538             :             {
     539           2 :                 CPLError(CE_Failure, CPLE_IllegalArg,
     540             :                          "Invalid value for %s: %s. Max supported value "
     541             :                          "= " CPL_FRMT_GUIB,
     542             :                          pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
     543           2 :                 return false;
     544             :             }
     545           2 :             nVal *= 1024;
     546             :         }
     547           6 :         else if (strcmp(end, "MB") == 0)
     548             :         {
     549           4 :             if (nVal > nMaxVal / (1024 * 1024))
     550             :             {
     551           2 :                 CPLError(CE_Failure, CPLE_IllegalArg,
     552             :                          "Invalid value for %s: %s. Max supported value "
     553             :                          "= " CPL_FRMT_GUIB,
     554             :                          pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
     555           2 :                 return false;
     556             :             }
     557           2 :             nVal *= (1024 * 1024);
     558             :         }
     559             :         else
     560             :         {
     561           2 :             CPLError(CE_Failure, CPLE_IllegalArg, "Invalid value for %s: %s",
     562             :                      pszKey, pszValue);
     563           2 :             return false;
     564             :         }
     565             :     }
     566           5 :     nOutVal = static_cast<size_t>(nVal);
     567           5 :     return true;
     568             : }
     569             : 
     570             : /************************************************************************/
     571             : /*                          AnalyzeFilename()                           */
     572             : /************************************************************************/
     573             : 
     574          26 : bool VSICachedFilesystemHandler::AnalyzeFilename(
     575             :     const char *pszFilename, std::string &osUnderlyingFilename,
     576             :     size_t &nChunkSize, size_t &nCacheSize)
     577             : {
     578             : 
     579          26 :     if (!STARTS_WITH(pszFilename, "/vsicached?"))
     580           0 :         return false;
     581             : 
     582             :     const CPLStringList aosTokens(
     583          52 :         CSLTokenizeString2(pszFilename + strlen("/vsicached?"), "&", 0));
     584             : 
     585          26 :     osUnderlyingFilename.clear();
     586          26 :     nChunkSize = 0;
     587          26 :     nCacheSize = 0;
     588             : 
     589          42 :     for (int i = 0; i < aosTokens.size(); ++i)
     590             :     {
     591             :         char *pszUnescaped =
     592          28 :             CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
     593          28 :         std::string osUnescaped(pszUnescaped);
     594          28 :         CPLFree(pszUnescaped);
     595          28 :         char *pszKey = nullptr;
     596          28 :         const char *pszValue = CPLParseNameValue(osUnescaped.c_str(), &pszKey);
     597          28 :         if (pszKey && pszValue)
     598             :         {
     599          28 :             if (strcmp(pszKey, "file") == 0)
     600             :             {
     601          10 :                 osUnderlyingFilename = pszValue;
     602             :             }
     603          18 :             else if (strcmp(pszKey, "chunk_size") == 0)
     604             :             {
     605          11 :                 if (!ParseSize(pszKey, pszValue, 1024 * 1024 * 1024,
     606             :                                nChunkSize))
     607             :                 {
     608           6 :                     CPLFree(pszKey);
     609           6 :                     return false;
     610             :                 }
     611             :             }
     612           7 :             else if (strcmp(pszKey, "cache_size") == 0)
     613             :             {
     614           6 :                 if (!ParseSize(pszKey, pszValue,
     615             :                                std::numeric_limits<size_t>::max(), nCacheSize))
     616             :                 {
     617           6 :                     CPLFree(pszKey);
     618           6 :                     return false;
     619             :                 }
     620             :             }
     621             :             else
     622             :             {
     623           1 :                 CPLError(CE_Warning, CPLE_NotSupported,
     624             :                          "Unsupported option: %s", pszKey);
     625             :             }
     626             :         }
     627          16 :         CPLFree(pszKey);
     628             :     }
     629             : 
     630          14 :     if (osUnderlyingFilename.empty())
     631             :     {
     632           4 :         CPLError(CE_Warning, CPLE_NotSupported, "Missing 'file' option");
     633             :     }
     634             : 
     635          14 :     return !osUnderlyingFilename.empty();
     636             : }
     637             : 
     638             : /************************************************************************/
     639             : /*                               Open()                                 */
     640             : /************************************************************************/
     641             : 
     642             : VSIVirtualHandleUniquePtr
     643          22 : VSICachedFilesystemHandler::Open(const char *pszFilename, const char *pszAccess,
     644             :                                  bool bSetError, CSLConstList papszOptions)
     645             : {
     646          44 :     std::string osUnderlyingFilename;
     647          22 :     size_t nChunkSize = 0;
     648          22 :     size_t nCacheSize = 0;
     649          22 :     if (!AnalyzeFilename(pszFilename, osUnderlyingFilename, nChunkSize,
     650             :                          nCacheSize))
     651          14 :         return nullptr;
     652           8 :     if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "rb") != 0)
     653             :     {
     654           1 :         if (bSetError)
     655             :         {
     656           0 :             VSIError(VSIE_FileError,
     657             :                      "/vsicached? supports only 'r' and 'rb' access modes");
     658             :         }
     659           1 :         return nullptr;
     660             :     }
     661             : 
     662             :     auto fp = VSIFilesystemHandler::OpenStatic(
     663          14 :         osUnderlyingFilename.c_str(), pszAccess, bSetError, papszOptions);
     664           7 :     if (!fp)
     665           1 :         return nullptr;
     666             :     return VSIVirtualHandleUniquePtr(
     667           6 :         VSICreateCachedFile(fp.release(), nChunkSize, nCacheSize));
     668             : }
     669             : 
     670             : /************************************************************************/
     671             : /*                               Stat()                                 */
     672             : /************************************************************************/
     673             : 
     674           2 : int VSICachedFilesystemHandler::Stat(const char *pszFilename,
     675             :                                      VSIStatBufL *pStatBuf, int nFlags)
     676             : {
     677           4 :     std::string osUnderlyingFilename;
     678           2 :     size_t nChunkSize = 0;
     679           2 :     size_t nCacheSize = 0;
     680           2 :     if (!AnalyzeFilename(pszFilename, osUnderlyingFilename, nChunkSize,
     681             :                          nCacheSize))
     682           1 :         return -1;
     683           1 :     return VSIStatExL(osUnderlyingFilename.c_str(), pStatBuf, nFlags);
     684             : }
     685             : 
     686             : /************************************************************************/
     687             : /*                          ReadDirEx()                                 */
     688             : /************************************************************************/
     689             : 
     690           2 : char **VSICachedFilesystemHandler::ReadDirEx(const char *pszDirname,
     691             :                                              int nMaxFiles)
     692             : {
     693           4 :     std::string osUnderlyingFilename;
     694           2 :     size_t nChunkSize = 0;
     695           2 :     size_t nCacheSize = 0;
     696           2 :     if (!AnalyzeFilename(pszDirname, osUnderlyingFilename, nChunkSize,
     697             :                          nCacheSize))
     698           1 :         return nullptr;
     699           1 :     return VSIReadDirEx(osUnderlyingFilename.c_str(), nMaxFiles);
     700             : }
     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          32 : VSIVirtualHandle *VSICreateCachedFile(VSIVirtualHandle *poBaseHandle,
     723             :                                       size_t nChunkSize, size_t nCacheSize)
     724             : 
     725             : {
     726          32 :     return new VSICachedFile(poBaseHandle, nChunkSize, nCacheSize);
     727             : }
     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        1754 : void VSIInstallCachedFileHandler(void)
     743             : {
     744        1754 :     VSIFilesystemHandler *poHandler = new VSICachedFilesystemHandler;
     745        1754 :     VSIFileManager::InstallHandler("/vsicached?", poHandler);
     746        1754 : }

Generated by: LCOV version 1.14