LCOV - code coverage report
Current view: top level - port - cpl_vsil_s3.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1954 2289 85.4 %
Date: 2024-04-28 18:08:58 Functions: 82 87 94.3 %

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

Generated by: LCOV version 1.14