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