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