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