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