LCOV - code coverage report
Current view: top level - port - cpl_vsil_s3.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2099 2452 85.6 %
Date: 2025-07-09 17:50:03 Functions: 88 93 94.6 %

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

Generated by: LCOV version 1.14