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

Generated by: LCOV version 1.14