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