Coverage Report

Created: 2026-11-12 08:28

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