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

Generated by: LCOV version 1.14