LCOV - code coverage report
Current view: top level - apps - gdalalg_vsi_sozip.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 316 376 84.0 %
Date: 2026-07-09 08:35:43 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         107 :     GDALVSISOZIPCreateBaseAlgorithm(const std::string &name,
      36             :                                     const std::string &description,
      37             :                                     const std::string &helpURL,
      38             :                                     bool optimizeFrom)
      39         107 :         : GDALAlgorithm(name, description, helpURL),
      40         107 :           m_optimizeFrom(optimizeFrom)
      41             :     {
      42         107 :         AddProgressArg();
      43         107 :         if (optimizeFrom)
      44          94 :             AddArg("input", 'i', _("Input ZIP filename"), &m_inputFilenames)
      45          47 :                 .SetRequired()
      46          47 :                 .SetPositional()
      47          47 :                 .SetMaxCount(1);
      48             :         else
      49         120 :             AddArg("input", 'i', _("Input filenames"), &m_inputFilenames)
      50          60 :                 .SetRequired()
      51          60 :                 .SetPositional();
      52         214 :         AddArg("output", 'o', _("Output ZIP filename"), &m_zipFilename)
      53         107 :             .SetRequired()
      54         107 :             .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         107 :                 });
      68         107 :         AddOverwriteArg(&m_overwrite);
      69         107 :         if (!optimizeFrom)
      70             :         {
      71             :             AddArg("recursive", 'r',
      72             :                    _("Travels the directory structure of the specified "
      73             :                      "directories recursively"),
      74         120 :                    &m_recursive)
      75          60 :                 .AddHiddenAlias("recurse");
      76             :         }
      77         107 :         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         120 :                    &m_noDirName)
      83          60 :                 .AddAlias("junk-paths");
      84             :         }
      85             :         AddArg("enable-sozip", 0,
      86             :                _("Whether to automatically/systematically/never apply the "
      87             :                  "SOZIP optimization"),
      88         214 :                &m_mode)
      89         107 :             .SetDefault(m_mode)
      90         107 :             .SetChoices("auto", "yes", "no");
      91             :         AddArg("sozip-chunk-size", 0, _("Chunk size for a seek-optimized file"),
      92         214 :                &m_chunkSize)
      93         214 :             .SetMetaVar("<value in bytes or with K/M suffix>")
      94         107 :             .SetDefault(m_chunkSize)
      95         107 :             .SetMinCharCount(1);
      96             :         AddArg(
      97             :             "sozip-min-file-size", 0,
      98             :             _("Minimum file size to decide if a file should be seek-optimized"),
      99         214 :             &m_minFileSize)
     100         214 :             .SetMetaVar("<value in bytes or with K/M/G suffix>")
     101         107 :             .SetDefault(m_minFileSize)
     102         107 :             .SetMinCharCount(1);
     103         107 :         if (!optimizeFrom)
     104             :             AddArg("content-type", 0,
     105             :                    _("Store the Content-Type of the file being added."),
     106         120 :                    &m_contentType)
     107          60 :                 .SetMinCharCount(1);
     108             : 
     109         107 :         AddOutputStringArg(&m_output);
     110         107 :         AddStdoutArg(&m_stdout);
     111         107 :     }
     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         120 : 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          60 :     GDALVSISOZIPCreateAlgorithm()
     352          60 :         : GDALVSISOZIPCreateBaseAlgorithm(NAME, DESCRIPTION, HELP_URL, false)
     353             :     {
     354          60 :     }
     355             : 
     356             :     ~GDALVSISOZIPCreateAlgorithm() override;
     357             : };
     358             : 
     359             : GDALVSISOZIPCreateAlgorithm::~GDALVSISOZIPCreateAlgorithm() = default;
     360             : 
     361             : /************************************************************************/
     362             : /*                    GDALVSISOZIPOptimizeAlgorithm                     */
     363             : /************************************************************************/
     364             : 
     365          94 : 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          47 :     GDALVSISOZIPOptimizeAlgorithm()
     375          47 :         : GDALVSISOZIPCreateBaseAlgorithm(NAME, DESCRIPTION, HELP_URL, true)
     376             :     {
     377          47 :     }
     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          45 :     GDALVSISOZIPListAlgorithm() : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
     397             :     {
     398          45 :         AddProgressArg(/* hidden = */ true);
     399             : 
     400          90 :         AddArg("input", 'i', _("Input ZIP filename"), &m_zipFilename)
     401          45 :             .SetRequired()
     402          45 :             .SetPositional();
     403          45 :         AddOutputStringArg(&m_output);
     404          45 :     }
     405             : 
     406             :   private:
     407             :     std::string m_zipFilename{};
     408             :     std::string m_output{};
     409             : 
     410             :     bool RunImpl(GDALProgressFunc, void *) override;
     411             : };
     412             : 
     413             : /************************************************************************/
     414             : /*                 GDALVSISOZIPListAlgorithm::RunImpl()                 */
     415             : /************************************************************************/
     416             : 
     417           2 : bool GDALVSISOZIPListAlgorithm::RunImpl(GDALProgressFunc, void *)
     418             : {
     419             :     std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
     420           4 :         VSIOpenDir(std::string("/vsizip/").append(m_zipFilename).c_str(), -1,
     421             :                    nullptr),
     422           6 :         VSICloseDir);
     423           2 :     if (!psDir)
     424             :     {
     425           1 :         ReportError(CE_Failure, CPLE_AppDefined, "%s is not a valid .zip file",
     426             :                     m_zipFilename.c_str());
     427           1 :         return false;
     428             :     }
     429             : 
     430             :     m_output = "  Length          DateTime        Seek-optimized / chunk size  "
     431           1 :                "Name               Properties\n";
     432             :     /* clang-format off */
     433           1 :     m_output += "-----------  -------------------  ---------------------------  -----------------  --------------\n";
     434             :     /* clang-format on */
     435             : 
     436           2 :     while (const auto psEntry = VSIGetNextDirEntry(psDir.get()))
     437             :     {
     438           1 :         if (!VSI_ISDIR(psEntry->nMode))
     439             :         {
     440             :             struct tm brokenDown;
     441           1 :             CPLUnixTimeToYMDHMS(psEntry->nMTime, &brokenDown);
     442           2 :             const std::string osFilename = std::string("/vsizip/{")
     443           1 :                                                .append(m_zipFilename)
     444           1 :                                                .append("}/")
     445           2 :                                                .append(psEntry->pszName);
     446           2 :             std::string osProperties;
     447             :             const CPLStringList aosMDGeneric(
     448           2 :                 VSIGetFileMetadata(osFilename.c_str(), nullptr, nullptr));
     449           1 :             for (const char *pszMDGeneric : aosMDGeneric)
     450             :             {
     451           0 :                 if (!osProperties.empty())
     452           0 :                     osProperties += ',';
     453           0 :                 osProperties += pszMDGeneric;
     454             :             }
     455             : 
     456             :             const CPLStringList aosMD(
     457           2 :                 VSIGetFileMetadata(osFilename.c_str(), "ZIP", nullptr));
     458             :             const bool bSeekOptimized =
     459           1 :                 aosMD.FetchNameValue("SOZIP_VALID") != nullptr;
     460           1 :             const char *pszChunkSize = aosMD.FetchNameValue("SOZIP_CHUNK_SIZE");
     461             :             m_output += CPLSPrintf(
     462             :                 "%11" CPL_FRMT_GB_WITHOUT_PREFIX
     463             :                 "u  %04d-%02d-%02d %02d:%02d:%02d  %s  %s               "
     464             :                 "%s\n",
     465           1 :                 static_cast<GUIntBig>(psEntry->nSize),
     466           1 :                 brokenDown.tm_year + 1900, brokenDown.tm_mon + 1,
     467             :                 brokenDown.tm_mday, brokenDown.tm_hour, brokenDown.tm_min,
     468             :                 brokenDown.tm_sec,
     469             :                 bSeekOptimized
     470           1 :                     ? CPLSPrintf("   yes (%9s bytes)   ", pszChunkSize)
     471             :                     : "                           ",
     472           2 :                 psEntry->pszName, osProperties.c_str());
     473             :         }
     474           1 :     }
     475           1 :     return true;
     476             : }
     477             : 
     478             : /************************************************************************/
     479             : /*                    GDALVSISOZIPValidateAlgorithm                     */
     480             : /************************************************************************/
     481             : 
     482             : class GDALVSISOZIPValidateAlgorithm final : public GDALAlgorithm
     483             : {
     484             :   public:
     485             :     static constexpr const char *NAME = "validate";
     486             :     static constexpr const char *DESCRIPTION =
     487             :         "Validate a ZIP file, possibly using SOZIP optimization.";
     488             :     static constexpr const char *HELP_URL = "/programs/gdal_vsi_sozip.html";
     489             : 
     490          51 :     GDALVSISOZIPValidateAlgorithm() : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
     491             :     {
     492          51 :         AddProgressArg(/* hidden = */ true);
     493             : 
     494         102 :         AddArg("input", 'i', _("Input ZIP filename"), &m_zipFilename)
     495          51 :             .SetRequired()
     496          51 :             .SetPositional();
     497          51 :         AddOutputStringArg(&m_output);
     498         102 :         AddArg("verbose", 'v', _("Turn on verbose mode"), &m_verbose)
     499          51 :             .SetHiddenForAPI();
     500          51 :         AddStdoutArg(&m_stdout);
     501          51 :     }
     502             : 
     503             :   private:
     504             :     std::string m_zipFilename{};
     505             :     std::string m_output{};
     506             :     bool m_stdout = false;
     507             :     bool m_verbose = false;
     508             : 
     509             :     bool RunImpl(GDALProgressFunc, void *) override;
     510             : 
     511          22 :     void Output(const std::string &s)
     512             :     {
     513          22 :         if (!m_quiet)
     514             :         {
     515          22 :             if (m_stdout)
     516          14 :                 printf("%s", s.c_str());
     517             :             else
     518           8 :                 m_output += s;
     519             :         }
     520          22 :     }
     521             : };
     522             : 
     523             : /************************************************************************/
     524             : /*               GDALVSISOZIPValidateAlgorithm::RunImpl()               */
     525             : /************************************************************************/
     526             : 
     527           8 : bool GDALVSISOZIPValidateAlgorithm::RunImpl(GDALProgressFunc, void *)
     528             : {
     529             :     std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
     530          16 :         VSIOpenDir(std::string("/vsizip/").append(m_zipFilename).c_str(), -1,
     531             :                    nullptr),
     532          24 :         VSICloseDir);
     533           8 :     if (!psDir)
     534             :     {
     535           1 :         ReportError(CE_Failure, CPLE_AppDefined, "%s is not a valid .zip file",
     536             :                     m_zipFilename.c_str());
     537           1 :         return false;
     538             :     }
     539             : 
     540           7 :     int nCountValidSOZIP = 0;
     541           7 :     bool ret = true;
     542           7 :     const bool bVerbose = m_verbose;
     543          65 :     while (const auto psEntry = VSIGetNextDirEntry(psDir.get()))
     544             :     {
     545          58 :         if (!VSI_ISDIR(psEntry->nMode))
     546             :         {
     547         108 :             const std::string osFilenameInZip = std::string("/vsizip/{")
     548          54 :                                                     .append(m_zipFilename)
     549          54 :                                                     .append("}/")
     550          54 :                                                     .append(psEntry->pszName);
     551          54 :             if (bVerbose)
     552           1 :                 Output(CPLSPrintf("Testing %s...\n", psEntry->pszName));
     553             : 
     554             :             const CPLStringList aosMD(
     555          54 :                 VSIGetFileMetadata(osFilenameInZip.c_str(), "ZIP", nullptr));
     556             :             bool bSeekOptimizedFound =
     557          54 :                 aosMD.FetchNameValue("SOZIP_FOUND") != nullptr;
     558             :             bool bSeekOptimizedValid =
     559          54 :                 aosMD.FetchNameValue("SOZIP_VALID") != nullptr;
     560          54 :             const char *pszChunkSize = aosMD.FetchNameValue("SOZIP_CHUNK_SIZE");
     561          54 :             if (bSeekOptimizedValid)
     562             :             {
     563           6 :                 if (bVerbose)
     564             :                 {
     565           2 :                     Output(
     566             :                         CPLSPrintf("  %s has an associated .sozip.idx file\n",
     567           1 :                                    psEntry->pszName));
     568             :                 }
     569             : 
     570             :                 const char *pszStartIdxDataOffset =
     571           6 :                     aosMD.FetchNameValue("SOZIP_START_DATA_OFFSET");
     572             :                 const vsi_l_offset nStartIdxOffset =
     573           6 :                     std::strtoull(pszStartIdxDataOffset, nullptr, 10);
     574           6 :                 VSILFILE *fpRaw = VSIFOpenL(m_zipFilename.c_str(), "rb");
     575           6 :                 CPLAssert(fpRaw);
     576             : 
     577           6 :                 if (VSIFSeekL(fpRaw, nStartIdxOffset + 4, SEEK_SET) != 0)
     578             :                 {
     579           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     580             :                                 "VSIFSeekL() failed.");
     581           0 :                     ret = false;
     582             :                 }
     583           6 :                 uint32_t nToSkip = 0;
     584           6 :                 if (VSIFReadL(&nToSkip, sizeof(nToSkip), 1, fpRaw) != 1)
     585             :                 {
     586           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     587             :                                 "VSIFReadL() failed.");
     588           0 :                     ret = false;
     589             :                 }
     590           6 :                 CPL_LSBPTR32(&nToSkip);
     591             : 
     592           6 :                 if (VSIFSeekL(fpRaw, nStartIdxOffset + 32 + nToSkip,
     593           6 :                               SEEK_SET) != 0)
     594             :                 {
     595           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     596             :                                 "VSIFSeekL() failed.");
     597           0 :                     ret = false;
     598             :                 }
     599           6 :                 const int nChunkSize = atoi(pszChunkSize);
     600           6 :                 const uint64_t nCompressedSize = std::strtoull(
     601             :                     aosMD.FetchNameValue("COMPRESSED_SIZE"), nullptr, 10);
     602           6 :                 const uint64_t nUncompressedSize = std::strtoull(
     603             :                     aosMD.FetchNameValue("UNCOMPRESSED_SIZE"), nullptr, 10);
     604          12 :                 if (nChunkSize == 0 ||  // cannot happen
     605           6 :                     (nUncompressedSize - 1) / nChunkSize >
     606           6 :                         static_cast<uint64_t>(std::numeric_limits<int>::max()))
     607             :                 {
     608           0 :                     ReportError(
     609             :                         CE_Failure, CPLE_AppDefined,
     610             :                         "* File %s has a SOZip index, but (nUncompressedSize - "
     611             :                         "1) / nChunkSize > INT_MAX !",
     612           0 :                         psEntry->pszName);
     613           0 :                     ret = false;
     614           0 :                     continue;
     615             :                 }
     616             : 
     617           6 :                 int nChunksItems =
     618           6 :                     static_cast<int>((nUncompressedSize - 1) / nChunkSize);
     619             : 
     620           6 :                 if (bVerbose)
     621             :                 {
     622           2 :                     Output(CPLSPrintf("  %s: checking index offset values...\n",
     623           1 :                                       psEntry->pszName));
     624             :                 }
     625             : 
     626          12 :                 std::vector<uint64_t> anOffsets;
     627             :                 try
     628             :                 {
     629           6 :                     anOffsets.reserve(nChunksItems);
     630             :                 }
     631           0 :                 catch (const std::exception &)
     632             :                 {
     633           0 :                     nChunksItems = 0;
     634           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     635             :                                 "Cannot allocate memory for chunk offsets.");
     636           0 :                     ret = false;
     637             :                 }
     638             : 
     639         151 :                 for (int i = 0; i < nChunksItems; ++i)
     640             :                 {
     641         145 :                     uint64_t nOffset64 = 0;
     642         145 :                     if (VSIFReadL(&nOffset64, sizeof(nOffset64), 1, fpRaw) != 1)
     643             :                     {
     644           0 :                         ReportError(CE_Failure, CPLE_AppDefined,
     645             :                                     "VSIFReadL() failed.");
     646           0 :                         ret = false;
     647             :                     }
     648         145 :                     CPL_LSBPTR64(&nOffset64);
     649         145 :                     if (nOffset64 >= nCompressedSize)
     650             :                     {
     651           0 :                         bSeekOptimizedValid = false;
     652           0 :                         ReportError(
     653             :                             CE_Failure, CPLE_AppDefined,
     654             :                             "Error: file %s, offset[%d] (= " CPL_FRMT_GUIB
     655             :                             ") >= compressed_size is invalid.",
     656           0 :                             psEntry->pszName, i,
     657             :                             static_cast<GUIntBig>(nOffset64));
     658             :                     }
     659         145 :                     if (!anOffsets.empty())
     660             :                     {
     661         139 :                         const auto nPrevOffset = anOffsets.back();
     662         139 :                         if (nOffset64 <= nPrevOffset)
     663             :                         {
     664           0 :                             bSeekOptimizedValid = false;
     665           0 :                             ReportError(
     666             :                                 CE_Failure, CPLE_AppDefined,
     667             :                                 "Error: file %s, offset[%d] (= " CPL_FRMT_GUIB
     668             :                                 ") <= offset[%d] (= " CPL_FRMT_GUIB ")",
     669           0 :                                 psEntry->pszName, i + 1,
     670             :                                 static_cast<GUIntBig>(nOffset64), i,
     671             :                                 static_cast<GUIntBig>(nPrevOffset));
     672             :                         }
     673             :                     }
     674           6 :                     else if (nOffset64 < 9)
     675             :                     {
     676           0 :                         bSeekOptimizedValid = false;
     677           0 :                         ReportError(
     678             :                             CE_Failure, CPLE_AppDefined,
     679             :                             "Error: file %s, offset[0] (= " CPL_FRMT_GUIB
     680             :                             ") is invalid.",
     681           0 :                             psEntry->pszName, static_cast<GUIntBig>(nOffset64));
     682             :                     }
     683         145 :                     anOffsets.push_back(nOffset64);
     684             :                 }
     685             : 
     686           6 :                 if (bVerbose)
     687             :                 {
     688           2 :                     Output(CPLSPrintf("  %s: checking if chunks can be "
     689             :                                       "independently decompressed...\n",
     690           1 :                                       psEntry->pszName));
     691             :                 }
     692             : 
     693             :                 const char *pszStartDataOffset =
     694           6 :                     aosMD.FetchNameValue("START_DATA_OFFSET");
     695             :                 const vsi_l_offset nStartOffset =
     696           6 :                     std::strtoull(pszStartDataOffset, nullptr, 10);
     697           6 :                 VSILFILE *fp = VSIFOpenL(osFilenameInZip.c_str(), "rb");
     698           6 :                 if (!fp)
     699             :                 {
     700           0 :                     bSeekOptimizedValid = false;
     701           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     702             :                                 "Error: cannot open %s",
     703             :                                 osFilenameInZip.c_str());
     704             :                 }
     705          12 :                 std::vector<GByte> abyData;
     706             :                 try
     707             :                 {
     708           6 :                     abyData.resize(nChunkSize);
     709             :                 }
     710           0 :                 catch (const std::exception &)
     711             :                 {
     712           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     713             :                                 "Cannot allocate memory for chunk data.");
     714           0 :                     ret = false;
     715             :                 }
     716         151 :                 for (int i = 0; fp != nullptr && i < nChunksItems; ++i)
     717             :                 {
     718         145 :                     if (VSIFSeekL(fpRaw, nStartOffset + anOffsets[i] - 9,
     719         145 :                                   SEEK_SET) != 0)
     720             :                     {
     721           0 :                         ReportError(CE_Failure, CPLE_AppDefined,
     722             :                                     "VSIFSeekL() failed.");
     723           0 :                         ret = false;
     724             :                     }
     725         145 :                     GByte abyEnd[9] = {0};
     726         145 :                     if (VSIFReadL(abyEnd, 9, 1, fpRaw) != 1)
     727             :                     {
     728           0 :                         ReportError(CE_Failure, CPLE_AppDefined,
     729             :                                     "VSIFReadL() failed.");
     730           0 :                         ret = false;
     731             :                     }
     732         145 :                     if (memcmp(abyEnd, "\x00\x00\xFF\xFF\x00\x00\x00\xFF\xFF",
     733             :                                9) != 0)
     734             :                     {
     735           0 :                         bSeekOptimizedValid = false;
     736           0 :                         ReportError(
     737             :                             CE_Failure, CPLE_AppDefined,
     738             :                             "Error: file %s, chunk[%d] is not terminated by "
     739             :                             "\\x00\\x00\\xFF\\xFF\\x00\\x00\\x00\\xFF\\xFF.",
     740           0 :                             psEntry->pszName, i);
     741             :                     }
     742         145 :                     if (!abyData.empty())
     743             :                     {
     744         290 :                         if (VSIFSeekL(fp,
     745         145 :                                       static_cast<vsi_l_offset>(i) * nChunkSize,
     746         145 :                                       SEEK_SET) != 0)
     747             :                         {
     748           0 :                             ReportError(CE_Failure, CPLE_AppDefined,
     749             :                                         "VSIFSeekL() failed.");
     750           0 :                             ret = false;
     751             :                         }
     752             :                         const size_t nRead =
     753         145 :                             VSIFReadL(&abyData[0], 1, nChunkSize, fp);
     754         145 :                         if (nRead != static_cast<size_t>(nChunkSize))
     755             :                         {
     756           0 :                             bSeekOptimizedValid = false;
     757           0 :                             ReportError(
     758             :                                 CE_Failure, CPLE_AppDefined,
     759             :                                 "Error: file %s, chunk[%d] cannot be fully "
     760             :                                 "read.",
     761           0 :                                 psEntry->pszName, i);
     762             :                         }
     763             :                     }
     764             :                 }
     765             : 
     766           6 :                 if (fp)
     767             :                 {
     768          12 :                     if (VSIFSeekL(fp,
     769           6 :                                   static_cast<vsi_l_offset>(nChunksItems) *
     770           6 :                                       nChunkSize,
     771           6 :                                   SEEK_SET) != 0)
     772             :                     {
     773           0 :                         ReportError(CE_Failure, CPLE_AppDefined,
     774             :                                     "VSIFSeekL() failed.");
     775           0 :                         ret = false;
     776             :                     }
     777             :                     const size_t nRead =
     778           6 :                         VSIFReadL(&abyData[0], 1, nChunkSize, fp);
     779           6 :                     if (nRead != static_cast<size_t>(
     780           6 :                                      nUncompressedSize -
     781           6 :                                      static_cast<vsi_l_offset>(nChunksItems) *
     782           6 :                                          nChunkSize))
     783             :                     {
     784           0 :                         bSeekOptimizedValid = false;
     785           0 :                         ReportError(
     786             :                             CE_Failure, CPLE_AppDefined,
     787             :                             "Error: file %s, chunk[%d] cannot be fully read.",
     788           0 :                             psEntry->pszName, nChunksItems);
     789             :                     }
     790             : 
     791           6 :                     VSIFCloseL(fp);
     792             :                 }
     793             : 
     794           6 :                 VSIFCloseL(fpRaw);
     795             :             }
     796             : 
     797          54 :             if (bSeekOptimizedValid)
     798             :             {
     799          12 :                 Output(CPLSPrintf(
     800             :                     "* File %s has a valid SOZip index, using chunk_size = "
     801             :                     "%s.\n",
     802           6 :                     psEntry->pszName, pszChunkSize));
     803           6 :                 nCountValidSOZIP++;
     804             :             }
     805          48 :             else if (bSeekOptimizedFound)
     806             :             {
     807           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
     808             :                             "* File %s has a SOZip index, but is is invalid!",
     809           0 :                             psEntry->pszName);
     810           0 :                 ret = false;
     811             :             }
     812             :         }
     813          58 :     }
     814             : 
     815           7 :     if (ret)
     816             :     {
     817           7 :         if (nCountValidSOZIP > 0)
     818             :         {
     819           5 :             Output("-----\n");
     820           5 :             Output(CPLSPrintf(
     821             :                 "%s is a valid .zip file, and contains %d SOZip-enabled "
     822             :                 "file(s).\n",
     823             :                 m_zipFilename.c_str(), nCountValidSOZIP));
     824             :         }
     825             :         else
     826           2 :             Output(
     827             :                 CPLSPrintf("%s is a valid .zip file, but does not contain any "
     828             :                            "SOZip-enabled files.\n",
     829             :                            m_zipFilename.c_str()));
     830             :     }
     831             :     else
     832             :     {
     833           0 :         ReportError(CE_Failure, CPLE_AppDefined,
     834             :                     "%s is not a valid SOZip file!", m_zipFilename.c_str());
     835             :     }
     836           7 :     return ret;
     837             : }
     838             : 
     839             : /************************************************************************/
     840             : /*            GDALVSISOZIPAlgorithm::GDALVSISOZIPAlgorithm()            */
     841             : /************************************************************************/
     842             : 
     843          75 : GDALVSISOZIPAlgorithm::GDALVSISOZIPAlgorithm()
     844          75 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
     845             : {
     846          75 :     RegisterSubAlgorithm<GDALVSISOZIPCreateAlgorithm>();
     847          75 :     RegisterSubAlgorithm<GDALVSISOZIPOptimizeAlgorithm>();
     848          75 :     RegisterSubAlgorithm<GDALVSISOZIPListAlgorithm>();
     849          75 :     RegisterSubAlgorithm<GDALVSISOZIPValidateAlgorithm>();
     850          75 : }
     851             : 
     852             : /************************************************************************/
     853             : /*                   GDALVSISOZIPAlgorithm::RunImpl()                   */
     854             : /************************************************************************/
     855             : 
     856           1 : bool GDALVSISOZIPAlgorithm::RunImpl(GDALProgressFunc, void *)
     857             : {
     858           1 :     CPLError(CE_Failure, CPLE_AppDefined,
     859             :              "The Run() method should not be called directly on the \"gdal "
     860             :              "sozip\" program.");
     861           1 :     return false;
     862             : }
     863             : 
     864             : //! @endcond

Generated by: LCOV version 1.14