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 107 : GDALVSISOZIPCreateBaseAlgorithm(const std::string &name,
36 : const std::string &description,
37 : const std::string &helpURL,
38 : bool optimizeFrom)
39 107 : : GDALAlgorithm(name, description, helpURL),
40 107 : m_optimizeFrom(optimizeFrom)
41 : {
42 107 : AddProgressArg();
43 107 : if (optimizeFrom)
44 94 : AddArg("input", 'i', _("Input ZIP filename"), &m_inputFilenames)
45 47 : .SetRequired()
46 47 : .SetPositional()
47 47 : .SetMaxCount(1);
48 : else
49 120 : AddArg("input", 'i', _("Input filenames"), &m_inputFilenames)
50 60 : .SetRequired()
51 60 : .SetPositional();
52 214 : AddArg("output", 'o', _("Output ZIP filename"), &m_zipFilename)
53 107 : .SetRequired()
54 107 : .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 107 : });
68 107 : AddOverwriteArg(&m_overwrite);
69 107 : if (!optimizeFrom)
70 : {
71 : AddArg("recursive", 'r',
72 : _("Travels the directory structure of the specified "
73 : "directories recursively"),
74 120 : &m_recursive)
75 60 : .AddHiddenAlias("recurse");
76 : }
77 107 : 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 120 : &m_noDirName)
83 60 : .AddAlias("junk-paths");
84 : }
85 : AddArg("enable-sozip", 0,
86 : _("Whether to automatically/systematically/never apply the "
87 : "SOZIP optimization"),
88 214 : &m_mode)
89 107 : .SetDefault(m_mode)
90 107 : .SetChoices("auto", "yes", "no");
91 : AddArg("sozip-chunk-size", 0, _("Chunk size for a seek-optimized file"),
92 214 : &m_chunkSize)
93 214 : .SetMetaVar("<value in bytes or with K/M suffix>")
94 107 : .SetDefault(m_chunkSize)
95 107 : .SetMinCharCount(1);
96 : AddArg(
97 : "sozip-min-file-size", 0,
98 : _("Minimum file size to decide if a file should be seek-optimized"),
99 214 : &m_minFileSize)
100 214 : .SetMetaVar("<value in bytes or with K/M/G suffix>")
101 107 : .SetDefault(m_minFileSize)
102 107 : .SetMinCharCount(1);
103 107 : if (!optimizeFrom)
104 : AddArg("content-type", 0,
105 : _("Store the Content-Type of the file being added."),
106 120 : &m_contentType)
107 60 : .SetMinCharCount(1);
108 :
109 107 : AddOutputStringArg(&m_output);
110 107 : AddStdoutArg(&m_stdout);
111 107 : }
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 120 : 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 60 : GDALVSISOZIPCreateAlgorithm()
352 60 : : GDALVSISOZIPCreateBaseAlgorithm(NAME, DESCRIPTION, HELP_URL, false)
353 : {
354 60 : }
355 :
356 : ~GDALVSISOZIPCreateAlgorithm() override;
357 : };
358 :
359 : GDALVSISOZIPCreateAlgorithm::~GDALVSISOZIPCreateAlgorithm() = default;
360 :
361 : /************************************************************************/
362 : /* GDALVSISOZIPOptimizeAlgorithm */
363 : /************************************************************************/
364 :
365 94 : 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 47 : GDALVSISOZIPOptimizeAlgorithm()
375 47 : : GDALVSISOZIPCreateBaseAlgorithm(NAME, DESCRIPTION, HELP_URL, true)
376 : {
377 47 : }
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 45 : GDALVSISOZIPListAlgorithm() : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
397 : {
398 45 : AddProgressArg(/* hidden = */ true);
399 :
400 90 : AddArg("input", 'i', _("Input ZIP filename"), &m_zipFilename)
401 45 : .SetRequired()
402 45 : .SetPositional();
403 45 : AddOutputStringArg(&m_output);
404 45 : }
405 :
406 : private:
407 : std::string m_zipFilename{};
408 : std::string m_output{};
409 :
410 : bool RunImpl(GDALProgressFunc, void *) override;
411 : };
412 :
413 : /************************************************************************/
414 : /* GDALVSISOZIPListAlgorithm::RunImpl() */
415 : /************************************************************************/
416 :
417 2 : bool GDALVSISOZIPListAlgorithm::RunImpl(GDALProgressFunc, void *)
418 : {
419 : std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
420 4 : VSIOpenDir(std::string("/vsizip/").append(m_zipFilename).c_str(), -1,
421 : nullptr),
422 6 : VSICloseDir);
423 2 : if (!psDir)
424 : {
425 1 : ReportError(CE_Failure, CPLE_AppDefined, "%s is not a valid .zip file",
426 : m_zipFilename.c_str());
427 1 : return false;
428 : }
429 :
430 : m_output = " Length DateTime Seek-optimized / chunk size "
431 1 : "Name Properties\n";
432 : /* clang-format off */
433 1 : m_output += "----------- ------------------- --------------------------- ----------------- --------------\n";
434 : /* clang-format on */
435 :
436 2 : while (const auto psEntry = VSIGetNextDirEntry(psDir.get()))
437 : {
438 1 : if (!VSI_ISDIR(psEntry->nMode))
439 : {
440 : struct tm brokenDown;
441 1 : CPLUnixTimeToYMDHMS(psEntry->nMTime, &brokenDown);
442 2 : const std::string osFilename = std::string("/vsizip/{")
443 1 : .append(m_zipFilename)
444 1 : .append("}/")
445 2 : .append(psEntry->pszName);
446 2 : std::string osProperties;
447 : const CPLStringList aosMDGeneric(
448 2 : VSIGetFileMetadata(osFilename.c_str(), nullptr, nullptr));
449 1 : for (const char *pszMDGeneric : aosMDGeneric)
450 : {
451 0 : if (!osProperties.empty())
452 0 : osProperties += ',';
453 0 : osProperties += pszMDGeneric;
454 : }
455 :
456 : const CPLStringList aosMD(
457 2 : VSIGetFileMetadata(osFilename.c_str(), "ZIP", nullptr));
458 : const bool bSeekOptimized =
459 1 : aosMD.FetchNameValue("SOZIP_VALID") != nullptr;
460 1 : const char *pszChunkSize = aosMD.FetchNameValue("SOZIP_CHUNK_SIZE");
461 : m_output += CPLSPrintf(
462 : "%11" CPL_FRMT_GB_WITHOUT_PREFIX
463 : "u %04d-%02d-%02d %02d:%02d:%02d %s %s "
464 : "%s\n",
465 1 : static_cast<GUIntBig>(psEntry->nSize),
466 1 : brokenDown.tm_year + 1900, brokenDown.tm_mon + 1,
467 : brokenDown.tm_mday, brokenDown.tm_hour, brokenDown.tm_min,
468 : brokenDown.tm_sec,
469 : bSeekOptimized
470 1 : ? CPLSPrintf(" yes (%9s bytes) ", pszChunkSize)
471 : : " ",
472 2 : psEntry->pszName, osProperties.c_str());
473 : }
474 1 : }
475 1 : return true;
476 : }
477 :
478 : /************************************************************************/
479 : /* GDALVSISOZIPValidateAlgorithm */
480 : /************************************************************************/
481 :
482 : class GDALVSISOZIPValidateAlgorithm final : public GDALAlgorithm
483 : {
484 : public:
485 : static constexpr const char *NAME = "validate";
486 : static constexpr const char *DESCRIPTION =
487 : "Validate a ZIP file, possibly using SOZIP optimization.";
488 : static constexpr const char *HELP_URL = "/programs/gdal_vsi_sozip.html";
489 :
490 51 : GDALVSISOZIPValidateAlgorithm() : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
491 : {
492 51 : AddProgressArg(/* hidden = */ true);
493 :
494 102 : AddArg("input", 'i', _("Input ZIP filename"), &m_zipFilename)
495 51 : .SetRequired()
496 51 : .SetPositional();
497 51 : AddOutputStringArg(&m_output);
498 102 : AddArg("verbose", 'v', _("Turn on verbose mode"), &m_verbose)
499 51 : .SetHiddenForAPI();
500 51 : AddStdoutArg(&m_stdout);
501 51 : }
502 :
503 : private:
504 : std::string m_zipFilename{};
505 : std::string m_output{};
506 : bool m_stdout = false;
507 : bool m_verbose = false;
508 :
509 : bool RunImpl(GDALProgressFunc, void *) override;
510 :
511 22 : void Output(const std::string &s)
512 : {
513 22 : if (!m_quiet)
514 : {
515 22 : if (m_stdout)
516 14 : printf("%s", s.c_str());
517 : else
518 8 : m_output += s;
519 : }
520 22 : }
521 : };
522 :
523 : /************************************************************************/
524 : /* GDALVSISOZIPValidateAlgorithm::RunImpl() */
525 : /************************************************************************/
526 :
527 8 : bool GDALVSISOZIPValidateAlgorithm::RunImpl(GDALProgressFunc, void *)
528 : {
529 : std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
530 16 : VSIOpenDir(std::string("/vsizip/").append(m_zipFilename).c_str(), -1,
531 : nullptr),
532 24 : VSICloseDir);
533 8 : if (!psDir)
534 : {
535 1 : ReportError(CE_Failure, CPLE_AppDefined, "%s is not a valid .zip file",
536 : m_zipFilename.c_str());
537 1 : return false;
538 : }
539 :
540 7 : int nCountValidSOZIP = 0;
541 7 : bool ret = true;
542 7 : const bool bVerbose = m_verbose;
543 65 : while (const auto psEntry = VSIGetNextDirEntry(psDir.get()))
544 : {
545 58 : if (!VSI_ISDIR(psEntry->nMode))
546 : {
547 108 : const std::string osFilenameInZip = std::string("/vsizip/{")
548 54 : .append(m_zipFilename)
549 54 : .append("}/")
550 54 : .append(psEntry->pszName);
551 54 : if (bVerbose)
552 1 : Output(CPLSPrintf("Testing %s...\n", psEntry->pszName));
553 :
554 : const CPLStringList aosMD(
555 54 : VSIGetFileMetadata(osFilenameInZip.c_str(), "ZIP", nullptr));
556 : bool bSeekOptimizedFound =
557 54 : aosMD.FetchNameValue("SOZIP_FOUND") != nullptr;
558 : bool bSeekOptimizedValid =
559 54 : aosMD.FetchNameValue("SOZIP_VALID") != nullptr;
560 54 : const char *pszChunkSize = aosMD.FetchNameValue("SOZIP_CHUNK_SIZE");
561 54 : if (bSeekOptimizedValid)
562 : {
563 6 : if (bVerbose)
564 : {
565 2 : Output(
566 : CPLSPrintf(" %s has an associated .sozip.idx file\n",
567 1 : psEntry->pszName));
568 : }
569 :
570 : const char *pszStartIdxDataOffset =
571 6 : aosMD.FetchNameValue("SOZIP_START_DATA_OFFSET");
572 : const vsi_l_offset nStartIdxOffset =
573 6 : std::strtoull(pszStartIdxDataOffset, nullptr, 10);
574 6 : VSILFILE *fpRaw = VSIFOpenL(m_zipFilename.c_str(), "rb");
575 6 : CPLAssert(fpRaw);
576 :
577 6 : if (VSIFSeekL(fpRaw, nStartIdxOffset + 4, SEEK_SET) != 0)
578 : {
579 0 : ReportError(CE_Failure, CPLE_AppDefined,
580 : "VSIFSeekL() failed.");
581 0 : ret = false;
582 : }
583 6 : uint32_t nToSkip = 0;
584 6 : if (VSIFReadL(&nToSkip, sizeof(nToSkip), 1, fpRaw) != 1)
585 : {
586 0 : ReportError(CE_Failure, CPLE_AppDefined,
587 : "VSIFReadL() failed.");
588 0 : ret = false;
589 : }
590 6 : CPL_LSBPTR32(&nToSkip);
591 :
592 6 : if (VSIFSeekL(fpRaw, nStartIdxOffset + 32 + nToSkip,
593 6 : SEEK_SET) != 0)
594 : {
595 0 : ReportError(CE_Failure, CPLE_AppDefined,
596 : "VSIFSeekL() failed.");
597 0 : ret = false;
598 : }
599 6 : const int nChunkSize = atoi(pszChunkSize);
600 6 : const uint64_t nCompressedSize = std::strtoull(
601 : aosMD.FetchNameValue("COMPRESSED_SIZE"), nullptr, 10);
602 6 : const uint64_t nUncompressedSize = std::strtoull(
603 : aosMD.FetchNameValue("UNCOMPRESSED_SIZE"), nullptr, 10);
604 12 : if (nChunkSize == 0 || // cannot happen
605 6 : (nUncompressedSize - 1) / nChunkSize >
606 6 : static_cast<uint64_t>(std::numeric_limits<int>::max()))
607 : {
608 0 : ReportError(
609 : CE_Failure, CPLE_AppDefined,
610 : "* File %s has a SOZip index, but (nUncompressedSize - "
611 : "1) / nChunkSize > INT_MAX !",
612 0 : psEntry->pszName);
613 0 : ret = false;
614 0 : continue;
615 : }
616 :
617 6 : int nChunksItems =
618 6 : static_cast<int>((nUncompressedSize - 1) / nChunkSize);
619 :
620 6 : if (bVerbose)
621 : {
622 2 : Output(CPLSPrintf(" %s: checking index offset values...\n",
623 1 : psEntry->pszName));
624 : }
625 :
626 12 : std::vector<uint64_t> anOffsets;
627 : try
628 : {
629 6 : anOffsets.reserve(nChunksItems);
630 : }
631 0 : catch (const std::exception &)
632 : {
633 0 : nChunksItems = 0;
634 0 : ReportError(CE_Failure, CPLE_AppDefined,
635 : "Cannot allocate memory for chunk offsets.");
636 0 : ret = false;
637 : }
638 :
639 151 : for (int i = 0; i < nChunksItems; ++i)
640 : {
641 145 : uint64_t nOffset64 = 0;
642 145 : if (VSIFReadL(&nOffset64, sizeof(nOffset64), 1, fpRaw) != 1)
643 : {
644 0 : ReportError(CE_Failure, CPLE_AppDefined,
645 : "VSIFReadL() failed.");
646 0 : ret = false;
647 : }
648 145 : CPL_LSBPTR64(&nOffset64);
649 145 : if (nOffset64 >= nCompressedSize)
650 : {
651 0 : bSeekOptimizedValid = false;
652 0 : ReportError(
653 : CE_Failure, CPLE_AppDefined,
654 : "Error: file %s, offset[%d] (= " CPL_FRMT_GUIB
655 : ") >= compressed_size is invalid.",
656 0 : psEntry->pszName, i,
657 : static_cast<GUIntBig>(nOffset64));
658 : }
659 145 : if (!anOffsets.empty())
660 : {
661 139 : const auto nPrevOffset = anOffsets.back();
662 139 : if (nOffset64 <= nPrevOffset)
663 : {
664 0 : bSeekOptimizedValid = false;
665 0 : ReportError(
666 : CE_Failure, CPLE_AppDefined,
667 : "Error: file %s, offset[%d] (= " CPL_FRMT_GUIB
668 : ") <= offset[%d] (= " CPL_FRMT_GUIB ")",
669 0 : psEntry->pszName, i + 1,
670 : static_cast<GUIntBig>(nOffset64), i,
671 : static_cast<GUIntBig>(nPrevOffset));
672 : }
673 : }
674 6 : else if (nOffset64 < 9)
675 : {
676 0 : bSeekOptimizedValid = false;
677 0 : ReportError(
678 : CE_Failure, CPLE_AppDefined,
679 : "Error: file %s, offset[0] (= " CPL_FRMT_GUIB
680 : ") is invalid.",
681 0 : psEntry->pszName, static_cast<GUIntBig>(nOffset64));
682 : }
683 145 : anOffsets.push_back(nOffset64);
684 : }
685 :
686 6 : if (bVerbose)
687 : {
688 2 : Output(CPLSPrintf(" %s: checking if chunks can be "
689 : "independently decompressed...\n",
690 1 : psEntry->pszName));
691 : }
692 :
693 : const char *pszStartDataOffset =
694 6 : aosMD.FetchNameValue("START_DATA_OFFSET");
695 : const vsi_l_offset nStartOffset =
696 6 : std::strtoull(pszStartDataOffset, nullptr, 10);
697 6 : VSILFILE *fp = VSIFOpenL(osFilenameInZip.c_str(), "rb");
698 6 : if (!fp)
699 : {
700 0 : bSeekOptimizedValid = false;
701 0 : ReportError(CE_Failure, CPLE_AppDefined,
702 : "Error: cannot open %s",
703 : osFilenameInZip.c_str());
704 : }
705 12 : std::vector<GByte> abyData;
706 : try
707 : {
708 6 : abyData.resize(nChunkSize);
709 : }
710 0 : catch (const std::exception &)
711 : {
712 0 : ReportError(CE_Failure, CPLE_AppDefined,
713 : "Cannot allocate memory for chunk data.");
714 0 : ret = false;
715 : }
716 151 : for (int i = 0; fp != nullptr && i < nChunksItems; ++i)
717 : {
718 145 : if (VSIFSeekL(fpRaw, nStartOffset + anOffsets[i] - 9,
719 145 : SEEK_SET) != 0)
720 : {
721 0 : ReportError(CE_Failure, CPLE_AppDefined,
722 : "VSIFSeekL() failed.");
723 0 : ret = false;
724 : }
725 145 : GByte abyEnd[9] = {0};
726 145 : if (VSIFReadL(abyEnd, 9, 1, fpRaw) != 1)
727 : {
728 0 : ReportError(CE_Failure, CPLE_AppDefined,
729 : "VSIFReadL() failed.");
730 0 : ret = false;
731 : }
732 145 : if (memcmp(abyEnd, "\x00\x00\xFF\xFF\x00\x00\x00\xFF\xFF",
733 : 9) != 0)
734 : {
735 0 : bSeekOptimizedValid = false;
736 0 : ReportError(
737 : CE_Failure, CPLE_AppDefined,
738 : "Error: file %s, chunk[%d] is not terminated by "
739 : "\\x00\\x00\\xFF\\xFF\\x00\\x00\\x00\\xFF\\xFF.",
740 0 : psEntry->pszName, i);
741 : }
742 145 : if (!abyData.empty())
743 : {
744 290 : if (VSIFSeekL(fp,
745 145 : static_cast<vsi_l_offset>(i) * nChunkSize,
746 145 : SEEK_SET) != 0)
747 : {
748 0 : ReportError(CE_Failure, CPLE_AppDefined,
749 : "VSIFSeekL() failed.");
750 0 : ret = false;
751 : }
752 : const size_t nRead =
753 145 : VSIFReadL(&abyData[0], 1, nChunkSize, fp);
754 145 : if (nRead != static_cast<size_t>(nChunkSize))
755 : {
756 0 : bSeekOptimizedValid = false;
757 0 : ReportError(
758 : CE_Failure, CPLE_AppDefined,
759 : "Error: file %s, chunk[%d] cannot be fully "
760 : "read.",
761 0 : psEntry->pszName, i);
762 : }
763 : }
764 : }
765 :
766 6 : if (fp)
767 : {
768 12 : if (VSIFSeekL(fp,
769 6 : static_cast<vsi_l_offset>(nChunksItems) *
770 6 : nChunkSize,
771 6 : SEEK_SET) != 0)
772 : {
773 0 : ReportError(CE_Failure, CPLE_AppDefined,
774 : "VSIFSeekL() failed.");
775 0 : ret = false;
776 : }
777 : const size_t nRead =
778 6 : VSIFReadL(&abyData[0], 1, nChunkSize, fp);
779 6 : if (nRead != static_cast<size_t>(
780 6 : nUncompressedSize -
781 6 : static_cast<vsi_l_offset>(nChunksItems) *
782 6 : nChunkSize))
783 : {
784 0 : bSeekOptimizedValid = false;
785 0 : ReportError(
786 : CE_Failure, CPLE_AppDefined,
787 : "Error: file %s, chunk[%d] cannot be fully read.",
788 0 : psEntry->pszName, nChunksItems);
789 : }
790 :
791 6 : VSIFCloseL(fp);
792 : }
793 :
794 6 : VSIFCloseL(fpRaw);
795 : }
796 :
797 54 : if (bSeekOptimizedValid)
798 : {
799 12 : Output(CPLSPrintf(
800 : "* File %s has a valid SOZip index, using chunk_size = "
801 : "%s.\n",
802 6 : psEntry->pszName, pszChunkSize));
803 6 : nCountValidSOZIP++;
804 : }
805 48 : else if (bSeekOptimizedFound)
806 : {
807 0 : ReportError(CE_Failure, CPLE_AppDefined,
808 : "* File %s has a SOZip index, but is is invalid!",
809 0 : psEntry->pszName);
810 0 : ret = false;
811 : }
812 : }
813 58 : }
814 :
815 7 : if (ret)
816 : {
817 7 : if (nCountValidSOZIP > 0)
818 : {
819 5 : Output("-----\n");
820 5 : Output(CPLSPrintf(
821 : "%s is a valid .zip file, and contains %d SOZip-enabled "
822 : "file(s).\n",
823 : m_zipFilename.c_str(), nCountValidSOZIP));
824 : }
825 : else
826 2 : Output(
827 : CPLSPrintf("%s is a valid .zip file, but does not contain any "
828 : "SOZip-enabled files.\n",
829 : m_zipFilename.c_str()));
830 : }
831 : else
832 : {
833 0 : ReportError(CE_Failure, CPLE_AppDefined,
834 : "%s is not a valid SOZip file!", m_zipFilename.c_str());
835 : }
836 7 : return ret;
837 : }
838 :
839 : /************************************************************************/
840 : /* GDALVSISOZIPAlgorithm::GDALVSISOZIPAlgorithm() */
841 : /************************************************************************/
842 :
843 75 : GDALVSISOZIPAlgorithm::GDALVSISOZIPAlgorithm()
844 75 : : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
845 : {
846 75 : RegisterSubAlgorithm<GDALVSISOZIPCreateAlgorithm>();
847 75 : RegisterSubAlgorithm<GDALVSISOZIPOptimizeAlgorithm>();
848 75 : RegisterSubAlgorithm<GDALVSISOZIPListAlgorithm>();
849 75 : RegisterSubAlgorithm<GDALVSISOZIPValidateAlgorithm>();
850 75 : }
851 :
852 : /************************************************************************/
853 : /* GDALVSISOZIPAlgorithm::RunImpl() */
854 : /************************************************************************/
855 :
856 1 : bool GDALVSISOZIPAlgorithm::RunImpl(GDALProgressFunc, void *)
857 : {
858 1 : CPLError(CE_Failure, CPLE_AppDefined,
859 : "The Run() method should not be called directly on the \"gdal "
860 : "sozip\" program.");
861 1 : return false;
862 : }
863 :
864 : //! @endcond
|