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