Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CPL - Common Portability Library
4 : * Purpose: Implement VSI large file api for Microsoft Azure Blob Storage
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2017-2018, Even Rouault <even.rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "cpl_http.h"
15 : #include "cpl_minixml.h"
16 : #include "cpl_time.h"
17 : #include "cpl_vsil_curl_priv.h"
18 : #include "cpl_vsil_curl_class.h"
19 :
20 : #include <errno.h>
21 :
22 : #include <algorithm>
23 : #include <set>
24 : #include <map>
25 : #include <memory>
26 :
27 : #include "cpl_azure.h"
28 :
29 : // #define DEBUG_VERBOSE 1
30 :
31 : #ifndef HAVE_CURL
32 :
33 : void VSIInstallAzureFileHandler(void)
34 : {
35 : // Not supported
36 : }
37 :
38 : #else
39 :
40 : //! @cond Doxygen_Suppress
41 : #ifndef DOXYGEN_SKIP
42 :
43 : #define ENABLE_DEBUG 0
44 :
45 : #define unchecked_curl_easy_setopt(handle, opt, param) \
46 : CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
47 :
48 : namespace cpl
49 : {
50 :
51 : const char GDAL_MARKER_FOR_DIR[] = ".gdal_marker_for_dir";
52 :
53 : /************************************************************************/
54 : /* VSIDIRAz */
55 : /************************************************************************/
56 :
57 : struct VSIDIRAz : public VSIDIRWithMissingDirSynthesis
58 : {
59 : int nRecurseDepth = 0;
60 :
61 : std::string osNextMarker{};
62 : int nPos = 0;
63 :
64 : std::string osBucket{};
65 : std::string osObjectKey{};
66 : IVSIS3LikeFSHandler *poFS = nullptr;
67 : std::unique_ptr<IVSIS3LikeHandleHelper> poHandleHelper{};
68 : int nMaxFiles = 0;
69 : bool bCacheEntries = true;
70 : bool m_bSynthetizeMissingDirectories = false;
71 : std::string m_osFilterPrefix{};
72 :
73 40 : explicit VSIDIRAz(IVSIS3LikeFSHandler *poFSIn) : poFS(poFSIn)
74 : {
75 40 : }
76 :
77 : VSIDIRAz(const VSIDIRAz &) = delete;
78 : VSIDIRAz &operator=(const VSIDIRAz &) = delete;
79 :
80 : const VSIDIREntry *NextDirEntry() override;
81 :
82 : bool IssueListDir();
83 : bool AnalyseAzureFileList(const std::string &osBaseURL, const char *pszXML);
84 : void clear();
85 : };
86 :
87 : /************************************************************************/
88 : /* clear() */
89 : /************************************************************************/
90 :
91 53 : void VSIDIRAz::clear()
92 : {
93 53 : osNextMarker.clear();
94 53 : nPos = 0;
95 53 : aoEntries.clear();
96 53 : }
97 :
98 : /************************************************************************/
99 : /* AnalyseAzureFileList() */
100 : /************************************************************************/
101 :
102 43 : bool VSIDIRAz::AnalyseAzureFileList(const std::string &osBaseURL,
103 : const char *pszXML)
104 : {
105 : #if DEBUG_VERBOSE
106 : CPLDebug("AZURE", "%s", pszXML);
107 : #endif
108 :
109 43 : CPLXMLNode *psTree = CPLParseXMLString(pszXML);
110 43 : if (psTree == nullptr)
111 0 : return false;
112 : CPLXMLNode *psEnumerationResults =
113 43 : CPLGetXMLNode(psTree, "=EnumerationResults");
114 :
115 43 : bool bOK = false;
116 43 : if (psEnumerationResults)
117 : {
118 86 : CPLString osPrefix = CPLGetXMLValue(psEnumerationResults, "Prefix", "");
119 43 : if (osPrefix.empty())
120 : {
121 : // in the case of an empty bucket
122 11 : bOK = true;
123 : }
124 32 : else if (osPrefix.endsWith(m_osFilterPrefix))
125 : {
126 32 : osPrefix.resize(osPrefix.size() - m_osFilterPrefix.size());
127 : }
128 :
129 43 : CPLXMLNode *psBlobs = CPLGetXMLNode(psEnumerationResults, "Blobs");
130 43 : if (psBlobs == nullptr)
131 : {
132 6 : psBlobs = CPLGetXMLNode(psEnumerationResults, "Containers");
133 6 : if (psBlobs != nullptr)
134 6 : bOK = true;
135 : }
136 :
137 86 : std::string GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH("/");
138 43 : GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH += GDAL_MARKER_FOR_DIR;
139 :
140 : // Count the number of occurrences of a path. Can be 1 or 2. 2 in the
141 : // case that both a filename and directory exist
142 86 : std::map<std::string, int> aoNameCount;
143 43 : for (CPLXMLNode *psIter = psBlobs ? psBlobs->psChild : nullptr;
144 75 : psIter != nullptr; psIter = psIter->psNext)
145 : {
146 32 : if (psIter->eType != CXT_Element)
147 0 : continue;
148 32 : if (strcmp(psIter->pszValue, "Blob") == 0)
149 : {
150 25 : const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
151 25 : if (pszKey && strstr(pszKey, GDAL_MARKER_FOR_DIR) != nullptr)
152 : {
153 9 : bOK = true;
154 9 : if (nRecurseDepth < 0)
155 : {
156 3 : if (strcmp(pszKey + osPrefix.size(),
157 3 : GDAL_MARKER_FOR_DIR) == 0)
158 1 : continue;
159 2 : char *pszName = CPLStrdup(pszKey + osPrefix.size());
160 2 : char *pszMarker = strstr(
161 : pszName,
162 : GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH.c_str());
163 2 : if (pszMarker)
164 2 : *pszMarker = '\0';
165 2 : aoNameCount[pszName]++;
166 2 : CPLFree(pszName);
167 8 : }
168 : }
169 16 : else if (pszKey && strlen(pszKey) > osPrefix.size())
170 : {
171 16 : bOK = true;
172 16 : aoNameCount[pszKey + osPrefix.size()]++;
173 : }
174 : }
175 7 : else if (strcmp(psIter->pszValue, "BlobPrefix") == 0 ||
176 4 : strcmp(psIter->pszValue, "Container") == 0)
177 : {
178 7 : bOK = true;
179 :
180 7 : const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
181 14 : if (pszKey &&
182 7 : strncmp(pszKey, osPrefix.c_str(), osPrefix.size()) == 0)
183 : {
184 14 : std::string osKey = pszKey;
185 7 : if (!osKey.empty() && osKey.back() == '/')
186 2 : osKey.pop_back();
187 7 : if (osKey.size() > osPrefix.size())
188 : {
189 7 : aoNameCount[osKey.c_str() + osPrefix.size()]++;
190 : }
191 : }
192 : }
193 : }
194 :
195 43 : for (CPLXMLNode *psIter = psBlobs ? psBlobs->psChild : nullptr;
196 75 : psIter != nullptr; psIter = psIter->psNext)
197 : {
198 32 : if (psIter->eType != CXT_Element)
199 0 : continue;
200 32 : if (strcmp(psIter->pszValue, "Blob") == 0)
201 : {
202 25 : const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
203 25 : if (pszKey && strstr(pszKey, GDAL_MARKER_FOR_DIR) != nullptr)
204 : {
205 9 : if (nRecurseDepth < 0)
206 : {
207 3 : if (strcmp(pszKey + osPrefix.size(),
208 3 : GDAL_MARKER_FOR_DIR) == 0)
209 1 : continue;
210 2 : aoEntries.push_back(
211 4 : std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
212 2 : auto &entry = aoEntries.back();
213 2 : entry->pszName = CPLStrdup(pszKey + osPrefix.size());
214 2 : char *pszMarker = strstr(
215 2 : entry->pszName,
216 : GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH.c_str());
217 2 : if (pszMarker)
218 2 : *pszMarker = '\0';
219 2 : entry->nMode = S_IFDIR;
220 2 : entry->bModeKnown = true;
221 8 : }
222 : }
223 16 : else if (pszKey && strlen(pszKey) > osPrefix.size())
224 : {
225 32 : const std::string osKeySuffix = pszKey + osPrefix.size();
226 16 : if (m_bSynthetizeMissingDirectories)
227 : {
228 0 : const auto nLastSlashPos = osKeySuffix.rfind('/');
229 0 : if (nLastSlashPos != std::string::npos &&
230 0 : (m_aosSubpathsStack.empty() ||
231 0 : osKeySuffix.compare(0, nLastSlashPos,
232 0 : m_aosSubpathsStack.back()) !=
233 : 0))
234 : {
235 : const bool bAddEntryForThisSubdir =
236 0 : nLastSlashPos != osKeySuffix.size() - 1;
237 0 : SynthetizeMissingDirectories(
238 0 : osKeySuffix.substr(0, nLastSlashPos),
239 : bAddEntryForThisSubdir);
240 : }
241 : }
242 :
243 16 : aoEntries.push_back(
244 32 : std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
245 16 : auto &entry = aoEntries.back();
246 16 : entry->pszName = CPLStrdup(osKeySuffix.c_str());
247 32 : entry->nSize =
248 16 : static_cast<GUIntBig>(CPLAtoGIntBig(CPLGetXMLValue(
249 : psIter, "Properties.Content-Length", "0")));
250 16 : entry->bSizeKnown = true;
251 16 : entry->nMode = S_IFREG;
252 16 : entry->bModeKnown = true;
253 :
254 32 : std::string ETag = CPLGetXMLValue(psIter, "Etag", "");
255 16 : if (!ETag.empty())
256 : {
257 0 : entry->papszExtra = CSLSetNameValue(
258 0 : entry->papszExtra, "ETag", ETag.c_str());
259 : }
260 :
261 : int nYear, nMonth, nDay, nHour, nMinute, nSecond;
262 16 : if (CPLParseRFC822DateTime(
263 : CPLGetXMLValue(psIter, "Properties.Last-Modified",
264 : ""),
265 : &nYear, &nMonth, &nDay, &nHour, &nMinute, &nSecond,
266 16 : nullptr, nullptr))
267 : {
268 : struct tm brokendowntime;
269 13 : brokendowntime.tm_year = nYear - 1900;
270 13 : brokendowntime.tm_mon = nMonth - 1;
271 13 : brokendowntime.tm_mday = nDay;
272 13 : brokendowntime.tm_hour = nHour;
273 13 : brokendowntime.tm_min = nMinute;
274 13 : brokendowntime.tm_sec = nSecond < 0 ? 0 : nSecond;
275 13 : entry->nMTime = CPLYMDHMSToUnixTime(&brokendowntime);
276 13 : entry->bMTimeKnown = true;
277 : }
278 :
279 16 : if (bCacheEntries)
280 : {
281 28 : FileProp prop;
282 14 : prop.eExists = EXIST_YES;
283 14 : prop.bHasComputedFileSize = true;
284 14 : prop.fileSize = entry->nSize;
285 14 : prop.bIsDirectory = false;
286 14 : prop.mTime = static_cast<time_t>(entry->nMTime);
287 14 : prop.ETag = std::move(ETag);
288 14 : prop.nMode = entry->nMode;
289 :
290 : std::string osCachedFilename =
291 28 : osBaseURL + "/" + CPLAWSURLEncode(osPrefix, false) +
292 56 : CPLAWSURLEncode(entry->pszName, false);
293 : #if DEBUG_VERBOSE
294 : CPLDebug("AZURE", "Cache %s", osCachedFilename.c_str());
295 : #endif
296 14 : poFS->SetCachedFileProp(osCachedFilename.c_str(), prop);
297 : }
298 : }
299 : }
300 7 : else if (strcmp(psIter->pszValue, "BlobPrefix") == 0 ||
301 4 : strcmp(psIter->pszValue, "Container") == 0)
302 : {
303 7 : const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
304 14 : if (pszKey &&
305 7 : strncmp(pszKey, osPrefix.c_str(), osPrefix.size()) == 0)
306 : {
307 14 : std::string osKey = pszKey;
308 7 : if (!osKey.empty() && osKey.back() == '/')
309 2 : osKey.pop_back();
310 7 : if (osKey.size() > osPrefix.size())
311 : {
312 7 : aoEntries.push_back(
313 14 : std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
314 7 : auto &entry = aoEntries.back();
315 14 : entry->pszName =
316 7 : CPLStrdup(osKey.c_str() + osPrefix.size());
317 7 : if (aoNameCount[entry->pszName] == 2)
318 : {
319 : // Add a / suffix to disambiguish the situation
320 : // Normally we don't suffix directories with /, but
321 : // we have no alternative here
322 0 : std::string osTemp(entry->pszName);
323 0 : osTemp += '/';
324 0 : CPLFree(entry->pszName);
325 0 : entry->pszName = CPLStrdup(osTemp.c_str());
326 : }
327 7 : entry->nMode = S_IFDIR;
328 7 : entry->bModeKnown = true;
329 :
330 7 : if (bCacheEntries)
331 : {
332 12 : FileProp prop;
333 6 : prop.eExists = EXIST_YES;
334 6 : prop.bIsDirectory = true;
335 6 : prop.bHasComputedFileSize = true;
336 6 : prop.fileSize = 0;
337 6 : prop.mTime = 0;
338 6 : prop.nMode = entry->nMode;
339 :
340 : std::string osCachedFilename =
341 12 : osBaseURL + "/" +
342 12 : CPLAWSURLEncode(osPrefix, false) +
343 24 : CPLAWSURLEncode(entry->pszName, false);
344 : #if DEBUG_VERBOSE
345 : CPLDebug("AZURE", "Cache %s",
346 : osCachedFilename.c_str());
347 : #endif
348 6 : poFS->SetCachedFileProp(osCachedFilename.c_str(),
349 : prop);
350 : }
351 : }
352 : }
353 : }
354 :
355 41 : if (nMaxFiles > 0 &&
356 10 : aoEntries.size() > static_cast<unsigned>(nMaxFiles))
357 0 : break;
358 : }
359 :
360 43 : osNextMarker = CPLGetXMLValue(psEnumerationResults, "NextMarker", "");
361 : // For some containers, a list blob request can return a response
362 : // with no blobs, but with a non-empty NextMarker, and the following
363 : // request using that marker will return blobs...
364 43 : if (!osNextMarker.empty())
365 14 : bOK = true;
366 : }
367 43 : CPLDestroyXMLNode(psTree);
368 :
369 43 : return bOK;
370 : }
371 :
372 : /************************************************************************/
373 : /* IssueListDir() */
374 : /************************************************************************/
375 :
376 53 : bool VSIDIRAz::IssueListDir()
377 : {
378 53 : WriteFuncStruct sWriteFuncData;
379 106 : const std::string l_osNextMarker(osNextMarker);
380 53 : clear();
381 :
382 106 : NetworkStatisticsFileSystem oContextFS("/vsiaz/");
383 106 : NetworkStatisticsAction oContextAction("ListBucket");
384 :
385 106 : CPLString osMaxKeys = CPLGetConfigOption("AZURE_MAX_RESULTS", "");
386 53 : const int AZURE_SERVER_LIMIT_SINGLE_REQUEST = 5000;
387 72 : if (nMaxFiles > 0 && nMaxFiles < AZURE_SERVER_LIMIT_SINGLE_REQUEST &&
388 19 : (osMaxKeys.empty() || nMaxFiles < atoi(osMaxKeys.c_str())))
389 : {
390 19 : osMaxKeys.Printf("%d", nMaxFiles);
391 : }
392 :
393 53 : poHandleHelper->ResetQueryParameters();
394 106 : std::string osBaseURL(poHandleHelper->GetURLNoKVP());
395 53 : if (osBaseURL.back() == '/')
396 7 : osBaseURL.pop_back();
397 :
398 53 : CURL *hCurlHandle = curl_easy_init();
399 :
400 53 : poHandleHelper->AddQueryParameter("comp", "list");
401 53 : if (!l_osNextMarker.empty())
402 13 : poHandleHelper->AddQueryParameter("marker", l_osNextMarker);
403 53 : if (!osMaxKeys.empty())
404 19 : poHandleHelper->AddQueryParameter("maxresults", osMaxKeys);
405 :
406 53 : if (!osBucket.empty())
407 : {
408 46 : poHandleHelper->AddQueryParameter("restype", "container");
409 :
410 46 : if (nRecurseDepth == 0)
411 40 : poHandleHelper->AddQueryParameter("delimiter", "/");
412 46 : if (!osObjectKey.empty())
413 80 : poHandleHelper->AddQueryParameter("prefix", osObjectKey + "/" +
414 40 : m_osFilterPrefix);
415 6 : else if (!m_osFilterPrefix.empty())
416 1 : poHandleHelper->AddQueryParameter("prefix", m_osFilterPrefix);
417 : }
418 :
419 106 : std::string osFilename("/vsiaz/");
420 53 : if (!osBucket.empty())
421 : {
422 46 : osFilename += osBucket;
423 46 : if (!osObjectKey.empty())
424 40 : osFilename += osObjectKey;
425 : }
426 : const CPLStringList aosHTTPOptions(
427 106 : CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
428 :
429 106 : struct curl_slist *headers = VSICurlSetOptions(
430 53 : hCurlHandle, poHandleHelper->GetURL().c_str(), aosHTTPOptions.List());
431 :
432 53 : headers = VSICurlMergeHeaders(
433 53 : headers, poHandleHelper->GetCurlHeaders("GET", headers));
434 53 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
435 :
436 106 : CurlRequestHelper requestHelper;
437 : const long response_code =
438 53 : requestHelper.perform(hCurlHandle, headers, poFS, poHandleHelper.get());
439 :
440 53 : NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
441 :
442 53 : if (requestHelper.sWriteFuncData.pBuffer == nullptr)
443 : {
444 8 : curl_easy_cleanup(hCurlHandle);
445 8 : return false;
446 : }
447 :
448 45 : bool ret = false;
449 45 : if (response_code != 200)
450 : {
451 2 : CPLDebug("AZURE", "%s", requestHelper.sWriteFuncData.pBuffer);
452 : }
453 : else
454 : {
455 43 : ret = AnalyseAzureFileList(osBaseURL,
456 43 : requestHelper.sWriteFuncData.pBuffer);
457 : }
458 45 : curl_easy_cleanup(hCurlHandle);
459 45 : return ret;
460 : }
461 :
462 : /************************************************************************/
463 : /* NextDirEntry() */
464 : /************************************************************************/
465 :
466 48 : const VSIDIREntry *VSIDIRAz::NextDirEntry()
467 : {
468 48 : constexpr int ARBITRARY_LIMIT = 10;
469 61 : for (int i = 0; i < ARBITRARY_LIMIT; ++i)
470 : {
471 60 : if (nPos < static_cast<int>(aoEntries.size()))
472 : {
473 25 : auto &entry = aoEntries[nPos];
474 25 : nPos++;
475 25 : return entry.get();
476 : }
477 35 : if (osNextMarker.empty())
478 : {
479 22 : return nullptr;
480 : }
481 13 : if (!IssueListDir())
482 : {
483 0 : return nullptr;
484 : }
485 : }
486 1 : CPLError(CE_Failure, CPLE_AppDefined,
487 : "More than %d consecutive List Blob "
488 : "requests returning no blobs",
489 : ARBITRARY_LIMIT);
490 1 : return nullptr;
491 : }
492 :
493 : /************************************************************************/
494 : /* VSIAzureFSHandler */
495 : /************************************************************************/
496 :
497 : class VSIAzureFSHandler final : public IVSIS3LikeFSHandlerWithMultipartUpload
498 : {
499 : CPL_DISALLOW_COPY_ASSIGN(VSIAzureFSHandler)
500 : const std::string m_osPrefix;
501 :
502 : int CreateContainer(const std::string &osDirname);
503 : int DeleteContainer(const std::string &osDirname);
504 :
505 : protected:
506 : VSICurlHandle *CreateFileHandle(const char *pszFilename) override;
507 : std::string
508 : GetURLFromFilename(const std::string &osFilename) const override;
509 :
510 : VSIAzureBlobHandleHelper *CreateAzHandleHelper(const char *pszURI,
511 : bool bAllowNoObject);
512 :
513 66 : IVSIS3LikeHandleHelper *CreateHandleHelper(const char *pszURI,
514 : bool bAllowNoObject) override
515 : {
516 66 : return CreateAzHandleHelper(pszURI, bAllowNoObject);
517 : }
518 :
519 : char **GetFileList(const char *pszFilename, int nMaxFiles,
520 : bool *pbGotFileList) override;
521 :
522 : void InvalidateRecursive(const std::string &osDirnameIn);
523 :
524 : int CopyFile(const char *pszSource, const char *pszTarget,
525 : VSILFILE *fpSource, vsi_l_offset nSourceSize,
526 : const char *const *papszOptions,
527 : GDALProgressFunc pProgressFunc, void *pProgressData) override;
528 :
529 : int CopyObject(const char *oldpath, const char *newpath,
530 : CSLConstList papszMetadata) override;
531 : int MkdirInternal(const char *pszDirname, long nMode,
532 : bool bDoStatCheck) override;
533 :
534 : void ClearCache() override;
535 :
536 4 : bool IsAllowedHeaderForObjectCreation(const char *pszHeaderName) override
537 : {
538 4 : return STARTS_WITH(pszHeaderName, "x-ms-");
539 : }
540 :
541 : VSIVirtualHandleUniquePtr
542 : CreateWriteHandle(const char *pszFilename,
543 : CSLConstList papszOptions) override;
544 :
545 : public:
546 1392 : explicit VSIAzureFSHandler(const char *pszPrefix) : m_osPrefix(pszPrefix)
547 : {
548 1392 : }
549 :
550 1882 : ~VSIAzureFSHandler() override = default;
551 :
552 1341 : std::string GetFSPrefix() const override
553 : {
554 1341 : return m_osPrefix;
555 : }
556 :
557 62 : const char *GetDebugKey() const override
558 : {
559 62 : return "AZURE";
560 : }
561 :
562 : int Unlink(const char *pszFilename) override;
563 : int *UnlinkBatch(CSLConstList papszFiles) override;
564 :
565 0 : int *DeleteObjectBatch(CSLConstList papszFilesOrDirs) override
566 : {
567 0 : return UnlinkBatch(papszFilesOrDirs);
568 : }
569 :
570 : int Mkdir(const char *, long) override;
571 : int Rmdir(const char *) override;
572 : int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
573 : int nFlags) override;
574 :
575 : char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
576 : CSLConstList papszOptions) override;
577 :
578 : bool SetFileMetadata(const char *pszFilename, CSLConstList papszMetadata,
579 : const char *pszDomain,
580 : CSLConstList papszOptions) override;
581 :
582 : const char *GetOptions() override;
583 :
584 : char *GetSignedURL(const char *pszFilename,
585 : CSLConstList papszOptions) override;
586 :
587 : char **GetFileList(const char *pszFilename, int nMaxFiles,
588 : bool bCacheEntries, bool *pbGotFileList);
589 :
590 : VSIDIR *OpenDir(const char *pszPath, int nRecurseDepth,
591 : const char *const *papszOptions) override;
592 :
593 : // Block list upload
594 : std::string PutBlock(const std::string &osFilename, int nPartNumber,
595 : const void *pabyBuffer, size_t nBufferSize,
596 : IVSIS3LikeHandleHelper *poS3HandleHelper,
597 : const CPLHTTPRetryParameters &oRetryParameters,
598 : CSLConstList papszOptions);
599 : bool PutBlockList(const std::string &osFilename,
600 : const std::vector<std::string> &aosBlockIds,
601 : IVSIS3LikeHandleHelper *poS3HandleHelper,
602 : const CPLHTTPRetryParameters &oRetryParameters);
603 :
604 : // Multipart upload (mapping of S3 interface to PutBlock/PutBlockList)
605 :
606 3 : std::string InitiateMultipartUpload(
607 : const std::string & /* osFilename */, IVSIS3LikeHandleHelper *,
608 : const CPLHTTPRetryParameters & /* oRetryParameters */,
609 : CSLConstList /* papszOptions */) override
610 : {
611 3 : return "dummy";
612 : }
613 :
614 6 : std::string UploadPart(const std::string &osFilename, int nPartNumber,
615 : const std::string & /* osUploadID */,
616 : vsi_l_offset /* nPosition */, const void *pabyBuffer,
617 : size_t nBufferSize,
618 : IVSIS3LikeHandleHelper *poS3HandleHelper,
619 : const CPLHTTPRetryParameters &oRetryParameters,
620 : CSLConstList papszOptions) override
621 : {
622 : return PutBlock(osFilename, nPartNumber, pabyBuffer, nBufferSize,
623 6 : poS3HandleHelper, oRetryParameters, papszOptions);
624 : }
625 :
626 3 : bool CompleteMultipart(
627 : const std::string &osFilename, const std::string & /* osUploadID */,
628 : const std::vector<std::string> &aosEtags, vsi_l_offset /* nTotalSize */,
629 : IVSIS3LikeHandleHelper *poS3HandleHelper,
630 : const CPLHTTPRetryParameters &oRetryParameters) override
631 : {
632 3 : return PutBlockList(osFilename, aosEtags, poS3HandleHelper,
633 3 : oRetryParameters);
634 : }
635 :
636 0 : bool AbortMultipart(
637 : const std::string & /* osFilename */,
638 : const std::string & /* osUploadID */,
639 : IVSIS3LikeHandleHelper * /*poS3HandleHelper */,
640 : const CPLHTTPRetryParameters & /* oRetryParameters */) override
641 : {
642 0 : return true;
643 : }
644 :
645 1 : bool MultipartUploadAbort(const char *, const char *, CSLConstList) override
646 : {
647 1 : CPLError(CE_Failure, CPLE_NotSupported,
648 : "MultipartUploadAbort() not supported by this file system");
649 1 : return false;
650 : }
651 :
652 1 : bool SupportsMultipartAbort() const override
653 : {
654 1 : return false;
655 : }
656 :
657 : std::string
658 : GetStreamingFilename(const std::string &osFilename) const override;
659 :
660 0 : VSIFilesystemHandler *Duplicate(const char *pszPrefix) override
661 : {
662 0 : return new VSIAzureFSHandler(pszPrefix);
663 : }
664 :
665 : //! Maximum number of parts for multipart upload
666 : // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/understanding-block-blobs--append-blobs--and-page-blobs
667 3 : int GetMaximumPartCount() override
668 : {
669 3 : return 50000;
670 : }
671 :
672 : //! Minimum size of a part for multipart upload (except last one), in MiB.
673 1 : int GetMinimumPartSizeInMiB() override
674 : {
675 1 : return 0;
676 : }
677 :
678 : //! Maximum size of a part for multipart upload, in MiB.
679 : // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/understanding-block-blobs--append-blobs--and-page-blobs
680 3 : int GetMaximumPartSizeInMiB() override
681 : {
682 : #if SIZEOF_VOIDP == 8
683 3 : return 4000;
684 : #else
685 : // Cannot be larger than 4GiB, otherwise integer overflow would occur
686 : // 1 GiB is the maximum reasonable value on a 32-bit machine
687 : return 1024;
688 : #endif
689 : }
690 : };
691 :
692 : /************************************************************************/
693 : /* VSIAzureHandle */
694 : /************************************************************************/
695 :
696 : class VSIAzureHandle final : public VSICurlHandle
697 : {
698 : CPL_DISALLOW_COPY_ASSIGN(VSIAzureHandle)
699 :
700 : std::unique_ptr<VSIAzureBlobHandleHelper> m_poHandleHelper{};
701 :
702 : protected:
703 : virtual struct curl_slist *
704 : GetCurlHeaders(const std::string &osVerb,
705 : const struct curl_slist *psExistingHeaders) override;
706 : virtual bool IsDirectoryFromExists(const char *pszVerb,
707 : int response_code) override;
708 :
709 : public:
710 : VSIAzureHandle(VSIAzureFSHandler *poFS, const char *pszFilename,
711 : VSIAzureBlobHandleHelper *poHandleHelper);
712 : };
713 :
714 : /************************************************************************/
715 : /* VSIAzureWriteHandle */
716 : /************************************************************************/
717 :
718 : class VSIAzureWriteHandle final : public VSIAppendWriteHandle
719 : {
720 : CPL_DISALLOW_COPY_ASSIGN(VSIAzureWriteHandle)
721 :
722 : std::unique_ptr<VSIAzureBlobHandleHelper> m_poHandleHelper{};
723 : CPLStringList m_aosOptions{};
724 : CPLStringList m_aosHTTPOptions{};
725 :
726 : bool Send(bool bIsLastBlock) override;
727 : bool SendInternal(bool bInitOnly, bool bIsLastBlock);
728 :
729 : void InvalidateParentDirectory();
730 :
731 : public:
732 : VSIAzureWriteHandle(VSIAzureFSHandler *poFS, const char *pszFilename,
733 : VSIAzureBlobHandleHelper *poHandleHelper,
734 : CSLConstList papszOptions);
735 : virtual ~VSIAzureWriteHandle();
736 : };
737 :
738 : /************************************************************************/
739 : /* CreateFileHandle() */
740 : /************************************************************************/
741 :
742 51 : VSICurlHandle *VSIAzureFSHandler::CreateFileHandle(const char *pszFilename)
743 : {
744 : VSIAzureBlobHandleHelper *poHandleHelper =
745 102 : VSIAzureBlobHandleHelper::BuildFromURI(
746 153 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
747 51 : if (poHandleHelper == nullptr)
748 4 : return nullptr;
749 47 : return new VSIAzureHandle(this, pszFilename, poHandleHelper);
750 : }
751 :
752 : /************************************************************************/
753 : /* CreateWriteHandle() */
754 : /************************************************************************/
755 :
756 : VSIVirtualHandleUniquePtr
757 6 : VSIAzureFSHandler::CreateWriteHandle(const char *pszFilename,
758 : CSLConstList papszOptions)
759 : {
760 : VSIAzureBlobHandleHelper *poHandleHelper =
761 12 : VSIAzureBlobHandleHelper::BuildFromURI(
762 18 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
763 6 : if (poHandleHelper == nullptr)
764 0 : return nullptr;
765 6 : const char *pszBlobType = CSLFetchNameValue(papszOptions, "BLOB_TYPE");
766 6 : if (pszBlobType && EQUAL(pszBlobType, "BLOCK"))
767 : {
768 : auto poHandle = std::make_unique<VSIMultipartWriteHandle>(
769 4 : this, pszFilename, poHandleHelper, papszOptions);
770 2 : if (!poHandle->IsOK())
771 : {
772 0 : return nullptr;
773 : }
774 2 : return VSIVirtualHandleUniquePtr(poHandle.release());
775 : }
776 : else
777 : {
778 : auto poHandle = std::make_unique<VSIAzureWriteHandle>(
779 8 : this, pszFilename, poHandleHelper, papszOptions);
780 4 : if (!poHandle->IsOK())
781 : {
782 0 : return nullptr;
783 : }
784 4 : return VSIVirtualHandleUniquePtr(poHandle.release());
785 : }
786 : }
787 :
788 : /************************************************************************/
789 : /* Stat() */
790 : /************************************************************************/
791 :
792 31 : int VSIAzureFSHandler::Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
793 : int nFlags)
794 : {
795 31 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
796 0 : return -1;
797 :
798 31 : if ((nFlags & VSI_STAT_CACHE_ONLY) != 0)
799 3 : return VSICurlFilesystemHandlerBase::Stat(pszFilename, pStatBuf,
800 3 : nFlags);
801 :
802 56 : std::string osFilename(pszFilename);
803 :
804 56 : if ((osFilename.find('/', GetFSPrefix().size()) == std::string::npos ||
805 60 : osFilename.find('/', GetFSPrefix().size()) == osFilename.size() - 1) &&
806 4 : CPLGetConfigOption("AZURE_SAS", nullptr) != nullptr)
807 : {
808 : // On "/vsiaz/container", a HEAD or GET request fails to authenticate
809 : // when SAS is used, so use directory listing instead.
810 0 : char **papszRet = ReadDirInternal(osFilename.c_str(), 100, nullptr);
811 0 : int nRet = papszRet ? 0 : -1;
812 0 : if (nRet == 0)
813 : {
814 0 : pStatBuf->st_mtime = 0;
815 0 : pStatBuf->st_size = 0;
816 0 : pStatBuf->st_mode = S_IFDIR;
817 :
818 0 : FileProp cachedFileProp;
819 0 : GetCachedFileProp(GetURLFromFilename(osFilename.c_str()).c_str(),
820 : cachedFileProp);
821 0 : cachedFileProp.eExists = EXIST_YES;
822 0 : cachedFileProp.bIsDirectory = true;
823 0 : cachedFileProp.bHasComputedFileSize = true;
824 0 : SetCachedFileProp(GetURLFromFilename(osFilename.c_str()).c_str(),
825 : cachedFileProp);
826 : }
827 0 : CSLDestroy(papszRet);
828 0 : return nRet;
829 : }
830 :
831 28 : if (osFilename.find('/', GetFSPrefix().size()) == std::string::npos)
832 : {
833 2 : osFilename += "/";
834 : }
835 :
836 28 : if (osFilename.size() > GetFSPrefix().size())
837 : {
838 : // Special case for container
839 28 : std::string osFilenameWithoutEndSlash(osFilename);
840 28 : if (osFilenameWithoutEndSlash.back() == '/')
841 14 : osFilenameWithoutEndSlash.resize(osFilenameWithoutEndSlash.size() -
842 : 1);
843 28 : if (osFilenameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
844 : std::string::npos)
845 : {
846 4 : char **papszFileList = ReadDir(GetFSPrefix().c_str());
847 4 : const int nIdx = CSLFindString(
848 : papszFileList,
849 8 : osFilenameWithoutEndSlash.substr(GetFSPrefix().size()).c_str());
850 4 : CSLDestroy(papszFileList);
851 4 : if (nIdx >= 0)
852 : {
853 2 : pStatBuf->st_mtime = 0;
854 2 : pStatBuf->st_size = 0;
855 2 : pStatBuf->st_mode = S_IFDIR;
856 2 : return 0;
857 : }
858 : }
859 : }
860 :
861 26 : return VSICurlFilesystemHandlerBase::Stat(osFilename.c_str(), pStatBuf,
862 26 : nFlags);
863 : }
864 :
865 : /************************************************************************/
866 : /* GetFileMetadata() */
867 : /************************************************************************/
868 :
869 4 : char **VSIAzureFSHandler::GetFileMetadata(const char *pszFilename,
870 : const char *pszDomain,
871 : CSLConstList papszOptions)
872 : {
873 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
874 0 : return nullptr;
875 :
876 4 : if (pszDomain == nullptr ||
877 4 : (!EQUAL(pszDomain, "TAGS") && !EQUAL(pszDomain, "METADATA")))
878 : {
879 1 : return VSICurlFilesystemHandlerBase::GetFileMetadata(
880 1 : pszFilename, pszDomain, papszOptions);
881 : }
882 :
883 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
884 6 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
885 3 : if (poHandleHelper == nullptr)
886 : {
887 0 : return nullptr;
888 : }
889 :
890 6 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
891 6 : NetworkStatisticsAction oContextAction("GetFileMetadata");
892 :
893 : bool bRetry;
894 3 : bool bError = true;
895 :
896 6 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
897 6 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
898 6 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
899 :
900 3 : CPLStringList aosMetadata;
901 3 : do
902 : {
903 3 : bRetry = false;
904 3 : CURL *hCurlHandle = curl_easy_init();
905 3 : if (EQUAL(pszDomain, "METADATA"))
906 2 : poHandleHelper->AddQueryParameter("comp", "metadata");
907 : else
908 1 : poHandleHelper->AddQueryParameter("comp", "tags");
909 :
910 : struct curl_slist *headers =
911 3 : VSICurlSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
912 : aosHTTPOptions.List());
913 :
914 3 : headers = VSICurlMergeHeaders(
915 3 : headers, poHandleHelper->GetCurlHeaders("GET", headers));
916 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
917 :
918 6 : CurlRequestHelper requestHelper;
919 3 : const long response_code = requestHelper.perform(
920 : hCurlHandle, headers, this, poHandleHelper.get());
921 :
922 3 : NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
923 :
924 3 : if (response_code != 200 ||
925 2 : requestHelper.sWriteFuncHeaderData.pBuffer == nullptr)
926 : {
927 : // Look if we should attempt a retry
928 1 : if (oRetryContext.CanRetry(
929 : static_cast<int>(response_code),
930 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
931 : requestHelper.szCurlErrBuf))
932 : {
933 0 : CPLError(CE_Warning, CPLE_AppDefined,
934 : "HTTP error code: %d - %s. "
935 : "Retrying again in %.1f secs",
936 : static_cast<int>(response_code),
937 0 : poHandleHelper->GetURL().c_str(),
938 : oRetryContext.GetCurrentDelay());
939 0 : CPLSleep(oRetryContext.GetCurrentDelay());
940 0 : bRetry = true;
941 : }
942 : else
943 : {
944 1 : CPLDebug(GetDebugKey(), "GetFileMetadata failed on %s: %s",
945 : pszFilename,
946 1 : requestHelper.sWriteFuncData.pBuffer
947 : ? requestHelper.sWriteFuncData.pBuffer
948 : : "(null)");
949 : }
950 : }
951 : else
952 : {
953 2 : if (EQUAL(pszDomain, "METADATA"))
954 : {
955 2 : char **papszHeaders = CSLTokenizeString2(
956 1 : requestHelper.sWriteFuncHeaderData.pBuffer, "\r\n", 0);
957 6 : for (int i = 0; papszHeaders[i]; ++i)
958 : {
959 5 : char *pszKey = nullptr;
960 : const char *pszValue =
961 5 : CPLParseNameValue(papszHeaders[i], &pszKey);
962 : // Content-Length is returned as 0
963 5 : if (pszKey && pszValue && !EQUAL(pszKey, "Content-Length"))
964 : {
965 3 : aosMetadata.SetNameValue(pszKey, pszValue);
966 : }
967 5 : CPLFree(pszKey);
968 : }
969 1 : CSLDestroy(papszHeaders);
970 : }
971 : else
972 : {
973 : CPLXMLNode *psXML =
974 1 : CPLParseXMLString(requestHelper.sWriteFuncData.pBuffer);
975 1 : if (psXML)
976 : {
977 1 : CPLXMLNode *psTagSet = CPLGetXMLNode(psXML, "=Tags.TagSet");
978 1 : if (psTagSet)
979 : {
980 2 : for (CPLXMLNode *psIter = psTagSet->psChild; psIter;
981 1 : psIter = psIter->psNext)
982 : {
983 1 : if (psIter->eType == CXT_Element &&
984 1 : strcmp(psIter->pszValue, "Tag") == 0)
985 : {
986 : const char *pszKey =
987 1 : CPLGetXMLValue(psIter, "Key", "");
988 : const char *pszValue =
989 1 : CPLGetXMLValue(psIter, "Value", "");
990 1 : aosMetadata.SetNameValue(pszKey, pszValue);
991 : }
992 : }
993 : }
994 1 : CPLDestroyXMLNode(psXML);
995 : }
996 : }
997 2 : bError = false;
998 : }
999 :
1000 3 : curl_easy_cleanup(hCurlHandle);
1001 : } while (bRetry);
1002 3 : return bError ? nullptr : CSLDuplicate(aosMetadata.List());
1003 : }
1004 :
1005 : /************************************************************************/
1006 : /* SetFileMetadata() */
1007 : /************************************************************************/
1008 :
1009 4 : bool VSIAzureFSHandler::SetFileMetadata(const char *pszFilename,
1010 : CSLConstList papszMetadata,
1011 : const char *pszDomain,
1012 : CSLConstList /* papszOptions */)
1013 : {
1014 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
1015 0 : return false;
1016 :
1017 4 : if (pszDomain == nullptr ||
1018 4 : !(EQUAL(pszDomain, "PROPERTIES") || EQUAL(pszDomain, "METADATA") ||
1019 1 : EQUAL(pszDomain, "TAGS")))
1020 : {
1021 0 : CPLError(CE_Failure, CPLE_NotSupported,
1022 : "Only PROPERTIES, METADATA and TAGS domain are supported");
1023 0 : return false;
1024 : }
1025 :
1026 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1027 8 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
1028 4 : if (poHandleHelper == nullptr)
1029 : {
1030 0 : return false;
1031 : }
1032 :
1033 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1034 8 : NetworkStatisticsAction oContextAction("SetFileMetadata");
1035 :
1036 : bool bRetry;
1037 8 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
1038 8 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1039 8 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1040 4 : bool bRet = false;
1041 :
1042 : // Compose XML content for TAGS
1043 4 : std::string osXML;
1044 4 : if (EQUAL(pszDomain, "TAGS"))
1045 : {
1046 1 : CPLXMLNode *psXML = CPLCreateXMLNode(nullptr, CXT_Element, "?xml");
1047 1 : CPLAddXMLAttributeAndValue(psXML, "version", "1.0");
1048 1 : CPLAddXMLAttributeAndValue(psXML, "encoding", "UTF-8");
1049 1 : CPLXMLNode *psTags = CPLCreateXMLNode(nullptr, CXT_Element, "Tags");
1050 1 : psXML->psNext = psTags;
1051 1 : CPLXMLNode *psTagSet = CPLCreateXMLNode(psTags, CXT_Element, "TagSet");
1052 2 : for (int i = 0; papszMetadata && papszMetadata[i]; ++i)
1053 : {
1054 1 : char *pszKey = nullptr;
1055 1 : const char *pszValue = CPLParseNameValue(papszMetadata[i], &pszKey);
1056 1 : if (pszKey && pszValue)
1057 : {
1058 : CPLXMLNode *psTag =
1059 1 : CPLCreateXMLNode(psTagSet, CXT_Element, "Tag");
1060 1 : CPLCreateXMLElementAndValue(psTag, "Key", pszKey);
1061 1 : CPLCreateXMLElementAndValue(psTag, "Value", pszValue);
1062 : }
1063 1 : CPLFree(pszKey);
1064 : }
1065 :
1066 1 : char *pszXML = CPLSerializeXMLTree(psXML);
1067 1 : osXML = pszXML;
1068 1 : CPLFree(pszXML);
1069 1 : CPLDestroyXMLNode(psXML);
1070 : }
1071 :
1072 4 : do
1073 : {
1074 4 : bRetry = false;
1075 4 : CURL *hCurlHandle = curl_easy_init();
1076 :
1077 4 : if (EQUAL(pszDomain, "PROPERTIES"))
1078 1 : poHandleHelper->AddQueryParameter("comp", "properties");
1079 3 : else if (EQUAL(pszDomain, "METADATA"))
1080 2 : poHandleHelper->AddQueryParameter("comp", "metadata");
1081 : else
1082 1 : poHandleHelper->AddQueryParameter("comp", "tags");
1083 :
1084 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1085 4 : if (!osXML.empty())
1086 : {
1087 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
1088 : osXML.c_str());
1089 : }
1090 :
1091 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1092 4 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1093 : aosHTTPOptions.List()));
1094 :
1095 8 : CPLStringList aosList;
1096 4 : if (EQUAL(pszDomain, "PROPERTIES") || EQUAL(pszDomain, "METADATA"))
1097 : {
1098 6 : for (CSLConstList papszIter = papszMetadata;
1099 6 : papszIter && *papszIter; ++papszIter)
1100 : {
1101 3 : char *pszKey = nullptr;
1102 3 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
1103 3 : if (pszKey && pszValue)
1104 : {
1105 : const char *pszHeader =
1106 3 : CPLSPrintf("%s: %s", pszKey, pszValue);
1107 3 : aosList.AddString(pszHeader);
1108 3 : headers = curl_slist_append(headers, pszHeader);
1109 : }
1110 3 : CPLFree(pszKey);
1111 : }
1112 : }
1113 :
1114 8 : CPLString osContentLength;
1115 : osContentLength.Printf("Content-Length: %d",
1116 4 : static_cast<int>(osXML.size()));
1117 4 : headers = curl_slist_append(headers, osContentLength.c_str());
1118 4 : if (!osXML.empty())
1119 : {
1120 1 : headers = curl_slist_append(
1121 : headers, "Content-Type: application/xml; charset=UTF-8");
1122 1 : headers = VSICurlMergeHeaders(
1123 1 : headers, poHandleHelper->GetCurlHeaders(
1124 1 : "PUT", headers, osXML.c_str(), osXML.size()));
1125 : }
1126 : else
1127 : {
1128 3 : headers = VSICurlMergeHeaders(
1129 3 : headers, poHandleHelper->GetCurlHeaders("PUT", headers));
1130 : }
1131 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1132 :
1133 4 : NetworkStatisticsLogger::LogPUT(osXML.size());
1134 :
1135 8 : CurlRequestHelper requestHelper;
1136 4 : const long response_code = requestHelper.perform(
1137 : hCurlHandle, headers, this, poHandleHelper.get());
1138 :
1139 4 : if (response_code != 200 && response_code != 204)
1140 : {
1141 : // Look if we should attempt a retry
1142 1 : if (oRetryContext.CanRetry(
1143 : static_cast<int>(response_code),
1144 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
1145 : requestHelper.szCurlErrBuf))
1146 : {
1147 0 : CPLError(CE_Warning, CPLE_AppDefined,
1148 : "HTTP error code: %d - %s. "
1149 : "Retrying again in %.1f secs",
1150 : static_cast<int>(response_code),
1151 0 : poHandleHelper->GetURL().c_str(),
1152 : oRetryContext.GetCurrentDelay());
1153 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1154 0 : bRetry = true;
1155 : }
1156 : else
1157 : {
1158 1 : CPLDebug(GetDebugKey(), "SetFileMetadata on %s failed: %s",
1159 : pszFilename,
1160 1 : requestHelper.sWriteFuncData.pBuffer
1161 : ? requestHelper.sWriteFuncData.pBuffer
1162 : : "(null)");
1163 : }
1164 : }
1165 : else
1166 : {
1167 3 : bRet = true;
1168 : }
1169 :
1170 4 : curl_easy_cleanup(hCurlHandle);
1171 : } while (bRetry);
1172 4 : return bRet;
1173 : }
1174 :
1175 : /************************************************************************/
1176 : /* GetStreamingFilename() */
1177 : /************************************************************************/
1178 :
1179 : std::string
1180 0 : VSIAzureFSHandler::GetStreamingFilename(const std::string &osFilename) const
1181 : {
1182 0 : if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
1183 0 : return "/vsiaz_streaming/" + osFilename.substr(GetFSPrefix().size());
1184 0 : return osFilename;
1185 : }
1186 :
1187 : /************************************************************************/
1188 : /* GetAzureAppendBufferSize() */
1189 : /************************************************************************/
1190 :
1191 11 : int GetAzureAppendBufferSize()
1192 : {
1193 : int nBufferSize;
1194 11 : int nChunkSizeMB = atoi(CPLGetConfigOption("VSIAZ_CHUNK_SIZE", "4"));
1195 11 : if (nChunkSizeMB <= 0 || nChunkSizeMB > 4)
1196 0 : nBufferSize = 4 * 1024 * 1024;
1197 : else
1198 11 : nBufferSize = nChunkSizeMB * 1024 * 1024;
1199 :
1200 : // For testing only !
1201 : const char *pszChunkSizeBytes =
1202 11 : CPLGetConfigOption("VSIAZ_CHUNK_SIZE_BYTES", nullptr);
1203 11 : if (pszChunkSizeBytes)
1204 3 : nBufferSize = atoi(pszChunkSizeBytes);
1205 11 : if (nBufferSize <= 0 || nBufferSize > 4 * 1024 * 1024)
1206 0 : nBufferSize = 4 * 1024 * 1024;
1207 11 : return nBufferSize;
1208 : }
1209 :
1210 : /************************************************************************/
1211 : /* VSIAzureWriteHandle() */
1212 : /************************************************************************/
1213 :
1214 4 : VSIAzureWriteHandle::VSIAzureWriteHandle(
1215 : VSIAzureFSHandler *poFS, const char *pszFilename,
1216 4 : VSIAzureBlobHandleHelper *poHandleHelper, CSLConstList papszOptions)
1217 4 : : VSIAppendWriteHandle(poFS, poFS->GetFSPrefix().c_str(), pszFilename,
1218 : GetAzureAppendBufferSize()),
1219 : m_poHandleHelper(poHandleHelper), m_aosOptions(papszOptions),
1220 8 : m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename))
1221 : {
1222 4 : }
1223 :
1224 : /************************************************************************/
1225 : /* ~VSIAzureWriteHandle() */
1226 : /************************************************************************/
1227 :
1228 8 : VSIAzureWriteHandle::~VSIAzureWriteHandle()
1229 : {
1230 4 : Close();
1231 8 : }
1232 :
1233 : /************************************************************************/
1234 : /* InvalidateParentDirectory() */
1235 : /************************************************************************/
1236 :
1237 6 : void VSIAzureWriteHandle::InvalidateParentDirectory()
1238 : {
1239 6 : m_poFS->InvalidateCachedData(m_poHandleHelper->GetURLNoKVP().c_str());
1240 :
1241 6 : std::string osFilenameWithoutSlash(m_osFilename);
1242 6 : if (!osFilenameWithoutSlash.empty() && osFilenameWithoutSlash.back() == '/')
1243 0 : osFilenameWithoutSlash.pop_back();
1244 6 : m_poFS->InvalidateDirContent(
1245 12 : CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
1246 6 : }
1247 :
1248 : /************************************************************************/
1249 : /* Send() */
1250 : /************************************************************************/
1251 :
1252 5 : bool VSIAzureWriteHandle::Send(bool bIsLastBlock)
1253 : {
1254 5 : if (!bIsLastBlock)
1255 : {
1256 1 : CPLAssert(m_nBufferOff == m_nBufferSize);
1257 1 : if (m_nCurOffset == static_cast<vsi_l_offset>(m_nBufferSize))
1258 : {
1259 : // First full buffer ? Then create the blob empty
1260 1 : if (!SendInternal(true, false))
1261 0 : return false;
1262 : }
1263 : }
1264 5 : return SendInternal(false, bIsLastBlock);
1265 : }
1266 :
1267 : /************************************************************************/
1268 : /* SendInternal() */
1269 : /************************************************************************/
1270 :
1271 6 : bool VSIAzureWriteHandle::SendInternal(bool bInitOnly, bool bIsLastBlock)
1272 : {
1273 12 : NetworkStatisticsFileSystem oContextFS("/vsiaz/");
1274 12 : NetworkStatisticsFile oContextFile(m_osFilename.c_str());
1275 12 : NetworkStatisticsAction oContextAction("Write");
1276 :
1277 6 : bool bSuccess = true;
1278 6 : const bool bSingleBlock =
1279 10 : bIsLastBlock &&
1280 4 : (m_nCurOffset <= static_cast<vsi_l_offset>(m_nBufferSize));
1281 :
1282 6 : CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
1283 6 : bool bHasAlreadyHandled409 = false;
1284 : bool bRetry;
1285 :
1286 10 : do
1287 : {
1288 10 : bRetry = false;
1289 :
1290 10 : m_nBufferOffReadCallback = 0;
1291 10 : CURL *hCurlHandle = curl_easy_init();
1292 :
1293 10 : m_poHandleHelper->ResetQueryParameters();
1294 10 : if (!bSingleBlock && !bInitOnly)
1295 : {
1296 4 : m_poHandleHelper->AddQueryParameter("comp", "appendblock");
1297 : }
1298 :
1299 10 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
1300 10 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
1301 : ReadCallBackBuffer);
1302 10 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA,
1303 : static_cast<VSIAppendWriteHandle *>(this));
1304 :
1305 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1306 10 : CPLHTTPSetOptions(hCurlHandle, m_poHandleHelper->GetURL().c_str(),
1307 10 : m_aosHTTPOptions.List()));
1308 20 : headers = VSICurlSetCreationHeadersFromOptions(
1309 10 : headers, m_aosOptions.List(), m_osFilename.c_str());
1310 :
1311 20 : CPLString osContentLength; // leave it in this scope
1312 10 : if (bSingleBlock)
1313 : {
1314 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
1315 : m_nBufferOff);
1316 4 : if (m_nBufferOff)
1317 2 : headers = curl_slist_append(headers, "Expect: 100-continue");
1318 4 : osContentLength.Printf("Content-Length: %d", m_nBufferOff);
1319 4 : headers = curl_slist_append(headers, osContentLength.c_str());
1320 4 : headers = curl_slist_append(headers, "x-ms-blob-type: BlockBlob");
1321 : }
1322 6 : else if (bInitOnly)
1323 : {
1324 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
1325 2 : headers = curl_slist_append(headers, "Content-Length: 0");
1326 2 : headers = curl_slist_append(headers, "x-ms-blob-type: AppendBlob");
1327 : }
1328 : else
1329 : {
1330 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
1331 : m_nBufferOff);
1332 4 : osContentLength.Printf("Content-Length: %d", m_nBufferOff);
1333 4 : headers = curl_slist_append(headers, osContentLength.c_str());
1334 4 : vsi_l_offset nStartOffset = m_nCurOffset - m_nBufferOff;
1335 4 : const char *pszAppendPos = CPLSPrintf(
1336 : "x-ms-blob-condition-appendpos: " CPL_FRMT_GUIB, nStartOffset);
1337 4 : headers = curl_slist_append(headers, pszAppendPos);
1338 : }
1339 :
1340 10 : headers = VSICurlMergeHeaders(
1341 : headers, m_poHandleHelper->GetCurlHeaders("PUT", headers));
1342 10 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1343 :
1344 20 : CurlRequestHelper requestHelper;
1345 10 : const long response_code = requestHelper.perform(
1346 10 : hCurlHandle, headers, m_poFS, m_poHandleHelper.get());
1347 :
1348 10 : NetworkStatisticsLogger::LogPUT(m_nBufferOff);
1349 :
1350 10 : if (!bHasAlreadyHandled409 && response_code == 409)
1351 : {
1352 0 : bHasAlreadyHandled409 = true;
1353 0 : CPLDebug(cpl::down_cast<VSIAzureFSHandler *>(m_poFS)->GetDebugKey(),
1354 : "%s",
1355 0 : requestHelper.sWriteFuncData.pBuffer
1356 : ? requestHelper.sWriteFuncData.pBuffer
1357 : : "(null)");
1358 :
1359 : // The blob type is invalid for this operation
1360 : // Delete the file, and retry
1361 0 : if (cpl::down_cast<VSIAzureFSHandler *>(m_poFS)->DeleteObject(
1362 0 : m_osFilename.c_str()) == 0)
1363 : {
1364 0 : bRetry = true;
1365 : }
1366 : }
1367 10 : else if (response_code != 201)
1368 : {
1369 : // Look if we should attempt a retry
1370 4 : if (oRetryContext.CanRetry(
1371 : static_cast<int>(response_code),
1372 4 : requestHelper.sWriteFuncHeaderData.pBuffer,
1373 : requestHelper.szCurlErrBuf))
1374 : {
1375 8 : CPLError(CE_Warning, CPLE_AppDefined,
1376 : "HTTP error code: %d - %s. "
1377 : "Retrying again in %.1f secs",
1378 : static_cast<int>(response_code),
1379 4 : m_poHandleHelper->GetURL().c_str(),
1380 : oRetryContext.GetCurrentDelay());
1381 4 : CPLSleep(oRetryContext.GetCurrentDelay());
1382 4 : bRetry = true;
1383 : }
1384 : else
1385 : {
1386 0 : CPLDebug(
1387 : cpl::down_cast<VSIAzureFSHandler *>(m_poFS)->GetDebugKey(),
1388 : "%s",
1389 0 : requestHelper.sWriteFuncData.pBuffer
1390 : ? requestHelper.sWriteFuncData.pBuffer
1391 : : "(null)");
1392 0 : CPLError(CE_Failure, CPLE_AppDefined, "PUT of %s failed",
1393 : m_osFilename.c_str());
1394 0 : bSuccess = false;
1395 : }
1396 : }
1397 : else
1398 : {
1399 6 : InvalidateParentDirectory();
1400 : }
1401 :
1402 10 : curl_easy_cleanup(hCurlHandle);
1403 : } while (bRetry);
1404 :
1405 12 : return bSuccess;
1406 : }
1407 :
1408 : /************************************************************************/
1409 : /* ClearCache() */
1410 : /************************************************************************/
1411 :
1412 313 : void VSIAzureFSHandler::ClearCache()
1413 : {
1414 313 : IVSIS3LikeFSHandler::ClearCache();
1415 :
1416 313 : VSIAzureBlobHandleHelper::ClearCache();
1417 313 : }
1418 :
1419 : /************************************************************************/
1420 : /* GetURLFromFilename() */
1421 : /************************************************************************/
1422 :
1423 : std::string
1424 48 : VSIAzureFSHandler::GetURLFromFilename(const std::string &osFilename) const
1425 : {
1426 : std::string osFilenameWithoutPrefix =
1427 96 : osFilename.substr(GetFSPrefix().size());
1428 : auto poHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
1429 : VSIAzureBlobHandleHelper::BuildFromURI(osFilenameWithoutPrefix.c_str(),
1430 96 : GetFSPrefix().c_str()));
1431 48 : if (!poHandleHelper)
1432 0 : return std::string();
1433 48 : return poHandleHelper->GetURLNoKVP();
1434 : }
1435 :
1436 : /************************************************************************/
1437 : /* CreateAzHandleHelper() */
1438 : /************************************************************************/
1439 :
1440 : VSIAzureBlobHandleHelper *
1441 73 : VSIAzureFSHandler::CreateAzHandleHelper(const char *pszURI, bool)
1442 : {
1443 73 : return VSIAzureBlobHandleHelper::BuildFromURI(pszURI,
1444 146 : GetFSPrefix().c_str());
1445 : }
1446 :
1447 : /************************************************************************/
1448 : /* InvalidateRecursive() */
1449 : /************************************************************************/
1450 :
1451 6 : void VSIAzureFSHandler::InvalidateRecursive(const std::string &osDirnameIn)
1452 : {
1453 : // As Azure directories disappear as soon there is no remaining file
1454 : // we may need to invalidate the whole hierarchy
1455 12 : std::string osDirname(osDirnameIn);
1456 16 : while (osDirname.size() > GetFSPrefix().size())
1457 : {
1458 10 : InvalidateDirContent(osDirname.c_str());
1459 10 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1460 10 : osDirname = CPLGetDirnameSafe(osDirname.c_str());
1461 : }
1462 6 : }
1463 :
1464 : /************************************************************************/
1465 : /* Unlink() */
1466 : /************************************************************************/
1467 :
1468 4 : int VSIAzureFSHandler::Unlink(const char *pszFilename)
1469 : {
1470 4 : int ret = IVSIS3LikeFSHandler::Unlink(pszFilename);
1471 4 : if (ret != 0)
1472 1 : return ret;
1473 :
1474 3 : InvalidateRecursive(CPLGetDirnameSafe(pszFilename));
1475 3 : return 0;
1476 : }
1477 :
1478 : /************************************************************************/
1479 : /* UnlinkBatch() */
1480 : /************************************************************************/
1481 :
1482 4 : int *VSIAzureFSHandler::UnlinkBatch(CSLConstList papszFiles)
1483 : {
1484 : // Implemented using
1485 : // https://learn.microsoft.com/en-us/rest/api/storageservices/blob-batch
1486 :
1487 4 : const char *pszFirstFilename =
1488 4 : papszFiles && papszFiles[0] ? papszFiles[0] : nullptr;
1489 :
1490 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1491 12 : VSIAzureBlobHandleHelper::BuildFromURI(
1492 4 : "", GetFSPrefix().c_str(),
1493 12 : pszFirstFilename &&
1494 8 : STARTS_WITH(pszFirstFilename, GetFSPrefix().c_str())
1495 12 : ? pszFirstFilename + GetFSPrefix().size()
1496 8 : : nullptr));
1497 :
1498 : int *panRet =
1499 4 : static_cast<int *>(CPLCalloc(sizeof(int), CSLCount(papszFiles)));
1500 :
1501 4 : if (!poHandleHelper || pszFirstFilename == nullptr)
1502 0 : return panRet;
1503 :
1504 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1505 8 : NetworkStatisticsAction oContextAction("UnlinkBatch");
1506 :
1507 : const CPLStringList aosHTTPOptions(
1508 8 : CPLHTTPGetOptionsFromEnv(pszFirstFilename));
1509 8 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1510 :
1511 : // For debug / testing only
1512 : const int nBatchSize =
1513 4 : std::max(1, std::min(256, atoi(CPLGetConfigOption(
1514 4 : "CPL_VSIAZ_UNLINK_BATCH_SIZE", "256"))));
1515 4 : std::string osPOSTContent;
1516 :
1517 4 : int nFilesInBatch = 0;
1518 4 : int nFirstIDInBatch = 0;
1519 :
1520 6 : const auto DoPOST = [this, panRet, &nFilesInBatch, &oRetryParameters,
1521 : &aosHTTPOptions, &poHandleHelper, &osPOSTContent,
1522 92 : &nFirstIDInBatch](int nLastID)
1523 : {
1524 6 : osPOSTContent += "--batch_ec2ce0a7-deaf-11ed-9ad8-3fabe5ecd589--\r\n";
1525 :
1526 : #ifdef DEBUG_VERBOSE
1527 : CPLDebug(GetDebugKey(), "%s", osPOSTContent.c_str());
1528 : #endif
1529 :
1530 : // Run request
1531 12 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1532 : bool bRetry;
1533 6 : std::string osResponse;
1534 6 : do
1535 : {
1536 6 : poHandleHelper->AddQueryParameter("comp", "batch");
1537 :
1538 6 : bRetry = false;
1539 6 : CURL *hCurlHandle = curl_easy_init();
1540 :
1541 6 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
1542 : "POST");
1543 6 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
1544 : osPOSTContent.c_str());
1545 :
1546 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1547 6 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1548 : aosHTTPOptions.List()));
1549 6 : headers = curl_slist_append(
1550 : headers, "Content-Type: multipart/mixed; "
1551 : "boundary=batch_ec2ce0a7-deaf-11ed-9ad8-3fabe5ecd589");
1552 6 : headers = curl_slist_append(
1553 : headers, CPLSPrintf("Content-Length: %d",
1554 6 : static_cast<int>(osPOSTContent.size())));
1555 6 : headers = VSICurlMergeHeaders(
1556 6 : headers, poHandleHelper->GetCurlHeaders("POST", headers));
1557 :
1558 12 : CurlRequestHelper requestHelper;
1559 6 : const long response_code = requestHelper.perform(
1560 : hCurlHandle, headers, this, poHandleHelper.get());
1561 :
1562 6 : NetworkStatisticsLogger::LogPOST(
1563 : osPOSTContent.size(), requestHelper.sWriteFuncData.nSize);
1564 :
1565 6 : if (response_code != 202 ||
1566 6 : requestHelper.sWriteFuncData.pBuffer == nullptr)
1567 : {
1568 : // Look if we should attempt a retry
1569 0 : if (oRetryContext.CanRetry(
1570 : static_cast<int>(response_code),
1571 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1572 : requestHelper.szCurlErrBuf))
1573 : {
1574 0 : CPLError(CE_Warning, CPLE_AppDefined,
1575 : "HTTP error code: %d - %s. "
1576 : "Retrying again in %.1f secs",
1577 : static_cast<int>(response_code),
1578 0 : poHandleHelper->GetURL().c_str(),
1579 : oRetryContext.GetCurrentDelay());
1580 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1581 0 : bRetry = true;
1582 : }
1583 : else
1584 : {
1585 0 : CPLDebug(GetDebugKey(), "%s",
1586 0 : requestHelper.sWriteFuncData.pBuffer
1587 : ? requestHelper.sWriteFuncData.pBuffer
1588 : : "(null)");
1589 0 : CPLError(CE_Failure, CPLE_AppDefined,
1590 : "DeleteObjects failed");
1591 : }
1592 : }
1593 : else
1594 : {
1595 : #ifdef DEBUG_VERBOSE
1596 : CPLDebug(GetDebugKey(), "%s",
1597 : requestHelper.sWriteFuncData.pBuffer);
1598 : #endif
1599 6 : osResponse = requestHelper.sWriteFuncData.pBuffer;
1600 : }
1601 :
1602 6 : curl_easy_cleanup(hCurlHandle);
1603 : } while (bRetry);
1604 :
1605 : // Mark deleted files
1606 16 : for (int j = nFirstIDInBatch; j <= nLastID; j++)
1607 : {
1608 10 : auto nPos = osResponse.find(CPLSPrintf("Content-ID: <%d>", j));
1609 10 : if (nPos != std::string::npos)
1610 : {
1611 8 : nPos = osResponse.find("HTTP/1.1 ", nPos);
1612 8 : if (nPos != std::string::npos)
1613 : {
1614 : const char *pszHTTPCode =
1615 8 : osResponse.c_str() + nPos + strlen("HTTP/1.1 ");
1616 8 : panRet[j] = (atoi(pszHTTPCode) == 202) ? 1 : 0;
1617 : }
1618 : }
1619 : }
1620 :
1621 6 : osPOSTContent.clear();
1622 6 : nFilesInBatch = 0;
1623 6 : nFirstIDInBatch = nLastID;
1624 6 : };
1625 :
1626 12 : for (int i = 0; papszFiles && papszFiles[i]; i++)
1627 : {
1628 8 : CPLAssert(STARTS_WITH_CI(papszFiles[i], GetFSPrefix().c_str()));
1629 :
1630 16 : std::string osAuthorization;
1631 16 : std::string osXMSDate;
1632 : {
1633 : auto poTmpHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
1634 8 : VSIAzureBlobHandleHelper::BuildFromURI(papszFiles[i] +
1635 16 : GetFSPrefix().size(),
1636 24 : GetFSPrefix().c_str()));
1637 : // x-ms-version must not be included in the subrequests...
1638 8 : poTmpHandleHelper->SetIncludeMSVersion(false);
1639 8 : CURL *hCurlHandle = curl_easy_init();
1640 : struct curl_slist *subrequest_headers =
1641 16 : static_cast<struct curl_slist *>(CPLHTTPSetOptions(
1642 8 : hCurlHandle, poTmpHandleHelper->GetURL().c_str(),
1643 : aosHTTPOptions.List()));
1644 8 : subrequest_headers = poTmpHandleHelper->GetCurlHeaders(
1645 : "DELETE", subrequest_headers, nullptr, 0);
1646 24 : for (struct curl_slist *iter = subrequest_headers; iter;
1647 16 : iter = iter->next)
1648 : {
1649 16 : if (STARTS_WITH_CI(iter->data, "Authorization: "))
1650 : {
1651 8 : osAuthorization = iter->data;
1652 : }
1653 8 : else if (STARTS_WITH_CI(iter->data, "x-ms-date: "))
1654 : {
1655 7 : osXMSDate = iter->data;
1656 : }
1657 : }
1658 8 : curl_slist_free_all(subrequest_headers);
1659 8 : curl_easy_cleanup(hCurlHandle);
1660 : }
1661 :
1662 16 : std::string osSubrequest;
1663 8 : osSubrequest += "--batch_ec2ce0a7-deaf-11ed-9ad8-3fabe5ecd589\r\n";
1664 8 : osSubrequest += "Content-Type: application/http\r\n";
1665 8 : osSubrequest += CPLSPrintf("Content-ID: <%d>\r\n", i);
1666 8 : osSubrequest += "Content-Transfer-Encoding: binary\r\n";
1667 8 : osSubrequest += "\r\n";
1668 8 : osSubrequest += "DELETE /";
1669 8 : osSubrequest += (papszFiles[i] + GetFSPrefix().size());
1670 8 : osSubrequest += " HTTP/1.1\r\n";
1671 8 : osSubrequest += osXMSDate;
1672 8 : osSubrequest += "\r\n";
1673 8 : osSubrequest += osAuthorization;
1674 8 : osSubrequest += "\r\n";
1675 8 : osSubrequest += "Content-Length: 0\r\n";
1676 8 : osSubrequest += "\r\n";
1677 8 : osSubrequest += "\r\n";
1678 :
1679 : // The size of the body for a batch request can't exceed 4 MB.
1680 : // Add some margin for the end boundary delimiter.
1681 12 : if (i > nFirstIDInBatch &&
1682 4 : osPOSTContent.size() + osSubrequest.size() > 4 * 1024 * 1024 - 100)
1683 : {
1684 1 : DoPOST(i - 1);
1685 : }
1686 :
1687 8 : osPOSTContent += osSubrequest;
1688 8 : nFilesInBatch++;
1689 :
1690 8 : if (nFilesInBatch == nBatchSize || papszFiles[i + 1] == nullptr)
1691 : {
1692 5 : DoPOST(i);
1693 : }
1694 : }
1695 4 : return panRet;
1696 : }
1697 :
1698 : /************************************************************************/
1699 : /* Mkdir() */
1700 : /************************************************************************/
1701 :
1702 5 : int VSIAzureFSHandler::MkdirInternal(const char *pszDirname, long /* nMode */,
1703 : bool bDoStatCheck)
1704 : {
1705 5 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1706 1 : return -1;
1707 :
1708 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1709 8 : NetworkStatisticsAction oContextAction("Mkdir");
1710 :
1711 8 : std::string osDirname(pszDirname);
1712 4 : if (!osDirname.empty() && osDirname.back() != '/')
1713 4 : osDirname += "/";
1714 :
1715 4 : if (bDoStatCheck)
1716 : {
1717 : VSIStatBufL sStat;
1718 5 : if (VSIStatL(osDirname.c_str(), &sStat) == 0 &&
1719 1 : sStat.st_mode == S_IFDIR)
1720 : {
1721 1 : CPLDebug(GetDebugKey(), "Directory %s already exists",
1722 : osDirname.c_str());
1723 1 : errno = EEXIST;
1724 1 : return -1;
1725 : }
1726 : }
1727 :
1728 6 : std::string osDirnameWithoutEndSlash(osDirname);
1729 3 : osDirnameWithoutEndSlash.pop_back();
1730 9 : if (osDirnameWithoutEndSlash.size() > GetFSPrefix().size() &&
1731 6 : osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
1732 : std::string::npos)
1733 : {
1734 1 : return CreateContainer(osDirnameWithoutEndSlash);
1735 : }
1736 :
1737 2 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1738 2 : InvalidateCachedData(
1739 4 : GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
1740 2 : InvalidateDirContent(CPLGetDirnameSafe(osDirnameWithoutEndSlash.c_str()));
1741 :
1742 2 : VSILFILE *fp = VSIFOpenL((osDirname + GDAL_MARKER_FOR_DIR).c_str(), "wb");
1743 2 : if (fp != nullptr)
1744 : {
1745 2 : CPLErrorReset();
1746 2 : VSIFCloseL(fp);
1747 2 : return CPLGetLastErrorType() == CPLE_None ? 0 : -1;
1748 : }
1749 : else
1750 : {
1751 0 : return -1;
1752 : }
1753 : }
1754 :
1755 5 : int VSIAzureFSHandler::Mkdir(const char *pszDirname, long nMode)
1756 : {
1757 5 : return MkdirInternal(pszDirname, nMode, true);
1758 : }
1759 :
1760 : /************************************************************************/
1761 : /* CreateContainer() */
1762 : /************************************************************************/
1763 :
1764 1 : int VSIAzureFSHandler::CreateContainer(const std::string &osDirname)
1765 : {
1766 2 : std::string osDirnameWithoutPrefix = osDirname.substr(GetFSPrefix().size());
1767 : auto poS3HandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1768 2 : CreateHandleHelper(osDirnameWithoutPrefix.c_str(), false));
1769 1 : if (poS3HandleHelper == nullptr)
1770 : {
1771 0 : return -1;
1772 : }
1773 :
1774 1 : int nRet = 0;
1775 :
1776 : bool bRetry;
1777 :
1778 : const CPLStringList aosHTTPOptions(
1779 2 : CPLHTTPGetOptionsFromEnv(osDirname.c_str()));
1780 2 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1781 1 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1782 :
1783 1 : do
1784 : {
1785 1 : poS3HandleHelper->AddQueryParameter("restype", "container");
1786 :
1787 1 : bRetry = false;
1788 1 : CURL *hCurlHandle = curl_easy_init();
1789 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1790 :
1791 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1792 1 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
1793 : aosHTTPOptions.List()));
1794 1 : headers = curl_slist_append(headers, "Content-Length: 0");
1795 1 : headers = VSICurlMergeHeaders(
1796 1 : headers, poS3HandleHelper->GetCurlHeaders("PUT", headers));
1797 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1798 :
1799 2 : CurlRequestHelper requestHelper;
1800 1 : const long response_code = requestHelper.perform(
1801 : hCurlHandle, headers, this, poS3HandleHelper.get());
1802 :
1803 1 : NetworkStatisticsLogger::LogPUT(0);
1804 :
1805 1 : if (response_code != 201)
1806 : {
1807 : // Look if we should attempt a retry
1808 0 : if (oRetryContext.CanRetry(
1809 : static_cast<int>(response_code),
1810 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1811 : requestHelper.szCurlErrBuf))
1812 : {
1813 0 : CPLError(CE_Warning, CPLE_AppDefined,
1814 : "HTTP error code: %d - %s. "
1815 : "Retrying again in %.1f secs",
1816 : static_cast<int>(response_code),
1817 0 : poS3HandleHelper->GetURL().c_str(),
1818 : oRetryContext.GetCurrentDelay());
1819 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1820 0 : bRetry = true;
1821 : }
1822 : else
1823 : {
1824 0 : CPLDebug(GetDebugKey(), "%s",
1825 0 : requestHelper.sWriteFuncData.pBuffer
1826 : ? requestHelper.sWriteFuncData.pBuffer
1827 : : "(null)");
1828 0 : CPLError(CE_Failure, CPLE_AppDefined,
1829 : "Creation of container %s failed", osDirname.c_str());
1830 0 : nRet = -1;
1831 : }
1832 : }
1833 : else
1834 : {
1835 1 : InvalidateCachedData(poS3HandleHelper->GetURLNoKVP().c_str());
1836 1 : InvalidateDirContent(GetFSPrefix().c_str());
1837 : }
1838 :
1839 1 : curl_easy_cleanup(hCurlHandle);
1840 : } while (bRetry);
1841 :
1842 1 : return nRet;
1843 : }
1844 :
1845 : /************************************************************************/
1846 : /* Rmdir() */
1847 : /************************************************************************/
1848 :
1849 9 : int VSIAzureFSHandler::Rmdir(const char *pszDirname)
1850 : {
1851 9 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1852 1 : return -1;
1853 :
1854 16 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1855 16 : NetworkStatisticsAction oContextAction("Rmdir");
1856 :
1857 16 : std::string osDirname(pszDirname);
1858 8 : if (!osDirname.empty() && osDirname.back() != '/')
1859 8 : osDirname += "/";
1860 :
1861 : VSIStatBufL sStat;
1862 8 : if (VSIStatL(osDirname.c_str(), &sStat) != 0)
1863 : {
1864 2 : InvalidateCachedData(
1865 4 : GetURLFromFilename(osDirname.substr(0, osDirname.size() - 1))
1866 : .c_str());
1867 : // The directory might have not been created by GDAL, and thus lacking
1868 : // the GDAL marker file, so do not turn non-existence as an error
1869 2 : return 0;
1870 : }
1871 6 : else if (sStat.st_mode != S_IFDIR)
1872 : {
1873 0 : CPLDebug(GetDebugKey(), "%s is not a directory", pszDirname);
1874 0 : errno = ENOTDIR;
1875 0 : return -1;
1876 : }
1877 :
1878 6 : char **papszFileList = ReadDirEx(osDirname.c_str(), 1);
1879 6 : bool bEmptyDir =
1880 10 : (papszFileList != nullptr && EQUAL(papszFileList[0], ".") &&
1881 4 : papszFileList[1] == nullptr);
1882 6 : CSLDestroy(papszFileList);
1883 6 : if (!bEmptyDir)
1884 : {
1885 2 : CPLDebug(GetDebugKey(), "%s is not empty", pszDirname);
1886 2 : errno = ENOTEMPTY;
1887 2 : return -1;
1888 : }
1889 :
1890 8 : std::string osDirnameWithoutEndSlash(osDirname);
1891 4 : osDirnameWithoutEndSlash.pop_back();
1892 12 : if (osDirnameWithoutEndSlash.size() > GetFSPrefix().size() &&
1893 8 : osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
1894 : std::string::npos)
1895 : {
1896 1 : return DeleteContainer(osDirnameWithoutEndSlash);
1897 : }
1898 :
1899 3 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1900 3 : InvalidateCachedData(
1901 6 : GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
1902 3 : InvalidateRecursive(CPLGetDirnameSafe(osDirnameWithoutEndSlash.c_str()));
1903 3 : if (osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
1904 : std::string::npos)
1905 : {
1906 0 : CPLDebug(GetDebugKey(), "%s is a container", pszDirname);
1907 0 : errno = ENOTDIR;
1908 0 : return -1;
1909 : }
1910 :
1911 3 : if (DeleteObject((osDirname + GDAL_MARKER_FOR_DIR).c_str()) == 0)
1912 3 : return 0;
1913 : // The directory might have not been created by GDAL, and thus lacking the
1914 : // GDAL marker file, so check if is there, and if not, return success.
1915 0 : if (VSIStatL(osDirname.c_str(), &sStat) != 0)
1916 0 : return 0;
1917 0 : return -1;
1918 : }
1919 :
1920 : /************************************************************************/
1921 : /* DeleteContainer() */
1922 : /************************************************************************/
1923 :
1924 1 : int VSIAzureFSHandler::DeleteContainer(const std::string &osDirname)
1925 : {
1926 2 : std::string osDirnameWithoutPrefix = osDirname.substr(GetFSPrefix().size());
1927 : auto poS3HandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1928 2 : CreateHandleHelper(osDirnameWithoutPrefix.c_str(), false));
1929 1 : if (poS3HandleHelper == nullptr)
1930 : {
1931 0 : return -1;
1932 : }
1933 :
1934 1 : int nRet = 0;
1935 :
1936 : bool bRetry;
1937 :
1938 : const CPLStringList aosHTTPOptions(
1939 2 : CPLHTTPGetOptionsFromEnv(osDirname.c_str()));
1940 2 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1941 1 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1942 :
1943 1 : do
1944 : {
1945 1 : poS3HandleHelper->AddQueryParameter("restype", "container");
1946 :
1947 1 : bRetry = false;
1948 1 : CURL *hCurlHandle = curl_easy_init();
1949 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
1950 : "DELETE");
1951 :
1952 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1953 1 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
1954 : aosHTTPOptions.List()));
1955 1 : headers = curl_slist_append(headers, "Content-Length: 0");
1956 1 : headers = VSICurlMergeHeaders(
1957 1 : headers, poS3HandleHelper->GetCurlHeaders("DELETE", headers));
1958 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1959 :
1960 2 : CurlRequestHelper requestHelper;
1961 1 : const long response_code = requestHelper.perform(
1962 : hCurlHandle, headers, this, poS3HandleHelper.get());
1963 :
1964 1 : NetworkStatisticsLogger::LogPUT(0);
1965 :
1966 1 : if (response_code != 202)
1967 : {
1968 : // Look if we should attempt a retry
1969 0 : if (oRetryContext.CanRetry(
1970 : static_cast<int>(response_code),
1971 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1972 : requestHelper.szCurlErrBuf))
1973 : {
1974 0 : CPLError(CE_Warning, CPLE_AppDefined,
1975 : "HTTP error code: %d - %s. "
1976 : "Retrying again in %.1f secs",
1977 : static_cast<int>(response_code),
1978 0 : poS3HandleHelper->GetURL().c_str(),
1979 : oRetryContext.GetCurrentDelay());
1980 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1981 0 : bRetry = true;
1982 : }
1983 : else
1984 : {
1985 0 : CPLDebug(GetDebugKey(), "%s",
1986 0 : requestHelper.sWriteFuncData.pBuffer
1987 : ? requestHelper.sWriteFuncData.pBuffer
1988 : : "(null)");
1989 0 : CPLError(CE_Failure, CPLE_AppDefined,
1990 : "Deletion of container %s failed", osDirname.c_str());
1991 0 : nRet = -1;
1992 : }
1993 : }
1994 : else
1995 : {
1996 1 : InvalidateCachedData(poS3HandleHelper->GetURLNoKVP().c_str());
1997 1 : InvalidateDirContent(GetFSPrefix().c_str());
1998 : }
1999 :
2000 1 : curl_easy_cleanup(hCurlHandle);
2001 : } while (bRetry);
2002 :
2003 1 : return nRet;
2004 : }
2005 :
2006 : /************************************************************************/
2007 : /* CopyFile() */
2008 : /************************************************************************/
2009 :
2010 3 : int VSIAzureFSHandler::CopyFile(const char *pszSource, const char *pszTarget,
2011 : VSILFILE *fpSource, vsi_l_offset nSourceSize,
2012 : CSLConstList papszOptions,
2013 : GDALProgressFunc pProgressFunc,
2014 : void *pProgressData)
2015 : {
2016 6 : const std::string osPrefix(GetFSPrefix());
2017 8 : if ((STARTS_WITH(pszSource, "/vsis3/") ||
2018 2 : STARTS_WITH(pszSource, "/vsigs/") ||
2019 2 : STARTS_WITH(pszSource, "/vsiadls/") ||
2020 6 : STARTS_WITH(pszSource, "/vsicurl/")) &&
2021 1 : STARTS_WITH(pszTarget, osPrefix.c_str()))
2022 : {
2023 2 : std::string osMsg("Copying of");
2024 1 : osMsg += pszSource;
2025 :
2026 2 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2027 1 : NetworkStatisticsAction oContextAction("CopyFile");
2028 :
2029 1 : bool bRet = CopyObject(pszSource, pszTarget, papszOptions) == 0;
2030 1 : if (bRet && pProgressFunc)
2031 : {
2032 0 : bRet = pProgressFunc(1.0, osMsg.c_str(), pProgressData) != 0;
2033 : }
2034 1 : return bRet ? 0 : -1;
2035 : }
2036 :
2037 2 : return IVSIS3LikeFSHandler::CopyFile(pszSource, pszTarget, fpSource,
2038 : nSourceSize, papszOptions,
2039 2 : pProgressFunc, pProgressData);
2040 : }
2041 :
2042 : /************************************************************************/
2043 : /* CopyObject() */
2044 : /************************************************************************/
2045 :
2046 4 : int VSIAzureFSHandler::CopyObject(const char *oldpath, const char *newpath,
2047 : CSLConstList /* papszMetadata */)
2048 : {
2049 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2050 8 : NetworkStatisticsAction oContextAction("CopyObject");
2051 :
2052 12 : std::string osTargetNameWithoutPrefix = newpath + GetFSPrefix().size();
2053 : auto poHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
2054 8 : CreateAzHandleHelper(osTargetNameWithoutPrefix.c_str(), false));
2055 4 : if (poHandleHelper == nullptr)
2056 : {
2057 0 : return -1;
2058 : }
2059 :
2060 8 : std::string osSourceHeader("x-ms-copy-source: ");
2061 4 : bool bUseSourceSignedURL = true;
2062 4 : if (STARTS_WITH(oldpath, GetFSPrefix().c_str()))
2063 : {
2064 6 : std::string osSourceNameWithoutPrefix = oldpath + GetFSPrefix().size();
2065 : auto poHandleHelperSource = std::unique_ptr<VSIAzureBlobHandleHelper>(
2066 3 : CreateAzHandleHelper(osSourceNameWithoutPrefix.c_str(), false));
2067 3 : if (poHandleHelperSource == nullptr)
2068 : {
2069 0 : return -1;
2070 : }
2071 : // We can use a unsigned source URL only if
2072 : // the source and target are in the same bucket
2073 3 : if (poHandleHelper->GetStorageAccount() ==
2074 9 : poHandleHelperSource->GetStorageAccount() &&
2075 3 : poHandleHelper->GetBucket() == poHandleHelperSource->GetBucket())
2076 : {
2077 2 : bUseSourceSignedURL = false;
2078 2 : osSourceHeader += poHandleHelperSource->GetURLNoKVP();
2079 : }
2080 : }
2081 :
2082 4 : if (bUseSourceSignedURL)
2083 : {
2084 : VSIStatBufL sStat;
2085 : // This has the effect of making sure that the S3 region is correct
2086 : // if copying from /vsis3/
2087 2 : if (VSIStatExL(oldpath, &sStat, VSI_STAT_EXISTS_FLAG) != 0)
2088 : {
2089 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s does not exist", oldpath);
2090 0 : return -1;
2091 : }
2092 :
2093 2 : char *pszSignedURL = VSIGetSignedURL(oldpath, nullptr);
2094 2 : if (!pszSignedURL)
2095 : {
2096 0 : CPLError(CE_Failure, CPLE_AppDefined,
2097 : "Cannot get signed URL for %s", oldpath);
2098 0 : return -1;
2099 : }
2100 2 : osSourceHeader += pszSignedURL;
2101 2 : VSIFree(pszSignedURL);
2102 : }
2103 :
2104 4 : int nRet = 0;
2105 :
2106 : bool bRetry;
2107 :
2108 8 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(oldpath));
2109 8 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
2110 4 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
2111 :
2112 4 : do
2113 : {
2114 4 : bRetry = false;
2115 4 : CURL *hCurlHandle = curl_easy_init();
2116 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
2117 :
2118 : struct curl_slist *headers = static_cast<struct curl_slist *>(
2119 4 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
2120 : aosHTTPOptions.List()));
2121 4 : headers = curl_slist_append(headers, osSourceHeader.c_str());
2122 4 : headers = VSICurlSetContentTypeFromExt(headers, newpath);
2123 4 : headers = curl_slist_append(headers, "Content-Length: 0");
2124 4 : headers = VSICurlMergeHeaders(
2125 : headers, poHandleHelper->GetCurlHeaders("PUT", headers));
2126 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
2127 :
2128 8 : CurlRequestHelper requestHelper;
2129 4 : const long response_code = requestHelper.perform(
2130 4 : hCurlHandle, headers, this, poHandleHelper.get());
2131 :
2132 4 : NetworkStatisticsLogger::LogPUT(0);
2133 :
2134 4 : if (response_code != 202)
2135 : {
2136 : // Look if we should attempt a retry
2137 0 : if (oRetryContext.CanRetry(
2138 : static_cast<int>(response_code),
2139 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
2140 : requestHelper.szCurlErrBuf))
2141 : {
2142 0 : CPLError(CE_Warning, CPLE_AppDefined,
2143 : "HTTP error code: %d - %s. "
2144 : "Retrying again in %.1f secs",
2145 : static_cast<int>(response_code),
2146 0 : poHandleHelper->GetURL().c_str(),
2147 : oRetryContext.GetCurrentDelay());
2148 0 : CPLSleep(oRetryContext.GetCurrentDelay());
2149 0 : bRetry = true;
2150 : }
2151 : else
2152 : {
2153 0 : CPLDebug(GetDebugKey(), "%s",
2154 0 : requestHelper.sWriteFuncData.pBuffer
2155 : ? requestHelper.sWriteFuncData.pBuffer
2156 : : "(null)");
2157 0 : CPLError(CE_Failure, CPLE_AppDefined, "Copy of %s to %s failed",
2158 : oldpath, newpath);
2159 0 : nRet = -1;
2160 : }
2161 : }
2162 : else
2163 : {
2164 4 : InvalidateCachedData(poHandleHelper->GetURLNoKVP().c_str());
2165 :
2166 4 : std::string osFilenameWithoutSlash(newpath);
2167 8 : if (!osFilenameWithoutSlash.empty() &&
2168 4 : osFilenameWithoutSlash.back() == '/')
2169 0 : osFilenameWithoutSlash.resize(osFilenameWithoutSlash.size() -
2170 : 1);
2171 :
2172 4 : InvalidateDirContent(
2173 8 : CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
2174 : }
2175 :
2176 4 : curl_easy_cleanup(hCurlHandle);
2177 : } while (bRetry);
2178 :
2179 4 : return nRet;
2180 : }
2181 :
2182 : /************************************************************************/
2183 : /* PutBlock() */
2184 : /************************************************************************/
2185 :
2186 6 : std::string VSIAzureFSHandler::PutBlock(
2187 : const std::string &osFilename, int nPartNumber, const void *pabyBuffer,
2188 : size_t nBufferSize, IVSIS3LikeHandleHelper *poS3HandleHelper,
2189 : const CPLHTTPRetryParameters &oRetryParameters, CSLConstList papszOptions)
2190 : {
2191 12 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2192 12 : NetworkStatisticsFile oContextFile(osFilename.c_str());
2193 12 : NetworkStatisticsAction oContextAction("PutBlock");
2194 :
2195 : bool bRetry;
2196 12 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
2197 6 : std::string osBlockId(CPLSPrintf("%012d", nPartNumber));
2198 :
2199 : const std::string osContentLength(
2200 12 : CPLSPrintf("Content-Length: %d", static_cast<int>(nBufferSize)));
2201 :
2202 6 : bool bHasAlreadyHandled409 = false;
2203 :
2204 : const CPLStringList aosHTTPOptions(
2205 12 : CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
2206 :
2207 7 : do
2208 : {
2209 7 : bRetry = false;
2210 :
2211 7 : poS3HandleHelper->AddQueryParameter("comp", "block");
2212 7 : poS3HandleHelper->AddQueryParameter("blockid", osBlockId);
2213 :
2214 7 : CURL *hCurlHandle = curl_easy_init();
2215 7 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
2216 7 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
2217 : PutData::ReadCallBackBuffer);
2218 7 : PutData putData;
2219 7 : putData.pabyData = static_cast<const GByte *>(pabyBuffer);
2220 7 : putData.nOff = 0;
2221 7 : putData.nTotalSize = nBufferSize;
2222 7 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
2223 7 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
2224 : nBufferSize);
2225 :
2226 : struct curl_slist *headers = static_cast<struct curl_slist *>(
2227 7 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
2228 : aosHTTPOptions.List()));
2229 7 : headers = VSICurlSetCreationHeadersFromOptions(headers, papszOptions,
2230 : osFilename.c_str());
2231 7 : headers = curl_slist_append(headers, osContentLength.c_str());
2232 7 : headers = VSICurlMergeHeaders(
2233 : headers, poS3HandleHelper->GetCurlHeaders("PUT", headers,
2234 7 : pabyBuffer, nBufferSize));
2235 :
2236 14 : CurlRequestHelper requestHelper;
2237 : const long response_code =
2238 7 : requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
2239 :
2240 7 : NetworkStatisticsLogger::LogPUT(nBufferSize);
2241 :
2242 7 : if (!bHasAlreadyHandled409 && response_code == 409)
2243 : {
2244 1 : bHasAlreadyHandled409 = true;
2245 1 : CPLDebug(GetDebugKey(), "%s",
2246 1 : requestHelper.sWriteFuncData.pBuffer
2247 : ? requestHelper.sWriteFuncData.pBuffer
2248 : : "(null)");
2249 :
2250 : // The blob type is invalid for this operation
2251 : // Delete the file, and retry
2252 1 : if (DeleteObject(osFilename.c_str()) == 0)
2253 : {
2254 1 : bRetry = true;
2255 : }
2256 : }
2257 6 : else if ((response_code != 200 && response_code != 201) ||
2258 6 : requestHelper.sWriteFuncHeaderData.pBuffer == nullptr)
2259 : {
2260 : // Look if we should attempt a retry
2261 0 : if (oRetryContext.CanRetry(
2262 : static_cast<int>(response_code),
2263 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
2264 : requestHelper.szCurlErrBuf))
2265 : {
2266 0 : CPLError(CE_Warning, CPLE_AppDefined,
2267 : "HTTP error code: %d - %s. "
2268 : "Retrying again in %.1f secs",
2269 : static_cast<int>(response_code),
2270 0 : poS3HandleHelper->GetURL().c_str(),
2271 : oRetryContext.GetCurrentDelay());
2272 0 : CPLSleep(oRetryContext.GetCurrentDelay());
2273 0 : bRetry = true;
2274 : }
2275 : else
2276 : {
2277 0 : CPLDebug(GetDebugKey(), "%s",
2278 0 : requestHelper.sWriteFuncData.pBuffer
2279 : ? requestHelper.sWriteFuncData.pBuffer
2280 : : "(null)");
2281 0 : CPLError(CE_Failure, CPLE_AppDefined,
2282 : "PutBlock(%d) of %s failed", nPartNumber,
2283 : osFilename.c_str());
2284 0 : osBlockId.clear();
2285 : }
2286 : }
2287 :
2288 7 : curl_easy_cleanup(hCurlHandle);
2289 : } while (bRetry);
2290 :
2291 12 : return osBlockId;
2292 : }
2293 :
2294 : /************************************************************************/
2295 : /* PutBlockList() */
2296 : /************************************************************************/
2297 :
2298 3 : bool VSIAzureFSHandler::PutBlockList(
2299 : const std::string &osFilename, const std::vector<std::string> &aosBlockIds,
2300 : IVSIS3LikeHandleHelper *poS3HandleHelper,
2301 : const CPLHTTPRetryParameters &oRetryParameters)
2302 : {
2303 3 : bool bSuccess = true;
2304 :
2305 6 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2306 6 : NetworkStatisticsFile oContextFile(osFilename.c_str());
2307 6 : NetworkStatisticsAction oContextAction("PutBlockList");
2308 :
2309 : std::string osXML =
2310 6 : "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<BlockList>\n";
2311 9 : for (const auto &osBlockId : aosBlockIds)
2312 : {
2313 6 : osXML += "<Latest>" + osBlockId + "</Latest>\n";
2314 : }
2315 3 : osXML += "</BlockList>\n";
2316 :
2317 : const std::string osContentLength(
2318 6 : CPLSPrintf("Content-Length: %d", static_cast<int>(osXML.size())));
2319 :
2320 : const CPLStringList aosHTTPOptions(
2321 6 : CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
2322 :
2323 3 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
2324 : bool bRetry;
2325 3 : do
2326 : {
2327 3 : bRetry = false;
2328 :
2329 3 : poS3HandleHelper->AddQueryParameter("comp", "blocklist");
2330 :
2331 3 : PutData putData;
2332 3 : putData.pabyData = reinterpret_cast<const GByte *>(osXML.data());
2333 3 : putData.nOff = 0;
2334 3 : putData.nTotalSize = osXML.size();
2335 :
2336 3 : CURL *hCurlHandle = curl_easy_init();
2337 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
2338 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
2339 : PutData::ReadCallBackBuffer);
2340 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
2341 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
2342 : static_cast<int>(osXML.size()));
2343 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
2344 :
2345 : struct curl_slist *headers = static_cast<struct curl_slist *>(
2346 3 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
2347 : aosHTTPOptions.List()));
2348 3 : headers = curl_slist_append(headers, osContentLength.c_str());
2349 3 : headers = VSICurlMergeHeaders(
2350 : headers, poS3HandleHelper->GetCurlHeaders(
2351 3 : "PUT", headers, osXML.c_str(), osXML.size()));
2352 :
2353 6 : CurlRequestHelper requestHelper;
2354 : const long response_code =
2355 3 : requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
2356 :
2357 3 : NetworkStatisticsLogger::LogPUT(osXML.size());
2358 :
2359 3 : if (response_code != 201)
2360 : {
2361 : // Look if we should attempt a retry
2362 0 : if (oRetryContext.CanRetry(
2363 : static_cast<int>(response_code),
2364 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
2365 : requestHelper.szCurlErrBuf))
2366 : {
2367 0 : CPLError(CE_Warning, CPLE_AppDefined,
2368 : "HTTP error code: %d - %s. "
2369 : "Retrying again in %.1f secs",
2370 : static_cast<int>(response_code),
2371 0 : poS3HandleHelper->GetURL().c_str(),
2372 : oRetryContext.GetCurrentDelay());
2373 0 : CPLSleep(oRetryContext.GetCurrentDelay());
2374 0 : bRetry = true;
2375 : }
2376 : else
2377 : {
2378 0 : CPLDebug(GetDebugKey(), "%s",
2379 0 : requestHelper.sWriteFuncData.pBuffer
2380 : ? requestHelper.sWriteFuncData.pBuffer
2381 : : "(null)");
2382 0 : CPLError(CE_Failure, CPLE_AppDefined,
2383 : "PutBlockList of %s failed", osFilename.c_str());
2384 0 : bSuccess = false;
2385 : }
2386 : }
2387 :
2388 3 : curl_easy_cleanup(hCurlHandle);
2389 : } while (bRetry);
2390 :
2391 6 : return bSuccess;
2392 : }
2393 :
2394 : /************************************************************************/
2395 : /* GetFileList() */
2396 : /************************************************************************/
2397 :
2398 21 : char **VSIAzureFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
2399 : bool *pbGotFileList)
2400 : {
2401 21 : return GetFileList(pszDirname, nMaxFiles, true, pbGotFileList);
2402 : }
2403 :
2404 34 : char **VSIAzureFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
2405 : bool bCacheEntries, bool *pbGotFileList)
2406 : {
2407 : if (ENABLE_DEBUG)
2408 : CPLDebug(GetDebugKey(), "GetFileList(%s)", pszDirname);
2409 :
2410 34 : *pbGotFileList = false;
2411 :
2412 : char **papszOptions =
2413 34 : CSLSetNameValue(nullptr, "MAXFILES", CPLSPrintf("%d", nMaxFiles));
2414 34 : papszOptions = CSLSetNameValue(papszOptions, "CACHE_ENTRIES",
2415 : bCacheEntries ? "YES" : "NO");
2416 34 : auto dir = OpenDir(pszDirname, 0, papszOptions);
2417 34 : CSLDestroy(papszOptions);
2418 34 : if (!dir)
2419 : {
2420 12 : return nullptr;
2421 : }
2422 44 : CPLStringList aosFileList;
2423 : while (true)
2424 : {
2425 35 : auto entry = dir->NextDirEntry();
2426 35 : if (!entry)
2427 : {
2428 18 : break;
2429 : }
2430 17 : aosFileList.AddString(entry->pszName);
2431 :
2432 17 : if (nMaxFiles > 0 && aosFileList.size() >= nMaxFiles)
2433 4 : break;
2434 13 : }
2435 22 : delete dir;
2436 22 : *pbGotFileList = true;
2437 22 : return aosFileList.StealList();
2438 : }
2439 :
2440 : /************************************************************************/
2441 : /* GetOptions() */
2442 : /************************************************************************/
2443 :
2444 2 : const char *VSIAzureFSHandler::GetOptions()
2445 : {
2446 : static std::string osOptions(
2447 2 : std::string("<Options>") +
2448 : " <Option name='AZURE_STORAGE_CONNECTION_STRING' type='string' "
2449 : "description='Connection string that contains account name and "
2450 : "secret key'/>"
2451 : " <Option name='AZURE_STORAGE_ACCOUNT' type='string' "
2452 : "description='Storage account. To use with AZURE_STORAGE_ACCESS_KEY'/>"
2453 : " <Option name='AZURE_STORAGE_ACCESS_KEY' type='string' "
2454 : "description='Secret key'/>"
2455 : " <Option name='AZURE_NO_SIGN_REQUEST' type='boolean' "
2456 : "description='Whether to disable signing of requests' default='NO'/>"
2457 : " <Option name='VSIAZ_CHUNK_SIZE' type='int' "
2458 : "description='Size in MB for chunks of files that are uploaded' "
2459 3 : "default='4' min='1' max='4'/>" +
2460 3 : VSICurlFilesystemHandlerBase::GetOptionsStatic() + "</Options>");
2461 2 : return osOptions.c_str();
2462 : }
2463 :
2464 : /************************************************************************/
2465 : /* GetSignedURL() */
2466 : /************************************************************************/
2467 :
2468 8 : char *VSIAzureFSHandler::GetSignedURL(const char *pszFilename,
2469 : CSLConstList papszOptions)
2470 : {
2471 8 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
2472 0 : return nullptr;
2473 :
2474 : VSIAzureBlobHandleHelper *poHandleHelper =
2475 16 : VSIAzureBlobHandleHelper::BuildFromURI(
2476 24 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str(), nullptr,
2477 : papszOptions);
2478 8 : if (poHandleHelper == nullptr)
2479 : {
2480 2 : return nullptr;
2481 : }
2482 :
2483 12 : std::string osRet(poHandleHelper->GetSignedURL(papszOptions));
2484 :
2485 6 : delete poHandleHelper;
2486 6 : return CPLStrdup(osRet.c_str());
2487 : }
2488 :
2489 : /************************************************************************/
2490 : /* OpenDir() */
2491 : /************************************************************************/
2492 :
2493 40 : VSIDIR *VSIAzureFSHandler::OpenDir(const char *pszPath, int nRecurseDepth,
2494 : const char *const *papszOptions)
2495 : {
2496 40 : if (nRecurseDepth > 0)
2497 : {
2498 0 : return VSIFilesystemHandler::OpenDir(pszPath, nRecurseDepth,
2499 0 : papszOptions);
2500 : }
2501 :
2502 40 : if (!STARTS_WITH_CI(pszPath, GetFSPrefix().c_str()))
2503 0 : return nullptr;
2504 :
2505 80 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2506 80 : NetworkStatisticsAction oContextAction("OpenDir");
2507 :
2508 120 : std::string osDirnameWithoutPrefix = pszPath + GetFSPrefix().size();
2509 40 : if (!osDirnameWithoutPrefix.empty() && osDirnameWithoutPrefix.back() == '/')
2510 : {
2511 0 : osDirnameWithoutPrefix.pop_back();
2512 : }
2513 :
2514 80 : std::string osBucket(osDirnameWithoutPrefix);
2515 80 : std::string osObjectKey;
2516 40 : size_t nSlashPos = osDirnameWithoutPrefix.find('/');
2517 40 : if (nSlashPos != std::string::npos)
2518 : {
2519 28 : osBucket = osDirnameWithoutPrefix.substr(0, nSlashPos);
2520 28 : osObjectKey = osDirnameWithoutPrefix.substr(nSlashPos + 1);
2521 : }
2522 :
2523 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
2524 80 : CreateHandleHelper(osBucket.c_str(), true));
2525 40 : if (poHandleHelper == nullptr)
2526 : {
2527 0 : return nullptr;
2528 : }
2529 :
2530 40 : VSIDIRAz *dir = new VSIDIRAz(this);
2531 40 : dir->nRecurseDepth = nRecurseDepth;
2532 40 : dir->poFS = this;
2533 40 : dir->poHandleHelper = std::move(poHandleHelper);
2534 40 : dir->osBucket = std::move(osBucket);
2535 40 : dir->osObjectKey = std::move(osObjectKey);
2536 40 : dir->nMaxFiles = atoi(CSLFetchNameValueDef(papszOptions, "MAXFILES", "0"));
2537 40 : dir->bCacheEntries =
2538 40 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "CACHE_ENTRIES", "YES"));
2539 40 : dir->m_osFilterPrefix = CSLFetchNameValueDef(papszOptions, "PREFIX", "");
2540 40 : dir->m_bSynthetizeMissingDirectories = CPLTestBool(CSLFetchNameValueDef(
2541 : papszOptions, "SYNTHETIZE_MISSING_DIRECTORIES", "NO"));
2542 40 : if (!dir->IssueListDir())
2543 : {
2544 13 : delete dir;
2545 13 : return nullptr;
2546 : }
2547 :
2548 27 : return dir;
2549 : }
2550 :
2551 : /************************************************************************/
2552 : /* VSIAzureHandle() */
2553 : /************************************************************************/
2554 :
2555 47 : VSIAzureHandle::VSIAzureHandle(VSIAzureFSHandler *poFSIn,
2556 : const char *pszFilename,
2557 47 : VSIAzureBlobHandleHelper *poHandleHelper)
2558 47 : : VSICurlHandle(poFSIn, pszFilename, poHandleHelper->GetURLNoKVP().c_str()),
2559 94 : m_poHandleHelper(poHandleHelper)
2560 : {
2561 47 : m_osQueryString = poHandleHelper->GetSASQueryString();
2562 47 : }
2563 :
2564 : /************************************************************************/
2565 : /* GetCurlHeaders() */
2566 : /************************************************************************/
2567 :
2568 : struct curl_slist *
2569 37 : VSIAzureHandle::GetCurlHeaders(const std::string &osVerb,
2570 : const struct curl_slist *psExistingHeaders)
2571 : {
2572 37 : return m_poHandleHelper->GetCurlHeaders(osVerb, psExistingHeaders);
2573 : }
2574 :
2575 : /************************************************************************/
2576 : /* IsDirectoryFromExists() */
2577 : /************************************************************************/
2578 :
2579 23 : bool VSIAzureHandle::IsDirectoryFromExists(const char * /*pszVerb*/,
2580 : int response_code)
2581 : {
2582 23 : if (response_code != 404)
2583 10 : return false;
2584 :
2585 26 : std::string osDirname(m_osFilename);
2586 26 : if (osDirname.size() > poFS->GetFSPrefix().size() &&
2587 13 : osDirname.back() == '/')
2588 10 : osDirname.pop_back();
2589 : bool bIsDir;
2590 13 : if (poFS->ExistsInCacheDirList(osDirname, &bIsDir))
2591 0 : return bIsDir;
2592 :
2593 13 : bool bGotFileList = false;
2594 : char **papszDirContent =
2595 13 : reinterpret_cast<VSIAzureFSHandler *>(poFS)->GetFileList(
2596 : osDirname.c_str(), 1, false, &bGotFileList);
2597 13 : CSLDestroy(papszDirContent);
2598 13 : return bGotFileList;
2599 : }
2600 :
2601 : } /* end of namespace cpl */
2602 :
2603 : #endif // DOXYGEN_SKIP
2604 : //! @endcond
2605 :
2606 : /************************************************************************/
2607 : /* VSIInstallAzureFileHandler() */
2608 : /************************************************************************/
2609 :
2610 : /*!
2611 : \brief Install /vsiaz/ Microsoft Azure Blob file system handler
2612 : (requires libcurl)
2613 :
2614 : \verbatim embed:rst
2615 : See :ref:`/vsiaz/ documentation <vsiaz>`
2616 : \endverbatim
2617 :
2618 : @since GDAL 2.3
2619 : */
2620 :
2621 1392 : void VSIInstallAzureFileHandler(void)
2622 : {
2623 1392 : VSIFileManager::InstallHandler("/vsiaz/",
2624 1392 : new cpl::VSIAzureFSHandler("/vsiaz/"));
2625 1392 : }
2626 :
2627 : #endif /* HAVE_CURL */
|