LCOV - code coverage report
Current view: top level - port - cpl_vsil_s3.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2221 2581 86.1 %
Date: 2026-06-17 14:20:39 Functions: 94 100 94.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  CPL - Common Portability Library
       4             :  * Purpose:  Implement VSI large file api for AWS S3
       5             :  * Author:   Even Rouault, even.rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2010-2018, Even Rouault <even.rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_atomic_ops.h"
      14             : #include "cpl_port.h"
      15             : #include "cpl_json.h"
      16             : #include "cpl_http.h"
      17             : #include "cpl_md5.h"
      18             : #include "cpl_minixml.h"
      19             : #include "cpl_multiproc.h"
      20             : #include "cpl_time.h"
      21             : #include "cpl_vsil_curl_priv.h"
      22             : #include "cpl_vsil_curl_class.h"
      23             : 
      24             : #include <errno.h>
      25             : 
      26             : #include <algorithm>
      27             : #include <condition_variable>
      28             : #include <functional>
      29             : #include <set>
      30             : #include <limits>
      31             : #include <map>
      32             : #include <memory>
      33             : #include <mutex>
      34             : #include <utility>
      35             : 
      36             : #include "cpl_aws.h"
      37             : 
      38             : // To avoid aliasing to GetDiskFreeSpace to GetDiskFreeSpaceA on Windows
      39             : #ifdef GetDiskFreeSpace
      40             : #undef GetDiskFreeSpace
      41             : #endif
      42             : 
      43             : #ifndef HAVE_CURL
      44             : 
      45             : void VSIInstallS3FileHandler(void)
      46             : {
      47             :     // Not supported.
      48             : }
      49             : 
      50             : #else
      51             : 
      52             : //! @cond Doxygen_Suppress
      53             : #ifndef DOXYGEN_SKIP
      54             : 
      55             : #define ENABLE_DEBUG 0
      56             : 
      57             : #define unchecked_curl_easy_setopt(handle, opt, param)                         \
      58             :     CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
      59             : 
      60             : // MebIByte
      61             : constexpr int MIB_CONSTANT = 1024 * 1024;
      62             : 
      63             : namespace cpl
      64             : {
      65             : 
      66             : /************************************************************************/
      67             : /*                               VSIDIRS3                               */
      68             : /************************************************************************/
      69             : 
      70             : struct VSIDIRS3 final : public VSIDIRS3Like
      71             : {
      72             :     bool m_bRegularListingDone = false;
      73             :     bool m_bDirectoryBucketListingDone = false;
      74             :     bool m_bListBucket = false;
      75             : 
      76          94 :     VSIDIRS3(const std::string &osDirName, IVSIS3LikeFSHandler *poFSIn)
      77          94 :         : VSIDIRS3Like(osDirName, poFSIn)
      78             :     {
      79          94 :     }
      80             : 
      81           0 :     VSIDIRS3(const std::string &osDirName, VSICurlFilesystemHandlerBase *poFSIn)
      82           0 :         : VSIDIRS3Like(osDirName, poFSIn)
      83             :     {
      84           0 :     }
      85             : 
      86             :     const VSIDIREntry *NextDirEntry() override;
      87             : 
      88             :     bool IssueListDir() override;
      89             :     bool
      90             :     AnalyseS3FileList(bool bIsListObjectV2, const std::string &osBaseURL,
      91             :                       const char *pszXML,
      92             :                       const std::set<std::string> &oSetIgnoredStorageClasses,
      93             :                       bool &bIsTruncated);
      94             : };
      95             : 
      96             : /************************************************************************/
      97             : /*                               clear()                                */
      98             : /************************************************************************/
      99             : 
     100         158 : void VSIDIRS3Like::clear()
     101             : {
     102         158 :     osNextMarker.clear();
     103         158 :     nPos = 0;
     104         158 :     aoEntries.clear();
     105         158 : }
     106             : 
     107             : /************************************************************************/
     108             : /*                   ~VSIDIRWithMissingDirSynthesis()                   */
     109             : /************************************************************************/
     110             : 
     111             : VSIDIRWithMissingDirSynthesis::~VSIDIRWithMissingDirSynthesis() = default;
     112             : 
     113             : /************************************************************************/
     114             : /*                    SynthetizeMissingDirectories()                    */
     115             : /************************************************************************/
     116             : 
     117           6 : void VSIDIRWithMissingDirSynthesis::SynthetizeMissingDirectories(
     118             :     const std::string &osCurSubdir, bool bAddEntryForThisSubdir)
     119             : {
     120           6 :     const auto nLastSlashPos = osCurSubdir.rfind('/');
     121           6 :     if (nLastSlashPos == std::string::npos)
     122             :     {
     123           6 :         m_aosSubpathsStack = {osCurSubdir};
     124             :     }
     125           3 :     else if (m_aosSubpathsStack.empty())
     126             :     {
     127           0 :         SynthetizeMissingDirectories(osCurSubdir.substr(0, nLastSlashPos),
     128             :                                      true);
     129             : 
     130           0 :         m_aosSubpathsStack.emplace_back(osCurSubdir);
     131             :     }
     132           3 :     else if (osCurSubdir.compare(0, nLastSlashPos, m_aosSubpathsStack.back()) ==
     133             :              0)
     134             :     {
     135           1 :         m_aosSubpathsStack.emplace_back(osCurSubdir);
     136             :     }
     137             :     else
     138             :     {
     139           2 :         size_t depth = 1;
     140          66 :         for (char c : osCurSubdir)
     141             :         {
     142          64 :             if (c == '/')
     143           2 :                 depth++;
     144             :         }
     145             : 
     146           4 :         while (depth <= m_aosSubpathsStack.size())
     147           2 :             m_aosSubpathsStack.pop_back();
     148             : 
     149           4 :         if (!m_aosSubpathsStack.empty() &&
     150           2 :             osCurSubdir.compare(0, nLastSlashPos, m_aosSubpathsStack.back()) !=
     151             :                 0)
     152             :         {
     153           1 :             SynthetizeMissingDirectories(osCurSubdir.substr(0, nLastSlashPos),
     154             :                                          true);
     155             :         }
     156             : 
     157           2 :         m_aosSubpathsStack.emplace_back(osCurSubdir);
     158             :     }
     159             : 
     160           6 :     if (bAddEntryForThisSubdir)
     161             :     {
     162           5 :         aoEntries.push_back(std::make_unique<VSIDIREntry>());
     163             :         // cppcheck-suppress constVariableReference
     164           5 :         auto &entry = aoEntries.back();
     165           5 :         entry->pszName = CPLStrdup(osCurSubdir.c_str());
     166           5 :         entry->nMode = S_IFDIR;
     167           5 :         entry->bModeKnown = true;
     168             :     }
     169           6 : }
     170             : 
     171             : /************************************************************************/
     172             : /*                         AnalyseS3FileList()                          */
     173             : /************************************************************************/
     174             : 
     175          67 : bool VSIDIRS3::AnalyseS3FileList(
     176             :     bool bIsListObjectV2, const std::string &osBaseURL, const char *pszXML,
     177             :     const std::set<std::string> &oSetIgnoredStorageClasses, bool &bIsTruncated)
     178             : {
     179             : #if DEBUG_VERBOSE
     180             :     const char *pszDebugPrefix = poS3FS ? poS3FS->GetDebugKey() : "S3";
     181             :     CPLDebug(pszDebugPrefix, "%s", pszXML);
     182             : #endif
     183             : 
     184          67 :     CPLXMLNode *psTree = CPLParseXMLString(pszXML);
     185          67 :     if (psTree == nullptr)
     186           0 :         return false;
     187          67 :     CPLXMLNode *psListBucketResult = CPLGetXMLNode(psTree, "=ListBucketResult");
     188             :     CPLXMLNode *psListAllMyBucketsResultBuckets =
     189             :         (psListBucketResult != nullptr)
     190          67 :             ? nullptr
     191           7 :             : CPLGetXMLNode(psTree, "=ListAllMyBucketsResult.Buckets");
     192          67 :     if (!psListBucketResult && !psListAllMyBucketsResultBuckets)
     193             :         psListAllMyBucketsResultBuckets =
     194           1 :             CPLGetXMLNode(psTree, "=ListAllMyDirectoryBucketsResult.Buckets");
     195             : 
     196          67 :     bool ret = true;
     197             : 
     198          67 :     bIsTruncated = false;
     199          67 :     if (psListBucketResult)
     200             :     {
     201          60 :         ret = false;
     202         120 :         CPLString osPrefix = CPLGetXMLValue(psListBucketResult, "Prefix", "");
     203          60 :         if (osPrefix.empty())
     204             :         {
     205             :             // in the case of an empty bucket
     206          22 :             ret = true;
     207             :         }
     208          60 :         if (osPrefix.endsWith(m_osFilterPrefix))
     209             :         {
     210          60 :             osPrefix.resize(osPrefix.size() - m_osFilterPrefix.size());
     211             :         }
     212             : 
     213          60 :         bIsTruncated = CPLTestBool(
     214             :             CPLGetXMLValue(psListBucketResult, "IsTruncated", "false"));
     215             : 
     216             :         // Count the number of occurrences of a path. Can be 1 or 2. 2 in the
     217             :         // case that both a filename and directory exist
     218         120 :         std::map<std::string, int> aoNameCount;
     219          60 :         for (CPLXMLNode *psIter = psListBucketResult->psChild;
     220         506 :              psIter != nullptr; psIter = psIter->psNext)
     221             :         {
     222         446 :             if (psIter->eType != CXT_Element)
     223          11 :                 continue;
     224         435 :             if (strcmp(psIter->pszValue, "Contents") == 0)
     225             :             {
     226         294 :                 ret = true;
     227         294 :                 const char *pszKey = CPLGetXMLValue(psIter, "Key", nullptr);
     228         294 :                 if (pszKey && strlen(pszKey) > osPrefix.size())
     229             :                 {
     230         289 :                     aoNameCount[pszKey + osPrefix.size()]++;
     231             :                 }
     232             :             }
     233         141 :             else if (strcmp(psIter->pszValue, "CommonPrefixes") == 0)
     234             :             {
     235          12 :                 const char *pszKey = CPLGetXMLValue(psIter, "Prefix", nullptr);
     236          24 :                 if (pszKey &&
     237          12 :                     strncmp(pszKey, osPrefix.c_str(), osPrefix.size()) == 0)
     238             :                 {
     239          24 :                     std::string osKey = pszKey;
     240          12 :                     if (!osKey.empty() && osKey.back() == '/')
     241           9 :                         osKey.pop_back();
     242          12 :                     if (osKey.size() > osPrefix.size())
     243             :                     {
     244          12 :                         ret = true;
     245          12 :                         aoNameCount[osKey.c_str() + osPrefix.size()]++;
     246             :                     }
     247             :                 }
     248             :             }
     249             :         }
     250             : 
     251          60 :         for (CPLXMLNode *psIter = psListBucketResult->psChild;
     252         506 :              psIter != nullptr; psIter = psIter->psNext)
     253             :         {
     254         446 :             if (psIter->eType != CXT_Element)
     255          11 :                 continue;
     256         435 :             if (strcmp(psIter->pszValue, "Contents") == 0)
     257             :             {
     258         294 :                 const char *pszKey = CPLGetXMLValue(psIter, "Key", nullptr);
     259         294 :                 if (pszKey && CPLHasUnbalancedPathTraversal(pszKey))
     260             :                 {
     261           1 :                     CPLError(
     262             :                         CE_Warning, CPLE_AppDefined,
     263             :                         "Ignoring key '%s' that has a path traversal pattern",
     264             :                         pszKey);
     265           1 :                     continue;
     266             :                 }
     267         293 :                 if (!bIsListObjectV2 && bIsTruncated && nRecurseDepth < 0 &&
     268             :                     pszKey)
     269             :                 {
     270           0 :                     osNextMarker = pszKey;
     271             :                 }
     272         293 :                 if (pszKey && strlen(pszKey) > osPrefix.size())
     273             :                 {
     274             :                     const char *pszStorageClass =
     275         288 :                         CPLGetXMLValue(psIter, "StorageClass", "");
     276         288 :                     if (oSetIgnoredStorageClasses.find(pszStorageClass) !=
     277         576 :                         oSetIgnoredStorageClasses.end())
     278             :                     {
     279           2 :                         continue;
     280             :                     }
     281             : 
     282         286 :                     const std::string osKeySuffix = pszKey + osPrefix.size();
     283         286 :                     if (m_bSynthetizeMissingDirectories)
     284             :                     {
     285          17 :                         const auto nLastSlashPos = osKeySuffix.rfind('/');
     286          29 :                         if (nLastSlashPos != std::string::npos &&
     287           7 :                             (m_aosSubpathsStack.empty() ||
     288           5 :                              osKeySuffix.compare(0, nLastSlashPos,
     289           5 :                                                  m_aosSubpathsStack.back()) !=
     290             :                                  0))
     291             :                         {
     292             :                             const bool bAddEntryForThisSubdir =
     293           5 :                                 nLastSlashPos != osKeySuffix.size() - 1;
     294           5 :                             SynthetizeMissingDirectories(
     295          10 :                                 osKeySuffix.substr(0, nLastSlashPos),
     296             :                                 bAddEntryForThisSubdir);
     297             :                         }
     298             :                     }
     299             : 
     300         286 :                     aoEntries.push_back(
     301         572 :                         std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
     302         286 :                     auto &entry = aoEntries.back();
     303         286 :                     entry->pszName = CPLStrdup(osKeySuffix.c_str());
     304         572 :                     entry->nSize = static_cast<GUIntBig>(
     305         286 :                         CPLAtoGIntBig(CPLGetXMLValue(psIter, "Size", "0")));
     306         286 :                     entry->bSizeKnown = true;
     307         286 :                     entry->nMode =
     308         286 :                         entry->pszName[0] != 0 &&
     309         286 :                                 entry->pszName[strlen(entry->pszName) - 1] ==
     310             :                                     '/'
     311         572 :                             ? S_IFDIR
     312             :                             : S_IFREG;
     313         289 :                     if (entry->nMode == S_IFDIR &&
     314         289 :                         aoNameCount[entry->pszName] < 2)
     315             :                     {
     316           3 :                         entry->pszName[strlen(entry->pszName) - 1] = 0;
     317             :                     }
     318         286 :                     entry->bModeKnown = true;
     319             : 
     320         286 :                     std::string ETag = CPLGetXMLValue(psIter, "ETag", "");
     321         286 :                     if (ETag.size() > 2 && ETag[0] == '"' && ETag.back() == '"')
     322             :                     {
     323         227 :                         ETag = ETag.substr(1, ETag.size() - 2);
     324         454 :                         entry->papszExtra = CSLSetNameValue(
     325         227 :                             entry->papszExtra, "ETag", ETag.c_str());
     326             :                     }
     327             : 
     328         286 :                     int nYear = 0;
     329         286 :                     int nMonth = 0;
     330         286 :                     int nDay = 0;
     331         286 :                     int nHour = 0;
     332         286 :                     int nMin = 0;
     333         286 :                     int nSec = 0;
     334         286 :                     if (sscanf(CPLGetXMLValue(psIter, "LastModified", ""),
     335             :                                "%04d-%02d-%02dT%02d:%02d:%02d", &nYear, &nMonth,
     336         286 :                                &nDay, &nHour, &nMin, &nSec) == 6)
     337             :                     {
     338             :                         struct tm brokendowntime;
     339         286 :                         brokendowntime.tm_year = nYear - 1900;
     340         286 :                         brokendowntime.tm_mon = nMonth - 1;
     341         286 :                         brokendowntime.tm_mday = nDay;
     342         286 :                         brokendowntime.tm_hour = nHour;
     343         286 :                         brokendowntime.tm_min = nMin;
     344         286 :                         brokendowntime.tm_sec = nSec;
     345         286 :                         entry->nMTime = CPLYMDHMSToUnixTime(&brokendowntime);
     346         286 :                         entry->bMTimeKnown = true;
     347             :                     }
     348             : 
     349         286 :                     if (nMaxFiles != 1 && bCacheEntries)
     350             :                     {
     351         566 :                         FileProp prop;
     352         283 :                         prop.nMode = entry->nMode;
     353         283 :                         prop.eExists = EXIST_YES;
     354         283 :                         prop.bHasComputedFileSize = true;
     355         283 :                         prop.fileSize = entry->nSize;
     356         283 :                         prop.bIsDirectory = (entry->nMode == S_IFDIR);
     357         283 :                         prop.mTime = static_cast<time_t>(entry->nMTime);
     358         283 :                         prop.ETag = std::move(ETag);
     359             : 
     360             :                         std::string osCachedFilename =
     361         566 :                             osBaseURL + CPLAWSURLEncode(osPrefix, false) +
     362        1132 :                             CPLAWSURLEncode(entry->pszName, false);
     363             : #if DEBUG_VERBOSE
     364             :                         CPLDebug(pszDebugPrefix, "Cache %s",
     365             :                                  osCachedFilename.c_str());
     366             : #endif
     367         283 :                         poFS->SetCachedFileProp(osCachedFilename.c_str(), prop);
     368             :                     }
     369             : 
     370         524 :                     if (nMaxFiles > 0 &&
     371         238 :                         aoEntries.size() >= static_cast<unsigned>(nMaxFiles))
     372           0 :                         break;
     373             :                 }
     374             :             }
     375         141 :             else if (strcmp(psIter->pszValue, "CommonPrefixes") == 0)
     376             :             {
     377          12 :                 const char *pszKey = CPLGetXMLValue(psIter, "Prefix", nullptr);
     378          24 :                 if (pszKey &&
     379          12 :                     strncmp(pszKey, osPrefix.c_str(), osPrefix.size()) == 0)
     380             :                 {
     381          12 :                     if (CPLHasUnbalancedPathTraversal(pszKey))
     382             :                     {
     383           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     384             :                                  "Ignoring prefix '%s' that has a path "
     385             :                                  "traversal pattern",
     386             :                                  pszKey);
     387           0 :                         continue;
     388             :                     }
     389          12 :                     std::string osKey = pszKey;
     390          12 :                     if (!osKey.empty() && osKey.back() == '/')
     391           9 :                         osKey.pop_back();
     392          12 :                     if (osKey.size() > osPrefix.size())
     393             :                     {
     394          12 :                         aoEntries.push_back(
     395          24 :                             std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
     396          12 :                         auto &entry = aoEntries.back();
     397          24 :                         entry->pszName =
     398          12 :                             CPLStrdup(osKey.c_str() + osPrefix.size());
     399          12 :                         if (aoNameCount[entry->pszName] == 2)
     400             :                         {
     401             :                             // Add a / suffix to disambiguish the situation
     402             :                             // Normally we don't suffix directories with /, but
     403             :                             // we have no alternative here
     404           2 :                             std::string osTemp(entry->pszName);
     405           2 :                             osTemp += '/';
     406           2 :                             CPLFree(entry->pszName);
     407           2 :                             entry->pszName = CPLStrdup(osTemp.c_str());
     408             :                         }
     409          12 :                         entry->nMode = S_IFDIR;
     410          12 :                         entry->bModeKnown = true;
     411             : 
     412          12 :                         if (nMaxFiles != 1 && bCacheEntries)
     413             :                         {
     414          24 :                             FileProp prop;
     415          12 :                             prop.eExists = EXIST_YES;
     416          12 :                             prop.bIsDirectory = true;
     417          12 :                             prop.bHasComputedFileSize = true;
     418          12 :                             prop.fileSize = 0;
     419          12 :                             prop.mTime = 0;
     420          12 :                             prop.nMode = S_IFDIR;
     421             : 
     422             :                             std::string osCachedFilename =
     423          24 :                                 osBaseURL + CPLAWSURLEncode(osPrefix, false) +
     424          48 :                                 CPLAWSURLEncode(entry->pszName, false);
     425             : #if DEBUG_VERBOSE
     426             :                             CPLDebug(pszDebugPrefix, "Cache %s",
     427             :                                      osCachedFilename.c_str());
     428             : #endif
     429          12 :                             poFS->SetCachedFileProp(osCachedFilename.c_str(),
     430             :                                                     prop);
     431             :                         }
     432             : 
     433          16 :                         if (nMaxFiles > 0 &&
     434           4 :                             aoEntries.size() >=
     435           4 :                                 static_cast<unsigned>(nMaxFiles))
     436           0 :                             break;
     437             :                     }
     438             :                 }
     439             :             }
     440             :         }
     441             : 
     442          60 :         if (bIsListObjectV2)
     443             :         {
     444          60 :             if (const char *pszNextContinuationToken = CPLGetXMLValue(
     445             :                     psListBucketResult, "NextContinuationToken", nullptr))
     446             :             {
     447           4 :                 osNextMarker = pszNextContinuationToken;
     448             :             }
     449             :         }
     450           0 :         else if (nRecurseDepth == 0)
     451             :         {
     452           0 :             osNextMarker = CPLGetXMLValue(psListBucketResult, "NextMarker", "");
     453             :         }
     454             :     }
     455           7 :     else if (psListAllMyBucketsResultBuckets != nullptr)
     456             :     {
     457           7 :         CPLXMLNode *psIter = psListAllMyBucketsResultBuckets->psChild;
     458          13 :         for (; psIter != nullptr; psIter = psIter->psNext)
     459             :         {
     460           6 :             if (psIter->eType != CXT_Element)
     461           0 :                 continue;
     462           6 :             if (strcmp(psIter->pszValue, "Bucket") == 0)
     463             :             {
     464           6 :                 const char *pszName = CPLGetXMLValue(psIter, "Name", nullptr);
     465           6 :                 if (pszName)
     466             :                 {
     467           6 :                     if (CPLHasUnbalancedPathTraversal(pszName))
     468             :                     {
     469           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     470             :                                  "Ignoring bucket name '%s' that has a path "
     471             :                                  "traversal pattern",
     472             :                                  pszName);
     473           0 :                         continue;
     474             :                     }
     475           6 :                     aoEntries.push_back(std::make_unique<VSIDIREntry>());
     476             :                     // cppcheck-suppress constVariableReference
     477           6 :                     auto &entry = aoEntries.back();
     478           6 :                     entry->pszName = CPLStrdup(pszName);
     479           6 :                     entry->nMode = S_IFDIR;
     480           6 :                     entry->bModeKnown = true;
     481             : 
     482           6 :                     if (nMaxFiles != 1 && bCacheEntries)
     483             :                     {
     484          12 :                         FileProp prop;
     485           6 :                         prop.eExists = EXIST_YES;
     486           6 :                         prop.bIsDirectory = true;
     487           6 :                         prop.bHasComputedFileSize = true;
     488           6 :                         prop.fileSize = 0;
     489           6 :                         prop.mTime = 0;
     490           6 :                         prop.nMode = S_IFDIR;
     491             : 
     492             :                         std::string osCachedFilename =
     493          18 :                             osBaseURL + CPLAWSURLEncode(pszName, false);
     494             : #if DEBUG_VERBOSE
     495             :                         CPLDebug(pszDebugPrefix, "Cache %s",
     496             :                                  osCachedFilename.c_str());
     497             : #endif
     498           6 :                         poFS->SetCachedFileProp(osCachedFilename.c_str(), prop);
     499             :                     }
     500             :                 }
     501             :             }
     502             :         }
     503             :     }
     504             : 
     505          67 :     CPLDestroyXMLNode(psTree);
     506          67 :     return ret;
     507             : }
     508             : 
     509             : /************************************************************************/
     510             : /*                            IssueListDir()                            */
     511             : /************************************************************************/
     512             : 
     513         101 : bool VSIDIRS3::IssueListDir()
     514             : {
     515         202 :     CPLString osMaxKeys = CPLGetConfigOption("AWS_MAX_KEYS", "");
     516         132 :     if (nMaxFiles > 0 && nMaxFiles <= 100 &&
     517          31 :         (osMaxKeys.empty() || nMaxFiles < atoi(osMaxKeys)))
     518             :     {
     519          31 :         osMaxKeys.Printf("%d", nMaxFiles);
     520             :     }
     521             : 
     522         202 :     NetworkStatisticsFileSystem oContextFS(poS3FS->GetFSPrefix().c_str());
     523         202 :     NetworkStatisticsAction oContextAction("ListBucket");
     524             : 
     525         202 :     const std::string l_osNextMarker(osNextMarker);
     526         101 :     clear();
     527             : 
     528         101 :     IVSIS3LikeHandleHelper *l_poHandlerHelper = poHandleHelper.get();
     529             : 
     530             :     auto poS3HandleHelper =
     531         101 :         dynamic_cast<VSIS3HandleHelper *>(poHandleHelper.get());
     532             :     const bool bUseListObjectsV2 =
     533         110 :         ((!osBucket.empty() || !m_osFilterPrefix.empty()) &&
     534          92 :          EQUAL(CPLGetConfigOption("CPL_VSIS3_LIST_OBJECTS_VERSION", "2"),
     535         208 :                "2")) ||
     536           6 :         (poS3HandleHelper && poS3HandleHelper->IsDirectoryBucket());
     537             : 
     538         101 :     std::unique_ptr<VSIS3HandleHelper> poTmpHandleHelper;
     539         101 :     if (m_bRegularListingDone && m_bListBucket)
     540             :     {
     541           3 :         const char *const apszOptions[] = {"LIST_DIRECTORY_BUCKETS=YES",
     542             :                                            nullptr};
     543           3 :         poTmpHandleHelper.reset(VSIS3HandleHelper::BuildFromURI(
     544           6 :             nullptr, poS3FS->GetFSPrefix().c_str(), true, apszOptions));
     545           3 :         l_poHandlerHelper = poTmpHandleHelper.get();
     546             :     }
     547             : 
     548             :     const CPLStringList aosHTTPOptions(
     549         202 :         CPLHTTPGetOptionsFromEnv(m_osDirName.c_str()));
     550             : 
     551             :     while (true)
     552             :     {
     553         109 :         l_poHandlerHelper->ResetQueryParameters();
     554         109 :         const std::string osBaseURL(l_poHandlerHelper->GetURL());
     555         109 :         if (bUseListObjectsV2)
     556         100 :             l_poHandlerHelper->AddQueryParameter("list-type", "2");
     557             : 
     558         109 :         CURL *hCurlHandle = curl_easy_init();
     559             : 
     560         109 :         if (!osBucket.empty())
     561             :         {
     562         100 :             if (nRecurseDepth == 0)
     563          84 :                 l_poHandlerHelper->AddQueryParameter("delimiter", "/");
     564         100 :             if (!l_osNextMarker.empty())
     565           4 :                 l_poHandlerHelper->AddQueryParameter(
     566             :                     bUseListObjectsV2 ? "continuation-token" : "marker",
     567             :                     l_osNextMarker);
     568         100 :             if (!osMaxKeys.empty())
     569          31 :                 l_poHandlerHelper->AddQueryParameter("max-keys", osMaxKeys);
     570         100 :             if (!osObjectKey.empty())
     571          62 :                 l_poHandlerHelper->AddQueryParameter(
     572         124 :                     "prefix", osObjectKey + "/" + m_osFilterPrefix);
     573          38 :             else if (!m_osFilterPrefix.empty())
     574           1 :                 l_poHandlerHelper->AddQueryParameter("prefix",
     575           1 :                                                      m_osFilterPrefix);
     576             :         }
     577             : 
     578             :         struct curl_slist *headers =
     579         109 :             VSICurlSetOptions(hCurlHandle, l_poHandlerHelper->GetURL().c_str(),
     580             :                               aosHTTPOptions.List());
     581             : 
     582         109 :         headers = l_poHandlerHelper->GetCurlHeaders("GET", headers);
     583             :         // Disable automatic redirection
     584         109 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
     585             : 
     586         109 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, nullptr);
     587             : 
     588         109 :         CurlRequestHelper requestHelper;
     589         109 :         const long response_code = requestHelper.perform(
     590             :             hCurlHandle, headers, poFS, l_poHandlerHelper);
     591             : 
     592         109 :         NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
     593             : 
     594         109 :         if (response_code != 200 ||
     595          78 :             requestHelper.sWriteFuncData.pBuffer == nullptr)
     596             :         {
     597          53 :             if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
     598          11 :                 l_poHandlerHelper->CanRestartOnError(
     599          11 :                     requestHelper.sWriteFuncData.pBuffer,
     600          11 :                     requestHelper.sWriteFuncHeaderData.pBuffer, true))
     601             :             {
     602             :                 // nothing to do
     603             :             }
     604             :             else
     605             :             {
     606          34 :                 if (m_bRegularListingDone && m_bListBucket)
     607             :                 {
     608           2 :                     CPLDebug(poS3FS->GetDebugKey(),
     609             :                              "ListDirectoryBuckets failed: %s",
     610           2 :                              requestHelper.sWriteFuncData.pBuffer
     611             :                                  ? requestHelper.sWriteFuncData.pBuffer
     612             :                                  : "(null)");
     613             :                 }
     614             :                 else
     615             :                 {
     616          32 :                     CPLDebug(poS3FS->GetDebugKey(), "%s",
     617          32 :                              requestHelper.sWriteFuncData.pBuffer
     618             :                                  ? requestHelper.sWriteFuncData.pBuffer
     619             :                                  : "(null)");
     620             :                 }
     621          34 :                 curl_easy_cleanup(hCurlHandle);
     622          34 :                 return false;
     623             :             }
     624             :         }
     625             :         else
     626             :         {
     627             :             bool bIsTruncated;
     628         134 :             bool ret = AnalyseS3FileList(
     629             :                 bUseListObjectsV2, osBaseURL,
     630          67 :                 requestHelper.sWriteFuncData.pBuffer,
     631         134 :                 VSICurlFilesystemHandlerBase::GetS3IgnoredStorageClasses(),
     632             :                 bIsTruncated);
     633             : 
     634          67 :             curl_easy_cleanup(hCurlHandle);
     635          67 :             return ret;
     636             :         }
     637             : 
     638           8 :         curl_easy_cleanup(hCurlHandle);
     639           8 :     }
     640             : }
     641             : 
     642             : /************************************************************************/
     643             : /*                            NextDirEntry()                            */
     644             : /************************************************************************/
     645             : 
     646         372 : const VSIDIREntry *VSIDIRS3::NextDirEntry()
     647             : {
     648         372 :     if (!m_bRegularListingDone)
     649             :     {
     650         371 :         const auto psRet = VSIDIRS3Like::NextDirEntry();
     651         371 :         if (psRet)
     652         311 :             return psRet;
     653          60 :         m_bRegularListingDone = true;
     654          60 :         if (!m_bListBucket)
     655          57 :             return nullptr;
     656           3 :         clear();
     657           3 :         m_subdir.reset();
     658             : 
     659           3 :         if (!IssueListDir())
     660             :         {
     661           2 :             return nullptr;
     662             :         }
     663             :     }
     664           2 :     if (!m_bDirectoryBucketListingDone)
     665             :     {
     666           2 :         const auto psRet = VSIDIRS3Like::NextDirEntry();
     667           2 :         if (psRet)
     668           1 :             return psRet;
     669           1 :         m_bDirectoryBucketListingDone = true;
     670             :     }
     671           1 :     return nullptr;
     672             : }
     673             : 
     674             : /************************************************************************/
     675             : /*                            NextDirEntry()                            */
     676             : /************************************************************************/
     677             : 
     678         421 : const VSIDIREntry *VSIDIRS3Like::NextDirEntry()
     679             : {
     680         421 :     constexpr int ARBITRARY_LIMIT = 10;
     681         440 :     for (int i = 0; i < ARBITRARY_LIMIT; ++i)
     682             :     {
     683         439 :         if (nPos < static_cast<int>(aoEntries.size()))
     684             :         {
     685         338 :             auto &entry = aoEntries[nPos];
     686         338 :             if (osBucket.empty())
     687             :             {
     688          15 :                 if (m_subdir)
     689             :                 {
     690           5 :                     if (auto subentry = m_subdir->NextDirEntry())
     691             :                     {
     692           6 :                         const std::string name = std::string(entry->pszName)
     693           3 :                                                      .append("/")
     694           3 :                                                      .append(subentry->pszName);
     695           3 :                         CPLFree(const_cast<VSIDIREntry *>(subentry)->pszName);
     696           3 :                         const_cast<VSIDIREntry *>(subentry)->pszName =
     697           3 :                             CPLStrdup(name.c_str());
     698           3 :                         return subentry;
     699             :                     }
     700           2 :                     m_subdir.reset();
     701           2 :                     nPos++;
     702           2 :                     continue;
     703             :                 }
     704          10 :                 else if (nRecurseDepth != 0)
     705             :                 {
     706           2 :                     m_subdir.reset(VSIOpenDir(std::string(poFS->GetFSPrefix())
     707           2 :                                                   .append(entry->pszName)
     708             :                                                   .c_str(),
     709           2 :                                               nRecurseDepth - 1, nullptr));
     710           2 :                     if (m_subdir)
     711           2 :                         return entry.get();
     712             :                 }
     713             :             }
     714         331 :             nPos++;
     715         331 :             return entry.get();
     716             :         }
     717         101 :         if (osNextMarker.empty())
     718             :         {
     719          84 :             return nullptr;
     720             :         }
     721          17 :         if (!IssueListDir())
     722             :         {
     723           0 :             return nullptr;
     724             :         }
     725             :     }
     726           1 :     CPLError(CE_Failure, CPLE_AppDefined,
     727             :              "More than %d consecutive List Blob "
     728             :              "requests returning no blobs",
     729             :              ARBITRARY_LIMIT);
     730           1 :     return nullptr;
     731             : }
     732             : 
     733             : /************************************************************************/
     734             : /*                         AnalyseS3FileList()                          */
     735             : /************************************************************************/
     736             : 
     737           0 : bool VSICurlFilesystemHandlerBase::AnalyseS3FileList(
     738             :     const std::string &osBaseURL, const char *pszXML, CPLStringList &osFileList,
     739             :     int nMaxFiles, const std::set<std::string> &oSetIgnoredStorageClasses,
     740             :     bool &bIsTruncated)
     741             : {
     742           0 :     VSIDIRS3 oDir(std::string(), this);
     743           0 :     oDir.nMaxFiles = nMaxFiles;
     744             :     bool ret =
     745           0 :         oDir.AnalyseS3FileList(/* bUseListObjectsV2 = */ false, osBaseURL,
     746             :                                pszXML, oSetIgnoredStorageClasses, bIsTruncated);
     747           0 :     for (const auto &entry : oDir.aoEntries)
     748             :     {
     749           0 :         osFileList.AddString(entry->pszName);
     750             :     }
     751           0 :     return ret;
     752             : }
     753             : 
     754             : /************************************************************************/
     755             : /*                            VSIS3FSHandler                            */
     756             : /************************************************************************/
     757             : 
     758             : class VSIS3FSHandler final : public IVSIS3LikeFSHandlerWithMultipartUpload
     759             : {
     760             :     CPL_DISALLOW_COPY_ASSIGN(VSIS3FSHandler)
     761             : 
     762             :     const std::string m_osPrefix;
     763             :     std::set<std::string> DeleteObjects(const char *pszBucket,
     764             :                                         const char *pszXML);
     765             : 
     766             :   protected:
     767             :     VSICurlHandle *CreateFileHandle(const char *pszFilename) override;
     768             :     std::string
     769             :     GetURLFromFilename(const std::string &osFilename) const override;
     770             : 
     771         386 :     const char *GetDebugKey() const override
     772             :     {
     773         386 :         return "S3";
     774             :     }
     775             : 
     776             :     IVSIS3LikeHandleHelper *CreateHandleHelper(const char *pszURI,
     777             :                                                bool bAllowNoObject) override;
     778             : 
     779       23359 :     std::string GetFSPrefix() const override
     780             :     {
     781       23359 :         return m_osPrefix;
     782             :     }
     783             : 
     784             :     void ClearCache() override;
     785             : 
     786          16 :     bool IsAllowedHeaderForObjectCreation(const char *pszHeaderName) override
     787             :     {
     788          16 :         return STARTS_WITH(pszHeaderName, "x-amz-");
     789             :     }
     790             : 
     791             :     VSIVirtualHandleUniquePtr
     792             :     CreateWriteHandle(const char *pszFilename,
     793             :                       CSLConstList papszOptions) override;
     794             : 
     795             :   public:
     796        2098 :     explicit VSIS3FSHandler(const char *pszPrefix) : m_osPrefix(pszPrefix)
     797             :     {
     798        2098 :     }
     799             : 
     800             :     ~VSIS3FSHandler() override;
     801             : 
     802             :     const char *GetOptions() override;
     803             : 
     804             :     char *GetSignedURL(const char *pszFilename,
     805             :                        CSLConstList papszOptions) override;
     806             : 
     807             :     int *UnlinkBatch(CSLConstList papszFiles) override;
     808             : 
     809           2 :     int *DeleteObjectBatch(CSLConstList papszFilesOrDirs) override
     810             :     {
     811           2 :         return UnlinkBatch(papszFilesOrDirs);
     812             :     }
     813             : 
     814             :     int RmdirRecursive(const char *pszDirname) override;
     815             : 
     816             :     char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
     817             :                            CSLConstList papszOptions) override;
     818             : 
     819             :     bool SetFileMetadata(const char *pszFilename, CSLConstList papszMetadata,
     820             :                          const char *pszDomain,
     821             :                          CSLConstList papszOptions) override;
     822             : 
     823             :     std::string
     824             :     GetStreamingFilename(const std::string &osFilename) const override;
     825             : 
     826           0 :     VSIFilesystemHandler *Duplicate(const char *pszPrefix) override
     827             :     {
     828           0 :         return new VSIS3FSHandler(pszPrefix);
     829             :     }
     830             : 
     831           1 :     bool SupportsMultipartAbort() const override
     832             :     {
     833           1 :         return true;
     834             :     }
     835             : 
     836           0 :     GIntBig GetDiskFreeSpace(const char * /* pszDirname */) override
     837             :     {
     838             :         // There is no limit per bucket, but a 5 TiB limit per object.
     839           0 :         return static_cast<GIntBig>(5) * 1024 * 1024 * 1024 * 1024;
     840             :     }
     841             : 
     842             :     std::string
     843        6012 :     GetHintForPotentiallyRecognizedPath(const std::string &osPath) override
     844             :     {
     845       30031 :         if (!cpl::starts_with(osPath, m_osPrefix) &&
     846       12015 :             !cpl::starts_with(osPath, GetStreamingFilename(m_osPrefix)) &&
     847        5996 :             cpl::starts_with(osPath, "s3://"))
     848             :         {
     849           2 :             return GetFSPrefix() + osPath.substr(strlen("s3://"));
     850             :         }
     851        6006 :         return std::string();
     852             :     }
     853             : };
     854             : 
     855             : /************************************************************************/
     856             : /*                             VSIS3Handle                              */
     857             : /************************************************************************/
     858             : 
     859             : IVSIS3LikeHandle::~IVSIS3LikeHandle() = default;
     860             : 
     861             : class VSIS3Handle final : public IVSIS3LikeHandle
     862             : {
     863             :     CPL_DISALLOW_COPY_ASSIGN(VSIS3Handle)
     864             : 
     865             :     VSIS3HandleHelper *m_poS3HandleHelper = nullptr;
     866             : 
     867             :   protected:
     868             :     struct curl_slist *GetCurlHeaders(const std::string &osVerb,
     869             :                                       struct curl_slist *psHeaders) override;
     870             :     bool CanRestartOnError(const char *, const char *, bool) override;
     871             : 
     872         169 :     bool AllowAutomaticRedirection() override
     873             :     {
     874         169 :         return m_poS3HandleHelper->AllowAutomaticRedirection();
     875             :     }
     876             : 
     877             :   public:
     878             :     VSIS3Handle(VSIS3FSHandler *poFS, const char *pszFilename,
     879             :                 VSIS3HandleHelper *poS3HandleHelper);
     880             :     ~VSIS3Handle() override;
     881             : };
     882             : 
     883             : /************************************************************************/
     884             : /*                      VSIMultipartWriteHandle()                       */
     885             : /************************************************************************/
     886             : 
     887          31 : VSIMultipartWriteHandle::VSIMultipartWriteHandle(
     888             :     IVSIS3LikeFSHandlerWithMultipartUpload *poFS, const char *pszFilename,
     889          31 :     IVSIS3LikeHandleHelper *poS3HandleHelper, CSLConstList papszOptions)
     890             :     : m_poFS(poFS), m_osFilename(pszFilename),
     891             :       m_poS3HandleHelper(poS3HandleHelper), m_aosOptions(papszOptions),
     892             :       m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)),
     893          31 :       m_oRetryParameters(m_aosHTTPOptions)
     894             : {
     895             :     // AWS S3, OSS and GCS can use the multipart upload mechanism, which has
     896             :     // the advantage of being retryable in case of errors.
     897             :     // Swift only supports the "Transfer-Encoding: chunked" PUT mechanism.
     898             :     // So two different implementations.
     899             : 
     900          31 :     const char *pszChunkSize = m_aosOptions.FetchNameValue("CHUNK_SIZE");
     901          31 :     if (pszChunkSize)
     902           0 :         m_nBufferSize = poFS->GetUploadChunkSizeInBytes(
     903           0 :             pszFilename, CPLSPrintf(CPL_FRMT_GIB, CPLAtoGIntBig(pszChunkSize) *
     904             :                                                       MIB_CONSTANT));
     905             :     else
     906          31 :         m_nBufferSize = poFS->GetUploadChunkSizeInBytes(pszFilename, nullptr);
     907             : 
     908          31 :     m_pabyBuffer = static_cast<GByte *>(VSIMalloc(m_nBufferSize));
     909          31 :     if (m_pabyBuffer == nullptr)
     910             :     {
     911           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     912             :                  "Cannot allocate working buffer for %s",
     913           0 :                  m_poFS->GetFSPrefix().c_str());
     914             :     }
     915          31 : }
     916             : 
     917             : /************************************************************************/
     918             : /*                     GetUploadChunkSizeInBytes()                      */
     919             : /************************************************************************/
     920             : 
     921          47 : size_t IVSIS3LikeFSHandlerWithMultipartUpload::GetUploadChunkSizeInBytes(
     922             :     const char *pszFilename, const char *pszSpecifiedValInBytes)
     923             : {
     924             :     const char *pszChunkSizeBytes =
     925          47 :         pszSpecifiedValInBytes ? pszSpecifiedValInBytes :
     926             :                                // For testing only !
     927          44 :             VSIGetPathSpecificOption(pszFilename,
     928          91 :                                      std::string("VSI")
     929          44 :                                          .append(GetDebugKey())
     930          44 :                                          .append("_CHUNK_SIZE_BYTES")
     931             :                                          .c_str(),
     932          47 :                                      nullptr);
     933          47 :     if (pszChunkSizeBytes)
     934             :     {
     935           4 :         const auto nChunkSizeInt = CPLAtoGIntBig(pszChunkSizeBytes);
     936           4 :         if (nChunkSizeInt <= 0)
     937             :         {
     938           0 :             return static_cast<size_t>(GetDefaultPartSizeInMiB()) *
     939           0 :                    MIB_CONSTANT;
     940             :         }
     941           4 :         else if (nChunkSizeInt >
     942           4 :                  static_cast<int64_t>(GetMaximumPartSizeInMiB()) * MIB_CONSTANT)
     943             :         {
     944           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     945             :                      "Specified chunk size too large. Clamping to %d MiB",
     946           0 :                      GetMaximumPartSizeInMiB());
     947           0 :             return static_cast<size_t>(GetMaximumPartSizeInMiB()) *
     948           0 :                    MIB_CONSTANT;
     949             :         }
     950             :         else
     951           4 :             return static_cast<size_t>(nChunkSizeInt);
     952             :     }
     953             :     else
     954             :     {
     955          86 :         const int nChunkSizeMiB = atoi(VSIGetPathSpecificOption(
     956             :             pszFilename,
     957          86 :             std::string("VSI")
     958          43 :                 .append(GetDebugKey())
     959          43 :                 .append("_CHUNK_SIZE")
     960             :                 .c_str(),
     961          43 :             CPLSPrintf("%d", GetDefaultPartSizeInMiB())));
     962          43 :         if (nChunkSizeMiB <= 0)
     963           0 :             return static_cast<size_t>(GetDefaultPartSizeInMiB()) *
     964           0 :                    MIB_CONSTANT;
     965          43 :         else if (nChunkSizeMiB > GetMaximumPartSizeInMiB())
     966             :         {
     967           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     968             :                      "Specified chunk size too large. Clamping to %d MiB",
     969           0 :                      GetMaximumPartSizeInMiB());
     970           0 :             return static_cast<size_t>(GetMaximumPartSizeInMiB()) *
     971           0 :                    MIB_CONSTANT;
     972             :         }
     973             :         else
     974          43 :             return static_cast<size_t>(nChunkSizeMiB) * MIB_CONSTANT;
     975             :     }
     976             : }
     977             : 
     978             : /************************************************************************/
     979             : /*                      ~VSIMultipartWriteHandle()                      */
     980             : /************************************************************************/
     981             : 
     982          62 : VSIMultipartWriteHandle::~VSIMultipartWriteHandle()
     983             : {
     984          31 :     VSIMultipartWriteHandle::Close();
     985          31 :     delete m_poS3HandleHelper;
     986          31 :     CPLFree(m_pabyBuffer);
     987          31 :     CPLFree(m_sWriteFuncHeaderData.pBuffer);
     988          62 : }
     989             : 
     990             : /************************************************************************/
     991             : /*                                Seek()                                */
     992             : /************************************************************************/
     993             : 
     994          14 : int VSIMultipartWriteHandle::Seek(vsi_l_offset nOffset, int nWhence)
     995             : {
     996          14 :     if (!((nWhence == SEEK_SET && nOffset == m_nCurOffset) ||
     997           3 :           (nWhence == SEEK_CUR && nOffset == 0) ||
     998           3 :           (nWhence == SEEK_END && nOffset == 0)))
     999             :     {
    1000           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    1001             :                  "Seek not supported on writable %s files",
    1002           4 :                  m_poFS->GetFSPrefix().c_str());
    1003           2 :         m_bError = true;
    1004           2 :         return -1;
    1005             :     }
    1006          12 :     return 0;
    1007             : }
    1008             : 
    1009             : /************************************************************************/
    1010             : /*                                Tell()                                */
    1011             : /************************************************************************/
    1012             : 
    1013           6 : vsi_l_offset VSIMultipartWriteHandle::Tell()
    1014             : {
    1015           6 :     return m_nCurOffset;
    1016             : }
    1017             : 
    1018             : /************************************************************************/
    1019             : /*                                Read()                                */
    1020             : /************************************************************************/
    1021             : 
    1022           2 : size_t VSIMultipartWriteHandle::Read(void * /* pBuffer */, size_t /* nBytes */)
    1023             : {
    1024           2 :     CPLError(CE_Failure, CPLE_NotSupported,
    1025             :              "Read not supported on writable %s files",
    1026           4 :              m_poFS->GetFSPrefix().c_str());
    1027           2 :     m_bError = true;
    1028           2 :     return 0;
    1029             : }
    1030             : 
    1031             : /************************************************************************/
    1032             : /*                      InitiateMultipartUpload()                       */
    1033             : /************************************************************************/
    1034             : 
    1035          11 : std::string IVSIS3LikeFSHandlerWithMultipartUpload::InitiateMultipartUpload(
    1036             :     const std::string &osFilename, IVSIS3LikeHandleHelper *poS3HandleHelper,
    1037             :     const CPLHTTPRetryParameters &oRetryParameters, CSLConstList papszOptions)
    1038             : {
    1039          22 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    1040          22 :     NetworkStatisticsFile oContextFile(osFilename.c_str());
    1041          22 :     NetworkStatisticsAction oContextAction("InitiateMultipartUpload");
    1042             : 
    1043             :     const CPLStringList aosHTTPOptions(
    1044          22 :         CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
    1045             : 
    1046          11 :     std::string osUploadID;
    1047             :     bool bRetry;
    1048          22 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    1049          11 :     do
    1050             :     {
    1051          11 :         bRetry = false;
    1052          11 :         CURL *hCurlHandle = curl_easy_init();
    1053          11 :         poS3HandleHelper->AddQueryParameter("uploads", "");
    1054          11 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "POST");
    1055             : 
    1056             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    1057          11 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    1058             :                               aosHTTPOptions.List()));
    1059          11 :         headers = VSICurlSetCreationHeadersFromOptions(headers, papszOptions,
    1060             :                                                        osFilename.c_str());
    1061          11 :         headers = poS3HandleHelper->GetCurlHeaders("POST", headers);
    1062          11 :         headers = curl_slist_append(
    1063             :             headers, "Content-Length: 0");  // Required by GCS in HTTP 1.1
    1064             : 
    1065          22 :         CurlRequestHelper requestHelper;
    1066             :         const long response_code =
    1067          11 :             requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
    1068             : 
    1069          11 :         NetworkStatisticsLogger::LogPOST(0, requestHelper.sWriteFuncData.nSize);
    1070             : 
    1071          11 :         if (response_code != 200 ||
    1072           8 :             requestHelper.sWriteFuncData.pBuffer == nullptr)
    1073             :         {
    1074             :             // Look if we should attempt a retry
    1075           3 :             if (oRetryContext.CanRetry(
    1076             :                     static_cast<int>(response_code),
    1077           3 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    1078             :                     requestHelper.szCurlErrBuf))
    1079             :             {
    1080           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1081             :                          "HTTP error code: %d - %s. "
    1082             :                          "Retrying again in %.1f secs",
    1083             :                          static_cast<int>(response_code),
    1084           0 :                          poS3HandleHelper->GetURL().c_str(),
    1085             :                          oRetryContext.GetCurrentDelay());
    1086           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    1087           0 :                 bRetry = true;
    1088             :             }
    1089           3 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
    1090           0 :                      poS3HandleHelper->CanRestartOnError(
    1091           0 :                          requestHelper.sWriteFuncData.pBuffer,
    1092           0 :                          requestHelper.sWriteFuncHeaderData.pBuffer, true))
    1093             :             {
    1094           0 :                 bRetry = true;
    1095             :             }
    1096             :             else
    1097             :             {
    1098           3 :                 CPLDebug(GetDebugKey(), "%s",
    1099           3 :                          requestHelper.sWriteFuncData.pBuffer
    1100             :                              ? requestHelper.sWriteFuncData.pBuffer
    1101             :                              : "(null)");
    1102           3 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1103             :                          "InitiateMultipartUpload of %s failed",
    1104             :                          osFilename.c_str());
    1105             :             }
    1106             :         }
    1107             :         else
    1108             :         {
    1109           8 :             InvalidateCachedData(poS3HandleHelper->GetURL().c_str());
    1110           8 :             InvalidateDirContent(CPLGetDirnameSafe(osFilename.c_str()));
    1111             : 
    1112             :             CPLXMLNode *psNode =
    1113           8 :                 CPLParseXMLString(requestHelper.sWriteFuncData.pBuffer);
    1114           8 :             if (psNode)
    1115             :             {
    1116             :                 osUploadID = CPLGetXMLValue(
    1117           8 :                     psNode, "=InitiateMultipartUploadResult.UploadId", "");
    1118           8 :                 CPLDebug(GetDebugKey(), "UploadId: %s", osUploadID.c_str());
    1119           8 :                 CPLDestroyXMLNode(psNode);
    1120             :             }
    1121           8 :             if (osUploadID.empty())
    1122             :             {
    1123           0 :                 CPLError(
    1124             :                     CE_Failure, CPLE_AppDefined,
    1125             :                     "InitiateMultipartUpload of %s failed: cannot get UploadId",
    1126             :                     osFilename.c_str());
    1127             :             }
    1128             :         }
    1129             : 
    1130          11 :         curl_easy_cleanup(hCurlHandle);
    1131             :     } while (bRetry);
    1132          22 :     return osUploadID;
    1133             : }
    1134             : 
    1135             : /************************************************************************/
    1136             : /*                             UploadPart()                             */
    1137             : /************************************************************************/
    1138             : 
    1139           2 : bool VSIMultipartWriteHandle::UploadPart()
    1140             : {
    1141           2 :     ++m_nPartNumber;
    1142           2 :     if (m_nPartNumber > m_poFS->GetMaximumPartCount())
    1143             :     {
    1144           0 :         m_bError = true;
    1145           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1146             :                  "%d parts have been uploaded for %s failed. "
    1147             :                  "This is the maximum. "
    1148             :                  "Increase VSI%s_CHUNK_SIZE to a higher value (e.g. 500 for "
    1149             :                  "500 MiB)",
    1150           0 :                  m_poFS->GetMaximumPartCount(), m_osFilename.c_str(),
    1151           0 :                  m_poFS->GetDebugKey());
    1152           0 :         return false;
    1153             :     }
    1154           2 :     const std::string osEtag = m_poFS->UploadPart(
    1155           2 :         m_osFilename, m_nPartNumber, m_osUploadID,
    1156           2 :         static_cast<vsi_l_offset>(m_nBufferSize) * (m_nPartNumber - 1),
    1157           2 :         m_pabyBuffer, m_nBufferOff, m_poS3HandleHelper, m_oRetryParameters,
    1158           2 :         nullptr);
    1159           2 :     m_nBufferOff = 0;
    1160           2 :     if (!osEtag.empty())
    1161             :     {
    1162           2 :         m_aosEtags.push_back(osEtag);
    1163             :     }
    1164           2 :     return !osEtag.empty();
    1165             : }
    1166             : 
    1167          16 : std::string IVSIS3LikeFSHandlerWithMultipartUpload::UploadPart(
    1168             :     const std::string &osFilename, int nPartNumber,
    1169             :     const std::string &osUploadID, vsi_l_offset /* nPosition */,
    1170             :     const void *pabyBuffer, size_t nBufferSize,
    1171             :     IVSIS3LikeHandleHelper *poS3HandleHelper,
    1172             :     const CPLHTTPRetryParameters &oRetryParameters,
    1173             :     CSLConstList /* papszOptions */)
    1174             : {
    1175          32 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    1176          32 :     NetworkStatisticsFile oContextFile(osFilename.c_str());
    1177          32 :     NetworkStatisticsAction oContextAction("UploadPart");
    1178             : 
    1179             :     bool bRetry;
    1180          32 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    1181          16 :     std::string osEtag;
    1182             : 
    1183             :     const CPLStringList aosHTTPOptions(
    1184          32 :         CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
    1185             : 
    1186          16 :     do
    1187             :     {
    1188          16 :         bRetry = false;
    1189             : 
    1190          16 :         CURL *hCurlHandle = curl_easy_init();
    1191          16 :         poS3HandleHelper->AddQueryParameter("partNumber",
    1192             :                                             CPLSPrintf("%d", nPartNumber));
    1193          16 :         poS3HandleHelper->AddQueryParameter("uploadId", osUploadID);
    1194          16 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
    1195          16 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
    1196             :                                    PutData::ReadCallBackBuffer);
    1197          16 :         PutData putData;
    1198          16 :         putData.pabyData = static_cast<const GByte *>(pabyBuffer);
    1199          16 :         putData.nOff = 0;
    1200          16 :         putData.nTotalSize = nBufferSize;
    1201          16 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
    1202          16 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
    1203             :                                    nBufferSize);
    1204             : 
    1205             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    1206          16 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    1207             :                               aosHTTPOptions));
    1208          16 :         headers = poS3HandleHelper->GetCurlHeaders("PUT", headers, pabyBuffer,
    1209          16 :                                                    nBufferSize);
    1210             : 
    1211          32 :         CurlRequestHelper requestHelper;
    1212             :         const long response_code =
    1213          16 :             requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
    1214             : 
    1215          16 :         NetworkStatisticsLogger::LogPUT(nBufferSize);
    1216             : 
    1217          16 :         if (response_code != 200 ||
    1218          12 :             requestHelper.sWriteFuncHeaderData.pBuffer == nullptr)
    1219             :         {
    1220             :             // Look if we should attempt a retry
    1221           4 :             if (oRetryContext.CanRetry(
    1222             :                     static_cast<int>(response_code),
    1223           4 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    1224             :                     requestHelper.szCurlErrBuf))
    1225             :             {
    1226           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1227             :                          "HTTP error code: %d - %s. "
    1228             :                          "Retrying again in %.1f secs",
    1229             :                          static_cast<int>(response_code),
    1230           0 :                          poS3HandleHelper->GetURL().c_str(),
    1231             :                          oRetryContext.GetCurrentDelay());
    1232           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    1233           0 :                 bRetry = true;
    1234             :             }
    1235           6 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
    1236           2 :                      poS3HandleHelper->CanRestartOnError(
    1237           2 :                          requestHelper.sWriteFuncData.pBuffer,
    1238           2 :                          requestHelper.sWriteFuncHeaderData.pBuffer, true))
    1239             :             {
    1240           0 :                 bRetry = true;
    1241             :             }
    1242             :             else
    1243             :             {
    1244           4 :                 CPLDebug(GetDebugKey(), "%s",
    1245           4 :                          requestHelper.sWriteFuncData.pBuffer
    1246             :                              ? requestHelper.sWriteFuncData.pBuffer
    1247             :                              : "(null)");
    1248           4 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1249             :                          "UploadPart(%d) of %s failed", nPartNumber,
    1250             :                          osFilename.c_str());
    1251             :             }
    1252             :         }
    1253             :         else
    1254             :         {
    1255             :             const CPLString osHeader(
    1256          24 :                 requestHelper.sWriteFuncHeaderData.pBuffer);
    1257          12 :             const size_t nPos = osHeader.ifind("ETag: ");
    1258          12 :             if (nPos != std::string::npos)
    1259             :             {
    1260          12 :                 osEtag = osHeader.substr(nPos + strlen("ETag: "));
    1261          12 :                 const size_t nPosEOL = osEtag.find("\r");
    1262          12 :                 if (nPosEOL != std::string::npos)
    1263          12 :                     osEtag.resize(nPosEOL);
    1264          12 :                 CPLDebug(GetDebugKey(), "Etag for part %d is %s", nPartNumber,
    1265             :                          osEtag.c_str());
    1266             :             }
    1267             :             else
    1268             :             {
    1269           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1270             :                          "UploadPart(%d) of %s (uploadId = %s) failed",
    1271             :                          nPartNumber, osFilename.c_str(), osUploadID.c_str());
    1272             :             }
    1273             :         }
    1274             : 
    1275          16 :         curl_easy_cleanup(hCurlHandle);
    1276             :     } while (bRetry);
    1277             : 
    1278          32 :     return osEtag;
    1279             : }
    1280             : 
    1281             : /************************************************************************/
    1282             : /*                               Write()                                */
    1283             : /************************************************************************/
    1284             : 
    1285          21 : size_t VSIMultipartWriteHandle::Write(const void *pBuffer, size_t nBytes)
    1286             : {
    1287          21 :     if (m_bError)
    1288           0 :         return 0;
    1289             : 
    1290          21 :     size_t nBytesToWrite = nBytes;
    1291          21 :     if (nBytesToWrite == 0)
    1292           0 :         return 0;
    1293             : 
    1294          21 :     const GByte *pabySrcBuffer = reinterpret_cast<const GByte *>(pBuffer);
    1295          42 :     while (nBytesToWrite > 0)
    1296             :     {
    1297             :         const size_t nToWriteInBuffer =
    1298          22 :             std::min(m_nBufferSize - m_nBufferOff, nBytesToWrite);
    1299          22 :         memcpy(m_pabyBuffer + m_nBufferOff, pabySrcBuffer, nToWriteInBuffer);
    1300          22 :         pabySrcBuffer += nToWriteInBuffer;
    1301          22 :         m_nBufferOff += nToWriteInBuffer;
    1302          22 :         m_nCurOffset += nToWriteInBuffer;
    1303          22 :         nBytesToWrite -= nToWriteInBuffer;
    1304          22 :         if (m_nBufferOff == m_nBufferSize)
    1305             :         {
    1306           2 :             if (m_nCurOffset == m_nBufferSize)
    1307             :             {
    1308           2 :                 m_osUploadID = m_poFS->InitiateMultipartUpload(
    1309           2 :                     m_osFilename, m_poS3HandleHelper, m_oRetryParameters,
    1310           4 :                     m_aosOptions.List());
    1311           2 :                 if (m_osUploadID.empty())
    1312             :                 {
    1313           1 :                     m_bError = true;
    1314           1 :                     return 0;
    1315             :                 }
    1316             :             }
    1317           1 :             if (!UploadPart())
    1318             :             {
    1319           0 :                 m_bError = true;
    1320           0 :                 return 0;
    1321             :             }
    1322           1 :             m_nBufferOff = 0;
    1323             :         }
    1324             :     }
    1325          20 :     return nBytes;
    1326             : }
    1327             : 
    1328             : /************************************************************************/
    1329             : /*                     InvalidateParentDirectory()                      */
    1330             : /************************************************************************/
    1331             : 
    1332          22 : void VSIMultipartWriteHandle::InvalidateParentDirectory()
    1333             : {
    1334          22 :     m_poFS->InvalidateCachedData(m_poS3HandleHelper->GetURL().c_str());
    1335             : 
    1336          22 :     std::string osFilenameWithoutSlash(m_osFilename);
    1337          22 :     if (!osFilenameWithoutSlash.empty() && osFilenameWithoutSlash.back() == '/')
    1338           5 :         osFilenameWithoutSlash.pop_back();
    1339          22 :     m_poFS->InvalidateDirContent(
    1340          44 :         CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
    1341          22 : }
    1342             : 
    1343             : /************************************************************************/
    1344             : /*                          DoSinglePartPUT()                           */
    1345             : /************************************************************************/
    1346             : 
    1347          25 : bool VSIMultipartWriteHandle::DoSinglePartPUT()
    1348             : {
    1349          25 :     bool bSuccess = true;
    1350             :     bool bRetry;
    1351          50 :     CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
    1352             : 
    1353          50 :     NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str());
    1354          50 :     NetworkStatisticsFile oContextFile(m_osFilename.c_str());
    1355          25 :     NetworkStatisticsAction oContextAction("Write");
    1356             : 
    1357          27 :     do
    1358             :     {
    1359          27 :         bRetry = false;
    1360             : 
    1361          27 :         PutData putData;
    1362          27 :         putData.pabyData = m_pabyBuffer;
    1363          27 :         putData.nOff = 0;
    1364          27 :         putData.nTotalSize = m_nBufferOff;
    1365             : 
    1366          27 :         CURL *hCurlHandle = curl_easy_init();
    1367          27 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
    1368          27 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
    1369             :                                    PutData::ReadCallBackBuffer);
    1370          27 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
    1371          27 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
    1372             :                                    m_nBufferOff);
    1373             : 
    1374             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    1375          27 :             CPLHTTPSetOptions(hCurlHandle, m_poS3HandleHelper->GetURL().c_str(),
    1376          27 :                               m_aosHTTPOptions.List()));
    1377          54 :         headers = VSICurlSetCreationHeadersFromOptions(
    1378          27 :             headers, m_aosOptions.List(), m_osFilename.c_str());
    1379          27 :         headers = m_poS3HandleHelper->GetCurlHeaders(
    1380          27 :             "PUT", headers, m_pabyBuffer, m_nBufferOff);
    1381          27 :         headers = curl_slist_append(headers, "Expect: 100-continue");
    1382             : 
    1383          54 :         CurlRequestHelper requestHelper;
    1384          54 :         const long response_code = requestHelper.perform(
    1385          27 :             hCurlHandle, headers, m_poFS, m_poS3HandleHelper);
    1386             : 
    1387          27 :         NetworkStatisticsLogger::LogPUT(m_nBufferOff);
    1388             : 
    1389          27 :         if (response_code != 200 && response_code != 201)
    1390             :         {
    1391             :             // Look if we should attempt a retry
    1392           6 :             if (oRetryContext.CanRetry(
    1393             :                     static_cast<int>(response_code),
    1394           6 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    1395             :                     requestHelper.szCurlErrBuf))
    1396             :             {
    1397           2 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1398             :                          "HTTP error code: %d - %s. "
    1399             :                          "Retrying again in %.1f secs",
    1400             :                          static_cast<int>(response_code),
    1401           1 :                          m_poS3HandleHelper->GetURL().c_str(),
    1402             :                          oRetryContext.GetCurrentDelay());
    1403           1 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    1404           1 :                 bRetry = true;
    1405             :             }
    1406           6 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
    1407           1 :                      m_poS3HandleHelper->CanRestartOnError(
    1408           1 :                          requestHelper.sWriteFuncData.pBuffer,
    1409           1 :                          requestHelper.sWriteFuncHeaderData.pBuffer, true))
    1410             :             {
    1411           1 :                 bRetry = true;
    1412             :             }
    1413             :             else
    1414             :             {
    1415           4 :                 CPLDebug("S3", "%s",
    1416           4 :                          requestHelper.sWriteFuncData.pBuffer
    1417             :                              ? requestHelper.sWriteFuncData.pBuffer
    1418             :                              : "(null)");
    1419           4 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1420             :                          "DoSinglePartPUT of %s failed", m_osFilename.c_str());
    1421           4 :                 bSuccess = false;
    1422             :             }
    1423             :         }
    1424             :         else
    1425             :         {
    1426          21 :             InvalidateParentDirectory();
    1427             :         }
    1428             : 
    1429          27 :         if (requestHelper.sWriteFuncHeaderData.pBuffer != nullptr)
    1430             :         {
    1431             :             const char *pzETag =
    1432          27 :                 strstr(requestHelper.sWriteFuncHeaderData.pBuffer, "ETag: \"");
    1433          27 :             if (pzETag)
    1434             :             {
    1435           1 :                 pzETag += strlen("ETag: \"");
    1436           1 :                 const char *pszEndOfETag = strchr(pzETag, '"');
    1437           1 :                 if (pszEndOfETag)
    1438             :                 {
    1439           1 :                     FileProp oFileProp;
    1440           1 :                     oFileProp.eExists = EXIST_YES;
    1441           1 :                     oFileProp.fileSize = m_nBufferOff;
    1442           1 :                     oFileProp.bHasComputedFileSize = true;
    1443           1 :                     oFileProp.ETag.assign(pzETag, pszEndOfETag - pzETag);
    1444           1 :                     m_poFS->SetCachedFileProp(
    1445           2 :                         m_poFS->GetURLFromFilename(m_osFilename.c_str())
    1446             :                             .c_str(),
    1447             :                         oFileProp);
    1448             :                 }
    1449             :             }
    1450             :         }
    1451             : 
    1452          27 :         curl_easy_cleanup(hCurlHandle);
    1453             :     } while (bRetry);
    1454          50 :     return bSuccess;
    1455             : }
    1456             : 
    1457             : /************************************************************************/
    1458             : /*                         CompleteMultipart()                          */
    1459             : /************************************************************************/
    1460             : 
    1461           8 : bool IVSIS3LikeFSHandlerWithMultipartUpload::CompleteMultipart(
    1462             :     const std::string &osFilename, const std::string &osUploadID,
    1463             :     const std::vector<std::string> &aosEtags, vsi_l_offset /* nTotalSize */,
    1464             :     IVSIS3LikeHandleHelper *poS3HandleHelper,
    1465             :     const CPLHTTPRetryParameters &oRetryParameters)
    1466             : {
    1467           8 :     bool bSuccess = true;
    1468             : 
    1469          16 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    1470          16 :     NetworkStatisticsFile oContextFile(osFilename.c_str());
    1471          16 :     NetworkStatisticsAction oContextAction("CompleteMultipart");
    1472             : 
    1473          16 :     std::string osXML = "<CompleteMultipartUpload>\n";
    1474          20 :     for (size_t i = 0; i < aosEtags.size(); i++)
    1475             :     {
    1476          12 :         osXML += "<Part>\n";
    1477             :         osXML +=
    1478          12 :             CPLSPrintf("<PartNumber>%d</PartNumber>", static_cast<int>(i + 1));
    1479          12 :         osXML += "<ETag>" + aosEtags[i] + "</ETag>";
    1480          12 :         osXML += "</Part>\n";
    1481             :     }
    1482           8 :     osXML += "</CompleteMultipartUpload>\n";
    1483             : 
    1484             : #ifdef DEBUG_VERBOSE
    1485             :     CPLDebug(GetDebugKey(), "%s", osXML.c_str());
    1486             : #endif
    1487             : 
    1488             :     const CPLStringList aosHTTPOptions(
    1489          16 :         CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
    1490             : 
    1491           8 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    1492             :     bool bRetry;
    1493           8 :     do
    1494             :     {
    1495           8 :         bRetry = false;
    1496             : 
    1497           8 :         PutData putData;
    1498           8 :         putData.pabyData = reinterpret_cast<const GByte *>(osXML.data());
    1499           8 :         putData.nOff = 0;
    1500           8 :         putData.nTotalSize = osXML.size();
    1501             : 
    1502           8 :         CURL *hCurlHandle = curl_easy_init();
    1503           8 :         poS3HandleHelper->AddQueryParameter("uploadId", osUploadID);
    1504           8 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
    1505           8 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
    1506             :                                    PutData::ReadCallBackBuffer);
    1507           8 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
    1508           8 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
    1509             :                                    static_cast<int>(osXML.size()));
    1510           8 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "POST");
    1511             : 
    1512             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    1513           8 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    1514             :                               aosHTTPOptions.List()));
    1515           8 :         headers = poS3HandleHelper->GetCurlHeaders("POST", headers,
    1516           8 :                                                    osXML.c_str(), osXML.size());
    1517             : 
    1518          16 :         CurlRequestHelper requestHelper;
    1519             :         const long response_code =
    1520           8 :             requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
    1521             : 
    1522           8 :         NetworkStatisticsLogger::LogPOST(
    1523             :             osXML.size(), requestHelper.sWriteFuncHeaderData.nSize);
    1524             : 
    1525           8 :         if (response_code != 200)
    1526             :         {
    1527             :             // Look if we should attempt a retry
    1528           2 :             if (oRetryContext.CanRetry(
    1529             :                     static_cast<int>(response_code),
    1530           2 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    1531             :                     requestHelper.szCurlErrBuf))
    1532             :             {
    1533           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1534             :                          "HTTP error code: %d - %s. "
    1535             :                          "Retrying again in %.1f secs",
    1536             :                          static_cast<int>(response_code),
    1537           0 :                          poS3HandleHelper->GetURL().c_str(),
    1538             :                          oRetryContext.GetCurrentDelay());
    1539           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    1540           0 :                 bRetry = true;
    1541             :             }
    1542           2 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
    1543           0 :                      poS3HandleHelper->CanRestartOnError(
    1544           0 :                          requestHelper.sWriteFuncData.pBuffer,
    1545           0 :                          requestHelper.sWriteFuncHeaderData.pBuffer, true))
    1546             :             {
    1547           0 :                 bRetry = true;
    1548             :             }
    1549             :             else
    1550             :             {
    1551           2 :                 CPLDebug("S3", "%s",
    1552           2 :                          requestHelper.sWriteFuncData.pBuffer
    1553             :                              ? requestHelper.sWriteFuncData.pBuffer
    1554             :                              : "(null)");
    1555           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1556             :                          "CompleteMultipart of %s (uploadId=%s) failed",
    1557             :                          osFilename.c_str(), osUploadID.c_str());
    1558           2 :                 bSuccess = false;
    1559             :             }
    1560             :         }
    1561             : 
    1562           8 :         curl_easy_cleanup(hCurlHandle);
    1563             :     } while (bRetry);
    1564             : 
    1565          16 :     return bSuccess;
    1566             : }
    1567             : 
    1568             : /************************************************************************/
    1569             : /*                           AbortMultipart()                           */
    1570             : /************************************************************************/
    1571             : 
    1572           6 : bool IVSIS3LikeFSHandlerWithMultipartUpload::AbortMultipart(
    1573             :     const std::string &osFilename, const std::string &osUploadID,
    1574             :     IVSIS3LikeHandleHelper *poS3HandleHelper,
    1575             :     const CPLHTTPRetryParameters &oRetryParameters)
    1576             : {
    1577           6 :     bool bSuccess = true;
    1578             : 
    1579          12 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    1580          12 :     NetworkStatisticsFile oContextFile(osFilename.c_str());
    1581          12 :     NetworkStatisticsAction oContextAction("AbortMultipart");
    1582             : 
    1583             :     const CPLStringList aosHTTPOptions(
    1584          12 :         CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
    1585             : 
    1586           6 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    1587             :     bool bRetry;
    1588           6 :     do
    1589             :     {
    1590           6 :         bRetry = false;
    1591           6 :         CURL *hCurlHandle = curl_easy_init();
    1592           6 :         poS3HandleHelper->AddQueryParameter("uploadId", osUploadID);
    1593           6 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
    1594             :                                    "DELETE");
    1595             : 
    1596             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    1597           6 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    1598             :                               aosHTTPOptions.List()));
    1599           6 :         headers = poS3HandleHelper->GetCurlHeaders("DELETE", headers);
    1600             : 
    1601          12 :         CurlRequestHelper requestHelper;
    1602             :         const long response_code =
    1603           6 :             requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
    1604             : 
    1605           6 :         NetworkStatisticsLogger::LogDELETE();
    1606             : 
    1607           6 :         if (response_code != 204)
    1608             :         {
    1609             :             // Look if we should attempt a retry
    1610           2 :             if (oRetryContext.CanRetry(
    1611             :                     static_cast<int>(response_code),
    1612           2 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    1613             :                     requestHelper.szCurlErrBuf))
    1614             :             {
    1615           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1616             :                          "HTTP error code: %d - %s. "
    1617             :                          "Retrying again in %.1f secs",
    1618             :                          static_cast<int>(response_code),
    1619           0 :                          poS3HandleHelper->GetURL().c_str(),
    1620             :                          oRetryContext.GetCurrentDelay());
    1621           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    1622           0 :                 bRetry = true;
    1623             :             }
    1624           2 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
    1625           0 :                      poS3HandleHelper->CanRestartOnError(
    1626           0 :                          requestHelper.sWriteFuncData.pBuffer,
    1627           0 :                          requestHelper.sWriteFuncHeaderData.pBuffer, true))
    1628             :             {
    1629           0 :                 bRetry = true;
    1630             :             }
    1631             :             else
    1632             :             {
    1633           2 :                 CPLDebug("S3", "%s",
    1634           2 :                          requestHelper.sWriteFuncData.pBuffer
    1635             :                              ? requestHelper.sWriteFuncData.pBuffer
    1636             :                              : "(null)");
    1637           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1638             :                          "AbortMultipart of %s (uploadId=%s) failed",
    1639             :                          osFilename.c_str(), osUploadID.c_str());
    1640           2 :                 bSuccess = false;
    1641             :             }
    1642             :         }
    1643             : 
    1644           6 :         curl_easy_cleanup(hCurlHandle);
    1645             :     } while (bRetry);
    1646             : 
    1647          12 :     return bSuccess;
    1648             : }
    1649             : 
    1650             : /************************************************************************/
    1651             : /*                        AbortPendingUploads()                         */
    1652             : /************************************************************************/
    1653             : 
    1654           1 : bool IVSIS3LikeFSHandlerWithMultipartUpload::AbortPendingUploads(
    1655             :     const char *pszFilename)
    1656             : {
    1657           2 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    1658           2 :     NetworkStatisticsFile oContextFile(pszFilename);
    1659           2 :     NetworkStatisticsAction oContextAction("AbortPendingUploads");
    1660             : 
    1661           3 :     std::string osDirnameWithoutPrefix = pszFilename + GetFSPrefix().size();
    1662           1 :     if (!osDirnameWithoutPrefix.empty() && osDirnameWithoutPrefix.back() == '/')
    1663             :     {
    1664           0 :         osDirnameWithoutPrefix.pop_back();
    1665             :     }
    1666             : 
    1667           2 :     std::string osBucket(osDirnameWithoutPrefix);
    1668           2 :     std::string osObjectKey;
    1669           1 :     size_t nSlashPos = osDirnameWithoutPrefix.find('/');
    1670           1 :     if (nSlashPos != std::string::npos)
    1671             :     {
    1672           0 :         osBucket = osDirnameWithoutPrefix.substr(0, nSlashPos);
    1673           0 :         osObjectKey = osDirnameWithoutPrefix.substr(nSlashPos + 1);
    1674             :     }
    1675             : 
    1676             :     auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    1677           2 :         CreateHandleHelper(osBucket.c_str(), true));
    1678           1 :     if (poHandleHelper == nullptr)
    1679             :     {
    1680           0 :         return false;
    1681             :     }
    1682             : 
    1683             :     // For debugging purposes
    1684             :     const int nMaxUploads = std::min(
    1685           1 :         1000, atoi(CPLGetConfigOption("CPL_VSIS3_LIST_UPLOADS_MAX", "1000")));
    1686             : 
    1687           2 :     std::string osKeyMarker;
    1688           2 :     std::string osUploadIdMarker;
    1689           2 :     std::vector<std::pair<std::string, std::string>> aosUploads;
    1690             : 
    1691           2 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
    1692           2 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    1693             : 
    1694             :     // First pass: collect (key, uploadId)
    1695             :     while (true)
    1696             :     {
    1697           2 :         CPLHTTPRetryContext oRetryContext(oRetryParameters);
    1698             :         bool bRetry;
    1699           2 :         std::string osXML;
    1700           2 :         bool bSuccess = true;
    1701             : 
    1702           2 :         do
    1703             :         {
    1704           2 :             bRetry = false;
    1705           2 :             CURL *hCurlHandle = curl_easy_init();
    1706           2 :             poHandleHelper->AddQueryParameter("uploads", "");
    1707           2 :             if (!osObjectKey.empty())
    1708             :             {
    1709           0 :                 poHandleHelper->AddQueryParameter("prefix", osObjectKey);
    1710             :             }
    1711           2 :             if (!osKeyMarker.empty())
    1712             :             {
    1713           1 :                 poHandleHelper->AddQueryParameter("key-marker", osKeyMarker);
    1714             :             }
    1715           2 :             if (!osUploadIdMarker.empty())
    1716             :             {
    1717           1 :                 poHandleHelper->AddQueryParameter("upload-id-marker",
    1718             :                                                   osUploadIdMarker);
    1719             :             }
    1720           2 :             poHandleHelper->AddQueryParameter("max-uploads",
    1721             :                                               CPLSPrintf("%d", nMaxUploads));
    1722             : 
    1723             :             struct curl_slist *headers = static_cast<struct curl_slist *>(
    1724           2 :                 CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
    1725             :                                   aosHTTPOptions.List()));
    1726           2 :             headers = poHandleHelper->GetCurlHeaders("GET", headers);
    1727             : 
    1728           4 :             CurlRequestHelper requestHelper;
    1729           2 :             const long response_code = requestHelper.perform(
    1730             :                 hCurlHandle, headers, this, poHandleHelper.get());
    1731             : 
    1732           2 :             NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
    1733             : 
    1734           2 :             if (response_code != 200)
    1735             :             {
    1736             :                 // Look if we should attempt a retry
    1737           0 :                 if (oRetryContext.CanRetry(
    1738             :                         static_cast<int>(response_code),
    1739           0 :                         requestHelper.sWriteFuncHeaderData.pBuffer,
    1740             :                         requestHelper.szCurlErrBuf))
    1741             :                 {
    1742           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1743             :                              "HTTP error code: %d - %s. "
    1744             :                              "Retrying again in %.1f secs",
    1745             :                              static_cast<int>(response_code),
    1746           0 :                              poHandleHelper->GetURL().c_str(),
    1747             :                              oRetryContext.GetCurrentDelay());
    1748           0 :                     CPLSleep(oRetryContext.GetCurrentDelay());
    1749           0 :                     bRetry = true;
    1750             :                 }
    1751           0 :                 else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
    1752           0 :                          poHandleHelper->CanRestartOnError(
    1753           0 :                              requestHelper.sWriteFuncData.pBuffer,
    1754           0 :                              requestHelper.sWriteFuncHeaderData.pBuffer, true))
    1755             :                 {
    1756           0 :                     bRetry = true;
    1757             :                 }
    1758             :                 else
    1759             :                 {
    1760           0 :                     CPLDebug(GetDebugKey(), "%s",
    1761           0 :                              requestHelper.sWriteFuncData.pBuffer
    1762             :                                  ? requestHelper.sWriteFuncData.pBuffer
    1763             :                                  : "(null)");
    1764           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1765             :                              "ListMultipartUpload failed");
    1766           0 :                     bSuccess = false;
    1767             :                 }
    1768             :             }
    1769             :             else
    1770             :             {
    1771           2 :                 osXML = requestHelper.sWriteFuncData.pBuffer
    1772             :                             ? requestHelper.sWriteFuncData.pBuffer
    1773           2 :                             : "(null)";
    1774             :             }
    1775             : 
    1776           2 :             curl_easy_cleanup(hCurlHandle);
    1777             :         } while (bRetry);
    1778             : 
    1779           2 :         if (!bSuccess)
    1780           0 :             return false;
    1781             : 
    1782             : #ifdef DEBUG_VERBOSE
    1783             :         CPLDebug(GetDebugKey(), "%s", osXML.c_str());
    1784             : #endif
    1785             : 
    1786           2 :         CPLXMLTreeCloser oTree(CPLParseXMLString(osXML.c_str()));
    1787           2 :         if (!oTree)
    1788           0 :             return false;
    1789             : 
    1790             :         const CPLXMLNode *psRoot =
    1791           2 :             CPLGetXMLNode(oTree.get(), "=ListMultipartUploadsResult");
    1792           2 :         if (!psRoot)
    1793           0 :             return false;
    1794             : 
    1795           8 :         for (const CPLXMLNode *psIter = psRoot->psChild; psIter;
    1796           6 :              psIter = psIter->psNext)
    1797             :         {
    1798           6 :             if (!(psIter->eType == CXT_Element &&
    1799           6 :                   strcmp(psIter->pszValue, "Upload") == 0))
    1800           4 :                 continue;
    1801           2 :             const char *pszKey = CPLGetXMLValue(psIter, "Key", nullptr);
    1802             :             const char *pszUploadId =
    1803           2 :                 CPLGetXMLValue(psIter, "UploadId", nullptr);
    1804           2 :             if (pszKey && pszUploadId)
    1805             :             {
    1806             :                 aosUploads.emplace_back(
    1807           2 :                     std::pair<std::string, std::string>(pszKey, pszUploadId));
    1808             :             }
    1809             :         }
    1810             : 
    1811             :         const bool bIsTruncated =
    1812           2 :             CPLTestBool(CPLGetXMLValue(psRoot, "IsTruncated", "false"));
    1813           2 :         if (!bIsTruncated)
    1814           1 :             break;
    1815             : 
    1816           1 :         osKeyMarker = CPLGetXMLValue(psRoot, "NextKeyMarker", "");
    1817           1 :         osUploadIdMarker = CPLGetXMLValue(psRoot, "NextUploadIdMarker", "");
    1818           1 :     }
    1819             : 
    1820             :     // Second pass: actually abort those pending uploads
    1821           1 :     bool bRet = true;
    1822           3 :     for (const auto &pair : aosUploads)
    1823             :     {
    1824           2 :         const auto &osKey = pair.first;
    1825           2 :         const auto &osUploadId = pair.second;
    1826           2 :         CPLDebug(GetDebugKey(), "Abort %s/%s", osKey.c_str(),
    1827             :                  osUploadId.c_str());
    1828             : 
    1829             :         auto poSubHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    1830           4 :             CreateHandleHelper((osBucket + '/' + osKey).c_str(), true));
    1831           2 :         if (poSubHandleHelper == nullptr)
    1832             :         {
    1833           0 :             bRet = false;
    1834           0 :             continue;
    1835             :         }
    1836             : 
    1837           2 :         if (!AbortMultipart(GetFSPrefix() + osBucket + '/' + osKey, osUploadId,
    1838           2 :                             poSubHandleHelper.get(), oRetryParameters))
    1839             :         {
    1840           0 :             bRet = false;
    1841             :         }
    1842             :     }
    1843             : 
    1844           1 :     return bRet;
    1845             : }
    1846             : 
    1847             : /************************************************************************/
    1848             : /*                               Close()                                */
    1849             : /************************************************************************/
    1850             : 
    1851          66 : int VSIMultipartWriteHandle::Close()
    1852             : {
    1853          66 :     int nRet = 0;
    1854          66 :     if (!m_bClosed)
    1855             :     {
    1856          31 :         m_bClosed = true;
    1857          31 :         if (m_osUploadID.empty())
    1858             :         {
    1859          30 :             if (!m_bError && !DoSinglePartPUT())
    1860           4 :                 nRet = -1;
    1861             :         }
    1862             :         else
    1863             :         {
    1864           1 :             if (m_bError)
    1865             :             {
    1866           0 :                 if (!m_poFS->AbortMultipart(m_osFilename, m_osUploadID,
    1867             :                                             m_poS3HandleHelper,
    1868           0 :                                             m_oRetryParameters))
    1869           0 :                     nRet = -1;
    1870             :             }
    1871           1 :             else if (m_nBufferOff > 0 && !UploadPart())
    1872           0 :                 nRet = -1;
    1873           1 :             else if (m_poFS->CompleteMultipart(
    1874           1 :                          m_osFilename, m_osUploadID, m_aosEtags, m_nCurOffset,
    1875           1 :                          m_poS3HandleHelper, m_oRetryParameters))
    1876             :             {
    1877           1 :                 InvalidateParentDirectory();
    1878             :             }
    1879             :             else
    1880           0 :                 nRet = -1;
    1881             :         }
    1882             :     }
    1883          66 :     return nRet;
    1884             : }
    1885             : 
    1886             : /************************************************************************/
    1887             : /*                         CreateWriteHandle()                          */
    1888             : /************************************************************************/
    1889             : 
    1890             : VSIVirtualHandleUniquePtr
    1891          23 : VSIS3FSHandler::CreateWriteHandle(const char *pszFilename,
    1892             :                                   CSLConstList papszOptions)
    1893             : {
    1894             :     auto poHandleHelper =
    1895          23 :         CreateHandleHelper(pszFilename + GetFSPrefix().size(), false);
    1896          23 :     if (poHandleHelper == nullptr)
    1897           1 :         return nullptr;
    1898             :     auto poHandle = std::make_unique<VSIMultipartWriteHandle>(
    1899          44 :         this, pszFilename, poHandleHelper, papszOptions);
    1900          22 :     if (!poHandle->IsOK())
    1901             :     {
    1902           0 :         return nullptr;
    1903             :     }
    1904          22 :     return VSIVirtualHandleUniquePtr(poHandle.release());
    1905             : }
    1906             : 
    1907             : /************************************************************************/
    1908             : /*                     NormalizeFilenameIfNeeded()                      */
    1909             : /************************************************************************/
    1910             : 
    1911         589 : static void NormalizeFilenameIfNeeded(CPLString &osFilename)
    1912             : {
    1913             :     // Transform '/vsis3/./foo' to '/vsis3/foo' by default
    1914             :     //
    1915             :     // Cf https://curl.se/libcurl/c/CURLOPT_PATH_AS_IS.html
    1916         589 :     if (!CPLTestBool(VSIGetPathSpecificOption(osFilename,
    1917             :                                               "GDAL_HTTP_PATH_VERBATIM", "NO")))
    1918             :     {
    1919         588 :         osFilename = CPLLexicallyNormalize(osFilename, '/');
    1920             :     }
    1921         589 : }
    1922             : 
    1923             : /************************************************************************/
    1924             : /*                                Open()                                */
    1925             : /************************************************************************/
    1926             : 
    1927         261 : VSIVirtualHandleUniquePtr VSICurlFilesystemHandlerBaseWritable::Open(
    1928             :     const char *pszFilename, const char *pszAccess, bool bSetError,
    1929             :     CSLConstList papszOptions)
    1930             : {
    1931         261 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
    1932           1 :         return nullptr;
    1933             : 
    1934         520 :     CPLString osFilename(pszFilename);
    1935         260 :     NormalizeFilenameIfNeeded(osFilename);
    1936         260 :     if (!STARTS_WITH_CI(osFilename.c_str(), GetFSPrefix().c_str()))
    1937             :     {
    1938           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
    1939             :                  pszFilename);
    1940           1 :         return nullptr;
    1941             :     }
    1942             : 
    1943         259 :     if (strchr(pszAccess, '+'))
    1944             :     {
    1945           8 :         if (!SupportsRandomWrite(osFilename, true))
    1946             :         {
    1947           2 :             if (bSetError)
    1948             :             {
    1949           0 :                 VSIError(
    1950             :                     VSIE_FileError,
    1951             :                     "%s not supported for %s, unless "
    1952             :                     "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE is set to YES",
    1953           0 :                     pszAccess, GetFSPrefix().c_str());
    1954             :             }
    1955           2 :             errno = EACCES;
    1956           2 :             return nullptr;
    1957             :         }
    1958             : 
    1959             :         const std::string osTmpFilename(
    1960          12 :             CPLGenerateTempFilenameSafe(CPLGetFilename(osFilename)));
    1961           6 :         if (strchr(pszAccess, 'r'))
    1962             :         {
    1963             :             auto poExistingFile =
    1964           2 :                 VSIFilesystemHandler::OpenStatic(osFilename, "rb");
    1965           2 :             if (!poExistingFile)
    1966             :             {
    1967           1 :                 return nullptr;
    1968             :             }
    1969           1 :             if (VSICopyFile(osFilename, osTmpFilename.c_str(),
    1970             :                             poExistingFile.get(), static_cast<vsi_l_offset>(-1),
    1971           1 :                             nullptr, nullptr, nullptr) != 0)
    1972             :             {
    1973           0 :                 VSIUnlink(osTmpFilename.c_str());
    1974           0 :                 return nullptr;
    1975             :             }
    1976             :         }
    1977             : 
    1978             :         auto fpTemp =
    1979          10 :             VSIFilesystemHandler::OpenStatic(osTmpFilename.c_str(), pszAccess);
    1980           5 :         VSIUnlink(osTmpFilename.c_str());
    1981           5 :         if (!fpTemp)
    1982             :         {
    1983           0 :             return nullptr;
    1984             :         }
    1985             : 
    1986          10 :         auto poWriteHandle = CreateWriteHandle(osFilename, papszOptions);
    1987           5 :         if (!poWriteHandle)
    1988             :         {
    1989           0 :             return nullptr;
    1990             :         }
    1991             : 
    1992             :         return VSIVirtualHandleUniquePtr(VSICreateUploadOnCloseFile(
    1993           5 :             std::move(poWriteHandle), std::move(fpTemp), osTmpFilename));
    1994             :     }
    1995         251 :     else if (strchr(pszAccess, 'w') || strchr(pszAccess, 'a'))
    1996             :     {
    1997             :         return VSIVirtualHandleUniquePtr(
    1998          48 :             CreateWriteHandle(osFilename, papszOptions).release());
    1999             :     }
    2000             : 
    2001         203 :     if (osFilename.back() != '/')
    2002             :     {
    2003             :         // If there's directory content for the directory where this file
    2004             :         // belongs to, use it to detect if the object does not exist
    2005         203 :         CachedDirList cachedDirList;
    2006         203 :         const std::string osDirname(CPLGetDirnameSafe(osFilename));
    2007         609 :         if (STARTS_WITH_CI(osDirname.c_str(), GetFSPrefix().c_str()) &&
    2008         628 :             GetCachedDirList(osDirname.c_str(), cachedDirList) &&
    2009          19 :             cachedDirList.bGotFileList)
    2010             :         {
    2011          14 :             const std::string osFilenameOnly(CPLGetFilename(osFilename));
    2012          14 :             bool bFound = false;
    2013          34 :             for (int i = 0; i < cachedDirList.oFileList.size(); i++)
    2014             :             {
    2015          27 :                 if (cachedDirList.oFileList[i] == osFilenameOnly)
    2016             :                 {
    2017           7 :                     bFound = true;
    2018           7 :                     break;
    2019             :                 }
    2020             :             }
    2021          14 :             if (!bFound)
    2022             :             {
    2023           7 :                 return nullptr;
    2024             :             }
    2025             :         }
    2026             :     }
    2027             : 
    2028             :     return VSICurlFilesystemHandlerBase::Open(osFilename, pszAccess, bSetError,
    2029         196 :                                               papszOptions);
    2030             : }
    2031             : 
    2032             : /************************************************************************/
    2033             : /*                        SupportsRandomWrite()                         */
    2034             : /************************************************************************/
    2035             : 
    2036          11 : bool VSICurlFilesystemHandlerBaseWritable::SupportsRandomWrite(
    2037             :     const char *pszPath, bool bAllowLocalTempFile)
    2038             : {
    2039          21 :     return bAllowLocalTempFile &&
    2040          10 :            CPLTestBool(VSIGetPathSpecificOption(
    2041          11 :                pszPath, "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO"));
    2042             : }
    2043             : 
    2044             : /************************************************************************/
    2045             : /*                          ~VSIS3FSHandler()                           */
    2046             : /************************************************************************/
    2047             : 
    2048        1301 : VSIS3FSHandler::~VSIS3FSHandler()
    2049             : {
    2050        1301 :     VSIS3FSHandler::ClearCache();
    2051        1301 :     VSIS3HandleHelper::CleanMutex();
    2052        1301 : }
    2053             : 
    2054             : /************************************************************************/
    2055             : /*                             ClearCache()                             */
    2056             : /************************************************************************/
    2057             : 
    2058        1709 : void VSIS3FSHandler::ClearCache()
    2059             : {
    2060        1709 :     VSICurlFilesystemHandlerBase::ClearCache();
    2061             : 
    2062        1709 :     VSIS3UpdateParams::ClearCache();
    2063             : 
    2064        1709 :     VSIS3HandleHelper::ClearCache();
    2065        1709 : }
    2066             : 
    2067             : /************************************************************************/
    2068             : /*                             GetOptions()                             */
    2069             : /************************************************************************/
    2070             : 
    2071           2 : const char *VSIS3FSHandler::GetOptions()
    2072             : {
    2073             :     static std::string osOptions(
    2074           2 :         std::string("<Options>")
    2075             :             .append(
    2076             :                 "  <Option name='AWS_SECRET_ACCESS_KEY' type='string' "
    2077             :                 "description='Secret access key. To use with "
    2078             :                 "AWS_ACCESS_KEY_ID'/>"
    2079             :                 "  <Option name='AWS_ACCESS_KEY_ID' type='string' "
    2080             :                 "description='Access key id'/>"
    2081             :                 "  <Option name='AWS_SESSION_TOKEN' type='string' "
    2082             :                 "description='Session token'/>"
    2083             :                 "  <Option name='AWS_S3SESSION_TOKEN' type='string' "
    2084             :                 "description='S3 Express session token (for directory "
    2085             :                 "buckets)'/>"
    2086             :                 "  <Option name='AWS_REQUEST_PAYER' type='string' "
    2087             :                 "description='Content of the x-amz-request-payer HTTP header. "
    2088             :                 "Typically \"requester\" for requester-pays buckets'/>"
    2089             :                 "  <Option name='AWS_S3_ENDPOINT' type='string' "
    2090             :                 "description='Endpoint for a S3-compatible API' "
    2091             :                 "default='https://s3.amazonaws.com'/>"
    2092             :                 "  <Option name='AWS_VIRTUAL_HOSTING' type='boolean' "
    2093             :                 "description='Whether to use virtual hosting server name when "
    2094             :                 "the "
    2095             :                 "bucket name is compatible with it' default='YES'/>"
    2096             :                 "  <Option name='AWS_NO_SIGN_REQUEST' type='boolean' "
    2097             :                 "description='Whether to disable signing of requests' "
    2098             :                 "default='NO'/>"
    2099             :                 "  <Option name='AWS_DEFAULT_REGION' type='string' "
    2100             :                 "description='AWS S3 default region' default='us-east-1'/>"
    2101             :                 "  <Option name='CPL_AWS_AUTODETECT_EC2' type='boolean' "
    2102             :                 "description='Whether to check Hypervisor and DMI identifiers "
    2103             :                 "to "
    2104             :                 "determine if current host is an AWS EC2 instance' "
    2105             :                 "default='YES'/>"
    2106             :                 "  <Option name='AWS_PROFILE' type='string' "
    2107             :                 "description='Name of the profile to use for IAM credentials "
    2108             :                 "retrieval on EC2 instances' default='default'/>"
    2109             :                 "  <Option name='AWS_DEFAULT_PROFILE' type='string' "
    2110             :                 "description='(deprecated) Name of the profile to use for "
    2111             :                 "IAM credentials "
    2112             :                 "retrieval on EC2 instances' default='default'/>"
    2113             :                 "  <Option name='AWS_CONFIG_FILE' type='string' "
    2114             :                 "description='Filename that contains AWS configuration' "
    2115             :                 "default='~/.aws/config'/>"
    2116             :                 "  <Option name='CPL_AWS_CREDENTIALS_FILE' type='string' "
    2117             :                 "description='Filename that contains AWS credentials' "
    2118             :                 "default='~/.aws/credentials'/>"
    2119             :                 "  <Option name='VSIS3_CHUNK_SIZE' type='int' "
    2120             :                 "description='Size in MiB for chunks of files that are "
    2121             :                 "uploaded. The"
    2122           1 :                 "default value allows for files up to ")
    2123           1 :             .append(CPLSPrintf("%d", GetDefaultPartSizeInMiB() *
    2124           1 :                                          GetMaximumPartCount() / 1024))
    2125           1 :             .append("GiB each' default='")
    2126           1 :             .append(CPLSPrintf("%d", GetDefaultPartSizeInMiB()))
    2127           1 :             .append("' min='")
    2128           1 :             .append(CPLSPrintf("%d", GetMinimumPartSizeInMiB()))
    2129           1 :             .append("' max='")
    2130           1 :             .append(CPLSPrintf("%d", GetMaximumPartSizeInMiB()))
    2131           1 :             .append("'/>")
    2132             :             .append(
    2133             :                 "  <Option name='VSIS3_EARTHDATA_CREDENTIALS_URL' "
    2134             :                 "type='string' "
    2135             :                 "description='URL from which to retrieve temporary S3 "
    2136             :                 "credentials for a resource protected by NASA Earthdata "
    2137             :                 "login (EDL)'/>"
    2138             :                 "  <Option name='EARTHDATA_TOKEN' type='string' description='"
    2139             :                 "Authorization token to transmit to "
    2140             :                 "VSIS3_EARTHDATA_CREDENTIALS_URL'/>"
    2141             :                 "  <Option name='EARTHDATA_USERNAME' type='string' "
    2142             :                 "description='"
    2143             :                 "Username of the EDL account to transmit to "
    2144             :                 "urs.earthdata.nasa.gov to get a token to transmit to "
    2145             :                 "VSIS3_EARTHDATA_CREDENTIALS_URL. Must be used together with "
    2146             :                 "EARTHDATA_PASSWORD. Mutually exclusive with EARTHDATA_TOKEN'/>"
    2147             :                 "  <Option name='EARTHDATA_PASSWORD' type='string' "
    2148             :                 "description='"
    2149             :                 "Password of the EDL account to transmit to "
    2150             :                 "urs.earthdata.nasa.gov to get a token to transmit to "
    2151           1 :                 "VSIS3_EARTHDATA_CREDENTIALS_URL'/>")
    2152           1 :             .append(VSICurlFilesystemHandlerBase::GetOptionsStatic())
    2153           3 :             .append("</Options>"));
    2154           2 :     return osOptions.c_str();
    2155             : }
    2156             : 
    2157             : /************************************************************************/
    2158             : /*                            GetSignedURL()                            */
    2159             : /************************************************************************/
    2160             : 
    2161           6 : char *VSIS3FSHandler::GetSignedURL(const char *pszFilename,
    2162             :                                    CSLConstList papszOptions)
    2163             : {
    2164           6 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
    2165           0 :         return nullptr;
    2166             : 
    2167          12 :     VSIS3HandleHelper *poS3HandleHelper = VSIS3HandleHelper::BuildFromURI(
    2168          18 :         pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str(), false,
    2169             :         papszOptions);
    2170           6 :     if (poS3HandleHelper == nullptr)
    2171             :     {
    2172           1 :         return nullptr;
    2173             :     }
    2174             : 
    2175          10 :     std::string osRet(poS3HandleHelper->GetSignedURL(papszOptions));
    2176             : 
    2177           5 :     delete poS3HandleHelper;
    2178           5 :     return CPLStrdup(osRet.c_str());
    2179             : }
    2180             : 
    2181             : /************************************************************************/
    2182             : /*                            UnlinkBatch()                             */
    2183             : /************************************************************************/
    2184             : 
    2185           4 : int *VSIS3FSHandler::UnlinkBatch(CSLConstList papszFiles)
    2186             : {
    2187             :     // Implemented using
    2188             :     // https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html
    2189             : 
    2190             :     int *panRet =
    2191           4 :         static_cast<int *>(CPLCalloc(sizeof(int), CSLCount(papszFiles)));
    2192           8 :     CPLStringList aosList;
    2193           8 :     std::string osCurBucket;
    2194           4 :     int iStartIndex = -1;
    2195             :     // For debug / testing only
    2196             :     const int nBatchSize =
    2197           4 :         atoi(CPLGetConfigOption("CPL_VSIS3_UNLINK_BATCH_SIZE", "1000"));
    2198          12 :     for (int i = 0; papszFiles && papszFiles[i]; i++)
    2199             :     {
    2200           8 :         CPLAssert(STARTS_WITH_CI(papszFiles[i], GetFSPrefix().c_str()));
    2201             :         const char *pszFilenameWithoutPrefix =
    2202           8 :             papszFiles[i] + GetFSPrefix().size();
    2203           8 :         const char *pszSlash = strchr(pszFilenameWithoutPrefix, '/');
    2204           8 :         if (!pszSlash)
    2205           0 :             return panRet;
    2206          16 :         std::string osBucket;
    2207             :         osBucket.assign(pszFilenameWithoutPrefix,
    2208           8 :                         pszSlash - pszFilenameWithoutPrefix);
    2209           8 :         bool bBucketChanged = false;
    2210           8 :         if ((osCurBucket.empty() || osCurBucket == osBucket))
    2211             :         {
    2212           8 :             if (osCurBucket.empty())
    2213             :             {
    2214           5 :                 iStartIndex = i;
    2215           5 :                 osCurBucket = osBucket;
    2216             :             }
    2217           8 :             aosList.AddString(pszSlash + 1);
    2218             :         }
    2219             :         else
    2220             :         {
    2221           0 :             bBucketChanged = true;
    2222             :         }
    2223          13 :         while (bBucketChanged || aosList.size() == nBatchSize ||
    2224           5 :                papszFiles[i + 1] == nullptr)
    2225             :         {
    2226             :             // Compose XML post content
    2227           5 :             CPLXMLNode *psXML = CPLCreateXMLNode(nullptr, CXT_Element, "?xml");
    2228           5 :             CPLAddXMLAttributeAndValue(psXML, "version", "1.0");
    2229           5 :             CPLAddXMLAttributeAndValue(psXML, "encoding", "UTF-8");
    2230             :             CPLXMLNode *psDelete =
    2231           5 :                 CPLCreateXMLNode(nullptr, CXT_Element, "Delete");
    2232           5 :             psXML->psNext = psDelete;
    2233           5 :             CPLAddXMLAttributeAndValue(
    2234             :                 psDelete, "xmlns", "http://s3.amazonaws.com/doc/2006-03-01/");
    2235           5 :             CPLXMLNode *psLastChild = psDelete->psChild;
    2236           5 :             CPLAssert(psLastChild != nullptr);
    2237           5 :             CPLAssert(psLastChild->psNext == nullptr);
    2238           5 :             std::map<std::string, int> mapKeyToIndex;
    2239          13 :             for (int j = 0; aosList[j]; ++j)
    2240             :             {
    2241             :                 CPLXMLNode *psObject =
    2242           8 :                     CPLCreateXMLNode(nullptr, CXT_Element, "Object");
    2243           8 :                 mapKeyToIndex[aosList[j]] = iStartIndex + j;
    2244           8 :                 CPLCreateXMLElementAndValue(psObject, "Key", aosList[j]);
    2245           8 :                 psLastChild->psNext = psObject;
    2246           8 :                 psLastChild = psObject;
    2247             :             }
    2248             : 
    2249             :             // Run request
    2250           5 :             char *pszXML = CPLSerializeXMLTree(psXML);
    2251           5 :             CPLDestroyXMLNode(psXML);
    2252           5 :             auto oDeletedKeys = DeleteObjects(osCurBucket.c_str(), pszXML);
    2253           5 :             CPLFree(pszXML);
    2254             : 
    2255             :             // Mark delete file
    2256          12 :             for (const auto &osDeletedKey : oDeletedKeys)
    2257             :             {
    2258           7 :                 auto mapKeyToIndexIter = mapKeyToIndex.find(osDeletedKey);
    2259           7 :                 if (mapKeyToIndexIter != mapKeyToIndex.end())
    2260             :                 {
    2261           7 :                     panRet[mapKeyToIndexIter->second] = true;
    2262             :                 }
    2263             :             }
    2264             : 
    2265           5 :             osCurBucket.clear();
    2266           5 :             aosList.Clear();
    2267           5 :             if (bBucketChanged)
    2268             :             {
    2269           0 :                 iStartIndex = i;
    2270           0 :                 osCurBucket = osBucket;
    2271           0 :                 aosList.AddString(pszSlash + 1);
    2272           0 :                 bBucketChanged = false;
    2273             :             }
    2274             :             else
    2275             :             {
    2276           5 :                 break;
    2277             :             }
    2278             :         }
    2279             :     }
    2280           4 :     return panRet;
    2281             : }
    2282             : 
    2283             : /************************************************************************/
    2284             : /*                           RmdirRecursive()                           */
    2285             : /************************************************************************/
    2286             : 
    2287           2 : int VSIS3FSHandler::RmdirRecursive(const char *pszDirname)
    2288             : {
    2289             :     // Some S3-like APIs do not support DeleteObjects
    2290           2 :     if (CPLTestBool(VSIGetPathSpecificOption(
    2291             :             pszDirname, "CPL_VSIS3_USE_BASE_RMDIR_RECURSIVE", "NO")))
    2292           1 :         return VSIFilesystemHandler::RmdirRecursive(pszDirname);
    2293             : 
    2294             :     // For debug / testing only
    2295             :     const int nBatchSize =
    2296           1 :         atoi(CPLGetConfigOption("CPL_VSIS3_UNLINK_BATCH_SIZE", "1000"));
    2297             : 
    2298           1 :     return RmdirRecursiveInternal(pszDirname, nBatchSize);
    2299             : }
    2300             : 
    2301           2 : int IVSIS3LikeFSHandler::RmdirRecursiveInternal(const char *pszDirname,
    2302             :                                                 int nBatchSize)
    2303             : {
    2304           4 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    2305           4 :     NetworkStatisticsAction oContextAction("RmdirRecursive");
    2306             : 
    2307           4 :     std::string osDirnameWithoutEndSlash(pszDirname);
    2308           4 :     if (!osDirnameWithoutEndSlash.empty() &&
    2309           2 :         osDirnameWithoutEndSlash.back() == '/')
    2310           0 :         osDirnameWithoutEndSlash.pop_back();
    2311             : 
    2312           4 :     CPLStringList aosOptions;
    2313           2 :     aosOptions.SetNameValue("CACHE_ENTRIES", "FALSE");
    2314             :     auto poDir = std::unique_ptr<VSIDIR>(
    2315           4 :         OpenDir(osDirnameWithoutEndSlash.c_str(), -1, aosOptions.List()));
    2316           2 :     if (!poDir)
    2317           0 :         return -1;
    2318           4 :     CPLStringList aosList;
    2319             : 
    2320             :     while (true)
    2321             :     {
    2322           5 :         auto entry = poDir->NextDirEntry();
    2323           5 :         if (entry)
    2324             :         {
    2325           0 :             std::string osFilename(osDirnameWithoutEndSlash + '/' +
    2326           6 :                                    entry->pszName);
    2327           3 :             if (entry->nMode == S_IFDIR)
    2328           1 :                 osFilename += '/';
    2329           3 :             aosList.AddString(osFilename.c_str());
    2330             :         }
    2331           5 :         if (entry == nullptr || aosList.size() == nBatchSize)
    2332             :         {
    2333           3 :             if (entry == nullptr && !osDirnameWithoutEndSlash.empty())
    2334             :             {
    2335           2 :                 aosList.AddString((osDirnameWithoutEndSlash + '/').c_str());
    2336             :             }
    2337           3 :             int *ret = DeleteObjectBatch(aosList.List());
    2338           3 :             if (ret == nullptr)
    2339           0 :                 return -1;
    2340           3 :             CPLFree(ret);
    2341           3 :             aosList.Clear();
    2342             :         }
    2343           5 :         if (entry == nullptr)
    2344           2 :             break;
    2345           3 :     }
    2346           2 :     PartialClearCache(osDirnameWithoutEndSlash.c_str());
    2347           2 :     return 0;
    2348             : }
    2349             : 
    2350             : /************************************************************************/
    2351             : /*                           DeleteObjects()                            */
    2352             : /************************************************************************/
    2353             : 
    2354           5 : std::set<std::string> VSIS3FSHandler::DeleteObjects(const char *pszBucket,
    2355             :                                                     const char *pszXML)
    2356             : {
    2357             :     auto poS3HandleHelper =
    2358             :         std::unique_ptr<VSIS3HandleHelper>(VSIS3HandleHelper::BuildFromURI(
    2359          10 :             pszBucket, GetFSPrefix().c_str(), true));
    2360           5 :     if (!poS3HandleHelper)
    2361           0 :         return std::set<std::string>();
    2362             : 
    2363          10 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    2364          10 :     NetworkStatisticsAction oContextAction("DeleteObjects");
    2365             : 
    2366          10 :     std::set<std::string> oDeletedKeys;
    2367             :     bool bRetry;
    2368          10 :     const std::string osFilename(GetFSPrefix() + pszBucket);
    2369             :     const CPLStringList aosHTTPOptions(
    2370          10 :         CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
    2371          10 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    2372          10 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    2373             : 
    2374             :     struct CPLMD5Context context;
    2375           5 :     CPLMD5Init(&context);
    2376           5 :     CPLMD5Update(&context, pszXML, strlen(pszXML));
    2377             :     unsigned char hash[16];
    2378           5 :     CPLMD5Final(hash, &context);
    2379           5 :     char *pszBase64 = CPLBase64Encode(16, hash);
    2380          10 :     std::string osContentMD5("Content-MD5: ");
    2381           5 :     osContentMD5 += pszBase64;
    2382           5 :     CPLFree(pszBase64);
    2383             : 
    2384           5 :     do
    2385             :     {
    2386           5 :         bRetry = false;
    2387           5 :         CURL *hCurlHandle = curl_easy_init();
    2388           5 :         poS3HandleHelper->AddQueryParameter("delete", "");
    2389           5 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "POST");
    2390           5 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS, pszXML);
    2391             : 
    2392             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    2393           5 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    2394             :                               aosHTTPOptions.List()));
    2395           5 :         headers = curl_slist_append(headers, "Content-Type: application/xml");
    2396           5 :         headers = curl_slist_append(headers, osContentMD5.c_str());
    2397           5 :         headers = poS3HandleHelper->GetCurlHeaders("POST", headers, pszXML,
    2398             :                                                    strlen(pszXML));
    2399             : 
    2400          10 :         CurlRequestHelper requestHelper;
    2401           5 :         const long response_code = requestHelper.perform(
    2402           5 :             hCurlHandle, headers, this, poS3HandleHelper.get());
    2403             : 
    2404           5 :         NetworkStatisticsLogger::LogPOST(strlen(pszXML),
    2405             :                                          requestHelper.sWriteFuncData.nSize);
    2406             : 
    2407           5 :         if (response_code != 200 ||
    2408           5 :             requestHelper.sWriteFuncData.pBuffer == nullptr)
    2409             :         {
    2410             :             // Look if we should attempt a retry
    2411           0 :             if (oRetryContext.CanRetry(
    2412             :                     static_cast<int>(response_code),
    2413           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    2414             :                     requestHelper.szCurlErrBuf))
    2415             :             {
    2416           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2417             :                          "HTTP error code: %d - %s. "
    2418             :                          "Retrying again in %.1f secs",
    2419             :                          static_cast<int>(response_code),
    2420           0 :                          poS3HandleHelper->GetURL().c_str(),
    2421             :                          oRetryContext.GetCurrentDelay());
    2422           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    2423           0 :                 bRetry = true;
    2424             :             }
    2425           0 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
    2426           0 :                      poS3HandleHelper->CanRestartOnError(
    2427           0 :                          requestHelper.sWriteFuncData.pBuffer,
    2428           0 :                          requestHelper.sWriteFuncHeaderData.pBuffer, true))
    2429             :             {
    2430           0 :                 bRetry = true;
    2431             :             }
    2432             :             else
    2433             :             {
    2434           0 :                 CPLDebug(GetDebugKey(), "%s",
    2435           0 :                          requestHelper.sWriteFuncData.pBuffer
    2436             :                              ? requestHelper.sWriteFuncData.pBuffer
    2437             :                              : "(null)");
    2438           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "DeleteObjects failed");
    2439             :             }
    2440             :         }
    2441             :         else
    2442             :         {
    2443             :             CPLXMLNode *psXML =
    2444           5 :                 CPLParseXMLString(requestHelper.sWriteFuncData.pBuffer);
    2445           5 :             if (psXML)
    2446             :             {
    2447             :                 CPLXMLNode *psDeleteResult =
    2448           5 :                     CPLGetXMLNode(psXML, "=DeleteResult");
    2449           5 :                 if (psDeleteResult)
    2450             :                 {
    2451          18 :                     for (CPLXMLNode *psIter = psDeleteResult->psChild; psIter;
    2452          13 :                          psIter = psIter->psNext)
    2453             :                     {
    2454          13 :                         if (psIter->eType == CXT_Element &&
    2455           8 :                             strcmp(psIter->pszValue, "Deleted") == 0)
    2456             :                         {
    2457             :                             std::string osKey =
    2458           7 :                                 CPLGetXMLValue(psIter, "Key", "");
    2459           7 :                             oDeletedKeys.insert(osKey);
    2460             : 
    2461           7 :                             InvalidateCachedData(
    2462          14 :                                 (poS3HandleHelper->GetURL() + osKey).c_str());
    2463             : 
    2464           7 :                             InvalidateDirContent(CPLGetDirnameSafe(
    2465          14 :                                 (GetFSPrefix() + pszBucket + "/" + osKey)
    2466             :                                     .c_str()));
    2467             :                         }
    2468             :                     }
    2469             :                 }
    2470           5 :                 CPLDestroyXMLNode(psXML);
    2471             :             }
    2472             :         }
    2473             : 
    2474           5 :         curl_easy_cleanup(hCurlHandle);
    2475             :     } while (bRetry);
    2476           5 :     return oDeletedKeys;
    2477             : }
    2478             : 
    2479             : /************************************************************************/
    2480             : /*                          GetFileMetadata()                           */
    2481             : /************************************************************************/
    2482             : 
    2483           4 : char **VSIS3FSHandler::GetFileMetadata(const char *pszFilename,
    2484             :                                        const char *pszDomain,
    2485             :                                        CSLConstList papszOptions)
    2486             : {
    2487           4 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
    2488           0 :         return nullptr;
    2489             : 
    2490           8 :     CPLString osFilename(pszFilename);
    2491           4 :     NormalizeFilenameIfNeeded(osFilename);
    2492           4 :     if (!STARTS_WITH_CI(osFilename.c_str(), GetFSPrefix().c_str()))
    2493             :     {
    2494           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
    2495             :                  pszFilename);
    2496           1 :         return nullptr;
    2497             :     }
    2498             : 
    2499           3 :     if (pszDomain == nullptr || !EQUAL(pszDomain, "TAGS"))
    2500             :     {
    2501           2 :         return VSICurlFilesystemHandlerBase::GetFileMetadata(
    2502           2 :             osFilename, pszDomain, papszOptions);
    2503             :     }
    2504             : 
    2505             :     auto poS3HandleHelper =
    2506             :         std::unique_ptr<VSIS3HandleHelper>(VSIS3HandleHelper::BuildFromURI(
    2507           3 :             osFilename.c_str() + GetFSPrefix().size(), GetFSPrefix().c_str(),
    2508           3 :             false));
    2509           1 :     if (!poS3HandleHelper)
    2510           0 :         return nullptr;
    2511             : 
    2512           2 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    2513           2 :     NetworkStatisticsAction oContextAction("GetFileMetadata");
    2514             : 
    2515             :     bool bRetry;
    2516             : 
    2517           2 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(osFilename));
    2518           2 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    2519           2 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    2520             : 
    2521           2 :     CPLStringList aosTags;
    2522           1 :     do
    2523             :     {
    2524           1 :         bRetry = false;
    2525           1 :         CURL *hCurlHandle = curl_easy_init();
    2526           1 :         poS3HandleHelper->AddQueryParameter("tagging", "");
    2527             : 
    2528             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    2529           1 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    2530             :                               aosHTTPOptions.List()));
    2531           1 :         headers = poS3HandleHelper->GetCurlHeaders("GET", headers);
    2532             : 
    2533           2 :         CurlRequestHelper requestHelper;
    2534           1 :         const long response_code = requestHelper.perform(
    2535           1 :             hCurlHandle, headers, this, poS3HandleHelper.get());
    2536             : 
    2537           1 :         NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
    2538             : 
    2539           1 :         if (response_code != 200 ||
    2540           1 :             requestHelper.sWriteFuncData.pBuffer == nullptr)
    2541             :         {
    2542             :             // Look if we should attempt a retry
    2543           0 :             if (oRetryContext.CanRetry(
    2544             :                     static_cast<int>(response_code),
    2545           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    2546             :                     requestHelper.szCurlErrBuf))
    2547             :             {
    2548           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2549             :                          "HTTP error code: %d - %s. "
    2550             :                          "Retrying again in %.1f secs",
    2551             :                          static_cast<int>(response_code),
    2552           0 :                          poS3HandleHelper->GetURL().c_str(),
    2553             :                          oRetryContext.GetCurrentDelay());
    2554           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    2555           0 :                 bRetry = true;
    2556             :             }
    2557           0 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
    2558           0 :                      poS3HandleHelper->CanRestartOnError(
    2559           0 :                          requestHelper.sWriteFuncData.pBuffer,
    2560           0 :                          requestHelper.sWriteFuncHeaderData.pBuffer, true))
    2561             :             {
    2562           0 :                 bRetry = true;
    2563             :             }
    2564             :             else
    2565             :             {
    2566           0 :                 CPLDebug(GetDebugKey(), "%s",
    2567           0 :                          requestHelper.sWriteFuncData.pBuffer
    2568             :                              ? requestHelper.sWriteFuncData.pBuffer
    2569             :                              : "(null)");
    2570           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2571             :                          "GetObjectTagging failed");
    2572             :             }
    2573             :         }
    2574             :         else
    2575             :         {
    2576             :             CPLXMLNode *psXML =
    2577           1 :                 CPLParseXMLString(requestHelper.sWriteFuncData.pBuffer);
    2578           1 :             if (psXML)
    2579             :             {
    2580           1 :                 CPLXMLNode *psTagSet = CPLGetXMLNode(psXML, "=Tagging.TagSet");
    2581           1 :                 if (psTagSet)
    2582             :                 {
    2583           2 :                     for (CPLXMLNode *psIter = psTagSet->psChild; psIter;
    2584           1 :                          psIter = psIter->psNext)
    2585             :                     {
    2586           1 :                         if (psIter->eType == CXT_Element &&
    2587           1 :                             strcmp(psIter->pszValue, "Tag") == 0)
    2588             :                         {
    2589             :                             const char *pszKey =
    2590           1 :                                 CPLGetXMLValue(psIter, "Key", "");
    2591             :                             const char *pszValue =
    2592           1 :                                 CPLGetXMLValue(psIter, "Value", "");
    2593           1 :                             aosTags.SetNameValue(pszKey, pszValue);
    2594             :                         }
    2595             :                     }
    2596             :                 }
    2597           1 :                 CPLDestroyXMLNode(psXML);
    2598             :             }
    2599             :         }
    2600             : 
    2601           1 :         curl_easy_cleanup(hCurlHandle);
    2602             :     } while (bRetry);
    2603           1 :     return CSLDuplicate(aosTags.List());
    2604             : }
    2605             : 
    2606             : /************************************************************************/
    2607             : /*                          SetFileMetadata()                           */
    2608             : /************************************************************************/
    2609             : 
    2610           5 : bool VSIS3FSHandler::SetFileMetadata(const char *pszFilename,
    2611             :                                      CSLConstList papszMetadata,
    2612             :                                      const char *pszDomain,
    2613             :                                      CSLConstList /* papszOptions */)
    2614             : {
    2615           5 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
    2616           0 :         return false;
    2617             : 
    2618          10 :     CPLString osFilename(pszFilename);
    2619           5 :     NormalizeFilenameIfNeeded(osFilename);
    2620           5 :     if (!STARTS_WITH_CI(osFilename.c_str(), GetFSPrefix().c_str()))
    2621             :     {
    2622           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
    2623             :                  pszFilename);
    2624           1 :         return false;
    2625             :     }
    2626             : 
    2627           4 :     if (pszDomain == nullptr ||
    2628           4 :         !(EQUAL(pszDomain, "HEADERS") || EQUAL(pszDomain, "TAGS")))
    2629             :     {
    2630           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    2631             :                  "Only HEADERS and TAGS domain are supported");
    2632           1 :         return false;
    2633             :     }
    2634             : 
    2635           3 :     if (EQUAL(pszDomain, "HEADERS"))
    2636             :     {
    2637           1 :         return CopyObject(osFilename, osFilename, papszMetadata) == 0;
    2638             :     }
    2639             : 
    2640             :     auto poS3HandleHelper =
    2641             :         std::unique_ptr<VSIS3HandleHelper>(VSIS3HandleHelper::BuildFromURI(
    2642           6 :             osFilename.c_str() + GetFSPrefix().size(), GetFSPrefix().c_str(),
    2643           6 :             false));
    2644           2 :     if (!poS3HandleHelper)
    2645           0 :         return false;
    2646             : 
    2647           4 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    2648           4 :     NetworkStatisticsAction oContextAction("SetFileMetadata");
    2649             : 
    2650             :     // Compose XML post content
    2651           4 :     std::string osXML;
    2652           2 :     if (papszMetadata != nullptr && papszMetadata[0] != nullptr)
    2653             :     {
    2654           1 :         CPLXMLNode *psXML = CPLCreateXMLNode(nullptr, CXT_Element, "?xml");
    2655           1 :         CPLAddXMLAttributeAndValue(psXML, "version", "1.0");
    2656           1 :         CPLAddXMLAttributeAndValue(psXML, "encoding", "UTF-8");
    2657             :         CPLXMLNode *psTagging =
    2658           1 :             CPLCreateXMLNode(nullptr, CXT_Element, "Tagging");
    2659           1 :         psXML->psNext = psTagging;
    2660           1 :         CPLAddXMLAttributeAndValue(psTagging, "xmlns",
    2661             :                                    "http://s3.amazonaws.com/doc/2006-03-01/");
    2662             :         CPLXMLNode *psTagSet =
    2663           1 :             CPLCreateXMLNode(psTagging, CXT_Element, "TagSet");
    2664           2 :         for (int i = 0; papszMetadata[i]; ++i)
    2665             :         {
    2666           1 :             char *pszKey = nullptr;
    2667           1 :             const char *pszValue = CPLParseNameValue(papszMetadata[i], &pszKey);
    2668           1 :             if (pszKey && pszValue)
    2669             :             {
    2670             :                 CPLXMLNode *psTag =
    2671           1 :                     CPLCreateXMLNode(psTagSet, CXT_Element, "Tag");
    2672           1 :                 CPLCreateXMLElementAndValue(psTag, "Key", pszKey);
    2673           1 :                 CPLCreateXMLElementAndValue(psTag, "Value", pszValue);
    2674             :             }
    2675           1 :             CPLFree(pszKey);
    2676             :         }
    2677             : 
    2678           1 :         char *pszXML = CPLSerializeXMLTree(psXML);
    2679           1 :         osXML = pszXML;
    2680           1 :         CPLFree(pszXML);
    2681           1 :         CPLDestroyXMLNode(psXML);
    2682             :     }
    2683             : 
    2684           4 :     std::string osContentMD5;
    2685           2 :     if (!osXML.empty())
    2686             :     {
    2687             :         struct CPLMD5Context context;
    2688           1 :         CPLMD5Init(&context);
    2689           1 :         CPLMD5Update(&context, osXML.data(), osXML.size());
    2690             :         unsigned char hash[16];
    2691           1 :         CPLMD5Final(hash, &context);
    2692           1 :         char *pszBase64 = CPLBase64Encode(16, hash);
    2693           1 :         osContentMD5 = "Content-MD5: ";
    2694           1 :         osContentMD5 += pszBase64;
    2695           1 :         CPLFree(pszBase64);
    2696             :     }
    2697             : 
    2698             :     bool bRetry;
    2699             : 
    2700           4 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(osFilename));
    2701           4 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    2702           2 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    2703             : 
    2704           2 :     bool bRet = false;
    2705             : 
    2706           2 :     do
    2707             :     {
    2708           2 :         bRetry = false;
    2709           2 :         CURL *hCurlHandle = curl_easy_init();
    2710           2 :         poS3HandleHelper->AddQueryParameter("tagging", "");
    2711           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
    2712             :                                    osXML.empty() ? "DELETE" : "PUT");
    2713           2 :         if (!osXML.empty())
    2714             :         {
    2715           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
    2716             :                                        osXML.c_str());
    2717             :         }
    2718             : 
    2719             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    2720           2 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    2721             :                               aosHTTPOptions.List()));
    2722           2 :         if (!osXML.empty())
    2723             :         {
    2724             :             headers =
    2725           1 :                 curl_slist_append(headers, "Content-Type: application/xml");
    2726           1 :             headers = curl_slist_append(headers, osContentMD5.c_str());
    2727           2 :             headers = poS3HandleHelper->GetCurlHeaders(
    2728           1 :                 "PUT", headers, osXML.c_str(), osXML.size());
    2729           1 :             NetworkStatisticsLogger::LogPUT(osXML.size());
    2730             :         }
    2731             :         else
    2732             :         {
    2733           1 :             headers = poS3HandleHelper->GetCurlHeaders("DELETE", headers);
    2734           1 :             NetworkStatisticsLogger::LogDELETE();
    2735             :         }
    2736             : 
    2737           4 :         CurlRequestHelper requestHelper;
    2738           2 :         const long response_code = requestHelper.perform(
    2739           2 :             hCurlHandle, headers, this, poS3HandleHelper.get());
    2740             : 
    2741           5 :         if ((!osXML.empty() && response_code != 200) ||
    2742           3 :             (osXML.empty() && response_code != 204))
    2743             :         {
    2744             :             // Look if we should attempt a retry
    2745           0 :             if (oRetryContext.CanRetry(
    2746             :                     static_cast<int>(response_code),
    2747           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    2748             :                     requestHelper.szCurlErrBuf))
    2749             :             {
    2750           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2751             :                          "HTTP error code: %d - %s. "
    2752             :                          "Retrying again in %.1f secs",
    2753             :                          static_cast<int>(response_code),
    2754           0 :                          poS3HandleHelper->GetURL().c_str(),
    2755             :                          oRetryContext.GetCurrentDelay());
    2756           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    2757           0 :                 bRetry = true;
    2758             :             }
    2759           0 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
    2760           0 :                      poS3HandleHelper->CanRestartOnError(
    2761           0 :                          requestHelper.sWriteFuncData.pBuffer,
    2762           0 :                          requestHelper.sWriteFuncHeaderData.pBuffer, true))
    2763             :             {
    2764           0 :                 bRetry = true;
    2765             :             }
    2766             :             else
    2767             :             {
    2768           0 :                 CPLDebug(GetDebugKey(), "%s",
    2769           0 :                          requestHelper.sWriteFuncData.pBuffer
    2770             :                              ? requestHelper.sWriteFuncData.pBuffer
    2771             :                              : "(null)");
    2772           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2773             :                          "PutObjectTagging failed");
    2774             :             }
    2775             :         }
    2776             :         else
    2777             :         {
    2778           2 :             bRet = true;
    2779             :         }
    2780             : 
    2781           2 :         curl_easy_cleanup(hCurlHandle);
    2782             :     } while (bRetry);
    2783           2 :     return bRet;
    2784             : }
    2785             : 
    2786             : /************************************************************************/
    2787             : /*                        GetStreamingFilename()                        */
    2788             : /************************************************************************/
    2789             : 
    2790             : std::string
    2791        6021 : VSIS3FSHandler::GetStreamingFilename(const std::string &osFilename) const
    2792             : {
    2793        6021 :     if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
    2794       12032 :         return "/vsis3_streaming/" + osFilename.substr(GetFSPrefix().size());
    2795           0 :     return osFilename;
    2796             : }
    2797             : 
    2798             : /************************************************************************/
    2799             : /*                               Mkdir()                                */
    2800             : /************************************************************************/
    2801             : 
    2802          11 : int IVSIS3LikeFSHandler::MkdirInternal(const char *pszDirname, long /*nMode*/,
    2803             :                                        bool bDoStatCheck)
    2804             : {
    2805          11 :     if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
    2806           1 :         return -1;
    2807             : 
    2808          20 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    2809          20 :     NetworkStatisticsAction oContextAction("Mkdir");
    2810             : 
    2811          20 :     CPLString osDirname(pszDirname);
    2812          10 :     NormalizeFilenameIfNeeded(osDirname);
    2813          10 :     if (!STARTS_WITH_CI(osDirname.c_str(), GetFSPrefix().c_str()))
    2814             :     {
    2815           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
    2816             :                  pszDirname);
    2817           1 :         return -1;
    2818             :     }
    2819             : 
    2820           9 :     if (!osDirname.empty() && osDirname.back() != '/')
    2821           9 :         osDirname += "/";
    2822             : 
    2823           9 :     if (bDoStatCheck)
    2824             :     {
    2825             :         VSIStatBufL sStat;
    2826          12 :         if (VSIStatL(osDirname.c_str(), &sStat) == 0 &&
    2827           3 :             VSI_ISDIR(sStat.st_mode))
    2828             :         {
    2829           3 :             CPLDebug(GetDebugKey(), "Directory %s already exists",
    2830             :                      osDirname.c_str());
    2831           3 :             errno = EEXIST;
    2832           3 :             return -1;
    2833             :         }
    2834             :     }
    2835             : 
    2836           6 :     int ret = 0;
    2837           6 :     if (CPLTestBool(CPLGetConfigOption("CPL_VSIS3_CREATE_DIR_OBJECT", "YES")))
    2838             :     {
    2839           6 :         VSILFILE *fp = VSIFOpenL(osDirname.c_str(), "wb");
    2840           6 :         if (fp != nullptr)
    2841             :         {
    2842           6 :             CPLErrorReset();
    2843           6 :             VSIFCloseL(fp);
    2844           6 :             ret = CPLGetLastErrorType() == CPLE_None ? 0 : -1;
    2845             :         }
    2846             :         else
    2847             :         {
    2848           0 :             ret = -1;
    2849             :         }
    2850             :     }
    2851             : 
    2852           6 :     if (ret == 0)
    2853             :     {
    2854          12 :         std::string osDirnameWithoutEndSlash(osDirname);
    2855           6 :         osDirnameWithoutEndSlash.pop_back();
    2856             : 
    2857           6 :         InvalidateDirContent(
    2858          12 :             CPLGetDirnameSafe(osDirnameWithoutEndSlash.c_str()));
    2859             : 
    2860          12 :         FileProp cachedFileProp;
    2861           6 :         GetCachedFileProp(GetURLFromFilename(osDirname.c_str()).c_str(),
    2862             :                           cachedFileProp);
    2863           6 :         cachedFileProp.eExists = EXIST_YES;
    2864           6 :         cachedFileProp.bIsDirectory = true;
    2865           6 :         cachedFileProp.bHasComputedFileSize = true;
    2866           6 :         SetCachedFileProp(GetURLFromFilename(osDirname.c_str()).c_str(),
    2867             :                           cachedFileProp);
    2868             : 
    2869           6 :         RegisterEmptyDir(osDirnameWithoutEndSlash);
    2870           6 :         RegisterEmptyDir(osDirname);
    2871             :     }
    2872           6 :     return ret;
    2873             : }
    2874             : 
    2875          11 : int IVSIS3LikeFSHandler::Mkdir(const char *pszDirname, long nMode)
    2876             : {
    2877          11 :     return MkdirInternal(pszDirname, nMode, true);
    2878             : }
    2879             : 
    2880             : /************************************************************************/
    2881             : /*                               Rmdir()                                */
    2882             : /************************************************************************/
    2883             : 
    2884          14 : int IVSIS3LikeFSHandler::Rmdir(const char *pszDirname)
    2885             : {
    2886          14 :     if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
    2887           1 :         return -1;
    2888             : 
    2889          26 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    2890          26 :     NetworkStatisticsAction oContextAction("Rmdir");
    2891             : 
    2892          26 :     CPLString osDirname(pszDirname);
    2893          13 :     NormalizeFilenameIfNeeded(osDirname);
    2894          13 :     if (!STARTS_WITH_CI(osDirname.c_str(), GetFSPrefix().c_str()))
    2895             :     {
    2896           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
    2897             :                  pszDirname);
    2898           1 :         return -1;
    2899             :     }
    2900             : 
    2901          12 :     if (!osDirname.empty() && osDirname.back() != '/')
    2902          12 :         osDirname += "/";
    2903             : 
    2904             :     VSIStatBufL sStat;
    2905          12 :     if (VSIStatL(osDirname.c_str(), &sStat) != 0)
    2906             :     {
    2907           5 :         CPLDebug(GetDebugKey(), "%s is not a object", pszDirname);
    2908           5 :         errno = ENOENT;
    2909           5 :         return -1;
    2910             :     }
    2911           7 :     else if (!VSI_ISDIR(sStat.st_mode))
    2912             :     {
    2913           0 :         CPLDebug(GetDebugKey(), "%s is not a directory", pszDirname);
    2914           0 :         errno = ENOTDIR;
    2915           0 :         return -1;
    2916             :     }
    2917             : 
    2918           7 :     char **papszFileList = ReadDirEx(osDirname.c_str(), 100);
    2919           7 :     bool bEmptyDir =
    2920          14 :         papszFileList == nullptr ||
    2921           7 :         (EQUAL(papszFileList[0], ".") && papszFileList[1] == nullptr);
    2922           7 :     CSLDestroy(papszFileList);
    2923           7 :     if (!bEmptyDir)
    2924             :     {
    2925           3 :         CPLDebug(GetDebugKey(), "%s is not empty", pszDirname);
    2926           3 :         errno = ENOTEMPTY;
    2927           3 :         return -1;
    2928             :     }
    2929             : 
    2930           8 :     std::string osDirnameWithoutEndSlash(osDirname);
    2931           4 :     osDirnameWithoutEndSlash.pop_back();
    2932           4 :     if (osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
    2933             :         std::string::npos)
    2934             :     {
    2935           0 :         CPLDebug(GetDebugKey(), "%s is a bucket", pszDirname);
    2936           0 :         errno = ENOTDIR;
    2937           0 :         return -1;
    2938             :     }
    2939             : 
    2940           4 :     int ret = DeleteObject(osDirname.c_str());
    2941           4 :     if (ret == 0)
    2942             :     {
    2943           4 :         InvalidateDirContent(osDirnameWithoutEndSlash.c_str());
    2944             :     }
    2945           4 :     return ret;
    2946             : }
    2947             : 
    2948             : /************************************************************************/
    2949             : /*                                Stat()                                */
    2950             : /************************************************************************/
    2951             : 
    2952         160 : int IVSIS3LikeFSHandler::Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
    2953             :                               int nFlags)
    2954             : {
    2955         160 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
    2956           1 :         return -1;
    2957             : 
    2958         318 :     CPLString osFilename(pszFilename);
    2959         159 :     NormalizeFilenameIfNeeded(osFilename);
    2960         159 :     if (!STARTS_WITH_CI(osFilename.c_str(), GetFSPrefix().c_str()))
    2961             :     {
    2962           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
    2963             :                  pszFilename);
    2964           1 :         return -1;
    2965             :     }
    2966             : 
    2967         158 :     if ((nFlags & VSI_STAT_CACHE_ONLY) != 0)
    2968           2 :         return VSICurlFilesystemHandlerBase::Stat(osFilename, pStatBuf, nFlags);
    2969             : 
    2970         156 :     memset(pStatBuf, 0, sizeof(VSIStatBufL));
    2971         156 :     if (!IsAllowedFilename(osFilename))
    2972           0 :         return -1;
    2973             : 
    2974         312 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    2975         312 :     NetworkStatisticsAction oContextAction("Stat");
    2976             : 
    2977         156 :     if (osFilename.find('/', GetFSPrefix().size()) == std::string::npos)
    2978           6 :         osFilename += "/";
    2979             : 
    2980         312 :     std::string osFilenameWithoutSlash(osFilename);
    2981         156 :     if (osFilenameWithoutSlash.back() == '/')
    2982          26 :         osFilenameWithoutSlash.pop_back();
    2983             : 
    2984             :     // If there's directory content for the directory where this file belongs
    2985             :     // to, use it to detect if the object does not exist
    2986         312 :     CachedDirList cachedDirList;
    2987             :     const std::string osDirname(
    2988         312 :         CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
    2989         468 :     if (STARTS_WITH_CI(osDirname.c_str(), GetFSPrefix().c_str()) &&
    2990         503 :         GetCachedDirList(osDirname.c_str(), cachedDirList) &&
    2991          35 :         cachedDirList.bGotFileList)
    2992             :     {
    2993             :         const std::string osFilenameOnly(
    2994          17 :             CPLGetFilename(osFilenameWithoutSlash.c_str()));
    2995          17 :         bool bFound = false;
    2996          20 :         for (int i = 0; i < cachedDirList.oFileList.size(); i++)
    2997             :         {
    2998          18 :             if (cachedDirList.oFileList[i] == osFilenameOnly)
    2999             :             {
    3000          15 :                 bFound = true;
    3001          15 :                 break;
    3002             :             }
    3003             :         }
    3004          17 :         if (!bFound)
    3005             :         {
    3006           2 :             return -1;
    3007             :         }
    3008             :     }
    3009             : 
    3010             :     // We cannot stat the root directory of a S3 directory bucket, otherwise
    3011             :     // we get a 501 error.
    3012         154 :     bool bStatBaseObject = true;
    3013         154 :     if (GetFSPrefix() == "/vsis3/")
    3014             :     {
    3015             :         auto poS3HandleHelper =
    3016             :             std::unique_ptr<VSIS3HandleHelper>(VSIS3HandleHelper::BuildFromURI(
    3017         212 :                 osFilename.c_str() + GetFSPrefix().size(),
    3018         318 :                 GetFSPrefix().c_str(), true));
    3019         106 :         if (poS3HandleHelper)
    3020             :         {
    3021             :             const bool bIsDirectoryBucket =
    3022         103 :                 poS3HandleHelper->IsDirectoryBucket();
    3023         103 :             if (bIsDirectoryBucket && poS3HandleHelper->GetObjectKey().empty())
    3024           0 :                 bStatBaseObject = false;
    3025             :         }
    3026             :     }
    3027             : 
    3028         154 :     if (bStatBaseObject && VSICurlFilesystemHandlerBase::Stat(
    3029             :                                osFilename.c_str(), pStatBuf, nFlags) == 0)
    3030             :     {
    3031          95 :         return 0;
    3032             :     }
    3033             : 
    3034          59 :     char **papszRet = ReadDirInternal(osFilename.c_str(), 100, nullptr);
    3035          59 :     int nRet = papszRet ? 0 : -1;
    3036          59 :     if (nRet == 0)
    3037             :     {
    3038           6 :         pStatBuf->st_mtime = 0;
    3039           6 :         pStatBuf->st_size = 0;
    3040           6 :         pStatBuf->st_mode = S_IFDIR;
    3041             : 
    3042           6 :         FileProp cachedFileProp;
    3043           6 :         GetCachedFileProp(GetURLFromFilename(osFilename.c_str()).c_str(),
    3044             :                           cachedFileProp);
    3045           6 :         cachedFileProp.eExists = EXIST_YES;
    3046           6 :         cachedFileProp.bIsDirectory = true;
    3047           6 :         cachedFileProp.bHasComputedFileSize = true;
    3048           6 :         SetCachedFileProp(GetURLFromFilename(osFilename.c_str()).c_str(),
    3049             :                           cachedFileProp);
    3050             :     }
    3051          59 :     CSLDestroy(papszRet);
    3052          59 :     return nRet;
    3053             : }
    3054             : 
    3055             : /************************************************************************/
    3056             : /*                          CreateFileHandle()                          */
    3057             : /************************************************************************/
    3058             : 
    3059         210 : VSICurlHandle *VSIS3FSHandler::CreateFileHandle(const char *pszFilename)
    3060             : {
    3061         420 :     VSIS3HandleHelper *poS3HandleHelper = VSIS3HandleHelper::BuildFromURI(
    3062         630 :         pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str(), false);
    3063         210 :     if (poS3HandleHelper)
    3064             :     {
    3065         194 :         return new VSIS3Handle(this, pszFilename, poS3HandleHelper);
    3066             :     }
    3067          16 :     return nullptr;
    3068             : }
    3069             : 
    3070             : /************************************************************************/
    3071             : /*                         GetURLFromFilename()                         */
    3072             : /************************************************************************/
    3073             : 
    3074             : std::string
    3075         331 : VSIS3FSHandler::GetURLFromFilename(const std::string &osFilename) const
    3076             : {
    3077             :     const std::string osFilenameWithoutPrefix =
    3078         662 :         osFilename.substr(GetFSPrefix().size());
    3079             : 
    3080             :     auto poS3HandleHelper =
    3081             :         std::unique_ptr<VSIS3HandleHelper>(VSIS3HandleHelper::BuildFromURI(
    3082         662 :             osFilenameWithoutPrefix.c_str(), GetFSPrefix().c_str(), true));
    3083         331 :     if (!poS3HandleHelper)
    3084             :     {
    3085          30 :         return std::string();
    3086             :     }
    3087         602 :     std::string osBaseURL(poS3HandleHelper->GetURL());
    3088         301 :     if (!osBaseURL.empty() && osBaseURL.back() == '/')
    3089          38 :         osBaseURL.pop_back();
    3090         301 :     return osBaseURL;
    3091             : }
    3092             : 
    3093             : /************************************************************************/
    3094             : /*                         CreateHandleHelper()                         */
    3095             : /************************************************************************/
    3096             : 
    3097         157 : IVSIS3LikeHandleHelper *VSIS3FSHandler::CreateHandleHelper(const char *pszURI,
    3098             :                                                            bool bAllowNoObject)
    3099             : {
    3100         314 :     return VSIS3HandleHelper::BuildFromURI(pszURI, GetFSPrefix().c_str(),
    3101         314 :                                            bAllowNoObject);
    3102             : }
    3103             : 
    3104             : /************************************************************************/
    3105             : /*                               Unlink()                               */
    3106             : /************************************************************************/
    3107             : 
    3108          19 : int IVSIS3LikeFSHandler::Unlink(const char *pszFilename)
    3109             : {
    3110          19 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
    3111           0 :         return -1;
    3112             : 
    3113          38 :     CPLString osFilename(pszFilename);
    3114          19 :     NormalizeFilenameIfNeeded(osFilename);
    3115          19 :     if (!STARTS_WITH_CI(osFilename.c_str(), GetFSPrefix().c_str()))
    3116             :     {
    3117           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
    3118             :                  pszFilename);
    3119           1 :         return -1;
    3120             :     }
    3121             : 
    3122          54 :     std::string osNameWithoutPrefix = osFilename.c_str() + GetFSPrefix().size();
    3123          18 :     if (osNameWithoutPrefix.find('/') == std::string::npos)
    3124             :     {
    3125           2 :         CPLDebug(GetDebugKey(), "%s is not a file", pszFilename);
    3126           2 :         errno = EISDIR;
    3127           2 :         return -1;
    3128             :     }
    3129             : 
    3130          32 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    3131          32 :     NetworkStatisticsAction oContextAction("Unlink");
    3132             : 
    3133             :     VSIStatBufL sStat;
    3134          16 :     if (VSIStatL(osFilename, &sStat) != 0)
    3135             :     {
    3136           1 :         CPLDebug(GetDebugKey(), "%s is not a object", pszFilename);
    3137           1 :         errno = ENOENT;
    3138           1 :         return -1;
    3139             :     }
    3140          15 :     else if (!VSI_ISREG(sStat.st_mode))
    3141             :     {
    3142           0 :         CPLDebug(GetDebugKey(), "%s is not a file", pszFilename);
    3143           0 :         errno = EISDIR;
    3144           0 :         return -1;
    3145             :     }
    3146             : 
    3147          15 :     return DeleteObject(osFilename);
    3148             : }
    3149             : 
    3150             : /************************************************************************/
    3151             : /*                               Rename()                               */
    3152             : /************************************************************************/
    3153             : 
    3154           8 : int IVSIS3LikeFSHandler::Rename(const char *oldpath, const char *newpath,
    3155             :                                 GDALProgressFunc pfnProgress,
    3156             :                                 void *pProgressData)
    3157             : {
    3158           8 :     if (!STARTS_WITH_CI(oldpath, GetFSPrefix().c_str()))
    3159           0 :         return -1;
    3160           8 :     if (!STARTS_WITH_CI(newpath, GetFSPrefix().c_str()))
    3161           0 :         return -1;
    3162             : 
    3163          16 :     CPLString osOldPath(oldpath);
    3164           8 :     NormalizeFilenameIfNeeded(osOldPath);
    3165           8 :     if (!STARTS_WITH_CI(osOldPath.c_str(), GetFSPrefix().c_str()))
    3166             :     {
    3167           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'", oldpath);
    3168           1 :         return -1;
    3169             :     }
    3170             : 
    3171          14 :     CPLString osNewPath(newpath);
    3172           7 :     NormalizeFilenameIfNeeded(osNewPath);
    3173           7 :     if (!STARTS_WITH_CI(osNewPath.c_str(), GetFSPrefix().c_str()))
    3174             :     {
    3175           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'", newpath);
    3176           1 :         return -1;
    3177             :     }
    3178          12 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    3179          12 :     NetworkStatisticsAction oContextAction("Rename");
    3180             : 
    3181             :     VSIStatBufL sStat;
    3182           6 :     if (VSIStatL(osOldPath, &sStat) != 0)
    3183             :     {
    3184           0 :         CPLDebug(GetDebugKey(), "%s is not a object", oldpath);
    3185           0 :         errno = ENOENT;
    3186           0 :         return -1;
    3187             :     }
    3188             : 
    3189             :     // AWS doesn't like renaming to the same name, and errors out
    3190             :     // But GCS does like it, and so we might end up killing ourselves !
    3191             :     // POSIX says renaming on the same file is OK
    3192           6 :     if (strcmp(osOldPath, osNewPath) == 0)
    3193           0 :         return 0;
    3194             : 
    3195           6 :     if (VSI_ISDIR(sStat.st_mode))
    3196             :     {
    3197           1 :         int ret = 0;
    3198           1 :         const CPLStringList aosList(VSIReadDir(osOldPath));
    3199           1 :         Mkdir(osNewPath, 0755);
    3200           1 :         const int nListSize = aosList.size();
    3201           2 :         for (int i = 0; ret == 0 && i < nListSize; i++)
    3202             :         {
    3203             :             const std::string osSrc =
    3204           2 :                 CPLFormFilenameSafe(osOldPath, aosList[i], nullptr);
    3205             :             const std::string osTarget =
    3206           2 :                 CPLFormFilenameSafe(osNewPath, aosList[i], nullptr);
    3207             :             void *pScaledProgress =
    3208           2 :                 GDALCreateScaledProgress(static_cast<double>(i) / nListSize,
    3209           1 :                                          static_cast<double>(i + 1) / nListSize,
    3210             :                                          pfnProgress, pProgressData);
    3211           1 :             ret = Rename(osSrc.c_str(), osTarget.c_str(),
    3212             :                          pScaledProgress ? GDALScaledProgress : nullptr,
    3213           1 :                          pScaledProgress);
    3214           1 :             GDALDestroyScaledProgress(pScaledProgress);
    3215             :         }
    3216           1 :         if (ret == 0)
    3217           1 :             Rmdir(osOldPath);
    3218           1 :         return ret;
    3219             :     }
    3220             :     else
    3221             :     {
    3222           5 :         if (VSIStatL(osNewPath, &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
    3223             :         {
    3224           1 :             CPLDebug(GetDebugKey(), "%s already exists and is a directory",
    3225             :                      newpath);
    3226           1 :             errno = ENOTEMPTY;
    3227           1 :             return -1;
    3228             :         }
    3229           4 :         if (CopyObject(osOldPath, osNewPath, nullptr) != 0)
    3230             :         {
    3231           0 :             return -1;
    3232             :         }
    3233           4 :         return DeleteObject(osOldPath);
    3234             :     }
    3235             : }
    3236             : 
    3237             : /************************************************************************/
    3238             : /*                             CopyObject()                             */
    3239             : /************************************************************************/
    3240             : 
    3241           7 : int IVSIS3LikeFSHandler::CopyObject(const char *oldpath, const char *newpath,
    3242             :                                     CSLConstList papszMetadata)
    3243             : {
    3244          21 :     std::string osTargetNameWithoutPrefix = newpath + GetFSPrefix().size();
    3245             :     std::unique_ptr<IVSIS3LikeHandleHelper> poS3HandleHelper(
    3246          14 :         CreateHandleHelper(osTargetNameWithoutPrefix.c_str(), false));
    3247           7 :     if (poS3HandleHelper == nullptr)
    3248             :     {
    3249           0 :         return -1;
    3250             :     }
    3251             : 
    3252          14 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    3253          14 :     NetworkStatisticsAction oContextAction("CopyObject");
    3254             : 
    3255          14 :     std::string osSourceHeader(poS3HandleHelper->GetCopySourceHeader());
    3256           7 :     if (osSourceHeader.empty())
    3257             :     {
    3258           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3259             :                  "Object copy not supported by this file system");
    3260           0 :         return -1;
    3261             :     }
    3262           7 :     osSourceHeader += ": /";
    3263           7 :     if (STARTS_WITH(oldpath, "/vsis3/"))
    3264             :         osSourceHeader +=
    3265           5 :             CPLAWSURLEncode(oldpath + GetFSPrefix().size(), false);
    3266             :     else
    3267           2 :         osSourceHeader += (oldpath + GetFSPrefix().size());
    3268             : 
    3269           7 :     int nRet = 0;
    3270             : 
    3271             :     bool bRetry;
    3272             : 
    3273          14 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(oldpath));
    3274          14 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    3275           7 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    3276             : 
    3277           7 :     do
    3278             :     {
    3279           7 :         bRetry = false;
    3280           7 :         CURL *hCurlHandle = curl_easy_init();
    3281           7 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
    3282             : 
    3283             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    3284           7 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    3285             :                               aosHTTPOptions.List()));
    3286           7 :         headers = curl_slist_append(headers, osSourceHeader.c_str());
    3287           7 :         headers = curl_slist_append(
    3288             :             headers, "Content-Length: 0");  // Required by GCS, but not by S3
    3289           7 :         if (papszMetadata && papszMetadata[0])
    3290             :         {
    3291             :             const char *pszReplaceDirective =
    3292           4 :                 poS3HandleHelper->GetMetadataDirectiveREPLACE();
    3293           4 :             if (pszReplaceDirective[0])
    3294           4 :                 headers = curl_slist_append(headers, pszReplaceDirective);
    3295           8 :             for (int i = 0; papszMetadata[i]; i++)
    3296             :             {
    3297           4 :                 char *pszKey = nullptr;
    3298             :                 const char *pszValue =
    3299           4 :                     CPLParseNameValue(papszMetadata[i], &pszKey);
    3300           4 :                 if (pszKey && pszValue)
    3301             :                 {
    3302           4 :                     headers = curl_slist_append(
    3303             :                         headers, CPLSPrintf("%s: %s", pszKey, pszValue));
    3304             :                 }
    3305           4 :                 CPLFree(pszKey);
    3306             :             }
    3307             :         }
    3308           7 :         headers = VSICurlSetContentTypeFromExt(headers, newpath);
    3309           7 :         headers = poS3HandleHelper->GetCurlHeaders("PUT", headers);
    3310             : 
    3311          14 :         CurlRequestHelper requestHelper;
    3312           7 :         const long response_code = requestHelper.perform(
    3313             :             hCurlHandle, headers, this, poS3HandleHelper.get());
    3314             : 
    3315           7 :         NetworkStatisticsLogger::LogPUT(0);
    3316             : 
    3317           7 :         if (response_code != 200)
    3318             :         {
    3319             :             // Look if we should attempt a retry
    3320           0 :             if (oRetryContext.CanRetry(
    3321             :                     static_cast<int>(response_code),
    3322           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    3323             :                     requestHelper.szCurlErrBuf))
    3324             :             {
    3325           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3326             :                          "HTTP error code: %d - %s. "
    3327             :                          "Retrying again in %.1f secs",
    3328             :                          static_cast<int>(response_code),
    3329           0 :                          poS3HandleHelper->GetURL().c_str(),
    3330             :                          oRetryContext.GetCurrentDelay());
    3331           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    3332           0 :                 bRetry = true;
    3333             :             }
    3334           0 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
    3335           0 :                      poS3HandleHelper->CanRestartOnError(
    3336           0 :                          requestHelper.sWriteFuncData.pBuffer,
    3337           0 :                          requestHelper.sWriteFuncHeaderData.pBuffer, true))
    3338             :             {
    3339           0 :                 bRetry = true;
    3340             :             }
    3341             :             else
    3342             :             {
    3343           0 :                 CPLDebug(GetDebugKey(), "%s",
    3344           0 :                          requestHelper.sWriteFuncData.pBuffer
    3345             :                              ? requestHelper.sWriteFuncData.pBuffer
    3346             :                              : "(null)");
    3347           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Copy of %s to %s failed",
    3348             :                          oldpath, newpath);
    3349           0 :                 nRet = -1;
    3350             :             }
    3351             :         }
    3352             :         else
    3353             :         {
    3354           7 :             InvalidateCachedData(poS3HandleHelper->GetURL().c_str());
    3355             : 
    3356           7 :             std::string osFilenameWithoutSlash(newpath);
    3357          14 :             if (!osFilenameWithoutSlash.empty() &&
    3358           7 :                 osFilenameWithoutSlash.back() == '/')
    3359           0 :                 osFilenameWithoutSlash.resize(osFilenameWithoutSlash.size() -
    3360             :                                               1);
    3361             : 
    3362           7 :             InvalidateDirContent(
    3363          14 :                 CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
    3364             :         }
    3365             : 
    3366           7 :         curl_easy_cleanup(hCurlHandle);
    3367             :     } while (bRetry);
    3368             : 
    3369           7 :     return nRet;
    3370             : }
    3371             : 
    3372             : /************************************************************************/
    3373             : /*                            DeleteObject()                            */
    3374             : /************************************************************************/
    3375             : 
    3376          28 : int IVSIS3LikeFSHandler::DeleteObject(const char *pszFilename)
    3377             : {
    3378          84 :     std::string osNameWithoutPrefix = pszFilename + GetFSPrefix().size();
    3379             :     IVSIS3LikeHandleHelper *poS3HandleHelper =
    3380          28 :         CreateHandleHelper(osNameWithoutPrefix.c_str(), false);
    3381          28 :     if (poS3HandleHelper == nullptr)
    3382             :     {
    3383           0 :         return -1;
    3384             :     }
    3385             : 
    3386          56 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    3387          56 :     NetworkStatisticsAction oContextAction("DeleteObject");
    3388             : 
    3389          28 :     int nRet = 0;
    3390             : 
    3391             :     bool bRetry;
    3392             : 
    3393          56 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
    3394          56 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    3395          28 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    3396             : 
    3397          29 :     do
    3398             :     {
    3399          29 :         bRetry = false;
    3400          29 :         CURL *hCurlHandle = curl_easy_init();
    3401          29 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
    3402             :                                    "DELETE");
    3403             : 
    3404             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    3405          29 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    3406             :                               aosHTTPOptions.List()));
    3407          29 :         headers = poS3HandleHelper->GetCurlHeaders("DELETE", headers);
    3408             : 
    3409          58 :         CurlRequestHelper requestHelper;
    3410             :         const long response_code =
    3411          29 :             requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
    3412             : 
    3413          29 :         NetworkStatisticsLogger::LogDELETE();
    3414             : 
    3415             :         // S3 and GS respond with 204. Azure with 202. ADLS with 200.
    3416          29 :         if (response_code != 204 && response_code != 202 &&
    3417             :             response_code != 200)
    3418             :         {
    3419             :             // Look if we should attempt a retry
    3420           6 :             if (oRetryContext.CanRetry(
    3421             :                     static_cast<int>(response_code),
    3422           6 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    3423             :                     requestHelper.szCurlErrBuf))
    3424             :             {
    3425           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3426             :                          "HTTP error code: %d - %s. "
    3427             :                          "Retrying again in %.1f secs",
    3428             :                          static_cast<int>(response_code),
    3429           0 :                          poS3HandleHelper->GetURL().c_str(),
    3430             :                          oRetryContext.GetCurrentDelay());
    3431           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    3432           0 :                 bRetry = true;
    3433             :             }
    3434           7 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
    3435           1 :                      poS3HandleHelper->CanRestartOnError(
    3436           1 :                          requestHelper.sWriteFuncData.pBuffer,
    3437           1 :                          requestHelper.sWriteFuncHeaderData.pBuffer, true))
    3438             :             {
    3439           1 :                 bRetry = true;
    3440             :             }
    3441             :             else
    3442             :             {
    3443           5 :                 CPLDebug(GetDebugKey(), "%s",
    3444           5 :                          requestHelper.sWriteFuncData.pBuffer
    3445             :                              ? requestHelper.sWriteFuncData.pBuffer
    3446             :                              : "(null)");
    3447           5 :                 CPLError(CE_Failure, CPLE_AppDefined, "Delete of %s failed",
    3448             :                          pszFilename);
    3449           5 :                 nRet = -1;
    3450             :             }
    3451             :         }
    3452             :         else
    3453             :         {
    3454          23 :             InvalidateCachedData(poS3HandleHelper->GetURL().c_str());
    3455             : 
    3456          23 :             std::string osFilenameWithoutSlash(pszFilename);
    3457          46 :             if (!osFilenameWithoutSlash.empty() &&
    3458          23 :                 osFilenameWithoutSlash.back() == '/')
    3459           5 :                 osFilenameWithoutSlash.resize(osFilenameWithoutSlash.size() -
    3460             :                                               1);
    3461             : 
    3462          23 :             InvalidateDirContent(
    3463          46 :                 CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
    3464             :         }
    3465             : 
    3466          29 :         curl_easy_cleanup(hCurlHandle);
    3467             :     } while (bRetry);
    3468             : 
    3469          28 :     delete poS3HandleHelper;
    3470          28 :     return nRet;
    3471             : }
    3472             : 
    3473             : /************************************************************************/
    3474             : /*                         DeleteObjectBatch()                          */
    3475             : /************************************************************************/
    3476             : 
    3477           1 : int *IVSIS3LikeFSHandler::DeleteObjectBatch(CSLConstList papszFilesOrDirs)
    3478             : {
    3479             :     int *panRet =
    3480           1 :         static_cast<int *>(CPLMalloc(sizeof(int) * CSLCount(papszFilesOrDirs)));
    3481           2 :     for (int i = 0; papszFilesOrDirs && papszFilesOrDirs[i]; ++i)
    3482             :     {
    3483           1 :         panRet[i] = DeleteObject(papszFilesOrDirs[i]) == 0;
    3484             :     }
    3485           1 :     return panRet;
    3486             : }
    3487             : 
    3488             : /************************************************************************/
    3489             : /*                            GetFileList()                             */
    3490             : /************************************************************************/
    3491             : 
    3492          84 : char **IVSIS3LikeFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
    3493             :                                         bool *pbGotFileList)
    3494             : {
    3495             :     if (ENABLE_DEBUG)
    3496             :         CPLDebug(GetDebugKey(), "GetFileList(%s)", pszDirname);
    3497             : 
    3498          84 :     *pbGotFileList = false;
    3499             : 
    3500             :     char **papszOptions =
    3501          84 :         CSLSetNameValue(nullptr, "MAXFILES", CPLSPrintf("%d", nMaxFiles));
    3502          84 :     auto dir = OpenDir(pszDirname, 0, papszOptions);
    3503          84 :     CSLDestroy(papszOptions);
    3504          84 :     if (!dir)
    3505             :     {
    3506          40 :         return nullptr;
    3507             :     }
    3508          88 :     CPLStringList aosFileList;
    3509             :     while (true)
    3510             :     {
    3511         313 :         auto entry = dir->NextDirEntry();
    3512         313 :         if (!entry)
    3513             :         {
    3514          44 :             break;
    3515             :         }
    3516         269 :         aosFileList.AddString(entry->pszName);
    3517             : 
    3518         269 :         if (nMaxFiles > 0 && aosFileList.size() >= nMaxFiles)
    3519           0 :             break;
    3520         269 :     }
    3521          44 :     delete dir;
    3522          44 :     *pbGotFileList = true;
    3523          44 :     return aosFileList.StealList();
    3524             : }
    3525             : 
    3526             : /************************************************************************/
    3527             : /*                              OpenDir()                               */
    3528             : /************************************************************************/
    3529             : 
    3530         105 : VSIDIR *IVSIS3LikeFSHandler::OpenDir(const char *pszPath, int nRecurseDepth,
    3531             :                                      const char *const *papszOptions)
    3532             : {
    3533         105 :     if (nRecurseDepth > 0)
    3534             :     {
    3535           1 :         return VSIFilesystemHandler::OpenDir(pszPath, nRecurseDepth,
    3536           1 :                                              papszOptions);
    3537             :     }
    3538             : 
    3539         104 :     if (!STARTS_WITH_CI(pszPath, GetFSPrefix().c_str()))
    3540           0 :         return nullptr;
    3541             : 
    3542         208 :     CPLString osPath(pszPath);
    3543         104 :     NormalizeFilenameIfNeeded(osPath);
    3544         104 :     if (!STARTS_WITH_CI(osPath.c_str(), GetFSPrefix().c_str()))
    3545             :     {
    3546           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'", pszPath);
    3547           1 :         return nullptr;
    3548             :     }
    3549             : 
    3550         206 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    3551         206 :     NetworkStatisticsAction oContextAction("OpenDir");
    3552             : 
    3553         309 :     std::string osDirnameWithoutPrefix = osPath.c_str() + GetFSPrefix().size();
    3554         103 :     if (!osDirnameWithoutPrefix.empty() && osDirnameWithoutPrefix.back() == '/')
    3555             :     {
    3556           0 :         osDirnameWithoutPrefix.pop_back();
    3557             :     }
    3558             : 
    3559         206 :     std::string osBucket(osDirnameWithoutPrefix);
    3560         206 :     std::string osObjectKey;
    3561         103 :     size_t nSlashPos = osDirnameWithoutPrefix.find('/');
    3562         103 :     if (nSlashPos != std::string::npos)
    3563             :     {
    3564          60 :         osBucket = osDirnameWithoutPrefix.substr(0, nSlashPos);
    3565          60 :         osObjectKey = osDirnameWithoutPrefix.substr(nSlashPos + 1);
    3566             :     }
    3567             : 
    3568             :     auto poS3HandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    3569         206 :         CreateHandleHelper(osBucket.c_str(), true));
    3570         103 :     if (poS3HandleHelper == nullptr)
    3571             :     {
    3572           9 :         return nullptr;
    3573             :     }
    3574             : 
    3575          94 :     VSIDIRS3 *dir = new VSIDIRS3(osPath, this);
    3576          94 :     dir->nRecurseDepth = nRecurseDepth;
    3577          94 :     dir->poHandleHelper = std::move(poS3HandleHelper);
    3578          94 :     dir->osBucket = std::move(osBucket);
    3579          94 :     dir->osObjectKey = std::move(osObjectKey);
    3580          94 :     dir->nMaxFiles = atoi(CSLFetchNameValueDef(papszOptions, "MAXFILES", "0"));
    3581          94 :     dir->bCacheEntries = CPLTestBool(
    3582             :         CSLFetchNameValueDef(papszOptions, "CACHE_ENTRIES", "TRUE"));
    3583          94 :     dir->m_osFilterPrefix = CSLFetchNameValueDef(papszOptions, "PREFIX", "");
    3584          94 :     dir->m_bSynthetizeMissingDirectories = CPLTestBool(CSLFetchNameValueDef(
    3585             :         papszOptions, "SYNTHETIZE_MISSING_DIRECTORIES", "NO"));
    3586          94 :     dir->m_bListBucket =
    3587         100 :         dir->osBucket.empty() &&
    3588           6 :         dynamic_cast<VSIS3HandleHelper *>(dir->poHandleHelper.get()) != nullptr;
    3589          94 :     if (!dir->IssueListDir())
    3590             :     {
    3591          34 :         delete dir;
    3592          34 :         return nullptr;
    3593             :     }
    3594             : 
    3595          60 :     return dir;
    3596             : }
    3597             : 
    3598             : /************************************************************************/
    3599             : /*                       ComputeMD5OfLocalFile()                        */
    3600             : /************************************************************************/
    3601             : 
    3602           8 : static std::string ComputeMD5OfLocalFile(VSILFILE *fp)
    3603             : {
    3604           8 :     constexpr size_t nBufferSize = 10 * 4096;
    3605           8 :     std::vector<GByte> abyBuffer(nBufferSize, 0);
    3606             : 
    3607             :     struct CPLMD5Context context;
    3608           8 :     CPLMD5Init(&context);
    3609             : 
    3610             :     while (true)
    3611             :     {
    3612           8 :         size_t nRead = VSIFReadL(&abyBuffer[0], 1, nBufferSize, fp);
    3613           8 :         CPLMD5Update(&context, &abyBuffer[0], nRead);
    3614           8 :         if (nRead < nBufferSize)
    3615             :         {
    3616           8 :             break;
    3617             :         }
    3618           0 :     }
    3619             : 
    3620             :     unsigned char hash[16];
    3621           8 :     CPLMD5Final(hash, &context);
    3622             : 
    3623           8 :     constexpr char tohex[] = "0123456789abcdef";
    3624             :     char hhash[33];
    3625         136 :     for (int i = 0; i < 16; ++i)
    3626             :     {
    3627         128 :         hhash[i * 2] = tohex[(hash[i] >> 4) & 0xf];
    3628         128 :         hhash[i * 2 + 1] = tohex[hash[i] & 0xf];
    3629             :     }
    3630           8 :     hhash[32] = '\0';
    3631             : 
    3632           8 :     VSIFSeekL(fp, 0, SEEK_SET);
    3633             : 
    3634          16 :     return hhash;
    3635             : }
    3636             : 
    3637             : /************************************************************************/
    3638             : /*                              CopyFile()                              */
    3639             : /************************************************************************/
    3640             : 
    3641          24 : int IVSIS3LikeFSHandler::CopyFile(const char *pszSource, const char *pszTarget,
    3642             :                                   VSILFILE *fpSource, vsi_l_offset nSourceSize,
    3643             :                                   CSLConstList papszOptions,
    3644             :                                   GDALProgressFunc pProgressFunc,
    3645             :                                   void *pProgressData)
    3646             : {
    3647          48 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    3648          48 :     NetworkStatisticsAction oContextAction("CopyFile");
    3649             : 
    3650          24 :     if (!pszSource)
    3651             :     {
    3652           0 :         return VSIFilesystemHandler::CopyFile(pszSource, pszTarget, fpSource,
    3653             :                                               nSourceSize, papszOptions,
    3654           0 :                                               pProgressFunc, pProgressData);
    3655             :     }
    3656             : 
    3657          48 :     std::string osMsg("Copying of ");
    3658          24 :     osMsg += pszSource;
    3659             : 
    3660          48 :     const std::string osPrefix(GetFSPrefix());
    3661          43 :     if (STARTS_WITH(pszSource, osPrefix.c_str()) &&
    3662          19 :         STARTS_WITH(pszTarget, osPrefix.c_str()))
    3663             :     {
    3664           7 :         bool bRet = CopyObject(pszSource, pszTarget, papszOptions) == 0;
    3665           7 :         if (bRet && pProgressFunc)
    3666             :         {
    3667           1 :             bRet = pProgressFunc(1.0, osMsg.c_str(), pProgressData) != 0;
    3668             :         }
    3669           7 :         return bRet ? 0 : -1;
    3670             :     }
    3671             : 
    3672          17 :     VSIVirtualHandleUniquePtr poFileHandleAutoClose;
    3673          17 :     bool bUsingStreaming = false;
    3674          17 :     if (!fpSource)
    3675             :     {
    3676          29 :         if (STARTS_WITH(pszSource, osPrefix.c_str()) &&
    3677          12 :             CPLTestBool(CPLGetConfigOption(
    3678             :                 "VSIS3_COPYFILE_USE_STREAMING_SOURCE", "YES")))
    3679             :         {
    3680             :             // Try to get a streaming path from the source path
    3681           0 :             auto poSourceFSHandler = dynamic_cast<IVSIS3LikeFSHandler *>(
    3682          11 :                 VSIFileManager::GetHandler(pszSource));
    3683          11 :             if (poSourceFSHandler)
    3684             :             {
    3685             :                 const std::string osStreamingPath =
    3686          33 :                     poSourceFSHandler->GetStreamingFilename(pszSource);
    3687          11 :                 if (!osStreamingPath.empty())
    3688             :                 {
    3689          11 :                     fpSource = VSIFOpenExL(osStreamingPath.c_str(), "rb", TRUE);
    3690          11 :                     if (fpSource)
    3691          10 :                         bUsingStreaming = true;
    3692             :                 }
    3693             :             }
    3694             :         }
    3695          17 :         if (!fpSource)
    3696             :         {
    3697           7 :             fpSource = VSIFOpenExL(pszSource, "rb", TRUE);
    3698             :         }
    3699          17 :         if (!fpSource)
    3700             :         {
    3701           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszSource);
    3702           0 :             return false;
    3703             :         }
    3704             : 
    3705          17 :         poFileHandleAutoClose.reset(fpSource);
    3706             :     }
    3707             : 
    3708          17 :     int ret = VSIFilesystemHandler::CopyFile(pszSource, pszTarget, fpSource,
    3709             :                                              nSourceSize, papszOptions,
    3710             :                                              pProgressFunc, pProgressData);
    3711          17 :     if (ret == -1 && bUsingStreaming)
    3712             :     {
    3713             :         // Retry without streaming. This may be useful for large files, when
    3714             :         // there are connectivity issues, as retry attempts will be more
    3715             :         // efficient when using range requests.
    3716           0 :         CPLDebug(GetDebugKey(), "Retrying copy without streaming");
    3717           0 :         fpSource = VSIFOpenExL(pszSource, "rb", TRUE);
    3718           0 :         if (fpSource)
    3719             :         {
    3720           0 :             poFileHandleAutoClose.reset(fpSource);
    3721           0 :             ret = VSIFilesystemHandler::CopyFile(pszSource, pszTarget, fpSource,
    3722             :                                                  nSourceSize, papszOptions,
    3723             :                                                  pProgressFunc, pProgressData);
    3724             :         }
    3725             :     }
    3726             : 
    3727          17 :     return ret;
    3728             : }
    3729             : 
    3730             : /************************************************************************/
    3731             : /*                   GetRequestedNumThreadsForCopy()                    */
    3732             : /************************************************************************/
    3733             : 
    3734          40 : static int GetRequestedNumThreadsForCopy(CSLConstList papszOptions)
    3735             : {
    3736             : #if defined(CPL_MULTIPROC_STUB)
    3737             :     (void)papszOptions;
    3738             :     return 1;
    3739             : #else
    3740             :     // 10 threads used by default by the Python s3transfer library
    3741             :     const char *pszValue =
    3742          40 :         CSLFetchNameValueDef(papszOptions, "NUM_THREADS", "10");
    3743          40 :     if (EQUAL(pszValue, "ALL_CPUS"))
    3744           0 :         return CPLGetNumCPUs();
    3745          40 :     return atoi(pszValue);
    3746             : #endif
    3747             : }
    3748             : 
    3749             : /************************************************************************/
    3750             : /*                        CopyFileRestartable()                         */
    3751             : /************************************************************************/
    3752             : 
    3753          18 : int IVSIS3LikeFSHandlerWithMultipartUpload::CopyFileRestartable(
    3754             :     const char *pszSource, const char *pszTarget, const char *pszInputPayload,
    3755             :     char **ppszOutputPayload, CSLConstList papszOptions,
    3756             :     GDALProgressFunc pProgressFunc, void *pProgressData)
    3757             : {
    3758          36 :     const std::string osPrefix(GetFSPrefix());
    3759          36 :     NetworkStatisticsFileSystem oContextFS(osPrefix.c_str());
    3760          36 :     NetworkStatisticsAction oContextAction("CopyFileRestartable");
    3761             : 
    3762          18 :     *ppszOutputPayload = nullptr;
    3763             : 
    3764          18 :     if (!STARTS_WITH(pszTarget, osPrefix.c_str()))
    3765           0 :         return -1;
    3766             : 
    3767          36 :     std::string osMsg("Copying of ");
    3768          18 :     osMsg += pszSource;
    3769             : 
    3770             :     // Can we use server-side copy ?
    3771          19 :     if (STARTS_WITH(pszSource, osPrefix.c_str()) &&
    3772           1 :         STARTS_WITH(pszTarget, osPrefix.c_str()))
    3773             :     {
    3774           1 :         bool bRet = CopyObject(pszSource, pszTarget, papszOptions) == 0;
    3775           1 :         if (bRet && pProgressFunc)
    3776             :         {
    3777           0 :             bRet = pProgressFunc(1.0, osMsg.c_str(), pProgressData) != 0;
    3778             :         }
    3779           1 :         return bRet ? 0 : -1;
    3780             :     }
    3781             : 
    3782             :     // If multipart upload is not supported, fallback to regular CopyFile()
    3783          17 :     if (!SupportsParallelMultipartUpload())
    3784             :     {
    3785           0 :         return CopyFile(pszSource, pszTarget, nullptr,
    3786             :                         static_cast<vsi_l_offset>(-1), papszOptions,
    3787           0 :                         pProgressFunc, pProgressData);
    3788             :     }
    3789             : 
    3790          34 :     VSIVirtualHandleUniquePtr fpSource(VSIFOpenExL(pszSource, "rb", TRUE));
    3791          17 :     if (!fpSource)
    3792             :     {
    3793           1 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszSource);
    3794           1 :         return -1;
    3795             :     }
    3796             : 
    3797          16 :     const char *pszChunkSize = CSLFetchNameValue(papszOptions, "CHUNK_SIZE");
    3798             :     size_t nChunkSize =
    3799          16 :         std::max<size_t>(1, GetUploadChunkSizeInBytes(pszTarget, pszChunkSize));
    3800             : 
    3801             :     VSIStatBufL sStatBuf;
    3802          16 :     if (VSIStatL(pszSource, &sStatBuf) != 0)
    3803           0 :         return -1;
    3804             : 
    3805             :     auto poS3HandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    3806          32 :         CreateHandleHelper(pszTarget + osPrefix.size(), false));
    3807          16 :     if (poS3HandleHelper == nullptr)
    3808           0 :         return -1;
    3809             : 
    3810          16 :     int nChunkCount = 0;
    3811          32 :     std::vector<std::string> aosEtags;
    3812          32 :     std::string osUploadID;
    3813             : 
    3814          16 :     if (pszInputPayload)
    3815             :     {
    3816             :         // If there is an input payload, parse it, and do sanity checks
    3817             :         // and initial setup
    3818             : 
    3819          10 :         CPLJSONDocument oDoc;
    3820          10 :         if (!oDoc.LoadMemory(pszInputPayload))
    3821           0 :             return -1;
    3822             : 
    3823          10 :         auto oRoot = oDoc.GetRoot();
    3824          10 :         if (oRoot.GetString("source") != pszSource)
    3825             :         {
    3826           1 :             CPLError(
    3827             :                 CE_Failure, CPLE_AppDefined,
    3828             :                 "'source' field in input payload does not match pszSource");
    3829           1 :             return -1;
    3830             :         }
    3831             : 
    3832           9 :         if (oRoot.GetString("target") != pszTarget)
    3833             :         {
    3834           1 :             CPLError(
    3835             :                 CE_Failure, CPLE_AppDefined,
    3836             :                 "'target' field in input payload does not match pszTarget");
    3837           1 :             return -1;
    3838             :         }
    3839             : 
    3840          16 :         if (static_cast<uint64_t>(oRoot.GetLong("source_size")) !=
    3841           8 :             static_cast<uint64_t>(sStatBuf.st_size))
    3842             :         {
    3843           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    3844             :                      "'source_size' field in input payload does not match "
    3845             :                      "source file size");
    3846           1 :             return -1;
    3847             :         }
    3848             : 
    3849          14 :         if (oRoot.GetLong("source_mtime") !=
    3850           7 :             static_cast<GIntBig>(sStatBuf.st_mtime))
    3851             :         {
    3852           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    3853             :                      "'source_mtime' field in input payload does not match "
    3854             :                      "source file modification time");
    3855           1 :             return -1;
    3856             :         }
    3857             : 
    3858           6 :         osUploadID = oRoot.GetString("upload_id");
    3859           6 :         if (osUploadID.empty())
    3860             :         {
    3861           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    3862             :                      "'upload_id' field in input payload missing or invalid");
    3863           1 :             return -1;
    3864             :         }
    3865             : 
    3866           5 :         const auto nChunkSizeLong = oRoot.GetLong("chunk_size");
    3867           5 :         if (nChunkSizeLong <= 0)
    3868             :         {
    3869           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    3870             :                      "'chunk_size' field in input payload missing or invalid");
    3871           1 :             return -1;
    3872             :         }
    3873             : #if SIZEOF_VOIDP < 8
    3874             :         if (static_cast<uint64_t>(nChunkSizeLong) >
    3875             :             std::numeric_limits<size_t>::max())
    3876             :         {
    3877             :             CPLError(CE_Failure, CPLE_AppDefined,
    3878             :                      "'chunk_size' field in input payload is too large");
    3879             :             return -1;
    3880             :         }
    3881             : #endif
    3882           4 :         nChunkSize = static_cast<size_t>(nChunkSizeLong);
    3883             : 
    3884           8 :         auto oEtags = oRoot.GetArray("chunk_etags");
    3885           4 :         if (!oEtags.IsValid())
    3886             :         {
    3887           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    3888             :                      "'chunk_etags' field in input payload missing or invalid");
    3889           1 :             return -1;
    3890             :         }
    3891             : 
    3892           3 :         const auto nChunkCountLarge = cpl::div_round_up(
    3893           3 :             static_cast<uint64_t>(sStatBuf.st_size), nChunkSize);
    3894           3 :         if (nChunkCountLarge != static_cast<size_t>(oEtags.Size()))
    3895             :         {
    3896           1 :             CPLError(
    3897             :                 CE_Failure, CPLE_AppDefined,
    3898             :                 "'chunk_etags' field in input payload has not expected size");
    3899           1 :             return -1;
    3900             :         }
    3901           2 :         nChunkCount = oEtags.Size();
    3902           6 :         for (int iChunk = 0; iChunk < nChunkCount; ++iChunk)
    3903             :         {
    3904           4 :             aosEtags.push_back(oEtags[iChunk].ToString());
    3905             :         }
    3906             :     }
    3907             :     else
    3908             :     {
    3909             :         // Compute the number of chunks
    3910           6 :         auto nChunkCountLarge = cpl::div_round_up(
    3911           6 :             static_cast<uint64_t>(sStatBuf.st_size), nChunkSize);
    3912           6 :         if (nChunkCountLarge > static_cast<size_t>(GetMaximumPartCount()))
    3913             :         {
    3914             :             // Re-adjust the chunk size if needed
    3915           0 :             const int nWishedChunkCount = GetMaximumPartCount() / 10;
    3916             :             const uint64_t nMinChunkSizeLarge =
    3917           0 :                 cpl::div_round_up(sStatBuf.st_size, nWishedChunkCount);
    3918           0 :             if (pszChunkSize)
    3919             :             {
    3920           0 :                 CPLError(
    3921             :                     CE_Failure, CPLE_AppDefined,
    3922             :                     "Too small CHUNK_SIZE compared to file size. Should be at "
    3923             :                     "least " CPL_FRMT_GUIB,
    3924             :                     static_cast<GUIntBig>(nMinChunkSizeLarge));
    3925           0 :                 return -1;
    3926             :             }
    3927           0 :             if (nMinChunkSizeLarge >
    3928           0 :                 static_cast<size_t>(GetMaximumPartSizeInMiB()) * MIB_CONSTANT)
    3929             :             {
    3930           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too large file");
    3931           0 :                 return -1;
    3932             :             }
    3933           0 :             nChunkSize = static_cast<size_t>(nMinChunkSizeLarge);
    3934           0 :             nChunkCountLarge = cpl::div_round_up(sStatBuf.st_size, nChunkSize);
    3935             :         }
    3936           6 :         nChunkCount = static_cast<int>(nChunkCountLarge);
    3937           6 :         aosEtags.resize(nChunkCount);
    3938             :     }
    3939             : 
    3940             :     const CPLHTTPRetryParameters oRetryParameters(
    3941          16 :         CPLStringList(CPLHTTPGetOptionsFromEnv(pszSource)));
    3942           8 :     if (osUploadID.empty())
    3943             :     {
    3944          12 :         osUploadID = InitiateMultipartUpload(pszTarget, poS3HandleHelper.get(),
    3945          12 :                                              oRetryParameters, nullptr);
    3946           6 :         if (osUploadID.empty())
    3947             :         {
    3948           1 :             return -1;
    3949             :         }
    3950             :     }
    3951             : 
    3952           7 :     const int nRequestedThreads = GetRequestedNumThreadsForCopy(papszOptions);
    3953           7 :     const int nNeededThreads = std::min(nRequestedThreads, nChunkCount);
    3954           7 :     std::mutex oMutex;
    3955          14 :     std::condition_variable oCV;
    3956           7 :     bool bSuccess = true;
    3957           7 :     bool bStop = false;
    3958           7 :     bool bAbort = false;
    3959           7 :     int iCurChunk = 0;
    3960             : 
    3961           7 :     const bool bRunInThread = nNeededThreads > 1;
    3962             : 
    3963             :     const auto threadFunc =
    3964           8 :         [this, &fpSource, &aosEtags, &oMutex, &oCV, &iCurChunk, &bStop, &bAbort,
    3965             :          &bSuccess, &osMsg, &osUploadID, &sStatBuf, &poS3HandleHelper,
    3966             :          &osPrefix, bRunInThread, pszSource, pszTarget, nChunkCount, nChunkSize,
    3967         188 :          &oRetryParameters, pProgressFunc, pProgressData]()
    3968             :     {
    3969           0 :         VSIVirtualHandleUniquePtr fpUniquePtr;
    3970           8 :         VSIVirtualHandle *fp = nullptr;
    3971             :         std::unique_ptr<IVSIS3LikeHandleHelper>
    3972           0 :             poS3HandleHelperThisThreadUniquePtr;
    3973           8 :         IVSIS3LikeHandleHelper *poS3HandleHelperThisThread = nullptr;
    3974             : 
    3975           8 :         std::vector<GByte> abyBuffer;
    3976             :         try
    3977             :         {
    3978           8 :             abyBuffer.resize(nChunkSize);
    3979             :         }
    3980           0 :         catch (const std::exception &)
    3981             :         {
    3982           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    3983             :                      "Cannot allocate working buffer");
    3984           0 :             std::lock_guard oLock(oMutex);
    3985           0 :             bSuccess = false;
    3986           0 :             bStop = true;
    3987           0 :             return;
    3988             :         }
    3989             : 
    3990             :         while (true)
    3991             :         {
    3992             :             int iChunk;
    3993             :             {
    3994          18 :                 std::lock_guard oLock(oMutex);
    3995          18 :                 if (bStop)
    3996           0 :                     break;
    3997          18 :                 if (iCurChunk == nChunkCount)
    3998           6 :                     break;
    3999          12 :                 iChunk = iCurChunk;
    4000          12 :                 ++iCurChunk;
    4001             :             }
    4002          12 :             if (!fp)
    4003             :             {
    4004           8 :                 if (iChunk == 0)
    4005             :                 {
    4006           7 :                     fp = fpSource.get();
    4007           7 :                     poS3HandleHelperThisThread = poS3HandleHelper.get();
    4008             :                 }
    4009             :                 else
    4010             :                 {
    4011           1 :                     fpUniquePtr.reset(VSIFOpenExL(pszSource, "rb", TRUE));
    4012           1 :                     if (!fpUniquePtr)
    4013             :                     {
    4014           0 :                         CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
    4015             :                                  pszSource);
    4016             : 
    4017           0 :                         std::lock_guard oLock(oMutex);
    4018           0 :                         bSuccess = false;
    4019           0 :                         bStop = true;
    4020           0 :                         break;
    4021             :                     }
    4022           1 :                     fp = fpUniquePtr.get();
    4023             : 
    4024           1 :                     poS3HandleHelperThisThreadUniquePtr.reset(
    4025           1 :                         CreateHandleHelper(pszTarget + osPrefix.size(), false));
    4026           1 :                     if (!poS3HandleHelperThisThreadUniquePtr)
    4027             :                     {
    4028           0 :                         std::lock_guard oLock(oMutex);
    4029           0 :                         bSuccess = false;
    4030           0 :                         bStop = true;
    4031           0 :                         break;
    4032             :                     }
    4033             :                     poS3HandleHelperThisThread =
    4034           1 :                         poS3HandleHelperThisThreadUniquePtr.get();
    4035             :                 }
    4036             :             }
    4037             : 
    4038          12 :             if (aosEtags[iChunk].empty())
    4039             :             {
    4040          10 :                 const auto nCurPos =
    4041          10 :                     iChunk * static_cast<vsi_l_offset>(nChunkSize);
    4042          10 :                 CPL_IGNORE_RET_VAL(fp->Seek(nCurPos, SEEK_SET));
    4043          10 :                 const auto nRemaining = sStatBuf.st_size - nCurPos;
    4044          10 :                 const size_t nToRead =
    4045             :                     nRemaining > static_cast<vsi_l_offset>(nChunkSize)
    4046          10 :                         ? nChunkSize
    4047           7 :                         : static_cast<int>(nRemaining);
    4048          10 :                 const size_t nRead = fp->Read(abyBuffer.data(), 1, nToRead);
    4049          10 :                 if (nRead != nToRead)
    4050             :                 {
    4051           0 :                     CPLError(
    4052             :                         CE_Failure, CPLE_FileIO,
    4053             :                         "Did not get expected number of bytes from input file");
    4054           0 :                     std::lock_guard oLock(oMutex);
    4055           0 :                     bAbort = true;
    4056           0 :                     bSuccess = false;
    4057           0 :                     bStop = true;
    4058           0 :                     break;
    4059             :                 }
    4060             :                 const auto osEtag = UploadPart(
    4061             :                     pszTarget, 1 + iChunk, osUploadID, nCurPos,
    4062          10 :                     abyBuffer.data(), nToRead, poS3HandleHelperThisThread,
    4063          20 :                     oRetryParameters, nullptr);
    4064          10 :                 if (osEtag.empty())
    4065             :                 {
    4066           4 :                     std::lock_guard oLock(oMutex);
    4067           2 :                     bSuccess = false;
    4068           2 :                     bStop = true;
    4069           2 :                     break;
    4070             :                 }
    4071           8 :                 aosEtags[iChunk] = std::move(osEtag);
    4072             :             }
    4073             : 
    4074          10 :             if (bRunInThread)
    4075             :             {
    4076           4 :                 std::lock_guard oLock(oMutex);
    4077           2 :                 oCV.notify_one();
    4078             :             }
    4079             :             else
    4080             :             {
    4081          10 :                 if (pProgressFunc &&
    4082          10 :                     !pProgressFunc(double(iChunk) / std::max(1, nChunkCount),
    4083             :                                    osMsg.c_str(), pProgressData))
    4084             :                 {
    4085             :                     // Lock taken only to make static analyzer happy...
    4086           0 :                     std::lock_guard oLock(oMutex);
    4087           0 :                     bSuccess = false;
    4088           0 :                     break;
    4089             :                 }
    4090             :             }
    4091          10 :         }
    4092           7 :     };
    4093             : 
    4094           7 :     if (bRunInThread)
    4095             :     {
    4096           2 :         std::vector<std::thread> aThreads;
    4097           3 :         for (int i = 0; i < nNeededThreads; i++)
    4098             :         {
    4099           2 :             aThreads.emplace_back(std::thread(threadFunc));
    4100             :         }
    4101           1 :         if (pProgressFunc)
    4102             :         {
    4103           0 :             std::unique_lock oLock(oMutex);
    4104           0 :             while (!bStop)
    4105             :             {
    4106           0 :                 oCV.wait(oLock);
    4107             :                 // coverity[ uninit_use_in_call]
    4108           0 :                 oLock.unlock();
    4109             :                 const bool bInterrupt =
    4110           0 :                     !pProgressFunc(double(iCurChunk) / nChunkCount,
    4111           0 :                                    osMsg.c_str(), pProgressData);
    4112           0 :                 oLock.lock();
    4113           0 :                 if (bInterrupt)
    4114             :                 {
    4115           0 :                     bSuccess = false;
    4116           0 :                     bStop = true;
    4117           0 :                     break;
    4118             :                 }
    4119             :             }
    4120             :         }
    4121           3 :         for (auto &thread : aThreads)
    4122             :         {
    4123           2 :             thread.join();
    4124             :         }
    4125             :     }
    4126             :     else
    4127             :     {
    4128           6 :         threadFunc();
    4129             :     }
    4130             : 
    4131           7 :     if (bAbort)
    4132             :     {
    4133           0 :         AbortMultipart(pszTarget, osUploadID, poS3HandleHelper.get(),
    4134           0 :                        oRetryParameters);
    4135           0 :         return -1;
    4136             :     }
    4137           7 :     else if (!bSuccess)
    4138             :     {
    4139             :         // Compose an output restart payload
    4140           4 :         CPLJSONDocument oDoc;
    4141           4 :         auto oRoot = oDoc.GetRoot();
    4142           2 :         oRoot.Add("type", "CopyFileRestartablePayload");
    4143           2 :         oRoot.Add("source", pszSource);
    4144           2 :         oRoot.Add("target", pszTarget);
    4145           2 :         oRoot.Add("source_size", static_cast<uint64_t>(sStatBuf.st_size));
    4146           2 :         oRoot.Add("source_mtime", static_cast<GIntBig>(sStatBuf.st_mtime));
    4147           2 :         oRoot.Add("chunk_size", static_cast<uint64_t>(nChunkSize));
    4148           2 :         oRoot.Add("upload_id", osUploadID);
    4149           2 :         CPLJSONArray oArray;
    4150           6 :         for (int iChunk = 0; iChunk < nChunkCount; ++iChunk)
    4151             :         {
    4152           4 :             if (aosEtags[iChunk].empty())
    4153           2 :                 oArray.AddNull();
    4154             :             else
    4155           2 :                 oArray.Add(aosEtags[iChunk]);
    4156             :         }
    4157           2 :         oRoot.Add("chunk_etags", oArray);
    4158           2 :         *ppszOutputPayload = CPLStrdup(oDoc.SaveAsString().c_str());
    4159           2 :         return 1;
    4160             :     }
    4161             : 
    4162           5 :     if (!CompleteMultipart(pszTarget, osUploadID, aosEtags, sStatBuf.st_size,
    4163           5 :                            poS3HandleHelper.get(), oRetryParameters))
    4164             :     {
    4165           1 :         AbortMultipart(pszTarget, osUploadID, poS3HandleHelper.get(),
    4166           1 :                        oRetryParameters);
    4167           1 :         return -1;
    4168             :     }
    4169             : 
    4170           4 :     return 0;
    4171             : }
    4172             : 
    4173             : /************************************************************************/
    4174             : /*                             CopyChunk()                              */
    4175             : /************************************************************************/
    4176             : 
    4177           4 : static bool CopyChunk(const char *pszSource, const char *pszTarget,
    4178             :                       vsi_l_offset nStartOffset, size_t nChunkSize)
    4179             : {
    4180           4 :     VSILFILE *fpIn = VSIFOpenExL(pszSource, "rb", TRUE);
    4181           4 :     if (fpIn == nullptr)
    4182             :     {
    4183           0 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszSource);
    4184           0 :         return false;
    4185             :     }
    4186             : 
    4187           4 :     VSILFILE *fpOut = VSIFOpenExL(pszTarget, "wb+", TRUE);
    4188           4 :     if (fpOut == nullptr)
    4189             :     {
    4190           0 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", pszTarget);
    4191           0 :         VSIFCloseL(fpIn);
    4192           0 :         return false;
    4193             :     }
    4194             : 
    4195           4 :     bool ret = true;
    4196           8 :     if (VSIFSeekL(fpIn, nStartOffset, SEEK_SET) < 0 ||
    4197           4 :         VSIFSeekL(fpOut, nStartOffset, SEEK_SET) < 0)
    4198             :     {
    4199           0 :         ret = false;
    4200             :     }
    4201             :     else
    4202             :     {
    4203           4 :         void *pBuffer = VSI_MALLOC_VERBOSE(nChunkSize);
    4204           4 :         if (pBuffer == nullptr)
    4205             :         {
    4206           0 :             ret = false;
    4207             :         }
    4208             :         else
    4209             :         {
    4210           8 :             if (VSIFReadL(pBuffer, 1, nChunkSize, fpIn) != nChunkSize ||
    4211           4 :                 VSIFWriteL(pBuffer, 1, nChunkSize, fpOut) != nChunkSize)
    4212             :             {
    4213           0 :                 ret = false;
    4214             :             }
    4215             :         }
    4216           4 :         VSIFree(pBuffer);
    4217             :     }
    4218             : 
    4219           4 :     VSIFCloseL(fpIn);
    4220           4 :     if (VSIFCloseL(fpOut) != 0)
    4221             :     {
    4222           0 :         ret = false;
    4223             :     }
    4224           4 :     if (!ret)
    4225             :     {
    4226           0 :         CPLError(CE_Failure, CPLE_FileIO, "Copying of %s to %s failed",
    4227             :                  pszSource, pszTarget);
    4228             :     }
    4229           4 :     return ret;
    4230             : }
    4231             : 
    4232             : /************************************************************************/
    4233             : /*                                Sync()                                */
    4234             : /************************************************************************/
    4235             : 
    4236          35 : bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget,
    4237             :                                const char *const *papszOptions,
    4238             :                                GDALProgressFunc pProgressFunc,
    4239             :                                void *pProgressData, char ***ppapszOutputs)
    4240             : {
    4241          35 :     if (ppapszOutputs)
    4242             :     {
    4243           0 :         *ppapszOutputs = nullptr;
    4244             :     }
    4245             : 
    4246          70 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    4247          70 :     NetworkStatisticsAction oContextAction("Sync");
    4248             : 
    4249          70 :     std::string osSource(pszSource);
    4250          70 :     std::string osSourceWithoutSlash(pszSource);
    4251          66 :     if (osSourceWithoutSlash.back() == '/' ||
    4252          31 :         osSourceWithoutSlash.back() == '\\')
    4253             :     {
    4254           4 :         osSourceWithoutSlash.pop_back();
    4255             :     }
    4256             : 
    4257             :     const CPLHTTPRetryParameters oRetryParameters(
    4258          70 :         CPLStringList(CPLHTTPGetOptionsFromEnv(pszSource)));
    4259             : 
    4260          35 :     const bool bRecursive = CPLFetchBool(papszOptions, "RECURSIVE", true);
    4261             : 
    4262             :     enum class SyncStrategy
    4263             :     {
    4264             :         TIMESTAMP,
    4265             :         ETAG,
    4266             :         OVERWRITE
    4267             :     };
    4268          35 :     SyncStrategy eSyncStrategy = SyncStrategy::TIMESTAMP;
    4269             :     const char *pszSyncStrategy =
    4270          35 :         CSLFetchNameValueDef(papszOptions, "SYNC_STRATEGY", "TIMESTAMP");
    4271          35 :     if (EQUAL(pszSyncStrategy, "TIMESTAMP"))
    4272          22 :         eSyncStrategy = SyncStrategy::TIMESTAMP;
    4273          13 :     else if (EQUAL(pszSyncStrategy, "ETAG"))
    4274          11 :         eSyncStrategy = SyncStrategy::ETAG;
    4275           2 :     else if (EQUAL(pszSyncStrategy, "OVERWRITE"))
    4276           2 :         eSyncStrategy = SyncStrategy::OVERWRITE;
    4277             :     else
    4278             :     {
    4279           0 :         CPLError(CE_Warning, CPLE_NotSupported,
    4280             :                  "Unsupported value for SYNC_STRATEGY: %s", pszSyncStrategy);
    4281             :     }
    4282             : 
    4283             :     const bool bDownloadFromNetworkToLocal =
    4284          69 :         (!STARTS_WITH(pszTarget, "/vsi") ||
    4285          48 :          STARTS_WITH(pszTarget, "/vsimem/")) &&
    4286          48 :         STARTS_WITH(pszSource, GetFSPrefix().c_str());
    4287          35 :     const bool bTargetIsThisFS = STARTS_WITH(pszTarget, GetFSPrefix().c_str());
    4288          35 :     const bool bUploadFromLocalToNetwork =
    4289          68 :         (!STARTS_WITH(pszSource, "/vsi") ||
    4290          35 :          STARTS_WITH(pszSource, "/vsimem/")) &&
    4291             :         bTargetIsThisFS;
    4292             : 
    4293             :     // If the source is likely to be a directory, try to issue a ReadDir()
    4294             :     // if we haven't stat'ed it yet
    4295          35 :     std::unique_ptr<VSIDIR> poSourceDir;
    4296          55 :     if (STARTS_WITH(pszSource, GetFSPrefix().c_str()) &&
    4297          20 :         (osSource.back() == '/' || osSource.back() == '\\'))
    4298             :     {
    4299           3 :         const char *const apszOptions[] = {"SYNTHETIZE_MISSING_DIRECTORIES=YES",
    4300             :                                            nullptr};
    4301           3 :         poSourceDir.reset(VSIOpenDir(osSourceWithoutSlash.c_str(),
    4302             :                                      bRecursive ? -1 : 0, apszOptions));
    4303             :     }
    4304             : 
    4305             :     VSIStatBufL sSource;
    4306          35 :     if (VSIStatL(osSourceWithoutSlash.c_str(), &sSource) < 0)
    4307             :     {
    4308           2 :         CPLError(CE_Failure, CPLE_FileIO, "%s does not exist", pszSource);
    4309           2 :         return false;
    4310             :     }
    4311             : 
    4312             :     const auto CanSkipDownloadFromNetworkToLocal =
    4313           8 :         [this, eSyncStrategy](
    4314             :             const char *l_pszSource, const char *l_pszTarget,
    4315             :             GIntBig sourceTime, GIntBig targetTime,
    4316          12 :             const std::function<std::string(const char *)> &getETAGSourceFile)
    4317             :     {
    4318           8 :         switch (eSyncStrategy)
    4319             :         {
    4320           4 :             case SyncStrategy::ETAG:
    4321             :             {
    4322           4 :                 VSILFILE *fpOutAsIn = VSIFOpenExL(l_pszTarget, "rb", TRUE);
    4323           4 :                 if (fpOutAsIn)
    4324             :                 {
    4325           4 :                     std::string md5 = ComputeMD5OfLocalFile(fpOutAsIn);
    4326           4 :                     VSIFCloseL(fpOutAsIn);
    4327           4 :                     if (getETAGSourceFile(l_pszSource) == md5)
    4328             :                     {
    4329           3 :                         CPLDebug(GetDebugKey(),
    4330             :                                  "%s has already same content as %s",
    4331             :                                  l_pszTarget, l_pszSource);
    4332           3 :                         return true;
    4333             :                     }
    4334             :                 }
    4335           1 :                 return false;
    4336             :             }
    4337             : 
    4338           3 :             case SyncStrategy::TIMESTAMP:
    4339             :             {
    4340           3 :                 if (targetTime <= sourceTime)
    4341             :                 {
    4342             :                     // Our local copy is older than the source, so
    4343             :                     // presumably the source was uploaded from it. Nothing to do
    4344           1 :                     CPLDebug(GetDebugKey(),
    4345             :                              "%s is older than %s. "
    4346             :                              "Do not replace %s assuming it was used to "
    4347             :                              "upload %s",
    4348             :                              l_pszTarget, l_pszSource, l_pszTarget,
    4349             :                              l_pszSource);
    4350           1 :                     return true;
    4351             :                 }
    4352           2 :                 return false;
    4353             :             }
    4354             : 
    4355           1 :             case SyncStrategy::OVERWRITE:
    4356             :             {
    4357           1 :                 break;
    4358             :             }
    4359             :         }
    4360           1 :         return false;
    4361          33 :     };
    4362             : 
    4363             :     const auto CanSkipUploadFromLocalToNetwork =
    4364           7 :         [this, eSyncStrategy](
    4365             :             VSILFILE *&l_fpIn, const char *l_pszSource, const char *l_pszTarget,
    4366             :             GIntBig sourceTime, GIntBig targetTime,
    4367          12 :             const std::function<std::string(const char *)> &getETAGTargetFile)
    4368             :     {
    4369           7 :         switch (eSyncStrategy)
    4370             :         {
    4371           4 :             case SyncStrategy::ETAG:
    4372             :             {
    4373           4 :                 l_fpIn = VSIFOpenExL(l_pszSource, "rb", TRUE);
    4374           8 :                 if (l_fpIn && getETAGTargetFile(l_pszTarget) ==
    4375           8 :                                   ComputeMD5OfLocalFile(l_fpIn))
    4376             :                 {
    4377           4 :                     CPLDebug(GetDebugKey(), "%s has already same content as %s",
    4378             :                              l_pszTarget, l_pszSource);
    4379           4 :                     VSIFCloseL(l_fpIn);
    4380           4 :                     l_fpIn = nullptr;
    4381           4 :                     return true;
    4382             :                 }
    4383           0 :                 return false;
    4384             :             }
    4385             : 
    4386           2 :             case SyncStrategy::TIMESTAMP:
    4387             :             {
    4388           2 :                 if (targetTime >= sourceTime)
    4389             :                 {
    4390             :                     // The remote copy is more recent than the source, so
    4391             :                     // presumably it was uploaded from the source. Nothing to do
    4392           1 :                     CPLDebug(GetDebugKey(),
    4393             :                              "%s is more recent than %s. "
    4394             :                              "Do not replace %s assuming it was uploaded from "
    4395             :                              "%s",
    4396             :                              l_pszTarget, l_pszSource, l_pszTarget,
    4397             :                              l_pszSource);
    4398           1 :                     return true;
    4399             :                 }
    4400           1 :                 return false;
    4401             :             }
    4402             : 
    4403           1 :             case SyncStrategy::OVERWRITE:
    4404             :             {
    4405           1 :                 break;
    4406             :             }
    4407             :         }
    4408           1 :         return false;
    4409          33 :     };
    4410             : 
    4411             :     struct ChunkToCopy
    4412             :     {
    4413             :         std::string osSrcFilename{};
    4414             :         std::string osDstFilename{};
    4415             :         GIntBig nMTime = 0;
    4416             :         std::string osETag{};
    4417             :         vsi_l_offset nTotalSize = 0;
    4418             :         vsi_l_offset nStartOffset = 0;
    4419             :         vsi_l_offset nSize = 0;
    4420             :     };
    4421             : 
    4422          66 :     std::vector<ChunkToCopy> aoChunksToCopy;
    4423          66 :     std::set<std::string> aoSetDirsToCreate;
    4424          33 :     const char *pszChunkSize = CSLFetchNameValue(papszOptions, "CHUNK_SIZE");
    4425          33 :     const int nRequestedThreads = GetRequestedNumThreadsForCopy(papszOptions);
    4426             :     auto poTargetFSMultipartHandler =
    4427           0 :         dynamic_cast<IVSIS3LikeFSHandlerWithMultipartUpload *>(
    4428          33 :             VSIFileManager::GetHandler(pszTarget));
    4429             :     const bool bSupportsParallelMultipartUpload =
    4430          47 :         bUploadFromLocalToNetwork && poTargetFSMultipartHandler != nullptr &&
    4431          14 :         poTargetFSMultipartHandler->SupportsParallelMultipartUpload();
    4432             :     const bool bSimulateThreading =
    4433          33 :         CPLTestBool(CPLGetConfigOption("VSIS3_SIMULATE_THREADING", "NO"));
    4434          33 :     const int nMinSizeChunk =
    4435          14 :         bSupportsParallelMultipartUpload && !bSimulateThreading
    4436          47 :             ? 8 * MIB_CONSTANT
    4437             :             : 1;  // 5242880 defined by S3 API as the minimum, but 8 MB used by
    4438             :                   // default by the Python s3transfer library
    4439          33 :     const int nMinThreads = bSimulateThreading ? 0 : 1;
    4440             :     const size_t nMaxChunkSize =
    4441           6 :         pszChunkSize && nRequestedThreads > nMinThreads &&
    4442           5 :                 (bDownloadFromNetworkToLocal ||
    4443             :                  bSupportsParallelMultipartUpload)
    4444          33 :             ? static_cast<size_t>(
    4445           6 :                   std::min(1024 * MIB_CONSTANT,
    4446           6 :                            std::max(nMinSizeChunk, atoi(pszChunkSize))))
    4447          33 :             : 0;
    4448             : 
    4449             :     // Filter x-amz- options when outputting to /vsis3/
    4450          66 :     CPLStringList aosObjectCreationOptions;
    4451          33 :     if (poTargetFSMultipartHandler != nullptr && papszOptions != nullptr)
    4452             :     {
    4453          37 :         for (auto papszIter = papszOptions; *papszIter != nullptr; ++papszIter)
    4454             :         {
    4455          22 :             char *pszKey = nullptr;
    4456          22 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
    4457          44 :             if (pszKey && pszValue &&
    4458          22 :                 poTargetFSMultipartHandler->IsAllowedHeaderForObjectCreation(
    4459          22 :                     pszKey))
    4460             :             {
    4461           3 :                 aosObjectCreationOptions.SetNameValue(pszKey, pszValue);
    4462             :             }
    4463          22 :             CPLFree(pszKey);
    4464             :         }
    4465             :     }
    4466             : 
    4467          33 :     uint64_t nTotalSize = 0;
    4468          66 :     std::vector<size_t> anIndexToCopy;  // points to aoChunksToCopy
    4469             : 
    4470             :     struct MultiPartDef
    4471             :     {
    4472             :         std::string osUploadID{};
    4473             :         int nCountValidETags = 0;
    4474             :         int nExpectedCount = 0;
    4475             :         // cppcheck-suppress unusedStructMember
    4476             :         std::vector<std::string> aosEtags{};
    4477             :         vsi_l_offset nTotalSize = 0;
    4478             :     };
    4479             : 
    4480          66 :     std::map<std::string, MultiPartDef> oMapMultiPartDefs;
    4481             : 
    4482             :     // Cleanup pending uploads in case of early exit
    4483             :     struct CleanupPendingUploads
    4484             :     {
    4485             :         IVSIS3LikeFSHandlerWithMultipartUpload *m_poFS;
    4486             :         std::map<std::string, MultiPartDef> &m_oMapMultiPartDefs;
    4487             :         const CPLHTTPRetryParameters &m_oRetryParameters;
    4488             : 
    4489          33 :         CleanupPendingUploads(
    4490             :             IVSIS3LikeFSHandlerWithMultipartUpload *poFSIn,
    4491             :             std::map<std::string, MultiPartDef> &oMapMultiPartDefsIn,
    4492             :             const CPLHTTPRetryParameters &oRetryParametersIn)
    4493          33 :             : m_poFS(poFSIn), m_oMapMultiPartDefs(oMapMultiPartDefsIn),
    4494          33 :               m_oRetryParameters(oRetryParametersIn)
    4495             :         {
    4496          33 :         }
    4497             : 
    4498          33 :         ~CleanupPendingUploads()
    4499          33 :         {
    4500          33 :             if (m_poFS)
    4501             :             {
    4502          22 :                 for (const auto &kv : m_oMapMultiPartDefs)
    4503             :                 {
    4504             :                     auto poS3HandleHelper =
    4505             :                         std::unique_ptr<IVSIS3LikeHandleHelper>(
    4506           1 :                             m_poFS->CreateHandleHelper(
    4507           1 :                                 kv.first.c_str() + m_poFS->GetFSPrefix().size(),
    4508           3 :                                 false));
    4509           1 :                     if (poS3HandleHelper)
    4510             :                     {
    4511           1 :                         m_poFS->AbortMultipart(kv.first, kv.second.osUploadID,
    4512             :                                                poS3HandleHelper.get(),
    4513           1 :                                                m_oRetryParameters);
    4514             :                     }
    4515             :                 }
    4516             :             }
    4517          33 :         }
    4518             : 
    4519             :         CleanupPendingUploads(const CleanupPendingUploads &) = delete;
    4520             :         CleanupPendingUploads &
    4521             :         operator=(const CleanupPendingUploads &) = delete;
    4522             :     };
    4523             : 
    4524             :     const CleanupPendingUploads cleanupPendingUploads(
    4525          66 :         poTargetFSMultipartHandler, oMapMultiPartDefs, oRetryParameters);
    4526             : 
    4527          66 :     std::string osTargetDir;  // set in the VSI_ISDIR(sSource.st_mode) case
    4528          66 :     std::string osTarget;     // set in the !(VSI_ISDIR(sSource.st_mode)) case
    4529             : 
    4530             :     const auto NormalizeDirSeparatorForDstFilename =
    4531          60 :         [&osSource, &osTargetDir](const std::string &s) -> std::string
    4532             :     {
    4533          40 :         return CPLString(s).replaceAll(
    4534             :             VSIGetDirectorySeparator(osSource.c_str()),
    4535          40 :             VSIGetDirectorySeparator(osTargetDir.c_str()));
    4536          33 :     };
    4537             : 
    4538          33 :     if (VSI_ISDIR(sSource.st_mode))
    4539             :     {
    4540          10 :         osTargetDir = pszTarget;
    4541          10 :         if (osSource.back() != '/' && osSource.back() != '\\')
    4542             :         {
    4543          12 :             osTargetDir = CPLFormFilenameSafe(
    4544           6 :                 osTargetDir.c_str(), CPLGetFilename(pszSource), nullptr);
    4545             :         }
    4546             : 
    4547          10 :         if (!poSourceDir)
    4548             :         {
    4549           7 :             const char *const apszOptions[] = {
    4550             :                 "SYNTHETIZE_MISSING_DIRECTORIES=YES", nullptr};
    4551           7 :             poSourceDir.reset(VSIOpenDir(osSourceWithoutSlash.c_str(),
    4552             :                                          bRecursive ? -1 : 0, apszOptions));
    4553           7 :             if (!poSourceDir)
    4554           0 :                 return false;
    4555             :         }
    4556             : 
    4557             :         auto poTargetDir = std::unique_ptr<VSIDIR>(
    4558          10 :             VSIOpenDir(osTargetDir.c_str(), bRecursive ? -1 : 0, nullptr));
    4559          10 :         std::set<std::string> oSetTargetSubdirs;
    4560          10 :         std::map<std::string, VSIDIREntry> oMapExistingTargetFiles;
    4561             :         // Enumerate existing target files and directories
    4562          10 :         if (poTargetDir)
    4563             :         {
    4564             :             while (true)
    4565             :             {
    4566           7 :                 const auto entry = VSIGetNextDirEntry(poTargetDir.get());
    4567           7 :                 if (!entry)
    4568           4 :                     break;
    4569             :                 auto osDstName =
    4570           9 :                     NormalizeDirSeparatorForDstFilename(entry->pszName);
    4571           3 :                 if (VSI_ISDIR(entry->nMode))
    4572             :                 {
    4573           0 :                     oSetTargetSubdirs.insert(std::move(osDstName));
    4574             :                 }
    4575             :                 else
    4576             :                 {
    4577             :                     oMapExistingTargetFiles.insert(
    4578           3 :                         std::make_pair(std::move(osDstName), *entry));
    4579             :                 }
    4580           3 :             }
    4581           4 :             poTargetDir.reset();
    4582             :         }
    4583             :         else
    4584             :         {
    4585             :             VSIStatBufL sTarget;
    4586          12 :             if (VSIStatL(osTargetDir.c_str(), &sTarget) < 0 &&
    4587           6 :                 VSIMkdirRecursive(osTargetDir.c_str(), 0755) < 0)
    4588             :             {
    4589           0 :                 CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s",
    4590             :                          osTargetDir.c_str());
    4591           0 :                 return false;
    4592             :             }
    4593             :         }
    4594             : 
    4595             :         // Enumerate source files and directories
    4596             :         while (true)
    4597             :         {
    4598          27 :             const auto entry = VSIGetNextDirEntry(poSourceDir.get());
    4599          27 :             if (!entry)
    4600          10 :                 break;
    4601          17 :             if (VSI_ISDIR(entry->nMode))
    4602             :             {
    4603             :                 const auto osDstName =
    4604           3 :                     NormalizeDirSeparatorForDstFilename(entry->pszName);
    4605           1 :                 if (oSetTargetSubdirs.find(osDstName) ==
    4606           2 :                     oSetTargetSubdirs.end())
    4607             :                 {
    4608             :                     std::string osTargetSubdir(CPLFormFilenameSafe(
    4609           2 :                         osTargetDir.c_str(), osDstName.c_str(), nullptr));
    4610           1 :                     aoSetDirsToCreate.insert(std::move(osTargetSubdir));
    4611             :                 }
    4612             :             }
    4613             :             else
    4614             :             {
    4615             :                 // Split file in possibly multiple chunks
    4616             :                 const vsi_l_offset nChunksLarge =
    4617             :                     nMaxChunkSize == 0
    4618          22 :                         ? 1
    4619             :                         : std::max<vsi_l_offset>(
    4620          22 :                               1,
    4621           6 :                               cpl::div_round_up(entry->nSize, nMaxChunkSize));
    4622          16 :                 if (nChunksLarge >
    4623             :                     1000)  // must also be below knMAX_PART_NUMBER for upload
    4624             :                 {
    4625           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    4626             :                              "Too small CHUNK_SIZE w.r.t file size");
    4627           0 :                     return false;
    4628             :                 }
    4629          32 :                 ChunkToCopy chunk;
    4630          16 :                 chunk.osSrcFilename = entry->pszName;
    4631             :                 chunk.osDstFilename =
    4632          16 :                     NormalizeDirSeparatorForDstFilename(entry->pszName);
    4633          16 :                 chunk.nMTime = entry->nMTime;
    4634          16 :                 chunk.nTotalSize = entry->nSize;
    4635             :                 chunk.osETag =
    4636          16 :                     CSLFetchNameValueDef(entry->papszExtra, "ETag", "");
    4637          16 :                 const size_t nChunks = static_cast<size_t>(nChunksLarge);
    4638          37 :                 for (size_t iChunk = 0; iChunk < nChunks; iChunk++)
    4639             :                 {
    4640          21 :                     chunk.nStartOffset = iChunk * nMaxChunkSize;
    4641          21 :                     chunk.nSize =
    4642             :                         nChunks == 1
    4643          31 :                             ? entry->nSize
    4644             :                             : std::min(
    4645          31 :                                   entry->nSize - chunk.nStartOffset,
    4646          10 :                                   static_cast<vsi_l_offset>(nMaxChunkSize));
    4647          21 :                     aoChunksToCopy.push_back(chunk);
    4648          21 :                     chunk.osETag.clear();
    4649             :                 }
    4650             :             }
    4651          17 :         }
    4652          10 :         poSourceDir.reset();
    4653             : 
    4654             :         // Create missing target directories, sorted in lexicographic order
    4655             :         // so that upper-level directories are listed before subdirectories.
    4656          11 :         for (const auto &osTargetSubdir : aoSetDirsToCreate)
    4657             :         {
    4658             :             const bool ok =
    4659             :                 (bTargetIsThisFS
    4660           1 :                      ? MkdirInternal(osTargetSubdir.c_str(), 0755, false)
    4661           1 :                      : VSIMkdir(osTargetSubdir.c_str(), 0755)) == 0;
    4662           1 :             if (!ok)
    4663             :             {
    4664           0 :                 CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s",
    4665             :                          osTargetSubdir.c_str());
    4666           0 :                 return false;
    4667             :             }
    4668             :         }
    4669             : 
    4670             :         // Collect source files to copy
    4671          10 :         const size_t nChunkCount = aoChunksToCopy.size();
    4672          26 :         for (size_t iChunk = 0; iChunk < nChunkCount; ++iChunk)
    4673             :         {
    4674          16 :             const auto &chunk = aoChunksToCopy[iChunk];
    4675          16 :             if (chunk.nStartOffset != 0)
    4676           0 :                 continue;
    4677             :             const std::string osSubSource(
    4678             :                 CPLFormFilenameSafe(osSourceWithoutSlash.c_str(),
    4679          16 :                                     chunk.osSrcFilename.c_str(), nullptr));
    4680             :             const std::string osSubTarget(CPLFormFilenameSafe(
    4681          16 :                 osTargetDir.c_str(), chunk.osDstFilename.c_str(), nullptr));
    4682          16 :             bool bSkip = false;
    4683             :             const auto oIterExistingTarget =
    4684          16 :                 oMapExistingTargetFiles.find(chunk.osDstFilename);
    4685          19 :             if (oIterExistingTarget != oMapExistingTargetFiles.end() &&
    4686           3 :                 oIterExistingTarget->second.nSize == chunk.nTotalSize)
    4687             :             {
    4688           3 :                 if (bDownloadFromNetworkToLocal)
    4689             :                 {
    4690           0 :                     if (CanSkipDownloadFromNetworkToLocal(
    4691             :                             osSubSource.c_str(), osSubTarget.c_str(),
    4692           0 :                             chunk.nMTime, oIterExistingTarget->second.nMTime,
    4693           0 :                             [&chunk](const char *) -> std::string
    4694           0 :                             { return chunk.osETag; }))
    4695             :                     {
    4696           0 :                         bSkip = true;
    4697             :                     }
    4698             :                 }
    4699           3 :                 else if (bUploadFromLocalToNetwork)
    4700             :                 {
    4701           1 :                     VSILFILE *fpIn = nullptr;
    4702           2 :                     if (CanSkipUploadFromLocalToNetwork(
    4703             :                             fpIn, osSubSource.c_str(), osSubTarget.c_str(),
    4704           1 :                             chunk.nMTime, oIterExistingTarget->second.nMTime,
    4705           2 :                             [&oIterExistingTarget](const char *) -> std::string
    4706             :                             {
    4707             :                                 return std::string(CSLFetchNameValueDef(
    4708           1 :                                     oIterExistingTarget->second.papszExtra,
    4709           2 :                                     "ETag", ""));
    4710             :                             }))
    4711             :                     {
    4712           1 :                         bSkip = true;
    4713             :                     }
    4714           1 :                     if (fpIn)
    4715           0 :                         VSIFCloseL(fpIn);
    4716             :                 }
    4717             :                 else
    4718             :                 {
    4719             : 
    4720           4 :                     if (eSyncStrategy == SyncStrategy::TIMESTAMP &&
    4721           2 :                         chunk.nMTime < oIterExistingTarget->second.nMTime)
    4722             :                     {
    4723             :                         // The target is more recent than the source.
    4724             :                         // Nothing to do
    4725           1 :                         CPLDebug(GetDebugKey(),
    4726             :                                  "%s is older than %s. "
    4727             :                                  "Do not replace %s assuming it was used to "
    4728             :                                  "upload %s",
    4729             :                                  osSubSource.c_str(), osSubTarget.c_str(),
    4730             :                                  osSubTarget.c_str(), osSubSource.c_str());
    4731           1 :                         bSkip = true;
    4732             :                     }
    4733             :                 }
    4734             :             }
    4735             : 
    4736          16 :             if (!bSkip)
    4737             :             {
    4738          14 :                 anIndexToCopy.push_back(iChunk);
    4739          14 :                 nTotalSize += chunk.nTotalSize;
    4740          14 :                 if (chunk.nSize < chunk.nTotalSize)
    4741             :                 {
    4742           5 :                     if (bDownloadFromNetworkToLocal)
    4743             :                     {
    4744             :                         // Suppress target file as we're going to open in wb+
    4745             :                         // mode for parallelized writing
    4746           2 :                         VSIUnlink(osSubTarget.c_str());
    4747             :                     }
    4748           3 :                     else if (bSupportsParallelMultipartUpload)
    4749             :                     {
    4750             :                         auto poS3HandleHelper =
    4751             :                             std::unique_ptr<IVSIS3LikeHandleHelper>(
    4752           3 :                                 CreateHandleHelper(osSubTarget.c_str() +
    4753           3 :                                                        GetFSPrefix().size(),
    4754           6 :                                                    false));
    4755           3 :                         if (poS3HandleHelper == nullptr)
    4756           0 :                             return false;
    4757             : 
    4758             :                         std::string osUploadID =
    4759             :                             poTargetFSMultipartHandler->InitiateMultipartUpload(
    4760             :                                 osSubTarget, poS3HandleHelper.get(),
    4761             :                                 oRetryParameters,
    4762           3 :                                 aosObjectCreationOptions.List());
    4763           3 :                         if (osUploadID.empty())
    4764             :                         {
    4765           0 :                             return false;
    4766             :                         }
    4767           6 :                         MultiPartDef def;
    4768           3 :                         def.osUploadID = std::move(osUploadID);
    4769           3 :                         def.nExpectedCount = static_cast<int>(
    4770           3 :                             cpl::div_round_up(chunk.nTotalSize, chunk.nSize));
    4771           3 :                         def.nTotalSize = chunk.nTotalSize;
    4772           3 :                         oMapMultiPartDefs[osSubTarget] = std::move(def);
    4773             :                     }
    4774             :                     else
    4775             :                     {
    4776           0 :                         CPLAssert(false);
    4777             :                     }
    4778             : 
    4779             :                     // Include all remaining chunks of the same file
    4780          16 :                     while (iChunk + 1 < nChunkCount &&
    4781           6 :                            aoChunksToCopy[iChunk + 1].nStartOffset > 0)
    4782             :                     {
    4783           5 :                         ++iChunk;
    4784           5 :                         anIndexToCopy.push_back(iChunk);
    4785             :                     }
    4786             :                 }
    4787             :             }
    4788             :         }
    4789             : 
    4790          20 :         const int nThreads = std::min(std::max(1, nRequestedThreads),
    4791          10 :                                       static_cast<int>(anIndexToCopy.size()));
    4792          10 :         if (nThreads <= nMinThreads)
    4793             :         {
    4794             :             // Proceed to file copy
    4795           4 :             bool ret = true;
    4796           4 :             uint64_t nAccSize = 0;
    4797           4 :             const uint64_t nTotalSizeDenom = std::max<uint64_t>(1, nTotalSize);
    4798           6 :             for (const size_t iChunk : anIndexToCopy)
    4799             :             {
    4800           2 :                 const auto &chunk = aoChunksToCopy[iChunk];
    4801           2 :                 CPLAssert(chunk.nStartOffset == 0);
    4802             :                 const std::string osSubSource(
    4803             :                     CPLFormFilenameSafe(osSourceWithoutSlash.c_str(),
    4804           2 :                                         chunk.osSrcFilename.c_str(), nullptr));
    4805             :                 const std::string osSubTarget(CPLFormFilenameSafe(
    4806           2 :                     osTargetDir.c_str(), chunk.osDstFilename.c_str(), nullptr));
    4807           4 :                 void *pScaledProgress = GDALCreateScaledProgress(
    4808           2 :                     double(nAccSize) / nTotalSizeDenom,
    4809           2 :                     double(nAccSize + chunk.nSize) / nTotalSizeDenom,
    4810             :                     pProgressFunc, pProgressData);
    4811           2 :                 ret =
    4812           2 :                     CopyFile(osSubSource.c_str(), osSubTarget.c_str(), nullptr,
    4813           2 :                              chunk.nSize, aosObjectCreationOptions.List(),
    4814           2 :                              GDALScaledProgress, pScaledProgress) == 0;
    4815           2 :                 GDALDestroyScaledProgress(pScaledProgress);
    4816           2 :                 if (!ret)
    4817             :                 {
    4818           0 :                     break;
    4819             :                 }
    4820           2 :                 nAccSize += chunk.nSize;
    4821             :             }
    4822             : 
    4823           4 :             return ret;
    4824             :         }
    4825             :     }
    4826             :     else
    4827             :     {
    4828          23 :         std::string osMsg("Copying of ");
    4829          23 :         osMsg += osSourceWithoutSlash;
    4830             : 
    4831             :         VSIStatBufL sTarget;
    4832          23 :         osTarget = pszTarget;
    4833          23 :         bool bTargetIsFile = false;
    4834          23 :         sTarget.st_size = 0;
    4835          23 :         if (VSIStatL(osTarget.c_str(), &sTarget) == 0)
    4836             :         {
    4837          20 :             bTargetIsFile = true;
    4838          20 :             if (VSI_ISDIR(sTarget.st_mode))
    4839             :             {
    4840          26 :                 osTarget = CPLFormFilenameSafe(
    4841          13 :                     osTarget.c_str(), CPLGetFilename(pszSource), nullptr);
    4842          23 :                 bTargetIsFile = VSIStatL(osTarget.c_str(), &sTarget) == 0 &&
    4843          10 :                                 !VSI_ISDIR(sTarget.st_mode);
    4844             :             }
    4845             :         }
    4846             : 
    4847          23 :         if (eSyncStrategy == SyncStrategy::TIMESTAMP && bTargetIsFile &&
    4848           8 :             !bDownloadFromNetworkToLocal && !bUploadFromLocalToNetwork &&
    4849           3 :             sSource.st_size == sTarget.st_size &&
    4850           3 :             sSource.st_mtime < sTarget.st_mtime)
    4851             :         {
    4852             :             // The target is more recent than the source. Nothing to do
    4853           1 :             CPLDebug(GetDebugKey(),
    4854             :                      "%s is older than %s. "
    4855             :                      "Do not replace %s assuming it was used to "
    4856             :                      "upload %s",
    4857             :                      osSource.c_str(), osTarget.c_str(), osTarget.c_str(),
    4858             :                      osSource.c_str());
    4859           1 :             if (pProgressFunc)
    4860             :             {
    4861           0 :                 pProgressFunc(1.0, osMsg.c_str(), pProgressData);
    4862             :             }
    4863           1 :             return true;
    4864             :         }
    4865             : 
    4866             :         // Download from network to local file system ?
    4867          22 :         if (bTargetIsFile && bDownloadFromNetworkToLocal &&
    4868           8 :             sSource.st_size == sTarget.st_size)
    4869             :         {
    4870          16 :             if (CanSkipDownloadFromNetworkToLocal(
    4871             :                     osSourceWithoutSlash.c_str(), osTarget.c_str(),
    4872           8 :                     sSource.st_mtime, sTarget.st_mtime,
    4873           8 :                     [this](const char *pszFilename) -> std::string
    4874             :                     {
    4875           8 :                         FileProp cachedFileProp;
    4876           4 :                         if (GetCachedFileProp(
    4877           8 :                                 GetURLFromFilename(pszFilename).c_str(),
    4878             :                                 cachedFileProp))
    4879             :                         {
    4880           4 :                             return cachedFileProp.ETag;
    4881             :                         }
    4882           0 :                         return std::string();
    4883             :                     }))
    4884             :             {
    4885           4 :                 if (pProgressFunc)
    4886             :                 {
    4887           0 :                     pProgressFunc(1.0, osMsg.c_str(), pProgressData);
    4888             :                 }
    4889           4 :                 return true;
    4890             :             }
    4891             :         }
    4892             : 
    4893          18 :         VSILFILE *fpIn = nullptr;
    4894             : 
    4895             :         // Upload from local file system to network ?
    4896          18 :         if (bUploadFromLocalToNetwork && sSource.st_size == sTarget.st_size)
    4897             :         {
    4898          12 :             if (CanSkipUploadFromLocalToNetwork(
    4899             :                     fpIn, osSourceWithoutSlash.c_str(), osTarget.c_str(),
    4900           6 :                     sSource.st_mtime, sTarget.st_mtime,
    4901           6 :                     [this](const char *pszFilename) -> std::string
    4902             :                     {
    4903           6 :                         FileProp cachedFileProp;
    4904           3 :                         if (GetCachedFileProp(
    4905           6 :                                 GetURLFromFilename(pszFilename).c_str(),
    4906             :                                 cachedFileProp))
    4907             :                         {
    4908           3 :                             return cachedFileProp.ETag;
    4909             :                         }
    4910           0 :                         return std::string();
    4911             :                     }))
    4912             :             {
    4913           4 :                 if (pProgressFunc)
    4914             :                 {
    4915           0 :                     pProgressFunc(1.0, osMsg.c_str(), pProgressData);
    4916             :                 }
    4917           4 :                 return true;
    4918             :             }
    4919             :         }
    4920             : 
    4921             :         // Split file in possibly multiple chunks
    4922             :         const vsi_l_offset nChunksLarge =
    4923             :             nMaxChunkSize == 0
    4924          16 :                 ? 1
    4925           2 :                 : cpl::div_round_up(sSource.st_size, nMaxChunkSize);
    4926          14 :         if (nChunksLarge >
    4927             :             1000)  // must also be below knMAX_PART_NUMBER for upload
    4928             :         {
    4929           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4930             :                      "Too small CHUNK_SIZE w.r.t file size");
    4931           0 :             return false;
    4932             :         }
    4933          14 :         ChunkToCopy chunk;
    4934          14 :         chunk.nMTime = sSource.st_mtime;
    4935          14 :         chunk.nTotalSize = sSource.st_size;
    4936          14 :         nTotalSize = chunk.nTotalSize;
    4937          14 :         const size_t nChunks = static_cast<size_t>(nChunksLarge);
    4938          30 :         for (size_t iChunk = 0; iChunk < nChunks; iChunk++)
    4939             :         {
    4940          16 :             chunk.nStartOffset = iChunk * nMaxChunkSize;
    4941          16 :             chunk.nSize =
    4942             :                 nChunks == 1
    4943          20 :                     ? sSource.st_size
    4944          20 :                     : std::min(sSource.st_size - chunk.nStartOffset,
    4945           4 :                                static_cast<vsi_l_offset>(nMaxChunkSize));
    4946          16 :             aoChunksToCopy.push_back(chunk);
    4947          16 :             anIndexToCopy.push_back(iChunk);
    4948             : 
    4949          16 :             if (nChunks > 1)
    4950             :             {
    4951           4 :                 if (iChunk == 0)
    4952             :                 {
    4953           2 :                     if (bDownloadFromNetworkToLocal)
    4954             :                     {
    4955             :                         // Suppress target file as we're going to open in wb+
    4956             :                         // mode for parallelized writing
    4957           0 :                         VSIUnlink(osTarget.c_str());
    4958             :                     }
    4959           2 :                     else if (bSupportsParallelMultipartUpload)
    4960             :                     {
    4961             :                         auto poS3HandleHelper =
    4962             :                             std::unique_ptr<IVSIS3LikeHandleHelper>(
    4963           2 :                                 CreateHandleHelper(osTarget.c_str() +
    4964           2 :                                                        GetFSPrefix().size(),
    4965           4 :                                                    false));
    4966           2 :                         if (poS3HandleHelper == nullptr)
    4967           0 :                             return false;
    4968             : 
    4969           2 :                         MultiPartDef def;
    4970             :                         def.osUploadID =
    4971           4 :                             poTargetFSMultipartHandler->InitiateMultipartUpload(
    4972             :                                 osTarget, poS3HandleHelper.get(),
    4973             :                                 oRetryParameters,
    4974           4 :                                 aosObjectCreationOptions.List());
    4975           2 :                         if (def.osUploadID.empty())
    4976             :                         {
    4977           0 :                             return false;
    4978             :                         }
    4979           2 :                         def.nExpectedCount = static_cast<int>(
    4980           2 :                             cpl::div_round_up(chunk.nTotalSize, chunk.nSize));
    4981           2 :                         def.nTotalSize = chunk.nTotalSize;
    4982           2 :                         oMapMultiPartDefs[osTarget] = std::move(def);
    4983             :                     }
    4984             :                     else
    4985             :                     {
    4986           0 :                         CPLAssert(false);
    4987             :                     }
    4988             :                 }
    4989             :             }
    4990             :         }
    4991             : 
    4992          28 :         const int nThreads = std::min(std::max(1, nRequestedThreads),
    4993          14 :                                       static_cast<int>(anIndexToCopy.size()));
    4994          14 :         if (nThreads <= nMinThreads)
    4995             :         {
    4996             :             bool bRet =
    4997          12 :                 CopyFile(osSourceWithoutSlash.c_str(), osTarget.c_str(), fpIn,
    4998          12 :                          sSource.st_size, aosObjectCreationOptions.List(),
    4999          12 :                          pProgressFunc, pProgressData) == 0;
    5000          12 :             if (fpIn)
    5001             :             {
    5002           0 :                 VSIFCloseL(fpIn);
    5003             :             }
    5004          12 :             return bRet;
    5005             :         }
    5006           2 :         if (fpIn)
    5007             :         {
    5008           0 :             VSIFCloseL(fpIn);
    5009             :         }
    5010             :     }
    5011             : 
    5012          16 :     const int nThreads = std::min(std::max(1, nRequestedThreads),
    5013           8 :                                   static_cast<int>(anIndexToCopy.size()));
    5014             : 
    5015             :     struct JobQueue
    5016             :     {
    5017             :         // Immutable variables
    5018             :         IVSIS3LikeFSHandler *const poFS;
    5019             :         IVSIS3LikeFSHandlerWithMultipartUpload *const
    5020             :             poTargetFSMultipartHandler;
    5021             :         const std::vector<ChunkToCopy> &aoChunksToCopy;
    5022             :         const std::vector<size_t> &anIndexToCopy;
    5023             :         const std::string osSourceDir;
    5024             :         const std::string osTargetDir;
    5025             :         const std::string osSource;
    5026             :         const std::string osTarget;
    5027             :         const bool bSupportsParallelMultipartUpload;
    5028             :         const size_t nMaxChunkSize;
    5029             :         const CPLHTTPRetryParameters &oRetryParameters;
    5030             :         const CPLStringList &aosObjectCreationOptions;
    5031             : 
    5032             :         // All variables below are modified in threads and their access must
    5033             :         // be protected by sMutex.
    5034             :         std::mutex sMutex{};
    5035             :         std::map<std::string, MultiPartDef> &oMapMultiPartDefs;
    5036             :         uint64_t nTotalCopied = 0;
    5037             :         int iCurIdx = 0;
    5038             :         bool ret = true;
    5039             :         bool stop = false;
    5040             : 
    5041           8 :         JobQueue(IVSIS3LikeFSHandler *poFSIn,
    5042             :                  IVSIS3LikeFSHandlerWithMultipartUpload
    5043             :                      *poTargetFSMultipartHandlerIn,
    5044             :                  const std::vector<ChunkToCopy> &aoChunksToCopyIn,
    5045             :                  const std::vector<size_t> &anIndexToCopyIn,
    5046             :                  std::map<std::string, MultiPartDef> &oMapMultiPartDefsIn,
    5047             :                  const std::string &osSourceDirIn,
    5048             :                  const std::string &osTargetDirIn,
    5049             :                  const std::string &osSourceIn, const std::string &osTargetIn,
    5050             :                  bool bSupportsParallelMultipartUploadIn,
    5051             :                  size_t nMaxChunkSizeIn,
    5052             :                  const CPLHTTPRetryParameters &oRetryParametersIn,
    5053             :                  const CPLStringList &aosObjectCreationOptionsIn)
    5054           8 :             : poFS(poFSIn),
    5055             :               poTargetFSMultipartHandler(poTargetFSMultipartHandlerIn),
    5056             :               aoChunksToCopy(aoChunksToCopyIn), anIndexToCopy(anIndexToCopyIn),
    5057             :               osSourceDir(osSourceDirIn), osTargetDir(osTargetDirIn),
    5058             :               osSource(osSourceIn), osTarget(osTargetIn),
    5059             :               bSupportsParallelMultipartUpload(
    5060             :                   bSupportsParallelMultipartUploadIn),
    5061             :               nMaxChunkSize(nMaxChunkSizeIn),
    5062             :               oRetryParameters(oRetryParametersIn),
    5063             :               aosObjectCreationOptions(aosObjectCreationOptionsIn),
    5064           8 :               oMapMultiPartDefs(oMapMultiPartDefsIn)
    5065             :         {
    5066           8 :         }
    5067             : 
    5068             :         JobQueue(const JobQueue &) = delete;
    5069             :         JobQueue &operator=(const JobQueue &) = delete;
    5070             :     };
    5071             : 
    5072          11 :     const auto threadFunc = [](void *pDataIn)
    5073             :     {
    5074             :         struct ProgressData
    5075             :         {
    5076             :             uint64_t nFileSize;
    5077             :             double dfLastPct;
    5078             :             JobQueue *queue;
    5079             : 
    5080          17 :             static int CPL_STDCALL progressFunc(double pct, const char *,
    5081             :                                                 void *pProgressDataIn)
    5082             :             {
    5083          17 :                 ProgressData *pProgress =
    5084             :                     static_cast<ProgressData *>(pProgressDataIn);
    5085          17 :                 const auto nInc = static_cast<uint64_t>(
    5086          17 :                     (pct - pProgress->dfLastPct) * pProgress->nFileSize + 0.5);
    5087             :                 {
    5088          17 :                     std::lock_guard oLock(pProgress->queue->sMutex);
    5089          17 :                     pProgress->queue->nTotalCopied += nInc;
    5090             :                 }
    5091          17 :                 pProgress->dfLastPct = pct;
    5092          17 :                 return TRUE;
    5093             :             }
    5094             :         };
    5095             : 
    5096          11 :         JobQueue *queue = static_cast<JobQueue *>(pDataIn);
    5097             :         while (true)
    5098             :         {
    5099          32 :             auto [idx, bStop] = [queue]()
    5100             :             {
    5101          32 :                 std::lock_guard oLock(queue->sMutex);
    5102          32 :                 const int newIdx = queue->iCurIdx;
    5103          32 :                 if (static_cast<size_t>(newIdx) >= queue->anIndexToCopy.size())
    5104             :                 {
    5105          11 :                     queue->stop = true;
    5106             :                 }
    5107             :                 else
    5108             :                 {
    5109          21 :                     ++queue->iCurIdx;
    5110             :                 }
    5111          64 :                 return std::pair(newIdx, queue->stop);
    5112          32 :             }();
    5113          32 :             if (bStop)
    5114          11 :                 break;
    5115             :             const auto &chunk =
    5116          21 :                 queue->aoChunksToCopy[queue->anIndexToCopy[idx]];
    5117             :             const std::string osSubSource(
    5118          21 :                 queue->osTargetDir.empty()
    5119           4 :                     ? queue->osSource
    5120             :                     : CPLFormFilenameSafe(queue->osSourceDir.c_str(),
    5121             :                                           chunk.osSrcFilename.c_str(),
    5122          42 :                                           nullptr));
    5123             :             const std::string osSubTarget(
    5124          21 :                 queue->osTargetDir.empty()
    5125           4 :                     ? queue->osTarget
    5126             :                     : CPLFormFilenameSafe(queue->osTargetDir.c_str(),
    5127             :                                           chunk.osDstFilename.c_str(),
    5128          42 :                                           nullptr));
    5129             : 
    5130             :             ProgressData progressData;
    5131          21 :             progressData.nFileSize = chunk.nSize;
    5132          21 :             progressData.dfLastPct = 0;
    5133          21 :             progressData.queue = queue;
    5134          21 :             if (chunk.nSize < chunk.nTotalSize)
    5135             :             {
    5136          14 :                 const size_t nSizeToRead = static_cast<size_t>(chunk.nSize);
    5137          14 :                 bool bSuccess = false;
    5138          14 :                 if (queue->bSupportsParallelMultipartUpload)
    5139             :                 {
    5140             :                     const auto iter =
    5141          10 :                         queue->oMapMultiPartDefs.find(osSubTarget);
    5142          10 :                     CPLAssert(iter != queue->oMapMultiPartDefs.end());
    5143             : 
    5144          10 :                     VSILFILE *fpIn = VSIFOpenL(osSubSource.c_str(), "rb");
    5145          10 :                     void *pBuffer = VSI_MALLOC_VERBOSE(nSizeToRead);
    5146             :                     auto poS3HandleHelper =
    5147             :                         std::unique_ptr<IVSIS3LikeHandleHelper>(
    5148          10 :                             queue->poFS->CreateHandleHelper(
    5149          10 :                                 osSubTarget.c_str() +
    5150          10 :                                     queue->poFS->GetFSPrefix().size(),
    5151          30 :                                 false));
    5152          10 :                     if (fpIn && pBuffer && poS3HandleHelper &&
    5153          30 :                         VSIFSeekL(fpIn, chunk.nStartOffset, SEEK_SET) == 0 &&
    5154          10 :                         VSIFReadL(pBuffer, 1, nSizeToRead, fpIn) == nSizeToRead)
    5155             :                     {
    5156          10 :                         const int nPartNumber =
    5157          20 :                             1 + (queue->nMaxChunkSize == 0
    5158          10 :                                      ? 0 /* shouldn't happen */
    5159          10 :                                      : static_cast<int>(chunk.nStartOffset /
    5160          10 :                                                         queue->nMaxChunkSize));
    5161             :                         std::string osEtag =
    5162          10 :                             queue->poTargetFSMultipartHandler->UploadPart(
    5163             :                                 osSubTarget, nPartNumber,
    5164          10 :                                 iter->second.osUploadID, chunk.nStartOffset,
    5165             :                                 pBuffer, nSizeToRead, poS3HandleHelper.get(),
    5166             :                                 queue->oRetryParameters,
    5167          30 :                                 queue->aosObjectCreationOptions.List());
    5168          10 :                         if (!osEtag.empty())
    5169             :                         {
    5170           9 :                             std::lock_guard<std::mutex> lock(queue->sMutex);
    5171           9 :                             iter->second.nCountValidETags++;
    5172           9 :                             iter->second.aosEtags.resize(
    5173           9 :                                 std::max(nPartNumber,
    5174           0 :                                          static_cast<int>(
    5175           9 :                                              iter->second.aosEtags.size())));
    5176           9 :                             iter->second.aosEtags[nPartNumber - 1] =
    5177          18 :                                 std::move(osEtag);
    5178           9 :                             bSuccess = true;
    5179             :                         }
    5180             :                     }
    5181          10 :                     if (fpIn)
    5182          10 :                         VSIFCloseL(fpIn);
    5183          10 :                     VSIFree(pBuffer);
    5184             :                 }
    5185             :                 else
    5186             :                 {
    5187             :                     bSuccess =
    5188           4 :                         CopyChunk(osSubSource.c_str(), osSubTarget.c_str(),
    5189           4 :                                   chunk.nStartOffset, nSizeToRead);
    5190             :                 }
    5191          14 :                 if (bSuccess)
    5192             :                 {
    5193          13 :                     ProgressData::progressFunc(1.0, "", &progressData);
    5194             :                 }
    5195             :                 else
    5196             :                 {
    5197           1 :                     std::lock_guard oLock(queue->sMutex);
    5198           1 :                     queue->ret = false;
    5199           1 :                     queue->stop = true;
    5200             :                 }
    5201             :             }
    5202             :             else
    5203             :             {
    5204           7 :                 CPLAssert(chunk.nStartOffset == 0);
    5205           7 :                 if (queue->poFS->CopyFile(
    5206             :                         osSubSource.c_str(), osSubTarget.c_str(), nullptr,
    5207           7 :                         chunk.nTotalSize,
    5208           7 :                         queue->aosObjectCreationOptions.List(),
    5209          14 :                         ProgressData::progressFunc, &progressData) != 0)
    5210             :                 {
    5211           0 :                     std::lock_guard oLock(queue->sMutex);
    5212           0 :                     queue->ret = false;
    5213           0 :                     queue->stop = true;
    5214             :                 }
    5215             :             }
    5216          21 :         }
    5217          11 :     };
    5218             : 
    5219             :     JobQueue sJobQueue(this, poTargetFSMultipartHandler, aoChunksToCopy,
    5220             :                        anIndexToCopy, oMapMultiPartDefs, osSourceWithoutSlash,
    5221             :                        osTargetDir, osSourceWithoutSlash, osTarget,
    5222             :                        bSupportsParallelMultipartUpload, nMaxChunkSize,
    5223           8 :                        oRetryParameters, aosObjectCreationOptions);
    5224             : 
    5225           8 :     if (CPLTestBool(CPLGetConfigOption("VSIS3_SYNC_MULTITHREADING", "YES")))
    5226             :     {
    5227          14 :         std::vector<CPLJoinableThread *> ahThreads;
    5228          17 :         for (int i = 0; i < nThreads; i++)
    5229             :         {
    5230          10 :             auto hThread = CPLCreateJoinableThread(threadFunc, &sJobQueue);
    5231          10 :             if (!hThread)
    5232             :             {
    5233           0 :                 std::lock_guard oLock(sJobQueue.sMutex);
    5234           0 :                 sJobQueue.ret = false;
    5235           0 :                 sJobQueue.stop = true;
    5236           0 :                 break;
    5237             :             }
    5238          10 :             ahThreads.push_back(hThread);
    5239             :         }
    5240           7 :         if (pProgressFunc)
    5241             :         {
    5242           5 :             const uint64_t nTotalSizeDenom = std::max<uint64_t>(1, nTotalSize);
    5243          12 :             while (![&sJobQueue]()
    5244             :                    {
    5245          17 :                        std::lock_guard oLock(sJobQueue.sMutex);
    5246          34 :                        return sJobQueue.stop;
    5247          17 :                    }())
    5248             :             {
    5249          12 :                 CPLSleep(0.1);
    5250          24 :                 const auto nTotalCopied = [&sJobQueue]()
    5251             :                 {
    5252          12 :                     std::lock_guard oLock(sJobQueue.sMutex);
    5253          24 :                     return sJobQueue.nTotalCopied;
    5254          12 :                 }();
    5255          12 :                 if (!pProgressFunc(double(nTotalCopied) / nTotalSizeDenom, "",
    5256             :                                    pProgressData))
    5257             :                 {
    5258           0 :                     std::lock_guard oLock(sJobQueue.sMutex);
    5259           0 :                     sJobQueue.ret = false;
    5260           0 :                     sJobQueue.stop = true;
    5261             :                 }
    5262             :             }
    5263           5 :             if (
    5264          10 :                 [&sJobQueue]()
    5265             :                 {
    5266           5 :                     std::lock_guard oLock(sJobQueue.sMutex);
    5267          10 :                     return sJobQueue.ret;
    5268           5 :                 }())
    5269             :             {
    5270           5 :                 pProgressFunc(1.0, "", pProgressData);
    5271             :             }
    5272             :         }
    5273          17 :         for (auto hThread : ahThreads)
    5274             :         {
    5275          10 :             CPLJoinThread(hThread);
    5276             :         }
    5277             :     }
    5278             :     else
    5279             :     {
    5280             :         // Only for simulation case
    5281           1 :         threadFunc(&sJobQueue);
    5282             :     }
    5283             : 
    5284             :     // Finalize multipart uploads
    5285           8 :     if (sJobQueue.ret && bSupportsParallelMultipartUpload)
    5286             :     {
    5287           8 :         std::set<std::string> oSetKeysToRemove;
    5288           8 :         for (const auto &kv : oMapMultiPartDefs)
    5289             :         {
    5290             :             auto poS3HandleHelper =
    5291             :                 std::unique_ptr<IVSIS3LikeHandleHelper>(CreateHandleHelper(
    5292           8 :                     kv.first.c_str() + GetFSPrefix().size(), false));
    5293           4 :             sJobQueue.ret = false;
    5294           4 :             if (poS3HandleHelper)
    5295             :             {
    5296           4 :                 CPLAssert(kv.second.nCountValidETags ==
    5297             :                           kv.second.nExpectedCount);
    5298           4 :                 if (poTargetFSMultipartHandler->CompleteMultipart(
    5299           4 :                         kv.first, kv.second.osUploadID, kv.second.aosEtags,
    5300           4 :                         kv.second.nTotalSize, poS3HandleHelper.get(),
    5301           4 :                         oRetryParameters))
    5302             :                 {
    5303           4 :                     sJobQueue.ret = true;
    5304           4 :                     oSetKeysToRemove.insert(kv.first);
    5305             : 
    5306           4 :                     InvalidateCachedData(poS3HandleHelper->GetURL().c_str());
    5307           4 :                     InvalidateDirContent(CPLGetDirnameSafe(kv.first.c_str()));
    5308             :                 }
    5309             :             }
    5310             :         }
    5311           8 :         for (const auto &key : oSetKeysToRemove)
    5312             :         {
    5313           4 :             oMapMultiPartDefs.erase(key);
    5314             :         }
    5315             :     }
    5316             : 
    5317           8 :     return sJobQueue.ret;
    5318             : }
    5319             : 
    5320             : /************************************************************************/
    5321             : /*                   MultipartUploadGetCapabilities()                   */
    5322             : /************************************************************************/
    5323             : 
    5324           5 : bool IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadGetCapabilities(
    5325             :     int *pbNonSequentialUploadSupported, int *pbParallelUploadSupported,
    5326             :     int *pbAbortSupported, size_t *pnMinPartSize, size_t *pnMaxPartSize,
    5327             :     int *pnMaxPartCount)
    5328             : {
    5329           5 :     if (pbNonSequentialUploadSupported)
    5330           5 :         *pbNonSequentialUploadSupported =
    5331           5 :             SupportsNonSequentialMultipartUpload();
    5332           5 :     if (pbParallelUploadSupported)
    5333           5 :         *pbParallelUploadSupported = SupportsParallelMultipartUpload();
    5334           5 :     if (pbAbortSupported)
    5335           5 :         *pbAbortSupported = SupportsMultipartAbort();
    5336           5 :     if (pnMinPartSize)
    5337           5 :         *pnMinPartSize = GetMinimumPartSizeInMiB();
    5338           5 :     if (pnMaxPartSize)
    5339           5 :         *pnMaxPartSize = GetMaximumPartSizeInMiB();
    5340           5 :     if (pnMaxPartCount)
    5341           5 :         *pnMaxPartCount = GetMaximumPartCount();
    5342           5 :     return true;
    5343             : }
    5344             : 
    5345             : /************************************************************************/
    5346             : /*                        MultipartUploadStart()                        */
    5347             : /************************************************************************/
    5348             : 
    5349           3 : char *IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadStart(
    5350             :     const char *pszFilename, CSLConstList papszOptions)
    5351             : {
    5352           3 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
    5353           0 :         return nullptr;
    5354             :     auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    5355           6 :         CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
    5356           3 :     if (poHandleHelper == nullptr)
    5357           1 :         return nullptr;
    5358           4 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
    5359           4 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    5360             : 
    5361             :     const std::string osRet = InitiateMultipartUpload(
    5362           6 :         pszFilename, poHandleHelper.get(), oRetryParameters, papszOptions);
    5363           2 :     if (osRet.empty())
    5364           1 :         return nullptr;
    5365           1 :     return CPLStrdup(osRet.c_str());
    5366             : }
    5367             : 
    5368             : /************************************************************************/
    5369             : /*                       MultipartUploadAddPart()                       */
    5370             : /************************************************************************/
    5371             : 
    5372           4 : char *IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadAddPart(
    5373             :     const char *pszFilename, const char *pszUploadId, int nPartNumber,
    5374             :     vsi_l_offset nFileOffset, const void *pData, size_t nDataLength,
    5375             :     CSLConstList papszOptions)
    5376             : {
    5377           4 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
    5378           0 :         return nullptr;
    5379             :     auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    5380           8 :         CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
    5381           4 :     if (poHandleHelper == nullptr)
    5382           1 :         return nullptr;
    5383           6 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
    5384           6 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    5385             : 
    5386             :     const std::string osRet = UploadPart(
    5387             :         pszFilename, nPartNumber, pszUploadId, nFileOffset, pData, nDataLength,
    5388           9 :         poHandleHelper.get(), oRetryParameters, papszOptions);
    5389           3 :     if (osRet.empty())
    5390           1 :         return nullptr;
    5391           2 :     return CPLStrdup(osRet.c_str());
    5392             : }
    5393             : 
    5394             : /************************************************************************/
    5395             : /*                         MultipartUploadEnd()                         */
    5396             : /************************************************************************/
    5397             : 
    5398           4 : bool IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadEnd(
    5399             :     const char *pszFilename, const char *pszUploadId, size_t nPartIdsCount,
    5400             :     const char *const *apszPartIds, vsi_l_offset nTotalSize, CSLConstList)
    5401             : {
    5402           4 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
    5403           0 :         return false;
    5404             :     auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    5405           8 :         CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
    5406           4 :     if (poHandleHelper == nullptr)
    5407           1 :         return false;
    5408           6 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
    5409           6 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    5410             : 
    5411           3 :     std::vector<std::string> aosTags;
    5412           6 :     for (size_t i = 0; i < nPartIdsCount; ++i)
    5413           3 :         aosTags.emplace_back(apszPartIds[i]);
    5414           3 :     return CompleteMultipart(pszFilename, pszUploadId, aosTags, nTotalSize,
    5415           3 :                              poHandleHelper.get(), oRetryParameters);
    5416             : }
    5417             : 
    5418             : /************************************************************************/
    5419             : /*                        MultipartUploadAbort()                        */
    5420             : /************************************************************************/
    5421             : 
    5422           3 : bool IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadAbort(
    5423             :     const char *pszFilename, const char *pszUploadId, CSLConstList)
    5424             : {
    5425           3 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
    5426           0 :         return false;
    5427             :     auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    5428           6 :         CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
    5429           3 :     if (poHandleHelper == nullptr)
    5430           1 :         return false;
    5431           4 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
    5432           2 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    5433           2 :     return AbortMultipart(pszFilename, pszUploadId, poHandleHelper.get(),
    5434           2 :                           oRetryParameters);
    5435             : }
    5436             : 
    5437             : /************************************************************************/
    5438             : /*                            VSIS3Handle()                             */
    5439             : /************************************************************************/
    5440             : 
    5441         194 : VSIS3Handle::VSIS3Handle(VSIS3FSHandler *poFSIn, const char *pszFilename,
    5442         194 :                          VSIS3HandleHelper *poS3HandleHelper)
    5443             :     : IVSIS3LikeHandle(poFSIn, pszFilename,
    5444         194 :                        poS3HandleHelper->GetURLNoKVP().c_str()),
    5445         194 :       m_poS3HandleHelper(poS3HandleHelper)
    5446             : {
    5447         194 : }
    5448             : 
    5449             : /************************************************************************/
    5450             : /*                            ~VSIS3Handle()                            */
    5451             : /************************************************************************/
    5452             : 
    5453         388 : VSIS3Handle::~VSIS3Handle()
    5454             : {
    5455         194 :     delete m_poS3HandleHelper;
    5456         388 : }
    5457             : 
    5458             : /************************************************************************/
    5459             : /*                           GetCurlHeaders()                           */
    5460             : /************************************************************************/
    5461             : 
    5462         169 : struct curl_slist *VSIS3Handle::GetCurlHeaders(const std::string &osVerb,
    5463             :                                                struct curl_slist *psHeaders)
    5464             : {
    5465         169 :     return m_poS3HandleHelper->GetCurlHeaders(osVerb, psHeaders);
    5466             : }
    5467             : 
    5468             : /************************************************************************/
    5469             : /*                         CanRestartOnError()                          */
    5470             : /************************************************************************/
    5471             : 
    5472          17 : bool VSIS3Handle::CanRestartOnError(const char *pszErrorMsg,
    5473             :                                     const char *pszHeaders, bool bSetError)
    5474             : {
    5475          17 :     if (m_poS3HandleHelper->CanRestartOnError(pszErrorMsg, pszHeaders,
    5476             :                                               bSetError))
    5477             :     {
    5478           9 :         SetURL(m_poS3HandleHelper->GetURL().c_str());
    5479           9 :         return true;
    5480             :     }
    5481           8 :     return false;
    5482             : }
    5483             : 
    5484             : } /* end of namespace cpl */
    5485             : 
    5486             : #endif  // DOXYGEN_SKIP
    5487             : //! @endcond
    5488             : 
    5489             : /************************************************************************/
    5490             : /*                      VSIInstallS3FileHandler()                       */
    5491             : /************************************************************************/
    5492             : 
    5493             : /*!
    5494             :  \brief Install /vsis3/ Amazon S3 file system handler (requires libcurl)
    5495             : 
    5496             :  \verbatim embed:rst
    5497             :  See :ref:`/vsis3/ documentation <vsis3>`
    5498             :  \endverbatim
    5499             : 
    5500             :  */
    5501        2098 : void VSIInstallS3FileHandler(void)
    5502             : {
    5503        2098 :     VSIFileManager::InstallHandler(
    5504        4196 :         "/vsis3/", std::make_shared<cpl::VSIS3FSHandler>("/vsis3/"));
    5505        2098 : }
    5506             : 
    5507             : #endif /* HAVE_CURL */

Generated by: LCOV version 1.14