LCOV - code coverage report
Current view: top level - port - cpl_vsil_cache.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 212 252 84.1 %
Date: 2025-07-09 17:50:03 Functions: 19 26 73.1 %

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

Generated by: LCOV version 1.14