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