LCOV - code coverage report
Current view: top level - port - cpl_vsil_libarchive.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 0 4 0.0 %
Date: 2024-04-28 21:03:45 Functions: 0 2 0.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  CPL - Common Portability Library
       4             :  * Purpose:  Implement VSI large file api for /vsi7z/ and /vsirar/
       5             :  * Author:   Even Rouault, even.rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "cpl_port.h"
      30             : #include "cpl_vsi_virtual.h"
      31             : 
      32             : #ifndef HAVE_LIBARCHIVE
      33             : 
      34             : /************************************************************************/
      35             : /*                    VSIInstall7zFileHandler()                         */
      36             : /************************************************************************/
      37             : 
      38             : /*!
      39             :  \brief Install /vsi7z/ 7zip file system handler (requires libarchive)
      40             : 
      41             :  \verbatim embed:rst
      42             :  See :ref:`/vsi7z/ documentation <vsi7z>`
      43             :  \endverbatim
      44             : 
      45             :  @since GDAL 3.7
      46             :  */
      47           0 : void VSIInstall7zFileHandler(void)
      48             : {
      49             :     // dummy
      50           0 : }
      51             : 
      52             : /************************************************************************/
      53             : /*                    VSIInstallRarFileHandler()                         */
      54             : /************************************************************************/
      55             : 
      56             : /*!
      57             :  \brief Install /vsirar/ RAR file system handler (requires libarchive)
      58             : 
      59             :  \verbatim embed:rst
      60             :  See :ref:`/vsirar/ documentation <vsirar>`
      61             :  \endverbatim
      62             : 
      63             :  @since GDAL 3.7
      64             :  */
      65           0 : void VSIInstallRarFileHandler(void)
      66             : {
      67             :     // dummy
      68           0 : }
      69             : 
      70             : #else
      71             : 
      72             : //! @cond Doxygen_Suppress
      73             : 
      74             : #include <algorithm>
      75             : #include <limits>
      76             : #include <memory>
      77             : 
      78             : // libarchive
      79             : #ifdef USE_INTERNAL_LIBARCHIVE
      80             : #include "archive_gdal_config.h"
      81             : #endif
      82             : 
      83             : #include "archive.h"
      84             : #include "archive_entry.h"
      85             : 
      86             : /************************************************************************/
      87             : /* ==================================================================== */
      88             : /*                      VSILibArchiveClientData                         */
      89             : /* ==================================================================== */
      90             : /************************************************************************/
      91             : 
      92             : struct VSILibArchiveClientData
      93             : {
      94             :     CPL_DISALLOW_COPY_ASSIGN(VSILibArchiveClientData)
      95             : 
      96             :     const std::string m_osFilename;
      97             :     VSIVirtualHandle *m_poBaseHandle = nullptr;
      98             :     std::vector<GByte> m_abyBuffer{};
      99             : 
     100             :     VSILibArchiveClientData(const char *pszFilename) : m_osFilename(pszFilename)
     101             :     {
     102             :         m_abyBuffer.resize(4096);
     103             :     }
     104             : 
     105             :     static int openCbk(struct archive *pArchive, void *pClientData)
     106             :     {
     107             :         auto poClientData = static_cast<VSILibArchiveClientData *>(pClientData);
     108             :         CPLDebug("VSIARCH", "Opening %s", poClientData->m_osFilename.c_str());
     109             :         poClientData->m_poBaseHandle = reinterpret_cast<VSIVirtualHandle *>(
     110             :             VSIFOpenL(poClientData->m_osFilename.c_str(), "rb"));
     111             :         if (poClientData->m_poBaseHandle == nullptr)
     112             :         {
     113             :             archive_set_error(pArchive, -1, "Cannot open file");
     114             :             return ARCHIVE_FATAL;
     115             :         }
     116             :         return ARCHIVE_OK;
     117             :     }
     118             : 
     119             :     static int closeCbk(struct archive *pArchive, void *pClientData)
     120             :     {
     121             :         auto poClientData = static_cast<VSILibArchiveClientData *>(pClientData);
     122             :         int ret = 0;
     123             :         if (poClientData->m_poBaseHandle)
     124             :         {
     125             :             ret = poClientData->m_poBaseHandle->Close();
     126             :             delete poClientData->m_poBaseHandle;
     127             :         }
     128             :         delete poClientData;
     129             :         if (ret == 0)
     130             :             return ARCHIVE_OK;
     131             :         archive_set_error(pArchive, -1, "Cannot close file");
     132             :         return ARCHIVE_FATAL;
     133             :     }
     134             : 
     135             :     static la_ssize_t readCbk(struct archive *, void *pClientData,
     136             :                               const void **ppBuffer)
     137             :     {
     138             :         auto poClientData = static_cast<VSILibArchiveClientData *>(pClientData);
     139             :         *ppBuffer = poClientData->m_abyBuffer.data();
     140             :         return static_cast<la_ssize_t>(poClientData->m_poBaseHandle->Read(
     141             :             poClientData->m_abyBuffer.data(), 1,
     142             :             poClientData->m_abyBuffer.size()));
     143             :     }
     144             : 
     145             :     static la_int64_t seekCkb(struct archive *, void *pClientData,
     146             :                               la_int64_t offset, int whence)
     147             :     {
     148             :         auto poClientData = static_cast<VSILibArchiveClientData *>(pClientData);
     149             :         if (whence == SEEK_CUR && offset < 0)
     150             :         {
     151             :             whence = SEEK_SET;
     152             :             offset = poClientData->m_poBaseHandle->Tell() + offset;
     153             :         }
     154             :         if (poClientData->m_poBaseHandle->Seek(
     155             :                 static_cast<vsi_l_offset>(offset), whence) != 0)
     156             :             return ARCHIVE_FATAL;
     157             :         return static_cast<la_int64_t>(poClientData->m_poBaseHandle->Tell());
     158             :     }
     159             : };
     160             : 
     161             : /************************************************************************/
     162             : /*                    VSILibArchiveReadOpen()                           */
     163             : /************************************************************************/
     164             : 
     165             : /**Open an archive, with the base handle being a VSIVirtualHandle* */
     166             : static int VSILibArchiveReadOpen(struct archive *pArchive,
     167             :                                  const char *pszFilename)
     168             : {
     169             :     archive_read_set_seek_callback(pArchive, VSILibArchiveClientData::seekCkb);
     170             :     return archive_read_open(pArchive, new VSILibArchiveClientData(pszFilename),
     171             :                              VSILibArchiveClientData::openCbk,
     172             :                              VSILibArchiveClientData::readCbk,
     173             :                              VSILibArchiveClientData::closeCbk);
     174             : }
     175             : 
     176             : /************************************************************************/
     177             : /*                    VSICreateArchiveHandle()                          */
     178             : /************************************************************************/
     179             : 
     180             : static struct archive *VSICreateArchiveHandle(const std::string &osFSPrefix)
     181             : {
     182             :     auto pArchive = archive_read_new();
     183             : 
     184             :     if (osFSPrefix == "/vsi7z")
     185             :     {
     186             :         archive_read_support_format_7zip(pArchive);
     187             :     }
     188             :     else
     189             :     {
     190             :         archive_read_support_format_rar(pArchive);
     191             :         archive_read_support_format_rar5(pArchive);
     192             :     }
     193             : 
     194             :     return pArchive;
     195             : }
     196             : 
     197             : /************************************************************************/
     198             : /* ==================================================================== */
     199             : /*                      VSILibArchiveReader                             */
     200             : /* ==================================================================== */
     201             : /************************************************************************/
     202             : 
     203             : class VSILibArchiveReader final : public VSIArchiveReader
     204             : {
     205             :     CPL_DISALLOW_COPY_ASSIGN(VSILibArchiveReader)
     206             : 
     207             :     std::string m_osArchiveFileName;
     208             :     struct archive *m_pArchive;
     209             :     std::string m_osPrefix;
     210             :     bool m_bFirst = true;
     211             :     std::string m_osFilename{};
     212             :     GUIntBig m_nFilesize = 0;
     213             :     GIntBig m_nMTime = 0;
     214             : 
     215             :   public:
     216             :     VSILibArchiveReader(const char *pszArchiveFileName,
     217             :                         struct archive *pArchive, const std::string &osPrefix)
     218             :         : m_osArchiveFileName(pszArchiveFileName), m_pArchive(pArchive),
     219             :           m_osPrefix(osPrefix)
     220             :     {
     221             :     }
     222             : 
     223             :     ~VSILibArchiveReader() override;
     224             : 
     225             :     struct archive *GetArchiveHandler()
     226             :     {
     227             :         return m_pArchive;
     228             :     }
     229             : 
     230             :     int GotoFirstFileForced();
     231             : 
     232             :     virtual int GotoFirstFile() override;
     233             :     virtual int GotoNextFile() override;
     234             :     virtual VSIArchiveEntryFileOffset *GetFileOffset() override;
     235             : 
     236             :     virtual GUIntBig GetFileSize() override
     237             :     {
     238             :         return m_nFilesize;
     239             :     }
     240             : 
     241             :     virtual CPLString GetFileName() override
     242             :     {
     243             :         return m_osFilename;
     244             :     }
     245             : 
     246             :     virtual GIntBig GetModifiedTime() override
     247             :     {
     248             :         return m_nMTime;
     249             :     }
     250             : 
     251             :     virtual int GotoFileOffset(VSIArchiveEntryFileOffset *pOffset) override;
     252             : 
     253             :     int GotoFileOffsetForced(VSIArchiveEntryFileOffset *pOffset);
     254             : };
     255             : 
     256             : /************************************************************************/
     257             : /*                       ~VSILibArchiveReader()                         */
     258             : /************************************************************************/
     259             : 
     260             : VSILibArchiveReader::~VSILibArchiveReader()
     261             : {
     262             :     archive_free(m_pArchive);
     263             : }
     264             : 
     265             : /************************************************************************/
     266             : /*                           GotoFirstFile()                            */
     267             : /************************************************************************/
     268             : 
     269             : int VSILibArchiveReader::GotoFirstFile()
     270             : {
     271             :     if (!m_bFirst)
     272             :     {
     273             :         archive_free(m_pArchive);
     274             : 
     275             :         m_pArchive = VSICreateArchiveHandle(m_osPrefix);
     276             : 
     277             :         if (VSILibArchiveReadOpen(m_pArchive, m_osArchiveFileName.c_str()))
     278             :         {
     279             :             CPLDebug("VSIARCH", "%s: %s", m_osArchiveFileName.c_str(),
     280             :                      archive_error_string(m_pArchive));
     281             :             return false;
     282             :         }
     283             :         m_bFirst = true;
     284             :     }
     285             :     return GotoNextFile();
     286             : }
     287             : 
     288             : /************************************************************************/
     289             : /*                           GotoNextFile()                             */
     290             : /************************************************************************/
     291             : 
     292             : int VSILibArchiveReader::GotoNextFile()
     293             : {
     294             :     struct archive_entry *entry;
     295             :     int r = archive_read_next_header(m_pArchive, &entry);
     296             :     if (r == ARCHIVE_EOF)
     297             :         return FALSE;
     298             :     if (r != ARCHIVE_OK)
     299             :     {
     300             :         CPLDebug("VSIARCH", "%s", archive_error_string(m_pArchive));
     301             :         return FALSE;
     302             :     }
     303             :     m_osFilename = archive_entry_pathname_utf8(entry);
     304             :     m_nFilesize = archive_entry_size(entry);
     305             :     m_nMTime = archive_entry_mtime(entry);
     306             :     return TRUE;
     307             : }
     308             : 
     309             : /************************************************************************/
     310             : /*                      VSILibArchiveEntryFileOffset                    */
     311             : /************************************************************************/
     312             : 
     313             : struct VSILibArchiveEntryFileOffset : public VSIArchiveEntryFileOffset
     314             : {
     315             :     const std::string m_osFilename;
     316             : 
     317             :     VSILibArchiveEntryFileOffset(const std::string &osFilename)
     318             :         : m_osFilename(osFilename)
     319             :     {
     320             :     }
     321             : };
     322             : 
     323             : /************************************************************************/
     324             : /*                          GetFileOffset()                             */
     325             : /************************************************************************/
     326             : 
     327             : VSIArchiveEntryFileOffset *VSILibArchiveReader::GetFileOffset()
     328             : {
     329             :     return new VSILibArchiveEntryFileOffset(m_osFilename);
     330             : }
     331             : 
     332             : /************************************************************************/
     333             : /*                         GotoFileOffset()                             */
     334             : /************************************************************************/
     335             : 
     336             : int VSILibArchiveReader::GotoFileOffset(VSIArchiveEntryFileOffset *pOffset)
     337             : {
     338             :     VSILibArchiveEntryFileOffset *pMyOffset =
     339             :         static_cast<VSILibArchiveEntryFileOffset *>(pOffset);
     340             :     if (!GotoFirstFile())
     341             :         return false;
     342             :     while (m_osFilename != pMyOffset->m_osFilename)
     343             :     {
     344             :         if (!GotoNextFile())
     345             :             return false;
     346             :     }
     347             :     return true;
     348             : }
     349             : 
     350             : /************************************************************************/
     351             : /*                       GotoFileOffsetForced()                         */
     352             : /************************************************************************/
     353             : 
     354             : int VSILibArchiveReader::GotoFileOffsetForced(
     355             :     VSIArchiveEntryFileOffset *pOffset)
     356             : {
     357             :     m_bFirst = false;
     358             :     return GotoFileOffset(pOffset);
     359             : }
     360             : 
     361             : /************************************************************************/
     362             : /* ==================================================================== */
     363             : /*                      VSILibArchiveHandler                            */
     364             : /* ==================================================================== */
     365             : /************************************************************************/
     366             : 
     367             : class VSILibArchiveHandler final : public VSIVirtualHandle
     368             : {
     369             :     const std::string m_osFilename;
     370             :     std::unique_ptr<VSILibArchiveReader> m_poReader;
     371             :     std::unique_ptr<VSIArchiveEntryFileOffset> m_pOffset;
     372             :     vsi_l_offset m_nOffset = 0;
     373             :     bool m_bEOF = false;
     374             :     bool m_bError = false;
     375             : 
     376             :   public:
     377             :     VSILibArchiveHandler(const std::string &osFilename,
     378             :                          VSILibArchiveReader *poReader)
     379             :         : m_osFilename(osFilename), m_poReader(poReader),
     380             :           m_pOffset(poReader->GetFileOffset())
     381             :     {
     382             :     }
     383             : 
     384             :     virtual size_t Read(void *pBuffer, size_t nSize, size_t nCount) override;
     385             :     virtual int Seek(vsi_l_offset nOffset, int nWhence) override;
     386             : 
     387             :     virtual vsi_l_offset Tell() override
     388             :     {
     389             :         return m_nOffset;
     390             :     }
     391             : 
     392             :     virtual size_t Write(const void *, size_t, size_t) override
     393             :     {
     394             :         return 0;
     395             :     }
     396             : 
     397             :     virtual int Eof() override
     398             :     {
     399             :         return m_bEOF ? 1 : 0;
     400             :     }
     401             : 
     402             :     virtual int Close() override
     403             :     {
     404             :         return 0;
     405             :     }
     406             : };
     407             : 
     408             : /************************************************************************/
     409             : /*                                Read()                                */
     410             : /************************************************************************/
     411             : 
     412             : size_t VSILibArchiveHandler::Read(void *pBuffer, size_t nSize, size_t nCount)
     413             : {
     414             :     if (m_bError || nSize == 0 || nCount == 0)
     415             :         return 0;
     416             :     if (m_nOffset == m_poReader->GetFileSize())
     417             :     {
     418             :         m_bEOF = true;
     419             :         return 0;
     420             :     }
     421             :     size_t nToRead = nSize * nCount;
     422             :     auto nRead = static_cast<size_t>(
     423             :         archive_read_data(m_poReader->GetArchiveHandler(), pBuffer, nToRead));
     424             :     if (nRead < nToRead)
     425             :         m_bEOF = true;
     426             :     m_nOffset += nRead;
     427             :     return nRead / nSize;
     428             : }
     429             : 
     430             : /************************************************************************/
     431             : /*                                Seek()                                */
     432             : /************************************************************************/
     433             : 
     434             : int VSILibArchiveHandler::Seek(vsi_l_offset nOffset, int nWhence)
     435             : {
     436             :     if (m_bError)
     437             :         return -1;
     438             :     m_bEOF = false;
     439             :     if (nWhence == SEEK_END && nOffset == 0)
     440             :     {
     441             :         m_nOffset = m_poReader->GetFileSize();
     442             :         return 0;
     443             :     }
     444             :     auto nNewOffset = m_nOffset;
     445             :     if (nWhence == SEEK_CUR)
     446             :         nNewOffset += nOffset;
     447             :     else
     448             :         nNewOffset = nOffset;
     449             :     if (nNewOffset == m_nOffset)
     450             :         return 0;
     451             : 
     452             :     if (nNewOffset < m_nOffset)
     453             :     {
     454             :         CPLDebug("VSIARCH", "Seeking backwards in %s", m_osFilename.c_str());
     455             :         // If we need to go backwards, we must completely reset the
     456             :         // reader!
     457             :         if (!m_poReader->GotoFileOffsetForced(m_pOffset.get()))
     458             :         {
     459             :             m_bError = true;
     460             :             return -1;
     461             :         }
     462             :         m_nOffset = 0;
     463             :     }
     464             : 
     465             :     std::vector<GByte> abyBuffer(4096);
     466             :     while (m_nOffset < nNewOffset)
     467             :     {
     468             :         size_t nToRead = static_cast<size_t>(
     469             :             std::min<vsi_l_offset>(abyBuffer.size(), nNewOffset - m_nOffset));
     470             :         if (Read(abyBuffer.data(), 1, nToRead) != nToRead)
     471             :             break;
     472             :     }
     473             : 
     474             :     return 0;
     475             : }
     476             : 
     477             : /************************************************************************/
     478             : /* ==================================================================== */
     479             : /*                      VSILibArchiveFilesystemHandler                  */
     480             : /* ==================================================================== */
     481             : /************************************************************************/
     482             : 
     483             : class VSILibArchiveFilesystemHandler final : public VSIArchiveFilesystemHandler
     484             : {
     485             :     CPL_DISALLOW_COPY_ASSIGN(VSILibArchiveFilesystemHandler)
     486             : 
     487             :     const std::string m_osPrefix;
     488             : 
     489             :     virtual const char *GetPrefix() override
     490             :     {
     491             :         return m_osPrefix.c_str();
     492             :     }
     493             : 
     494             :     virtual std::vector<CPLString> GetExtensions() override
     495             :     {
     496             :         if (m_osPrefix == "/vsi7z")
     497             :         {
     498             :             return {".7z", ".lpk", ".lpkx", ".mpk", ".mpkx", ".ppkx"};
     499             :         }
     500             :         else
     501             :         {
     502             :             return {".rar"};
     503             :         }
     504             :     }
     505             : 
     506             :     virtual VSIArchiveReader *
     507             :     CreateReader(const char *pszArchiveFileName) override;
     508             : 
     509             :   public:
     510             :     VSILibArchiveFilesystemHandler(const std::string &osPrefix)
     511             :         : m_osPrefix(osPrefix)
     512             :     {
     513             :     }
     514             : 
     515             :     virtual VSIVirtualHandle *Open(const char *pszFilename,
     516             :                                    const char *pszAccess, bool bSetError,
     517             :                                    CSLConstList papszOptions) override;
     518             : };
     519             : 
     520             : /************************************************************************/
     521             : /*                                 Open()                               */
     522             : /************************************************************************/
     523             : 
     524             : VSIVirtualHandle *VSILibArchiveFilesystemHandler::Open(const char *pszFilename,
     525             :                                                        const char *pszAccess,
     526             :                                                        bool, CSLConstList)
     527             : {
     528             :     if (strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, '+') != nullptr)
     529             :     {
     530             :         CPLError(CE_Failure, CPLE_AppDefined,
     531             :                  "Only read-only mode is supported for %s", m_osPrefix.c_str());
     532             :         return nullptr;
     533             :     }
     534             : 
     535             :     CPLString osFileInArchive;
     536             :     char *pszArchiveFileName =
     537             :         SplitFilename(pszFilename, osFileInArchive, TRUE);
     538             :     if (pszArchiveFileName == nullptr)
     539             :         return nullptr;
     540             : 
     541             :     VSILibArchiveReader *poReader = cpl::down_cast<VSILibArchiveReader *>(
     542             :         OpenArchiveFile(pszArchiveFileName, osFileInArchive));
     543             :     CPLFree(pszArchiveFileName);
     544             :     if (poReader == nullptr)
     545             :     {
     546             :         return nullptr;
     547             :     }
     548             : 
     549             :     return new VSILibArchiveHandler(pszFilename, poReader);
     550             : }
     551             : 
     552             : /************************************************************************/
     553             : /*                           CreateReader()                             */
     554             : /************************************************************************/
     555             : 
     556             : VSIArchiveReader *
     557             : VSILibArchiveFilesystemHandler::CreateReader(const char *pszArchiveFileName)
     558             : {
     559             :     auto pArchive = VSICreateArchiveHandle(m_osPrefix);
     560             : 
     561             :     if (VSILibArchiveReadOpen(pArchive, pszArchiveFileName))
     562             :     {
     563             :         CPLDebug("VSIARCH", "%s: %s", pszArchiveFileName,
     564             :                  archive_error_string(pArchive));
     565             :         archive_read_free(pArchive);
     566             :         return nullptr;
     567             :     }
     568             :     return new VSILibArchiveReader(pszArchiveFileName, pArchive, m_osPrefix);
     569             : }
     570             : 
     571             : //! @endcond
     572             : 
     573             : /************************************************************************/
     574             : /*                    VSIInstall7zFileHandler()                         */
     575             : /************************************************************************/
     576             : 
     577             : /*!
     578             :  \brief Install /vsi7z/ 7zip file system handler (requires libarchive)
     579             : 
     580             :  \verbatim embed:rst
     581             :  See :ref:`/vsi7z/ documentation <vsi7z>`
     582             :  \endverbatim
     583             : 
     584             :  @since GDAL 3.7
     585             :  */
     586             : void VSIInstall7zFileHandler(void)
     587             : {
     588             :     VSIFileManager::InstallHandler(
     589             :         "/vsi7z/", new VSILibArchiveFilesystemHandler("/vsi7z"));
     590             : }
     591             : 
     592             : /************************************************************************/
     593             : /*                    VSIInstallRarFileHandler()                         */
     594             : /************************************************************************/
     595             : 
     596             : /*!
     597             :  \brief Install /vsirar/ rar file system handler (requires libarchive)
     598             : 
     599             :  \verbatim embed:rst
     600             :  See :ref:`/vsirar/ documentation <vsirar>`
     601             :  \endverbatim
     602             : 
     603             :  @since GDAL 3.7
     604             :  */
     605             : void VSIInstallRarFileHandler(void)
     606             : {
     607             :     VSIFileManager::InstallHandler(
     608             :         "/vsirar/", new VSILibArchiveFilesystemHandler("/vsirar"));
     609             : }
     610             : 
     611             : #endif

Generated by: LCOV version 1.14