LCOV - code coverage report
Current view: top level - frmts/icechunk - vsiicechunk.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 269 287 93.7 %
Date: 2026-07-24 18:27:47 Functions: 17 18 94.4 %

          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

Generated by: LCOV version 1.14