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