Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: Zarr driver. Virtual file system for
5 : * https://fsspec.github.io/kerchunk/spec.html#version-1
6 : * Author: Even Rouault <even dot rouault at spatialys.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #undef _REENTRANT
15 :
16 : #include "vsikerchunk.h"
17 : #include "vsikerchunk_inline.hpp"
18 :
19 : #include "cpl_conv.h"
20 : #include "cpl_json.h"
21 : #include "cpl_json_streaming_parser.h"
22 : #include "cpl_json_streaming_writer.h"
23 : #include "cpl_mem_cache.h"
24 : #include "cpl_multiproc.h" // CPLSleep()
25 : #include "cpl_vsi_error.h"
26 : #include "cpl_vsi_virtual.h"
27 :
28 : #include "gdal_priv.h"
29 : #include "ogrsf_frmts.h"
30 :
31 : #include <cerrno>
32 : #include <cinttypes>
33 : #include <limits>
34 : #include <mutex>
35 : #include <set>
36 : #include <utility>
37 :
38 : /************************************************************************/
39 : /* VSIKerchunkKeyInfo */
40 : /************************************************************************/
41 :
42 : struct VSIKerchunkKeyInfo
43 : {
44 : // points to an element in VSIKerchunkRefFile::m_oSetURI
45 : const std::string *posURI = nullptr;
46 :
47 : uint64_t nOffset = 0;
48 : uint32_t nSize = 0;
49 : std::vector<GByte> abyValue{};
50 : };
51 :
52 : /************************************************************************/
53 : /* VSIKerchunkRefFile */
54 : /************************************************************************/
55 :
56 : class VSIKerchunkRefFile
57 : {
58 : private:
59 : std::set<std::string> m_oSetURI{};
60 : std::map<std::string, VSIKerchunkKeyInfo> m_oMapKeys{};
61 :
62 : public:
63 578 : const std::map<std::string, VSIKerchunkKeyInfo> &GetMapKeys() const
64 : {
65 578 : return m_oMapKeys;
66 : }
67 :
68 125 : void AddInlineContent(const std::string &key, std::vector<GByte> &&abyValue)
69 : {
70 250 : VSIKerchunkKeyInfo info;
71 125 : info.abyValue = std::move(abyValue);
72 125 : m_oMapKeys[key] = std::move(info);
73 125 : }
74 :
75 128 : bool AddInlineContent(const std::string &key, const std::string_view &str)
76 : {
77 256 : std::vector<GByte> abyValue;
78 128 : if (cpl::starts_with(str, "base64:"))
79 : {
80 : abyValue.insert(
81 0 : abyValue.end(),
82 4 : reinterpret_cast<const GByte *>(str.data()) + strlen("base64:"),
83 4 : reinterpret_cast<const GByte *>(str.data()) + str.size());
84 4 : abyValue.push_back(0);
85 4 : const int nSize = CPLBase64DecodeInPlace(abyValue.data());
86 4 : if (nSize == 0)
87 : {
88 3 : CPLError(CE_Failure, CPLE_AppDefined,
89 : "VSIKerchunkJSONRefFileSystem: Base64 decoding "
90 : "failed for key '%s'",
91 : key.c_str());
92 3 : return false;
93 : }
94 1 : abyValue.resize(nSize);
95 : }
96 : else
97 : {
98 : abyValue.insert(
99 124 : abyValue.end(), reinterpret_cast<const GByte *>(str.data()),
100 248 : reinterpret_cast<const GByte *>(str.data()) + str.size());
101 : }
102 :
103 125 : AddInlineContent(key, std::move(abyValue));
104 125 : return true;
105 : }
106 :
107 20 : void AddReferencedContent(const std::string &key, const std::string &osURI,
108 : uint64_t nOffset, uint32_t nSize)
109 : {
110 20 : auto oPair = m_oSetURI.insert(osURI);
111 :
112 40 : VSIKerchunkKeyInfo info;
113 20 : info.posURI = &(*(oPair.first));
114 20 : info.nOffset = nOffset;
115 20 : info.nSize = nSize;
116 20 : m_oMapKeys[key] = std::move(info);
117 20 : }
118 :
119 : bool ConvertToParquetRef(const std::string &osCacheDir,
120 : GDALProgressFunc pfnProgress, void *pProgressData);
121 : };
122 :
123 : /************************************************************************/
124 : /* VSIKerchunkJSONRefFileSystem */
125 : /************************************************************************/
126 :
127 : class VSIKerchunkJSONRefFileSystem final : public VSIFilesystemHandler
128 : {
129 : public:
130 1755 : VSIKerchunkJSONRefFileSystem()
131 1755 : {
132 1755 : IsFileSystemInstantiated() = true;
133 1755 : }
134 :
135 2246 : ~VSIKerchunkJSONRefFileSystem() override
136 1123 : {
137 1123 : IsFileSystemInstantiated() = false;
138 2246 : }
139 :
140 4633 : static bool &IsFileSystemInstantiated()
141 : {
142 : static bool bIsFileSystemInstantiated = false;
143 4633 : return bIsFileSystemInstantiated;
144 : }
145 :
146 : VSIVirtualHandleUniquePtr Open(const char *pszFilename,
147 : const char *pszAccess, bool bSetError,
148 : CSLConstList papszOptions) override;
149 :
150 : int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
151 : int nFlags) override;
152 :
153 : char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
154 :
155 : char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
156 : CSLConstList papszOptions) override;
157 :
158 : private:
159 : friend bool VSIKerchunkConvertJSONToParquet(const char *pszSrcJSONFilename,
160 : const char *pszDstDirname,
161 : GDALProgressFunc pfnProgress,
162 : void *pProgressData);
163 :
164 : lru11::Cache<std::string, std::shared_ptr<VSIKerchunkRefFile>, std::mutex>
165 : m_oCache{};
166 :
167 : static std::pair<std::string, std::string>
168 : SplitFilename(const char *pszFilename);
169 :
170 : std::pair<std::shared_ptr<VSIKerchunkRefFile>, std::string>
171 : Load(const std::string &osJSONFilename, bool bUseCache);
172 : std::shared_ptr<VSIKerchunkRefFile>
173 : LoadInternal(const std::string &osJSONFilename,
174 : GDALProgressFunc pfnProgress, void *pProgressData);
175 : std::shared_ptr<VSIKerchunkRefFile>
176 : LoadStreaming(const std::string &osJSONFilename,
177 : GDALProgressFunc pfnProgress, void *pProgressData);
178 : };
179 :
180 : /************************************************************************/
181 : /* VSIKerchunkJSONRefFileSystem::SplitFilename() */
182 : /************************************************************************/
183 :
184 : /*static*/
185 : std::pair<std::string, std::string>
186 375 : VSIKerchunkJSONRefFileSystem::SplitFilename(const char *pszFilename)
187 : {
188 375 : if (STARTS_WITH(pszFilename, JSON_REF_FS_PREFIX))
189 319 : pszFilename += strlen(JSON_REF_FS_PREFIX);
190 56 : else if (STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX))
191 56 : pszFilename += strlen(JSON_REF_CACHED_FS_PREFIX);
192 : else
193 0 : return {std::string(), std::string()};
194 :
195 750 : std::string osJSONFilename;
196 :
197 375 : if (*pszFilename == '{')
198 : {
199 : // Parse /vsikerchunk_json_ref/{/path/to/some.json}[key]
200 345 : int nLevel = 1;
201 345 : ++pszFilename;
202 23875 : for (; *pszFilename; ++pszFilename)
203 : {
204 23863 : if (*pszFilename == '{')
205 : {
206 0 : ++nLevel;
207 : }
208 23863 : else if (*pszFilename == '}')
209 : {
210 333 : --nLevel;
211 333 : if (nLevel == 0)
212 : {
213 333 : ++pszFilename;
214 333 : break;
215 : }
216 : }
217 23530 : osJSONFilename += *pszFilename;
218 : }
219 345 : if (nLevel != 0)
220 : {
221 12 : CPLError(CE_Failure, CPLE_AppDefined,
222 : "Invalid %s syntax: should be "
223 : "%s{/path/to/some/file}[/optional_key]",
224 : JSON_REF_FS_PREFIX, JSON_REF_FS_PREFIX);
225 12 : return {std::string(), std::string()};
226 : }
227 :
228 : return {osJSONFilename,
229 666 : *pszFilename == '/' ? pszFilename + 1 : pszFilename};
230 : }
231 : else
232 : {
233 30 : int nCountDotJson = 0;
234 30 : const char *pszIter = pszFilename;
235 30 : const char *pszAfterJSON = nullptr;
236 48 : while ((pszIter = strstr(pszIter, ".json")) != nullptr)
237 : {
238 18 : ++nCountDotJson;
239 18 : if (nCountDotJson == 1)
240 18 : pszAfterJSON = pszIter + strlen(".json");
241 : else
242 0 : pszAfterJSON = nullptr;
243 18 : pszIter += strlen(".json");
244 : }
245 30 : if (!pszAfterJSON)
246 : {
247 12 : if (nCountDotJson >= 2)
248 : {
249 0 : CPLError(CE_Failure, CPLE_AppDefined,
250 : "Ambiguous %s syntax: should be "
251 : "%s{/path/to/some/file}[/optional_key]",
252 : JSON_REF_FS_PREFIX, JSON_REF_FS_PREFIX);
253 : }
254 : else
255 : {
256 12 : CPLError(CE_Failure, CPLE_AppDefined,
257 : "Invalid %s syntax: should be "
258 : "%s/path/to/some.json[/optional_key] or "
259 : "%s{/path/to/some/file}[/optional_key]",
260 : JSON_REF_FS_PREFIX, JSON_REF_FS_PREFIX,
261 : JSON_REF_FS_PREFIX);
262 : }
263 12 : return {std::string(), std::string()};
264 : }
265 36 : return {std::string(pszFilename, pszAfterJSON - pszFilename),
266 36 : *pszAfterJSON == '/' ? pszAfterJSON + 1 : pszAfterJSON};
267 : }
268 : }
269 :
270 : /************************************************************************/
271 : /* class VSIKerchunkJSONRefParser */
272 : /************************************************************************/
273 :
274 : namespace
275 : {
276 : class VSIKerchunkJSONRefParser final : public CPLJSonStreamingParser
277 : {
278 : public:
279 71 : explicit VSIKerchunkJSONRefParser(
280 : const std::shared_ptr<VSIKerchunkRefFile> &refFile)
281 71 : : m_refFile(refFile)
282 : {
283 71 : m_oWriter.SetPrettyFormatting(false);
284 71 : }
285 :
286 71 : ~VSIKerchunkJSONRefParser() override
287 71 : {
288 : // In case the parsing would be stopped, the writer may be in
289 : // an inconsistent state. This avoids assertion in debug mode.
290 71 : m_oWriter.clear();
291 71 : }
292 :
293 : protected:
294 78 : void String(std::string_view sValue) override
295 : {
296 78 : if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 0)
297 : {
298 9 : size_t nLength = sValue.size();
299 9 : if (nLength > 0 && sValue[nLength - 1] == 0)
300 2 : --nLength;
301 :
302 9 : if (!m_refFile->AddInlineContent(
303 9 : m_osCurKey, std::string_view(sValue.data(), nLength)))
304 : {
305 2 : StopParsing();
306 : }
307 :
308 9 : m_oWriter.clear();
309 :
310 9 : m_osCurKey.clear();
311 : }
312 69 : else if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 1)
313 : {
314 42 : if (m_iArrayMemberIdx == 0)
315 : {
316 36 : m_osURI = sValue;
317 : }
318 : else
319 : {
320 6 : UnexpectedContentInArray();
321 : }
322 : }
323 27 : else if (m_nLevel > m_nKeyLevel)
324 : {
325 25 : m_oWriter.Add(sValue);
326 : }
327 78 : }
328 :
329 161 : void Number(std::string_view sValue) override
330 : {
331 161 : if (m_nLevel == m_nKeyLevel)
332 : {
333 52 : if (m_nArrayLevel == 1)
334 : {
335 48 : if (m_iArrayMemberIdx == 1)
336 : {
337 27 : m_osTmpForNumber = sValue;
338 27 : errno = 0;
339 27 : m_nOffset =
340 27 : std::strtoull(m_osTmpForNumber.c_str(), nullptr, 10);
341 50 : if (errno != 0 || m_osTmpForNumber[0] == '-' ||
342 23 : m_osTmpForNumber.find('.') != std::string::npos)
343 : {
344 6 : CPLError(
345 : CE_Failure, CPLE_AppDefined,
346 : "VSIKerchunkJSONRefFileSystem: array value at "
347 : "index 1 for key '%s' is not an unsigned 64 bit "
348 : "integer",
349 : m_osCurKey.c_str());
350 6 : StopParsing();
351 : }
352 : }
353 21 : else if (m_iArrayMemberIdx == 2)
354 : {
355 19 : m_osTmpForNumber = sValue;
356 19 : errno = 0;
357 : const uint64_t nSize =
358 19 : std::strtoull(m_osTmpForNumber.c_str(), nullptr, 10);
359 19 : if (errno != 0 || m_osTmpForNumber[0] == '-' ||
360 53 : nSize > std::numeric_limits<uint32_t>::max() ||
361 15 : m_osTmpForNumber.find('.') != std::string::npos)
362 : {
363 6 : CPLError(
364 : CE_Failure, CPLE_AppDefined,
365 : "VSIKerchunkJSONRefFileSystem: array value at "
366 : "index 2 for key '%s' is not an unsigned 32 bit "
367 : "integer",
368 : m_osCurKey.c_str());
369 6 : StopParsing();
370 : }
371 : else
372 : {
373 13 : m_nSize = static_cast<uint32_t>(nSize);
374 : }
375 : }
376 : else
377 : {
378 2 : UnexpectedContentInArray();
379 : }
380 : }
381 : else
382 : {
383 4 : UnexpectedContent();
384 : }
385 : }
386 109 : else if (m_nLevel > m_nKeyLevel)
387 : {
388 108 : m_oWriter.AddSerializedValue(sValue);
389 : }
390 161 : }
391 :
392 4 : void Boolean(bool b) override
393 : {
394 4 : if (m_nLevel == m_nKeyLevel)
395 : {
396 4 : UnexpectedContent();
397 : }
398 0 : else if (m_nLevel > m_nKeyLevel)
399 : {
400 0 : m_oWriter.Add(b);
401 : }
402 4 : }
403 :
404 28 : void Null() override
405 : {
406 28 : if (m_nLevel == m_nKeyLevel)
407 : {
408 4 : UnexpectedContent();
409 : }
410 24 : else if (m_nLevel > m_nKeyLevel)
411 : {
412 24 : m_oWriter.AddNull();
413 : }
414 28 : }
415 :
416 168 : void StartObject() override
417 : {
418 168 : if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 1)
419 : {
420 2 : UnexpectedContentInArray();
421 : }
422 : else
423 : {
424 166 : if (m_nLevel >= m_nKeyLevel)
425 : {
426 95 : m_oWriter.StartObj();
427 : }
428 166 : ++m_nLevel;
429 166 : m_bFirstMember = true;
430 : }
431 168 : }
432 :
433 124 : void EndObject() override
434 : {
435 124 : if (m_nLevel == m_nKeyLevel)
436 : {
437 28 : FinishObjectValueProcessing();
438 : }
439 124 : --m_nLevel;
440 124 : if (m_nLevel >= m_nKeyLevel)
441 : {
442 95 : m_oWriter.EndObj();
443 : }
444 124 : }
445 :
446 330 : void StartObjectMember(std::string_view sKey) override
447 : {
448 330 : if (m_nLevel == 1 && m_bFirstMember)
449 : {
450 81 : if (sKey == "version")
451 : {
452 3 : m_nKeyLevel = 2;
453 : }
454 : else
455 : {
456 78 : m_nKeyLevel = 1;
457 : }
458 : }
459 249 : else if (m_nLevel == 1 && m_nKeyLevel == 2 && sKey == "templates")
460 : {
461 1 : CPLError(CE_Failure, CPLE_NotSupported,
462 : "VSIKerchunkJSONRefFileSystem: 'templates' key found, but "
463 : "not supported");
464 1 : StopParsing();
465 : }
466 248 : else if (m_nLevel == 1 && m_nKeyLevel == 2 && sKey == "gen")
467 : {
468 1 : CPLError(CE_Failure, CPLE_NotSupported,
469 : "VSIKerchunkJSONRefFileSystem: 'gen' key found, but not "
470 : "supported");
471 1 : StopParsing();
472 : }
473 :
474 330 : if (m_nLevel == m_nKeyLevel)
475 : {
476 158 : FinishObjectValueProcessing();
477 158 : m_osCurKey = sKey;
478 : }
479 172 : else if (m_nLevel > m_nKeyLevel)
480 : {
481 164 : m_oWriter.AddObjKey(sKey);
482 : }
483 330 : m_bFirstMember = false;
484 330 : }
485 :
486 83 : void StartArray() override
487 : {
488 83 : if (m_nLevel == m_nKeyLevel)
489 : {
490 46 : if (m_nArrayLevel == 0)
491 : {
492 46 : m_iArrayMemberIdx = -1;
493 46 : m_osURI.clear();
494 46 : m_nOffset = 0;
495 46 : m_nSize = 0;
496 46 : m_nArrayLevel = 1;
497 : }
498 : else
499 : {
500 0 : UnexpectedContentInArray();
501 : }
502 : }
503 37 : else if (m_nLevel > m_nKeyLevel)
504 : {
505 37 : m_oWriter.StartArray();
506 37 : ++m_nArrayLevel;
507 : }
508 83 : }
509 :
510 57 : void EndArray() override
511 : {
512 57 : if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 1)
513 : {
514 20 : if (m_iArrayMemberIdx == -1)
515 : {
516 2 : CPLError(CE_Failure, CPLE_AppDefined,
517 : "VSIKerchunkJSONRefFileSystem: array value for key "
518 : "'%s' is not of size 1 or 3",
519 : m_osCurKey.c_str());
520 2 : StopParsing();
521 : }
522 : else
523 : {
524 18 : m_refFile->AddReferencedContent(m_osCurKey, m_osURI, m_nOffset,
525 : m_nSize);
526 18 : --m_nArrayLevel;
527 18 : m_oWriter.clear();
528 18 : m_osCurKey.clear();
529 : }
530 : }
531 37 : else if (m_nLevel >= m_nKeyLevel)
532 : {
533 37 : --m_nArrayLevel;
534 37 : if (m_nLevel > m_nKeyLevel)
535 37 : m_oWriter.EndArray();
536 : }
537 57 : }
538 :
539 126 : void StartArrayMember() override
540 : {
541 126 : if (m_nLevel >= m_nKeyLevel)
542 126 : ++m_iArrayMemberIdx;
543 126 : }
544 :
545 2 : void Exception(const char *pszMessage) override
546 : {
547 2 : CPLError(CE_Failure, CPLE_AppDefined, "%s", pszMessage);
548 2 : }
549 :
550 : private:
551 : std::shared_ptr<VSIKerchunkRefFile> m_refFile{};
552 : int m_nLevel = 0;
553 : int m_nArrayLevel = 0;
554 : int m_iArrayMemberIdx = -1;
555 : bool m_bFirstMember = false;
556 : int m_nKeyLevel = std::numeric_limits<int>::max();
557 : std::string m_osCurKey{};
558 : std::string m_osURI{};
559 : std::string m_osTmpForNumber{};
560 : uint64_t m_nOffset = 0;
561 : uint32_t m_nSize = 0;
562 :
563 : CPLJSonStreamingWriter m_oWriter{nullptr, nullptr};
564 :
565 186 : void FinishObjectValueProcessing()
566 : {
567 186 : if (!m_osCurKey.empty())
568 : {
569 93 : const std::string &osStr = m_oWriter.GetString();
570 93 : CPL_IGNORE_RET_VAL(m_refFile->AddInlineContent(m_osCurKey, osStr));
571 :
572 93 : m_oWriter.clear();
573 :
574 93 : m_osCurKey.clear();
575 : }
576 186 : }
577 :
578 12 : void UnexpectedContent()
579 : {
580 12 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected content");
581 12 : StopParsing();
582 12 : }
583 :
584 10 : void UnexpectedContentInArray()
585 : {
586 10 : CPLError(CE_Failure, CPLE_AppDefined,
587 : "Unexpected content at position %d of array",
588 : m_iArrayMemberIdx);
589 10 : StopParsing();
590 10 : }
591 : };
592 : } // namespace
593 :
594 : /************************************************************************/
595 : /* VSIKerchunkJSONRefFileSystem::LoadStreaming() */
596 : /************************************************************************/
597 :
598 : std::shared_ptr<VSIKerchunkRefFile>
599 71 : VSIKerchunkJSONRefFileSystem::LoadStreaming(const std::string &osJSONFilename,
600 : GDALProgressFunc pfnProgress,
601 : void *pProgressData)
602 : {
603 142 : auto refFile = std::make_shared<VSIKerchunkRefFile>();
604 142 : VSIKerchunkJSONRefParser parser(refFile);
605 :
606 71 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem",
607 : "Using streaming parser for %s", osJSONFilename.c_str());
608 :
609 : // For network file systems, get the streaming version of the filename,
610 : // as we don't need arbitrary seeking in the file
611 : const std::string osFilename =
612 71 : VSIFileManager::GetHandler(osJSONFilename.c_str())
613 142 : ->GetStreamingFilename(osJSONFilename);
614 :
615 142 : auto f = VSIVirtualHandleUniquePtr(VSIFOpenL(osFilename.c_str(), "rb"));
616 71 : if (!f)
617 : {
618 2 : CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
619 : osJSONFilename.c_str());
620 2 : return nullptr;
621 : }
622 69 : uint64_t nTotalSize = 0;
623 69 : if (!cpl::starts_with(osFilename, "/vsigzip/"))
624 : {
625 69 : f->Seek(0, SEEK_END);
626 69 : nTotalSize = f->Tell();
627 69 : f->Seek(0, SEEK_SET);
628 : }
629 138 : std::string sBuffer;
630 69 : constexpr size_t BUFFER_SIZE = 10 * 1024 * 1024; // Arbitrary
631 69 : sBuffer.resize(BUFFER_SIZE);
632 : while (true)
633 : {
634 70 : const size_t nRead = f->Read(sBuffer.data(), 1, sBuffer.size());
635 70 : const bool bFinished = nRead < sBuffer.size();
636 : try
637 : {
638 70 : if (!parser.Parse(std::string_view(sBuffer.data(), nRead),
639 : bFinished))
640 : {
641 : // The parser will have emitted an error
642 42 : return nullptr;
643 : }
644 : }
645 0 : catch (const std::exception &e)
646 : {
647 : // Out-of-memory typically
648 0 : CPLError(CE_Failure, CPLE_AppDefined,
649 : "Exception occurred while parsing %s: %s",
650 0 : osJSONFilename.c_str(), e.what());
651 0 : return nullptr;
652 : }
653 28 : if (nTotalSize)
654 : {
655 27 : const double dfProgressRatio = static_cast<double>(f->Tell()) /
656 27 : static_cast<double>(nTotalSize);
657 27 : CPLDebug("VSIKerchunkJSONRefFileSystem", "%02.1f %% of %s read",
658 : 100 * dfProgressRatio, osJSONFilename.c_str());
659 28 : if (pfnProgress &&
660 1 : !pfnProgress(dfProgressRatio, "Parsing of JSON file",
661 : pProgressData))
662 : {
663 0 : return nullptr;
664 : }
665 : }
666 : else
667 : {
668 1 : CPLDebug("VSIKerchunkJSONRefFileSystem",
669 : "%" PRIu64 " bytes read in %s",
670 1 : static_cast<uint64_t>(f->Tell()), osJSONFilename.c_str());
671 : }
672 28 : if (nRead < sBuffer.size())
673 : {
674 27 : break;
675 : }
676 1 : }
677 27 : if (f->Tell() == 0)
678 : {
679 1 : CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
680 : osJSONFilename.c_str());
681 1 : return nullptr;
682 : }
683 26 : if (pfnProgress)
684 1 : pfnProgress(1.0, "Parsing of JSON file", pProgressData);
685 :
686 26 : return refFile;
687 : }
688 :
689 : /************************************************************************/
690 : /* VSIKerchunkJSONRefFileSystem::LoadInternal() */
691 : /************************************************************************/
692 :
693 : std::shared_ptr<VSIKerchunkRefFile>
694 109 : VSIKerchunkJSONRefFileSystem::LoadInternal(const std::string &osJSONFilename,
695 : GDALProgressFunc pfnProgress,
696 : void *pProgressData)
697 : {
698 109 : const char *pszUseStreamingParser = VSIGetPathSpecificOption(
699 : osJSONFilename.c_str(), "VSIKERCHUNK_USE_STREAMING_PARSER", "AUTO");
700 109 : if (EQUAL(pszUseStreamingParser, "AUTO"))
701 : {
702 : auto f =
703 50 : VSIVirtualHandleUniquePtr(VSIFOpenL(osJSONFilename.c_str(), "rb"));
704 50 : if (!f)
705 : {
706 6 : CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
707 : osJSONFilename.c_str());
708 6 : return nullptr;
709 : }
710 44 : std::string sBuffer;
711 44 : constexpr size_t HEADER_SIZE = 1024; // Arbitrary
712 44 : sBuffer.resize(HEADER_SIZE);
713 44 : const size_t nRead = f->Read(sBuffer.data(), 1, sBuffer.size());
714 44 : sBuffer.resize(nRead);
715 44 : if (ZARRIsLikelyStreamableKerchunkJSONRefContent(sBuffer))
716 : {
717 41 : return LoadStreaming(osJSONFilename, pfnProgress, pProgressData);
718 : }
719 : }
720 59 : else if (CPLTestBool(pszUseStreamingParser))
721 : {
722 30 : return LoadStreaming(osJSONFilename, pfnProgress, pProgressData);
723 : }
724 :
725 64 : CPLJSONDocument oDoc;
726 : {
727 : #if SIZEOF_VOIDP > 4
728 32 : CPLConfigOptionSetter oSetter("CPL_JSON_MAX_SIZE", "1GB", true);
729 : #endif
730 32 : if (!oDoc.Load(osJSONFilename))
731 : {
732 5 : CPLError(CE_Failure, CPLE_AppDefined,
733 : "VSIKerchunkJSONRefFileSystem: cannot open %s",
734 : osJSONFilename.c_str());
735 5 : return nullptr;
736 : }
737 : }
738 :
739 54 : auto oRoot = oDoc.GetRoot();
740 81 : const auto oVersion = oRoot.GetObj("version");
741 54 : CPLJSONObject oRefs;
742 27 : if (!oVersion.IsValid())
743 : {
744 : // Cf https://fsspec.github.io/kerchunk/spec.html#version-0
745 :
746 23 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem",
747 : "'version' key not found. Assuming version 0");
748 23 : oRefs = std::move(oRoot);
749 23 : if (!oRefs.GetObj(".zgroup").IsValid())
750 : {
751 0 : CPLError(CE_Failure, CPLE_AppDefined,
752 : "VSIKerchunkJSONRefFileSystem: '.zgroup' key not found");
753 0 : return nullptr;
754 : }
755 : }
756 4 : else if (oVersion.GetType() != CPLJSONObject::Type::Integer)
757 : {
758 4 : CPLError(CE_Failure, CPLE_AppDefined,
759 : "VSIKerchunkJSONRefFileSystem: 'version' key not integer");
760 4 : return nullptr;
761 : }
762 0 : else if (oVersion.ToInteger() != 1)
763 : {
764 0 : CPLError(CE_Failure, CPLE_NotSupported,
765 : "VSIKerchunkJSONRefFileSystem: 'version' = %d not handled",
766 : oVersion.ToInteger());
767 0 : return nullptr;
768 : }
769 : else
770 : {
771 : // Cf https://fsspec.github.io/kerchunk/spec.html#version-1
772 :
773 0 : if (oRoot.GetObj("templates").IsValid())
774 : {
775 0 : CPLError(CE_Failure, CPLE_NotSupported,
776 : "VSIKerchunkJSONRefFileSystem: 'templates' key found, but "
777 : "not supported");
778 0 : return nullptr;
779 : }
780 :
781 0 : if (oRoot.GetObj("gen").IsValid())
782 : {
783 0 : CPLError(CE_Failure, CPLE_NotSupported,
784 : "VSIKerchunkJSONRefFileSystem: 'gen' key found, but not "
785 : "supported");
786 0 : return nullptr;
787 : }
788 :
789 0 : oRefs = oRoot.GetObj("refs");
790 0 : if (!oRefs.IsValid())
791 : {
792 0 : CPLError(CE_Failure, CPLE_AppDefined,
793 : "VSIKerchunkJSONRefFileSystem: 'refs' key not found");
794 0 : return nullptr;
795 : }
796 :
797 0 : if (oRoot.GetObj("templates").IsValid())
798 : {
799 0 : CPLError(CE_Failure, CPLE_NotSupported,
800 : "VSIKerchunkJSONRefFileSystem: 'templates' key found but "
801 : "not supported");
802 0 : return nullptr;
803 : }
804 :
805 0 : if (oRoot.GetObj("templates").IsValid())
806 : {
807 0 : CPLError(CE_Failure, CPLE_NotSupported,
808 : "VSIKerchunkJSONRefFileSystem: 'templates' key found but "
809 : "not supported");
810 0 : return nullptr;
811 : }
812 : }
813 :
814 23 : if (oRefs.GetType() != CPLJSONObject::Type::Object)
815 : {
816 0 : CPLError(CE_Failure, CPLE_AppDefined,
817 : "VSIKerchunkJSONRefFileSystem: value of 'refs' is not a dict");
818 0 : return nullptr;
819 : }
820 :
821 46 : auto refFile = std::make_shared<VSIKerchunkRefFile>();
822 50 : for (const auto &oEntry : oRefs.GetChildren())
823 : {
824 45 : const std::string osKeyName = oEntry.GetName();
825 45 : if (oEntry.GetType() == CPLJSONObject::Type::String)
826 : {
827 2 : if (!refFile->AddInlineContent(osKeyName, oEntry.ToString()))
828 : {
829 1 : return nullptr;
830 : }
831 : }
832 43 : else if (oEntry.GetType() == CPLJSONObject::Type::Object)
833 : {
834 : const std::string osSerialized =
835 24 : oEntry.Format(CPLJSONObject::PrettyFormat::Plain);
836 24 : CPL_IGNORE_RET_VAL(
837 24 : refFile->AddInlineContent(osKeyName, osSerialized));
838 : }
839 19 : else if (oEntry.GetType() == CPLJSONObject::Type::Array)
840 : {
841 15 : const auto oArray = oEntry.ToArray();
842 : // Some files such as https://ncsa.osn.xsede.org/Pangeo/pangeo-forge/pangeo-forge/aws-noaa-oisst-feedstock/aws-noaa-oisst-avhrr-only.zarr/reference.json
843 : // (pointed by https://guide.cloudnativegeo.org/kerchunk/kerchunk-in-practice.html)
844 : // have array entries with just the URL, and no offset/size
845 : // This is when the whole file needs to be read
846 15 : if (oArray.Size() != 1 && oArray.Size() != 3)
847 : {
848 3 : CPLError(CE_Failure, CPLE_AppDefined,
849 : "VSIKerchunkJSONRefFileSystem: array value for key "
850 : "'%s' is not of size 1 or 3",
851 : osKeyName.c_str());
852 3 : return nullptr;
853 : }
854 12 : if (oArray[0].GetType() != CPLJSONObject::Type::String)
855 : {
856 4 : CPLError(CE_Failure, CPLE_AppDefined,
857 : "VSIKerchunkJSONRefFileSystem: array value at index 0 "
858 : "for key '%s' is not a string",
859 : osKeyName.c_str());
860 4 : return nullptr;
861 : }
862 8 : if (oArray.Size() == 3)
863 : {
864 8 : if ((oArray[1].GetType() != CPLJSONObject::Type::Integer &&
865 23 : oArray[1].GetType() != CPLJSONObject::Type::Long) ||
866 15 : !(oArray[1].ToLong() >= 0))
867 : {
868 2 : CPLError(
869 : CE_Failure, CPLE_AppDefined,
870 : "VSIKerchunkJSONRefFileSystem: array value at index 1 "
871 : "for key '%s' is not an unsigned 64 bit integer",
872 : osKeyName.c_str());
873 2 : return nullptr;
874 : }
875 6 : if ((oArray[2].GetType() != CPLJSONObject::Type::Integer &&
876 16 : oArray[2].GetType() != CPLJSONObject::Type::Long) ||
877 10 : !(oArray[2].ToLong() >= 0 &&
878 9 : static_cast<uint64_t>(oArray[2].ToLong()) <=
879 3 : std::numeric_limits<uint32_t>::max()))
880 : {
881 4 : CPLError(
882 : CE_Failure, CPLE_AppDefined,
883 : "VSIKerchunkJSONRefFileSystem: array value at index 2 "
884 : "for key '%s' is not an unsigned 32 bit integer",
885 : osKeyName.c_str());
886 4 : return nullptr;
887 : }
888 : }
889 :
890 4 : refFile->AddReferencedContent(
891 4 : osKeyName, oArray[0].ToString(),
892 4 : oArray.Size() == 3 ? oArray[1].ToLong() : 0,
893 4 : oArray.Size() == 3 ? static_cast<uint32_t>(oArray[2].ToLong())
894 : : 0);
895 : }
896 : else
897 : {
898 4 : CPLError(
899 : CE_Failure, CPLE_AppDefined,
900 : "VSIKerchunkJSONRefFileSystem: invalid value type for key '%s'",
901 : osKeyName.c_str());
902 4 : return nullptr;
903 : }
904 : }
905 :
906 5 : return refFile;
907 : }
908 :
909 : /************************************************************************/
910 : /* VSIKerchunkJSONRefFileSystem::Load() */
911 : /************************************************************************/
912 :
913 : std::pair<std::shared_ptr<VSIKerchunkRefFile>, std::string>
914 351 : VSIKerchunkJSONRefFileSystem::Load(const std::string &osJSONFilename,
915 : bool bUseCache)
916 : {
917 351 : std::shared_ptr<VSIKerchunkRefFile> refFile;
918 351 : if (m_oCache.tryGet(osJSONFilename, refFile))
919 227 : return {refFile, std::string()};
920 :
921 : // Deal with local file cache
922 124 : const char *pszUseCache = VSIGetPathSpecificOption(
923 : osJSONFilename.c_str(), "VSIKERCHUNK_USE_CACHE", "NO");
924 124 : if (bUseCache || CPLTestBool(pszUseCache))
925 : {
926 26 : if (GDALGetDriverByName("PARQUET") == nullptr)
927 : {
928 0 : CPLError(CE_Failure, CPLE_NotSupported,
929 : "VSIKERCHUNK_USE_CACHE=YES only enabled if PARQUET driver "
930 : "is available");
931 0 : return {nullptr, std::string()};
932 : }
933 :
934 : VSIStatBufL sStat;
935 52 : if (VSIStatL(osJSONFilename.c_str(), &sStat) != 0 ||
936 26 : VSI_ISDIR(sStat.st_mode))
937 : {
938 0 : CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
939 : osJSONFilename.c_str());
940 0 : return {nullptr, std::string()};
941 : }
942 :
943 26 : std::string osCacheSubDir = CPLGetBasenameSafe(osJSONFilename.c_str());
944 : osCacheSubDir += CPLSPrintf("_%" PRIu64 "_%" PRIu64,
945 26 : static_cast<uint64_t>(sStat.st_size),
946 26 : static_cast<uint64_t>(sStat.st_mtime));
947 :
948 26 : const std::string osRootCacheDir = GDALGetCacheDirectory();
949 26 : if (!osRootCacheDir.empty())
950 : {
951 : const std::string osKerchunkCacheDir = VSIGetPathSpecificOption(
952 : osJSONFilename.c_str(), "VSIKERCHUNK_CACHE_DIR",
953 52 : CPLFormFilenameSafe(osRootCacheDir.c_str(),
954 : "zarr_kerchunk_cache", nullptr)
955 78 : .c_str());
956 : const std::string osCacheDir = CPLFormFilenameSafe(
957 52 : osKerchunkCacheDir.c_str(), osCacheSubDir.c_str(), "zarr");
958 26 : CPLDebug("VSIKerchunkJSONRefFileSystem", "Using cache dir %s",
959 : osCacheDir.c_str());
960 :
961 52 : if (VSIStatL(CPLFormFilenameSafe(osCacheDir.c_str(), ".zmetadata",
962 : nullptr)
963 : .c_str(),
964 26 : &sStat) == 0)
965 : {
966 9 : CPLDebug("VSIKerchunkJSONRefFileSystem",
967 : "Using Kerchunk Parquet cache %s", osCacheDir.c_str());
968 9 : return {nullptr, osCacheDir};
969 : }
970 :
971 23 : if (VSIMkdirRecursive(osCacheDir.c_str(), 0755) != 0 &&
972 6 : !(VSIStatL(osCacheDir.c_str(), &sStat) == 0 &&
973 0 : VSI_ISDIR(sStat.st_mode)))
974 : {
975 6 : CPLError(CE_Failure, CPLE_AppDefined,
976 : "Cannot create directory %s", osCacheDir.c_str());
977 6 : return {nullptr, std::string()};
978 : }
979 :
980 : const std::string osLockFilename =
981 22 : CPLFormFilenameSafe(osCacheDir.c_str(), ".lock", nullptr);
982 :
983 11 : CPLLockFileHandle hLockHandle = nullptr;
984 22 : CPLStringList aosOptions;
985 11 : aosOptions.SetNameValue("VERBOSE_WAIT_MESSAGE", "YES");
986 : const char *pszKerchunkDebug =
987 11 : CPLGetConfigOption("VSIKERCHUNK_FOR_TESTS", nullptr);
988 11 : if (pszKerchunkDebug &&
989 4 : strstr(pszKerchunkDebug, "SHORT_DELAY_STALLED_LOCK"))
990 : {
991 2 : aosOptions.SetNameValue("STALLED_DELAY", "1");
992 : }
993 :
994 11 : CPLDebug("VSIKerchunkJSONRefFileSystem", "Acquiring lock");
995 11 : switch (CPLLockFileEx(osLockFilename.c_str(), &hLockHandle,
996 11 : aosOptions.List()))
997 : {
998 10 : case CLFS_OK:
999 10 : break;
1000 1 : case CLFS_CANNOT_CREATE_LOCK:
1001 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create lock %s",
1002 : osLockFilename.c_str());
1003 1 : break;
1004 0 : case CLFS_LOCK_BUSY:
1005 0 : CPLAssert(false); // cannot happen with infinite wait time
1006 : break;
1007 0 : case CLFS_API_MISUSE:
1008 0 : CPLAssert(false);
1009 : break;
1010 0 : case CLFS_THREAD_CREATION_FAILED:
1011 0 : CPLError(CE_Failure, CPLE_AppDefined,
1012 : "Thread creation failed for refresh of %s",
1013 : osLockFilename.c_str());
1014 0 : break;
1015 : }
1016 11 : if (!hLockHandle)
1017 : {
1018 1 : return {nullptr, std::string()};
1019 : }
1020 :
1021 : struct LockFileHolder
1022 : {
1023 : CPLLockFileHandle m_hLockHandle = nullptr;
1024 :
1025 10 : explicit LockFileHolder(CPLLockFileHandle hLockHandleIn)
1026 10 : : m_hLockHandle(hLockHandleIn)
1027 : {
1028 10 : }
1029 :
1030 10 : ~LockFileHolder()
1031 10 : {
1032 10 : release();
1033 10 : }
1034 :
1035 20 : void release()
1036 : {
1037 20 : if (m_hLockHandle)
1038 : {
1039 10 : CPLDebug("VSIKerchunkJSONRefFileSystem",
1040 : "Releasing lock");
1041 10 : CPLUnlockFileEx(m_hLockHandle);
1042 10 : m_hLockHandle = nullptr;
1043 : }
1044 20 : }
1045 :
1046 : CPL_DISALLOW_COPY_ASSIGN(LockFileHolder)
1047 : };
1048 :
1049 20 : LockFileHolder lockFileHolder(hLockHandle);
1050 :
1051 20 : if (VSIStatL(CPLFormFilenameSafe(osCacheDir.c_str(), ".zmetadata",
1052 : nullptr)
1053 : .c_str(),
1054 10 : &sStat) == 0)
1055 : {
1056 0 : CPLDebug("VSIKerchunkJSONRefFileSystem",
1057 : "Using Kerchunk Parquet cache %s (after lock taking)",
1058 : osCacheDir.c_str());
1059 0 : return {nullptr, osCacheDir};
1060 : }
1061 :
1062 10 : refFile = LoadInternal(osJSONFilename, nullptr, nullptr);
1063 :
1064 10 : if (refFile)
1065 : {
1066 10 : CPLDebug("VSIKerchunkJSONRefFileSystem",
1067 : "Generating Kerchunk Parquet cache %s...",
1068 : osCacheDir.c_str());
1069 :
1070 10 : if (pszKerchunkDebug &&
1071 3 : strstr(pszKerchunkDebug,
1072 : "WAIT_BEFORE_CONVERT_TO_PARQUET_REF"))
1073 : {
1074 1 : CPLSleep(0.5);
1075 : }
1076 :
1077 10 : if (refFile->ConvertToParquetRef(osCacheDir, nullptr, nullptr))
1078 : {
1079 4 : CPLDebug("VSIKerchunkJSONRefFileSystem",
1080 : "Generation Kerchunk Parquet cache %s: OK",
1081 : osCacheDir.c_str());
1082 : }
1083 : else
1084 : {
1085 6 : CPLError(CE_Failure, CPLE_AppDefined,
1086 : "Generation of Kerchunk Parquet cache %s failed",
1087 : osCacheDir.c_str());
1088 6 : refFile.reset();
1089 : }
1090 :
1091 10 : lockFileHolder.release();
1092 10 : m_oCache.insert(osJSONFilename, refFile);
1093 : }
1094 :
1095 10 : return {refFile, std::string()};
1096 : }
1097 : }
1098 :
1099 98 : refFile = LoadInternal(osJSONFilename, nullptr, nullptr);
1100 98 : if (refFile)
1101 20 : m_oCache.insert(osJSONFilename, refFile);
1102 98 : return {refFile, std::string()};
1103 : }
1104 :
1105 : /************************************************************************/
1106 : /* VSIKerchunkRefFile::ConvertToParquetRef() */
1107 : /************************************************************************/
1108 :
1109 11 : bool VSIKerchunkRefFile::ConvertToParquetRef(const std::string &osCacheDir,
1110 : GDALProgressFunc pfnProgress,
1111 : void *pProgressData)
1112 : {
1113 : struct Serializer
1114 : {
1115 : VSIVirtualHandle *m_poFile = nullptr;
1116 :
1117 11 : explicit Serializer(VSIVirtualHandle *poFile) : m_poFile(poFile)
1118 : {
1119 11 : }
1120 :
1121 289 : static void func(const char *pszTxt, void *pUserData)
1122 : {
1123 289 : Serializer *self = static_cast<Serializer *>(pUserData);
1124 289 : self->m_poFile->Write(pszTxt, 1, strlen(pszTxt));
1125 289 : }
1126 : };
1127 :
1128 : const std::string osZMetadataFilename =
1129 22 : CPLFormFilenameSafe(osCacheDir.c_str(), ".zmetadata", nullptr);
1130 22 : const std::string osZMetadataTmpFilename = osZMetadataFilename + ".tmp";
1131 : auto poFile = std::unique_ptr<VSIVirtualHandle>(
1132 22 : VSIFOpenL(osZMetadataTmpFilename.c_str(), "wb"));
1133 11 : if (!poFile)
1134 0 : return false;
1135 11 : Serializer serializer(poFile.get());
1136 :
1137 : struct ZarrArrayInfo
1138 : {
1139 : std::vector<uint64_t> anChunkCount{};
1140 : std::map<uint64_t, const VSIKerchunkKeyInfo *> chunkInfo{};
1141 : };
1142 :
1143 22 : std::map<std::string, ZarrArrayInfo> zarrArrays;
1144 :
1145 : // First pass on keys to write JSON objects in .zmetadata
1146 22 : CPLJSonStreamingWriter oWriter(Serializer::func, &serializer);
1147 11 : oWriter.StartObj();
1148 11 : oWriter.AddObjKey("metadata");
1149 11 : oWriter.StartObj();
1150 :
1151 11 : bool bOK = true;
1152 11 : size_t nCurObjectIter = 0;
1153 11 : const size_t nTotalObjects = m_oMapKeys.size();
1154 44 : for (const auto &[key, info] : m_oMapKeys)
1155 : {
1156 54 : if (cpl::ends_with(key, ".zarray") || cpl::ends_with(key, ".zgroup") ||
1157 16 : cpl::ends_with(key, ".zattrs"))
1158 : {
1159 32 : CPLJSONDocument oDoc;
1160 32 : std::string osStr;
1161 32 : osStr.append(reinterpret_cast<const char *>(info.abyValue.data()),
1162 64 : info.abyValue.size());
1163 32 : if (!oDoc.LoadMemory(osStr.c_str()))
1164 : {
1165 1 : CPLError(CE_Failure, CPLE_AppDefined,
1166 : "Cannot parse JSON content for %s", key.c_str());
1167 1 : bOK = false;
1168 1 : break;
1169 : }
1170 :
1171 31 : if (cpl::ends_with(key, ".zarray"))
1172 : {
1173 10 : const std::string osArrayName = CPLGetDirnameSafe(key.c_str());
1174 :
1175 20 : const auto oShape = oDoc.GetRoot().GetArray("shape");
1176 20 : const auto oChunks = oDoc.GetRoot().GetArray("chunks");
1177 20 : if (oShape.IsValid() && oChunks.IsValid() &&
1178 10 : oShape.Size() == oChunks.Size())
1179 : {
1180 18 : std::vector<uint64_t> anChunkCount;
1181 9 : uint64_t nTotalChunkCount = 1;
1182 17 : for (int i = 0; i < oShape.Size(); ++i)
1183 : {
1184 11 : const auto nShape = oShape[i].ToLong();
1185 11 : const auto nChunk = oChunks[i].ToLong();
1186 11 : if (nShape == 0 || nChunk == 0)
1187 : {
1188 2 : bOK = false;
1189 3 : break;
1190 : }
1191 9 : const uint64_t nChunkCount =
1192 9 : DIV_ROUND_UP(nShape, nChunk);
1193 9 : if (nChunkCount > std::numeric_limits<uint64_t>::max() /
1194 : nTotalChunkCount)
1195 : {
1196 1 : bOK = false;
1197 1 : break;
1198 : }
1199 8 : anChunkCount.push_back(nChunkCount);
1200 8 : nTotalChunkCount *= nChunkCount;
1201 : }
1202 9 : zarrArrays[osArrayName].anChunkCount =
1203 18 : std::move(anChunkCount);
1204 : }
1205 : else
1206 : {
1207 1 : bOK = false;
1208 : }
1209 10 : if (!bOK)
1210 : {
1211 4 : CPLError(CE_Failure, CPLE_AppDefined,
1212 : "Invalid Zarr array definition for %s",
1213 : osArrayName.c_str());
1214 4 : oWriter.clear();
1215 4 : break;
1216 : }
1217 : }
1218 :
1219 27 : oWriter.AddObjKey(key);
1220 27 : oWriter.AddSerializedValue(osStr);
1221 :
1222 27 : ++nCurObjectIter;
1223 31 : if (pfnProgress &&
1224 4 : !pfnProgress(static_cast<double>(nCurObjectIter) /
1225 4 : static_cast<double>(nTotalObjects),
1226 : "", pProgressData))
1227 : {
1228 0 : return false;
1229 : }
1230 : }
1231 : }
1232 :
1233 11 : constexpr int nRecordSize = 100000;
1234 :
1235 11 : if (bOK)
1236 : {
1237 6 : oWriter.EndObj();
1238 6 : oWriter.AddObjKey("record_size");
1239 6 : oWriter.Add(nRecordSize);
1240 6 : oWriter.EndObj();
1241 : }
1242 :
1243 11 : bOK = (poFile->Close() == 0) && bOK;
1244 11 : poFile.reset();
1245 :
1246 11 : if (!bOK)
1247 : {
1248 5 : oWriter.clear();
1249 5 : VSIUnlink(osZMetadataTmpFilename.c_str());
1250 5 : return false;
1251 : }
1252 :
1253 : // Second pass to retrieve chunks
1254 33 : for (const auto &[key, info] : m_oMapKeys)
1255 : {
1256 44 : if (cpl::ends_with(key, ".zarray") || cpl::ends_with(key, ".zgroup") ||
1257 16 : cpl::ends_with(key, ".zattrs"))
1258 : {
1259 : // already done
1260 : }
1261 : else
1262 : {
1263 6 : const std::string osArrayName = CPLGetDirnameSafe(key.c_str());
1264 6 : auto oIter = zarrArrays.find(osArrayName);
1265 6 : if (oIter != zarrArrays.end())
1266 : {
1267 6 : auto &oArrayInfo = oIter->second;
1268 : const CPLStringList aosIndices(
1269 6 : CSLTokenizeString2(CPLGetFilename(key.c_str()), ".", 0));
1270 6 : if ((static_cast<size_t>(aosIndices.size()) ==
1271 6 : oArrayInfo.anChunkCount.size()) ||
1272 0 : (aosIndices.size() == 1 &&
1273 0 : strcmp(aosIndices[0], "0") == 0 &&
1274 0 : oArrayInfo.anChunkCount.empty()))
1275 : {
1276 6 : std::vector<uint64_t> anIndices;
1277 11 : for (size_t i = 0; i < oArrayInfo.anChunkCount.size(); ++i)
1278 : {
1279 6 : char *endptr = nullptr;
1280 6 : anIndices.push_back(
1281 6 : std::strtoull(aosIndices[i], &endptr, 10));
1282 6 : if (aosIndices[i][0] == '-' ||
1283 12 : endptr != aosIndices[i] + strlen(aosIndices[i]) ||
1284 6 : anIndices[i] >= oArrayInfo.anChunkCount[i])
1285 : {
1286 1 : CPLError(CE_Failure, CPLE_AppDefined,
1287 : "Invalid key indices: %s", key.c_str());
1288 1 : return false;
1289 : }
1290 : }
1291 :
1292 5 : uint64_t nLinearIndex = 0;
1293 5 : uint64_t nMulFactor = 1;
1294 10 : for (size_t i = anIndices.size(); i > 0;)
1295 : {
1296 5 : --i;
1297 5 : nLinearIndex += anIndices[i] * nMulFactor;
1298 5 : nMulFactor *= oArrayInfo.anChunkCount[i];
1299 : }
1300 :
1301 : #ifdef DEBUG_VERBOSE
1302 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem",
1303 : "Chunk %" PRIu64 " of array %s found",
1304 : nLinearIndex, osArrayName.c_str());
1305 : #endif
1306 5 : oArrayInfo.chunkInfo[nLinearIndex] = &info;
1307 : }
1308 : }
1309 : }
1310 : }
1311 :
1312 5 : auto poDrv = GetGDALDriverManager()->GetDriverByName("PARQUET");
1313 5 : if (!poDrv)
1314 : {
1315 : // shouldn't happen given earlier checks
1316 0 : CPLAssert(false);
1317 : return false;
1318 : }
1319 :
1320 : // Third pass to create Parquet files
1321 10 : CPLStringList aosLayerCreationOptions;
1322 : aosLayerCreationOptions.SetNameValue("ROW_GROUP_SIZE",
1323 5 : CPLSPrintf("%d", nRecordSize));
1324 :
1325 10 : for (const auto &[osArrayName, oArrayInfo] : zarrArrays)
1326 : {
1327 5 : uint64_t nChunkCount = 1;
1328 10 : for (size_t i = 0; i < oArrayInfo.anChunkCount.size(); ++i)
1329 : {
1330 5 : nChunkCount *= oArrayInfo.anChunkCount[i];
1331 : }
1332 :
1333 0 : std::unique_ptr<GDALDataset> poDS;
1334 5 : OGRLayer *poLayer = nullptr;
1335 :
1336 10 : for (uint64_t i = 0; i < nChunkCount; ++i)
1337 : {
1338 5 : bOK = poLayer != nullptr;
1339 5 : if ((i % nRecordSize) == 0)
1340 : {
1341 5 : if (poDS)
1342 : {
1343 0 : if (poDS->Close() != CE_None)
1344 : {
1345 0 : CPLError(CE_Failure, CPLE_AppDefined,
1346 : "Close() on %s failed",
1347 0 : poDS->GetDescription());
1348 0 : return false;
1349 : }
1350 0 : poDS.reset();
1351 : }
1352 5 : if (CPLHasPathTraversal(osArrayName.c_str()))
1353 : {
1354 0 : CPLError(CE_Failure, CPLE_AppDefined,
1355 : "Path traversal detected in %s",
1356 : osArrayName.c_str());
1357 0 : return false;
1358 : }
1359 : const std::string osParqFilename = CPLFormFilenameSafe(
1360 5 : CPLFormFilenameSafe(osCacheDir.c_str(), osArrayName.c_str(),
1361 : nullptr)
1362 : .c_str(),
1363 : CPLSPrintf("refs.%" PRIu64 ".parq", i / nRecordSize),
1364 10 : nullptr);
1365 5 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Creating %s",
1366 : osParqFilename.c_str());
1367 5 : VSIMkdirRecursive(
1368 10 : CPLGetPathSafe(osParqFilename.c_str()).c_str(), 0755);
1369 5 : poDS.reset(poDrv->Create(osParqFilename.c_str(), 0, 0, 0,
1370 : GDT_Unknown, nullptr));
1371 5 : if (!poDS)
1372 0 : return false;
1373 10 : poLayer = poDS->CreateLayer(
1374 10 : CPLGetBasenameSafe(osParqFilename.c_str()).c_str(), nullptr,
1375 5 : wkbNone, aosLayerCreationOptions.List());
1376 5 : bOK = false;
1377 5 : if (poLayer)
1378 : {
1379 : {
1380 5 : OGRFieldDefn oFieldDefn("path", OFTString);
1381 5 : bOK = poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
1382 : }
1383 : {
1384 5 : OGRFieldDefn oFieldDefn("offset", OFTInteger64);
1385 10 : bOK = bOK &&
1386 5 : poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
1387 : }
1388 : {
1389 5 : OGRFieldDefn oFieldDefn("size", OFTInteger64);
1390 10 : bOK = bOK &&
1391 5 : poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
1392 : }
1393 : {
1394 5 : OGRFieldDefn oFieldDefn("raw", OFTBinary);
1395 10 : bOK = bOK &&
1396 5 : poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
1397 : }
1398 : }
1399 : }
1400 5 : if (!bOK)
1401 0 : return false;
1402 :
1403 : auto poFeature =
1404 5 : std::make_unique<OGRFeature>(poLayer->GetLayerDefn());
1405 5 : auto oIter = oArrayInfo.chunkInfo.find(i);
1406 5 : if (oIter != oArrayInfo.chunkInfo.end())
1407 : {
1408 5 : const auto *chunkInfo = oIter->second;
1409 5 : if (chunkInfo->posURI)
1410 5 : poFeature->SetField(0, chunkInfo->posURI->c_str());
1411 5 : poFeature->SetField(1,
1412 5 : static_cast<GIntBig>(chunkInfo->nOffset));
1413 5 : poFeature->SetField(2, static_cast<GIntBig>(chunkInfo->nSize));
1414 5 : if (!chunkInfo->abyValue.empty())
1415 : {
1416 0 : if (chunkInfo->abyValue.size() >
1417 : static_cast<size_t>(INT_MAX))
1418 : {
1419 0 : CPLError(CE_Failure, CPLE_NotSupported,
1420 : "Too big blob for chunk %" PRIu64
1421 : " of array %s",
1422 : i, osArrayName.c_str());
1423 0 : return false;
1424 : }
1425 0 : poFeature->SetField(
1426 0 : 3, static_cast<int>(chunkInfo->abyValue.size()),
1427 0 : static_cast<const void *>(chunkInfo->abyValue.data()));
1428 : }
1429 : }
1430 :
1431 5 : if (poLayer->CreateFeature(poFeature.get()) != OGRERR_NONE)
1432 : {
1433 0 : CPLError(CE_Failure, CPLE_AppDefined,
1434 : "CreateFeature() on %s failed",
1435 0 : poDS->GetDescription());
1436 0 : return false;
1437 : }
1438 :
1439 5 : ++nCurObjectIter;
1440 5 : if (pfnProgress && (nCurObjectIter % 1000) == 0 &&
1441 0 : !pfnProgress(static_cast<double>(nCurObjectIter) /
1442 0 : static_cast<double>(nTotalObjects),
1443 : "", pProgressData))
1444 : {
1445 0 : return false;
1446 : }
1447 : }
1448 :
1449 5 : if (poDS)
1450 : {
1451 5 : if (poDS->Close() != CE_None)
1452 : {
1453 0 : CPLError(CE_Failure, CPLE_AppDefined, "Close() on %s failed",
1454 0 : poDS->GetDescription());
1455 0 : return false;
1456 : }
1457 5 : poDS.reset();
1458 : }
1459 : }
1460 :
1461 5 : if (VSIRename(osZMetadataTmpFilename.c_str(),
1462 5 : osZMetadataFilename.c_str()) != 0)
1463 : {
1464 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot rename %s to %s",
1465 : osZMetadataTmpFilename.c_str(), osZMetadataFilename.c_str());
1466 0 : return false;
1467 : }
1468 :
1469 5 : if (pfnProgress)
1470 1 : pfnProgress(1.0, "", pProgressData);
1471 :
1472 5 : return true;
1473 : }
1474 :
1475 : /************************************************************************/
1476 : /* VSIKerchunkConvertJSONToParquet() */
1477 : /************************************************************************/
1478 :
1479 1 : bool VSIKerchunkConvertJSONToParquet(const char *pszSrcJSONFilename,
1480 : const char *pszDstDirname,
1481 : GDALProgressFunc pfnProgress,
1482 : void *pProgressData)
1483 : {
1484 1 : if (GDALGetDriverByName("PARQUET") == nullptr)
1485 : {
1486 0 : CPLError(CE_Failure, CPLE_NotSupported,
1487 : "Conversion to a Parquet reference store is not possible "
1488 : "because the PARQUET driver is not available.");
1489 0 : return false;
1490 : }
1491 :
1492 1 : auto poFS = cpl::down_cast<VSIKerchunkJSONRefFileSystem *>(
1493 : VSIFileManager::GetHandler(JSON_REF_FS_PREFIX));
1494 1 : std::shared_ptr<VSIKerchunkRefFile> refFile;
1495 1 : if (!poFS->m_oCache.tryGet(pszSrcJSONFilename, refFile))
1496 : {
1497 : void *pScaledProgressData =
1498 1 : GDALCreateScaledProgress(0.0, 0.5, pfnProgress, pProgressData);
1499 : try
1500 : {
1501 2 : refFile = poFS->LoadInternal(
1502 : pszSrcJSONFilename,
1503 : pScaledProgressData ? GDALScaledProgress : nullptr,
1504 1 : pScaledProgressData);
1505 : }
1506 0 : catch (const std::exception &e)
1507 : {
1508 0 : CPLError(CE_Failure, CPLE_AppDefined,
1509 : "VSIKerchunkJSONRefFileSystem::Load() failed: %s",
1510 0 : e.what());
1511 0 : GDALDestroyScaledProgress(pScaledProgressData);
1512 0 : return false;
1513 : }
1514 1 : GDALDestroyScaledProgress(pScaledProgressData);
1515 : }
1516 1 : if (!refFile)
1517 : {
1518 0 : CPLError(CE_Failure, CPLE_AppDefined,
1519 : "%s is not a Kerchunk JSON reference store",
1520 : pszSrcJSONFilename);
1521 0 : return false;
1522 : }
1523 :
1524 1 : VSIMkdir(pszDstDirname, 0755);
1525 :
1526 : void *pScaledProgressData =
1527 1 : GDALCreateScaledProgress(0.5, 1.0, pfnProgress, pProgressData);
1528 1 : const bool bRet = refFile->ConvertToParquetRef(
1529 : pszDstDirname, pScaledProgressData ? GDALScaledProgress : nullptr,
1530 : pScaledProgressData);
1531 1 : GDALDestroyScaledProgress(pScaledProgressData);
1532 1 : return bRet;
1533 : }
1534 :
1535 : /************************************************************************/
1536 : /* VSIKerchunkJSONRefFileSystem::Open() */
1537 : /************************************************************************/
1538 :
1539 : VSIVirtualHandleUniquePtr
1540 91 : VSIKerchunkJSONRefFileSystem::Open(const char *pszFilename,
1541 : const char *pszAccess, bool /* bSetError */,
1542 : CSLConstList /* papszOptions */)
1543 : {
1544 91 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Open(%s)", pszFilename);
1545 91 : if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "rb") != 0)
1546 0 : return nullptr;
1547 :
1548 182 : const auto [osJSONFilename, osKey] = SplitFilename(pszFilename);
1549 91 : if (osJSONFilename.empty())
1550 4 : return nullptr;
1551 :
1552 87 : const auto [refFile, osParqFilename] = Load(
1553 174 : osJSONFilename, STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX));
1554 87 : if (!refFile)
1555 : {
1556 6 : if (osParqFilename.empty())
1557 2 : return nullptr;
1558 :
1559 : return VSIFilesystemHandler::OpenStatic(
1560 8 : CPLFormFilenameSafe(CPLSPrintf("%s{%s}", PARQUET_REF_FS_PREFIX,
1561 : osParqFilename.c_str()),
1562 : osKey.c_str(), nullptr)
1563 : .c_str(),
1564 4 : pszAccess);
1565 : }
1566 :
1567 81 : const auto oIter = refFile->GetMapKeys().find(osKey);
1568 81 : if (oIter == refFile->GetMapKeys().end())
1569 6 : return nullptr;
1570 :
1571 75 : const auto &keyInfo = oIter->second;
1572 75 : if (!keyInfo.posURI)
1573 : {
1574 : return VSIVirtualHandleUniquePtr(VSIFileFromMemBuffer(
1575 55 : nullptr, const_cast<GByte *>(keyInfo.abyValue.data()),
1576 110 : keyInfo.abyValue.size(), /* bTakeOwnership = */ false));
1577 : }
1578 : else
1579 : {
1580 : std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
1581 40 : *(keyInfo.posURI), CPLGetPathSafe(osJSONFilename.c_str()));
1582 20 : if (osVSIPath.empty())
1583 2 : return nullptr;
1584 : const std::string osPath =
1585 18 : keyInfo.nSize
1586 7 : ? CPLSPrintf("/vsisubfile/%" PRIu64 "_%u,%s", keyInfo.nOffset,
1587 7 : keyInfo.nSize, osVSIPath.c_str())
1588 36 : : std::move(osVSIPath);
1589 18 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Opening %s",
1590 : osPath.c_str());
1591 : CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
1592 36 : "EMPTY_DIR", false);
1593 36 : auto fp = VSIFilesystemHandler::OpenStatic(osPath.c_str(), "rb", true);
1594 18 : if (!fp)
1595 : {
1596 1 : if (!VSIToCPLError(CE_Failure, CPLE_FileIO))
1597 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
1598 : osPath.c_str());
1599 : }
1600 18 : return fp;
1601 : }
1602 : }
1603 :
1604 : /************************************************************************/
1605 : /* VSIKerchunkJSONRefFileSystem::Stat() */
1606 : /************************************************************************/
1607 :
1608 258 : int VSIKerchunkJSONRefFileSystem::Stat(const char *pszFilename,
1609 : VSIStatBufL *pStatBuf, int nFlags)
1610 : {
1611 258 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Stat(%s)", pszFilename);
1612 258 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
1613 :
1614 516 : const auto [osJSONFilename, osKey] = SplitFilename(pszFilename);
1615 258 : if (osJSONFilename.empty())
1616 12 : return -1;
1617 :
1618 246 : const auto [refFile, osParqFilename] = Load(
1619 492 : osJSONFilename, STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX));
1620 246 : if (!refFile)
1621 : {
1622 91 : if (osParqFilename.empty())
1623 86 : return -1;
1624 :
1625 5 : return VSIStatExL(
1626 10 : CPLFormFilenameSafe(CPLSPrintf("%s{%s}", PARQUET_REF_FS_PREFIX,
1627 : osParqFilename.c_str()),
1628 : osKey.c_str(), nullptr)
1629 : .c_str(),
1630 5 : pStatBuf, nFlags);
1631 : }
1632 :
1633 155 : if (osKey.empty())
1634 : {
1635 27 : pStatBuf->st_mode = S_IFDIR;
1636 27 : return 0;
1637 : }
1638 :
1639 128 : const auto oIter = refFile->GetMapKeys().find(osKey);
1640 128 : if (oIter == refFile->GetMapKeys().end())
1641 : {
1642 210 : if (cpl::contains(refFile->GetMapKeys(), osKey + "/.zgroup") ||
1643 140 : cpl::contains(refFile->GetMapKeys(), osKey + "/.zarray"))
1644 : {
1645 8 : pStatBuf->st_mode = S_IFDIR;
1646 8 : return 0;
1647 : }
1648 :
1649 62 : return -1;
1650 : }
1651 :
1652 58 : const auto &keyInfo = oIter->second;
1653 58 : if (!(keyInfo.posURI))
1654 : {
1655 50 : pStatBuf->st_size = keyInfo.abyValue.size();
1656 : }
1657 : else
1658 : {
1659 8 : if (keyInfo.nSize)
1660 : {
1661 4 : pStatBuf->st_size = keyInfo.nSize;
1662 : }
1663 : else
1664 : {
1665 : const std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
1666 8 : *(keyInfo.posURI), CPLGetPathSafe(osJSONFilename.c_str()));
1667 4 : if (osVSIPath.empty())
1668 0 : return -1;
1669 4 : return VSIStatExL(osVSIPath.c_str(), pStatBuf, nFlags);
1670 : }
1671 : }
1672 54 : pStatBuf->st_mode = S_IFREG;
1673 :
1674 54 : return 0;
1675 : }
1676 :
1677 : /************************************************************************/
1678 : /* VSIKerchunkJSONRefFileSystem::GetFileMetadata() */
1679 : /************************************************************************/
1680 :
1681 : char **
1682 11 : VSIKerchunkJSONRefFileSystem::GetFileMetadata(const char *pszFilename,
1683 : const char *pszDomain,
1684 : CSLConstList /* papszOptions */)
1685 : {
1686 11 : if (!pszDomain || !EQUAL(pszDomain, "CHUNK_INFO"))
1687 2 : return nullptr;
1688 :
1689 18 : const auto [osJSONFilename, osKey] = SplitFilename(pszFilename);
1690 9 : if (osJSONFilename.empty() || osKey.empty())
1691 4 : return nullptr;
1692 :
1693 5 : const auto [refFile, osParqFilename] = Load(
1694 10 : osJSONFilename, STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX));
1695 5 : if (!refFile)
1696 0 : return nullptr;
1697 :
1698 5 : const auto oIter = refFile->GetMapKeys().find(osKey);
1699 5 : if (oIter == refFile->GetMapKeys().end())
1700 2 : return nullptr;
1701 :
1702 3 : const auto &keyInfo = oIter->second;
1703 6 : CPLStringList aosMetadata;
1704 3 : if (!(keyInfo.posURI))
1705 : {
1706 : aosMetadata.SetNameValue(
1707 : "SIZE", CPLSPrintf(CPL_FRMT_GUIB,
1708 0 : static_cast<GUIntBig>(keyInfo.abyValue.size())));
1709 0 : if (keyInfo.abyValue.size() <
1710 0 : static_cast<size_t>(std::numeric_limits<int>::max() - 1))
1711 : {
1712 : char *pszBase64 =
1713 0 : CPLBase64Encode(static_cast<int>(keyInfo.abyValue.size()),
1714 : keyInfo.abyValue.data());
1715 0 : aosMetadata.SetNameValue("BASE64", pszBase64);
1716 0 : CPLFree(pszBase64);
1717 : }
1718 : }
1719 : else
1720 : {
1721 : const std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
1722 3 : *(keyInfo.posURI), CPLGetPathSafe(osJSONFilename.c_str()));
1723 3 : if (osVSIPath.empty())
1724 0 : return nullptr;
1725 3 : if (keyInfo.nSize)
1726 : {
1727 1 : aosMetadata.SetNameValue("SIZE", CPLSPrintf("%u", keyInfo.nSize));
1728 : }
1729 : else
1730 : {
1731 : VSIStatBufL sStatBuf;
1732 2 : if (VSIStatL(osVSIPath.c_str(), &sStatBuf) != 0)
1733 0 : return nullptr;
1734 : aosMetadata.SetNameValue(
1735 : "SIZE", CPLSPrintf(CPL_FRMT_GUIB,
1736 2 : static_cast<GUIntBig>(sStatBuf.st_size)));
1737 : }
1738 : aosMetadata.SetNameValue(
1739 : "OFFSET",
1740 3 : CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(keyInfo.nOffset)));
1741 3 : aosMetadata.SetNameValue("FILENAME", osVSIPath.c_str());
1742 : }
1743 :
1744 3 : return aosMetadata.StealList();
1745 : }
1746 :
1747 : /************************************************************************/
1748 : /* VSIKerchunkJSONRefFileSystem::ReadDirEx() */
1749 : /************************************************************************/
1750 :
1751 17 : char **VSIKerchunkJSONRefFileSystem::ReadDirEx(const char *pszDirname,
1752 : int nMaxFiles)
1753 : {
1754 17 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "ReadDir(%s)", pszDirname);
1755 :
1756 34 : const auto [osJSONFilename, osAskedKey] = SplitFilename(pszDirname);
1757 17 : if (osJSONFilename.empty())
1758 4 : return nullptr;
1759 :
1760 13 : const auto [refFile, osParqFilename] = Load(
1761 26 : osJSONFilename, STARTS_WITH(pszDirname, JSON_REF_CACHED_FS_PREFIX));
1762 13 : if (!refFile)
1763 : {
1764 3 : if (osParqFilename.empty())
1765 3 : return nullptr;
1766 :
1767 0 : return VSIReadDirEx(
1768 0 : CPLFormFilenameSafe(CPLSPrintf("%s{%s}", PARQUET_REF_FS_PREFIX,
1769 : osParqFilename.c_str()),
1770 : osAskedKey.c_str(), nullptr)
1771 : .c_str(),
1772 0 : nMaxFiles);
1773 : }
1774 :
1775 20 : std::set<std::string> set;
1776 60 : for (const auto &[key, value] : refFile->GetMapKeys())
1777 : {
1778 50 : if (osAskedKey.empty())
1779 : {
1780 20 : const auto nPos = key.find('/');
1781 20 : if (nPos == std::string::npos)
1782 8 : set.insert(key);
1783 : else
1784 12 : set.insert(key.substr(0, nPos));
1785 : }
1786 30 : else if (key.size() > osAskedKey.size() &&
1787 42 : cpl::starts_with(key, osAskedKey) &&
1788 12 : key[osAskedKey.size()] == '/')
1789 : {
1790 24 : std::string subKey = key.substr(osAskedKey.size() + 1);
1791 12 : const auto nPos = subKey.find('/');
1792 12 : if (nPos == std::string::npos)
1793 12 : set.insert(std::move(subKey));
1794 : else
1795 0 : set.insert(subKey.substr(0, nPos));
1796 : }
1797 : }
1798 :
1799 20 : CPLStringList aosRet;
1800 34 : for (const std::string &v : set)
1801 : {
1802 : // CPLDebugOnly("VSIKerchunkJSONRefFileSystem", ".. %s", v.c_str());
1803 24 : aosRet.AddString(v.c_str());
1804 : }
1805 10 : return aosRet.StealList();
1806 : }
1807 :
1808 : /************************************************************************/
1809 : /* VSIInstallKerchunkJSONRefFileSystem() */
1810 : /************************************************************************/
1811 :
1812 1755 : void VSIInstallKerchunkJSONRefFileSystem()
1813 : {
1814 : static std::mutex oMutex;
1815 3510 : std::lock_guard<std::mutex> oLock(oMutex);
1816 : // cppcheck-suppress knownConditionTrueFalse
1817 1755 : if (!VSIKerchunkJSONRefFileSystem::IsFileSystemInstantiated())
1818 : {
1819 1755 : auto fs = std::make_unique<VSIKerchunkJSONRefFileSystem>().release();
1820 1755 : VSIFileManager::InstallHandler(JSON_REF_FS_PREFIX, fs);
1821 1755 : VSIFileManager::InstallHandler(JSON_REF_CACHED_FS_PREFIX, fs);
1822 : }
1823 1755 : }
|