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

Generated by: LCOV version 1.14