Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: Icechunk driver
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "icechunkdrivercore.h"
14 : #include "icechunkutils.h"
15 : #include "icechunkmanifest.h"
16 : #include "icechunkrepo.h"
17 : #include "icechunksnapshot.h"
18 :
19 : #include "cpl_mem_cache.h"
20 : #include "cpl_vsi_virtual.h"
21 :
22 : #include <cinttypes>
23 : #include <limits>
24 : #include <mutex>
25 : #include <utility>
26 :
27 : namespace gdal::icechunk
28 : {
29 :
30 : /************************************************************************/
31 : /* VSIIcechunkFileSystem */
32 : /************************************************************************/
33 :
34 : class VSIIcechunkFileSystem final : public VSIFilesystemHandler
35 : {
36 : public:
37 : // If the input of SplitFilename() is /vsiicechunk/{/path/to/some/icechunk/repo?branch=my_branch]}/optional_key
38 : // - osRootFilenameWithBranchOrTag = "/path/to/some/icechunk/repo?branch=my_branch"
39 : // - osRootFilename = "/path/to/some/icechunk/repo"
40 : // - osBranchName = "my_branch"
41 : // - osKey = "/optional_key"
42 : //
43 : // If /optional_key is not present, osKey is set to "/"
44 : struct Ref
45 : {
46 : std::string osRootFilenameWithBranchOrTag{};
47 : std::string osRootFilename{};
48 : std::string osBranchName{};
49 : std::string osTagName{};
50 : std::string osKey{};
51 : };
52 :
53 1872 : VSIIcechunkFileSystem()
54 1872 : {
55 1872 : bool *pbInstantiated = &IsFileSystemInstantiated();
56 1872 : *pbInstantiated = true;
57 1872 : }
58 :
59 : ~VSIIcechunkFileSystem() override;
60 :
61 5045 : static bool &IsFileSystemInstantiated()
62 : {
63 : static bool bIsFileSystemInstantiated = false;
64 5045 : return bIsFileSystemInstantiated;
65 : }
66 :
67 : VSIVirtualHandleUniquePtr Open(const char *pszFilename,
68 : const char *pszAccess, bool bSetError,
69 : CSLConstList papszOptions) override;
70 :
71 : int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
72 : int nFlags) override;
73 :
74 : char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
75 :
76 : void ClearCaches();
77 :
78 : char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
79 : CSLConstList papszOptions) override;
80 :
81 : private:
82 : using RepoAndSnapshot = std::pair<std::shared_ptr<IcechunkRepo>,
83 : std::shared_ptr<IcechunkSnapshot>>;
84 :
85 : lru11::Cache<std::string, RepoAndSnapshot, std::mutex> m_oCache{};
86 :
87 : static Ref SplitFilename(const char *pszFilename);
88 :
89 : RepoAndSnapshot Load(const Ref &ref);
90 : std::pair<VSIIcechunkFileSystem::Ref, RepoAndSnapshot>
91 : Load(const char *pszFilename);
92 :
93 : struct FileInfo
94 : {
95 : bool bIsDir = false;
96 : std::string osFilename{};
97 : uint64_t nOffset = 0;
98 : uint64_t nSize = 0;
99 : const void *pabyData = nullptr;
100 :
101 : // To keep pabyData alive
102 : std::shared_ptr<IcechunkFile> dataOwner{};
103 : };
104 :
105 : FileInfo GetFileInfo(const char *pszFilename);
106 : };
107 :
108 : /************************************************************************/
109 : /* ~VSIIcechunkFileSystem() */
110 : /************************************************************************/
111 :
112 1301 : VSIIcechunkFileSystem::~VSIIcechunkFileSystem()
113 : {
114 1301 : bool *pbInstantiated = &IsFileSystemInstantiated();
115 1301 : *pbInstantiated = false;
116 1301 : }
117 :
118 : /************************************************************************/
119 : /* ClearCaches() */
120 : /************************************************************************/
121 :
122 1006 : void VSIIcechunkFileSystem::ClearCaches()
123 : {
124 1006 : m_oCache.clear();
125 1006 : }
126 :
127 : /************************************************************************/
128 : /* VSIIcechunkFileSystem::SplitFilename() */
129 : /************************************************************************/
130 :
131 : /** Decompose a filename into a repository root file name, a branch/tag name
132 : * and a key.
133 : */
134 :
135 : /*static*/
136 : VSIIcechunkFileSystem::Ref
137 8384 : VSIIcechunkFileSystem::SplitFilename(const char *pszFilename)
138 : {
139 8384 : Ref ref{};
140 8384 : if (!STARTS_WITH(pszFilename, FS_PREFIX))
141 0 : return ref;
142 :
143 16768 : std::string osRootFilename;
144 :
145 8384 : pszFilename += strlen(FS_PREFIX);
146 :
147 8384 : if (*pszFilename == '{')
148 : {
149 : // Parse /vsiicechunk/{/path/to/some/icechunk/repo[?[branch|tag]=<name>]}[/optional_key]
150 8383 : int nLevel = 1;
151 8383 : ++pszFilename;
152 256898 : for (; *pszFilename; ++pszFilename)
153 : {
154 256897 : if (*pszFilename == '{')
155 : {
156 0 : ++nLevel;
157 : }
158 256897 : else if (*pszFilename == '}')
159 : {
160 8382 : --nLevel;
161 8382 : if (nLevel == 0)
162 : {
163 8382 : ++pszFilename;
164 8382 : break;
165 : }
166 : }
167 248515 : osRootFilename += *pszFilename;
168 : }
169 8383 : if (nLevel == 0)
170 : {
171 8382 : ref.osRootFilenameWithBranchOrTag = osRootFilename;
172 :
173 8382 : ref.osRootFilename = GetFilenameFromDatasetName(
174 8382 : osRootFilename, ref.osBranchName, ref.osTagName);
175 8382 : if (ref.osBranchName.empty() && ref.osTagName.empty())
176 8382 : ref.osBranchName = "main";
177 :
178 8382 : ref.osKey = *pszFilename == 0 ? "/" : pszFilename;
179 : }
180 : }
181 8384 : if (ref.osRootFilename.empty())
182 : {
183 2 : CPLError(CE_Failure, CPLE_AppDefined,
184 : "Invalid %s syntax for \"%s\": should be "
185 : "%s{/path/to/some/icechunk/repo[?[branch|tag]=<name>]}[/"
186 : "optional_key]",
187 : FS_PREFIX, pszFilename, FS_PREFIX);
188 : }
189 8384 : return ref;
190 : }
191 :
192 : /************************************************************************/
193 : /* VSIIcechunkFileSystem::Load() */
194 : /************************************************************************/
195 :
196 : /** Load the repo and snapshot files associated to the passed ref.
197 : *
198 : * Uses an internal cache.
199 : */
200 : VSIIcechunkFileSystem::RepoAndSnapshot
201 8382 : VSIIcechunkFileSystem::Load(const Ref &ref)
202 : {
203 8382 : VSIIcechunkFileSystem::RepoAndSnapshot repoAndSnapshot;
204 8382 : if (!m_oCache.tryGet(ref.osRootFilenameWithBranchOrTag, repoAndSnapshot))
205 : {
206 4248 : auto repo = IcechunkRepo::Open(ref.osRootFilename.c_str());
207 2124 : if (repo)
208 : {
209 2123 : auto snapshot = !ref.osBranchName.empty()
210 2123 : ? repo->OpenSnapshotOnBranch(ref.osBranchName)
211 4246 : : repo->OpenSnapshotOnTag(ref.osTagName);
212 2123 : if (snapshot)
213 : {
214 2123 : repoAndSnapshot.first = std::move(repo);
215 2123 : repoAndSnapshot.second = std::move(snapshot);
216 2123 : m_oCache.insert(ref.osRootFilenameWithBranchOrTag,
217 : repoAndSnapshot);
218 : }
219 : }
220 : }
221 8382 : return repoAndSnapshot;
222 : }
223 :
224 : /************************************************************************/
225 : /* VSIIcechunkFileSystem::Load() */
226 : /************************************************************************/
227 :
228 : /** Load the repo and snapshot files associated to the passed filename.
229 : *
230 : * Uses an internal cache.
231 : */
232 : std::pair<VSIIcechunkFileSystem::Ref, VSIIcechunkFileSystem::RepoAndSnapshot>
233 8384 : VSIIcechunkFileSystem::Load(const char *pszFilename)
234 : {
235 16768 : const auto ref = SplitFilename(pszFilename);
236 8384 : if (ref.osRootFilename.empty())
237 2 : return {};
238 :
239 16764 : return {ref, Load(ref)};
240 : }
241 :
242 : /************************************************************************/
243 : /* GetChunkIndices() */
244 : /************************************************************************/
245 :
246 8116 : static ChunkIdx GetChunkIndices(const IcechunkSnapshot::Node &node,
247 : const char *pszChunkIndices)
248 : {
249 : const CPLStringList aosChunkIdx(
250 16232 : CSLTokenizeString2(pszChunkIndices, "/", 0));
251 16232 : ChunkIdx anChunkIdx;
252 8116 : if (static_cast<size_t>(aosChunkIdx.size()) <= node.numChunks.size())
253 : {
254 16261 : for (int i = 0; i < aosChunkIdx.size(); ++i)
255 : {
256 8153 : if (CPLGetValueType(aosChunkIdx[i]) != CPL_VALUE_INTEGER)
257 0 : return {};
258 8153 : const int nIdx = atoi(aosChunkIdx[i]);
259 8153 : if (nIdx < 0 || static_cast<unsigned>(nIdx) >= node.numChunks[i])
260 4 : return {};
261 8149 : anChunkIdx.push_back(static_cast<unsigned>(nIdx));
262 : }
263 : }
264 8112 : return anChunkIdx;
265 : }
266 :
267 : /************************************************************************/
268 : /* GetChunkRef() */
269 : /************************************************************************/
270 :
271 : static std::pair<std::shared_ptr<IcechunkManifest>,
272 : const IcechunkManifest::ChunkRef *>
273 8103 : GetChunkRef(const IcechunkRepo &repo, const IcechunkSnapshot &snapshot,
274 : const IcechunkSnapshot::Node &node, const ChunkIdx &anChunkIdx)
275 : {
276 8103 : const auto *manifestId = node.findManifestIdForChunk(anChunkIdx);
277 8103 : if (!manifestId)
278 : {
279 : // This is not necessary an error. This can happen for sparse chunks.
280 1 : return {};
281 : }
282 :
283 8102 : const auto *manifestInfo = snapshot.GetManifestInfoFromId(*manifestId);
284 8102 : if (!manifestInfo)
285 : {
286 2 : CPLError(CE_Failure, CPLE_AppDefined,
287 : "Manifest %s not referenced in snapshot %s",
288 2 : CrockfordBase32Encode(*manifestId).c_str(),
289 1 : snapshot.GetFilename().c_str());
290 1 : return {};
291 : }
292 :
293 : auto manifest =
294 8101 : repo.OpenManifest(manifestInfo->strId, manifestInfo->sizeBytes,
295 16202 : manifestInfo->numChunkRefs);
296 8101 : if (!manifest)
297 : {
298 : // OpenManifest() will have emitted an error
299 15 : return {};
300 : }
301 :
302 8086 : const auto *chunkRef = manifest->GetChunkRef(node.id, anChunkIdx);
303 8086 : return {std::move(manifest), chunkRef};
304 : }
305 :
306 : /************************************************************************/
307 : /* GetChunkFilename() */
308 : /************************************************************************/
309 :
310 4030 : static std::string GetChunkFilename(const IcechunkManifest &manifest,
311 : const IcechunkManifest::ChunkRef &chunkRef)
312 : {
313 4030 : std::string osChunkFilename;
314 4030 : if (!chunkRef.chunkId.empty())
315 : {
316 4 : osChunkFilename = manifest.GetChunkFilename(chunkRef.chunkId);
317 : }
318 : else
319 : {
320 : static const struct
321 : {
322 : const char *pszStandardPrefix;
323 : const char *pszVSIPrefix;
324 : } asPrefixes[] = {
325 : {"s3://", "/vsis3/"},
326 : {"gs://", "/vsigs/"},
327 : {"gcs://", "/vsigs/"},
328 : {"az://", "/vsiaz/"},
329 : {"azure://", "/vsiaz/"},
330 : {"http://", "/vsicurl/http://"},
331 : {"https://", "/vsicurl/https://"},
332 : };
333 :
334 4059 : for (const auto &sPrefix : asPrefixes)
335 : {
336 4056 : if (cpl::starts_with(chunkRef.location, sPrefix.pszStandardPrefix))
337 : {
338 8046 : osChunkFilename = std::string(sPrefix.pszVSIPrefix)
339 4023 : .append(chunkRef.location.substr(
340 4023 : strlen(sPrefix.pszStandardPrefix)));
341 4023 : break;
342 : }
343 : }
344 4026 : if (osChunkFilename.empty())
345 : {
346 3 : if (CPLTestBool(CPLGetConfigOption(
347 : "ICECHUNK_ALLOW_LOCAL_CHUNK_LOCATION", "NO")))
348 : {
349 1 : osChunkFilename = chunkRef.location;
350 : }
351 : else
352 : {
353 2 : CPLError(
354 : CE_Failure, CPLE_AppDefined,
355 : "Access to non-network chunk location '%s' disabled by "
356 : "default. Set the ICECHUNK_ALLOW_LOCAL_CHUNK_LOCATION "
357 : "cICECHUNK_ALLOW_LOCAL_CHUNK_LOCATION configuration option "
358 : "to YES to enable it.",
359 : chunkRef.location.c_str());
360 : }
361 : }
362 : }
363 4030 : return osChunkFilename;
364 : }
365 :
366 : /************************************************************************/
367 : /* VSIIcechunkFileSystem::GetFileInfo() */
368 : /************************************************************************/
369 :
370 : VSIIcechunkFileSystem::FileInfo
371 8366 : VSIIcechunkFileSystem::GetFileInfo(const char *pszFilename)
372 : {
373 8366 : FileInfo info;
374 :
375 16732 : auto [ref, repoAndSnapshot] = Load(pszFilename);
376 8366 : const auto &[repo, snapshot] = repoAndSnapshot;
377 8366 : if (!snapshot)
378 3 : return info;
379 :
380 : // Deal with chunk directory
381 8363 : const auto nLastCPos = ref.osKey.rfind("/c");
382 8363 : if (nLastCPos != std::string::npos)
383 : {
384 8125 : const std::string osTmpKey = ref.osKey.substr(0, nLastCPos);
385 8125 : const auto *nodePtr = snapshot->GetNodeFromPath(osTmpKey);
386 8125 : if (nodePtr && nodePtr->isArray)
387 : {
388 8123 : const auto &node = *nodePtr;
389 16246 : ChunkIdx anChunkIdx;
390 8123 : if (nLastCPos + 2 == ref.osKey.size())
391 : {
392 13 : info.bIsDir = !node.numChunks.empty();
393 : }
394 : else
395 : {
396 8110 : if (node.numChunks.empty())
397 3 : return info;
398 16214 : anChunkIdx = GetChunkIndices(
399 24321 : node, ref.osKey.substr(nLastCPos + 2).c_str());
400 : }
401 :
402 8120 : if (!info.bIsDir)
403 : {
404 8116 : if (anChunkIdx.empty() && !node.numChunks.empty())
405 : {
406 : // wrong path
407 : }
408 8110 : else if (anChunkIdx.size() < node.numChunks.size())
409 : {
410 7 : info.bIsDir = true;
411 : }
412 : else
413 : {
414 8103 : const auto [manifest, chunkRef] =
415 16206 : GetChunkRef(*repo, *snapshot, node, anChunkIdx);
416 8103 : if (chunkRef)
417 : {
418 8083 : if (chunkRef->length)
419 : {
420 : info.osFilename =
421 4030 : GetChunkFilename(*manifest, *chunkRef);
422 4030 : if (!info.osFilename.empty())
423 : {
424 4028 : info.nOffset = chunkRef->offset;
425 4028 : info.nSize = chunkRef->length;
426 : }
427 : }
428 : else
429 : {
430 4053 : info.nSize = chunkRef->inlineContent.size();
431 4053 : info.pabyData = chunkRef->inlineContent.data();
432 4053 : info.dataOwner = std::move(manifest);
433 : }
434 : }
435 : }
436 : }
437 :
438 8120 : return info;
439 : }
440 : }
441 :
442 240 : bool bIsZarrDotJson = false;
443 480 : std::string key = ref.osKey;
444 240 : if (cpl::ends_with(key, "/zarr.json"))
445 : {
446 186 : bIsZarrDotJson = true;
447 186 : key.resize(key.size() - strlen("/zarr.json"));
448 186 : if (key.empty())
449 88 : key = "/";
450 : }
451 :
452 240 : if (key == "/" && snapshot->GetNodeCount() <= 1)
453 : {
454 2 : info.bIsDir = true;
455 : }
456 238 : else if (const auto *node = snapshot->GetNodeFromPath(key))
457 : {
458 170 : if (bIsZarrDotJson)
459 : {
460 164 : info.nSize = node->content.size();
461 164 : info.pabyData = node->content.data();
462 164 : info.dataOwner = std::move(snapshot);
463 : }
464 : else
465 : {
466 6 : info.bIsDir = true;
467 : }
468 : }
469 :
470 240 : return info;
471 : }
472 :
473 : /************************************************************************/
474 : /* VSIIcechunkFileSystem::Open() */
475 : /************************************************************************/
476 :
477 : VSIVirtualHandleUniquePtr
478 159 : VSIIcechunkFileSystem::Open(const char *pszFilename, const char *pszAccess,
479 : bool /* bSetError */,
480 : CSLConstList /* papszOptions */)
481 : {
482 159 : CPLDebugOnly("VSIIcechunkFileSystem", "Open(%s)", pszFilename);
483 159 : if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "rb") != 0)
484 0 : return nullptr;
485 :
486 318 : auto info = GetFileInfo(pszFilename);
487 159 : if (!info.osFilename.empty())
488 : {
489 : CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
490 26 : "EMPTY_DIR", false);
491 :
492 : const std::string osSubfileName =
493 : CPLSPrintf("/vsisubfile/%" PRIu64 "_%" PRIu64 ",%s", info.nOffset,
494 26 : info.nSize, info.osFilename.c_str());
495 26 : auto fp = VSIFilesystemHandler::OpenStatic(osSubfileName.c_str(), "rb");
496 26 : if (fp)
497 : {
498 : VSIStatBufL sStat;
499 52 : if (VSIStatL(info.osFilename.c_str(), &sStat) != 0 ||
500 26 : info.nOffset + info.nSize >
501 26 : static_cast<uint64_t>(sStat.st_size))
502 : {
503 1 : CPLError(CE_Failure, CPLE_AppDefined,
504 : "(offset,length)=(%" PRIu64 ",%" PRIu64
505 : ") beyond %s size",
506 : info.nOffset, info.nSize, info.osFilename.c_str());
507 : }
508 : else
509 : {
510 25 : return fp;
511 : }
512 : }
513 : }
514 133 : else if (info.dataOwner)
515 : {
516 106 : const size_t nSize = static_cast<size_t>(info.nSize);
517 : if constexpr (sizeof(size_t) < sizeof(uint64_t))
518 : {
519 : // Guaranteed because info.nSize is the result of container.size()
520 : CPLAssert(nSize == info.nSize);
521 : }
522 106 : GByte *pabyData = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nSize));
523 106 : if (pabyData)
524 : {
525 106 : memcpy(pabyData, info.pabyData, nSize);
526 : return VSIVirtualHandleUniquePtr(
527 : VSIFileFromMemBuffer(nullptr, pabyData, nSize,
528 106 : /* bTakeOwnership = */ true));
529 : }
530 : }
531 :
532 28 : return nullptr;
533 : }
534 :
535 : /************************************************************************/
536 : /* VSIIcechunkFileSystem::Stat() */
537 : /************************************************************************/
538 :
539 190 : int VSIIcechunkFileSystem::Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
540 : int /* nFlags */)
541 : {
542 190 : CPLDebugOnly("VSIIcechunkFileSystem", "Stat(%s)", pszFilename);
543 190 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
544 :
545 190 : int nRet = -1;
546 190 : auto info = GetFileInfo(pszFilename);
547 190 : if (!info.osFilename.empty())
548 : {
549 : CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
550 0 : "EMPTY_DIR", false);
551 0 : nRet = VSIStatL(info.osFilename.c_str(), pStatBuf);
552 0 : if (nRet == 0)
553 : {
554 0 : if (info.nOffset + info.nSize >
555 0 : static_cast<uint64_t>(pStatBuf->st_size))
556 : {
557 0 : CPLError(CE_Failure, CPLE_AppDefined,
558 : "(offset,length)=(%" PRIu64 ",%" PRIu64
559 : ") beyond %s size",
560 : info.nOffset, info.nSize, info.osFilename.c_str());
561 0 : nRet = -1;
562 : }
563 : else
564 : {
565 0 : pStatBuf->st_size = info.nSize;
566 : }
567 : }
568 : }
569 190 : else if (info.dataOwner)
570 : {
571 106 : nRet = VSIStatL(info.dataOwner->GetFilename().c_str(), pStatBuf);
572 106 : if (nRet == 0)
573 : {
574 106 : pStatBuf->st_mode = S_IFREG;
575 106 : pStatBuf->st_size = info.nSize;
576 : }
577 : }
578 84 : else if (info.bIsDir)
579 : {
580 16 : nRet = 0;
581 16 : pStatBuf->st_mode = S_IFDIR;
582 : }
583 :
584 380 : return nRet;
585 : }
586 :
587 : /************************************************************************/
588 : /* VSIIcechunkFileSystem::GetFileMetadata() */
589 : /************************************************************************/
590 :
591 8018 : char **VSIIcechunkFileSystem::GetFileMetadata(const char *pszFilename,
592 : const char *pszDomain,
593 : CSLConstList /* papszOptions */)
594 : {
595 8018 : if (!pszDomain || !EQUAL(pszDomain, "CHUNK_INFO"))
596 1 : return nullptr;
597 :
598 16034 : CPLStringList aosMetadata;
599 :
600 16034 : auto info = GetFileInfo(pszFilename);
601 8017 : if (!info.osFilename.empty())
602 : {
603 4002 : aosMetadata.SetNameValue("SIZE", CPLSPrintf("%" PRIu64, info.nSize));
604 : aosMetadata.SetNameValue("OFFSET",
605 4002 : CPLSPrintf("%" PRIu64, info.nOffset));
606 4002 : aosMetadata.SetNameValue("FILENAME", info.osFilename.c_str());
607 : }
608 4015 : else if (info.dataOwner)
609 : {
610 4005 : aosMetadata.SetNameValue("SIZE", CPLSPrintf("%" PRIu64, info.nSize));
611 8010 : if (info.nSize <
612 4005 : static_cast<size_t>(std::numeric_limits<int>::max() - 1))
613 : {
614 : char *pszBase64 =
615 8010 : CPLBase64Encode(static_cast<int>(info.nSize),
616 4005 : static_cast<const GByte *>(info.pabyData));
617 4005 : aosMetadata.SetNameValue("BASE64", pszBase64);
618 4005 : CPLFree(pszBase64);
619 : }
620 : }
621 :
622 8017 : return aosMetadata.StealList();
623 : }
624 :
625 : /************************************************************************/
626 : /* VSIIcechunkFileSystem::ReadDirEx() */
627 : /************************************************************************/
628 :
629 18 : char **VSIIcechunkFileSystem::ReadDirEx(const char *pszDirname, int nMaxFiles)
630 : {
631 18 : CPLDebugOnly("VSIIcechunkFileSystem", "ReadDirEx(%s, %d)", pszDirname,
632 : nMaxFiles);
633 :
634 36 : auto [ref, repoAndSnapshot] = Load(pszDirname);
635 18 : const auto &[repo, snapshot] = repoAndSnapshot;
636 18 : if (!snapshot)
637 0 : return nullptr;
638 :
639 36 : CPLStringList aosFiles;
640 :
641 : // Deal with chunk directory
642 18 : const auto nLastCPos = ref.osKey.rfind("/c");
643 18 : if (nLastCPos != std::string::npos)
644 : {
645 10 : const std::string osTmpKey = ref.osKey.substr(0, nLastCPos);
646 10 : const auto *nodePtr = snapshot->GetNodeFromPath(osTmpKey);
647 10 : if (nodePtr && nodePtr->isArray)
648 : {
649 10 : const auto &node = *nodePtr;
650 :
651 10 : if (!(nLastCPos + 2 == ref.osKey.size() && node.numChunks.empty()))
652 : {
653 : auto anChunkIdx = GetChunkIndices(
654 18 : node, ref.osKey.substr(nLastCPos + 2).c_str());
655 18 : if (anChunkIdx.size() < node.numChunks.size() &&
656 9 : !(anChunkIdx.empty() && nLastCPos + 2 != ref.osKey.size()))
657 : {
658 7 : const size_t iDim = anChunkIdx.size();
659 7 : anChunkIdx.push_back(0);
660 31 : for (uint32_t i = 0; i < node.numChunks[iDim]; ++i)
661 : {
662 24 : if (iDim + 1 == node.numChunks.size())
663 : {
664 18 : anChunkIdx.back() = i;
665 18 : if (!node.findManifestIdForChunk(anChunkIdx))
666 0 : continue;
667 : }
668 24 : aosFiles.push_back(std::to_string(i));
669 24 : if (nMaxFiles > 0 && aosFiles.size() == nMaxFiles)
670 0 : break;
671 : }
672 : }
673 : }
674 :
675 10 : return aosFiles.StealList();
676 : }
677 : }
678 :
679 8 : CPLAssert(ref.osKey == "/" ||
680 : (!ref.osKey.empty() && ref.osKey.back() != '/'));
681 : const std::string refKeyWithTrailingSlash =
682 16 : ref.osKey == "/" ? ref.osKey : std::string(ref.osKey).append("/");
683 22 : for (const auto &node : snapshot->GetNodes())
684 : {
685 14 : CPLAssert(node.path == "/" ||
686 : (!node.path.empty() && node.path.back() != '/'));
687 14 : if (node.path == ref.osKey)
688 : {
689 6 : aosFiles.push_back("zarr.json");
690 6 : if (node.isArray)
691 : {
692 2 : aosFiles.push_back("c");
693 : }
694 : }
695 8 : else if (cpl::starts_with(node.path, refKeyWithTrailingSlash))
696 : {
697 : std::string dirName =
698 8 : node.path.substr(refKeyWithTrailingSlash.size());
699 4 : if (dirName.find('/') == std::string::npos)
700 : {
701 4 : aosFiles.push_back(dirName);
702 : }
703 : }
704 14 : if (nMaxFiles > 0 && aosFiles.size() == nMaxFiles)
705 0 : break;
706 : }
707 :
708 8 : return aosFiles.StealList();
709 : }
710 :
711 : /************************************************************************/
712 : /* VSIInstallIcechunkFileSystem() */
713 : /************************************************************************/
714 :
715 1872 : void VSIInstallIcechunkFileSystem()
716 : {
717 : static std::mutex oMutex;
718 3744 : std::lock_guard<std::mutex> oLock(oMutex);
719 : // cppcheck-suppress knownConditionTrueFalse
720 1872 : if (!VSIIcechunkFileSystem::IsFileSystemInstantiated())
721 : {
722 1872 : VSIFileManager::InstallHandler(
723 3744 : FS_PREFIX, std::make_shared<VSIIcechunkFileSystem>());
724 : }
725 1872 : }
726 :
727 : /************************************************************************/
728 : /* VSIIcechunkFileSystemClearCaches() */
729 : /************************************************************************/
730 :
731 1006 : void VSIIcechunkFileSystemClearCaches()
732 : {
733 0 : auto poFS = dynamic_cast<VSIIcechunkFileSystem *>(
734 1006 : VSIFileManager::GetHandler(FS_PREFIX));
735 1006 : if (poFS)
736 1006 : poFS->ClearCaches();
737 1006 : }
738 :
739 : } // namespace gdal::icechunk
|