LCOV - code coverage report
Current view: top level - frmts/zarr - vsikerchunk_json_ref.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 713 818 87.2 %
Date: 2026-02-11 08:43:47 Functions: 39 41 95.1 %

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

Generated by: LCOV version 1.14