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

Generated by: LCOV version 1.14