LCOV - code coverage report
Current view: top level - apps - gdalalg_vsi_sozip.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 316 376 84.0 %
Date: 2025-08-01 10:10:57 Functions: 17 17 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "sozip" subcommand
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_vsi_sozip.h"
      14             : 
      15             : #include "cpl_conv.h"
      16             : #include "cpl_string.h"
      17             : #include "cpl_time.h"
      18             : 
      19             : #include <limits>
      20             : 
      21             : //! @cond Doxygen_Suppress
      22             : 
      23             : #ifndef _
      24             : #define _(x) (x)
      25             : #endif
      26             : 
      27             : /************************************************************************/
      28             : /*                    GDALVSISOZIPCreateBaseAlgorithm                   */
      29             : /************************************************************************/
      30             : 
      31             : class GDALVSISOZIPCreateBaseAlgorithm /* non final */ : public GDALAlgorithm
      32             : {
      33             :   protected:
      34          23 :     GDALVSISOZIPCreateBaseAlgorithm(const std::string &name,
      35             :                                     const std::string &description,
      36             :                                     const std::string &helpURL,
      37             :                                     bool optimizeFrom)
      38          23 :         : GDALAlgorithm(name, description, helpURL),
      39          23 :           m_optimizeFrom(optimizeFrom)
      40             :     {
      41          23 :         AddProgressArg();
      42          23 :         if (optimizeFrom)
      43          10 :             AddArg("input", 'i', _("Input ZIP filename"), &m_inputFilenames)
      44           5 :                 .SetRequired()
      45           5 :                 .SetPositional()
      46           5 :                 .SetMaxCount(1);
      47             :         else
      48          36 :             AddArg("input", 'i', _("Input filenames"), &m_inputFilenames)
      49          18 :                 .SetRequired()
      50          18 :                 .SetPositional();
      51          46 :         AddArg("output", 'o', _("Output ZIP filename"), &m_zipFilename)
      52          23 :             .SetRequired()
      53          23 :             .SetPositional()
      54             :             .AddValidationAction(
      55          22 :                 [this]()
      56             :                 {
      57          21 :                     if (!EQUAL(
      58             :                             CPLGetExtensionSafe(m_zipFilename.c_str()).c_str(),
      59             :                             "zip"))
      60             :                     {
      61           1 :                         ReportError(CE_Failure, CPLE_AppDefined,
      62             :                                     "Extension of zip filename should be .zip");
      63           1 :                         return false;
      64             :                     }
      65          20 :                     return true;
      66          23 :                 });
      67          23 :         AddOverwriteArg(&m_overwrite);
      68          23 :         if (!optimizeFrom)
      69             :         {
      70             :             AddArg("recursive", 'r',
      71             :                    _("Travels the directory structure of the specified "
      72             :                      "directories recursively"),
      73          36 :                    &m_recursive)
      74          18 :                 .AddHiddenAlias("recurse");
      75             :         }
      76          23 :         if (!optimizeFrom)
      77             :         {
      78             :             AddArg("no-paths", 'j',
      79             :                    _("Store just the name of a saved file, and do not store "
      80             :                      "directory names"),
      81          36 :                    &m_noDirName)
      82          18 :                 .AddAlias("junk-paths");
      83             :         }
      84             :         AddArg("enable-sozip", 0,
      85             :                _("Whether to automatically/systematically/never apply the "
      86             :                  "SOZIP optimization"),
      87          46 :                &m_mode)
      88          23 :             .SetDefault(m_mode)
      89          23 :             .SetChoices("auto", "yes", "no");
      90             :         AddArg("sozip-chunk-size", 0, _("Chunk size for a seek-optimized file"),
      91          46 :                &m_chunkSize)
      92          46 :             .SetMetaVar("<value in bytes or with K/M suffix>")
      93          23 :             .SetDefault(m_chunkSize)
      94          23 :             .SetMinCharCount(1);
      95             :         AddArg(
      96             :             "sozip-min-file-size", 0,
      97             :             _("Minimum file size to decide if a file should be seek-optimized"),
      98          46 :             &m_minFileSize)
      99          46 :             .SetMetaVar("<value in bytes or with K/M/G suffix>")
     100          23 :             .SetDefault(m_minFileSize)
     101          23 :             .SetMinCharCount(1);
     102          23 :         if (!optimizeFrom)
     103             :             AddArg("content-type", 0,
     104             :                    _("Store the Content-Type of the file being added."),
     105          36 :                    &m_contentType)
     106          18 :                 .SetMinCharCount(1);
     107             : 
     108          23 :         AddOutputStringArg(&m_output);
     109             :         AddArg("stdout", 0,
     110             :                _("Directly output on stdout. If enabled, "
     111             :                  "output-string will be empty"),
     112          46 :                &m_stdout)
     113          23 :             .SetHiddenForCLI();
     114          23 :     }
     115             : 
     116             :   private:
     117             :     const bool m_optimizeFrom;
     118             :     std::vector<std::string> m_inputFilenames{};
     119             :     std::string m_zipFilename{};
     120             :     bool m_overwrite = false;
     121             :     bool m_recursive = false;
     122             :     bool m_noDirName = false;
     123             :     std::string m_mode = "auto";
     124             :     std::string m_chunkSize = "32768";
     125             :     std::string m_minFileSize = "1 MB";
     126             :     std::string m_contentType{};
     127             :     std::string m_output{};
     128             :     bool m_stdout = false;
     129             : 
     130             :     bool RunImpl(GDALProgressFunc, void *) override;
     131             : 
     132          65 :     void Output(const std::string &s)
     133             :     {
     134          65 :         if (!m_quiet)
     135             :         {
     136          65 :             if (m_stdout)
     137          52 :                 printf("%s", s.c_str());
     138             :             else
     139          13 :                 m_output += s;
     140             :         }
     141          65 :     }
     142             : };
     143             : 
     144             : /************************************************************************/
     145             : /*                GDALVSISOZIPCreateBaseAlgorithm::RunImpl()            */
     146             : /************************************************************************/
     147             : 
     148          20 : bool GDALVSISOZIPCreateBaseAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
     149             :                                               void *pProgressData)
     150             : {
     151          40 :     CPLStringList aosOptions;
     152          20 :     aosOptions.SetNameValue("SOZIP_ENABLED", m_mode.c_str());
     153          20 :     aosOptions.SetNameValue("SOZIP_CHUNK_SIZE", m_chunkSize.c_str());
     154          20 :     aosOptions.SetNameValue("SOZIP_MIN_FILE_SIZE", m_minFileSize.c_str());
     155          20 :     if (!m_contentType.empty())
     156           4 :         aosOptions.SetNameValue("CONTENT_TYPE", m_contentType.c_str());
     157             : 
     158             :     VSIStatBufL sBuf;
     159          40 :     CPLStringList aosOptionsCreateZip;
     160          20 :     if (m_overwrite)
     161             :     {
     162           1 :         VSIUnlink(m_zipFilename.c_str());
     163             :     }
     164             :     else
     165             :     {
     166          19 :         if (VSIStatExL(m_zipFilename.c_str(), &sBuf, VSI_STAT_EXISTS_FLAG) == 0)
     167             :         {
     168           5 :             if (m_optimizeFrom)
     169             :             {
     170           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     171             :                             "%s already exists. Use --overwrite",
     172             :                             m_zipFilename.c_str());
     173           1 :                 return false;
     174             :             }
     175           4 :             aosOptionsCreateZip.SetNameValue("APPEND", "TRUE");
     176             :         }
     177             :     }
     178             : 
     179          38 :     std::vector<std::string> aosFiles = m_inputFilenames;
     180          38 :     std::string osRemovePrefix;
     181          19 :     if (m_optimizeFrom)
     182             :     {
     183             :         std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
     184             :             VSIOpenDir(
     185           6 :                 std::string("/vsizip/").append(m_inputFilenames[0]).c_str(), -1,
     186             :                 nullptr),
     187           6 :             VSICloseDir);
     188           3 :         if (!psDir)
     189             :         {
     190           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     191             :                         "%s is not a valid .zip file",
     192           1 :                         m_inputFilenames[0].c_str());
     193           1 :             return false;
     194             :         }
     195             : 
     196             :         osRemovePrefix =
     197           2 :             std::string("/vsizip/{").append(m_inputFilenames[0]).append("}/");
     198          55 :         while (const auto psEntry = VSIGetNextDirEntry(psDir.get()))
     199             :         {
     200          53 :             if (!VSI_ISDIR(psEntry->nMode))
     201             :             {
     202          49 :                 aosFiles.push_back(osRemovePrefix + psEntry->pszName);
     203             :             }
     204          53 :         }
     205             :     }
     206          16 :     else if (m_recursive)
     207             :     {
     208           2 :         std::vector<std::string> aosNewFiles;
     209           4 :         for (const std::string &osFile : m_inputFilenames)
     210             :         {
     211           2 :             if (VSIStatL(osFile.c_str(), &sBuf) == 0 && VSI_ISDIR(sBuf.st_mode))
     212             :             {
     213             :                 std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
     214           2 :                     VSIOpenDir(osFile.c_str(), -1, nullptr), VSICloseDir);
     215           2 :                 if (!psDir)
     216           0 :                     return false;
     217           8 :                 while (const auto psEntry = VSIGetNextDirEntry(psDir.get()))
     218             :                 {
     219           6 :                     if (!VSI_ISDIR(psEntry->nMode))
     220             :                     {
     221           4 :                         std::string osName(osFile);
     222           4 :                         if (osName.back() != '/')
     223           4 :                             osName += '/';
     224           4 :                         osName += psEntry->pszName;
     225           4 :                         aosNewFiles.push_back(std::move(osName));
     226           4 :                         if (aosNewFiles.size() > 10 * 1000 * 1000)
     227             :                         {
     228           0 :                             ReportError(CE_Failure, CPLE_NotSupported,
     229             :                                         "Too many source files");
     230           0 :                             return false;
     231             :                         }
     232             :                     }
     233           6 :                 }
     234             :             }
     235             :         }
     236           2 :         aosFiles = std::move(aosNewFiles);
     237             :     }
     238             : 
     239          18 :     uint64_t nTotalSize = 0;
     240          36 :     std::vector<uint64_t> anFileSizes;
     241             : 
     242          18 :     if (pfnProgress)
     243             :     {
     244             : #if defined(__GNUC__)
     245             : #pragma GCC diagnostic push
     246             : #pragma GCC diagnostic ignored "-Wnull-dereference"
     247             : #endif
     248           8 :         anFileSizes.resize(aosFiles.size());
     249             : #if defined(__GNUC__)
     250             : #pragma GCC diagnostic pop
     251             : #endif
     252          63 :         for (size_t i = 0; i < aosFiles.size(); ++i)
     253             :         {
     254          56 :             if (VSIStatL(aosFiles[i].c_str(), &sBuf) == 0)
     255             :             {
     256          55 :                 anFileSizes[i] = sBuf.st_size;
     257          55 :                 nTotalSize += sBuf.st_size;
     258             :             }
     259             :             else
     260             :             {
     261           1 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s does not exist",
     262           1 :                             aosFiles[i].c_str());
     263           1 :                 return false;
     264             :             }
     265             :         }
     266             :     }
     267             : 
     268             :     std::unique_ptr<void, decltype(&CPLCloseZip)> hZIP(
     269             :         CPLCreateZip(m_zipFilename.c_str(), aosOptionsCreateZip.List()),
     270          34 :         CPLCloseZip);
     271          17 :     if (!hZIP)
     272           0 :         return false;
     273             : 
     274          17 :     uint64_t nCurSize = 0;
     275          82 :     for (size_t i = 0; i < aosFiles.size(); ++i)
     276             :     {
     277          68 :         if (!m_quiet)
     278             :         {
     279         130 :             Output(CPLSPrintf("Adding %s... (%d/%d)\n", aosFiles[i].c_str(),
     280          65 :                               int(i + 1), static_cast<int>(aosFiles.size())));
     281             :         }
     282             : 
     283          68 :         if (VSIStatL(aosFiles[i].c_str(), &sBuf) != 0)
     284             :         {
     285           1 :             ReportError(CE_Failure, CPLE_AppDefined, "%s does not exist",
     286           1 :                         aosFiles[i].c_str());
     287           3 :             return false;
     288             :         }
     289          67 :         else if (VSI_ISDIR(sBuf.st_mode))
     290             :         {
     291           1 :             ReportError(CE_Failure, CPLE_AppDefined, "%s is a directory",
     292           1 :                         aosFiles[i].c_str());
     293           1 :             return false;
     294             :         }
     295             : 
     296          66 :         std::string osArchiveFilename(aosFiles[i]);
     297          66 :         if (m_noDirName)
     298             :         {
     299          11 :             osArchiveFilename = CPLGetFilename(aosFiles[i].c_str());
     300             :         }
     301         106 :         else if (!osRemovePrefix.empty() &&
     302          51 :                  STARTS_WITH(osArchiveFilename.c_str(), osRemovePrefix.c_str()))
     303             :         {
     304          49 :             osArchiveFilename = osArchiveFilename.substr(osRemovePrefix.size());
     305             :         }
     306           6 :         else if (osArchiveFilename[0] == '/')
     307             :         {
     308           5 :             osArchiveFilename = osArchiveFilename.substr(1);
     309             :         }
     310           1 :         else if (osArchiveFilename.size() > 3 && osArchiveFilename[1] == ':' &&
     311           0 :                  (osArchiveFilename[2] == '/' || osArchiveFilename[2] == '\\'))
     312             :         {
     313           0 :             osArchiveFilename = osArchiveFilename.substr(3);
     314             :         }
     315             : 
     316             :         std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
     317          66 :             pScaledProgress(nullptr, GDALDestroyScaledProgress);
     318          66 :         if (nTotalSize != 0)
     319             :         {
     320          55 :             pScaledProgress.reset(GDALCreateScaledProgress(
     321          55 :                 double(nCurSize) / nTotalSize,
     322          55 :                 double(nCurSize + anFileSizes[i]) / nTotalSize, pfnProgress,
     323             :                 pProgressData));
     324          55 :             nCurSize += anFileSizes[i];
     325             :         }
     326             : 
     327         198 :         const CPLErr eErr = CPLAddFileInZip(
     328          66 :             hZIP.get(), osArchiveFilename.c_str(), aosFiles[i].c_str(), nullptr,
     329         132 :             aosOptions.List(), pScaledProgress ? GDALScaledProgress : nullptr,
     330             :             pScaledProgress.get());
     331          66 :         if (eErr != CE_None)
     332             :         {
     333           1 :             ReportError(CE_Failure, CPLE_AppDefined, "Failed adding %s",
     334           1 :                         aosFiles[i].c_str());
     335           1 :             return false;
     336             :         }
     337             :     }
     338             : 
     339          14 :     return true;
     340             : }
     341             : 
     342             : /************************************************************************/
     343             : /*                    GDALVSISOZIPCreateAlgorithm                       */
     344             : /************************************************************************/
     345             : 
     346          36 : class GDALVSISOZIPCreateAlgorithm final : public GDALVSISOZIPCreateBaseAlgorithm
     347             : {
     348             :   public:
     349             :     static constexpr const char *NAME = "create";
     350             :     static constexpr const char *DESCRIPTION =
     351             :         "Create a Seek-optimized ZIP (SOZIP) file.";
     352             :     static constexpr const char *HELP_URL = "/programs/gdal_vsi_sozip.html";
     353             : 
     354          18 :     GDALVSISOZIPCreateAlgorithm()
     355          18 :         : GDALVSISOZIPCreateBaseAlgorithm(NAME, DESCRIPTION, HELP_URL, false)
     356             :     {
     357          18 :     }
     358             : 
     359             :     ~GDALVSISOZIPCreateAlgorithm() override;
     360             : };
     361             : 
     362             : GDALVSISOZIPCreateAlgorithm::~GDALVSISOZIPCreateAlgorithm() = default;
     363             : 
     364             : /************************************************************************/
     365             : /*                  GDALVSISOZIPOptimizeAlgorithm                       */
     366             : /************************************************************************/
     367             : 
     368          10 : class GDALVSISOZIPOptimizeAlgorithm final
     369             :     : public GDALVSISOZIPCreateBaseAlgorithm
     370             : {
     371             :   public:
     372             :     static constexpr const char *NAME = "optimize";
     373             :     static constexpr const char *DESCRIPTION =
     374             :         "Create a Seek-optimized ZIP (SOZIP) file from a regular ZIP file.";
     375             :     static constexpr const char *HELP_URL = "/programs/gdal_vsi_sozip.html";
     376             : 
     377           5 :     GDALVSISOZIPOptimizeAlgorithm()
     378           5 :         : GDALVSISOZIPCreateBaseAlgorithm(NAME, DESCRIPTION, HELP_URL, true)
     379             :     {
     380           5 :     }
     381             : 
     382             :     ~GDALVSISOZIPOptimizeAlgorithm() override;
     383             : };
     384             : 
     385             : GDALVSISOZIPOptimizeAlgorithm::~GDALVSISOZIPOptimizeAlgorithm() = default;
     386             : 
     387             : /************************************************************************/
     388             : /*                      GDALVSISOZIPListAlgorithm                       */
     389             : /************************************************************************/
     390             : 
     391             : class GDALVSISOZIPListAlgorithm final : public GDALAlgorithm
     392             : {
     393             :   public:
     394             :     static constexpr const char *NAME = "list";
     395             :     static constexpr const char *DESCRIPTION =
     396             :         "List content of a ZIP file, with SOZIP related information.";
     397             :     static constexpr const char *HELP_URL = "/programs/gdal_vsi_sozip.html";
     398             : 
     399           3 :     GDALVSISOZIPListAlgorithm() : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
     400             :     {
     401           6 :         AddArg("input", 'i', _("Input ZIP filename"), &m_zipFilename)
     402           3 :             .SetRequired()
     403           3 :             .SetPositional();
     404           3 :         AddOutputStringArg(&m_output);
     405           3 :     }
     406             : 
     407             :   private:
     408             :     std::string m_zipFilename{};
     409             :     std::string m_output{};
     410             : 
     411             :     bool RunImpl(GDALProgressFunc, void *) override;
     412             : };
     413             : 
     414             : /************************************************************************/
     415             : /*                 GDALVSISOZIPListAlgorithm::RunImpl()                 */
     416             : /************************************************************************/
     417             : 
     418           2 : bool GDALVSISOZIPListAlgorithm::RunImpl(GDALProgressFunc, void *)
     419             : {
     420             :     std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
     421           4 :         VSIOpenDir(std::string("/vsizip/").append(m_zipFilename).c_str(), -1,
     422             :                    nullptr),
     423           6 :         VSICloseDir);
     424           2 :     if (!psDir)
     425             :     {
     426           1 :         ReportError(CE_Failure, CPLE_AppDefined, "%s is not a valid .zip file",
     427             :                     m_zipFilename.c_str());
     428           1 :         return false;
     429             :     }
     430             : 
     431             :     m_output = "  Length          DateTime        Seek-optimized / chunk size  "
     432           1 :                "Name               Properties\n";
     433             :     /* clang-format off */
     434           1 :     m_output += "-----------  -------------------  ---------------------------  -----------------  --------------\n";
     435             :     /* clang-format on */
     436             : 
     437           2 :     while (const auto psEntry = VSIGetNextDirEntry(psDir.get()))
     438             :     {
     439           1 :         if (!VSI_ISDIR(psEntry->nMode))
     440             :         {
     441             :             struct tm brokenDown;
     442           1 :             CPLUnixTimeToYMDHMS(psEntry->nMTime, &brokenDown);
     443           2 :             const std::string osFilename = std::string("/vsizip/{")
     444           1 :                                                .append(m_zipFilename)
     445           1 :                                                .append("}/")
     446           2 :                                                .append(psEntry->pszName);
     447           2 :             std::string osProperties;
     448             :             const CPLStringList aosMDGeneric(
     449           2 :                 VSIGetFileMetadata(osFilename.c_str(), nullptr, nullptr));
     450           1 :             for (const char *pszMDGeneric : aosMDGeneric)
     451             :             {
     452           0 :                 if (!osProperties.empty())
     453           0 :                     osProperties += ',';
     454           0 :                 osProperties += pszMDGeneric;
     455             :             }
     456             : 
     457             :             const CPLStringList aosMD(
     458           2 :                 VSIGetFileMetadata(osFilename.c_str(), "ZIP", nullptr));
     459             :             const bool bSeekOptimized =
     460           1 :                 aosMD.FetchNameValue("SOZIP_VALID") != nullptr;
     461           1 :             const char *pszChunkSize = aosMD.FetchNameValue("SOZIP_CHUNK_SIZE");
     462             :             m_output += CPLSPrintf(
     463             :                 "%11" CPL_FRMT_GB_WITHOUT_PREFIX
     464             :                 "u  %04d-%02d-%02d %02d:%02d:%02d  %s  %s               "
     465             :                 "%s\n",
     466           1 :                 static_cast<GUIntBig>(psEntry->nSize),
     467           1 :                 brokenDown.tm_year + 1900, brokenDown.tm_mon + 1,
     468             :                 brokenDown.tm_mday, brokenDown.tm_hour, brokenDown.tm_min,
     469             :                 brokenDown.tm_sec,
     470             :                 bSeekOptimized
     471           1 :                     ? CPLSPrintf("   yes (%9s bytes)   ", pszChunkSize)
     472             :                     : "                           ",
     473           2 :                 psEntry->pszName, osProperties.c_str());
     474             :         }
     475           1 :     }
     476           1 :     return true;
     477             : }
     478             : 
     479             : /************************************************************************/
     480             : /*                      GDALVSISOZIPValidateAlgorithm                   */
     481             : /************************************************************************/
     482             : 
     483             : class GDALVSISOZIPValidateAlgorithm final : public GDALAlgorithm
     484             : {
     485             :   public:
     486             :     static constexpr const char *NAME = "validate";
     487             :     static constexpr const char *DESCRIPTION =
     488             :         "Validate a ZIP file, possibly using SOZIP optimization.";
     489             :     static constexpr const char *HELP_URL = "/programs/gdal_vsi_sozip.html";
     490             : 
     491           9 :     GDALVSISOZIPValidateAlgorithm() : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
     492             :     {
     493          18 :         AddArg("input", 'i', _("Input ZIP filename"), &m_zipFilename)
     494           9 :             .SetRequired()
     495           9 :             .SetPositional();
     496           9 :         AddOutputStringArg(&m_output);
     497          18 :         AddArg("verbose", 'v', _("Turn on verbose mode"), &m_verbose)
     498           9 :             .SetOnlyForCLI();
     499             :         AddArg("stdout", 0,
     500             :                _("Directly output on stdout. If enabled, "
     501             :                  "output-string will be empty"),
     502          18 :                &m_stdout)
     503           9 :             .SetHiddenForCLI();
     504           9 :     }
     505             : 
     506             :   private:
     507             :     std::string m_zipFilename{};
     508             :     std::string m_output{};
     509             :     bool m_stdout = false;
     510             :     bool m_verbose = false;
     511             : 
     512             :     bool RunImpl(GDALProgressFunc, void *) override;
     513             : 
     514          22 :     void Output(const std::string &s)
     515             :     {
     516          22 :         if (!m_quiet)
     517             :         {
     518          22 :             if (m_stdout)
     519          14 :                 printf("%s", s.c_str());
     520             :             else
     521           8 :                 m_output += s;
     522             :         }
     523          22 :     }
     524             : };
     525             : 
     526             : /************************************************************************/
     527             : /*                 GDALVSISOZIPValidateAlgorithm::RunImpl()             */
     528             : /************************************************************************/
     529             : 
     530           8 : bool GDALVSISOZIPValidateAlgorithm::RunImpl(GDALProgressFunc, void *)
     531             : {
     532             :     std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
     533          16 :         VSIOpenDir(std::string("/vsizip/").append(m_zipFilename).c_str(), -1,
     534             :                    nullptr),
     535          24 :         VSICloseDir);
     536           8 :     if (!psDir)
     537             :     {
     538           1 :         ReportError(CE_Failure, CPLE_AppDefined, "%s is not a valid .zip file",
     539             :                     m_zipFilename.c_str());
     540           1 :         return false;
     541             :     }
     542             : 
     543           7 :     int nCountValidSOZIP = 0;
     544           7 :     bool ret = true;
     545           7 :     const bool bVerbose = m_verbose;
     546          65 :     while (const auto psEntry = VSIGetNextDirEntry(psDir.get()))
     547             :     {
     548          58 :         if (!VSI_ISDIR(psEntry->nMode))
     549             :         {
     550         108 :             const std::string osFilenameInZip = std::string("/vsizip/{")
     551          54 :                                                     .append(m_zipFilename)
     552          54 :                                                     .append("}/")
     553          54 :                                                     .append(psEntry->pszName);
     554          54 :             if (bVerbose)
     555           1 :                 Output(CPLSPrintf("Testing %s...\n", psEntry->pszName));
     556             : 
     557             :             const CPLStringList aosMD(
     558          54 :                 VSIGetFileMetadata(osFilenameInZip.c_str(), "ZIP", nullptr));
     559             :             bool bSeekOptimizedFound =
     560          54 :                 aosMD.FetchNameValue("SOZIP_FOUND") != nullptr;
     561             :             bool bSeekOptimizedValid =
     562          54 :                 aosMD.FetchNameValue("SOZIP_VALID") != nullptr;
     563          54 :             const char *pszChunkSize = aosMD.FetchNameValue("SOZIP_CHUNK_SIZE");
     564          54 :             if (bSeekOptimizedValid)
     565             :             {
     566           6 :                 if (bVerbose)
     567             :                 {
     568           2 :                     Output(
     569             :                         CPLSPrintf("  %s has an associated .sozip.idx file\n",
     570           1 :                                    psEntry->pszName));
     571             :                 }
     572             : 
     573             :                 const char *pszStartIdxDataOffset =
     574           6 :                     aosMD.FetchNameValue("SOZIP_START_DATA_OFFSET");
     575             :                 const vsi_l_offset nStartIdxOffset =
     576           6 :                     std::strtoull(pszStartIdxDataOffset, nullptr, 10);
     577           6 :                 VSILFILE *fpRaw = VSIFOpenL(m_zipFilename.c_str(), "rb");
     578           6 :                 CPLAssert(fpRaw);
     579             : 
     580           6 :                 if (VSIFSeekL(fpRaw, nStartIdxOffset + 4, SEEK_SET) != 0)
     581             :                 {
     582           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     583             :                                 "VSIFSeekL() failed.");
     584           0 :                     ret = false;
     585             :                 }
     586           6 :                 uint32_t nToSkip = 0;
     587           6 :                 if (VSIFReadL(&nToSkip, sizeof(nToSkip), 1, fpRaw) != 1)
     588             :                 {
     589           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     590             :                                 "VSIFReadL() failed.");
     591           0 :                     ret = false;
     592             :                 }
     593           6 :                 CPL_LSBPTR32(&nToSkip);
     594             : 
     595           6 :                 if (VSIFSeekL(fpRaw, nStartIdxOffset + 32 + nToSkip,
     596           6 :                               SEEK_SET) != 0)
     597             :                 {
     598           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     599             :                                 "VSIFSeekL() failed.");
     600           0 :                     ret = false;
     601             :                 }
     602           6 :                 const int nChunkSize = atoi(pszChunkSize);
     603           6 :                 const uint64_t nCompressedSize = std::strtoull(
     604             :                     aosMD.FetchNameValue("COMPRESSED_SIZE"), nullptr, 10);
     605           6 :                 const uint64_t nUncompressedSize = std::strtoull(
     606             :                     aosMD.FetchNameValue("UNCOMPRESSED_SIZE"), nullptr, 10);
     607          12 :                 if (nChunkSize == 0 ||  // cannot happen
     608           6 :                     (nUncompressedSize - 1) / nChunkSize >
     609           6 :                         static_cast<uint64_t>(std::numeric_limits<int>::max()))
     610             :                 {
     611           0 :                     ReportError(
     612             :                         CE_Failure, CPLE_AppDefined,
     613             :                         "* File %s has a SOZip index, but (nUncompressedSize - "
     614             :                         "1) / nChunkSize > INT_MAX !",
     615           0 :                         psEntry->pszName);
     616           0 :                     ret = false;
     617           0 :                     continue;
     618             :                 }
     619             : 
     620           6 :                 int nChunksItems =
     621           6 :                     static_cast<int>((nUncompressedSize - 1) / nChunkSize);
     622             : 
     623           6 :                 if (bVerbose)
     624             :                 {
     625           2 :                     Output(CPLSPrintf("  %s: checking index offset values...\n",
     626           1 :                                       psEntry->pszName));
     627             :                 }
     628             : 
     629          12 :                 std::vector<uint64_t> anOffsets;
     630             :                 try
     631             :                 {
     632           6 :                     anOffsets.reserve(nChunksItems);
     633             :                 }
     634           0 :                 catch (const std::exception &)
     635             :                 {
     636           0 :                     nChunksItems = 0;
     637           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     638             :                                 "Cannot allocate memory for chunk offsets.");
     639           0 :                     ret = false;
     640             :                 }
     641             : 
     642         151 :                 for (int i = 0; i < nChunksItems; ++i)
     643             :                 {
     644         145 :                     uint64_t nOffset64 = 0;
     645         145 :                     if (VSIFReadL(&nOffset64, sizeof(nOffset64), 1, fpRaw) != 1)
     646             :                     {
     647           0 :                         ReportError(CE_Failure, CPLE_AppDefined,
     648             :                                     "VSIFReadL() failed.");
     649           0 :                         ret = false;
     650             :                     }
     651         145 :                     CPL_LSBPTR64(&nOffset64);
     652         145 :                     if (nOffset64 >= nCompressedSize)
     653             :                     {
     654           0 :                         bSeekOptimizedValid = false;
     655           0 :                         ReportError(
     656             :                             CE_Failure, CPLE_AppDefined,
     657             :                             "Error: file %s, offset[%d] (= " CPL_FRMT_GUIB
     658             :                             ") >= compressed_size is invalid.",
     659           0 :                             psEntry->pszName, i,
     660             :                             static_cast<GUIntBig>(nOffset64));
     661             :                     }
     662         145 :                     if (!anOffsets.empty())
     663             :                     {
     664         139 :                         const auto nPrevOffset = anOffsets.back();
     665         139 :                         if (nOffset64 <= nPrevOffset)
     666             :                         {
     667           0 :                             bSeekOptimizedValid = false;
     668           0 :                             ReportError(
     669             :                                 CE_Failure, CPLE_AppDefined,
     670             :                                 "Error: file %s, offset[%d] (= " CPL_FRMT_GUIB
     671             :                                 ") <= offset[%d] (= " CPL_FRMT_GUIB ")",
     672           0 :                                 psEntry->pszName, i + 1,
     673             :                                 static_cast<GUIntBig>(nOffset64), i,
     674             :                                 static_cast<GUIntBig>(nPrevOffset));
     675             :                         }
     676             :                     }
     677           6 :                     else if (nOffset64 < 9)
     678             :                     {
     679           0 :                         bSeekOptimizedValid = false;
     680           0 :                         ReportError(
     681             :                             CE_Failure, CPLE_AppDefined,
     682             :                             "Error: file %s, offset[0] (= " CPL_FRMT_GUIB
     683             :                             ") is invalid.",
     684           0 :                             psEntry->pszName, static_cast<GUIntBig>(nOffset64));
     685             :                     }
     686         145 :                     anOffsets.push_back(nOffset64);
     687             :                 }
     688             : 
     689           6 :                 if (bVerbose)
     690             :                 {
     691           2 :                     Output(CPLSPrintf("  %s: checking if chunks can be "
     692             :                                       "independently decompressed...\n",
     693           1 :                                       psEntry->pszName));
     694             :                 }
     695             : 
     696             :                 const char *pszStartDataOffset =
     697           6 :                     aosMD.FetchNameValue("START_DATA_OFFSET");
     698             :                 const vsi_l_offset nStartOffset =
     699           6 :                     std::strtoull(pszStartDataOffset, nullptr, 10);
     700           6 :                 VSILFILE *fp = VSIFOpenL(osFilenameInZip.c_str(), "rb");
     701           6 :                 if (!fp)
     702             :                 {
     703           0 :                     bSeekOptimizedValid = false;
     704           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     705             :                                 "Error: cannot open %s",
     706             :                                 osFilenameInZip.c_str());
     707             :                 }
     708          12 :                 std::vector<GByte> abyData;
     709             :                 try
     710             :                 {
     711           6 :                     abyData.resize(nChunkSize);
     712             :                 }
     713           0 :                 catch (const std::exception &)
     714             :                 {
     715           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     716             :                                 "Cannot allocate memory for chunk data.");
     717           0 :                     ret = false;
     718             :                 }
     719         151 :                 for (int i = 0; fp != nullptr && i < nChunksItems; ++i)
     720             :                 {
     721         145 :                     if (VSIFSeekL(fpRaw, nStartOffset + anOffsets[i] - 9,
     722         145 :                                   SEEK_SET) != 0)
     723             :                     {
     724           0 :                         ReportError(CE_Failure, CPLE_AppDefined,
     725             :                                     "VSIFSeekL() failed.");
     726           0 :                         ret = false;
     727             :                     }
     728         145 :                     GByte abyEnd[9] = {0};
     729         145 :                     if (VSIFReadL(abyEnd, 9, 1, fpRaw) != 1)
     730             :                     {
     731           0 :                         ReportError(CE_Failure, CPLE_AppDefined,
     732             :                                     "VSIFReadL() failed.");
     733           0 :                         ret = false;
     734             :                     }
     735         145 :                     if (memcmp(abyEnd, "\x00\x00\xFF\xFF\x00\x00\x00\xFF\xFF",
     736             :                                9) != 0)
     737             :                     {
     738           0 :                         bSeekOptimizedValid = false;
     739           0 :                         ReportError(
     740             :                             CE_Failure, CPLE_AppDefined,
     741             :                             "Error: file %s, chunk[%d] is not terminated by "
     742             :                             "\\x00\\x00\\xFF\\xFF\\x00\\x00\\x00\\xFF\\xFF.",
     743           0 :                             psEntry->pszName, i);
     744             :                     }
     745         145 :                     if (!abyData.empty())
     746             :                     {
     747         290 :                         if (VSIFSeekL(fp,
     748         145 :                                       static_cast<vsi_l_offset>(i) * nChunkSize,
     749         145 :                                       SEEK_SET) != 0)
     750             :                         {
     751           0 :                             ReportError(CE_Failure, CPLE_AppDefined,
     752             :                                         "VSIFSeekL() failed.");
     753           0 :                             ret = false;
     754             :                         }
     755             :                         const size_t nRead =
     756         145 :                             VSIFReadL(&abyData[0], 1, nChunkSize, fp);
     757         145 :                         if (nRead != static_cast<size_t>(nChunkSize))
     758             :                         {
     759           0 :                             bSeekOptimizedValid = false;
     760           0 :                             ReportError(
     761             :                                 CE_Failure, CPLE_AppDefined,
     762             :                                 "Error: file %s, chunk[%d] cannot be fully "
     763             :                                 "read.",
     764           0 :                                 psEntry->pszName, i);
     765             :                         }
     766             :                     }
     767             :                 }
     768             : 
     769           6 :                 if (fp)
     770             :                 {
     771          12 :                     if (VSIFSeekL(fp,
     772           6 :                                   static_cast<vsi_l_offset>(nChunksItems) *
     773           6 :                                       nChunkSize,
     774           6 :                                   SEEK_SET) != 0)
     775             :                     {
     776           0 :                         ReportError(CE_Failure, CPLE_AppDefined,
     777             :                                     "VSIFSeekL() failed.");
     778           0 :                         ret = false;
     779             :                     }
     780             :                     const size_t nRead =
     781           6 :                         VSIFReadL(&abyData[0], 1, nChunkSize, fp);
     782           6 :                     if (nRead != static_cast<size_t>(
     783           6 :                                      nUncompressedSize -
     784           6 :                                      static_cast<vsi_l_offset>(nChunksItems) *
     785           6 :                                          nChunkSize))
     786             :                     {
     787           0 :                         bSeekOptimizedValid = false;
     788           0 :                         ReportError(
     789             :                             CE_Failure, CPLE_AppDefined,
     790             :                             "Error: file %s, chunk[%d] cannot be fully read.",
     791           0 :                             psEntry->pszName, nChunksItems);
     792             :                     }
     793             : 
     794           6 :                     VSIFCloseL(fp);
     795             :                 }
     796             : 
     797           6 :                 VSIFCloseL(fpRaw);
     798             :             }
     799             : 
     800          54 :             if (bSeekOptimizedValid)
     801             :             {
     802          12 :                 Output(CPLSPrintf(
     803             :                     "* File %s has a valid SOZip index, using chunk_size = "
     804             :                     "%s.\n",
     805           6 :                     psEntry->pszName, pszChunkSize));
     806           6 :                 nCountValidSOZIP++;
     807             :             }
     808          48 :             else if (bSeekOptimizedFound)
     809             :             {
     810           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
     811             :                             "* File %s has a SOZip index, but is is invalid!",
     812           0 :                             psEntry->pszName);
     813           0 :                 ret = false;
     814             :             }
     815             :         }
     816          58 :     }
     817             : 
     818           7 :     if (ret)
     819             :     {
     820           7 :         if (nCountValidSOZIP > 0)
     821             :         {
     822           5 :             Output("-----\n");
     823           5 :             Output(CPLSPrintf(
     824             :                 "%s is a valid .zip file, and contains %d SOZip-enabled "
     825             :                 "file(s).\n",
     826             :                 m_zipFilename.c_str(), nCountValidSOZIP));
     827             :         }
     828             :         else
     829           2 :             Output(
     830             :                 CPLSPrintf("%s is a valid .zip file, but does not contain any "
     831             :                            "SOZip-enabled files.\n",
     832             :                            m_zipFilename.c_str()));
     833             :     }
     834             :     else
     835             :     {
     836           0 :         ReportError(CE_Failure, CPLE_AppDefined,
     837             :                     "%s is not a valid SOZip file!", m_zipFilename.c_str());
     838             :     }
     839           7 :     return ret;
     840             : }
     841             : 
     842             : /************************************************************************/
     843             : /*               GDALVSISOZIPAlgorithm::GDALVSISOZIPAlgorithm()         */
     844             : /************************************************************************/
     845             : 
     846          33 : GDALVSISOZIPAlgorithm::GDALVSISOZIPAlgorithm()
     847          33 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
     848             : {
     849          33 :     RegisterSubAlgorithm<GDALVSISOZIPCreateAlgorithm>();
     850          33 :     RegisterSubAlgorithm<GDALVSISOZIPOptimizeAlgorithm>();
     851          33 :     RegisterSubAlgorithm<GDALVSISOZIPListAlgorithm>();
     852          33 :     RegisterSubAlgorithm<GDALVSISOZIPValidateAlgorithm>();
     853          33 : }
     854             : 
     855             : /************************************************************************/
     856             : /*               GDALVSISOZIPAlgorithm::RunImpl()                       */
     857             : /************************************************************************/
     858             : 
     859           1 : bool GDALVSISOZIPAlgorithm::RunImpl(GDALProgressFunc, void *)
     860             : {
     861           1 :     CPLError(CE_Failure, CPLE_AppDefined,
     862             :              "The Run() method should not be called directly on the \"gdal "
     863             :              "sozip\" program.");
     864           1 :     return false;
     865             : }
     866             : 
     867             : //! @endcond

Generated by: LCOV version 1.14