LCOV - code coverage report
Current view: top level - apps - gdalalg_vsi_sozip.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 321 381 84.3 %
Date: 2025-06-19 12:30:01 Functions: 17 17 100.0 %

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

Generated by: LCOV version 1.14