LCOV - code coverage report
Current view: top level - port - cpl_vsil_cache.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 200 236 84.7 %
Date: 2024-04-29 01:40:10 Functions: 17 25 68.0 %

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

Generated by: LCOV version 1.14