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