LCOV - code coverage report
Current view: top level - port - cpl_vsil_sparsefile.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 158 185 85.4 %
Date: 2026-02-08 20:30:07 Functions: 19 24 79.2 %

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

Generated by: LCOV version 1.14