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