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

Generated by: LCOV version 1.14