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