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