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

Generated by: LCOV version 1.14