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 90 : VSIDIRS3(const std::string &osDirName, IVSIS3LikeFSHandler *poFSIn)
77 90 : : VSIDIRS3Like(osDirName, poFSIn)
78 : {
79 90 : }
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 154 : void VSIDIRS3Like::clear()
101 : {
102 154 : osNextMarker.clear();
103 154 : nPos = 0;
104 154 : aoEntries.clear();
105 154 : }
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 63 : 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 63 : CPLXMLNode *psTree = CPLParseXMLString(pszXML);
185 63 : if (psTree == nullptr)
186 0 : return false;
187 63 : CPLXMLNode *psListBucketResult = CPLGetXMLNode(psTree, "=ListBucketResult");
188 : CPLXMLNode *psListAllMyBucketsResultBuckets =
189 : (psListBucketResult != nullptr)
190 63 : ? nullptr
191 7 : : CPLGetXMLNode(psTree, "=ListAllMyBucketsResult.Buckets");
192 63 : if (!psListBucketResult && !psListAllMyBucketsResultBuckets)
193 : psListAllMyBucketsResultBuckets =
194 1 : CPLGetXMLNode(psTree, "=ListAllMyDirectoryBucketsResult.Buckets");
195 :
196 63 : bool ret = true;
197 :
198 63 : bIsTruncated = false;
199 63 : if (psListBucketResult)
200 : {
201 56 : ret = false;
202 112 : CPLString osPrefix = CPLGetXMLValue(psListBucketResult, "Prefix", "");
203 56 : if (osPrefix.empty())
204 : {
205 : // in the case of an empty bucket
206 21 : ret = true;
207 : }
208 56 : if (osPrefix.endsWith(m_osFilterPrefix))
209 : {
210 56 : osPrefix.resize(osPrefix.size() - m_osFilterPrefix.size());
211 : }
212 :
213 56 : 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 112 : std::map<std::string, int> aoNameCount;
219 56 : for (CPLXMLNode *psIter = psListBucketResult->psChild;
220 660 : psIter != nullptr; psIter = psIter->psNext)
221 : {
222 604 : if (psIter->eType != CXT_Element)
223 8 : continue;
224 596 : if (strcmp(psIter->pszValue, "Contents") == 0)
225 : {
226 475 : ret = true;
227 475 : const char *pszKey = CPLGetXMLValue(psIter, "Key", nullptr);
228 475 : if (pszKey && strlen(pszKey) > osPrefix.size())
229 : {
230 471 : aoNameCount[pszKey + osPrefix.size()]++;
231 : }
232 : }
233 121 : else if (strcmp(psIter->pszValue, "CommonPrefixes") == 0)
234 : {
235 11 : const char *pszKey = CPLGetXMLValue(psIter, "Prefix", nullptr);
236 22 : if (pszKey &&
237 11 : strncmp(pszKey, osPrefix.c_str(), osPrefix.size()) == 0)
238 : {
239 22 : std::string osKey = pszKey;
240 11 : if (!osKey.empty() && osKey.back() == '/')
241 9 : osKey.pop_back();
242 11 : if (osKey.size() > osPrefix.size())
243 : {
244 11 : ret = true;
245 11 : aoNameCount[osKey.c_str() + osPrefix.size()]++;
246 : }
247 : }
248 : }
249 : }
250 :
251 56 : for (CPLXMLNode *psIter = psListBucketResult->psChild;
252 660 : psIter != nullptr; psIter = psIter->psNext)
253 : {
254 604 : if (psIter->eType != CXT_Element)
255 8 : continue;
256 596 : if (strcmp(psIter->pszValue, "Contents") == 0)
257 : {
258 475 : const char *pszKey = CPLGetXMLValue(psIter, "Key", nullptr);
259 475 : 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 474 : if (!bIsListObjectV2 && bIsTruncated && nRecurseDepth < 0 &&
268 : pszKey)
269 : {
270 0 : osNextMarker = pszKey;
271 : }
272 474 : if (pszKey && strlen(pszKey) > osPrefix.size())
273 : {
274 : const char *pszStorageClass =
275 470 : CPLGetXMLValue(psIter, "StorageClass", "");
276 470 : if (oSetIgnoredStorageClasses.find(pszStorageClass) !=
277 940 : oSetIgnoredStorageClasses.end())
278 : {
279 2 : continue;
280 : }
281 :
282 468 : const std::string osKeySuffix = pszKey + osPrefix.size();
283 468 : if (m_bSynthetizeMissingDirectories)
284 : {
285 14 : const auto nLastSlashPos = osKeySuffix.rfind('/');
286 26 : 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 468 : aoEntries.push_back(
301 936 : std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
302 468 : auto &entry = aoEntries.back();
303 468 : entry->pszName = CPLStrdup(osKeySuffix.c_str());
304 936 : entry->nSize = static_cast<GUIntBig>(
305 468 : CPLAtoGIntBig(CPLGetXMLValue(psIter, "Size", "0")));
306 468 : entry->bSizeKnown = true;
307 468 : entry->nMode =
308 468 : entry->pszName[0] != 0 &&
309 468 : entry->pszName[strlen(entry->pszName) - 1] ==
310 : '/'
311 936 : ? S_IFDIR
312 : : S_IFREG;
313 471 : if (entry->nMode == S_IFDIR &&
314 471 : aoNameCount[entry->pszName] < 2)
315 : {
316 3 : entry->pszName[strlen(entry->pszName) - 1] = 0;
317 : }
318 468 : entry->bModeKnown = true;
319 :
320 468 : std::string ETag = CPLGetXMLValue(psIter, "ETag", "");
321 468 : if (ETag.size() > 2 && ETag[0] == '"' && ETag.back() == '"')
322 : {
323 409 : ETag = ETag.substr(1, ETag.size() - 2);
324 818 : entry->papszExtra = CSLSetNameValue(
325 409 : entry->papszExtra, "ETag", ETag.c_str());
326 : }
327 :
328 468 : int nYear = 0;
329 468 : int nMonth = 0;
330 468 : int nDay = 0;
331 468 : int nHour = 0;
332 468 : int nMin = 0;
333 468 : int nSec = 0;
334 468 : if (sscanf(CPLGetXMLValue(psIter, "LastModified", ""),
335 : "%04d-%02d-%02dT%02d:%02d:%02d", &nYear, &nMonth,
336 468 : &nDay, &nHour, &nMin, &nSec) == 6)
337 : {
338 : struct tm brokendowntime;
339 468 : brokendowntime.tm_year = nYear - 1900;
340 468 : brokendowntime.tm_mon = nMonth - 1;
341 468 : brokendowntime.tm_mday = nDay;
342 468 : brokendowntime.tm_hour = nHour;
343 468 : brokendowntime.tm_min = nMin;
344 468 : brokendowntime.tm_sec = nSec;
345 468 : entry->nMTime = CPLYMDHMSToUnixTime(&brokendowntime);
346 468 : entry->bMTimeKnown = true;
347 : }
348 :
349 468 : if (nMaxFiles != 1 && bCacheEntries)
350 : {
351 930 : FileProp prop;
352 465 : prop.nMode = entry->nMode;
353 465 : prop.eExists = EXIST_YES;
354 465 : prop.bHasComputedFileSize = true;
355 465 : prop.fileSize = entry->nSize;
356 465 : prop.bIsDirectory = (entry->nMode == S_IFDIR);
357 465 : prop.mTime = static_cast<time_t>(entry->nMTime);
358 465 : prop.ETag = std::move(ETag);
359 :
360 : std::string osCachedFilename =
361 930 : osBaseURL + CPLAWSURLEncode(osPrefix, false) +
362 1860 : CPLAWSURLEncode(entry->pszName, false);
363 : #if DEBUG_VERBOSE
364 : CPLDebug(pszDebugPrefix, "Cache %s",
365 : osCachedFilename.c_str());
366 : #endif
367 465 : poFS->SetCachedFileProp(osCachedFilename.c_str(), prop);
368 : }
369 :
370 891 : if (nMaxFiles > 0 &&
371 423 : aoEntries.size() >= static_cast<unsigned>(nMaxFiles))
372 0 : break;
373 : }
374 : }
375 121 : else if (strcmp(psIter->pszValue, "CommonPrefixes") == 0)
376 : {
377 11 : const char *pszKey = CPLGetXMLValue(psIter, "Prefix", nullptr);
378 22 : if (pszKey &&
379 11 : strncmp(pszKey, osPrefix.c_str(), osPrefix.size()) == 0)
380 : {
381 11 : 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 11 : std::string osKey = pszKey;
390 11 : if (!osKey.empty() && osKey.back() == '/')
391 9 : osKey.pop_back();
392 11 : if (osKey.size() > osPrefix.size())
393 : {
394 11 : aoEntries.push_back(
395 22 : std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
396 11 : auto &entry = aoEntries.back();
397 22 : entry->pszName =
398 11 : CPLStrdup(osKey.c_str() + osPrefix.size());
399 11 : 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 11 : entry->nMode = S_IFDIR;
410 11 : entry->bModeKnown = true;
411 :
412 11 : if (nMaxFiles != 1 && bCacheEntries)
413 : {
414 22 : FileProp prop;
415 11 : prop.eExists = EXIST_YES;
416 11 : prop.bIsDirectory = true;
417 11 : prop.bHasComputedFileSize = true;
418 11 : prop.fileSize = 0;
419 11 : prop.mTime = 0;
420 11 : prop.nMode = S_IFDIR;
421 :
422 : std::string osCachedFilename =
423 22 : osBaseURL + CPLAWSURLEncode(osPrefix, false) +
424 44 : CPLAWSURLEncode(entry->pszName, false);
425 : #if DEBUG_VERBOSE
426 : CPLDebug(pszDebugPrefix, "Cache %s",
427 : osCachedFilename.c_str());
428 : #endif
429 11 : poFS->SetCachedFileProp(osCachedFilename.c_str(),
430 : prop);
431 : }
432 :
433 14 : if (nMaxFiles > 0 &&
434 3 : aoEntries.size() >=
435 3 : static_cast<unsigned>(nMaxFiles))
436 0 : break;
437 : }
438 : }
439 : }
440 : }
441 :
442 56 : if (bIsListObjectV2)
443 : {
444 56 : 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 63 : CPLDestroyXMLNode(psTree);
506 63 : return ret;
507 : }
508 :
509 : /************************************************************************/
510 : /* IssueListDir() */
511 : /************************************************************************/
512 :
513 97 : bool VSIDIRS3::IssueListDir()
514 : {
515 194 : CPLString osMaxKeys = CPLGetConfigOption("AWS_MAX_KEYS", "");
516 126 : if (nMaxFiles > 0 && nMaxFiles <= 100 &&
517 29 : (osMaxKeys.empty() || nMaxFiles < atoi(osMaxKeys)))
518 : {
519 29 : osMaxKeys.Printf("%d", nMaxFiles);
520 : }
521 :
522 194 : NetworkStatisticsFileSystem oContextFS(poS3FS->GetFSPrefix().c_str());
523 194 : NetworkStatisticsAction oContextAction("ListBucket");
524 :
525 194 : const std::string l_osNextMarker(osNextMarker);
526 97 : clear();
527 :
528 97 : IVSIS3LikeHandleHelper *l_poHandlerHelper = poHandleHelper.get();
529 :
530 : auto poS3HandleHelper =
531 97 : dynamic_cast<VSIS3HandleHelper *>(poHandleHelper.get());
532 : const bool bUseListObjectsV2 =
533 106 : ((!osBucket.empty() || !m_osFilterPrefix.empty()) &&
534 88 : EQUAL(CPLGetConfigOption("CPL_VSIS3_LIST_OBJECTS_VERSION", "2"),
535 200 : "2")) ||
536 6 : (poS3HandleHelper && poS3HandleHelper->IsDirectoryBucket());
537 :
538 97 : std::unique_ptr<VSIS3HandleHelper> poTmpHandleHelper;
539 97 : 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 194 : CPLHTTPGetOptionsFromEnv(m_osDirName.c_str()));
550 :
551 : while (true)
552 : {
553 103 : l_poHandlerHelper->ResetQueryParameters();
554 103 : const std::string osBaseURL(l_poHandlerHelper->GetURL());
555 103 : if (bUseListObjectsV2)
556 94 : l_poHandlerHelper->AddQueryParameter("list-type", "2");
557 :
558 103 : CURL *hCurlHandle = curl_easy_init();
559 :
560 103 : if (!osBucket.empty())
561 : {
562 94 : if (nRecurseDepth == 0)
563 78 : l_poHandlerHelper->AddQueryParameter("delimiter", "/");
564 94 : if (!l_osNextMarker.empty())
565 4 : l_poHandlerHelper->AddQueryParameter(
566 : bUseListObjectsV2 ? "continuation-token" : "marker",
567 : l_osNextMarker);
568 94 : if (!osMaxKeys.empty())
569 29 : l_poHandlerHelper->AddQueryParameter("max-keys", osMaxKeys);
570 94 : if (!osObjectKey.empty())
571 59 : l_poHandlerHelper->AddQueryParameter(
572 118 : "prefix", osObjectKey + "/" + m_osFilterPrefix);
573 35 : else if (!m_osFilterPrefix.empty())
574 1 : l_poHandlerHelper->AddQueryParameter("prefix",
575 1 : m_osFilterPrefix);
576 : }
577 :
578 : struct curl_slist *headers =
579 103 : VSICurlSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
580 : aosHTTPOptions.List());
581 :
582 103 : headers = l_poHandlerHelper->GetCurlHeaders("GET", headers);
583 : // Disable automatic redirection
584 103 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
585 :
586 103 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, nullptr);
587 :
588 103 : CurlRequestHelper requestHelper;
589 103 : const long response_code = requestHelper.perform(
590 : hCurlHandle, headers, poFS, l_poHandlerHelper);
591 :
592 103 : NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
593 :
594 103 : if (response_code != 200 ||
595 74 : requestHelper.sWriteFuncData.pBuffer == nullptr)
596 : {
597 54 : if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
598 14 : l_poHandlerHelper->CanRestartOnError(
599 14 : requestHelper.sWriteFuncData.pBuffer,
600 14 : 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 126 : bool ret = AnalyseS3FileList(
629 : bUseListObjectsV2, osBaseURL,
630 63 : requestHelper.sWriteFuncData.pBuffer,
631 126 : VSICurlFilesystemHandlerBase::GetS3IgnoredStorageClasses(),
632 : bIsTruncated);
633 :
634 63 : curl_easy_cleanup(hCurlHandle);
635 63 : return ret;
636 : }
637 :
638 6 : curl_easy_cleanup(hCurlHandle);
639 6 : }
640 : }
641 :
642 : /************************************************************************/
643 : /* NextDirEntry() */
644 : /************************************************************************/
645 :
646 551 : const VSIDIREntry *VSIDIRS3::NextDirEntry()
647 : {
648 551 : if (!m_bRegularListingDone)
649 : {
650 550 : const auto psRet = VSIDIRS3Like::NextDirEntry();
651 550 : if (psRet)
652 492 : return psRet;
653 58 : m_bRegularListingDone = true;
654 58 : if (!m_bListBucket)
655 55 : 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 600 : const VSIDIREntry *VSIDIRS3Like::NextDirEntry()
679 : {
680 600 : constexpr int ARBITRARY_LIMIT = 10;
681 619 : for (int i = 0; i < ARBITRARY_LIMIT; ++i)
682 : {
683 618 : if (nPos < static_cast<int>(aoEntries.size()))
684 : {
685 519 : auto &entry = aoEntries[nPos];
686 519 : 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 512 : nPos++;
715 512 : return entry.get();
716 : }
717 99 : if (osNextMarker.empty())
718 : {
719 82 : 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 360 : const char *GetDebugKey() const override
772 : {
773 360 : return "S3";
774 : }
775 :
776 : IVSIS3LikeHandleHelper *CreateHandleHelper(const char *pszURI,
777 : bool bAllowNoObject) override;
778 :
779 22591 : std::string GetFSPrefix() const override
780 : {
781 22591 : 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 1805 : explicit VSIS3FSHandler(const char *pszPrefix) : m_osPrefix(pszPrefix)
797 : {
798 1805 : }
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 5973 : GetHintForPotentiallyRecognizedPath(const std::string &osPath) override
844 : {
845 29871 : if (!cpl::starts_with(osPath, m_osPrefix) &&
846 11946 : !cpl::starts_with(osPath, GetStreamingFilename(m_osPrefix)) &&
847 5970 : cpl::starts_with(osPath, "s3://"))
848 : {
849 2 : return GetFSPrefix() + osPath.substr(strlen("s3://"));
850 : }
851 5972 : 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 152 : bool AllowAutomaticRedirection() override
873 : {
874 152 : 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 556 : 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 556 : if (!CPLTestBool(VSIGetPathSpecificOption(osFilename,
1917 : "GDAL_HTTP_PATH_VERBATIM", "NO")))
1918 : {
1919 555 : osFilename = CPLLexicallyNormalize(osFilename, '/');
1920 : }
1921 556 : }
1922 :
1923 : /************************************************************************/
1924 : /* Open() */
1925 : /************************************************************************/
1926 :
1927 238 : VSIVirtualHandleUniquePtr VSICurlFilesystemHandlerBaseWritable::Open(
1928 : const char *pszFilename, const char *pszAccess, bool bSetError,
1929 : CSLConstList papszOptions)
1930 : {
1931 238 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
1932 1 : return nullptr;
1933 :
1934 474 : CPLString osFilename(pszFilename);
1935 237 : NormalizeFilenameIfNeeded(osFilename);
1936 237 : 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 236 : 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 228 : else if (strchr(pszAccess, 'w') || strchr(pszAccess, 'a'))
1996 : {
1997 : return VSIVirtualHandleUniquePtr(
1998 48 : CreateWriteHandle(osFilename, papszOptions).release());
1999 : }
2000 :
2001 180 : 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 180 : CachedDirList cachedDirList;
2006 180 : const std::string osDirname(CPLGetDirnameSafe(osFilename));
2007 540 : if (STARTS_WITH_CI(osDirname.c_str(), GetFSPrefix().c_str()) &&
2008 552 : GetCachedDirList(osDirname.c_str(), cachedDirList) &&
2009 12 : cachedDirList.bGotFileList)
2010 : {
2011 7 : const std::string osFilenameOnly(CPLGetFilename(osFilename));
2012 7 : bool bFound = false;
2013 8 : for (int i = 0; i < cachedDirList.oFileList.size(); i++)
2014 : {
2015 7 : if (cachedDirList.oFileList[i] == osFilenameOnly)
2016 : {
2017 6 : bFound = true;
2018 6 : break;
2019 : }
2020 : }
2021 7 : if (!bFound)
2022 : {
2023 1 : return nullptr;
2024 : }
2025 : }
2026 : }
2027 :
2028 : return VSICurlFilesystemHandlerBase::Open(osFilename, pszAccess, bSetError,
2029 179 : 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 1131 : VSIS3FSHandler::~VSIS3FSHandler()
2049 : {
2050 1131 : VSIS3FSHandler::ClearCache();
2051 1131 : VSIS3HandleHelper::CleanMutex();
2052 1131 : }
2053 :
2054 : /************************************************************************/
2055 : /* ClearCache() */
2056 : /************************************************************************/
2057 :
2058 1498 : void VSIS3FSHandler::ClearCache()
2059 : {
2060 1498 : VSICurlFilesystemHandlerBase::ClearCache();
2061 :
2062 1498 : VSIS3UpdateParams::ClearCache();
2063 :
2064 1498 : VSIS3HandleHelper::ClearCache();
2065 1498 : }
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 1 : .append(VSICurlFilesystemHandlerBase::GetOptionsStatic())
2133 3 : .append("</Options>"));
2134 2 : return osOptions.c_str();
2135 : }
2136 :
2137 : /************************************************************************/
2138 : /* GetSignedURL() */
2139 : /************************************************************************/
2140 :
2141 6 : char *VSIS3FSHandler::GetSignedURL(const char *pszFilename,
2142 : CSLConstList papszOptions)
2143 : {
2144 6 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
2145 0 : return nullptr;
2146 :
2147 12 : VSIS3HandleHelper *poS3HandleHelper = VSIS3HandleHelper::BuildFromURI(
2148 18 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str(), false,
2149 : papszOptions);
2150 6 : if (poS3HandleHelper == nullptr)
2151 : {
2152 1 : return nullptr;
2153 : }
2154 :
2155 10 : std::string osRet(poS3HandleHelper->GetSignedURL(papszOptions));
2156 :
2157 5 : delete poS3HandleHelper;
2158 5 : return CPLStrdup(osRet.c_str());
2159 : }
2160 :
2161 : /************************************************************************/
2162 : /* UnlinkBatch() */
2163 : /************************************************************************/
2164 :
2165 4 : int *VSIS3FSHandler::UnlinkBatch(CSLConstList papszFiles)
2166 : {
2167 : // Implemented using
2168 : // https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html
2169 :
2170 : int *panRet =
2171 4 : static_cast<int *>(CPLCalloc(sizeof(int), CSLCount(papszFiles)));
2172 8 : CPLStringList aosList;
2173 8 : std::string osCurBucket;
2174 4 : int iStartIndex = -1;
2175 : // For debug / testing only
2176 : const int nBatchSize =
2177 4 : atoi(CPLGetConfigOption("CPL_VSIS3_UNLINK_BATCH_SIZE", "1000"));
2178 12 : for (int i = 0; papszFiles && papszFiles[i]; i++)
2179 : {
2180 8 : CPLAssert(STARTS_WITH_CI(papszFiles[i], GetFSPrefix().c_str()));
2181 : const char *pszFilenameWithoutPrefix =
2182 8 : papszFiles[i] + GetFSPrefix().size();
2183 8 : const char *pszSlash = strchr(pszFilenameWithoutPrefix, '/');
2184 8 : if (!pszSlash)
2185 0 : return panRet;
2186 16 : std::string osBucket;
2187 : osBucket.assign(pszFilenameWithoutPrefix,
2188 8 : pszSlash - pszFilenameWithoutPrefix);
2189 8 : bool bBucketChanged = false;
2190 8 : if ((osCurBucket.empty() || osCurBucket == osBucket))
2191 : {
2192 8 : if (osCurBucket.empty())
2193 : {
2194 5 : iStartIndex = i;
2195 5 : osCurBucket = osBucket;
2196 : }
2197 8 : aosList.AddString(pszSlash + 1);
2198 : }
2199 : else
2200 : {
2201 0 : bBucketChanged = true;
2202 : }
2203 13 : while (bBucketChanged || aosList.size() == nBatchSize ||
2204 5 : papszFiles[i + 1] == nullptr)
2205 : {
2206 : // Compose XML post content
2207 5 : CPLXMLNode *psXML = CPLCreateXMLNode(nullptr, CXT_Element, "?xml");
2208 5 : CPLAddXMLAttributeAndValue(psXML, "version", "1.0");
2209 5 : CPLAddXMLAttributeAndValue(psXML, "encoding", "UTF-8");
2210 : CPLXMLNode *psDelete =
2211 5 : CPLCreateXMLNode(nullptr, CXT_Element, "Delete");
2212 5 : psXML->psNext = psDelete;
2213 5 : CPLAddXMLAttributeAndValue(
2214 : psDelete, "xmlns", "http://s3.amazonaws.com/doc/2006-03-01/");
2215 5 : CPLXMLNode *psLastChild = psDelete->psChild;
2216 5 : CPLAssert(psLastChild != nullptr);
2217 5 : CPLAssert(psLastChild->psNext == nullptr);
2218 5 : std::map<std::string, int> mapKeyToIndex;
2219 13 : for (int j = 0; aosList[j]; ++j)
2220 : {
2221 : CPLXMLNode *psObject =
2222 8 : CPLCreateXMLNode(nullptr, CXT_Element, "Object");
2223 8 : mapKeyToIndex[aosList[j]] = iStartIndex + j;
2224 8 : CPLCreateXMLElementAndValue(psObject, "Key", aosList[j]);
2225 8 : psLastChild->psNext = psObject;
2226 8 : psLastChild = psObject;
2227 : }
2228 :
2229 : // Run request
2230 5 : char *pszXML = CPLSerializeXMLTree(psXML);
2231 5 : CPLDestroyXMLNode(psXML);
2232 5 : auto oDeletedKeys = DeleteObjects(osCurBucket.c_str(), pszXML);
2233 5 : CPLFree(pszXML);
2234 :
2235 : // Mark delete file
2236 12 : for (const auto &osDeletedKey : oDeletedKeys)
2237 : {
2238 7 : auto mapKeyToIndexIter = mapKeyToIndex.find(osDeletedKey);
2239 7 : if (mapKeyToIndexIter != mapKeyToIndex.end())
2240 : {
2241 7 : panRet[mapKeyToIndexIter->second] = true;
2242 : }
2243 : }
2244 :
2245 5 : osCurBucket.clear();
2246 5 : aosList.Clear();
2247 5 : if (bBucketChanged)
2248 : {
2249 0 : iStartIndex = i;
2250 0 : osCurBucket = osBucket;
2251 0 : aosList.AddString(pszSlash + 1);
2252 0 : bBucketChanged = false;
2253 : }
2254 : else
2255 : {
2256 5 : break;
2257 : }
2258 : }
2259 : }
2260 4 : return panRet;
2261 : }
2262 :
2263 : /************************************************************************/
2264 : /* RmdirRecursive() */
2265 : /************************************************************************/
2266 :
2267 2 : int VSIS3FSHandler::RmdirRecursive(const char *pszDirname)
2268 : {
2269 : // Some S3-like APIs do not support DeleteObjects
2270 2 : if (CPLTestBool(VSIGetPathSpecificOption(
2271 : pszDirname, "CPL_VSIS3_USE_BASE_RMDIR_RECURSIVE", "NO")))
2272 1 : return VSIFilesystemHandler::RmdirRecursive(pszDirname);
2273 :
2274 : // For debug / testing only
2275 : const int nBatchSize =
2276 1 : atoi(CPLGetConfigOption("CPL_VSIS3_UNLINK_BATCH_SIZE", "1000"));
2277 :
2278 1 : return RmdirRecursiveInternal(pszDirname, nBatchSize);
2279 : }
2280 :
2281 2 : int IVSIS3LikeFSHandler::RmdirRecursiveInternal(const char *pszDirname,
2282 : int nBatchSize)
2283 : {
2284 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2285 4 : NetworkStatisticsAction oContextAction("RmdirRecursive");
2286 :
2287 4 : std::string osDirnameWithoutEndSlash(pszDirname);
2288 4 : if (!osDirnameWithoutEndSlash.empty() &&
2289 2 : osDirnameWithoutEndSlash.back() == '/')
2290 0 : osDirnameWithoutEndSlash.pop_back();
2291 :
2292 4 : CPLStringList aosOptions;
2293 2 : aosOptions.SetNameValue("CACHE_ENTRIES", "FALSE");
2294 : auto poDir = std::unique_ptr<VSIDIR>(
2295 4 : OpenDir(osDirnameWithoutEndSlash.c_str(), -1, aosOptions.List()));
2296 2 : if (!poDir)
2297 0 : return -1;
2298 4 : CPLStringList aosList;
2299 :
2300 : while (true)
2301 : {
2302 5 : auto entry = poDir->NextDirEntry();
2303 5 : if (entry)
2304 : {
2305 0 : std::string osFilename(osDirnameWithoutEndSlash + '/' +
2306 6 : entry->pszName);
2307 3 : if (entry->nMode == S_IFDIR)
2308 1 : osFilename += '/';
2309 3 : aosList.AddString(osFilename.c_str());
2310 : }
2311 5 : if (entry == nullptr || aosList.size() == nBatchSize)
2312 : {
2313 3 : if (entry == nullptr && !osDirnameWithoutEndSlash.empty())
2314 : {
2315 2 : aosList.AddString((osDirnameWithoutEndSlash + '/').c_str());
2316 : }
2317 3 : int *ret = DeleteObjectBatch(aosList.List());
2318 3 : if (ret == nullptr)
2319 0 : return -1;
2320 3 : CPLFree(ret);
2321 3 : aosList.Clear();
2322 : }
2323 5 : if (entry == nullptr)
2324 2 : break;
2325 3 : }
2326 2 : PartialClearCache(osDirnameWithoutEndSlash.c_str());
2327 2 : return 0;
2328 : }
2329 :
2330 : /************************************************************************/
2331 : /* DeleteObjects() */
2332 : /************************************************************************/
2333 :
2334 5 : std::set<std::string> VSIS3FSHandler::DeleteObjects(const char *pszBucket,
2335 : const char *pszXML)
2336 : {
2337 : auto poS3HandleHelper =
2338 : std::unique_ptr<VSIS3HandleHelper>(VSIS3HandleHelper::BuildFromURI(
2339 10 : pszBucket, GetFSPrefix().c_str(), true));
2340 5 : if (!poS3HandleHelper)
2341 0 : return std::set<std::string>();
2342 :
2343 10 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2344 10 : NetworkStatisticsAction oContextAction("DeleteObjects");
2345 :
2346 10 : std::set<std::string> oDeletedKeys;
2347 : bool bRetry;
2348 10 : const std::string osFilename(GetFSPrefix() + pszBucket);
2349 : const CPLStringList aosHTTPOptions(
2350 10 : CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
2351 10 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
2352 10 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
2353 :
2354 : struct CPLMD5Context context;
2355 5 : CPLMD5Init(&context);
2356 5 : CPLMD5Update(&context, pszXML, strlen(pszXML));
2357 : unsigned char hash[16];
2358 5 : CPLMD5Final(hash, &context);
2359 5 : char *pszBase64 = CPLBase64Encode(16, hash);
2360 10 : std::string osContentMD5("Content-MD5: ");
2361 5 : osContentMD5 += pszBase64;
2362 5 : CPLFree(pszBase64);
2363 :
2364 5 : do
2365 : {
2366 5 : bRetry = false;
2367 5 : CURL *hCurlHandle = curl_easy_init();
2368 5 : poS3HandleHelper->AddQueryParameter("delete", "");
2369 5 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "POST");
2370 5 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS, pszXML);
2371 :
2372 : struct curl_slist *headers = static_cast<struct curl_slist *>(
2373 5 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
2374 : aosHTTPOptions.List()));
2375 5 : headers = curl_slist_append(headers, "Content-Type: application/xml");
2376 5 : headers = curl_slist_append(headers, osContentMD5.c_str());
2377 5 : headers = poS3HandleHelper->GetCurlHeaders("POST", headers, pszXML,
2378 : strlen(pszXML));
2379 :
2380 10 : CurlRequestHelper requestHelper;
2381 5 : const long response_code = requestHelper.perform(
2382 5 : hCurlHandle, headers, this, poS3HandleHelper.get());
2383 :
2384 5 : NetworkStatisticsLogger::LogPOST(strlen(pszXML),
2385 : requestHelper.sWriteFuncData.nSize);
2386 :
2387 5 : if (response_code != 200 ||
2388 5 : requestHelper.sWriteFuncData.pBuffer == nullptr)
2389 : {
2390 : // Look if we should attempt a retry
2391 0 : if (oRetryContext.CanRetry(
2392 : static_cast<int>(response_code),
2393 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
2394 : requestHelper.szCurlErrBuf))
2395 : {
2396 0 : CPLError(CE_Warning, CPLE_AppDefined,
2397 : "HTTP error code: %d - %s. "
2398 : "Retrying again in %.1f secs",
2399 : static_cast<int>(response_code),
2400 0 : poS3HandleHelper->GetURL().c_str(),
2401 : oRetryContext.GetCurrentDelay());
2402 0 : CPLSleep(oRetryContext.GetCurrentDelay());
2403 0 : bRetry = true;
2404 : }
2405 0 : else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
2406 0 : poS3HandleHelper->CanRestartOnError(
2407 0 : requestHelper.sWriteFuncData.pBuffer,
2408 0 : requestHelper.sWriteFuncHeaderData.pBuffer, true))
2409 : {
2410 0 : bRetry = true;
2411 : }
2412 : else
2413 : {
2414 0 : CPLDebug(GetDebugKey(), "%s",
2415 0 : requestHelper.sWriteFuncData.pBuffer
2416 : ? requestHelper.sWriteFuncData.pBuffer
2417 : : "(null)");
2418 0 : CPLError(CE_Failure, CPLE_AppDefined, "DeleteObjects failed");
2419 : }
2420 : }
2421 : else
2422 : {
2423 : CPLXMLNode *psXML =
2424 5 : CPLParseXMLString(requestHelper.sWriteFuncData.pBuffer);
2425 5 : if (psXML)
2426 : {
2427 : CPLXMLNode *psDeleteResult =
2428 5 : CPLGetXMLNode(psXML, "=DeleteResult");
2429 5 : if (psDeleteResult)
2430 : {
2431 18 : for (CPLXMLNode *psIter = psDeleteResult->psChild; psIter;
2432 13 : psIter = psIter->psNext)
2433 : {
2434 13 : if (psIter->eType == CXT_Element &&
2435 8 : strcmp(psIter->pszValue, "Deleted") == 0)
2436 : {
2437 : std::string osKey =
2438 7 : CPLGetXMLValue(psIter, "Key", "");
2439 7 : oDeletedKeys.insert(osKey);
2440 :
2441 7 : InvalidateCachedData(
2442 14 : (poS3HandleHelper->GetURL() + osKey).c_str());
2443 :
2444 7 : InvalidateDirContent(CPLGetDirnameSafe(
2445 14 : (GetFSPrefix() + pszBucket + "/" + osKey)
2446 : .c_str()));
2447 : }
2448 : }
2449 : }
2450 5 : CPLDestroyXMLNode(psXML);
2451 : }
2452 : }
2453 :
2454 5 : curl_easy_cleanup(hCurlHandle);
2455 : } while (bRetry);
2456 5 : return oDeletedKeys;
2457 : }
2458 :
2459 : /************************************************************************/
2460 : /* GetFileMetadata() */
2461 : /************************************************************************/
2462 :
2463 4 : char **VSIS3FSHandler::GetFileMetadata(const char *pszFilename,
2464 : const char *pszDomain,
2465 : CSLConstList papszOptions)
2466 : {
2467 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
2468 0 : return nullptr;
2469 :
2470 8 : CPLString osFilename(pszFilename);
2471 4 : NormalizeFilenameIfNeeded(osFilename);
2472 4 : if (!STARTS_WITH_CI(osFilename.c_str(), GetFSPrefix().c_str()))
2473 : {
2474 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
2475 : pszFilename);
2476 1 : return nullptr;
2477 : }
2478 :
2479 3 : if (pszDomain == nullptr || !EQUAL(pszDomain, "TAGS"))
2480 : {
2481 2 : return VSICurlFilesystemHandlerBase::GetFileMetadata(
2482 2 : osFilename, pszDomain, papszOptions);
2483 : }
2484 :
2485 : auto poS3HandleHelper =
2486 : std::unique_ptr<VSIS3HandleHelper>(VSIS3HandleHelper::BuildFromURI(
2487 3 : osFilename.c_str() + GetFSPrefix().size(), GetFSPrefix().c_str(),
2488 3 : false));
2489 1 : if (!poS3HandleHelper)
2490 0 : return nullptr;
2491 :
2492 2 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2493 2 : NetworkStatisticsAction oContextAction("GetFileMetadata");
2494 :
2495 : bool bRetry;
2496 :
2497 2 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(osFilename));
2498 2 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
2499 2 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
2500 :
2501 2 : CPLStringList aosTags;
2502 1 : do
2503 : {
2504 1 : bRetry = false;
2505 1 : CURL *hCurlHandle = curl_easy_init();
2506 1 : poS3HandleHelper->AddQueryParameter("tagging", "");
2507 :
2508 : struct curl_slist *headers = static_cast<struct curl_slist *>(
2509 1 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
2510 : aosHTTPOptions.List()));
2511 1 : headers = poS3HandleHelper->GetCurlHeaders("GET", headers);
2512 :
2513 2 : CurlRequestHelper requestHelper;
2514 1 : const long response_code = requestHelper.perform(
2515 1 : hCurlHandle, headers, this, poS3HandleHelper.get());
2516 :
2517 1 : NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
2518 :
2519 1 : if (response_code != 200 ||
2520 1 : requestHelper.sWriteFuncData.pBuffer == nullptr)
2521 : {
2522 : // Look if we should attempt a retry
2523 0 : if (oRetryContext.CanRetry(
2524 : static_cast<int>(response_code),
2525 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
2526 : requestHelper.szCurlErrBuf))
2527 : {
2528 0 : CPLError(CE_Warning, CPLE_AppDefined,
2529 : "HTTP error code: %d - %s. "
2530 : "Retrying again in %.1f secs",
2531 : static_cast<int>(response_code),
2532 0 : poS3HandleHelper->GetURL().c_str(),
2533 : oRetryContext.GetCurrentDelay());
2534 0 : CPLSleep(oRetryContext.GetCurrentDelay());
2535 0 : bRetry = true;
2536 : }
2537 0 : else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
2538 0 : poS3HandleHelper->CanRestartOnError(
2539 0 : requestHelper.sWriteFuncData.pBuffer,
2540 0 : requestHelper.sWriteFuncHeaderData.pBuffer, true))
2541 : {
2542 0 : bRetry = true;
2543 : }
2544 : else
2545 : {
2546 0 : CPLDebug(GetDebugKey(), "%s",
2547 0 : requestHelper.sWriteFuncData.pBuffer
2548 : ? requestHelper.sWriteFuncData.pBuffer
2549 : : "(null)");
2550 0 : CPLError(CE_Failure, CPLE_AppDefined,
2551 : "GetObjectTagging failed");
2552 : }
2553 : }
2554 : else
2555 : {
2556 : CPLXMLNode *psXML =
2557 1 : CPLParseXMLString(requestHelper.sWriteFuncData.pBuffer);
2558 1 : if (psXML)
2559 : {
2560 1 : CPLXMLNode *psTagSet = CPLGetXMLNode(psXML, "=Tagging.TagSet");
2561 1 : if (psTagSet)
2562 : {
2563 2 : for (CPLXMLNode *psIter = psTagSet->psChild; psIter;
2564 1 : psIter = psIter->psNext)
2565 : {
2566 1 : if (psIter->eType == CXT_Element &&
2567 1 : strcmp(psIter->pszValue, "Tag") == 0)
2568 : {
2569 : const char *pszKey =
2570 1 : CPLGetXMLValue(psIter, "Key", "");
2571 : const char *pszValue =
2572 1 : CPLGetXMLValue(psIter, "Value", "");
2573 1 : aosTags.SetNameValue(pszKey, pszValue);
2574 : }
2575 : }
2576 : }
2577 1 : CPLDestroyXMLNode(psXML);
2578 : }
2579 : }
2580 :
2581 1 : curl_easy_cleanup(hCurlHandle);
2582 : } while (bRetry);
2583 1 : return CSLDuplicate(aosTags.List());
2584 : }
2585 :
2586 : /************************************************************************/
2587 : /* SetFileMetadata() */
2588 : /************************************************************************/
2589 :
2590 5 : bool VSIS3FSHandler::SetFileMetadata(const char *pszFilename,
2591 : CSLConstList papszMetadata,
2592 : const char *pszDomain,
2593 : CSLConstList /* papszOptions */)
2594 : {
2595 5 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
2596 0 : return false;
2597 :
2598 10 : CPLString osFilename(pszFilename);
2599 5 : NormalizeFilenameIfNeeded(osFilename);
2600 5 : if (!STARTS_WITH_CI(osFilename.c_str(), GetFSPrefix().c_str()))
2601 : {
2602 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
2603 : pszFilename);
2604 1 : return false;
2605 : }
2606 :
2607 4 : if (pszDomain == nullptr ||
2608 4 : !(EQUAL(pszDomain, "HEADERS") || EQUAL(pszDomain, "TAGS")))
2609 : {
2610 1 : CPLError(CE_Failure, CPLE_NotSupported,
2611 : "Only HEADERS and TAGS domain are supported");
2612 1 : return false;
2613 : }
2614 :
2615 3 : if (EQUAL(pszDomain, "HEADERS"))
2616 : {
2617 1 : return CopyObject(osFilename, osFilename, papszMetadata) == 0;
2618 : }
2619 :
2620 : auto poS3HandleHelper =
2621 : std::unique_ptr<VSIS3HandleHelper>(VSIS3HandleHelper::BuildFromURI(
2622 6 : osFilename.c_str() + GetFSPrefix().size(), GetFSPrefix().c_str(),
2623 6 : false));
2624 2 : if (!poS3HandleHelper)
2625 0 : return false;
2626 :
2627 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2628 4 : NetworkStatisticsAction oContextAction("SetFileMetadata");
2629 :
2630 : // Compose XML post content
2631 4 : std::string osXML;
2632 2 : if (papszMetadata != nullptr && papszMetadata[0] != nullptr)
2633 : {
2634 1 : CPLXMLNode *psXML = CPLCreateXMLNode(nullptr, CXT_Element, "?xml");
2635 1 : CPLAddXMLAttributeAndValue(psXML, "version", "1.0");
2636 1 : CPLAddXMLAttributeAndValue(psXML, "encoding", "UTF-8");
2637 : CPLXMLNode *psTagging =
2638 1 : CPLCreateXMLNode(nullptr, CXT_Element, "Tagging");
2639 1 : psXML->psNext = psTagging;
2640 1 : CPLAddXMLAttributeAndValue(psTagging, "xmlns",
2641 : "http://s3.amazonaws.com/doc/2006-03-01/");
2642 : CPLXMLNode *psTagSet =
2643 1 : CPLCreateXMLNode(psTagging, CXT_Element, "TagSet");
2644 2 : for (int i = 0; papszMetadata[i]; ++i)
2645 : {
2646 1 : char *pszKey = nullptr;
2647 1 : const char *pszValue = CPLParseNameValue(papszMetadata[i], &pszKey);
2648 1 : if (pszKey && pszValue)
2649 : {
2650 : CPLXMLNode *psTag =
2651 1 : CPLCreateXMLNode(psTagSet, CXT_Element, "Tag");
2652 1 : CPLCreateXMLElementAndValue(psTag, "Key", pszKey);
2653 1 : CPLCreateXMLElementAndValue(psTag, "Value", pszValue);
2654 : }
2655 1 : CPLFree(pszKey);
2656 : }
2657 :
2658 1 : char *pszXML = CPLSerializeXMLTree(psXML);
2659 1 : osXML = pszXML;
2660 1 : CPLFree(pszXML);
2661 1 : CPLDestroyXMLNode(psXML);
2662 : }
2663 :
2664 4 : std::string osContentMD5;
2665 2 : if (!osXML.empty())
2666 : {
2667 : struct CPLMD5Context context;
2668 1 : CPLMD5Init(&context);
2669 1 : CPLMD5Update(&context, osXML.data(), osXML.size());
2670 : unsigned char hash[16];
2671 1 : CPLMD5Final(hash, &context);
2672 1 : char *pszBase64 = CPLBase64Encode(16, hash);
2673 1 : osContentMD5 = "Content-MD5: ";
2674 1 : osContentMD5 += pszBase64;
2675 1 : CPLFree(pszBase64);
2676 : }
2677 :
2678 : bool bRetry;
2679 :
2680 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(osFilename));
2681 4 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
2682 2 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
2683 :
2684 2 : bool bRet = false;
2685 :
2686 2 : do
2687 : {
2688 2 : bRetry = false;
2689 2 : CURL *hCurlHandle = curl_easy_init();
2690 2 : poS3HandleHelper->AddQueryParameter("tagging", "");
2691 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
2692 : osXML.empty() ? "DELETE" : "PUT");
2693 2 : if (!osXML.empty())
2694 : {
2695 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
2696 : osXML.c_str());
2697 : }
2698 :
2699 : struct curl_slist *headers = static_cast<struct curl_slist *>(
2700 2 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
2701 : aosHTTPOptions.List()));
2702 2 : if (!osXML.empty())
2703 : {
2704 : headers =
2705 1 : curl_slist_append(headers, "Content-Type: application/xml");
2706 1 : headers = curl_slist_append(headers, osContentMD5.c_str());
2707 2 : headers = poS3HandleHelper->GetCurlHeaders(
2708 1 : "PUT", headers, osXML.c_str(), osXML.size());
2709 1 : NetworkStatisticsLogger::LogPUT(osXML.size());
2710 : }
2711 : else
2712 : {
2713 1 : headers = poS3HandleHelper->GetCurlHeaders("DELETE", headers);
2714 1 : NetworkStatisticsLogger::LogDELETE();
2715 : }
2716 :
2717 4 : CurlRequestHelper requestHelper;
2718 2 : const long response_code = requestHelper.perform(
2719 2 : hCurlHandle, headers, this, poS3HandleHelper.get());
2720 :
2721 5 : if ((!osXML.empty() && response_code != 200) ||
2722 3 : (osXML.empty() && response_code != 204))
2723 : {
2724 : // Look if we should attempt a retry
2725 0 : if (oRetryContext.CanRetry(
2726 : static_cast<int>(response_code),
2727 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
2728 : requestHelper.szCurlErrBuf))
2729 : {
2730 0 : CPLError(CE_Warning, CPLE_AppDefined,
2731 : "HTTP error code: %d - %s. "
2732 : "Retrying again in %.1f secs",
2733 : static_cast<int>(response_code),
2734 0 : poS3HandleHelper->GetURL().c_str(),
2735 : oRetryContext.GetCurrentDelay());
2736 0 : CPLSleep(oRetryContext.GetCurrentDelay());
2737 0 : bRetry = true;
2738 : }
2739 0 : else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
2740 0 : poS3HandleHelper->CanRestartOnError(
2741 0 : requestHelper.sWriteFuncData.pBuffer,
2742 0 : requestHelper.sWriteFuncHeaderData.pBuffer, true))
2743 : {
2744 0 : bRetry = true;
2745 : }
2746 : else
2747 : {
2748 0 : CPLDebug(GetDebugKey(), "%s",
2749 0 : requestHelper.sWriteFuncData.pBuffer
2750 : ? requestHelper.sWriteFuncData.pBuffer
2751 : : "(null)");
2752 0 : CPLError(CE_Failure, CPLE_AppDefined,
2753 : "PutObjectTagging failed");
2754 : }
2755 : }
2756 : else
2757 : {
2758 2 : bRet = true;
2759 : }
2760 :
2761 2 : curl_easy_cleanup(hCurlHandle);
2762 : } while (bRetry);
2763 2 : return bRet;
2764 : }
2765 :
2766 : /************************************************************************/
2767 : /* GetStreamingFilename() */
2768 : /************************************************************************/
2769 :
2770 : std::string
2771 5979 : VSIS3FSHandler::GetStreamingFilename(const std::string &osFilename) const
2772 : {
2773 5979 : if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
2774 11965 : return "/vsis3_streaming/" + osFilename.substr(GetFSPrefix().size());
2775 0 : return osFilename;
2776 : }
2777 :
2778 : /************************************************************************/
2779 : /* Mkdir() */
2780 : /************************************************************************/
2781 :
2782 11 : int IVSIS3LikeFSHandler::MkdirInternal(const char *pszDirname, long /*nMode*/,
2783 : bool bDoStatCheck)
2784 : {
2785 11 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
2786 1 : return -1;
2787 :
2788 20 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2789 20 : NetworkStatisticsAction oContextAction("Mkdir");
2790 :
2791 20 : CPLString osDirname(pszDirname);
2792 10 : NormalizeFilenameIfNeeded(osDirname);
2793 10 : if (!STARTS_WITH_CI(osDirname.c_str(), GetFSPrefix().c_str()))
2794 : {
2795 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
2796 : pszDirname);
2797 1 : return -1;
2798 : }
2799 :
2800 9 : if (!osDirname.empty() && osDirname.back() != '/')
2801 9 : osDirname += "/";
2802 :
2803 9 : if (bDoStatCheck)
2804 : {
2805 : VSIStatBufL sStat;
2806 12 : if (VSIStatL(osDirname.c_str(), &sStat) == 0 &&
2807 3 : VSI_ISDIR(sStat.st_mode))
2808 : {
2809 3 : CPLDebug(GetDebugKey(), "Directory %s already exists",
2810 : osDirname.c_str());
2811 3 : errno = EEXIST;
2812 3 : return -1;
2813 : }
2814 : }
2815 :
2816 6 : int ret = 0;
2817 6 : if (CPLTestBool(CPLGetConfigOption("CPL_VSIS3_CREATE_DIR_OBJECT", "YES")))
2818 : {
2819 6 : VSILFILE *fp = VSIFOpenL(osDirname.c_str(), "wb");
2820 6 : if (fp != nullptr)
2821 : {
2822 6 : CPLErrorReset();
2823 6 : VSIFCloseL(fp);
2824 6 : ret = CPLGetLastErrorType() == CPLE_None ? 0 : -1;
2825 : }
2826 : else
2827 : {
2828 0 : ret = -1;
2829 : }
2830 : }
2831 :
2832 6 : if (ret == 0)
2833 : {
2834 12 : std::string osDirnameWithoutEndSlash(osDirname);
2835 6 : osDirnameWithoutEndSlash.pop_back();
2836 :
2837 6 : InvalidateDirContent(
2838 12 : CPLGetDirnameSafe(osDirnameWithoutEndSlash.c_str()));
2839 :
2840 12 : FileProp cachedFileProp;
2841 6 : GetCachedFileProp(GetURLFromFilename(osDirname.c_str()).c_str(),
2842 : cachedFileProp);
2843 6 : cachedFileProp.eExists = EXIST_YES;
2844 6 : cachedFileProp.bIsDirectory = true;
2845 6 : cachedFileProp.bHasComputedFileSize = true;
2846 6 : SetCachedFileProp(GetURLFromFilename(osDirname.c_str()).c_str(),
2847 : cachedFileProp);
2848 :
2849 6 : RegisterEmptyDir(osDirnameWithoutEndSlash);
2850 6 : RegisterEmptyDir(osDirname);
2851 : }
2852 6 : return ret;
2853 : }
2854 :
2855 11 : int IVSIS3LikeFSHandler::Mkdir(const char *pszDirname, long nMode)
2856 : {
2857 11 : return MkdirInternal(pszDirname, nMode, true);
2858 : }
2859 :
2860 : /************************************************************************/
2861 : /* Rmdir() */
2862 : /************************************************************************/
2863 :
2864 14 : int IVSIS3LikeFSHandler::Rmdir(const char *pszDirname)
2865 : {
2866 14 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
2867 1 : return -1;
2868 :
2869 26 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2870 26 : NetworkStatisticsAction oContextAction("Rmdir");
2871 :
2872 26 : CPLString osDirname(pszDirname);
2873 13 : NormalizeFilenameIfNeeded(osDirname);
2874 13 : if (!STARTS_WITH_CI(osDirname.c_str(), GetFSPrefix().c_str()))
2875 : {
2876 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
2877 : pszDirname);
2878 1 : return -1;
2879 : }
2880 :
2881 12 : if (!osDirname.empty() && osDirname.back() != '/')
2882 12 : osDirname += "/";
2883 :
2884 : VSIStatBufL sStat;
2885 12 : if (VSIStatL(osDirname.c_str(), &sStat) != 0)
2886 : {
2887 5 : CPLDebug(GetDebugKey(), "%s is not a object", pszDirname);
2888 5 : errno = ENOENT;
2889 5 : return -1;
2890 : }
2891 7 : else if (!VSI_ISDIR(sStat.st_mode))
2892 : {
2893 0 : CPLDebug(GetDebugKey(), "%s is not a directory", pszDirname);
2894 0 : errno = ENOTDIR;
2895 0 : return -1;
2896 : }
2897 :
2898 7 : char **papszFileList = ReadDirEx(osDirname.c_str(), 100);
2899 7 : bool bEmptyDir =
2900 14 : papszFileList == nullptr ||
2901 7 : (EQUAL(papszFileList[0], ".") && papszFileList[1] == nullptr);
2902 7 : CSLDestroy(papszFileList);
2903 7 : if (!bEmptyDir)
2904 : {
2905 3 : CPLDebug(GetDebugKey(), "%s is not empty", pszDirname);
2906 3 : errno = ENOTEMPTY;
2907 3 : return -1;
2908 : }
2909 :
2910 8 : std::string osDirnameWithoutEndSlash(osDirname);
2911 4 : osDirnameWithoutEndSlash.pop_back();
2912 4 : if (osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
2913 : std::string::npos)
2914 : {
2915 0 : CPLDebug(GetDebugKey(), "%s is a bucket", pszDirname);
2916 0 : errno = ENOTDIR;
2917 0 : return -1;
2918 : }
2919 :
2920 4 : int ret = DeleteObject(osDirname.c_str());
2921 4 : if (ret == 0)
2922 : {
2923 4 : InvalidateDirContent(osDirnameWithoutEndSlash.c_str());
2924 : }
2925 4 : return ret;
2926 : }
2927 :
2928 : /************************************************************************/
2929 : /* Stat() */
2930 : /************************************************************************/
2931 :
2932 156 : int IVSIS3LikeFSHandler::Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
2933 : int nFlags)
2934 : {
2935 156 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
2936 1 : return -1;
2937 :
2938 310 : CPLString osFilename(pszFilename);
2939 155 : NormalizeFilenameIfNeeded(osFilename);
2940 155 : if (!STARTS_WITH_CI(osFilename.c_str(), GetFSPrefix().c_str()))
2941 : {
2942 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
2943 : pszFilename);
2944 1 : return -1;
2945 : }
2946 :
2947 154 : if ((nFlags & VSI_STAT_CACHE_ONLY) != 0)
2948 2 : return VSICurlFilesystemHandlerBase::Stat(osFilename, pStatBuf, nFlags);
2949 :
2950 152 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
2951 152 : if (!IsAllowedFilename(osFilename))
2952 0 : return -1;
2953 :
2954 304 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2955 304 : NetworkStatisticsAction oContextAction("Stat");
2956 :
2957 152 : if (osFilename.find('/', GetFSPrefix().size()) == std::string::npos)
2958 6 : osFilename += "/";
2959 :
2960 304 : std::string osFilenameWithoutSlash(osFilename);
2961 152 : if (osFilenameWithoutSlash.back() == '/')
2962 26 : osFilenameWithoutSlash.pop_back();
2963 :
2964 : // If there's directory content for the directory where this file belongs
2965 : // to, use it to detect if the object does not exist
2966 304 : CachedDirList cachedDirList;
2967 : const std::string osDirname(
2968 304 : CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
2969 456 : if (STARTS_WITH_CI(osDirname.c_str(), GetFSPrefix().c_str()) &&
2970 485 : GetCachedDirList(osDirname.c_str(), cachedDirList) &&
2971 29 : cachedDirList.bGotFileList)
2972 : {
2973 : const std::string osFilenameOnly(
2974 17 : CPLGetFilename(osFilenameWithoutSlash.c_str()));
2975 17 : bool bFound = false;
2976 20 : for (int i = 0; i < cachedDirList.oFileList.size(); i++)
2977 : {
2978 18 : if (cachedDirList.oFileList[i] == osFilenameOnly)
2979 : {
2980 15 : bFound = true;
2981 15 : break;
2982 : }
2983 : }
2984 17 : if (!bFound)
2985 : {
2986 2 : return -1;
2987 : }
2988 : }
2989 :
2990 : // We cannot stat the root directory of a S3 directory bucket, otherwise
2991 : // we get a 501 error.
2992 150 : bool bStatBaseObject = true;
2993 150 : if (GetFSPrefix() == "/vsis3/")
2994 : {
2995 : auto poS3HandleHelper =
2996 : std::unique_ptr<VSIS3HandleHelper>(VSIS3HandleHelper::BuildFromURI(
2997 204 : osFilename.c_str() + GetFSPrefix().size(),
2998 306 : GetFSPrefix().c_str(), true));
2999 102 : if (poS3HandleHelper)
3000 : {
3001 : const bool bIsDirectoryBucket =
3002 99 : poS3HandleHelper->IsDirectoryBucket();
3003 99 : if (bIsDirectoryBucket && poS3HandleHelper->GetObjectKey().empty())
3004 0 : bStatBaseObject = false;
3005 : }
3006 : }
3007 :
3008 150 : if (bStatBaseObject && VSICurlFilesystemHandlerBase::Stat(
3009 : osFilename.c_str(), pStatBuf, nFlags) == 0)
3010 : {
3011 93 : return 0;
3012 : }
3013 :
3014 57 : char **papszRet = ReadDirInternal(osFilename.c_str(), 100, nullptr);
3015 57 : int nRet = papszRet ? 0 : -1;
3016 57 : if (nRet == 0)
3017 : {
3018 6 : pStatBuf->st_mtime = 0;
3019 6 : pStatBuf->st_size = 0;
3020 6 : pStatBuf->st_mode = S_IFDIR;
3021 :
3022 6 : FileProp cachedFileProp;
3023 6 : GetCachedFileProp(GetURLFromFilename(osFilename.c_str()).c_str(),
3024 : cachedFileProp);
3025 6 : cachedFileProp.eExists = EXIST_YES;
3026 6 : cachedFileProp.bIsDirectory = true;
3027 6 : cachedFileProp.bHasComputedFileSize = true;
3028 6 : SetCachedFileProp(GetURLFromFilename(osFilename.c_str()).c_str(),
3029 : cachedFileProp);
3030 : }
3031 57 : CSLDestroy(papszRet);
3032 57 : return nRet;
3033 : }
3034 :
3035 : /************************************************************************/
3036 : /* CreateFileHandle() */
3037 : /************************************************************************/
3038 :
3039 192 : VSICurlHandle *VSIS3FSHandler::CreateFileHandle(const char *pszFilename)
3040 : {
3041 384 : VSIS3HandleHelper *poS3HandleHelper = VSIS3HandleHelper::BuildFromURI(
3042 576 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str(), false);
3043 192 : if (poS3HandleHelper)
3044 : {
3045 181 : return new VSIS3Handle(this, pszFilename, poS3HandleHelper);
3046 : }
3047 11 : return nullptr;
3048 : }
3049 :
3050 : /************************************************************************/
3051 : /* GetURLFromFilename() */
3052 : /************************************************************************/
3053 :
3054 : std::string
3055 123 : VSIS3FSHandler::GetURLFromFilename(const std::string &osFilename) const
3056 : {
3057 : const std::string osFilenameWithoutPrefix =
3058 246 : osFilename.substr(GetFSPrefix().size());
3059 :
3060 : auto poS3HandleHelper =
3061 : std::unique_ptr<VSIS3HandleHelper>(VSIS3HandleHelper::BuildFromURI(
3062 246 : osFilenameWithoutPrefix.c_str(), GetFSPrefix().c_str(), true));
3063 123 : if (!poS3HandleHelper)
3064 : {
3065 3 : return std::string();
3066 : }
3067 240 : std::string osBaseURL(poS3HandleHelper->GetURL());
3068 120 : if (!osBaseURL.empty() && osBaseURL.back() == '/')
3069 42 : osBaseURL.pop_back();
3070 120 : return osBaseURL;
3071 : }
3072 :
3073 : /************************************************************************/
3074 : /* CreateHandleHelper() */
3075 : /************************************************************************/
3076 :
3077 152 : IVSIS3LikeHandleHelper *VSIS3FSHandler::CreateHandleHelper(const char *pszURI,
3078 : bool bAllowNoObject)
3079 : {
3080 304 : return VSIS3HandleHelper::BuildFromURI(pszURI, GetFSPrefix().c_str(),
3081 304 : bAllowNoObject);
3082 : }
3083 :
3084 : /************************************************************************/
3085 : /* Unlink() */
3086 : /************************************************************************/
3087 :
3088 19 : int IVSIS3LikeFSHandler::Unlink(const char *pszFilename)
3089 : {
3090 19 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
3091 0 : return -1;
3092 :
3093 38 : CPLString osFilename(pszFilename);
3094 19 : NormalizeFilenameIfNeeded(osFilename);
3095 19 : if (!STARTS_WITH_CI(osFilename.c_str(), GetFSPrefix().c_str()))
3096 : {
3097 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'",
3098 : pszFilename);
3099 1 : return -1;
3100 : }
3101 :
3102 54 : std::string osNameWithoutPrefix = osFilename.c_str() + GetFSPrefix().size();
3103 18 : if (osNameWithoutPrefix.find('/') == std::string::npos)
3104 : {
3105 2 : CPLDebug(GetDebugKey(), "%s is not a file", pszFilename);
3106 2 : errno = EISDIR;
3107 2 : return -1;
3108 : }
3109 :
3110 32 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
3111 32 : NetworkStatisticsAction oContextAction("Unlink");
3112 :
3113 : VSIStatBufL sStat;
3114 16 : if (VSIStatL(osFilename, &sStat) != 0)
3115 : {
3116 1 : CPLDebug(GetDebugKey(), "%s is not a object", pszFilename);
3117 1 : errno = ENOENT;
3118 1 : return -1;
3119 : }
3120 15 : else if (!VSI_ISREG(sStat.st_mode))
3121 : {
3122 0 : CPLDebug(GetDebugKey(), "%s is not a file", pszFilename);
3123 0 : errno = EISDIR;
3124 0 : return -1;
3125 : }
3126 :
3127 15 : return DeleteObject(osFilename);
3128 : }
3129 :
3130 : /************************************************************************/
3131 : /* Rename() */
3132 : /************************************************************************/
3133 :
3134 8 : int IVSIS3LikeFSHandler::Rename(const char *oldpath, const char *newpath,
3135 : GDALProgressFunc pfnProgress,
3136 : void *pProgressData)
3137 : {
3138 8 : if (!STARTS_WITH_CI(oldpath, GetFSPrefix().c_str()))
3139 0 : return -1;
3140 8 : if (!STARTS_WITH_CI(newpath, GetFSPrefix().c_str()))
3141 0 : return -1;
3142 :
3143 16 : CPLString osOldPath(oldpath);
3144 8 : NormalizeFilenameIfNeeded(osOldPath);
3145 8 : if (!STARTS_WITH_CI(osOldPath.c_str(), GetFSPrefix().c_str()))
3146 : {
3147 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'", oldpath);
3148 1 : return -1;
3149 : }
3150 :
3151 14 : CPLString osNewPath(newpath);
3152 7 : NormalizeFilenameIfNeeded(osNewPath);
3153 7 : if (!STARTS_WITH_CI(osNewPath.c_str(), GetFSPrefix().c_str()))
3154 : {
3155 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'", newpath);
3156 1 : return -1;
3157 : }
3158 12 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
3159 12 : NetworkStatisticsAction oContextAction("Rename");
3160 :
3161 : VSIStatBufL sStat;
3162 6 : if (VSIStatL(osOldPath, &sStat) != 0)
3163 : {
3164 0 : CPLDebug(GetDebugKey(), "%s is not a object", oldpath);
3165 0 : errno = ENOENT;
3166 0 : return -1;
3167 : }
3168 :
3169 : // AWS doesn't like renaming to the same name, and errors out
3170 : // But GCS does like it, and so we might end up killing ourselves !
3171 : // POSIX says renaming on the same file is OK
3172 6 : if (strcmp(osOldPath, osNewPath) == 0)
3173 0 : return 0;
3174 :
3175 6 : if (VSI_ISDIR(sStat.st_mode))
3176 : {
3177 1 : int ret = 0;
3178 1 : const CPLStringList aosList(VSIReadDir(osOldPath));
3179 1 : Mkdir(osNewPath, 0755);
3180 1 : const int nListSize = aosList.size();
3181 2 : for (int i = 0; ret == 0 && i < nListSize; i++)
3182 : {
3183 : const std::string osSrc =
3184 2 : CPLFormFilenameSafe(osOldPath, aosList[i], nullptr);
3185 : const std::string osTarget =
3186 2 : CPLFormFilenameSafe(osNewPath, aosList[i], nullptr);
3187 : void *pScaledProgress =
3188 2 : GDALCreateScaledProgress(static_cast<double>(i) / nListSize,
3189 1 : static_cast<double>(i + 1) / nListSize,
3190 : pfnProgress, pProgressData);
3191 1 : ret = Rename(osSrc.c_str(), osTarget.c_str(),
3192 : pScaledProgress ? GDALScaledProgress : nullptr,
3193 1 : pScaledProgress);
3194 1 : GDALDestroyScaledProgress(pScaledProgress);
3195 : }
3196 1 : if (ret == 0)
3197 1 : Rmdir(osOldPath);
3198 1 : return ret;
3199 : }
3200 : else
3201 : {
3202 5 : if (VSIStatL(osNewPath, &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
3203 : {
3204 1 : CPLDebug(GetDebugKey(), "%s already exists and is a directory",
3205 : newpath);
3206 1 : errno = ENOTEMPTY;
3207 1 : return -1;
3208 : }
3209 4 : if (CopyObject(osOldPath, osNewPath, nullptr) != 0)
3210 : {
3211 0 : return -1;
3212 : }
3213 4 : return DeleteObject(osOldPath);
3214 : }
3215 : }
3216 :
3217 : /************************************************************************/
3218 : /* CopyObject() */
3219 : /************************************************************************/
3220 :
3221 7 : int IVSIS3LikeFSHandler::CopyObject(const char *oldpath, const char *newpath,
3222 : CSLConstList papszMetadata)
3223 : {
3224 21 : std::string osTargetNameWithoutPrefix = newpath + GetFSPrefix().size();
3225 : std::unique_ptr<IVSIS3LikeHandleHelper> poS3HandleHelper(
3226 14 : CreateHandleHelper(osTargetNameWithoutPrefix.c_str(), false));
3227 7 : if (poS3HandleHelper == nullptr)
3228 : {
3229 0 : return -1;
3230 : }
3231 :
3232 14 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
3233 14 : NetworkStatisticsAction oContextAction("CopyObject");
3234 :
3235 14 : std::string osSourceHeader(poS3HandleHelper->GetCopySourceHeader());
3236 7 : if (osSourceHeader.empty())
3237 : {
3238 0 : CPLError(CE_Failure, CPLE_NotSupported,
3239 : "Object copy not supported by this file system");
3240 0 : return -1;
3241 : }
3242 7 : osSourceHeader += ": /";
3243 7 : if (STARTS_WITH(oldpath, "/vsis3/"))
3244 : osSourceHeader +=
3245 5 : CPLAWSURLEncode(oldpath + GetFSPrefix().size(), false);
3246 : else
3247 2 : osSourceHeader += (oldpath + GetFSPrefix().size());
3248 :
3249 7 : int nRet = 0;
3250 :
3251 : bool bRetry;
3252 :
3253 14 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(oldpath));
3254 14 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
3255 7 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
3256 :
3257 7 : do
3258 : {
3259 7 : bRetry = false;
3260 7 : CURL *hCurlHandle = curl_easy_init();
3261 7 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
3262 :
3263 : struct curl_slist *headers = static_cast<struct curl_slist *>(
3264 7 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
3265 : aosHTTPOptions.List()));
3266 7 : headers = curl_slist_append(headers, osSourceHeader.c_str());
3267 7 : headers = curl_slist_append(
3268 : headers, "Content-Length: 0"); // Required by GCS, but not by S3
3269 7 : if (papszMetadata && papszMetadata[0])
3270 : {
3271 : const char *pszReplaceDirective =
3272 4 : poS3HandleHelper->GetMetadataDirectiveREPLACE();
3273 4 : if (pszReplaceDirective[0])
3274 4 : headers = curl_slist_append(headers, pszReplaceDirective);
3275 8 : for (int i = 0; papszMetadata[i]; i++)
3276 : {
3277 4 : char *pszKey = nullptr;
3278 : const char *pszValue =
3279 4 : CPLParseNameValue(papszMetadata[i], &pszKey);
3280 4 : if (pszKey && pszValue)
3281 : {
3282 4 : headers = curl_slist_append(
3283 : headers, CPLSPrintf("%s: %s", pszKey, pszValue));
3284 : }
3285 4 : CPLFree(pszKey);
3286 : }
3287 : }
3288 7 : headers = VSICurlSetContentTypeFromExt(headers, newpath);
3289 7 : headers = poS3HandleHelper->GetCurlHeaders("PUT", headers);
3290 :
3291 14 : CurlRequestHelper requestHelper;
3292 7 : const long response_code = requestHelper.perform(
3293 : hCurlHandle, headers, this, poS3HandleHelper.get());
3294 :
3295 7 : NetworkStatisticsLogger::LogPUT(0);
3296 :
3297 7 : if (response_code != 200)
3298 : {
3299 : // Look if we should attempt a retry
3300 0 : if (oRetryContext.CanRetry(
3301 : static_cast<int>(response_code),
3302 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
3303 : requestHelper.szCurlErrBuf))
3304 : {
3305 0 : CPLError(CE_Warning, CPLE_AppDefined,
3306 : "HTTP error code: %d - %s. "
3307 : "Retrying again in %.1f secs",
3308 : static_cast<int>(response_code),
3309 0 : poS3HandleHelper->GetURL().c_str(),
3310 : oRetryContext.GetCurrentDelay());
3311 0 : CPLSleep(oRetryContext.GetCurrentDelay());
3312 0 : bRetry = true;
3313 : }
3314 0 : else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
3315 0 : poS3HandleHelper->CanRestartOnError(
3316 0 : requestHelper.sWriteFuncData.pBuffer,
3317 0 : requestHelper.sWriteFuncHeaderData.pBuffer, true))
3318 : {
3319 0 : bRetry = true;
3320 : }
3321 : else
3322 : {
3323 0 : CPLDebug(GetDebugKey(), "%s",
3324 0 : requestHelper.sWriteFuncData.pBuffer
3325 : ? requestHelper.sWriteFuncData.pBuffer
3326 : : "(null)");
3327 0 : CPLError(CE_Failure, CPLE_AppDefined, "Copy of %s to %s failed",
3328 : oldpath, newpath);
3329 0 : nRet = -1;
3330 : }
3331 : }
3332 : else
3333 : {
3334 7 : InvalidateCachedData(poS3HandleHelper->GetURL().c_str());
3335 :
3336 7 : std::string osFilenameWithoutSlash(newpath);
3337 14 : if (!osFilenameWithoutSlash.empty() &&
3338 7 : osFilenameWithoutSlash.back() == '/')
3339 0 : osFilenameWithoutSlash.resize(osFilenameWithoutSlash.size() -
3340 : 1);
3341 :
3342 7 : InvalidateDirContent(
3343 14 : CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
3344 : }
3345 :
3346 7 : curl_easy_cleanup(hCurlHandle);
3347 : } while (bRetry);
3348 :
3349 7 : return nRet;
3350 : }
3351 :
3352 : /************************************************************************/
3353 : /* DeleteObject() */
3354 : /************************************************************************/
3355 :
3356 28 : int IVSIS3LikeFSHandler::DeleteObject(const char *pszFilename)
3357 : {
3358 84 : std::string osNameWithoutPrefix = pszFilename + GetFSPrefix().size();
3359 : IVSIS3LikeHandleHelper *poS3HandleHelper =
3360 28 : CreateHandleHelper(osNameWithoutPrefix.c_str(), false);
3361 28 : if (poS3HandleHelper == nullptr)
3362 : {
3363 0 : return -1;
3364 : }
3365 :
3366 56 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
3367 56 : NetworkStatisticsAction oContextAction("DeleteObject");
3368 :
3369 28 : int nRet = 0;
3370 :
3371 : bool bRetry;
3372 :
3373 56 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
3374 56 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
3375 28 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
3376 :
3377 29 : do
3378 : {
3379 29 : bRetry = false;
3380 29 : CURL *hCurlHandle = curl_easy_init();
3381 29 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
3382 : "DELETE");
3383 :
3384 : struct curl_slist *headers = static_cast<struct curl_slist *>(
3385 29 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
3386 : aosHTTPOptions.List()));
3387 29 : headers = poS3HandleHelper->GetCurlHeaders("DELETE", headers);
3388 :
3389 58 : CurlRequestHelper requestHelper;
3390 : const long response_code =
3391 29 : requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
3392 :
3393 29 : NetworkStatisticsLogger::LogDELETE();
3394 :
3395 : // S3 and GS respond with 204. Azure with 202. ADLS with 200.
3396 29 : if (response_code != 204 && response_code != 202 &&
3397 : response_code != 200)
3398 : {
3399 : // Look if we should attempt a retry
3400 6 : if (oRetryContext.CanRetry(
3401 : static_cast<int>(response_code),
3402 6 : requestHelper.sWriteFuncHeaderData.pBuffer,
3403 : requestHelper.szCurlErrBuf))
3404 : {
3405 0 : CPLError(CE_Warning, CPLE_AppDefined,
3406 : "HTTP error code: %d - %s. "
3407 : "Retrying again in %.1f secs",
3408 : static_cast<int>(response_code),
3409 0 : poS3HandleHelper->GetURL().c_str(),
3410 : oRetryContext.GetCurrentDelay());
3411 0 : CPLSleep(oRetryContext.GetCurrentDelay());
3412 0 : bRetry = true;
3413 : }
3414 7 : else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
3415 1 : poS3HandleHelper->CanRestartOnError(
3416 1 : requestHelper.sWriteFuncData.pBuffer,
3417 1 : requestHelper.sWriteFuncHeaderData.pBuffer, true))
3418 : {
3419 1 : bRetry = true;
3420 : }
3421 : else
3422 : {
3423 5 : CPLDebug(GetDebugKey(), "%s",
3424 5 : requestHelper.sWriteFuncData.pBuffer
3425 : ? requestHelper.sWriteFuncData.pBuffer
3426 : : "(null)");
3427 5 : CPLError(CE_Failure, CPLE_AppDefined, "Delete of %s failed",
3428 : pszFilename);
3429 5 : nRet = -1;
3430 : }
3431 : }
3432 : else
3433 : {
3434 23 : InvalidateCachedData(poS3HandleHelper->GetURL().c_str());
3435 :
3436 23 : std::string osFilenameWithoutSlash(pszFilename);
3437 46 : if (!osFilenameWithoutSlash.empty() &&
3438 23 : osFilenameWithoutSlash.back() == '/')
3439 5 : osFilenameWithoutSlash.resize(osFilenameWithoutSlash.size() -
3440 : 1);
3441 :
3442 23 : InvalidateDirContent(
3443 46 : CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
3444 : }
3445 :
3446 29 : curl_easy_cleanup(hCurlHandle);
3447 : } while (bRetry);
3448 :
3449 28 : delete poS3HandleHelper;
3450 28 : return nRet;
3451 : }
3452 :
3453 : /************************************************************************/
3454 : /* DeleteObjectBatch() */
3455 : /************************************************************************/
3456 :
3457 1 : int *IVSIS3LikeFSHandler::DeleteObjectBatch(CSLConstList papszFilesOrDirs)
3458 : {
3459 : int *panRet =
3460 1 : static_cast<int *>(CPLMalloc(sizeof(int) * CSLCount(papszFilesOrDirs)));
3461 2 : for (int i = 0; papszFilesOrDirs && papszFilesOrDirs[i]; ++i)
3462 : {
3463 1 : panRet[i] = DeleteObject(papszFilesOrDirs[i]) == 0;
3464 : }
3465 1 : return panRet;
3466 : }
3467 :
3468 : /************************************************************************/
3469 : /* GetFileList() */
3470 : /************************************************************************/
3471 :
3472 78 : char **IVSIS3LikeFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
3473 : bool *pbGotFileList)
3474 : {
3475 : if (ENABLE_DEBUG)
3476 : CPLDebug(GetDebugKey(), "GetFileList(%s)", pszDirname);
3477 :
3478 78 : *pbGotFileList = false;
3479 :
3480 : char **papszOptions =
3481 78 : CSLSetNameValue(nullptr, "MAXFILES", CPLSPrintf("%d", nMaxFiles));
3482 78 : auto dir = OpenDir(pszDirname, 0, papszOptions);
3483 78 : CSLDestroy(papszOptions);
3484 78 : if (!dir)
3485 : {
3486 36 : return nullptr;
3487 : }
3488 84 : CPLStringList aosFileList;
3489 : while (true)
3490 : {
3491 495 : auto entry = dir->NextDirEntry();
3492 495 : if (!entry)
3493 : {
3494 42 : break;
3495 : }
3496 453 : aosFileList.AddString(entry->pszName);
3497 :
3498 453 : if (nMaxFiles > 0 && aosFileList.size() >= nMaxFiles)
3499 0 : break;
3500 453 : }
3501 42 : delete dir;
3502 42 : *pbGotFileList = true;
3503 42 : return aosFileList.StealList();
3504 : }
3505 :
3506 : /************************************************************************/
3507 : /* OpenDir() */
3508 : /************************************************************************/
3509 :
3510 99 : VSIDIR *IVSIS3LikeFSHandler::OpenDir(const char *pszPath, int nRecurseDepth,
3511 : const char *const *papszOptions)
3512 : {
3513 99 : if (nRecurseDepth > 0)
3514 : {
3515 1 : return VSIFilesystemHandler::OpenDir(pszPath, nRecurseDepth,
3516 1 : papszOptions);
3517 : }
3518 :
3519 98 : if (!STARTS_WITH_CI(pszPath, GetFSPrefix().c_str()))
3520 0 : return nullptr;
3521 :
3522 196 : CPLString osPath(pszPath);
3523 98 : NormalizeFilenameIfNeeded(osPath);
3524 98 : if (!STARTS_WITH_CI(osPath.c_str(), GetFSPrefix().c_str()))
3525 : {
3526 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filename '%s'", pszPath);
3527 1 : return nullptr;
3528 : }
3529 :
3530 194 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
3531 194 : NetworkStatisticsAction oContextAction("OpenDir");
3532 :
3533 291 : std::string osDirnameWithoutPrefix = osPath.c_str() + GetFSPrefix().size();
3534 97 : if (!osDirnameWithoutPrefix.empty() && osDirnameWithoutPrefix.back() == '/')
3535 : {
3536 0 : osDirnameWithoutPrefix.pop_back();
3537 : }
3538 :
3539 194 : std::string osBucket(osDirnameWithoutPrefix);
3540 194 : std::string osObjectKey;
3541 97 : size_t nSlashPos = osDirnameWithoutPrefix.find('/');
3542 97 : if (nSlashPos != std::string::npos)
3543 : {
3544 57 : osBucket = osDirnameWithoutPrefix.substr(0, nSlashPos);
3545 57 : osObjectKey = osDirnameWithoutPrefix.substr(nSlashPos + 1);
3546 : }
3547 :
3548 : auto poS3HandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
3549 194 : CreateHandleHelper(osBucket.c_str(), true));
3550 97 : if (poS3HandleHelper == nullptr)
3551 : {
3552 7 : return nullptr;
3553 : }
3554 :
3555 90 : VSIDIRS3 *dir = new VSIDIRS3(osPath, this);
3556 90 : dir->nRecurseDepth = nRecurseDepth;
3557 90 : dir->poHandleHelper = std::move(poS3HandleHelper);
3558 90 : dir->osBucket = std::move(osBucket);
3559 90 : dir->osObjectKey = std::move(osObjectKey);
3560 90 : dir->nMaxFiles = atoi(CSLFetchNameValueDef(papszOptions, "MAXFILES", "0"));
3561 90 : dir->bCacheEntries = CPLTestBool(
3562 : CSLFetchNameValueDef(papszOptions, "CACHE_ENTRIES", "TRUE"));
3563 90 : dir->m_osFilterPrefix = CSLFetchNameValueDef(papszOptions, "PREFIX", "");
3564 90 : dir->m_bSynthetizeMissingDirectories = CPLTestBool(CSLFetchNameValueDef(
3565 : papszOptions, "SYNTHETIZE_MISSING_DIRECTORIES", "NO"));
3566 90 : dir->m_bListBucket =
3567 96 : dir->osBucket.empty() &&
3568 6 : dynamic_cast<VSIS3HandleHelper *>(dir->poHandleHelper.get()) != nullptr;
3569 90 : if (!dir->IssueListDir())
3570 : {
3571 32 : delete dir;
3572 32 : return nullptr;
3573 : }
3574 :
3575 58 : return dir;
3576 : }
3577 :
3578 : /************************************************************************/
3579 : /* ComputeMD5OfLocalFile() */
3580 : /************************************************************************/
3581 :
3582 8 : static std::string ComputeMD5OfLocalFile(VSILFILE *fp)
3583 : {
3584 8 : constexpr size_t nBufferSize = 10 * 4096;
3585 8 : std::vector<GByte> abyBuffer(nBufferSize, 0);
3586 :
3587 : struct CPLMD5Context context;
3588 8 : CPLMD5Init(&context);
3589 :
3590 : while (true)
3591 : {
3592 8 : size_t nRead = VSIFReadL(&abyBuffer[0], 1, nBufferSize, fp);
3593 8 : CPLMD5Update(&context, &abyBuffer[0], nRead);
3594 8 : if (nRead < nBufferSize)
3595 : {
3596 8 : break;
3597 : }
3598 0 : }
3599 :
3600 : unsigned char hash[16];
3601 8 : CPLMD5Final(hash, &context);
3602 :
3603 8 : constexpr char tohex[] = "0123456789abcdef";
3604 : char hhash[33];
3605 136 : for (int i = 0; i < 16; ++i)
3606 : {
3607 128 : hhash[i * 2] = tohex[(hash[i] >> 4) & 0xf];
3608 128 : hhash[i * 2 + 1] = tohex[hash[i] & 0xf];
3609 : }
3610 8 : hhash[32] = '\0';
3611 :
3612 8 : VSIFSeekL(fp, 0, SEEK_SET);
3613 :
3614 16 : return hhash;
3615 : }
3616 :
3617 : /************************************************************************/
3618 : /* CopyFile() */
3619 : /************************************************************************/
3620 :
3621 21 : int IVSIS3LikeFSHandler::CopyFile(const char *pszSource, const char *pszTarget,
3622 : VSILFILE *fpSource, vsi_l_offset nSourceSize,
3623 : CSLConstList papszOptions,
3624 : GDALProgressFunc pProgressFunc,
3625 : void *pProgressData)
3626 : {
3627 42 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
3628 42 : NetworkStatisticsAction oContextAction("CopyFile");
3629 :
3630 21 : if (!pszSource)
3631 : {
3632 0 : return VSIFilesystemHandler::CopyFile(pszSource, pszTarget, fpSource,
3633 : nSourceSize, papszOptions,
3634 0 : pProgressFunc, pProgressData);
3635 : }
3636 :
3637 42 : std::string osMsg("Copying of ");
3638 21 : osMsg += pszSource;
3639 :
3640 42 : const std::string osPrefix(GetFSPrefix());
3641 37 : if (STARTS_WITH(pszSource, osPrefix.c_str()) &&
3642 16 : STARTS_WITH(pszTarget, osPrefix.c_str()))
3643 : {
3644 7 : bool bRet = CopyObject(pszSource, pszTarget, papszOptions) == 0;
3645 7 : if (bRet && pProgressFunc)
3646 : {
3647 1 : bRet = pProgressFunc(1.0, osMsg.c_str(), pProgressData) != 0;
3648 : }
3649 7 : return bRet ? 0 : -1;
3650 : }
3651 :
3652 14 : VSIVirtualHandleUniquePtr poFileHandleAutoClose;
3653 14 : bool bUsingStreaming = false;
3654 14 : if (!fpSource)
3655 : {
3656 23 : if (STARTS_WITH(pszSource, osPrefix.c_str()) &&
3657 9 : CPLTestBool(CPLGetConfigOption(
3658 : "VSIS3_COPYFILE_USE_STREAMING_SOURCE", "YES")))
3659 : {
3660 : // Try to get a streaming path from the source path
3661 0 : auto poSourceFSHandler = dynamic_cast<IVSIS3LikeFSHandler *>(
3662 8 : VSIFileManager::GetHandler(pszSource));
3663 8 : if (poSourceFSHandler)
3664 : {
3665 : const std::string osStreamingPath =
3666 24 : poSourceFSHandler->GetStreamingFilename(pszSource);
3667 8 : if (!osStreamingPath.empty())
3668 : {
3669 8 : fpSource = VSIFOpenExL(osStreamingPath.c_str(), "rb", TRUE);
3670 8 : if (fpSource)
3671 8 : bUsingStreaming = true;
3672 : }
3673 : }
3674 : }
3675 14 : if (!fpSource)
3676 : {
3677 6 : fpSource = VSIFOpenExL(pszSource, "rb", TRUE);
3678 : }
3679 14 : if (!fpSource)
3680 : {
3681 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszSource);
3682 0 : return false;
3683 : }
3684 :
3685 14 : poFileHandleAutoClose.reset(fpSource);
3686 : }
3687 :
3688 14 : int ret = VSIFilesystemHandler::CopyFile(pszSource, pszTarget, fpSource,
3689 : nSourceSize, papszOptions,
3690 : pProgressFunc, pProgressData);
3691 14 : if (ret == -1 && bUsingStreaming)
3692 : {
3693 : // Retry without streaming. This may be useful for large files, when
3694 : // there are connectivity issues, as retry attempts will be more
3695 : // efficient when using range requests.
3696 0 : CPLDebug(GetDebugKey(), "Retrying copy without streaming");
3697 0 : fpSource = VSIFOpenExL(pszSource, "rb", TRUE);
3698 0 : if (fpSource)
3699 : {
3700 0 : poFileHandleAutoClose.reset(fpSource);
3701 0 : ret = VSIFilesystemHandler::CopyFile(pszSource, pszTarget, fpSource,
3702 : nSourceSize, papszOptions,
3703 : pProgressFunc, pProgressData);
3704 : }
3705 : }
3706 :
3707 14 : return ret;
3708 : }
3709 :
3710 : /************************************************************************/
3711 : /* GetRequestedNumThreadsForCopy() */
3712 : /************************************************************************/
3713 :
3714 40 : static int GetRequestedNumThreadsForCopy(CSLConstList papszOptions)
3715 : {
3716 : #if defined(CPL_MULTIPROC_STUB)
3717 : (void)papszOptions;
3718 : return 1;
3719 : #else
3720 : // 10 threads used by default by the Python s3transfer library
3721 : const char *pszValue =
3722 40 : CSLFetchNameValueDef(papszOptions, "NUM_THREADS", "10");
3723 40 : if (EQUAL(pszValue, "ALL_CPUS"))
3724 0 : return CPLGetNumCPUs();
3725 40 : return atoi(pszValue);
3726 : #endif
3727 : }
3728 :
3729 : /************************************************************************/
3730 : /* CopyFileRestartable() */
3731 : /************************************************************************/
3732 :
3733 18 : int IVSIS3LikeFSHandlerWithMultipartUpload::CopyFileRestartable(
3734 : const char *pszSource, const char *pszTarget, const char *pszInputPayload,
3735 : char **ppszOutputPayload, CSLConstList papszOptions,
3736 : GDALProgressFunc pProgressFunc, void *pProgressData)
3737 : {
3738 36 : const std::string osPrefix(GetFSPrefix());
3739 36 : NetworkStatisticsFileSystem oContextFS(osPrefix.c_str());
3740 36 : NetworkStatisticsAction oContextAction("CopyFileRestartable");
3741 :
3742 18 : *ppszOutputPayload = nullptr;
3743 :
3744 18 : if (!STARTS_WITH(pszTarget, osPrefix.c_str()))
3745 0 : return -1;
3746 :
3747 36 : std::string osMsg("Copying of ");
3748 18 : osMsg += pszSource;
3749 :
3750 : // Can we use server-side copy ?
3751 19 : if (STARTS_WITH(pszSource, osPrefix.c_str()) &&
3752 1 : STARTS_WITH(pszTarget, osPrefix.c_str()))
3753 : {
3754 1 : bool bRet = CopyObject(pszSource, pszTarget, papszOptions) == 0;
3755 1 : if (bRet && pProgressFunc)
3756 : {
3757 0 : bRet = pProgressFunc(1.0, osMsg.c_str(), pProgressData) != 0;
3758 : }
3759 1 : return bRet ? 0 : -1;
3760 : }
3761 :
3762 : // If multipart upload is not supported, fallback to regular CopyFile()
3763 17 : if (!SupportsParallelMultipartUpload())
3764 : {
3765 0 : return CopyFile(pszSource, pszTarget, nullptr,
3766 : static_cast<vsi_l_offset>(-1), papszOptions,
3767 0 : pProgressFunc, pProgressData);
3768 : }
3769 :
3770 34 : VSIVirtualHandleUniquePtr fpSource(VSIFOpenExL(pszSource, "rb", TRUE));
3771 17 : if (!fpSource)
3772 : {
3773 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszSource);
3774 1 : return -1;
3775 : }
3776 :
3777 16 : const char *pszChunkSize = CSLFetchNameValue(papszOptions, "CHUNK_SIZE");
3778 : size_t nChunkSize =
3779 16 : std::max<size_t>(1, GetUploadChunkSizeInBytes(pszTarget, pszChunkSize));
3780 :
3781 : VSIStatBufL sStatBuf;
3782 16 : if (VSIStatL(pszSource, &sStatBuf) != 0)
3783 0 : return -1;
3784 :
3785 : auto poS3HandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
3786 32 : CreateHandleHelper(pszTarget + osPrefix.size(), false));
3787 16 : if (poS3HandleHelper == nullptr)
3788 0 : return -1;
3789 :
3790 16 : int nChunkCount = 0;
3791 32 : std::vector<std::string> aosEtags;
3792 32 : std::string osUploadID;
3793 :
3794 16 : if (pszInputPayload)
3795 : {
3796 : // If there is an input payload, parse it, and do sanity checks
3797 : // and initial setup
3798 :
3799 10 : CPLJSONDocument oDoc;
3800 10 : if (!oDoc.LoadMemory(pszInputPayload))
3801 0 : return -1;
3802 :
3803 10 : auto oRoot = oDoc.GetRoot();
3804 10 : if (oRoot.GetString("source") != pszSource)
3805 : {
3806 1 : CPLError(
3807 : CE_Failure, CPLE_AppDefined,
3808 : "'source' field in input payload does not match pszSource");
3809 1 : return -1;
3810 : }
3811 :
3812 9 : if (oRoot.GetString("target") != pszTarget)
3813 : {
3814 1 : CPLError(
3815 : CE_Failure, CPLE_AppDefined,
3816 : "'target' field in input payload does not match pszTarget");
3817 1 : return -1;
3818 : }
3819 :
3820 16 : if (static_cast<uint64_t>(oRoot.GetLong("source_size")) !=
3821 8 : static_cast<uint64_t>(sStatBuf.st_size))
3822 : {
3823 1 : CPLError(CE_Failure, CPLE_AppDefined,
3824 : "'source_size' field in input payload does not match "
3825 : "source file size");
3826 1 : return -1;
3827 : }
3828 :
3829 14 : if (oRoot.GetLong("source_mtime") !=
3830 7 : static_cast<GIntBig>(sStatBuf.st_mtime))
3831 : {
3832 1 : CPLError(CE_Failure, CPLE_AppDefined,
3833 : "'source_mtime' field in input payload does not match "
3834 : "source file modification time");
3835 1 : return -1;
3836 : }
3837 :
3838 6 : osUploadID = oRoot.GetString("upload_id");
3839 6 : if (osUploadID.empty())
3840 : {
3841 1 : CPLError(CE_Failure, CPLE_AppDefined,
3842 : "'upload_id' field in input payload missing or invalid");
3843 1 : return -1;
3844 : }
3845 :
3846 5 : const auto nChunkSizeLong = oRoot.GetLong("chunk_size");
3847 5 : if (nChunkSizeLong <= 0)
3848 : {
3849 1 : CPLError(CE_Failure, CPLE_AppDefined,
3850 : "'chunk_size' field in input payload missing or invalid");
3851 1 : return -1;
3852 : }
3853 : #if SIZEOF_VOIDP < 8
3854 : if (static_cast<uint64_t>(nChunkSizeLong) >
3855 : std::numeric_limits<size_t>::max())
3856 : {
3857 : CPLError(CE_Failure, CPLE_AppDefined,
3858 : "'chunk_size' field in input payload is too large");
3859 : return -1;
3860 : }
3861 : #endif
3862 4 : nChunkSize = static_cast<size_t>(nChunkSizeLong);
3863 :
3864 8 : auto oEtags = oRoot.GetArray("chunk_etags");
3865 4 : if (!oEtags.IsValid())
3866 : {
3867 1 : CPLError(CE_Failure, CPLE_AppDefined,
3868 : "'chunk_etags' field in input payload missing or invalid");
3869 1 : return -1;
3870 : }
3871 :
3872 3 : const auto nChunkCountLarge = cpl::div_round_up(
3873 3 : static_cast<uint64_t>(sStatBuf.st_size), nChunkSize);
3874 3 : if (nChunkCountLarge != static_cast<size_t>(oEtags.Size()))
3875 : {
3876 1 : CPLError(
3877 : CE_Failure, CPLE_AppDefined,
3878 : "'chunk_etags' field in input payload has not expected size");
3879 1 : return -1;
3880 : }
3881 2 : nChunkCount = oEtags.Size();
3882 6 : for (int iChunk = 0; iChunk < nChunkCount; ++iChunk)
3883 : {
3884 4 : aosEtags.push_back(oEtags[iChunk].ToString());
3885 : }
3886 : }
3887 : else
3888 : {
3889 : // Compute the number of chunks
3890 6 : auto nChunkCountLarge = cpl::div_round_up(
3891 6 : static_cast<uint64_t>(sStatBuf.st_size), nChunkSize);
3892 6 : if (nChunkCountLarge > static_cast<size_t>(GetMaximumPartCount()))
3893 : {
3894 : // Re-adjust the chunk size if needed
3895 0 : const int nWishedChunkCount = GetMaximumPartCount() / 10;
3896 : const uint64_t nMinChunkSizeLarge =
3897 0 : cpl::div_round_up(sStatBuf.st_size, nWishedChunkCount);
3898 0 : if (pszChunkSize)
3899 : {
3900 0 : CPLError(
3901 : CE_Failure, CPLE_AppDefined,
3902 : "Too small CHUNK_SIZE compared to file size. Should be at "
3903 : "least " CPL_FRMT_GUIB,
3904 : static_cast<GUIntBig>(nMinChunkSizeLarge));
3905 0 : return -1;
3906 : }
3907 0 : if (nMinChunkSizeLarge >
3908 0 : static_cast<size_t>(GetMaximumPartSizeInMiB()) * MIB_CONSTANT)
3909 : {
3910 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large file");
3911 0 : return -1;
3912 : }
3913 0 : nChunkSize = static_cast<size_t>(nMinChunkSizeLarge);
3914 0 : nChunkCountLarge = cpl::div_round_up(sStatBuf.st_size, nChunkSize);
3915 : }
3916 6 : nChunkCount = static_cast<int>(nChunkCountLarge);
3917 6 : aosEtags.resize(nChunkCount);
3918 : }
3919 :
3920 : const CPLHTTPRetryParameters oRetryParameters(
3921 16 : CPLStringList(CPLHTTPGetOptionsFromEnv(pszSource)));
3922 8 : if (osUploadID.empty())
3923 : {
3924 12 : osUploadID = InitiateMultipartUpload(pszTarget, poS3HandleHelper.get(),
3925 12 : oRetryParameters, nullptr);
3926 6 : if (osUploadID.empty())
3927 : {
3928 1 : return -1;
3929 : }
3930 : }
3931 :
3932 7 : const int nRequestedThreads = GetRequestedNumThreadsForCopy(papszOptions);
3933 7 : const int nNeededThreads = std::min(nRequestedThreads, nChunkCount);
3934 7 : std::mutex oMutex;
3935 14 : std::condition_variable oCV;
3936 7 : bool bSuccess = true;
3937 7 : bool bStop = false;
3938 7 : bool bAbort = false;
3939 7 : int iCurChunk = 0;
3940 :
3941 7 : const bool bRunInThread = nNeededThreads > 1;
3942 :
3943 : const auto threadFunc =
3944 8 : [this, &fpSource, &aosEtags, &oMutex, &oCV, &iCurChunk, &bStop, &bAbort,
3945 : &bSuccess, &osMsg, &osUploadID, &sStatBuf, &poS3HandleHelper,
3946 : &osPrefix, bRunInThread, pszSource, pszTarget, nChunkCount, nChunkSize,
3947 188 : &oRetryParameters, pProgressFunc, pProgressData]()
3948 : {
3949 0 : VSIVirtualHandleUniquePtr fpUniquePtr;
3950 8 : VSIVirtualHandle *fp = nullptr;
3951 : std::unique_ptr<IVSIS3LikeHandleHelper>
3952 0 : poS3HandleHelperThisThreadUniquePtr;
3953 8 : IVSIS3LikeHandleHelper *poS3HandleHelperThisThread = nullptr;
3954 :
3955 8 : std::vector<GByte> abyBuffer;
3956 : try
3957 : {
3958 8 : abyBuffer.resize(nChunkSize);
3959 : }
3960 0 : catch (const std::exception &)
3961 : {
3962 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
3963 : "Cannot allocate working buffer");
3964 0 : std::lock_guard oLock(oMutex);
3965 0 : bSuccess = false;
3966 0 : bStop = true;
3967 0 : return;
3968 : }
3969 :
3970 : while (true)
3971 : {
3972 : int iChunk;
3973 : {
3974 18 : std::lock_guard oLock(oMutex);
3975 18 : if (bStop)
3976 0 : break;
3977 18 : if (iCurChunk == nChunkCount)
3978 6 : break;
3979 12 : iChunk = iCurChunk;
3980 12 : ++iCurChunk;
3981 : }
3982 12 : if (!fp)
3983 : {
3984 8 : if (iChunk == 0)
3985 : {
3986 7 : fp = fpSource.get();
3987 7 : poS3HandleHelperThisThread = poS3HandleHelper.get();
3988 : }
3989 : else
3990 : {
3991 1 : fpUniquePtr.reset(VSIFOpenExL(pszSource, "rb", TRUE));
3992 1 : if (!fpUniquePtr)
3993 : {
3994 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
3995 : pszSource);
3996 :
3997 0 : std::lock_guard oLock(oMutex);
3998 0 : bSuccess = false;
3999 0 : bStop = true;
4000 0 : break;
4001 : }
4002 1 : fp = fpUniquePtr.get();
4003 :
4004 1 : poS3HandleHelperThisThreadUniquePtr.reset(
4005 1 : CreateHandleHelper(pszTarget + osPrefix.size(), false));
4006 1 : if (!poS3HandleHelperThisThreadUniquePtr)
4007 : {
4008 0 : std::lock_guard oLock(oMutex);
4009 0 : bSuccess = false;
4010 0 : bStop = true;
4011 0 : break;
4012 : }
4013 : poS3HandleHelperThisThread =
4014 1 : poS3HandleHelperThisThreadUniquePtr.get();
4015 : }
4016 : }
4017 :
4018 12 : if (aosEtags[iChunk].empty())
4019 : {
4020 10 : const auto nCurPos =
4021 10 : iChunk * static_cast<vsi_l_offset>(nChunkSize);
4022 10 : CPL_IGNORE_RET_VAL(fp->Seek(nCurPos, SEEK_SET));
4023 10 : const auto nRemaining = sStatBuf.st_size - nCurPos;
4024 10 : const size_t nToRead =
4025 : nRemaining > static_cast<vsi_l_offset>(nChunkSize)
4026 10 : ? nChunkSize
4027 7 : : static_cast<int>(nRemaining);
4028 10 : const size_t nRead = fp->Read(abyBuffer.data(), 1, nToRead);
4029 10 : if (nRead != nToRead)
4030 : {
4031 0 : CPLError(
4032 : CE_Failure, CPLE_FileIO,
4033 : "Did not get expected number of bytes from input file");
4034 0 : std::lock_guard oLock(oMutex);
4035 0 : bAbort = true;
4036 0 : bSuccess = false;
4037 0 : bStop = true;
4038 0 : break;
4039 : }
4040 : const auto osEtag = UploadPart(
4041 : pszTarget, 1 + iChunk, osUploadID, nCurPos,
4042 10 : abyBuffer.data(), nToRead, poS3HandleHelperThisThread,
4043 20 : oRetryParameters, nullptr);
4044 10 : if (osEtag.empty())
4045 : {
4046 4 : std::lock_guard oLock(oMutex);
4047 2 : bSuccess = false;
4048 2 : bStop = true;
4049 2 : break;
4050 : }
4051 8 : aosEtags[iChunk] = std::move(osEtag);
4052 : }
4053 :
4054 10 : if (bRunInThread)
4055 : {
4056 4 : std::lock_guard oLock(oMutex);
4057 2 : oCV.notify_one();
4058 : }
4059 : else
4060 : {
4061 10 : if (pProgressFunc &&
4062 10 : !pProgressFunc(double(iChunk) / std::max(1, nChunkCount),
4063 : osMsg.c_str(), pProgressData))
4064 : {
4065 : // Lock taken only to make static analyzer happy...
4066 0 : std::lock_guard oLock(oMutex);
4067 0 : bSuccess = false;
4068 0 : break;
4069 : }
4070 : }
4071 10 : }
4072 7 : };
4073 :
4074 7 : if (bRunInThread)
4075 : {
4076 2 : std::vector<std::thread> aThreads;
4077 3 : for (int i = 0; i < nNeededThreads; i++)
4078 : {
4079 2 : aThreads.emplace_back(std::thread(threadFunc));
4080 : }
4081 1 : if (pProgressFunc)
4082 : {
4083 0 : std::unique_lock oLock(oMutex);
4084 0 : while (!bStop)
4085 : {
4086 0 : oCV.wait(oLock);
4087 : // coverity[ uninit_use_in_call]
4088 0 : oLock.unlock();
4089 : const bool bInterrupt =
4090 0 : !pProgressFunc(double(iCurChunk) / nChunkCount,
4091 0 : osMsg.c_str(), pProgressData);
4092 0 : oLock.lock();
4093 0 : if (bInterrupt)
4094 : {
4095 0 : bSuccess = false;
4096 0 : bStop = true;
4097 0 : break;
4098 : }
4099 : }
4100 : }
4101 3 : for (auto &thread : aThreads)
4102 : {
4103 2 : thread.join();
4104 : }
4105 : }
4106 : else
4107 : {
4108 6 : threadFunc();
4109 : }
4110 :
4111 7 : if (bAbort)
4112 : {
4113 0 : AbortMultipart(pszTarget, osUploadID, poS3HandleHelper.get(),
4114 0 : oRetryParameters);
4115 0 : return -1;
4116 : }
4117 7 : else if (!bSuccess)
4118 : {
4119 : // Compose an output restart payload
4120 4 : CPLJSONDocument oDoc;
4121 4 : auto oRoot = oDoc.GetRoot();
4122 2 : oRoot.Add("type", "CopyFileRestartablePayload");
4123 2 : oRoot.Add("source", pszSource);
4124 2 : oRoot.Add("target", pszTarget);
4125 2 : oRoot.Add("source_size", static_cast<uint64_t>(sStatBuf.st_size));
4126 2 : oRoot.Add("source_mtime", static_cast<GIntBig>(sStatBuf.st_mtime));
4127 2 : oRoot.Add("chunk_size", static_cast<uint64_t>(nChunkSize));
4128 2 : oRoot.Add("upload_id", osUploadID);
4129 2 : CPLJSONArray oArray;
4130 6 : for (int iChunk = 0; iChunk < nChunkCount; ++iChunk)
4131 : {
4132 4 : if (aosEtags[iChunk].empty())
4133 2 : oArray.AddNull();
4134 : else
4135 2 : oArray.Add(aosEtags[iChunk]);
4136 : }
4137 2 : oRoot.Add("chunk_etags", oArray);
4138 2 : *ppszOutputPayload = CPLStrdup(oDoc.SaveAsString().c_str());
4139 2 : return 1;
4140 : }
4141 :
4142 5 : if (!CompleteMultipart(pszTarget, osUploadID, aosEtags, sStatBuf.st_size,
4143 5 : poS3HandleHelper.get(), oRetryParameters))
4144 : {
4145 1 : AbortMultipart(pszTarget, osUploadID, poS3HandleHelper.get(),
4146 1 : oRetryParameters);
4147 1 : return -1;
4148 : }
4149 :
4150 4 : return 0;
4151 : }
4152 :
4153 : /************************************************************************/
4154 : /* CopyChunk() */
4155 : /************************************************************************/
4156 :
4157 4 : static bool CopyChunk(const char *pszSource, const char *pszTarget,
4158 : vsi_l_offset nStartOffset, size_t nChunkSize)
4159 : {
4160 4 : VSILFILE *fpIn = VSIFOpenExL(pszSource, "rb", TRUE);
4161 4 : if (fpIn == nullptr)
4162 : {
4163 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszSource);
4164 0 : return false;
4165 : }
4166 :
4167 4 : VSILFILE *fpOut = VSIFOpenExL(pszTarget, "wb+", TRUE);
4168 4 : if (fpOut == nullptr)
4169 : {
4170 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", pszTarget);
4171 0 : VSIFCloseL(fpIn);
4172 0 : return false;
4173 : }
4174 :
4175 4 : bool ret = true;
4176 8 : if (VSIFSeekL(fpIn, nStartOffset, SEEK_SET) < 0 ||
4177 4 : VSIFSeekL(fpOut, nStartOffset, SEEK_SET) < 0)
4178 : {
4179 0 : ret = false;
4180 : }
4181 : else
4182 : {
4183 4 : void *pBuffer = VSI_MALLOC_VERBOSE(nChunkSize);
4184 4 : if (pBuffer == nullptr)
4185 : {
4186 0 : ret = false;
4187 : }
4188 : else
4189 : {
4190 8 : if (VSIFReadL(pBuffer, 1, nChunkSize, fpIn) != nChunkSize ||
4191 4 : VSIFWriteL(pBuffer, 1, nChunkSize, fpOut) != nChunkSize)
4192 : {
4193 0 : ret = false;
4194 : }
4195 : }
4196 4 : VSIFree(pBuffer);
4197 : }
4198 :
4199 4 : VSIFCloseL(fpIn);
4200 4 : if (VSIFCloseL(fpOut) != 0)
4201 : {
4202 0 : ret = false;
4203 : }
4204 4 : if (!ret)
4205 : {
4206 0 : CPLError(CE_Failure, CPLE_FileIO, "Copying of %s to %s failed",
4207 : pszSource, pszTarget);
4208 : }
4209 4 : return ret;
4210 : }
4211 :
4212 : /************************************************************************/
4213 : /* Sync() */
4214 : /************************************************************************/
4215 :
4216 35 : bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget,
4217 : const char *const *papszOptions,
4218 : GDALProgressFunc pProgressFunc,
4219 : void *pProgressData, char ***ppapszOutputs)
4220 : {
4221 35 : if (ppapszOutputs)
4222 : {
4223 0 : *ppapszOutputs = nullptr;
4224 : }
4225 :
4226 70 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
4227 70 : NetworkStatisticsAction oContextAction("Sync");
4228 :
4229 70 : std::string osSource(pszSource);
4230 70 : std::string osSourceWithoutSlash(pszSource);
4231 66 : if (osSourceWithoutSlash.back() == '/' ||
4232 31 : osSourceWithoutSlash.back() == '\\')
4233 : {
4234 4 : osSourceWithoutSlash.pop_back();
4235 : }
4236 :
4237 : const CPLHTTPRetryParameters oRetryParameters(
4238 70 : CPLStringList(CPLHTTPGetOptionsFromEnv(pszSource)));
4239 :
4240 35 : const bool bRecursive = CPLFetchBool(papszOptions, "RECURSIVE", true);
4241 :
4242 : enum class SyncStrategy
4243 : {
4244 : TIMESTAMP,
4245 : ETAG,
4246 : OVERWRITE
4247 : };
4248 35 : SyncStrategy eSyncStrategy = SyncStrategy::TIMESTAMP;
4249 : const char *pszSyncStrategy =
4250 35 : CSLFetchNameValueDef(papszOptions, "SYNC_STRATEGY", "TIMESTAMP");
4251 35 : if (EQUAL(pszSyncStrategy, "TIMESTAMP"))
4252 22 : eSyncStrategy = SyncStrategy::TIMESTAMP;
4253 13 : else if (EQUAL(pszSyncStrategy, "ETAG"))
4254 11 : eSyncStrategy = SyncStrategy::ETAG;
4255 2 : else if (EQUAL(pszSyncStrategy, "OVERWRITE"))
4256 2 : eSyncStrategy = SyncStrategy::OVERWRITE;
4257 : else
4258 : {
4259 0 : CPLError(CE_Warning, CPLE_NotSupported,
4260 : "Unsupported value for SYNC_STRATEGY: %s", pszSyncStrategy);
4261 : }
4262 :
4263 : const bool bDownloadFromNetworkToLocal =
4264 69 : (!STARTS_WITH(pszTarget, "/vsi") ||
4265 48 : STARTS_WITH(pszTarget, "/vsimem/")) &&
4266 48 : STARTS_WITH(pszSource, GetFSPrefix().c_str());
4267 35 : const bool bTargetIsThisFS = STARTS_WITH(pszTarget, GetFSPrefix().c_str());
4268 35 : const bool bUploadFromLocalToNetwork =
4269 68 : (!STARTS_WITH(pszSource, "/vsi") ||
4270 35 : STARTS_WITH(pszSource, "/vsimem/")) &&
4271 : bTargetIsThisFS;
4272 :
4273 : // If the source is likely to be a directory, try to issue a ReadDir()
4274 : // if we haven't stat'ed it yet
4275 35 : std::unique_ptr<VSIDIR> poSourceDir;
4276 55 : if (STARTS_WITH(pszSource, GetFSPrefix().c_str()) &&
4277 20 : (osSource.back() == '/' || osSource.back() == '\\'))
4278 : {
4279 3 : const char *const apszOptions[] = {"SYNTHETIZE_MISSING_DIRECTORIES=YES",
4280 : nullptr};
4281 3 : poSourceDir.reset(VSIOpenDir(osSourceWithoutSlash.c_str(),
4282 : bRecursive ? -1 : 0, apszOptions));
4283 : }
4284 :
4285 : VSIStatBufL sSource;
4286 35 : if (VSIStatL(osSourceWithoutSlash.c_str(), &sSource) < 0)
4287 : {
4288 2 : CPLError(CE_Failure, CPLE_FileIO, "%s does not exist", pszSource);
4289 2 : return false;
4290 : }
4291 :
4292 : const auto CanSkipDownloadFromNetworkToLocal =
4293 8 : [this, eSyncStrategy](
4294 : const char *l_pszSource, const char *l_pszTarget,
4295 : GIntBig sourceTime, GIntBig targetTime,
4296 12 : const std::function<std::string(const char *)> &getETAGSourceFile)
4297 : {
4298 8 : switch (eSyncStrategy)
4299 : {
4300 4 : case SyncStrategy::ETAG:
4301 : {
4302 4 : VSILFILE *fpOutAsIn = VSIFOpenExL(l_pszTarget, "rb", TRUE);
4303 4 : if (fpOutAsIn)
4304 : {
4305 4 : std::string md5 = ComputeMD5OfLocalFile(fpOutAsIn);
4306 4 : VSIFCloseL(fpOutAsIn);
4307 4 : if (getETAGSourceFile(l_pszSource) == md5)
4308 : {
4309 3 : CPLDebug(GetDebugKey(),
4310 : "%s has already same content as %s",
4311 : l_pszTarget, l_pszSource);
4312 3 : return true;
4313 : }
4314 : }
4315 1 : return false;
4316 : }
4317 :
4318 3 : case SyncStrategy::TIMESTAMP:
4319 : {
4320 3 : if (targetTime <= sourceTime)
4321 : {
4322 : // Our local copy is older than the source, so
4323 : // presumably the source was uploaded from it. Nothing to do
4324 1 : CPLDebug(GetDebugKey(),
4325 : "%s is older than %s. "
4326 : "Do not replace %s assuming it was used to "
4327 : "upload %s",
4328 : l_pszTarget, l_pszSource, l_pszTarget,
4329 : l_pszSource);
4330 1 : return true;
4331 : }
4332 2 : return false;
4333 : }
4334 :
4335 1 : case SyncStrategy::OVERWRITE:
4336 : {
4337 1 : break;
4338 : }
4339 : }
4340 1 : return false;
4341 33 : };
4342 :
4343 : const auto CanSkipUploadFromLocalToNetwork =
4344 7 : [this, eSyncStrategy](
4345 : VSILFILE *&l_fpIn, const char *l_pszSource, const char *l_pszTarget,
4346 : GIntBig sourceTime, GIntBig targetTime,
4347 12 : const std::function<std::string(const char *)> &getETAGTargetFile)
4348 : {
4349 7 : switch (eSyncStrategy)
4350 : {
4351 4 : case SyncStrategy::ETAG:
4352 : {
4353 4 : l_fpIn = VSIFOpenExL(l_pszSource, "rb", TRUE);
4354 8 : if (l_fpIn && getETAGTargetFile(l_pszTarget) ==
4355 8 : ComputeMD5OfLocalFile(l_fpIn))
4356 : {
4357 4 : CPLDebug(GetDebugKey(), "%s has already same content as %s",
4358 : l_pszTarget, l_pszSource);
4359 4 : VSIFCloseL(l_fpIn);
4360 4 : l_fpIn = nullptr;
4361 4 : return true;
4362 : }
4363 0 : return false;
4364 : }
4365 :
4366 2 : case SyncStrategy::TIMESTAMP:
4367 : {
4368 2 : if (targetTime >= sourceTime)
4369 : {
4370 : // The remote copy is more recent than the source, so
4371 : // presumably it was uploaded from the source. Nothing to do
4372 1 : CPLDebug(GetDebugKey(),
4373 : "%s is more recent than %s. "
4374 : "Do not replace %s assuming it was uploaded from "
4375 : "%s",
4376 : l_pszTarget, l_pszSource, l_pszTarget,
4377 : l_pszSource);
4378 1 : return true;
4379 : }
4380 1 : return false;
4381 : }
4382 :
4383 1 : case SyncStrategy::OVERWRITE:
4384 : {
4385 1 : break;
4386 : }
4387 : }
4388 1 : return false;
4389 33 : };
4390 :
4391 : struct ChunkToCopy
4392 : {
4393 : std::string osSrcFilename{};
4394 : std::string osDstFilename{};
4395 : GIntBig nMTime = 0;
4396 : std::string osETag{};
4397 : vsi_l_offset nTotalSize = 0;
4398 : vsi_l_offset nStartOffset = 0;
4399 : vsi_l_offset nSize = 0;
4400 : };
4401 :
4402 66 : std::vector<ChunkToCopy> aoChunksToCopy;
4403 66 : std::set<std::string> aoSetDirsToCreate;
4404 33 : const char *pszChunkSize = CSLFetchNameValue(papszOptions, "CHUNK_SIZE");
4405 33 : const int nRequestedThreads = GetRequestedNumThreadsForCopy(papszOptions);
4406 : auto poTargetFSMultipartHandler =
4407 0 : dynamic_cast<IVSIS3LikeFSHandlerWithMultipartUpload *>(
4408 33 : VSIFileManager::GetHandler(pszTarget));
4409 : const bool bSupportsParallelMultipartUpload =
4410 47 : bUploadFromLocalToNetwork && poTargetFSMultipartHandler != nullptr &&
4411 14 : poTargetFSMultipartHandler->SupportsParallelMultipartUpload();
4412 : const bool bSimulateThreading =
4413 33 : CPLTestBool(CPLGetConfigOption("VSIS3_SIMULATE_THREADING", "NO"));
4414 33 : const int nMinSizeChunk =
4415 14 : bSupportsParallelMultipartUpload && !bSimulateThreading
4416 47 : ? 8 * MIB_CONSTANT
4417 : : 1; // 5242880 defined by S3 API as the minimum, but 8 MB used by
4418 : // default by the Python s3transfer library
4419 33 : const int nMinThreads = bSimulateThreading ? 0 : 1;
4420 : const size_t nMaxChunkSize =
4421 6 : pszChunkSize && nRequestedThreads > nMinThreads &&
4422 5 : (bDownloadFromNetworkToLocal ||
4423 : bSupportsParallelMultipartUpload)
4424 33 : ? static_cast<size_t>(
4425 6 : std::min(1024 * MIB_CONSTANT,
4426 6 : std::max(nMinSizeChunk, atoi(pszChunkSize))))
4427 33 : : 0;
4428 :
4429 : // Filter x-amz- options when outputting to /vsis3/
4430 66 : CPLStringList aosObjectCreationOptions;
4431 33 : if (poTargetFSMultipartHandler != nullptr && papszOptions != nullptr)
4432 : {
4433 37 : for (auto papszIter = papszOptions; *papszIter != nullptr; ++papszIter)
4434 : {
4435 22 : char *pszKey = nullptr;
4436 22 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
4437 44 : if (pszKey && pszValue &&
4438 22 : poTargetFSMultipartHandler->IsAllowedHeaderForObjectCreation(
4439 22 : pszKey))
4440 : {
4441 3 : aosObjectCreationOptions.SetNameValue(pszKey, pszValue);
4442 : }
4443 22 : CPLFree(pszKey);
4444 : }
4445 : }
4446 :
4447 33 : uint64_t nTotalSize = 0;
4448 66 : std::vector<size_t> anIndexToCopy; // points to aoChunksToCopy
4449 :
4450 : struct MultiPartDef
4451 : {
4452 : std::string osUploadID{};
4453 : int nCountValidETags = 0;
4454 : int nExpectedCount = 0;
4455 : // cppcheck-suppress unusedStructMember
4456 : std::vector<std::string> aosEtags{};
4457 : vsi_l_offset nTotalSize = 0;
4458 : };
4459 :
4460 66 : std::map<std::string, MultiPartDef> oMapMultiPartDefs;
4461 :
4462 : // Cleanup pending uploads in case of early exit
4463 : struct CleanupPendingUploads
4464 : {
4465 : IVSIS3LikeFSHandlerWithMultipartUpload *m_poFS;
4466 : std::map<std::string, MultiPartDef> &m_oMapMultiPartDefs;
4467 : const CPLHTTPRetryParameters &m_oRetryParameters;
4468 :
4469 33 : CleanupPendingUploads(
4470 : IVSIS3LikeFSHandlerWithMultipartUpload *poFSIn,
4471 : std::map<std::string, MultiPartDef> &oMapMultiPartDefsIn,
4472 : const CPLHTTPRetryParameters &oRetryParametersIn)
4473 33 : : m_poFS(poFSIn), m_oMapMultiPartDefs(oMapMultiPartDefsIn),
4474 33 : m_oRetryParameters(oRetryParametersIn)
4475 : {
4476 33 : }
4477 :
4478 33 : ~CleanupPendingUploads()
4479 33 : {
4480 33 : if (m_poFS)
4481 : {
4482 22 : for (const auto &kv : m_oMapMultiPartDefs)
4483 : {
4484 : auto poS3HandleHelper =
4485 : std::unique_ptr<IVSIS3LikeHandleHelper>(
4486 1 : m_poFS->CreateHandleHelper(
4487 1 : kv.first.c_str() + m_poFS->GetFSPrefix().size(),
4488 3 : false));
4489 1 : if (poS3HandleHelper)
4490 : {
4491 1 : m_poFS->AbortMultipart(kv.first, kv.second.osUploadID,
4492 : poS3HandleHelper.get(),
4493 1 : m_oRetryParameters);
4494 : }
4495 : }
4496 : }
4497 33 : }
4498 :
4499 : CleanupPendingUploads(const CleanupPendingUploads &) = delete;
4500 : CleanupPendingUploads &
4501 : operator=(const CleanupPendingUploads &) = delete;
4502 : };
4503 :
4504 : const CleanupPendingUploads cleanupPendingUploads(
4505 66 : poTargetFSMultipartHandler, oMapMultiPartDefs, oRetryParameters);
4506 :
4507 66 : std::string osTargetDir; // set in the VSI_ISDIR(sSource.st_mode) case
4508 66 : std::string osTarget; // set in the !(VSI_ISDIR(sSource.st_mode)) case
4509 :
4510 : const auto NormalizeDirSeparatorForDstFilename =
4511 51 : [&osSource, &osTargetDir](const std::string &s) -> std::string
4512 : {
4513 34 : return CPLString(s).replaceAll(
4514 : VSIGetDirectorySeparator(osSource.c_str()),
4515 34 : VSIGetDirectorySeparator(osTargetDir.c_str()));
4516 33 : };
4517 :
4518 33 : if (VSI_ISDIR(sSource.st_mode))
4519 : {
4520 10 : osTargetDir = pszTarget;
4521 10 : if (osSource.back() != '/' && osSource.back() != '\\')
4522 : {
4523 12 : osTargetDir = CPLFormFilenameSafe(
4524 6 : osTargetDir.c_str(), CPLGetFilename(pszSource), nullptr);
4525 : }
4526 :
4527 10 : if (!poSourceDir)
4528 : {
4529 7 : const char *const apszOptions[] = {
4530 : "SYNTHETIZE_MISSING_DIRECTORIES=YES", nullptr};
4531 7 : poSourceDir.reset(VSIOpenDir(osSourceWithoutSlash.c_str(),
4532 : bRecursive ? -1 : 0, apszOptions));
4533 7 : if (!poSourceDir)
4534 0 : return false;
4535 : }
4536 :
4537 : auto poTargetDir = std::unique_ptr<VSIDIR>(
4538 10 : VSIOpenDir(osTargetDir.c_str(), bRecursive ? -1 : 0, nullptr));
4539 10 : std::set<std::string> oSetTargetSubdirs;
4540 10 : std::map<std::string, VSIDIREntry> oMapExistingTargetFiles;
4541 : // Enumerate existing target files and directories
4542 10 : if (poTargetDir)
4543 : {
4544 : while (true)
4545 : {
4546 7 : const auto entry = VSIGetNextDirEntry(poTargetDir.get());
4547 7 : if (!entry)
4548 4 : break;
4549 : auto osDstName =
4550 9 : NormalizeDirSeparatorForDstFilename(entry->pszName);
4551 3 : if (VSI_ISDIR(entry->nMode))
4552 : {
4553 0 : oSetTargetSubdirs.insert(std::move(osDstName));
4554 : }
4555 : else
4556 : {
4557 : oMapExistingTargetFiles.insert(
4558 3 : std::make_pair(std::move(osDstName), *entry));
4559 : }
4560 3 : }
4561 4 : poTargetDir.reset();
4562 : }
4563 : else
4564 : {
4565 : VSIStatBufL sTarget;
4566 12 : if (VSIStatL(osTargetDir.c_str(), &sTarget) < 0 &&
4567 6 : VSIMkdirRecursive(osTargetDir.c_str(), 0755) < 0)
4568 : {
4569 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s",
4570 : osTargetDir.c_str());
4571 0 : return false;
4572 : }
4573 : }
4574 :
4575 : // Enumerate source files and directories
4576 : while (true)
4577 : {
4578 24 : const auto entry = VSIGetNextDirEntry(poSourceDir.get());
4579 24 : if (!entry)
4580 10 : break;
4581 14 : if (VSI_ISDIR(entry->nMode))
4582 : {
4583 : const auto osDstName =
4584 3 : NormalizeDirSeparatorForDstFilename(entry->pszName);
4585 1 : if (oSetTargetSubdirs.find(osDstName) ==
4586 2 : oSetTargetSubdirs.end())
4587 : {
4588 : std::string osTargetSubdir(CPLFormFilenameSafe(
4589 2 : osTargetDir.c_str(), osDstName.c_str(), nullptr));
4590 1 : aoSetDirsToCreate.insert(std::move(osTargetSubdir));
4591 : }
4592 : }
4593 : else
4594 : {
4595 : // Split file in possibly multiple chunks
4596 : const vsi_l_offset nChunksLarge =
4597 : nMaxChunkSize == 0
4598 13 : ? 1
4599 5 : : cpl::div_round_up(entry->nSize, nMaxChunkSize);
4600 13 : if (nChunksLarge >
4601 : 1000) // must also be below knMAX_PART_NUMBER for upload
4602 : {
4603 0 : CPLError(CE_Failure, CPLE_AppDefined,
4604 : "Too small CHUNK_SIZE w.r.t file size");
4605 0 : return false;
4606 : }
4607 26 : ChunkToCopy chunk;
4608 13 : chunk.osSrcFilename = entry->pszName;
4609 : chunk.osDstFilename =
4610 13 : NormalizeDirSeparatorForDstFilename(entry->pszName);
4611 13 : chunk.nMTime = entry->nMTime;
4612 13 : chunk.nTotalSize = entry->nSize;
4613 : chunk.osETag =
4614 13 : CSLFetchNameValueDef(entry->papszExtra, "ETag", "");
4615 13 : const size_t nChunks = static_cast<size_t>(nChunksLarge);
4616 31 : for (size_t iChunk = 0; iChunk < nChunks; iChunk++)
4617 : {
4618 18 : chunk.nStartOffset = iChunk * nMaxChunkSize;
4619 18 : chunk.nSize =
4620 : nChunks == 1
4621 28 : ? entry->nSize
4622 : : std::min(
4623 28 : entry->nSize - chunk.nStartOffset,
4624 10 : static_cast<vsi_l_offset>(nMaxChunkSize));
4625 18 : aoChunksToCopy.push_back(chunk);
4626 18 : chunk.osETag.clear();
4627 : }
4628 : }
4629 14 : }
4630 10 : poSourceDir.reset();
4631 :
4632 : // Create missing target directories, sorted in lexicographic order
4633 : // so that upper-level directories are listed before subdirectories.
4634 11 : for (const auto &osTargetSubdir : aoSetDirsToCreate)
4635 : {
4636 : const bool ok =
4637 : (bTargetIsThisFS
4638 1 : ? MkdirInternal(osTargetSubdir.c_str(), 0755, false)
4639 1 : : VSIMkdir(osTargetSubdir.c_str(), 0755)) == 0;
4640 1 : if (!ok)
4641 : {
4642 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s",
4643 : osTargetSubdir.c_str());
4644 0 : return false;
4645 : }
4646 : }
4647 :
4648 : // Collect source files to copy
4649 10 : const size_t nChunkCount = aoChunksToCopy.size();
4650 23 : for (size_t iChunk = 0; iChunk < nChunkCount; ++iChunk)
4651 : {
4652 13 : const auto &chunk = aoChunksToCopy[iChunk];
4653 13 : if (chunk.nStartOffset != 0)
4654 0 : continue;
4655 : const std::string osSubSource(
4656 : CPLFormFilenameSafe(osSourceWithoutSlash.c_str(),
4657 13 : chunk.osSrcFilename.c_str(), nullptr));
4658 : const std::string osSubTarget(CPLFormFilenameSafe(
4659 13 : osTargetDir.c_str(), chunk.osDstFilename.c_str(), nullptr));
4660 13 : bool bSkip = false;
4661 : const auto oIterExistingTarget =
4662 13 : oMapExistingTargetFiles.find(chunk.osDstFilename);
4663 16 : if (oIterExistingTarget != oMapExistingTargetFiles.end() &&
4664 3 : oIterExistingTarget->second.nSize == chunk.nTotalSize)
4665 : {
4666 3 : if (bDownloadFromNetworkToLocal)
4667 : {
4668 0 : if (CanSkipDownloadFromNetworkToLocal(
4669 : osSubSource.c_str(), osSubTarget.c_str(),
4670 0 : chunk.nMTime, oIterExistingTarget->second.nMTime,
4671 0 : [&chunk](const char *) -> std::string
4672 0 : { return chunk.osETag; }))
4673 : {
4674 0 : bSkip = true;
4675 : }
4676 : }
4677 3 : else if (bUploadFromLocalToNetwork)
4678 : {
4679 1 : VSILFILE *fpIn = nullptr;
4680 2 : if (CanSkipUploadFromLocalToNetwork(
4681 : fpIn, osSubSource.c_str(), osSubTarget.c_str(),
4682 1 : chunk.nMTime, oIterExistingTarget->second.nMTime,
4683 2 : [&oIterExistingTarget](const char *) -> std::string
4684 : {
4685 : return std::string(CSLFetchNameValueDef(
4686 1 : oIterExistingTarget->second.papszExtra,
4687 2 : "ETag", ""));
4688 : }))
4689 : {
4690 1 : bSkip = true;
4691 : }
4692 1 : if (fpIn)
4693 0 : VSIFCloseL(fpIn);
4694 : }
4695 : else
4696 : {
4697 :
4698 4 : if (eSyncStrategy == SyncStrategy::TIMESTAMP &&
4699 2 : chunk.nMTime < oIterExistingTarget->second.nMTime)
4700 : {
4701 : // The target is more recent than the source.
4702 : // Nothing to do
4703 1 : CPLDebug(GetDebugKey(),
4704 : "%s is older than %s. "
4705 : "Do not replace %s assuming it was used to "
4706 : "upload %s",
4707 : osSubSource.c_str(), osSubTarget.c_str(),
4708 : osSubTarget.c_str(), osSubSource.c_str());
4709 1 : bSkip = true;
4710 : }
4711 : }
4712 : }
4713 :
4714 13 : if (!bSkip)
4715 : {
4716 11 : anIndexToCopy.push_back(iChunk);
4717 11 : nTotalSize += chunk.nTotalSize;
4718 11 : if (chunk.nSize < chunk.nTotalSize)
4719 : {
4720 5 : if (bDownloadFromNetworkToLocal)
4721 : {
4722 : // Suppress target file as we're going to open in wb+
4723 : // mode for parallelized writing
4724 2 : VSIUnlink(osSubTarget.c_str());
4725 : }
4726 3 : else if (bSupportsParallelMultipartUpload)
4727 : {
4728 : auto poS3HandleHelper =
4729 : std::unique_ptr<IVSIS3LikeHandleHelper>(
4730 3 : CreateHandleHelper(osSubTarget.c_str() +
4731 3 : GetFSPrefix().size(),
4732 6 : false));
4733 3 : if (poS3HandleHelper == nullptr)
4734 0 : return false;
4735 :
4736 : std::string osUploadID =
4737 : poTargetFSMultipartHandler->InitiateMultipartUpload(
4738 : osSubTarget, poS3HandleHelper.get(),
4739 : oRetryParameters,
4740 3 : aosObjectCreationOptions.List());
4741 3 : if (osUploadID.empty())
4742 : {
4743 0 : return false;
4744 : }
4745 6 : MultiPartDef def;
4746 3 : def.osUploadID = std::move(osUploadID);
4747 3 : def.nExpectedCount = static_cast<int>(
4748 3 : cpl::div_round_up(chunk.nTotalSize, chunk.nSize));
4749 3 : def.nTotalSize = chunk.nTotalSize;
4750 3 : oMapMultiPartDefs[osSubTarget] = std::move(def);
4751 : }
4752 : else
4753 : {
4754 0 : CPLAssert(false);
4755 : }
4756 :
4757 : // Include all remaining chunks of the same file
4758 16 : while (iChunk + 1 < nChunkCount &&
4759 6 : aoChunksToCopy[iChunk + 1].nStartOffset > 0)
4760 : {
4761 5 : ++iChunk;
4762 5 : anIndexToCopy.push_back(iChunk);
4763 : }
4764 : }
4765 : }
4766 : }
4767 :
4768 20 : const int nThreads = std::min(std::max(1, nRequestedThreads),
4769 10 : static_cast<int>(anIndexToCopy.size()));
4770 10 : if (nThreads <= nMinThreads)
4771 : {
4772 : // Proceed to file copy
4773 4 : bool ret = true;
4774 4 : uint64_t nAccSize = 0;
4775 4 : const uint64_t nTotalSizeDenom = std::max<uint64_t>(1, nTotalSize);
4776 6 : for (const size_t iChunk : anIndexToCopy)
4777 : {
4778 2 : const auto &chunk = aoChunksToCopy[iChunk];
4779 2 : CPLAssert(chunk.nStartOffset == 0);
4780 : const std::string osSubSource(
4781 : CPLFormFilenameSafe(osSourceWithoutSlash.c_str(),
4782 2 : chunk.osSrcFilename.c_str(), nullptr));
4783 : const std::string osSubTarget(CPLFormFilenameSafe(
4784 2 : osTargetDir.c_str(), chunk.osDstFilename.c_str(), nullptr));
4785 4 : void *pScaledProgress = GDALCreateScaledProgress(
4786 2 : double(nAccSize) / nTotalSizeDenom,
4787 2 : double(nAccSize + chunk.nSize) / nTotalSizeDenom,
4788 : pProgressFunc, pProgressData);
4789 2 : ret =
4790 2 : CopyFile(osSubSource.c_str(), osSubTarget.c_str(), nullptr,
4791 2 : chunk.nSize, aosObjectCreationOptions.List(),
4792 2 : GDALScaledProgress, pScaledProgress) == 0;
4793 2 : GDALDestroyScaledProgress(pScaledProgress);
4794 2 : if (!ret)
4795 : {
4796 0 : break;
4797 : }
4798 2 : nAccSize += chunk.nSize;
4799 : }
4800 :
4801 4 : return ret;
4802 : }
4803 : }
4804 : else
4805 : {
4806 23 : std::string osMsg("Copying of ");
4807 23 : osMsg += osSourceWithoutSlash;
4808 :
4809 : VSIStatBufL sTarget;
4810 23 : osTarget = pszTarget;
4811 23 : bool bTargetIsFile = false;
4812 23 : sTarget.st_size = 0;
4813 23 : if (VSIStatL(osTarget.c_str(), &sTarget) == 0)
4814 : {
4815 20 : bTargetIsFile = true;
4816 20 : if (VSI_ISDIR(sTarget.st_mode))
4817 : {
4818 26 : osTarget = CPLFormFilenameSafe(
4819 13 : osTarget.c_str(), CPLGetFilename(pszSource), nullptr);
4820 23 : bTargetIsFile = VSIStatL(osTarget.c_str(), &sTarget) == 0 &&
4821 10 : !CPL_TO_BOOL(VSI_ISDIR(sTarget.st_mode));
4822 : }
4823 : }
4824 :
4825 23 : if (eSyncStrategy == SyncStrategy::TIMESTAMP && bTargetIsFile &&
4826 8 : !bDownloadFromNetworkToLocal && !bUploadFromLocalToNetwork &&
4827 3 : sSource.st_size == sTarget.st_size &&
4828 3 : sSource.st_mtime < sTarget.st_mtime)
4829 : {
4830 : // The target is more recent than the source. Nothing to do
4831 1 : CPLDebug(GetDebugKey(),
4832 : "%s is older than %s. "
4833 : "Do not replace %s assuming it was used to "
4834 : "upload %s",
4835 : osSource.c_str(), osTarget.c_str(), osTarget.c_str(),
4836 : osSource.c_str());
4837 1 : if (pProgressFunc)
4838 : {
4839 0 : pProgressFunc(1.0, osMsg.c_str(), pProgressData);
4840 : }
4841 1 : return true;
4842 : }
4843 :
4844 : // Download from network to local file system ?
4845 22 : if (bTargetIsFile && bDownloadFromNetworkToLocal &&
4846 8 : sSource.st_size == sTarget.st_size)
4847 : {
4848 16 : if (CanSkipDownloadFromNetworkToLocal(
4849 : osSourceWithoutSlash.c_str(), osTarget.c_str(),
4850 8 : sSource.st_mtime, sTarget.st_mtime,
4851 8 : [this](const char *pszFilename) -> std::string
4852 : {
4853 8 : FileProp cachedFileProp;
4854 4 : if (GetCachedFileProp(
4855 8 : GetURLFromFilename(pszFilename).c_str(),
4856 : cachedFileProp))
4857 : {
4858 4 : return cachedFileProp.ETag;
4859 : }
4860 0 : return std::string();
4861 : }))
4862 : {
4863 4 : if (pProgressFunc)
4864 : {
4865 0 : pProgressFunc(1.0, osMsg.c_str(), pProgressData);
4866 : }
4867 4 : return true;
4868 : }
4869 : }
4870 :
4871 18 : VSILFILE *fpIn = nullptr;
4872 :
4873 : // Upload from local file system to network ?
4874 18 : if (bUploadFromLocalToNetwork && sSource.st_size == sTarget.st_size)
4875 : {
4876 12 : if (CanSkipUploadFromLocalToNetwork(
4877 : fpIn, osSourceWithoutSlash.c_str(), osTarget.c_str(),
4878 6 : sSource.st_mtime, sTarget.st_mtime,
4879 6 : [this](const char *pszFilename) -> std::string
4880 : {
4881 6 : FileProp cachedFileProp;
4882 3 : if (GetCachedFileProp(
4883 6 : GetURLFromFilename(pszFilename).c_str(),
4884 : cachedFileProp))
4885 : {
4886 3 : return cachedFileProp.ETag;
4887 : }
4888 0 : return std::string();
4889 : }))
4890 : {
4891 4 : if (pProgressFunc)
4892 : {
4893 0 : pProgressFunc(1.0, osMsg.c_str(), pProgressData);
4894 : }
4895 4 : return true;
4896 : }
4897 : }
4898 :
4899 : // Split file in possibly multiple chunks
4900 : const vsi_l_offset nChunksLarge =
4901 : nMaxChunkSize == 0
4902 16 : ? 1
4903 2 : : cpl::div_round_up(sSource.st_size, nMaxChunkSize);
4904 14 : if (nChunksLarge >
4905 : 1000) // must also be below knMAX_PART_NUMBER for upload
4906 : {
4907 0 : CPLError(CE_Failure, CPLE_AppDefined,
4908 : "Too small CHUNK_SIZE w.r.t file size");
4909 0 : return false;
4910 : }
4911 14 : ChunkToCopy chunk;
4912 14 : chunk.nMTime = sSource.st_mtime;
4913 14 : chunk.nTotalSize = sSource.st_size;
4914 14 : nTotalSize = chunk.nTotalSize;
4915 14 : const size_t nChunks = static_cast<size_t>(nChunksLarge);
4916 30 : for (size_t iChunk = 0; iChunk < nChunks; iChunk++)
4917 : {
4918 16 : chunk.nStartOffset = iChunk * nMaxChunkSize;
4919 16 : chunk.nSize =
4920 : nChunks == 1
4921 20 : ? sSource.st_size
4922 20 : : std::min(sSource.st_size - chunk.nStartOffset,
4923 4 : static_cast<vsi_l_offset>(nMaxChunkSize));
4924 16 : aoChunksToCopy.push_back(chunk);
4925 16 : anIndexToCopy.push_back(iChunk);
4926 :
4927 16 : if (nChunks > 1)
4928 : {
4929 4 : if (iChunk == 0)
4930 : {
4931 2 : if (bDownloadFromNetworkToLocal)
4932 : {
4933 : // Suppress target file as we're going to open in wb+
4934 : // mode for parallelized writing
4935 0 : VSIUnlink(osTarget.c_str());
4936 : }
4937 2 : else if (bSupportsParallelMultipartUpload)
4938 : {
4939 : auto poS3HandleHelper =
4940 : std::unique_ptr<IVSIS3LikeHandleHelper>(
4941 2 : CreateHandleHelper(osTarget.c_str() +
4942 2 : GetFSPrefix().size(),
4943 4 : false));
4944 2 : if (poS3HandleHelper == nullptr)
4945 0 : return false;
4946 :
4947 2 : MultiPartDef def;
4948 : def.osUploadID =
4949 4 : poTargetFSMultipartHandler->InitiateMultipartUpload(
4950 : osTarget, poS3HandleHelper.get(),
4951 : oRetryParameters,
4952 4 : aosObjectCreationOptions.List());
4953 2 : if (def.osUploadID.empty())
4954 : {
4955 0 : return false;
4956 : }
4957 2 : def.nExpectedCount = static_cast<int>(
4958 2 : cpl::div_round_up(chunk.nTotalSize, chunk.nSize));
4959 2 : def.nTotalSize = chunk.nTotalSize;
4960 2 : oMapMultiPartDefs[osTarget] = std::move(def);
4961 : }
4962 : else
4963 : {
4964 0 : CPLAssert(false);
4965 : }
4966 : }
4967 : }
4968 : }
4969 :
4970 28 : const int nThreads = std::min(std::max(1, nRequestedThreads),
4971 14 : static_cast<int>(anIndexToCopy.size()));
4972 14 : if (nThreads <= nMinThreads)
4973 : {
4974 : bool bRet =
4975 12 : CopyFile(osSourceWithoutSlash.c_str(), osTarget.c_str(), fpIn,
4976 12 : sSource.st_size, aosObjectCreationOptions.List(),
4977 12 : pProgressFunc, pProgressData) == 0;
4978 12 : if (fpIn)
4979 : {
4980 0 : VSIFCloseL(fpIn);
4981 : }
4982 12 : return bRet;
4983 : }
4984 2 : if (fpIn)
4985 : {
4986 0 : VSIFCloseL(fpIn);
4987 : }
4988 : }
4989 :
4990 16 : const int nThreads = std::min(std::max(1, nRequestedThreads),
4991 8 : static_cast<int>(anIndexToCopy.size()));
4992 :
4993 : struct JobQueue
4994 : {
4995 : IVSIS3LikeFSHandler *poFS;
4996 : IVSIS3LikeFSHandlerWithMultipartUpload *poTargetFSMultipartHandler;
4997 : const std::vector<ChunkToCopy> &aoChunksToCopy;
4998 : const std::vector<size_t> &anIndexToCopy;
4999 : std::map<std::string, MultiPartDef> &oMapMultiPartDefs;
5000 : volatile int iCurIdx = 0;
5001 : volatile bool ret = true;
5002 : volatile bool stop = false;
5003 : std::string osSourceDir{};
5004 : std::string osTargetDir{};
5005 : std::string osSource{};
5006 : std::string osTarget{};
5007 : std::mutex sMutex{};
5008 : uint64_t nTotalCopied = 0;
5009 : bool bSupportsParallelMultipartUpload = false;
5010 : size_t nMaxChunkSize = 0;
5011 : const CPLHTTPRetryParameters &oRetryParameters;
5012 : const CPLStringList &aosObjectCreationOptions;
5013 :
5014 8 : JobQueue(IVSIS3LikeFSHandler *poFSIn,
5015 : IVSIS3LikeFSHandlerWithMultipartUpload
5016 : *poTargetFSMultipartHandlerIn,
5017 : const std::vector<ChunkToCopy> &aoChunksToCopyIn,
5018 : const std::vector<size_t> &anIndexToCopyIn,
5019 : std::map<std::string, MultiPartDef> &oMapMultiPartDefsIn,
5020 : const std::string &osSourceDirIn,
5021 : const std::string &osTargetDirIn,
5022 : const std::string &osSourceIn, const std::string &osTargetIn,
5023 : bool bSupportsParallelMultipartUploadIn,
5024 : size_t nMaxChunkSizeIn,
5025 : const CPLHTTPRetryParameters &oRetryParametersIn,
5026 : const CPLStringList &aosObjectCreationOptionsIn)
5027 8 : : poFS(poFSIn),
5028 : poTargetFSMultipartHandler(poTargetFSMultipartHandlerIn),
5029 : aoChunksToCopy(aoChunksToCopyIn), anIndexToCopy(anIndexToCopyIn),
5030 : oMapMultiPartDefs(oMapMultiPartDefsIn),
5031 : osSourceDir(osSourceDirIn), osTargetDir(osTargetDirIn),
5032 : osSource(osSourceIn), osTarget(osTargetIn),
5033 : bSupportsParallelMultipartUpload(
5034 : bSupportsParallelMultipartUploadIn),
5035 : nMaxChunkSize(nMaxChunkSizeIn),
5036 : oRetryParameters(oRetryParametersIn),
5037 8 : aosObjectCreationOptions(aosObjectCreationOptionsIn)
5038 : {
5039 8 : }
5040 :
5041 : JobQueue(const JobQueue &) = delete;
5042 : JobQueue &operator=(const JobQueue &) = delete;
5043 : };
5044 :
5045 11 : const auto threadFunc = [](void *pDataIn)
5046 : {
5047 : struct ProgressData
5048 : {
5049 : uint64_t nFileSize;
5050 : double dfLastPct;
5051 : JobQueue *queue;
5052 :
5053 17 : static int CPL_STDCALL progressFunc(double pct, const char *,
5054 : void *pProgressDataIn)
5055 : {
5056 17 : ProgressData *pProgress =
5057 : static_cast<ProgressData *>(pProgressDataIn);
5058 17 : const auto nInc = static_cast<uint64_t>(
5059 17 : (pct - pProgress->dfLastPct) * pProgress->nFileSize + 0.5);
5060 17 : pProgress->queue->sMutex.lock();
5061 17 : pProgress->queue->nTotalCopied += nInc;
5062 17 : pProgress->queue->sMutex.unlock();
5063 17 : pProgress->dfLastPct = pct;
5064 17 : return TRUE;
5065 : }
5066 : };
5067 :
5068 11 : JobQueue *queue = static_cast<JobQueue *>(pDataIn);
5069 29 : while (!queue->stop)
5070 : {
5071 25 : const int idx = CPLAtomicInc(&(queue->iCurIdx)) - 1;
5072 25 : if (static_cast<size_t>(idx) >= queue->anIndexToCopy.size())
5073 : {
5074 7 : queue->stop = true;
5075 7 : break;
5076 : }
5077 : const auto &chunk =
5078 18 : queue->aoChunksToCopy[queue->anIndexToCopy[idx]];
5079 : const std::string osSubSource(
5080 18 : queue->osTargetDir.empty()
5081 4 : ? queue->osSource
5082 : : CPLFormFilenameSafe(queue->osSourceDir.c_str(),
5083 : chunk.osSrcFilename.c_str(),
5084 36 : nullptr));
5085 : const std::string osSubTarget(
5086 18 : queue->osTargetDir.empty()
5087 4 : ? queue->osTarget
5088 : : CPLFormFilenameSafe(queue->osTargetDir.c_str(),
5089 : chunk.osDstFilename.c_str(),
5090 36 : nullptr));
5091 :
5092 : ProgressData progressData;
5093 18 : progressData.nFileSize = chunk.nSize;
5094 18 : progressData.dfLastPct = 0;
5095 18 : progressData.queue = queue;
5096 18 : if (chunk.nSize < chunk.nTotalSize)
5097 : {
5098 14 : const size_t nSizeToRead = static_cast<size_t>(chunk.nSize);
5099 14 : bool bSuccess = false;
5100 14 : if (queue->bSupportsParallelMultipartUpload)
5101 : {
5102 : const auto iter =
5103 10 : queue->oMapMultiPartDefs.find(osSubTarget);
5104 10 : CPLAssert(iter != queue->oMapMultiPartDefs.end());
5105 :
5106 10 : VSILFILE *fpIn = VSIFOpenL(osSubSource.c_str(), "rb");
5107 10 : void *pBuffer = VSI_MALLOC_VERBOSE(nSizeToRead);
5108 : auto poS3HandleHelper =
5109 : std::unique_ptr<IVSIS3LikeHandleHelper>(
5110 10 : queue->poFS->CreateHandleHelper(
5111 10 : osSubTarget.c_str() +
5112 10 : queue->poFS->GetFSPrefix().size(),
5113 30 : false));
5114 10 : if (fpIn && pBuffer && poS3HandleHelper &&
5115 30 : VSIFSeekL(fpIn, chunk.nStartOffset, SEEK_SET) == 0 &&
5116 10 : VSIFReadL(pBuffer, 1, nSizeToRead, fpIn) == nSizeToRead)
5117 : {
5118 10 : const int nPartNumber =
5119 20 : 1 + (queue->nMaxChunkSize == 0
5120 10 : ? 0 /* shouldn't happen */
5121 10 : : static_cast<int>(chunk.nStartOffset /
5122 10 : queue->nMaxChunkSize));
5123 : std::string osEtag =
5124 10 : queue->poTargetFSMultipartHandler->UploadPart(
5125 : osSubTarget, nPartNumber,
5126 10 : iter->second.osUploadID, chunk.nStartOffset,
5127 : pBuffer, nSizeToRead, poS3HandleHelper.get(),
5128 : queue->oRetryParameters,
5129 30 : queue->aosObjectCreationOptions.List());
5130 10 : if (!osEtag.empty())
5131 : {
5132 9 : std::lock_guard<std::mutex> lock(queue->sMutex);
5133 9 : iter->second.nCountValidETags++;
5134 9 : iter->second.aosEtags.resize(
5135 9 : std::max(nPartNumber,
5136 0 : static_cast<int>(
5137 9 : iter->second.aosEtags.size())));
5138 9 : iter->second.aosEtags[nPartNumber - 1] =
5139 18 : std::move(osEtag);
5140 9 : bSuccess = true;
5141 : }
5142 : }
5143 10 : if (fpIn)
5144 10 : VSIFCloseL(fpIn);
5145 10 : VSIFree(pBuffer);
5146 : }
5147 : else
5148 : {
5149 : bSuccess =
5150 4 : CopyChunk(osSubSource.c_str(), osSubTarget.c_str(),
5151 4 : chunk.nStartOffset, nSizeToRead);
5152 : }
5153 14 : if (bSuccess)
5154 : {
5155 13 : ProgressData::progressFunc(1.0, "", &progressData);
5156 : }
5157 : else
5158 : {
5159 1 : queue->ret = false;
5160 1 : queue->stop = true;
5161 : }
5162 : }
5163 : else
5164 : {
5165 4 : CPLAssert(chunk.nStartOffset == 0);
5166 4 : if (queue->poFS->CopyFile(
5167 : osSubSource.c_str(), osSubTarget.c_str(), nullptr,
5168 4 : chunk.nTotalSize,
5169 4 : queue->aosObjectCreationOptions.List(),
5170 8 : ProgressData::progressFunc, &progressData) != 0)
5171 : {
5172 0 : queue->ret = false;
5173 0 : queue->stop = true;
5174 : }
5175 : }
5176 : }
5177 11 : };
5178 :
5179 : JobQueue sJobQueue(this, poTargetFSMultipartHandler, aoChunksToCopy,
5180 : anIndexToCopy, oMapMultiPartDefs, osSourceWithoutSlash,
5181 : osTargetDir, osSourceWithoutSlash, osTarget,
5182 : bSupportsParallelMultipartUpload, nMaxChunkSize,
5183 8 : oRetryParameters, aosObjectCreationOptions);
5184 :
5185 8 : if (CPLTestBool(CPLGetConfigOption("VSIS3_SYNC_MULTITHREADING", "YES")))
5186 : {
5187 14 : std::vector<CPLJoinableThread *> ahThreads;
5188 17 : for (int i = 0; i < nThreads; i++)
5189 : {
5190 10 : auto hThread = CPLCreateJoinableThread(threadFunc, &sJobQueue);
5191 10 : if (!hThread)
5192 : {
5193 0 : sJobQueue.ret = false;
5194 0 : sJobQueue.stop = true;
5195 0 : break;
5196 : }
5197 10 : ahThreads.push_back(hThread);
5198 : }
5199 7 : if (pProgressFunc)
5200 : {
5201 5 : const uint64_t nTotalSizeDenom = std::max<uint64_t>(1, nTotalSize);
5202 14 : while (!sJobQueue.stop)
5203 : {
5204 9 : CPLSleep(0.1);
5205 9 : sJobQueue.sMutex.lock();
5206 9 : const auto nTotalCopied = sJobQueue.nTotalCopied;
5207 9 : sJobQueue.sMutex.unlock();
5208 9 : if (!pProgressFunc(double(nTotalCopied) / nTotalSizeDenom, "",
5209 : pProgressData))
5210 : {
5211 0 : sJobQueue.ret = false;
5212 0 : sJobQueue.stop = true;
5213 : }
5214 : }
5215 5 : if (sJobQueue.ret)
5216 : {
5217 5 : pProgressFunc(1.0, "", pProgressData);
5218 : }
5219 : }
5220 17 : for (auto hThread : ahThreads)
5221 : {
5222 10 : CPLJoinThread(hThread);
5223 : }
5224 : }
5225 : else
5226 : {
5227 : // Only for simulation case
5228 1 : threadFunc(&sJobQueue);
5229 : }
5230 :
5231 : // Finalize multipart uploads
5232 8 : if (sJobQueue.ret && bSupportsParallelMultipartUpload)
5233 : {
5234 8 : std::set<std::string> oSetKeysToRemove;
5235 8 : for (const auto &kv : oMapMultiPartDefs)
5236 : {
5237 : auto poS3HandleHelper =
5238 : std::unique_ptr<IVSIS3LikeHandleHelper>(CreateHandleHelper(
5239 8 : kv.first.c_str() + GetFSPrefix().size(), false));
5240 4 : sJobQueue.ret = false;
5241 4 : if (poS3HandleHelper)
5242 : {
5243 4 : CPLAssert(kv.second.nCountValidETags ==
5244 : kv.second.nExpectedCount);
5245 4 : if (poTargetFSMultipartHandler->CompleteMultipart(
5246 4 : kv.first, kv.second.osUploadID, kv.second.aosEtags,
5247 4 : kv.second.nTotalSize, poS3HandleHelper.get(),
5248 4 : oRetryParameters))
5249 : {
5250 4 : sJobQueue.ret = true;
5251 4 : oSetKeysToRemove.insert(kv.first);
5252 :
5253 4 : InvalidateCachedData(poS3HandleHelper->GetURL().c_str());
5254 4 : InvalidateDirContent(CPLGetDirnameSafe(kv.first.c_str()));
5255 : }
5256 : }
5257 : }
5258 8 : for (const auto &key : oSetKeysToRemove)
5259 : {
5260 4 : oMapMultiPartDefs.erase(key);
5261 : }
5262 : }
5263 :
5264 8 : return sJobQueue.ret;
5265 : }
5266 :
5267 : /************************************************************************/
5268 : /* MultipartUploadGetCapabilities() */
5269 : /************************************************************************/
5270 :
5271 5 : bool IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadGetCapabilities(
5272 : int *pbNonSequentialUploadSupported, int *pbParallelUploadSupported,
5273 : int *pbAbortSupported, size_t *pnMinPartSize, size_t *pnMaxPartSize,
5274 : int *pnMaxPartCount)
5275 : {
5276 5 : if (pbNonSequentialUploadSupported)
5277 5 : *pbNonSequentialUploadSupported =
5278 5 : SupportsNonSequentialMultipartUpload();
5279 5 : if (pbParallelUploadSupported)
5280 5 : *pbParallelUploadSupported = SupportsParallelMultipartUpload();
5281 5 : if (pbAbortSupported)
5282 5 : *pbAbortSupported = SupportsMultipartAbort();
5283 5 : if (pnMinPartSize)
5284 5 : *pnMinPartSize = GetMinimumPartSizeInMiB();
5285 5 : if (pnMaxPartSize)
5286 5 : *pnMaxPartSize = GetMaximumPartSizeInMiB();
5287 5 : if (pnMaxPartCount)
5288 5 : *pnMaxPartCount = GetMaximumPartCount();
5289 5 : return true;
5290 : }
5291 :
5292 : /************************************************************************/
5293 : /* MultipartUploadStart() */
5294 : /************************************************************************/
5295 :
5296 3 : char *IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadStart(
5297 : const char *pszFilename, CSLConstList papszOptions)
5298 : {
5299 3 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
5300 0 : return nullptr;
5301 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
5302 6 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
5303 3 : if (poHandleHelper == nullptr)
5304 1 : return nullptr;
5305 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
5306 4 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
5307 :
5308 : const std::string osRet = InitiateMultipartUpload(
5309 6 : pszFilename, poHandleHelper.get(), oRetryParameters, papszOptions);
5310 2 : if (osRet.empty())
5311 1 : return nullptr;
5312 1 : return CPLStrdup(osRet.c_str());
5313 : }
5314 :
5315 : /************************************************************************/
5316 : /* MultipartUploadAddPart() */
5317 : /************************************************************************/
5318 :
5319 4 : char *IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadAddPart(
5320 : const char *pszFilename, const char *pszUploadId, int nPartNumber,
5321 : vsi_l_offset nFileOffset, const void *pData, size_t nDataLength,
5322 : CSLConstList papszOptions)
5323 : {
5324 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
5325 0 : return nullptr;
5326 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
5327 8 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
5328 4 : if (poHandleHelper == nullptr)
5329 1 : return nullptr;
5330 6 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
5331 6 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
5332 :
5333 : const std::string osRet = UploadPart(
5334 : pszFilename, nPartNumber, pszUploadId, nFileOffset, pData, nDataLength,
5335 9 : poHandleHelper.get(), oRetryParameters, papszOptions);
5336 3 : if (osRet.empty())
5337 1 : return nullptr;
5338 2 : return CPLStrdup(osRet.c_str());
5339 : }
5340 :
5341 : /************************************************************************/
5342 : /* MultipartUploadEnd() */
5343 : /************************************************************************/
5344 :
5345 4 : bool IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadEnd(
5346 : const char *pszFilename, const char *pszUploadId, size_t nPartIdsCount,
5347 : const char *const *apszPartIds, vsi_l_offset nTotalSize, CSLConstList)
5348 : {
5349 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
5350 0 : return false;
5351 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
5352 8 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
5353 4 : if (poHandleHelper == nullptr)
5354 1 : return false;
5355 6 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
5356 6 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
5357 :
5358 3 : std::vector<std::string> aosTags;
5359 6 : for (size_t i = 0; i < nPartIdsCount; ++i)
5360 3 : aosTags.emplace_back(apszPartIds[i]);
5361 3 : return CompleteMultipart(pszFilename, pszUploadId, aosTags, nTotalSize,
5362 3 : poHandleHelper.get(), oRetryParameters);
5363 : }
5364 :
5365 : /************************************************************************/
5366 : /* MultipartUploadAbort() */
5367 : /************************************************************************/
5368 :
5369 3 : bool IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadAbort(
5370 : const char *pszFilename, const char *pszUploadId, CSLConstList)
5371 : {
5372 3 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
5373 0 : return false;
5374 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
5375 6 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
5376 3 : if (poHandleHelper == nullptr)
5377 1 : return false;
5378 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
5379 2 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
5380 2 : return AbortMultipart(pszFilename, pszUploadId, poHandleHelper.get(),
5381 2 : oRetryParameters);
5382 : }
5383 :
5384 : /************************************************************************/
5385 : /* VSIS3Handle() */
5386 : /************************************************************************/
5387 :
5388 181 : VSIS3Handle::VSIS3Handle(VSIS3FSHandler *poFSIn, const char *pszFilename,
5389 181 : VSIS3HandleHelper *poS3HandleHelper)
5390 : : IVSIS3LikeHandle(poFSIn, pszFilename,
5391 181 : poS3HandleHelper->GetURLNoKVP().c_str()),
5392 181 : m_poS3HandleHelper(poS3HandleHelper)
5393 : {
5394 181 : }
5395 :
5396 : /************************************************************************/
5397 : /* ~VSIS3Handle() */
5398 : /************************************************************************/
5399 :
5400 362 : VSIS3Handle::~VSIS3Handle()
5401 : {
5402 181 : delete m_poS3HandleHelper;
5403 362 : }
5404 :
5405 : /************************************************************************/
5406 : /* GetCurlHeaders() */
5407 : /************************************************************************/
5408 :
5409 152 : struct curl_slist *VSIS3Handle::GetCurlHeaders(const std::string &osVerb,
5410 : struct curl_slist *psHeaders)
5411 : {
5412 152 : return m_poS3HandleHelper->GetCurlHeaders(osVerb, psHeaders);
5413 : }
5414 :
5415 : /************************************************************************/
5416 : /* CanRestartOnError() */
5417 : /************************************************************************/
5418 :
5419 21 : bool VSIS3Handle::CanRestartOnError(const char *pszErrorMsg,
5420 : const char *pszHeaders, bool bSetError)
5421 : {
5422 21 : if (m_poS3HandleHelper->CanRestartOnError(pszErrorMsg, pszHeaders,
5423 : bSetError))
5424 : {
5425 9 : SetURL(m_poS3HandleHelper->GetURL().c_str());
5426 9 : return true;
5427 : }
5428 12 : return false;
5429 : }
5430 :
5431 : } /* end of namespace cpl */
5432 :
5433 : #endif // DOXYGEN_SKIP
5434 : //! @endcond
5435 :
5436 : /************************************************************************/
5437 : /* VSIInstallS3FileHandler() */
5438 : /************************************************************************/
5439 :
5440 : /*!
5441 : \brief Install /vsis3/ Amazon S3 file system handler (requires libcurl)
5442 :
5443 : \verbatim embed:rst
5444 : See :ref:`/vsis3/ documentation <vsis3>`
5445 : \endverbatim
5446 :
5447 : */
5448 1805 : void VSIInstallS3FileHandler(void)
5449 : {
5450 1805 : VSIFileManager::InstallHandler(
5451 3610 : "/vsis3/", std::make_shared<cpl::VSIS3FSHandler>("/vsis3/"));
5452 1805 : }
5453 :
5454 : #endif /* HAVE_CURL */
|