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
|