LCOV - code coverage report
Current view: top level - port - cpl_vsil_s3.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2176 2534 85.9 %
Date: 2025-09-10 17:48:50 Functions: 90 95 94.7 %

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

Generated by: LCOV version 1.14