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