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