LCOV - code coverage report
Current view: top level - port - cpl_vsil_sparsefile.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 151 189 79.9 %
Date: 2025-08-19 18:03:11 Functions: 17 24 70.8 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  VSI Virtual File System
       4             :  * Purpose:  Implementation of sparse file virtual io driver.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2010, Frank Warmerdam <warmerdam@pobox.com>
       9             :  * Copyright (c) 2010-2013, 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.h"
      16             : 
      17             : #include <cerrno>
      18             : #include <cstddef>
      19             : #include <cstdlib>
      20             : #include <cstring>
      21             : 
      22             : #include <algorithm>
      23             : #include <map>
      24             : #include <memory>
      25             : #include <vector>
      26             : 
      27             : #include "cpl_conv.h"
      28             : #include "cpl_error.h"
      29             : #include "cpl_minixml.h"
      30             : #include "cpl_multiproc.h"
      31             : #include "cpl_string.h"
      32             : #include "cpl_vsi_virtual.h"
      33             : 
      34             : class SFRegion
      35             : {
      36             :   public:
      37             :     CPLString osFilename{};
      38             :     VSILFILE *fp = nullptr;
      39             :     GUIntBig nDstOffset = 0;
      40             :     GUIntBig nSrcOffset = 0;
      41             :     GUIntBig nLength = 0;
      42             :     GByte byValue = 0;
      43             :     bool bTriedOpen = false;
      44             : };
      45             : 
      46             : /************************************************************************/
      47             : /* ==================================================================== */
      48             : /*                         VSISparseFileHandle                          */
      49             : /* ==================================================================== */
      50             : /************************************************************************/
      51             : 
      52             : class VSISparseFileFilesystemHandler;
      53             : 
      54             : class VSISparseFileHandle final : public VSIVirtualHandle
      55             : {
      56             :     CPL_DISALLOW_COPY_ASSIGN(VSISparseFileHandle)
      57             : 
      58             :     VSISparseFileFilesystemHandler *m_poFS = nullptr;
      59             :     bool bEOF = false;
      60             :     bool bError = false;
      61             : 
      62             :   public:
      63          72 :     explicit VSISparseFileHandle(VSISparseFileFilesystemHandler *poFS)
      64          72 :         : m_poFS(poFS)
      65             :     {
      66          72 :     }
      67             : 
      68             :     ~VSISparseFileHandle() override;
      69             : 
      70             :     GUIntBig nOverallLength = 0;
      71             :     GUIntBig nCurOffset = 0;
      72             : 
      73             :     std::vector<SFRegion> aoRegions{};
      74             : 
      75             :     int Seek(vsi_l_offset nOffset, int nWhence) override;
      76             :     vsi_l_offset Tell() override;
      77             :     size_t Read(void *pBuffer, size_t nSize, size_t nMemb) override;
      78             :     size_t Write(const void *pBuffer, size_t nSize, size_t nMemb) override;
      79             :     void ClearErr() override;
      80             :     int Eof() override;
      81             :     int Error() override;
      82             :     int Close() override;
      83             : };
      84             : 
      85             : /************************************************************************/
      86             : /* ==================================================================== */
      87             : /*                   VSISparseFileFilesystemHandler                     */
      88             : /* ==================================================================== */
      89             : /************************************************************************/
      90             : 
      91             : class VSISparseFileFilesystemHandler : public VSIFilesystemHandler
      92             : {
      93             :     std::map<GIntBig, int> oRecOpenCount{};
      94             :     CPL_DISALLOW_COPY_ASSIGN(VSISparseFileFilesystemHandler)
      95             : 
      96             :   public:
      97        1694 :     VSISparseFileFilesystemHandler() = default;
      98        2244 :     ~VSISparseFileFilesystemHandler() override = default;
      99             : 
     100             :     int DecomposePath(const char *pszPath, CPLString &osFilename,
     101             :                       vsi_l_offset &nSparseFileOffset,
     102             :                       vsi_l_offset &nSparseFileSize);
     103             : 
     104             :     // TODO(schwehr): Fix VSISparseFileFilesystemHandler::Stat to not need
     105             :     // using.
     106             :     using VSIFilesystemHandler::Open;
     107             : 
     108             :     VSIVirtualHandle *Open(const char *pszFilename, const char *pszAccess,
     109             :                            bool bSetError,
     110             :                            CSLConstList /* papszOptions */) override;
     111             :     int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
     112             :              int nFlags) override;
     113             :     int Unlink(const char *pszFilename) override;
     114             :     int Mkdir(const char *pszDirname, long nMode) override;
     115             :     int Rmdir(const char *pszDirname) override;
     116             :     char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
     117             : 
     118         233 :     int GetRecCounter()
     119             :     {
     120         233 :         return oRecOpenCount[CPLGetPID()];
     121             :     }
     122             : 
     123        7124 :     void IncRecCounter()
     124             :     {
     125        7124 :         oRecOpenCount[CPLGetPID()]++;
     126        7124 :     }
     127             : 
     128        7124 :     void DecRecCounter()
     129             :     {
     130        7124 :         oRecOpenCount[CPLGetPID()]--;
     131        7124 :     }
     132             : };
     133             : 
     134             : /************************************************************************/
     135             : /* ==================================================================== */
     136             : /*                             VSISparseFileHandle                      */
     137             : /* ==================================================================== */
     138             : /************************************************************************/
     139             : 
     140             : /************************************************************************/
     141             : /*                        ~VSISparseFileHandle()                        */
     142             : /************************************************************************/
     143             : 
     144         144 : VSISparseFileHandle::~VSISparseFileHandle()
     145             : {
     146          72 :     VSISparseFileHandle::Close();
     147         144 : }
     148             : 
     149             : /************************************************************************/
     150             : /*                               Close()                                */
     151             : /************************************************************************/
     152             : 
     153         139 : int VSISparseFileHandle::Close()
     154             : 
     155             : {
     156         311 :     for (unsigned int i = 0; i < aoRegions.size(); i++)
     157             :     {
     158         172 :         if (aoRegions[i].fp != nullptr)
     159         121 :             CPL_IGNORE_RET_VAL(VSIFCloseL(aoRegions[i].fp));
     160             :     }
     161         139 :     aoRegions.clear();
     162             : 
     163         139 :     return 0;
     164             : }
     165             : 
     166             : /************************************************************************/
     167             : /*                                Seek()                                */
     168             : /************************************************************************/
     169             : 
     170         336 : int VSISparseFileHandle::Seek(vsi_l_offset nOffset, int nWhence)
     171             : 
     172             : {
     173         336 :     bEOF = false;
     174         336 :     if (nWhence == SEEK_SET)
     175         265 :         nCurOffset = nOffset;
     176          71 :     else if (nWhence == SEEK_CUR)
     177             :     {
     178          60 :         nCurOffset += nOffset;
     179             :     }
     180          11 :     else if (nWhence == SEEK_END)
     181             :     {
     182          11 :         nCurOffset = nOverallLength + nOffset;
     183             :     }
     184             :     else
     185             :     {
     186           0 :         errno = EINVAL;
     187           0 :         return -1;
     188             :     }
     189             : 
     190         336 :     return 0;
     191             : }
     192             : 
     193             : /************************************************************************/
     194             : /*                                Tell()                                */
     195             : /************************************************************************/
     196             : 
     197          79 : vsi_l_offset VSISparseFileHandle::Tell()
     198             : 
     199             : {
     200          79 :     return nCurOffset;
     201             : }
     202             : 
     203             : /************************************************************************/
     204             : /*                                Read()                                */
     205             : /************************************************************************/
     206             : 
     207        7156 : size_t VSISparseFileHandle::Read(void *pBuffer, size_t nSize, size_t nCount)
     208             : 
     209             : {
     210        7156 :     if (nCurOffset >= nOverallLength)
     211             :     {
     212           5 :         bEOF = true;
     213           5 :         return 0;
     214             :     }
     215             : 
     216             :     /* -------------------------------------------------------------------- */
     217             :     /*      Find what region we are in, searching linearly from the         */
     218             :     /*      start.                                                          */
     219             :     /* -------------------------------------------------------------------- */
     220        7151 :     unsigned int iRegion = 0;  // Used after for.
     221             : 
     222       22233 :     for (; iRegion < aoRegions.size(); iRegion++)
     223             :     {
     224       44461 :         if (nCurOffset >= aoRegions[iRegion].nDstOffset &&
     225       22230 :             nCurOffset <
     226       22230 :                 aoRegions[iRegion].nDstOffset + aoRegions[iRegion].nLength)
     227        7149 :             break;
     228             :     }
     229             : 
     230        7151 :     size_t nBytesRequested = nSize * nCount;
     231        7151 :     if (nBytesRequested == 0)
     232             :     {
     233           0 :         return 0;
     234             :     }
     235        7151 :     if (nCurOffset + nBytesRequested > nOverallLength)
     236             :     {
     237          12 :         nBytesRequested = static_cast<size_t>(nOverallLength - nCurOffset);
     238          12 :         bEOF = true;
     239             :     }
     240             : 
     241             :     /* -------------------------------------------------------------------- */
     242             :     /*      Default to zeroing the buffer if no corresponding region was    */
     243             :     /*      found.                                                          */
     244             :     /* -------------------------------------------------------------------- */
     245        7151 :     if (iRegion == aoRegions.size())
     246             :     {
     247           2 :         memset(pBuffer, 0, nBytesRequested);
     248           2 :         nCurOffset += nBytesRequested;
     249           2 :         return nBytesRequested / nSize;
     250             :     }
     251             : 
     252             :     /* -------------------------------------------------------------------- */
     253             :     /*      If this request crosses region boundaries, split it into two    */
     254             :     /*      requests.                                                       */
     255             :     /* -------------------------------------------------------------------- */
     256        7149 :     size_t nBytesReturnCount = 0;
     257             :     const GUIntBig nEndOffsetOfRegion =
     258        7149 :         aoRegions[iRegion].nDstOffset + aoRegions[iRegion].nLength;
     259             : 
     260        7149 :     if (nCurOffset + nBytesRequested > nEndOffsetOfRegion)
     261             :     {
     262          86 :         const size_t nExtraBytes = static_cast<size_t>(
     263          86 :             nCurOffset + nBytesRequested - nEndOffsetOfRegion);
     264             :         // Recurse to get the rest of the request.
     265             : 
     266          86 :         const GUIntBig nCurOffsetSave = nCurOffset;
     267          86 :         nCurOffset += nBytesRequested - nExtraBytes;
     268          86 :         bool bEOFSave = bEOF;
     269          86 :         bEOF = false;
     270         172 :         const size_t nBytesRead = this->Read(static_cast<char *>(pBuffer) +
     271          86 :                                                  nBytesRequested - nExtraBytes,
     272             :                                              1, nExtraBytes);
     273          86 :         nCurOffset = nCurOffsetSave;
     274          86 :         bEOF = bEOFSave;
     275          86 :         if (nBytesRead < nExtraBytes)
     276             :         {
     277             :             // A short read in a region of a sparse file is always an error
     278           0 :             bError = true;
     279             :         }
     280             : 
     281          86 :         nBytesReturnCount += nBytesRead;
     282          86 :         nBytesRequested -= nExtraBytes;
     283             :     }
     284             : 
     285             :     /* -------------------------------------------------------------------- */
     286             :     /*      Handle a constant region.                                       */
     287             :     /* -------------------------------------------------------------------- */
     288        7149 :     if (aoRegions[iRegion].osFilename.empty())
     289             :     {
     290          25 :         memset(pBuffer, aoRegions[iRegion].byValue,
     291             :                static_cast<size_t>(nBytesRequested));
     292             : 
     293          25 :         nBytesReturnCount += nBytesRequested;
     294             :     }
     295             : 
     296             :     /* -------------------------------------------------------------------- */
     297             :     /*      Otherwise handle as a file.                                     */
     298             :     /* -------------------------------------------------------------------- */
     299             :     else
     300             :     {
     301        7124 :         if (aoRegions[iRegion].fp == nullptr)
     302             :         {
     303         121 :             if (!aoRegions[iRegion].bTriedOpen)
     304             :             {
     305         242 :                 aoRegions[iRegion].fp =
     306         121 :                     VSIFOpenL(aoRegions[iRegion].osFilename, "r");
     307         121 :                 if (aoRegions[iRegion].fp == nullptr)
     308             :                 {
     309           0 :                     CPLDebug("/vsisparse/", "Failed to open '%s'.",
     310           0 :                              aoRegions[iRegion].osFilename.c_str());
     311             :                 }
     312         121 :                 aoRegions[iRegion].bTriedOpen = true;
     313             :             }
     314         121 :             if (aoRegions[iRegion].fp == nullptr)
     315             :             {
     316           0 :                 bError = true;
     317           0 :                 return 0;
     318             :             }
     319             :         }
     320             : 
     321        7124 :         if (VSIFSeekL(aoRegions[iRegion].fp,
     322        7124 :                       nCurOffset - aoRegions[iRegion].nDstOffset +
     323        7124 :                           aoRegions[iRegion].nSrcOffset,
     324        7124 :                       SEEK_SET) != 0)
     325             :         {
     326           0 :             bError = true;
     327           0 :             return 0;
     328             :         }
     329             : 
     330        7124 :         m_poFS->IncRecCounter();
     331             :         const size_t nBytesRead =
     332        7124 :             VSIFReadL(pBuffer, 1, static_cast<size_t>(nBytesRequested),
     333        7124 :                       aoRegions[iRegion].fp);
     334        7124 :         m_poFS->DecRecCounter();
     335        7124 :         if (nBytesRead < static_cast<size_t>(nBytesRequested))
     336             :         {
     337             :             // A short read in a region of a sparse file is always an error
     338           0 :             bError = true;
     339             :         }
     340             : 
     341        7124 :         nBytesReturnCount += nBytesRead;
     342             :     }
     343             : 
     344        7149 :     nCurOffset += nBytesReturnCount;
     345             : 
     346        7149 :     return nBytesReturnCount / nSize;
     347             : }
     348             : 
     349             : /************************************************************************/
     350             : /*                               Write()                                */
     351             : /************************************************************************/
     352             : 
     353           0 : size_t VSISparseFileHandle::Write(const void * /* pBuffer */,
     354             :                                   size_t /* nSize */, size_t /* nCount */)
     355             : {
     356           0 :     errno = EBADF;
     357           0 :     return 0;
     358             : }
     359             : 
     360             : /************************************************************************/
     361             : /*                                Eof()                                 */
     362             : /************************************************************************/
     363             : 
     364           0 : int VSISparseFileHandle::Eof()
     365             : 
     366             : {
     367           0 :     return bEOF ? 1 : 0;
     368             : }
     369             : 
     370             : /************************************************************************/
     371             : /*                               Error()                                */
     372             : /************************************************************************/
     373             : 
     374           0 : int VSISparseFileHandle::Error()
     375             : 
     376             : {
     377           0 :     return bError ? 1 : 0;
     378             : }
     379             : 
     380             : /************************************************************************/
     381             : /*                             ClearErr()                               */
     382             : /************************************************************************/
     383             : 
     384           0 : void VSISparseFileHandle::ClearErr()
     385             : 
     386             : {
     387           0 :     for (const auto &region : aoRegions)
     388             :     {
     389           0 :         if (region.fp)
     390           0 :             region.fp->ClearErr();
     391             :     }
     392           0 :     bEOF = false;
     393           0 :     bError = false;
     394           0 : }
     395             : 
     396             : /************************************************************************/
     397             : /* ==================================================================== */
     398             : /*                       VSISparseFileFilesystemHandler                 */
     399             : /* ==================================================================== */
     400             : /************************************************************************/
     401             : 
     402             : /************************************************************************/
     403             : /*                                Open()                                */
     404             : /************************************************************************/
     405             : 
     406         235 : VSIVirtualHandle *VSISparseFileFilesystemHandler::Open(
     407             :     const char *pszFilename, const char *pszAccess, bool /* bSetError */,
     408             :     CSLConstList /* papszOptions */)
     409             : 
     410             : {
     411         235 :     if (!STARTS_WITH_CI(pszFilename, "/vsisparse/"))
     412           2 :         return nullptr;
     413             : 
     414         233 :     if (!EQUAL(pszAccess, "r") && !EQUAL(pszAccess, "rb"))
     415             :     {
     416           0 :         errno = EACCES;
     417           0 :         return nullptr;
     418             :     }
     419             : 
     420             :     // Arbitrary number.
     421         233 :     if (GetRecCounter() == 32)
     422           0 :         return nullptr;
     423             : 
     424         466 :     const CPLString osSparseFilePath = pszFilename + 11;
     425             : 
     426             :     /* -------------------------------------------------------------------- */
     427             :     /*      Does this file even exist?                                      */
     428             :     /* -------------------------------------------------------------------- */
     429         233 :     VSILFILE *fp = VSIFOpenL(osSparseFilePath, "r");
     430         233 :     if (fp == nullptr)
     431         161 :         return nullptr;
     432          72 :     CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
     433             : 
     434             :     /* -------------------------------------------------------------------- */
     435             :     /*      Read the XML file.                                              */
     436             :     /* -------------------------------------------------------------------- */
     437          72 :     CPLXMLNode *psXMLRoot = CPLParseXMLFile(osSparseFilePath);
     438             : 
     439          72 :     if (psXMLRoot == nullptr)
     440           0 :         return nullptr;
     441             : 
     442             :     /* -------------------------------------------------------------------- */
     443             :     /*      Setup the file handle on this file.                             */
     444             :     /* -------------------------------------------------------------------- */
     445          72 :     VSISparseFileHandle *poHandle = new VSISparseFileHandle(this);
     446             : 
     447             :     /* -------------------------------------------------------------------- */
     448             :     /*      Translate the desired fields out of the XML tree.               */
     449             :     /* -------------------------------------------------------------------- */
     450         273 :     for (CPLXMLNode *psRegion = psXMLRoot->psChild; psRegion != nullptr;
     451         201 :          psRegion = psRegion->psNext)
     452             :     {
     453         201 :         if (psRegion->eType != CXT_Element)
     454          29 :             continue;
     455             : 
     456         200 :         if (!EQUAL(psRegion->pszValue, "SubfileRegion") &&
     457          60 :             !EQUAL(psRegion->pszValue, "ConstantRegion"))
     458          28 :             continue;
     459             : 
     460         344 :         SFRegion oRegion;
     461             : 
     462         172 :         oRegion.osFilename = CPLGetXMLValue(psRegion, "Filename", "");
     463         172 :         if (atoi(CPLGetXMLValue(psRegion, "Filename.relative", "0")) != 0)
     464             :         {
     465          34 :             const std::string osSFPath = CPLGetPathSafe(osSparseFilePath);
     466          68 :             oRegion.osFilename = CPLFormFilenameSafe(
     467          34 :                 osSFPath.c_str(), oRegion.osFilename, nullptr);
     468             :         }
     469             : 
     470             :         // TODO(schwehr): Symbolic constant and an explanation for 32.
     471         172 :         oRegion.nDstOffset = CPLScanUIntBig(
     472             :             CPLGetXMLValue(psRegion, "DestinationOffset", "0"), 32);
     473             : 
     474         172 :         oRegion.nSrcOffset =
     475         172 :             CPLScanUIntBig(CPLGetXMLValue(psRegion, "SourceOffset", "0"), 32);
     476             : 
     477         172 :         oRegion.nLength =
     478         172 :             CPLScanUIntBig(CPLGetXMLValue(psRegion, "RegionLength", "0"), 32);
     479             : 
     480         172 :         oRegion.byValue =
     481         172 :             static_cast<GByte>(atoi(CPLGetXMLValue(psRegion, "Value", "0")));
     482             : 
     483         172 :         poHandle->aoRegions.push_back(std::move(oRegion));
     484             :     }
     485             : 
     486             :     /* -------------------------------------------------------------------- */
     487             :     /*      Get sparse file length, use maximum bound of regions if not     */
     488             :     /*      explicit in file.                                               */
     489             :     /* -------------------------------------------------------------------- */
     490          72 :     poHandle->nOverallLength =
     491          72 :         CPLScanUIntBig(CPLGetXMLValue(psXMLRoot, "Length", "0"), 32);
     492          72 :     if (poHandle->nOverallLength == 0)
     493             :     {
     494         132 :         for (unsigned int i = 0; i < poHandle->aoRegions.size(); i++)
     495             :         {
     496          88 :             poHandle->nOverallLength = std::max(
     497         176 :                 poHandle->nOverallLength, poHandle->aoRegions[i].nDstOffset +
     498          88 :                                               poHandle->aoRegions[i].nLength);
     499             :         }
     500             :     }
     501             : 
     502          72 :     CPLDestroyXMLNode(psXMLRoot);
     503             : 
     504          72 :     return poHandle;
     505             : }
     506             : 
     507             : /************************************************************************/
     508             : /*                                Stat()                                */
     509             : /************************************************************************/
     510             : 
     511          53 : int VSISparseFileFilesystemHandler::Stat(const char *pszFilename,
     512             :                                          VSIStatBufL *psStatBuf, int nFlags)
     513             : 
     514             : {
     515             :     // TODO(schwehr): Fix this so that the using statement is not needed.
     516             :     // Will just adding the bool for bSetError be okay?
     517          53 :     VSIVirtualHandle *poFile = Open(pszFilename, "r");
     518             : 
     519          53 :     memset(psStatBuf, 0, sizeof(VSIStatBufL));
     520             : 
     521          53 :     if (poFile == nullptr)
     522          46 :         return -1;
     523             : 
     524           7 :     poFile->Seek(0, SEEK_END);
     525           7 :     const vsi_l_offset nLength = poFile->Tell();
     526           7 :     delete poFile;
     527             : 
     528             :     const int nResult =
     529           7 :         VSIStatExL(pszFilename + strlen("/vsisparse/"), psStatBuf, nFlags);
     530             : 
     531           7 :     psStatBuf->st_size = nLength;
     532             : 
     533           7 :     return nResult;
     534             : }
     535             : 
     536             : /************************************************************************/
     537             : /*                               Unlink()                               */
     538             : /************************************************************************/
     539             : 
     540           0 : int VSISparseFileFilesystemHandler::Unlink(const char * /* pszFilename */)
     541             : {
     542           0 :     errno = EACCES;
     543           0 :     return -1;
     544             : }
     545             : 
     546             : /************************************************************************/
     547             : /*                               Mkdir()                                */
     548             : /************************************************************************/
     549             : 
     550           0 : int VSISparseFileFilesystemHandler::Mkdir(const char * /* pszPathname */,
     551             :                                           long /* nMode */)
     552             : {
     553           0 :     errno = EACCES;
     554           0 :     return -1;
     555             : }
     556             : 
     557             : /************************************************************************/
     558             : /*                               Rmdir()                                */
     559             : /************************************************************************/
     560             : 
     561           0 : int VSISparseFileFilesystemHandler::Rmdir(const char * /* pszPathname */)
     562             : {
     563           0 :     errno = EACCES;
     564           0 :     return -1;
     565             : }
     566             : 
     567             : /************************************************************************/
     568             : /*                              ReadDirEx()                             */
     569             : /************************************************************************/
     570             : 
     571          15 : char **VSISparseFileFilesystemHandler::ReadDirEx(const char * /* pszPath */,
     572             :                                                  int /* nMaxFiles */)
     573             : {
     574          15 :     errno = EACCES;
     575          15 :     return nullptr;
     576             : }
     577             : 
     578             : /************************************************************************/
     579             : /*                 VSIInstallSparseFileFilesystemHandler()              */
     580             : /************************************************************************/
     581             : 
     582             : /*!
     583             :  \brief Install /vsisparse/ virtual file handler.
     584             : 
     585             :  \verbatim embed:rst
     586             :  See :ref:`/vsisparse/ documentation <vsisparse>`
     587             :  \endverbatim
     588             :  */
     589             : 
     590        1694 : void VSIInstallSparseFileHandler()
     591             : {
     592        1694 :     VSIFileManager::InstallHandler("/vsisparse/",
     593        1694 :                                    new VSISparseFileFilesystemHandler);
     594        1694 : }

Generated by: LCOV version 1.14