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