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

Generated by: LCOV version 1.14