LCOV - code coverage report
Current view: top level - port - cpl_vsil_s3.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2076 2428 85.5 %
Date: 2025-03-28 11:40:40 Functions: 90 94 95.7 %

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

Generated by: LCOV version 1.14