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