LCOV - code coverage report
Current view: top level - apps - sozip.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 259 388 66.8 %
Date: 2024-05-03 15:49:35 Functions: 5 6 83.3 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL Utilities
       4             :  * Purpose:  Command line application to build seek-optimized ZIP files
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2022, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "cpl_string.h"
      30             : #include "cpl_time.h"
      31             : #include "cpl_progress.h"
      32             : #include "gdal_version.h"
      33             : #include "gdal_priv.h"
      34             : #include "commonutils.h"
      35             : #include "gdalargumentparser.h"
      36             : 
      37             : #include <limits>
      38             : 
      39             : /************************************************************************/
      40             : /*                          Validate()                                  */
      41             : /************************************************************************/
      42             : 
      43           2 : static int Validate(const char *pszZipFilename, bool bVerbose)
      44             : {
      45           2 :     VSIDIR *psDir = VSIOpenDir(
      46           4 :         (std::string("/vsizip/") + pszZipFilename).c_str(), -1, nullptr);
      47           2 :     if (!psDir)
      48             :     {
      49           0 :         fprintf(stderr, "%s is not a valid .zip file\n", pszZipFilename);
      50           0 :         return 1;
      51             :     }
      52             : 
      53           2 :     int nCountInvalidSOZIP = 0;
      54           2 :     int nCountValidSOZIP = 0;
      55           2 :     int ret = 0;
      56          50 :     while (auto psEntry = VSIGetNextDirEntry(psDir))
      57             :     {
      58          48 :         if (!VSI_ISDIR(psEntry->nMode))
      59             :         {
      60          94 :             const std::string osFilenameInZip = std::string("/vsizip/{") +
      61          94 :                                                 pszZipFilename + "}/" +
      62          94 :                                                 psEntry->pszName;
      63          47 :             if (bVerbose)
      64           0 :                 printf("Testing %s...\n", psEntry->pszName);
      65             : 
      66             :             char **papszMD =
      67          47 :                 VSIGetFileMetadata(osFilenameInZip.c_str(), "ZIP", nullptr);
      68             :             bool bSeekOptimizedFound =
      69          47 :                 CSLFetchNameValue(papszMD, "SOZIP_FOUND") != nullptr;
      70             :             bool bSeekOptimizedValid =
      71          47 :                 CSLFetchNameValue(papszMD, "SOZIP_VALID") != nullptr;
      72             :             const char *pszChunkSize =
      73          47 :                 CSLFetchNameValue(papszMD, "SOZIP_CHUNK_SIZE");
      74          47 :             if (bSeekOptimizedValid)
      75             :             {
      76           3 :                 if (bVerbose)
      77           0 :                     printf("  %s has an associated .sozip.idx file\n",
      78           0 :                            psEntry->pszName);
      79             : 
      80             :                 const char *pszStartIdxDataOffset =
      81           3 :                     CSLFetchNameValue(papszMD, "SOZIP_START_DATA_OFFSET");
      82             :                 const vsi_l_offset nStartIdxOffset =
      83           3 :                     std::strtoull(pszStartIdxDataOffset, nullptr, 10);
      84           3 :                 VSILFILE *fpRaw = VSIFOpenL(pszZipFilename, "rb");
      85           3 :                 CPLAssert(fpRaw);
      86             : 
      87           3 :                 if (VSIFSeekL(fpRaw, nStartIdxOffset + 4, SEEK_SET) != 0)
      88             :                 {
      89           0 :                     fprintf(stderr, "VSIFSeekL() failed.\n");
      90           0 :                     ret = 1;
      91             :                 }
      92           3 :                 uint32_t nToSkip = 0;
      93           3 :                 if (VSIFReadL(&nToSkip, sizeof(nToSkip), 1, fpRaw) != 1)
      94             :                 {
      95           0 :                     fprintf(stderr, "VSIFReadL() failed.\n");
      96           0 :                     ret = 1;
      97             :                 }
      98           3 :                 CPL_LSBPTR32(&nToSkip);
      99             : 
     100           3 :                 if (VSIFSeekL(fpRaw, nStartIdxOffset + 32 + nToSkip,
     101           3 :                               SEEK_SET) != 0)
     102             :                 {
     103           0 :                     fprintf(stderr, "VSIFSeekL() failed.\n");
     104           0 :                     ret = 1;
     105             :                 }
     106           3 :                 const int nChunkSize = atoi(pszChunkSize);
     107           3 :                 const uint64_t nCompressedSize = std::strtoull(
     108             :                     CSLFetchNameValue(papszMD, "COMPRESSED_SIZE"), nullptr, 10);
     109           3 :                 const uint64_t nUncompressedSize = std::strtoull(
     110             :                     CSLFetchNameValue(papszMD, "UNCOMPRESSED_SIZE"), nullptr,
     111             :                     10);
     112           6 :                 if (nChunkSize == 0 ||  // cannot happen
     113           3 :                     (nUncompressedSize - 1) / nChunkSize >
     114           3 :                         static_cast<uint64_t>(std::numeric_limits<int>::max()))
     115             :                 {
     116           0 :                     fprintf(
     117             :                         stderr,
     118             :                         "* File %s has a SOZip index, but (nUncompressedSize - "
     119             :                         "1) / nChunkSize > INT_MAX !\n",
     120           0 :                         psEntry->pszName);
     121           0 :                     nCountInvalidSOZIP++;
     122           0 :                     ret = 1;
     123           0 :                     CSLDestroy(papszMD);
     124           0 :                     continue;
     125             :                 }
     126           3 :                 int nChunksItems =
     127           3 :                     static_cast<int>((nUncompressedSize - 1) / nChunkSize);
     128             : 
     129           3 :                 if (bVerbose)
     130           0 :                     printf("  %s: checking index offset values...\n",
     131           0 :                            psEntry->pszName);
     132             : 
     133           6 :                 std::vector<uint64_t> anOffsets;
     134             :                 try
     135             :                 {
     136           3 :                     anOffsets.reserve(nChunksItems);
     137             :                 }
     138           0 :                 catch (const std::exception &)
     139             :                 {
     140           0 :                     nChunksItems = 0;
     141           0 :                     fprintf(stderr,
     142             :                             "Cannot allocate memory for chunk offsets.\n");
     143           0 :                     ret = 1;
     144             :                 }
     145             : 
     146         109 :                 for (int i = 0; i < nChunksItems; ++i)
     147             :                 {
     148         106 :                     uint64_t nOffset64 = 0;
     149         106 :                     if (VSIFReadL(&nOffset64, sizeof(nOffset64), 1, fpRaw) != 1)
     150             :                     {
     151           0 :                         fprintf(stderr, "VSIFReadL() failed.\n");
     152           0 :                         ret = 1;
     153             :                     }
     154         106 :                     CPL_LSBPTR64(&nOffset64);
     155         106 :                     if (nOffset64 >= nCompressedSize)
     156             :                     {
     157           0 :                         bSeekOptimizedValid = false;
     158           0 :                         fprintf(stderr,
     159             :                                 "Error: file %s, offset[%d] (= " CPL_FRMT_GUIB
     160             :                                 ") >= compressed_size is invalid.\n",
     161           0 :                                 psEntry->pszName, i,
     162             :                                 static_cast<GUIntBig>(nOffset64));
     163             :                     }
     164         106 :                     if (!anOffsets.empty())
     165             :                     {
     166         103 :                         const auto nPrevOffset = anOffsets.back();
     167         103 :                         if (nOffset64 <= nPrevOffset)
     168             :                         {
     169           0 :                             bSeekOptimizedValid = false;
     170           0 :                             fprintf(
     171             :                                 stderr,
     172             :                                 "Error: file %s, offset[%d] (= " CPL_FRMT_GUIB
     173             :                                 ") <= offset[%d] (= " CPL_FRMT_GUIB ")\n",
     174           0 :                                 psEntry->pszName, i + 1,
     175             :                                 static_cast<GUIntBig>(nOffset64), i,
     176             :                                 static_cast<GUIntBig>(nPrevOffset));
     177             :                         }
     178             :                     }
     179           3 :                     else if (nOffset64 < 9)
     180             :                     {
     181           0 :                         bSeekOptimizedValid = false;
     182           0 :                         fprintf(stderr,
     183             :                                 "Error: file %s, offset[0] (= " CPL_FRMT_GUIB
     184             :                                 ") is invalid.\n",
     185           0 :                                 psEntry->pszName,
     186             :                                 static_cast<GUIntBig>(nOffset64));
     187             :                     }
     188         106 :                     anOffsets.push_back(nOffset64);
     189             :                 }
     190             : 
     191           3 :                 if (bVerbose)
     192           0 :                     printf("  %s: checking chunks can be independently "
     193             :                            "decompressed...\n",
     194           0 :                            psEntry->pszName);
     195             : 
     196             :                 const char *pszStartDataOffset =
     197           3 :                     CSLFetchNameValue(papszMD, "START_DATA_OFFSET");
     198             :                 const vsi_l_offset nStartOffset =
     199           3 :                     std::strtoull(pszStartDataOffset, nullptr, 10);
     200           3 :                 VSILFILE *fp = VSIFOpenL(osFilenameInZip.c_str(), "rb");
     201           3 :                 if (!fp)
     202             :                 {
     203           0 :                     bSeekOptimizedValid = false;
     204           0 :                     fprintf(stderr, "Error: cannot open %s\n",
     205             :                             osFilenameInZip.c_str());
     206             :                 }
     207           6 :                 std::vector<GByte> abyData;
     208             :                 try
     209             :                 {
     210           3 :                     abyData.resize(nChunkSize);
     211             :                 }
     212           0 :                 catch (const std::exception &)
     213             :                 {
     214           0 :                     fprintf(stderr, "Cannot allocate memory for chunk data.\n");
     215           0 :                     ret = 1;
     216             :                 }
     217         109 :                 for (int i = 0; fp != nullptr && i < nChunksItems; ++i)
     218             :                 {
     219         106 :                     if (VSIFSeekL(fpRaw, nStartOffset + anOffsets[i] - 9,
     220         106 :                                   SEEK_SET) != 0)
     221             :                     {
     222           0 :                         fprintf(stderr, "VSIFSeekL() failed.\n");
     223           0 :                         ret = 1;
     224             :                     }
     225         106 :                     GByte abyEnd[9] = {0};
     226         106 :                     if (VSIFReadL(abyEnd, 9, 1, fpRaw) != 1)
     227             :                     {
     228           0 :                         fprintf(stderr, "VSIFReadL() failed.\n");
     229           0 :                         ret = 1;
     230             :                     }
     231         106 :                     if (memcmp(abyEnd, "\x00\x00\xFF\xFF\x00\x00\x00\xFF\xFF",
     232             :                                9) != 0)
     233             :                     {
     234           0 :                         bSeekOptimizedValid = false;
     235           0 :                         fprintf(
     236             :                             stderr,
     237             :                             "Error: file %s, chunk[%d] is not terminated by "
     238             :                             "\\x00\\x00\\xFF\\xFF\\x00\\x00\\x00\\xFF\\xFF.\n",
     239           0 :                             psEntry->pszName, i);
     240             :                     }
     241         106 :                     if (!abyData.empty())
     242             :                     {
     243         212 :                         if (VSIFSeekL(fp,
     244         106 :                                       static_cast<vsi_l_offset>(i) * nChunkSize,
     245         106 :                                       SEEK_SET) != 0)
     246             :                         {
     247           0 :                             fprintf(stderr, "VSIFSeekL() failed.\n");
     248           0 :                             ret = 1;
     249             :                         }
     250             :                         const size_t nRead =
     251         106 :                             VSIFReadL(&abyData[0], 1, nChunkSize, fp);
     252         106 :                         if (nRead != static_cast<size_t>(nChunkSize))
     253             :                         {
     254           0 :                             bSeekOptimizedValid = false;
     255           0 :                             fprintf(stderr,
     256             :                                     "Error: file %s, chunk[%d] cannot be fully "
     257             :                                     "read.\n",
     258           0 :                                     psEntry->pszName, i);
     259             :                         }
     260             :                     }
     261             :                 }
     262             : 
     263           3 :                 if (fp)
     264             :                 {
     265           6 :                     if (VSIFSeekL(fp,
     266           3 :                                   static_cast<vsi_l_offset>(nChunksItems) *
     267           3 :                                       nChunkSize,
     268           3 :                                   SEEK_SET) != 0)
     269             :                     {
     270           0 :                         fprintf(stderr, "VSIFSeekL() failed.\n");
     271           0 :                         ret = 1;
     272             :                     }
     273             :                     const size_t nRead =
     274           3 :                         VSIFReadL(&abyData[0], 1, nChunkSize, fp);
     275           3 :                     if (nRead != static_cast<size_t>(
     276           3 :                                      nUncompressedSize -
     277           3 :                                      static_cast<vsi_l_offset>(nChunksItems) *
     278           3 :                                          nChunkSize))
     279             :                     {
     280           0 :                         bSeekOptimizedValid = false;
     281           0 :                         fprintf(
     282             :                             stderr,
     283             :                             "Error: file %s, chunk[%d] cannot be fully read.\n",
     284           0 :                             psEntry->pszName, nChunksItems);
     285             :                     }
     286             : 
     287           3 :                     VSIFCloseL(fp);
     288             :                 }
     289             : 
     290           3 :                 VSIFCloseL(fpRaw);
     291             :             }
     292             : 
     293          47 :             if (bSeekOptimizedValid)
     294             :             {
     295           3 :                 printf("* File %s has a valid SOZip index, using chunk_size = "
     296             :                        "%s.\n",
     297           3 :                        psEntry->pszName, pszChunkSize);
     298           3 :                 nCountValidSOZIP++;
     299             :             }
     300          44 :             else if (bSeekOptimizedFound)
     301             :             {
     302           0 :                 fprintf(stderr,
     303             :                         "* File %s has a SOZip index, but is is invalid!\n",
     304           0 :                         psEntry->pszName);
     305           0 :                 nCountInvalidSOZIP++;
     306           0 :                 ret = 1;
     307             :             }
     308          47 :             CSLDestroy(papszMD);
     309             :         }
     310          48 :     }
     311             : 
     312           2 :     VSICloseDir(psDir);
     313             : 
     314           2 :     if (ret == 0)
     315             :     {
     316           2 :         if (nCountValidSOZIP > 0)
     317             :         {
     318           2 :             printf("-----\n");
     319           2 :             printf("%s is a valid .zip file, and contains %d SOZip-enabled "
     320             :                    "file(s).\n",
     321             :                    pszZipFilename, nCountValidSOZIP);
     322             :         }
     323             :         else
     324           0 :             printf("%s is a valid .zip file, but does not contain any "
     325             :                    "SOZip-enabled files.\n",
     326             :                    pszZipFilename);
     327             :     }
     328             :     else
     329             :     {
     330           0 :         if (nCountInvalidSOZIP > 0)
     331           0 :             printf("-----\n");
     332           0 :         fprintf(stderr, "%s is not a valid SOZip file!\n", pszZipFilename);
     333             :     }
     334             : 
     335           2 :     return ret;
     336             : }
     337             : 
     338             : /************************************************************************/
     339             : /*                                main()                                */
     340             : /************************************************************************/
     341             : 
     342           9 : MAIN_START(nArgc, papszArgv)
     343             : {
     344           9 :     EarlySetConfigOptions(nArgc, papszArgv);
     345           9 :     nArgc = GDALGeneralCmdLineProcessor(nArgc, &papszArgv, 0);
     346          17 :     CPLStringList aosArgv;
     347           9 :     aosArgv.Assign(papszArgv, /* bTakeOwnership= */ true);
     348           9 :     if (nArgc < 1)
     349           0 :         std::exit(-nArgc);
     350             : 
     351          26 :     GDALArgumentParser argParser(aosArgv[0], /* bForBinary=*/true);
     352             : 
     353           9 :     argParser.add_description(_("Generate a seek-optimized ZIP (SOZip) file."));
     354             : 
     355             :     argParser.add_epilog(
     356           9 :         _("For more details, consult https://gdal.org/programs/sozip.html"));
     357             : 
     358          17 :     std::string osZipFilename;
     359           9 :     argParser.add_argument("zip_filename")
     360          18 :         .metavar("<zip_filename>")
     361           9 :         .store_into(osZipFilename)
     362           9 :         .help(_("ZIP filename."));
     363             : 
     364           9 :     bool bRecurse = false;
     365           9 :     argParser.add_argument("-r", "--recurse-paths")
     366           9 :         .store_into(bRecurse)
     367             :         .help(_("Travels the directory structure of the specified directories "
     368           9 :                 "recursively."));
     369             : 
     370           9 :     bool bOverwrite = false;
     371             :     {
     372           9 :         auto &group = argParser.add_mutually_exclusive_group();
     373           9 :         group.add_argument("-g", "--grow")
     374           9 :             .flag()  // Default mode. Nothing to do
     375             :             .help(
     376             :                 _("Grow an existing zip file with the content of the specified "
     377           9 :                   "filename(s). Default mode."));
     378           9 :         group.add_argument("--overwrite")
     379           9 :             .store_into(bOverwrite)
     380           9 :             .help(_("Overwrite the target zip file if it already exists."));
     381             :     }
     382             : 
     383           9 :     bool bList = false;
     384           9 :     bool bValidate = false;
     385          17 :     std::string osOptimizeFrom;
     386          17 :     std::vector<std::string> aosFiles;
     387             :     {
     388           9 :         auto &group = argParser.add_mutually_exclusive_group();
     389           9 :         group.add_argument("-l", "--list")
     390           9 :             .store_into(bList)
     391           9 :             .help(_("List the files contained in the zip file."));
     392           9 :         group.add_argument("--validate")
     393           9 :             .store_into(bValidate)
     394           9 :             .help(_("Validates a ZIP/SOZip file."));
     395           9 :         group.add_argument("--optimize-from")
     396          18 :             .metavar("<input.zip>")
     397           9 :             .store_into(osOptimizeFrom)
     398             :             .help(
     399           9 :                 _("Re-process {input.zip} to generate a SOZip-optimized .zip"));
     400           9 :         group.add_argument("input_files")
     401          18 :             .metavar("<input_files>")
     402           9 :             .store_into(aosFiles)
     403          18 :             .help(_("Filename of the file to add."))
     404           9 :             .nargs(argparse::nargs_pattern::any);
     405             :     }
     406             : 
     407           9 :     bool bQuiet = false;
     408           9 :     bool bVerbose = false;
     409           9 :     argParser.add_group("Advanced options");
     410             :     {
     411           9 :         auto &group = argParser.add_mutually_exclusive_group();
     412           9 :         group.add_argument("--quiet").store_into(bQuiet).help(
     413             :             _("Quiet mode. No progress message is emitted on the standard "
     414           9 :               "output."));
     415           9 :         group.add_argument("--verbose")
     416           9 :             .store_into(bVerbose)
     417           9 :             .help(_("Verbose mode."));
     418             :     }
     419           9 :     bool bJunkPaths = false;
     420           9 :     argParser.add_argument("-j", "--junk-paths")
     421           9 :         .store_into(bJunkPaths)
     422             :         .help(
     423             :             _("Store just the name of a saved file (junk the path), and do not "
     424           9 :               "store directory names."));
     425             : 
     426          17 :     CPLStringList aosOptions;
     427           9 :     argParser.add_argument("--enable-sozip")
     428           9 :         .choices("auto", "yes", "no")
     429          18 :         .metavar("auto|yes|no")
     430           3 :         .action([&aosOptions](const std::string &s)
     431          12 :                 { aosOptions.SetNameValue("SOZIP_ENABLED", s.c_str()); })
     432             :         .help(_("In auto mode, a file is seek-optimized only if its size is "
     433             :                 "above the value of\n"
     434             :                 "--sozip-chunk-size. In yes mode, all input files will be "
     435             :                 "seek-optimized.\n"
     436           9 :                 "In no mode, no input files will be seek-optimized."));
     437           9 :     argParser.add_argument("--sozip-chunk-size")
     438          18 :         .metavar("<value in bytes or with K/M suffix>")
     439           3 :         .action([&aosOptions](const std::string &s)
     440          12 :                 { aosOptions.SetNameValue("SOZIP_CHUNK_SIZE", s.c_str()); })
     441             :         .help(_(
     442           9 :             "Chunk size for a seek-optimized file. Defaults to 32768 bytes."));
     443           9 :     argParser.add_argument("--sozip-min-file-size")
     444          18 :         .metavar("<value in bytes or with K/M/G suffix>")
     445           0 :         .action([&aosOptions](const std::string &s)
     446           9 :                 { aosOptions.SetNameValue("SOZIP_MIN_FILE_SIZE", s.c_str()); })
     447             :         .help(
     448             :             _("Minimum file size to decide if a file should be seek-optimized. "
     449           9 :               "Defaults to 1 MB byte."));
     450           9 :     argParser.add_argument("--content-type")
     451          18 :         .metavar("<string>")
     452           1 :         .action([&aosOptions](const std::string &s)
     453          10 :                 { aosOptions.SetNameValue("CONTENT_TYPE", s.c_str()); })
     454           9 :         .help(_("Store the Content-Type for the file being added."));
     455             : 
     456             :     try
     457             :     {
     458           9 :         argParser.parse_args(aosArgv);
     459             :     }
     460           0 :     catch (const std::exception &err)
     461             :     {
     462           0 :         argParser.display_error_and_usage(err);
     463           0 :         std::exit(1);
     464             :     }
     465             : 
     466           8 :     if (!bList && !bValidate && osOptimizeFrom.empty() && aosFiles.empty())
     467             :     {
     468           0 :         std::cerr << _("Missing source filename(s)") << std::endl << std::endl;
     469           0 :         std::cerr << argParser << std::endl;
     470           0 :         std::exit(1);
     471             :     }
     472             : 
     473           8 :     const char *pszZipFilename = osZipFilename.c_str();
     474           8 :     if (!EQUAL(CPLGetExtension(pszZipFilename), "zip"))
     475             :     {
     476           0 :         std::cerr << _("Extension of zip filename should be .zip") << std::endl
     477           0 :                   << std::endl;
     478           0 :         std::cerr << argParser << std::endl;
     479           0 :         std::exit(1);
     480             :     }
     481             : 
     482           8 :     if (bValidate)
     483             :     {
     484           2 :         return Validate(pszZipFilename, bVerbose);
     485             :     }
     486             : 
     487           6 :     if (bList)
     488             :     {
     489           1 :         VSIDIR *psDir = VSIOpenDir(
     490           2 :             (std::string("/vsizip/") + pszZipFilename).c_str(), -1, nullptr);
     491           1 :         if (psDir == nullptr)
     492           0 :             return 1;
     493           1 :         printf("  Length          DateTime        Seek-optimized / chunk size  "
     494             :                "Name               Properties\n");
     495             :         /* clang-format off */
     496           1 :         printf("-----------  -------------------  ---------------------------  -----------------  --------------\n");
     497             :         /* clang-format on */
     498           2 :         while (auto psEntry = VSIGetNextDirEntry(psDir))
     499             :         {
     500           1 :             if (!VSI_ISDIR(psEntry->nMode))
     501             :             {
     502             :                 struct tm brokenDown;
     503           1 :                 CPLUnixTimeToYMDHMS(psEntry->nMTime, &brokenDown);
     504           2 :                 const std::string osFilename = std::string("/vsizip/{") +
     505           2 :                                                pszZipFilename + "}/" +
     506           3 :                                                psEntry->pszName;
     507           2 :                 std::string osProperties;
     508             :                 const CPLStringList aosMDGeneric(
     509           2 :                     VSIGetFileMetadata(osFilename.c_str(), nullptr, nullptr));
     510           1 :                 for (const char *pszMDGeneric : aosMDGeneric)
     511             :                 {
     512           0 :                     if (!osProperties.empty())
     513           0 :                         osProperties += ',';
     514           0 :                     osProperties += pszMDGeneric;
     515             :                 }
     516             : 
     517             :                 const CPLStringList aosMD(
     518           2 :                     VSIGetFileMetadata(osFilename.c_str(), "ZIP", nullptr));
     519             :                 const bool bSeekOptimized =
     520           1 :                     aosMD.FetchNameValue("SOZIP_VALID") != nullptr;
     521             :                 const char *pszChunkSize =
     522           1 :                     aosMD.FetchNameValue("SOZIP_CHUNK_SIZE");
     523           1 :                 printf("%11" CPL_FRMT_GB_WITHOUT_PREFIX
     524             :                        "u  %04d-%02d-%02d %02d:%02d:%02d  %s  %s               "
     525             :                        "%s\n",
     526           1 :                        static_cast<GUIntBig>(psEntry->nSize),
     527           1 :                        brokenDown.tm_year + 1900, brokenDown.tm_mon + 1,
     528             :                        brokenDown.tm_mday, brokenDown.tm_hour,
     529             :                        brokenDown.tm_min, brokenDown.tm_sec,
     530             :                        bSeekOptimized
     531           1 :                            ? CPLSPrintf("   yes (%9s bytes)   ", pszChunkSize)
     532             :                            : "                           ",
     533           1 :                        psEntry->pszName, osProperties.c_str());
     534             :             }
     535           1 :         }
     536           1 :         VSICloseDir(psDir);
     537           1 :         return 0;
     538             :     }
     539             : 
     540             :     VSIStatBufL sBuf;
     541          10 :     CPLStringList aosOptionsCreateZip;
     542           5 :     if (bOverwrite)
     543             :     {
     544           1 :         VSIUnlink(pszZipFilename);
     545             :     }
     546             :     else
     547             :     {
     548           4 :         if (VSIStatExL(pszZipFilename, &sBuf, VSI_STAT_EXISTS_FLAG) == 0)
     549             :         {
     550           1 :             if (!osOptimizeFrom.empty())
     551             :             {
     552           0 :                 fprintf(
     553             :                     stderr,
     554             :                     "%s already exists. Use --overwrite or delete it before.\n",
     555             :                     pszZipFilename);
     556           0 :                 return 1;
     557             :             }
     558           1 :             aosOptionsCreateZip.SetNameValue("APPEND", "TRUE");
     559             :         }
     560             :     }
     561             : 
     562           5 :     uint64_t nTotalSize = 0;
     563          10 :     std::vector<uint64_t> anFileSizes;
     564             : 
     565          10 :     std::string osRemovePrefix;
     566           5 :     if (!osOptimizeFrom.empty())
     567             :     {
     568           1 :         VSIDIR *psDir = VSIOpenDir(
     569           2 :             (std::string("/vsizip/") + osOptimizeFrom).c_str(), -1, nullptr);
     570           1 :         if (psDir == nullptr)
     571             :         {
     572           0 :             fprintf(stderr, "%s is not a valid .zip file\n",
     573             :                     osOptimizeFrom.c_str());
     574           0 :             return 1;
     575             :         }
     576             : 
     577             :         osRemovePrefix =
     578           1 :             std::string("/vsizip/{").append(osOptimizeFrom).append("}/");
     579          48 :         while (auto psEntry = VSIGetNextDirEntry(psDir))
     580             :         {
     581          47 :             if (!VSI_ISDIR(psEntry->nMode))
     582             :             {
     583             :                 const std::string osFilenameInZip =
     584          92 :                     osRemovePrefix + psEntry->pszName;
     585          46 :                 aosFiles.push_back(osFilenameInZip);
     586             :             }
     587          47 :         }
     588           1 :         VSICloseDir(psDir);
     589             :     }
     590           4 :     else if (bRecurse)
     591             :     {
     592           0 :         std::vector<std::string> aosNewFiles;
     593           0 :         for (const std::string &osFile : aosFiles)
     594             :         {
     595           0 :             if (VSIStatL(osFile.c_str(), &sBuf) == 0 && VSI_ISDIR(sBuf.st_mode))
     596             :             {
     597           0 :                 VSIDIR *psDir = VSIOpenDir(osFile.c_str(), -1, nullptr);
     598           0 :                 if (psDir == nullptr)
     599           0 :                     return 1;
     600           0 :                 while (auto psEntry = VSIGetNextDirEntry(psDir))
     601             :                 {
     602           0 :                     if (!VSI_ISDIR(psEntry->nMode))
     603             :                     {
     604           0 :                         std::string osName(osFile);
     605           0 :                         if (osName.back() != '/')
     606           0 :                             osName += '/';
     607           0 :                         osName += psEntry->pszName;
     608           0 :                         aosNewFiles.push_back(osName);
     609           0 :                         if (aosNewFiles.size() > 10 * 1000 * 1000)
     610             :                         {
     611           0 :                             CPLError(CE_Failure, CPLE_NotSupported,
     612             :                                      "Too many source files");
     613           0 :                             VSICloseDir(psDir);
     614           0 :                             return 1;
     615             :                         }
     616             :                     }
     617           0 :                 }
     618           0 :                 VSICloseDir(psDir);
     619             :             }
     620             :         }
     621           0 :         aosFiles = std::move(aosNewFiles);
     622             :     }
     623             : 
     624           5 :     if (!bVerbose && !bQuiet)
     625             :     {
     626           5 :         anFileSizes.resize(aosFiles.size());
     627          55 :         for (size_t i = 0; i < aosFiles.size(); ++i)
     628             :         {
     629          50 :             if (VSIStatL(aosFiles[i].c_str(), &sBuf) == 0)
     630             :             {
     631          50 :                 anFileSizes[i] = sBuf.st_size;
     632          50 :                 nTotalSize += sBuf.st_size;
     633             :             }
     634             :             else
     635             :             {
     636           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s\n",
     637           0 :                          aosFiles[i].c_str());
     638           0 :                 return 1;
     639             :             }
     640             :         }
     641             :     }
     642             : 
     643           5 :     void *hZIP = CPLCreateZip(pszZipFilename, aosOptionsCreateZip.List());
     644             : 
     645           5 :     if (!hZIP)
     646           0 :         return 1;
     647             : 
     648           5 :     uint64_t nCurSize = 0;
     649          55 :     for (size_t i = 0; i < aosFiles.size(); ++i)
     650             :     {
     651          50 :         if (bVerbose)
     652           0 :             printf("Adding %s... (%d/%d)\n", aosFiles[i].c_str(), int(i + 1),
     653           0 :                    static_cast<int>(aosFiles.size()));
     654          50 :         void *pScaledProgress = nullptr;
     655          50 :         if (!bVerbose && !bQuiet && nTotalSize != 0)
     656             :         {
     657          50 :             pScaledProgress = GDALCreateScaledProgress(
     658          50 :                 double(nCurSize) / nTotalSize,
     659          50 :                 double(nCurSize + anFileSizes[i]) / nTotalSize,
     660             :                 GDALTermProgress, nullptr);
     661             :         }
     662           0 :         else if (!bQuiet)
     663             :         {
     664           0 :             GDALTermProgress(0, nullptr, nullptr);
     665             :         }
     666         100 :         if (VSIStatL(aosFiles[i].c_str(), &sBuf) != 0 ||
     667          50 :             VSI_ISDIR(sBuf.st_mode))
     668             :         {
     669           0 :             CPLError(CE_Failure, CPLE_AppDefined, "%s is not a regular file",
     670           0 :                      aosFiles[i].c_str());
     671           0 :             CPLCloseZip(hZIP);
     672           0 :             return 1;
     673             :         }
     674             : 
     675          50 :         std::string osArchiveFilename(aosFiles[i]);
     676          50 :         if (bJunkPaths)
     677             :         {
     678           4 :             osArchiveFilename = CPLGetFilename(aosFiles[i].c_str());
     679             :         }
     680          92 :         else if (!osRemovePrefix.empty() &&
     681          46 :                  STARTS_WITH(osArchiveFilename.c_str(), osRemovePrefix.c_str()))
     682             :         {
     683          46 :             osArchiveFilename = osArchiveFilename.substr(osRemovePrefix.size());
     684             :         }
     685           0 :         else if (osArchiveFilename[0] == '/')
     686             :         {
     687           0 :             osArchiveFilename = osArchiveFilename.substr(1);
     688             :         }
     689           0 :         else if (osArchiveFilename.size() > 3 && osArchiveFilename[1] == ':' &&
     690           0 :                  (osArchiveFilename[2] == '/' || osArchiveFilename[2] == '\\'))
     691             :         {
     692           0 :             osArchiveFilename = osArchiveFilename.substr(3);
     693             :         }
     694             : 
     695             :         CPLErr eErr =
     696         100 :             CPLAddFileInZip(hZIP, osArchiveFilename.c_str(),
     697          50 :                             aosFiles[i].c_str(), nullptr, aosOptions.List(),
     698             :                             pScaledProgress ? GDALScaledProgress
     699           0 :                             : bQuiet        ? nullptr
     700             :                                             : GDALTermProgress,
     701             :                             pScaledProgress ? pScaledProgress : nullptr);
     702          50 :         if (pScaledProgress)
     703             :         {
     704          50 :             GDALDestroyScaledProgress(pScaledProgress);
     705          50 :             nCurSize += anFileSizes[i];
     706             :         }
     707          50 :         if (eErr != CE_None)
     708             :         {
     709           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Failed adding %s",
     710           0 :                      aosFiles[i].c_str());
     711           0 :             CPLCloseZip(hZIP);
     712           0 :             return 1;
     713             :         }
     714             :     }
     715           5 :     CPLCloseZip(hZIP);
     716           5 :     return 0;
     717             : }
     718             : 
     719           0 : MAIN_END

Generated by: LCOV version 1.14